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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
26 changes: 26 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,29 @@ class IdentifierNode : public ASTNode {
std::string m_name;
};

class MemberOfNode : public ASTNode {
public:
MemberOfNode(uint32_t location, ASTNodeUP base, bool is_arrow,
ConstString name)
: ASTNode(location, NodeKind::eMemberOfNode), m_base(std::move(base)),
m_is_arrow(is_arrow), m_field_name(name) { }

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

ASTNode *base() const { return m_base.get(); }
bool IsArrow() const { return m_is_arrow; }
ConstString FieldName() const { return m_field_name; }

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

private:
ASTNodeUP m_base;
bool m_is_arrow;
ConstString m_field_name;
};

class UnaryOpNode : public ASTNode {
public:
UnaryOpNode(uint32_t location, UnaryOpKind kind, ASTNodeUP operand)
Expand Down Expand Up @@ -118,6 +142,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
8 changes: 8 additions & 0 deletions lldb/include/lldb/ValueObject/DILEval.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,16 @@ 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;

lldb::ValueObjectSP EvaluateMemberOf(lldb::ValueObjectSP value,
const std::vector<uint32_t> &path,
bool use_synthetic, bool is_dynamic);

lldb::ValueObjectSP FindMemberWithName(lldb::ValueObjectSP base,
ConstString name, bool is_arrow);

// Used by the interpreter to create objects, perform casts, etc.
lldb::TargetSP m_target;
llvm::StringRef m_expr;
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
1 change: 1 addition & 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
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
200 changes: 200 additions & 0 deletions lldb/source/ValueObject/DILEval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,204 @@ Interpreter::Visit(const UnaryOpNode *node) {
m_expr, "invalid ast: unexpected binary operator", node->GetLocation());
}

lldb::ValueObjectSP
Interpreter::EvaluateMemberOf(lldb::ValueObjectSP value,
const std::vector<uint32_t> &path,
bool use_synthetic, bool is_dynamic) {
lldb::ValueObjectSP member_val_sp = value;

lldb::DynamicValueType use_dynamic =
(!is_dynamic) ? lldb::eNoDynamicValues : lldb::eDynamicDontRunTarget;
// Walk the path from the base value to the value that contains the requested field.
for (uint32_t idx : path) {
member_val_sp = member_val_sp->GetChildAtIndex(idx, /*can_create*/ true);
}
// If that didn't work, try it with the dynamic value.
if (!member_val_sp && is_dynamic) {
lldb::ValueObjectSP dyn_val_sp = value->GetDynamicValue(use_dynamic);
if (dyn_val_sp) {
for (uint32_t idx : path) {
dyn_val_sp = dyn_val_sp->GetChildAtIndex(idx, true);
}
member_val_sp = dyn_val_sp;
}
}
assert(member_val_sp && "invalid ast: invalid member access");

return member_val_sp;
}

static bool GetFieldIndex(CompilerType type, const std::string &name,
std::vector<uint32_t> *idx_path) {
bool found = false;
uint32_t num_fields = type.GetNumFields();
for (uint32_t i = 0; i < num_fields; ++i) {
uint64_t bit_offset = 0;
uint32_t bitfield_bit_size = 0;
bool is_bitfield = false;
std::string name_sstr;
CompilerType field_type(type.GetFieldAtIndex(
i, name_sstr, &bit_offset, &bitfield_bit_size, &is_bitfield));
auto field_name =
name_sstr.length() == 0 ? std::optional<std::string>() : name_sstr;
if (field_type.IsValid() && name_sstr == name) {
idx_path->push_back(i + type.GetNumberOfNonEmptyBaseClasses());
found = true;
break;
} else if (field_type.IsAnonymousType()) {
found = GetFieldIndex(field_type, name, idx_path);
if (found) {
idx_path->push_back(i + type.GetNumberOfNonEmptyBaseClasses());
break;
}
}
}
return found;
}

static bool SearchBaseClassesForField(lldb::ValueObjectSP base_sp,
CompilerType base_type,
const std::string &name,
std::vector<uint32_t> *idx_path,
bool use_synthetic, bool is_dynamic) {
bool found = false;
uint32_t num_non_empty_bases = 0;
uint32_t num_direct_bases = base_type.GetNumDirectBaseClasses();
for (uint32_t i = 0; i < num_direct_bases; ++i) {
uint32_t bit_offset;
CompilerType base_class =
base_type.GetDirectBaseClassAtIndex(i, &bit_offset);
std::vector<uint32_t> field_idx_path;
if (GetFieldIndex(base_class, name, &field_idx_path)) {
for (uint32_t j : field_idx_path)
idx_path->push_back(j + base_class.GetNumberOfNonEmptyBaseClasses());
idx_path->push_back(i);
return true;
}

found = SearchBaseClassesForField(base_sp, base_class, name, idx_path,
use_synthetic, is_dynamic);
if (found) {
idx_path->push_back(i);
return true;
}

if (base_class.GetNumFields() > 0)
num_non_empty_bases += 1;
}
return false;
}

