Skip to content

Commit 043a2de

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.
1 parent 94fa0a9 commit 043a2de

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
@@ -403,6 +403,28 @@ class Language : public PluginInterface {
403403
return nullptr;
404404
}
405405

406+
// BEGIN SWIFT
407+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
408+
// rdar://152321823
409+
410+
/// If `mangled_name` refers to a function that is a "closure-like" function,
411+
/// returns the name of the parent function where the input closure was
412+
/// defined. Returns an empty string if there is no such parent, or if the
413+
/// query does not make sense for this language.
414+
virtual std::string
415+
GetParentNameIfClosure(llvm::StringRef mangled_name) const {
416+
return "";
417+
}
418+
419+
/// If `sc` corresponds to a "closure-like" function (as defined in
420+
/// `GetParentNameIfClosure`), returns the parent Function*
421+
/// where a variable named `variable_name` exists.
422+
/// Returns nullptr if `sc` is not a closure, or if the query does
423+
/// not make sense for this language.
424+
Function *FindParentOfClosureWithVariable(llvm::StringRef variable_name,
425+
const SymbolContext &sc) const;
426+
// END SWIFT
427+
406428
protected:
407429
// Classes that inherit from Language can see and modify these
408430

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"
@@ -475,6 +476,31 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals,
475476
return m_variable_list_sp.get();
476477
}
477478

479+
// BEGIN SWIFT
480+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
481+
// rdar://152321823
482+
483+
/// If `sc` represents a "closure-like" function according to `lang`, and
484+
/// `missing_var_name` can be found in a parent context, create a diagnostic
485+
/// explaining that this variable is available but not captured by the closure.
486+
static std::string
487+
GetVariableNotCapturedDiagnostic(SymbolContext &sc, SourceLanguage lang,
488+
ConstString missing_var_name) {
489+
Language *lang_plugin = Language::FindPlugin(lang.AsLanguageType());
490+
if (lang_plugin == nullptr)
491+
return "";
492+
Function *parent_func =
493+
lang_plugin->FindParentOfClosureWithVariable(missing_var_name, sc);
494+
if (!parent_func)
495+
return "";
496+
return llvm::formatv("A variable named '{0}' existed in function '{1}', but "
497+
"it was not captured in the closure.\nHint: the "
498+
"variable may be available in a parent frame.",
499+
missing_var_name,
500+
parent_func->GetDisplayName().GetStringRef());
501+
}
502+
// END SWIFT
503+
478504
VariableListSP
479505
StackFrame::GetInScopeVariableList(bool get_file_globals,
480506
bool must_have_valid_location) {
@@ -620,6 +646,15 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
620646
return valobj_sp;
621647
}
622648
if (!valobj_sp) {
649+
// BEGIN SWIFT
650+
// Implement LanguageCPlusPlus::GetParentNameIfClosure and upstream this.
651+
// rdar://152321823
652+
if (std::string message = GetVariableNotCapturedDiagnostic(
653+
m_sc, GetLanguage(), name_const_string);
654+
!message.empty())
655+
error = Status::FromErrorString(message.c_str());
656+
else
657+
// END SWIFT
623658
error = Status::FromErrorStringWithFormatv(
624659
"no variable named '{0}' found in this frame", name_const_string);
625660
return ValueObjectSP();

0 commit comments

Comments
 (0)