diff --git a/amendments.csv b/amendments.csv index cd0085493e..ce285a29ba 100644 --- a/amendments.csv +++ b/amendments.csv @@ -15,7 +15,7 @@ c,MISRA-C-2012,Amendment4,RULE-11-3,Yes,Expand,No,Easy c,MISRA-C-2012,Amendment4,RULE-11-8,Yes,Expand,No,Easy c,MISRA-C-2012,Amendment4,RULE-13-2,Yes,Expand,No,Very Hard c,MISRA-C-2012,Amendment4,RULE-18-6,Yes,Expand,No,Medium -c,MISRA-C-2012,Amendment4,RULE-18-8,Yes,Split,No,Easy +c,MISRA-C-2012,Amendment4,RULE-18-8,Yes,Split,Yes,Easy c,MISRA-C-2012,Corrigendum2,RULE-2-2,Yes,Clarification,No,Import c,MISRA-C-2012,Corrigendum2,RULE-2-7,Yes,Clarification,No,Import c,MISRA-C-2012,Corrigendum2,RULE-3-1,Yes,Refine,No,Easy diff --git a/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql index 2d66b8643c..6a018ed8c4 100644 --- a/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql +++ b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql @@ -13,18 +13,7 @@ import cpp import codingstandards.c.cert - -/** - * A struct or union type that contains an array type - */ -class StructOrUnionTypeWithArrayField extends Struct { - StructOrUnionTypeWithArrayField() { - this.getAField().getUnspecifiedType() instanceof ArrayType - or - // nested struct or union containing an array type - this.getAField().getUnspecifiedType().(Struct) instanceof StructOrUnionTypeWithArrayField - } -} +import codingstandards.cpp.lifetimes.CLifetimes // Note: Undefined behavior is possible regardless of whether the accessed field from the returned // struct is an array or a scalar (i.e. arithmetic and pointer types) member, according to the standard. diff --git a/c/misra/src/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.ql b/c/misra/src/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.ql new file mode 100644 index 0000000000..fec8f5d2e1 --- /dev/null +++ b/c/misra/src/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.ql @@ -0,0 +1,122 @@ +/** + * @id c/misra/pointers-to-variably-modified-array-types-used + * @name RULE-18-10: Pointers to variably-modified array types shall not be used + * @description Pointers to variably-modified array types shall not be used, as these pointer types + * are frequently incompatible with other fixed or variably sized arrays, resulting in + * undefined behavior. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-18-10 + * external/misra/c/2012/amendment4 + * correctness + * security + * external/misra/obligation/mandatory + */ + +import cpp +import codingstandards.c.misra +import codingstandards.cpp.VariablyModifiedTypes + +/** + * Check that the declaration entry, which may be a parameter or a variable + * etc., seems to subsume the location of `inner`, including the declaration + * type text. + * + * The location of the `DeclarationEntry` itself points to the _identifier_ + * that is declared. This range will not include the type of the declaration. + * + * For parameters, the `before` and `end` `Location` objects will be + * constrained to the closest earlier element (parameter or function body), + * these values can therefore be captured and inspected for debugging. + * + * For declarations which occur in statements, the `before` and `end` + * `Location` objects will be both constrained to be equal, and equal to, + * the `Location` of the containing `DeclStmt`. + */ +predicate declarationSubsumes( + DeclarationEntry entry, Location inner, Location before, Location after +) { + inner.getFile() = entry.getLocation().getFile() and + ( + exists(ParameterDeclarationEntry param, FunctionDeclarationEntry func, int i | + param = entry and + func = param.getFunctionDeclarationEntry() and + func.getParameterDeclarationEntry(i) = param and + before = entry.getLocation() and + ( + after = func.getParameterDeclarationEntry(i + 1).getLocation() + or + not exists(ParameterDeclarationEntry afterParam | + afterParam = func.getParameterDeclarationEntry(i + 1) + ) and + after = func.getBlock().getLocation() + ) + ) and + before.isBefore(inner, _) and + inner.isBefore(after, _) + or + exists(DeclStmt s | + s.getADeclaration() = entry.getDeclaration() and + before = s.getLocation() and + after = before and + before.subsumes(inner) + ) + ) +} + +/** + * A declaration involving a pointer to a variably-modified type. + */ +class InvalidDeclaration extends DeclarationEntry { + Expr sizeExpr; + CandidateVlaType vlaType; + // `before` and `after` are captured for debugging, see doc comment for + // `declarationSubsumes`. + Location before; + Location after; + + InvalidDeclaration() { + sizeExpr = any(VlaDimensionStmt vla).getDimensionExpr() and + declarationSubsumes(this, sizeExpr.getLocation(), before, after) and + ( + if this instanceof ParameterDeclarationEntry + then vlaType = this.getType().(VariablyModifiedTypeIfAdjusted).getInnerVlaType() + else vlaType = this.getType().(VariablyModifiedTypeIfUnadjusted).getInnerVlaType() + ) and + // Capture only pointers to VLA types, not raw VLA types. + not vlaType = this.getType() + } + + Expr getSizeExpr() { result = sizeExpr } + + CandidateVlaType getVlaType() { result = vlaType } +} + +from InvalidDeclaration v, string declstr, string adjuststr, string relationstr +where + not isExcluded(v, InvalidMemory3Package::pointersToVariablyModifiedArrayTypesUsedQuery()) and + ( + if v instanceof ParameterDeclarationEntry + then declstr = "Parameter " + else + if v instanceof VariableDeclarationEntry + then declstr = "Variable " + else declstr = "Declaration " + ) and + ( + if + v instanceof ParameterDeclarationEntry and + v.getType() instanceof ParameterAdjustedVariablyModifiedType + then adjuststr = "adjusted to" + else adjuststr = "declared with" + ) and + ( + if v.getType().(PointerType).getBaseType() instanceof CandidateVlaType + then relationstr = "pointer to" + else relationstr = "with inner" + ) +select v, + declstr + v.getName() + " is " + adjuststr + " variably-modified type, " + relationstr + + " variable length array of non constant size $@ and element type '" + + v.getVlaType().getVariableBaseType() + "'", v.getSizeExpr(), v.getSizeExpr().toString() diff --git a/c/misra/src/rules/RULE-18-8/VariableLengthArrayTypesUsed.ql b/c/misra/src/rules/RULE-18-8/VariableLengthArrayTypesUsed.ql index a7c25ed35e..96fbf697af 100644 --- a/c/misra/src/rules/RULE-18-8/VariableLengthArrayTypesUsed.ql +++ b/c/misra/src/rules/RULE-18-8/VariableLengthArrayTypesUsed.ql @@ -15,34 +15,15 @@ import cpp import codingstandards.c.misra -/** - * A variable length array (VLA) - * ie an array where the size - * is not an integer constant expression - */ -class VariableLengthArray extends VariableDeclarationEntry { - VariableLengthArray() { - //VLAs will not have: static/extern specifiers (compilation error) - not this.hasSpecifier("static") and - not this.hasSpecifier("extern") and - //VLAs are not allowed to be initialized - not this.getDeclaration().hasInitializer() and - exists(ArrayType a | - //a.hasArraySize() does not catch multidimensional VLAs like a[1][] - a.toString().matches("%[]%") and - this.getUnspecifiedType() = a and - //variable length array is one declared in block or function prototype - ( - this.getDeclaration().getParentScope() instanceof Function or - this.getDeclaration().getParentScope() instanceof BlockStmt - ) - ) - } -} - -from VariableLengthArray v +from VlaDeclStmt v, Expr size, ArrayType arrayType, string typeStr where not isExcluded(v, Declarations7Package::variableLengthArrayTypesUsedQuery()) and - //an exception, argv in : int main(int argc, char *argv[]) - not v.getDeclaration().getParentScope().(Function).hasName("main") -select v, "Variable length array declared." + size = v.getVlaDimensionStmt(0).getDimensionExpr() and + ( + arrayType = v.getVariable().getType() + or + arrayType = v.getType().getUnspecifiedType() + ) and + typeStr = arrayType.getBaseType().toString() +select v, "Variable length array of element type '" + typeStr + "' with non-constant size $@.", + size, size.toString() diff --git a/c/misra/src/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.ql b/c/misra/src/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.ql new file mode 100644 index 0000000000..7df4e5371c --- /dev/null +++ b/c/misra/src/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.ql @@ -0,0 +1,86 @@ +/** + * @id c/misra/array-to-pointer-conversion-of-temporary-object + * @name RULE-18-9: An object with temporary lifetime shall not undergo array to pointer conversion + * @description Modifying or accessing elements of an array with temporary lifetime that has been + * converted to a pointer will result in undefined behavior. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-18-9 + * external/misra/c/2012/amendment3 + * correctness + * security + * external/misra/obligation/required + */ + +import cpp +import codingstandards.c.misra +import codingstandards.cpp.lifetimes.CLifetimes + +/** + * Get the expression(s) whose value is "used" by this expression. + * + * For instance, `(x)` does not use any values, but `x + y` uses `x` and `y`. + * + * A pointer-to-array conversion does not need to be flagged if the result of + * that conversion is not used or stored. + */ +Expr usedValuesOf(Expr expr) { + result = expr.(BinaryOperation).getLeftOperand() + or + result = expr.(BinaryOperation).getRightOperand() + or + result = expr.(UnaryOperation).getOperand() + or + result = expr.(ConditionalExpr).getCondition() + or + result = expr.(Call).getAnArgument() +} + +/** + * Get the expression(s) whose value is stored by this declaration. + * + * A pointer-to-array conversion does not need to be flagged if the result of + * that conversion is not used or stored. + */ +predicate isStored(Expr e) { + e = any(VariableDeclarationEntry d).getDeclaration().getInitializer().getExpr() + or + e = any(ClassAggregateLiteral l).getAFieldExpr(_) +} + +/** + * Find expressions that defer their value directly to an inner expression + * value. + * + * When an array is on the rhs of a comma expr, or in the then/else branch of a + * ternary expr, and the result us used as a pointer, then the ArrayToPointer + * conversion is marked inside comma expr/ternary expr, on the operands. These + * conversions are only non-compliant if they flow into an operation or store. + * + * Full flow analysis with localFlowStep should not be necessary, and may cast a + * wider net than needed for some queries, potentially resulting in false + * positives. + */ +Expr temporaryObjectFlowStep(Expr e) { + e = result.(CommaExpr).getRightOperand() + or + e = result.(ConditionalExpr).getThen() + or + e = result.(ConditionalExpr).getElse() +} + +from + TemporaryLifetimeArrayAccess fa, TemporaryLifetimeExpr temporary, + ArrayToPointerConversion conversion +where + not isExcluded(conversion, InvalidMemory3Package::arrayToPointerConversionOfTemporaryObjectQuery()) and + fa.getTemporary() = temporary and + conversion.getExpr() = fa and + ( + temporaryObjectFlowStep*(conversion.getExpr()) = usedValuesOf(any(Expr e)) + or + isStored(temporaryObjectFlowStep*(conversion.getExpr())) + ) +select conversion, "Array to pointer conversion of array $@ from temporary object $@", + fa.getTarget(), fa.getTarget().getName(), temporary, temporary.toString() diff --git a/c/misra/src/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.ql b/c/misra/src/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.ql new file mode 100644 index 0000000000..f8a341b9bd --- /dev/null +++ b/c/misra/src/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.ql @@ -0,0 +1,59 @@ +/** + * @id c/misra/modifiable-l-value-subscripted-with-temporary-lifetime + * @name RULE-18-9: Usage of the subscript operator on an object with temporary lifetime shall not return a modifiable value + * @description Modifying elements of an array with temporary lifetime will result in undefined + * behavior. + * @kind problem + * @precision high + * @problem.severity error + * @tags external/misra/id/rule-18-9 + * external/misra/c/2012/amendment3 + * correctness + * security + * external/misra/obligation/required + */ + +import cpp +import codingstandards.c.misra +import codingstandards.cpp.lifetimes.CLifetimes + +class TemporaryLifetimeArrayExpr extends ArrayExpr { + TemporaryLifetimeArrayAccess member; + Type elementType; + + TemporaryLifetimeArrayExpr() { + member = getArrayBase() and + elementType = member.getType().(ArrayType).getBaseType() + or + exists(TemporaryLifetimeArrayExpr inner | + inner = getArrayBase() and + member = inner.getMember() and + elementType = inner.getElementType().(ArrayType).getBaseType() + ) + } + + TemporaryLifetimeArrayAccess getMember() { result = member } + + Type getElementType() { result = elementType } +} + +predicate usedAsModifiableLvalue(Expr expr) { + exists(Assignment parent | parent.getLValue() = expr) + or + exists(CrementOperation parent | parent.getOperand() = expr) + or + exists(AddressOfExpr parent | parent.getOperand() = expr) + or + exists(FieldAccess parent | parent.getQualifier() = expr and usedAsModifiableLvalue(parent)) +} + +from TemporaryLifetimeArrayExpr expr, TemporaryLifetimeArrayAccess member +where + not isExcluded(expr, + InvalidMemory3Package::modifiableLValueSubscriptedWithTemporaryLifetimeQuery()) and + member = expr.getMember() and + not expr.isUnevaluated() and + usedAsModifiableLvalue(expr) +select expr, + "Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ ", + member, member.getTarget().getName(), member.getTemporary(), member.getTemporary().toString() diff --git a/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.expected b/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.expected new file mode 100644 index 0000000000..76b3da5eb0 --- /dev/null +++ b/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.expected @@ -0,0 +1,17 @@ +| test.c:17:11:17:12 | definition of p5 | Parameter p5 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int' | test.c:17:15:17:16 | p0 | p0 | +| test.c:18:11:18:12 | definition of p6 | Parameter p6 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:18:18:18:19 | p0 | p0 | +| test.c:19:11:19:12 | definition of p7 | Parameter p7 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int[2]' | test.c:19:15:19:16 | p0 | p0 | +| test.c:20:11:20:12 | definition of p8 | Parameter p8 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int[]' | test.c:20:15:20:16 | p0 | p0 | +| test.c:20:11:20:12 | definition of p8 | Parameter p8 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int[]' | test.c:20:19:20:20 | p0 | p0 | +| test.c:24:12:24:13 | definition of p9 | Parameter p9 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int *' | test.c:24:16:24:17 | p0 | p0 | +| test.c:25:13:25:15 | definition of p10 | Parameter p10 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int *' | test.c:25:18:25:19 | p0 | p0 | +| test.c:28:12:28:14 | definition of p11 | Parameter p11 is adjusted to variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:28:21:28:22 | p0 | p0 | +| test.c:32:17:32:19 | definition of p13 | Parameter p13 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'const int' | test.c:32:22:32:23 | p0 | p0 | +| test.c:33:17:33:19 | definition of p14 | Parameter p14 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:33:22:33:23 | p0 | p0 | +| test.c:40:12:40:14 | definition of p17 | Parameter p17 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:40:24:40:25 | p0 | p0 | +| test.c:41:14:41:16 | definition of p18 | Parameter p18 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:41:27:41:28 | p0 | p0 | +| test.c:68:9:68:11 | definition of p27 | Parameter p27 is adjusted to variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:68:13:68:14 | p0 | p0 | +| test.c:68:9:68:11 | definition of p27 | Parameter p27 is adjusted to variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:68:17:68:18 | p0 | p0 | +| test.c:74:8:74:9 | definition of l3 | Variable l3 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int' | test.c:74:12:74:13 | p0 | p0 | +| test.c:79:15:79:16 | definition of l4 | Variable l4 is declared with variably-modified type, pointer to variable length array of non constant size $@ and element type 'int' | test.c:79:19:79:20 | p0 | p0 | +| test.c:84:16:84:18 | declaration of td3 | Declaration td3 is declared with variably-modified type, with inner variable length array of non constant size $@ and element type 'int' | test.c:84:21:84:22 | p0 | p0 | diff --git a/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.qlref b/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.qlref new file mode 100644 index 0000000000..1a60cfacca --- /dev/null +++ b/c/misra/test/rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.qlref @@ -0,0 +1 @@ +rules/RULE-18-10/PointersToVariablyModifiedArrayTypesUsed.ql \ No newline at end of file diff --git a/c/misra/test/rules/RULE-18-10/test.c b/c/misra/test/rules/RULE-18-10/test.c new file mode 100644 index 0000000000..dbddbecec8 --- /dev/null +++ b/c/misra/test/rules/RULE-18-10/test.c @@ -0,0 +1,95 @@ +#define CONSTANT 1 + +int g1[3]; // COMPLIANT +int (*g2)[3]; // COMPLIANT +int (*g3)[CONSTANT]; // COMPLIANT + +void f1( + int p0, + + // Basic fixed length array types: + int p1[3], // COMPLIANT + int (*p2)[3], // COMPLIANT + int (*p3)[2][3], // COMPLIANT + int (*p4)[CONSTANT], // COMPLIANT + + // Basic pointers to VMTs: + int (*p5)[p0], // NON-COMPLIANT + int (*p6)[2][p0], // NON-COMPLIANT + int (*p7)[p0][2], // NON-COMPLIANT + int (*p8)[p0][p0], // NON-COMPLIANT + + // Types referring to pointers to VMTs: + // - pointer to pointer to VMT + int(*(*p9)[p0]), // NON-COMPLIANT + int(*(**p10)[p0]), // NON-COMPLIANT + + // - array of pointers to VMT + int (*(p11[3]))[p0], // NON-COMPLIANT + + // - const VMTs, const array-to-pointer adjustment + const int p12[p0], // COMPLIANT + const int (*p13)[p0], // NON-COMPLIANT + int (*const p14)[p0], // NON-COMPLIANT + + // - function types with argument that is a pointer to a VMT + int p15(int (*inner)[p0]), // NON-COMPLIANT[FALSE_NEGATIVE] + int (*p16)(int (*inner)[p0]), // NON-COMPLIANT[FALSE_NEGATIVE] + + // - function types that returns a pointer to a VMT + int (*(p17(void)))[p0], // NON-COMPLIANT + int (*((*p18)(void)))[p0], // NON-COMPLIANT + + // - structs cannot contain a VMT as a member. + struct { + int g1[3]; // COMPLIANT + int(*g2)[3]; // COMPLIANT + int(*g3)[CONSTANT]; // COMPLIANT + // Pointer to VMT (`int (*g4)[p0]`) is not allowed. + } p19, + + // - unions cannot contain a VMT as a member. + union { + int g1[3]; // COMPLIANT + int(*g2)[3]; // COMPLIANT + int(*g3)[CONSTANT]; // COMPLIANT + // Pointer to VMT (`int (*g4)[p0]`) is not allowed. + } p20, + + // Unknown array length types: + int p21[], // COMPLIANT + int p22[][], // COMPLIANT + int (*p23)[], // COMPLIANT + int (*p24)[2][], // COMPLIANT + int (*p25)[][2], // COMPLIANT + + // VLA types that are rewritten as pointers: + int p26[p0], // COMPLIANT + int p27[p0][p0] // NON-COMPLIANT +) { + // Local variables may contain pointers to VMTs: + int l0[p0]; // COMPLIANT + int(*l1)[]; // COMPLIANT + int(*l2)[3]; // COMPLIANT + int(*l3)[p0]; // NON-COMPLIANT + + int l6[10] = p23; + + // A pointer to a VMT may be declared `static`. + static int(*l4)[p0]; // NON-COMPLIANT + + // Block scope typedefs may refer to VMTs + typedef int(*td1)[3]; // COMPLIANT + typedef int(*td2)[]; // COMPLIANT + typedef int(*td3)[p0]; // NON-COMPLIANT + + td3 l5; // NON-COMPLIANT +} + +// Function prototypes may contain VMTs using '*' syntax: +void f2(int (*p1)[3], // COMPLIANT + int (*p2)[*], // NON-COMPLIANT[FALSE_NEGATIVE] + int (*p3)[2][*], // NON-COMPLIANT[FALSE_NEGATIVE] + int (*p4)[*][2], // NON-COMPLIANT[FALSE_NEGATIVE] + int (*p5)[*][*] // NON-COMPLIANT[FALSE_NEGATIVE] +); \ No newline at end of file diff --git a/c/misra/test/rules/RULE-18-8/VariableLengthArrayTypesUsed.expected b/c/misra/test/rules/RULE-18-8/VariableLengthArrayTypesUsed.expected index e9721ce642..24856619bf 100644 --- a/c/misra/test/rules/RULE-18-8/VariableLengthArrayTypesUsed.expected +++ b/c/misra/test/rules/RULE-18-8/VariableLengthArrayTypesUsed.expected @@ -1,5 +1,5 @@ -| test.c:3:19:3:20 | definition of pa | Variable length array declared. | -| test.c:6:7:6:8 | definition of a1 | Variable length array declared. | -| test.c:7:7:7:8 | definition of a2 | Variable length array declared. | -| test.c:8:7:8:8 | definition of a3 | Variable length array declared. | -| test.c:14:20:14:21 | definition of pa | Variable length array declared. | +| test.c:6:7:6:7 | VLA declaration | Variable length array of element type 'int' with non-constant size $@. | test.c:6:10:6:14 | ... + ... | ... + ... | +| test.c:7:7:7:7 | VLA declaration | Variable length array of element type 'int' with non-constant size $@. | test.c:7:10:7:10 | n | n | +| test.c:8:7:8:7 | VLA declaration | Variable length array of element type 'int[]' with non-constant size $@. | test.c:8:13:8:13 | n | n | +| test.c:12:7:12:7 | VLA declaration | Variable length array of element type 'int[1]' with non-constant size $@. | test.c:12:10:12:10 | n | n | +| test.c:18:15:18:15 | VLA declaration | Variable length array of element type 'int' with non-constant size $@. | test.c:18:26:18:26 | n | n | diff --git a/c/misra/test/rules/RULE-18-8/test.c b/c/misra/test/rules/RULE-18-8/test.c index 3a0a040f6d..c2f6027216 100644 --- a/c/misra/test/rules/RULE-18-8/test.c +++ b/c/misra/test/rules/RULE-18-8/test.c @@ -1,7 +1,7 @@ #define TEST 1 -void f(int n, int pa[1][n]) { // NON_COMPLIANT - int a[1]; // COMPLIANT +void f(int n) { + int a[1]; // COMPLIANT int x = 1; int a1[1 + x]; // NON_COMPLIANT - not integer constant expr int a2[n]; // NON_COMPLIANT @@ -9,7 +9,29 @@ void f(int n, int pa[1][n]) { // NON_COMPLIANT int a4[] = {1}; // COMPLIANT - not a VLA int a5[TEST]; // COMPLIANT int a6[1 + 1]; // COMPLIANT + int a7[n][1]; // NON_COMPLIANT + int(*a8)[n]; // COMPLIANT - pointer to VLA, see RULE-18-10 + + extern int e1[]; // COMPLIANT + + // A typedef is not a VLA. However, `VlaDeclStmt`s match the typedef. + typedef int vlaTypedef[n]; // COMPLIANT[FALSE_POSITIVE] + vlaTypedef t1; // NON_COMPLIANT[FALSE_NEGATIVE] } -void f1(int n, int pa[n]) { // NON_COMPLIANT -} \ No newline at end of file +void f1(int n, + // Parameter array types are adjusted to pointers + int p1[n], // COMPLIANT + // Pointers to variably-modified types are not VLAs. + int p2[n][n], + int p3[], // array of unknown length is converted to pointer + int p4[][] // array of unknown length are not VLAs. +) {} + +struct s { + // Structs must have at least one non-flexible array member. + int foo; + + // Flexible array members are not VLAs. + int flexibleArrayMember[]; // COMPLIANT +}; \ No newline at end of file diff --git a/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.expected b/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.expected new file mode 100644 index 0000000000..7d760dc4a6 --- /dev/null +++ b/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.expected @@ -0,0 +1,30 @@ +| test.c:45:3:45:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:45:3:45:8 | call to get_s1 | call to get_s1 | +| test.c:46:3:46:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:46:3:46:8 | call to get_s1 | call to get_s1 | +| test.c:47:7:47:24 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:47:7:47:12 | call to get_s1 | call to get_s1 | +| test.c:48:4:48:21 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:48:4:48:9 | call to get_s1 | call to get_s1 | +| test.c:49:4:49:21 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:49:4:49:9 | call to get_s1 | call to get_s1 | +| test.c:50:3:50:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:50:3:50:8 | call to get_s1 | call to get_s1 | +| test.c:51:3:51:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:51:3:51:8 | call to get_s1 | call to get_s1 | +| test.c:52:3:52:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:52:3:52:8 | call to get_s1 | call to get_s1 | +| test.c:53:3:53:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:53:3:53:8 | call to get_s1 | call to get_s1 | +| test.c:54:3:54:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:54:3:54:8 | call to get_s1 | call to get_s1 | +| test.c:55:8:55:25 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:55:8:55:13 | call to get_s1 | call to get_s1 | +| test.c:56:3:56:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:56:3:56:8 | call to get_s1 | call to get_s1 | +| test.c:57:8:57:25 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:57:8:57:13 | call to get_s1 | call to get_s1 | +| test.c:58:3:58:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:58:3:58:8 | call to get_s1 | call to get_s1 | +| test.c:59:3:59:20 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:59:3:59:8 | call to get_s1 | call to get_s1 | +| test.c:60:15:60:32 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:60:15:60:20 | call to get_s1 | call to get_s1 | +| test.c:61:16:61:33 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:61:16:61:21 | call to get_s1 | call to get_s1 | +| test.c:62:23:62:40 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:62:23:62:28 | call to get_s1 | call to get_s1 | +| test.c:63:7:63:24 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:63:7:63:12 | call to get_s1 | call to get_s1 | +| test.c:64:16:64:33 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:64:16:64:21 | call to get_s1 | call to get_s1 | +| test.c:65:15:65:32 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:65:15:65:20 | call to get_s1 | call to get_s1 | +| test.c:66:16:66:33 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:66:16:66:21 | call to get_s1 | call to get_s1 | +| test.c:67:23:67:40 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:67:23:67:28 | call to get_s1 | call to get_s1 | +| test.c:89:3:89:30 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:89:12:89:20 | member_s1 | member_s1 | +| test.c:90:3:90:36 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:90:3:90:26 | access to array | access to array | +| test.c:91:15:91:42 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:91:24:91:32 | member_s1 | member_s1 | +| test.c:92:15:92:48 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:92:15:92:38 | access to array | access to array | +| test.c:111:15:111:33 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:111:16:111:22 | ... = ... | ... = ... | +| test.c:113:15:113:37 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:113:16:113:26 | ... ? ... : ... | ... ? ... : ... | +| test.c:114:15:114:31 | array to pointer conversion | Array to pointer conversion of array $@ from temporary object $@ | test.c:3:13:3:21 | const_arr | const_arr | test.c:114:16:114:20 | ... , ... | ... , ... | diff --git a/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.qlref b/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.qlref new file mode 100644 index 0000000000..d2db40e77c --- /dev/null +++ b/c/misra/test/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.qlref @@ -0,0 +1 @@ +rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.ql \ No newline at end of file diff --git a/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.expected b/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.expected new file mode 100644 index 0000000000..ae140dcd59 --- /dev/null +++ b/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.expected @@ -0,0 +1,15 @@ +| test.c:80:3:80:17 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:80:12:80:14 | arr | arr | test.c:80:3:80:8 | call to get_s1 | call to get_s1 | +| test.c:81:3:81:17 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:81:12:81:14 | arr | arr | test.c:81:3:81:8 | call to get_s1 | call to get_s1 | +| test.c:82:3:82:17 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:82:12:82:14 | arr | arr | test.c:82:3:82:8 | call to get_s1 | call to get_s1 | +| test.c:83:3:83:17 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:83:12:83:14 | arr | arr | test.c:83:3:83:8 | call to get_s1 | call to get_s1 | +| test.c:84:5:84:19 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:84:14:84:16 | arr | arr | test.c:84:5:84:10 | call to get_s1 | call to get_s1 | +| test.c:93:3:93:27 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:93:22:93:24 | arr | arr | test.c:93:12:93:20 | member_s1 | member_s1 | +| test.c:94:3:94:27 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:94:22:94:24 | arr | arr | test.c:94:3:94:20 | access to array | access to array | +| test.c:137:3:137:23 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:137:12:137:20 | arr_union | arr_union | test.c:137:3:137:8 | call to get_s3 | call to get_s3 | +| test.c:138:3:138:24 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:138:12:138:21 | arr_struct | arr_struct | test.c:138:3:138:8 | call to get_s3 | call to get_s3 | +| test.c:139:3:139:24 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:139:12:139:21 | arr_struct | arr_struct | test.c:139:3:139:8 | call to get_s3 | call to get_s3 | +| test.c:140:3:140:24 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:140:12:140:21 | arr_struct | arr_struct | test.c:140:3:140:8 | call to get_s3 | call to get_s3 | +| test.c:141:3:141:24 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:141:12:141:21 | arr_struct | arr_struct | test.c:141:3:141:8 | call to get_s3 | call to get_s3 | +| test.c:142:4:142:25 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:142:13:142:22 | arr_struct | arr_struct | test.c:142:4:142:9 | call to get_s3 | call to get_s3 | +| test.c:146:3:146:22 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:146:12:146:16 | arr2d | arr2d | test.c:146:3:146:8 | call to get_s3 | call to get_s3 | +| test.c:147:4:147:20 | access to array | Modifiable lvalue produced by subscripting array member $@ of temporary lifetime object $@ | test.c:147:13:147:17 | arr2d | arr2d | test.c:147:4:147:9 | call to get_s3 | call to get_s3 | diff --git a/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.qlref b/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.qlref new file mode 100644 index 0000000000..c1fb0bd2d4 --- /dev/null +++ b/c/misra/test/rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.qlref @@ -0,0 +1 @@ +rules/RULE-18-9/ModifiableLValueSubscriptedWithTemporaryLifetime.ql \ No newline at end of file diff --git a/c/misra/test/rules/RULE-18-9/test.c b/c/misra/test/rules/RULE-18-9/test.c new file mode 100644 index 0000000000..f2fb44fdc9 --- /dev/null +++ b/c/misra/test/rules/RULE-18-9/test.c @@ -0,0 +1,151 @@ +struct s1 { + int m1; + const int const_arr[10]; + int arr[10]; +}; + +struct s1 get_s1(); + +struct s2 { + struct s1 member_s1; + struct s1 const const_s1_arr[10]; + struct s1 *s1ptr; + struct s1 s1_arr[10]; +}; + +struct s2 get_s2(); +struct s2 *get_s2_ptr(); + +void use_int(int x) {} +void use_int_ptr(int *x) {} + +void f(void) { + struct s1 l1; + + // Auto lifetime, allowed: + l1.const_arr + 1; // COMPLIANT + l1.const_arr - 1; // COMPLIANT + &l1.const_arr; // COMPLIANT + use_int_ptr(l1.const_arr); // COMPLIANT + l1.arr[0] = 1; // COMPLIANT + + // Extern lifetime, allowed: + extern struct s1 g1; + g1.const_arr + 1; // COMPLIANT + g1.const_arr - 1; // COMPLIANT + &g1.const_arr; // COMPLIANT + use_int_ptr(g1.const_arr); // COMPLIANT + g1.arr[0] = 1; // COMPLIANT + + // Temporary lifetime, no conversion: + get_s1().const_arr; // COMPLIANT - not used as a value. + get_s1().m1 + 1; // COMPLIANT - not an array. + + // Temporary lifetime, array to pointer conversions: + get_s1().const_arr + 1; // NON-COMPLIANT + get_s1().const_arr - 1; // NON-COMPLIANT + 1 + get_s1().const_arr; // NON-COMPLIANT + *get_s1().const_arr; // NON-COMPLIANT + !get_s1().const_arr; // NON-COMPLIANT + get_s1().const_arr < 1; // NON-COMPLIANT + get_s1().const_arr <= 1; // NON-COMPLIANT + get_s1().const_arr > 1; // NON-COMPLIANT + get_s1().const_arr >= 1; // NON-COMPLIANT + get_s1().const_arr == 1; // NON-COMPLIANT + 1 == get_s1().const_arr; // NON-COMPLIANT + get_s1().const_arr && 1; // NON-COMPLIANT + 1 && get_s1().const_arr; // NON-COMPLIANT + get_s1().const_arr || 1; // NON-COMPLIANT + get_s1().const_arr ? 1 : 1; // NON-COMPLIANT + use_int_ptr(get_s1().const_arr); // NON-COMPLIANT + use_int_ptr((get_s1().const_arr)); // NON-COMPLIANT + use_int_ptr((void *)get_s1().const_arr); // NON-COMPLIANT + (1, get_s1().const_arr) + 1; // NON-COMPLIANT + int *local = get_s1().const_arr; // NON-COMPLIANT + (struct s1){get_s1().const_arr}; // NON-COMPLIANT + (struct s2){{get_s1().const_arr}}; // NON-COMPLIANT + struct s1 local2 = {get_s1().const_arr}; // NON-COMPLIANT + + // Results are not 'used' as a value. + (void *)get_s1().const_arr; // COMPLIANT + sizeof(get_s1().const_arr); // COMPLIANT + get_s1().const_arr, 1; // COMPLIANT + 1, get_s1().const_arr; // COMPLIANT + (get_s1().const_arr); // COMPLIANT + + get_s1().const_arr[0]; // COMPLIANT - subscripted value not modifiable + get_s1().arr[0]; // COMPLIANT - subscripted value not used as modifiable + use_int(get_s1().const_arr[0]); // COMPLIANT + use_int(get_s1().arr[0]); // COMPLIANT + get_s1().arr[0] = 1; // NON-COMPLIANT + get_s1().arr[0] -= 1; // NON-COMPLIANT + get_s1().arr[0]--; // NON-COMPLIANT + get_s1().arr[0]++; // NON-COMPLIANT + &(get_s1().arr[0]); // NON-COMPLIANT + + struct s2 l2; + + // Deeper accesses: + get_s2().member_s1.const_arr + 1; // NON-COMPLIANT + get_s2().const_s1_arr[0].const_arr + 1; // NON-COMPLIANT + use_int_ptr(get_s2().member_s1.const_arr); // NON-COMPLIANT + use_int_ptr(get_s2().const_s1_arr[0].const_arr); // NON-COMPLIANT + get_s2().member_s1.arr[0] = 1; // NON-COMPLIANT + get_s2().s1_arr[0].arr[0] = 1; // NON-COMPLIANT + get_s2().member_s1.const_arr[0]; // COMPLIANT + get_s2().const_s1_arr[0].const_arr[0]; // COMPLIANT + get_s2().s1_arr[0].const_arr[0]; // COMPLIANT + get_s2().s1ptr->const_arr[0]; // COMPLIANT + use_int(get_s2().member_s1.const_arr[0]); // COMPLIANT + use_int(get_s2().const_s1_arr[0].const_arr[0]); // COMPLIANT + use_int(get_s2().s1ptr->const_arr[0]); // COMPLIANT + + // Pointer members of a struct don't have temporary lifetime. + get_s2().s1ptr->const_arr + 1; // COMPLIANT + use_int_ptr(get_s2().s1ptr->const_arr); // COMPLIANT + get_s2().s1ptr->arr[0] = 1; // COMPLIANT + get_s2_ptr()->member_s1.const_arr + 1; // COMPLIANT + get_s2_ptr()->member_s1.arr[0] = 1; // COMPLIANT + + // Other types of non-lvalue types + use_int_ptr((l1 = l1).const_arr); // NON-COMPLIANT + use_int_ptr(((struct s1)l1).const_arr); // NON-COMPLIANT[FALSE_NEGATIVE] + use_int_ptr((1 ? l1 : l1).const_arr); // NON-COMPLIANT + use_int_ptr((0, l1).const_arr); // NON-COMPLIANT + use_int_ptr((l2.s1ptr++)->const_arr); // COMPLIANT + use_int_ptr((--l2.s1ptr)->const_arr); // COMPLIANT +} + +// Additional modifiable lvalue tests +struct s3 { + struct s4 { + struct s5 { + struct s6 { + int x; + } m1; + } m1; + } arr_struct[1]; + + union u1 { + int x; + } arr_union[1]; + + int arr2d[1][1]; +} get_s3(); + +void f2(void) { + get_s3().arr_union[0].x = 1; // NON_COMPLIANT + get_s3().arr_struct[0] = (struct s4){0}; // NON_COMPLIANT + get_s3().arr_struct[0].m1 = (struct s5){0}; // NON_COMPLIANT + get_s3().arr_struct[0].m1.m1 = (struct s6){0}; // NON_COMPLIANT + get_s3().arr_struct[0].m1.m1.x = 1; // NON_COMPLIANT + &get_s3().arr_struct[0].m1.m1.x; // NON_COMPLIANT + get_s3().arr_struct[0].m1.m1.x + 1; // COMPLIANT + + get_s3().arr2d[1][1] + 1; // COMPLIANT + get_s3().arr2d[1][1] = 1; // NON_COMPLIANT + &get_s3().arr2d[1]; // NON_COMPLIANT + // The following cases are missing an ArrayToPointerConversion + use_int_ptr(get_s3().arr2d[1]); // NON_COMPLIANT[FALSE NEGATIVE] + get_s3().arr2d[1] + 1; // NON_COMPLIANT[FALSE NEGATIVE] +} \ No newline at end of file diff --git a/change_notes/2024-10-10-rule-18-8-vla-rule-changes-amendment4.md b/change_notes/2024-10-10-rule-18-8-vla-rule-changes-amendment4.md new file mode 100644 index 0000000000..f465836052 --- /dev/null +++ b/change_notes/2024-10-10-rule-18-8-vla-rule-changes-amendment4.md @@ -0,0 +1,4 @@ +- `RULE-18-8` - `VariableLengthArrayTypesUsed.ql`: + - Implement changes declared in MISRA C 2012 Amendment 4. This rule now only bans the use of VLA objects. Rules restricting the use of VLA types -- specifically, pointers to VLA types -- are now implemented in `RULE-18-10`. +- `EXP-35-C` - `DoNotModifyObjectsWithTemporaryLifetime.ql` + - Refactor component into a shared library, should not have any effect on rule results. \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/Clvalues.qll b/cpp/common/src/codingstandards/cpp/Clvalues.qll new file mode 100644 index 0000000000..73fcd65eb1 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/Clvalues.qll @@ -0,0 +1,17 @@ +import cpp + +/** + * An lvalue in C (as opposed to C++). + * + * Note that `Expr.isLValue()` matches for C++ lvalues, which is a larger set + * than the set of C lvalues. + */ +predicate isCLValue(Expr expr) { + expr instanceof PointerFieldAccess + or + expr.isLValue() and + not expr instanceof ConditionalExpr and + not expr instanceof AssignExpr and + not expr instanceof CommaExpr and + not exists(Cast c | c = expr.getConversion*()) +} diff --git a/cpp/common/src/codingstandards/cpp/VariablyModifiedTypes.qll b/cpp/common/src/codingstandards/cpp/VariablyModifiedTypes.qll new file mode 100644 index 0000000000..730a52d763 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/VariablyModifiedTypes.qll @@ -0,0 +1,143 @@ +import cpp + +/** + * A candidate to be a variably length array type (VLA). + * + * This class represents a candidate only, for a few reasons. + * + * Firstly, the `ArrayType` class does not know when it has variable size, so + * this class matches all array types with unknown size, including `x[]` which + * is not a VLA. To determine the difference, we must compare locations between + * where * these types are declared, and the location of `VlaDecl`s etc. + * + * Secondly, function parameters of array type are adjusted into pointers. This + * means that while a parameter type can be a `CandidateVlaType`, that + * parameter is not a VLA. + */ +class CandidateVlaType extends ArrayType { + CandidateVlaType() { not hasArraySize() } + + Type getVariableBaseType() { result = this.getBaseType() } +} + +/** + * A type that is a variably modified type (VMT) if it does not undergo + * parameter type adjustment. + * + * A variably modified type is a VLA type, or a type containing a VMT type, for + * instance, a pointer to a VLA or a pointer to a pointer to a VLA. + * + * Function parameters and function type parameters of type `T[]` are adjusted + * to type `T*`, which can turn VMTs into non-VMTs. To check if a parameter + * type is a VMT, use `VariablyModifiedTypeIfAdjusted`. + */ +class VariablyModifiedTypeIfUnadjusted extends Type { + CandidateVlaType innerVlaType; + + VariablyModifiedTypeIfUnadjusted() { + // Take care that `int[x][y]` only matches for `innerVlaType = int[y]`. + if this instanceof CandidateVlaType + then innerVlaType = this + else innerVlaType = this.(NoAdjustmentVariablyModifiedType).getInnerVlaType() + } + + CandidateVlaType getInnerVlaType() { result = innerVlaType } +} + +/** + * A type that is a variably modified type (VMT) if it undergoes parameter type + * adjustment. + * + * A variably modified type is a VLA type, or a type containing a VMT type, for + * instance, a pointer to a VLA or a pointer to a pointer to a VLA. + * + * Function parameters and function type parameters of type `T[]` are adjusted + * to type `T*`, which can turn VMTs into non-VMTs. To check if a non-parameter + * type (for instance, the type of a local variable) is a VMT, use + * `VariablyModifiedTypeIfUnadjusted`. + */ +class VariablyModifiedTypeIfAdjusted extends Type { + CandidateVlaType innerVlaType; + + VariablyModifiedTypeIfAdjusted() { + innerVlaType = this.(ParameterAdjustedVariablyModifiedType).getInnerVlaType() + or + innerVlaType = this.(NoAdjustmentVariablyModifiedType).getInnerVlaType() + } + + CandidateVlaType getInnerVlaType() { result = innerVlaType } +} + +/** + * A variably modified type candidate which is unaffected by parameter type + * adjustment (from `T[]` to `*T`). + * + * Parameter adjustment (from `T[]` to `*T`) occurs on all function parameter + * types for exactly one level of depth. + * + * A variably-modified type (VMT) is a type which includes an inner type that is + * a VLA type. That is to say, a pointer to a VLA is a VMT, and a pointer to a + * VMT is a VMT. + * + * Note: This class does *not* match all VLA types. While VLA types *are* VMTs, + * VMTs can be parameter-adjusted to pointers, which are not VLA types. This + * class *will* match multidimensional VLAs, as those are adjusted to pointers + * to VLAs, and pointers to VLAs are VMTs. + */ +class NoAdjustmentVariablyModifiedType extends Type { + CandidateVlaType vlaType; + + NoAdjustmentVariablyModifiedType() { + exists(Type innerType | + ( + innerType = this.(PointerType).getBaseType() + or + innerType = this.(ArrayType).getBaseType() + or + innerType = this.(RoutineType).getReturnType() + or + innerType = this.(RoutineType).getAParameterType() + or + innerType = this.(FunctionPointerType).getReturnType() + or + innerType = this.(TypedefType).getBaseType() + or + innerType = this.(SpecifiedType).getBaseType() + ) and + vlaType = innerType.(VariablyModifiedTypeIfUnadjusted).getInnerVlaType() + ) + or + vlaType = + this.(FunctionPointerType) + .getAParameterType() + .(VariablyModifiedTypeIfAdjusted) + .getInnerVlaType() + or + vlaType = + this.(RoutineType).getAParameterType().(VariablyModifiedTypeIfAdjusted).getInnerVlaType() + } + + CandidateVlaType getInnerVlaType() { result = vlaType } +} + +/** + * An array type that adjusts to a variably-modified type (a type which is or + * contains a VLA type) when it is a parameter type. + * + * A variably-modified type (VMT) is a VLA type or a type which has an inner type + * that is a VMT type, for instance, a pointer to a VLA type. + * + * Parameter adjustment occurs on all function parameter types, changing type + * `T[]` to `*T` for exactly one level of depth. Therefore, a VLA type will not + * be a VLA type/VMT after parameter adjustment, unless it is an array of VMTs, + * such that it parameter adjustment produces a pointer to a VMT. + */ +class ParameterAdjustedVariablyModifiedType extends ArrayType { + CandidateVlaType innerVlaType; + + ParameterAdjustedVariablyModifiedType() { + innerVlaType = getBaseType().(VariablyModifiedTypeIfUnadjusted).getInnerVlaType() + } + + CandidateVlaType getInnerVlaType() { result = innerVlaType } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/c/InvalidMemory3.qll b/cpp/common/src/codingstandards/cpp/exclusions/c/InvalidMemory3.qll new file mode 100644 index 0000000000..c4e39882ec --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/c/InvalidMemory3.qll @@ -0,0 +1,61 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype InvalidMemory3Query = + TPointersToVariablyModifiedArrayTypesUsedQuery() or + TArrayToPointerConversionOfTemporaryObjectQuery() or + TModifiableLValueSubscriptedWithTemporaryLifetimeQuery() + +predicate isInvalidMemory3QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `pointersToVariablyModifiedArrayTypesUsed` query + InvalidMemory3Package::pointersToVariablyModifiedArrayTypesUsedQuery() and + queryId = + // `@id` for the `pointersToVariablyModifiedArrayTypesUsed` query + "c/misra/pointers-to-variably-modified-array-types-used" and + ruleId = "RULE-18-10" and + category = "mandatory" + or + query = + // `Query` instance for the `arrayToPointerConversionOfTemporaryObject` query + InvalidMemory3Package::arrayToPointerConversionOfTemporaryObjectQuery() and + queryId = + // `@id` for the `arrayToPointerConversionOfTemporaryObject` query + "c/misra/array-to-pointer-conversion-of-temporary-object" and + ruleId = "RULE-18-9" and + category = "required" + or + query = + // `Query` instance for the `modifiableLValueSubscriptedWithTemporaryLifetime` query + InvalidMemory3Package::modifiableLValueSubscriptedWithTemporaryLifetimeQuery() and + queryId = + // `@id` for the `modifiableLValueSubscriptedWithTemporaryLifetime` query + "c/misra/modifiable-l-value-subscripted-with-temporary-lifetime" and + ruleId = "RULE-18-9" and + category = "required" +} + +module InvalidMemory3Package { + Query pointersToVariablyModifiedArrayTypesUsedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `pointersToVariablyModifiedArrayTypesUsed` query + TQueryC(TInvalidMemory3PackageQuery(TPointersToVariablyModifiedArrayTypesUsedQuery())) + } + + Query arrayToPointerConversionOfTemporaryObjectQuery() { + //autogenerate `Query` type + result = + // `Query` type for `arrayToPointerConversionOfTemporaryObject` query + TQueryC(TInvalidMemory3PackageQuery(TArrayToPointerConversionOfTemporaryObjectQuery())) + } + + Query modifiableLValueSubscriptedWithTemporaryLifetimeQuery() { + //autogenerate `Query` type + result = + // `Query` type for `modifiableLValueSubscriptedWithTemporaryLifetime` query + TQueryC(TInvalidMemory3PackageQuery(TModifiableLValueSubscriptedWithTemporaryLifetimeQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll index 3833533d50..6bc908ebb2 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll @@ -38,6 +38,7 @@ import IO4 import IntegerOverflow import InvalidMemory1 import InvalidMemory2 +import InvalidMemory3 import Language1 import Language2 import Language3 @@ -114,6 +115,7 @@ newtype TCQuery = TIntegerOverflowPackageQuery(IntegerOverflowQuery q) or TInvalidMemory1PackageQuery(InvalidMemory1Query q) or TInvalidMemory2PackageQuery(InvalidMemory2Query q) or + TInvalidMemory3PackageQuery(InvalidMemory3Query q) or TLanguage1PackageQuery(Language1Query q) or TLanguage2PackageQuery(Language2Query q) or TLanguage3PackageQuery(Language3Query q) or @@ -190,6 +192,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isIntegerOverflowQueryMetadata(query, queryId, ruleId, category) or isInvalidMemory1QueryMetadata(query, queryId, ruleId, category) or isInvalidMemory2QueryMetadata(query, queryId, ruleId, category) or + isInvalidMemory3QueryMetadata(query, queryId, ruleId, category) or isLanguage1QueryMetadata(query, queryId, ruleId, category) or isLanguage2QueryMetadata(query, queryId, ruleId, category) or isLanguage3QueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/lifetimes/CLifetimes.qll b/cpp/common/src/codingstandards/cpp/lifetimes/CLifetimes.qll new file mode 100644 index 0000000000..d27034f50d --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/lifetimes/CLifetimes.qll @@ -0,0 +1,48 @@ +import cpp +import codingstandards.cpp.Clvalues + +/** + * A struct or union type that contains an array type. + */ +class StructOrUnionTypeWithArrayField extends Struct { + StructOrUnionTypeWithArrayField() { + this.getAField().getUnspecifiedType() instanceof ArrayType + or + // nested struct or union containing an array type + this.getAField().getUnspecifiedType().(Struct) instanceof StructOrUnionTypeWithArrayField + } +} + +/** + * A non-lvalue expression with struct or or union type that has a field member + * of array type, has a temporary lifetime. + * + * The array members are also part of that object, and thus also have temporary + * lifetime. + */ +class TemporaryLifetimeExpr extends Expr { + TemporaryLifetimeExpr() { + getUnconverted().getUnspecifiedType() instanceof StructOrUnionTypeWithArrayField and + not isCLValue(this) + or + this.(ArrayExpr).getArrayBase() instanceof TemporaryLifetimeArrayAccess + } +} + +/** + * A field access on a temporary object that returns an array member. + */ +class TemporaryLifetimeArrayAccess extends FieldAccess { + // The temporary lifetime object which owns the array that is returned. + TemporaryLifetimeExpr temporary; + + TemporaryLifetimeArrayAccess() { + getQualifier().getUnconverted() = temporary and + getUnspecifiedType() instanceof ArrayType + } + + /** + * Get the temporary lifetime object which own the array that is returned. + */ + Expr getTemporary() { result = temporary } +} diff --git a/rule_packages/c/InvalidMemory3.json b/rule_packages/c/InvalidMemory3.json new file mode 100644 index 0000000000..feeb8b2b47 --- /dev/null +++ b/rule_packages/c/InvalidMemory3.json @@ -0,0 +1,59 @@ +{ + "MISRA-C-2012": { + "RULE-18-10": { + "properties": { + "obligation": "mandatory" + }, + "queries": [ + { + "description": "Pointers to variably-modified array types shall not be used, as these pointer types are frequently incompatible with other fixed or variably sized arrays, resulting in undefined behavior.", + "kind": "problem", + "name": "Pointers to variably-modified array types shall not be used", + "precision": "high", + "severity": "error", + "short_name": "PointersToVariablyModifiedArrayTypesUsed", + "tags": [ + "external/misra/c/2012/amendment4", + "correctness", + "security" + ] + } + ], + "title": "Pointers to variably-modified array types shall not be used" + }, + "RULE-18-9": { + "properties": { + "obligation": "required" + }, + "queries": [ + { + "description": "Modifying or accessing elements of an array with temporary lifetime that has been converted to a pointer will result in undefined behavior.", + "kind": "problem", + "name": "An object with temporary lifetime shall not undergo array to pointer conversion", + "precision": "high", + "severity": "error", + "short_name": "ArrayToPointerConversionOfTemporaryObject", + "tags": [ + "external/misra/c/2012/amendment3", + "correctness", + "security" + ] + }, + { + "description": "Modifying elements of an array with temporary lifetime will result in undefined behavior.", + "kind": "problem", + "name": "Usage of the subscript operator on an object with temporary lifetime shall not return a modifiable value", + "precision": "high", + "severity": "error", + "short_name": "ModifiableLValueSubscriptedWithTemporaryLifetime", + "tags": [ + "external/misra/c/2012/amendment3", + "correctness", + "security" + ] + } + ], + "title": "An object with temporary lifetime shall not undergo array to pointer conversion" + } + } +} \ No newline at end of file