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