https://github.com/kuilpd created https://github.com/llvm/llvm-project/pull/209742
Add binary logical operators `||`, `&&`, and a unary logical `!` to DIL. >From fffdae3a777949d78609ea02b4f8851f9be31169 Mon Sep 17 00:00:00 2001 From: Ilia Kuklin <[email protected]> Date: Tue, 14 Jul 2026 22:38:56 +0500 Subject: [PATCH] [lldb] Add logical operators to DIL --- lldb/docs/dil-expr-lang.ebnf | 10 ++- lldb/include/lldb/ValueObject/DILAST.h | 3 + lldb/include/lldb/ValueObject/DILEval.h | 2 + lldb/include/lldb/ValueObject/DILLexer.h | 3 + lldb/include/lldb/ValueObject/DILParser.h | 2 + lldb/source/ValueObject/DILAST.cpp | 4 + lldb/source/ValueObject/DILEval.cpp | 81 +++++++++++++++++++ lldb/source/ValueObject/DILLexer.cpp | 36 ++++----- lldb/source/ValueObject/DILParser.cpp | 58 +++++++++++-- .../frame/var-dil/expr/Logical/Makefile | 3 + .../expr/Logical/TestFrameVarDILLogical.py | 78 ++++++++++++++++++ .../frame/var-dil/expr/Logical/main.cpp | 20 +++++ 12 files changed, 273 insertions(+), 27 deletions(-) create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Logical/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Logical/TestFrameVarDILLogical.py create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Logical/main.cpp diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index fce8f4461b46f..998b9b1acadd7 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -5,13 +5,17 @@ expression = assignment_expression ; -assignment_expression = shift_expression - | shift_expression assignment_operator assignment_expression ; +assignment_expression = logical_or_expression + | logical_or_expression assignment_operator assignment_expression ; assignment_operator = "=" | "+=" | "-=" ; +logical_or_expression = logical_and_expression {"||" logical_and_expression} ; + +logical_and_expression = shift_expression {"&&" shift_expression} ; + shift_expression = additive_expression {"<<" additive_expression} | additive_expression {">>" additive_expression} ; @@ -28,7 +32,7 @@ cast_expression = unary_expression unary_expression = postfix_expression | unary_operator cast_expression ; -unary_operator = "*" | "&" | "+" | "-"; +unary_operator = "*" | "&" | "+" | "-" | "!" ; postfix_expression = primary_expression | postfix_expression "[" expression "]" diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 93310a91a15bb..ba2da78cd826d 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -38,6 +38,7 @@ enum class UnaryOpKind { Deref, ///< "*" Minus, ///< "-" Plus, ///< "+" + LNot, ///< "!" }; /// The binary operators recognized by DIL. @@ -52,6 +53,8 @@ enum class BinaryOpKind { Shr, ///< ">>" Sub, ///< "-" SubAssign, ///< "-=" + LAnd, ///< "&&" + LOr, ///< "||" }; /// Translates DIL tokens to BinaryOpKind. diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 35784ea9987f9..7335702f83510 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -126,6 +126,8 @@ class Interpreter : Visitor { llvm::Expected<lldb::ValueObjectSP> EvaluateBinaryRemainder(lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location); + llvm::Expected<lldb::ValueObjectSP> EvaluateLogical(const BinaryOpNode &node); + llvm::Expected<CompilerType> PickIntegerType(lldb::TypeSystemSP type_system, ExecutionContextScope &ctx, const IntegerLiteralNode &literal); diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index f9f42dc59b311..400494d1c0c6d 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -26,11 +26,13 @@ class Token { public: enum Kind { amp, + ampamp, arrow, colon, coloncolon, eof, equal, + exclaim, float_constant, greatergreater, identifier, @@ -44,6 +46,7 @@ class Token { minusequal, percent, period, + pipepipe, plus, plusequal, r_paren, diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index 9e2bbff4b6614..246deb49f26c0 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 ParseLogicalOrExpression(); + ASTNodeUP ParseLogicalAndExpression(); ASTNodeUP ParseShiftExpression(); ASTNodeUP ParseAdditiveExpression(); ASTNodeUP ParseMultiplicativeExpression(); diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 40bf07bdd5aab..4cea4d044e11c 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -33,6 +33,10 @@ BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind) { return BinaryOpKind::Shl; case Token::greatergreater: return BinaryOpKind::Shr; + case Token::ampamp: + return BinaryOpKind::LAnd; + case Token::pipepipe: + return BinaryOpKind::LOr; default: break; } diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 5fc2376be0e75..1ed3fa39bc605 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -559,6 +559,25 @@ Interpreter::Visit(const UnaryOpNode &node) { } return operand; } + case UnaryOpKind::LNot: { + CompilerType operand_type = operand->GetCompilerType(); + if (!operand_type.IsContextuallyConvertibleToBool()) { + std::string errMsg = + llvm::formatv("invalid argument type '{0}' to unary expression", + operand_type.GetTypeName()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node.GetLocation()); + } + llvm::Expected<lldb::TypeSystemSP> type_system = + GetTypeSystemFromCU(m_stack_frame); + if (!type_system) + return type_system.takeError(); + auto value_or_err = operand->GetValueAsBool(); + if (value_or_err) + return ValueObject::CreateValueObjectFromBool(m_stack_frame, *type_system, + !(*value_or_err), "result"); + break; + } } return llvm::make_error<DILDiagnosticError>(m_expr, "invalid unary operation", node.GetLocation()); @@ -931,8 +950,68 @@ Interpreter::EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs, return EvaluateScalarOp(kind, lhs, rhs, lhs_type, location); } +llvm::Expected<lldb::ValueObjectSP> +Interpreter::EvaluateLogical(const BinaryOpNode &node) { + // Operations {'&&', '||'} work for: + // {IsContextuallyConvertibleToBool} <-> {IsContextuallyConvertibleToBool} + // Note: Unlike C++, these operators will not evaluate or check the type + // of RHS if the result is determined after evaluating LHS. + auto lhs_or_err = EvaluateAndDereference(node.GetLHS()); + if (!lhs_or_err) + return lhs_or_err; + lldb::ValueObjectSP lhs = *lhs_or_err; + auto lhs_type = lhs->GetCompilerType(); + if (!lhs_type.IsContextuallyConvertibleToBool()) { + std::string errMsg = llvm::formatv( + "value of type {0} is not contextually convertible to 'bool'", + lhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node.GetLocation()); + } + llvm::Expected<lldb::TypeSystemSP> type_system = + GetTypeSystemFromCU(m_stack_frame); + if (!type_system) + return type_system.takeError(); + + // For "&&", exit early if LHS is "false" + // For "||", exit early if LHS is "true". + auto lvalue_or_err = lhs->GetValueAsBool(); + if (!lvalue_or_err) + return lvalue_or_err.takeError(); + bool lhs_val = *lvalue_or_err; + bool exit_early = node.GetKind() == BinaryOpKind::LAnd ? !lhs_val : lhs_val; + if (exit_early) + return ValueObject::CreateValueObjectFromBool(m_stack_frame, *type_system, + lhs_val, "result"); + + // If the result is to be determined, evaluate the RHS. + auto rhs_or_err = EvaluateAndDereference(node.GetRHS()); + if (!rhs_or_err) + return rhs_or_err; + lldb::ValueObjectSP rhs = *rhs_or_err; + auto rhs_type = rhs->GetCompilerType(); + if (!rhs_type.IsContextuallyConvertibleToBool()) { + std::string errMsg = llvm::formatv( + "value of type {0} is not contextually convertible to 'bool'", + rhs_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node.GetLocation()); + } + + auto rvalue_or_err = rhs->GetValueAsBool(); + if (!rvalue_or_err) + return rvalue_or_err.takeError(); + return ValueObject::CreateValueObjectFromBool(m_stack_frame, *type_system, + *rvalue_or_err, "result"); +} + llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const BinaryOpNode &node) { + // Handle logical operators separately. They may or may not evaluate RHS. + if (node.GetKind() == BinaryOpKind::LAnd || + node.GetKind() == BinaryOpKind::LOr) + return EvaluateLogical(node); + auto lhs_or_err = EvaluateAndDereference(node.GetLHS()); if (!lhs_or_err) return lhs_or_err; @@ -980,6 +1059,8 @@ Interpreter::Visit(const BinaryOpNode &node) { case BinaryOpKind::Shl: case BinaryOpKind::Shr: return EvaluateBinaryShift(node.GetKind(), lhs, rhs, node.GetLocation()); + default: + break; } return llvm::make_error<DILDiagnosticError>( diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp index 997ba6b09f872..eb5da7be06b14 100644 --- a/lldb/source/ValueObject/DILLexer.cpp +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -22,6 +22,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) { switch (kind) { case Kind::amp: return "amp"; + case Kind::ampamp: + return "ampamp"; case Kind::arrow: return "arrow"; case Kind::colon: @@ -30,6 +32,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) { return "coloncolon"; case Kind::equal: return "equal"; + case Kind::exclaim: + return "exclaim"; case Kind::eof: return "eof"; case Kind::float_constant: @@ -58,6 +62,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) { return "percent"; case Kind::period: return "period"; + case Kind::pipepipe: + return "pipepipe"; case Kind::plus: return "plus"; case Kind::plusequal: @@ -198,25 +204,17 @@ 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::star, "*"}, + {Token::arrow, "->"}, {Token::ampamp, "&&"}, + {Token::coloncolon, "::"}, {Token::greatergreater, ">>"}, + {Token::lessless, "<<"}, {Token::minusequal, "-="}, + {Token::pipepipe, "||"}, {Token::plusequal, "+="}, + {Token::amp, "&"}, {Token::colon, ":"}, + {Token::equal, "="}, {Token::exclaim, "!"}, + {Token::l_paren, "("}, {Token::l_square, "["}, + {Token::minus, "-"}, {Token::percent, "%"}, + {Token::period, "."}, {Token::plus, "+"}, + {Token::r_paren, ")"}, {Token::r_square, "]"}, + {Token::slash, "/"}, {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 b55b12a2bc42a..1b8852523dad0 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -134,8 +134,8 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); } // Parse an assignment_expression // // assignment_expression -// shift_expression -// shift_expression assignment_operator assignment_expression +// logical_or_expression +// logical_or_expression assignment_operator assignment_expression // // assignment_operator: // "=" @@ -143,7 +143,7 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); } // "-=" // ASTNodeUP DILParser::ParseAssignmentExpression() { - auto lhs = ParseShiftExpression(); + auto lhs = ParseLogicalOrExpression(); assert(lhs && "ASTNodeUP must not contain a nullptr"); // Check if it's an assignment expression. @@ -160,6 +160,50 @@ ASTNodeUP DILParser::ParseAssignmentExpression() { return lhs; } +// Parse a logical_or_expression. +// +// logical_or_expression: +// logical_and_expression {"||" logical_and_expression} +// +ASTNodeUP DILParser::ParseLogicalOrExpression() { + auto lhs = ParseLogicalAndExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + + while (CurToken().Is(Token::pipepipe)) { + Token token = CurToken(); + m_dil_lexer.Advance(); + auto rhs = ParseLogicalAndExpression(); + 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 logical_and_expression. +// +// logical_and_expression: +// shift_expression {"&&" shift_expression} +// +ASTNodeUP DILParser::ParseLogicalAndExpression() { + auto lhs = ParseShiftExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + + while (CurToken().Is(Token::ampamp)) { + 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: @@ -293,10 +337,11 @@ ASTNodeUP DILParser::ParseCastExpression() { // "*" // "+" // "-" +// "!" // ASTNodeUP DILParser::ParseUnaryExpression() { - if (CurToken().IsOneOf( - {Token::amp, Token::star, Token::minus, Token::plus})) { + if (CurToken().IsOneOf({Token::amp, Token::star, Token::minus, Token::plus, + Token::exclaim})) { Token token = CurToken(); uint32_t loc = token.GetLocation(); m_dil_lexer.Advance(); @@ -315,6 +360,9 @@ ASTNodeUP DILParser::ParseUnaryExpression() { case Token::plus: return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Plus, std::move(rhs)); + case Token::exclaim: + return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::LNot, + std::move(rhs)); default: llvm_unreachable("invalid token kind"); } diff --git a/lldb/test/API/commands/frame/var-dil/expr/Logical/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Logical/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Logical/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/Logical/TestFrameVarDILLogical.py b/lldb/test/API/commands/frame/var-dil/expr/Logical/TestFrameVarDILLogical.py new file mode 100644 index 0000000000000..452973092ffff --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Logical/TestFrameVarDILLogical.py @@ -0,0 +1,78 @@ +""" +Test DIL logical operators. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarLogical(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_logical(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("1 && 2", value="true") + self.expect_var_path("0 && 1", value="false") + self.expect_var_path("0 || 1", value="true") + self.expect_var_path("0 || 0", value="false") + + self.expect_var_path("!1", value="false") + self.expect_var_path("!!1", value="true") + + self.expect_var_path("!trueVar", value="false") + self.expect_var_path("!!trueVar", value="true") + self.expect_var_path("!falseVar", value="true") + self.expect_var_path("!!falseVar", value="false") + + self.expect_var_path("trueVar || true", value="true") + self.expect_var_path("trueVar && false", value="false") + self.expect_var_path("falseVar || true", value="true") + self.expect_var_path("falseVar && true", value="false") + self.expect_var_path("true || false && false", value="true") + self.expect_var_path("(true || false) && false", value="false") + + self.expect_var_path("!p_ptr", value="false") + self.expect_var_path("!!p_ptr", value="true") + self.expect_var_path("p_ptr && true", value="true") + self.expect_var_path("p_ptr && false", value="false") + self.expect_var_path("!p_nullptr", value="true") + self.expect_var_path("!!p_nullptr", value="false") + self.expect_var_path("p_nullptr || true", value="true") + self.expect_var_path("p_nullptr || false", value="false") + + self.expect_var_path("!array", value="false") + self.expect_var_path("!!array", value="true") + self.expect_var_path("array || true", value="true") + self.expect_var_path("false || array", value="true") + self.expect_var_path("array && true", value="true") + self.expect_var_path("array && false", value="false") + + # DIL doesn't evaluate the right operand if the left one + # determines the result + self.expect_var_path("true || __doesnt_exist", value="true") + self.expect_var_path("false && __doesnt_exist", value="false") + + # Check errors + self.expect( + "frame var -- 'false || !s'", + error=True, + substrs=["invalid argument type 'S' to unary expression"], + ) + self.expect( + "frame var -- 's || false'", + error=True, + substrs=["value of type 'S' is not contextually convertible to 'bool'"], + ) + self.expect( + "frame var -- 'true && s'", + error=True, + substrs=["value of type 'S' is not contextually convertible to 'bool'"], + ) diff --git a/lldb/test/API/commands/frame/var-dil/expr/Logical/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Logical/main.cpp new file mode 100644 index 0000000000000..3258016157cac --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Logical/main.cpp @@ -0,0 +1,20 @@ +#include <cstdint> +#include <limits> + +void stop() {} + +int main(int argc, char **argv) { + bool trueVar = true; + bool falseVar = false; + + const char *p_ptr = "str"; + const char *p_nullptr = nullptr; + + int array[2] = {1, 2}; + + struct S { + } s; + + stop(); // Set a breakpoint here + return 0; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
