1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <zlib.h>
17 #include <unistd.h>
18 #include "assembler/assembly-emitter.h"
19 #include "assembler/assembly-parser.h"
20 #include "libpandafile/class_data_accessor-inl.h"
21
22 #include "ecmascript/global_env.h"
23 #include "ecmascript/js_function_kind.h"
24 #include "ecmascript/jspandafile/js_pandafile.h"
25 #include "ecmascript/jspandafile/js_pandafile_manager.h"
26 #include "ecmascript/jspandafile/js_pandafile_snapshot.h"
27 #include "ecmascript/jspandafile/method_literal.h"
28 #include "ecmascript/jspandafile/panda_file_translator.h"
29 #include "ecmascript/jspandafile/program_object.h"
30 #include "ecmascript/tests/test_helper.h"
31
32 using namespace panda::ecmascript;
33 using namespace panda::panda_file;
34 using namespace panda::pandasm;
35
36 namespace panda::test {
37 class MockJSPandaFileSnapshot : public JSPandaFileSnapshot {
38 public:
WriteDataToFile(JSThread * thread,JSPandaFile * jsPandaFile,const CString & path,const CString & version)39 static bool WriteDataToFile(JSThread *thread, JSPandaFile *jsPandaFile, const CString &path, const CString &version)
40 {
41 return JSPandaFileSnapshot::WriteDataToFile(thread, jsPandaFile, path, version);
42 }
43 };
44 class JSPandaFileSnapshotTest : public testing::Test {
45 public:
SetUpTestCase()46 static void SetUpTestCase()
47 {
48 GTEST_LOG_(INFO) << "SetUpTestCase";
49 }
50
TearDownTestCase()51 static void TearDownTestCase()
52 {
53 GTEST_LOG_(INFO) << "TearDownCase";
54 }
55
SetUp()56 void SetUp() override
57 {
58 CString path = GetSnapshotPath();
59 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
60 if (remove(fileName.c_str()) != 0) {
61 GTEST_LOG_(ERROR) << "remove " << fileName << " failed";
62 }
63 TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
64 }
65
TearDown()66 void TearDown() override
67 {
68 CString path = GetSnapshotPath();
69 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
70 if (remove(fileName.c_str()) != 0) {
71 GTEST_LOG_(ERROR) << "remove " << fileName << " failed";
72 }
73 TestHelper::DestroyEcmaVMWithScope(instance, scope);
74 }
75
GetSnapshotPath()76 static CString GetSnapshotPath()
77 {
78 char buff[FILENAME_MAX];
79 getcwd(buff, FILENAME_MAX);
80 CString currentPath(buff);
81 if (currentPath.back() != '/') {
82 currentPath += "/";
83 }
84 return currentPath;
85 }
86
NewMockJSPandaFile() const87 std::shared_ptr<JSPandaFile> NewMockJSPandaFile() const
88 {
89 Parser parser;
90 const char *filename = "/data/storage/el1/bundle/entry/ets/main/modules.abc";
91 const char *data = R"(
92 .function any func_main_0(any a0, any a1, any a2) {
93 ldai 1
94 return
95 }
96 )";
97 auto res = parser.Parse(data);
98 JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
99 std::unique_ptr<const File> pfPtr = pandasm::AsmEmitter::Emit(res.Value());
100 return pfManager->NewJSPandaFile(pfPtr.release(), CString(filename));
101 }
102
NormalTranslateJSPandaFile(const std::shared_ptr<JSPandaFile> & pf) const103 void NormalTranslateJSPandaFile(const std::shared_ptr<JSPandaFile>& pf) const
104 {
105 const File *file = pf->GetPandaFile();
106 const uint8_t *typeDesc = utf::CStringAsMutf8("L_GLOBAL;");
107 const File::EntityId classId = file->GetClassId(typeDesc);
108 ClassDataAccessor cda(*file, classId);
109 std::vector<File::EntityId> methodId {};
110 cda.EnumerateMethods([&](const MethodDataAccessor &mda) {
111 methodId.push_back(mda.GetMethodId());
112 });
113 pf->UpdateMainMethodIndex(methodId[0].GetOffset());
114 const char *methodName = MethodLiteral::GetMethodName(pf.get(), methodId[0]);
115 PandaFileTranslator::TranslateClasses(thread, pf.get(), CString(methodName));
116 }
117
CheckMethodLiteral(MethodLiteral * serializeMethodLiteral,MethodLiteral * deserializeMethodLiteral)118 static void CheckMethodLiteral(MethodLiteral *serializeMethodLiteral, MethodLiteral *deserializeMethodLiteral)
119 {
120 EXPECT_TRUE(serializeMethodLiteral != nullptr);
121 EXPECT_TRUE(deserializeMethodLiteral != nullptr);
122 ASSERT_EQ(serializeMethodLiteral->GetCallField(), deserializeMethodLiteral->GetCallField());
123 ASSERT_EQ(*serializeMethodLiteral->GetBytecodeArray(), *deserializeMethodLiteral->GetBytecodeArray());
124 ASSERT_EQ(serializeMethodLiteral->GetLiteralInfo(), deserializeMethodLiteral->GetLiteralInfo());
125 ASSERT_EQ(serializeMethodLiteral->GetExtraLiteralInfo(), deserializeMethodLiteral->GetExtraLiteralInfo());
126 }
CheckJSRecordInfo(JSRecordInfo * serializeRecordInfo,JSRecordInfo * deserializeRecordInfo)127 static void CheckJSRecordInfo(JSRecordInfo *serializeRecordInfo, JSRecordInfo *deserializeRecordInfo)
128 {
129 ASSERT_TRUE(serializeRecordInfo != nullptr);
130 ASSERT_TRUE(deserializeRecordInfo != nullptr);
131 ASSERT_EQ(serializeRecordInfo->mainMethodIndex, deserializeRecordInfo->mainMethodIndex);
132 ASSERT_EQ(serializeRecordInfo->isCjs, deserializeRecordInfo->isCjs);
133 ASSERT_EQ(serializeRecordInfo->isJson, deserializeRecordInfo->isJson);
134 ASSERT_EQ(serializeRecordInfo->isSharedModule, deserializeRecordInfo->isSharedModule);
135 ASSERT_EQ(serializeRecordInfo->jsonStringId, deserializeRecordInfo->jsonStringId);
136 ASSERT_EQ(serializeRecordInfo->moduleRecordIdx, deserializeRecordInfo->moduleRecordIdx);
137 ASSERT_EQ(serializeRecordInfo->hasTopLevelAwait, deserializeRecordInfo->hasTopLevelAwait);
138 ASSERT_EQ(serializeRecordInfo->lazyImportIdx, deserializeRecordInfo->lazyImportIdx);
139 ASSERT_EQ(serializeRecordInfo->classId, deserializeRecordInfo->classId);
140 ASSERT_EQ(serializeRecordInfo->npmPackageName, deserializeRecordInfo->npmPackageName);
141 }
142 EcmaVM *instance {nullptr};
143 EcmaHandleScope *scope {nullptr};
144 JSThread *thread {nullptr};
145 };
146
HWTEST_F_L0(JSPandaFileSnapshotTest,SerializeAndDeserializeTest)147 HWTEST_F_L0(JSPandaFileSnapshotTest, SerializeAndDeserializeTest)
148 {
149 // construct JSPandaFile
150 CString path = GetSnapshotPath();
151 CString version = "version 205.0.1.120(SP20)";
152 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
153 NormalTranslateJSPandaFile(serializePf);
154 // serialize and persist
155 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
156
157 // deserialize
158 const std::shared_ptr<JSPandaFile> pf = NewMockJSPandaFile();
159 JSPandaFile *deserializePf = pf.get();
160 ASSERT_TRUE(JSPandaFileSnapshot::ReadData(thread, deserializePf, path, version));
161
162 // check numMethods
163 EXPECT_EQ(serializePf->GetNumMethods(), deserializePf->GetNumMethods());
164 // check MethodLiterals
165 ASSERT_EQ(serializePf->GetMainMethodIndex(), deserializePf->GetMainMethodIndex());
166 MethodLiteral *serializeMethodLiteral = serializePf->FindMethodLiteral(serializePf->GetMainMethodIndex());
167 MethodLiteral *deserializeMethodLiteral = deserializePf->FindMethodLiteral(deserializePf->GetMainMethodIndex());
168 CheckMethodLiteral(serializeMethodLiteral, deserializeMethodLiteral);
169 // check jsRecord
170 JSRecordInfo *serializeRecordInfo = serializePf->CheckAndGetRecordInfo("func_main_0");
171 JSRecordInfo *deserializeRecordInfo = deserializePf->CheckAndGetRecordInfo("func_main_0");
172 CheckJSRecordInfo(serializeRecordInfo, deserializeRecordInfo);
173 }
174
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldNotSerializeWhenFileIsExists)175 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldNotSerializeWhenFileIsExists)
176 {
177 // construct JSPandaFile
178 CString path = GetSnapshotPath();
179 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
180 CString version = "version 205.0.1.120(SP20)";
181 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
182 NormalTranslateJSPandaFile(serializePf);
183 // serialize and persist
184 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
185 ASSERT_TRUE(FileExist(fileName.c_str()));
186 // return false when file is already exists
187 ASSERT_FALSE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
188 }
189
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldNotDeSerializeWhenFileIsNotExists)190 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldNotDeSerializeWhenFileIsNotExists)
191 {
192 // construct JSPandaFile
193 CString path = GetSnapshotPath();
194 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
195 CString version = "version 205.0.1.120(SP20)";
196 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
197 // return false when file is not exists
198 ASSERT_FALSE(FileExist(fileName.c_str()));
199 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
200 }
201
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenFileIsEmpty)202 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenFileIsEmpty)
203 {
204 // construct JSPandaFile
205 CString path = GetSnapshotPath();
206 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
207 CString version = "version 205.0.1.120(SP20)";
208 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
209 std::ofstream ofStream(fileName.c_str());
210 ofStream.close();
211 // return false when file is empty
212 ASSERT_TRUE(FileExist(fileName.c_str()));
213 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
214 // check file is deleted
215 ASSERT_FALSE(FileExist(fileName.c_str()));
216 }
217
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenCheckSumIsNotMatch)218 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenCheckSumIsNotMatch)
219 {
220 // construct JSPandaFile
221 CString path = GetSnapshotPath();
222 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
223 CString version = "version 205.0.1.120(SP20)";
224 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
225 NormalTranslateJSPandaFile(serializePf);
226 // serialize and persist
227 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
228 ASSERT_TRUE(FileExist(fileName.c_str()));
229 // modify file content
230 std::ofstream ofStream(fileName.c_str(), std::ios::app);
231 uint32_t mockCheckSum = 123456;
232 ofStream << mockCheckSum;
233 ofStream.close();
234 // deserialize failed when checksum is not match
235 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
236 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
237 // check file is deleted
238 ASSERT_FALSE(FileExist(fileName.c_str()));
239 }
240
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenAppVersionCodeIsNotMatch)241 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenAppVersionCodeIsNotMatch)
242 {
243 // construct JSPandaFile
244 CString path = GetSnapshotPath();
245 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
246 CString version = "version 205.0.1.120(SP20)";
247 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
248 NormalTranslateJSPandaFile(serializePf);
249 // serialize and persist
250 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
251 ASSERT_TRUE(FileExist(fileName.c_str()));
252 // modify app version code
253 thread->GetEcmaVM()->SetApplicationVersionCode(1);
254 // deserialize failed when app version code is not match
255 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
256 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
257 // check file is deleted
258 ASSERT_FALSE(FileExist(fileName.c_str()));
259 }
260
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenVersionCodeIsNotMatch)261 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenVersionCodeIsNotMatch)
262 {
263 // construct JSPandaFile
264 CString path = GetSnapshotPath();
265 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
266 CString version = "version 205.0.1.120(SP20)";
267 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
268 NormalTranslateJSPandaFile(serializePf);
269 // serialize and persist
270 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
271 ASSERT_TRUE(FileExist(fileName.c_str()));
272 // deserialize failed when version code is not match
273 CString updatedVersion = "version 205.0.1.125(SP20)";
274 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
275 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, updatedVersion));
276 // check file is deleted
277 ASSERT_FALSE(FileExist(fileName.c_str()));
278 }
279
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenFileSizeIsNotMatch)280 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenFileSizeIsNotMatch)
281 {
282 // construct JSPandaFile
283 CString path = GetSnapshotPath();
284 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
285 CString version = "version 205.0.1.120(SP20)";
286 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
287 NormalTranslateJSPandaFile(serializePf);
288 // serialize and persist
289 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
290 ASSERT_TRUE(FileExist(fileName.c_str()));
291 // construct a different JSPandaFile
292 Parser parser;
293 const char *filename = "/data/storage/el1/bundle/entry/ets/main/modules.abc";
294 const char *data = R"(
295 .function any func_main_0(any a0, any a1, any a2) {
296 ldai 1
297 ldai 2
298 return
299 }
300 )";
301 auto res = parser.Parse(data);
302 JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
303 std::unique_ptr<const File> pfPtr = pandasm::AsmEmitter::Emit(res.Value());
304 const std::shared_ptr<JSPandaFile> deserializePf = pfManager->NewJSPandaFile(pfPtr.release(), CString(filename));
305 // deserialize failed when file size is not match
306 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
307 // check file is deleted
308 ASSERT_FALSE(FileExist(fileName.c_str()));
309 }
310
HWTEST_F_L0(JSPandaFileSnapshotTest,ShouldDeSerializeFailedWhenModuleNameIsNotMatch)311 HWTEST_F_L0(JSPandaFileSnapshotTest, ShouldDeSerializeFailedWhenModuleNameIsNotMatch)
312 {
313 // construct JSPandaFile
314 CString path = GetSnapshotPath();
315 CString fileName = path + "entry" + JSPandaFileSnapshot::JSPANDAFILE_FILE_NAME.data();
316 CString version = "version 205.0.1.120(SP20)";
317 const std::shared_ptr<JSPandaFile> serializePf = NewMockJSPandaFile();
318 NormalTranslateJSPandaFile(serializePf);
319 // serialize and persist
320 ASSERT_TRUE(MockJSPandaFileSnapshot::WriteDataToFile(thread, serializePf.get(), path, version));
321 ASSERT_TRUE(FileExist(fileName.c_str()));
322 // change moduleName from entry to ntest
323 uint32_t fileSize = sizeof(uint32_t);
324 uint32_t appVersionCodeSize = sizeof(uint32_t);
325 uint32_t versionStrLenSize = sizeof(uint32_t);
326 uint32_t versionStrLen = version.size();
327 uint32_t moduleNameLenSize = sizeof(uint32_t);
328 uint32_t moduleNameOffset = appVersionCodeSize + versionStrLenSize + versionStrLen + fileSize + moduleNameLenSize;
329 CString moduleName = "ntest";
330 std::fstream fStream(fileName.c_str(), std::ios_base::in | std::ios_base::out | std::ios_base::binary);
331 fStream.seekp(moduleNameOffset);
332 fStream << moduleName;
333 fStream.close();
334 // adapt checksum
335 uint32_t newCheckSum;
336 uint32_t checksumSize = sizeof(uint32_t);
337 uint32_t contentSize;
338 {
339 MemMap fileMapMem = FileMap(fileName.c_str(), FILE_RDONLY, PAGE_PROT_READ, 0);
340 MemMapScope memMapScope(fileMapMem);
341 contentSize = fileMapMem.GetSize() - checksumSize;
342 newCheckSum = adler32(0, static_cast<const Bytef*>(fileMapMem.GetOriginAddr()), contentSize);
343 }
344 fStream.open(fileName.c_str(), std::ios_base::in | std::ios_base::out | std::ios_base::binary);
345 fStream.seekp(contentSize);
346 fStream.write(reinterpret_cast<char *>(&newCheckSum), checksumSize);
347 fStream.close();
348 // deserialize failed when module Name is not match
349 const std::shared_ptr<JSPandaFile> deserializePf = NewMockJSPandaFile();
350 ASSERT_FALSE(JSPandaFileSnapshot::ReadData(thread, deserializePf.get(), path, version));
351 // check file is deleted
352 ASSERT_FALSE(FileExist(fileName.c_str()));
353 }
354 }