diff --git a/change_notes/2026-03-17-share-out-of-range-enum-cast-query.md b/change_notes/2026-03-17-share-out-of-range-enum-cast-query.md new file mode 100644 index 0000000000..64e3d791bd --- /dev/null +++ b/change_notes/2026-03-17-share-out-of-range-enum-cast-query.md @@ -0,0 +1,2 @@ + - `INT50-CPP` - `DoNotCastToAnOutOfRangeEnumerationValue.ql`: + - Refactored query logic into a shared library (`DoNotCastToAnOutOfRangeEnumerationValueShared.qll`) to enable reuse by MISRA C++ `RULE-4-1-3`. The query logic is unchanged and no visible changes to results or performance are expected. diff --git a/cpp/cert/src/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.ql b/cpp/cert/src/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.ql index c7437073e9..3920cc7544 100644 --- a/cpp/cert/src/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.ql +++ b/cpp/cert/src/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.ql @@ -18,44 +18,12 @@ import cpp import codingstandards.cpp.cert -import codingstandards.cpp.Enums -import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis -import codingstandards.cpp.SimpleRangeAnalysisCustomizations +import codingstandards.cpp.rules.donotcasttoanoutofrangeenumerationvalueshared.DoNotCastToAnOutOfRangeEnumerationValueShared -from Cast c, Enum e, string description -where - not isExcluded(c, TypeRangesPackage::doNotCastToAnOutOfRangeEnumerationValueQuery()) and - // Conversion from an integral type to an enum type - c.getExpr().getType().getUnspecifiedType() instanceof IntegralType and - c.getType().getUnspecifiedType() = e and - not ( - // The deduced bound for the expression is within the type range for the explicit type - upperBound(c.getExpr()) <= Enums::getValueRangeUpperBound(e) and - lowerBound(c.getExpr()) >= Enums::getValueRangeLowerBound(e) - ) and - // Not a compile time constant with the same value as an existing enum constant - not exists(float enumConstantValue | - enumConstantValue = Enums::getEnumConstantValue(e.getAnEnumConstant()) - | - // Expression is a constant - c.getExpr().getValue().toFloat() = enumConstantValue - or - // Range analysis has precise bounds - enumConstantValue = upperBound(c.getExpr()) and - enumConstantValue = lowerBound(c.getExpr()) - ) and - ( - if exists(upperBound(c.getExpr())) - then - description = - "Cast to enum $@ with value range " + Enums::getValueRangeLowerBound(e) + "..." + - Enums::getValueRangeUpperBound(e) + " from expression with wider value range " + - lowerBound(c.getExpr()) + "..." + upperBound(c.getExpr()) + " in function " + - c.getEnclosingFunction().getName() + "." - else - description = - "Cast to enum $@ with value range " + Enums::getValueRangeLowerBound(e) + "..." + - Enums::getValueRangeUpperBound(e) + - " from expression with a potentially wider value range." - ) -select c, description, e, e.getName() +module DoNotCastToAnOutOfRangeEnumerationValueConfig implements + DoNotCastToAnOutOfRangeEnumerationValueSharedConfigSig +{ + Query getQuery() { result = TypeRangesPackage::doNotCastToAnOutOfRangeEnumerationValueQuery() } +} + +import DoNotCastToAnOutOfRangeEnumerationValueShared diff --git a/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.qlref b/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.qlref deleted file mode 100644 index b46c9b3689..0000000000 --- a/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.qlref +++ /dev/null @@ -1 +0,0 @@ -rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.ql \ No newline at end of file diff --git a/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.testref b/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.testref new file mode 100644 index 0000000000..fef28fe330 --- /dev/null +++ b/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.testref @@ -0,0 +1 @@ +cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.ql \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Undefined.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Undefined.qll index 37ae63fa53..8f52820346 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Undefined.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Undefined.qll @@ -8,7 +8,8 @@ newtype UndefinedQuery = TCriticalUnspecifiedBehaviorQuery() or TUndefinedBehaviorAuditQuery() or TCriticalUnspecifiedBehaviorAuditQuery() or - TPossibleDataRaceBetweenThreadsQuery() + TPossibleDataRaceBetweenThreadsQuery() or + TOutOfRangeEnumCastCriticalUnspecifiedBehaviorQuery() predicate isUndefinedQueryMetadata(Query query, string queryId, string ruleId, string category) { query = @@ -55,6 +56,15 @@ predicate isUndefinedQueryMetadata(Query query, string queryId, string ruleId, s "cpp/misra/possible-data-race-between-threads" and ruleId = "RULE-4-1-3" and category = "required" + or + query = + // `Query` instance for the `outOfRangeEnumCastCriticalUnspecifiedBehavior` query + UndefinedPackage::outOfRangeEnumCastCriticalUnspecifiedBehaviorQuery() and + queryId = + // `@id` for the `outOfRangeEnumCastCriticalUnspecifiedBehavior` query + "cpp/misra/out-of-range-enum-cast-critical-unspecified-behavior" and + ruleId = "RULE-4-1-3" and + category = "required" } module UndefinedPackage { @@ -92,4 +102,11 @@ module UndefinedPackage { // `Query` type for `possibleDataRaceBetweenThreads` query TQueryCPP(TUndefinedPackageQuery(TPossibleDataRaceBetweenThreadsQuery())) } + + Query outOfRangeEnumCastCriticalUnspecifiedBehaviorQuery() { + //autogenerate `Query` type + result = + // `Query` type for `outOfRangeEnumCastCriticalUnspecifiedBehavior` query + TQueryCPP(TUndefinedPackageQuery(TOutOfRangeEnumCastCriticalUnspecifiedBehaviorQuery())) + } } diff --git a/cpp/common/src/codingstandards/cpp/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.qll b/cpp/common/src/codingstandards/cpp/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.qll new file mode 100644 index 0000000000..7f8896bc01 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.qll @@ -0,0 +1,59 @@ +/** + * Provides a configurable module DoNotCastToAnOutOfRangeEnumerationValueShared with a `problems` predicate + * for the following issue: + * Casting to an out-of-range enumeration value leads to unspecified or undefined + * behavior. + */ + +import cpp +import codingstandards.cpp.Customizations +import codingstandards.cpp.Exclusions +import codingstandards.cpp.Enums +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis +import codingstandards.cpp.SimpleRangeAnalysisCustomizations + +signature module DoNotCastToAnOutOfRangeEnumerationValueSharedConfigSig { + Query getQuery(); +} + +module DoNotCastToAnOutOfRangeEnumerationValueShared< + DoNotCastToAnOutOfRangeEnumerationValueSharedConfigSig Config> +{ + query predicate problems(Cast c, string description, Enum e, string enumName) { + not isExcluded(c, Config::getQuery()) and + // Conversion from an integral type to an enum type + c.getExpr().getType().getUnspecifiedType() instanceof IntegralType and + c.getType().getUnspecifiedType() = e and + not ( + // The deduced bound for the expression is within the type range for the explicit type + upperBound(c.getExpr()) <= Enums::getValueRangeUpperBound(e) and + lowerBound(c.getExpr()) >= Enums::getValueRangeLowerBound(e) + ) and + // Not a compile time constant with the same value as an existing enum constant + not exists(float enumConstantValue | + enumConstantValue = Enums::getEnumConstantValue(e.getAnEnumConstant()) + | + // Expression is a constant + c.getExpr().getValue().toFloat() = enumConstantValue + or + // Range analysis has precise bounds + enumConstantValue = upperBound(c.getExpr()) and + enumConstantValue = lowerBound(c.getExpr()) + ) and + enumName = e.getName() and + ( + if exists(upperBound(c.getExpr())) + then + description = + "Cast to enum $@ with value range " + Enums::getValueRangeLowerBound(e) + "..." + + Enums::getValueRangeUpperBound(e) + " from expression with wider value range " + + lowerBound(c.getExpr()) + "..." + upperBound(c.getExpr()) + " in function " + + c.getEnclosingFunction().getName() + "." + else + description = + "Cast to enum $@ with value range " + Enums::getValueRangeLowerBound(e) + "..." + + Enums::getValueRangeUpperBound(e) + + " from expression with a potentially wider value range." + ) + } +} diff --git a/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.expected b/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.expected similarity index 92% rename from cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.expected rename to cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.expected index 777759b05c..d9492d3764 100644 --- a/cpp/cert/test/rules/INT50-CPP/DoNotCastToAnOutOfRangeEnumerationValue.expected +++ b/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.expected @@ -1,4 +1,4 @@ | test.cpp:9:11:9:16 | (Foo)... | Cast to enum $@ with value range 0...3 from expression with wider value range -2147483648...2147483647 in function test_unconstrained_cast. | test.cpp:3:6:3:8 | Foo | Foo | | test.cpp:15:13:15:18 | (Foo)... | Cast to enum $@ with value range 0...3 from expression with wider value range 0...2147483647 in function test_range_check. | test.cpp:3:6:3:8 | Foo | Foo | | test.cpp:20:16:20:21 | (Foo)... | Cast to enum $@ with value range 0...3 from expression with wider value range 0...4 in function test_range_check. | test.cpp:3:6:3:8 | Foo | Foo | -| test.cpp:27:12:27:25 | (Foo)... | Cast to enum $@ with value range 0...3 from expression with wider value range 0...7 in function test_unsanitize. | test.cpp:3:6:3:8 | Foo | Foo | +| test.cpp:27:12:27:25 | (Foo)... | Cast to enum $@ with value range 0...3 from expression with wider value range 0...7 in function test_unsanitize. | test.cpp:3:6:3:8 | Foo | Foo | \ No newline at end of file diff --git a/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.ql b/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.ql new file mode 100644 index 0000000000..072eb65012 --- /dev/null +++ b/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.ql @@ -0,0 +1,8 @@ +// GENERATED FILE - DO NOT MODIFY +import codingstandards.cpp.rules.donotcasttoanoutofrangeenumerationvalueshared.DoNotCastToAnOutOfRangeEnumerationValueShared + +module TestFileConfig implements DoNotCastToAnOutOfRangeEnumerationValueSharedConfigSig { + Query getQuery() { result instanceof TestQuery } +} + +import DoNotCastToAnOutOfRangeEnumerationValueShared diff --git a/cpp/cert/test/rules/INT50-CPP/test.cpp b/cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/test.cpp similarity index 100% rename from cpp/cert/test/rules/INT50-CPP/test.cpp rename to cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/test.cpp diff --git a/cpp/misra/src/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.ql b/cpp/misra/src/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.ql new file mode 100644 index 0000000000..b1a0cc4589 --- /dev/null +++ b/cpp/misra/src/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.ql @@ -0,0 +1,28 @@ +/** + * @id cpp/misra/out-of-range-enum-cast-critical-unspecified-behavior + * @name RULE-4-1-3: Out-of-range enumeration cast leads to critical unspecified behavior + * @description Casting to an enumeration value outside the range of the enumeration's values + * results in critical unspecified behavior. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-4-1-3 + * correctness + * scope/system + * external/misra/enforcement/undecidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.rules.donotcasttoanoutofrangeenumerationvalueshared.DoNotCastToAnOutOfRangeEnumerationValueShared + +module OutOfRangeEnumCastCriticalUnspecifiedBehaviorConfig implements + DoNotCastToAnOutOfRangeEnumerationValueSharedConfigSig +{ + Query getQuery() { + result = UndefinedPackage::outOfRangeEnumCastCriticalUnspecifiedBehaviorQuery() + } +} + +import DoNotCastToAnOutOfRangeEnumerationValueShared diff --git a/cpp/misra/test/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.testref b/cpp/misra/test/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.testref new file mode 100644 index 0000000000..fef28fe330 --- /dev/null +++ b/cpp/misra/test/rules/RULE-4-1-3/OutOfRangeEnumCastCriticalUnspecifiedBehavior.testref @@ -0,0 +1 @@ +cpp/common/test/rules/donotcasttoanoutofrangeenumerationvalueshared/DoNotCastToAnOutOfRangeEnumerationValueShared.ql \ No newline at end of file diff --git a/rule_packages/cpp/TypeRanges.json b/rule_packages/cpp/TypeRanges.json index 1e8ef914bf..5bd5c69ece 100644 --- a/rule_packages/cpp/TypeRanges.json +++ b/rule_packages/cpp/TypeRanges.json @@ -206,6 +206,7 @@ "name": "Do not cast to an out-of-range enumeration value", "precision": "high", "severity": "error", + "shared_implementation_short_name": "DoNotCastToAnOutOfRangeEnumerationValueShared", "short_name": "DoNotCastToAnOutOfRangeEnumerationValue", "tags": [ "correctness", diff --git a/rule_packages/cpp/Undefined.json b/rule_packages/cpp/Undefined.json index bc0b10af3d..8cc4a4f1c9 100644 --- a/rule_packages/cpp/Undefined.json +++ b/rule_packages/cpp/Undefined.json @@ -69,6 +69,19 @@ "concurrency", "scope/system" ] + }, + { + "description": "Casting to an enumeration value outside the range of the enumeration's values results in critical unspecified behavior.", + "kind": "problem", + "name": "Out-of-range enumeration cast leads to critical unspecified behavior", + "precision": "high", + "severity": "error", + "shared_implementation_short_name": "DoNotCastToAnOutOfRangeEnumerationValueShared", + "short_name": "OutOfRangeEnumCastCriticalUnspecifiedBehavior", + "tags": [ + "correctness", + "scope/system" + ] } ], "title": "There shall be no occurrence of undefined or critical unspecified behaviour"