1 /*
2 * Copyright (c) 2022 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 <unistd.h>
24
25 #include "hilog_wrapper.h"
26 namespace OHOS {
27 namespace AbilityRuntime {
28
29 constexpr char SOURCES[] = "sources";
30 constexpr char NAMES[] = "names";
31 constexpr char MAPPINGS[] = "mappings";
32 constexpr char FILE[] = "file";
33 constexpr char SOURCE_CONTENT[] = "sourceContent";
34 constexpr char SOURCE_ROOT[] = "sourceRoot";
35 constexpr char DELIMITER_COMMA = ',';
36 constexpr char DELIMITER_SEMICOLON = ';';
37 constexpr char DOUBLE_SLASH = '\\';
38 constexpr char WEBPACK[] = "webpack:///";
39 const std::string REALPATH_FLAG = "/temprary/";
40 const std::string ABILITYPATH_FLAG = "/entry/ets/";
41 const std::string NOT_FOUNDMAP = "Cannot get SourceMap info, dump raw stack:\n";
42 constexpr int64_t ASSET_FILE_MAX_SIZE = 20 * (1 << 20);
43
ReadSourceMapData(const std::string & filePath,std::string & content)44 bool ModSourceMap::ReadSourceMapData(const std::string& filePath, std::string& content)
45 {
46 char path[PATH_MAX] ;
47 if (realpath(filePath.c_str(), path) == nullptr) {
48 HILOG_ERROR("ModSourceMap::ReadSourceMapData realpath(%{public}s) failed, errno = %{public}d",
49 filePath.c_str(), errno);
50 return false;
51 }
52
53 std::ifstream stream(path, std::ios::binary | std::ios::ate);
54 if (!stream.is_open()) {
55 HILOG_ERROR("ModSourceMap::ReadSourceMapData failed to open file %{public}s", path);
56 return false;
57 }
58
59 int64_t fileLen = stream.tellg();
60 if (fileLen > ASSET_FILE_MAX_SIZE) {
61 return false;
62 }
63
64 char buffer[fileLen];
65 buffer[fileLen - 1] = '\0';
66 stream.seekg(0);
67 stream.read(buffer, fileLen);
68 content = buffer;
69
70 return true;
71 }
72
Find(int32_t row,int32_t col,const SourceMapData & targetMap,const std::string & key)73 MappingInfo ModSourceMap::Find(int32_t row, int32_t col, const SourceMapData& targetMap, const std::string& key)
74 {
75 if (row < 1 || col < 1) {
76 HILOG_ERROR("the input pos is wrong");
77 return MappingInfo {};
78 }
79 row--;
80 col--;
81 // binary search
82 int32_t left = 0;
83 int32_t right = static_cast<int32_t>(targetMap.afterPos_.size()) - 1;
84 int32_t res = 0;
85 if (row > targetMap.afterPos_[targetMap.afterPos_.size() - 1].afterRow) {
86 return MappingInfo { row + 1, col + 1, targetMap.files_[0] };
87 }
88 while (right - left >= 0) {
89 int32_t mid = (right + left) / 2;
90 if ((targetMap.afterPos_[mid].afterRow == row && targetMap.afterPos_[mid].afterColumn > col) ||
91 targetMap.afterPos_[mid].afterRow > row) {
92 right = mid - 1;
93 } else {
94 res = mid;
95 left = mid + 1;
96 }
97 }
98 std::string sources = key;
99 auto pos = sources.find(WEBPACK);
100 if (pos != std::string::npos) {
101 sources.replace(pos, sizeof(WEBPACK) - 1, "");
102 }
103
104 return MappingInfo {
105 .row = targetMap.afterPos_[res].beforeRow + 1,
106 .col = targetMap.afterPos_[res].beforeColumn + 1,
107 .sources = sources,
108 };
109 }
110
ExtractKeyInfo(const std::string & sourceMap,std::vector<std::string> & sourceKeyInfo)111 void ModSourceMap::ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)
112 {
113
114 uint32_t cnt = 0;
115 std::string tempStr;
116 for (uint32_t i = 0; i < sourceMap.size(); i++) {
117 // reslove json file
118 if (sourceMap[i] == DOUBLE_SLASH) {
119 i++;
120 tempStr += sourceMap[i];
121 continue;
122 }
123 // cnt is used to represent a pair of double quotation marks: ""
124 if (sourceMap[i] == '"') {
125 cnt++;
126 }
127 if (cnt == 2) {
128 sourceKeyInfo.push_back(tempStr);
129 tempStr = "";
130 cnt = 0;
131 } else if (cnt == 1) {
132 if (sourceMap[i] != '"') {
133 tempStr += sourceMap[i];
134 }
135 }
136 }
137 }
138
GetPosInfo(const std::string & temp,int32_t start,std::string & line,std::string & column)139 void ModSourceMap::GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)
140 {
141 // 0 for colum, 1 for row
142 int32_t flag = 0;
143 // find line, column
144 for (int32_t i = start - 1; i > 0; i--) {
145 if (temp[i] == ':') {
146 flag += 1;
147 continue;
148 }
149 if (flag == 0) {
150 column = temp[i] + column;
151 } else if (flag == 1) {
152 line = temp[i] + line;
153 } else {
154 break;
155 }
156 }
157 }
158
StringToInt(const std::string & value)159 int32_t ModSourceMap::StringToInt(const std::string& value)
160 {
161 errno = 0;
162 char* pEnd = nullptr;
163 int64_t result = std::strtol(value.c_str(), &pEnd, 10);
164 if (pEnd == value.c_str() || (result < INT_MIN || result > INT_MAX) || errno == ERANGE) {
165 return 0;
166 } else {
167 return result;
168 }
169 }
170
GetRelativePath(const std::string & sources)171 std::string ModSourceMap::GetRelativePath(const std::string& sources)
172 {
173 std::string temp = sources;
174 std::size_t splitPos = std::string::npos;
175 const static int pathLevel = 3;
176 int i = 0;
177 while (i < pathLevel) {
178 splitPos = temp.find_last_of("/\\");
179 if (splitPos != std::string::npos) {
180 temp = temp.substr(0, splitPos - 1);
181 } else {
182 break;
183 }
184 i++;
185 }
186 if (i == pathLevel) {
187 return sources.substr(splitPos);
188 }
189 return sources;
190 }
191
Init(const std::string & sourceMap,SourceMapData & curMapData)192 void ModSourceMap::Init(const std::string& sourceMap, SourceMapData& curMapData)
193 {
194 std::vector<std::string> sourceKeyInfo;
195 std::string mark = "";
196
197 ExtractKeyInfo(sourceMap, sourceKeyInfo);
198
199 // first: find the key info and record the temp key info
200 // second: add the detail into the keyinfo
201 for (auto keyInfo : sourceKeyInfo) {
202 if (keyInfo == SOURCES || keyInfo == NAMES || keyInfo == MAPPINGS || keyInfo == FILE ||
203 keyInfo == SOURCE_CONTENT || keyInfo == SOURCE_ROOT) {
204 // record the temp key info
205 mark = keyInfo;
206 } else if (mark == SOURCES) {
207 curMapData.sources_.push_back(keyInfo);
208 } else if (mark == NAMES) {
209 curMapData.names_.push_back(keyInfo);
210 } else if (mark == MAPPINGS) {
211 curMapData.mappings_.push_back(keyInfo);
212 } else if (mark == FILE) {
213 curMapData.files_.push_back(keyInfo);
214 } else {
215 continue;
216 }
217 }
218
219 // transform to vector for mapping easily
220 curMapData.mappings_ = HandleMappings(curMapData.mappings_[0]);
221
222 // the first bit: the column after transferring.
223 // the second bit: the source file.
224 // the third bit: the row before transferring.
225 // the fourth bit: the column before transferring.
226 // the fifth bit: the variable name.
227 for (const auto& mapping : curMapData.mappings_) {
228 if (mapping == ";") {
229 // plus a line for each semicolon
230 curMapData.nowPos_.afterRow++,
231 curMapData.nowPos_.afterColumn = 0;
232 continue;
233 }
234 // decode each mapping ";QAABC"
235 std::vector<int32_t> ans;
236 if (!VlqRevCode(mapping, ans)) {
237 HILOG_ERROR("decode code fail");
238 return;
239 }
240 if (ans.size() == 0) {
241 HILOG_ERROR("decode sourcemap fail, mapping: %{public}s", mapping.c_str());
242 break;
243 }
244 if (ans.size() == 1) {
245 curMapData.nowPos_.afterColumn += ans[0];
246 continue;
247 }
248 // after decode, assgin each value to the position
249 curMapData.nowPos_.afterColumn += ans[0];
250 curMapData.nowPos_.sourcesVal += ans[1];
251 curMapData.nowPos_.beforeRow += ans[2];
252 curMapData.nowPos_.beforeColumn += ans[3];
253 if (ans.size() == 5) {
254 curMapData.nowPos_.namesVal += ans[4];
255 }
256 curMapData.afterPos_.push_back({
257 curMapData.nowPos_.beforeRow,
258 curMapData.nowPos_.beforeColumn,
259 curMapData.nowPos_.afterRow,
260 curMapData.nowPos_.afterColumn,
261 curMapData.nowPos_.sourcesVal,
262 curMapData.nowPos_.namesVal
263 });
264 }
265 curMapData.mappings_.clear();
266 curMapData.mappings_.shrink_to_fit();
267 sourceKeyInfo.clear();
268 sourceKeyInfo.shrink_to_fit();
269 };
270
HandleMappings(const std::string & mapping)271 std::vector<std::string> ModSourceMap::HandleMappings(const std::string& mapping)
272 {
273 std::vector<std::string> keyInfo;
274 std::string tempStr;
275 for (uint32_t i = 0; i < mapping.size(); i++) {
276 if (mapping[i] == DELIMITER_COMMA) {
277 keyInfo.push_back(tempStr);
278 tempStr = "";
279 } else if (mapping[i] == DELIMITER_SEMICOLON) {
280 if (tempStr != "") {
281 keyInfo.push_back(tempStr);
282 }
283 tempStr = "";
284 keyInfo.push_back(";");
285 } else {
286 tempStr += mapping[i];
287 }
288 }
289 if (tempStr != "") {
290 keyInfo.push_back(tempStr);
291 }
292 return keyInfo;
293 };
294
Base64CharToInt(char charCode)295 uint32_t ModSourceMap::Base64CharToInt(char charCode)
296 {
297 if ('A' <= charCode && charCode <= 'Z') {
298 // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
299 return charCode - 'A';
300 } else if ('a' <= charCode && charCode <= 'z') {
301 // 26 - 51: abcdefghijklmnopqrstuvwxyz
302 return charCode - 'a' + 26;
303 } else if ('0' <= charCode && charCode <= '9') {
304 // 52 - 61: 0123456789
305 return charCode - '0' + 52;
306 } else if (charCode == '+') {
307 // 62: +
308 return 62;
309 } else if (charCode == '/') {
310 // 63: /
311 return 63;
312 }
313 return 64;
314 };
315
VlqRevCode(const std::string & vStr,std::vector<int32_t> & ans)316 bool ModSourceMap::VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)
317 {
318 const int32_t VLQ_BASE_SHIFT = 5;
319 // binary: 100000
320 uint32_t VLQ_BASE = 1 << VLQ_BASE_SHIFT;
321 // binary: 011111
322 uint32_t VLQ_BASE_MASK = VLQ_BASE - 1;
323 // binary: 100000
324 uint32_t VLQ_CONTINUATION_BIT = VLQ_BASE;
325 uint32_t result = 0;
326 uint32_t shift = 0;
327 bool continuation = 0;
328 for (uint32_t i = 0; i < vStr.size(); i++) {
329 uint32_t digit = Base64CharToInt(vStr[i]);
330 if (digit == 64) {
331 return false;
332 }
333 continuation = digit & VLQ_CONTINUATION_BIT;
334 digit &= VLQ_BASE_MASK;
335 result += digit << shift;
336 if (continuation) {
337 shift += VLQ_BASE_SHIFT;
338 } else {
339 bool isNegate = result & 1;
340 result >>= 1;
341 ans.push_back(isNegate ? -result : result);
342 result = 0;
343 shift = 0;
344 }
345 }
346 if (continuation) {
347 return false;
348 }
349 return true;
350 };
351
GetSourceMapData(ModSourceMap & bindSourceMaps,const std::string & temp,SourceMapData & curMapData)352 bool ModSourceMap::GetSourceMapData(ModSourceMap& bindSourceMaps, const std::string& temp, SourceMapData& curMapData)
353 {
354 // get the file path of the .map file
355 int32_t startPos = static_cast<int32_t>(temp.find(REALPATH_FLAG));
356 if (startPos == -1) {
357 HILOG_ERROR("ModSourceMap::TranslateBySourceMap Get /temprary/ pos error.");
358 return false;
359 }
360 int32_t endPos = static_cast<int32_t>(temp.size() - 1);
361 std::string mapFilePath = bindSourceMaps.bundleCodeDir_ + ABILITYPATH_FLAG +
362 temp.substr(startPos + REALPATH_FLAG.size(), endPos - startPos) + ".map";
363
364 // parse file and cache
365 auto iter = bindSourceMaps.sourceMaps_.find(mapFilePath);
366 if (iter == bindSourceMaps.sourceMaps_.end()) {
367 std::string curSourceMap;
368 if (!ReadSourceMapData(mapFilePath, curSourceMap)) {
369 return false;
370 }
371
372 Init(curSourceMap, curMapData);
373 bindSourceMaps.sourceMaps_.insert(std::pair<std::string, SourceMapData>(mapFilePath, curMapData));
374 } else {
375 curMapData = iter->second;
376 }
377 return true;
378 }
379
TranslateBySourceMap(const std::string & stackStr,ModSourceMap & bindSourceMaps,const std::string & BundleCodeDir)380 std::string ModSourceMap::TranslateBySourceMap(const std::string& stackStr, ModSourceMap& bindSourceMaps, const std::string& BundleCodeDir)
381 {
382 const std::string closeBrace = ")";
383 const std::string openBrace = "(";
384 std::string ans = "";
385 std::string tempStack = stackStr;
386
387 // find per line of stack
388 std::vector<std::string> res;
389 std::string tempStr = "";
390 for (uint32_t i = 0; i < tempStack.length(); i++) {
391 if (tempStack[i] == '\n') {
392 res.push_back(tempStr);
393 tempStr = "";
394 } else {
395 tempStr += tempStack[i];
396 }
397 }
398 if (!tempStr.empty()) {
399 res.push_back(tempStr);
400 }
401
402 // collect error info first
403 bool needGetErrorPos = false;
404 int32_t errorPos = 0;
405 uint32_t i = 0;
406 std::string codeStart = "SourceCode (";
407 std::string sourceCode = "";
408 if (res.size() >= 1) {
409 std::string fristLine = res[0];
410 uint32_t codeStartLen = codeStart.length();
411 if (fristLine.substr(0, codeStartLen).compare(codeStart) == 0) {
412 sourceCode = fristLine.substr(codeStartLen, fristLine.length() - codeStartLen - 1);
413 i = 1; // 1 means Convert from the second line
414 needGetErrorPos = true;
415 }
416 }
417 std::string curSourceMap;
418 std::string filePath = BundleCodeDir + ABILITYPATH_FLAG + "sourceMaps.map";
419 if (!ReadSourceMapData(filePath, curSourceMap)) {
420 HILOG_ERROR("ReadSourceMapData fail");
421 return stackStr;
422 }
423 std::size_t s = 0;
424 std::size_t j = 0;
425 std::string value;
426 std::string key;
427 std::map<std::string, std::string> MapData;
428 while((s = curSourceMap.find(": {", j)) != std::string::npos)
429 {
430 j = curSourceMap.find("}," , s);
431 uint32_t q = s;
432 uint32_t jj = j;
433 value = curSourceMap.substr(q + 1, jj - q+2);
434 uint32_t sources = value.find("\"sources\": [");
435 uint32_t names = value.find("],");
436 key = value.substr(sources + 20 , names - sources - 26);
437 MapData.insert(std::pair<std::string, std::string>(key, value));
438 }
439
440 for (; i < res.size(); i++) {
441 std::string temp = res[i];
442 uint32_t start = temp.find("(");
443 uint32_t end = temp.find(":");
444 std::string key = temp.substr(start + 1 , end - start - 1);
445 int32_t closeBracePos = static_cast<int32_t>(temp.find(closeBrace));
446 int32_t openBracePos = static_cast<int32_t>(temp.find(openBrace));
447
448 std::string line = "";
449 std::string column = "";
450 GetPosInfo(temp, closeBracePos, line, column);
451 if (needGetErrorPos) {
452 errorPos = StringToInt(column);
453 needGetErrorPos = false;
454 }
455 if (line.empty() || column.empty()) {
456 break;
457 }
458 static SourceMapData curMapData;
459 if(!bindSourceMaps.isStageModel) {
460 if (i == 1) { // The non module scenario initializes curmapdata only at the first traversal
461 if (!bindSourceMaps.nonModularMap_) {
462 return NOT_FOUNDMAP + stackStr;
463 }
464 curMapData = *bindSourceMaps.nonModularMap_;
465 }
466 } else {
467 auto iter = MapData.find(key);
468 if (iter != MapData.end()) {
469 Init(iter->second, curMapData);
470 } else {
471 ans += NOT_FOUNDMAP + temp + "\n";
472 continue;
473 }
474 }
475 const std::string sourceInfo = GetSourceInfo(line, column, curMapData, key);
476 if (sourceInfo.empty()) {
477 break;
478 }
479 temp.replace(openBracePos, closeBracePos - openBracePos + 1, sourceInfo);
480 ans += temp + "\n";
481 }
482
483 if (ans.empty()) {
484 return tempStack;
485 }
486 return ans;
487 }
488
GetSourceInfo(const std::string & line,const std::string & column,const SourceMapData & targetMap,const std::string & key)489 std::string ModSourceMap::GetSourceInfo(const std::string& line, const std::string& column, const SourceMapData& targetMap, const std::string& key)
490 {
491 int32_t offSet = 0;
492 std::string sourceInfo;
493 MappingInfo mapInfo;
494 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
495 mapInfo = Find(StringToInt(line) - offSet + OFFSET_PREVIEW, StringToInt(column), targetMap, key);
496 #else
497 mapInfo = Find(StringToInt(line) - offSet, StringToInt(column), targetMap, key);
498 #endif
499 if (mapInfo.row == 0 || mapInfo.col == 0) {
500 return "";
501 }
502 std::string sources = GetRelativePath(mapInfo.sources);
503 sourceInfo = "(" + sources + ":" + std::to_string(mapInfo.row) + ":" + std::to_string(mapInfo.col) + ")";
504 return sourceInfo;
505 }
506
NonModularLoadSourceMap(ModSourceMap & targetMaps,const std::string & targetMap)507 void ModSourceMap::NonModularLoadSourceMap(ModSourceMap& targetMaps, const std::string& targetMap) {
508 if (!targetMaps.nonModularMap_) {
509 targetMaps.nonModularMap_ = std::make_shared<SourceMapData>();
510 }
511 Init(targetMap, *targetMaps.nonModularMap_);
512 }
513
GetOriginalNames(std::shared_ptr<SourceMapData> targetMapData,const std::string & sourceCode,uint32_t & errorPos)514 std::string ModSourceMap::GetOriginalNames(std::shared_ptr<SourceMapData> targetMapData, const std::string& sourceCode, uint32_t& errorPos)
515 {
516 if (sourceCode.empty() || sourceCode.find("SourceCode:\n") == std::string::npos) {
517 return sourceCode;
518 }
519 std::vector<std::string> names = targetMapData->names_;
520 if (names.size() % 2 != 0) {
521 HILOG_ERROR("Names in sourcemap is wrong.");
522 return sourceCode;
523 }
524
525 std::string jsCode = sourceCode;
526 int32_t posDiff = 0;
527 for (uint32_t i = 0; i < names.size(); i += 2) {
528 auto found = jsCode.find(names[i]);
529 while (found != std::string::npos) {
530 // names_[i + 1] is the original name of names_[i]
531 jsCode.replace(found, names[i].length(), names[i + 1]);
532 if (static_cast<uint32_t>(found) < errorPos) {
533 // sum the errorPos differences to adjust position of ^
534 posDiff += static_cast<int32_t>(names[i + 1].length()) - static_cast<int32_t>(names[i].length());
535 }
536 // In case there are other variable names not replaced.
537 // example:var e = process.a.b + _ohos_process_1.a.b;
538 found = jsCode.find(names[i], found + names[i + 1].length());
539 }
540 }
541 auto lineBreakPos = jsCode.rfind('\n', jsCode.length() - 2);
542 if (lineBreakPos == std::string::npos) {
543 HILOG_WARN("There is something wrong in source code of summaryBody.");
544 return jsCode;
545 }
546 // adjust position of ^ in dump file
547 if (posDiff < 0) {
548 int32_t flagPos = static_cast<int32_t>(lineBreakPos) + static_cast<int32_t>(errorPos);
549 if (lineBreakPos > 0 && errorPos > 0 && flagPos < 0) {
550 HILOG_WARN("Add overflow of sourceCode.");
551 return jsCode;
552 }
553 if (flagPos < static_cast<int32_t>(jsCode.length()) && jsCode[flagPos] == '^' && flagPos + posDiff - 1 > 0) {
554 jsCode.erase(flagPos + posDiff - 1, -posDiff);
555 }
556 } else if (posDiff > 0) {
557 if (lineBreakPos + 1 < jsCode.length() - 1) {
558 jsCode.insert(lineBreakPos + 1, posDiff, ' ');
559 }
560 }
561 return jsCode;
562 }
563
GetErrorPos(const std::string & rawStack)564 ErrorPos ModSourceMap::GetErrorPos(const std::string& rawStack)
565 {
566 size_t findLineEnd = rawStack.find("\n");
567 if (findLineEnd == std::string::npos) {
568 return std::make_pair(0, 0);
569 }
570 int32_t lineEnd = findLineEnd - 1;
571 if (lineEnd < 1 || rawStack[lineEnd - 1] == '?') {
572 return std::make_pair(0, 0);
573 }
574
575 uint32_t secondPos = rawStack.rfind(':', lineEnd);
576 uint32_t fristPos = rawStack.rfind(':', secondPos - 1);
577
578 std::string lineStr = rawStack.substr(fristPos + 1, secondPos - 1 - fristPos);
579 std::string columnStr = rawStack.substr(secondPos + 1, lineEnd - 1 - secondPos);
580
581 return std::make_pair(StringToInt(lineStr), StringToInt(columnStr));
582 }
583 } // namespace AbilityRuntime
584 } // namespace OHOS
585