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