Skip to content

Commit a59d97a

Browse files
[lldb] Implement swift diagnostic for non-captured vars in "frame var"
This commit implements SwiftLanguage::GetParentNameIfClosure, effectively adding support for diagnosing when a `frame var` command inside a closure targets a variable that was not captured.
1 parent 8c1129a commit a59d97a

File tree

7 files changed

+191
-1
lines changed

7 files changed

+191
-1
lines changed

lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,6 +2001,11 @@ SwiftLanguage::AreEqualForFrameComparison(const SymbolContext &sc1,
20012001
llvm_unreachable("unhandled enumeration in AreEquivalentFunctions");
20022002
}
20032003

2004+
std::string
2005+
SwiftLanguage::GetParentNameIfClosure(llvm::StringRef mangled_name) const {
2006+
return SwiftLanguageRuntime::GetParentNameIfClosure(mangled_name);
2007+
}
2008+
20042009
//------------------------------------------------------------------
20052010
// Static Functions
20062011
//------------------------------------------------------------------

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ class SwiftLanguageRuntime : public LanguageRuntime {
205205
const SymbolContext *sc = nullptr,
206206
const ExecutionContext *exe_ctx = nullptr);
207207

208+
static std::string GetParentNameIfClosure(llvm::StringRef mangled_name);
209+
208210
/// Demangle a symbol to a swift::Demangle node tree.
209211
///
210212
/// This is a central point of access, for purposes such as logging.

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "Plugins/TypeSystem/Swift/SwiftASTContext.h"
1314
#include "SwiftLanguageRuntime.h"
1415

1516
#include "lldb/Breakpoint/StoppointCallbackContext.h"
@@ -74,6 +75,34 @@ childAtPath(swift::Demangle::NodePointer node,
7475
return nullptr;
7576
}
7677

78+
namespace {
79+
using NodePointer = swift::Demangle::NodePointer;
80+
using Node = swift::Demangle::Node;
81+
82+
/// Returns the first child of `node` whose kind is in `kinds`.
83+
NodePointer getFirstChildOfKind(NodePointer node,
84+
llvm::ArrayRef<Node::Kind> kinds) {
85+
if (!node)
86+
return nullptr;
87+
for (auto *child : *node)
88+
if (llvm::is_contained(kinds, child->getKind()))
89+
return child;
90+
return nullptr;
91+
}
92+
93+
/// Assumes that `to_replace` is a child of `parent`, and replaces it with
94+
/// `new_child`. The Nodes must all be owned by the same context.
95+
void replaceChildWith(Node &parent, Node &to_replace, Node &new_child) {
96+
for (unsigned idx = 0; idx < parent.getNumChildren(); idx++) {
97+
auto *child = parent.getChild(idx);
98+
if (child == &to_replace)
99+
parent.replaceChild(idx, &new_child);
100+
return;
101+
}
102+
llvm_unreachable("invalid child passed to replaceChildWith");
103+
}
104+
} // namespace
105+
77106
static bool hasChild(swift::Demangle::NodePointer node,
78107
swift::Demangle::Node::Kind kind) {
79108
return childAtPath(node, {kind});
@@ -1496,4 +1525,29 @@ SwiftLanguageRuntime::GetGenericSignature(llvm::StringRef function_name,
14961525
return signature;
14971526
}
14981527

1528+
std::string SwiftLanguageRuntime::GetParentNameIfClosure(StringRef name) {
1529+
using Kind = Node::Kind;
1530+
swift::Demangle::Context ctx;
1531+
auto *node = SwiftLanguageRuntime::DemangleSymbolAsNode(name, ctx);
1532+
if (!node || node->getKind() != Node::Kind::Global)
1533+
return "";
1534+
1535+
// Replace the top level closure node with the child function-like node, and
1536+
// attempt to remangle. If successful, this produces the parent function-like
1537+
// entity.
1538+
static const auto closure_kinds = {Kind::ImplicitClosure,
1539+
Kind::ExplicitClosure};
1540+
static const auto function_kinds = {Kind::ImplicitClosure,
1541+
Kind::ExplicitClosure, Kind::Function};
1542+
auto *closure_node = getFirstChildOfKind(node, closure_kinds);
1543+
auto *parent_func_node = getFirstChildOfKind(closure_node, function_kinds);
1544+
if (!parent_func_node)
1545+
return "";
1546+
replaceChildWith(*node, *closure_node, *parent_func_node);
1547+
1548+
if (ManglingErrorOr<std::string> mangled = swift::Demangle::mangleNode(node);
1549+
mangled.isSuccess())
1550+
return mangled.result();
1551+
return "";
1552+
}
14991553
} // namespace lldb_private

lldb/source/Target/StackFrame.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals,
480480
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
481481
// rdar://152321823
482482

