Skip to content

Commit 6eaa3d6

Browse files
[lldb][NFCI] Implement language-agnostic diagnostic for non-captured vars in "frame var"
This commit adds the language-agnostic logic to support diagnosing when a `frame var` command failed because a variable is not visible in the current frame, but the current frame is a "closure-like"(*) function and it could have access to the variable, had it been captured by the closure. (*) A lambda in C++, a block in C or Objective C, a closure in Swift. The logic relies on language plugins being able to produce the name of the function where the current closure is defined, given the mangled name of the closure. Since this commit does not implement support for this in any languages, it is NFCI. It contains all the necessary pieces to upstream the code, once we implement Language support for at least one upstream language. (cherry picked from commit 043a2de)
1 parent 52ef9f8 commit 6eaa3d6

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-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/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

lldb/source/Target/StackFrame.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "lldb/Symbol/VariableList.h"
2323
#include "lldb/Target/ABI.h"
2424
#include "lldb/Target/ExecutionContext.h"
25+
#include "lldb/Target/Language.h"
2526
#include "lldb/Target/LanguageRuntime.h"
2627
#include "lldb/Target/Process.h"
2728
#include "lldb/Target/RegisterContext.h"
@@ -473,6 +474,31 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals,
473474
return m_variable_list_sp.get();
474475
}
475476

477+
// BEGIN SWIFT
478+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
479+
// rdar://152321823
480+
481+
/// If `sc` represents a "closure-like" function according to `lang`, and
482+
/// `missing_var_name` can be found in a parent context, create a diagnostic
483+
/// explaining that this variable is available but not captured by the closure.
484+
static std::string
485+
GetVariableNotCapturedDiagnostic(SymbolContext &sc, SourceLanguage lang,
486+
ConstString missing_var_name) {
487+
Language *lang_plugin = Language::FindPlugin(lang.AsLanguageType());
488+
if (lang_plugin == nullptr)
489+
return "";
490+
Function *parent_func =
491+
lang_plugin->FindParentOfClosureWithVariable(missing_var_name, sc);
492+
if (!parent_func)
493+
return "";
494+
return llvm::formatv("A variable named '{0}' existed in function '{1}', but "
495+
"it was not captured in the closure.\nHint: the "
496+
"variable may be available in a parent frame.",
497+
missing_var_name,
498+
parent_func->GetDisplayName().GetStringRef());
499+
}
500+
// END SWIFT
501+
476502
VariableListSP
477503
StackFrame::GetInScopeVariableList(bool get_file_globals,
478504
bool must_have_valid_location) {
@@ -674,6 +700,15 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
674700
return valobj_sp;
675701
}
676702
if (!valobj_sp) {
703+
// BEGIN SWIFT
704+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
705+
// rdar://152321823
706+
if (std::string message = GetVariableNotCapturedDiagnostic(
707+
m_sc, GetLanguage(), name_const_string);
708+
!message.empty())
709+
error = Status::FromErrorString(message.c_str());
710+
else
711+
// END SWIFT
677712
error = Status::FromErrorStringWithFormatv(
678713
"no variable named '{0}' found in this frame", name_const_string);
679714
return ValueObjectSP();

0 commit comments

Comments
 (0)