• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2024 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 "debug_info_extractor.h"
17 #include "annotation_data_accessor.h"
18 #include "class_data_accessor-inl.h"
19 #include "code_data_accessor-inl.h"
20 #include "debug_data_accessor-inl.h"
21 #include "field_data_accessor-inl.h"
22 #include "file.h"
23 #include "file_item_container.h"
24 #include "file_writer.h"
25 #include "helpers.h"
26 #include "method_data_accessor-inl.h"
27 #include "modifiers.h"
28 #include "proto_data_accessor-inl.h"
29 #include "libpandabase/utils/utils.h"
30 
31 #include <cstdio>
32 
33 #include <memory>
34 #include <vector>
35 
36 #include <gtest/gtest.h>
37 
38 namespace ark::panda_file::test {
39 
40 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
41 static const char SOURCE_FILE[] = "asm.pa";
42 
PreparePandaFile(ItemContainer * container)43 void PreparePandaFile(ItemContainer *container)
44 {
45     ClassItem *classItem = container->GetOrCreateClassItem("A");
46     classItem->SetAccessFlags(ACC_PUBLIC);
47 
48     StringItem *methodName = container->GetOrCreateStringItem("foo");
49 
50     PrimitiveTypeItem *retType = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
51     std::vector<MethodParamItem> params;
52     params.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
53     ProtoItem *protoItem = container->GetOrCreateProtoItem(retType, params);
54     MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
55 
56     std::vector<uint8_t> instructions {1U, 2U, 3U, 4U};
57     auto *codeItem = container->CreateItem<CodeItem>(4U, 1U, instructions);
58 
59     methodItem->SetCode(codeItem);
60 
61     StringItem *sourceFileItem = container->GetOrCreateStringItem(SOURCE_FILE);
62     StringItem *paramStringItem = container->GetOrCreateStringItem("arg0");
63     StringItem *localVariableName0 = container->GetOrCreateStringItem("local_0");
64     StringItem *localVariableName1 = container->GetOrCreateStringItem("local_1");
65     StringItem *localVariableName2 = container->GetOrCreateStringItem("local_2");
66     StringItem *localVariableTypeI32 = container->GetOrCreateStringItem("I");
67     StringItem *localVariableSigTypeI32 = container->GetOrCreateStringItem("type_i32");
68 
69     LineNumberProgramItem *lineNumberProgramItem = container->CreateLineNumberProgramItem();
70     auto *debugInfoItem = container->CreateItem<DebugInfoItem>(lineNumberProgramItem);
71     methodItem->SetDebugInfo(debugInfoItem);
72 
73     // Add static method with ref arg
74 
75     StringItem *methodNameBar = container->GetOrCreateStringItem("bar");
76 
77     PrimitiveTypeItem *retTypeBar = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
78     std::vector<MethodParamItem> paramsBar;
79     paramsBar.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
80     paramsBar.emplace_back(container->GetOrCreateClassItem("RefArg"));
81     ProtoItem *protoItemBar = container->GetOrCreateProtoItem(retTypeBar, paramsBar);
82     MethodItem *methodItemBar = classItem->AddMethod(methodNameBar, protoItemBar, ACC_PUBLIC | ACC_STATIC, paramsBar);
83 
84     auto *codeItemBar = container->CreateItem<CodeItem>(0U, 2U, instructions);
85 
86     methodItemBar->SetCode(codeItemBar);
87 
88     StringItem *paramStringItemBar1 = container->GetOrCreateStringItem("arg0");
89     StringItem *paramStringItemBar2 = container->GetOrCreateStringItem("arg1");
90 
91     LineNumberProgramItem *lineNumberProgramItemBar = container->CreateLineNumberProgramItem();
92     auto *debugInfoItemBar = container->CreateItem<DebugInfoItem>(lineNumberProgramItemBar);
93     methodItemBar->SetDebugInfo(debugInfoItemBar);
94 
95     // Add non static method with ref arg
96 
97     StringItem *methodNameBaz = container->GetOrCreateStringItem("baz");
98 
99     PrimitiveTypeItem *retTypeBaz = container->GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
100     std::vector<MethodParamItem> paramsBaz;
101     paramsBaz.emplace_back(container->GetOrCreateClassItem("RefArg"));
102     paramsBaz.emplace_back(container->GetOrCreatePrimitiveTypeItem(Type::TypeId::U1));
103     ProtoItem *protoItemBaz = container->GetOrCreateProtoItem(retTypeBaz, paramsBaz);
104     MethodItem *methodItemBaz = classItem->AddMethod(methodNameBaz, protoItemBaz, ACC_PUBLIC, paramsBaz);
105 
106     auto *codeItemBaz = container->CreateItem<CodeItem>(0U, 2U, instructions);
107 
108     methodItemBaz->SetCode(codeItemBaz);
109 
110     StringItem *paramStringItemBaz1 = container->GetOrCreateStringItem("arg0");
111     StringItem *paramStringItemBaz2 = container->GetOrCreateStringItem("arg1");
112 
113     LineNumberProgramItem *lineNumberProgramItemBaz = container->CreateLineNumberProgramItem();
114     auto *debugInfoItemBaz = container->CreateItem<DebugInfoItem>(lineNumberProgramItemBaz);
115     methodItemBaz->SetDebugInfo(debugInfoItemBaz);
116 
117     // Add debug info for the following source file;
118 
119     //  1 # file: asm.pa
120     //  2 .function foo(i32 arg0) {
121     //  3   ldai arg0
122     //  4   stai v1     // START_LOCAL: reg=1, name="local_0", type="i32"
123     //  5   ldai 2
124     //  6   stai v2     // START_LOCAL_EXTENDED: reg=2, name="local_1",
125     //  type="i32", type_signature="type_i32" 7               // END_LOCAL: reg=1
126     //  8   stai v3     // START_LOCAL: reg=3, name="local_2", type="i32"
127     //  9
128     // 10   return.void
129     // 11 }
130     // 12 .function bar(i32 arg0, B arg1) { // static
131     // 13   ldai arg0
132     // 13   return.void
133     // 14 }
134     // 15 .function baz(B arg0, u1 arg1) { // non static
135     // 17   ldai arg0
136     // 16   return.void
137     // 17 }
138 
139     container->ComputeLayout();
140 
141     // foo line number program
142     auto *constantPool = debugInfoItem->GetConstantPool();
143     // Line 3
144     debugInfoItem->SetLineNumber(3U);
145     lineNumberProgramItem->EmitSetFile(constantPool, sourceFileItem);
146     lineNumberProgramItem->EmitAdvancePc(constantPool, 1);
147     lineNumberProgramItem->EmitAdvanceLine(constantPool, 1);
148     lineNumberProgramItem->EmitSpecialOpcode(0, 0);
149     lineNumberProgramItem->EmitColumn(constantPool, 0, 7U);
150     // Line 4
151     lineNumberProgramItem->EmitStartLocal(constantPool, 1, localVariableName0, localVariableTypeI32);
152     lineNumberProgramItem->EmitSpecialOpcode(1, 1);
153     lineNumberProgramItem->EmitColumn(constantPool, 0, 8U);
154     // Line 5
155     lineNumberProgramItem->EmitSpecialOpcode(1, 1);
156     // NOLINTNEXTLINE(readability-magic-numbers)
157     lineNumberProgramItem->EmitColumn(constantPool, 0, 9U);
158     // Line 6
159     lineNumberProgramItem->EmitStartLocalExtended(constantPool, 2_I, localVariableName1, localVariableTypeI32,
160                                                   localVariableSigTypeI32);
161     lineNumberProgramItem->EmitEndLocal(1);
162     lineNumberProgramItem->EmitSpecialOpcode(1, 2_I);
163     // NOLINTNEXTLINE(readability-magic-numbers)
164     lineNumberProgramItem->EmitColumn(constantPool, 0, 10U);
165     // Line 8
166     lineNumberProgramItem->EmitStartLocal(constantPool, 3_I, localVariableName2, localVariableTypeI32);
167     lineNumberProgramItem->EmitAdvanceLine(constantPool, 2_I);
168     lineNumberProgramItem->EmitSpecialOpcode(0, 0);
169     // NOLINTNEXTLINE(readability-magic-numbers)
170     lineNumberProgramItem->EmitColumn(constantPool, 0, 11U);
171     // Line 10
172     lineNumberProgramItem->EmitEnd();
173 
174     debugInfoItem->AddParameter(paramStringItem);
175 
176     methodItem->SetDebugInfo(debugInfoItem);
177 
178     // bar line number program
179     auto *constantPoolBar = debugInfoItemBar->GetConstantPool();
180     // NOLINTNEXTLINE(readability-magic-numbers)
181     debugInfoItemBar->SetLineNumber(13U);
182     lineNumberProgramItemBar->EmitSetFile(constantPoolBar, sourceFileItem);
183     lineNumberProgramItemBar->EmitAdvancePc(constantPoolBar, 1);
184     lineNumberProgramItemBar->EmitAdvanceLine(constantPoolBar, 1);
185     lineNumberProgramItemBar->EmitSpecialOpcode(0, 0);
186     lineNumberProgramItemBar->EmitColumn(constantPoolBar, 0, 5U);
187     lineNumberProgramItemBar->EmitEnd();
188 
189     debugInfoItemBar->AddParameter(paramStringItemBar1);
190     debugInfoItemBar->AddParameter(paramStringItemBar2);
191 
192     methodItemBar->SetDebugInfo(debugInfoItemBar);
193 
194     // baz line number program
195     auto *constantPoolBaz = debugInfoItemBaz->GetConstantPool();
196     // NOLINTNEXTLINE(readability-magic-numbers)
197     debugInfoItemBaz->SetLineNumber(15U);
198     lineNumberProgramItemBaz->EmitSetFile(constantPoolBaz, sourceFileItem);
199     lineNumberProgramItemBaz->EmitAdvancePc(constantPoolBaz, 1);
200     lineNumberProgramItemBaz->EmitAdvanceLine(constantPoolBaz, 1);
201     lineNumberProgramItemBaz->EmitSpecialOpcode(0, 0);
202     lineNumberProgramItemBaz->EmitColumn(constantPoolBaz, 0, 6U);
203     lineNumberProgramItemBaz->EmitEnd();
204 
205     debugInfoItemBaz->AddParameter(paramStringItemBaz1);
206     debugInfoItemBaz->AddParameter(paramStringItemBaz2);
207 
208     methodItemBaz->SetDebugInfo(debugInfoItemBaz);
209 }
210 
211 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
212 struct SourcePairLocation {
213     std::string path;  // NOLINT(misc-non-private-member-variables-in-classes)
214     size_t line;       // NOLINT(misc-non-private-member-variables-in-classes)
215 
operator ==ark::panda_file::test::SourcePairLocation216     bool operator==(const SourcePairLocation &other) const
217     {
218         return path == other.path && line == other.line;
219     }
220 
IsValidark::panda_file::test::SourcePairLocation221     bool IsValid() const
222     {
223         return !path.empty();
224     }
225 };
226 
GetLineNumberByTableOffsetWrapper(const panda_file::LineNumberTable & table,uint32_t offset)227 static std::optional<size_t> GetLineNumberByTableOffsetWrapper(const panda_file::LineNumberTable &table,
228                                                                uint32_t offset)
229 {
230     for (const auto &value : table) {
231         if (value.offset == offset) {
232             return value.line;
233         }
234     }
235     return std::nullopt;
236 }
237 
GetOffsetByTableLineNumberWrapper(const panda_file::LineNumberTable & table,size_t line)238 static std::optional<uint32_t> GetOffsetByTableLineNumberWrapper(const panda_file::LineNumberTable &table, size_t line)
239 {
240     for (const auto &value : table) {
241         if (value.line == line) {
242             return value.offset;
243         }
244     }
245     return std::nullopt;
246 }
247 
GetBreakpointAddressWrapper(const DebugInfoExtractor & extractor,const SourcePairLocation & sourceLocation)248 static std::pair<File::EntityId, uint32_t> GetBreakpointAddressWrapper(const DebugInfoExtractor &extractor,
249                                                                        const SourcePairLocation &sourceLocation)
250 {
251     auto pos = sourceLocation.path.find_last_of("/\\");
252     auto name = sourceLocation.path;
253 
254     if (pos != std::string::npos) {
255         name = name.substr(pos + 1);
256     }
257 
258     std::vector<panda_file::File::EntityId> methods = extractor.GetMethodIdList();
259     for (const auto &method : methods) {
260         if (extractor.GetSourceFile(method) == sourceLocation.path || extractor.GetSourceFile(method) == name) {
261             const panda_file::LineNumberTable &lineTable = extractor.GetLineNumberTable(method);
262             if (lineTable.empty()) {
263                 continue;
264             }
265 
266             std::optional<size_t> offset = GetOffsetByTableLineNumberWrapper(lineTable, sourceLocation.line);
267             if (offset == std::nullopt) {
268                 continue;
269             }
270             return {method, offset.value()};
271         }
272     }
273     return {File::EntityId(), 0};
274 }
275 
GetLocalVariableInfoWrapper(const DebugInfoExtractor & extractor,File::EntityId methodId,size_t offset)276 static std::vector<panda_file::LocalVariableInfo> GetLocalVariableInfoWrapper(const DebugInfoExtractor &extractor,
277                                                                               File::EntityId methodId, size_t offset)
278 {
279     std::vector<panda_file::LocalVariableInfo> variables = extractor.GetLocalVariableTable(methodId);
280     std::vector<panda_file::LocalVariableInfo> result;
281 
282     for (const auto &variable : variables) {
283         if (variable.startOffset <= offset && offset <= variable.endOffset) {
284             result.push_back(variable);
285         }
286     }
287     return result;
288 }
289 
GetSourcePairLocationWrapper(const DebugInfoExtractor & extractor,File::EntityId methodId,uint32_t bytecodeOffset)290 static SourcePairLocation GetSourcePairLocationWrapper(const DebugInfoExtractor &extractor, File::EntityId methodId,
291                                                        uint32_t bytecodeOffset)
292 {
293     const panda_file::LineNumberTable &lineTable = extractor.GetLineNumberTable(methodId);
294     if (lineTable.empty()) {
295         return SourcePairLocation();
296     }
297 
298     std::optional<size_t> line = GetLineNumberByTableOffsetWrapper(lineTable, bytecodeOffset);
299     if (line == std::nullopt) {
300         return SourcePairLocation();
301     }
302 
303     return SourcePairLocation {extractor.GetSourceFile(methodId), line.value()};
304 }
305 
GetPandaFile(std::vector<uint8_t> & data)306 static std::unique_ptr<const File> GetPandaFile(std::vector<uint8_t> &data)
307 {
308     os::mem::ConstBytePtr ptr(reinterpret_cast<std::byte *>(data.data()), data.size(),
309                               [](std::byte *, size_t) noexcept {});
310     return File::OpenFromMemory(std::move(ptr));
311 }
312 
313 class ExtractorTest : public testing::Test {
314 public:
SetUpTestSuite()315     static void SetUpTestSuite()
316     {
317         ItemContainer container;
318         PreparePandaFile(&container);
319         MemoryWriter writer;
320         ASSERT_TRUE(container.Write(&writer));
321 
322         fileData_ = writer.GetData();
323         uFile_ = GetPandaFile(fileData_);
324     }
325 
326     // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
327     static std::unique_ptr<const panda_file::File> uFile_;
328     // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
329     static std::vector<uint8_t> fileData_;
330 };
331 
332 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
333 std::unique_ptr<const panda_file::File> ExtractorTest::uFile_ {nullptr};
334 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
335 std::vector<uint8_t> ExtractorTest::fileData_;
336 
TEST_F(ExtractorTest,DebugInfoTest)337 TEST_F(ExtractorTest, DebugInfoTest)
338 {
339     const panda_file::File *pf = uFile_.get();
340     ASSERT_TRUE(pf != nullptr);
341     DebugInfoExtractor extractor(pf);
342 
343     auto breakpoint1Address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
344     ASSERT_FALSE(breakpoint1Address.first.IsValid());
345     auto [method_id, bytecode_offset] = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 6U});
346     ASSERT_TRUE(method_id.IsValid());
347     ASSERT_EQ(bytecode_offset, 3U);
348 
349     auto sourceLocation = GetSourcePairLocationWrapper(extractor, method_id, 2U);
350     ASSERT_EQ(sourceLocation.path, SOURCE_FILE);
351     ASSERT_EQ(sourceLocation.line, 5U);
352 
353     auto vars = GetLocalVariableInfoWrapper(extractor, method_id, 4U);
354     EXPECT_EQ(vars.size(), 2U);
355     ASSERT_EQ(vars[0].name, "local_1");
356     ASSERT_EQ(vars[0].type, "I");
357     ASSERT_EQ(vars[1].name, "local_2");
358     ASSERT_EQ(vars[1].type, "I");
359 }
360 
TEST_F(ExtractorTest,DebugInfoTestStaticWithRefArg)361 TEST_F(ExtractorTest, DebugInfoTestStaticWithRefArg)
362 {
363     const panda_file::File *pf = uFile_.get();
364     ASSERT_TRUE(pf != nullptr);
365     DebugInfoExtractor extractor(pf);
366 
367     auto breakpoint1Address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
368     ASSERT_FALSE(breakpoint1Address.first.IsValid());
369     // NOLINTNEXTLINE(readability-magic-numbers)
370     auto methodId = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 14U}).first;
371     ASSERT_TRUE(methodId.IsValid());
372 
373     // NOLINTNEXTLINE(readability-magic-numbers)
374     auto vars = GetLocalVariableInfoWrapper(extractor, methodId, 14U);
375     EXPECT_EQ(vars.size(), 0);
376 }
377 
TEST_F(ExtractorTest,DebugInfoTestNonStaticWithRefArg)378 TEST_F(ExtractorTest, DebugInfoTestNonStaticWithRefArg)
379 {
380     const panda_file::File *pf = uFile_.get();
381     ASSERT_TRUE(pf != nullptr);
382     DebugInfoExtractor extractor(pf);
383 
384     auto breakpoint1Address = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 1});
385     ASSERT_FALSE(breakpoint1Address.first.IsValid());
386     // NOLINTNEXTLINE(readability-magic-numbers)
387     auto methodId = GetBreakpointAddressWrapper(extractor, SourcePairLocation {SOURCE_FILE, 16U}).first;
388     ASSERT_TRUE(methodId.IsValid());
389 
390     // NOLINTNEXTLINE(readability-magic-numbers)
391     auto vars = GetLocalVariableInfoWrapper(extractor, methodId, 16U);
392     EXPECT_EQ(vars.size(), 0);
393 }
394 
TEST_F(ExtractorTest,DebugInfoTestColumnNumber)395 TEST_F(ExtractorTest, DebugInfoTestColumnNumber)
396 {
397     const panda_file::File *pf = uFile_.get();
398     ASSERT_TRUE(pf != nullptr);
399     DebugInfoExtractor extractor(pf);
400 
401     auto methods = extractor.GetMethodIdList();
402     for (auto const &methodId : methods) {
403         auto &cnt = extractor.GetColumnNumberTable(methodId);
404 
405         ASSERT(!cnt.empty());
406         if (cnt[0].column == 5U || cnt[0].column == 6U) {
407             ASSERT(cnt.size() == 1);
408         } else {
409             ASSERT(cnt[0].column == 7U);
410             ASSERT(cnt.size() == 5U);
411 
412             auto i = 7;
413             for (auto const &col : cnt) {
414                 EXPECT_EQ(col.column, i++);
415             }
416         }
417     }
418 }
419 }  // namespace ark::panda_file::test
420