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 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)198 MappingInfo SourceMap::Find(int32_t row, int32_t col, const SourceMapData& targetMap)
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 return MappingInfo { row + 1, col + 1};
215 }
216 while (right - left >= 0) {
217 int32_t mid = (right + left) / 2;
218 if ((targetMap.afterPos_[mid].afterRow == row && targetMap.afterPos_[mid].afterColumn > col) ||
219 targetMap.afterPos_[mid].afterRow > row) {
220 right = mid - 1;
221 } else {
222 res = mid;
223 left = mid + 1;
224 }
225 }
226 return MappingInfo { targetMap.afterPos_[res].beforeRow + 1, targetMap.afterPos_[res].beforeColumn + 1 };
227 }
228
ExtractKeyInfo(const std::string & sourceMap,std::vector<std::string> & sourceKeyInfo)229 void SourceMap::ExtractKeyInfo(const std::string& sourceMap, std::vector<std::string>& sourceKeyInfo)
230 {
231 uint32_t cnt = 0;
232 std::string tempStr;
233 for (uint32_t i = 0; i < sourceMap.size(); i++) {
234 // reslove json file
235 if (sourceMap[i] == DOUBLE_SLASH) {
236 i++;
237 tempStr += sourceMap[i];
238 continue;
239 }
240 // cnt is used to represent a pair of double quotation marks: ""
241 if (sourceMap[i] == '"') {
242 cnt++;
243 }
244 if (cnt == INDEX_TWO) {
245 sourceKeyInfo.push_back(tempStr);
246 tempStr = "";
247 cnt = 0;
248 } else if (cnt == 1) {
249 if (sourceMap[i] != '"') {
250 tempStr += sourceMap[i];
251 }
252 }
253 }
254 }
255
GetPosInfo(const std::string & temp,int32_t start,std::string & line,std::string & column)256 void SourceMap::GetPosInfo(const std::string& temp, int32_t start, std::string& line, std::string& column)
257 {
258 // 0 for colum, 1 for row
259 int32_t flag = 0;
260 // find line, column
261 for (int32_t i = start - 1; i > 0; i--) {
262 if (temp[i] == ':') {
263 flag += 1;
264 continue;
265 }
266 if (flag == 0) {
267 column = temp[i] + column;
268 } else if (flag == 1) {
269 line = temp[i] + line;
270 } else {
271 break;
272 }
273 }
274 }
275
HandleMappings(const std::string & mapping)276 std::vector<std::string> SourceMap::HandleMappings(const std::string& mapping)
277 {
278 std::vector<std::string> keyInfo;
279 std::string tempStr;
280 for (uint32_t i = 0; i < mapping.size(); i++) {
281 if (mapping[i] == DELIMITER_COMMA) {
282 keyInfo.push_back(tempStr);
283 tempStr = "";
284 } else if (mapping[i] == DELIMITER_SEMICOLON) {
285 if (tempStr != "") {
286 keyInfo.push_back(tempStr);
287 }
288 tempStr = "";
289 keyInfo.push_back(";");
290 } else {
291 tempStr += mapping[i];
292 }
293 }
294 if (tempStr != "") {
295 keyInfo.push_back(tempStr);
296 }
297 return keyInfo;
298 };
299
VlqRevCode(const std::string & vStr,std::vector<int32_t> & ans)300 bool SourceMap::VlqRevCode(const std::string& vStr, std::vector<int32_t>& ans)
301 {
302 const int32_t VLQ_BASE_SHIFT = 5;
303 // binary: 100000
304 uint32_t VLQ_BASE = 1 << VLQ_BASE_SHIFT;
305 // binary: 011111
306 uint32_t VLQ_BASE_MASK = VLQ_BASE - 1;
307 // binary: 100000
308 uint32_t VLQ_CONTINUATION_BIT = VLQ_BASE;
309 uint32_t result = 0;
310 uint32_t shift = 0;
311 bool continuation = 0;
312 for (uint32_t i = 0; i < vStr.size(); i++) {
313 uint32_t digit = Base64CharToInt(vStr[i]);
314 if (digit == DIGIT_NUM) {
315 return false;
316 }
317 continuation = digit & VLQ_CONTINUATION_BIT;
318 digit &= VLQ_BASE_MASK;
319 result += digit << shift;
320 if (continuation) {
321 shift += VLQ_BASE_SHIFT;
322 } else {
323 bool isNegate = result & 1;
324 result >>= 1;
325 ans.push_back(isNegate ? -result : result);
326 result = 0;
327 shift = 0;
328 }
329 }
330 if (continuation) {
331 return false;
332 }
333 return true;
334 };
335
TranslateUrlPositionBySourceMap(std::string & url,int & line,int & column)336 bool SourceMap::TranslateUrlPositionBySourceMap(std::string& url, int& line, int& column)
337 {
338 std::string tmp = sources_[url];
339 if (tmp.empty() || tmp.size() < REAL_SOURCE_SIZE + 1) {
340 LOG_ECMA(ERROR) << "Translate failed, url: " << url;
341 return false;
342 }
343 tmp = tmp.substr(REAL_SOURCE_INDEX, tmp.size() - REAL_SOURCE_SIZE - 1);
344 if (url.rfind(".js") != std::string::npos) {
345 url = tmp;
346 return true;
347 }
348 auto iterData = sourceMaps_.find(url);
349 if (iterData != sourceMaps_.end()) {
350 if (iterData->second == nullptr) {
351 LOG_ECMA(ERROR) << "Extract mappings failed, url: " << url;
352 return false;
353 }
354 url = tmp;
355 return GetLineAndColumnNumbers(line, column, *(iterData->second));
356 }
357 auto iter = mappings_.find(url);
358 if (iter != mappings_.end()) {
359 std::string mappings = mappings_[url];
360 if (mappings.size() < FLAG_MAPPINGS_LEN + 1) {
361 LOG_ECMA(ERROR) << "Translate failed, url: " << url << ", mappings: " << mappings;
362 return false;
363 }
364 std::shared_ptr<SourceMapData> modularMap = std::make_shared<SourceMapData>();
365 if (modularMap == nullptr) {
366 LOG_ECMA(ERROR) << "New SourceMapData failed";
367 return false;
368 }
369 ExtractSourceMapData(mappings.substr(FLAG_MAPPINGS_LEN, mappings.size() - FLAG_MAPPINGS_LEN - 1), modularMap);
370 sourceMaps_.emplace(url, modularMap);
371 url = tmp;
372 return GetLineAndColumnNumbers(line, column, *(modularMap));
373 }
374 return false;
375 }
376
GetLineAndColumnNumbers(int & line,int & column,SourceMapData & targetMap)377 bool SourceMap::GetLineAndColumnNumbers(int& line, int& column, SourceMapData& targetMap)
378 {
379 int32_t offSet = 0;
380 MappingInfo mapInfo;
381 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
382 mapInfo = Find(line - offSet + OFFSET_PREVIEW, column, targetMap);
383 #else
384 mapInfo = Find(line - offSet, column, targetMap);
385 #endif
386 if (mapInfo.row == 0 || mapInfo.col == 0) {
387 return false;
388 } else {
389 line = mapInfo.row;
390 column = mapInfo.col;
391 return true;
392 }
393 }
394 } // namespace panda
395 } // namespace ecmascript
396