Skip to content

[LLDB] Add unary operators Dereference and AddressOf to DIL #134428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lldb/docs/dil-expr-lang.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
(* This is currently a subset of the final DIL Language, matching the current
DIL implementation. *)

expression = primary_expression ;
expression = unary_expression ;

unary_expression = unary_operator expression
| primary_expression ;

unary_operator = "*" | "&" ;

primary_expression = id_expression
| "(" expression ")";
Expand Down
29 changes: 29 additions & 0 deletions lldb/include/lldb/ValueObject/DILAST.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ namespace lldb_private::dil {
enum class NodeKind {
eErrorNode,
eIdentifierNode,
eUnaryOpNode,
};

/// The Unary operators recognized by DIL.
enum class UnaryOpKind {
AddrOf, // "&"
Deref, // "*"
};

/// Forward declaration, for use in DIL AST nodes. Definition is at the very
Expand Down Expand Up @@ -81,6 +88,26 @@ class IdentifierNode : public ASTNode {
std::string m_name;
};

class UnaryOpNode : public ASTNode {
public:
UnaryOpNode(uint32_t location, UnaryOpKind kind, ASTNodeUP operand)
: ASTNode(location, NodeKind::eUnaryOpNode), m_kind(kind),
m_operand(std::move(operand)) {}

llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;

UnaryOpKind kind() const { return m_kind; }
ASTNode *operand() const { return m_operand.get(); }

static bool classof(const ASTNode *node) {
return node->GetKind() == NodeKind::eUnaryOpNode;
}

private:
UnaryOpKind m_kind;
ASTNodeUP m_operand;
};

/// 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
Expand All @@ -90,6 +117,8 @@ class Visitor {
virtual ~Visitor() = default;
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const IdentifierNode *node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const UnaryOpNode *node) = 0;
};

} // namespace lldb_private::dil
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/ValueObject/DILEval.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Interpreter : Visitor {
private:
llvm::Expected<lldb::ValueObjectSP>
Visit(const IdentifierNode *node) override;
llvm::Expected<lldb::ValueObjectSP> Visit(const UnaryOpNode *node) override;

// Used by the interpreter to create objects, perform casts, etc.
lldb::TargetSP m_target;
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/ValueObject/DILLexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ namespace lldb_private::dil {
class Token {
public:
enum Kind {
amp,
coloncolon,
eof,
identifier,
l_paren,
r_paren,
star,
};

Token(Kind kind, std::string spelling, uint32_t start)
Expand Down
3 changes: 2 additions & 1 deletion lldb/include/lldb/ValueObject/DILParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class DILDiagnosticError
m_detail(std::move(detail)) {}

DILDiagnosticError(llvm::StringRef expr, const std::string &message,
uint32_t loc, uint16_t err_len);
uint32_t loc, uint16_t err_len = 1);

std::unique_ptr<CloneableError> Clone() const override {
return std::make_unique<DILDiagnosticError>(m_detail);
Expand Down Expand Up @@ -83,6 +83,7 @@ class DILParser {
ASTNodeUP Run();

ASTNodeUP ParseExpression();
ASTNodeUP ParseUnaryExpression();
ASTNodeUP ParsePrimaryExpression();

std::string ParseNestedNameSpecifier();
Expand Down
6 changes: 3 additions & 3 deletions lldb/source/Target/StackFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath(
auto lex_or_err = dil::DILLexer::Create(var_expr);
if (!lex_or_err) {
error = Status::FromError(lex_or_err.takeError());
return ValueObjectSP();
return ValueObjectConstResult::Create(nullptr, std::move(error));
}

// Parse the expression.
Expand All @@ -547,7 +547,7 @@ ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath(
!no_synth_child, !no_fragile_ivar, check_ptr_vs_member);
if (!tree_or_error) {
error = Status::FromError(tree_or_error.takeError());
return ValueObjectSP();
return ValueObjectConstResult::Create(nullptr, std::move(error));
}

// Evaluate the parsed expression.
Expand All @@ -558,7 +558,7 @@ ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath(
auto valobj_or_error = interpreter.Evaluate((*tree_or_error).get());
if (!valobj_or_error) {
error = Status::FromError(valobj_or_error.takeError());
return ValueObjectSP();
return ValueObjectConstResult::Create(nullptr, std::move(error));
}

return *valobj_or_error;
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/ValueObject/DILAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ llvm::Expected<lldb::ValueObjectSP> IdentifierNode::Accept(Visitor *v) const {
return v->Visit(this);
}

llvm::Expected<lldb::ValueObjectSP> UnaryOpNode::Accept(Visitor *v) const {
return v->Visit(this);
}

} // namespace lldb_private::dil
46 changes: 43 additions & 3 deletions lldb/source/ValueObject/DILEval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,11 @@ Interpreter::Interpreter(lldb::TargetSP target, llvm::StringRef expr,
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);
// Evaluate an AST.
auto value_or_error = node->Accept(this);
// Return the computed value-or-error. The caller is responsible for
// checking if an error occured during the evaluation.
return value_or_error;
}

llvm::Expected<lldb::ValueObjectSP>
Expand All @@ -232,4 +234,42 @@ Interpreter::Visit(const IdentifierNode *node) {
return identifier;
}

llvm::Expected<lldb::ValueObjectSP>
Interpreter::Visit(const UnaryOpNode *node) {
Status error;
auto rhs_or_err = Evaluate(node->operand());
if (!rhs_or_err)
return rhs_or_err;

lldb::ValueObjectSP rhs = *rhs_or_err;

switch (node->kind()) {
case UnaryOpKind::Deref: {
lldb::ValueObjectSP dynamic_rhs = rhs->GetDynamicValue(m_default_dynamic);
if (dynamic_rhs)
rhs = dynamic_rhs;

lldb::ValueObjectSP child_sp = rhs->Dereference(error);
if (error.Fail())
return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(),
node->GetLocation());

return child_sp;
}
case UnaryOpKind::AddrOf: {
Status error;
lldb::ValueObjectSP value = rhs->AddressOf(error);
if (error.Fail())
return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(),
node->GetLocation());

return value;
}
}

// Unsupported/invalid operation.
return llvm::make_error<DILDiagnosticError>(
m_expr, "invalid ast: unexpected binary operator", node->GetLocation());
}

} // namespace lldb_private::dil
9 changes: 6 additions & 3 deletions lldb/source/ValueObject/DILLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace lldb_private::dil {

llvm::StringRef Token::GetTokenName(Kind kind) {
switch (kind) {
case Kind::amp:
return "amp";
case Kind::coloncolon:
return "coloncolon";
case Kind::eof:
Expand All @@ -29,6 +31,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "l_paren";
case Kind::r_paren:
return "r_paren";
case Token::star:
return "star";
}
llvm_unreachable("Unknown token name");
}
Expand Down Expand Up @@ -82,9 +86,8 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
return Token(Token::identifier, maybe_word->str(), position);

constexpr std::pair<Token::Kind, const char *> operators[] = {
{Token::l_paren, "("},
{Token::r_paren, ")"},
{Token::coloncolon, "::"},
{Token::amp, "&"}, {Token::coloncolon, "::"}, {Token::l_paren, "("},
{Token::r_paren, ")"}, {Token::star, "*"},
};
for (auto [kind, str] : operators) {
if (remainder.consume_front(str))
Expand Down
33 changes: 32 additions & 1 deletion lldb/source/ValueObject/DILParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,38 @@ ASTNodeUP DILParser::Run() {
// expression:
// primary_expression
//
ASTNodeUP DILParser::ParseExpression() { return ParsePrimaryExpression(); }
ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); }

// Parse an unary_expression.
//
// unary_expression:
// unary_operator expression
// primary_expression
//
// unary_operator:
// "&"
// "*"
//
ASTNodeUP DILParser::ParseUnaryExpression() {
if (CurToken().IsOneOf({Token::amp, Token::star})) {
Token token = CurToken();
uint32_t loc = token.GetLocation();
m_dil_lexer.Advance();
auto rhs = ParseExpression();
switch (token.GetKind()) {
case Token::star:
return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Deref,
std::move(rhs));
case Token::amp:
return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::AddrOf,
std::move(rhs));

default:
llvm_unreachable("invalid token kind");
}
}
return ParsePrimaryExpression();
}

