Skip to content

Commit 30c6853

Browse files
Merge pull request #10836 from felipepiovezan/felipe/cherry-pick-diagnostics-notcaptured
🍒[lldb] Implement diagnostic for non-captured vars
2 parents 367b83b + 06f2a44 commit 30c6853

File tree

12 files changed

+548
-0
lines changed

12 files changed

+548
-0
lines changed

lldb/include/lldb/Target/Language.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,28 @@ class Language : public PluginInterface {
505505

506506
virtual FormatEntity::Entry GetFunctionNameFormat() const { return {}; }
507507

508+
// BEGIN SWIFT
509+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
510+
// rdar://152321823
511+
512+
/// If `mangled_name` refers to a function that is a "closure-like" function,
513+
/// returns the name of the parent function where the input closure was
514+
/// defined. Returns an empty string if there is no such parent, or if the
515+
/// query does not make sense for this language.
516+
virtual std::string
517+
GetParentNameIfClosure(llvm::StringRef mangled_name) const {
518+
return "";
519+
}
520+
521+
/// If `sc` corresponds to a "closure-like" function (as defined in
522+
/// `GetParentNameIfClosure`), returns the parent Function*
523+
/// where a variable named `variable_name` exists.
524+
/// Returns nullptr if `sc` is not a closure, or if the query does
525+
/// not make sense for this language.
526+
Function *FindParentOfClosureWithVariable(llvm::StringRef variable_name,
527+
const SymbolContext &sc) const;
528+
// END SWIFT
529+
508530
protected:
509531
// Classes that inherit from Language can see and modify these
510532

lldb/source/Plugins/ExpressionParser/Swift/SwiftUserExpression.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "lldb/Symbol/Type.h"
3636
#include "lldb/Symbol/Variable.h"
3737
#include "lldb/Symbol/VariableList.h"
38+
#include "lldb/Target/Language.h"
3839
#include "lldb/Utility/LLDBAssert.h"
3940
#include "lldb/Utility/LLDBLog.h"
4041
#include "lldb/Utility/Log.h"
@@ -693,6 +694,68 @@ SwiftUserExpression::GetTextAndSetExpressionParser(
693694
return parse_result;
694695
}
695696

697+
/// If `sc` represents a "closure-like" function according to `lang`, and
698+
/// `var_name` can be found in a parent context, create a diagnostic
699+
/// explaining that this variable is available but not captured by the closure.
700+
static std::string
701+
CreateVarInParentScopeDiagnostic(StringRef var_name,
702+
StringRef parent_func_name) {
703+
return llvm::formatv("A variable named '{0}' existed in function '{1}', but "
704+
"it was not captured in the closure.\nHint: the "
705+
"variable may be available in a parent frame.",
706+
var_name, parent_func_name);
707+
}
708+
709+
/// If `diagnostic_manager` contains a "cannot find <var_name> in scope"
710+
/// diagnostic, attempt to enhance it by showing if `var_name` is used inside a
711+
/// closure, not captured, but defined in a parent scope.
712+
static void EnhanceNotInScopeDiagnostics(DiagnosticManager &diagnostic_manager,
713+
ExecutionContextScope *exe_scope) {
714+
if (!exe_scope)
715+
return;
716+
lldb::StackFrameSP stack_frame = exe_scope->CalculateStackFrame();
717+
if (!stack_frame)
718+
return;
719+
SymbolContext sc =
720+
stack_frame->GetSymbolContext(lldb::eSymbolContextEverything);
721+
Language *swift_lang =
722+
Language::FindPlugin(lldb::LanguageType::eLanguageTypeSwift);
723+
if (!swift_lang)
724+
return;
725+
726+
static const RegularExpression not_in_scope_regex =
727+
RegularExpression("(.*): cannot find '([^']+)' in scope\n(.*)");
728+
for (auto &diag : diagnostic_manager.Diagnostics()) {
729+
if (!diag)
730+
continue;
731+
732+
llvm::SmallVector<StringRef, 4> match_groups;
733+
734+
if (StringRef old_rendered_msg = diag->GetDetail().rendered;
735+
!not_in_scope_regex.Execute(old_rendered_msg, &match_groups))
736+
continue;
737+
738+
StringRef prefix = match_groups[1];
739+
StringRef var_name = match_groups[2];
740+
StringRef suffix = match_groups[3];
741+
742+
Function *parent_func =
743+
swift_lang->FindParentOfClosureWithVariable(var_name, sc);
744+
if (!parent_func)
745+
continue;
746+
std::string new_message = CreateVarInParentScopeDiagnostic(
747+
var_name, parent_func->GetDisplayName());
748+
749+
std::string new_rendered =
750+
llvm::formatv("{0}: {1}\n{2}", prefix, new_message, suffix);
751+
const DiagnosticDetail &old_detail = diag->GetDetail();
752+
diag = std::make_unique<Diagnostic>(
753+
diag->getKind(), diag->GetCompilerID(),
754+
DiagnosticDetail{old_detail.source_location, old_detail.severity,
755+
std::move(new_message), std::move(new_rendered)});
756+
}
757+
}
758+
696759
bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
697760
ExecutionContext &exe_ctx,
698761
lldb_private::ExecutionPolicy execution_policy,
@@ -888,6 +951,7 @@ bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
888951
fixed_expression.substr(fixed_start, fixed_end - fixed_start);
889952
}
890953
}
954+
EnhanceNotInScopeDiagnostics(diagnostic_manager, exe_scope);
891955
return false;
892956
case ParseResult::success:
893957
llvm_unreachable("Success case is checked separately before switch!");

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,6 +2029,10 @@ SwiftLanguage::GetFunctionNameInfo(ConstString name) const {
20292029
return {func_name_type, ConstString(basename)};
20302030
}
20312031

