• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 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 #ifndef SOURCE_MAP_H
17 #define SOURCE_MAP_H
18 
19 #include <algorithm>
20 #include <iostream>
21 #include <map>
22 #include <string>
23 #include <v8.h>
24 #include <vector>
25 
26 #include "jsvm_util.h"
27 
28 // Base64 VLQ decoding
DecodeVLQ(const std::string & input,size_t & pos)29 int DecodeVLQ(const std::string& input, size_t& pos)
30 {
31     // Const value
32     constexpr int vlqBaseShift = 5;
33     constexpr int vlqBaseMask = (1 << 5) - 1;
34     constexpr int vlqContinuationMask = 1 << 5;
35     static std::unordered_map<char, int> base64Map {
36         { 'A', 0 },  { 'B', 1 },  { 'C', 2 },  { 'D', 3 },  { 'E', 4 },  { 'F', 5 },  { 'G', 6 },  { 'H', 7 },
37         { 'I', 8 },  { 'J', 9 },  { 'K', 10 }, { 'L', 11 }, { 'M', 12 }, { 'N', 13 }, { 'O', 14 }, { 'P', 15 },
38         { 'Q', 16 }, { 'R', 17 }, { 'S', 18 }, { 'T', 19 }, { 'U', 20 }, { 'V', 21 }, { 'W', 22 }, { 'X', 23 },
39         { 'Y', 24 }, { 'Z', 25 }, { 'a', 26 }, { 'b', 27 }, { 'c', 28 }, { 'd', 29 }, { 'e', 30 }, { 'f', 31 },
40         { 'g', 32 }, { 'h', 33 }, { 'i', 34 }, { 'j', 35 }, { 'k', 36 }, { 'l', 37 }, { 'm', 38 }, { 'n', 39 },
41         { 'o', 40 }, { 'p', 41 }, { 'q', 42 }, { 'r', 43 }, { 's', 44 }, { 't', 45 }, { 'u', 46 }, { 'v', 47 },
42         { 'w', 48 }, { 'x', 49 }, { 'y', 50 }, { 'z', 51 }, { '0', 52 }, { '1', 53 }, { '2', 54 }, { '3', 55 },
43         { '4', 56 }, { '5', 57 }, { '6', 58 }, { '7', 59 }, { '8', 60 }, { '9', 61 }, { '+', 62 }, { '/', 63 }
44     };
45 
46     // Decode VLQ
47     int result = 0;
48     int shift = 0;
49     int digit;
50     do {
51         digit = base64Map[input[pos++]];
52         result += (digit & vlqBaseMask) << shift;
53         shift += vlqBaseShift;
54     } while (digit & vlqContinuationMask);
55 
56     // Fix the sign
57     int negative = result & 1;
58     result >>= 1;
59     return negative ? -result : result;
60 }
61 
62 struct Offset {
63 public:
OffsetOffset64     constexpr Offset(int line, int column) : line(line), column(column) {}
65 
InvalidOffsetOffset66     constexpr static Offset InvalidOffset()
67     {
68         return Offset(-1, -1);
69     }
70 
IsInvalidOffset71     bool IsInvalid() const
72     {
73         return line < 0 || column < 0;
74     }
75 
76     bool operator<(const Offset& other) const
77     {
78         return line < other.line || (line == other.line && column < other.column);
79     }
80 
81 public:
82     int line;
83     int column;
84 };
85 
86 struct Mappings {
87 public:
88     explicit Mappings(Offset traceOffset,
89              Offset sourceOffset = Offset::InvalidOffset(),
90              std::string sourceName = "",
91              int nameIdx = -1)
traceOffsetMappings92         : traceOffset(traceOffset), sourceOffset(sourceOffset), nameIdx(nameIdx), sourceName(sourceName)
93     {}
94 
IsInvalidMappings95     bool IsInvalid() const
96     {
97         return traceOffset.IsInvalid() || sourceOffset.IsInvalid();
98     }
99 
100     bool operator<(const Mappings& other) const
101     {
102         return traceOffset < other.traceOffset;
103     }
104 
ToStringMappings105     std::string ToString() const
106     {
107         if (IsInvalid()) {
108             return "";
109         }
110         return sourceName + ":" + std::to_string(sourceOffset.line + 1) + ":" + std::to_string(sourceOffset.column + 1);
111     }
112 
113 public:
114     Offset traceOffset;
115     Offset sourceOffset;
116     int nameIdx;
117     std::string sourceName;
118 };
119 
120 class SourceMap {
121 public:
SourceMap(v8::Isolate * isolate,v8::Local<v8::Context> context,v8::Local<v8::Object> payload)122     SourceMap(v8::Isolate* isolate, v8::Local<v8::Context> context, v8::Local<v8::Object> payload)
123         : isolate(isolate), context(context), payload(payload)
124     {
125         ParseMappingPayload();
126     }
127 
FindEntry(int lineOffset,int columnOffset)128     Mappings FindEntry(int lineOffset, int columnOffset)
129     {
130         int count = sourceMappings.size();
131         if (count == 0) {
132             return Mappings(Offset::InvalidOffset());
133         }
134 
135         int left = 0;
136         Offset offset(lineOffset, columnOffset);
137 
138         while (count > 1) {
139             int step = count >> 1;
140             int middle = left + step;
141             if (offset < sourceMappings[middle].traceOffset) {
142                 count = step;
143             } else {
144                 left = middle;
145                 count -= step;
146             }
147         }
148 
149         if (left == 0 && offset < sourceMappings[0].traceOffset) {
150             return Mappings(Offset::InvalidOffset());
151         }
152 
153         return sourceMappings[left];
154     }
155 
156 private:
157     void ParseMappingPayload();
158     void ParseMap(v8::Local<v8::Value> map, int line, int column);
159     void ParseMappings(const std::string& mappings,
160                        const std::vector<std::string>& sources,
161                        int lineNumber,
162                        int columnNumber);
163     void ParseSections(v8::Local<v8::Value> sections);
164     std::vector<std::string> ParseSourceNames(v8::Local<v8::Array> sources);
165 
166 private:
167     v8::Isolate* isolate;
168     v8::Local<v8::Context> context;
169     v8::Local<v8::Object> payload;
170     std::vector<Mappings> sourceMappings;
171 };
172 
ParseMappingPayload()173 void SourceMap::ParseMappingPayload()
174 {
175     auto sections = payload->Get(context, v8::String::NewFromUtf8Literal(isolate, "sections"));
176     if (!sections.IsEmpty() && sections.ToLocalChecked()->ToBoolean(isolate)->Value()) {
177         ParseSections(sections.ToLocalChecked());
178     } else {
179         ParseMap(payload, 0, 0);
180     }
181 
182     std::sort(sourceMappings.begin(), sourceMappings.end());
183 }
184 
ParseSourceNames(v8::Local<v8::Array> sources)185 std::vector<std::string> SourceMap::ParseSourceNames(v8::Local<v8::Array> sources)
186 {
187     std::vector<std::string> names;
188 
189     for (uint32_t i = 0; i < sources->Length(); ++i) {
190         auto element = sources->Get(context, i);
191         // should be string
192         v8::Local<v8::Value> fromMaybe;
193         if (!element.ToLocal(&fromMaybe) || !fromMaybe->IsString()) {
194             names.emplace_back("");
195             continue;
196         }
197         v8::String::Utf8Value source(isolate, fromMaybe);
198         names.emplace_back(*source);
199     }
200 
201     return names;
202 }
203 
ParseMap(v8::Local<v8::Value> map,int line,int column)204 void SourceMap::ParseMap(v8::Local<v8::Value> map, int line, int column)
205 {
206     if (!map->IsObject()) {
207         return;
208     }
209 
210     auto mapObj = map.As<v8::Object>();
211 
212     // Get sources
213     auto maybeSources = mapObj->Get(context, v8::String::NewFromUtf8Literal(isolate, "sources"));
214     v8::Local<v8::Value> fromMaybe;
215     if (!maybeSources.ToLocal(&fromMaybe) || !fromMaybe->IsArray()) {
216         return;
217     }
218     auto arr = fromMaybe.As<v8::Array>();
219     auto names = ParseSourceNames(arr);
220 
221     // Get mappings
222     v8::Local<v8::Value> mappingsValue =
223         mapObj->Get(context, v8::String::NewFromUtf8Literal(isolate, "mappings")).ToLocalChecked();
224     if (!mappingsValue->IsString()) {
225         return;
226     }
227     v8::String::Utf8Value mappingsStr(isolate, mappingsValue);
228 
229     // Parse mappings
230     ParseMappings(*mappingsStr, names, line, column);
231 }
232 
ParseMappings(const std::string & mappings,const std::vector<std::string> & sources,int lineNumber,int columnNumber)233 void SourceMap::ParseMappings(const std::string& mappings,
234                               const std::vector<std::string>& sources,
235                               int lineNumber,
236                               int columnNumber)
237 {
238     size_t pos = 0;
239     int sourceIndex = 0;
240     int sourceLineNumber = 0;
241     int sourceColumnNumber = 0;
242     int nameIndex = 0;
243 
244     while (pos < mappings.length()) {
245         if (mappings[pos] == ',') {
246             pos++;
247         } else {
248             while (pos < mappings.length() && mappings[pos] == ';') {
249                 lineNumber++;
250                 columnNumber = 0;
251                 pos++;
252             }
253 
254             if (pos == mappings.length()) {
255                 break;
256             }
257         }
258 
259         columnNumber += DecodeVLQ(mappings, pos);
260         if (pos >= mappings.length() || mappings[pos] == ',' || mappings[pos] == ';') {
261             sourceMappings.emplace_back(Offset(lineNumber, columnNumber));
262             continue;
263         }
264 
265         int sourceIndexDelta = DecodeVLQ(mappings, pos);
266         if (sourceIndexDelta) {
267             sourceIndex += sourceIndexDelta;
268         }
269         sourceLineNumber += DecodeVLQ(mappings, pos);
270         sourceColumnNumber += DecodeVLQ(mappings, pos);
271 
272         if (pos < mappings.length() && mappings[pos] != ',' && mappings[pos] != ';') {
273             nameIndex += DecodeVLQ(mappings, pos);
274         }
275         sourceMappings.emplace_back(Offset(lineNumber, columnNumber), Offset(sourceLineNumber, sourceColumnNumber),
276                                     sources[sourceIndex], nameIndex);
277     }
278 }
279 
ParseSections(v8::Local<v8::Value> sections)280 void SourceMap::ParseSections(v8::Local<v8::Value> sections)
281 {
282     if (!sections->IsArray()) {
283         return;
284     }
285 
286     v8::Local<v8::Array> arr = sections.As<v8::Array>();
287     for (uint32_t i = 0; i < arr->Length(); ++i) {
288         auto element = arr->Get(context, i);
289 
290         v8::Local<v8::Value> fromMaybe;
291         if (!element.ToLocal(&fromMaybe) || !fromMaybe->IsObject()) {
292             continue;
293         }
294         auto section = fromMaybe.As<v8::Object>();
295 
296         auto maybeMap = section->Get(context, v8::String::NewFromUtf8Literal(isolate, "map"));
297         if (maybeMap.IsEmpty()) {
298             continue;
299         }
300 
301         auto maybeOffset = section->Get(context, v8::String::NewFromUtf8Literal(isolate, "offset"));
302         if (!maybeOffset.ToLocal(&fromMaybe) || !fromMaybe->IsObject()) {
303             continue;
304         }
305         auto offset = fromMaybe.As<v8::Object>();
306 
307         auto maybeLine = offset->Get(context, v8::String::NewFromUtf8Literal(isolate, "line"));
308         if (!maybeLine.ToLocal(&fromMaybe)) {
309             continue;
310         }
311         auto maybeInt = fromMaybe->ToInt32(context);
312         int line = maybeInt.IsEmpty() ? -1 : maybeInt.ToLocalChecked()->Value();
313 
314         auto maybeColumn = offset->Get(context, v8::String::NewFromUtf8Literal(isolate, "column"));
315         if (!maybeColumn.ToLocal(&fromMaybe)) {
316             continue;
317         }
318         maybeInt = fromMaybe->ToInt32(context);
319         int column = maybeInt.IsEmpty() ? -1 : maybeInt.ToLocalChecked()->Value();
320         if (line == -1 || column == -1) {
321             continue;
322         }
323 
324         ParseMap(maybeMap.ToLocalChecked(), line, column);
325     }
326 }
327 
HandleError(v8::Isolate * isolate,v8::Local<v8::Context> ctx,v8::Local<v8::Value> error,v8::Local<v8::Array> trace)328 v8::MaybeLocal<v8::Value> HandleError(v8::Isolate* isolate,
329                                       v8::Local<v8::Context> ctx,
330                                       v8::Local<v8::Value> error,
331                                       v8::Local<v8::Array> trace)
332 {
333     v8::Local<v8::String> stackStr;
334     if (!error->ToString(ctx).ToLocal(&stackStr)) {
335         return v8::MaybeLocal<v8::Value>();
336     }
337 
338     auto left = v8::String::NewFromUtf8Literal(isolate, "\n    at ");
339 
340     for (uint32_t i = 0; i < trace->Length(); ++i) {
341         v8::Local<v8::Value> element = trace->Get(ctx, i).ToLocalChecked();
342         v8::Local<v8::String> str;
343 
344         if (!element->ToString(ctx).ToLocal(&str)) {
345             return v8::MaybeLocal<v8::Value>();
346         }
347 
348         auto traceStr = v8::String::Concat(isolate, left, str);
349 
350         stackStr = v8::String::Concat(isolate, stackStr, traceStr);
351     }
352 
353     return stackStr;
354 }
355 
356 static std::string SourceMapRunner = R"JS(
357 result =  (t, originalSouceInfo) => {
358     const str = '\n    at ';
359     try {
360         if (originalSouceInfo != "") {
361             let fileName = t.getFileName();
362             if (fileName === undefined) {
363                 fileName = t.getEvalOrigin()
364             }
365             const fnName = t.getFunctionName() ?? t.getMethodName();
366             const typeName = t.getTypeName();
367             const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : '';
368             const originalName = `${namePrefix}${fnName||'<anonymous>'}`;
369             const hasName = !!originalName;
370             return `${str}${originalName}${hasName?' (':''}` + originalSouceInfo + `${hasName?')':''}`
371         }
372         return `${str}${t}`
373     } catch (e) {
374         return `${str}${t}`
375     }
376 }
377 )JS";
378 
GetAndCallFunction(v8::Isolate * isolate,v8::Local<v8::Context> ctx,v8::Local<v8::Object> obj,v8::Local<v8::String> funcName)379 int GetAndCallFunction(v8::Isolate* isolate,
380                        v8::Local<v8::Context> ctx,
381                        v8::Local<v8::Object> obj,
382                        v8::Local<v8::String> funcName)
383 {
384     auto maybeFunc = obj->Get(ctx, funcName);
385 
386     v8::Local<v8::Value> value;
387     if (!maybeFunc.ToLocal(&value) || !value->IsFunction()) {
388         return -1;
389     }
390 
391     auto func = value.As<v8::Function>();
392 
393     // eval obj.funcName()
394     auto maybeRes = func->Call(ctx, obj, 0, nullptr);
395     if (maybeRes.IsEmpty()) {
396         return -1;
397     }
398 
399     auto maybeInt = maybeRes.ToLocalChecked()->ToInt32(ctx);
400     return maybeInt.IsEmpty() ? -1 : maybeInt.ToLocalChecked()->Value();
401 }
402 
ParseSourceMap(v8::Isolate * isolate,v8::Local<v8::Context> ctx,v8::Local<v8::Value> error,v8::Local<v8::Array> trace,v8::Local<v8::Function> toStringFunc,std::string & sourceMapContent)403 v8::MaybeLocal<v8::Value> ParseSourceMap(v8::Isolate* isolate,
404                                          v8::Local<v8::Context> ctx,
405                                          v8::Local<v8::Value> error,
406                                          v8::Local<v8::Array> trace,
407                                          v8::Local<v8::Function> toStringFunc,
408                                          std::string& sourceMapContent)
409 {
410     v8::TryCatch tryCatch(isolate);
411     auto sourceMapStr = v8::String::NewFromUtf8(isolate, sourceMapContent.c_str(), v8::NewStringType::kNormal,
412                                                 sourceMapContent.length())
413                             .ToLocalChecked();
414 
415     // Parse json string to object
416     v8::Local<v8::Value> sourceMapObj;
417     if (!v8::JSON::Parse(ctx, sourceMapStr).ToLocal(&sourceMapObj) || !sourceMapObj->IsObject()) {
418         return HandleError(isolate, ctx, error, trace);
419     }
420 
421     v8::Local<v8::String> stackStr;
422     if (!error->ToString(ctx).ToLocal(&stackStr)) {
423         return v8::MaybeLocal<v8::Value>();
424     }
425 
426     SourceMap sourceMap(isolate, ctx, sourceMapObj.As<v8::Object>());
427 
428     // Get line and column
429     auto getLineStr = v8::String::NewFromUtf8Literal(isolate, "getLineNumber");
430     auto getColumnStr = v8::String::NewFromUtf8Literal(isolate, "getColumnNumber");
431 
432     for (uint32_t i = 0; i < trace->Length(); ++i) {
433         v8::Local<v8::Value> element = trace->Get(ctx, i).ToLocalChecked();
434         if (!element->IsObject()) {
435             continue;
436         }
437 
438         auto t = element.As<v8::Object>();
439         auto line = GetAndCallFunction(isolate, ctx, t, getLineStr) - 1;
440         auto column = GetAndCallFunction(isolate, ctx, t, getColumnStr) - 1;
441 
442         auto str = sourceMap.FindEntry(line, column).ToString();
443         auto originalSouceInfo =
444             v8::String::NewFromUtf8(isolate, str.c_str(), v8::NewStringType::kNormal, str.length()).ToLocalChecked();
445 
446         v8::Local<v8::Value> args[] = { t, originalSouceInfo };
447         auto traceStr = toStringFunc->Call(ctx, v8::Undefined(isolate), jsvm::ArraySize(args), args)
448                             .ToLocalChecked()
449                             .As<v8::String>();
450         stackStr = v8::String::Concat(isolate, stackStr, traceStr);
451     }
452 
453     // Check execption
454     if (tryCatch.HasCaught()) {
455         return HandleError(isolate, ctx, error, trace);
456     }
457 
458     return stackStr;
459 }
460 
461 #endif