Skip to content

Commit 6e9d5b3

Browse files
Merge pull request #9154 from felipepiovezan/felipe/cherry-pick-step_out_preliminaries
[cherry-pick][lldb][Swift] Small steps towards fixing StepOut in async functions
2 parents 4a1df21 + fdcd0e7 commit 6e9d5b3

File tree

7 files changed

+215
-3
lines changed

7 files changed

+215
-3
lines changed

lldb/source/Target/StackID.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,41 @@ bool lldb_private::operator!=(const StackID &lhs, const StackID &rhs) {
7373
return lhs_scope != rhs_scope;
7474
}
7575

76-
bool StackID::IsYounger(const StackID &lhs, const StackID &rhs,
77-
Process &process) {
76+
// BEGIN SWIFT
77+
enum class HeapCFAComparisonResult { Younger, Older, NoOpinion };
78+
/// If at least one of the stack IDs (lhs, rhs) is a heap CFA, perform the
79+
/// swift-specific async frame comparison. Otherwise, returns NoOpinion.
80+
static HeapCFAComparisonResult
81+
IsYoungerHeapCFAs(const StackID &lhs, const StackID &rhs, Process &process) {
82+
const bool lhs_cfa_on_stack = lhs.IsCFAOnStack(process);
83+
const bool rhs_cfa_on_stack = rhs.IsCFAOnStack(process);
84+
if (lhs_cfa_on_stack && rhs_cfa_on_stack)
85+
return HeapCFAComparisonResult::NoOpinion;
86+
7887
// FIXME: rdar://76119439
7988
// At the boundary between an async parent frame calling a regular child
8089
// frame, the CFA of the parent async function is a heap addresses, and the
8190
// CFA of concrete child function is a stack address. Therefore, if lhs is
8291
// on stack, and rhs is not, lhs is considered less than rhs, independent of
8392
// address values.
84-
if (lhs.IsCFAOnStack(process) && !rhs.IsCFAOnStack(process))
93+
if (lhs_cfa_on_stack && !rhs_cfa_on_stack)
94+
return HeapCFAComparisonResult::Younger;
95+
return HeapCFAComparisonResult::NoOpinion;
96+
}
97+
// END SWIFT
98+
99+
bool StackID::IsYounger(const StackID &lhs, const StackID &rhs,
100+
Process &process) {
101+
// BEGIN SWIFT
102+
switch (IsYoungerHeapCFAs(lhs, rhs, process)) {
103+
case HeapCFAComparisonResult::Younger:
85104
return true;
105+
case HeapCFAComparisonResult::Older:
106+
return false;
107+
case HeapCFAComparisonResult::NoOpinion:
108+
break;
109+
}
110+
// END SWIFT
86111

87112
const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress();
88113
const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
import lldbsuite.test.lldbtest as lldbtest
4+
import lldbsuite.test.lldbutil as lldbutil
5+
import re
6+
7+
8+
class TestCase(lldbtest.TestBase):
9+
10+
def check_and_get_frame_names(self, process):
11+
frames = process.GetSelectedThread().frames
12+
# Expected frames:
13+
# ASYNC___0___ <- ASYNC___1___ <- ASYNC___2___ <- ASYNC___3___ <- Main
14+
num_frames = 5
15+
func_names = [frame.GetFunctionName() for frame in frames[:num_frames]]
16+
for idx in range(num_frames - 1):
17+
self.assertIn(f"ASYNC___{idx}___", func_names[idx])
18+
self.assertIn("Main", func_names[num_frames - 1])
19+
return func_names
20+
21+
def step_out_checks(self, thread, expected_func_names):
22+
# Keep stepping out, comparing the top frame's name with the expected name.
23+
for expected_func_name in expected_func_names:
24+
error = lldb.SBError()
25+
thread.StepOut(error)
26+
self.assertSuccess(error, "step out failed")
27+
stop_reason = thread.GetStopReason()
28+
self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete)
29+
self.assertEqual(thread.frames[0].GetFunctionName(), expected_func_name)
30+
31+
@swiftTest
32+
@skipIf(oslist=["windows", "linux"])
33+
@skipIf("rdar://133849022")
34+
def test(self):
35+
"""Test `frame variable` in async functions"""
36+
self.build()
37+
38+
source_file = lldb.SBFileSpec("main.swift")
39+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
40+
self, "BREAK HERE", source_file
41+
)
42+
43+
func_names = self.check_and_get_frame_names(process)
44+
self.step_out_checks(thread, func_names[1:])
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
func work() {
2+
print("working")
3+
}
4+
5+
func ASYNC___0___() async -> Int {
6+
var myvar = 111;
7+
work()
8+
myvar += 1; // BREAK HERE
9+
return myvar
10+
}
11+
12+
func ASYNC___1___() async -> Int {
13+
var blah = 333
14+
work()
15+
var result = await ASYNC___0___()
16+
work()
17+
return result + blah
18+
}
19+
20+
func ASYNC___2___() async -> Int {
21+
work()
22+
var result1 = await ASYNC___1___()
23+
work()
24+
return result1
25+
}
26+
27+
func ASYNC___3___() async -> Int {
28+
var result = await ASYNC___2___()
29+
work()
30+
return result
31+
}
32+
33+
@main struct Main {
34+
static func main() async {
35+
let result = await ASYNC___3___()
36+
print(result)
37+
}
38+
}

