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