1/*
2 * Copyright (c) 2021 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#ifndef ECMASCRIPT_JSPANDAFILE_DEBUG_INFO_EXTRACTOR_H
17#define ECMASCRIPT_JSPANDAFILE_DEBUG_INFO_EXTRACTOR_H
18
19#include <mutex>
20
21#include "ecmascript/common.h"
22#include "ecmascript/debugger/js_pt_location.h"
23#include "ecmascript/jspandafile/js_pandafile.h"
24#include "ecmascript/mem/c_containers.h"
25#include "ecmascript/mem/c_string.h"
26
27#include "libpandafile/class_data_accessor-inl.h"
28#include "libpandafile/file.h"
29#include "libpandabase/utils/utf.h"
30
31namespace panda::ecmascript {
32class JSPandaFile;
33
34struct LineTableEntry {
35    uint32_t offset;
36    int32_t line;
37
38    bool operator<(const LineTableEntry &other) const
39    {
40        return offset < other.offset;
41    }
42};
43
44struct ColumnTableEntry {
45    uint32_t offset;
46    int32_t column;
47
48    bool operator<(const ColumnTableEntry &other) const
49    {
50        return offset < other.offset;
51    }
52};
53
54using LineNumberTable = CVector<LineTableEntry>;
55using ColumnNumberTable = CVector<ColumnTableEntry>;
56using JSPtLocation = tooling::JSPtLocation;
57
58/*
59 * Full version of LocalVariableInfo is defined in frontend,
60 * here only using name, reg_number, start_offset, and end_offset:
61 *   std::string name
62 *   std::string type
63 *   std::string typeSignature
64 *   int32_t regNumber
65 *   uint32_t startOffset
66 *   uint32_t endOffset
67 */
68struct LocalVariableInfo {
69    std::string name;
70    int32_t regNumber;
71    uint32_t startOffset;
72    uint32_t endOffset;
73};
74using LocalVariableTable = CVector<LocalVariableInfo>;
75
76// public for debugger
77class PUBLIC_API DebugInfoExtractor {
78public:
79    explicit DebugInfoExtractor(const JSPandaFile *jsPandaFile) : jsPandaFile_(jsPandaFile)
80    {}
81
82    ~DebugInfoExtractor() = default;
83
84    const LineNumberTable &GetLineNumberTable(const panda_file::File::EntityId methodId);
85
86    const ColumnNumberTable &GetColumnNumberTable(const panda_file::File::EntityId methodId);
87
88    const LocalVariableTable &GetLocalVariableTable(const panda_file::File::EntityId methodId);
89
90    const std::string &GetSourceFile(const panda_file::File::EntityId methodId);
91
92    const std::string &GetSourceCode(const panda_file::File::EntityId methodId);
93
94    template<class Callback>
95    bool MatchWithLocation(const Callback &cb, int32_t line, int32_t column,
96        const std::string &url, const std::unordered_set<std::string> &debugRecordName)
97    {
98        if (line == SPECIAL_LINE_MARK) {
99            return false;
100        }
101        auto &pandaFile = *jsPandaFile_->GetPandaFile();
102        auto classes = jsPandaFile_->GetClasses();
103        for (size_t i = 0; i < classes.Size(); i++) {
104            panda_file::File::EntityId id(classes[i]);
105            if (jsPandaFile_->IsExternal(id)) {
106                continue;
107            }
108
109            CVector<panda_file::File::EntityId> methodIds;
110            panda_file::ClassDataAccessor cda(pandaFile, id);
111            CString recordName = JSPandaFile::ParseEntryPoint(utf::Mutf8AsCString(cda.GetDescriptor()));
112            // Check record name in stage mode
113            if (!jsPandaFile_->IsBundlePack()) {
114                // the recordName for testcases is empty
115                if (!debugRecordName.empty()) {
116                    auto iter = debugRecordName.find(std::string(recordName.c_str()));
117                    if (iter == debugRecordName.end()) {
118                        continue;
119                    }
120                }
121            }
122            cda.EnumerateMethods([&](panda_file::MethodDataAccessor &mda) {
123                methodIds.push_back(mda.GetMethodId());
124            });
125
126            int32_t minColumn = INT32_MAX;
127            uint32_t currentOffset = UINT32_MAX;
128            uint32_t minColumnOffset = UINT32_MAX;
129            EntityId currentMethodId;
130            EntityId minColumnMethodId;
131            for (auto &methodId : methodIds) {
132                const std::string &sourceFile = GetSourceFile(methodId);
133                // the url for testcases is empty
134                if (!url.empty() && sourceFile != url) {
135                    continue;
136                }
137                const LineNumberTable &lineTable = GetLineNumberTable(methodId);
138                const ColumnNumberTable &columnTable = GetColumnNumberTable(methodId);
139                for (uint32_t j = 0; j < lineTable.size(); j++) {
140                    if (lineTable[j].line != line) {
141                        continue;
142                    }
143                    currentMethodId = methodId;
144                    currentOffset = lineTable[j].offset;
145                    uint32_t nextOffset = ((j == lineTable.size() - 1) ? UINT32_MAX : lineTable[j + 1].offset);
146                    for (const auto &pair : columnTable) {
147                        if (pair.offset >= currentOffset && pair.offset < nextOffset) {
148                            if (pair.column == column) {
149                                return cb(JSPtLocation(jsPandaFile_, methodId, pair.offset, url));
150                            } else if (pair.column < minColumn && currentOffset < minColumnOffset) {
151                                minColumn = pair.column;
152                                minColumnOffset = currentOffset;
153                                minColumnMethodId = currentMethodId;
154                            }
155                        }
156                    }
157                }
158            }
159            if (minColumn != INT32_MAX) { // find the smallest column for the corresponding row
160                return cb(JSPtLocation(jsPandaFile_, minColumnMethodId, minColumnOffset, url));
161            }
162            if (currentOffset != UINT32_MAX) { // find corresponding row, but not find corresponding column
163                return cb(JSPtLocation(jsPandaFile_, currentMethodId, currentOffset, url));
164            }
165        }
166        return false;
167    }
168
169    template<class Callback>
170    bool MatchLineWithOffset(const Callback &cb, panda_file::File::EntityId methodId, uint32_t offset)
171    {
172        int32_t line = 0;
173        const LineNumberTable &lineTable = GetLineNumberTable(methodId);
174        auto iter = std::upper_bound(lineTable.begin(), lineTable.end(), LineTableEntry {offset, 0});
175        if (iter != lineTable.begin()) {
176            line = (iter - 1)->line;
177        }
178        return cb(line);
179    }
180
181    template<class Callback>
182    bool MatchColumnWithOffset(const Callback &cb, panda_file::File::EntityId methodId, uint32_t offset)
183    {
184        int32_t column = 0;
185        const ColumnNumberTable &columnTable = GetColumnNumberTable(methodId);
186        auto iter = std::upper_bound(columnTable.begin(), columnTable.end(), ColumnTableEntry {offset, 0});
187        if (iter != columnTable.begin()) {
188            column = (iter - 1)->column;
189        }
190        return cb(column);
191    }
192
193    int32_t GetFristLine(panda_file::File::EntityId methodId)
194    {
195        const LineNumberTable &lineTable = GetLineNumberTable(methodId);
196        auto tableSize = lineTable.size();
197        if (tableSize == 0) {
198            return 0;
199        }
200        if (tableSize == 1) {
201            return lineTable[0].line + 1;
202        }
203        int firstLineIndex = ((lineTable[0].line == SPECIAL_LINE_MARK) ? 1 : 0);
204        return lineTable[firstLineIndex].line + 1;
205    }
206
207    int32_t GetFristColumn(panda_file::File::EntityId methodId)
208    {
209        const ColumnNumberTable &columnTable = GetColumnNumberTable(methodId);
210        auto tableSize = columnTable.size();
211        if (tableSize == 0) {
212            return 0;
213        }
214        if (tableSize == 1) {
215            return columnTable[0].column + 1;
216        }
217        int firstColumnIndex = ((columnTable[0].column == SPECIAL_LINE_MARK) ? 1 : 0);
218        return columnTable[firstColumnIndex].column + 1;
219    }
220
221    void Extract();
222
223    constexpr static int32_t SPECIAL_LINE_MARK = -1;
224
225private:
226    bool ExtractorMethodDebugInfo(const panda_file::File::EntityId methodId);
227    void ExtractorMethodDebugInfo(const panda_file::File &pandaFile,
228                                  const std::optional<panda_file::File::EntityId> sourceFileId,
229                                  const std::optional<panda_file::File::EntityId> debugInfoId,
230                                  uint32_t offset);
231    struct MethodDebugInfo {
232        std::string sourceFile;
233        std::string sourceCode;
234        LineNumberTable lineNumberTable;
235        ColumnNumberTable columnNumberTable;
236        LocalVariableTable localVariableTable;
237    };
238
239    std::recursive_mutex mutex_;
240    CUnorderedMap<uint32_t, MethodDebugInfo> methods_;
241    const JSPandaFile *jsPandaFile_ {nullptr};
242};
243}  // namespace panda::ecmascript
244
245#endif  // ECMASCRIPT_JSPANDAFILE_DEBUG_INFO_EXTRACTOR_H
246