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