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 <cstdio>
17#include <fstream>
18#include <cstdlib>
19#include <ctime>
20
21#include "ecmascript/accessor_data.h"
22#include "ecmascript/ecma_vm.h"
23#include "ecmascript/global_dictionary-inl.h"
24#include "ecmascript/global_env.h"
25#include "ecmascript/dfx/hprof/heap_profiler.h"
26#include "ecmascript/dfx/hprof/heap_profiler_interface.h"
27#include "ecmascript/dfx/hprof/heap_snapshot.h"
28#include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h"
29#include "ecmascript/dfx/hprof/string_hashmap.h"
30#include "ecmascript/ic/ic_handler.h"
31#include "ecmascript/ic/property_box.h"
32#include "ecmascript/ic/proto_change_details.h"
33#include "ecmascript/jobs/micro_job_queue.h"
34#include "ecmascript/jobs/pending_job.h"
35#include "ecmascript/jspandafile/program_object.h"
36#include "ecmascript/js_arguments.h"
37#include "ecmascript/js_array.h"
38#include "ecmascript/js_array_iterator.h"
39#include "ecmascript/js_arraybuffer.h"
40#include "ecmascript/js_async_function.h"
41#include "ecmascript/js_collator.h"
42#include "ecmascript/js_dataview.h"
43#include "ecmascript/js_date.h"
44#include "ecmascript/js_date_time_format.h"
45#include "ecmascript/js_for_in_iterator.h"
46#include "ecmascript/js_function.h"
47#include "ecmascript/js_generator_object.h"
48#include "ecmascript/js_global_object.h"
49#include "ecmascript/js_handle.h"
50#include "ecmascript/js_intl.h"
51#include "ecmascript/js_locale.h"
52#include "ecmascript/js_map.h"
53#include "ecmascript/js_map_iterator.h"
54#include "ecmascript/js_number_format.h"
55#include "ecmascript/js_object-inl.h"
56#include "ecmascript/js_plural_rules.h"
57#include "ecmascript/js_primitive_ref.h"
58#include "ecmascript/js_promise.h"
59#include "ecmascript/js_realm.h"
60#include "ecmascript/js_regexp.h"
61#include "ecmascript/js_relative_time_format.h"
62#include "ecmascript/js_set.h"
63#include "ecmascript/js_set_iterator.h"
64#include "ecmascript/js_string_iterator.h"
65#include "ecmascript/js_tagged_number.h"
66#include "ecmascript/js_tagged_value-inl.h"
67#include "ecmascript/js_thread.h"
68#include "ecmascript/js_typed_array.h"
69#include "ecmascript/js_weak_container.h"
70#include "ecmascript/layout_info-inl.h"
71#include "ecmascript/lexical_env.h"
72#include "ecmascript/linked_hash_table.h"
73#include "ecmascript/mem/assert_scope.h"
74#include "ecmascript/mem/c_containers.h"
75#include "ecmascript/mem/machine_code.h"
76#include "ecmascript/object_factory.h"
77#include "ecmascript/tagged_array.h"
78#include "ecmascript/tagged_dictionary.h"
79#include "ecmascript/template_map.h"
80#include "ecmascript/tests/test_helper.h"
81#include "ecmascript/transitions_dictionary.h"
82
83using namespace panda::ecmascript;
84using namespace panda::ecmascript::base;
85
86namespace panda::test {
87using MicroJobQueue = panda::ecmascript::job::MicroJobQueue;
88using PendingJob = panda::ecmascript::job::PendingJob;
89class HProfTest : public testing::Test {
90public:
91    static void SetUpTestCase()
92    {
93        GTEST_LOG_(INFO) << "SetUpTestCase";
94    }
95
96    static void TearDownTestCase()
97    {
98        GTEST_LOG_(INFO) << "TearDownCase";
99    }
100
101    void SetUp() override
102    {
103        TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
104    }
105
106    void TearDown() override
107    {
108        TestHelper::DestroyEcmaVMWithScope(instance, scope);
109    }
110    EcmaVM *instance {nullptr};
111    EcmaHandleScope *scope {nullptr};
112    JSThread *thread {nullptr};
113};
114
115class HProfTestHelper {
116public:
117    explicit HProfTestHelper(EcmaVM *vm) : instance(vm) {}
118
119    ~HProfTestHelper()
120    {
121        HeapProfilerInterface::Destroy(instance);
122    }
123
124    size_t GenerateSnapShot(const std::string &filePath)
125    {
126        // first generate this file of filePath if not exist,
127        // so the function `realpath` of FileStream can not failed on arm/arm64.
128        fstream outputString(filePath, std::ios::out);
129        outputString.close();
130        outputString.clear();
131        FileStream stream(filePath.c_str());
132        HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
133        DumpSnapShotOption dumpOption;
134        dumpOption.dumpFormat = DumpFormat::JSON;
135        heapProfile->DumpHeapSnapshot(&stream, dumpOption);
136        return heapProfile->GetIdCount();
137    }
138
139    bool ContrastJSONLineHeader(const std::string &filePath, std::string lineHeader)
140    {
141        std::string line;
142        std::ifstream inputStream(filePath);
143        while (getline(inputStream, line)) {
144            if (line.find(lineHeader) != line.npos) {
145                return true;
146            }
147        }
148        return false;
149    }
150
151    bool ContrastJSONSectionPayload(const std::string &filePath, std::string dataLable, int fieldNum)
152    {
153        std::string line;
154        int i = 1;
155        std::ifstream inputStream(filePath);
156        while (getline(inputStream, line)) {
157            if (i > 10 && line.find(dataLable) != line.npos) {  // 10 : Hit the line
158                std::string::size_type pos = 0;
159                int loop = 0;
160                while ((pos = line.find(",", pos)) != line.npos) {
161                    pos++;
162                    loop++;  // "," count
163                }
164                return loop == fieldNum - 1;
165            }
166            i++;  // Search the Next Line
167        }
168        return false;  // Lost the Line
169    }
170
171    bool ContrastJSONPayloadCntAndHitStrField(const std::string &filePath, std::string dataLable, int fieldCnt,
172                                              std::string fieldStr)
173    {
174        std::string line;
175        int i = 1;
176        std::ifstream inputStream(filePath);
177        while (getline(inputStream, line)) {
178            if (line.find(dataLable) != line.npos) {  // 3 : Hit the line
179                std::string::size_type pos = 0;
180                int loop = 0;
181                while ((pos = line.find(",", pos)) != line.npos) {
182                    pos++;
183                    loop++;  // "," count
184                }
185                if (loop != fieldCnt - 1) {
186                    return false;
187                }
188                return line.find(fieldStr) != line.npos; // check if hit the target field
189            }
190            i++;  // Search the Next Line
191        }
192        return false;  // Lost the Line
193    }
194
195    bool ContrastJSONNativeSizeNum(const std::string &filePath, std::string nodesLable, int nNum)
196    {
197        std::string line;
198        bool hit = false;
199        std::vector<std::string> nodeNativeSizes;
200        std::ifstream inputStream(filePath);
201        while (getline(inputStream, line)) {
202            if (!hit && line.find(nodesLable) == line.npos) {
203                continue; // Not Get
204            }
205            if (line.find("[]") != line.npos) {  // Empty
206                break;
207            }
208            if (!hit) {
209                hit = true;
210            }
211            if (hit && (line.find("],") != line.npos)) {
212                break; // Reach End
213            }
214            int rCommaInd = line.rfind(','); // find last one
215            if (rCommaInd == line.npos) {
216                return false;
217            }
218            std::string nativeSizeStr = line.substr(rCommaInd + 1, line.size() - rCommaInd);
219            if (nativeSizeStr.compare("0") != 0) {
220                nodeNativeSizes.push_back(nativeSizeStr);
221            }
222        }
223        const Heap* heap = instance->GetHeap();
224        if (heap->nativePointerList_.size() != nodeNativeSizes.size() || nodeNativeSizes.size() != nNum) {
225            return false;
226        }
227        int ind = 0;
228        for (JSNativePointer* jsNp : heap->nativePointerList_) {
229            if (nodeNativeSizes[ind].compare(std::to_string(jsNp->GetBindingSize())) != 0) {
230                return false;
231            }
232            ind++;
233        }
234        return true;
235    }
236
237    bool ContrastJSONClousure(const std::string &filePath)
238    {
239        std::string lineBk;  // The Last Line
240        std::string line;
241        std::ifstream inputStream(filePath);
242        while (getline(inputStream, line)) {
243            lineBk = line;
244        }
245        return lineBk.compare("}") == 0;
246    }
247
248    int ExtractCountFromMeta(const std::string &filePath, std::string typeLable)
249    {
250        std::string line;
251        std::ifstream inputStream(filePath);
252        while (getline(inputStream, line)) {
253            int length = line.length() - typeLable.length() - 1;
254            if (line.find(typeLable) != line.npos) {  // Get
255                if (line.find(",") == line.npos) {    // "trace_function_count" end without ","
256                    length = line.length() - typeLable.length();
257                }
258                line = line.substr(typeLable.length(), length);
259                return std::stoi(line.c_str());
260            }
261        }
262        return -1;
263    }
264
265    int ExtractCountFromPayload(const std::string &filePath, std::string dataLabel)
266    {
267        std::string line;
268        bool hit = false;
269        int loop = 0;
270        std::ifstream inputStream(filePath);
271        while (getline(inputStream, line)) {
272            if (!hit && line.find(dataLabel) != line.npos) {  // Get
273                loop += 1;                                    // First Line
274                hit = true;
275                if (line.find("[]") != line.npos) {  // Empty
276                    loop = 0;
277                    return loop;
278                } else {
279                    continue;
280                }
281            }
282            if (hit) {
283                if (line.find("],") != line.npos) {  // Reach End
284                    loop += 1;                       // End Line
285                    return loop;
286                } else {
287                    loop++;
288                    continue;
289                }
290            }
291        }
292        return -1;
293    }
294
295private:
296    EcmaVM *instance {nullptr};
297};
298
299HWTEST_F_L0(HProfTest, ParseJSONHeader)
300{
301    HProfTestHelper tester(instance);
302    tester.GenerateSnapShot("test1.heapsnapshot");
303    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"snapshot\":"));
304    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"meta\":"));
305    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"node_fields\":"));
306    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"node_types\":"));
307    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"edge_fields\":"));
308    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"edge_types\":"));
309    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"trace_function_info_fields\":"));
310    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"trace_node_fields\":"));
311    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"sample_fields\":"));
312    ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"location_fields\":"));
313}
314
315HWTEST_F_L0(HProfTest, ContrastTraceFunctionInfo)
316{
317    HProfTestHelper tester(instance);
318    tester.GenerateSnapShot("test2.heapsnapshot");
319    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test2.heapsnapshot", "\"trace_function_infos\":", 2));  // Empty
320}
321
322HWTEST_F_L0(HProfTest, ContrastTraceTree)
323{
324    HProfTestHelper tester(instance);
325    tester.GenerateSnapShot("test3.heapsnapshot");
326    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test3.heapsnapshot", "\"trace_tree\":", 2));  // Empty
327}
328
329HWTEST_F_L0(HProfTest, ContrastSamples)
330{
331    HProfTestHelper tester(instance);
332    tester.GenerateSnapShot("test4.heapsnapshot");
333    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test4.heapsnapshot", "\"samples\":", 2));  // Empty
334}
335
336HWTEST_F_L0(HProfTest, ContrastLocations)
337{
338    HProfTestHelper tester(instance);
339    tester.GenerateSnapShot("test5.heapsnapshot");
340    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test5.heapsnapshot", "\"locations\":", 2));  // Empty
341}
342
343HWTEST_F_L0(HProfTest, ContrastString)
344{
345    HProfTestHelper tester(instance);
346    tester.GenerateSnapShot("test6.heapsnapshot");
347    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test6.heapsnapshot", "\"strings\":[", 2));
348}
349
350HWTEST_F_L0(HProfTest, ContrastClosure)
351{
352    HProfTestHelper tester(instance);
353    tester.GenerateSnapShot("test7.heapsnapshot");
354    ASSERT_TRUE(tester.ContrastJSONClousure("test7.heapsnapshot"));
355}
356
357HWTEST_F_L0(HProfTest, ContrastEdgeCount)
358{
359    HProfTestHelper tester(instance);
360    tester.GenerateSnapShot("test8.heapsnapshot");
361    ASSERT_TRUE(tester.ExtractCountFromMeta("test8.heapsnapshot", "\"edge_count\":") ==
362                tester.ExtractCountFromPayload("test8.heapsnapshot", "\"edges\":["));
363}
364
365HWTEST_F_L0(HProfTest, TraceFuncInfoCount)
366{
367    HProfTestHelper tester(instance);
368    tester.GenerateSnapShot("test9.heapsnapshot");
369    ASSERT_TRUE(tester.ExtractCountFromMeta("test9.heapsnapshot", "\"trace_function_count\":") ==
370                tester.ExtractCountFromPayload("test9.heapsnapshot", "\"trace_function_infos\":"));
371}
372
373HWTEST_F_L0(HProfTest, DumpNativeSize)
374{
375    int nativeSizeNum = 6;
376    int count = nativeSizeNum / 2;
377    while (count-- > 0) {
378        instance->GetFactory()->NewJSArrayBuffer(10);
379        instance->GetFactory()->NewJSArrayBuffer(20);
380    }
381    HProfTestHelper tester(instance);
382    tester.GenerateSnapShot("test10.heapsnapshot");
383
384    ASSERT_TRUE(tester.ContrastJSONPayloadCntAndHitStrField("test10.heapsnapshot", "{\"node_fields\":",
385                                                            9, "native_size"));
386    ASSERT_TRUE(tester.ContrastJSONSectionPayload("test10.heapsnapshot", "\"nodes\":[", 8));
387    ASSERT_TRUE(tester.ContrastJSONNativeSizeNum("test10.heapsnapshot", "\"nodes\":[", nativeSizeNum));
388}
389
390HWTEST_F_L0(HProfTest, TestSetDumpFormatInRandomNum)
391{
392    std::srand(std::time(nullptr));
393    for (int i = 0; i < 20; i++) {
394        size_t size = std::rand() % 128;
395        if (size <= 0) {
396            continue;
397        }
398        EcmaVM *vm = instance;
399        size_t maxEnumNum = static_cast<size_t>(DumpFormat::OTHER) + 1;
400        DumpFormat dumpFormat = static_cast<DumpFormat>(size % maxEnumNum);
401        DumpSnapShotOption dumpOption;
402        dumpOption.dumpFormat = dumpFormat;
403        dumpOption.isVmMode = true;
404        dumpOption.isPrivate = false;
405        dumpOption.captureNumericValue = false;
406        if (size > sizeof(double)) {
407            size = sizeof(double);
408        }
409        uint8_t* data = reinterpret_cast<uint8_t*>(malloc(size));
410        for (size_t j = 0; j < size - 1; j++) {
411            data[j] = size;
412        }
413        data[size - 1] = 0;
414        std::string path(data, data + size);
415        FileStream stream(path);
416        Progress *progress = nullptr;
417        DFXJSNApi::DumpHeapSnapshot(vm, &stream, dumpOption, progress);
418        HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
419        heapProfile->DumpHeapSnapshot(&stream, dumpOption);
420        ASSERT_TRUE(heapProfile->GetIdCount() > 0);
421        free(data);
422    }
423}
424
425HWTEST_F_L0(HProfTest, TestIdConsistency)
426{
427    HProfTestHelper tester(instance);
428    int64_t count1 = tester.GenerateSnapShot("TestIdConsistency_1.heapsnapshot");
429    for (int i = 0; i < 100; ++i) {
430        instance->GetFactory()->NewJSAsyncFuncObject();
431        instance->GetFactory()->NewJSSymbol();
432    }
433    int64_t count2 = tester.GenerateSnapShot("TestIdConsistency_2.heapsnapshot");
434    ASSERT_TRUE(std::abs(count1 - count2) <= 500LL);
435    // load two heapsnapshots into chrome, and further use "Comparision View"
436}
437}  // namespace panda::test
438