|
| 1 | +/** |
| 2 | + * @id cpp/misra/pointer-or-ref-param-not-const |
| 3 | + * @name RULE-10-1-1: The target type of a pointer or lvalue reference parameter should be const-qualified appropriately |
| 4 | + * @description Pointer or lvalue reference parameters that do not modify the target object should |
| 5 | + * be const-qualified to accurately reflect function behavior and prevent unintended |
| 6 | + * modifications. |
| 7 | + * @kind problem |
| 8 | + * @precision high |
| 9 | + * @problem.severity warning |
| 10 | + * @tags external/misra/id/rule-10-1-1 |
| 11 | + * maintainability |
| 12 | + * readability |
| 13 | + * scope/single-translation-unit |
| 14 | + * external/misra/enforcement/decidable |
| 15 | + * external/misra/obligation/advisory |
| 16 | + */ |
| 17 | + |
| 18 | +import cpp |
| 19 | +import codingstandards.cpp.misra |
| 20 | +import codingstandards.cpp.types.Pointers |
| 21 | +import codingstandards.cpp.SideEffect |
| 22 | +import codingstandards.cpp.alertreporting.HoldsForAllCopies |
| 23 | + |
| 24 | +/** |
| 25 | + * Holds if the function is in a template scope and should be excluded. |
| 26 | + */ |
| 27 | +predicate isInTemplateScope(Function f) { |
| 28 | + // Function templates |
| 29 | + f.isFromTemplateInstantiation(_) |
| 30 | + or |
| 31 | + f instanceof TemplateFunction |
| 32 | + or |
| 33 | + // Functions declared within the scope of a template class |
| 34 | + exists(TemplateClass tc | f.getDeclaringType() = tc) |
| 35 | + or |
| 36 | + f.getDeclaringType().isFromTemplateInstantiation(_) |
| 37 | + or |
| 38 | + // Lambdas within template scope |
| 39 | + exists(LambdaExpression le | |
| 40 | + le.getLambdaFunction() = f and |
| 41 | + isInTemplateScope(le.getEnclosingFunction()) |
| 42 | + ) |
| 43 | +} |
| 44 | + |
| 45 | +/** |
| 46 | + * A `Type` that may be a pointer, array, or reference, to a const or a non-const type. |
| 47 | + * |
| 48 | + * For example, `const int*`, `int* const`, `cont int* const`, `int*`, `int&`, `const int&` are all |
| 49 | + * `PointerLikeType`s, while `int`, `int&&`, and `const int` are not. |
| 50 | + * |
| 51 | + * To check if a `PointerLikeType` points/refers to a const-qualified type, use the `pointsToConst()` |
| 52 | + * predicate. |
| 53 | + */ |
| 54 | +class PointerLikeType extends Type { |
| 55 | + Type innerType; |
| 56 | + Type outerType; |
| 57 | + |
| 58 | + PointerLikeType() { |
| 59 | + innerType = this.(UnspecifiedPointerOrArrayType).getBaseType() and |
| 60 | + outerType = this |
| 61 | + or |
| 62 | + innerType = this.(LValueReferenceType).getBaseType() and |
| 63 | + outerType = this |
| 64 | + or |
| 65 | + exists(PointerLikeType stripped | |
| 66 | + stripped = this.stripTopLevelSpecifiers() and not stripped = this |
| 67 | + | |
| 68 | + innerType = stripped.getInnerType() and |
| 69 | + outerType = stripped.getOuterType() |
| 70 | + ) |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * Get the pointed to or referred to type, for instance `int` for `int*` or `const int&`. |
| 75 | + */ |
| 76 | + Type getInnerType() { result = innerType } |
| 77 | + |
| 78 | + /** |
| 79 | + * Get the resolved pointer, array, or reference type itself, for instance `int*` in `int* const`. |
| 80 | + * |
| 81 | + * Removes cv-qualification and resolves typedefs and decltypes and specifiers via |
| 82 | + * `stripTopLevelSpecifiers()`. |
| 83 | + */ |
| 84 | + Type getOuterType() { result = outerType } |
| 85 | + |
| 86 | + /** |
| 87 | + * Holds when this type points to const -- for example, `const int*` and `const int&` point to |
| 88 | + * const, while `int*`, `int *const` and `int&` do not. |
| 89 | + */ |
| 90 | + predicate pointsToConst() { innerType.isConst() } |
| 91 | + |
| 92 | + /** |
| 93 | + * Holds when this type points to non-const -- for example, `int*` and `int&` and `int const*` |
| 94 | + * point to non-const, while `const int*`, `const int&` do not. |
| 95 | + */ |
| 96 | + predicate pointsToNonConst() { not innerType.isConst() } |
| 97 | +} |
| 98 | + |
| 99 | +class PointerLikeParam extends Parameter { |
| 100 | + PointerLikeType pointerLikeType; |
| 101 | + |
| 102 | + PointerLikeParam() { |
| 103 | + pointerLikeType = this.getType() and |
| 104 | + not pointerLikeType.pointsToConst() and |
| 105 | + // Exclude pointers to non-object types |
| 106 | + not pointerLikeType.getInnerType() instanceof RoutineType and |
| 107 | + not pointerLikeType.getInnerType() instanceof VoidType |
| 108 | + } |
| 109 | + |
| 110 | + Expr getAPointerLikeAccess() { |
| 111 | + result = this.getAnAccess() |
| 112 | + or |
| 113 | + // For reference parameters, also consider accesses to the parameter itself as accesses to the referent |
| 114 | + pointerLikeType.getOuterType() instanceof ReferenceType and |
| 115 | + result.(AddressOfExpr).getOperand() = this.getAnAccess() |
| 116 | + or |
| 117 | + // A pointer is dereferenced, but the result is not copied |
| 118 | + pointerLikeType.getOuterType() instanceof PointerType and |
| 119 | + result.(PointerDereferenceExpr).getOperand() = this.getAnAccess() and |
| 120 | + not any(ReferenceDereferenceExpr rde).getExpr() = result.getConversion+() |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +query predicate test( |
| 125 | + PointerLikeParam param, Expr ptrLikeAccess, Type argtype, Type innerType, string explain |
| 126 | +) { |
| 127 | + ptrLikeAccess = param.getAPointerLikeAccess() and |
| 128 | + exists(FunctionCall fc | |
| 129 | + fc.getArgument(0) = ptrLikeAccess and |
| 130 | + argtype = fc.getTarget().getParameter(0).getType() and |
| 131 | + argtype.(PointerLikeType).pointsToNonConst() and |
| 132 | + innerType = argtype.(PointerLikeType).getInnerType() |
| 133 | + ) and |
| 134 | + explain = argtype.explain() |
| 135 | +} |
| 136 | + |
| 137 | +/** |
| 138 | + * A candidate parameter that could have its target type const-qualified. |
| 139 | + */ |
| 140 | +class NonConstParam extends PointerLikeParam { |
| 141 | + NonConstParam() { |
| 142 | + not pointerLikeType.pointsToConst() and |
| 143 | + // Ignore parameters in functions without bodies |
| 144 | + exists(this.getFunction().getBlock()) and |
| 145 | + // Ignore unnamed parameters |
| 146 | + this.isNamed() and |
| 147 | + // Ignore functions that use ASM statements |
| 148 | + not exists(AsmStmt a | a.getEnclosingFunction() = this.getFunction()) and |
| 149 | + // Must have a pointer, array, or lvalue reference type with non-const target |
| 150 | + // Exclude pointers to non-object types |
| 151 | + not pointerLikeType.getInnerType() instanceof RoutineType and |
| 152 | + not pointerLikeType.getInnerType() instanceof VoidType and |
| 153 | + // Exclude virtual functions |
| 154 | + not this.getFunction().isVirtual() and |
| 155 | + // Exclude functions in template scope |
| 156 | + not isInTemplateScope(this.getFunction()) and |
| 157 | + // Exclude main |
| 158 | + not this.getFunction().hasGlobalName("main") and |
| 159 | + // Exclude deleted functions |
| 160 | + not this.getFunction().isDeleted() and |
| 161 | + // Exclude any parameter whose underlying data is modified (VariableEffect) |
| 162 | + not exists(VariableEffect effect | |
| 163 | + effect.getTarget() = this and |
| 164 | + ( |
| 165 | + // For reference types, any effect is a target modification |
| 166 | + pointerLikeType.getOuterType() instanceof ReferenceType |
| 167 | + or |
| 168 | + // For pointer types, exclude effects that only modify the pointer itself |
| 169 | + not effect.(AssignExpr).getLValue() = this.getAnAccess() and |
| 170 | + not effect.(CrementOperation).getOperand() = this.getAnAccess() |
| 171 | + ) |
| 172 | + ) and |
| 173 | + // Exclude parameters passed as arguments to functions taking non-const pointer/ref params |
| 174 | + not exists(FunctionCall fc, int i | |
| 175 | + fc.getArgument(i) = this.getAPointerLikeAccess() and |
| 176 | + fc.getTarget().getParameter(i).getType().(PointerLikeType).pointsToNonConst() |
| 177 | + ) and |
| 178 | + // Exclude parameters used as qualifier for a non-const member function |
| 179 | + not exists(FunctionCall fc | |
| 180 | + fc.getQualifier() = this.getAnAccess() and |
| 181 | + not fc.getTarget().hasSpecifier("const") and |
| 182 | + not fc.getTarget().isStatic() |
| 183 | + ) and |
| 184 | + // Exclude parameters assigned to a non-const pointer/reference alias |
| 185 | + not exists(Variable v | |
| 186 | + v.getAnAssignedValue() = this.getAnAccess() and |
| 187 | + v.getType().(PointerLikeType).pointsToNonConst() |
| 188 | + ) and |
| 189 | + // Exclude parameters returned as non-const pointer/reference |
| 190 | + not exists(ReturnStmt ret | |
| 191 | + ret.getExpr() = this.getAnAccess() and |
| 192 | + ret.getEnclosingFunction().getType().(PointerLikeType).pointsToNonConst() |
| 193 | + ) |
| 194 | + } |
| 195 | +} |
| 196 | + |
| 197 | +from NonConstParam param |
| 198 | +where not isExcluded(param, Declarations3Package::pointerOrRefParamNotConstQuery()) |
| 199 | +select param, |
| 200 | + "Parameter '" + param.getName() + |
| 201 | + "' points/refers to a non-const-qualified type but does not modify the target object." |
0 commit comments