1 /**
2 * Copyright (c) 2021-2022 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 panda::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
TEST_F(StackWalkerTest,ModifyVreg)122 TEST_F(StackWalkerTest, ModifyVreg)
123 {
124 auto source = R"(
125 .function i32 main() {
126 movi.64 v0, 27
127 try_begin:
128 call.short testa, v0
129 jmp try_end
130 try_end:
131 return
132 .catchall try_begin, try_end, try_end
133 }
134 .function i32 testa(i64 a0) {
135 call.short testb, a0
136 return
137 }
138 .function i32 testb(i64 a0) {
139 call.short testc, a0
140 return
141 }
142 .function i32 testc(i64 a0) {
143 lda a0
144 try_begin:
145 call.short hook # change vregs in all frames
146 jnez exit
147 call.short hook # verify vregs in all frames
148 jmp exit
149 exit:
150 return
151 .catchall try_begin, exit, exit
152 }
153 .function i32 hook() {
154 ldai 1
155 return
156 }
157 )";
158
159 PandaRunner runner;
160 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
161 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
162 runner.GetCompilerOptions().SetCompilerRematConst(false);
163 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::testb|_GLOBAL::hook).*");
164 [[maybe_unused]] static constexpr std::array<uint64_t, 3> FRAME_VALUES = {0x123456789abcdef, 0xaaaabbbbccccdddd,
165 0xabcdef20};
166 static int runCount = 0;
167 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
168 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
169 bool success = false;
170 walker.NextFrame();
171 if (runCount == 0) {
172 bool wasSet = false;
173 HOOK_ASSERT(!walker.IsCFrame(), return 1);
174 success = walker.IterateVRegsWithInfo([&wasSet, &walker](const auto ®Info, const auto ®) {
175 if (!regInfo.IsAccumulator()) {
176 HOOK_ASSERT(reg.GetLong() == 27L, return false);
177 walker.SetVRegValue(regInfo, FRAME_VALUES[0]);
178 wasSet = true;
179 }
180 return true;
181 });
182 HOOK_ASSERT(success, return 1);
183 HOOK_ASSERT(wasSet, return 1);
184
185 walker.NextFrame();
186 HOOK_ASSERT(walker.IsCFrame(), return 1);
187 success = walker.IterateVRegsWithInfo([&walker](const auto ®Info, const auto ®) {
188 if (!regInfo.IsAccumulator()) {
189 HOOK_ASSERT(reg.GetLong() == 27L, return false);
190 walker.SetVRegValue(regInfo, FRAME_VALUES[1]);
191 }
192 return true;
193 });
194 HOOK_ASSERT(success, return 1);
195
196 walker.NextFrame();
197 HOOK_ASSERT(walker.IsCFrame(), return 1);
198 success = walker.IterateVRegsWithInfo([&walker](const auto ®Info, const auto ®) {
199 if (!regInfo.IsAccumulator()) {
200 HOOK_ASSERT(reg.GetLong() == 27L, return true);
201 walker.SetVRegValue(regInfo, FRAME_VALUES[2U]);
202 }
203 return true;
204 });
205 HOOK_ASSERT(success, return 1);
206 } else if (runCount == 1) {
207 HOOK_ASSERT(!walker.IsCFrame(), return 1);
208 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
209 if (!regInfo.IsAccumulator()) {
210 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[0]), return true);
211 }
212 return true;
213 });
214 HOOK_ASSERT(success, return 1);
215
216 walker.NextFrame();
217 HOOK_ASSERT(walker.IsCFrame(), return 1);
218 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
219 if (!regInfo.IsAccumulator()) {
220 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[1]), return true);
221 }
222 return true;
223 });
224 HOOK_ASSERT(success, return 1);
225
226 walker.NextFrame();
227 HOOK_ASSERT(walker.IsCFrame(), return 1);
228 success = walker.IterateVRegsWithInfo([](const auto ®Info, const auto ®) {
229 if (!regInfo.IsAccumulator()) {
230 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(FRAME_VALUES[2U]), return true);
231 }
232 return true;
233 });
234 HOOK_ASSERT(success, return 1);
235 } else {
236 return 1;
237 }
238 runCount++;
239 return 0;
240 });
241 ASSERT_EQ(runCount, 2_I);
242 }
243
TestModifyManyVregs(bool isCompiled)244 void StackWalkerTest::TestModifyManyVregs(bool isCompiled)
245 {
246 auto source = 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 static bool firstRun;
320 static bool compiled;
321
322 PandaRunner runner;
323 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
324 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
325
326 if (!isCompiled) {
327 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main)(?!_GLOBAL::test)(?!_GLOBAL::hook).*");
328 }
329
330 firstRun = true;
331 compiled = isCompiled;
332 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
333 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
334
335 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::stub", return 1);
336 walker.NextFrame();
337 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::test", return 1);
338 HOOK_ASSERT(walker.IsCFrame() == compiled, return 1);
339
340 int regIndex = 1;
341 bool success = false;
342 if (firstRun) {
343 auto storage = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage();
344 StackWalkerTest::globalObj_ =
345 storage->Add(panda::mem::AllocateNullifiedPayloadString(1), mem::Reference::ObjectType::GLOBAL);
346 }
347 auto obj = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage()->Get(StackWalkerTest::globalObj_);
348 if (firstRun) {
349 success = walker.IterateVRegsWithInfo([®Index, &walker, &obj](const auto ®Info, const auto ®) {
350 if (!regInfo.IsAccumulator()) {
351 if (reg.HasObject()) {
352 HOOK_ASSERT(reg.GetReference() != nullptr, return false);
353 walker.SetVRegValue(regInfo, obj);
354 } else if (regInfo.GetLocation() != VRegInfo::Location::CONSTANT) {
355 // frame_size is more than nregs_ now for inst `V4_V4_ID16` and `V4_V4_V4_V4_ID16`
356 auto regIdx = regInfo.GetIndex();
357 if (regIdx < walker.GetMethod()->GetNumVregs()) {
358 HOOK_ASSERT(regIdx == reg.GetLong(), return false);
359 }
360 // NOLINTNEXTLINE(readability-magic-numbers)
361 walker.SetVRegValue(regInfo, regIdx + 100000000000L);
362 }
363 regIndex++;
364 }
365 return true;
366 });
367 HOOK_ASSERT(success, return 1);
368 HOOK_ASSERT(regIndex >= 32_I, return 1);
369 firstRun = false;
370 } else {
371 success = walker.IterateVRegsWithInfo([®Index, &obj](const auto ®Info, const auto ®) {
372 if (!regInfo.IsAccumulator()) {
373 if (reg.HasObject()) {
374 HOOK_ASSERT((reg.GetReference() == reinterpret_cast<ObjectHeader *>(Low32Bits(obj))),
375 return false);
376 } else {
377 if (regInfo.GetLocation() != VRegInfo::Location::CONSTANT) {
378 HOOK_ASSERT(reg.GetLong() == (regInfo.GetIndex() + 100000000000), return false);
379 }
380 regIndex++;
381 }
382 }
383 return true;
384 });
385 HOOK_ASSERT(success, return 1);
386 HOOK_ASSERT(regIndex >= 32_I, return 1);
387 }
388
389 HOOK_ASSERT(success, return 1);
390 return 0;
391 });
392 }
393
TEST_F(StackWalkerTest,ModifyMultipleVregs)394 TEST_F(StackWalkerTest, ModifyMultipleVregs)
395 {
396 if constexpr (ArchTraits<RUNTIME_ARCH>::SUPPORT_DEOPTIMIZATION) {
397 TestModifyManyVregs(true);
398 TestModifyManyVregs(false);
399 }
400 }
401
TEST_F(StackWalkerTest,ThrowExceptionThroughMultipleFrames)402 TEST_F(StackWalkerTest, ThrowExceptionThroughMultipleFrames)
403 {
404 auto source = R"(
405 .record E {}
406
407 .function u1 f4() {
408 newobj v0, E
409 throw v0
410 return
411 }
412
413 .function u1 f3() {
414 call f4
415 return
416 }
417
418 .function u1 f2() {
419 call f3
420 return
421 }
422
423 .function u1 f1() {
424 call f2
425 return
426 }
427
428 .function u1 main() {
429 try_begin:
430 movi v0, 123
431 call f1
432 ldai 1
433 return
434 try_end:
435
436 catch_block1_begin:
437 movi v1, 123
438 lda v0
439 jne v1, exit_1
440 ldai 0
441 return
442 exit_1:
443 ldai 1
444 return
445
446 .catch E, try_begin, try_end, catch_block1_begin
447 }
448 )";
449
450 PandaRunner runner;
451 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
452 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
453
454 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
455 runner.Run(source, nullptr);
456
457 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
458 runner.Run(source, nullptr);
459
460 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f2).*");
461 runner.Run(source, nullptr);
462
463 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4).*");
464 runner.Run(source, nullptr);
465
466 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
467 runner.Run(source, nullptr);
468
469 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
470 runner.Run(source, nullptr);
471
472 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
473 runner.Run(source, nullptr);
474
475 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
476 runner.Run(source, nullptr);
477
478 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f2).*");
479 runner.Run(source, nullptr);
480
481 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4).*");
482 runner.Run(source, nullptr);
483
484 runner.GetCompilerOptions().SetCompilerRegex(".*");
485 runner.Run(source, nullptr);
486
487 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
488 runner.Run(source, nullptr);
489
490 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2).*");
491 runner.Run(source, nullptr);
492
493 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f2).*");
494 runner.Run(source, nullptr);
495
496 runner.GetCompilerOptions().SetCompilerRegex(".*");
497 runner.Run(source, nullptr);
498 }
499
TEST_F(StackWalkerTest,CatchInCompiledCode)500 TEST_F(StackWalkerTest, CatchInCompiledCode)
501 {
502 auto source = R"(
503 .record panda.String <external>
504 .record panda.ArrayIndexOutOfBoundsException <external>
505
506 .function i32 f2() {
507 movi v0, 4
508 newarr v1, v0, i64[]
509 ldai 42
510 ldarr v1 # BoundsException
511 return
512 }
513
514 .function i32 f1() {
515 try_begin:
516 call f2
517 ldai 1
518 return
519 try_end:
520
521 catch_block1_begin:
522 ldai 123
523 return
524
525 .catch panda.ArrayIndexOutOfBoundsException, try_begin, try_end, catch_block1_begin
526 }
527
528 .function u1 main() {
529 call f1
530 movi v0, 123
531 jne v0, exit_1
532 ldai 0
533 return
534 exit_1:
535 ldai 1
536 return
537 }
538 )";
539
540 PandaRunner runner;
541 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
542 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
543
544 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f2).*");
545 runner.Run(source, nullptr);
546
547 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1).*");
548 runner.Run(source, nullptr);
549
550 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
551 runner.Run(source, nullptr);
552
553 runner.GetCompilerOptions().SetCompilerRegex(".*");
554 runner.Run(source, nullptr);
555 }
556
557 } // namespace panda::test
558