2032+
std::string
2033+
SwiftLanguage::GetParentNameIfClosure(llvm::StringRef mangled_name) const {
2034+
return SwiftLanguageRuntime::GetParentNameIfClosure(mangled_name);
2035+
}
20322036
//------------------------------------------------------------------
20332037
// Static Functions
20342038
//------------------------------------------------------------------

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class SwiftLanguage : public Language {
8484

8585
std::pair<lldb::FunctionNameType, std::optional<ConstString>>
8686
GetFunctionNameInfo(ConstString name) const override;
87+
88+
std::string
89+
GetParentNameIfClosure(llvm::StringRef mangled_name) const override;
8790
//------------------------------------------------------------------
8891
// Static Functions
8992
//------------------------------------------------------------------

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

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

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

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

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

13+
#include "Plugins/TypeSystem/Swift/SwiftASTContext.h"
14+
#include "Plugins/TypeSystem/Swift/SwiftDemangle.h"
1315
#include "SwiftLanguageRuntime.h"
1416

1517
#include "lldb/Breakpoint/StoppointCallbackContext.h"
@@ -1497,4 +1499,30 @@ SwiftLanguageRuntime::GetGenericSignature(llvm::StringRef function_name,
14971499
return signature;
14981500
}
14991501

1502+
std::string SwiftLanguageRuntime::GetParentNameIfClosure(StringRef name) {
1503+
using Kind = Node::Kind;
1504+
swift::Demangle::Context ctx;
1505+
auto *node = SwiftLanguageRuntime::DemangleSymbolAsNode(name, ctx);
1506+
if (!node || node->getKind() != Node::Kind::Global)
1507+
return "";
1508+
1509+
// Replace the top level closure node with the child function-like node, and
1510+
// attempt to remangle. If successful, this produces the parent function-like
1511+
// entity.
1512+
static const auto closure_kinds = {Kind::ImplicitClosure,
1513+
Kind::ExplicitClosure};
1514+
static const auto function_kinds = {Kind::ImplicitClosure,
1515+
Kind::ExplicitClosure, Kind::Function};
1516+
auto *closure_node = swift_demangle::GetFirstChildOfKind(node, closure_kinds);
1517+
auto *parent_func_node =
1518+
swift_demangle::GetFirstChildOfKind(closure_node, function_kinds);
1519+
if (!parent_func_node)
1520+
return "";
1521+
swift_demangle::ReplaceChildWith(*node, *closure_node, *parent_func_node);
1522+
1523+
if (ManglingErrorOr<std::string> mangled = swift::Demangle::mangleNode(node);
1524+
mangled.isSuccess())
1525+
return mangled.result();
1526+
return "";
1527+
}
15001528
} // namespace lldb_private

