Author: cmtice Date: 2025-04-03T21:39:30-07:00 New Revision: 46e2c07fa28bd42da8f8ca52e93603297114afa2
URL: https://github.com/llvm/llvm-project/commit/46e2c07fa28bd42da8f8ca52e93603297114afa2 DIFF: https://github.com/llvm/llvm-project/commit/46e2c07fa28bd42da8f8ca52e93603297114afa2.diff LOG: [LLDB] Add DIL code for handling plain variable names. (#120971) Add the Data Inspection Language (DIL) implementation pieces for handling plain local and global variable names. See https://discourse.llvm.org/t/rfc-data-inspection-language/69893 for information about DIL. This change includes the basic AST, Lexer, Parser and Evaluator pieces, as well as some tests. Added: lldb/docs/dil-expr-lang.ebnf lldb/include/lldb/ValueObject/DILAST.h lldb/include/lldb/ValueObject/DILEval.h lldb/include/lldb/ValueObject/DILParser.h lldb/source/ValueObject/DILAST.cpp lldb/source/ValueObject/DILEval.cpp lldb/source/ValueObject/DILParser.cpp lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp Modified: lldb/include/lldb/ValueObject/DILLexer.h lldb/source/Target/StackFrame.cpp lldb/source/ValueObject/CMakeLists.txt lldb/unittests/ValueObject/DILLexerTests.cpp Removed: ################################################################################ diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf new file mode 100644 index 0000000000000..0bbbecbdc78c1 --- /dev/null +++ b/lldb/docs/dil-expr-lang.ebnf @@ -0,0 +1,42 @@ +(* Data Inspection Language (DIL) definition - LLDB Debug Expressions *) + +(* This is currently a subset of the final DIL Language, matching the current + DIL implementation. *) + +expression = primary_expression ; + +primary_expression = id_expression + | "(" expression ")"; + +id_expression = unqualified_id + | qualified_id + | register ; + +unqualified_id = identifier ; + +qualified_id = ["::"] [nested_name_specifier] unqualified_id + | ["::"] identifier ; + +identifier = ? C99 Identifier ? ; + +register = "$" ? Register name ? ; + +nested_name_specifier = type_name "::" + | namespace_name '::' + | nested_name_specifier identifier "::" ; + +type_name = class_name + | enum_name + | typedef_name; + +class_name = identifier ; + +enum_name = identifier ; + +typedef_name = identifier ; + +namespace_name = identifier ; + + + + diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h new file mode 100644 index 0000000000000..05d87e9cc4b6b --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -0,0 +1,97 @@ +//===-- DILAST.h ------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_VALUEOBJECT_DILAST_H +#define LLDB_VALUEOBJECT_DILAST_H + +#include "lldb/ValueObject/ValueObject.h" +#include "llvm/Support/Error.h" +#include <cstdint> +#include <string> + +namespace lldb_private::dil { + +/// The various types DIL AST nodes (used by the DIL parser). +enum class NodeKind { + eErrorNode, + eIdentifierNode, +}; + +/// Forward declaration, for use in DIL AST nodes. Definition is at the very +/// end of this file. +class Visitor; + +/// The rest of the classes in this file, except for the Visitor class at the +/// very end, define all the types of AST nodes used by the DIL parser and +/// expression evaluator. The DIL parser parses the input string and creates +/// the AST parse tree from the AST nodes. The resulting AST node tree gets +/// passed to the DIL expression evaluator, which evaluates the DIL AST nodes +/// and creates/returns a ValueObjectSP containing the result. + +/// Base class for AST nodes used by the Data Inspection Language (DIL) parser. +/// All of the specialized types of AST nodes inherit from this (virtual) base +/// class. +class ASTNode { +public: + ASTNode(uint32_t location, NodeKind kind) + : m_location(location), m_kind(kind) {} + virtual ~ASTNode() = default; + + virtual llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const = 0; + + uint32_t GetLocation() const { return m_location; } + NodeKind GetKind() const { return m_kind; } + +private: + uint32_t m_location; + const NodeKind m_kind; +}; + +using ASTNodeUP = std::unique_ptr<ASTNode>; + +class ErrorNode : public ASTNode { +public: + ErrorNode() : ASTNode(0, NodeKind::eErrorNode) {} + llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + + static bool classof(const ASTNode *node) { + return node->GetKind() == NodeKind::eErrorNode; + } +}; + +class IdentifierNode : public ASTNode { +public: + IdentifierNode(uint32_t location, std::string name) + : ASTNode(location, NodeKind::eIdentifierNode), m_name(std::move(name)) {} + + llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + + std::string GetName() const { return m_name; } + + static bool classof(const ASTNode *node) { + return node->GetKind() == NodeKind::eIdentifierNode; + } + +private: + std::string m_name; +}; + +/// This class contains one Visit method for each specialized type of +/// DIL AST node. The Visit methods are used to dispatch a DIL AST node to +/// the correct function in the DIL expression evaluator for evaluating that +/// type of AST node. +class Visitor { +public: + virtual ~Visitor() = default; + virtual llvm::Expected<lldb::ValueObjectSP> + Visit(const IdentifierNode *node) = 0; +}; + +} // namespace lldb_private::dil + +#endif // LLDB_VALUEOBJECT_DILAST_H diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h new file mode 100644 index 0000000000000..335035d3f9248 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -0,0 +1,63 @@ +//===-- DILEval.h -----------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_VALUEOBJECT_DILEVAL_H +#define LLDB_VALUEOBJECT_DILEVAL_H + +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILParser.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <memory> +#include <vector> + +namespace lldb_private::dil { + +/// Given the name of an identifier (variable name, member name, type name, +/// etc.), find the ValueObject for that name (if it exists), excluding global +/// variables, and create and return an IdentifierInfo object containing all +/// the relevant information about that object (for DIL parsing and +/// evaluating). +lldb::ValueObjectSP LookupIdentifier(llvm::StringRef name_ref, + std::shared_ptr<StackFrame> frame_sp, + lldb::DynamicValueType use_dynamic, + CompilerType *scope_ptr = nullptr); + +/// Given the name of an identifier, check to see if it matches the name of a +/// global variable. If so, find the ValueObject for that global variable, and +/// create and return an IdentifierInfo object containing all the relevant +/// informatin about it. +lldb::ValueObjectSP LookupGlobalIdentifier(llvm::StringRef name_ref, + std::shared_ptr<StackFrame> frame_sp, + lldb::TargetSP target_sp, + lldb::DynamicValueType use_dynamic, + CompilerType *scope_ptr = nullptr); + +class Interpreter : Visitor { +public: + Interpreter(lldb::TargetSP target, llvm::StringRef expr, + lldb::DynamicValueType use_dynamic, + std::shared_ptr<StackFrame> frame_sp); + + llvm::Expected<lldb::ValueObjectSP> Evaluate(const ASTNode *node); + +private: + llvm::Expected<lldb::ValueObjectSP> + Visit(const IdentifierNode *node) override; + + // Used by the interpreter to create objects, perform casts, etc. + lldb::TargetSP m_target; + llvm::StringRef m_expr; + lldb::ValueObjectSP m_scope; + lldb::DynamicValueType m_default_dynamic; + std::shared_ptr<StackFrame> m_exe_ctx_scope; +}; + +} // namespace lldb_private::dil + +#endif // LLDB_VALUEOBJECT_DILEVAL_H diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index e1182da5b20ab..d15fc382d1623 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -11,6 +11,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" #include <cstdint> #include <memory> #include <string> @@ -41,10 +42,8 @@ class Token { bool IsNot(Kind kind) const { return m_kind != kind; } - bool IsOneOf(Kind kind1, Kind kind2) const { return Is(kind1) || Is(kind2); } - - template <typename... Ts> bool IsOneOf(Kind kind, Ts... Ks) const { - return Is(kind) || IsOneOf(Ks...); + bool IsOneOf(llvm::ArrayRef<Kind> kinds) const { + return llvm::is_contained(kinds, m_kind); } uint32_t GetLocation() const { return m_start_pos; } @@ -120,4 +119,24 @@ class DILLexer { } // namespace lldb_private::dil +namespace llvm { + +template <> struct format_provider<lldb_private::dil::Token::Kind> { + static void format(const lldb_private::dil::Token::Kind &k, raw_ostream &OS, + llvm::StringRef Options) { + OS << "'" << lldb_private::dil::Token::GetTokenName(k) << "'"; + } +}; + +template <> struct format_provider<lldb_private::dil::Token> { + static void format(const lldb_private::dil::Token &t, raw_ostream &OS, + llvm::StringRef Options) { + lldb_private::dil::Token::Kind kind = t.GetKind(); + OS << "<'" << t.GetSpelling() << "' (" + << lldb_private::dil::Token::GetTokenName(kind) << ")>"; + } +}; + +} // namespace llvm + #endif // LLDB_VALUEOBJECT_DILLEXER_H diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h new file mode 100644 index 0000000000000..9b7a6cd487939 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -0,0 +1,125 @@ +//===-- DILParser.h ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_VALUEOBJECT_DILPARSER_H +#define LLDB_VALUEOBJECT_DILPARSER_H + +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Utility/Status.h" +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILLexer.h" +#include "llvm/Support/Error.h" +#include <memory> +#include <optional> +#include <string> +#include <system_error> +#include <tuple> +#include <vector> + +namespace lldb_private::dil { + +enum class ErrorCode : unsigned char { + kOk = 0, + kInvalidExpressionSyntax, + kUndeclaredIdentifier, + kUnknown, +}; + +// The following is modeled on class OptionParseError. +class DILDiagnosticError + : public llvm::ErrorInfo<DILDiagnosticError, DiagnosticError> { + DiagnosticDetail m_detail; + +public: + using llvm::ErrorInfo<DILDiagnosticError, DiagnosticError>::ErrorInfo; + DILDiagnosticError(DiagnosticDetail detail) + : ErrorInfo(make_error_code(std::errc::invalid_argument)), + m_detail(std::move(detail)) {} + + DILDiagnosticError(llvm::StringRef expr, const std::string &message, + uint32_t loc, uint16_t err_len); + + std::unique_ptr<CloneableError> Clone() const override { + return std::make_unique<DILDiagnosticError>(m_detail); + } + + llvm::ArrayRef<DiagnosticDetail> GetDetails() const override { + return {m_detail}; + } + + std::string message() const override { return m_detail.rendered; } +}; + +/// Pure recursive descent parser for C++ like expressions. +/// EBNF grammar for the parser is described in lldb/docs/dil-expr-lang.ebnf +class DILParser { +public: + static llvm::Expected<ASTNodeUP> Parse(llvm::StringRef dil_input_expr, + DILLexer lexer, + std::shared_ptr<StackFrame> frame_sp, + lldb::DynamicValueType use_dynamic, + bool use_synthetic, bool fragile_ivar, + bool check_ptr_vs_member); + + ~DILParser() = default; + + bool UseSynthetic() { return m_use_synthetic; } + + lldb::DynamicValueType UseDynamic() { return m_use_dynamic; } + +private: + explicit DILParser(llvm::StringRef dil_input_expr, DILLexer lexer, + std::shared_ptr<StackFrame> frame_sp, + lldb::DynamicValueType use_dynamic, bool use_synthetic, + bool fragile_ivar, bool check_ptr_vs_member, + llvm::Error &error); + + ASTNodeUP Run(); + + ASTNodeUP ParseExpression(); + ASTNodeUP ParsePrimaryExpression(); + + std::string ParseNestedNameSpecifier(); + + std::string ParseIdExpression(); + std::string ParseUnqualifiedId(); + + void BailOut(const std::string &error, uint32_t loc, uint16_t err_len); + + void Expect(Token::Kind kind); + + void TentativeParsingRollback(uint32_t saved_idx) { + if (m_error) + llvm::consumeError(std::move(m_error)); + m_dil_lexer.ResetTokenIdx(saved_idx); + } + + Token CurToken() { return m_dil_lexer.GetCurrentToken(); } + + // Parser doesn't own the evaluation context. The produced AST may depend on + // it (for example, for source locations), so it's expected that expression + // context will outlive the parser. + std::shared_ptr<StackFrame> m_ctx_scope; + + llvm::StringRef m_input_expr; + + DILLexer m_dil_lexer; + + // Holds an error if it occures during parsing. + llvm::Error &m_error; + + lldb::DynamicValueType m_use_dynamic; + bool m_use_synthetic; + bool m_fragile_ivar; + bool m_check_ptr_vs_member; +}; // class DILParser + +} // namespace lldb_private::dil + +#endif // LLDB_VALUEOBJECT_DILPARSER_H diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index bab36e9aa1033..0306f68169a98 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -31,6 +31,9 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegisterValue.h" +#include "lldb/ValueObject/DILEval.h" +#include "lldb/ValueObject/DILLexer.h" +#include "lldb/ValueObject/DILParser.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectMemory.h" #include "lldb/ValueObject/ValueObjectVariable.h" @@ -523,10 +526,42 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath( ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath( llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic, uint32_t options, lldb::VariableSP &var_sp, Status &error) { - // This is a place-holder for the calls into the DIL parser and - // evaluator. For now, just call the "real" frame variable implementation. - return LegacyGetValueForVariableExpressionPath(var_expr, use_dynamic, options, - var_sp, error); + + const bool check_ptr_vs_member = + (options & eExpressionPathOptionCheckPtrVsMember) != 0; + const bool no_fragile_ivar = + (options & eExpressionPathOptionsNoFragileObjcIvar) != 0; + const bool no_synth_child = + (options & eExpressionPathOptionsNoSyntheticChildren) != 0; + + // Lex the expression. + auto lex_or_err = dil::DILLexer::Create(var_expr); + if (!lex_or_err) { + error = Status::FromError(lex_or_err.takeError()); + return ValueObjectSP(); + } + + // Parse the expression. + auto tree_or_error = dil::DILParser::Parse( + var_expr, std::move(*lex_or_err), shared_from_this(), use_dynamic, + !no_synth_child, !no_fragile_ivar, check_ptr_vs_member); + if (!tree_or_error) { + error = Status::FromError(tree_or_error.takeError()); + return ValueObjectSP(); + } + + // Evaluate the parsed expression. + lldb::TargetSP target = this->CalculateTarget(); + dil::Interpreter interpreter(target, var_expr, use_dynamic, + shared_from_this()); + + auto valobj_or_error = interpreter.Evaluate((*tree_or_error).get()); + if (!valobj_or_error) { + error = Status::FromError(valobj_or_error.takeError()); + return ValueObjectSP(); + } + + return *valobj_or_error; } ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath( diff --git a/lldb/source/ValueObject/CMakeLists.txt b/lldb/source/ValueObject/CMakeLists.txt index 30c34472289e7..92683916f5a52 100644 --- a/lldb/source/ValueObject/CMakeLists.txt +++ b/lldb/source/ValueObject/CMakeLists.txt @@ -1,5 +1,8 @@ add_lldb_library(lldbValueObject + DILAST.cpp + DILEval.cpp DILLexer.cpp + DILParser.cpp ValueObject.cpp ValueObjectCast.cpp ValueObjectChild.cpp diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp new file mode 100644 index 0000000000000..e75958d784627 --- /dev/null +++ b/lldb/source/ValueObject/DILAST.cpp @@ -0,0 +1,22 @@ +//===-- DILAST.cpp --------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/ValueObject/DILAST.h" +#include "llvm/Support/ErrorHandling.h" + +namespace lldb_private::dil { + +llvm::Expected<lldb::ValueObjectSP> ErrorNode::Accept(Visitor *v) const { + llvm_unreachable("Attempting to Visit a DIL ErrorNode."); +} + +llvm::Expected<lldb::ValueObjectSP> IdentifierNode::Accept(Visitor *v) const { + return v->Visit(this); +} + +} // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp new file mode 100644 index 0000000000000..4889834c7a3c1 --- /dev/null +++ b/lldb/source/ValueObject/DILEval.cpp @@ -0,0 +1,235 @@ +//===-- DILEval.cpp -------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/ValueObject/DILEval.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/ValueObject.h" +#include "lldb/ValueObject/ValueObjectRegister.h" +#include "lldb/ValueObject/ValueObjectVariable.h" +#include "llvm/Support/FormatAdapters.h" +#include <memory> + +namespace lldb_private::dil { + +static lldb::ValueObjectSP LookupStaticIdentifier( + VariableList &variable_list, std::shared_ptr<StackFrame> exe_scope, + llvm::StringRef name_ref, llvm::StringRef unqualified_name) { + // First look for an exact match to the (possibly) qualified name. + for (const lldb::VariableSP &var_sp : variable_list) { + lldb::ValueObjectSP valobj_sp( + ValueObjectVariable::Create(exe_scope.get(), var_sp)); + if (valobj_sp && valobj_sp->GetVariable() && + (valobj_sp->GetVariable()->NameMatches(ConstString(name_ref)))) + return valobj_sp; + } + + // If the qualified name is the same as the unqualfied name, there's nothing + // more to be done. + if (name_ref == unqualified_name) + return nullptr; + + // We didn't match the qualified name; try to match the unqualified name. + for (const lldb::VariableSP &var_sp : variable_list) { + lldb::ValueObjectSP valobj_sp( + ValueObjectVariable::Create(exe_scope.get(), var_sp)); + if (valobj_sp && valobj_sp->GetVariable() && + (valobj_sp->GetVariable()->NameMatches(ConstString(unqualified_name)))) + return valobj_sp; + } + + return nullptr; +} + +static lldb::VariableSP DILFindVariable(ConstString name, + lldb::VariableListSP variable_list) { + lldb::VariableSP exact_match; + std::vector<lldb::VariableSP> possible_matches; + + for (lldb::VariableSP var_sp : *variable_list) { + llvm::StringRef str_ref_name = var_sp->GetName().GetStringRef(); + // Check for global vars, which might start with '::'. + str_ref_name.consume_front("::"); + + if (str_ref_name == name.GetStringRef()) + possible_matches.push_back(var_sp); + else if (var_sp->NameMatches(name)) + possible_matches.push_back(var_sp); + } + + // Look for exact matches (favors local vars over global vars) + auto exact_match_it = + llvm::find_if(possible_matches, [&](lldb::VariableSP var_sp) { + return var_sp->GetName() == name; + }); + + if (exact_match_it != possible_matches.end()) + return *exact_match_it; + + // Look for a global var exact match. + for (auto var_sp : possible_matches) { + llvm::StringRef str_ref_name = var_sp->GetName().GetStringRef(); + str_ref_name.consume_front("::"); + if (str_ref_name == name.GetStringRef()) + return var_sp; + } + + // If there's a single non-exact match, take it. + if (possible_matches.size() == 1) + return possible_matches[0]; + + return nullptr; +} + +lldb::ValueObjectSP LookupGlobalIdentifier( + llvm::StringRef name_ref, std::shared_ptr<StackFrame> stack_frame, + lldb::TargetSP target_sp, lldb::DynamicValueType use_dynamic, + CompilerType *scope_ptr) { + // First look for match in "local" global variables. + lldb::VariableListSP variable_list(stack_frame->GetInScopeVariableList(true)); + name_ref.consume_front("::"); + + lldb::ValueObjectSP value_sp; + if (variable_list) { + lldb::VariableSP var_sp = + DILFindVariable(ConstString(name_ref), variable_list); + if (var_sp) + value_sp = + stack_frame->GetValueObjectForFrameVariable(var_sp, use_dynamic); + } + + if (value_sp) + return value_sp; + + // Also check for static global vars. + if (variable_list) { + const char *type_name = ""; + if (scope_ptr) + type_name = scope_ptr->GetCanonicalType().GetTypeName().AsCString(); + std::string name_with_type_prefix = + llvm::formatv("{0}::{1}", type_name, name_ref).str(); + value_sp = LookupStaticIdentifier(*variable_list, stack_frame, + name_with_type_prefix, name_ref); + if (!value_sp) + value_sp = LookupStaticIdentifier(*variable_list, stack_frame, name_ref, + name_ref); + } + + if (value_sp) + return value_sp; + + // Check for match in modules global variables. + VariableList modules_var_list; + target_sp->GetImages().FindGlobalVariables( + ConstString(name_ref), std::numeric_limits<uint32_t>::max(), + modules_var_list); + if (modules_var_list.Empty()) + return nullptr; + + for (const lldb::VariableSP &var_sp : modules_var_list) { + std::string qualified_name = llvm::formatv("::{0}", name_ref).str(); + if (var_sp->NameMatches(ConstString(name_ref)) || + var_sp->NameMatches(ConstString(qualified_name))) { + value_sp = ValueObjectVariable::Create(stack_frame.get(), var_sp); + break; + } + } + + if (value_sp) + return value_sp; + + return nullptr; +} + +lldb::ValueObjectSP LookupIdentifier(llvm::StringRef name_ref, + std::shared_ptr<StackFrame> stack_frame, + lldb::DynamicValueType use_dynamic, + CompilerType *scope_ptr) { + // Support $rax as a special syntax for accessing registers. + // Will return an invalid value in case the requested register doesn't exist. + if (name_ref.consume_front("$")) { + lldb::RegisterContextSP reg_ctx(stack_frame->GetRegisterContext()); + if (!reg_ctx) + return nullptr; + + if (const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(name_ref)) + return ValueObjectRegister::Create(stack_frame.get(), reg_ctx, reg_info); + + return nullptr; + } + + lldb::VariableListSP variable_list( + stack_frame->GetInScopeVariableList(false)); + + if (!name_ref.contains("::")) { + if (!scope_ptr || !scope_ptr->IsValid()) { + // Lookup in the current frame. + // Try looking for a local variable in current scope. + lldb::ValueObjectSP value_sp; + if (variable_list) { + lldb::VariableSP var_sp = + DILFindVariable(ConstString(name_ref), variable_list); + if (var_sp) + value_sp = + stack_frame->GetValueObjectForFrameVariable(var_sp, use_dynamic); + } + if (!value_sp) + value_sp = stack_frame->FindVariable(ConstString(name_ref)); + + if (value_sp) + return value_sp; + + // Try looking for an instance variable (class member). + SymbolContext sc = stack_frame->GetSymbolContext( + lldb::eSymbolContextFunction | lldb::eSymbolContextBlock); + llvm::StringRef ivar_name = sc.GetInstanceVariableName(); + value_sp = stack_frame->FindVariable(ConstString(ivar_name)); + if (value_sp) + value_sp = value_sp->GetChildMemberWithName(name_ref); + + if (value_sp) + return value_sp; + } + } + return nullptr; +} + +Interpreter::Interpreter(lldb::TargetSP target, llvm::StringRef expr, + lldb::DynamicValueType use_dynamic, + std::shared_ptr<StackFrame> frame_sp) + : m_target(std::move(target)), m_expr(expr), m_default_dynamic(use_dynamic), + m_exe_ctx_scope(frame_sp) {} + +llvm::Expected<lldb::ValueObjectSP> Interpreter::Evaluate(const ASTNode *node) { + + // Traverse an AST pointed by the `node`. + return node->Accept(this); +} + +llvm::Expected<lldb::ValueObjectSP> +Interpreter::Visit(const IdentifierNode *node) { + lldb::DynamicValueType use_dynamic = m_default_dynamic; + + lldb::ValueObjectSP identifier = + LookupIdentifier(node->GetName(), m_exe_ctx_scope, use_dynamic); + + if (!identifier) + identifier = LookupGlobalIdentifier(node->GetName(), m_exe_ctx_scope, + m_target, use_dynamic); + if (!identifier) { + std::string errMsg = + llvm::formatv("use of undeclared identifier '{0}'", node->GetName()); + return llvm::make_error<DILDiagnosticError>( + m_expr, errMsg, node->GetLocation(), node->GetName().size()); + } + + return identifier; +} + +} // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp new file mode 100644 index 0000000000000..a8baba2c06e7a --- /dev/null +++ b/lldb/source/ValueObject/DILParser.cpp @@ -0,0 +1,260 @@ +//===-- DILParser.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// This implements the recursive descent parser for the Data Inspection +// Language (DIL), and its helper functions, which will eventually underlie the +// 'frame variable' command. The language that this parser recognizes is +// described in lldb/docs/dil-expr-lang.ebnf +// +//===----------------------------------------------------------------------===// + +#include "lldb/ValueObject/DILParser.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILEval.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatAdapters.h" +#include <cstdlib> +#include <limits.h> +#include <memory> +#include <sstream> +#include <string> + +namespace lldb_private::dil { + +DILDiagnosticError::DILDiagnosticError(llvm::StringRef expr, + const std::string &message, uint32_t loc, + uint16_t err_len) + : ErrorInfo(make_error_code(std::errc::invalid_argument)) { + DiagnosticDetail::SourceLocation sloc = { + FileSpec{}, /*line=*/1, static_cast<uint16_t>(loc + 1), + err_len, false, /*in_user_input=*/true}; + std::string rendered_msg = + llvm::formatv("<user expression 0>:1:{0}: {1}\n 1 | {2}\n | ^", + loc + 1, message, expr); + m_detail.source_location = sloc; + m_detail.severity = lldb::eSeverityError; + m_detail.message = message; + m_detail.rendered = std::move(rendered_msg); +} + +llvm::Expected<ASTNodeUP> +DILParser::Parse(llvm::StringRef dil_input_expr, DILLexer lexer, + std::shared_ptr<StackFrame> frame_sp, + lldb::DynamicValueType use_dynamic, bool use_synthetic, + bool fragile_ivar, bool check_ptr_vs_member) { + llvm::Error error = llvm::Error::success(); + DILParser parser(dil_input_expr, lexer, frame_sp, use_dynamic, use_synthetic, + fragile_ivar, check_ptr_vs_member, error); + + ASTNodeUP node_up = parser.Run(); + + if (error) + return error; + + return node_up; +} + +DILParser::DILParser(llvm::StringRef dil_input_expr, DILLexer lexer, + std::shared_ptr<StackFrame> frame_sp, + lldb::DynamicValueType use_dynamic, bool use_synthetic, + bool fragile_ivar, bool check_ptr_vs_member, + llvm::Error &error) + : m_ctx_scope(frame_sp), m_input_expr(dil_input_expr), + m_dil_lexer(std::move(lexer)), m_error(error), m_use_dynamic(use_dynamic), + m_use_synthetic(use_synthetic), m_fragile_ivar(fragile_ivar), + m_check_ptr_vs_member(check_ptr_vs_member) {} + +ASTNodeUP DILParser::Run() { + ASTNodeUP expr = ParseExpression(); + + Expect(Token::Kind::eof); + + return expr; +} + +// Parse an expression. +// +// expression: +// primary_expression +// +ASTNodeUP DILParser::ParseExpression() { return ParsePrimaryExpression(); } + +// Parse a primary_expression. +// +// primary_expression: +// id_expression +// "(" expression ")" +// +ASTNodeUP DILParser::ParsePrimaryExpression() { + if (CurToken().IsOneOf({Token::coloncolon, Token::identifier})) { + // Save the source location for the diagnostics message. + uint32_t loc = CurToken().GetLocation(); + auto identifier = ParseIdExpression(); + + return std::make_unique<IdentifierNode>(loc, identifier); + } + + if (CurToken().Is(Token::l_paren)) { + m_dil_lexer.Advance(); + auto expr = ParseExpression(); + Expect(Token::r_paren); + m_dil_lexer.Advance(); + return expr; + } + + BailOut(llvm::formatv("Unexpected token: {0}", CurToken()), + CurToken().GetLocation(), CurToken().GetSpelling().length()); + return std::make_unique<ErrorNode>(); +} + +// Parse nested_name_specifier. +// +// nested_name_specifier: +// type_name "::" +// namespace_name "::" +// nested_name_specifier identifier "::" +// +std::string DILParser::ParseNestedNameSpecifier() { + // The first token in nested_name_specifier is always an identifier, or + // '(anonymous namespace)'. + switch (CurToken().GetKind()) { + case Token::l_paren: { + // Anonymous namespaces need to be treated specially: They are + // represented the the string '(anonymous namespace)', which has a + // space in it (throwing off normal parsing) and is not actually + // proper C++> Check to see if we're looking at + // '(anonymous namespace)::...' + + // Look for all the pieces, in order: + // l_paren 'anonymous' 'namespace' r_paren coloncolon + if (m_dil_lexer.LookAhead(1).Is(Token::identifier) && + (m_dil_lexer.LookAhead(1).GetSpelling() == "anonymous") && + m_dil_lexer.LookAhead(2).Is(Token::identifier) && + (m_dil_lexer.LookAhead(2).GetSpelling() == "namespace") && + m_dil_lexer.LookAhead(3).Is(Token::r_paren) && + m_dil_lexer.LookAhead(4).Is(Token::coloncolon)) { + m_dil_lexer.Advance(4); + + assert( + (CurToken().Is(Token::identifier) || CurToken().Is(Token::l_paren)) && + "Expected an identifier or anonymous namespace, but not found."); + // Continue parsing the nested_namespace_specifier. + std::string identifier2 = ParseNestedNameSpecifier(); + if (identifier2.empty()) { + Expect(Token::identifier); + identifier2 = CurToken().GetSpelling(); + m_dil_lexer.Advance(); + } + return "(anonymous namespace)::" + identifier2; + } + + return ""; + } // end of special handling for '(anonymous namespace)' + case Token::identifier: { + // If the next token is scope ("::"), then this is indeed a + // nested_name_specifier + if (m_dil_lexer.LookAhead(1).Is(Token::coloncolon)) { + // This nested_name_specifier is a single identifier. + std::string identifier = CurToken().GetSpelling(); + m_dil_lexer.Advance(1); + Expect(Token::coloncolon); + m_dil_lexer.Advance(); + // Continue parsing the nested_name_specifier. + return identifier + "::" + ParseNestedNameSpecifier(); + } + + return ""; + } + default: + return ""; + } +} + +// Parse an id_expression. +// +// id_expression: +// unqualified_id +// qualified_id +// +// qualified_id: +// ["::"] [nested_name_specifier] unqualified_id +// ["::"] identifier +// +// identifier: +// ? Token::identifier ? +// +std::string DILParser::ParseIdExpression() { + // Try parsing optional global scope operator. + bool global_scope = false; + if (CurToken().Is(Token::coloncolon)) { + global_scope = true; + m_dil_lexer.Advance(); + } + + // Try parsing optional nested_name_specifier. + std::string nested_name_specifier = ParseNestedNameSpecifier(); + + // If nested_name_specifier is present, then it's qualified_id production. + // Follow the first production rule. + if (!nested_name_specifier.empty()) { + // Parse unqualified_id and construct a fully qualified id expression. + auto unqualified_id = ParseUnqualifiedId(); + + return llvm::formatv("{0}{1}{2}", global_scope ? "::" : "", + nested_name_specifier, unqualified_id); + } + + // No nested_name_specifier, but with global scope -- this is also a + // qualified_id production. Follow the second production rule. + if (global_scope) { + Expect(Token::identifier); + std::string identifier = CurToken().GetSpelling(); + m_dil_lexer.Advance(); + return llvm::formatv("{0}{1}", global_scope ? "::" : "", identifier); + } + + // This is unqualified_id production. + return ParseUnqualifiedId(); +} + +// Parse an unqualified_id. +// +// unqualified_id: +// identifier +// +// identifier: +// ? Token::identifier ? +// +std::string DILParser::ParseUnqualifiedId() { + Expect(Token::identifier); + std::string identifier = CurToken().GetSpelling(); + m_dil_lexer.Advance(); + return identifier; +} + +void DILParser::BailOut(const std::string &error, uint32_t loc, + uint16_t err_len) { + if (m_error) + // If error is already set, then the parser is in the "bail-out" mode. Don't + // do anything and keep the original error. + return; + + m_error = + llvm::make_error<DILDiagnosticError>(m_input_expr, error, loc, err_len); + // Advance the lexer token index to the end of the lexed tokens vector. + m_dil_lexer.ResetTokenIdx(m_dil_lexer.NumLexedTokens() - 1); +} + +void DILParser::Expect(Token::Kind kind) { + if (CurToken().IsNot(kind)) { + BailOut(llvm::formatv("expected {0}, got: {1}", kind, CurToken()), + CurToken().GetLocation(), CurToken().GetSpelling().length()); + } +} + +} // namespace lldb_private::dil diff --git a/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py new file mode 100644 index 0000000000000..edb013c7b3526 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py @@ -0,0 +1,51 @@ +""" +Make sure 'frame var' using DIL parser/evaultor works for local variables. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + +import os +import shutil +import time + + +class TestFrameVarDILGlobalVariableLookup(TestBase): + # If your test case doesn't stress debug info, then + # set this to true. That way it won't be run once for + # each debug info format. + NO_DEBUG_INFO_TESTCASE = True + + def test_frame_var(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + self.expect_var_path("globalVar", type="int", value="-559038737") # 0xDEADBEEF + self.expect_var_path("globalPtr", type="int *") + self.expect_var_path("globalRef", type="int &") + self.expect_var_path("::globalVar", value="-559038737") + self.expect_var_path("::globalPtr", type="int *") + self.expect_var_path("::globalRef", type="int &") + + self.expect( + "frame variable 'externGlobalVar'", + error=True, + substrs=["use of undeclared identifier"], + ) # 0x00C0FFEE + self.expect( + "frame variable '::externGlobalVar'", + error=True, + substrs=["use of undeclared identifier"], + ) # ["12648430"]) + + self.expect_var_path("ns::globalVar", value="13") + self.expect_var_path("ns::globalPtr", type="int *") + self.expect_var_path("ns::globalRef", type="int &") + self.expect_var_path("::ns::globalVar", value="13") + self.expect_var_path("::ns::globalPtr", type="int *") + self.expect_var_path("::ns::globalRef", type="int &") diff --git a/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp new file mode 100644 index 0000000000000..5bae4fd423e32 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp @@ -0,0 +1,15 @@ +int globalVar = 0xDEADBEEF; +extern int externGlobalVar; + +int *globalPtr = &globalVar; +int &globalRef = globalVar; + +namespace ns { +int globalVar = 13; +int *globalPtr = &globalVar; +int &globalRef = globalVar; +} // namespace ns + +int main(int argc, char **argv) { + return 0; // Set a breakpoint here +} diff --git a/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py new file mode 100644 index 0000000000000..cf230928fc117 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py @@ -0,0 +1,29 @@ +""" +Make sure 'frame var' using DIL parser/evaultor works for local variables. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + +import os +import shutil +import time + + +class TestFrameVarDILInstanceVariables(TestBase): + # If your test case doesn't stress debug info, then + # set this to true. That way it won't be run once for + # each debug info format. + NO_DEBUG_INFO_TESTCASE = True + + def test_frame_var(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + self.expect_var_path("this", type="TestMethods *") + self.expect_var_path("c", children=[ValueCheck(name="field_", value="-1")]) diff --git a/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp new file mode 100644 index 0000000000000..7a559c4007415 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp @@ -0,0 +1,23 @@ +#include <string> + +class C { +public: + int field_ = 1337; +}; + +class TestMethods { +public: + void TestInstanceVariables() { + C c; + c.field_ = -1; + + return; // Set a breakpoint here + } +}; + +int main(int argc, char **argv) { + TestMethods tm; + + tm.TestInstanceVariables(); + return 0; +} diff --git a/lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py new file mode 100644 index 0000000000000..0f6618fe47984 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py @@ -0,0 +1,31 @@ +""" +Make sure 'frame var' using DIL parser/evaultor works for local variables. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + +import os +import shutil +import time + + +class TestFrameVarDILLocalVars(TestBase): + # If your test case doesn't stress debug info, then + # set this to true. That way it won't be run once for + # each debug info format. + NO_DEBUG_INFO_TESTCASE = True + + def test_frame_var(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + self.expect_var_path("a", value="1") + self.expect_var_path("b", value="2") + self.expect_var_path("c", value="'\\xfd'") + self.expect_var_path("s", value="4") diff --git a/lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp new file mode 100644 index 0000000000000..04c73539c5f89 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp @@ -0,0 +1,9 @@ +int main(int argc, char **argv) { + int a = 1; + int b = 2; + + char c = -3; + unsigned short s = 4; + + return 0; // Set a breakpoint here +} diff --git a/lldb/unittests/ValueObject/DILLexerTests.cpp b/lldb/unittests/ValueObject/DILLexerTests.cpp index 9e5b8efd7af80..9afa957901ae7 100644 --- a/lldb/unittests/ValueObject/DILLexerTests.cpp +++ b/lldb/unittests/ValueObject/DILLexerTests.cpp @@ -54,9 +54,9 @@ TEST(DILLexerTests, TokenKindTest) { EXPECT_TRUE(token.Is(Token::identifier)); EXPECT_FALSE(token.Is(Token::l_paren)); - EXPECT_TRUE(token.IsOneOf(Token::eof, Token::identifier)); - EXPECT_FALSE(token.IsOneOf(Token::l_paren, Token::r_paren, Token::coloncolon, - Token::eof)); + EXPECT_TRUE(token.IsOneOf({Token::eof, Token::identifier})); + EXPECT_FALSE(token.IsOneOf( + {Token::l_paren, Token::r_paren, Token::coloncolon, Token::eof})); } TEST(DILLexerTests, LookAheadTest) { @@ -150,7 +150,7 @@ TEST(DILLexerTests, IdentifiersTest) { DILLexer lexer(*maybe_lexer); Token token = lexer.GetCurrentToken(); EXPECT_TRUE(token.IsNot(Token::identifier)); - EXPECT_TRUE(token.IsOneOf(Token::eof, Token::coloncolon, Token::l_paren, - Token::r_paren)); + EXPECT_TRUE(token.IsOneOf( + {Token::eof, Token::coloncolon, Token::l_paren, Token::r_paren})); } } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits