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 28namespace panda { 29namespace ecmascript { 30namespace { 31static constexpr char DELIMITER_COMMA = ','; 32static constexpr char DELIMITER_SEMICOLON = ';'; 33static constexpr char DOUBLE_SLASH = '\\'; 34 35static constexpr int32_t INDEX_ONE = 1; 36static constexpr int32_t INDEX_TWO = 2; 37static constexpr int32_t INDEX_THREE = 3; 38static constexpr int32_t INDEX_FOUR = 4; 39static constexpr int32_t ANS_MAP_SIZE = 5; 40static constexpr int32_t DIGIT_NUM = 64; 41 42const std::string MEGER_SOURCE_MAP_PATH = "ets/sourceMaps.map"; 43static const CString FLAG_SOURCES = " \"sources\":"; 44static const CString FLAG_MAPPINGS = " \"mappings\": \""; 45static const CString FLAG_END = " }"; 46 47static constexpr size_t FLAG_MAPPINGS_LEN = 17; 48static constexpr size_t REAL_SOURCE_SIZE = 7; 49static constexpr size_t REAL_URL_INDEX = 3; 50static constexpr size_t REAL_SOURCE_INDEX = 7; 51} // namespace 52 53#if defined(PANDA_TARGET_OHOS) 54bool 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 75uint32_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) 97void 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 110void SourceMap::Init(uint8_t *data, size_t dataSize) 111{ 112 std::string content; 113 content.assign(data, data + dataSize); 114 SplitSourceMap(content); 115} 116 117void 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 149void 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 198MappingInfo 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 230void 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 257void 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 277std::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 301bool 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 337bool 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 386bool 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