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