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