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 #include "ecmascript/jspandafile/debug_info_extractor.h"
17 
18 #include "libpandafile/debug_data_accessor-inl.h"
19 #include "libpandafile/line_number_program.h"
20 
21 namespace panda::ecmascript {
22 using panda::panda_file::ClassDataAccessor;
23 using panda::panda_file::DebugInfoDataAccessor;
24 using panda::panda_file::File;
25 using panda::panda_file::LineNumberProgramItem;
26 using panda::panda_file::LineProgramState;
27 using panda::panda_file::LineNumberProgramProcessor;
28 using panda::panda_file::MethodDataAccessor;
29 using panda::panda_file::ProtoDataAccessor;
30 
GetStringFromConstantPool(const panda_file::File &pf, uint32_t offset)31 static const char *GetStringFromConstantPool(const panda_file::File &pf, uint32_t offset)
32 {
33     return reinterpret_cast<const char *>(pf.GetStringData(panda_file::File::EntityId(offset)).data);
34 }
35 
36 class LineNumberProgramHandler {
37 public:
LineNumberProgramHandler(LineProgramState *state)38     explicit LineNumberProgramHandler(LineProgramState *state) : state_(state) {}
39     ~LineNumberProgramHandler() = default;
40 
41     NO_COPY_SEMANTIC(LineNumberProgramHandler);
42     NO_MOVE_SEMANTIC(LineNumberProgramHandler);
43 
GetState() const44     LineProgramState *GetState() const
45     {
46         return state_;
47     }
48 
ProcessBegin()49     void ProcessBegin()
50     {
51         lnt_.push_back({state_->GetAddress(), static_cast<int32_t>(state_->GetLine())});
52     }
53 
ProcessEnd()54     void ProcessEnd()
55     {
56         // When process ends, update any variableInfo
57         // with end_offset = 0, set it to the state address.
58         for (auto iter = lvt_.begin(); iter != lvt_.end(); iter++) {
59             if (iter->endOffset == 0) {
60                 iter->endOffset = state_->GetAddress();
61             }
62         }
63     }
64 
HandleAdvanceLine(int32_t lineDiff) const65     bool HandleAdvanceLine(int32_t lineDiff) const
66     {
67         state_->AdvanceLine(lineDiff);
68         return true;
69     }
70 
HandleAdvancePc(uint32_t pcDiff) const71     bool HandleAdvancePc(uint32_t pcDiff) const
72     {
73         state_->AdvancePc(pcDiff);
74         return true;
75     }
76 
HandleSetFile(uint32_t sourceFileId) const77     bool HandleSetFile(uint32_t sourceFileId) const
78     {
79         state_->SetFile(sourceFileId);
80         return true;
81     }
82 
HandleSetSourceCode(uint32_t sourceCodeId) const83     bool HandleSetSourceCode(uint32_t sourceCodeId) const
84     {
85         state_->SetSourceCode(sourceCodeId);
86         return true;
87     }
88 
HandleSetPrologueEnd() const89     bool HandleSetPrologueEnd() const
90     {
91         return true;
92     }
93 
HandleSetEpilogueBegin() const94     bool HandleSetEpilogueBegin() const
95     {
96         return true;
97     }
98 
HandleStartLocal(int32_t regNumber, uint32_t nameId, [[maybe_unused]] uint32_t typeId)99     bool HandleStartLocal(int32_t regNumber, uint32_t nameId, [[maybe_unused]] uint32_t typeId)
100     {
101         // start_offset is the current state address, end_offset will temporarily be 0 here,
102         // then being updated inside the HandleEndLocal method.
103         uint32_t startOffset = state_->GetAddress();
104         uint32_t endOffset = 0;
105         const char *name = GetStringFromConstantPool(state_->GetPandaFile(), nameId);
106         lvt_.push_back({name, regNumber, startOffset, endOffset});
107         return true;
108     }
109 
HandleStartLocalExtended(int32_t regNumber, uint32_t nameId, [[maybe_unused]] uint32_t typeId, [[maybe_unused]] uint32_t typeSignatureId)110     bool HandleStartLocalExtended(int32_t regNumber, uint32_t nameId, [[maybe_unused]] uint32_t typeId,
111                                   [[maybe_unused]] uint32_t typeSignatureId)
112     {
113         uint32_t startOffset = state_->GetAddress();
114         uint32_t endOffset = 0;
115         const char *name = GetStringFromConstantPool(state_->GetPandaFile(), nameId);
116         lvt_.push_back({name, regNumber, startOffset, endOffset});
117         return true;
118     }
119 
HandleEndLocal([[maybe_unused]] int32_t regNumber)120     bool HandleEndLocal([[maybe_unused]] int32_t regNumber)
121     {
122         for (auto iter = lvt_.rbegin(); iter != lvt_.rend(); iter++) {
123             // reversely finds the variable and updates its end_offset to be state address
124             if (iter->regNumber == regNumber && iter->endOffset == 0) {
125                 iter->endOffset = state_->GetAddress();
126                 break;
127             }
128         }
129         return true;
130     }
131 
HandleSetColumn(int32_t columnNumber)132     bool HandleSetColumn(int32_t columnNumber)
133     {
134         state_->SetColumn(columnNumber);
135         cnt_.push_back({state_->GetAddress(), static_cast<int32_t>(state_->GetColumn())});
136         return true;
137     }
138 
HandleSpecialOpcode(uint32_t pcOffset, int32_t lineOffset)139     bool HandleSpecialOpcode(uint32_t pcOffset, int32_t lineOffset)
140     {
141         state_->AdvancePc(pcOffset);
142         state_->AdvanceLine(lineOffset);
143         lnt_.push_back({state_->GetAddress(), static_cast<int32_t>(state_->GetLine())});
144         return true;
145     }
146 
GetLineNumberTable() const147     LineNumberTable GetLineNumberTable() const
148     {
149         return lnt_;
150     }
151 
GetLocalVariableTable() const152     LocalVariableTable GetLocalVariableTable() const
153     {
154         return lvt_;
155     }
156 
GetColumnNumberTable() const157     ColumnNumberTable GetColumnNumberTable() const
158     {
159         return cnt_;
160     }
161 
GetFile() const162     const uint8_t *GetFile() const
163     {
164         return state_->GetFile();
165     }
166 
GetSourceCode() const167     const uint8_t *GetSourceCode() const
168     {
169         return state_->GetSourceCode();
170     }
171 
172 private:
173     using Opcode = LineNumberProgramItem::Opcode;
174 
175     LineProgramState *state_;
176     LineNumberTable lnt_;
177     LocalVariableTable lvt_;
178     ColumnNumberTable cnt_;
179 };
180 
GetLineNumberTable(const panda_file::File::EntityId methodId)181 const LineNumberTable &DebugInfoExtractor::GetLineNumberTable(const panda_file::File::EntityId methodId)
182 {
183     std::lock_guard<std::recursive_mutex> lock(mutex_);
184     static const LineNumberTable EMPTY_LINE_TABLE {};
185 
186     auto iter = methods_.find(methodId.GetOffset());
187     if (iter == methods_.end()) {
188         if (!ExtractorMethodDebugInfo(methodId)) {
189             return EMPTY_LINE_TABLE;
190         }
191         return methods_[methodId.GetOffset()].lineNumberTable;
192     }
193     ASSERT(iter != methods_.end());
194     return iter->second.lineNumberTable;
195 }
196 
GetColumnNumberTable(const panda_file::File::EntityId methodId)197 const ColumnNumberTable &DebugInfoExtractor::GetColumnNumberTable(const panda_file::File::EntityId methodId)
198 {
199     std::lock_guard<std::recursive_mutex> lock(mutex_);
200     static const ColumnNumberTable EMPTY_COLUMN_TABLE {};
201 
202     auto iter = methods_.find(methodId.GetOffset());
203     if (iter == methods_.end()) {
204         if (!ExtractorMethodDebugInfo(methodId)) {
205             return EMPTY_COLUMN_TABLE;
206         }
207         return methods_[methodId.GetOffset()].columnNumberTable;
208     }
209     ASSERT(iter != methods_.end());
210     return iter->second.columnNumberTable;
211 }
212 
GetLocalVariableTable(const panda_file::File::EntityId methodId)213 const LocalVariableTable &DebugInfoExtractor::GetLocalVariableTable(const panda_file::File::EntityId methodId)
214 {
215     std::lock_guard<std::recursive_mutex> lock(mutex_);
216     static const LocalVariableTable EMPTY_VARIABLE_TABLE {};
217 
218     auto iter = methods_.find(methodId.GetOffset());
219     if (iter == methods_.end()) {
220         if (!ExtractorMethodDebugInfo(methodId)) {
221             return EMPTY_VARIABLE_TABLE;
222         }
223         return methods_[methodId.GetOffset()].localVariableTable;
224     }
225     ASSERT(iter != methods_.end());
226     return iter->second.localVariableTable;
227 }
228 
GetSourceFile(const panda_file::File::EntityId methodId)229 const std::string &DebugInfoExtractor::GetSourceFile(const panda_file::File::EntityId methodId)
230 {
231     std::lock_guard<std::recursive_mutex> lock(mutex_);
232     static const std::string sourceFile = "";
233 
234     auto iter = methods_.find(methodId.GetOffset());
235     if (iter == methods_.end()) {
236         if (!ExtractorMethodDebugInfo(methodId)) {
237             return sourceFile;
238         }
239         return methods_[methodId.GetOffset()].sourceFile;
240     }
241     ASSERT(iter != methods_.end());
242     return iter->second.sourceFile;
243 }
244 
GetSourceCode(const panda_file::File::EntityId methodId)245 const std::string &DebugInfoExtractor::GetSourceCode(const panda_file::File::EntityId methodId)
246 {
247     std::lock_guard<std::recursive_mutex> lock(mutex_);
248     static const std::string sourceCode = "";
249 
250     auto iter = methods_.find(methodId.GetOffset());
251     if (iter == methods_.end()) {
252         if (!ExtractorMethodDebugInfo(methodId)) {
253             return sourceCode;
254         }
255         return methods_[methodId.GetOffset()].sourceCode;
256     }
257     ASSERT(iter != methods_.end());
258     return iter->second.sourceCode;
259 }
260 
ExtractorMethodDebugInfo(const panda_file::File::EntityId methodId)261 bool DebugInfoExtractor::ExtractorMethodDebugInfo(const panda_file::File::EntityId methodId)
262 {
263     if (!methodId.IsValid()) {
264         return false;
265     }
266 
267     uint32_t offset = methodId.GetOffset();
268     if (offset >= jsPandaFile_->GetFileSize()) {
269         return false;
270     }
271 
272     auto &pandaFile = *jsPandaFile_->GetPandaFile();
273     MethodDataAccessor mda(pandaFile, methodId);
274     auto classId = mda.GetClassId();
275     ASSERT(classId.IsValid() && !pandaFile.IsExternal(classId));
276     ClassDataAccessor cda(pandaFile, classId);
277     auto sourceFileId = cda.GetSourceFileId();
278     auto debugInfoId = mda.GetDebugInfoId();
279     if (!debugInfoId) {
280         return false;
281     }
282 
283     ExtractorMethodDebugInfo(pandaFile, sourceFileId, debugInfoId, offset);
284     return true;
285 }
286 
ExtractorMethodDebugInfo(const panda_file::File &pandaFile, const std::optional<panda_file::File::EntityId> sourceFileId, const std::optional<panda_file::File::EntityId> debugInfoId, uint32_t offset)287 void DebugInfoExtractor::ExtractorMethodDebugInfo(const panda_file::File &pandaFile,
288                                                   const std::optional<panda_file::File::EntityId> sourceFileId,
289                                                   const std::optional<panda_file::File::EntityId> debugInfoId,
290                                                   uint32_t offset)
291 {
292     DebugInfoDataAccessor dda(pandaFile, debugInfoId.value());
293     const uint8_t *program = dda.GetLineNumberProgram();
294     LineProgramState state(pandaFile, sourceFileId.value_or(panda_file::File::EntityId(0)), dda.GetLineStart(),
295                             dda.GetConstantPool());
296 
297     LineNumberProgramHandler handler(&state);
298     LineNumberProgramProcessor<LineNumberProgramHandler> programProcessor(program, &handler);
299     programProcessor.Process();
300 
301     const char *sourceFile = "";
302     if (state.HasFile()) {
303         sourceFile = reinterpret_cast<const char *>(handler.GetFile());
304     }
305 
306     const char *sourceCode = "";
307     if (state.HasSourceCode()) {
308         sourceCode = reinterpret_cast<const char *>(handler.GetSourceCode());
309     }
310 
311     MethodDebugInfo methodDebugInfo = {sourceFile, sourceCode, handler.GetLineNumberTable(),
312         handler.GetColumnNumberTable(), handler.GetLocalVariableTable()};
313     methods_.emplace(offset, std::move(methodDebugInfo));
314 }
315 
Extract()316 void DebugInfoExtractor::Extract()
317 {
318     std::lock_guard<std::recursive_mutex> lock(mutex_);
319     auto &pandaFile = *jsPandaFile_->GetPandaFile();
320     auto classes = jsPandaFile_->GetClasses();
321 
322     for (size_t i = 0; i < classes.Size(); i++) {
323         panda_file::File::EntityId classId(classes[i]);
324         if (!classId.IsValid() || jsPandaFile_->IsExternal(classId)) {
325             continue;
326         }
327 
328         ClassDataAccessor cda(pandaFile, classId);
329         auto sourceFileId = cda.GetSourceFileId();
330 
331         cda.EnumerateMethods([&](MethodDataAccessor &mda) {
332             panda_file::File::EntityId methodId = mda.GetMethodId();
333             if (!methodId.IsValid()) {
334                 return;
335             }
336 
337             uint32_t offset = methodId.GetOffset();
338             if (offset >= jsPandaFile_->GetFileSize()) {
339                 return;
340             }
341 
342             auto iter = methods_.find(offset);
343             if (iter != methods_.end()) {
344                 return;
345             }
346 
347             auto debugInfoId = mda.GetDebugInfoId();
348             if (!debugInfoId) {
349                 return;
350             }
351 
352             ExtractorMethodDebugInfo(pandaFile, sourceFileId, debugInfoId, offset);
353         });
354     }
355 }
356 }  // namespace panda::ecmascript
357