Skip to content

Commit e017d52

Browse files
authored
Merge pull request #1102 from github/michaelrfairhurst/declarations-10-1-1-properly-qualify-lvalue-refs-pointers
Michaelrfairhurst/declarations 10-1-1 properly qualify lvalue refs pointers
2 parents cee8713 + e42b259 commit e017d52

File tree

12 files changed

+630
-3
lines changed

12 files changed

+630
-3
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- All queries related to side effects:
2+
- Compound assignments of pointer parameters (e.g. `p += 1`) are no longer treated as a modification of the pointed-to object. This was previously only handled for simple assignments (e.g. `p = ...`).

cpp/common/src/codingstandards/cpp/Call.qll

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,48 @@ FunctionType getExprCallFunctionType(ExprCall call) {
1717
// Returns a RoutineType
1818
result = call.(ExprCall).getChild(-1).getType().(PointerToMemberType).getBaseType()
1919
}
20+
21+
/**
22+
* An `Expr` that is used as an argument to a `Call`, and has helpers to handle with the differences
23+
* between `ExprCall` and `FunctionCall` cases.
24+
*/
25+
class CallArgumentExpr extends Expr {
26+
Call call;
27+
Type paramType;
28+
int argIndex;
29+
30+
CallArgumentExpr() {
31+
this = call.getArgument(argIndex) and
32+
(
33+
paramType = call.getTarget().getParameter(argIndex).getType()
34+
or
35+
paramType = getExprCallFunctionType(call).getParameterType(argIndex)
36+
)
37+
}
38+
39+
/**
40+
* Gets the `FunctionExpr` or `FunctionCall` that this argument appears in.
41+
*/
42+
Call getCall() { result = call }
43+
44+
/**
45+
* Gets the `Type` of the parameter corresponding to this argument, whether its based on the
46+
* target function or the function pointer type.
47+
*/
48+
Type getParamType() { result = paramType }
49+
50+
/**
51+
* Gets the argument index of this argument in the call.
52+
*/
53+
int getArgIndex() { result = argIndex }
54+
55+
/**
56+
* Gets the target `Function` if this is an argument to a `FunctionCall`.
57+
*/
58+
Function getKnownFunction() { result = call.getTarget() }
59+
60+
/**
61+
* Gets the target `Parameter` if this is an argument to a `FunctionCall`.
62+
*/
63+
Parameter getKnownParameter() { result = call.getTarget().getParameter(argIndex) }
64+
}

cpp/common/src/codingstandards/cpp/SideEffect.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ class AliasParameter extends Parameter {
295295
this.getUnspecifiedType() instanceof ReferenceType
296296
or
297297
// Exclude effects that change the who we point to, since we care about changes to the referenced object.
298-
not result.(AssignExpr).getLValue() = va and
298+
not result.(Assignment).getLValue() = va and
299299
not result.(CrementOperation).getOperand() = va
300300
)
301301
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
2+
import cpp
3+
import RuleMetadata
4+
import codingstandards.cpp.exclusions.RuleMetadata
5+
6+
newtype Declarations6Query = TPointerOrRefParamNotConstQuery()
7+
8+
predicate isDeclarations6QueryMetadata(Query query, string queryId, string ruleId, string category) {
9+
query =
10+
// `Query` instance for the `pointerOrRefParamNotConst` query
11+
Declarations6Package::pointerOrRefParamNotConstQuery() and
12+
queryId =
13+
// `@id` for the `pointerOrRefParamNotConst` query
14+
"cpp/misra/pointer-or-ref-param-not-const" and
15+
ruleId = "RULE-10-1-1" and
16+
category = "advisory"
17+
}
18+
19+
module Declarations6Package {
20+
Query pointerOrRefParamNotConstQuery() {
21+
//autogenerate `Query` type
22+
result =
23+
// `Query` type for `pointerOrRefParamNotConst` query
24+
TQueryCPP(TDeclarations6PackageQuery(TPointerOrRefParamNotConstQuery()))
25+
}
26+
}

cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import Declarations1
3939
import Declarations2
4040
import Declarations3
4141
import Declarations4
42+
import Declarations6
4243
import Declarations7
4344
import ExceptionSafety
4445
import Exceptions1
@@ -143,6 +144,7 @@ newtype TCPPQuery =
143144
TDeclarations2PackageQuery(Declarations2Query q) or
144145
TDeclarations3PackageQuery(Declarations3Query q) or
145146
TDeclarations4PackageQuery(Declarations4Query q) or
147+
TDeclarations6PackageQuery(Declarations6Query q) or
146148
TDeclarations7PackageQuery(Declarations7Query q) or
147149
TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or
148150
TExceptions1PackageQuery(Exceptions1Query q) or
@@ -247,6 +249,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
247249
isDeclarations2QueryMetadata(query, queryId, ruleId, category) or
248250
isDeclarations3QueryMetadata(query, queryId, ruleId, category) or
249251
isDeclarations4QueryMetadata(query, queryId, ruleId, category) or
252+
isDeclarations6QueryMetadata(query, queryId, ruleId, category) or
250253
isDeclarations7QueryMetadata(query, queryId, ruleId, category) or
251254
isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or
252255
isExceptions1QueryMetadata(query, queryId, ruleId, category) or

cpp/common/src/codingstandards/cpp/types/Pointers.qll

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,89 @@ Type baseType(Type t) {
125125
// Make sure that the type has a size and that it isn't ambiguous.
126126
strictcount(result.getSize()) = 1
127127
}
128+
129+
/**
130+
* A `Type` that may be a pointer, array, or reference, to a const or a non-const type.
131+
*
132+
* For example, `const int*`, `int* const`, `const int* const`, `int*`, `int&`, `const int&` are all
133+
* `PointerLikeType`s, while `int`, `int&&`, and `const int` are not.
134+
*
135+
* To check if a `PointerLikeType` points/refers to a const-qualified type, use the `pointsToConst()`
136+
* predicate.
137+
*/
138+
class PointerLikeType extends Type {
139+
Type innerType;
140+
Type outerType;
141+
142+
PointerLikeType() {
143+
innerType = this.(UnspecifiedPointerOrArrayType).getBaseType() and
144+
outerType = this
145+
or
146+
innerType = this.(LValueReferenceType).getBaseType() and
147+
outerType = this
148+
or
149+
exists(PointerLikeType stripped |
150+
stripped = this.stripTopLevelSpecifiers() and not stripped = this
151+
|
152+
innerType = stripped.getInnerType() and
153+
outerType = stripped.getOuterType()
154+
)
155+
}
156+
157+
/**
158+
* Gets the pointed to or referred to type, for instance `int` for `int*` or `const int&`.
159+
*/
160+
Type getInnerType() { result = innerType }
161+
162+
/**
163+
* Gets the resolved pointer, array, or reference type itself, for instance `int*` in `int* const`.
164+
*
165+
* Removes cv-qualification and resolves typedefs and decltypes and specifiers via
166+
* `stripTopLevelSpecifiers()`.
167+
*/
168+
Type getOuterType() { result = outerType }
169+
170+
/**
171+
* Holds when this type points to const -- for example, `const int*` and `const int&` point to
172+
* const, while `int*`, `int *const` and `int&` do not.
173+
*/
174+
predicate pointsToConst() { innerType.isConst() }
175+
176+
/**
177+
* Holds when this type points to non-const -- for example, `int*` and `int&` and `int *const`
178+
* point to non-const, while `const int*`, `const int&` do not.
179+
*/
180+
predicate pointsToNonConst() { not innerType.isConst() }
181+
}
182+
183+
/**
184+
* Gets usages of this parameter that maintain pointer-like semantics -- typically this means
185+
* either a normal access, or switching between pointers and reference semantics.
186+
*
187+
* Examples of accesses with pointer-like semantics include:
188+
* - `ref` in `int &x = ref`, or `&ref` in `int *x = &ref`;
189+
* - `ptr` in `int *x = ptr`, or `*ptr` in `int &x = *ptr`;
190+
*
191+
* In the above examples, we can still access the value pointed to by `ref` or `ptr` through the
192+
* expression.
193+
*
194+
* Examples of non-pointer-like semantics include:
195+
* - `ref` in `int x = ref` and `*ptr` in `int x = *ptr`;
196+
*
197+
* In the above examples, the value pointed to by `ref` or `ptr` is copied and the expression
198+
* refers to a new/different object.
199+
*/
200+
Expr getAPointerLikeAccessOf(Expr expr) {
201+
exists(PointerLikeType pointerLikeType | pointerLikeType = expr.getType() |
202+
result = expr
203+
or
204+
// For reference parameters, also consider accesses to the parameter itself as accesses to the referent
205+
pointerLikeType.getOuterType() instanceof ReferenceType and
206+
result.(AddressOfExpr).getOperand() = expr
207+
or
208+
// A pointer is dereferenced, but the result is not copied
209+
pointerLikeType.getOuterType() instanceof PointerType and
210+
result.(PointerDereferenceExpr).getOperand() = expr and
211+
not any(ReferenceDereferenceExpr rde).getExpr() = result.getConversion+()
212+
)
213+
}

cpp/common/test/includes/standard-library/vector.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ template <class T, class Allocator = std::allocator<T>> class vector {
2020

2121
iterator begin();
2222
iterator end();
23-
const_iterator cbegin();
24-
const_iterator cend();
23+
const_iterator cbegin() const;
24+
const_iterator cend() const;
2525
size_type size() const noexcept;
2626
void resize(size_type sz);
2727
void resize(size_type sz, const T &c);
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
* correctness
12+
* readability
13+
* performance
14+
* scope/single-translation-unit
15+
* external/misra/enforcement/decidable
16+
* external/misra/obligation/advisory
17+
*/
18+
19+
import cpp
20+
import codingstandards.cpp.misra
21+
import codingstandards.cpp.types.Pointers
22+
import codingstandards.cpp.Call
23+
import codingstandards.cpp.SideEffect
24+
25+
/**
26+
* Holds if `f` is a `Function` in a template scope and should be excluded.
27+
*/
28+
predicate isInTemplateScope(Function f) {
29+
f.isFromTemplateInstantiation(_)
30+
or
31+
f.isFromUninstantiatedTemplate(_)
32+
}
33+
34+
/**
35+
* A `Parameter` whose type is a `PointerLikeType` such as a pointer or reference.
36+
*/
37+
class PointerLikeParam extends Parameter {
38+
PointerLikeType pointerLikeType;
39+
40+
PointerLikeParam() { pointerLikeType = this.getType() }
41+
42+
/**
43+
* Gets the pointer like type of this parameter.
44+
*/
45+
PointerLikeType getPointerLikeType() { result = pointerLikeType }
46+
47+
/**
48+
* Gets usages of this parameter that maintain pointer-like semantics -- typically this means
49+
* either a normal access, or switching between pointers and reference semantics.
50+
*
51+
* Examples of accesses with pointer-like semantics include:
52+
* - `ref` in `int &x = ref`, or `&ref` in `int *x = &ref`;
53+
* - `ptr` in `int *x = ptr`, or `*ptr` in `int &x = *ptr`;
54+
*
55+
* In the above examples, we can still access the value pointed to by `ref` or `ptr` through the
56+
* expression.
57+
*
58+
* Examples of non-pointer-like semantics include:
59+
* - `ref` in `int x = ref` and `*ptr` in `int x = *ptr`;
60+
*
61+
* In the above examples, the value pointed to by `ref` or `ptr` is copied and the expression
62+
* refers to a new/different object.
63+
*/
64+
Expr getAPointerLikeAccess() { result = getAPointerLikeAccessOf(this.getAnAccess()) }
65+
}
66+
67+
/**
68+
* A candidate parameter that could have its target type const-qualified.
69+
*/
70+
class NonConstParam extends PointerLikeParam {
71+
NonConstParam() {
72+
not pointerLikeType.pointsToConst() and
73+
// Ignore parameters in functions without bodies
74+
exists(this.getFunction().getBlock()) and
75+
// Ignore unnamed parameters
76+
this.isNamed() and
77+
// Ignore functions that use ASM statements
78+
not exists(AsmStmt a | a.getEnclosingFunction() = this.getFunction()) and
79+
// Must have a pointer, array, or lvalue reference type with non-const target
80+
// Exclude pointers to non-object types
81+
not pointerLikeType.getInnerType+().getUnderlyingType() instanceof RoutineType and
82+
not pointerLikeType.getInnerType+().getUnderlyingType() instanceof VoidType and
83+
// Exclude virtual functions
84+
not this.getFunction().isVirtual() and
85+
// Exclude functions in template scope
86+
not isInTemplateScope(this.getFunction()) and
87+
// Exclude main
88+
not this.getFunction().hasGlobalName("main") and
89+
// Exclude deleted functions
90+
not this.getFunction().isDeleted() and
91+
// Exclude any parameter whose underlying data is modified
92+
not exists(AliasParameter alias | alias = this | alias.isModified()) and
93+
// Exclude parameters passed as arguments to non-const pointer/ref params
94+
not exists(CallArgumentExpr arg |
95+
arg = this.getAPointerLikeAccess() and
96+
arg.getParamType().(PointerLikeType).pointsToNonConst()
97+
) and
98+
// Exclude parameters used as qualifier for a non-const member function
99+
not exists(FunctionCall fc |
100+
fc.getQualifier() = [this.getAnAccess(), this.getAPointerLikeAccess()] and
101+
not fc.getTarget().hasSpecifier("const") and
102+
not fc.getTarget().isStatic()
103+
) and
104+
// Exclude parameters assigned to a non-const pointer/reference alias
105+
not exists(Variable v |
106+
v.getAnAssignedValue() = this.getAPointerLikeAccess() and
107+
v.getType().(PointerLikeType).pointsToNonConst()
108+
) and
109+
// Exclude parameters returned as non-const pointer/reference
110+
not exists(ReturnStmt ret |
111+
ret.getExpr() = this.getAPointerLikeAccess() and
112+
ret.getEnclosingFunction().getType().(PointerLikeType).pointsToNonConst()
113+
) and
114+
not exists(FieldAccess fa |
115+
fa.getQualifier() = [this.getAPointerLikeAccess(), this.getAnAccess()] and
116+
fa.isLValueCategory()
117+
) and
118+
not exists(AddressOfExpr addrOf |
119+
// exclude pointer to pointer and reference to pointer cases.
120+
addrOf.getOperand() = this.getAPointerLikeAccess() and
121+
addrOf.getType().(PointerLikeType).getInnerType() instanceof PointerLikeType
122+
) and
123+
not exists(PointerArithmeticOperation pointerManip |
124+
pointerManip.getAnOperand() = this.getAPointerLikeAccess() and
125+
pointerManip.getType().(PointerLikeType).pointsToNonConst()
126+
)
127+
}
128+
}
129+
130+
from NonConstParam param, Type innerType
131+
where
132+
not isExcluded(param, Declarations6Package::pointerOrRefParamNotConstQuery()) and
133+
innerType = param.getPointerLikeType().getInnerType() and
134+
not param.isAffectedByMacro() and
135+
// There are some odd database patterns where a function has multiple parameters with the same
136+
// index and different names, due to strange extraction+linker scenarios. These give wrong
137+
// results, and should be excluded.
138+
count(Parameter p |
139+
p.getFunction() = param.getFunction() and
140+
p.getIndex() = param.getIndex()
141+
) = 1
142+
select param,
143+
"Parameter '" + param.getName() + "' points/refers to a non-const type '" + innerType.toString() +
144+
"' but does not modify the target object in the $@.", param.getFunction().getDefinition(),
145+
"function definition"

0 commit comments

Comments
 (0)