From c88993fdc3327fda883b2eb935a7b634e7b72c60 Mon Sep 17 00:00:00 2001 From: Jan Schlosser Date: Mon, 26 Oct 2026 13:52:23 +0100 Subject: [PATCH] Fix RULE-7-0-2 false positives for contextual bool conversions NoImplicitBoolConversion reported contextual conversions that the rule permits: - Condition declarations (if (T* p = f()), while (auto sp = w.lock()), for (...; T* p = f(); ...)): the initializer is contextually converted to bool. Treat the initializer of a condition declaration as a contextual bool context, so pointers and classes with an explicit operator bool are no longer flagged. - static_cast(x) where x is a class (or class template instantiation) with an explicit operator bool: the extractor leaves members of uninstantiated templates unresolved, so look the operator up on the instantiated template as well. --- ...ix-fp-rule-7-0-2-contextual-conversions.md | 7 ++++ .../RULE-7-0-2/NoImplicitBoolConversion.ql | 32 ++++++++++++++++++- .../NoImplicitBoolConversion.expected | 1 + cpp/misra/test/rules/RULE-7-0-2/test.cpp | 32 ++++++++++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 change_notes/2026-10-23-fix-fp-rule-7-0-2-contextual-conversions.md diff --git a/change_notes/2026-10-23-fix-fp-rule-7-0-2-contextual-conversions.md b/change_notes/2026-10-23-fix-fp-rule-7-0-2-contextual-conversions.md new file mode 100644 index 0000000000..5b7e4042bd --- /dev/null +++ b/change_notes/2026-10-23-fix-fp-rule-7-0-2-contextual-conversions.md @@ -0,0 +1,7 @@ +- `RULE-7-0-2` - `NoImplicitBoolConversion.ql`: + - Fixed false positives on the initializer of an `if`/`for` condition + declaration (e.g. `if (T* p = f())` or `if (auto sp = weak.lock())`) when + the initialized type is a pointer or a class with an explicit + `operator bool`. These are contextual conversions, which are permitted. + - Fixed false positives on `static_cast` of a class type with an + explicit `operator bool` inside a class template. diff --git a/cpp/misra/src/rules/RULE-7-0-2/NoImplicitBoolConversion.ql b/cpp/misra/src/rules/RULE-7-0-2/NoImplicitBoolConversion.ql index cbf189d447..c79e24d45c 100644 --- a/cpp/misra/src/rules/RULE-7-0-2/NoImplicitBoolConversion.ql +++ b/cpp/misra/src/rules/RULE-7-0-2/NoImplicitBoolConversion.ql @@ -16,6 +16,21 @@ import cpp import codingstandards.cpp.misra +/** + * The initializer of a condition declaration is contextually converted to bool, e.g. + * `if (T* p = f())` or `if (auto sp = weak.lock())`. + */ +predicate isInConditionDeclarationInitializer(Expr expr) { + exists(ConditionDeclExpr condDecl | + condDecl.getExpr() = expr and + ( + exists(IfStmt ifStmt | ifStmt.getCondition() = condDecl) or + exists(WhileStmt whileStmt | whileStmt.getCondition() = condDecl) or + exists(ForStmt forStmt | forStmt.getCondition() = condDecl) + ) + ) +} + predicate isInContextualBoolContext(Expr expr) { exists(IfStmt ifStmt | ifStmt.getCondition() = expr) or exists(WhileStmt whileStmt | whileStmt.getCondition() = expr) or @@ -24,7 +39,8 @@ predicate isInContextualBoolContext(Expr expr) { exists(ConditionalExpr condExpr | condExpr.getCondition() = expr) or exists(LogicalAndExpr logicalAnd | logicalAnd.getAnOperand() = expr) or exists(LogicalOrExpr logicalOr | logicalOr.getAnOperand() = expr) or - exists(NotExpr notExpr | notExpr.getOperand() = expr) + exists(NotExpr notExpr | notExpr.getOperand() = expr) or + isInConditionDeclarationInitializer(expr) } predicate isInWhileConditionDeclaration(Expr expr) { @@ -88,6 +104,20 @@ where // an array to a pointer, then the pointer to bool isInContextualBoolContext(conv.getUnconverted()) ) and + // Exception 1: Static cast to bool from a class with an explicit operator bool. In an + // uninstantiated template the cast has no resolved call to the conversion operator, so it + // appears only as a `Conversion` from the class type. + not ( + conv instanceof StaticCast and + exists(ConversionOperator op | + op.isExplicit() and + op.getType().getUnspecifiedType() instanceof BoolType and + exists(Class source | source = conv.getExpr().getType().getUnspecifiedType() | + op = source.getAMember() or + op = source.(ClassTemplateInstantiation).getTemplate().getAMember() + ) + ) + ) and // Exception 3: Unconverted expression is a bit-field of size 1 not isBitFieldOfSizeOne(conv.getUnconverted()) and // Exception 4: Unconverted expression is in a while condition declaration diff --git a/cpp/misra/test/rules/RULE-7-0-2/NoImplicitBoolConversion.expected b/cpp/misra/test/rules/RULE-7-0-2/NoImplicitBoolConversion.expected index cc1db5b723..bb238d0ba1 100644 --- a/cpp/misra/test/rules/RULE-7-0-2/NoImplicitBoolConversion.expected +++ b/cpp/misra/test/rules/RULE-7-0-2/NoImplicitBoolConversion.expected @@ -25,3 +25,4 @@ | test.cpp:201:13:201:14 | (bool)... | Conversion from 'int32_t *' to 'bool'. | | test.cpp:213:13:213:14 | (bool)... | Conversion from '..:: *' to 'bool'. | | test.cpp:214:13:214:14 | (bool)... | Conversion from '..:: *' to 'bool'. | +| test.cpp:283:20:283:21 | (bool)... | Conversion from 'int32_t' to 'bool'. | diff --git a/cpp/misra/test/rules/RULE-7-0-2/test.cpp b/cpp/misra/test/rules/RULE-7-0-2/test.cpp index ce57597632..47700951f3 100644 --- a/cpp/misra/test/rules/RULE-7-0-2/test.cpp +++ b/cpp/misra/test/rules/RULE-7-0-2/test.cpp @@ -268,4 +268,34 @@ constexpr bool derived_from_template_v = is_something_v; // COMPLIANT bool test_dependent_variable_template_instantiation() { return derived_from_template_v; // COMPLIANT -} \ No newline at end of file +} +void test_condition_declaration() { + if (std::int32_t *l1 = f3()) { // COMPLIANT - exception #2 + } + if (auto *const l2 = f3()) { // COMPLIANT - exception #2 + } + while (std::int32_t *l3 = f3()) { // COMPLIANT - exception #2 + } + if (TestClassExplicit l4 = TestClassExplicit{}) { // COMPLIANT + } + if (std::int32_t *l5 = f3(); l5) { // COMPLIANT - exception #2 + } + if (std::int32_t l6 = f1()) { // NON_COMPLIANT + } +} + +template class TestTemplateExplicit { +public: + explicit operator bool() const { return true; } +}; + +template +bool test_static_cast_template(const TestTemplateExplicit &l1) { + return !static_cast(l1); // COMPLIANT - exception #1 +} + +bool test_static_cast_template_use( + const TestTemplateExplicit &l1) { + bool l2 = static_cast(l1); // COMPLIANT - exception #1 + return l2 && test_static_cast_template(l1); +}