[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:
cmtice 2025-04-03 21:39:30 -07:00 committed by GitHub
parent 6333f8457c
commit 46e2c07fa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1081 additions and 13 deletions

View 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 ;

View 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

View 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

View File

@ -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

View 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

View File

@ -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(

View File

@ -1,5 +1,8 @@
add_lldb_library(lldbValueObject
DILAST.cpp
DILEval.cpp
DILLexer.cpp
DILParser.cpp
ValueObject.cpp
ValueObjectCast.cpp
ValueObjectChild.cpp

View 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

View 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

View 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

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -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 &")

View File

@ -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
}

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -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")])

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -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")

View File

@ -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
}

View File

@ -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}));
}
}