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