lldb::ValueObjectSP Interpreter::FindMemberWithName(lldb::ValueObjectSP base,
ConstString name,
bool is_arrow) {
bool is_synthetic = false;
bool is_dynamic = true;
// See if GetChildMemberWithName works.
lldb::ValueObjectSP field_obj =
base->GetChildMemberWithName(name.GetStringRef());
if (field_obj && field_obj->GetName() == name)
return field_obj;

// Check for synthetic member.
lldb::ValueObjectSP child_sp = base->GetSyntheticValue();
if (child_sp) {
is_synthetic = true;
field_obj = child_sp->GetChildMemberWithName(name);
if (field_obj && field_obj->GetName() == name)
return field_obj;
}

// Check indices of immediate member fields of base's type.
CompilerType base_type = base->GetCompilerType();
std::vector<uint32_t> field_idx_path;
if (GetFieldIndex(base_type, name.GetString(), &field_idx_path)) {
std::reverse(field_idx_path.begin(), field_idx_path.end());
// Traverse the path & verify the final object is correct.
field_obj = base;
for (uint32_t i : field_idx_path)
field_obj = field_obj->GetChildAtIndex(i, true);
if (field_obj && field_obj->GetName() == name)
return field_obj;
}

// Go through base classes and look for field there.
std::vector<uint32_t> base_class_idx_path;
bool found =
SearchBaseClassesForField(base, base_type, name.GetString(),
&base_class_idx_path, is_synthetic, is_dynamic);
if (found && !base_class_idx_path.empty()) {
std::reverse(base_class_idx_path.begin(), base_class_idx_path.end());
field_obj =
EvaluateMemberOf(base, base_class_idx_path, is_synthetic, is_dynamic);
if (field_obj && field_obj->GetName() == name)
return field_obj;
}

// Field not found.
return lldb::ValueObjectSP();
}

llvm::Expected<lldb::ValueObjectSP>
Interpreter::Visit(const MemberOfNode *node) {
Status error;
auto base_or_err = Evaluate(node->base());
if (!base_or_err) {
return base_or_err;
}
lldb::ValueObjectSP base = *base_or_err;

// Perform basic type checking.
CompilerType base_type = base->GetCompilerType();
// When using an arrow, make sure the base is a pointer or array type.
// When using a period, make sure the base type is NOT a pointer type.
if (node->IsArrow() && !base_type.IsPointerType() &&
!base_type.IsArrayType()) {
lldb::ValueObjectSP deref_sp = base->Dereference(error);
if (error.Success()) {
base = deref_sp;
base_type = deref_sp->GetCompilerType().GetPointerType();
} else {
std::string errMsg =
llvm::formatv("member reference type {0} is not a pointer; "
"did you mean to use '.'?",
base_type.TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->FieldName().GetLength());
}
} else if (!node->IsArrow() && base_type.IsPointerType()) {
std::string errMsg =
llvm::formatv("member reference type {0} is a pointer; "
"did you mean to use '->'?",
base_type.TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->FieldName().GetLength());
}

// User specified array->elem; need to get to element[0] to look for fields.
if (node->IsArrow() && base_type.IsArrayType())
base = base->GetChildAtIndex(0);

// Now look for the member with the specified name.
lldb::ValueObjectSP field_obj =
FindMemberWithName(base, node->FieldName(), node->IsArrow());
if (field_obj) {
if (field_obj->GetCompilerType().IsReferenceType()) {
lldb::ValueObjectSP tmp_obj = field_obj->Dereference(error);
if (error.Fail())
return error.ToError();
return tmp_obj;
}
return field_obj;
}

if (node->IsArrow() && base_type.IsPointerType())
base_type = base_type.GetPointeeType();
std::string errMsg = llvm::formatv(
"no member named '{0}' in {1}", node->FieldName().GetStringRef(),
base_type.GetFullyUnqualifiedType().TypeDescription());
return llvm::make_error<DILDiagnosticError>(
m_expr, errMsg, node->GetLocation(), node->FieldName().GetLength());
}

} // 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
27 changes: 24 additions & 3 deletions lldb/source/ValueObject/DILParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,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 +111,28 @@ 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,
ConstString(member_id));
}
return lhs;
}

// Parse a primary_expression.
Expand Down
Loading
Loading