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/dfx/hprof/heap_snapshot_json_serializer.h"
17
18#include "ecmascript/dfx/hprof/heap_snapshot.h"
19#include "securec.h"
20
21namespace panda::ecmascript {
22
23bool HeapSnapshotJSONSerializer::Serialize(HeapSnapshot *snapshot, Stream *stream)
24{
25    // Serialize Node/Edge/String-Table
26    LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize begin";
27    ASSERT(snapshot->GetNodes() != nullptr && snapshot->GetEdges() != nullptr &&
28           snapshot->GetEcmaStringTable() != nullptr);
29    auto writer = new StreamWriter(stream);
30
31    SerializeSnapshotHeader(snapshot, writer);     // 1.
32    SerializeNodes(snapshot, writer);              // 2.
33    SerializeEdges(snapshot, writer);              // 3.
34    SerializeTraceFunctionInfo(snapshot, writer);  // 4.
35    SerializeTraceTree(snapshot, writer);          // 5.
36    SerializeSamples(snapshot, writer);            // 6.
37    SerializeLocations(writer);          // 7.
38    SerializeStringTable(snapshot, writer);        // 8.
39    SerializerSnapshotClosure(writer);   // 9.
40    writer->End();
41
42    delete writer;
43
44    LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize exit";
45    return true;
46}
47
48void HeapSnapshotJSONSerializer::DumpStringTable(HeapSnapshot *snapshot, Stream *stream)
49{
50    const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
51    ASSERT(stringTable != nullptr);
52    size_t size5MB = 5 * 1024 * 1024;
53    char *buf = new char[size5MB]; // 5MB buf use for string dump
54    auto strNum = stringTable->GetCapcity();
55    auto ret = memcpy_s(buf, size5MB, &strNum, sizeof(size_t));
56    if (ret != EOK) {
57        delete[] buf;
58        LOG_ECMA(ERROR) << "DumpStringTable: memcpy_s failed, strNum=" << strNum;
59        return;
60    }
61    size_t offset = sizeof(size_t);
62    for (auto key : stringTable->GetOrderedKeyStorage()) {
63        auto [strId, str] = stringTable->GetStringAndIdPair(key);
64        const char *s = str->data();
65        auto currLen = str->size() + 1;
66        if (currLen + sizeof(uint64_t) > size5MB) {
67            stream->WriteBinBlock(buf, offset);
68            offset = 0;
69            delete[] buf;
70            size5MB = currLen + sizeof(uint64_t);
71            buf = new char[size5MB];
72        }
73        if (offset + currLen + sizeof(uint64_t) > size5MB) {
74            stream->WriteBinBlock(buf, offset);
75            offset = 0;
76        }
77        auto strIdPtr = reinterpret_cast<uint64_t *>(buf + offset);
78        *strIdPtr = strId;
79        ret = memcpy_s(buf + offset + sizeof(uint64_t), size5MB - offset, s, currLen);
80        if (ret != EOK) {
81            delete[] buf;
82            LOG_ECMA(ERROR) << "DumpStringTable: memcpy_s failed";
83            return;
84        }
85        offset += currLen + sizeof(uint64_t);
86    }
87    if (offset > 0) {
88        stream->WriteBinBlock(buf, offset);
89    }
90    delete[] buf;
91}
92
93void HeapSnapshotJSONSerializer::SerializeSnapshotHeader(HeapSnapshot *snapshot, StreamWriter *writer)
94{
95    writer->WriteString("{\"snapshot\":\n");  // 1.
96    writer->WriteString("{\"meta\":\n");      // 2.
97    // NOLINTNEXTLINE(modernize-raw-string-literal)
98    writer->WriteString("{\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",");
99    writer->WriteString("\"detachedness\",\"native_size\"],\n");  // 3.
100    // NOLINTNEXTLINE(modernize-raw-string-literal)
101    writer->WriteString("\"node_types\":[[\"hidden\",\"array\",\"string\",\"object\",\"code\",\"closure\",\"regexp\",");
102    // NOLINTNEXTLINE(modernize-raw-string-literal)
103    writer->WriteString("\"number\",\"native\",\"synthetic\",\"concatenated string\",\"slicedstring\",\"symbol\",");
104    // NOLINTNEXTLINE(modernize-raw-string-literal)
105    writer->WriteString("\"bigint\"],\"string\",\"number\",\"number\",\"number\",\"number\",\"number\"],\n");  // 4.
106    // NOLINTNEXTLINE(modernize-raw-string-literal)
107    writer->WriteString("\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],\n");  // 5.
108    // NOLINTNEXTLINE(modernize-raw-string-literal)
109    writer->WriteString("\"edge_types\":[[\"context\",\"element\",\"property\",\"internal\",\"hidden\",\"shortcut\",");
110    // NOLINTNEXTLINE(modernize-raw-string-literal)
111    writer->WriteString("\"weak\"],\"string_or_number\",\"node\"],\n");  // 6.
112    // NOLINTNEXTLINE(modernize-raw-string-literal)
113    writer->WriteString("\"trace_function_info_fields\":[\"function_id\",\"name\",\"script_name\",\"script_id\",");
114    // NOLINTNEXTLINE(modernize-raw-string-literal)
115    writer->WriteString("\"line\",\"column\"],\n");  // 7.
116    // NOLINTNEXTLINE(modernize-raw-string-literal)
117    writer->WriteString("\"trace_node_fields\":[\"id\",\"function_info_index\",\"count\",\"size\",\"children\"],\n");
118    // NOLINTNEXTLINE(modernize-raw-string-literal)
119    writer->WriteString("\"sample_fields\":[\"timestamp_us\",\"last_assigned_id\"],\n");  // 9.
120    // NOLINTNEXTLINE(modernize-raw-string-literal)
121    // 10.
122    writer->WriteString("\"location_fields\":[\"object_index\",\"script_id\",\"line\",\"column\"]},\n\"node_count\":");
123    writer->WriteNumber(snapshot->GetNodeCount());                         // 11.
124    writer->WriteString(",\n\"edge_count\":");
125    writer->WriteNumber(snapshot->GetEdgeCount());                         // 12.
126    writer->WriteString(",\n\"trace_function_count\":");
127    writer->WriteNumber(snapshot->GetTrackAllocationsStack().size());   // 13.
128    writer->WriteString("\n},\n");  // 14.
129}
130
131void HeapSnapshotJSONSerializer::SerializeNodes(HeapSnapshot *snapshot, StreamWriter *writer)
132{
133    const CList<Node *> *nodes = snapshot->GetNodes();
134    const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
135    ASSERT(nodes != nullptr);
136    writer->WriteString("\"nodes\":[");  // Section Header
137    size_t i = 0;
138    for (auto *node : *nodes) {
139        if (i > 0) {
140            writer->WriteChar(',');  // add comma except first line
141        }
142        writer->WriteNumber(static_cast<int>(node->GetType()));  // 1.
143        writer->WriteChar(',');
144        writer->WriteNumber(stringTable->GetStringId(node->GetName()));                      // 2.
145        writer->WriteChar(',');
146        writer->WriteNumber(node->GetId());                                                  // 3.
147        writer->WriteChar(',');
148        writer->WriteNumber(node->GetSelfSize());                                            // 4.
149        writer->WriteChar(',');
150        writer->WriteNumber(node->GetEdgeCount());                                           // 5.
151        writer->WriteChar(',');
152        writer->WriteNumber(node->GetStackTraceId());                                        // 6.
153        writer->WriteChar(',');
154        writer->WriteChar('0');                                                              // 7.detachedness default 0
155        writer->WriteChar(',');
156        writer->WriteNumber(node->GetNativeSize());
157        if (i == nodes->size() - 1) {    // add comma at last the line
158            writer->WriteString("],\n"); // 7. detachedness default
159        } else {
160            writer->WriteString("\n");   // 7.
161        }
162        i++;
163    }
164}
165
166void HeapSnapshotJSONSerializer::SerializeEdges(HeapSnapshot *snapshot, StreamWriter *writer)
167{
168    const CList<Edge *> *edges = snapshot->GetEdges();
169    const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
170    ASSERT(edges != nullptr);
171    writer->WriteString("\"edges\":[");
172    size_t i = 0;
173    for (auto *edge : *edges) {
174        StringId nameOrIndex = edge->GetType() == EdgeType::ELEMENT ?
175            edge->GetIndex() : stringTable->GetStringId(edge->GetName());
176        if (i > 0) {  // add comma except the first line
177            writer->WriteChar(',');
178        }
179        writer->WriteNumber(static_cast<int>(edge->GetType()));          // 1.
180        writer->WriteChar(',');
181        writer->WriteNumber(nameOrIndex);  // 2. Use StringId
182        writer->WriteChar(',');
183
184        if (i == edges->size() - 1) {  // add comma at last the line
185            writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT);  // 3.
186            writer->WriteString("],\n");
187        } else {
188            writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT);    // 3.
189            writer->WriteChar('\n');
190        }
191        i++;
192    }
193}
194
195void HeapSnapshotJSONSerializer::SerializeTraceFunctionInfo(HeapSnapshot *snapshot, StreamWriter *writer)
196{
197    const CVector<FunctionInfo> trackAllocationsStack = snapshot->GetTrackAllocationsStack();
198    const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
199
200    writer->WriteString("\"trace_function_infos\":[");  // Empty
201    size_t i = 0;
202
203    for (const auto &info : trackAllocationsStack) {
204        if (i > 0) {  // add comma except the first line
205            writer->WriteChar(',');
206        }
207        writer->WriteNumber(info.functionId);
208        writer->WriteChar(',');
209        CString functionName(info.functionName.c_str());
210        writer->WriteNumber(stringTable->GetStringId(&functionName));
211        writer->WriteChar(',');
212        CString scriptName(info.scriptName.c_str());
213        writer->WriteNumber(stringTable->GetStringId(&scriptName));
214        writer->WriteChar(',');
215        writer->WriteNumber(info.scriptId);
216        writer->WriteChar(',');
217        writer->WriteNumber(info.lineNumber);
218        writer->WriteChar(',');
219        writer->WriteNumber(info.columnNumber);
220        writer->WriteChar('\n');
221        i++;
222    }
223    writer->WriteString("],\n");
224}
225
226void HeapSnapshotJSONSerializer::SerializeTraceTree(HeapSnapshot *snapshot, StreamWriter *writer)
227{
228    writer->WriteString("\"trace_tree\":[");
229    TraceTree* tree = snapshot->GetTraceTree();
230    if ((tree != nullptr) && (snapshot->trackAllocations())) {
231        SerializeTraceNode(tree->GetRoot(), writer);
232    }
233    writer->WriteString("],\n");
234}
235
236void HeapSnapshotJSONSerializer::SerializeTraceNode(TraceNode* node, StreamWriter *writer)
237{
238    if (node == nullptr) {
239        return;
240    }
241
242    writer->WriteNumber(node->GetId());
243    writer->WriteChar(',');
244    writer->WriteNumber(node->GetNodeIndex());
245    writer->WriteChar(',');
246    writer->WriteNumber(node->GetTotalCount());
247    writer->WriteChar(',');
248    writer->WriteNumber(node->GetTotalSize());
249    writer->WriteString(",[");
250
251    int i = 0;
252    for (TraceNode* child : node->GetChildren()) {
253        if (i > 0) {
254            writer->WriteChar(',');
255        }
256        SerializeTraceNode(child, writer);
257        i++;
258    }
259    writer->WriteChar(']');
260}
261
262void HeapSnapshotJSONSerializer::SerializeSamples(HeapSnapshot *snapshot, StreamWriter *writer)
263{
264    writer->WriteString("\"samples\":[");
265    const CVector<TimeStamp> &timeStamps = snapshot->GetTimeStamps();
266    if (!timeStamps.empty()) {
267        auto firstTimeStamp = timeStamps[0];
268        bool isFirst = true;
269        for (auto timeStamp : timeStamps) {
270            if (!isFirst) {
271                writer->WriteString("\n, ");
272            } else {
273                isFirst = false;
274            }
275            writer->WriteNumber(timeStamp.GetTimeStamp() - firstTimeStamp.GetTimeStamp());
276            writer->WriteString(", ");
277            writer->WriteNumber(timeStamp.GetLastSequenceId());
278        }
279    }
280    writer->WriteString("],\n");
281}
282
283void HeapSnapshotJSONSerializer::SerializeLocations(StreamWriter *writer)
284{
285    writer->WriteString("\"locations\":[],\n");
286}
287
288void HeapSnapshotJSONSerializer::SerializeStringTable(HeapSnapshot *snapshot, StreamWriter *writer)
289{
290    const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
291    ASSERT(stringTable != nullptr);
292    writer->WriteString("\"strings\":[\"<dummy>\",\n");
293    writer->WriteString("\"\",\n");
294    writer->WriteString("\"GC roots\",\n");
295    // StringId Range from 3
296    size_t capcity = stringTable->GetCapcity();
297    ASSERT(capcity > 0);
298    size_t i = 0;
299    for (auto key : stringTable->GetOrderedKeyStorage()) {
300        if (i == capcity - 1) {
301            writer->WriteChar('\"');
302            SerializeString(stringTable->GetStringByKey(key), writer); // No Comma for the last line
303            writer->WriteString("\"\n");
304        } else {
305            writer->WriteChar('\"');
306            SerializeString(stringTable->GetStringByKey(key), writer);
307            writer->WriteString("\",\n");
308        }
309        i++;
310    }
311    writer->WriteString("]\n");
312}
313
314void HeapSnapshotJSONSerializer::SerializeString(CString *str, StreamWriter *writer)
315{
316    if (str == nullptr || writer == nullptr) {
317        return;
318    }
319    const char *s = str->c_str();
320    while (*s != '\0') {
321        if (*s == '\"' || *s == '\\') {
322            writer->WriteChar('\\');
323            writer->WriteChar(*s);
324            s++;
325        } else if (*s == '\n') {
326            writer->WriteString("\\n");
327            s++;
328        } else if (*s == '\b') {
329            writer->WriteString("\\b");
330            s++;
331        } else if (*s == '\f') {
332            writer->WriteString("\\f");
333            s++;
334        } else if (*s == '\r') {
335            writer->WriteString("\\r");
336            s++;
337        } else if (*s == '\t') {
338            writer->WriteString("\\t");
339            s++;
340        } else if (*s > ASCII_US && *s < ASCII_DEL) {
341            writer->WriteChar(*s);
342            s++;
343        } else if (*s <= ASCII_US || *s == ASCII_DEL) {
344            // special char convert to \u unicode
345            SerializeUnicodeChar(static_cast<uint32_t>(*s), writer);
346            s++;
347        } else {
348            // convert utf-8 to \u unicode
349            size_t len = 1;
350            while (len <= UTF8_MAX_BYTES && *(s + len) != '\0') {
351                len++;
352            }
353            auto [unicode, bytes] =
354                base::utf_helper::ConvertUtf8ToUnicodeChar(reinterpret_cast<const uint8_t *>(s), len);
355            if (unicode == base::utf_helper::INVALID_UTF8) {
356                LOG_ECMA(WARN) << "HeapSnapshotJSONSerializer::SerializeString, str is not utf-8";
357                writer->WriteChar('?');
358                s++;
359            } else {
360                SerializeUnicodeChar(unicode, writer);
361                s += bytes;
362            }
363        }
364    }
365}
366
367void HeapSnapshotJSONSerializer::SerializeUnicodeChar(uint32_t unicodeChar, StreamWriter *writer)
368{
369    static const char hexChars[] = "0123456789ABCDEF";
370    writer->WriteString("\\u");
371    writer->WriteChar(hexChars[(unicodeChar >> 0xC) & 0xF]);
372    writer->WriteChar(hexChars[(unicodeChar >> 0x8) & 0xF]);
373    writer->WriteChar(hexChars[(unicodeChar >> 0x4) & 0xF]);
374    writer->WriteChar(hexChars[unicodeChar & 0xF]);
375}
376
377void HeapSnapshotJSONSerializer::SerializerSnapshotClosure(StreamWriter *writer)
378{
379    writer->WriteString("}\n");
380}
381}  // namespace panda::ecmascript
382