From c8f559c1af50db1419c068dfbcac3cf587639449 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Tue, 29 Jul 2025 19:33:14 -0400 Subject: [PATCH 1/9] Generate query files for "Statements" package --- .../cpp/exclusions/cpp/RuleMetadata.qll | 3 + .../cpp/exclusions/cpp/Statements.qll | 61 +++++++++++++++ .../AppropriateStructureOfSwitchStatement.ql | 25 ++++++ .../LegacyForStatementsShouldBeSimple.ql | 24 ++++++ ...orRangeInitializerAtMostOneFunctionCall.ql | 25 ++++++ ...opriateStructureOfSwitchStatement.expected | 1 + ...ppropriateStructureOfSwitchStatement.qlref | 1 + ...LegacyForStatementsShouldBeSimple.expected | 1 + .../LegacyForStatementsShouldBeSimple.qlref | 1 + ...eInitializerAtMostOneFunctionCall.expected | 1 + ...angeInitializerAtMostOneFunctionCall.qlref | 1 + rule_packages/cpp/Statements.json | 78 +++++++++++++++++++ 12 files changed, 222 insertions(+) create mode 100644 cpp/common/src/codingstandards/cpp/exclusions/cpp/Statements.qll create mode 100644 cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql create mode 100644 cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql create mode 100644 cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql create mode 100644 cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected create mode 100644 cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.qlref create mode 100644 cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.expected create mode 100644 cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.qlref create mode 100644 cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected create mode 100644 cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.qlref create mode 100644 rule_packages/cpp/Statements.json diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index abd6aeff96..0db51d4224 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -47,6 +47,7 @@ import SideEffects1 import SideEffects2 import SmartPointers1 import SmartPointers2 +import Statements import Strings import Templates import Toolchain @@ -102,6 +103,7 @@ newtype TCPPQuery = TSideEffects2PackageQuery(SideEffects2Query q) or TSmartPointers1PackageQuery(SmartPointers1Query q) or TSmartPointers2PackageQuery(SmartPointers2Query q) or + TStatementsPackageQuery(StatementsQuery q) or TStringsPackageQuery(StringsQuery q) or TTemplatesPackageQuery(TemplatesQuery q) or TToolchainPackageQuery(ToolchainQuery q) or @@ -157,6 +159,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isSideEffects2QueryMetadata(query, queryId, ruleId, category) or isSmartPointers1QueryMetadata(query, queryId, ruleId, category) or isSmartPointers2QueryMetadata(query, queryId, ruleId, category) or + isStatementsQueryMetadata(query, queryId, ruleId, category) or isStringsQueryMetadata(query, queryId, ruleId, category) or isTemplatesQueryMetadata(query, queryId, ruleId, category) or isToolchainQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Statements.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Statements.qll new file mode 100644 index 0000000000..fe202ce31f --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Statements.qll @@ -0,0 +1,61 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype StatementsQuery = + TAppropriateStructureOfSwitchStatementQuery() or + TLegacyForStatementsShouldBeSimpleQuery() or + TForRangeInitializerAtMostOneFunctionCallQuery() + +predicate isStatementsQueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `appropriateStructureOfSwitchStatement` query + StatementsPackage::appropriateStructureOfSwitchStatementQuery() and + queryId = + // `@id` for the `appropriateStructureOfSwitchStatement` query + "cpp/misra/appropriate-structure-of-switch-statement" and + ruleId = "RULE-9-4-2" and + category = "required" + or + query = + // `Query` instance for the `legacyForStatementsShouldBeSimple` query + StatementsPackage::legacyForStatementsShouldBeSimpleQuery() and + queryId = + // `@id` for the `legacyForStatementsShouldBeSimple` query + "cpp/misra/legacy-for-statements-should-be-simple" and + ruleId = "RULE-9-5-1" and + category = "advisory" + or + query = + // `Query` instance for the `forRangeInitializerAtMostOneFunctionCall` query + StatementsPackage::forRangeInitializerAtMostOneFunctionCallQuery() and + queryId = + // `@id` for the `forRangeInitializerAtMostOneFunctionCall` query + "cpp/misra/for-range-initializer-at-most-one-function-call" and + ruleId = "RULE-9-5-2" and + category = "required" +} + +module StatementsPackage { + Query appropriateStructureOfSwitchStatementQuery() { + //autogenerate `Query` type + result = + // `Query` type for `appropriateStructureOfSwitchStatement` query + TQueryCPP(TStatementsPackageQuery(TAppropriateStructureOfSwitchStatementQuery())) + } + + Query legacyForStatementsShouldBeSimpleQuery() { + //autogenerate `Query` type + result = + // `Query` type for `legacyForStatementsShouldBeSimple` query + TQueryCPP(TStatementsPackageQuery(TLegacyForStatementsShouldBeSimpleQuery())) + } + + Query forRangeInitializerAtMostOneFunctionCallQuery() { + //autogenerate `Query` type + result = + // `Query` type for `forRangeInitializerAtMostOneFunctionCall` query + TQueryCPP(TStatementsPackageQuery(TForRangeInitializerAtMostOneFunctionCallQuery())) + } +} diff --git a/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql new file mode 100644 index 0000000000..8bfa68f5f2 --- /dev/null +++ b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql @@ -0,0 +1,25 @@ +/** + * @id cpp/misra/appropriate-structure-of-switch-statement + * @name RULE-9-4-2: The structure of a switch statement shall be appropriate + * @description A switch statement should have an appropriate structure with proper cases, default + * labels, and break statements to ensure clear control flow and prevent unintended + * fall-through behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-9-4-2 + * correctness + * maintainability + * readability + * external/misra/allocated-target/single-translation-unit + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from +where + not isExcluded(x, StatementsPackage::appropriateStructureOfSwitchStatementQuery()) and +select diff --git a/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql b/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql new file mode 100644 index 0000000000..1a29e90a40 --- /dev/null +++ b/cpp/misra/src/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql @@ -0,0 +1,24 @@ +/** + * @id cpp/misra/legacy-for-statements-should-be-simple + * @name RULE-9-5-1: Legacy for statements should be simple + * @description Legacy for statements with complex initialization, condition, and increment + * expressions can be difficult to understand and maintain. Simple for loops are more + * readable. + * @kind problem + * @precision high + * @problem.severity recommendation + * @tags external/misra/id/rule-9-5-1 + * maintainability + * readability + * external/misra/allocated-target/single-translation-unit + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra + +from +where + not isExcluded(x, StatementsPackage::legacyForStatementsShouldBeSimpleQuery()) and +select diff --git a/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql b/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql new file mode 100644 index 0000000000..47d27287bc --- /dev/null +++ b/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql @@ -0,0 +1,25 @@ +/** + * @id cpp/misra/for-range-initializer-at-most-one-function-call + * @name RULE-9-5-2: A for-range-initializer shall contain at most one function call + * @description Multiple function calls in a for-range-initializer can lead to unclear iteration + * behavior and potential side effects that make the code harder to understand and + * debug. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-9-5-2 + * correctness + * maintainability + * readability + * external/misra/allocated-target/single-translation-unit + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from +where + not isExcluded(x, StatementsPackage::forRangeInitializerAtMostOneFunctionCallQuery()) and +select diff --git a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected new file mode 100644 index 0000000000..2ec1a0ac6c --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected @@ -0,0 +1 @@ +No expected results have yet been specified \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.qlref b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.qlref new file mode 100644 index 0000000000..9f475afbe1 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.qlref @@ -0,0 +1 @@ +rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.expected b/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.expected new file mode 100644 index 0000000000..2ec1a0ac6c --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.expected @@ -0,0 +1 @@ +No expected results have yet been specified \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.qlref b/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.qlref new file mode 100644 index 0000000000..bf443d6d68 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.qlref @@ -0,0 +1 @@ +rules/RULE-9-5-1/LegacyForStatementsShouldBeSimple.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected new file mode 100644 index 0000000000..2ec1a0ac6c --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected @@ -0,0 +1 @@ +No expected results have yet been specified \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.qlref b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.qlref new file mode 100644 index 0000000000..be7fc0ef10 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.qlref @@ -0,0 +1 @@ +rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql \ No newline at end of file diff --git a/rule_packages/cpp/Statements.json b/rule_packages/cpp/Statements.json new file mode 100644 index 0000000000..91a643253d --- /dev/null +++ b/rule_packages/cpp/Statements.json @@ -0,0 +1,78 @@ +{ + "MISRA-C++-2023": { + "RULE-9-4-2": { + "properties": { + "allocated-target": [ + "Single Translation Unit" + ], + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "A switch statement should have an appropriate structure with proper cases, default labels, and break statements to ensure clear control flow and prevent unintended fall-through behavior.", + "kind": "problem", + "name": "The structure of a switch statement shall be appropriate", + "precision": "very-high", + "severity": "error", + "short_name": "AppropriateStructureOfSwitchStatement", + "tags": [ + "correctness", + "maintainability", + "readability" + ] + } + ], + "title": "The structure of a switch statement shall be appropriate" + }, + "RULE-9-5-1": { + "properties": { + "allocated-target": [ + "Single Translation Unit" + ], + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Legacy for statements with complex initialization, condition, and increment expressions can be difficult to understand and maintain. Simple for loops are more readable.", + "kind": "problem", + "name": "Legacy for statements should be simple", + "precision": "high", + "severity": "recommendation", + "short_name": "LegacyForStatementsShouldBeSimple", + "tags": [ + "maintainability", + "readability" + ] + } + ], + "title": "Legacy for statements should be simple" + }, + "RULE-9-5-2": { + "properties": { + "allocated-target": [ + "Single Translation Unit" + ], + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Multiple function calls in a for-range-initializer can lead to unclear iteration behavior and potential side effects that make the code harder to understand and debug.", + "kind": "problem", + "name": "A for-range-initializer shall contain at most one function call", + "precision": "very-high", + "severity": "error", + "short_name": "ForRangeInitializerAtMostOneFunctionCall", + "tags": [ + "correctness", + "maintainability", + "readability" + ] + } + ], + "title": "A for-range-initializer shall contain at most one function call" + } + } +} From ffcb4323eb4ad9d95f1ed6332a06457afb92ed54 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 30 Jul 2025 17:13:22 -0400 Subject: [PATCH 2/9] Add agent-generated first draft --- .../AppropriateStructureOfSwitchStatement.ql | 52 ++- ...opriateStructureOfSwitchStatement.expected | 16 +- cpp/misra/test/rules/RULE-9-4-2/test.cpp | 304 ++++++++++++++++++ 3 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 cpp/misra/test/rules/RULE-9-4-2/test.cpp diff --git a/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql index 8bfa68f5f2..3d68e31abb 100644 --- a/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql +++ b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql @@ -18,8 +18,54 @@ import cpp import codingstandards.cpp.misra +import codingstandards.cpp.SwitchStatement -from +from SwitchStmt switch, string message where - not isExcluded(x, StatementsPackage::appropriateStructureOfSwitchStatementQuery()) and -select + not isExcluded(switch, StatementsPackage::appropriateStructureOfSwitchStatementQuery()) and + ( + // RULE-16-1: Switch not well-formed (has inappropriate statements) + exists(SwitchCase case | + case = switch.getASwitchCase() and + switchCaseNotWellFormed(case) and + message = "has a case that contains inappropriate statements (only expression, compound, selection, iteration or try statements are allowed)" + ) + or + // RULE-16-2: Nested switch labels + exists(SwitchCase case | + case = switch.getASwitchCase() and + case instanceof NestedSwitchCase and + message = "contains a switch label that is not directly within the switch body" + ) + or + // RULE-16-3: Non-empty case doesn't terminate with break + exists(SwitchCase case | + case = switch.getASwitchCase() and + case instanceof CaseDoesNotTerminate and + message = "has a non-empty case that does not terminate with an unconditional break or throw statement" + ) + or + // RULE-16-4: Missing default clause + not switch.hasDefaultCase() and + message = "is missing a default clause" + or + // RULE-16-5: Default clause not first or last + exists(SwitchCase defaultCase | + switch.getDefaultCase() = defaultCase and + exists(defaultCase.getPreviousSwitchCase()) and + finalClauseInSwitchNotDefault(switch) and + message = "has a default clause that is not the first or last switch label" + ) + or + // RULE-16-6: Less than two case clauses + count(SwitchCase case | + switch.getASwitchCase() = case and + case.getNextSwitchCase() != case.getFollowingStmt() + ) + 1 < 2 and + message = "has fewer than two switch-clauses" + or + // RULE-16-7: Boolean switch expression + switch instanceof BooleanSwitchStmt and + message = "has a controlling expression of essentially Boolean type" + ) +select switch, "Switch statement " + message + "." diff --git a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected index 2ec1a0ac6c..8900facc63 100644 --- a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected +++ b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected @@ -1 +1,15 @@ -No expected results have yet been specified \ No newline at end of file +| test.cpp:40:3:40:8 | Switch statement has a case that contains inappropriate statements (only expression, compound, selection, iteration or try statements are allowed). | +| test.cpp:86:3:86:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | +| test.cpp:86:3:86:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | +| test.cpp:98:3:98:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | +| test.cpp:123:3:123:8 | Switch statement is missing a default clause. | +| test.cpp:166:3:166:8 | Switch statement has a default clause that is not the first or last switch label. | +| test.cpp:203:3:203:8 | Switch statement has fewer than two switch-clauses. | +| test.cpp:210:3:210:8 | Switch statement has fewer than two switch-clauses. | +| test.cpp:235:3:235:8 | Switch statement has a controlling expression of essentially Boolean type. | +| test.cpp:245:3:245:8 | Switch statement has a controlling expression of essentially Boolean type. | +| test.cpp:266:3:266:8 | Switch statement has a controlling expression of essentially Boolean type. | +| test.cpp:266:3:266:8 | Switch statement is missing a default clause. | +| test.cpp:266:3:266:8 | Switch statement has fewer than two switch-clauses. | +| test.cpp:275:3:275:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | +| test.cpp:275:3:275:8 | Switch statement has a default clause that is not the first or last switch label. | \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-4-2/test.cpp b/cpp/misra/test/rules/RULE-9-4-2/test.cpp new file mode 100644 index 0000000000..fc948480b2 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-4-2/test.cpp @@ -0,0 +1,304 @@ +// Test cases for RULE-9-4-2: The structure of a switch statement shall be +// appropriate This rule combines RULE-16-1 through RULE-16-7 + +void test_rule_16_1_well_formed(int expr) { + int i = 0; + + // COMPLIANT - well-formed switch with proper statements + switch (expr) { + case 1: + i++; // expression statement + break; + case 2: { // compound statement + i++; + } break; + case 3: + if (i > 0) { // selection statement + i++; + } + break; + case 4: + while (i < 10) { // iteration statement + i++; + } + break; + default: + break; + } + + // NON_COMPLIANT - switch with inappropriate statement (declaration) + switch (expr) { + case 1: + int j = 5; // declaration statement - not allowed + break; + default: + break; + } +} + +void test_rule_16_2_nested_labels(int expr) { + // COMPLIANT - labels directly within switch body + switch (expr) { + case 1: + break; + case 2: + break; + default: + break; + } + + // NON_COMPLIANT - nested switch labels (this would be a compiler error in + // practice) switch (expr) { case 1: + // { + // case 2: // nested label - not allowed + // break; + // } + // break; + // default: + // break; + // } +} + +void test_rule_16_3_termination(int expr) { + int i = 0; + + // COMPLIANT - all cases properly terminated + switch (expr) { + case 1: + i++; + break; + case 2: + case 3: // empty cases are fine + i++; + break; + case 4: + throw "error"; // throw is also valid termination + default: + break; + } + + // NON_COMPLIANT - case 1 falls through without break + switch (expr) { + case 1: // NON_COMPLIANT - missing break + i++; + case 2: // COMPLIANT - properly terminated + i++; + break; + default: + break; + } + + // NON_COMPLIANT - default case falls through + switch (expr) { + case 1: + i++; + break; + default: // NON_COMPLIANT - missing break + i++; + } +} + +void test_rule_16_4_default_label(int expr) { + int i = 0; + + // COMPLIANT - has default label + switch (expr) { + case 1: + i++; + break; + case 2: + i++; + break; + default: + break; + } + + // NON_COMPLIANT - missing default label + switch (expr) { + case 1: + i++; + break; + case 2: + i++; + break; + } +} + +void test_rule_16_5_default_position(int expr) { + int i = 0; + + // COMPLIANT - default is first + switch (expr) { + default: + i++; + break; + case 1: + i++; + break; + case 2: + i++; + break; + } + + // COMPLIANT - default is last + switch (expr) { + case 1: + i++; + break; + case 2: + i++; + break; + default: + i++; + break; + } + + // NON_COMPLIANT - default is in the middle + switch (expr) { + case 1: + i++; + break; + default: // NON_COMPLIANT - not first or last + i++; + break; + case 2: + i++; + break; + } +} + +void test_rule_16_6_two_clauses(int expr) { + int i = 0; + + // COMPLIANT - has multiple clauses + switch (expr) { + case 1: + i++; + break; + case 2: + i++; + break; + default: + break; + } + + // NON_COMPLIANT - only has default (single clause) + switch (expr) { + default: + i++; + break; + } + + // NON_COMPLIANT - only has one case plus default (still only one effective + // clause) + switch (expr) { + case 1: + default: + i++; + break; + } +} + +void test_rule_16_7_boolean_expression() { + int i = 0; + bool flag = true; + + // COMPLIANT - non-boolean expression + switch (i) { + case 0: + break; + case 1: + break; + default: + break; + } + + // NON_COMPLIANT - boolean expression + switch (flag) { + case true: + break; + case false: + break; + default: + break; + } + + // NON_COMPLIANT - boolean comparison expression + switch (i == 0) { + case true: + break; + case false: + break; + default: + break; + } +} + +int f() { return 1; } + +void test_complex_violations(int expr) { + int i = 0; + bool flag = true; + + // NON_COMPLIANT - multiple violations: + // - Boolean expression (16-7) + // - Missing default (16-4) + // - Single clause (16-6) + switch (flag) { + case true: + i++; + } + + // NON_COMPLIANT - multiple violations: + // - Fall-through case (16-3) + // - Default not first/last (16-5) + switch (expr) { + case 1: // NON_COMPLIANT - falls through + i++; + default: // NON_COMPLIANT - not first/last + i++; + break; + case 2: + i++; + break; + } + + switch (expr) { + int i = 0; + case 1: + i++; + } + + switch (int x = f(); x) { + case 1: + i++; + } + + switch (expr = f(); expr) { + case 1: + i++; + } + + switch (expr) { + case 1: + { + case 2: + i++; + } + } + + switch (expr) { + case 1: { + i++; + goto someLabel; + someLabel: + i++; + } + } + + switch (expr) { + int x = 1; + case 1: + i++; + } +} \ No newline at end of file From 221b9b2c4b1be5074aacb763549e8fa1c78bfa52 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Wed, 30 Jul 2025 18:41:25 -0400 Subject: [PATCH 3/9] Add some test cases --- cpp/misra/test/rules/RULE-9-4-2/test.cpp | 310 ++++++++++------------- 1 file changed, 128 insertions(+), 182 deletions(-) diff --git a/cpp/misra/test/rules/RULE-9-4-2/test.cpp b/cpp/misra/test/rules/RULE-9-4-2/test.cpp index fc948480b2..e99a2fb1f4 100644 --- a/cpp/misra/test/rules/RULE-9-4-2/test.cpp +++ b/cpp/misra/test/rules/RULE-9-4-2/test.cpp @@ -1,304 +1,250 @@ -// Test cases for RULE-9-4-2: The structure of a switch statement shall be -// appropriate This rule combines RULE-16-1 through RULE-16-7 +int i = 0; -void test_rule_16_1_well_formed(int expr) { - int i = 0; - - // COMPLIANT - well-formed switch with proper statements - switch (expr) { +/** + * Test the initializer of a switch statement. + */ +void testInitializer(int expr) { + switch (expr) { // COMPLIANT: No initializer case 1: - i++; // expression statement - break; - case 2: { // compound statement i++; - } break; - case 3: - if (i > 0) { // selection statement - i++; - } - break; - case 4: - while (i < 10) { // iteration statement - i++; - } break; default: + i--; break; } - // NON_COMPLIANT - switch with inappropriate statement (declaration) - switch (expr) { + switch (int j = 0; + expr) { // COMPLIANT: Only declaration statement can be an initializer case 1: - int j = 5; // declaration statement - not allowed + j++; break; default: + j--; break; } -} -void test_rule_16_2_nested_labels(int expr) { - // COMPLIANT - labels directly within switch body - switch (expr) { + switch ( + i = 1; + expr) { // NON_COMPLIANT: Only declaration statement can be an initializer case 1: - break; - case 2: + i++; break; default: + i--; break; } - - // NON_COMPLIANT - nested switch labels (this would be a compiler error in - // practice) switch (expr) { case 1: - // { - // case 2: // nested label - not allowed - // break; - // } - // break; - // default: - // break; - // } } -void test_rule_16_3_termination(int expr) { - int i = 0; - - // COMPLIANT - all cases properly terminated - switch (expr) { +void testNestedCaseLabels(int expr) { + switch (expr) { // COMPLIANT: Consecutive case labels are allowed case 1: - i++; - break; case 2: - case 3: // empty cases are fine i++; break; - case 4: - throw "error"; // throw is also valid termination default: + i--; break; } - // NON_COMPLIANT - case 1 falls through without break - switch (expr) { - case 1: // NON_COMPLIANT - missing break - i++; - case 2: // COMPLIANT - properly terminated + switch (expr) { // NON_COMPLIANT: Statements with case labels should all be at + // the same level + case 1: { + case 2: i++; break; + } default: break; } - // NON_COMPLIANT - default case falls through - switch (expr) { + switch (expr) { // NON_COMPLIANT: Statements with case labels should all be at + // the same level case 1: i++; break; - default: // NON_COMPLIANT - missing break - i++; + case 2: { + default: + break; + } } } -void test_rule_16_4_default_label(int expr) { - int i = 0; - - // COMPLIANT - has default label - switch (expr) { - case 1: +void testOtherLabelsInBranch(int expr) { + switch (expr) { // NON_COMPLIANT: Non-case labels appearing in a switch branch + case 1: { i++; - break; - case 2: + goto someLabel; + someLabel: i++; break; default: break; } + } +} + +void testLeadingNonCaseStatement(int expr) { + switch (expr) { // NON_COMPLIANT: Non-case statement is the first statement in + // the switch body - // NON_COMPLIANT - missing default label - switch (expr) { case 1: i++; break; - case 2: - i++; + default: break; } } -void test_rule_16_5_default_position(int expr) { - int i = 0; +[[noreturn]] void f() {} +void g() {} - // COMPLIANT - default is first - switch (expr) { - default: - i++; - break; +void testSwitchBranchTerminator(int expr) { + switch (expr) { // COMPLIANT: Break is allowed as a branch terminator case 1: i++; break; - case 2: - i++; + default: break; } - // COMPLIANT - default is last - switch (expr) { + for (int j = 0; j++; j < 10) { + switch (expr) { // COMPLIANT: Continue is allowed as a branch terminator + case 1: + i++; + continue; + default: + continue; + } + } + + switch (expr) { // COMPLIANT: Goto is allowed as a branch terminator case 1: i++; - break; - case 2: - i++; - break; + goto error; default: - i++; - break; + goto error; } - // NON_COMPLIANT - default is in the middle - switch (expr) { + switch (expr) { // COMPLIANT: Throw is allowed as a branch terminator case 1: i++; - break; - default: // NON_COMPLIANT - not first or last - i++; - break; - case 2: - i++; - break; + throw; + default: + throw; } -} - -void test_rule_16_6_two_clauses(int expr) { - int i = 0; - // COMPLIANT - has multiple clauses - switch (expr) { + switch (expr) { // COMPLIANT: Call to a `[[noreturn]]` function is allowed as + // a branch terminator case 1: i++; - break; - case 2: - i++; - break; + f(); default: - break; + f(); } - // NON_COMPLIANT - only has default (single clause) - switch (expr) { + switch (expr) { // NON_COMPLIANT: Branch ends with a call to a function that + // is not `[[noreturn]]` + case 1: + i++; + g(); default: + g(); + } + + switch (expr) { // COMPLIANT: Return is allowed as a branch terminator + case 1: i++; - break; + return; + default: + return; } - // NON_COMPLIANT - only has one case plus default (still only one effective - // clause) - switch (expr) { + switch (expr) { // COMPLIANT: Empty statement with `[[fallthrough]]` is + // allowed as a branch terminator case 1: + i++; + [[fallthrough]]; default: i++; - break; } -} -void test_rule_16_7_boolean_expression() { - int i = 0; - bool flag = true; +error: + return; +} - // COMPLIANT - non-boolean expression - switch (i) { - case 0: - break; +void testSwitchBranchCount(int expr) { + switch (expr) { // COMPLIANT: Branch count is 2 case 1: + i++; break; default: + i++; break; } - // NON_COMPLIANT - boolean expression - switch (flag) { - case true: - break; - case false: - break; + switch (expr) { // NON_COMPLIANT: Branch count is 1 default: + i++; break; } - // NON_COMPLIANT - boolean comparison expression - switch (i == 0) { - case true: - break; - case false: - break; + switch (expr) { // NON_COMPLIANT: Branch count is 1 + case 1: + case 2: default: + i++; break; } } -int f() { return 1; } - -void test_complex_violations(int expr) { - int i = 0; - bool flag = true; - - // NON_COMPLIANT - multiple violations: - // - Boolean expression (16-7) - // - Missing default (16-4) - // - Single clause (16-6) - switch (flag) { - case true: - i++; - } +enum E { V1, V2, V3 }; - // NON_COMPLIANT - multiple violations: - // - Fall-through case (16-3) - // - Default not first/last (16-5) - switch (expr) { - case 1: // NON_COMPLIANT - falls through - i++; - default: // NON_COMPLIANT - not first/last +void testDefaultLabelPresence(int expr) { + switch (expr) { // COMPLIANT: There is a default branch + case 1: i++; break; - case 2: + default: i++; break; } - switch (expr) { - int i = 0; - case 1: - i++; - } - - switch (int x = f(); x) { + switch (expr) { // NON_COMPLIANT: Default branch is missing case 1: i++; + break; } - switch (expr = f(); expr) { - case 1: - i++; - } + E e; - switch (expr) { - case 1: - { - case 2: - i++; - } + switch (e) { // COMPLIANT: There is a default branch + case V1: + i++; + break; + default: + break; } - switch (expr) { - case 1: { + switch (e) { // NON_COMPLIANT: Default branch is missing on a non-exhaustive + // enum switch + case V1: i++; - goto someLabel; - someLabel: + break; + case V2: i++; - } + break; } - switch (expr) { - int x = 1; - case 1: + switch (e) { // COMPLIANT: Default branch can be omitted on an exhaustive enum + // switch + case V1: + i++; + break; + case V2: i++; + break; + case V3: + i++; + break; } } \ No newline at end of file From fabb7e5db8af43e4b245dee1fb61dc971674b1b2 Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Thu, 31 Jul 2025 18:39:06 -0400 Subject: [PATCH 4/9] Finish first draft --- .../AppropriateStructureOfSwitchStatement.ql | 109 +++++++++++------- ...opriateStructureOfSwitchStatement.expected | 27 ++--- cpp/misra/test/rules/RULE-9-4-2/test.cpp | 9 +- 3 files changed, 83 insertions(+), 62 deletions(-) diff --git a/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql index 3d68e31abb..f419834325 100644 --- a/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql +++ b/cpp/misra/src/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.ql @@ -19,53 +19,74 @@ import cpp import codingstandards.cpp.misra import codingstandards.cpp.SwitchStatement +import codingstandards.cpp.Noreturn from SwitchStmt switch, string message where not isExcluded(switch, StatementsPackage::appropriateStructureOfSwitchStatementQuery()) and - ( - // RULE-16-1: Switch not well-formed (has inappropriate statements) - exists(SwitchCase case | - case = switch.getASwitchCase() and - switchCaseNotWellFormed(case) and - message = "has a case that contains inappropriate statements (only expression, compound, selection, iteration or try statements are allowed)" + /* 1. There is a statement that appears as an initializer and is not a declaration statement. */ + exists(Stmt initializer | initializer = switch.getInitialization() | + not initializer instanceof DeclStmt + ) and + message = "contains a statement that that is not a simple declaration" + or + /* 2. There is a switch case label that does not lead a branch (i.e. a switch case label is nested). */ + exists(SwitchCase case | case = switch.getASwitchCase() | case instanceof NestedSwitchCase) and + message = "contains a switch label that is not directly within the switch body" + or + /* 3. There is a non-case label in a label group. */ + exists(SwitchCase case | case = switch.getASwitchCase() | + case.getAStmt().getChildStmt*() instanceof LabelStmt + ) and + message = "contains a statement label that is not a case label" + or + /* 4. There is a statement before the first case label. */ + exists(Stmt switchBody | switchBody = switch.getStmt() | + not switchBody.getChild(0) instanceof SwitchCase + ) and + message = "has a statement that is not a case label as its first element" + or + /* 5. There is a switch case whose terminator is not one of the allowed kinds. */ + exists(SwitchCase case, Stmt lastStmt | + case = switch.getASwitchCase() and lastStmt = case.getLastStmt() + | + not ( + lastStmt instanceof BreakStmt or + lastStmt instanceof ReturnStmt or + lastStmt instanceof GotoStmt or + lastStmt instanceof ContinueStmt or + lastStmt.(ExprStmt).getExpr() instanceof ThrowExpr or + lastStmt.(ExprStmt).getExpr().(Call).getTarget() instanceof NoreturnFunction or + lastStmt.getAnAttribute().getName().matches("%fallthrough") // We'd like to consider compiler variants such as `clang::fallthrough`. ) - or - // RULE-16-2: Nested switch labels - exists(SwitchCase case | - case = switch.getASwitchCase() and - case instanceof NestedSwitchCase and - message = "contains a switch label that is not directly within the switch body" - ) - or - // RULE-16-3: Non-empty case doesn't terminate with break - exists(SwitchCase case | - case = switch.getASwitchCase() and - case instanceof CaseDoesNotTerminate and - message = "has a non-empty case that does not terminate with an unconditional break or throw statement" - ) - or - // RULE-16-4: Missing default clause - not switch.hasDefaultCase() and - message = "is missing a default clause" - or - // RULE-16-5: Default clause not first or last - exists(SwitchCase defaultCase | - switch.getDefaultCase() = defaultCase and - exists(defaultCase.getPreviousSwitchCase()) and - finalClauseInSwitchNotDefault(switch) and - message = "has a default clause that is not the first or last switch label" - ) - or - // RULE-16-6: Less than two case clauses - count(SwitchCase case | - switch.getASwitchCase() = case and - case.getNextSwitchCase() != case.getFollowingStmt() - ) + 1 < 2 and - message = "has fewer than two switch-clauses" - or - // RULE-16-7: Boolean switch expression - switch instanceof BooleanSwitchStmt and - message = "has a controlling expression of essentially Boolean type" - ) + ) and + message = "is missing a terminator that moves the control out of its body" + or + /* 6. The switch statement does not have more than two unique branches. */ + count(SwitchCase case | + case = switch.getASwitchCase() and + /* + * If the next switch case is the following statement of this switch case, then the two + * switch cases are consecutive and should be considered as constituting one branch + * together. + */ + + not case.getNextSwitchCase() = case.getFollowingStmt() + | + case + ) < 2 and + message = "contains less than two branches" + or + /* 7-1. The switch statement is not an enum switch statement and is missing a default case. */ + not switch instanceof EnumSwitch and + not switch.hasDefaultCase() and + message = "lacks a default case" + or + /* + * 7-2. The switch statement is an enum switch statement and is missing a branch for a + * variant. + */ + + exists(switch.(EnumSwitch).getAMissingCase()) and + message = "lacks a case for one of its variants" select switch, "Switch statement " + message + "." diff --git a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected index 8900facc63..ab8320ef8f 100644 --- a/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected +++ b/cpp/misra/test/rules/RULE-9-4-2/AppropriateStructureOfSwitchStatement.expected @@ -1,15 +1,12 @@ -| test.cpp:40:3:40:8 | Switch statement has a case that contains inappropriate statements (only expression, compound, selection, iteration or try statements are allowed). | -| test.cpp:86:3:86:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | -| test.cpp:86:3:86:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | -| test.cpp:98:3:98:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | -| test.cpp:123:3:123:8 | Switch statement is missing a default clause. | -| test.cpp:166:3:166:8 | Switch statement has a default clause that is not the first or last switch label. | -| test.cpp:203:3:203:8 | Switch statement has fewer than two switch-clauses. | -| test.cpp:210:3:210:8 | Switch statement has fewer than two switch-clauses. | -| test.cpp:235:3:235:8 | Switch statement has a controlling expression of essentially Boolean type. | -| test.cpp:245:3:245:8 | Switch statement has a controlling expression of essentially Boolean type. | -| test.cpp:266:3:266:8 | Switch statement has a controlling expression of essentially Boolean type. | -| test.cpp:266:3:266:8 | Switch statement is missing a default clause. | -| test.cpp:266:3:266:8 | Switch statement has fewer than two switch-clauses. | -| test.cpp:275:3:275:8 | Switch statement has a non-empty case that does not terminate with an unconditional break or throw statement. | -| test.cpp:275:3:275:8 | Switch statement has a default clause that is not the first or last switch label. | \ No newline at end of file +| test.cpp:28:3:37:3 | switch (...) ... | Switch statement contains a statement that that is not a simple declaration. | +| test.cpp:51:3:60:3 | switch (...) ... | Switch statement contains a switch label that is not directly within the switch body. | +| test.cpp:62:3:71:3 | switch (...) ... | Switch statement contains a switch label that is not directly within the switch body. | +| test.cpp:75:3:85:3 | switch (...) ... | Switch statement contains a statement label that is not a case label. | +| test.cpp:89:3:97:3 | switch (...) ... | Switch statement has a statement that is not a case label as its first element. | +| test.cpp:147:3:154:3 | switch (...) ... | Switch statement is missing a terminator that moves the control out of its body. | +| test.cpp:188:3:192:3 | switch (...) ... | Switch statement contains less than two branches. | +| test.cpp:194:3:200:3 | switch (...) ... | Switch statement contains less than two branches. | +| test.cpp:215:3:219:3 | switch (...) ... | Switch statement contains less than two branches. | +| test.cpp:215:3:219:3 | switch (...) ... | Switch statement lacks a default case. | +| test.cpp:223:3:229:3 | switch (...) ... | Switch statement lacks a case for one of its variants. | +| test.cpp:231:3:239:3 | switch (...) ... | Switch statement lacks a case for one of its variants. | diff --git a/cpp/misra/test/rules/RULE-9-4-2/test.cpp b/cpp/misra/test/rules/RULE-9-4-2/test.cpp index e99a2fb1f4..6bbb97e81b 100644 --- a/cpp/misra/test/rules/RULE-9-4-2/test.cpp +++ b/cpp/misra/test/rules/RULE-9-4-2/test.cpp @@ -1,3 +1,5 @@ +#include + int i = 0; /** @@ -77,16 +79,16 @@ void testOtherLabelsInBranch(int expr) { someLabel: i++; break; + } default: break; } - } } void testLeadingNonCaseStatement(int expr) { switch (expr) { // NON_COMPLIANT: Non-case statement is the first statement in // the switch body - + int x = 1; case 1: i++; break; @@ -95,7 +97,7 @@ void testLeadingNonCaseStatement(int expr) { } } -[[noreturn]] void f() {} +[[noreturn]] void f() { exit(0); } void g() {} void testSwitchBranchTerminator(int expr) { @@ -166,6 +168,7 @@ void testSwitchBranchTerminator(int expr) { [[fallthrough]]; default: i++; + break; } error: From 6909528361de035834ce6cdd60e7fa30311560cc Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Aug 2025 14:56:20 -0400 Subject: [PATCH 5/9] Add test case for Rule 9.5.2 --- cpp/misra/test/rules/RULE-9-5-2/test.cpp | 129 +++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 cpp/misra/test/rules/RULE-9-5-2/test.cpp diff --git a/cpp/misra/test/rules/RULE-9-5-2/test.cpp b/cpp/misra/test/rules/RULE-9-5-2/test.cpp new file mode 100644 index 0000000000..9d5255f0c0 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-2/test.cpp @@ -0,0 +1,129 @@ +#include + +/* Helper functions */ +std::vector getData() { return {1, 2, 3}; } +std::vector processData(const std::vector &input) { return input; } +std::vector getContainer() { return {4, 5, 6}; } + +class MyContainer { +public: + MyContainer() = default; + MyContainer(std::vector data) : data_(data) {} + std::vector::iterator begin() { return data_.begin(); } + std::vector::iterator end() { return data_.end(); } + +private: + std::vector data_{7, 8, 9}; +}; + +class ConvertibleToVector { +public: + operator std::vector() const { return {}; } + std::array::iterator begin() { return data_.begin(); } + std::array::iterator end() { return data_.end(); } + +private: + std::array data_{7, 8, 9}; +}; + +std::vector operator+(const std::vector &a, + const std::vector &b) { + std::vector result = a; + result.insert(result.end(), b.begin(), b.end()); + return result; +} + +std::vector convertToIntVector(std::vector vector) { return vector; } + +int main() { + std::vector localVec = {1, 2, 3}; + std::vector *vecPtr = &localVec; + ConvertibleToVector convertible; + + /* ========== 1. EXPLICIT FUNCTION CALLS ========== */ + + for (auto x : getContainer()) { // COMPLIANT: 1 function call only + } + + for (auto x : processData(getData())) { // NON_COMPLIANT: 2 function calls + } + + /* ========== 2. OBJECT CREATION (CONSTRUCTOR CALLS) ========== */ + + for (auto x : + std::vector{1, 2, 3}) { // COMPLIANT: 1 constructor call only + } + + for (auto x : MyContainer()) { // COMPLIANT: 1 constructor call only + } + + for (auto x : std::string("hello")) { // COMPLIANT: 1 constructor call only + } + + for (auto x : std::vector( + getData())) { // NON-COMPLIANT: 1 constructor + 1 function call + } + + for (auto x : MyContainer(processData( + localVec))) { // NON-COMPLIANT: 1 constructor + 1 function call + } + + auto data = std::vector(getData()); + for (auto x : data) { // NON-COMPLIANT: 1 constructor + 1 function call + } + + MyContainer myContainer = MyContainer(processData(localVec)); + for (auto x : myContainer) { // NON-COMPLIANT: 1 constructor + 1 function call + } + + /* ========== 3. OPERATOR OVERLOADING ========== */ + + std::vector anotherVec = {4, 5, 6}; + for (auto x : localVec + anotherVec) { // COMPLIANT: 1 operator+ call only + } + + std::vector vec1 = {1}, vec2 = {2}, vec3 = {3}; + for (auto x : (vec1 + vec2) + vec3) { // NON-COMPLIANT: 2 operator+ calls + } + + for (auto x : + getData() + + processData( + localVec)) { // NON-COMPLIANT: 2 function calls + 1 operator call + } + + std::vector vec1 = {1}, vec2 = {2}, vec3 = {3}; + std::vector appendedVector = (vec1 + vec2) + vec3; + for (auto x : appendedVector) { // COMPLIANT: 0 calls + } + + std::vector appendedVector2 = getData() + processData(localVec); + for (auto x : appendedVector2) { // COMPLIANT: 0 calls + } + + /* ========== 4. IMPLICIT CONVERSIONS ========== */ + + ConvertibleToVector convertible; + for (auto x : convertible) { // COMPLIANT: 1 conversion operator call only + } + + for (auto x : + convertToIntVector(convertible)) { // NON_COMPLIANT: 1 function call + 1 + // conversion operator call + } + + for (auto x : + convertToIntVector(convertible)) { // NON_COMPLIANT: 1 function call + 1 + // conversion operator call + } + + std::vector intVector1 = convertToIntVector(convertible); + for (auto x : intVector1) { // NON_COMPLIANT: 1 function call + 1 + // conversion operator call + } + + std::vector intVector2 = convertToIntVector(convertible); + for (auto x : intVector2) { // NON_COMPLIANT: 1 function call + 1 + // conversion operator call + } +} \ No newline at end of file From 0b5250f892849cee3c9959005534c1196513a30f Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Aug 2025 15:07:53 -0400 Subject: [PATCH 6/9] Minor --- cpp/misra/test/rules/RULE-9-5-2/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/misra/test/rules/RULE-9-5-2/test.cpp b/cpp/misra/test/rules/RULE-9-5-2/test.cpp index 9d5255f0c0..ba3bfa191b 100644 --- a/cpp/misra/test/rules/RULE-9-5-2/test.cpp +++ b/cpp/misra/test/rules/RULE-9-5-2/test.cpp @@ -18,7 +18,7 @@ class MyContainer { class ConvertibleToVector { public: - operator std::vector() const { return {}; } + operator std::vector() const { return {7, 8, 9}; } std::array::iterator begin() { return data_.begin(); } std::array::iterator end() { return data_.end(); } From b23986210658da7dab5751520793f26aad706c2a Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Aug 2025 15:52:58 -0400 Subject: [PATCH 7/9] Update test case for Rule 9.5.2 --- cpp/misra/test/rules/RULE-9-5-2/test.cpp | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/cpp/misra/test/rules/RULE-9-5-2/test.cpp b/cpp/misra/test/rules/RULE-9-5-2/test.cpp index ba3bfa191b..025a247ad7 100644 --- a/cpp/misra/test/rules/RULE-9-5-2/test.cpp +++ b/cpp/misra/test/rules/RULE-9-5-2/test.cpp @@ -1,4 +1,5 @@ #include +#include /* Helper functions */ std::vector getData() { return {1, 2, 3}; } @@ -9,8 +10,8 @@ class MyContainer { public: MyContainer() = default; MyContainer(std::vector data) : data_(data) {} - std::vector::iterator begin() { return data_.begin(); } - std::vector::iterator end() { return data_.end(); } + std::vector::const_iterator begin() const { return data_.begin(); } + std::vector::const_iterator end() const { return data_.end(); } private: std::vector data_{7, 8, 9}; @@ -19,15 +20,15 @@ class MyContainer { class ConvertibleToVector { public: operator std::vector() const { return {7, 8, 9}; } - std::array::iterator begin() { return data_.begin(); } - std::array::iterator end() { return data_.end(); } + std::array::const_iterator begin() const { return data_.begin(); } + std::array::const_iterator end() const { return data_.end(); } private: std::array data_{7, 8, 9}; }; -std::vector operator+(const std::vector &a, - const std::vector &b) { +std::vector operator+(std::vector a, + std::vector b) { std::vector result = a; result.insert(result.end(), b.begin(), b.end()); return result; @@ -37,8 +38,6 @@ std::vector convertToIntVector(std::vector vector) { return vector; } int main() { std::vector localVec = {1, 2, 3}; - std::vector *vecPtr = &localVec; - ConvertibleToVector convertible; /* ========== 1. EXPLICIT FUNCTION CALLS ========== */ @@ -92,7 +91,6 @@ int main() { localVec)) { // NON-COMPLIANT: 2 function calls + 1 operator call } - std::vector vec1 = {1}, vec2 = {2}, vec3 = {3}; std::vector appendedVector = (vec1 + vec2) + vec3; for (auto x : appendedVector) { // COMPLIANT: 0 calls } @@ -104,26 +102,26 @@ int main() { /* ========== 4. IMPLICIT CONVERSIONS ========== */ ConvertibleToVector convertible; - for (auto x : convertible) { // COMPLIANT: 1 conversion operator call only + for (int x : convertible) { // COMPLIANT: 1 conversion operator call only } - for (auto x : + for (int x : convertToIntVector(convertible)) { // NON_COMPLIANT: 1 function call + 1 // conversion operator call } - for (auto x : + for (int x : convertToIntVector(convertible)) { // NON_COMPLIANT: 1 function call + 1 // conversion operator call } std::vector intVector1 = convertToIntVector(convertible); - for (auto x : intVector1) { // NON_COMPLIANT: 1 function call + 1 + for (int x : intVector1) { // NON_COMPLIANT: 1 function call + 1 // conversion operator call } std::vector intVector2 = convertToIntVector(convertible); - for (auto x : intVector2) { // NON_COMPLIANT: 1 function call + 1 + for (int x : intVector2) { // NON_COMPLIANT: 1 function call + 1 // conversion operator call } } \ No newline at end of file From a0133887da526bb2ab7ed971556ecfe142d07b1a Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 1 Aug 2025 18:01:15 -0400 Subject: [PATCH 8/9] Add first draft of `ForRangeInitializerAtMostOneFunctionCall` --- ...orRangeInitializerAtMostOneFunctionCall.ql | 8 +-- ...eInitializerAtMostOneFunctionCall.expected | 9 +++- cpp/misra/test/rules/RULE-9-5-2/test.cpp | 54 ++++++++++--------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql b/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql index 47d27287bc..b78da2ad99 100644 --- a/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql +++ b/cpp/misra/src/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.ql @@ -19,7 +19,9 @@ import cpp import codingstandards.cpp.misra -from +from RangeBasedForStmt foreach, string message where - not isExcluded(x, StatementsPackage::forRangeInitializerAtMostOneFunctionCallQuery()) and -select + not isExcluded(foreach, StatementsPackage::forRangeInitializerAtMostOneFunctionCallQuery()) and + count(Call call | call = foreach.getRange().getAChild*() | call) >= 2 and + message = "has nested call expression in its initializer" +select foreach, "Range-based for loop " + message + "." diff --git a/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected index 2ec1a0ac6c..a8567ff48d 100644 --- a/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected +++ b/cpp/misra/test/rules/RULE-9-5-2/ForRangeInitializerAtMostOneFunctionCall.expected @@ -1 +1,8 @@ -No expected results have yet been specified \ No newline at end of file +| test.cpp:48:3:49:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:56:3:59:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:71:3:73:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:95:3:97:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:99:3:101:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:103:3:107:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:116:3:119:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | +| test.cpp:121:3:124:3 | for(...:...) ... | Range-based for loop has nested call expression in its initializer. | diff --git a/cpp/misra/test/rules/RULE-9-5-2/test.cpp b/cpp/misra/test/rules/RULE-9-5-2/test.cpp index 025a247ad7..b9ec5a0574 100644 --- a/cpp/misra/test/rules/RULE-9-5-2/test.cpp +++ b/cpp/misra/test/rules/RULE-9-5-2/test.cpp @@ -1,5 +1,5 @@ -#include #include +#include /* Helper functions */ std::vector getData() { return {1, 2, 3}; } @@ -10,8 +10,8 @@ class MyContainer { public: MyContainer() = default; MyContainer(std::vector data) : data_(data) {} - std::vector::const_iterator begin() const { return data_.begin(); } - std::vector::const_iterator end() const { return data_.end(); } + std::vector::iterator begin() { return data_.begin(); } + std::vector::iterator end() { return data_.end(); } private: std::vector data_{7, 8, 9}; @@ -20,15 +20,16 @@ class MyContainer { class ConvertibleToVector { public: operator std::vector() const { return {7, 8, 9}; } - std::array::const_iterator begin() const { return data_.begin(); } - std::array::const_iterator end() const { return data_.end(); } + std::array::iterator begin() { return data_.begin(); } + std::array::iterator end() { return data_.end(); } + std::array::const_iterator begin() const { return data_.cbegin(); } + std::array::const_iterator end() const { return data_.cend(); } private: std::array data_{7, 8, 9}; }; -std::vector operator+(std::vector a, - std::vector b) { +std::vector operator+(std::vector a, std::vector b) { std::vector result = a; result.insert(result.end(), b.begin(), b.end()); return result; @@ -49,8 +50,12 @@ int main() { /* ========== 2. OBJECT CREATION (CONSTRUCTOR CALLS) ========== */ + for (auto x : std::vector(3)) { // COMPLIANT: 1 constructor call only + } + for (auto x : - std::vector{1, 2, 3}) { // COMPLIANT: 1 constructor call only + std::vector{1, 2, 3}) { // NON_COMPLIANT: 2 constructor call to + // `vector` and `initializer_list`, respectively } for (auto x : MyContainer()) { // COMPLIANT: 1 constructor call only @@ -77,12 +82,22 @@ int main() { /* ========== 3. OPERATOR OVERLOADING ========== */ + std::vector vec1 = {1}, vec2 = {2}, vec3 = {3}; + std::vector appendedVector = (vec1 + vec2) + vec3; + for (auto x : appendedVector) { // COMPLIANT: 0 calls + } + + std::vector appendedVector2 = getData() + processData(localVec); + for (auto x : appendedVector2) { // COMPLIANT: 0 calls + } + std::vector anotherVec = {4, 5, 6}; - for (auto x : localVec + anotherVec) { // COMPLIANT: 1 operator+ call only + for (auto x : localVec + anotherVec) { // NON_COMPLIANT: 2 calls to vector's + // constructor, 1 operator+ call } - std::vector vec1 = {1}, vec2 = {2}, vec3 = {3}; - for (auto x : (vec1 + vec2) + vec3) { // NON-COMPLIANT: 2 operator+ calls + for (auto x : (vec1 + vec2) + vec3) { // NON-COMPLIANT: 3 calls to vector's + // constructor, 2 operator+ calls } for (auto x : @@ -91,18 +106,11 @@ int main() { localVec)) { // NON-COMPLIANT: 2 function calls + 1 operator call } - std::vector appendedVector = (vec1 + vec2) + vec3; - for (auto x : appendedVector) { // COMPLIANT: 0 calls - } - - std::vector appendedVector2 = getData() + processData(localVec); - for (auto x : appendedVector2) { // COMPLIANT: 0 calls - } - /* ========== 4. IMPLICIT CONVERSIONS ========== */ ConvertibleToVector convertible; - for (int x : convertible) { // COMPLIANT: 1 conversion operator call only + for (int x : + ConvertibleToVector()) { // COMPLIANT: 1 conversion operator call only } for (int x : @@ -116,12 +124,10 @@ int main() { } std::vector intVector1 = convertToIntVector(convertible); - for (int x : intVector1) { // NON_COMPLIANT: 1 function call + 1 - // conversion operator call + for (int x : intVector1) { // COMPLIANT: 0 function calls } std::vector intVector2 = convertToIntVector(convertible); - for (int x : intVector2) { // NON_COMPLIANT: 1 function call + 1 - // conversion operator call + for (int x : intVector2) { // COMPLIANT: 0 function calls } } \ No newline at end of file From 1c623f4dc2c3664152114ba5622b9ae515415b2c Mon Sep 17 00:00:00 2001 From: Jeongsoo Lee Date: Fri, 8 Aug 2025 12:57:43 -0400 Subject: [PATCH 9/9] Add test cases --- cpp/misra/test/rules/RULE-9-5-1/test.cpp | 147 +++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 cpp/misra/test/rules/RULE-9-5-1/test.cpp diff --git a/cpp/misra/test/rules/RULE-9-5-1/test.cpp b/cpp/misra/test/rules/RULE-9-5-1/test.cpp new file mode 100644 index 0000000000..0dafea56ef --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-5-1/test.cpp @@ -0,0 +1,147 @@ +void f(int &x) {} // Function that takes a non-const integer reference +void g(int *x) {} // Function that takes a non-const integer pointer + +int main() { + int j = 5; + + /* ========== 1. Type of the initialized counter variable ========== */ + + for (int i = 0; i < 10; i++) { // COMPLIANT: `i` has an integer type + } + + for (float i = 0.0; i < 10; + i++) { // NON_COMPLIANT: `i` has a non-integer type + } + + /* ========== 2. Termination condition ========== */ + + for (int i = 0; i < 10; i++) { // COMPLIANT: `<` is a relational operator + } + + for (int i = 0; i == 10; + i++) { // NON_COMPLIANT: `==` is not a relational operator + } + + for (int i = 0; j < 10; i++) { // NON_COMPLIANT: `j` is not the loop counter + j++; + } + + /* ========== 3. Updating expression ========== */ + + for (int i = 0; i < 10; + ++i) { // COMPLIANT: Pre-increment operator used as the update expression + } + + for (int i = 0; i < 10; i++) { // COMPLIANT: Post-increment operator used as + // the update expression + } + + for (int i = 0; i < 10; i += 3) { // COMPLIANT: Add-and-assign operator used + // as the update expression with loop step 3 + } + + for (int i = 0; i < 10; + i *= 2) { // NON_COMPLIANT: Mutiplication is not incrementing + } + + /* ========== 4. Type of the loop counter and the loop bound ========== */ + + for (int i = 0; i < 10; i++) { // COMPLIANT: 0 and 10 are of same type + } + + for (unsigned long long int i = 0; i < 10; + i++) { // COMPLIANT: The loop counter has type bigger than that of the + // loop bound + } + + for (int i = 0; i < 10ull; + i++) { // NON_COMPLIANT: The type of the loop counter is not bigger + // than that of the loop bound + } + + for (int i = 0; i < j; + i++) { // NON_COMPLIANT: The loop bound is not a constant + } + + /* ========== 5. Immutability of the loop bound and the loop step ========== + */ + + for (int i = 0; i < 10; + i++) { // COMPLIANT: The update expression is an post-increment operation + // and its loop step is always 1 + } + + for (int i = 0; i < 10; i += 2) { // COMPLIANT: The loop step is always 2 + } + + for (int i = 0; i < 10; + i += + j) { // COMPLIANT: The loop step `j` is not mutated anywhere in the loop + } + + for (int i = 0; i < 10; + i += j) { // NON_COMPLIANT: The loop step `j` is mutated in the loop + j++; + } + + for (int i = 0; i < 10; + i += j, j++) { // NON_COMPLIANT: The loop step `j` is mutated in the loop + } + + for (int i = 0; i < j; i++) { // COMPLIANT: The loop bound `j` is not mutated + // anywhere in the loop + } + + for (int i = 0; i < j; i++) { // COMPLIANT: The loop bound `j` is not mutated + // anywhere in the loop + } + + for (int i = 0; i < j; + i++) { // NON_COMPLIANT: The loop bound `j` is mutated in the loop + j++; + } + + /* ========== 6. Existence of pointers to the loop counter, loop bound, and + * loop step ========== */ + + int k = 10; + int l = 2; + + for (int i = 0; i < k; i += l) { // COMPLIANT: The loop counter, bound, and + // step are not taken addresses of + } + + for (int i = j; i < k; i += l) { // NON_COMPLIANT: The loop counter is passed + // as a non-const reference + f(j); + } + + for (int i = j; i < k; i += l) { // NON_COMPLIANT: The loop counter is passed + // as a non-const pointer + g(&j); + } + + for (int i = j; i < k; + i += + l) { // NON_COMPLIANT: The loop bound is passed as a non-const pointer + f(k); + } + + for (int i = j; i < k; + i += + l) { // NON_COMPLIANT: The loop bound is passed as a non-const pointer + g(&k); + } + + for (int i = j; i < k; + i += + l) { // NON_COMPLIANT: The loop step is passed as a non-const pointer + f(l); + } + + for (int i = j; i < k; + i += + l) { // NON_COMPLIANT: The loop step is passed as a non-const pointer + g(&l); + } +} \ No newline at end of file