https://github.com/cmtice created https://github.com/llvm/llvm-project/pull/138093
Add the arrow and period operators, allowing DIL to find and access member fields. >From fe9ac0fa05bb43ea718214746f0ea9b7eefc929a Mon Sep 17 00:00:00 2001 From: Caroline Tice <cmt...@google.com> Date: Thu, 1 May 2025 00:05:57 -0700 Subject: [PATCH] [LLDB] Add field member operators to DIL Add the arrow and period operators, allowing DIL to find and access member fields. --- lldb/docs/dil-expr-lang.ebnf | 10 +- lldb/include/lldb/ValueObject/DILAST.h | 26 +++ lldb/include/lldb/ValueObject/DILEval.h | 8 + lldb/include/lldb/ValueObject/DILLexer.h | 2 + lldb/include/lldb/ValueObject/DILParser.h | 1 + lldb/source/ValueObject/DILAST.cpp | 4 + lldb/source/ValueObject/DILEval.cpp | 200 ++++++++++++++++++ lldb/source/ValueObject/DILLexer.cpp | 9 +- lldb/source/ValueObject/DILParser.cpp | 27 ++- .../frame/var-dil/basics/MemberOf/Makefile | 3 + .../MemberOf/TestFrameVarDILMemberOf.py | 47 ++++ .../frame/var-dil/basics/MemberOf/main.cpp | 59 ++++++ .../basics/MemberOfAnonymousMember/Makefile | 3 + .../TestFrameVarDILMemberOfAnonymousMember.py | 62 ++++++ .../basics/MemberOfAnonymousMember/main.cpp | 74 +++++++ .../basics/MemberOfInheritance/Makefile | 3 + .../TestFrameVarDILMemberOfInheritance.py | 49 +++++ .../basics/MemberOfInheritance/main.cpp | 87 ++++++++ 18 files changed, 666 insertions(+), 8 deletions(-) create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOf/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOf/TestFrameVarDILMemberOf.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOf/main.cpp create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/TestFrameVarDILMemberOfAnonymousMember.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/main.cpp create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/TestFrameVarDILMemberOfInheritance.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/main.cpp diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index c8bf4231b3e4a..580626862c005 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -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 diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index fe3827ef0516a..a74a12bd8be9d 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -20,6 +20,7 @@ namespace lldb_private::dil { enum class NodeKind { eErrorNode, eIdentifierNode, + eMemberOfNode, eUnaryOpNode, }; @@ -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) @@ -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; }; diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index b1dd3fdb49739..053daffaa41f2 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -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; diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index 3508b8b7a85c6..9475519a43d2a 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -25,10 +25,12 @@ class Token { public: enum Kind { amp, + arrow, coloncolon, eof, identifier, l_paren, + period, r_paren, star, }; diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index f5c00b1040ef4..c62f8908290f5 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -84,6 +84,7 @@ class DILParser { ASTNodeUP ParseExpression(); ASTNodeUP ParseUnaryExpression(); + ASTNodeUP ParsePostfixExpression(); ASTNodeUP ParsePrimaryExpression(); std::string ParseNestedNameSpecifier(); diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index ea847587501ee..2814d96b94e6e 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -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); } diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 15a66d4866305..1f1ad7161f42e 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -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 diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp index b9c2e7971e3b4..1610b4e87d7fb 100644 --- a/lldb/source/ValueObject/DILLexer.cpp +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -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: @@ -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: @@ -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)) diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index 2c78eae8cf6bf..9c5bc71775fb2 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -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: // "&" @@ -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. diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOf/Makefile b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOf/TestFrameVarDILMemberOf.py b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/TestFrameVarDILMemberOf.py new file mode 100644 index 0000000000000..ff942c88bf183 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/TestFrameVarDILMemberOf.py @@ -0,0 +1,47 @@ +""" +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", value="2") + self.expect_var_path("sr.x", value="1") + self.expect_var_path("sr.r", value="2") + self.expect_var_path("sp->x", value="1") + self.expect_var_path("sp->r", value="2") + self.expect_var_path("sarr->x", value="5"); + self.expect_var_path("sarr->r", value="2") + + 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 '->'"]) + self.expect("frame variable 'sarr.x'", error=True, + substrs=["no member named 'x' in 'Sx[2]'"]) + + # Test for record typedefs. + self.expect_var_path("sa.x", value="3") + self.expect_var_path("sa.y", value="'\\x04'") diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOf/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/main.cpp new file mode 100644 index 0000000000000..dace888bef4dc --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOf/main.cpp @@ -0,0 +1,59 @@ +int +main(int argc, char**argv) +{ + int x = 2; + struct Sx { + int x; + int& r; + char y; + } s{1, x, 2}; + + Sx& sr = s; + Sx* sp = &s; + + Sx sarr[2] = {{5, x, 2}, {1, x, 3}}; + + using SxAlias = Sx; + SxAlias sa{3, x, 4}; + + return 0; // Set a breakpoint here +} + +/* + EXPECT_THAT(Eval("s.x"), IsEqual("1")); + EXPECT_THAT(Eval("s.r"), IsEqual("2")); + EXPECT_THAT(Eval("s.r + 1"), IsEqual("3")); + EXPECT_THAT(Eval("sr.x"), IsEqual("1")); + EXPECT_THAT(Eval("sr.r"), IsEqual("2")); + EXPECT_THAT(Eval("sr.r + 1"), IsEqual("3")); + EXPECT_THAT(Eval("sp->x"), IsEqual("1")); + EXPECT_THAT(Eval("sp->r"), IsEqual("2")); + EXPECT_THAT(Eval("sp->r + 1"), IsEqual("3")); + EXPECT_THAT(Eval("sarr->x"), IsEqual("5")); + EXPECT_THAT(Eval("sarr->r"), IsEqual("2")); + EXPECT_THAT(Eval("sarr->r + 1"), IsEqual("3")); + EXPECT_THAT(Eval("(sarr + 1)->x"), IsEqual("1")); + + EXPECT_THAT( + Eval("sp->4"), + IsError( + "<expr>:1:5: expected 'identifier', got: <'4' (numeric_constant)>\n" + "sp->4\n" + " ^")); + EXPECT_THAT(Eval("sp->foo"), IsError("no member named 'foo' in 'Sx'")); + EXPECT_THAT( + Eval("sp->r / (void*)0"), + IsError("invalid operands to binary expression ('int' and 'void *')")); + + EXPECT_THAT(Eval("sp.x"), IsError("member reference type 'Sx *' is a " + "pointer; did you mean to use '->'")); + EXPECT_THAT( + Eval("sarr.x"), + IsError( + "member reference base type 'Sx[2]' is not a structure or union")); + + // Test for record typedefs. + EXPECT_THAT(Eval("sa.x"), IsEqual("3")); + EXPECT_THAT(Eval("sa.y"), IsEqual("'\\x04'")); + +*/ diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/Makefile b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/TestFrameVarDILMemberOfAnonymousMember.py b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/TestFrameVarDILMemberOfAnonymousMember.py new file mode 100644 index 0000000000000..1bde4706da90f --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/TestFrameVarDILMemberOfAnonymousMember.py @@ -0,0 +1,62 @@ +""" +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 TestFrameVarDILMemberOfAnonymousMember(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("a.x", value="1") + self.expect_var_path("a.y", value="2") + + self.expect("frame variable 'b.x'", error=True, + substrs=["no member named 'x' in 'B'"]) + #self.expect_var_path("b.y", value="0") + self.expect_var_path("b.z", value="3") + self.expect_var_path("b.w", value="4") + self.expect_var_path("b.a.x", value="1") + self.expect_var_path("b.a.y", value="2") + + self.expect_var_path("c.x", value="5") + self.expect_var_path("c.y", value="6") + + self.expect_var_path("d.x", value="7") + self.expect_var_path("d.y", value="8") + self.expect_var_path("d.z", value="9") + self.expect_var_path("d.w", value="10") + + self.expect("frame variable 'e.x'", error=True, + substrs=["no member named 'x' in 'E'"]) + self.expect("frame variable 'f.x'", error=True, + substrs=["no member named 'x' in 'F'"]) + self.expect_var_path("f.named_field.x", value="12") + + self.expect_var_path("unnamed_derived.y", value="2") + self.expect_var_path("unnamed_derived.z", value="13") + + self.expect("frame variable 'derb.x'", error=True, + substrs=["no member named 'x' in 'DerivedB'"]) + self.expect("frame variable 'derb.y'", error=True, + substrs=["no member named 'y' in 'DerivedB'"]) + self.expect_var_path("derb.w", value="14") + self.expect_var_path("derb.k", value="15") + self.expect_var_path("derb.a.x", value="1") + self.expect_var_path("derb.a.y", value="2") diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/main.cpp new file mode 100644 index 0000000000000..6237523ac6bf3 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfAnonymousMember/main.cpp @@ -0,0 +1,74 @@ +int main(int argc, char** argv) +{ + struct A { + struct { + int x = 1; + }; + int y = 2; + } a; + + struct B { + // Anonymous struct inherits another struct. + struct : public A { + int z = 3; + }; + int w = 4; + A a; + } b; + + // Anonymous classes and unions. + struct C { + union { + int x = 5; + }; + class { + public: + int y = 6; + }; + } c; + + // Multiple levels of anonymous structs. + struct D { + struct { + struct { + int x = 7; + struct { + int y = 8; + }; + }; + int z = 9; + struct { + int w = 10; + }; + }; + } d; + + struct E { + struct IsNotAnon { + int x = 11; + }; + } e; + + struct F { + struct { + int x = 12; + } named_field; + } f; + + // Inherited unnamed struct without an enclosing parent class. + struct : public A { + struct { + int z = 13; + }; + } unnamed_derived; + + struct DerivedB : public B { + struct { + // `w` in anonymous struct overrides `w` from `B`. + int w = 14; + int k = 15; + }; + } derb; + + return 0; // Set a breakpoint here +} diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/Makefile b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/TestFrameVarDILMemberOfInheritance.py b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/TestFrameVarDILMemberOfInheritance.py new file mode 100644 index 0000000000000..c38ab5d30b238 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/TestFrameVarDILMemberOfInheritance.py @@ -0,0 +1,49 @@ +""" +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 TestFrameVarDILMemberOfInheritance(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("a.a_", value="1") + self.expect_var_path("b.b_", value="2") + self.expect_var_path("c.a_", value="1") + self.expect_var_path("c.b_", value="2") + self.expect_var_path("c.c_", value="3") + self.expect_var_path("d.a_", value="1") + self.expect_var_path("d.b_", value="2") + self.expect_var_path("d.c_", value="3") + self.expect_var_path("d.d_", value="4") + self.expect_var_path("d.fa_.a_", value="5") + + self.expect_var_path("plugin.x", value="1") + self.expect_var_path("plugin.y", value="2") + + self.expect_var_path("engine.x", value="1") + self.expect_var_path("engine.y", value="2") + self.expect_var_path("engine.z", value="3") + + self.expect_var_path("parent_base->x", value="1") + self.expect_var_path("parent_base->y", value="2") + self.expect_var_path("parent->x", value="1") + self.expect_var_path("parent->y", value="2") + self.expect_var_path("parent->z", value="3") diff --git a/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/main.cpp new file mode 100644 index 0000000000000..4d29af1b7f15b --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/MemberOfInheritance/main.cpp @@ -0,0 +1,87 @@ +int main(int argc, char** argv) +{ + struct A { + int a_; + } a{1}; + + struct B { + int b_; + } b{2}; + + struct C : A, B { + int c_; + } c; + // } c{{1}, {2}, 3}; + c.a_ = 1; + c.b_ = 2; + c.c_ = 3; + + struct D : C { + int d_; + A fa_; + } d; + // } d{{{1}, {2}, 3}, 4, {5}}; + d.a_ = 1; + d.b_ = 2; + d.c_ = 3; + d.d_ = 4; + d.fa_.a_ = 5; + + // Virtual inheritance example. + struct Animal { + virtual ~Animal() = default; + int weight_; + }; + struct Mammal : virtual Animal {}; + struct WingedAnimal : virtual Animal {}; + struct Bat : Mammal, WingedAnimal { + } bat; + bat.weight_ = 10; + + // Empty bases example. + struct IPlugin { + virtual ~IPlugin() {} + }; + struct Plugin : public IPlugin { + int x; + int y; + }; + Plugin plugin; + plugin.x = 1; + plugin.y = 2; + + struct ObjectBase { + int x; + }; + struct Object : ObjectBase {}; + struct Engine : Object { + int y; + int z; + }; + + Engine engine; + engine.x = 1; + engine.y = 2; + engine.z = 3; + + // Empty multiple inheritance with empty base. + struct Base { + int x; + int y; + virtual void Do() = 0; + virtual ~Base() {} + }; + struct Mixin {}; + struct Parent : private Mixin, public Base { + int z; + virtual void Do(){}; + }; + Parent obj; + obj.x = 1; + obj.y = 2; + obj.z = 3; + Base* parent_base = &obj; + Parent* parent = &obj; + + return 0; // Set a breakpoint here +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits