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 <unistd.h>
24 
25 #include "ecmascript/base/string_helper.h"
26 #include "ecmascript/extractortool/src/extractor.h"
27 
28 namespace panda {
29 namespace ecmascript {
30 namespace {
31 static constexpr char DELIMITER_COMMA = ',';
32 static constexpr char DELIMITER_SEMICOLON = ';';
33 static constexpr char DOUBLE_SLASH = '\\';
34 
35 static constexpr int32_t INDEX_ONE = 1;
36 static constexpr int32_t INDEX_TWO = 2;
37 static constexpr int32_t INDEX_THREE = 3;
38 static constexpr int32_t INDEX_FOUR = 4;
39 static constexpr int32_t ANS_MAP_SIZE = 5;
40 static constexpr int32_t DIGIT_NUM = 64;
41 
42 const std::string MEGER_SOURCE_MAP_PATH = "ets/sourceMaps.map";
43 static const CString FLAG_SOURCES = "    \"sources\":";
44 static const CString FLAG_MAPPINGS = "    \"mappings\": \"";
45 static const CString FLAG_END = "  }";
46 
47 static constexpr size_t FLAG_MAPPINGS_LEN = 17;
48 static constexpr size_t REAL_SOURCE_SIZE = 7;
49 static constexpr size_t REAL_URL_INDEX = 3;
50 static constexpr size_t REAL_SOURCE_INDEX = 7;
51 } // namespace
52 
53 #if defined(PANDA_TARGET_OHOS)
ReadSourceMapData(const std::string& hapPath, std::string& content)54 bool SourceMap::ReadSourceMapData(const std::string& hapPath, std::string& content)
55 {
56     if (hapPath.empty()) {
57         return false;
58     }
59     bool newCreate = false;
60     std::shared_ptr<Extractor> extractor = ExtractorUtil::GetExtractor(
61         ExtractorUtil::GetLoadFilePath(hapPath), newCreate);
62     if (extractor == nullptr) {
63         return false;
64     }
65     std::unique_ptr<uint8_t[]> dataPtr = nullptr;
66     size_t len = 0;
67     if (!extractor->ExtractToBufByName(MEGER_SOURCE_MAP_PATH, dataPtr, len)) {
68         return false;
69     }
70     content.assign(dataPtr.get(), dataPtr.get() + len);
71     return true;
72 }
73 #endif
74 
Base64CharToInt(char charCode)75 uint32_t SourceMap::Base64CharToInt(char charCode)
76 {
77     if ('A' <= charCode && charCode <= 'Z') {
78         // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
79         return charCode - 'A';
80     } else if ('a' <= charCode && charCode <= 'z') {
81         // 26 - 51: abcdefghijklmnopqrstuvwxyz
82         return charCode - 'a' + 26;
83     } else if ('0' <= charCode && charCode <= '9') {
84         // 52 - 61: 0123456789
85         return charCode - '0' + 52;
86     } else if (charCode == '+') {
87         // 62: +
88         return 62;
89     } else if (charCode == '/') {
90         // 63: /
91         return 63;
92     }
93     return DIGIT_NUM;
94 };
95 
96 #if defined(PANDA_TARGET_OHOS)
Init(const std::string& hapPath)97 void SourceMap::Init(const std::string& hapPath)
98 {
99     auto start = Clock::now();
100     std::string sourceMapData;
101     if (ReadSourceMapData(hapPath, sourceMapData)) {
102         SplitSourceMap(sourceMapData);
103     }
104     auto end = Clock::now();
105     auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
106     LOG_ECMA(DEBUG) << "Init sourcemap time: " << duration.count() << "ms";
107 }
108 #endif
109 
Init(uint8_t *data, size_t dataSize)110 void SourceMap::Init(uint8_t *data, size_t dataSize)
111 {
112     std::string content;
113     content.assign(data, data + dataSize);
114     SplitSourceMap(content);
115 }
116 
SplitSourceMap(const std::string& sourceMapData)117 void SourceMap::SplitSourceMap(const std::string& sourceMapData)
118 {
119     std::stringstream ss(sourceMapData);
120     std::string tmp;
121     std::string url;
122 
123     std::getline(ss, tmp);
124     bool isUrl = true;
125     while (std::getline(ss, tmp)) {
126         if (isUrl && tmp.size() > REAL_SOURCE_SIZE) {
127             url = tmp.substr(REAL_URL_INDEX, tmp.size() - REAL_SOURCE_SIZE);
128             isUrl = false;
129             continue;
130         }
131 
132         if (base::StringHelper::StringStartWith(tmp.c_str(), FLAG_SOURCES)) {
133             std::getline(ss, tmp);
134             sources_.emplace(url, tmp);
135             continue;
136         }
137 
138         if (base::StringHelper::StringStartWith(tmp.c_str(), FLAG_MAPPINGS)) {
139             mappings_.emplace(url, tmp);
140             continue;
141         }
142 
143         if (base::StringHelper::StringStartWith(tmp.c_str(), FLAG_END)) {
144             isUrl = true;
145         }
146     }
147 }
148 
ExtractSourceMapData(const std::string& allmappings, std::shared_ptr<SourceMapData>& curMapData)149 void SourceMap::ExtractSourceMapData(const std::string& allmappings, std::shared_ptr<SourceMapData>& curMapData)
150 {
151     curMapData->mappings_ = HandleMappings(allmappings);
152 
153     // the first bit: the column after transferring.
154     // the second bit: the source file.
155     // the third bit: the row before transferring.
156     // the fourth bit: the column before transferring.
157     // the fifth bit: the variable name.
158     for (const auto& mapping : curMapData->mappings_) {
159         if (mapping == ";") {
160             // plus a line for each semicolon
161             curMapData->nowPos_.afterRow++,
162             curMapData->nowPos_.afterColumn = 0;
163             continue;
164         }
165         std::vector<int32_t> ans;
166 
167         if (!VlqRevCode(mapping, ans)) {
168             return;
169         }
170         if (ans.empty()) {
171             break;
172         }
173         if (ans.size() == 1) {
174             curMapData->nowPos_.afterColumn += ans[0];
175             continue;
176         }
177         // after decode, assgin each value to the position
178         curMapData->nowPos_.afterColumn += ans[0];
179         curMapData->nowPos_.sourcesVal += ans[INDEX_ONE];
180         curMapData->nowPos_.beforeRow += ans[INDEX_TWO];
181         curMapData->nowPos_.beforeColumn += ans[INDEX_THREE];
182         if (ans.size() == ANS_MAP_SIZE) {
183             curMapData->nowPos_.namesVal += ans[INDEX_FOUR];
184         }
185         curMapData->afterPos_.push_back({
186             curMapData->nowPos_.beforeRow,
187             curMapData->nowPos_.beforeColumn,
188             curMapData->nowPos_.afterRow,
189             curMapData->nowPos_.afterColumn,
190             curMapData->nowPos_.sourcesVal,
191             curMapData->nowPos_.namesVal
192         });
193     }
194     curMapData->mappings_.clear();
195     curMapData->mappings_.shrink_to_fit();
196 }
197 
Find(int32_t row, int32_t col, const SourceMapData& targetMap, bool& isReplaces)198 MappingInfo SourceMap::Find(int32_t row, int32_t col, const SourceMapData& targetMap, bool& isReplaces)
199 {
200     if (row < 1 || col < 1) {
201         LOG_ECMA(ERROR) << "SourceMap find failed, line: " << row << ", column: " << col;
202         return MappingInfo { 0, 0 };
203     } else if (targetMap.afterPos_.empty()) {
204         LOG_ECMA(ERROR) << "Target map can't find after pos.";
205         return MappingInfo { 0, 0 };
206     }
207     row--;
208     col--;
209     // binary search
210     int32_t left = 0;
211     int32_t right = static_cast<int32_t>(targetMap.afterPos_.size()) - 1;
212     int32_t res = 0;
213     if (row > targetMap.afterPos_[targetMap.afterPos_.size() - 1].afterRow) {
214         isReplaces = false;
215         return MappingInfo { row + 1, col + 1};
216     }
217     while (right - left >= 0) {
218         int32_t mid = (right + left) / 2;
219         if ((targetMap.afterPos_[mid].afterRow == row && targetMap.afterPos_[mid].afterColumn > col) ||
220              targetMap.afterPos_[mid].afterRow > row) {
221             right = mid - 1;
222         } else {
223             res = mid;
224             left = mid + 1;
225         }
226     }
227     return MappingInfo { targetMap.afterPos_[res].beforeRow + 1, targetMap.afterPos_[res].beforeColumn + 1 };
228 }
229 
ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)230 void SourceMap::ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)
231 {
232     uint32_t cnt = 0;
233     std::string tempStr;
234     for (uint32_t i = 0; i < sourceMap.size(); i++) {
235         // reslove json file
236         if (sourceMap[i] == DOUBLE_SLASH) {
237             i++;
238             tempStr += sourceMap[i];
239             continue;
240         }
241         // cnt is used to represent a pair of double quotation marks: ""
242         if (sourceMap[i] == '"') {
243             cnt++;
244         }
245         if (cnt == INDEX_TWO) {
246             sourceKeyInfo.push_back(tempStr);
247             tempStr = "";
248             cnt = 0;
249         } else if (cnt == 1) {
250             if (sourceMap[i] != '"') {
251                 tempStr += sourceMap[i];
252             }
253         }
254     }
255 }
256 
GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)257 void SourceMap::GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)
258 {
259     // 0 for colum, 1 for row
260     int32_t flag = 0;
261     // find line, column
262     for (int32_t i = start - 1; i > 0; i--) {
263         if (temp[i] == ':') {
264             flag += 1;
265             continue;
266         }
267         if (flag == 0) {
268             column = temp[i] + column;
269         } else if (flag == 1) {
270             line = temp[i] + line;
271         } else {
272             break;
273         }
274     }
275 }
276 
HandleMappings(const std::string& mapping)277 std::vector<std::string> SourceMap::HandleMappings(const std::string& mapping)
278 {
279     std::vector<std::string> keyInfo;
280     std::string tempStr;
281     for (uint32_t i = 0; i < mapping.size(); i++) {
282         if (mapping[i] == DELIMITER_COMMA) {
283             keyInfo.push_back(tempStr);
284             tempStr = "";
285         } else if (mapping[i] == DELIMITER_SEMICOLON) {
286             if (tempStr != "") {
287                 keyInfo.push_back(tempStr);
288             }
289             tempStr = "";
290             keyInfo.push_back(";");
291         } else {
292             tempStr += mapping[i];
293         }
294     }
295     if (tempStr != "") {
296         keyInfo.push_back(tempStr);
297     }
298     return keyInfo;
299 };
300 
VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)301 bool SourceMap::VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)
302 {
303     const int32_t VLQ_BASE_SHIFT = 5;
304     // binary: 100000
305     uint32_t VLQ_BASE = 1 << VLQ_BASE_SHIFT;
306     // binary: 011111
307     uint32_t VLQ_BASE_MASK = VLQ_BASE - 1;
308     // binary: 100000
309     uint32_t VLQ_CONTINUATION_BIT = VLQ_BASE;
310     uint32_t result = 0;
311     uint32_t shift = 0;
312     bool continuation = 0;
313     for (uint32_t i = 0; i < vStr.size(); i++) {
314         uint32_t digit = Base64CharToInt(vStr[i]);
315         if (digit == DIGIT_NUM) {
316             return false;
317         }
318         continuation = digit & VLQ_CONTINUATION_BIT;
319         digit &= VLQ_BASE_MASK;
320         result += digit << shift;
321         if (continuation) {
322             shift += VLQ_BASE_SHIFT;
323         } else {
324             bool isNegate = result & 1;
325             result >>= 1;
326             ans.push_back(isNegate ? -result : result);
327             result = 0;
328             shift = 0;
329         }
330     }
331     if (continuation) {
332         return false;
333     }
334     return true;
335 };
336 
TranslateUrlPositionBySourceMap(std::string& url, int& line, int& column)337 bool SourceMap::TranslateUrlPositionBySourceMap(std::string& url, int& line, int& column)
338 {
339     std::string tmp = sources_[url];
340     if (tmp.empty() || tmp.size() < REAL_SOURCE_SIZE + 1) {
341         LOG_ECMA(ERROR) << "Translate failed, url: " << url;
342         return false;
343     }
344     tmp = tmp.substr(REAL_SOURCE_INDEX, tmp.size() - REAL_SOURCE_SIZE - 1);
345     if (url.rfind(".js") != std::string::npos) {
346         url = tmp;
347         return true;
348     }
349     bool isReplaces = true;
350     bool ret = false;
351     auto iterData = sourceMaps_.find(url);
352     if (iterData != sourceMaps_.end()) {
353         if (iterData->second == nullptr) {
354             LOG_ECMA(ERROR) << "Extract mappings failed, url: " << url;
355             return false;
356         }
357         ret = GetLineAndColumnNumbers(line, column, *(iterData->second), isReplaces);
358         if (isReplaces) {
359             url = tmp;
360         }
361         return ret;
362     }
363     auto iter = mappings_.find(url);
364     if (iter != mappings_.end()) {
365         std::string mappings = mappings_[url];
366         if (mappings.size() < FLAG_MAPPINGS_LEN + 1) {
367             LOG_ECMA(ERROR) << "Translate failed, url: " << url << ", mappings: " << mappings;
368             return false;
369         }
370         std::shared_ptr<SourceMapData> modularMap = std::make_shared<SourceMapData>();
371         if (modularMap == nullptr) {
372             LOG_ECMA(ERROR) << "New SourceMapData failed";
373             return false;
374         }
375         ExtractSourceMapData(mappings.substr(FLAG_MAPPINGS_LEN, mappings.size() - FLAG_MAPPINGS_LEN - 1), modularMap);
376         sourceMaps_.emplace(url, modularMap);
377         ret = GetLineAndColumnNumbers(line, column, *(modularMap), isReplaces);
378         if (isReplaces) {
379             url = tmp;
380         }
381         return ret;
382     }
383     return false;
384 }
385 
GetLineAndColumnNumbers(int& line, int& column, SourceMapData& targetMap, bool& isReplaces)386 bool SourceMap::GetLineAndColumnNumbers(int& line, int& column, SourceMapData& targetMap, bool& isReplaces)
387 {
388     int32_t offSet = 0;
389     MappingInfo mapInfo;
390 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
391     mapInfo = Find(line - offSet + OFFSET_PREVIEW, column, targetMap, isReplaces);
392 #else
393     mapInfo = Find(line - offSet, column, targetMap, isReplaces);
394 #endif
395     if (mapInfo.row == 0 || mapInfo.col == 0) {
396         return false;
397     } else {
398         line = mapInfo.row;
399         column = mapInfo.col;
400         return true;
401     }
402 }
403 }   // namespace panda
404 }   // namespace ecmascript
405