lldb/source/Plugins/TypeSystem/Swift/SwiftDemangle.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@
2222
namespace lldb_private {
2323
namespace swift_demangle {
2424

25+
using NodePointer = swift::Demangle::NodePointer;
26+
using Node = swift::Demangle::Node;
27+
28+
/// Returns the first child of `node` whose kind is in `kinds`.
29+
inline NodePointer GetFirstChildOfKind(NodePointer node,
30+
llvm::ArrayRef<Node::Kind> kinds) {
31+
if (!node)
32+
return nullptr;
33+
for (auto *child : *node)
34+
if (llvm::is_contained(kinds, child->getKind()))
35+
return child;
36+
return nullptr;
37+
}
38+
39+
/// Assumes that `to_replace` is a child of `parent`, and replaces it with
40+
/// `new_child`. The Nodes must all be owned by the same context.
41+
inline void ReplaceChildWith(Node &parent, Node &to_replace, Node &new_child) {
42+
for (unsigned idx = 0; idx < parent.getNumChildren(); idx++) {
43+
auto *child = parent.getChild(idx);
44+
if (child == &to_replace)
45+
parent.replaceChild(idx, &new_child);
46+
return;
47+
}
48+
llvm_unreachable("invalid child passed to replaceChildWith");
49+
}
50+
2551
/// Access an inner node by following the given Node::Kind path.
2652
///
2753
/// Note: The Node::Kind path is relative to the given root node. The root

lldb/source/Target/Language.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
#include "lldb/Core/PluginManager.h"
1616
#include "lldb/Interpreter/OptionValueProperties.h"
17+
#include "lldb/Symbol/CompileUnit.h"
1718
#include "lldb/Symbol/SymbolFile.h"
1819
#include "lldb/Symbol/TypeList.h"
20+
#include "lldb/Symbol/VariableList.h"
1921
#include "lldb/Target/Target.h"
2022
#include "lldb/Utility/Stream.h"
2123

@@ -574,3 +576,165 @@ bool SourceLanguage::IsObjC() const {
574576
bool SourceLanguage::IsCPlusPlus() const {
575577
return name == llvm::dwarf::DW_LNAME_C_plus_plus;
576578
}
579+
580+
// BEGIN SWIFT
581+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
582+
// rdar://152321823
583+
584+
/// If `sc` represents a "closure"-like function (according to
585+
/// Language::GetParentNameIfClosure), returns sc.function and all parent
586+
/// functions up to and including the first non-closure-like function. If `sc`
587+
/// is not a closure, or if the query does not make sense for `language`,
588+
/// returns a list containing only sc.function.
589+
static llvm::SmallVector<Function *>
590+
GetParentFunctionsWhileClosure(const SymbolContext &sc,
591+
const Language &language) {
592+
// The algorithm below terminates on the assumption that
593+
// `GetParentNameIfClosure` produces an empty string when composing that
594+
// function with itself enough times. For safety, define an upper limit.
595+
constexpr auto upper_limit = 8;
596+
597+
llvm::SmallVector<Function *> parents;
598+
Function *root = sc.function;
599+
if (root == nullptr)
600+
return parents;
601+
602+
parents.push_back(root);
603+
for (int idx = 0; idx < upper_limit; idx++) {
604+
ConstString mangled = root->GetMangled().GetMangledName();
605+
std::string parent = language.GetParentNameIfClosure(mangled);
606+
if (parent.empty())
607+
break;
608+
609+
// Find the enclosing function, if it exists.
610+
SymbolContextList sc_list;
611+
Module::LookupInfo lookup_info(
612+
ConstString(parent), lldb::FunctionNameType::eFunctionNameTypeFull,
613+
lldb::eLanguageTypeSwift);
614+
sc.module_sp->FindFunctions(lookup_info, CompilerDeclContext(),
615+
ModuleFunctionSearchOptions(), sc_list);
616+
if (sc_list.GetSize() != 1 || sc_list[0].function == nullptr)
617+
break;
618+
parents.push_back(sc_list[0].function);
619+
root = sc_list[0].function;
620+
}
621+
return parents;
622+
}
623+
624+
/// Scans the line table of `function` looking for the first entry whose line
625+
/// number is `line_number`. If no such entry is found, returns the entry
626+
/// closest to but after `line_number`.
627+
static std::optional<Address> FindAddressForLineNumber(Function &function,
628+
uint32_t line_number) {
629+
CompileUnit *cu = function.GetCompileUnit();
630+
LineTable *line_table = cu ? cu->GetLineTable() : nullptr;
631+
if (line_table == nullptr)
632+
return std::nullopt;
633+
634+
// Get the first line entry for this function.
635+
AddressRange func_range = function.GetAddressRange();
636+
uint32_t first_entry_idx;
637+
{
638+
LineEntry first_line_entry;
639+
line_table->FindLineEntryByAddress(func_range.GetBaseAddress(),
640+
first_line_entry, &first_entry_idx);
641+
}
642+
643+
LineEntry best_match;
644+
for (uint32_t entry_idx = first_entry_idx; entry_idx < line_table->GetSize();
645+
entry_idx++) {
646+
LineEntry next_line;
647+
line_table->GetLineEntryAtIndex(entry_idx, next_line);
648+
649+
// Stop if this entry is outside the range of `function`.
650+
Address base_addr = next_line.range.GetBaseAddress();
651+
if (!func_range.ContainsFileAddress(base_addr))
652+
break;
653+
654+
// Stop on an exact match.
655+
if (next_line.line == line_number) {
656+
best_match = next_line;
657+
break;
658+
}
659+
660+
// Otherwise, keep track of the best match so far.
661+
if (next_line.line > line_number && next_line.line < best_match.line)
662+
best_match = next_line;
663+
}
664+
665+
return best_match.range.GetBaseAddress();
666+
}
667+
668+
/// Given a list of functions, returns a map: Function -> VariableList
669+
/// containing local variables of each function.
670+
static std::map<Function *, VariableList>
671+
GetFuncToLocalVariablesMap(llvm::ArrayRef<Function *> funcs) {
672+
std::map<Function *, VariableList> map;
673+
for (Function *function : funcs) {
674+
VariableList &variable_list = map[function];
675+
Block &block = function->GetBlock(true /*can_create=*/);
676+
block.AppendBlockVariables(
677+
true /*can_create=*/, true /*get_child_block_variables=*/,
678+
true /*stop_if_child_block_is_inlined_function=*/,
679+
[](Variable *v) { return true; }, &variable_list);
680+
}
681+
return map;
682+
}
683+
684+
/// Returns the first line associated with `function`.
685+
static uint32_t GetLineNumberForFunction(Function &function) {
686+
FileSpec filespec;
687+
uint32_t line_num = 0;
688+
function.GetStartLineSourceInfo(filespec, line_num);
689+
return line_num;
690+
}
691+
692+
/// Checks if `var` is in scope inside `function` at line `line_number`.
693+
/// If this check can't be done, a best-effort comparison of:
694+
/// line_number >= var.line_number
695+
/// is performed.
696+
static bool IsVariableInScopeAtLine(uint32_t line_number,
697+
std::optional<Address> line_addr,
698+
Variable &var) {
699+
auto fallback_line_comp = [&] {
700+
return line_number >= var.GetDeclaration().GetLine();
701+
};
702+
703+
if (!line_addr)
704+
return fallback_line_comp();
705+
706+
Block *defining_block = line_addr->CalculateSymbolContextBlock();
707+
if (defining_block)
708+
return var.IsInScope(*defining_block, *line_addr);
709+
return fallback_line_comp();
710+
}
711+
712+
Function *Language::FindParentOfClosureWithVariable(
713+
llvm::StringRef variable_name, const SymbolContext &closure_sc) const {
714+
llvm::SmallVector<Function *> function_chain =
715+
GetParentFunctionsWhileClosure(closure_sc, *this);
716+
if (function_chain.empty())
717+
return nullptr;
718+
719+
std::map<Function *, VariableList> func_to_locals =
720+
GetFuncToLocalVariablesMap(function_chain);
721+
722+
llvm::ArrayRef<Function *> children =
723+
llvm::ArrayRef(function_chain).drop_back();
724+
llvm::ArrayRef<Function *> parents =
725+
llvm::ArrayRef(function_chain).drop_front();
726+
727+
for (auto [parent, child] : llvm::zip_equal(parents, children)) {
728+
VariableList &parent_variables = func_to_locals[parent];
729+
uint32_t child_line_number = GetLineNumberForFunction(*child);
730+
std::optional<Address> parent_line_addr =
731+
FindAddressForLineNumber(*parent, child_line_number);
732+
733+
for (const VariableSP &var : parent_variables)
734+
if (var->GetName() == variable_name &&
735+
IsVariableInScopeAtLine(child_line_number, parent_line_addr, *var))
736+
return parent;
737+
}
738+
return nullptr;
739+
}
740+
// END SWIFT

0 commit comments

Comments
 (0)