• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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