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