lldb/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ add_subdirectory(Platform)
7373
add_subdirectory(Process)
7474
add_subdirectory(ScriptInterpreter)
7575
add_subdirectory(Signals)
76+
add_subdirectory(StackID)
7677
add_subdirectory(Symbol)
7778
add_subdirectory(SymbolFile)
7879
add_subdirectory(TypeSystem)

lldb/unittests/StackID/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
add_lldb_unittest(StackIDTests
2+
StackIDTest.cpp
3+
4+
LINK_LIBS
5+
lldbCore
6+
lldbTarget
7+
lldbPluginPlatformMacOSX
8+
)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
#include "lldb/Target/StackID.h"
3+
#include "Plugins/Platform/MacOSX/PlatformMacOSX.h"
4+
#include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h"
5+
#include "lldb/Core/Debugger.h"
6+
#include "lldb/Host/HostInfo.h"
7+
#include "lldb/Target/Process.h"
8+
#include "gtest/gtest.h"
9+
10+
using namespace lldb_private;
11+
using namespace lldb;
12+
13+
static std::once_flag initialize_flag;
14+
15+
// Initialize the bare minimum to enable defining a mock Process class.
16+
class StackIDTest : public ::testing::Test {
17+
public:
18+
void SetUp() override {
19+
std::call_once(initialize_flag, []() {
20+
HostInfo::Initialize();
21+
PlatformMacOSX::Initialize();
22+
FileSystem::Initialize();
23+
});
24+
ArchSpec arch("x86_64-apple-macosx-");
25+
Platform::SetHostPlatform(
26+
PlatformRemoteMacOSX::CreateInstance(true, &arch));
27+
m_debugger_sp = Debugger::CreateInstance();
28+
m_debugger_sp->GetTargetList().CreateTarget(*m_debugger_sp, "", arch,
29+
eLoadDependentsNo,
30+
m_platform_sp, m_target_sp);
31+
ASSERT_TRUE(m_target_sp);
32+
ASSERT_TRUE(m_target_sp->GetArchitecture().IsValid());
33+
ASSERT_TRUE(m_platform_sp);
34+
}
35+
36+
PlatformSP m_platform_sp;
37+
TargetSP m_target_sp;
38+
DebuggerSP m_debugger_sp;
39+
};
40+
41+
struct MockProcess : Process {
42+
MockProcess(TargetSP target_sp, ListenerSP listener_sp)
43+
: Process(target_sp, listener_sp) {}
44+
size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size,
45+
Status &error) override {
46+
return 0;
47+
}
48+
size_t ReadMemory(addr_t addr, void *buf, size_t size,
49+
Status &status) override {
50+
return DoReadMemory(addr, buf, size, status);
51+
}
52+
bool CanDebug(TargetSP, bool) override { return true; }
53+
Status DoDestroy() override { return Status(); }
54+
llvm::StringRef GetPluginName() override { return ""; }
55+
void RefreshStateAfterStop() override {}
56+
bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; }
57+
};
58+
59+
enum OnStack { Yes, No };
60+
/// Helper class to enable testing StackID::IsYounger.
61+
struct MockStackID : StackID {
62+
MockStackID(addr_t cfa, OnStack on_stack) : StackID() {
63+
SetPC(0);
64+
SetCFA(cfa);
65+
m_cfa_on_stack = on_stack == OnStack::Yes ? LazyBool::eLazyBoolYes
66+
: LazyBool::eLazyBoolNo;
67+
}
68+
};
69+
70+
TEST_F(StackIDTest, StackStackCFAComparison) {
71+
auto process = MockProcess(m_target_sp, Listener::MakeListener("dummy"));
72+
73+
MockStackID small_cfa_on_stack(/*cfa*/ 10, OnStack::Yes);
74+
MockStackID big_cfa_on_stack(/*cfa*/ 100, OnStack::Yes);
75+
76+
EXPECT_TRUE(
77+
StackID::IsYounger(small_cfa_on_stack, big_cfa_on_stack, process));
78+
EXPECT_FALSE(
79+
StackID::IsYounger(big_cfa_on_stack, small_cfa_on_stack, process));
80+
}
81+
82+
TEST_F(StackIDTest, StackHeapCFAComparison) {
83+
auto process = MockProcess(m_target_sp, Listener::MakeListener("dummy"));
84+
85+
MockStackID cfa_on_stack(/*cfa*/ 100, OnStack::Yes);
86+
MockStackID cfa_on_heap(/*cfa*/ 10, OnStack::No);
87+
88+
EXPECT_TRUE(StackID::IsYounger(cfa_on_stack, cfa_on_heap, process));
89+
90+
// FIXME: if the above returned true, swapping the arguments **must** return
91+
// false. And yet it doesn't.
92+
// EXPECT_FALSE(StackID::IsYounger(cfa_on_heap, cfa_on_stack, process));
93+
}

0 commit comments

Comments
 (0)