483-
/// If `sc` represents a "closure-like" function according to `lang, and
483+
/// If `sc` represents a "closure-like" function according to `lang`, and
484484
/// `missing_var_name` can be found in a parent context, create a diagnostic
485485
/// explaining that this variable is available but not captured by the closure.
486486
static std::string
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
3+
include Makefile.rules
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Test that we can print and call closures passed in various contexts
3+
"""
4+
5+
import os
6+
import re
7+
import lldb
8+
import lldbsuite.test.lldbutil as lldbutil
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
12+
13+
def check_not_captured_error(test, frame, var_name, parent_function):
14+
expected_error = (
15+
f"A variable named '{var_name}' exists in function '{parent_function}'"
16+
)
17+
test.expect(f"frame variable {var_name}", substrs=[expected_error], error=True)
18+
test.expect(f"frame variable {var_name} + 1", substrs=[expected_error], error=True)
19+
20+
21+
class TestSwiftClosureVarNotCaptured(TestBase):
22+
def get_to_bkpt(self, bkpt_name):
23+
return lldbutil.run_to_source_breakpoint(
24+
self, bkpt_name, lldb.SBFileSpec("main.swift")
25+
)
26+
27+
def simple_closure(self):
28+
(target, process, thread, bkpt) = self.get_to_bkpt("break_simple_closure")
29+
check_not_captured_error(self, thread.frames[0], "var_in_foo", "func_1(arg:)")
30+
check_not_captured_error(self, thread.frames[0], "arg", "func_1(arg:)")
31+
32+
def nested_closure(self):
33+
(target, process, thread, bkpt) = self.get_to_bkpt("break_double_closure_1")
34+
check_not_captured_error(self, thread.frames[0], "var_in_foo", "func_2(arg:)")
35+
check_not_captured_error(self, thread.frames[0], "arg", "func_2(arg:)")
36+
check_not_captured_error(
37+
self, thread.frames[0], "var_in_outer_closure", "closure #1 in func_2(arg:)"
38+
)
39+
40+
lldbutil.continue_to_source_breakpoint(
41+
self, process, "break_double_closure_2", lldb.SBFileSpec("main.swift")
42+
)
43+
check_not_captured_error(self, thread.frames[0], "var_in_foo", "func_2(arg:)")
44+
check_not_captured_error(self, thread.frames[0], "arg", "func_2(arg:)")
45+
check_not_captured_error(
46+
self, thread.frames[0], "var_in_outer_closure", "closure #1 in func_2(arg:)"
47+
)
48+
49+
def async_closure(self):
50+
(target, process, thread, bkpt) = self.get_to_bkpt("break_async_closure_1")
51+
check_not_captured_error(self, thread.frames[0], "var_in_foo", "func_3(arg:)")
52+
check_not_captured_error(self, thread.frames[0], "arg", "func_3(arg:)")
53+
check_not_captured_error(
54+
self, thread.frames[0], "var_in_outer_closure", "closure #1 in func_3(arg:)"
55+
)
56+
57+
lldbutil.continue_to_source_breakpoint(
58+
self, process, "break_async_closure_2", lldb.SBFileSpec("main.swift")
59+
)
60+
check_not_captured_error(self, thread.frames[0], "var_in_foo", "func_3(arg:)")
61+
check_not_captured_error(self, thread.frames[0], "arg", "func_3(arg:)")
62+
check_not_captured_error(
63+
self, thread.frames[0], "var_in_outer_closure", "closure #1 in func_3(arg:)"
64+
)
65+
66+
def test(self):
67+
self.build()
68+
self.simple_closure()
69+
self.nested_closure()
70+
self.async_closure()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
func func_1(arg:Int) {
2+
let var_in_foo = "Alice"
3+
let simple_closure = {
4+
print("Hi there!") // break_simple_closure
5+
}
6+
simple_closure()
7+
}
8+
9+
func func_2(arg: Int) {
10+
let var_in_foo = "Alice"
11+
let outer_closure = {
12+
let var_in_outer_closure = "Alice"
13+
let inner_closure_1 = {
14+
print("Hi inside!") // break_double_closure_1
15+
}
16+
inner_closure_1()
17+
18+
let inner_closure_2 = {
19+
print("Hi inside!") // break_double_closure_2
20+
}
21+
inner_closure_2()
22+
}
23+
outer_closure()
24+
}
25+
26+
func func_3(arg:Int) async {
27+
let var_in_foo = "Alice"
28+
29+
// FIXME: if we comment the line below, the test fails. For some reason,
30+
// without this line, most variables don't have debug info in the entry
31+
// funclet, which is the "parent name" derived from the closure name.
32+
// rdar://152271048
33+
try! await Task.sleep(for: .seconds(0))
34+
35+
let outer_closure = {
36+
let var_in_outer_closure = "Alice"
37+
38+
let inner_closure_1 = {
39+
print("Hi inside!") // break_async_closure_1
40+
}
41+
inner_closure_1()
42+
43+
try await Task.sleep(for: .seconds(0))
44+
45+
let inner_closure_2 = {
46+
print("Hi inside!") // break_async_closure_2
47+
}
48+
inner_closure_2()
49+
}
50+
51+
try! await outer_closure()
52+
}
53+
54+
func_1(arg:42)
55+
func_2(arg:42)
56+
await func_3(arg:42)

0 commit comments

Comments
 (0)