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

This commit is contained in:
Ilia Kuklin
2025-04-29 21:29:52 +05:00
committed by GitHub
parent 6022a5214b
commit d637038429
16 changed files with 269 additions and 12 deletions

View File

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

View File

@@ -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
@@ -81,6 +88,26 @@ private:
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
@@ -90,6 +117,8 @@ public:
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

View File

@@ -49,6 +49,7 @@ public:
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;

View File

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

View File

@@ -43,7 +43,7 @@ public:
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);
@@ -83,6 +83,7 @@ private:
ASTNodeUP Run();
ASTNodeUP ParseExpression();
ASTNodeUP ParseUnaryExpression();
ASTNodeUP ParsePrimaryExpression();
std::string ParseNestedNameSpecifier();

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,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.
//

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
int main(int argc, char **argv) {
int *p_null = nullptr;
const char *p_char1 = "hello";
typedef const char *my_char_ptr;
my_char_ptr my_p_char1 = p_char1;
int offset = 5;
int *offset_p = &offset;
int *&offset_pref = offset_p;
int array[10];
array[0] = 0;
array[offset] = offset;
int(&array_ref)[10] = array;
int *p_int0 = &array[0];
int **pp_int0 = &p_int0;
const int *cp_int0 = &array[0];
const int *cp_int5 = &array[offset];
const int *&rcp_int0 = cp_int0;
typedef int *td_int_ptr_t;
td_int_ptr_t td_int_ptr0 = &array[0];
void *p_void = (void *)p_char1;
void **pp_void0 = &p_void;
void **pp_void1 = pp_void0 + 1;
return 0; // Set a breakpoint here
}