1 /*
2 * Copyright (c) 2023 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 "source_map.h"
17
18 #include <cerrno>
19 #include <climits>
20 #include <cstdlib>
21 #include <fstream>
22 #include <vector>
23 #include <unistd.h>
24
25 #include "js_env_logger.h"
26
27 namespace OHOS {
28 namespace JsEnv {
29 namespace {
30 constexpr char SOURCES[] = "sources";
31 constexpr char NAMES[] = "names";
32 constexpr char MAPPINGS[] = "mappings";
33 constexpr char FILE[] = "file";
34 constexpr char SOURCE_CONTENT[] = "sourceContent";
35 constexpr char SOURCE_ROOT[] = "sourceRoot";
36 constexpr char DELIMITER_COMMA = ',';
37 constexpr char DELIMITER_SEMICOLON = ';';
38 constexpr char DOUBLE_SLASH = '\\';
39 constexpr char WEBPACK[] = "webpack:///";
40 constexpr int32_t INDEX_ONE = 1;
41 constexpr int32_t INDEX_TWO = 2;
42 constexpr int32_t INDEX_THREE = 3;
43 constexpr int32_t INDEX_FOUR = 4;
44 constexpr int32_t ANS_MAP_SIZE = 5;
45 constexpr int32_t NUM_TWENTY = 20;
46 constexpr int32_t NUM_TWENTYSIX = 26;
47 constexpr int32_t DIGIT_NUM = 64;
48 const std::string NOT_FOUNDMAP = "Cannot get SourceMap info, dump raw stack:\n";
49 const std::string MEGER_SOURCE_MAP_PATH = "ets/sourceMaps.map";
50 } // namespace
51 ReadSourceMapCallback SourceMap::readSourceMapFunc_ = nullptr;
52
StringToInt(const std::string & value)53 int32_t StringToInt(const std::string& value)
54 {
55 errno = 0;
56 char* pEnd = nullptr;
57 int64_t result = std::strtol(value.c_str(), &pEnd, 10);
58 if (pEnd == value.c_str() || (result < INT_MIN || result > INT_MAX) || errno == ERANGE) {
59 return 0;
60 } else {
61 return result;
62 }
63 }
64
Base64CharToInt(char charCode)65 uint32_t Base64CharToInt(char charCode)
66 {
67 if ('A' <= charCode && charCode <= 'Z') {
68 // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
69 return charCode - 'A';
70 } else if ('a' <= charCode && charCode <= 'z') {
71 // 26 - 51: abcdefghijklmnopqrstuvwxyz
72 return charCode - 'a' + 26;
73 } else if ('0' <= charCode && charCode <= '9') {
74 // 52 - 61: 0123456789
75 return charCode - '0' + 52;
76 } else if (charCode == '+') {
77 // 62: +
78 return 62;
79 } else if (charCode == '/') {
80 // 63: /
81 return 63;
82 }
83 return DIGIT_NUM;
84 };
85
Init(bool isModular,const std::string & hapPath)86 void SourceMap::Init(bool isModular, const std::string& hapPath)
87 {
88 isModular_ = isModular;
89 hapPath_ = hapPath;
90 if (isModular_) {
91 std::string sourceMapData;
92 ReadSourceMapData(hapPath_, MEGER_SOURCE_MAP_PATH, sourceMapData);
93 SplitSourceMap(sourceMapData);
94 } else {
95 if (!nonModularMap_) {
96 nonModularMap_ = std::make_shared<SourceMapData>();
97 }
98 }
99 }
100
TranslateBySourceMap(const std::string & stackStr)101 std::string SourceMap::TranslateBySourceMap(const std::string& stackStr)
102 {
103 std::string closeBrace = ")";
104 std::string openBrace = "(";
105 std::string ans = "";
106
107 // find per line of stack
108 std::vector<std::string> res;
109 ExtractStackInfo(stackStr, res);
110
111 // collect error info first
112 uint32_t i = 0;
113 std::string codeStart = "SourceCode (";
114 std::string sourceCode = "";
115 if (!res.empty()) {
116 std::string fristLine = res[0];
117 uint32_t codeStartLen = codeStart.length();
118 if (fristLine.substr(0, codeStartLen).compare(codeStart) == 0) {
119 sourceCode = fristLine.substr(codeStartLen, fristLine.length() - codeStartLen - 1);
120 i = 1; // 1 means Convert from the second line
121 }
122 }
123
124 // collect error info first
125 for (; i < res.size(); i++) {
126 std::string temp = res[i];
127 size_t start;
128 size_t end;
129 if (isModular_) {
130 start = temp.find(openBrace);
131 end = temp.find(":");
132 } else {
133 start = temp.find("/ets/");
134 end = temp.rfind("_.js");
135 }
136 if (end <= start) {
137 continue;
138 }
139 std::string key = temp.substr(start + 1, end - start - 1);
140 auto closeBracePos = static_cast<int32_t>(temp.find(closeBrace));
141 auto openBracePos = static_cast<int32_t>(temp.find(openBrace));
142 std::string line;
143 std::string column;
144 GetPosInfo(temp, closeBracePos, line, column);
145 if (line.empty() || column.empty()) {
146 JSENV_LOG_W("the stack without line info");
147 break;
148 }
149 std::string sourceInfo;
150 if (isModular_) {
151 auto iter = sourceMaps_.find(key);
152 if (iter != sourceMaps_.end()) {
153 sourceInfo = GetSourceInfo(line, column, *(iter->second), key);
154 }
155 } else {
156 std::string url = key + ".js.map";
157 std::string curSourceMap;
158 if (!ReadSourceMapData(hapPath_, url, curSourceMap)) {
159 JSENV_LOG_W("ReadSourceMapData fail");
160 continue;
161 }
162 ExtractSourceMapData(curSourceMap, nonModularMap_);
163 sourceInfo = GetSourceInfo(line, column, *nonModularMap_, key + ".ts");
164 }
165 if (sourceInfo.empty()) {
166 break;
167 }
168 temp.replace(openBracePos, closeBracePos - openBracePos + 1, sourceInfo);
169 replace(temp.begin(), temp.end(), '\\', '/');
170 ans = ans + temp + "\n";
171 }
172 if (ans.empty()) {
173 return (NOT_FOUNDMAP + stackStr + "\n");
174 }
175 return ans;
176 }
177
SplitSourceMap(const std::string & sourceMapData)178 void SourceMap::SplitSourceMap(const std::string& sourceMapData)
179 {
180 if (!isModular_) {
181 if (!nonModularMap_) {
182 nonModularMap_ = std::make_shared<SourceMapData>();
183 }
184 return ExtractSourceMapData(sourceMapData, nonModularMap_);
185 }
186
187 size_t leftBracket = 0;
188 size_t rightBracket = 0;
189 std::string value;
190 while ((leftBracket = sourceMapData.find(": {", rightBracket)) != std::string::npos) {
191 rightBracket = sourceMapData.find("},", leftBracket);
192 value = sourceMapData.substr(leftBracket, rightBracket);
193 std::size_t sources = value.find("\"sources\": [");
194 if (sources == std::string::npos) {
195 continue;
196 }
197 std::size_t names = value.find("],", sources);
198 if (names == std::string::npos) {
199 continue;
200 }
201 // Intercept the sourcemap file path as the key
202 std::string key = value.substr(sources + NUM_TWENTY, names - sources - NUM_TWENTYSIX);
203 std::shared_ptr<SourceMapData> modularMap = std::make_shared<SourceMapData>();
204 ExtractSourceMapData(value, modularMap);
205 sourceMaps_.emplace(key, modularMap);
206 }
207 }
208
ExtractStackInfo(const std::string & stackStr,std::vector<std::string> & res)209 void SourceMap::ExtractStackInfo(const std::string& stackStr, std::vector<std::string>& res)
210 {
211 std::string tempStr;
212 for (uint32_t i = 0; i < stackStr.length(); i++) {
213 if (stackStr[i] == '\n') {
214 res.push_back(tempStr);
215 tempStr = "";
216 } else {
217 tempStr += stackStr[i];
218 }
219 }
220 if (!tempStr.empty()) {
221 res.push_back(tempStr);
222 }
223 }
224
ExtractSourceMapData(const std::string & sourceMapData,std::shared_ptr<SourceMapData> & curMapData)225 void SourceMap::ExtractSourceMapData(const std::string& sourceMapData, std::shared_ptr<SourceMapData>& curMapData)
226 {
227 std::vector<std::string> sourceKey;
228 ExtractKeyInfo(sourceMapData, sourceKey);
229
230 std::string mark = "";
231 for (auto sourceKeyInfo : sourceKey) {
232 if (sourceKeyInfo == SOURCES || sourceKeyInfo == NAMES ||
233 sourceKeyInfo == MAPPINGS || sourceKeyInfo == FILE ||
234 sourceKeyInfo == SOURCE_CONTENT || sourceKeyInfo == SOURCE_ROOT) {
235 mark = sourceKeyInfo;
236 } else if (mark == SOURCES) {
237 curMapData->sources_.push_back(sourceKeyInfo);
238 } else if (mark == NAMES) {
239 curMapData->names_.push_back(sourceKeyInfo);
240 } else if (mark == MAPPINGS) {
241 curMapData->mappings_.push_back(sourceKeyInfo);
242 } else if (mark == FILE) {
243 curMapData->files_.push_back(sourceKeyInfo);
244 } else {
245 continue;
246 }
247 }
248
249 if (curMapData->mappings_.empty()) {
250 return;
251 }
252
253 // transform to vector for mapping easily
254 curMapData->mappings_ = HandleMappings(curMapData->mappings_[0]);
255
256 // the first bit: the column after transferring.
257 // the second bit: the source file.
258 // the third bit: the row before transferring.
259 // the fourth bit: the column before transferring.
260 // the fifth bit: the variable name.
261 for (const auto& mapping : curMapData->mappings_) {
262 if (mapping == ";") {
263 // plus a line for each semicolon
264 curMapData->nowPos_.afterRow++,
265 curMapData->nowPos_.afterColumn = 0;
266 continue;
267 }
268 std::vector<int32_t> ans;
269
270 if (!VlqRevCode(mapping, ans)) {
271 JSENV_LOG_E("decode code fail");
272 return;
273 }
274 if (ans.empty()) {
275 JSENV_LOG_E("decode sourcemap fail, mapping: %{public}s", mapping.c_str());
276 break;
277 }
278 if (ans.size() == 1) {
279 curMapData->nowPos_.afterColumn += ans[0];
280 continue;
281 }
282 // after decode, assgin each value to the position
283 curMapData->nowPos_.afterColumn += ans[0];
284 curMapData->nowPos_.sourcesVal += ans[INDEX_ONE];
285 curMapData->nowPos_.beforeRow += ans[INDEX_TWO];
286 curMapData->nowPos_.beforeColumn += ans[INDEX_THREE];
287 if (ans.size() == ANS_MAP_SIZE) {
288 curMapData->nowPos_.namesVal += ans[INDEX_FOUR];
289 }
290 curMapData->afterPos_.push_back({
291 curMapData->nowPos_.beforeRow,
292 curMapData->nowPos_.beforeColumn,
293 curMapData->nowPos_.afterRow,
294 curMapData->nowPos_.afterColumn,
295 curMapData->nowPos_.sourcesVal,
296 curMapData->nowPos_.namesVal
297 });
298 }
299 curMapData->mappings_.clear();
300 curMapData->mappings_.shrink_to_fit();
301 sourceKey.clear();
302 sourceKey.shrink_to_fit();
303 }
304
Find(int32_t row,int32_t col,const SourceMapData & targetMap,const std::string & key)305 MappingInfo SourceMap::Find(int32_t row, int32_t col, const SourceMapData& targetMap, const std::string& key)
306 {
307 if (row < 1 || col < 1 || targetMap.afterPos_.empty()) {
308 return MappingInfo {0, 0, ""};
309 }
310 row--;
311 col--;
312 // binary search
313 int32_t left = 0;
314 int32_t right = static_cast<int32_t>(targetMap.afterPos_.size()) - 1;
315 int32_t res = 0;
316 if (row > targetMap.afterPos_[targetMap.afterPos_.size() - 1].afterRow) {
317 return MappingInfo { row + 1, col + 1, targetMap.files_[0] };
318 }
319 while (right - left >= 0) {
320 int32_t mid = (right + left) / 2;
321 if ((targetMap.afterPos_[mid].afterRow == row && targetMap.afterPos_[mid].afterColumn > col) ||
322 targetMap.afterPos_[mid].afterRow > row) {
323 right = mid - 1;
324 } else {
325 res = mid;
326 left = mid + 1;
327 }
328 }
329 std::string sources = key;
330 auto pos = sources.find(WEBPACK);
331 if (pos != std::string::npos) {
332 sources.replace(pos, sizeof(WEBPACK) - 1, "");
333 }
334
335 return MappingInfo {
336 .row = targetMap.afterPos_[res].beforeRow + 1,
337 .col = targetMap.afterPos_[res].beforeColumn + 1,
338 .sources = sources,
339 };
340 }
341
ExtractKeyInfo(const std::string & sourceMap,std::vector<std::string> & sourceKeyInfo)342 void SourceMap::ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)
343 {
344 uint32_t cnt = 0;
345 std::string tempStr;
346 for (uint32_t i = 0; i < sourceMap.size(); i++) {
347 // reslove json file
348 if (sourceMap[i] == DOUBLE_SLASH) {
349 i++;
350 tempStr += sourceMap[i];
351 continue;
352 }
353 // cnt is used to represent a pair of double quotation marks: ""
354 if (sourceMap[i] == '"') {
355 cnt++;
356 }
357 if (cnt == INDEX_TWO) {
358 sourceKeyInfo.push_back(tempStr);
359 tempStr = "";
360 cnt = 0;
361 } else if (cnt == 1) {
362 if (sourceMap[i] != '"') {
363 tempStr += sourceMap[i];
364 }
365 }
366 }
367 }
368
GetPosInfo(const std::string & temp,int32_t start,std::string & line,std::string & column)369 void SourceMap::GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)
370 {
371 // 0 for colum, 1 for row
372 int32_t flag = 0;
373 // find line, column
374 for (int32_t i = start - 1; i > 0; i--) {
375 if (temp[i] == ':') {
376 flag += 1;
377 continue;
378 }
379 if (flag == 0) {
380 column = temp[i] + column;
381 } else if (flag == 1) {
382 line = temp[i] + line;
383 } else {
384 break;
385 }
386 }
387 }
388
GetRelativePath(const std::string & sources)389 std::string SourceMap::GetRelativePath(const std::string& sources)
390 {
391 std::string temp = sources;
392 std::size_t splitPos = std::string::npos;
393 const static int pathLevel = 3;
394 int i = 0;
395 while (i < pathLevel) {
396 splitPos = temp.find_last_of("/\\");
397 if (splitPos != std::string::npos) {
398 temp = temp.substr(0, splitPos - 1);
399 } else {
400 break;
401 }
402 i++;
403 }
404 if (i == pathLevel) {
405 return sources.substr(splitPos);
406 }
407 return sources;
408 }
409
HandleMappings(const std::string & mapping)410 std::vector<std::string> SourceMap::HandleMappings(const std::string& mapping)
411 {
412 std::vector<std::string> keyInfo;
413 std::string tempStr;
414 for (uint32_t i = 0; i < mapping.size(); i++) {
415 if (mapping[i] == DELIMITER_COMMA) {
416 keyInfo.push_back(tempStr);
417 tempStr = "";
418 } else if (mapping[i] == DELIMITER_SEMICOLON) {
419 if (tempStr != "") {
420 keyInfo.push_back(tempStr);
421 }
422 tempStr = "";
423 keyInfo.push_back(";");
424 } else {
425 tempStr += mapping[i];
426 }
427 }
428 if (tempStr != "") {
429 keyInfo.push_back(tempStr);
430 }
431 return keyInfo;
432 };
433
VlqRevCode(const std::string & vStr,std::vector<int32_t> & ans)434 bool SourceMap::VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)
435 {
436 const int32_t VLQ_BASE_SHIFT = 5;
437 // binary: 100000
438 uint32_t VLQ_BASE = 1 << VLQ_BASE_SHIFT;
439 // binary: 011111
440 uint32_t VLQ_BASE_MASK = VLQ_BASE - 1;
441 // binary: 100000
442 uint32_t VLQ_CONTINUATION_BIT = VLQ_BASE;
443 uint32_t result = 0;
444 uint32_t shift = 0;
445 bool continuation = 0;
446 for (uint32_t i = 0; i < vStr.size(); i++) {
447 uint32_t digit = Base64CharToInt(vStr[i]);
448 if (digit == DIGIT_NUM) {
449 return false;
450 }
451 continuation = digit & VLQ_CONTINUATION_BIT;
452 digit &= VLQ_BASE_MASK;
453 result += digit << shift;
454 if (continuation) {
455 shift += VLQ_BASE_SHIFT;
456 } else {
457 bool isNegate = result & 1;
458 result >>= 1;
459 ans.push_back(isNegate ? -result : result);
460 result = 0;
461 shift = 0;
462 }
463 }
464 if (continuation) {
465 return false;
466 }
467 return true;
468 };
469
GetSourceInfo(const std::string & line,const std::string & column,const SourceMapData & targetMap,const std::string & key)470 std::string SourceMap::GetSourceInfo(const std::string& line, const std::string& column,
471 const SourceMapData& targetMap, const std::string& key)
472 {
473 int32_t offSet = 0;
474 std::string sourceInfo;
475 MappingInfo mapInfo;
476 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
477 mapInfo = Find(StringToInt(line) - offSet + OFFSET_PREVIEW, StringToInt(column), targetMap, key);
478 #else
479 mapInfo = Find(StringToInt(line) - offSet, StringToInt(column), targetMap, key);
480 #endif
481 if (mapInfo.row == 0 || mapInfo.col == 0) {
482 return "";
483 }
484 std::string sources = isModular_ ? mapInfo.sources : GetRelativePath(mapInfo.sources);
485 sourceInfo = "(" + sources + ":" + std::to_string(mapInfo.row) + ":" + std::to_string(mapInfo.col) + ")";
486 return sourceInfo;
487 }
488
GetErrorPos(const std::string & rawStack)489 ErrorPos SourceMap::GetErrorPos(const std::string& rawStack)
490 {
491 size_t findLineEnd = rawStack.find("\n");
492 if (findLineEnd == std::string::npos) {
493 return std::make_pair(0, 0);
494 }
495 int32_t lineEnd = findLineEnd - 1;
496 if (lineEnd < 1 || rawStack[lineEnd - 1] == '?') {
497 return std::make_pair(0, 0);
498 }
499
500 uint32_t secondPos = rawStack.rfind(':', lineEnd);
501 uint32_t fristPos = rawStack.rfind(':', secondPos - 1);
502
503 std::string lineStr = rawStack.substr(fristPos + 1, secondPos - 1 - fristPos);
504 std::string columnStr = rawStack.substr(secondPos + 1, lineEnd - 1 - secondPos);
505
506 return std::make_pair(StringToInt(lineStr), StringToInt(columnStr));
507 }
508
GetOriginalNames(std::shared_ptr<SourceMapData> targetMapData,const std::string & sourceCode,uint32_t & errorPos)509 std::string SourceMap::GetOriginalNames(std::shared_ptr<SourceMapData> targetMapData,
510 const std::string& sourceCode, uint32_t& errorPos)
511 {
512 if (sourceCode.empty() || sourceCode.find("SourceCode:\n") == std::string::npos) {
513 return sourceCode;
514 }
515 std::vector<std::string> names = targetMapData->names_;
516 if (names.size() % INDEX_TWO != 0) {
517 JSENV_LOG_E("Names in sourcemap is wrong.");
518 return sourceCode;
519 }
520
521 std::string jsCode = sourceCode;
522 int32_t posDiff = 0;
523 for (uint32_t i = 0; i < names.size(); i += INDEX_TWO) {
524 auto found = jsCode.find(names[i]);
525 while (found != std::string::npos) {
526 // names_[i + 1] is the original name of names_[i]
527 jsCode.replace(found, names[i].length(), names[i + 1]);
528 if (static_cast<uint32_t>(found) < errorPos) {
529 // sum the errorPos differences to adjust position of ^
530 posDiff += static_cast<int32_t>(names[i + 1].length()) - static_cast<int32_t>(names[i].length());
531 }
532 // In case there are other variable names not replaced.
533 found = jsCode.find(names[i], found + names[i + 1].length());
534 }
535 }
536 auto lineBreakPos = jsCode.rfind('\n', jsCode.length() - 2);
537 if (lineBreakPos == std::string::npos) {
538 JSENV_LOG_W("There is something wrong in source code of summaryBody.");
539 return jsCode;
540 }
541 // adjust position of ^ in dump file
542 if (posDiff < 0) {
543 int32_t flagPos = static_cast<int32_t>(lineBreakPos) + static_cast<int32_t>(errorPos);
544 if (lineBreakPos > 0 && errorPos > 0 && flagPos < 0) {
545 JSENV_LOG_W("Add overflow of sourceCode.");
546 return jsCode;
547 }
548 if (flagPos < static_cast<int32_t>(jsCode.length()) && jsCode[flagPos] == '^' && flagPos + posDiff - 1 > 0) {
549 jsCode.erase(flagPos + posDiff - 1, -posDiff);
550 }
551 } else if (posDiff > 0) {
552 if (lineBreakPos + 1 < jsCode.length() - 1) {
553 jsCode.insert(lineBreakPos + 1, posDiff, ' ');
554 }
555 }
556 return jsCode;
557 }
558
RegisterReadSourceMapCallback(ReadSourceMapCallback readFunc)559 void SourceMap::RegisterReadSourceMapCallback(ReadSourceMapCallback readFunc)
560 {
561 readSourceMapFunc_ = readFunc;
562 }
563
ReadSourceMapData(const std::string & hapPath,const std::string & sourceMapPath,std::string & content)564 bool SourceMap::ReadSourceMapData(const std::string& hapPath, const std::string& sourceMapPath, std::string& content)
565 {
566 if (readSourceMapFunc_) {
567 return readSourceMapFunc_(hapPath, sourceMapPath, content);
568 }
569 return false;
570 }
571 } // namespace JsEnv
572 } // namespace OHOS
573