-
Notifications
You must be signed in to change notification settings - Fork 76
Add Banned2, Banned3, and Banned4
#1080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6dc64be
21af40d
20f7399
447d961
1a08d11
ba3afac
c4179a0
c91e5c5
cfc6de7
a1f17d3
96fdc0c
4ae8c1d
7f4ebd4
075c51f
2e62897
b4683d2
538fafa
db276dc
f7d6a49
4ab4507
113c464
2cf4b66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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())) | ||
| } | ||
| } | ||
| 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())) | ||
| } | ||
| } |
| 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 |
|---|---|---|
| @@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably should be a warning, and either include |
||
| * @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." | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's include the enum name here, e.g. Generally speaking, we want to avoid constant alert messages. |
||
| 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`: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! Yes. Conversions are annoying and this is exactly the correct approach 👍 👍 👍 (I didn't know |
||
| * `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, false] | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * 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 | ||
| 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() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI, we don't need to split rules into their own packages unless they're being split into different PRs. In this case, these can all be part of
Banned2.Also, in this case, three PRs would likely be preferable over one, since the three rules don't have any shared imports in common/ or updates to test/includes/, etc. It is especially valuable to separate 10-2-2, 10-3-1, which are quick to review and approve, from 10-2-3 which has over 200LOC and might take a few back-and-forths to land.
Even the small queries, such as 10-2-2 and 10-3-1, are probably easier to review in isolation going forward because they often a lot of test code each. All in all, one review of 1300loc is harder to find time for than three reviews of ~430loc each, and will take longer to land.
No action required in this case, but to keep in mind going forward 👍