diff --git a/change_notes/2025-12-17-unfeasible-statement-refactor.md b/change_notes/2025-12-17-unfeasible-statement-refactor.md new file mode 100644 index 0000000000..78bd05bccf --- /dev/null +++ b/change_notes/2025-12-17-unfeasible-statement-refactor.md @@ -0,0 +1,2 @@ +- `M0-1-2` - `InfeasiblePath.ql`: + - Refactored to share logic with `RULE-0-0-2` while allowing for different exceptional cases. No change in behavior expected. \ No newline at end of file diff --git a/cpp/autosar/src/rules/M0-1-2/InfeasiblePath.ql b/cpp/autosar/src/rules/M0-1-2/InfeasiblePath.ql index 83e056472b..6e76647435 100644 --- a/cpp/autosar/src/rules/M0-1-2/InfeasiblePath.ql +++ b/cpp/autosar/src/rules/M0-1-2/InfeasiblePath.ql @@ -16,22 +16,7 @@ import cpp import codingstandards.cpp.autosar -import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis -import codingstandards.cpp.deadcode.UnreachableCode -import semmle.code.cpp.controlflow.Guards - -/** - * A "conditional" node in the control flow graph, i.e. one that can potentially have a true and false path. - */ -class ConditionalControlFlowNode extends ControlFlowNode { - ConditionalControlFlowNode() { - // A conditional node is one with at least one of a true or false successor - (exists(getATrueSuccessor()) or exists(getAFalseSuccessor())) and - // Ignore conditions affected by macros, as they may include deliberate infeasible paths, or - // paths which are only feasible in certain macro expansions - not isAffectedByMacro() - } -} +import codingstandards.cpp.rules.invariantcondition.InvariantCondition /** * A `Loop` that contains a `break` statement. @@ -40,132 +25,21 @@ class BreakingLoop extends Loop { BreakingLoop() { exists(BreakStmt break | this = break.getBreakable()) } } -/** - * Holds if the `ConditionalNode` has an infeasible `path` according to the control flow graph library. - */ -predicate hasCFGDeducedInfeasiblePath( - ConditionalControlFlowNode cond, boolean infeasiblePath, string explanation -) { - not cond.isFromTemplateInstantiation(_) and - // No true successor, so the true path has already been deduced as infeasible - not exists(cond.getATrueSuccessor()) and - infeasiblePath = true and - explanation = "this expression consists of constants which evaluate to false" - or - // No false successor, so false path has already been deduced as infeasible - not exists(cond.getAFalseSuccessor()) and - not cond.getEnclosingStmt() instanceof BreakingLoop and - infeasiblePath = false and - explanation = "this expression consists of constants which evaluate to true" -} - -predicate isConstantRelationalOperation( - RelationalOperation rel, boolean infeasiblePath, string explanation -) { - /* - * This predicate identifies a number of a cases where we can conclusive determine that a relational - * operation will always return true or false, based on the ranges for each operand as determined - * by the SimpleRangeAnalysis library (and any extensions provide in the Coding Standards library). - * - * Important note: in order to deduce that an relational operation _always_ returns true or false, - * we must ensure that it returns true or false for _all_ possible values of the operands. For - * example, it may be tempting to look at this relational operation on these ranges: - * ``` - * [0..5] < [0..10] - * ``` - * And say that ub(lesser) < ub(greater) and therefore it is `true`, however this is not the case - * for all permutations (e.g. 5 < 0). - * - * Instead, we look at all four permutations of these two dimensions: - * - Equal-to or not equal-to - * - Always true or always false - */ +module AutosarConfig implements InvariantConditionConfigSig { + Query getQuery() { result = DeadCodePackage::infeasiblePathQuery() } - // This restricts the comparison to occur directly within the conditional node - // In theory we could also extend this to identify comparisons where the result is stored, then - // later read in a conditional control flow node within the same function (using SSA) - // Doing so would benefit from path explanations, but would require a more complex analysis - rel instanceof ConditionalControlFlowNode and - // If at least one operand includes an access of a volatile variable, the range analysis library may - // provide inaccurate results, so we ignore this case - not rel.getAnOperand().getAChild*().(VariableAccess).getTarget().isVolatile() and - exists(boolean isEqual | - if - rel instanceof GEExpr - or - rel instanceof LEExpr - then isEqual = true - else isEqual = false - | - // Not equal-to/always true - // If the largest value of the lesser operand is less than the smallest value of the greater - // operand, then the LT/GT comparison is always true - // Example: [0..5] < [6..10] - upperBound(rel.getLesserOperand()) < lowerBound(rel.getGreaterOperand()) and - explanation = - rel.getLesserOperand() + " (max value: " + upperBound(rel.getLesserOperand()) + - ") is always less than " + rel.getGreaterOperand() + " (minimum value: " + - lowerBound(rel.getGreaterOperand()) + ")" and - isEqual = false and - infeasiblePath = false - or - // Equal-to/always true - // If the largest value of the lesser operand is less than or equal to the smallest value of the - // greater operand, then the LTE/GTE comparison is always true - // Example: [0..6] <= [6..10] - upperBound(rel.getLesserOperand()) <= lowerBound(rel.getGreaterOperand()) and - explanation = - rel.getLesserOperand() + " (max value: " + upperBound(rel.getLesserOperand()) + - ") is always less than or equal to " + rel.getGreaterOperand() + " (minimum value: " + - lowerBound(rel.getGreaterOperand()) + ")" and - isEqual = true and - infeasiblePath = false - or - // Equal to/always false - // If the largest value of the greater operand is less than the smallest value of the lesser - // operand, then the LTE/GTE comparison is always false - // Example: [6..10] <= [0..5] - upperBound(rel.getGreaterOperand()) < lowerBound(rel.getLesserOperand()) and - explanation = - rel.getGreaterOperand() + " (max value: " + upperBound(rel.getGreaterOperand()) + - ") is always less than " + rel.getLesserOperand() + " (minimum value: " + - lowerBound(rel.getLesserOperand()) + ")" and - isEqual = true and - infeasiblePath = true + predicate isException(ControlFlowNode node) { + node.getEnclosingElement() instanceof BreakingLoop and + exists(node.getATrueSuccessor()) or - // Equal to/always true - // If the largest value of the greater operand is less than or equal to the smallest value of the - // lesser operand, then the LT/GT comparison is always false - // Example: [6..10] < [0..6] - upperBound(rel.getGreaterOperand()) <= lowerBound(rel.getLesserOperand()) and - explanation = - rel.getGreaterOperand() + " (max value: " + upperBound(rel.getGreaterOperand()) + - ") is always less than or equal to " + rel.getLesserOperand() + " (minimum value: " + - lowerBound(rel.getLesserOperand()) + ")" and - isEqual = false and - infeasiblePath = true - ) -} - -/** - * Holds if the `ConditionalNode` has an infeasible `path` for the reason given in `explanation`. - */ -predicate hasInfeasiblePath(ConditionalControlFlowNode node, string message) { - exists(boolean infeasiblePath, string explanation | - not node.isFromTemplateInstantiation(_) and - if node.isFromUninstantiatedTemplate(_) - then message = "The path is unreachable in a template." - else message = "The " + infeasiblePath + " path is infeasible because " + explanation + "." - | - hasCFGDeducedInfeasiblePath(node, infeasiblePath, explanation) and - not isConstantRelationalOperation(node, infeasiblePath, _) + // Ignore conditions affected by macros, as they may include deliberate infeasible paths, or + // paths which are only feasible in certain macro expansions + node.isAffectedByMacro() or - isConstantRelationalOperation(node, infeasiblePath, explanation) - ) + // Only consider reachability in uninstantiated templates, to avoid false positives + node.isFromTemplateInstantiation(_) + } } -from ConditionalControlFlowNode cond, string explanation -where - not isExcluded(cond, DeadCodePackage::infeasiblePathQuery()) and - hasInfeasiblePath(cond, explanation) -select cond, explanation +// Import the problems query predicate +import InvariantCondition diff --git a/cpp/common/src/codingstandards/cpp/Literals.qll b/cpp/common/src/codingstandards/cpp/Literals.qll index edec04152e..32adc3cc09 100644 --- a/cpp/common/src/codingstandards/cpp/Literals.qll +++ b/cpp/common/src/codingstandards/cpp/Literals.qll @@ -69,6 +69,10 @@ class BoolLiteral extends Literal { or this.getValue() = "0" and this.getValueText() = "false" } + + predicate isTrue() { this.getValue() = "1" } + + predicate isFalse() { this.getValue() = "0" } } /** diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/DeadCode4.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/DeadCode4.qll new file mode 100644 index 0000000000..cadf6260d1 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/DeadCode4.qll @@ -0,0 +1,26 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype DeadCode4Query = TInvariantConditionQuery() + +predicate isDeadCode4QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `invariantCondition` query + DeadCode4Package::invariantConditionQuery() and + queryId = + // `@id` for the `invariantCondition` query + "cpp/misra/invariant-condition" and + ruleId = "RULE-0-0-2" and + category = "advisory" +} + +module DeadCode4Package { + Query invariantConditionQuery() { + //autogenerate `Query` type + result = + // `Query` type for `invariantCondition` query + TQueryCPP(TDeadCode4PackageQuery(TInvariantConditionQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index 0c3cbcc28c..76c0488376 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -16,6 +16,7 @@ import Const import Conversions import Conversions2 import DeadCode +import DeadCode4 import Declarations import ExceptionSafety import Exceptions1 @@ -78,6 +79,7 @@ newtype TCPPQuery = TConversionsPackageQuery(ConversionsQuery q) or TConversions2PackageQuery(Conversions2Query q) or TDeadCodePackageQuery(DeadCodeQuery q) or + TDeadCode4PackageQuery(DeadCode4Query q) or TDeclarationsPackageQuery(DeclarationsQuery q) or TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or TExceptions1PackageQuery(Exceptions1Query q) or @@ -140,6 +142,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isConversionsQueryMetadata(query, queryId, ruleId, category) or isConversions2QueryMetadata(query, queryId, ruleId, category) or isDeadCodeQueryMetadata(query, queryId, ruleId, category) or + isDeadCode4QueryMetadata(query, queryId, ruleId, category) or isDeclarationsQueryMetadata(query, queryId, ruleId, category) or isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or isExceptions1QueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/rules/invariantcondition/InvariantCondition.qll b/cpp/common/src/codingstandards/cpp/rules/invariantcondition/InvariantCondition.qll new file mode 100644 index 0000000000..e2a0d414a2 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/rules/invariantcondition/InvariantCondition.qll @@ -0,0 +1,163 @@ +/** + * Provides a library which includes a `problems` predicate for reporting + * invariant/infeasible code paths. + */ + +import cpp +import codingstandards.cpp.Customizations +import codingstandards.cpp.Exclusions +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis +import codingstandards.cpp.deadcode.UnreachableCode +import semmle.code.cpp.controlflow.Guards + +signature module InvariantConditionConfigSig { + Query getQuery(); + + predicate isException(ControlFlowNode element); +} + +private module Impl { + /** + * A "conditional" node in the control flow graph, i.e. one that can potentially have a true and + * false path. + */ + class ConditionalControlFlowNode extends ControlFlowNode { + ConditionalControlFlowNode() { + // A conditional node is one with at least one of a true or false successor + (exists(getATrueSuccessor()) or exists(getAFalseSuccessor())) + } + } + + /** + * Holds if the `ConditionalNode` has an infeasible `path` according to the control flow graph + * library. + */ + predicate hasCFGDeducedInfeasiblePath( + ConditionalControlFlowNode cond, boolean infeasiblePath, string explanation + ) { + not cond.isFromTemplateInstantiation(_) and + // No true successor, so the true path has already been deduced as infeasible + not exists(cond.getATrueSuccessor()) and + infeasiblePath = true and + explanation = "this expression consists of constants which evaluate to false" + or + // No false successor, so false path has already been deduced as infeasible + not exists(cond.getAFalseSuccessor()) and + infeasiblePath = false and + explanation = "this expression consists of constants which evaluate to true" + } + + predicate isConstantRelationalOperation( + RelationalOperation rel, boolean infeasiblePath, string explanation + ) { + /* + * This predicate identifies a number of cases where we can conclusive determine that a + * relational operation will always return true or false, based on the ranges for each operand + * as determined by the SimpleRangeAnalysis library (and any extensions provided in the Coding + * Standards library). + * + * Important note: in order to deduce that a relational operation _always_ returns true or + * false, we must ensure that it returns true or false for _all_ possible values of the + * operands. For example, it may be tempting to look at this relational operation on these + * ranges: + * ``` + * [0..5] < [0..10] + * ``` + * And say that ub(lesser) < ub(greater) and therefore it is `true`, however this is not the + * case for all permutations (e.g. 5 < 0). + * + * Instead, we look at all four permutations of these two dimensions: + * - Equal-to or not equal-to + * - Always true or always false + */ + + // This restricts the comparison to occur directly within the conditional node + // In theory we could also extend this to identify comparisons where the result is stored, then + // later read in a conditional control flow node within the same function (using SSA) + // Doing so would benefit from path explanations, but would require a more complex analysis + rel instanceof ConditionalControlFlowNode and + // If at least one operand includes an access of a volatile variable, the range analysis library + // may provide inaccurate results, so we ignore this case + not rel.getAnOperand().getAChild*().(VariableAccess).getTarget().isVolatile() and + exists(boolean isEqual | + if + rel instanceof GEExpr + or + rel instanceof LEExpr + then isEqual = true + else isEqual = false + | + // Not equal-to/always true + // If the largest value of the lesser operand is less than the smallest value of the greater + // operand, then the LT/GT comparison is always true + // Example: [0..5] < [6..10] + upperBound(rel.getLesserOperand()) < lowerBound(rel.getGreaterOperand()) and + explanation = + rel.getLesserOperand() + " (max value: " + upperBound(rel.getLesserOperand()) + + ") is always less than " + rel.getGreaterOperand() + " (minimum value: " + + lowerBound(rel.getGreaterOperand()) + ")" and + isEqual = false and + infeasiblePath = false + or + // Equal-to/always true + // If the largest value of the lesser operand is less than or equal to the smallest value of + // the greater operand, then the LTE/GTE comparison is always true + // Example: [0..6] <= [6..10] + upperBound(rel.getLesserOperand()) <= lowerBound(rel.getGreaterOperand()) and + explanation = + rel.getLesserOperand() + " (max value: " + upperBound(rel.getLesserOperand()) + + ") is always less than or equal to " + rel.getGreaterOperand() + " (minimum value: " + + lowerBound(rel.getGreaterOperand()) + ")" and + isEqual = true and + infeasiblePath = false + or + // Equal to/always false + // If the largest value of the greater operand is less than the smallest value of the lesser + // operand, then the LTE/GTE comparison is always false + // Example: [6..10] <= [0..5] + upperBound(rel.getGreaterOperand()) < lowerBound(rel.getLesserOperand()) and + explanation = + rel.getGreaterOperand() + " (max value: " + upperBound(rel.getGreaterOperand()) + + ") is always less than " + rel.getLesserOperand() + " (minimum value: " + + lowerBound(rel.getLesserOperand()) + ")" and + isEqual = true and + infeasiblePath = true + or + // Equal to/always false + // If the largest value of the greater operand is less than or equal to the smallest value of + // the lesser operand, then the LT/GT comparison is always false + // Example: [6..10] < [0..6] + upperBound(rel.getGreaterOperand()) <= lowerBound(rel.getLesserOperand()) and + explanation = + rel.getGreaterOperand() + " (max value: " + upperBound(rel.getGreaterOperand()) + + ") is always less than or equal to " + rel.getLesserOperand() + " (minimum value: " + + lowerBound(rel.getLesserOperand()) + ")" and + isEqual = false and + infeasiblePath = true + ) + } + + /** + * Holds if the `ConditionalNode` has an infeasible `path` for the reason given in `explanation`. + */ + predicate hasInfeasiblePath(ConditionalControlFlowNode node, string message) { + exists(boolean infeasiblePath, string explanation | + if node.isFromUninstantiatedTemplate(_) + then message = "The path is unreachable in a template." + else message = "The " + infeasiblePath + " path is infeasible because " + explanation + "." + | + hasCFGDeducedInfeasiblePath(node, infeasiblePath, explanation) and + not isConstantRelationalOperation(node, infeasiblePath, _) + or + isConstantRelationalOperation(node, infeasiblePath, explanation) + ) + } +} + +module InvariantCondition { + query predicate problems(Impl::ConditionalControlFlowNode cond, string explanation) { + not isExcluded(cond, Config::getQuery()) and + Impl::hasInfeasiblePath(cond, explanation) and + not Config::isException(cond) + } +} diff --git a/cpp/misra/src/rules/RULE-0-0-2/InvariantCondition.ql b/cpp/misra/src/rules/RULE-0-0-2/InvariantCondition.ql new file mode 100644 index 0000000000..554fd55a4d --- /dev/null +++ b/cpp/misra/src/rules/RULE-0-0-2/InvariantCondition.ql @@ -0,0 +1,44 @@ +/** + * @id cpp/misra/invariant-condition + * @name RULE-0-0-2: Controlling expressions should not be invariant + * @description Invariant expressions in controlling statements can indicate logic errors or + * redundant code. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-0-0-2 + * scope/system + * maintainability + * correctness + * external/misra/enforcement/undecidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.Literals +import codingstandards.cpp.rules.invariantcondition.InvariantCondition + +class WhileTrue extends WhileStmt { + WhileTrue() { this.getCondition().(BoolLiteral).isTrue() } +} + +class DoWhileFalse extends DoStmt { + DoWhileFalse() { this.getCondition().(BoolLiteral).isFalse() } +} + +module MisraConfig implements InvariantConditionConfigSig { + Query getQuery() { result = DeadCode4Package::invariantConditionQuery() } + + predicate isException(ControlFlowNode node) { + node.isAffectedByMacro() and + node = any(DoWhileFalse dwf).getCondition() + or + node = any(WhileTrue wt).getCondition() + or + node = any(ConstexprIfStmt cifs).getCondition() + } +} + +// Import the problems query predicate +import InvariantCondition diff --git a/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.expected b/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.expected new file mode 100644 index 0000000000..01e2416cf1 --- /dev/null +++ b/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.expected @@ -0,0 +1,15 @@ +| test.cpp:11:7:11:13 | ... >= ... | The false path is infeasible because 0 (max value: 0) is always less than or equal to a (minimum value: 0). | +| test.cpp:14:7:14:22 | ... <= ... | The false path is infeasible because a (max value: 4294967295) is always less than or equal to 4294967295 (minimum value: 4294967295). | +| test.cpp:21:7:21:13 | ... < ... | The false path is infeasible because l1 (max value: 2) is always less than 10 (minimum value: 10). | +| test.cpp:37:7:37:7 | 0 | The path is unreachable in a template. | +| test.cpp:41:7:41:14 | call to isVal | The false path is infeasible because this expression consists of constants which evaluate to true. | +| test.cpp:62:10:62:14 | 0 | The true path is infeasible because this expression consists of constants which evaluate to false. | +| test.cpp:69:12:69:13 | 10 | The false path is infeasible because this expression consists of constants which evaluate to true. | +| test.cpp:73:14:73:14 | x | The false path is infeasible because this expression consists of constants which evaluate to true. | +| test.cpp:95:12:95:16 | 0 | The true path is infeasible because this expression consists of constants which evaluate to false. | +| test.cpp:99:12:99:15 | 1 | The false path is infeasible because this expression consists of constants which evaluate to true. | +| test.cpp:102:3:102:17 | 1 | The false path is infeasible because this expression consists of constants which evaluate to true. | +| test.cpp:104:3:104:15 | 0 | The true path is infeasible because this expression consists of constants which evaluate to false. | +| test.cpp:107:5:107:17 | 0 | The true path is infeasible because this expression consists of constants which evaluate to false. | +| test.cpp:108:12:108:16 | 0 | The true path is infeasible because this expression consists of constants which evaluate to false. | +| test.cpp:112:7:112:7 | 1 | The false path is infeasible because this expression consists of constants which evaluate to true. | diff --git a/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.qlref b/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.qlref new file mode 100644 index 0000000000..e1273dc1df --- /dev/null +++ b/cpp/misra/test/rules/RULE-0-0-2/InvariantCondition.qlref @@ -0,0 +1 @@ +rules/RULE-0-0-2/InvariantCondition.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-0-0-2/test.cpp b/cpp/misra/test/rules/RULE-0-0-2/test.cpp new file mode 100644 index 0000000000..f7b2ca7dec --- /dev/null +++ b/cpp/misra/test/rules/RULE-0-0-2/test.cpp @@ -0,0 +1,124 @@ +/** + * This is mostly copied from `cpp/autosar/test/rules/M0-1-2/test.cpp`, however + * there are some slight differences: + * - Tests have been added that constexprs are considered compliant + * - Tests related to `while(true)` have been changed to allow `while(true)`. + * - Tests have been added for macro-generated `do {} while(false);` loops. + * - Some examples have been removed to reduce maintenance burden + */ + +void test_infeasible(unsigned int a) { + if (a >= 0U) // NON_COMPLIANT - `a` is unsigned, therefore the comparison is + // always true + ; + if (a <= 0xffffffffU) // NON_COMPLIANT - `a` is unsigned, and cannot be above + // 0xffffffff as 32 bit int + ; + if (a > 0U) // COMPLIANT - `a` is unsigned, but may be equal to zero + ; + + int l1 = 2; + if (l1 < 10) // NON_COMPLIANT - both variables are constants + ; + + if (a < l1) { // COMPLIANT - `a` is not constant + ; + } + + volatile int l3; + if (l3 < 10) { // COMPLIANT - `l3` is not constant + if (l3 < 10) // COMPLIANT - `l3` is volatile, so while `l3 < 10`, the value + // of `l3` could change + ; + } +} + +template int f() { + if (0) { // NON_COMPLIANT - true path is infeasible in all circumstances + return 3; + } + + if (T::isVal()) { // NON_COMPLIANT - not using constexpr if. + return 2; + } + + if constexpr (T::isVal()) { // COMPLIANT + return 2; + } +} + +class A { +public: + static constexpr bool isVal() { return true; } +}; + +void test_f() { f(); } + +void test_while(int a) { + while (a < 0) { // COMPLIANT + a++; + } + + while (false) { // NON_COMPLIANT + ; + } + + while (true) { // COMPLIANT -- by exception + ; + + while (10) { // NON_COMPLIANT + ; + + constexpr bool x = true; + while (x) { // NON_COMPLIANT + ; + } + } + } +} + +#define DO_WHILE_FALSE() \ + do { \ + } while (false) + +#define DO_WHILE_TRUE() \ + do { \ + } while (true) + +#define WHILE_FALSE() \ + while (false) \ + ; + +void test_do() { + do { + ; + } while (false); // NON_COMPLIANT + + do { + ; + } while (true); // NON_COMPLIANT + + DO_WHILE_FALSE(); // COMPLIANT -- by exception + DO_WHILE_TRUE(); // NON_COMPLIANT + + WHILE_FALSE(); // NON_COMPLIANT + + do { + WHILE_FALSE(); // NON_COMPLIANT + } while (false); // NON_COMPLIANT +} + +template int foo() { + if (x) { // NON_COMPLIANT - should use constexpr if + return 1; + } + if constexpr (x) { // COMPLIANT - should use constexpr if + return 1; + } + return 0; // COMPLIANT - block is reachable in the uninstantiated template +} + +void test() { + foo(); + foo(); +} \ No newline at end of file diff --git a/rule_packages/cpp/DeadCode4.json b/rule_packages/cpp/DeadCode4.json new file mode 100644 index 0000000000..507b9beedf --- /dev/null +++ b/rule_packages/cpp/DeadCode4.json @@ -0,0 +1,26 @@ +{ + "MISRA-C++-2023": { + "RULE-0-0-2": { + "properties": { + "enforcement": "undecidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Invariant expressions in controlling statements can indicate logic errors or redundant code.", + "kind": "problem", + "name": "Controlling expressions should not be invariant", + "precision": "very-high", + "severity": "error", + "short_name": "InvariantCondition", + "tags": [ + "scope/system", + "maintainability", + "correctness" + ] + } + ], + "title": "Controlling expressions should not be invariant" + } + } +} \ No newline at end of file diff --git a/rules.csv b/rules.csv index a2eb7eddd8..8d94b0a68a 100644 --- a/rules.csv +++ b/rules.csv @@ -824,7 +824,7 @@ c,MISRA-C-2012,RULE-23-6,Yes,Required,,,The controlling expression of a generic c,MISRA-C-2012,RULE-23-7,Yes,Advisory,,,A generic selection that is expanded from a macro should evaluate its argument only once,,Generics,Medium, c,MISRA-C-2012,RULE-23-8,Yes,Required,,,A default association shall appear as either the first or the last association of a generic selection,,Generics,Easy, cpp,MISRA-C++-2023,RULE-0-0-1,Yes,Required,Decidable,Single Translation Unit,A function shall not contain unreachable statements,M0-1-1,DeadCode2,Medium, -cpp,MISRA-C++-2023,RULE-0-0-2,Yes,Advisory,Undecidable,System,Controlling expressions should not be invariant,M0-1-2,DeadCode2,Easy, +cpp,MISRA-C++-2023,RULE-0-0-2,Yes,Advisory,Undecidable,System,Controlling expressions should not be invariant,M0-1-2,DeadCode4,Easy, cpp,MISRA-C++-2023,RULE-0-1-1,Yes,Advisory,Undecidable,System,A value should not be unnecessarily written to a local object,A0-1-1,DeadCode2,Medium, cpp,MISRA-C++-2023,RULE-0-1-2,Yes,Required,Decidable,Single Translation Unit,The value returned by a function shall be used,A0-1-2,DeadCode2,Easy, cpp,MISRA-C++-2023,RULE-0-2-1,Yes,Advisory,Decidable,Single Translation Unit,Variables with limited visibility should be used at least once,M0-1-3,DeadCode2,Easy,