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 <compiler/compiler_options.h>
17 #include <gtest/gtest.h>
18
19 #include <bitset>
20 #include <vector>
21
22 #include "assembly-emitter.h"
23 #include "assembly-parser.h"
24 #include "code_info/code_info_builder.h"
25 #include "libpandabase/utils/cframe_layout.h"
26 #include "libpandabase/utils/utils.h"
27 #include "runtime/bridge/bridge.h"
28 #include "runtime/include/runtime.h"
29 #include "runtime/include/stack_walker-inl.h"
30 #include "runtime/include/thread_scopes.h"
31 #include "runtime/mem/refstorage/global_object_storage.h"
32 #include "runtime/tests/test_utils.h"
33 #include "compiler/tests/panda_runner.h"
34
35 namespace ark::test {
36
37 // NOLINTNEXTLINE(google-build-using-namespace)
38 using namespace compiler;
39
40 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
41 #define HOOK_ASSERT(cond, action) \
42 do { \
43 if (!(cond)) { \
44 std::cerr << "ASSERT FAILED(" << __LINE__ << "): " << #cond << std::endl; \
45 action; \
46 } \
47 } while (0)
48
49 class StackWalkerTest : public testing::Test {
50 public:
51 using Callback = int (*)(uintptr_t, uintptr_t);
52
StackWalkerTest()53 StackWalkerTest()
54 : defaultCompilerNonOptimizing_(g_options.IsCompilerNonOptimizing()),
55 defaultCompilerRegex_(g_options.GetCompilerRegex())
56 {
57 }
58
~StackWalkerTest()59 ~StackWalkerTest() override
60 {
61 g_options.SetCompilerNonOptimizing(defaultCompilerNonOptimizing_);
62 g_options.SetCompilerRegex(defaultCompilerRegex_);
63 }
64
65 NO_COPY_SEMANTIC(StackWalkerTest);
66 NO_MOVE_SEMANTIC(StackWalkerTest);
67
SetUp()68 void SetUp() override
69 {
70 #ifndef PANDA_COMPILER_ENABLE
71 GTEST_SKIP();
72 #endif
73 }
74
75 void TestModifyManyVregs(bool isCompiled);
76
GetMethod(std::string_view methodName)77 static Method *GetMethod(std::string_view methodName)
78 {
79 PandaString descriptor;
80 auto *thread = MTManagedThread::GetCurrent();
81 thread->ManagedCodeBegin();
82 auto *extension = Runtime::GetCurrent()->GetClassLinker()->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
83 auto cls = extension->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
84 thread->ManagedCodeEnd();
85 ASSERT(cls);
86 return cls->GetDirectMethod(utf::CStringAsMutf8(methodName.data()));
87 }
88
89 static mem::Reference *globalObj_;
90
91 private:
92 bool defaultCompilerNonOptimizing_;
93 std::string defaultCompilerRegex_;
94 };
95
96 mem::Reference *StackWalkerTest::globalObj_;
97
98 template <typename T>
ConvertToU64(T val)99 uint64_t ConvertToU64(T val)
100 {
101 if constexpr (sizeof(T) == sizeof(uint32_t)) {
102 return bit_cast<uint32_t>(val);
103 } else if constexpr (sizeof(T) == sizeof(uint64_t)) {
104 return bit_cast<uint64_t>(val);
105 } else {
106 return static_cast<uint64_t>(val);
107 }
108 }
109
110 template <Arch ARCH>
ToCalleeRegister(size_t reg)111 int32_t ToCalleeRegister(size_t reg)
112 {
113 return reg + GetFirstCalleeReg(ARCH, false);
114 }
115
116 template <Arch ARCH>
ToCalleeFpRegister(size_t reg)117 int32_t ToCalleeFpRegister(size_t reg)
118 {
119 return reg + GetFirstCalleeReg(ARCH, true);
120 }
121
122 static auto g_modifyVregSource = R"(
123 .function i32 main() {
124 movi.64 v0, 27
125 try_begin:
126 call.short testa, v0
127 jmp try_end
128 try_end:
129 return
130 .catchall try_begin, try_end, try_end
131 }
132 .function i32 testa(i64 a0) {
133 call.short testb, a0
134 return
135 }
136 .function i32 testb(i64 a0) {
137 call.short testc, a0
138 return
139 }
140 .function i32 testc(i64 a0) {
141 lda a0
142 try_begin:
143 call.short hook # change vregs in all frames
144 jnez exit
145 call.short hook # verify vregs in all frames
146 jmp exit
147 exit:
148 return
149 .catchall try_begin, exit, exit
150 }
151 .function i32 hook() {
152 ldai 1
153 return
154 }
155 )";
156
TEST_F(StackWalkerTest,ModifyVreg)157 TEST_F(StackWalkerTest, ModifyVreg)
158 {
159 auto source = g_modifyVregSource;
160
161 PandaRunner runner;
162 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
163 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
164 runner.GetCompilerOptions().SetCompilerRematConst(false);
165 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::testb|_GLOBAL::hook).*");
166 [[maybe_unused]] static constexpr std::array<uint64_t, 3> FRAME_VALUES = {0x123456789abcdef, 0xaaaabbbbccccdddd,
167 0xabcdef20};
168 static int runCount = 0;
169 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
170 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
171 bool success = false;
172 walker.NextFrame();
173 if (runCount == 0) {
174 bool wasSet = false;
175 HOOK_ASSERT(!walker.IsCFrame(), return 1);
176 success = walker.IterateVRegsWithInfo([&wasSet, &walker](const auto ®Info, const auto ®) {
177 if (!regInfo.IsAccumulator()) {
178 HOOK_ASSERT(reg.GetLong() == 27L, return false);
179 walker.SetVRegValue(regInfo, FRAME_VALUES[0]);
180 wasSet = true;
181 }
182 return true;
183 });
184 HOOK_ASSERT(success, return 1);
185 HOOK_ASSERT(wasSet, return 1);
186
187 walker.NextFrame();
188 HOOK_ASSERT(walker.IsCFrame(), return 1);
189 success = walker.IterateVRegsWithInfo([&walker](const auto ®Info, const auto ®) {
190 if (!regInfo.IsAccumulator()) {
191 HOOK_ASSERT(reg.GetLong() == 27L, return false);
192 walker.SetVRegValue(regInfo, FRAME_VALUES[1]);
193 }
194 return true;
195 });
196 HOOK_ASSERT(success, return 1);
197
198 walker.NextFrame();
199 HOOK_ASSERT(walker.IsCFrame(), return 1);
200 success = walker.IterateVRegsWithInfo([&walker](const auto ®Info, const auto ®) {
201 if (!regInfo.IsAccumulator()) {
202 HOOK_ASSERT(reg.GetLong() == 27L, return true);
203 walker.SetVRegValue(regInfo, FRAME_VALUES[2U]);
204 }
205 return true;
206 });
207 HOOK_ASSERT(success, return 1);
208 } else if (runCount == 1) {
209 HOOK_ASSERT(!walker.IsCFrame(), return 1);
210 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
211 if (!regInfo.IsAccumulator()) {
212 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[0]), return true);
213 }
214 return true;
215 });
216 HOOK_ASSERT(success, return 1);
217
218 walker.NextFrame();
219 HOOK_ASSERT(walker.IsCFrame(), return 1);
220 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
221 if (!regInfo.IsAccumulator()) {
222 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[1]), return true);
223 }
224 return true;
225 });
226 HOOK_ASSERT(success, return 1);
227
228 walker.NextFrame();
229 HOOK_ASSERT(walker.IsCFrame(), return 1);
230 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
231 if (!regInfo.IsAccumulator()) {
232 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[2U]), return true);
233 }
234 return true;
235 });
236 HOOK_ASSERT(success, return 1);
237 } else {
238 return 1;
239 }
240 runCount++;
241 return 0;
242 });
243 ASSERT_EQ(runCount, 2_I);
244 }
245
246 static auto g_testModifyManyVregsSource = R"(
247 .function i32 main() {
248 movi.64 v0, 5
249 try_begin:
250 call.short test
251 jmp try_end
252 try_end:
253 return
254
255 .catchall try_begin, try_end, try_end
256 }
257
258 .function i32 test() {
259 movi.64 v1, 1
260 movi.64 v2, 2
261 movi.64 v3, 3
262 movi.64 v4, 4
263 movi.64 v5, 5
264 movi.64 v6, 6
265 movi.64 v7, 7
266 movi.64 v8, 8
267 movi.64 v9, 9
268 movi.64 v10, 10
269 movi.64 v11, 11
270 movi.64 v12, 12
271 movi.64 v13, 13
272 movi.64 v14, 14
273 movi.64 v15, 15
274 movi.64 v16, 16
275 movi.64 v17, 17
276 movi.64 v18, 18
277 movi.64 v19, 19
278 movi.64 v20, 20
279 movi.64 v21, 21
280 movi.64 v22, 22
281 movi.64 v23, 23
282 movi.64 v24, 24
283 movi.64 v25, 25
284 movi.64 v26, 26
285 movi.64 v27, 27
286 movi.64 v28, 28
287 movi.64 v29, 29
288 movi.64 v30, 30
289 movi.64 v31, 31
290 try_begin:
291 mov v0, v31
292 newarr v0, v0, i32[]
293 call.short stub
294 jmp try_end
295 try_end:
296 return
297
298 .catchall try_begin, try_end, try_end
299 }
300
301 .function i32 stub() {
302 try_begin:
303 call.short hook # change vregs in all frames
304 jnez exit
305 call.short hook # verify vregs in all frames
306 jmp exit
307 exit:
308 return
309
310 .catchall try_begin, exit, exit
311 }
312
313 .function i32 hook() {
314 ldai 1
315 return
316 }
317 )";
318
319 template <typename VRegRef>
FirstRunModifyVregs(int * regIndex,StackWalker * walker,ObjectHeader * obj,const VRegInfo * regInfo,const VRegRef & reg)320 static bool FirstRunModifyVregs(int *regIndex, StackWalker *walker, ObjectHeader *obj, const VRegInfo *regInfo,
321 const VRegRef ®)
322 {
323 if (!regInfo->IsAccumulator()) {
324 if (reg.HasObject()) {
325 HOOK_ASSERT(reg.GetReference() != nullptr, return false);
326 walker->SetVRegValue(*regInfo, obj);
327 } else if (regInfo->GetLocation() != VRegInfo::Location::CONSTANT) {
328 // frame_size is more than nregs_ now for inst `V4_V4_ID16` and `V4_V4_V4_V4_ID16`
329 auto regIdx = regInfo->GetIndex();
330 if (regIdx < walker->GetMethod()->GetNumVregs()) {
331 HOOK_ASSERT(regIdx == reg.GetLong(), return false);
332 }
333 // NOLINTNEXTLINE(readability-magic-numbers)
334 walker->SetVRegValue(*regInfo, regIdx + 100000000000L);
335 }
336 (*regIndex)++;
337 }
338 return true;
339 }
340
341 template <typename VRegRef>
CheckVregs(int * regIndex,ObjectHeader * obj,const VRegInfo & regInfo,const VRegRef & reg)342 static bool CheckVregs(int *regIndex, ObjectHeader *obj, const VRegInfo ®Info, const VRegRef ®)
343 {
344 if (!regInfo.IsAccumulator()) {
345 if (reg.HasObject()) {
346 HOOK_ASSERT((reg.GetReference() == reinterpret_cast<ObjectHeader *>(Low32Bits(obj))), return false);
347 } else {
348 if (regInfo.GetLocation() != VRegInfo::Location::CONSTANT) {
349 HOOK_ASSERT(reg.GetLong() == (regInfo.GetIndex() + 100000000000L), return false);
350 }
351 (*regIndex)++;
352 }
353 }
354 return true;
355 }
356
TestModifyManyVregs(bool isCompiled)357 void StackWalkerTest::TestModifyManyVregs(bool isCompiled)
358 {
359 auto source = g_testModifyManyVregsSource;
360 static bool firstRun;
361 static bool compiled;
362
363 PandaRunner runner;
364 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
365 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
366
367 if (!isCompiled) {
368 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main)(?!_GLOBAL::test)(?!_GLOBAL::hook).*");
369 }
370
371 firstRun = true;
372 compiled = isCompiled;
373 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
374 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
375
376 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::stub", return 1);
377 walker.NextFrame();
378 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::test", return 1);
379 HOOK_ASSERT(walker.IsCFrame() == compiled, return 1);
380
381 int regIndex = 1;
382 bool success = false;
383 if (firstRun) {
384 auto storage = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage();
385 StackWalkerTest::globalObj_ =
386 storage->Add(ark::mem::AllocateNullifiedPayloadString(1), mem::Reference::ObjectType::GLOBAL);
387 }
388 auto obj = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage()->Get(StackWalkerTest::globalObj_);
389 if (firstRun) {
390 success = walker.IterateVRegsWithInfo([®Index, &walker, &obj](const auto ®Info, const auto ®) {
391 return FirstRunModifyVregs(®Index, &walker, obj, ®Info, reg);
392 });
393 HOOK_ASSERT(success, return 1);
394 HOOK_ASSERT(regIndex >= 32_I, return 1);
395 firstRun = false;
396 } else {
397 success = walker.IterateVRegsWithInfo([®Index, &obj](const auto ®Info, const auto ®) {
398 return CheckVregs(®Index, obj, regInfo, reg);
399 });
400 HOOK_ASSERT(success, return 1);
401 HOOK_ASSERT(regIndex >= 32_I, return 1);
402 }
403
404 HOOK_ASSERT(success, return 1);
405 return 0;
406 });
407 }
408
TEST_F(StackWalkerTest,ModifyMultipleVregs)409 TEST_F(StackWalkerTest, ModifyMultipleVregs)
410 {
411 if constexpr (ArchTraits<RUNTIME_ARCH>::SUPPORT_DEOPTIMIZATION) {
412 TestModifyManyVregs(true);
413 TestModifyManyVregs(false);
414 }
415 }
416
417 static auto g_throwExceptionThroughMultipleFramesSource = R"(
418 .record E {}
419
420 .function u1 f4() {
421 newobj v0, E
422 throw v0
423 return
424 }
425
426 .function u1 f3() {
427 call f4
428 return
429 }
430
431 .function u1 f2() {
432 call f3
433 return
434 }
435
436 .function u1 f1() {
437 call f2
438 return
439 }
440
441 .function u1 main() {
442 try_begin:
443 movi v0, 123
444 call f1
445 ldai 1
446 return
447 try_end:
448
449 catch_block1_begin:
450 movi v1, 123
451 lda v0
452 jne v1, exit_1
453 ldai 0
454 return
455 exit_1:
456 ldai 1
457 return
458
459 .catch E, try_begin, try_end, catch_block1_begin
460 }
461 )";
462
TEST_F(StackWalkerTest,ThrowExceptionThroughMultipleFrames)463 TEST_F(StackWalkerTest, ThrowExceptionThroughMultipleFrames)
464 {
465 auto source = g_throwExceptionThroughMultipleFramesSource;
466
467 PandaRunner runner;
468 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
469 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
470
471 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
472 runner.Run(source, nullptr);
473
474 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
475 runner.Run(source, nullptr);
476
477 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f2).*");
478 runner.Run(source, nullptr);
479
480 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4).*");
481 runner.Run(source, nullptr);
482
483 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
484 runner.Run(source, nullptr);
485
486 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
487 runner.Run(source, nullptr);
488
489 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
490 runner.Run(source, nullptr);
491
492 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
493 runner.Run(source, nullptr);
494
495 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f2).*");
496 runner.Run(source, nullptr);
497
498 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4).*");
499 runner.Run(source, nullptr);
500
501 runner.GetCompilerOptions().SetCompilerRegex(".*");
502 runner.Run(source, nullptr);
503
504 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
505 runner.Run(source, nullptr);
506
507 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2).*");
508 runner.Run(source, nullptr);
509
510 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f2).*");
511 runner.Run(source, nullptr);
512
513 runner.GetCompilerOptions().SetCompilerRegex(".*");
514 runner.Run(source, nullptr);
515 }
516
TEST_F(StackWalkerTest,CatchInCompiledCode)517 TEST_F(StackWalkerTest, CatchInCompiledCode)
518 {
519 auto source = R"(
520 .record panda.String <external>
521 .record panda.ArrayIndexOutOfBoundsException <external>
522
523 .function i32 f2() {
524 movi v0, 4
525 newarr v1, v0, i64[]
526 ldai 42
527 ldarr v1 # BoundsException
528 return
529 }
530
531 .function i32 f1() {
532 try_begin:
533 call f2
534 ldai 1
535 return
536 try_end:
537
538 catch_block1_begin:
539 ldai 123
540 return
541
542 .catch panda.ArrayIndexOutOfBoundsException, try_begin, try_end, catch_block1_begin
543 }
544
545 .function u1 main() {
546 call f1
547 movi v0, 123
548 jne v0, exit_1
549 ldai 0
550 return
551 exit_1:
552 ldai 1
553 return
554 }
555 )";
556
557 PandaRunner runner;
558 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
559 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
560
561 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f2).*");
562 runner.Run(source, nullptr);
563
564 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1).*");
565 runner.Run(source, nullptr);
566
567 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
568 runner.Run(source, nullptr);
569
570 runner.GetCompilerOptions().SetCompilerRegex(".*");
571 runner.Run(source, nullptr);
572 }
573
574 } // namespace ark::test
575