https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/208832
>From 6f77215d31c060774ed2b2c3127d49eedca3cc9f Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Thu, 9 Jul 2026 16:46:53 +0500 Subject: [PATCH 1/3] [lldb] Add comparison operators to DIL --- lldb/docs/dil-expr-lang.ebnf | 12 +- lldb/include/lldb/ValueObject/DILAST.h | 19 +- lldb/include/lldb/ValueObject/DILEval.h | 7 + lldb/include/lldb/ValueObject/DILLexer.h | 6 + lldb/include/lldb/ValueObject/DILParser.h | 2 + lldb/source/ValueObject/DILAST.cpp | 12 ++ lldb/source/ValueObject/DILEval.cpp | 202 ++++++++++++++++++ lldb/source/ValueObject/DILLexer.cpp | 42 ++-- lldb/source/ValueObject/DILParser.cpp | 51 ++++- .../frame/var-dil/expr/Comparison/Makefile | 3 + .../TestFrameVarDILExprComparison.py | 173 +++++++++++++++ .../frame/var-dil/expr/Comparison/main.cpp | 34 +++ 12 files changed, 538 insertions(+), 25 deletions(-) create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index f3c465711e956..827dec34dd0a8 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -5,13 +5,21 @@ expression = assignment_expression ; -assignment_expression = shift_expression - | shift_expression assignment_operator assignment_expression ; +assignment_expression = equality_expression + | equality_expression assignment_operator assignment_expression ; assignment_operator = "=" | "+=" | "-=" ; +equality_expression = relational_expression {"==" relational_expression} + | relational_expression {"!=" relational_expression} ; + +relational_expression = shift_expression {"<" shift_expression} + | shift_expression {">" shift_expression} + | shift_expression {"<=" shift_expression} + | shift_expression {">=" shift_expression} ; + shift_expression = additive_expression {"<<" additive_expression} | additive_expression {">>" additive_expression} ; diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 93310a91a15bb..90c15a6352062 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -42,16 +42,22 @@ enum class UnaryOpKind { /// The binary operators recognized by DIL. enum class BinaryOpKind { - Add, ///< "+" - AddAssign, ///< "+=" Assign, ///< "=" - Div, ///< "/" + Add, ///< "+" + Sub, ///< "-" Mul, ///< "*" + Div, ///< "/" Rem, ///< "%" Shl, ///< "<<" Shr, ///< ">>" - Sub, ///< "-" + AddAssign, ///< "+=" SubAssign, ///< "-=" + LT, ///< "<" + GT, ///< ">" + LE, ///< "<=" + GE, ///< ">=" + EQ, ///< "==" + NE, ///< "!=" }; /// Translates DIL tokens to BinaryOpKind. @@ -87,6 +93,8 @@ class ASTNode { virtual llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const = 0; + virtual bool IsConstLiteral() const { return false; } + uint32_t GetLocation() const { return m_location; } NodeKind GetKind() const { return m_kind; } @@ -246,6 +254,7 @@ class IntegerLiteralNode : public ASTNode { llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + bool IsConstLiteral() const override { return true; } const llvm::APInt &GetValue() const { return m_value; } uint32_t GetRadix() const { return m_radix; } bool IsUnsigned() const { return m_is_unsigned; } @@ -270,6 +279,7 @@ class FloatLiteralNode : public ASTNode { llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + bool IsConstLiteral() const override { return true; } const llvm::APFloat &GetValue() const { return m_value; } static bool classof(const ASTNode &node) { @@ -287,6 +297,7 @@ class BooleanLiteralNode : public ASTNode { llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + bool IsConstLiteral() const override { return true; } bool GetValue() const & { return m_value; } static bool classof(const ASTNode &node) { diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 35784ea9987f9..5b05073983855 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -106,6 +106,13 @@ class Interpreter : Visitor { lldb::ValueObjectSP rhs, CompilerType result_type, uint32_t location); + llvm::Error ValidateComparison(BinaryOpKind kind, lldb::ValueObjectSP &lhs, + lldb::ValueObjectSP &rhs, bool lhs_is_literal, + bool rhs_is_literal, uint32_t location); + llvm::Expected<lldb::ValueObjectSP> + EvaluateComparison(BinaryOpKind kind, lldb::ValueObjectSP lhs, + lldb::ValueObjectSP rhs, bool lhs_is_literal, + bool rhs_is_literal, uint32_t location); llvm::Expected<lldb::ValueObjectSP> EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location); diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index f9f42dc59b311..263ef0353d601 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -31,7 +31,11 @@ class Token { coloncolon, eof, equal, + equalequal, + exclaimequal, float_constant, + greater, + greaterequal, greatergreater, identifier, integer_constant, @@ -39,6 +43,8 @@ class Token { kw_true, l_paren, l_square, + less, + lessequal, lessless, minus, minusequal, diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index 9e2bbff4b6614..2cebb5c222fa7 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -84,6 +84,8 @@ class DILParser { ASTNodeUP ParseExpression(); ASTNodeUP ParseAssignmentExpression(); + ASTNodeUP ParseEqualityExpression(); + ASTNodeUP ParseRelationalExpression(); ASTNodeUP ParseShiftExpression(); ASTNodeUP ParseAdditiveExpression(); ASTNodeUP ParseMultiplicativeExpression(); diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 40bf07bdd5aab..02252d9345a5a 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -33,6 +33,18 @@ BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind) { return BinaryOpKind::Shl; case Token::greatergreater: return BinaryOpKind::Shr; + case Token::less: + return BinaryOpKind::LT; + case Token::greater: + return BinaryOpKind::GT; + case Token::lessequal: + return BinaryOpKind::LE; + case Token::greaterequal: + return BinaryOpKind::GE; + case Token::equalequal: + return BinaryOpKind::EQ; + case Token::exclaimequal: + return BinaryOpKind::NE; default: break; } diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 4c5ac96dccf74..2947d59c80e37 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -653,6 +653,18 @@ Interpreter::EvaluateScalarOp(BinaryOpKind kind, lldb::ValueObjectSP lhs, return value_object(l << r); case BinaryOpKind::Shr: return value_object(l >> r); + case BinaryOpKind::LT: + return value_object(l < r); + case BinaryOpKind::GT: + return value_object(l > r); + case BinaryOpKind::LE: + return value_object(l <= r); + case BinaryOpKind::GE: + return value_object(l >= r); + case BinaryOpKind::EQ: + return value_object(l == r); + case BinaryOpKind::NE: + return value_object(l != r); default: break; } @@ -906,6 +918,186 @@ Interpreter::EvaluateAssignment(lldb::ValueObjectSP lhs, return lhs; } +static bool IsLiteralZero(lldb::ValueObjectSP &val, bool is_literal) { + bool is_zero = val->GetValueAsUnsigned(-1) == 0; + bool is_boolean = val->GetCompilerType().IsBoolean(); + return is_zero && !is_boolean && is_literal; +} + +llvm::Error +Interpreter::ValidateComparison(BinaryOpKind kind, lldb::ValueObjectSP &lhs, + lldb::ValueObjectSP &rhs, bool lhs_is_literal, + bool rhs_is_literal, uint32_t location) { + auto orig_lhs_type = lhs->GetCompilerType(); + auto orig_rhs_type = rhs->GetCompilerType(); + + if (orig_lhs_type == orig_rhs_type) + return llvm::Error::success(); + + bool is_ordered = (kind == BinaryOpKind::LT || kind == BinaryOpKind::LE || + kind == BinaryOpKind::GT || kind == BinaryOpKind::GE); + bool lhs_nullptr_or_zero = + orig_lhs_type.IsNullPtrType() || IsLiteralZero(lhs, lhs_is_literal); + bool rhs_nullptr_or_zero = + orig_rhs_type.IsNullPtrType() || IsLiteralZero(rhs, rhs_is_literal); + + if (orig_lhs_type.IsArrayType()) + lhs = ArrayToPointerConversion(*lhs, m_stack_frame, "result"); + if (orig_rhs_type.IsArrayType()) + rhs = ArrayToPointerConversion(*rhs, m_stack_frame, "result"); + + CompilerType lhs_type = lhs->GetCompilerType(); + CompilerType rhs_type = rhs->GetCompilerType(); + lldb::ValueObjectSP lhs_child; + lldb::ValueObjectSP rhs_child; + bool is_signed; + + if (!lhs_nullptr_or_zero && !lhs_type.IsPointerType() && + !lhs_type.IsIntegerOrEnumerationType(is_signed)) { + // lhs is not a nullptr, pointer, enum or integer. Check to see if its + // first child could be a pointer. If so, update lhs_type accordingly. + lhs_child = lhs->GetChildAtIndex(0); + if (lhs_child && (lhs_child->IsPointerType() || + lhs_child->GetCompilerType().IsNullPtrType())) + lhs_type = lhs_child->GetCompilerType(); + } + if (!rhs_nullptr_or_zero && !rhs_type.IsPointerType() && + !rhs_type.IsIntegerOrEnumerationType(is_signed)) { + // rhs is not a nullptr, pointer, enum or integer. Check to see if its + // first child could be a pointer. If so, update rhs_type accordingly. + rhs_child = rhs->GetChildAtIndex(0); + if (rhs_child && (rhs_child->IsPointerType() || + rhs_child->GetCompilerType().IsNullPtrType())) + rhs_type = rhs_child->GetCompilerType(); + } + + if ((lhs_type != orig_lhs_type) || (rhs_type != orig_rhs_type)) { + if (lhs_type.IsNullPtrType() || rhs_type.IsNullPtrType()) + return llvm::Error::success(); + + // May be an integer or enum. + if (!lhs_type.IsPointerType() || !rhs_type.IsPointerType()) + return llvm::Error::success(); + + CompilerType lhs_unqualified = + lhs_type.GetCanonicalType().GetFullyUnqualifiedType(); + CompilerType rhs_unqualified = + rhs_type.GetCanonicalType().GetFullyUnqualifiedType(); + + if (lhs_unqualified.IsPointerToVoid() || rhs_unqualified.IsPointerToVoid()) + return llvm::Error::success(); + + // We have two pointers, neither of which is nullptr or void *. Make + // sure their types are compatible. + bool comparable = lhs_unqualified.CompareTypes(rhs_unqualified); + if (comparable) + return llvm::Error::success(); + + std::string errMsg = llvm::formatv( + "comparison of distinct pointer types ({0} and {1})", + orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location); + } + + if (!is_ordered && ((orig_lhs_type.IsNullPtrType() && rhs_nullptr_or_zero) || + (lhs_nullptr_or_zero && orig_rhs_type.IsNullPtrType()))) + return llvm::Error::success(); + + // If the operands has arithmetic or enumeration type (scoped or unscoped), + // usual arithmetic conversions are performed on both operands following the + // rules for arithmetic operators. + auto type_or_err = ArithmeticConversion(lhs, rhs, location); + if (!type_or_err) + return type_or_err.takeError(); + + lhs_type = lhs->GetCompilerType(); + rhs_type = rhs->GetCompilerType(); + if (lhs_type.IsScalarOrUnscopedEnumerationType() && + rhs_type.IsScalarOrUnscopedEnumerationType()) + return llvm::Error::success(); + + // Scoped enums can be compared only to the instances of the same type. + if (lhs_type.IsScopedEnumerationType() || + rhs_type.IsScopedEnumerationType()) { + if (lhs_type.CompareTypes(rhs_type)) + return llvm::Error::success(); + std::string errMsg = llvm::formatv( + "invalid operands to binary expression ({0} and {1})", + orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location); + } + + // Check if the value can be compared to a pointer. We allow all pointers, + // integers, unscoped enumerations and a nullptr literal if it's an + // equality/inequality comparison. For "pointer <-> integer" C++ allows only + // equality/inequality comparison against literal zero and nullptr. However in + // the debugger context it's often useful to compare a pointer with an integer + // representing an address. That said, this also allows comparing nullptr and + // any integer, not just literal zero, e.g. "nullptr == 1 -> false". C++ + // doesn't allow it, but we implement this for convenience. + auto comparable_to_pointer = [&](CompilerType t) { + return t.IsPointerType() || t.IsInteger() || + t.IsUnscopedEnumerationType() || (!is_ordered && t.IsNullPtrType()); + }; + + if ((lhs_type.IsPointerType() && comparable_to_pointer(rhs_type)) || + (comparable_to_pointer(lhs_type) && rhs_type.IsPointerType())) { + // If both are pointers, check if they have comparable types. + if ((lhs_type.IsPointerType() && !lhs_type.IsPointerToVoid()) && + (rhs_type.IsPointerType() && !rhs_type.IsPointerToVoid())) { + // Compare canonical unqualified pointer types. + CompilerType lhs_unqualified_type = + lhs_type.GetCanonicalType().GetFullyUnqualifiedType(); + CompilerType rhs_unqualified_type = + rhs_type.GetCanonicalType().GetFullyUnqualifiedType(); + bool comparable = lhs_unqualified_type.CompareTypes(rhs_unqualified_type); + + if (!comparable) { + + std::string errMsg = llvm::formatv( + "comparison of distinct pointer types ({0} and {1})", + orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location); + } + } + // Comparing pointers to void is always allowed. + return llvm::Error::success(); + } + + std::string errMsg = llvm::formatv( + "invalid operands to binary expression ({0} and {1})", + orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location); +} + +llvm::Expected<lldb::ValueObjectSP> +Interpreter::EvaluateComparison(BinaryOpKind kind, lldb::ValueObjectSP lhs, + lldb::ValueObjectSP rhs, bool lhs_is_literal, + bool rhs_is_literal, uint32_t location) { + // Comparison works for: + // nullptr_t <-> {nullptr_t,integer} (if integer is literal zero) + // {nullptr_t,integer} <-> nullptr_t (if integer is literal zero) + // {scalar,unscoped_enum} <-> {scalar,unscoped_enum} + // scoped_enum <-> scoped_enum (if the same type) + // pointer <-> pointer (if pointee types are compatible) + // pointer <-> {integer,unscoped_enum,nullptr_t} + // {integer,unscoped_enum,nullptr_t} <-> pointer + if (auto error = ValidateComparison(kind, lhs, rhs, lhs_is_literal, + rhs_is_literal, location)) + return error; + + CompilerType lhs_type = lhs->GetCompilerType(); + CompilerType rhs_type = rhs->GetCompilerType(); + + llvm::Expected<lldb::TypeSystemSP> type_system = + GetTypeSystemFromCU(m_stack_frame); + if (!type_system) + return type_system.takeError(); + CompilerType boolean_type = GetBasicType(*type_system, lldb::eBasicTypeBool); + + return EvaluateScalarOp(kind, lhs, rhs, boolean_type, location); +} + llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location) { @@ -957,6 +1149,8 @@ Interpreter::Visit(const BinaryOpNode &node) { return rhs_or_err; lldb::ValueObjectSP rhs = *rhs_or_err; + bool lhs_is_literal = node.GetLHS().IsConstLiteral(); + bool rhs_is_literal = node.GetRHS().IsConstLiteral(); lldb::TypeSystemSP lhs_system = lhs->GetCompilerType().GetTypeSystem().GetSharedPointer(); lldb::TypeSystemSP rhs_system = @@ -995,6 +1189,14 @@ Interpreter::Visit(const BinaryOpNode &node) { case BinaryOpKind::Shl: case BinaryOpKind::Shr: return EvaluateBinaryShift(node.GetKind(), lhs, rhs, node.GetLocation()); + case BinaryOpKind::EQ: + case BinaryOpKind::NE: + case BinaryOpKind::LT: + case BinaryOpKind::LE: + case BinaryOpKind::GT: + case BinaryOpKind::GE: + return EvaluateComparison(node.GetKind(), lhs, rhs, lhs_is_literal, + rhs_is_literal, node.GetLocation()); } return llvm::make_error<DILDiagnosticError>( diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp index 997ba6b09f872..0aed547ebb0aa 100644 --- a/lldb/source/ValueObject/DILLexer.cpp +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -32,8 +32,16 @@ llvm::StringRef Token::GetTokenName(Kind kind) { return "equal"; case Kind::eof: return "eof"; + case Kind::equalequal: + return "equalequal"; + case Kind::exclaimequal: + return "exclaimequal"; case Kind::float_constant: return "float_constant"; + case Kind::greater: + return "greater"; + case Kind::greaterequal: + return "greaterequal"; case Kind::greatergreater: return "greatergreater"; case Kind::identifier: @@ -48,6 +56,10 @@ llvm::StringRef Token::GetTokenName(Kind kind) { return "l_paren"; case Kind::l_square: return "l_square"; + case Kind::less: + return "less"; + case Kind::lessequal: + return "lessequal"; case Kind::lessless: return "lessless"; case Kind::minus: @@ -198,24 +210,18 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr, // be ordered longest-to-shortest in the list below. E.g. '::' must come // before ':', and '+=' must come before '+'. constexpr std::pair<Token::Kind, const char *> operators[] = { - {Token::arrow, "->"}, - {Token::coloncolon, "::"}, - {Token::greatergreater, ">>"}, - {Token::lessless, "<<"}, - {Token::minusequal, "-="}, - {Token::plusequal, "+="}, - {Token::amp, "&"}, - {Token::colon, ":"}, - {Token::equal, "="}, - {Token::l_paren, "("}, - {Token::l_square, "["}, - {Token::minus, "-"}, - {Token::percent, "%"}, - {Token::period, "."}, - {Token::plus, "+"}, - {Token::r_paren, ")"}, - {Token::r_square, "]"}, - {Token::slash, "/"}, + {Token::arrow, "->"}, {Token::coloncolon, "::"}, + {Token::equalequal, "=="}, {Token::exclaimequal, "!="}, + {Token::greaterequal, ">="}, {Token::greatergreater, ">>"}, + {Token::lessequal, "<="}, {Token::lessless, "<<"}, + {Token::minusequal, "-="}, {Token::plusequal, "+="}, + {Token::amp, "&"}, {Token::colon, ":"}, + {Token::equal, "="}, {Token::greater, ">"}, + {Token::l_paren, "("}, {Token::l_square, "["}, + {Token::less, "<"}, {Token::minus, "-"}, + {Token::percent, "%"}, {Token::period, "."}, + {Token::plus, "+"}, {Token::r_paren, ")"}, + {Token::r_square, "]"}, {Token::slash, "/"}, {Token::star, "*"}, }; for (auto [kind, str] : operators) { diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index b55b12a2bc42a..a6afff57734e8 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -143,7 +143,7 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); } // "-=" // ASTNodeUP DILParser::ParseAssignmentExpression() { - auto lhs = ParseShiftExpression(); + auto lhs = ParseEqualityExpression(); assert(lhs && "ASTNodeUP must not contain a nullptr"); // Check if it's an assignment expression. @@ -160,6 +160,55 @@ ASTNodeUP DILParser::ParseAssignmentExpression() { return lhs; } +// Parse an equality_expression. +// +// equality_expression: +// relational_expression {"==" relational_expression} +// relational_expression {"!=" relational_expression} +// +ASTNodeUP DILParser::ParseEqualityExpression() { + auto lhs = ParseRelationalExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + + while (CurToken().IsOneOf({Token::equalequal, Token::exclaimequal})) { + Token token = CurToken(); + m_dil_lexer.Advance(); + auto rhs = ParseRelationalExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + lhs = std::make_unique<BinaryOpNode>( + token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()), + std::move(lhs), std::move(rhs)); + } + + return lhs; +} + +// Parse a relational_expression. +// +// relational_expression: +// shift_expression {"<" shift_expression} +// shift_expression {">" shift_expression} +// shift_expression {"<=" shift_expression} +// shift_expression {">=" shift_expression} +// +ASTNodeUP DILParser::ParseRelationalExpression() { + auto lhs = ParseShiftExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + + while (CurToken().IsOneOf( + {Token::less, Token::greater, Token::lessequal, Token::greaterequal})) { + Token token = CurToken(); + m_dil_lexer.Advance(); + auto rhs = ParseShiftExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + lhs = std::make_unique<BinaryOpNode>( + token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()), + std::move(lhs), std::move(rhs)); + } + + return lhs; +} + // Parse a shift_expression. // // shift_expression: diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py b/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py new file mode 100644 index 0000000000000..f5aa61e15ecbf --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py @@ -0,0 +1,173 @@ +""" +Test DIL comparison operators. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarComparison(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_comparison(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") + + # Check arithmetic comparison + self.expect_var_path("1 == 1", value="true") + self.expect_var_path("1 == 1.0", value="true") + self.expect_var_path("i == 1", value="true") + self.expect_var_path("iref == i", value="true") + self.expect_var_path("array[0] == i", value="true") + self.expect_var_path("trueVar == true", value="true") + self.expect_var_path("1 == true", value="true") + self.expect_var_path("array[0] != array[1]", value="true") + self.expect_var_path("1 != 2 == true", value="true") + self.expect_var_path("true != 2 < 3", value="false") + self.expect_var_path("ScopedEnum::kZeroS < ScopedEnum::kOneS", value="true") + self.expect_var_path("1 > 2", value="false") + self.expect_var_path("1 > 0.1", value="true") + self.expect_var_path("1 >= 2", value="false") + self.expect_var_path("2 >= 2", value="true") + self.expect_var_path("1.0 <= 1.25", value="true") + self.expect_var_path("1.25f <= 1.0", value="false") + self.expect_var_path("1.0 <= 1.0", value="true") + + self.expect( + "frame var -- 'ScopedEnum::kZeroS < ScopedEnumInt8::kOneS8'", + error=True, + substrs=[ + "invalid operands to binary expression " + "('ScopedEnum' and 'ScopedEnumInt8')" + ], + ) + + self.expect( + "frame var -- 's < 4", + error=True, + substrs=["invalid operands to binary expression ('S' and 'int')"], + ) + + # Check pointer comparison + self.expect_var_path("p_void == p_void", value="true") + self.expect_var_path("p_void == p_char1", value="true") + self.expect_var_path("p_void != p_char1", value="false") + self.expect_var_path("p_void > p_char1", value="false") + self.expect_var_path("p_void >= p_char1", value="true") + self.expect_var_path("p_void < (p_char1 + 1)", value="true") + self.expect_var_path("pp_void0 + 1 == pp_void1", value="true") + + self.expect_var_path("(void*)1 == (void*)1", value="true") + self.expect_var_path("(void*)1 != (void*)1", value="false") + self.expect_var_path("(void*)2 > (void*)1", value="true") + self.expect_var_path("(void*)2 < (void*)1", value="false") + + self.expect_var_path("(void*)1 == (char*)1", value="true") + self.expect_var_path("(char*)1 != (void*)1", value="false") + self.expect_var_path("(void*)2 > (char*)1", value="true") + self.expect_var_path("(char*)2 < (void*)1", value="false") + + self.expect_var_path("(void*)0 == 0", value="true") + self.expect_var_path("0 != (void*)0", value="false") + + self.expect_var_path("(void*)0 == nullptr", value="true") + self.expect_var_path("(void*)0 != nullptr", value="false") + self.expect_var_path("nullptr == (void*)1", value="false") + self.expect_var_path("nullptr != (void*)1", value="true") + + self.expect_var_path("nullptr == nullptr", value="true") + self.expect_var_path("nullptr != nullptr", value="false") + + self.expect_var_path("nullptr == 0", value="true") + self.expect_var_path("0 != nullptr", value="false") + self.expect_var_path("nullptr == 0U", value="true") + self.expect_var_path("0L != nullptr", value="false") + self.expect_var_path("nullptr == 0UL", value="true") + self.expect_var_path("0ULL != nullptr", value="false") + self.expect_var_path("nullptr == 0x0", value="true") + self.expect_var_path("0b0 != nullptr", value="false") + self.expect_var_path("nullptr == 00", value="true") + self.expect_var_path("0x0LLU != nullptr", value="false") + + self.expect_var_path("0 == std_nullptr_t", value="true") + self.expect_var_path("std_nullptr_t != 0", value="false") + + self.expect_var_path("array == p_int0", value="true") + self.expect_var_path("p_int0 == array", value="true") + self.expect_var_path("array < p_int1", value="true") + self.expect_var_path("array == nullptr", value="false") + + # These are not allowed by C++, but DIL supports these for convenience. + self.expect_var_path("(void*)1 == 1", value="true") + self.expect_var_path("(void*)1 == 0", value="false") + self.expect_var_path("(void*)1 > 0", value="true") + self.expect_var_path("(void*)1 < 0", value="false") + self.expect_var_path("1 > (void*)0", value="true") + self.expect_var_path("2 < (void*)3", value="true") + + # Integer is converted to uintptr_t, so negative numbers because large + # positive numbers. + self.expect_var_path("(void*)-1 == -1", value="true") + self.expect_var_path("(void*)1 > -1", value="false") + + self.expect( + "frame var -- '(void*)0 > nullptr'", + error=True, + substrs=[ + "invalid operands to binary expression ('void *' and 'std::nullptr_t')" + ], + ) + + self.expect( + "frame var -- 'nullptr > 0'", + error=True, + substrs=[ + "invalid operands to binary expression ('std::nullptr_t' and 'int')" + ], + ) + + self.expect( + "frame var -- '1 == nullptr'", + error=True, + substrs=[ + "invalid operands to binary expression ('int' and 'std::nullptr_t')" + ], + ) + + self.expect( + "frame var -- 'nullptr == (int)0'", + error=True, + substrs=[ + "invalid operands to binary expression ('std::nullptr_t' and 'int')" + ], + ) + + self.expect( + "frame var -- 'false == nullptr'", + error=True, + substrs=[ + "invalid operands to binary expression ('bool' and 'std::nullptr_t')" + ], + ) + + self.expect( + "frame var -- 'p_int0 > p_char1'", + error=True, + substrs=[ + "comparison of distinct pointer types ('int *' and 'const char *')" + ], + ) + + self.expect( + "frame var -- 'pp_void0 == p_char1'", + error=True, + substrs=[ + "comparison of distinct pointer types ('void **' and 'const char *')" + ], + ) diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp new file mode 100644 index 0000000000000..982df688a88ca --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp @@ -0,0 +1,34 @@ +#include <cstdint> +#include <cstddef> + +enum class ScopedEnum { kZeroS, kOneS }; +enum class ScopedEnumInt8 : int8_t { kZeroS8, kOneS8 }; + +void stop() {} + +int main(int argc, char **argv) { + auto enum_one = ScopedEnum::kOneS; + auto enum_one_8 = ScopedEnumInt8::kOneS8; + + bool trueVar = true; + + int i = 1; + int j = 2; + int &iref = i; + int array[2] = {1, 2}; + + struct S { + } s; + + int *p_int0 = &array[0]; + int *p_int1 = &array[1]; + const char *p_char1 = "hello"; + void *p_void = (void *)p_char1; + void **pp_void0 = &p_void; + void **pp_void1 = pp_void0 + 1; + + std::nullptr_t std_nullptr_t = nullptr; + + stop(); // Set a breakpoint here + return 0; +} >From 0a373725f76060ee9c2b05fb2bdb3cc144f3beae Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Tue, 14 Jul 2026 21:05:25 +0500 Subject: [PATCH 2/3] Fix formatting --- lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp index 982df688a88ca..4ff21bb8f982f 100644 --- a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp +++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp @@ -1,5 +1,5 @@ -#include <cstdint> #include <cstddef> +#include <cstdint> enum class ScopedEnum { kZeroS, kOneS }; enum class ScopedEnumInt8 : int8_t { kZeroS8, kOneS8 }; >From e3a2d8443528d2de640eb46e5b880940a0daafff Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Wed, 15 Jul 2026 19:25:33 +0500 Subject: [PATCH 3/3] Fix asserts --- lldb/source/ValueObject/DILParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index a6afff57734e8..d193e7d7a5f1f 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -174,7 +174,7 @@ ASTNodeUP DILParser::ParseEqualityExpression() { Token token = CurToken(); m_dil_lexer.Advance(); auto rhs = ParseRelationalExpression(); - assert(lhs && "ASTNodeUP must not contain a nullptr"); + assert(rhs && "ASTNodeUP must not contain a nullptr"); lhs = std::make_unique<BinaryOpNode>( token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()), std::move(lhs), std::move(rhs)); @@ -200,7 +200,7 @@ ASTNodeUP DILParser::ParseRelationalExpression() { Token token = CurToken(); m_dil_lexer.Advance(); auto rhs = ParseShiftExpression(); - assert(lhs && "ASTNodeUP must not contain a nullptr"); + assert(rhs && "ASTNodeUP must not contain a nullptr"); lhs = std::make_unique<BinaryOpNode>( token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()), std::move(lhs), std::move(rhs)); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