// Parse a primary_expression.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Test DIL address calculation.
"""

import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test import lldbutil


class TestFrameVarDILGlobalVariableLookup(TestBase):
NO_DEBUG_INFO_TESTCASE = True

def expect_var_path(self, expr, compare_to_framevar=False, value=None, type=None):
value_dil = super().expect_var_path(expr, value=value, type=type)
if compare_to_framevar:
self.runCmd("settings set target.experimental.use-DIL false")
value_frv = super().expect_var_path(expr, value=value, type=type)
self.runCmd("settings set target.experimental.use-DIL true")
self.assertEqual(value_dil.GetValue(), value_frv.GetValue())

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("&x", True, type="int *")
self.expect_var_path("r", True, type="int &")
self.expect_var_path("&r", True, type="int &*")
self.expect_var_path("pr", True, type="int *&")
self.expect_var_path("&pr", True, type="int *&*")
self.expect_var_path("my_pr", True)
self.expect_var_path("&my_pr", True, type="mypr *")
self.expect_var_path("&globalVar", True, type="int *")
self.expect_var_path("&s_str", True, type="const char **")
self.expect_var_path("&argc", True, type="int *")
16 changes: 16 additions & 0 deletions lldb/test/API/commands/frame/var-dil/basics/AddressOf/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
int globalVar = 0xDEADBEEF;

int main(int argc, char **argv) {
int x = 42;
int &r = x;
int *p = &x;
int *&pr = p;

typedef int *&mypr;
mypr my_pr = p;

const char *s_str = "hello";

char c = 1;
return 0; // Set a breakpoint here
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Test DIL pointer arithmetic.
"""

import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test import lldbutil


class TestFrameVarDILGlobalVariableLookup(TestBase):
NO_DEBUG_INFO_TESTCASE = True

def expect_var_path(self, expr, compare_to_framevar=False, value=None, type=None):
value_dil = super().expect_var_path(expr, value=value, type=type)
if compare_to_framevar:
self.runCmd("settings set target.experimental.use-DIL false")
value_frv = super().expect_var_path(expr, value=value, type=type)
self.runCmd("settings set target.experimental.use-DIL true")
self.assertEqual(value_dil.GetValue(), value_frv.GetValue())

def test_dereference(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("*p_int0", True, value="0")
self.expect_var_path("*cp_int5", True, value="5")
self.expect_var_path("*rcp_int0", True, type="const int *")
self.expect_var_path("*offset_p", True, value="5")
self.expect_var_path("*offset_pref", True, type="int *")
self.expect_var_path("**pp_int0", value="0")
self.expect_var_path("&**pp_int0", type="int *")
self.expect(
"frame var '*array'",
error=True,
substrs=["not a pointer or reference type"],
)
self.expect(
"frame var '&*p_null'",
error=True,
substrs=["doesn't have a valid address"],
)
self.expect(
"frame var '&*p_void'",
error=True,
substrs=["dereference failed: (void *) p_void"],
)
Loading
Loading