Skip to content

[LLDB] Add field member operators to DIL #138093

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 7 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 7 additions & 3 deletions lldb/docs/dil-expr-lang.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@

expression = unary_expression ;

unary_expression = unary_operator expression
| primary_expression ;
unary_expression = postfix_expression
| unary_operator expression ;

unary_operator = "*" | "&" ;

postfix_expresson = primary_expression
| postfix_expression "." id_expression
| postfix_expression "->" id_expression ;

primary_expression = id_expression
| "(" expression ")";
| "(" expression ")" ;

id_expression = unqualified_id
| qualified_id
Expand Down
39 changes: 39 additions & 0 deletions lldb/include/lldb/ValueObject/DILAST.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace lldb_private::dil {
enum class NodeKind {
eErrorNode,
eIdentifierNode,
eMemberOfNode,
eUnaryOpNode,
};

Expand Down Expand Up @@ -88,6 +89,42 @@ class IdentifierNode : public ASTNode {
std::string m_name;
};

class MemberOfNode : public ASTNode {
public:
MemberOfNode(uint32_t location, ASTNodeUP base, bool is_arrow,
std::string name, lldb::DynamicValueType use_dynamic,
bool fragile_ivar, bool use_synth_child,
bool check_ptr_vs_member)
: ASTNode(location, NodeKind::eMemberOfNode), m_base(std::move(base)),
m_is_arrow(is_arrow), m_field_name(std::move(name)),
m_use_dynamic(use_dynamic), m_fragile_ivar(fragile_ivar),
m_use_synth_child(use_synth_child),
m_check_ptr_vs_member(check_ptr_vs_member) {}

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

ASTNode *GetBase() const { return m_base.get(); }
bool GetIsArrow() const { return m_is_arrow; }
llvm::StringRef GetFieldName() const { return llvm::StringRef(m_field_name); }
bool GetCheckPtrVsMember() const { return m_check_ptr_vs_member; }
bool GetFragileIvar() const { return m_fragile_ivar; }
bool GetSynthChild() const { return m_use_synth_child; }
lldb::DynamicValueType GetUseDynamic() const { return m_use_dynamic; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this (briefly) at one of the previous PRs, and I think the conclusion was that things like these should be a property of the interpreter rather than of a specific node.
I can sort of imagine a world some parts of an expression are evaluated using dynamic types and some aren't, but I don't think it's your intention to create that world. (And if it is, then these properties (at least some of them), should be other nodes as well, as e.g. [] also needs to know whether it should be looking at synthetic children).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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

private:
ASTNodeUP m_base;
bool m_is_arrow;
std::string m_field_name;
lldb::DynamicValueType m_use_dynamic;
bool m_fragile_ivar;
bool m_use_synth_child;
bool m_check_ptr_vs_member;
};

class UnaryOpNode : public ASTNode {
public:
UnaryOpNode(uint32_t location, UnaryOpKind kind, ASTNodeUP operand)
Expand Down Expand Up @@ -118,6 +155,8 @@ class Visitor {
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const IdentifierNode *node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const MemberOfNode *node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const UnaryOpNode *node) = 0;
};

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 MemberOfNode *node) override;
llvm::Expected<lldb::ValueObjectSP> Visit(const UnaryOpNode *node) override;

// Used by the interpreter to create objects, perform casts, etc.
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 @@ -25,10 +25,12 @@ class Token {
public:
enum Kind {
amp,
arrow,
coloncolon,
eof,
identifier,
l_paren,
period,
r_paren,
star,
};
Expand Down
3 changes: 3 additions & 0 deletions lldb/include/lldb/ValueObject/DILParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class DILParser {

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

std::string ParseNestedNameSpecifier();
Expand Down Expand Up @@ -117,6 +118,8 @@ class DILParser {

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
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,6 +19,10 @@ llvm::Expected<lldb::ValueObjectSP> IdentifierNode::Accept(Visitor *v) const {
return v->Visit(this);
}

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

llvm::Expected<lldb::ValueObjectSP> UnaryOpNode::Accept(Visitor *v) const {
return v->Visit(this);
}
Expand Down
112 changes: 112 additions & 0 deletions lldb/source/ValueObject/DILEval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,116 @@ Interpreter::Visit(const UnaryOpNode *node) {
m_expr, "invalid ast: unexpected binary operator", node->GetLocation());
}

llvm::Expected<lldb::ValueObjectSP>
Interpreter::Visit(const MemberOfNode *node) {
auto base_or_err = Evaluate(node->GetBase());
if (!base_or_err)
return base_or_err;
lldb::ValueObjectSP base = *base_or_err;
bool check_ptr_vs_member = node->GetCheckPtrVsMember();
bool fragile_ivar = node->GetFragileIvar();
bool synth_child = node->GetSynthChild();
lldb::DynamicValueType use_dynamic = node->GetUseDynamic();

// Perform some basic type & correctness checking.
if (node->GetIsArrow()) {
if (!fragile_ivar) {
// Make sure we aren't trying to deref an objective
// C ivar if this is not allowed
const uint32_t pointer_type_flags =
base->GetCompilerType().GetTypeInfo(nullptr);
if ((pointer_type_flags & lldb::eTypeIsObjC) &&
(pointer_type_flags & lldb::eTypeIsPointer)) {
// This was an objective C object pointer and it was requested we
// skip any fragile ivars so return nothing here
return lldb::ValueObjectSP();
}
}

// If we have a non-pointer type with a synthetic value then lets check
// if we have a synthetic dereference specified.
if (!base->IsPointerType() && base->HasSyntheticValue()) {
Status deref_error;
if (lldb::ValueObjectSP synth_deref_sp =
base->GetSyntheticValue()->Dereference(deref_error);
synth_deref_sp && deref_error.Success()) {
base = std::move(synth_deref_sp);
}
if (!base || deref_error.Fail()) {
std::string errMsg = llvm::formatv(
"Failed to dereference synthetic value: {0}", deref_error);
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
}

// Some synthetic plug-ins fail to set the error in Dereference
if (!base) {
std::string errMsg = "Failed to dereference synthetic value";
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
}
}
}

if (check_ptr_vs_member) {
bool expr_is_ptr = node->GetIsArrow();
bool base_is_ptr = base->IsPointerType();

if (expr_is_ptr != base_is_ptr) {
if (base_is_ptr) {
std::string errMsg =
llvm::formatv("member reference type {0} is a pointer; "
"did you mean to use '->'?",
base->GetCompilerType().TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
} else {
std::string errMsg =
llvm::formatv("member reference type {0} is not a pointer; "
"did you mean to use '.'?",
base->GetCompilerType().TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
}
}
}

lldb::ValueObjectSP field_obj =
base->GetChildMemberWithName(node->GetFieldName());
if (!field_obj) {
if (synth_child) {
field_obj = base->GetSyntheticValue();
if (field_obj)
field_obj = field_obj->GetChildMemberWithName(node->GetFieldName());
}

if (!synth_child || !field_obj) {
std::string errMsg = llvm::formatv(
"no member named '{0}' in {1}", node->GetFieldName(),
base->GetCompilerType().GetFullyUnqualifiedType().TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
}
}

if (field_obj && field_obj->GetName() == node->GetFieldName()) {
if (use_dynamic != lldb::eNoDynamicValues) {
lldb::ValueObjectSP dynamic_val_sp =
field_obj->GetDynamicValue(use_dynamic);
if (dynamic_val_sp)
field_obj = dynamic_val_sp;
}
return field_obj;
}

CompilerType base_type = base->GetCompilerType();
if (node->GetIsArrow() && base->IsPointerType())
base_type = base_type.GetPointeeType();
std::string errMsg =
llvm::formatv("no member named '{0}' in {1}", node->GetFieldName(),
base_type.GetFullyUnqualifiedType().TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
}

} // namespace lldb_private::dil
9 changes: 7 additions & 2 deletions lldb/source/ValueObject/DILLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
switch (kind) {
case Kind::amp:
return "amp";
case Kind::arrow:
return "arrow";
case Kind::coloncolon:
return "coloncolon";
case Kind::eof:
Expand All @@ -29,6 +31,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "identifier";
case Kind::l_paren:
return "l_paren";
case Kind::period:
return "period";
case Kind::r_paren:
return "r_paren";
case Token::star:
Expand Down Expand Up @@ -86,8 +90,9 @@ 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::amp, "&"}, {Token::coloncolon, "::"}, {Token::l_paren, "("},
{Token::r_paren, ")"}, {Token::star, "*"},
{Token::amp, "&"}, {Token::arrow, "->"}, {Token::coloncolon, "::"},
{Token::l_paren, "("}, {Token::period, "."}, {Token::r_paren, ")"},
{Token::star, "*"},
};
for (auto [kind, str] : operators) {
if (remainder.consume_front(str))
Expand Down
31 changes: 27 additions & 4 deletions lldb/source/ValueObject/DILParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ DILParser::DILParser(llvm::StringRef dil_input_expr, DILLexer lexer,
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_use_synthetic(use_synthetic), m_fragile_ivar(fragile_ivar),
m_check_ptr_vs_member(check_ptr_vs_member) {}

ASTNodeUP DILParser::Run() {
ASTNodeUP expr = ParseExpression();
Expand All @@ -79,15 +80,15 @@ ASTNodeUP DILParser::Run() {
// Parse an expression.
//
// expression:
// primary_expression
// unary_expression
//
ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); }

// Parse an unary_expression.
//
// unary_expression:
// postfix_expression
// unary_operator expression
// primary_expression
//
// unary_operator:
// "&"
Expand All @@ -111,7 +112,29 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
llvm_unreachable("invalid token kind");
}
}
return ParsePrimaryExpression();
return ParsePostfixExpression();
}

// Parse a postfix_expression.
//
// postfix_expression:
// primary_expression
// postfix_expression "." id_expression
// postfix_expression "->" id_expression
//
ASTNodeUP DILParser::ParsePostfixExpression() {
ASTNodeUP lhs = ParsePrimaryExpression();
while (CurToken().IsOneOf({Token::period, Token::arrow})) {
Token token = CurToken();
m_dil_lexer.Advance();
Token member_token = CurToken();
std::string member_id = ParseIdExpression();
lhs = std::make_unique<MemberOfNode>(
member_token.GetLocation(), std::move(lhs),
token.GetKind() == Token::arrow, member_id, UseDynamic(),
m_fragile_ivar, m_use_synthetic, m_check_ptr_vs_member);
}
return lhs;
}

// 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,43 @@
"""
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 TestFrameVarDILMemberOf(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.expect("settings set target.experimental.use-DIL true",
substrs=[""])
self.expect_var_path("s.x", value="1")
self.expect_var_path("s.r", type="int &")
self.expect_var_path("sr.x", value="1")
self.expect_var_path("sr.r", type="int &")
self.expect_var_path("sp->x", value="1")
self.expect_var_path("sp->r", type="int &")

self.expect("frame variable 'sp->foo'", error=True,
substrs=["no member named 'foo' in 'Sx *'"])

self.expect("frame variable 'sp.x'", error=True,
substrs=["member reference type 'Sx *' is a "
"pointer; did you mean to use '->'"])

# Test for record typedefs.
self.expect_var_path("sa.x", value="3")
self.expect_var_path("sa.y", value="'\\x04'")
Loading
Loading