mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-14 15:46:32 +00:00
[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.
This commit is contained in:
parent
6333f8457c
commit
46e2c07fa2
42
lldb/docs/dil-expr-lang.ebnf
Normal file
42
lldb/docs/dil-expr-lang.ebnf
Normal file
@ -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 ;
|
||||
|
||||
|
||||
|
||||
|
97
lldb/include/lldb/ValueObject/DILAST.h
Normal file
97
lldb/include/lldb/ValueObject/DILAST.h
Normal file
@ -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
|
63
lldb/include/lldb/ValueObject/DILEval.h
Normal file
63
lldb/include/lldb/ValueObject/DILEval.h
Normal file
@ -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
|
@ -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 @@ public:
|
||||
|
||||
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 @@ private:
|
||||
|
||||
} // 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
|
||||
|
125
lldb/include/lldb/ValueObject/DILParser.h
Normal file
125
lldb/include/lldb/ValueObject/DILParser.h
Normal file
@ -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
|
@ -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(
|
||||
|
@ -1,5 +1,8 @@
|
||||
add_lldb_library(lldbValueObject
|
||||
DILAST.cpp
|
||||
DILEval.cpp
|
||||
DILLexer.cpp
|
||||
DILParser.cpp
|
||||
ValueObject.cpp
|
||||
ValueObjectCast.cpp
|
||||
ValueObjectChild.cpp
|
||||
|
22
lldb/source/ValueObject/DILAST.cpp
Normal file
22
lldb/source/ValueObject/DILAST.cpp
Normal file
@ -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
|
235
lldb/source/ValueObject/DILEval.cpp
Normal file
235
lldb/source/ValueObject/DILEval.cpp
Normal file
@ -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
|
260
lldb/source/ValueObject/DILParser.cpp
Normal file
260
lldb/source/ValueObject/DILParser.cpp
Normal file
@ -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
|
@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
@ -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 &")
|
@ -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
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
@ -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")])
|
@ -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;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
@ -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")
|
@ -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
|
||||
}
|
@ -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}));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user