1/*
2 * Copyright (c) 2021 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 "ecmascript/tests/test_helper.h"
17
18#include "ecmascript/ecma_vm.h"
19#include "ecmascript/global_env.h"
20#include "ecmascript/js_handle.h"
21#include "ecmascript/js_hclass.h"
22#include "ecmascript/js_thread.h"
23#include "ecmascript/jspandafile/program_object.h"
24#include "ecmascript/object_factory.h"
25#include "ecmascript/snapshot/mem/snapshot.h"
26#include "ecmascript/snapshot/mem/snapshot_processor.h"
27#include "ecmascript/snapshot/tests/snapshot_mock.h"
28
29using namespace panda::ecmascript;
30
31namespace panda::test {
32class SnapshotTest : public testing::Test {
33public:
34    static void SetUpTestCase()
35    {
36        GTEST_LOG_(INFO) << "SetUpTestCase";
37    }
38
39    static void TearDownTestCase()
40    {
41        GTEST_LOG_(INFO) << "TearDownCase";
42    }
43
44    void SetUp() override
45    {
46        JSRuntimeOptions options;
47        ecmaVm = JSNApi::CreateEcmaVM(options);
48        ASSERT_TRUE(ecmaVm != nullptr) << "Cannot create EcmaVM";
49        thread = ecmaVm->GetJSThread();
50        thread->ManagedCodeBegin();
51        scope = new EcmaHandleScope(thread);
52    }
53
54    void TearDown() override
55    {
56        thread->ManagedCodeEnd();
57        delete scope;
58        scope = nullptr;
59        ecmaVm->SetEnableForceGC(false);
60        thread->ClearException();
61        JSNApi::DestroyJSVM(ecmaVm);
62    }
63
64    void CompatibilityHelper(base::FileHeaderBase::VersionType serializeVersion,
65                             base::FileHeaderBase::VersionType deserializeVersion, bool expected)
66    {
67        static constexpr uint32_t ARRAY_SIZE = 300;
68        static constexpr uint32_t KILO_BITS = 1024;
69        auto factory = ecmaVm->GetFactory();
70        auto env = ecmaVm->GetGlobalEnv();
71
72        JSHandle<TaggedArray> array1 = factory->NewTaggedArray(ARRAY_SIZE * KILO_BITS / sizeof(uint8_t));
73
74        JSHandle<JSFunction> funcFunc(env->GetFunctionFunction());
75        array1->Set(thread, 0, funcFunc.GetTaggedValue());
76
77        CString fileName = "snapshot";
78        SnapshotMock snapshotSerialize(ecmaVm);
79        snapshotSerialize.SetLastVersion(serializeVersion);
80        // serialize in earlier version tag
81        snapshotSerialize.Serialize(*array1, nullptr, fileName);
82
83        std::thread t1([&]() {
84            JSRuntimeOptions options;
85            EcmaVM *ecmaVm2 = JSNApi::CreateEcmaVM(options);
86            // deserialize with last version tag
87            SnapshotMock snapshotDeserialize(ecmaVm2);
88            snapshotDeserialize.SetLastVersion(deserializeVersion);
89            EXPECT_EQ(snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName), expected);
90
91            ASSERT_EQ(const_cast<Heap *>(ecmaVm2->GetHeap())->GetHugeObjectSpace()->GetFirstRegion() != nullptr,
92                      expected);
93            JSNApi::DestroyJSVM(ecmaVm2);
94        });
95        {
96            ThreadSuspensionScope suspensionScope(thread);
97            t1.join();
98        }
99        std::remove(fileName.c_str());
100    }
101
102    EcmaVM *ecmaVm {nullptr};
103    ecmascript::EcmaHandleScope *scope {nullptr};
104    JSThread *thread {nullptr};
105};
106
107HWTEST_F_L0(SnapshotTest, SerializeConstPool)
108{
109    auto factory = ecmaVm->GetFactory();
110    auto env = ecmaVm->GetGlobalEnv();
111
112    JSHandle<ConstantPool> constpool = factory->NewConstantPool(10);
113    JSHandle<JSFunction> funcFunc(env->GetFunctionFunction());
114    JSHandle<JSFunction> dateFunc(env->GetDateFunction());
115    JSHandle<JSFunction> numberFunc(env->GetNumberFunction());
116    JSHandle<EcmaString> str1 = factory->NewFromASCII("str11");
117    JSHandle<EcmaString> str2 = factory->NewFromASCII("str22");
118    JSHandle<EcmaString> str3 = factory->NewFromASCII("str333333333333");
119    JSHandle<EcmaString> str4 = factory->ConcatFromString(str1, str3);
120    JSHandle<EcmaString> str5 = factory->NewFromASCII("str44");
121    constpool->SetObjectToCache(thread, 0, funcFunc.GetTaggedValue());
122    constpool->SetObjectToCache(thread, 1, dateFunc.GetTaggedValue());
123    constpool->SetObjectToCache(thread, 2, str1.GetTaggedValue());
124    constpool->SetObjectToCache(thread, 3, numberFunc.GetTaggedValue());
125    constpool->SetObjectToCache(thread, 4, str2.GetTaggedValue());
126    constpool->SetObjectToCache(thread, 5, str1.GetTaggedValue());
127    constpool->SetObjectToCache(thread, 6, str3.GetTaggedValue());
128    constpool->SetObjectToCache(thread, 7, str4.GetTaggedValue());
129    constpool->SetObjectToCache(thread, 8, str4.GetTaggedValue());
130    constpool->SetObjectToCache(thread, 9, str5.GetTaggedValue());
131
132    CString fileName = "snapshot";
133    Snapshot snapshotSerialize(ecmaVm);
134    // serialize
135    snapshotSerialize.Serialize(*constpool, nullptr, fileName);
136    // deserialize
137    Snapshot snapshotDeserialize(ecmaVm);
138    snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName);
139
140    auto beginRegion = const_cast<Heap *>(ecmaVm->GetHeap())->GetOldSpace()->GetCurrentRegion();
141    auto constpool1 = reinterpret_cast<ConstantPool *>(beginRegion->GetBegin());
142    EXPECT_EQ(constpool->GetClass()->SizeFromJSHClass(*constpool),
143              constpool1->GetClass()->SizeFromJSHClass(constpool1));
144    EXPECT_TRUE(constpool1->GetObjectFromCache(0).IsJSFunction());
145    EXPECT_TRUE(constpool1->GetObjectFromCache(1).IsJSFunction());
146    EXPECT_TRUE(constpool1->GetObjectFromCache(3).IsJSFunction());
147    EcmaString *str11 = reinterpret_cast<EcmaString *>(constpool1->Get(2).GetTaggedObject());
148    EcmaString *str22 = reinterpret_cast<EcmaString *>(constpool1->Get(4).GetTaggedObject());
149    EcmaString *str33 = reinterpret_cast<EcmaString *>(constpool1->Get(5).GetTaggedObject());
150    EcmaString *str44 = reinterpret_cast<EcmaString *>(constpool1->Get(6).GetTaggedObject());
151    EcmaString *str55 = reinterpret_cast<EcmaString *>(constpool1->Get(7).GetTaggedObject());
152    EcmaString *str66 = reinterpret_cast<EcmaString *>(constpool1->Get(8).GetTaggedObject());
153    EcmaString *str77 = reinterpret_cast<EcmaString *>(constpool1->Get(9).GetTaggedObject());
154    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str11).ToCString().c_str(), "str11"), 0);
155    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str22).ToCString().c_str(), "str22"), 0);
156    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str33).ToCString().c_str(), "str11"), 0);
157    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str44).ToCString().c_str(), "str333333333333"), 0);
158    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str55).ToCString().c_str(), "str11str333333333333"), 0);
159    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str66).ToCString().c_str(), "str11str333333333333"), 0);
160    EXPECT_EQ(std::strcmp(EcmaStringAccessor(str77).ToCString().c_str(), "str44"), 0);
161    std::remove(fileName.c_str());
162}
163
164HWTEST_F_L0(SnapshotTest, SerializeDifferentSpace)
165{
166    auto factory = ecmaVm->GetFactory();
167    JSHandle<ConstantPool> constpool = factory->NewConstantPool(400);
168    for (int i = 0; i < 100; i++) {
169        JSHandle<TaggedArray> array = factory->NewTaggedArray(10, JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE);
170        constpool->SetObjectToCache(thread, i, array.GetTaggedValue());
171    }
172    for (int i = 0; i < 100; i++) {
173        JSHandle<TaggedArray> array = factory->NewTaggedArray(10, JSTaggedValue::Hole(), MemSpaceType::OLD_SPACE);
174        constpool->SetObjectToCache(thread, i + 100, array.GetTaggedValue());
175    }
176    for (int i = 0; i < 100; i++) {
177        JSHandle<ConstantPool> constpool1 = factory->NewConstantPool(10);
178        constpool->SetObjectToCache(thread, i + 300, constpool1.GetTaggedValue());
179    }
180
181    CString fileName = "snapshot";
182    Snapshot snapshotSerialize(ecmaVm);
183    // serialize
184    snapshotSerialize.Serialize(*constpool, nullptr, fileName);
185    // deserialize
186    Snapshot snapshotDeserialize(ecmaVm);
187    snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName);
188
189    auto beginRegion = const_cast<Heap *>(ecmaVm->GetHeap())->GetOldSpace()->GetCurrentRegion();
190    auto constpool1 = reinterpret_cast<ConstantPool *>(beginRegion->GetBegin());
191    EXPECT_EQ(constpool->GetClass()->SizeFromJSHClass(*constpool),
192              constpool1->GetClass()->SizeFromJSHClass(constpool1));
193    EXPECT_TRUE(constpool1->GetObjectFromCache(0).IsTaggedArray());
194    EXPECT_TRUE(constpool1->GetObjectFromCache(100).IsTaggedArray());
195    EXPECT_TRUE(constpool1->GetObjectFromCache(300).IsTaggedArray());
196
197    auto obj1 = constpool1->GetObjectFromCache(0).GetTaggedObject();
198    EXPECT_TRUE(Region::ObjectAddressToRange(obj1)->InOldSpace());
199    auto obj2 = constpool1->GetObjectFromCache(100).GetTaggedObject();
200    EXPECT_TRUE(Region::ObjectAddressToRange(obj2)->InOldSpace());
201    std::remove(fileName.c_str());
202}
203
204HWTEST_F_L0(SnapshotTest, SerializeMultiFile)
205{
206    auto factory = ecmaVm->GetFactory();
207    JSHandle<ConstantPool> constpool1 = factory->NewConstantPool(400);
208    JSHandle<ConstantPool> constpool2 = factory->NewConstantPool(400);
209    for (int i = 0; i < 100; i++) {
210        JSHandle<TaggedArray> array = factory->NewTaggedArray(10, JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE);
211        constpool1->SetObjectToCache(thread, i, array.GetTaggedValue());
212        constpool2->SetObjectToCache(thread, i, array.GetTaggedValue());
213    }
214    for (int i = 0; i < 100; i++) {
215        JSHandle<TaggedArray> array = factory->NewTaggedArray(10, JSTaggedValue::Hole(), MemSpaceType::OLD_SPACE);
216        constpool1->SetObjectToCache(thread, i + 100, array.GetTaggedValue());
217        constpool2->SetObjectToCache(thread, i + 100, array.GetTaggedValue());
218    }
219    for (int i = 0; i < 100; i++) {
220        JSHandle<ConstantPool> constpool3 = factory->NewConstantPool(10);
221        constpool1->SetObjectToCache(thread, i + 300, constpool3.GetTaggedValue());
222        constpool2->SetObjectToCache(thread, i + 300, constpool3.GetTaggedValue());
223    }
224
225    CString fileName1 = "snapshot1";
226    CString fileName2 = "snapshot2";
227    Snapshot snapshotSerialize(ecmaVm);
228    // serialize
229    snapshotSerialize.Serialize(*constpool1, nullptr, fileName1);
230    snapshotSerialize.Serialize(*constpool2, nullptr, fileName2);
231    // deserialize
232    Snapshot snapshotDeserialize(ecmaVm);
233    snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName1);
234    snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName2);
235
236    auto beginRegion = const_cast<Heap *>(ecmaVm->GetHeap())->GetOldSpace()->GetCurrentRegion();
237    auto constpool = reinterpret_cast<ConstantPool *>(beginRegion->GetBegin());
238    EXPECT_TRUE(constpool->GetObjectFromCache(0).IsTaggedArray());
239    EXPECT_TRUE(constpool->GetObjectFromCache(100).IsTaggedArray());
240    auto obj1 = constpool->GetObjectFromCache(0).GetTaggedObject();
241    EXPECT_TRUE(Region::ObjectAddressToRange(obj1)->InOldSpace());
242    auto obj2 = constpool->GetObjectFromCache(100).GetTaggedObject();
243    EXPECT_TRUE(Region::ObjectAddressToRange(obj2)->InOldSpace());
244    std::remove(fileName1.c_str());
245    std::remove(fileName2.c_str());
246}
247
248HWTEST_F_L0(SnapshotTest, SerializeBuiltins)
249{
250    // remove builtins.snapshot file first if exist
251    CString fileName = "builtins.snapshot";
252    std::remove(fileName.c_str());
253    // generate builtins.snapshot file
254    std::thread t1([]() {
255        JSRuntimeOptions options1;
256        options1.SetArkProperties(ArkProperties::ENABLE_SNAPSHOT_SERIALIZE);
257        EcmaVM *ecmaVm1 = JSNApi::CreateEcmaVM(options1);
258        JSNApi::DestroyJSVM(ecmaVm1);
259    });
260    {
261        ThreadSuspensionScope suspensionScope(thread);
262        t1.join();
263    }
264
265    // create EcmaVM use builtins deserialzie
266    std::thread t2([&]() {
267        JSRuntimeOptions options2;
268        options2.SetArkProperties(ArkProperties::ENABLE_SNAPSHOT_DESERIALIZE);
269        EcmaVM *ecmaVm2 = JSNApi::CreateEcmaVM(options2);
270        EXPECT_TRUE(ecmaVm2->GetGlobalEnv()->GetClass()->GetObjectType() == JSType::GLOBAL_ENV);
271        auto globalConst = const_cast<GlobalEnvConstants *>(ecmaVm2->GetJSThread()->GlobalConstants());
272        globalConst->VisitRangeSlot([]([[maybe_unused]] Root type, ObjectSlot start, ObjectSlot end) {
273            size_t sharedBeginHclassIndex = static_cast<size_t>(ConstantIndex::SHARED_HCLASS_BEGIN);
274            size_t sharedHclassEndIndex = static_cast<size_t>(ConstantIndex::SHARED_HCLASS_END);
275            size_t hclassBeginIndex = static_cast<size_t>(ConstantIndex::NON_SHARED_HCLASS_BEGIN);
276            size_t hclassEndIndex = static_cast<size_t>(ConstantIndex::NON_SHARED_HCLASS_END);
277            size_t index = sharedBeginHclassIndex;
278            while (start < end) {
279                JSTaggedValue object(start.GetTaggedType());
280                start++;
281                if ((index >= sharedBeginHclassIndex && index <= sharedHclassEndIndex) ||
282                    (index >= hclassBeginIndex && index <= hclassEndIndex)) {
283                    EXPECT_TRUE(object.IsJSHClass());
284                }
285                index++;
286            }
287        });
288        JSNApi::DestroyJSVM(ecmaVm2);
289    });
290    {
291        ThreadSuspensionScope suspensionScope(thread);
292        t2.join();
293    }
294    std::remove(fileName.c_str());
295}
296
297HWTEST_F_L0(SnapshotTest, SerializeHugeObject)
298{
299    auto factory = ecmaVm->GetFactory();
300    auto env = ecmaVm->GetGlobalEnv();
301
302    JSHandle<TaggedArray> array1 = factory->NewTaggedArray(300 * 1024 / 8);
303    JSHandle<TaggedArray> array2 = factory->NewTaggedArray(300 * 1024 / 8);
304    JSHandle<TaggedArray> array3 = factory->NewTaggedArray(100);
305
306    JSHandle<JSFunction> funcFunc(env->GetFunctionFunction());
307    JSHandle<JSFunction> dateFunc(env->GetDateFunction());
308    JSHandle<JSFunction> numberFunc(env->GetNumberFunction());
309    array1->Set(thread, 0, array2.GetTaggedValue());
310    array1->Set(thread, 1, funcFunc.GetTaggedValue());
311    array1->Set(thread, 2, dateFunc.GetTaggedValue());
312    array1->Set(thread, 3, numberFunc.GetTaggedValue());
313    array2->Set(thread, 0, array3.GetTaggedValue());
314    array2->Set(thread, 1, funcFunc.GetTaggedValue());
315    array2->Set(thread, 2, dateFunc.GetTaggedValue());
316    array2->Set(thread, 3, numberFunc.GetTaggedValue());
317
318    CString fileName = "snapshot";
319    Snapshot snapshotSerialize(ecmaVm);
320    // serialize
321    snapshotSerialize.Serialize(*array1, nullptr, fileName);
322    // deserialize
323    Snapshot snapshotDeserialize(ecmaVm);
324    snapshotDeserialize.Deserialize(SnapshotType::VM_ROOT, fileName);
325
326    auto lastRegion = const_cast<Heap *>(ecmaVm->GetHeap())->GetHugeObjectSpace()->GetCurrentRegion();
327    auto array4 = reinterpret_cast<TaggedArray *>(lastRegion->GetBegin());
328    EXPECT_TRUE(array4->Get(0).IsTaggedArray());
329    EXPECT_TRUE(array4->Get(1).IsJSFunction());
330    EXPECT_TRUE(array4->Get(2).IsJSFunction());
331    EXPECT_TRUE(array4->Get(3).IsJSFunction());
332    std::remove(fileName.c_str());
333}
334
335HWTEST_F_L0(SnapshotTest, BackwardCompatibility)
336{
337    base::FileHeaderBase::VersionType oldVersion = {0, 0, 0, 1};
338    base::FileHeaderBase::VersionType newVersion = {4, 0, 0, 1};
339    CompatibilityHelper(oldVersion, newVersion, false);
340}
341
342HWTEST_F_L0(SnapshotTest, ForwardCompatibility)
343{
344    base::FileHeaderBase::VersionType oldVersion = {0, 0, 0, 1};
345    base::FileHeaderBase::VersionType newVersion = {4, 0, 0, 1};
346    CompatibilityHelper(newVersion, oldVersion, false);
347}
348
349HWTEST_F_L0(SnapshotTest, StrictCompatibility)
350{
351    base::FileHeaderBase::VersionType newVersion = {4, 0, 0, 1};
352    CompatibilityHelper(newVersion, newVersion, true);
353}
354
355HWTEST_F_L0(SnapshotTest, VersionTest)
356{
357    base::FileHeaderBase::VersionType version = {4, 3, 2, 1};
358    uint32_t versionNumber = 0x04030201U;
359    EXPECT_EQ(version, base::FileHeaderBase::ToVersion(versionNumber));
360    EXPECT_EQ(versionNumber, base::FileHeaderBase::ToVersionNumber(version));
361}
362
363}  // namespace panda::test
364