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