• 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 <sstream>
24 #include <unistd.h>
25 
26 #include "hilog_tag_wrapper.h"
27 
28 namespace OHOS {
29 namespace JsEnv {
30 namespace {
31 constexpr char DELIMITER_COMMA = ',';
32 constexpr char DELIMITER_SEMICOLON = ';';
33 constexpr char DOUBLE_SLASH = '\\';
34 constexpr char WEBPACK[] = "webpack:///";
35 constexpr int32_t INDEX_ONE = 1;
36 constexpr int32_t INDEX_TWO = 2;
37 constexpr int32_t INDEX_THREE = 3;
38 constexpr int32_t INDEX_FOUR = 4;
39 constexpr int32_t ANS_MAP_SIZE = 5;
40 constexpr int32_t DIGIT_NUM = 64;
41 const std::string MEGER_SOURCE_MAP_PATH = "ets/sourceMaps.map";
42 const std::string FLAG_SOURCES = "    \"sources\":";
43 const std::string FLAG_MAPPINGS = "    \"mappings\": \"";
44 const std::string FLAG_ENTRY_PACKAGE_INFO = "    \"entry-package-info\": \"";
45 const std::string FLAG_PACKAGE_INFO = "    \"package-info\": \"";
46 const std::string FLAG_END = "  }";
47 static constexpr size_t FLAG_MAPPINGS_LEN = 17;
48 static constexpr size_t FLAG_ENTRY_PACKAGE_INFO_SIZE = 27;
49 static constexpr size_t FLAG_PACKAGE_INFO_SIZE = 21;
50 static constexpr size_t REAL_URL_INDEX = 3;
51 static constexpr size_t REAL_SOURCE_INDEX = 7;
52 } // namespace
53 ReadSourceMapCallback SourceMap::readSourceMapFunc_ = nullptr;
54 GetHapPathCallback SourceMap::getHapPathFunc_ = nullptr;
55 std::mutex SourceMap::sourceMapMutex_;
56 
StringToInt(const std::string & value)57 int32_t StringToInt(const std::string& value)
58 {
59     errno = 0;
60     char* pEnd = nullptr;
61     int64_t result = std::strtol(value.c_str(), &pEnd, 10);
62     if (pEnd == value.c_str() || (result < INT_MIN || result > INT_MAX) || errno == ERANGE) {
63         return 0;
64     } else {
65         return result;
66     }
67 }
68 
Base64CharToInt(char charCode)69 uint32_t Base64CharToInt(char charCode)
70 {
71     if ('A' <= charCode && charCode <= 'Z') {
72         // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
73         return charCode - 'A';
74     } else if ('a' <= charCode && charCode <= 'z') {
75         // 26 - 51: abcdefghijklmnopqrstuvwxyz
76         return charCode - 'a' + 26;
77     } else if ('0' <= charCode && charCode <= '9') {
78         // 52 - 61: 0123456789
79         return charCode - '0' + 52;
80     } else if (charCode == '+') {
81         // 62: +
82         return 62;
83     } else if (charCode == '/') {
84         // 63: /
85         return 63;
86     }
87     return DIGIT_NUM;
88 };
89 
StringStartWith(const std::string & str,const std::string & startStr)90 bool StringStartWith(const std::string& str, const std::string& startStr)
91 {
92     size_t startStrLen = startStr.length();
93     return ((str.length() >= startStrLen) && (str.compare(0, startStrLen, startStr) == 0));
94 }
95 
Init(bool & hasFile,const std::string & hapPath)96 void SourceMap::Init(bool& hasFile, const std::string& hapPath)
97 {
98     std::string sourceMapData;
99     if (ReadSourceMapData(hapPath, MEGER_SOURCE_MAP_PATH, sourceMapData)) {
100         hasFile = true;
101     }
102     SplitSourceMap(sourceMapData);
103 }
104 
TranslateBySourceMap(const std::string & stackStr)105 std::string SourceMap::TranslateBySourceMap(const std::string& stackStr)
106 {
107     std::string closeBrace = ")";
108     std::string openBrace = "(";
109     std::string ans = "";
110 
111     // find per line of stack
112     std::vector<std::string> res;
113     ExtractStackInfo(stackStr, res);
114 
115     // collect error info first
116     for (uint32_t i = 0; i < res.size(); i++) {
117         std::string temp = res[i];
118         size_t start = temp.find(openBrace);
119         size_t end = temp.find(":");
120         if (end <= start) {
121             continue;
122         }
123         std::string key = temp.substr(start + 1, end - start - 1);
124         auto closeBracePos = static_cast<int32_t>(temp.find(closeBrace));
125         auto openBracePos = static_cast<int32_t>(temp.find(openBrace));
126         std::string line;
127         std::string column;
128         GetPosInfo(temp, closeBracePos, line, column);
129         if (line.empty() || column.empty()) {
130             TAG_LOGW(AAFwkTag::JSENV, "the stack without line info");
131             break;
132         }
133         std::string sourceInfo;
134         auto iter = sourceMaps_.find(key);
135         if (iter != sourceMaps_.end()) {
136             sourceInfo = GetSourceInfo(line, column, *(iter->second), key);
137         } else {
138             ans = ans + temp + "\n";
139             continue;
140         }
141         if (sourceInfo.empty()) {
142             continue;
143         }
144         temp.replace(openBracePos, closeBracePos - openBracePos + 1, sourceInfo);
145         replace(temp.begin(), temp.end(), '\\', '/');
146         ans = ans + temp + "\n";
147     }
148     if (ans.empty()) {
149         return (NOT_FOUNDMAP + stackStr);
150     }
151     return ans;
152 }
153 
SplitSourceMap(const std::string & sourceMapData)154 void SourceMap::SplitSourceMap(const std::string& sourceMapData)
155 {
156     std::lock_guard<std::mutex> lock(sourceMapMutex_);
157     std::stringstream ss(sourceMapData);
158     std::string tmp;
159     std::string url;
160 
161     std::getline(ss, tmp);
162     bool isUrl = true;
163     std::shared_ptr<SourceMapData> mapData;
164     while (std::getline(ss, tmp)) {
165         if (isUrl && tmp.size() > REAL_SOURCE_INDEX) { // url
166             url = tmp.substr(REAL_URL_INDEX, tmp.size() - REAL_SOURCE_INDEX);
167             isUrl = false;
168             mapData = std::make_shared<SourceMapData>();
169             continue;
170         }
171         if (StringStartWith(tmp.c_str(), FLAG_SOURCES)) { // sources
172             std::getline(ss, tmp);
173             mapData->sources_ = tmp;
174             continue;
175         }
176         if (StringStartWith(tmp.c_str(), FLAG_MAPPINGS)) { // mapping
177             ExtractSourceMapData(tmp.substr(FLAG_MAPPINGS_LEN, tmp.size() - FLAG_MAPPINGS_LEN - 1), mapData);
178             continue;
179         }
180         if (StringStartWith(tmp.c_str(), FLAG_ENTRY_PACKAGE_INFO)) { // entryPackageInfo
181             mapData->packageName_ = tmp;
182             continue;
183         }
184         if (StringStartWith(tmp.c_str(), FLAG_PACKAGE_INFO)) { // packageInfo
185             mapData->packageName_ = tmp;
186             mapData->isPackageInfo_ = true;
187             continue;
188         }
189         if (StringStartWith(tmp.c_str(), FLAG_END)) {
190             sourceMaps_[url] = mapData;
191             isUrl = true;
192         }
193     }
194 }
195 
ExtractStackInfo(const std::string & stackStr,std::vector<std::string> & res)196 void SourceMap::ExtractStackInfo(const std::string& stackStr, std::vector<std::string>& res)
197 {
198     std::stringstream ss(stackStr);
199     std::string tempStr;
200     while (std::getline(ss, tempStr)) {
201         res.push_back(tempStr);
202     }
203 }
204 
ExtractSourceMapData(const std::string & allmappings,std::shared_ptr<SourceMapData> & curMapData)205 void SourceMap::ExtractSourceMapData(const std::string& allmappings, std::shared_ptr<SourceMapData>& curMapData)
206 {
207     curMapData->mappings_ = HandleMappings(allmappings);
208     // the first bit: the column after transferring.
209     // the second bit: the source file.
210     // the third bit: the row before transferring.
211     // the fourth bit: the column before transferring.
212     // the fifth bit: the variable name.
213     for (const auto& mapping : curMapData->mappings_) {
214         if (mapping == ";") {
215             // plus a line for each semicolon
216             curMapData->nowPos_.afterRow++,
217             curMapData->nowPos_.afterColumn = 0;
218             continue;
219         }
220         std::vector<int32_t> ans;
221 
222         if (!VlqRevCode(mapping, ans)) {
223             return;
224         }
225         if (ans.empty()) {
226             TAG_LOGE(AAFwkTag::JSENV, "decode sourcemap fail, mapping: %{public}s", mapping.c_str());
227             break;
228         }
229         if (ans.size() == 1) {
230             curMapData->nowPos_.afterColumn += ans[0];
231             continue;
232         }
233         // after decode, assgin each value to the position
234         curMapData->nowPos_.afterColumn += ans[0];
235         curMapData->nowPos_.beforeRow += ans[INDEX_TWO];
236         curMapData->nowPos_.beforeColumn += ans[INDEX_THREE];
237         curMapData->afterPos_.push_back({
238             curMapData->nowPos_.beforeRow,
239             curMapData->nowPos_.beforeColumn,
240             curMapData->nowPos_.afterRow,
241             curMapData->nowPos_.afterColumn,
242         });
243     }
244     curMapData->mappings_.clear();
245     curMapData->mappings_.shrink_to_fit();
246 }
247 
Find(int32_t row,int32_t col,const SourceMapData & targetMap,const std::string & key)248 MappingInfo SourceMap::Find(int32_t row, int32_t col, const SourceMapData& targetMap, const std::string& key)
249 {
250     if (row < 1 || col < 1 || targetMap.afterPos_.empty() || targetMap.sources_.empty()) {
251         return MappingInfo {row, col, key};
252     }
253     row--;
254     col--;
255     // binary search
256     int32_t left = 0;
257     int32_t right = static_cast<int32_t>(targetMap.afterPos_.size()) - 1;
258     int32_t res = 0;
259     if (row > targetMap.afterPos_[targetMap.afterPos_.size() - 1].afterRow) {
260         return MappingInfo { row + 1, col + 1, key };
261     }
262     while (right - left >= 0) {
263         int32_t mid = (right + left) / 2;
264         if ((targetMap.afterPos_[mid].afterRow == row && targetMap.afterPos_[mid].afterColumn > col) ||
265              targetMap.afterPos_[mid].afterRow > row) {
266             right = mid - 1;
267         } else {
268             res = mid;
269             left = mid + 1;
270         }
271     }
272     std::string sources = targetMap.sources_.substr(REAL_SOURCE_INDEX,
273                                                     targetMap.sources_.size() - REAL_SOURCE_INDEX - 1);
274     auto pos = sources.find(WEBPACK);
275     if (pos != std::string::npos) {
276         sources.replace(pos, sizeof(WEBPACK) - 1, "");
277     }
278 
279     return MappingInfo {
280         .row = targetMap.afterPos_[res].beforeRow + 1,
281         .col = targetMap.afterPos_[res].beforeColumn + 1,
282         .sources = sources,
283     };
284 }
285 
ExtractKeyInfo(const std::string & sourceMap,std::vector<std::string> & sourceKeyInfo)286 void SourceMap::ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)
287 {
288     uint32_t cnt = 0;
289     std::string tempStr;
290     for (uint32_t i = 0; i < sourceMap.size(); i++) {
291         // reslove json file
292         if (sourceMap[i] == DOUBLE_SLASH) {
293             i++;
294             tempStr += sourceMap[i];
295             continue;
296         }
297         // cnt is used to represent a pair of double quotation marks: ""
298         if (sourceMap[i] == '"') {
299             cnt++;
300         }
301         if (cnt == INDEX_TWO) {
302             sourceKeyInfo.push_back(tempStr);
303             tempStr = "";
304             cnt = 0;
305         } else if (cnt == 1) {
306             if (sourceMap[i] != '"') {
307                 tempStr += sourceMap[i];
308             }
309         }
310     }
311 }
312 
GetPosInfo(const std::string & temp,int32_t start,std::string & line,std::string & column)313 void SourceMap::GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)
314 {
315     // 0 for colum, 1 for row
316     int32_t flag = 0;
317     // find line, column
318     for (int32_t i = start - 1; i > 0; i--) {
319         if (temp[i] == ':') {
320             flag += 1;
321             continue;
322         }
323         if (flag == 0) {
324             column = temp[i] + column;
325         } else if (flag == 1) {
326             line = temp[i] + line;
327         } else {
328             break;
329         }
330     }
331 }
332 
HandleMappings(const std::string & mapping)333 std::vector<std::string> SourceMap::HandleMappings(const std::string& mapping)
334 {
335     std::vector<std::string> keyInfo;
336     std::string tempStr;
337     for (uint32_t i = 0; i < mapping.size(); i++) {
338         if (mapping[i] == DELIMITER_COMMA) {
339             keyInfo.push_back(tempStr);
340             tempStr = "";
341         } else if (mapping[i] == DELIMITER_SEMICOLON) {
342             if (tempStr != "") {
343                 keyInfo.push_back(tempStr);
344             }
345             tempStr = "";
346             keyInfo.push_back(";");
347         } else {
348             tempStr += mapping[i];
349         }
350     }
351     if (tempStr != "") {
352         keyInfo.push_back(tempStr);
353     }
354     return keyInfo;
355 };
356 
VlqRevCode(const std::string & vStr,std::vector<int32_t> & ans)357 bool SourceMap::VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)
358 {
359     const int32_t VLQ_BASE_SHIFT = 5;
360     // binary: 100000
361     uint32_t VLQ_BASE = 1 << VLQ_BASE_SHIFT;
362     // binary: 011111
363     uint32_t VLQ_BASE_MASK = VLQ_BASE - 1;
364     // binary: 100000
365     uint32_t VLQ_CONTINUATION_BIT = VLQ_BASE;
366     uint32_t result = 0;
367     uint32_t shift = 0;
368     bool continuation = 0;
369     for (uint32_t i = 0; i < vStr.size(); i++) {
370         uint32_t digit = Base64CharToInt(vStr[i]);
371         if (digit == DIGIT_NUM) {
372             return false;
373         }
374         continuation = digit & VLQ_CONTINUATION_BIT;
375         digit &= VLQ_BASE_MASK;
376         result += digit << shift;
377         if (continuation) {
378             shift += VLQ_BASE_SHIFT;
379         } else {
380             bool isNegate = result & 1;
381             result >>= 1;
382             ans.push_back(isNegate ? -result : result);
383             result = 0;
384             shift = 0;
385         }
386     }
387     if (continuation) {
388         return false;
389     }
390     return true;
391 };
392 
GetSourceInfo(const std::string & line,const std::string & column,const SourceMapData & targetMap,const std::string & key)393 std::string SourceMap::GetSourceInfo(const std::string& line, const std::string& column,
394     const SourceMapData& targetMap, const std::string& key)
395 {
396     int32_t offSet = 0;
397     std::string sourceInfo;
398     MappingInfo mapInfo;
399 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
400         mapInfo = Find(StringToInt(line) - offSet + OFFSET_PREVIEW, StringToInt(column), targetMap, key);
401 #else
402         mapInfo = Find(StringToInt(line) - offSet, StringToInt(column), targetMap, key);
403 #endif
404     std::string sources = mapInfo.sources;
405     std::string packageName = targetMap.packageName_;
406     if (!packageName.empty()) {
407         auto last = packageName.rfind('|');
408         if (last != std::string::npos) {
409             auto packageNameSize = targetMap.isPackageInfo_ ? FLAG_PACKAGE_INFO_SIZE : FLAG_ENTRY_PACKAGE_INFO_SIZE;
410             sourceInfo = packageName.substr(packageNameSize, last - packageNameSize);
411             return sourceInfo.append(" (" + sources + ":" + std::to_string(mapInfo.row) + ":" +
412                 std::to_string(mapInfo.col) + ")");
413         }
414     }
415     sourceInfo = "(" + sources + ":" + std::to_string(mapInfo.row) + ":" + std::to_string(mapInfo.col) + ")";
416     return sourceInfo;
417 }
418 
GetErrorPos(const std::string & rawStack)419 ErrorPos SourceMap::GetErrorPos(const std::string& rawStack)
420 {
421     size_t findLineEnd = rawStack.find("\n");
422     if (findLineEnd == std::string::npos) {
423         return std::make_pair(0, 0);
424     }
425     int32_t lineEnd = (int32_t)findLineEnd - 1;
426     if (lineEnd < 1 || rawStack[lineEnd - 1] == '?') {
427         return std::make_pair(0, 0);
428     }
429 
430     uint32_t secondPos = rawStack.rfind(':', lineEnd);
431     uint32_t fristPos = rawStack.rfind(':', secondPos - 1);
432 
433     std::string lineStr = rawStack.substr(fristPos + 1, secondPos - 1 - fristPos);
434     std::string columnStr = rawStack.substr(secondPos + 1, lineEnd - 1 - secondPos);
435 
436     return std::make_pair(StringToInt(lineStr), StringToInt(columnStr));
437 }
438 
RegisterReadSourceMapCallback(ReadSourceMapCallback readFunc)439 void SourceMap::RegisterReadSourceMapCallback(ReadSourceMapCallback readFunc)
440 {
441     std::lock_guard<std::mutex> lock(sourceMapMutex_);
442     readSourceMapFunc_ = readFunc;
443 }
444 
ReadSourceMapData(const std::string & hapPath,const std::string & sourceMapPath,std::string & content)445 bool SourceMap::ReadSourceMapData(const std::string& hapPath, const std::string& sourceMapPath, std::string& content)
446 {
447     std::lock_guard<std::mutex> lock(sourceMapMutex_);
448     if (readSourceMapFunc_) {
449         return readSourceMapFunc_(hapPath, sourceMapPath, content);
450     }
451     return false;
452 }
453 
TranslateUrlPositionBySourceMap(std::string & url,int & line,int & column,std::string & packageName)454 bool SourceMap::TranslateUrlPositionBySourceMap(std::string& url, int& line, int& column, std::string& packageName)
455 {
456     auto iter = sourceMaps_.find(url);
457     if (iter != sourceMaps_.end()) {
458         return GetLineAndColumnNumbers(line, column, *(iter->second), url, packageName);
459     }
460     TAG_LOGE(AAFwkTag::JSENV, "stageMode sourceMaps find fail");
461     return false;
462 }
463 
GetLineAndColumnNumbers(int & line,int & column,SourceMapData & targetMap,std::string & url,std::string & packageName)464 bool SourceMap::GetLineAndColumnNumbers(int& line, int& column, SourceMapData& targetMap,
465     std::string& url, std::string& packageName)
466 {
467     int32_t offSet = 0;
468     MappingInfo mapInfo;
469 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
470         mapInfo = Find(line - offSet + OFFSET_PREVIEW, column, targetMap, url);
471 #else
472         mapInfo = Find(line - offSet, column, targetMap, url);
473 #endif
474     if (mapInfo.row == 0 || mapInfo.col == 0) {
475         return false;
476     } else {
477         line = mapInfo.row;
478         column = mapInfo.col;
479         url = mapInfo.sources;
480         GetPackageName(targetMap, packageName);
481         return true;
482     }
483 }
484 
RegisterGetHapPathCallback(GetHapPathCallback getFunc)485 void SourceMap::RegisterGetHapPathCallback(GetHapPathCallback getFunc)
486 {
487     std::lock_guard<std::mutex> lock(sourceMapMutex_);
488     getHapPathFunc_ = getFunc;
489 }
490 
GetHapPath(const std::string & bundleName,std::vector<std::string> & hapList)491 void SourceMap::GetHapPath(const std::string &bundleName, std::vector<std::string> &hapList)
492 {
493     std::lock_guard<std::mutex> lock(sourceMapMutex_);
494     if (getHapPathFunc_) {
495         getHapPathFunc_(bundleName, hapList);
496     }
497 }
498 
GetPackageName(const SourceMapData & targetMap,std::string & packageName)499 void SourceMap::GetPackageName(const SourceMapData& targetMap, std::string& packageName)
500 {
501     std::string packageInfo = targetMap.packageName_;
502     if (!packageInfo.empty()) {
503         auto last = packageInfo.rfind('|');
504         if (last != std::string::npos) {
505             auto packageNameSize = targetMap.isPackageInfo_ ? FLAG_PACKAGE_INFO_SIZE : FLAG_ENTRY_PACKAGE_INFO_SIZE;
506             packageName = packageInfo.substr(packageNameSize, last - packageNameSize);
507         }
508     }
509 }
510 }   // namespace JsEnv
511 }   // namespace OHOS
512