llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: None (cmtice) <details> <summary>Changes</summary> 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. --- Patch is 63.08 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/120971.diff 20 Files Affected: - (added) lldb/docs/dil-expr-lang.ebnf (+40) - (added) lldb/include/lldb/ValueObject/DILAST.h (+161) - (added) lldb/include/lldb/ValueObject/DILEval.h (+62) - (added) lldb/include/lldb/ValueObject/DILLexer.h (+166) - (added) lldb/include/lldb/ValueObject/DILParser.h (+105) - (modified) lldb/source/Target/StackFrame.cpp (+45-7) - (modified) lldb/source/ValueObject/CMakeLists.txt (+4) - (added) lldb/source/ValueObject/DILAST.cpp (+228) - (added) lldb/source/ValueObject/DILEval.cpp (+117) - (added) lldb/source/ValueObject/DILLexer.cpp (+191) - (added) lldb/source/ValueObject/DILParser.cpp (+356) - (added) lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile (+3) - (added) lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py (+82) - (added) lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp (+18) - (added) lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile (+3) - (added) lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py (+62) - (added) lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp (+25) - (added) lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile (+3) - (added) lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py (+65) - (added) lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp (+26) ``````````diff diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf new file mode 100644 index 00000000000000..64b3e7758229c2 --- /dev/null +++ b/lldb/docs/dil-expr-lang.ebnf @@ -0,0 +1,40 @@ +(* 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 + | "this" + | "(" expression ")"; + +id_expression = unqualified_id + | qualified_id ; + +unqualified_id = identifier ; + +qualified_id = ["::"] [nested_name_specifier] unqualified_id + | ["::"] identifier ; + +identifier = ? dil::TokenKind::identifier ? ; + +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 00000000000000..9f0a1a2221e388 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -0,0 +1,161 @@ +//===-- 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 <memory> +#include <string> +#include <vector> + +#include "lldb/ValueObject/ValueObject.h" + +namespace lldb_private { + +namespace dil { + +/// The various types DIL AST nodes (used by the DIL parser). +enum class NodeKind { + eErrorNode, + eIdentifierNode, +}; + +/// Class used to store & manipulate information about identifiers. +class IdentifierInfo { +public: + enum class Kind { + eValue, + eContextArg, + }; + + static std::unique_ptr<IdentifierInfo> FromValue(ValueObject &valobj) { + CompilerType type; + type = valobj.GetCompilerType(); + return std::unique_ptr<IdentifierInfo>( + new IdentifierInfo(Kind::eValue, type, valobj.GetSP(), {})); + } + + static std::unique_ptr<IdentifierInfo> FromContextArg(CompilerType type) { + lldb::ValueObjectSP empty_value; + return std::unique_ptr<IdentifierInfo>( + new IdentifierInfo(Kind::eContextArg, type, empty_value, {})); + } + + Kind GetKind() const { return m_kind; } + lldb::ValueObjectSP GetValue() const { return m_value; } + + CompilerType GetType() { return m_type; } + bool IsValid() const { return m_type.IsValid(); } + + IdentifierInfo(Kind kind, CompilerType type, lldb::ValueObjectSP value, + std::vector<uint32_t> path) + : m_kind(kind), m_type(type), m_value(std::move(value)) {} + +private: + Kind m_kind; + CompilerType m_type; + lldb::ValueObjectSP m_value; +}; + +/// Given the name of an identifier (variable name, member name, type name, +/// etc.), find the ValueObject for that name (if it exists) and create and +/// return an IdentifierInfo object containing all the relevant information +/// about that object (for DIL parsing and evaluating). +std::unique_ptr<IdentifierInfo> LookupIdentifier( + const std::string &name, std::shared_ptr<ExecutionContextScope> ctx_scope, + lldb::DynamicValueType use_dynamic, CompilerType *scope_ptr = nullptr); + +/// 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 DILASTNode { +public: + DILASTNode(uint32_t location, NodeKind kind) + : m_location(location), m_kind(kind) {} + virtual ~DILASTNode() = default; + + virtual void 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 DILASTNodeUP = std::unique_ptr<DILASTNode>; + +class ErrorNode : public DILASTNode { +public: + ErrorNode(CompilerType empty_type) + : DILASTNode(0, NodeKind::eErrorNode), m_empty_type(empty_type) {} + void Accept(Visitor *v) const override; + + static bool classof(const DILASTNode *node) { + return node->GetKind() == NodeKind::eErrorNode; + } + +private: + CompilerType m_empty_type; +}; + +class IdentifierNode : public DILASTNode { +public: + IdentifierNode(uint32_t location, std::string name, + lldb::DynamicValueType use_dynamic, + std::shared_ptr<ExecutionContextScope> exe_ctx_scope) + : DILASTNode(location, NodeKind::eIdentifierNode), + m_name(std::move(name)), m_use_dynamic(use_dynamic), + m_ctx_scope(exe_ctx_scope) {} + + void Accept(Visitor *v) const override; + + lldb::DynamicValueType use_dynamic() const { return m_use_dynamic; } + std::string name() const { return m_name; } + std::shared_ptr<ExecutionContextScope> get_exe_context() const { + return m_ctx_scope; + } + + static bool classof(const DILASTNode *node) { + return node->GetKind() == NodeKind::eIdentifierNode; + } + +private: + std::string m_name; + lldb::DynamicValueType m_use_dynamic; + std::shared_ptr<ExecutionContextScope> m_ctx_scope; +}; + +/// 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 void Visit(const ErrorNode *node) = 0; + virtual void Visit(const IdentifierNode *node) = 0; +}; + +} // namespace dil + +} // namespace lldb_private + +#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 00000000000000..4006bb10630f24 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -0,0 +1,62 @@ +//===-- 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 <memory> +#include <vector> + +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILParser.h" + +namespace lldb_private { + +namespace dil { + +class DILInterpreter : Visitor { +public: + DILInterpreter(lldb::TargetSP target, std::shared_ptr<std::string> sm); + DILInterpreter(lldb::TargetSP target, std::shared_ptr<std::string> sm, + lldb::ValueObjectSP scope); + DILInterpreter(lldb::TargetSP target, std::shared_ptr<std::string> sm, + lldb::DynamicValueType use_dynamic); + + lldb::ValueObjectSP DILEval(const DILASTNode *tree, lldb::TargetSP target_sp, + Status &error); + +private: + lldb::ValueObjectSP DILEvalNode(const DILASTNode *node); + + bool Success() { return m_error.Success(); } + + void SetError(ErrorCode error_code, std::string error, uint32_t loc); + + void Visit(const ErrorNode *node) override; + void Visit(const IdentifierNode *node) override; + +private: + // Used by the interpreter to create objects, perform casts, etc. + lldb::TargetSP m_target; + + std::shared_ptr<std::string> m_sm; + + lldb::ValueObjectSP m_result; + + lldb::ValueObjectSP m_scope; + + lldb::DynamicValueType m_default_dynamic; + + Status m_error; +}; + +} // namespace dil + +} // namespace lldb_private + +#endif // LLDB_VALUEOBJECT_DILEVAL_H_ diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h new file mode 100644 index 00000000000000..c794fb2bfc0ed3 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -0,0 +1,166 @@ +//===-- DILLexer.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_DILLEXER_H_ +#define LLDB_VALUEOBJECT_DILLEXER_H_ + +#include <limits.h> + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include "llvm/ADT/StringRef.h" + +namespace lldb_private { + +namespace dil { + +enum class TokenKind { + coloncolon, + eof, + identifier, + invalid, + kw_namespace, + kw_this, + l_paren, + none, + r_paren, + unknown, +}; + +/// Class defining the tokens generated by the DIL lexer and used by the +/// DIL parser. +class DILToken { +public: + DILToken(dil::TokenKind kind, std::string spelling, uint32_t start, + uint32_t len) + : m_kind(kind), m_spelling(spelling), m_start_pos(start), m_length(len) {} + + DILToken() + : m_kind(dil::TokenKind::none), m_spelling(""), m_start_pos(0), + m_length(0) {} + + void setKind(dil::TokenKind kind) { m_kind = kind; } + dil::TokenKind getKind() const { return m_kind; } + + std::string getSpelling() const { return m_spelling; } + + uint32_t getLength() const { return m_length; } + + bool is(dil::TokenKind kind) const { return m_kind == kind; } + + bool isNot(dil::TokenKind kind) const { return m_kind != kind; } + + bool isOneOf(dil::TokenKind kind1, dil::TokenKind kind2) const { + return is(kind1) || is(kind2); + } + + template <typename... Ts> bool isOneOf(dil::TokenKind kind, Ts... Ks) const { + return is(kind) || isOneOf(Ks...); + } + + uint32_t getLocation() const { return m_start_pos; } + + void setValues(dil::TokenKind kind, std::string spelling, uint32_t start, + uint32_t len) { + m_kind = kind; + m_spelling = spelling; + m_start_pos = start; + m_length = len; + } + + static const std::string getTokenName(dil::TokenKind kind); + +private: + dil::TokenKind m_kind; + std::string m_spelling; + uint32_t m_start_pos; // within entire expression string + uint32_t m_length; +}; + +/// Class for doing the simple lexing required by DIL. +class DILLexer { +public: + DILLexer(std::shared_ptr<std::string> dil_sm) : m_expr(*dil_sm) { + m_cur_pos = m_expr.begin(); + // Use UINT_MAX to indicate invalid/uninitialized value. + m_tokens_idx = UINT_MAX; + } + + bool Lex(DILToken &result, bool look_ahead = false); + + bool Is_Word(std::string::iterator start, uint32_t &length); + + uint32_t GetLocation() { return m_cur_pos - m_expr.begin(); } + + /// Update 'result' with the other paremeter values, create a + /// duplicate token, and push the duplicate token onto the vector of + /// lexed tokens. + void UpdateLexedTokens(DILToken &result, dil::TokenKind tok_kind, + std::string tok_str, uint32_t tok_pos, + uint32_t tok_len); + + /// Return the lexed token N+1 positions ahead of the 'current' token + /// being handled by the DIL parser. + const DILToken &LookAhead(uint32_t N); + + const DILToken &AcceptLookAhead(uint32_t N); + + /// Return the index for the 'current' token being handled by the DIL parser. + uint32_t GetCurrentTokenIdx() { return m_tokens_idx; } + + /// Return the current token to be handled by the DIL parser. + DILToken &GetCurrentToken() { return m_lexed_tokens[m_tokens_idx]; } + + /// Update the index for the 'current' token, to point to the next lexed + /// token. + bool IncrementTokenIdx() { + if (m_tokens_idx >= m_lexed_tokens.size() - 1) + return false; + + m_tokens_idx++; + return true; + } + + /// Set the index for the 'current' token (to be handled by the parser) + /// to a particular position. Used for either committing 'look ahead' parsing + /// or rolling back tentative parsing. + bool ResetTokenIdx(uint32_t new_value) { + if (new_value > m_lexed_tokens.size() - 1) + return false; + + m_tokens_idx = new_value; + return true; + } + +private: + // The input string we are lexing & parsing. + std::string m_expr; + + // The current position of the lexer within m_expr (the character position, + // within the string, of the next item to be lexed). + std::string::iterator m_cur_pos; + + // Holds all of the tokens lexed so far. + std::vector<DILToken> m_lexed_tokens; + + // Index into m_lexed_tokens; indicates which token the DIL parser is + // currently trying to parse/handle. + uint32_t m_tokens_idx; + + // "invalid" token; to be returned by lexer when 'look ahead' fails. + DILToken m_invalid_token; +}; + +} // namespace dil + +} // namespace lldb_private + +#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 00000000000000..b718903b7bea49 --- /dev/null +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -0,0 +1,105 @@ +//===-- 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 <memory> +#include <optional> +#include <string> +#include <tuple> +#include <vector> + +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Utility/Status.h" +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILLexer.h" + +namespace lldb_private { + +namespace dil { + +enum class ErrorCode : unsigned char { + kOk = 0, + kInvalidExpressionSyntax, + kUndeclaredIdentifier, + kUnknown, +}; + +std::string FormatDiagnostics(std::shared_ptr<std::string> input_expr, + const std::string &message, uint32_t loc); + +/// 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: + explicit DILParser(std::shared_ptr<std::string> dil_input_expr, + std::shared_ptr<ExecutionContextScope> exe_ctx_scope, + lldb::DynamicValueType use_dynamic, bool use_synthetic, + bool fragile_ivar, bool check_ptr_vs_member); + + DILASTNodeUP Run(Status &error); + + ~DILParser() { m_ctx_scope.reset(); } + + bool UseSynthetic() { return m_use_synthetic; } + + lldb::DynamicValueType UseDynamic() { return m_use_dynamic; } + + using PtrOperator = std::tuple<dil::TokenKind, uint32_t>; + +private: + DILASTNodeUP ParseExpression(); + DILASTNodeUP ParsePrimaryExpression(); + + std::string ParseNestedNameSpecifier(); + + std::string ParseIdExpression(); + std::string ParseUnqualifiedId(); + + void ConsumeToken(); + + void BailOut(ErrorCode error_code, const std::string &error, uint32_t loc); + + void BailOut(Status error); + + void Expect(dil::TokenKind kind); + + std::string TokenDescription(const DILToken &token); + + template <typename... Ts> void ExpectOneOf(dil::TokenKind k, Ts... ks); + + void TentativeParsingRollback(uint32_t saved_idx) { + m_error.Clear(); + m_dil_lexer.ResetTokenIdx(saved_idx); + m_dil_token = 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<ExecutionContextScope> m_ctx_scope; + + std::shared_ptr<std::string> m_input_expr; + // The token lexer is stopped at (aka "current token"). + DILToken m_dil_token; + // Holds an error if it occures during parsing. + Status m_error; + + lldb::DynamicValueType m_use_dynamic; + bool m_use_synthetic; + bool m_fragile_ivar; + bool m_check_ptr_vs_member; + DILLexer m_dil_lexer; +}; // class DILParser + +} // namespace dil + +} // namespace lldb_private + +#endif // LLDB_VALUEOBJECT_DILPARSER_H_ diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 2633c976c13bf4..28450b1d1c1c1e 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -31,6 +31,8 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegisterValue.h" +#include "lldb/ValueObject/DILEval.h" +#include "lldb/ValueObject/DILParser.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectMemory.h" #include "lldb/ValueObject/ValueObjectVariable.h" @@ -511,22 +513,58 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath( VariableSP &var_sp, Status &error) { ExecutionContext exe_ctx; CalculateExecutionContext(exe_ctx); + bool use_DIL = exe_ctx.GetTargetRef().GetUseDIL(&exe_ctx); + if (use_DIL) return DILGetValueForVariableExpressionPath(var_expr, use_dynamic, options, var_sp, error); - - return LegacyGetValueForVariableExpressionPath(var_expr, use_dynamic, options, - var_sp, error); + else + return LegacyGetValueForVariableExpressionPath(var_expr, use_dynamic, + options, var_sp, error); } 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); + ValueObjectSP ret_val; + std::shared_ptr<std::string> source = + std::make_shared<std::string>(var_expr.data()); + + 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; + + // Parse the expression. + Status parse_error, eval_error; + dil::DILParser parser(source, shared_from_this(), use_dynamic, + !no_synth_child, !no_fragile_ivar, check_ptr_vs_member); + dil::DILASTNodeUP tree = parser.Run(parse_error); + if (parse_error.Fail()) { + error = std::move(parse_error); + return ValueObjectSP(); + } + + // Evaluate the parsed expression. + lldb::TargetSP target = this->CalculateTarget(); + dil::DILInterpreter interpreter(target, source, use_dynamic); + + ret_val = interpreter.DILEval(tree.get(), target, eval_error); + if (eval_error.Fail()) { + error = std::move(eval_error); + return ValueObjectSP(); + } + + if (ret_val) { + var_sp = ret_val->GetVariable(); + if (!var_sp && ret_val->GetParent()) { + var_sp = ret_val->GetParent()->GetVariable(); + } + } + return ret_val; } ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath( diff --git a/lldb/source/ValueObject/CMakeLists.txt b/lldb/source/... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/120971 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits