1 /**
2 * Copyright (c) 2023-2025 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 "debugger/debug_info_cache.h"
17
18 #include "gtest/gtest.h"
19
20 #include "assembly-emitter.h"
21 #include "assembly-parser.h"
22 #include "runtime.h"
23 #include "runtime_options.h"
24 #include "thread_scopes.h"
25
26 #include "test_frame.h"
27
28 // NOLINTBEGIN
29
30 namespace ark::tooling::inspector::test {
31
32 static constexpr const char *g_source = R"(
33 .record Test {}
34
35 .function i32 Test.foo(u64 a0, u64 a1) {
36 mov v0, v1 # line 2, offset 0, 1
37 mov v100, v101 # line 3, offset 2, 3, 4
38 movi v0, 4 # line 4, offset 5, 6
39 ldai 222 # line 5, offset 7, 8, 9
40 return # line 6, offset 10
41 }
42 )";
43
44 class DebugInfoCacheTest : public testing::Test {
45 protected:
SetUpTestSuite()46 static void SetUpTestSuite()
47 {
48 pandasm::Parser p;
49
50 auto res = p.Parse(g_source, SOURCE_FILE_NAME.data());
51 ASSERT_TRUE(res.HasValue());
52 ASSERT_TRUE(pandasm::AsmEmitter::Emit(ASM_FILE_NAME.data(), res.Value()));
53 auto pf = panda_file::OpenPandaFile(ASM_FILE_NAME);
54 ASSERT_NE(pf, nullptr);
55
56 cache.AddPandaFile(*pf, true);
57
58 RuntimeOptions options;
59 options.SetShouldInitializeIntrinsics(false);
60 options.SetShouldLoadBootPandaFiles(false);
61 Runtime::Create(options);
62
63 thread_ = ManagedThread::GetCurrent();
64 {
65 ScopedManagedCodeThread s(thread_);
66
67 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
68 classLinker->AddPandaFile(std::move(pf));
69
70 PandaString descriptorHolder;
71 const auto *descriptor = ClassHelper::GetDescriptor(utf::CStringAsMutf8("Test"), &descriptorHolder);
72 auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
73 Class *klass = ext->GetClass(descriptor, true, ext->GetBootContext());
74 ASSERT_NE(klass, nullptr);
75
76 auto methods = klass->GetMethods();
77 ASSERT_EQ(methods.size(), 1);
78 methodFoo = &methods[0];
79 }
80 }
81
TearDownTestSuite()82 static void TearDownTestSuite()
83 {
84 Runtime::Destroy();
85 }
86
87 static constexpr std::string_view ASM_FILE_NAME = "source.abc";
88 // This test intentionally sets empty source file name to ensure that disassembly is used for debug info
89 static constexpr std::string_view SOURCE_FILE_NAME = "";
90 static DebugInfoCache cache;
91 static ManagedThread *thread_;
92 static Method *methodFoo;
93 };
94
95 DebugInfoCache DebugInfoCacheTest::cache {};
96 ManagedThread *DebugInfoCacheTest::thread_ = nullptr;
97 Method *DebugInfoCacheTest::methodFoo = nullptr;
98
TEST_F(DebugInfoCacheTest,GetCurrentLineLocations)99 TEST_F(DebugInfoCacheTest, GetCurrentLineLocations)
100 {
101 auto fr0 = TestFrame(methodFoo, 2U); // offset 2, line 3 of function
102 auto fr1 = TestFrame(methodFoo, 6U); // offset 6, line 4 of function
103 auto fr2 = TestFrame(methodFoo, 10U); // offset 10, line 6 of function
104
105 auto curr = cache.GetCurrentLineLocations(fr0);
106 ASSERT_EQ(curr.size(), 3U);
107 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 2U)), curr.end());
108 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 3U)), curr.end());
109 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 4U)), curr.end());
110
111 curr = cache.GetCurrentLineLocations(fr1);
112 ASSERT_EQ(curr.size(), 2U);
113 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 5U)), curr.end());
114 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 6U)), curr.end());
115
116 curr = cache.GetCurrentLineLocations(fr2);
117 ASSERT_EQ(curr.size(), 1);
118 ASSERT_NE(curr.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 10U)), curr.end());
119 }
120
TEST_F(DebugInfoCacheTest,GetLocals)121 TEST_F(DebugInfoCacheTest, GetLocals)
122 {
123 static constexpr size_t ARGUMENTS_COUNT = 2U;
124 static constexpr size_t LOCALS_COUNT = 103U;
125
126 auto fr0 = TestFrame(methodFoo, 2U); // offset 2, line 3 of function
127
128 for (size_t i = 0; i < ARGUMENTS_COUNT; i++) {
129 fr0.SetArgument(i, i + 1);
130 fr0.SetArgumentKind(i, PtFrame::RegisterKind::PRIMITIVE);
131 }
132 for (size_t i = 0; i < LOCALS_COUNT; i++) {
133 fr0.SetVReg(i, i);
134 fr0.SetVRegKind(i, PtFrame::RegisterKind::PRIMITIVE);
135 }
136 auto mapLocals = cache.GetLocals(fr0);
137 ASSERT_EQ(ARGUMENTS_COUNT + LOCALS_COUNT, mapLocals.size());
138
139 EXPECT_NO_THROW(mapLocals.at("a0"));
140 EXPECT_NO_THROW(mapLocals.at("a1"));
141 EXPECT_NO_THROW(mapLocals.at("v101"));
142
143 ASSERT_EQ(mapLocals.at("a0").GetAsU64(), 1U);
144 ASSERT_EQ(mapLocals.at("a1").GetAsU64(), 2U);
145 ASSERT_EQ(mapLocals.at("v101").GetAsU64(), 101U);
146 }
147
TEST_F(DebugInfoCacheTest,GetSourceLocation)148 TEST_F(DebugInfoCacheTest, GetSourceLocation)
149 {
150 auto fr0 = TestFrame(methodFoo, 2U); // offset 2, line 3 of function
151 auto fr1 = TestFrame(methodFoo, 6U); // offset 6, line 4 of function
152
153 std::string_view disasm_file;
154 std::string_view method_name;
155 size_t line_number = 0;
156
157 cache.GetSourceLocation(fr0, disasm_file, method_name, line_number);
158 ASSERT_NE(disasm_file.find(ASM_FILE_NAME.data()), std::string::npos);
159 ASSERT_EQ(method_name, "foo");
160 ASSERT_EQ(line_number, 3U);
161
162 cache.GetSourceLocation(fr1, disasm_file, method_name, line_number);
163 ASSERT_NE(disasm_file.find(ASM_FILE_NAME.data()), std::string::npos);
164 ASSERT_EQ(method_name, "foo");
165 ASSERT_EQ(line_number, 4U);
166
167 auto set_locs = cache.GetContinueToLocations(disasm_file, 4U);
168 ASSERT_EQ(set_locs.size(), 2U);
169 ASSERT_NE(set_locs.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 6U)), set_locs.end());
170 ASSERT_NE(set_locs.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 5U)), set_locs.end());
171
172 set_locs = cache.GetContinueToLocations(disasm_file, 6U);
173 ASSERT_EQ(set_locs.size(), 1);
174 ASSERT_NE(set_locs.find(PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 10U)), set_locs.end());
175
176 set_locs = cache.GetContinueToLocations(disasm_file, 1);
177 ASSERT_EQ(set_locs.size(), 0);
178
179 auto valid_locs = cache.GetValidLineNumbers(disasm_file, 0U, 100U, false);
180 ASSERT_EQ(valid_locs.size(), 5U);
181
182 ASSERT_NE(valid_locs.find(2U), valid_locs.end());
183 ASSERT_NE(valid_locs.find(3U), valid_locs.end());
184 ASSERT_NE(valid_locs.find(4U), valid_locs.end());
185 ASSERT_NE(valid_locs.find(5U), valid_locs.end());
186 ASSERT_NE(valid_locs.find(6U), valid_locs.end());
187
188 auto s = cache.GetSourceCode(disasm_file);
189 ASSERT_NE(s.find(".function i32 Test.foo(u64 a0, u64 a1)"), std::string::npos);
190
191 s = cache.GetSourceCode("source.pa");
192 ASSERT_TRUE(s.empty());
193
194 std::set<std::string_view> sets;
195 auto breaks = cache.GetBreakpointLocations([](auto) { return true; }, 4U, sets);
196 ASSERT_EQ(breaks.size(), 1);
197 ASSERT_EQ(sets.size(), 1);
198 ASSERT_EQ(*sets.begin(), disasm_file);
199
200 ASSERT_NE(std::find(breaks.begin(), breaks.end(), PtLocation(ASM_FILE_NAME.data(), methodFoo->GetFileId(), 5U)),
201 breaks.end());
202 }
203
204 } // namespace ark::tooling::inspector::test
205
206 // NOLINTEND
207