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