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