https://github.com/cmtice created https://github.com/llvm/llvm-project/pull/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. >From c14a0e3f80c1cc49a6717b41c8b90e2258168399 Mon Sep 17 00:00:00 2001 From: Caroline Tice <cmt...@google.com> Date: Mon, 23 Dec 2024 06:41:46 -0800 Subject: [PATCH] [LLDB] Add DIL code for handling plain variable names. 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. --- lldb/docs/dil-expr-lang.ebnf | 40 ++ lldb/include/lldb/ValueObject/DILAST.h | 161 ++++++++ lldb/include/lldb/ValueObject/DILEval.h | 62 +++ lldb/include/lldb/ValueObject/DILLexer.h | 166 ++++++++ lldb/include/lldb/ValueObject/DILParser.h | 105 ++++++ lldb/source/Target/StackFrame.cpp | 52 ++- lldb/source/ValueObject/CMakeLists.txt | 4 + lldb/source/ValueObject/DILAST.cpp | 228 +++++++++++ lldb/source/ValueObject/DILEval.cpp | 117 ++++++ lldb/source/ValueObject/DILLexer.cpp | 191 ++++++++++ lldb/source/ValueObject/DILParser.cpp | 356 ++++++++++++++++++ .../basics/GlobalVariableLookup/Makefile | 3 + .../TestFrameVarDILGlobalVariableLookup.py | 82 ++++ .../basics/GlobalVariableLookup/main.cpp | 18 + .../var-dil/basics/InstanceVariables/Makefile | 3 + .../TestFrameVarDILInstanceVariables.py | 62 +++ .../var-dil/basics/InstanceVariables/main.cpp | 25 ++ .../frame/var-dil/basics/LocalVars/Makefile | 3 + .../LocalVars/TestFrameVarDILLocalVars.py | 65 ++++ .../frame/var-dil/basics/LocalVars/main.cpp | 26 ++ 20 files changed, 1762 insertions(+), 7 deletions(-) create mode 100644 lldb/docs/dil-expr-lang.ebnf create mode 100644 lldb/include/lldb/ValueObject/DILAST.h create mode 100644 lldb/include/lldb/ValueObject/DILEval.h create mode 100644 lldb/include/lldb/ValueObject/DILLexer.h create mode 100644 lldb/include/lldb/ValueObject/DILParser.h create mode 100644 lldb/source/ValueObject/DILAST.cpp create mode 100644 lldb/source/ValueObject/DILEval.cpp create mode 100644 lldb/source/ValueObject/DILLexer.cpp create mode 100644 lldb/source/ValueObject/DILParser.cpp create mode 100644 lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp create mode 100644 lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp create mode 100644 lldb/test/API/commands/frame/var-dil/basics/LocalVars/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp 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/ValueObject/CMakeLists.txt index 70cb3d6d53f071..92683916f5a522 100644 --- a/lldb/source/ValueObject/CMakeLists.txt +++ b/lldb/source/ValueObject/CMakeLists.txt @@ -1,4 +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 00000000000000..1af0dc57ddcea7 --- /dev/null +++ b/lldb/source/ValueObject/DILAST.cpp @@ -0,0 +1,228 @@ +//===-- 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 "lldb/API/SBType.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/ValueObject/ValueObjectRegister.h" +#include "lldb/ValueObject/ValueObjectVariable.h" +#include "llvm/ADT/StringRef.h" + +#include <vector> + +namespace lldb_private { + +namespace dil { + +static lldb::ValueObjectSP +LookupStaticIdentifier(lldb::TargetSP target_sp, + const llvm::StringRef &name_ref, + ConstString unqualified_name) { + // List global variable with the same "basename". There can be many matches + // from other scopes (namespaces, classes), so we do additional filtering + // later. + VariableList variable_list; + ConstString name(name_ref); + target_sp->GetImages().FindGlobalVariables(name, 1, variable_list); + if (!variable_list.Empty()) { + ExecutionContextScope *exe_scope = target_sp->GetProcessSP().get(); + if (exe_scope == nullptr) + exe_scope = target_sp.get(); + for (const lldb::VariableSP &var_sp : variable_list) { + lldb::ValueObjectSP valobj_sp( + ValueObjectVariable::Create(exe_scope, var_sp)); + if (valobj_sp && valobj_sp->GetVariable() && + (valobj_sp->GetVariable()->NameMatches(unqualified_name) || + valobj_sp->GetVariable()->NameMatches(ConstString(name_ref)))) + return valobj_sp; + } + } + return nullptr; +} + +static lldb::VariableSP DILFindVariable(ConstString name, + VariableList *variable_list) { + lldb::VariableSP exact_match; + std::vector<lldb::VariableSP> possible_matches; + + typedef std::vector<lldb::VariableSP> collection; + typedef collection::iterator iterator; + + iterator pos, end = variable_list->end(); + for (pos = variable_list->begin(); pos != end; ++pos) { + llvm::StringRef str_ref_name = pos->get()->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(*pos); + else if (pos->get()->NameMatches(name)) + possible_matches.push_back(*pos); + } + + // 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 != llvm::adl_end(possible_matches)) + exact_match = *exact_match_it; + + if (!exact_match) + // Look for a global var exact match. + for (auto var_sp : possible_matches) { + llvm::StringRef str_ref_name = var_sp->GetName().GetStringRef(); + if (str_ref_name.size() > 2 && str_ref_name[0] == ':' && + str_ref_name[1] == ':') + str_ref_name = str_ref_name.drop_front(2); + ConstString tmp_name(str_ref_name); + if (tmp_name == name) { + exact_match = var_sp; + break; + } + } + + // Take any match at this point. + if (!exact_match && possible_matches.size() > 0) + exact_match = possible_matches[0]; + + return exact_match; +} + +std::unique_ptr<IdentifierInfo> +LookupIdentifier(const std::string &name, + std::shared_ptr<ExecutionContextScope> ctx_scope, + lldb::DynamicValueType use_dynamic, CompilerType *scope_ptr) { + ConstString name_str(name); + llvm::StringRef name_ref = name_str.GetStringRef(); + + // 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.starts_with("$")) { + lldb::ValueObjectSP value_sp; + const char *reg_name = name_ref.drop_front(1).data(); + Target *target = ctx_scope->CalculateTarget().get(); + Process *process = ctx_scope->CalculateProcess().get(); + if (target && process) { + StackFrame *stack_frame = ctx_scope->CalculateStackFrame().get(); + if (stack_frame) { + lldb::RegisterContextSP reg_ctx(stack_frame->GetRegisterContext()); + if (reg_ctx) { + if (const RegisterInfo *reg_info = + reg_ctx->GetRegisterInfoByName(reg_name)) + value_sp = + ValueObjectRegister::Create(stack_frame, reg_ctx, reg_info); + } + } + } + if (value_sp) + return IdentifierInfo::FromValue(*value_sp); + else + return nullptr; + } + + // Internally values don't have global scope qualifier in their names and + // LLDB doesn't support queries with it too. + bool global_scope = false; + if (name_ref.starts_with("::")) { + name_ref = name_ref.drop_front(2); + global_scope = true; + } + + // If the identifier doesn't refer to the global scope and doesn't have any + // other scope qualifiers, try looking among the local and instance variables. + if (!global_scope && !name_ref.contains("::")) { + if (!scope_ptr || !scope_ptr->IsValid()) { + // Lookup in the current frame. + lldb::StackFrameSP frame = ctx_scope->CalculateStackFrame(); + // Try looking for a local variable in current scope. + lldb::ValueObjectSP value_sp; + lldb::VariableListSP var_list_sp(frame->GetInScopeVariableList(true)); + VariableList *variable_list = var_list_sp.get(); + if (variable_list) { + lldb::VariableSP var_sp = + DILFindVariable(ConstString(name_ref), variable_list); + if (var_sp) + value_sp = frame->GetValueObjectForFrameVariable(var_sp, use_dynamic); + } + if (!value_sp) + value_sp = frame->FindVariable(ConstString(name_ref)); + + if (value_sp) + // Force static value, otherwise we can end up with the "real" type. + return IdentifierInfo::FromValue(*value_sp); + + // Try looking for an instance variable (class member). + ConstString this_string("this"); + value_sp = frame->FindVariable(this_string); + if (value_sp) + value_sp = value_sp->GetChildMemberWithName(name_ref.data()); + + if (value_sp) + // Force static value, otherwise we can end up with the "real" type. + return IdentifierInfo::FromValue(*(value_sp->GetStaticValue())); + } + } + + // Try looking for a global or static variable. + + lldb::ValueObjectSP value; + if (!global_scope) { + // Try looking for static member of the current scope value, e.g. + // `ScopeType::NAME`. NAME can include nested struct (`Nested::SUBNAME`), + // but it cannot be part of the global scope (start with "::"). + 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 = LookupStaticIdentifier(ctx_scope->CalculateTarget(), + name_with_type_prefix, name_str); + } + + // Lookup a regular global variable. + if (!value) + value = LookupStaticIdentifier(ctx_scope->CalculateTarget(), name_ref, + name_str); + + // Last resort, lookup as a register (e.g. `rax` or `rip`). + if (!value) { + Target *target = ctx_scope->CalculateTarget().get(); + Process *process = ctx_scope->CalculateProcess().get(); + if (target && process) { + StackFrame *stack_frame = ctx_scope->CalculateStackFrame().get(); + if (stack_frame) { + lldb::RegisterContextSP reg_ctx(stack_frame->GetRegisterContext()); + if (reg_ctx) { + if (const RegisterInfo *reg_info = + reg_ctx->GetRegisterInfoByName(name_ref.data())) + value = ValueObjectRegister::Create(stack_frame, reg_ctx, reg_info); + } + } + } + } + + // Force static value, otherwise we can end up with the "real" type. + if (value) + return IdentifierInfo::FromValue(*value); + else + return nullptr; +} + +void ErrorNode::Accept(Visitor *v) const { v->Visit(this); } + +void IdentifierNode::Accept(Visitor *v) const { v->Visit(this); } + +} // namespace dil + +} // namespace lldb_private diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp new file mode 100644 index 00000000000000..01da8521536503 --- /dev/null +++ b/lldb/source/ValueObject/DILEval.cpp @@ -0,0 +1,117 @@ +//===-- 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 <memory> + +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/ValueObject.h" +#include "llvm/Support/FormatAdapters.h" + +namespace lldb_private { + +namespace dil { + +DILInterpreter::DILInterpreter(lldb::TargetSP target, + std::shared_ptr<std::string> sm) + : m_target(std::move(target)), m_sm(std::move(sm)) { + m_default_dynamic = lldb::eNoDynamicValues; +} + +DILInterpreter::DILInterpreter(lldb::TargetSP target, + std::shared_ptr<std::string> sm, + lldb::DynamicValueType use_dynamic) + : m_target(std::move(target)), m_sm(std::move(sm)), + m_default_dynamic(use_dynamic) {} + +DILInterpreter::DILInterpreter(lldb::TargetSP target, + std::shared_ptr<std::string> sm, + lldb::ValueObjectSP scope) + : m_target(std::move(target)), m_sm(std::move(sm)), + m_scope(std::move(scope)) { + m_default_dynamic = lldb::eNoDynamicValues; + // If `m_scope` is a reference, dereference it. All operations on a reference + // should be operations on the referent. + if (m_scope->GetCompilerType().IsValid() && + m_scope->GetCompilerType().IsReferenceType()) { + Status error; + m_scope = m_scope->Dereference(error); + } +} + +lldb::ValueObjectSP DILInterpreter::DILEval(const DILASTNode *tree, + lldb::TargetSP target_sp, + Status &error) { + m_error.Clear(); + // Evaluate an AST. + DILEvalNode(tree); + // Set the error. + error = std::move(m_error); + // Return the computed result. If there was an error, it will be invalid. + return m_result; +} + +lldb::ValueObjectSP DILInterpreter::DILEvalNode(const DILASTNode *node) { + + // Traverse an AST pointed by the `node`. + node->Accept(this); + + // Return the computed value for convenience. The caller is responsible for + // checking if an error occured during the evaluation. + return m_result; +} + +void DILInterpreter::SetError(ErrorCode code, std::string error, uint32_t loc) { + assert(m_error.Success() && "interpreter can error only once"); + m_error = Status((uint32_t)code, lldb::eErrorTypeGeneric, + FormatDiagnostics(m_sm, error, loc)); +} + +void DILInterpreter::Visit(const ErrorNode *node) { + // The AST is not valid. + m_result = lldb::ValueObjectSP(); +} + +void DILInterpreter::Visit(const IdentifierNode *node) { + std::shared_ptr<ExecutionContextScope> exe_ctx_scope = + node->get_exe_context(); + lldb::DynamicValueType use_dynamic = node->use_dynamic(); + + std::unique_ptr<IdentifierInfo> identifier = + LookupIdentifier(node->name(), exe_ctx_scope, use_dynamic); + + if (!identifier) { + std::string errMsg; + std::string name = node->name(); + if (name == "this") + errMsg = "invalid use of 'this' outside of a non-static member function"; + else + errMsg = llvm::formatv("use of undeclared identifier '{0}'", name); + SetError(ErrorCode::kUndeclaredIdentifier, errMsg, node->GetLocation()); + m_result = lldb::ValueObjectSP(); + return; + } + lldb::ValueObjectSP val; + lldb::TargetSP target_sp; + Status error; + + assert(identifier->GetKind() == IdentifierInfo::Kind::eValue && + "Unrecognized identifier kind"); + + val = identifier->GetValue(); + target_sp = val->GetTargetSP(); + assert(target_sp && target_sp->IsValid() && + "identifier doesn't resolve to a valid value"); + + m_result = val; +} + +} // namespace dil + +} // namespace lldb_private diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp new file mode 100644 index 00000000000000..c63a83bea6f1df --- /dev/null +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -0,0 +1,191 @@ +//===-- DILLexer.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/DILLexer.h" + +namespace lldb_private { + +namespace dil { + +const std::string DILToken::getTokenName(dil::TokenKind kind) { + std::string retval; + switch (kind) { + case dil::TokenKind::coloncolon: + retval = "coloncolon"; + break; + case dil::TokenKind::eof: + retval = "eof"; + break; + case dil::TokenKind::identifier: + retval = "identifier"; + break; + case dil::TokenKind::kw_namespace: + retval = "namespace"; + break; + case dil::TokenKind::kw_this: + retval = "this"; + break; + case dil::TokenKind::l_paren: + retval = "l_paren"; + break; + case dil::TokenKind::r_paren: + retval = "r_paren"; + break; + case dil::TokenKind::unknown: + retval = "unknown"; + break; + default: + retval = "token_name"; + break; + } + return retval; +} + +static bool Is_Letter(char c) { + if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) + return true; + return false; +} + +static bool Is_Digit(char c) { return ('0' <= c && c <= '9'); } + +bool DILLexer::Is_Word(std::string::iterator start, uint32_t &length) { + bool done = false; + for (; m_cur_pos != m_expr.end() && !done; ++m_cur_pos) { + char c = *m_cur_pos; + if (!Is_Letter(c) && !Is_Digit(c) && c != '_') { + done = true; + break; + } else + length++; + } + if (length > 0) + return true; + else + m_cur_pos = start; + return false; +} + +void DILLexer::UpdateLexedTokens(DILToken &result, dil::TokenKind tok_kind, + std::string tok_str, uint32_t tok_pos, + uint32_t tok_len) { + DILToken new_token; + result.setValues(tok_kind, tok_str, tok_pos, tok_len); + new_token = result; + m_lexed_tokens.push_back(std::move(new_token)); +} + +bool DILLexer::Lex(DILToken &result, bool look_ahead) { + bool retval = true; + + if (!look_ahead) { + // We're being asked for the 'next' token, and not a part of a LookAhead. + // Check to see if we've already lexed it and pushed it onto our tokens + // vector; if so, return the next token from the vector, rather than doing + // more lexing. + if ((m_tokens_idx != UINT_MAX) && + (m_tokens_idx < m_lexed_tokens.size() - 1)) { + result = m_lexed_tokens[m_tokens_idx + 1]; + return retval; + } + } + + // Skip over whitespace (spaces). + while (m_cur_pos != m_expr.end() && *m_cur_pos == ' ') + m_cur_pos++; + + // Check to see if we've reached the end of our input string. + if (m_cur_pos == m_expr.end()) { + UpdateLexedTokens(result, dil::TokenKind::eof, "", m_expr.length(), 0); + return retval; + } + + uint32_t position = m_cur_pos - m_expr.begin(); + ; + std::string::iterator start = m_cur_pos; + uint32_t length = 0; + if (Is_Word(start, length)) { + dil::TokenKind kind; + std::string word = m_expr.substr(position, length); + if (word == "this") + kind = dil::TokenKind::kw_this; + else if (word == "namespace") + kind = dil::TokenKind::kw_namespace; + else + kind = dil::TokenKind::identifier; + + UpdateLexedTokens(result, kind, word, position, length); + return true; + } + + switch (*m_cur_pos) { + case '(': + m_cur_pos++; + UpdateLexedTokens(result, dil::TokenKind::l_paren, "(", position, 1); + return true; + case ')': + m_cur_pos++; + UpdateLexedTokens(result, dil::TokenKind::r_paren, ")", position, 1); + return true; + case ':': + if (position + 1 < m_expr.size() && m_expr[position + 1] == ':') { + m_cur_pos += 2; + UpdateLexedTokens(result, dil::TokenKind::coloncolon, "::", position, 2); + return true; + } + break; + default: + break; + } + // Empty Token + result.setValues(dil::TokenKind::none, "", m_expr.length(), 0); + return false; +} + +const DILToken &DILLexer::LookAhead(uint32_t N) { + uint32_t extra_lexed_tokens = m_lexed_tokens.size() - m_tokens_idx - 1; + + if (N + 1 < extra_lexed_tokens) + return m_lexed_tokens[m_tokens_idx + N + 1]; + + uint32_t remaining_tokens = + (m_tokens_idx + N + 1) - m_lexed_tokens.size() + 1; + + bool done = false; + bool look_ahead = true; + while (!done && remaining_tokens > 0) { + DILToken tok; + Lex(tok, look_ahead); + if (tok.getKind() == dil::TokenKind::eof) + done = true; + remaining_tokens--; + }; + + if (remaining_tokens > 0) { + m_invalid_token.setValues(dil::TokenKind::invalid, "", 0, 0); + return m_invalid_token; + } else + return m_lexed_tokens[m_tokens_idx + N + 1]; +} + +const DILToken &DILLexer::AcceptLookAhead(uint32_t N) { + if (m_tokens_idx + N + 1 > m_lexed_tokens.size()) + return m_invalid_token; + + m_tokens_idx += N + 1; + return m_lexed_tokens[m_tokens_idx]; +} + +} // namespace dil + +} // namespace lldb_private diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp new file mode 100644 index 00000000000000..eb36de80be76a5 --- /dev/null +++ b/lldb/source/ValueObject/DILParser.cpp @@ -0,0 +1,356 @@ +//===-- 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 <limits.h> +#include <stdlib.h> + +#include <memory> +#include <sstream> +#include <string> + +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/ValueObject/DILAST.h" +#include "lldb/ValueObject/DILEval.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatAdapters.h" + +namespace lldb_private { + +namespace dil { + +inline void TokenKindsJoinImpl(std::ostringstream &os, dil::TokenKind k) { + os << "'" << DILToken::getTokenName(k) << "'"; +} + +template <typename... Ts> +inline void TokenKindsJoinImpl(std::ostringstream &os, dil::TokenKind k, + Ts... ks) { + TokenKindsJoinImpl(os, k); + os << ", "; + TokenKindsJoinImpl(os, ks...); +} + +template <typename... Ts> +inline std::string TokenKindsJoin(dil::TokenKind k, Ts... ks) { + std::ostringstream os; + TokenKindsJoinImpl(os, k, ks...); + + return os.str(); +} + +std::string FormatDiagnostics(std::shared_ptr<std::string> input_expr, + const std::string &message, uint32_t loc) { + // Get the source buffer and the location of the current token. + llvm::StringRef text(*input_expr); + size_t loc_offset = (size_t)loc; + + // Look for the start of the line. + size_t line_start = text.rfind('\n', loc_offset); + line_start = line_start == llvm::StringRef::npos ? 0 : line_start + 1; + + // Look for the end of the line. + size_t line_end = text.find('\n', loc_offset); + line_end = line_end == llvm::StringRef::npos ? text.size() : line_end; + + // Get a view of the current line in the source code and the position of the + // diagnostics pointer. + llvm::StringRef line = text.slice(line_start, line_end); + int32_t arrow = loc + 1; // Column offset starts at 1, not 0. + + // Calculate the padding in case we point outside of the expression (this can + // happen if the parser expected something, but got EOF).˚ + size_t expr_rpad = std::max(0, arrow - static_cast<int32_t>(line.size())); + size_t arrow_rpad = std::max(0, static_cast<int32_t>(line.size()) - arrow); + + return llvm::formatv("<expr:1:{0}>: {1}\n{2}\n{3}", loc, message, + llvm::fmt_pad(line, 0, expr_rpad), + llvm::fmt_pad("^", arrow - 1, arrow_rpad)); +} + +DILParser::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) + : m_ctx_scope(exe_ctx_scope), m_input_expr(dil_input_expr), + m_use_dynamic(use_dynamic), m_use_synthetic(use_synthetic), + m_fragile_ivar(fragile_ivar), m_check_ptr_vs_member(check_ptr_vs_member), + m_dil_lexer(DILLexer(dil_input_expr)) { + // Initialize the token. + m_dil_token.setKind(dil::TokenKind::unknown); +} + +DILASTNodeUP DILParser::Run(Status &error) { + ConsumeToken(); + + DILASTNodeUP expr; + + expr = ParseExpression(); + + Expect(dil::TokenKind::eof); + + error = std::move(m_error); + m_error.Clear(); + + // Explicitly return ErrorNode if there was an error during the parsing. + // Some routines raise an error, but don't change the return value (e.g. + // Expect). + if (error.Fail()) { + CompilerType bad_type; + return std::make_unique<ErrorNode>(bad_type); + } + return expr; +} + +// Parse an expression. +// +// expression: +// primary_expression +// +DILASTNodeUP DILParser::ParseExpression() { return ParsePrimaryExpression(); } + +// Parse a primary_expression. +// +// primary_expression: +// id_expression +// "this" +// "(" expression ")" +// +DILASTNodeUP DILParser::ParsePrimaryExpression() { + CompilerType bad_type; + if (m_dil_token.isOneOf(dil::TokenKind::coloncolon, + dil::TokenKind::identifier)) { + // Save the source location for the diagnostics message. + uint32_t loc = m_dil_token.getLocation(); + auto identifier = ParseIdExpression(); + + return std::make_unique<IdentifierNode>(loc, identifier, m_use_dynamic, + m_ctx_scope); + } else if (m_dil_token.is(dil::TokenKind::kw_this)) { + // Save the source location for the diagnostics message. + uint32_t loc = m_dil_token.getLocation(); + ConsumeToken(); + + // Special case for "this" pointer. As per C++ standard, it's a prvalue. + return std::make_unique<IdentifierNode>(loc, "this", m_use_dynamic, + m_ctx_scope); + } else if (m_dil_token.is(dil::TokenKind::l_paren)) { + ConsumeToken(); + auto expr = ParseExpression(); + Expect(dil::TokenKind::r_paren); + ConsumeToken(); + return expr; + } + + BailOut(ErrorCode::kInvalidExpressionSyntax, + llvm::formatv("Unexpected token: {0}", TokenDescription(m_dil_token)), + m_dil_token.getLocation()); + return std::make_unique<ErrorNode>(bad_type); +} + +// 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)'. + if (m_dil_token.isNot(dil::TokenKind::identifier) && + m_dil_token.isNot(dil::TokenKind::l_paren)) { + return ""; + } + + // 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)::...' + if (m_dil_token.is(dil::TokenKind::l_paren)) { + // Look for all the pieces, in order: + // l_paren 'anonymous' 'namespace' r_paren coloncolon + if (m_dil_lexer.LookAhead(0).is(dil::TokenKind::identifier) && + (((DILToken)m_dil_lexer.LookAhead(0)).getSpelling() == "anonymous") && + m_dil_lexer.LookAhead(1).is(dil::TokenKind::kw_namespace) && + m_dil_lexer.LookAhead(2).is(dil::TokenKind::r_paren) && + m_dil_lexer.LookAhead(3).is(dil::TokenKind::coloncolon)) { + m_dil_token = m_dil_lexer.AcceptLookAhead(3); + + assert((m_dil_token.is(dil::TokenKind::identifier) || + m_dil_token.is(dil::TokenKind::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(dil::TokenKind::identifier); + identifier2 = m_dil_token.getSpelling(); + ConsumeToken(); + } + return "(anonymous namespace)::" + identifier2; + } else { + return ""; + } + } // end of special handling for '(anonymous namespace)' + + // If the next token is scope ("::"), then this is indeed a + // nested_name_specifier + if (m_dil_lexer.LookAhead(0).is(dil::TokenKind::coloncolon)) { + // This nested_name_specifier is a single identifier. + std::string identifier = m_dil_token.getSpelling(); + m_dil_token = m_dil_lexer.AcceptLookAhead(0); + Expect(dil::TokenKind::coloncolon); + ConsumeToken(); + // Continue parsing the nested_name_specifier. + return identifier + "::" + ParseNestedNameSpecifier(); + } + + return ""; +} + +// Parse an id_expression. +// +// id_expression: +// unqualified_id +// qualified_id +// +// qualified_id: +// ["::"] [nested_name_specifier] unqualified_id +// ["::"] identifier +// +// identifier: +// ? dil::TokenKind::identifier ? +// +std::string DILParser::ParseIdExpression() { + // Try parsing optional global scope operator. + bool global_scope = false; + if (m_dil_token.is(dil::TokenKind::coloncolon)) { + global_scope = true; + ConsumeToken(); + } + + // Try parsing optional nested_name_specifier. + auto 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. + else if (global_scope) { + Expect(dil::TokenKind::identifier); + std::string identifier = m_dil_token.getSpelling(); + ConsumeToken(); + return llvm::formatv("{0}{1}", global_scope ? "::" : "", identifier); + } + + // This is unqualified_id production. + return ParseUnqualifiedId(); +} + +// Parse an unqualified_id. +// +// unqualified_id: +// identifier +// +// identifier: +// ? dil::TokenKind::identifier ? +// +std::string DILParser::ParseUnqualifiedId() { + Expect(dil::TokenKind::identifier); + std::string identifier = m_dil_token.getSpelling(); + ConsumeToken(); + return identifier; +} + +void DILParser::BailOut(ErrorCode code, const std::string &error, + uint32_t loc) { + if (m_error.Fail()) { + // 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 = Status((uint32_t)code, lldb::eErrorTypeGeneric, + FormatDiagnostics(m_input_expr, error, loc)); + m_dil_token.setKind(dil::TokenKind::eof); +} + +void DILParser::BailOut(Status error) { + if (m_error.Fail()) { + // 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 = std::move(error); + m_dil_token.setKind(dil::TokenKind::eof); +} + +void DILParser::ConsumeToken() { + if (m_dil_token.is(dil::TokenKind::eof)) { + // Don't do anything if we're already at eof. This can happen if an error + // occurred during parsing and we're trying to bail out. + return; + } + bool all_ok; + m_dil_lexer.Lex(m_dil_token); + if (m_dil_lexer.GetCurrentTokenIdx() == UINT_MAX) + all_ok = m_dil_lexer.ResetTokenIdx(0); + else + all_ok = m_dil_lexer.IncrementTokenIdx(); + if (!all_ok) + BailOut(ErrorCode::kUnknown, "Invalid lexer token index", 0); +} + +void DILParser::Expect(dil::TokenKind kind) { + if (m_dil_token.isNot(kind)) { + BailOut(ErrorCode::kUnknown, + llvm::formatv("expected {0}, got: {1}", TokenKindsJoin(kind), + TokenDescription(m_dil_token)), + m_dil_token.getLocation()); + } +} + +template <typename... Ts> +void DILParser::ExpectOneOf(dil::TokenKind k, Ts... ks) { + static_assert((std::is_same_v<Ts, dil::TokenKind> && ...), + "ExpectOneOf can be only called with values of type " + "dil::TokenKind"); + + if (!m_dil_token.isOneOf(k, ks...)) { + BailOut(ErrorCode::kUnknown, + llvm::formatv("expected any of ({0}), got: {1}", + TokenKindsJoin(k, ks...), + TokenDescription(m_dil_token)), + m_dil_token.getLocation()); + } +} + +std::string DILParser::TokenDescription(const DILToken &token) { + const auto &spelling = ((DILToken)token).getSpelling(); + const std::string kind_name = + DILToken::getTokenName(((DILToken)token).getKind()); + return llvm::formatv("<'{0}' ({1})>", spelling, kind_name); +} + +} // namespace dil + +} // namespace lldb_private 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 00000000000000..99998b20bcb050 --- /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 00000000000000..b87de2ab27ebb2 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py @@ -0,0 +1,82 @@ +""" +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() + self.do_test() + + def do_test(self): + target = self.createTestTarget() + + # Now create a breakpoint in main.c at the source matching + # "Set a breakpoint here" + breakpoint = target.BreakpointCreateBySourceRegex( + "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() >= 1, VALID_BREAKPOINT + ) + + error = lldb.SBError() + # This is the launch info. If you want to launch with arguments or + # environment variables, add them using SetArguments or + # SetEnvironmentEntries + + launch_info = target.GetLaunchInfo() + process = target.Launch(launch_info, error) + self.assertTrue(process, PROCESS_IS_VALID) + + # Did we hit our breakpoint? + from lldbsuite.test.lldbutil import get_threads_stopped_at_breakpoint + + threads = get_threads_stopped_at_breakpoint(process, breakpoint) + self.assertEqual( + len(threads), 1, "There should be a thread stopped at our breakpoint" + ) + # The hit count for the breakpoint should be 1. + self.assertEquals(breakpoint.GetHitCount(), 1) + + frame = threads[0].GetFrameAtIndex(0) + command_result = lldb.SBCommandReturnObject() + interp = self.dbg.GetCommandInterpreter() + + + self.expect("settings set target.experimental.use-DIL true", + substrs=[""]) + self.expect("frame variable 'globalVar'", substrs=["-559038737"]) # 0xDEADBEEF + self.expect("frame variable 'globalPtr'", patterns=["0x[0-9]+"]) + self.expect("frame variable 'globalRef'", substrs=["-559038737"]) + self.expect("frame variable '::globalPtr'", patterns=["0x[0-9]+"]) + self.expect("frame variable '::globalRef'", substrs=["-559038737"]) + + self.expect("frame variable 'externGlobalVar'", error=True, + substrs=["use of undeclared identifier"]) # 0x00C0FFEE + #substrs=["no variable named 'externGlobalVar' found in this frame"]) # 0x00C0FFEE + self.expect("frame variable '::externGlobalVar'", error=True, + substrs=["use of undeclared identifier"]) # ["12648430"]) + #substrs=["no variable named '::externGlobalVar' found in this frame"]) # ["12648430"]) + # "use of undeclared identifier" + + self.expect("frame variable 'ns::globalVar'", substrs=["13"]) + self.expect("frame variable 'ns::globalPtr'", + patterns=["0x[0-9]+"]) + self.expect("frame variable 'ns::globalRef'", substrs=["13"]) + self.expect("frame variable '::ns::globalVar'", substrs=["13"]) + self.expect("frame variable '::ns::globalPtr'", + patterns=["0x[0-9]+"]) 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 00000000000000..605142d169db28 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/main.cpp @@ -0,0 +1,18 @@ +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 00000000000000..99998b20bcb050 --- /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 00000000000000..dcc408f1f37bd1 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/TestFrameVarDILInstanceVariables.py @@ -0,0 +1,62 @@ +""" +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() + self.do_test() + + def do_test(self): + target = self.createTestTarget() + + # Now create a breakpoint in main.c at the source matching + # "Set a breakpoint here" + breakpoint = target.BreakpointCreateBySourceRegex( + "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() >= 1, VALID_BREAKPOINT + ) + + error = lldb.SBError() + # This is the launch info. If you want to launch with arguments or + # environment variables, add them using SetArguments or + # SetEnvironmentEntries + + launch_info = target.GetLaunchInfo() + process = target.Launch(launch_info, error) + self.assertTrue(process, PROCESS_IS_VALID) + + # Did we hit our breakpoint? + from lldbsuite.test.lldbutil import get_threads_stopped_at_breakpoint + + threads = get_threads_stopped_at_breakpoint(process, breakpoint) + self.assertEqual( + len(threads), 1, "There should be a thread stopped at our breakpoint" + ) + # The hit count for the breakpoint should be 1. + self.assertEquals(breakpoint.GetHitCount(), 1) + + frame = threads[0].GetFrameAtIndex(0) + command_result = lldb.SBCommandReturnObject() + interp = self.dbg.GetCommandInterpreter() + + self.expect("settings set target.experimental.use-DIL true", + substrs=[""]) + self.expect("frame variable 'this'", patterns=["0x[0-9]+"]) + self.expect("frame variable 'c'", substrs=["(field_ = -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 00000000000000..276166f97a7bda --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/InstanceVariables/main.cpp @@ -0,0 +1,25 @@ +#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 00000000000000..99998b20bcb050 --- /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 00000000000000..c19f09f2f06b68 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/TestFrameVarDILLocalVars.py @@ -0,0 +1,65 @@ +""" +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() + self.do_test() + + def do_test(self): + target = self.createTestTarget() + + # Now create a breakpoint in main.c at the source matching + # "Set a breakpoint here" + breakpoint = target.BreakpointCreateBySourceRegex( + "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() >= 1, VALID_BREAKPOINT + ) + + error = lldb.SBError() + # This is the launch info. If you want to launch with arguments or + # environment variables, add them using SetArguments or + # SetEnvironmentEntries + + launch_info = target.GetLaunchInfo() + process = target.Launch(launch_info, error) + self.assertTrue(process, PROCESS_IS_VALID) + + # Did we hit our breakpoint? + from lldbsuite.test.lldbutil import get_threads_stopped_at_breakpoint + + threads = get_threads_stopped_at_breakpoint(process, breakpoint) + self.assertEqual( + len(threads), 1, "There should be a thread stopped at our breakpoint" + ) + # The hit count for the breakpoint should be 1. + self.assertEquals(breakpoint.GetHitCount(), 1) + + frame = threads[0].GetFrameAtIndex(0) + command_result = lldb.SBCommandReturnObject() + interp = self.dbg.GetCommandInterpreter() + + # Test 'a' is 1 + self.expect("settings set target.experimental.use-DIL true", + substrs=[""]) + self.expect("frame variable a", substrs=["1"]) + self.expect("frame variable b", substrs=["2"]) + self.expect("frame variable c", substrs=["\\xfd"]) + self.expect("frame variable s", substrs=["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 00000000000000..c0dd24291429a3 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/LocalVars/main.cpp @@ -0,0 +1,26 @@ +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 +} + +/* +TEST_F(EvalTest, TestLocalVariables) { + EXPECT_THAT(Eval("a"), IsEqual("1")); + EXPECT_THAT(Eval("b"), IsEqual("2")); + EXPECT_THAT(Eval("a + b"), IsEqual("3")); + + EXPECT_THAT(Eval("c + 1"), IsEqual("-2")); + EXPECT_THAT(Eval("s + 1"), IsEqual("5")); + EXPECT_THAT(Eval("c + s"), IsEqual("1")); + + EXPECT_THAT(Eval("__test_non_variable + 1"), + IsError("use of undeclared identifier '__test_non_variable'")); +} +*/ _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits