Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned2.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
import cpp
import RuleMetadata
import codingstandards.cpp.exclusions.RuleMetadata

newtype Banned2Query = TUnscopedEnumerationsShouldNotBeDeclaredQuery()

predicate isBanned2QueryMetadata(Query query, string queryId, string ruleId, string category) {
query =
// `Query` instance for the `unscopedEnumerationsShouldNotBeDeclared` query
Banned2Package::unscopedEnumerationsShouldNotBeDeclaredQuery() and
queryId =
// `@id` for the `unscopedEnumerationsShouldNotBeDeclared` query
"cpp/misra/unscoped-enumerations-should-not-be-declared" and
ruleId = "RULE-10-2-2" and
category = "advisory"
}

module Banned2Package {
Query unscopedEnumerationsShouldNotBeDeclaredQuery() {
//autogenerate `Query` type
result =
// `Query` type for `unscopedEnumerationsShouldNotBeDeclared` query
TQueryCPP(TBanned2PackageQuery(TUnscopedEnumerationsShouldNotBeDeclaredQuery()))
}
}
26 changes: 26 additions & 0 deletions cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned3.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
import cpp
import RuleMetadata
import codingstandards.cpp.exclusions.RuleMetadata

newtype Banned3Query = TUnscopedEnumWithoutFixedUnderlyingTypeUsedQuery()

predicate isBanned3QueryMetadata(Query query, string queryId, string ruleId, string category) {
query =
// `Query` instance for the `unscopedEnumWithoutFixedUnderlyingTypeUsed` query
Banned3Package::unscopedEnumWithoutFixedUnderlyingTypeUsedQuery() and
queryId =
// `@id` for the `unscopedEnumWithoutFixedUnderlyingTypeUsed` query
"cpp/misra/unscoped-enum-without-fixed-underlying-type-used" and
ruleId = "RULE-10-2-3" and
category = "required"
}

module Banned3Package {
Query unscopedEnumWithoutFixedUnderlyingTypeUsedQuery() {
//autogenerate `Query` type
result =
// `Query` type for `unscopedEnumWithoutFixedUnderlyingTypeUsed` query
TQueryCPP(TBanned3PackageQuery(TUnscopedEnumWithoutFixedUnderlyingTypeUsedQuery()))
}
}
26 changes: 26 additions & 0 deletions cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned4.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
import cpp
import RuleMetadata
import codingstandards.cpp.exclusions.RuleMetadata

newtype Banned4Query = TUnnamedNamespacesInHeaderFilesQuery()

predicate isBanned4QueryMetadata(Query query, string queryId, string ruleId, string category) {
query =
// `Query` instance for the `unnamedNamespacesInHeaderFiles` query
Banned4Package::unnamedNamespacesInHeaderFilesQuery() and
queryId =
// `@id` for the `unnamedNamespacesInHeaderFiles` query
"cpp/misra/unnamed-namespaces-in-header-files" and
ruleId = "RULE-10-3-1" and
category = "advisory"
}

module Banned4Package {
Query unnamedNamespacesInHeaderFilesQuery() {
//autogenerate `Query` type
result =
// `Query` type for `unnamedNamespacesInHeaderFiles` query
TQueryCPP(TBanned4PackageQuery(TUnnamedNamespacesInHeaderFilesQuery()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import codingstandards.cpp.exclusions.RuleMetadata
//** Import packages for this language **/
import Allocations
import Banned1
import Banned2
import Banned3
import Banned4
import BannedAPIs
import BannedFunctions
import BannedLibraries
Expand Down Expand Up @@ -94,6 +97,9 @@ import VirtualFunctions
newtype TCPPQuery =
TAllocationsPackageQuery(AllocationsQuery q) or
TBanned1PackageQuery(Banned1Query q) or
TBanned2PackageQuery(Banned2Query q) or
TBanned3PackageQuery(Banned3Query q) or
TBanned4PackageQuery(Banned4Query q) or
TBannedAPIsPackageQuery(BannedAPIsQuery q) or
TBannedFunctionsPackageQuery(BannedFunctionsQuery q) or
TBannedLibrariesPackageQuery(BannedLibrariesQuery q) or
Expand Down Expand Up @@ -184,6 +190,9 @@ newtype TCPPQuery =
predicate isQueryMetadata(Query query, string queryId, string ruleId, string category) {
isAllocationsQueryMetadata(query, queryId, ruleId, category) or
isBanned1QueryMetadata(query, queryId, ruleId, category) or
isBanned2QueryMetadata(query, queryId, ruleId, category) or
isBanned3QueryMetadata(query, queryId, ruleId, category) or
isBanned4QueryMetadata(query, queryId, ruleId, category) or
isBannedAPIsQueryMetadata(query, queryId, ruleId, category) or
isBannedFunctionsQueryMetadata(query, queryId, ruleId, category) or
isBannedLibrariesQueryMetadata(query, queryId, ruleId, category) or
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @id cpp/misra/unscoped-enumerations-should-not-be-declared
* @name RULE-10-2-2: Unscoped enumerations should not be declared
* @description An unscoped enumeration should not be used outside of a class/struct scope; use
* 'enum class' instead to prevent name clashes and implicit conversions to integral
* types.
* @kind problem
* @precision very-high
* @problem.severity error
* @tags external/misra/id/rule-10-2-2
* scope/single-translation-unit
* correctness
* external/misra/enforcement/decidable
* external/misra/obligation/advisory
*/

import cpp
import codingstandards.cpp.misra

class NestedUnscopedEnum extends Enum, NestedEnum {
NestedUnscopedEnum() { not this instanceof ScopedEnum }
}

from Enum enum
where
not isExcluded(enum, Banned2Package::unscopedEnumerationsShouldNotBeDeclaredQuery()) and
not (enum instanceof ScopedEnum or enum instanceof NestedUnscopedEnum)
select enum, "This enumeration is an unscoped enum not enclosed in a class or a struct."
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/**
* @id cpp/misra/unscoped-enum-without-fixed-underlying-type-used
* @name RULE-10-2-3: The numeric value of an unscoped enumeration with no fixed underlying type shall not be used
* @description Treating unscoped enumeration without a fixed underlying type as an integral type is
* not portable and might cause unintended behaviors.
* @kind problem
* @precision very-high
* @problem.severity error
* @tags external/misra/id/rule-10-2-3
* scope/single-translation-unit
* correctness
* external/misra/enforcement/decidable
* external/misra/obligation/required
*/

import cpp
import codingstandards.cpp.misra

private predicate isUnscopedEnum(Enum enum) { not enum instanceof ScopedEnum }

private predicate withoutFixedUnderlyingType(Enum enum) { not enum.hasExplicitUnderlyingType() }

private predicate isUnscopedEnumWithoutFixedUnderlyingType(Enum enum) {
isUnscopedEnum(enum) and withoutFixedUnderlyingType(enum)
}

class ArithmeticBitwiseLogicalBinaryOperation extends BinaryOperation {
ArithmeticBitwiseLogicalBinaryOperation() {
this instanceof BinaryArithmeticOperation or
this instanceof BinaryBitwiseOperation or
this instanceof BinaryLogicalOperation
}
}

/**
* ``` C++
* static_cast<int>(u) == static_cast<int>(s); // COMPLIANT: comparing ints
* ```
* ^^^ To solve this, we use `getExplicitlyConverted`:
* `binOp.getLeftOperand().getExplicitlyConverted()` gives `int`.
*/
predicate arithmeticBitwiseLogicalOperationUsesUnscopedUnfixedEnum(
ArithmeticBitwiseLogicalBinaryOperation binOp, Enum enum
) {
/*
* We want to strip explicit casts and not implicit ones. Without the
* stripping of explicit casts, our query would raise a false alarm on
* cases such as below.
*
* ``` C++
* static_cast<int>(u) + 1 // COMPLIANT
* ```
*/

isUnscopedEnumWithoutFixedUnderlyingType(enum) and
(
enum = binOp.getLeftOperand().getExplicitlyConverted().getUnderlyingType() or
enum = binOp.getRightOperand().getExplicitlyConverted().getUnderlyingType()
)
}

class RelationalEqualityBinaryOperation extends BinaryOperation {
RelationalEqualityBinaryOperation() {
this instanceof RelationalOperation or
this instanceof EqualityOperation
}
}

predicate relationalEqualityOperationUsesUnscopedUnfixedEnum(
RelationalEqualityBinaryOperation binOp, Enum enum
) {
exists(Type leftOperandType, Type rightOperandType |
/*
* We want to strip explicit casts and not implicit ones. Without the
* stripping of explicit casts, our query would raise a false alarm on
* cases such as below.
*
* ``` C++
* static_cast<int>(u) == 1 // COMPLIANT
* ```
*/

leftOperandType = binOp.getLeftOperand().getExplicitlyConverted().getUnderlyingType() and
rightOperandType = binOp.getRightOperand().getExplicitlyConverted().getUnderlyingType()
|
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
(
enum = leftOperandType or
enum = rightOperandType
) and
leftOperandType != rightOperandType
)
}

class ArithmeticBitwiseCompoundAssignment extends AssignOperation {
ArithmeticBitwiseCompoundAssignment() {
this instanceof AssignArithmeticOperation or
this instanceof AssignBitwiseOperation
}
}

predicate compoundAssignmentUsesUnscopedUnfixedEnum(
ArithmeticBitwiseCompoundAssignment compoundAssignment, Enum enum
) {
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
enum = compoundAssignment.getAnOperand().getUnderlyingType()
}

/**
* Gets the minimum number of bits required to hold all values of enum `e`.
*/
int enumMinBits(Enum e, boolean signed) {
exists(QlBuiltins::BigInt minVal, QlBuiltins::BigInt maxVal |
minVal = min(EnumConstant c | c.getDeclaringEnum() = e | c.getValue().toBigInt()) and
maxVal = max(EnumConstant c | c.getDeclaringEnum() = e | c.getValue().toBigInt())
|
// 8 bits: signed [-128, 127] or unsigned [0, 255]
if minVal >= "-128".toBigInt() and maxVal <= "127".toBigInt()
then result = 8 and signed = true
else
if minVal >= "0".toBigInt() and maxVal <= "255".toBigInt()
then (
result = 8 and signed = false
) else
// 16 bits: signed [-32768, 32767] or unsigned [0, 65535]
if minVal >= "-32768".toBigInt() and maxVal <= "32767".toBigInt()
then (
result = 16 and signed = true
) else
if minVal >= "0".toBigInt() and maxVal <= "65535".toBigInt()
then (
result = 16 and signed = false
) else
// 32 bits: signed [-2147483648, 2147483647] or unsigned [0, 4294967295]
if minVal >= "-2147483648".toBigInt() and maxVal <= "2147483647".toBigInt()
then (
result = 32 and signed = true
) else
if minVal >= "0".toBigInt() and maxVal <= "4294967295".toBigInt()
then (
result = 32 and signed = false
) else (
// 64 bits: everything else
result = 64 and signed = true
Comment on lines +143 to +144
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumMinBits defaults the 64-bit case to signed = true (lines 142-145). This will mis-handle enums whose enumerators require an unsigned 64-bit underlying type (for example, large non-negative values > 2^63-1), potentially causing false positives/negatives in the size/signedness checks. Consider extending the range checks to distinguish signed vs unsigned 64-bit (or derive signedness directly from the enum’s value range / underlying type selection logic).

Suggested change
// 64 bits: everything else
result = 64 and signed = true
// 64 bits: everything else; choose signedness based on value range
if minVal >= "0".toBigInt()
then (
result = 64 and signed = false
) else (
result = 64 and signed = true
)

Copilot uses AI. Check for mistakes.
)
)
}

/**
* Holds if the enum `e` can fit in an integral type `type`.
*/
predicate enumFitsInType(Enum e, IntegralType type) {
exists(int minBits, boolean signed | minBits = enumMinBits(e, signed) |
/* If it has exactly the minimum number of bits, then check its signedness. */
type.getSize() * 8 = minBits and
(
signed = true and type.isSigned()
or
signed = false and type.isUnsigned()
)
or
/* If it exceeds the minimum number of bits, signedness doesn't matter. */
type.getSize() * 8 > minBits
)
}

predicate assignmentSourceIsUnscopedUnfixedEnum(AssignExpr assign, Enum enum, Type targetType) {
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
enum = assign.getRValue().getUnderlyingType() and
targetType = assign.getLValue().getUnderlyingType() and
not enumFitsInType(enum, targetType) and
not enum = targetType
}

predicate staticCastSourceIsUnscopedUnfixedEnumVariant(StaticCast cast, Enum enum, Type targetType) {
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
enum = cast.getExpr().getUnderlyingType() and
targetType = cast.getUnderlyingType() and
not enumFitsInType(enum, targetType) and
not enum = targetType
}

predicate switchConditionIsAnUnfixedEnumVariant(SwitchStmt switch, Enum enum, SwitchCase invalidCase) {
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
enum = switch.getExpr().getType() and
invalidCase = switch.getASwitchCase() and
not invalidCase.getExpr().getUnderlyingType() = enum
}

/**
* Holds if a `static_cast` expression has an unscoped enum without fixed
* underlying type as the target type.
*/
predicate staticCastTargetIsUnscopedUnfixedEnumVariant(StaticCast cast, Enum enum) {
isUnscopedEnumWithoutFixedUnderlyingType(enum) and
enum = cast.getType() and
not cast.getExpr().getType() = enum
}

from Element x, Enum enum, string message
where
not isExcluded(x, Banned3Package::unscopedEnumWithoutFixedUnderlyingTypeUsedQuery()) and
(
arithmeticBitwiseLogicalOperationUsesUnscopedUnfixedEnum(x, enum) and
message =
"Arithmetic, bitwise, or logical operation uses unscoped enum $@ without fixed underlying type."
or
relationalEqualityOperationUsesUnscopedUnfixedEnum(x, enum) and
message =
"Relational or equality operation compares unscoped enum $@ without fixed underlying type to a different type."
or
compoundAssignmentUsesUnscopedUnfixedEnum(x, enum) and
message = "Compound assignment uses unscoped enum $@ without fixed underlying type."
or
exists(Type targetType |
assignmentSourceIsUnscopedUnfixedEnum(x, enum, targetType) and
message =
"Assignment from unscoped enum $@ without fixed underlying type to '" + targetType.getName()
+ "' which may not be large enough."
)
or
exists(Type targetType |
staticCastSourceIsUnscopedUnfixedEnumVariant(x, enum, targetType) and
message =
"Static cast from unscoped enum $@ without fixed underlying type to '" +
targetType.getName() + "' which may not be large enough."
)
or
exists(SwitchStmt switch |
switchConditionIsAnUnfixedEnumVariant(switch, enum, x) and
message =
"Switch on unscoped enum $@ without fixed underlying type has case not of the same enum type."
)
or
staticCastTargetIsUnscopedUnfixedEnumVariant(x, enum) and
message = "Static cast to unscoped enum $@ without fixed underlying type."
)
select x, message, enum, enum.getName()
Loading
Loading