llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: Ilia Kuklin (kuilpd) <details> <summary>Changes</summary> Add ternary conditional operator to DIL: `condition ? true_expr : false_expr`. Unlike C++, DIL will evaluate only the operand chosen by the condition, and will not check the type or evaluate the other operand. --- Full diff: https://github.com/llvm/llvm-project/pull/211344.diff 12 Files Affected: - (modified) lldb/docs/dil-expr-lang.ebnf (+4-1) - (modified) lldb/include/lldb/ValueObject/DILAST.h (+27) - (modified) lldb/include/lldb/ValueObject/DILEval.h (+2) - (modified) lldb/include/lldb/ValueObject/DILLexer.h (+1) - (modified) lldb/include/lldb/ValueObject/DILParser.h (+1-1) - (modified) lldb/source/ValueObject/DILAST.cpp (+4) - (modified) lldb/source/ValueObject/DILEval.cpp (+33) - (modified) lldb/source/ValueObject/DILLexer.cpp (+3) - (modified) lldb/source/ValueObject/DILParser.cpp (+29-1) - (added) lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile (+3) - (added) lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py (+76) - (added) lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp (+20) ``````````diff diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index f3c465711e956..94e5859e5f8eb 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -3,7 +3,10 @@ (* This is currently a subset of the final DIL Language, matching the current DIL implementation. *) -expression = assignment_expression ; +expression = conditional_expression ; + +conditional_expression = assignment_expression + | assignment_expression "?" expression ":" expression ; assignment_expression = shift_expression | shift_expression assignment_operator assignment_expression ; diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 93310a91a15bb..fc95215e896fe 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -24,6 +24,7 @@ enum class NodeKind { eBitExtractionNode, eBooleanLiteralNode, eCastNode, + eConditionalNode, eErrorNode, eFloatLiteralNode, eIdentifierNode, @@ -320,6 +321,30 @@ class CastNode : public ASTNode { CastKind m_cast_kind; }; +class ConditionalNode : public ASTNode { +public: + ConditionalNode(uint32_t location, ASTNodeUP condition, ASTNodeUP true_op, + ASTNodeUP false_op) + : ASTNode(location, NodeKind::eConditionalNode), + m_condition(std::move(condition)), m_true_op(std::move(true_op)), + m_false_op(std::move(false_op)) {} + + llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + + ASTNode &GetCondition() const { return *m_condition; } + ASTNode &GetTrueOperand() const { return *m_true_op; } + ASTNode &GetFalseOperand() const { return *m_false_op; } + + static bool classof(const ASTNode &node) { + return node.GetKind() == NodeKind::eConditionalNode; + } + +private: + ASTNodeUP m_condition; + ASTNodeUP m_true_op; + ASTNodeUP m_false_op; +}; + /// This class contains one Visit method for each specialized type of /// DIL AST node. The Visit methods are used to dispatch a DIL AST node to /// the correct function in the DIL expression evaluator for evaluating that @@ -346,6 +371,8 @@ class Visitor { virtual llvm::Expected<lldb::ValueObjectSP> Visit(const BooleanLiteralNode &node) = 0; virtual llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode &node) = 0; + virtual llvm::Expected<lldb::ValueObjectSP> + Visit(const ConditionalNode &node) = 0; }; } // namespace lldb_private::dil diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 35784ea9987f9..05b99618d2f13 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -73,6 +73,8 @@ class Interpreter : Visitor { llvm::Expected<lldb::ValueObjectSP> Visit(const BooleanLiteralNode &node) override; llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode &node) override; + llvm::Expected<lldb::ValueObjectSP> + Visit(const ConditionalNode &node) override; /// Perform usual unary conversions on a value. At the moment this /// includes array-to-pointer and integral promotion for eligible types. diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h index f9f42dc59b311..52a9701b2e2a9 100644 --- a/lldb/include/lldb/ValueObject/DILLexer.h +++ b/lldb/include/lldb/ValueObject/DILLexer.h @@ -46,6 +46,7 @@ class Token { period, plus, plusequal, + question, r_paren, r_square, slash, diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index 9e2bbff4b6614..4ad58f36f736c 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -82,7 +82,7 @@ class DILParser { ASTNodeUP Run(); ASTNodeUP ParseExpression(); - + ASTNodeUP ParseConditionalExpression(); ASTNodeUP ParseAssignmentExpression(); ASTNodeUP ParseShiftExpression(); ASTNodeUP ParseAdditiveExpression(); diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 40bf07bdd5aab..86fa2031207a1 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -87,4 +87,8 @@ llvm::Expected<lldb::ValueObjectSP> CastNode::Accept(Visitor *v) const { return v->Visit(*this); } +llvm::Expected<lldb::ValueObjectSP> ConditionalNode::Accept(Visitor *v) const { + return v->Visit(*this); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 4c5ac96dccf74..a63e03de05bf2 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1578,4 +1578,37 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const CastNode &node) { node.GetLocation()); } +llvm::Expected<lldb::ValueObjectSP> +Interpreter::Visit(const ConditionalNode &node) { + auto cond_or_err = EvaluateAndDereference(node.GetCondition()); + if (!cond_or_err) + return cond_or_err; + lldb::ValueObjectSP condition = *cond_or_err; + + CompilerType cond_type = condition->GetCompilerType(); + if (!cond_type.IsContextuallyConvertibleToBool()) { + std::string errMsg = llvm::formatv( + "value of type {0} is not contextually convertible to 'bool'", + cond_type.TypeDescription()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node.GetLocation()); + } + // Note: Unlike C++, DIL evaluates only the operand chosen by the condition, + // and doesn't check the type or evaluate the other operand. + auto value_or_err = condition->GetValueAsBool(); + if (value_or_err) { + if (*value_or_err) { + auto true_or_err = EvaluateAndDereference(node.GetTrueOperand()); + if (!true_or_err) + return true_or_err; + return *true_or_err; + } + auto false_or_err = EvaluateAndDereference(node.GetFalseOperand()); + if (!false_or_err) + return false_or_err; + return *false_or_err; + } + return value_or_err.takeError(); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp index 997ba6b09f872..fa8b960908f4b 100644 --- a/lldb/source/ValueObject/DILLexer.cpp +++ b/lldb/source/ValueObject/DILLexer.cpp @@ -62,6 +62,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) { return "plus"; case Kind::plusequal: return "plusequal"; + case Kind::question: + return "question"; case Kind::r_paren: return "r_paren"; case Kind::r_square: @@ -213,6 +215,7 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr, {Token::percent, "%"}, {Token::period, "."}, {Token::plus, "+"}, + {Token::question, "?"}, {Token::r_paren, ")"}, {Token::r_square, "]"}, {Token::slash, "/"}, diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index b55b12a2bc42a..467d766ebd1ef 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -129,7 +129,35 @@ ASTNodeUP DILParser::Run() { // expression: // assignment_expression // -ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); } +ASTNodeUP DILParser::ParseExpression() { return ParseConditionalExpression(); } + +// Parse a conditional_expression. +// +// conditional_expression: +// assignment_expression +// assignment_expression "?" expression ":" expression +// +ASTNodeUP DILParser::ParseConditionalExpression() { + auto lhs = ParseAssignmentExpression(); + assert(lhs && "ASTNodeUP must not contain a nullptr"); + + // Check if it's a ternary operator. + if (CurToken().Is(Token::question)) { + Token token = CurToken(); + m_dil_lexer.Advance(); + auto true_op = ParseExpression(); + assert(true_op && "ASTNodeUP must not contain a nullptr"); + Expect(Token::colon); + m_dil_lexer.Advance(); + auto false_op = ParseExpression(); + assert(false_op && "ASTNodeUP must not contain a nullptr"); + lhs = std::make_unique<ConditionalNode>(token.GetLocation(), std::move(lhs), + std::move(true_op), + std::move(false_op)); + } + + return lhs; +} // Parse an assignment_expression // diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py new file mode 100644 index 0000000000000..20bc6464b7779 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/TestFrameVarDILConditional.py @@ -0,0 +1,76 @@ +""" +Test DIL ternary conditional operator. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarDILConditional(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_conditional(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") + + # Unlike C++, DIL evaluates only the operand chosen by the condition, + # and doesn't check the type or evaluate the other operand. + # Check integer values. + self.expect_var_path("true ? sh : sh", value="2", type="short") + self.expect_var_path("true ? sh : 1", value="2", type="short") + self.expect_var_path("true ? sh : 1.0f", value="2", type="short") + self.expect_var_path("false ? 1 : sh", value="2", type="short") + self.expect_var_path("1 + false ? 10 : 20", value="10") + self.expect_var_path("1 + (false ? 10 : 20)", value="21") + + # Check enums. + self.expect_var_path("false ? b_enum : a_enum", value="kTwoA") + self.expect_var_path("false ? sh : b_enum", value="kOneB") + self.expect_var_path("false ? b_enum : sh", value="2") + + # Check references + self.expect_var_path("iref ? 1 : 2", value="1", type="int") + self.expect_var_path("true ? iref : 2", value="1", type="int") + self.expect_var_path("false ? 1 : iref", value="1", type="int") + + # Check pointers and arrays. + nullptr = "0x" + "00" * self.target().GetAddressByteSize() + self.expect_var_path("true ? 0 : nullptr", value="0") + self.expect_var_path("true ? nullptr : 0", value=nullptr) + self.expect_var_path("true ? arr2 : arr3", type="int[2]") + self.expect_var_path("true ? arr2 : 0", type="int[2]") + self.expect_var_path("true ? 0 : arr2", value="0") + self.expect_var_path("true ? nullptr : arr2", value=nullptr) + self.expect_var_path("*(true ? arr2 : arr3)", value="1") + + # Check result with incompatible type operands. + # (these would return an error in C++) + self.expect_var_path("true ? s : 1", type="S") + self.expect_var_path("true ? 1 : t", value="1") + self.expect_var_path("true ? nullptr : 1", value=nullptr) + self.expect_var_path("true ? 1.25 : arr", value="1.25") + self.expect_var_path("*(true ? iptr : 2.0)", value="1") + self.expect_var_path("&(true ? i : arr)", type="int *") + self.expect_var_path("true ? iptr : nullptr", type="int *") + + # Check non-existent values + self.expect_var_path("true ? 1 : __doesnt_exist", value="1") + self.expect_var_path("false ? __doesnt_exist : 2", value="2") + + # Use different types in bool context. + self.expect_var_path("iptr ? 1 : 2", value="1") + self.expect_var_path("nptr ? 1 : 2", value="2") + self.expect_var_path("arr2 ? 1 : 2", value="1") + self.expect_var_path("1.0f ? 1 : 2", value="1") + self.expect_var_path("a_enum ? 1 : 2", value="1") + self.expect( + "frame var -- 's ? 1 : 2'", + error=True, + substrs=["value of type 'S' is not contextually convertible to 'bool'"], + ) diff --git a/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp new file mode 100644 index 0000000000000..83820c53a7070 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Conditional/main.cpp @@ -0,0 +1,20 @@ +void stop() {} + +int main(int argc, char **argv) { + int i = 1; + int &iref = i; + int *iptr = &i; + short sh = 2; + int arr2[2] = {1, 2}; + int arr3[3] = {0, 1, 2}; + double dbl_arr[2] = {1.0, 2.0}; + void *nptr = nullptr; + + struct S { + } s; + + enum EnumA { kOneA = 1, kTwoA } a_enum = kTwoA; + enum EnumB { kOneB = 1 } b_enum = kOneB; + + stop(); // Set a breakpoint here +} `````````` </details> https://github.com/llvm/llvm-project/pull/211344 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
