Skip to content

Commit 98e981d

Browse files
Add new library, backported to a small handful of queries, for resolving types.
Expands upon `PossiblySpecified<T>` module, which was nothing but a renaming of `.stripSpeciifers` that was intended to be clearer (and not resolve typedefs). However, there is almost never a reason not to resolve _certain_ typedefs. Assume we are writing a query to detect usages of a typedef such as `uint8_t`. If we have a candidate type and wish to check if it is a `uint8_t`, we may do so via `candidate.(TypedefType).hasName("uint8_t")`. However, this will not match `const uint8_t`. The type methods such as `stripSpecifiers`, `getUnderlyingType`, can be used but are sometimes vague, and some implicitly resolve typedefs while others do not, and some even have incorrect documentation. Furthermore, if a user has declared `typedef uint8_t uchar_t` or `typedef const uint8_t uint8_const_t`, then we _must_ resolve typedefs to find these usages of `uint8_t` in the project. However, `candidate.resolveTypedefs()` will also unwrap the `uint8_t`. In C++ the problem is worse too, as we would need to detect cases such as `const uint8_t&`, and decltypes. This project needs, IMO, a better API for incrementally resolving typedefs, decltypes, specifiers, and references. This PR adds what is hopefully a decent starting point for this. Instead of writing `candidate.resolveTypedefs() instanceof FooType` or `candidate.stripTopLevelSpecifiers() instanceof FooType` or `candidate.getUnderlyingType() instanceof FooType`, the `cpp.types.Resolve` import now allows `candidate instanceof ResolvesTo<FooType>::Exactly` to only resolve typedefs and decltypes, or `::IgnoringSpecifiers` to resolve typedefs, decltypes, and unwrap `SpecifiedType`s as well. These types have a `.resolve()` member predicate to unwrap everything and get the resolved `FooType`. The API basically intends to define an interterface for how to unwrap type A in search of a type B. Types are like linked lists, with flags on certain links. We really just want a means of traversing different kinds of links in different contexts, sometimes requiring the presence of a certain kind of link, and sometimes aggregating properties about what links were traversed. We also want an API that provides some guard rails and funnels people towards correct usage of itself. Hopefully `::Exactly` comes across as too strict, and hopefully `CvConst` makes it clear that a type may be `const` or `const volatile`. Also added some types that compliment this API well such as `PointerTo<T>::Type` and `ReferenceOf<T>::Type`. Also integrated this library into a few example queries so the PR includes example usage.
1 parent 33bc0b1 commit 98e981d

File tree

13 files changed

+481
-45
lines changed

13 files changed

+481
-45
lines changed

c/cert/src/rules/INT36-C/ConvertingAPointerToIntegerOrIntegerToPointer.ql

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import cpp
1919
import codingstandards.c.cert
20+
import codingstandards.cpp.types.Resolve
2021

2122
class LiteralZero extends Literal {
2223
LiteralZero() { this.getValue() = "0" }
@@ -37,21 +38,30 @@ class StdIntIntPtrType extends Type {
3738
}
3839
}
3940

41+
class ResolvesToStdIntIntPtrType = ResolvesTo<StdIntIntPtrType>::IgnoringSpecifiers;
42+
43+
class ResolvesToVoidPointerType = ResolvesTo<VoidPointerType>::IgnoringSpecifiers;
44+
4045
/**
4146
* Casting a pointer value to integer, excluding literal 0.
4247
* Includes implicit conversions made during declarations or assignments.
4348
*/
4449
predicate conversionBetweenPointerAndInteger(Cast cast, string message) {
4550
/* Ensure that `int` has different size than that of pointers */
46-
exists(IntType intType, PointerType ptrType | intType.getSize() < ptrType.getSize() |
47-
cast.getExpr().getUnderlyingType() = intType and
48-
cast.getUnderlyingType() = ptrType and
51+
exists(
52+
ResolvesTo<IntType>::IgnoringSpecifiers intType,
53+
ResolvesTo<PointerType>::IgnoringSpecifiers ptrType
54+
|
55+
intType.getSize() < ptrType.getSize()
56+
|
57+
cast.getExpr().getType() = intType and
58+
cast.getType() = ptrType and
4959
if cast.isCompilerGenerated()
5060
then message = "Integer expression " + cast.getExpr() + " is implicitly cast to a pointer type."
5161
else message = "Integer expression " + cast.getExpr() + " is cast to a pointer type."
5262
or
53-
cast.getExpr().getUnderlyingType() = ptrType and
54-
cast.getUnderlyingType() = intType and
63+
cast.getExpr().getType() = ptrType and
64+
cast.getType() = intType and
5565
if cast.isCompilerGenerated()
5666
then
5767
message = "Pointer expression " + cast.getExpr() + " is implicitly cast to an integer type."
@@ -61,11 +71,11 @@ predicate conversionBetweenPointerAndInteger(Cast cast, string message) {
6171
not cast.getExpr() instanceof LiteralZero and
6272
/* Compliant exception 2: variable's declared type is (u)intptr_t */
6373
not (
64-
cast.getType() instanceof StdIntIntPtrType and
65-
cast.getExpr().getType() instanceof VoidPointerType
74+
cast.getType() instanceof ResolvesToStdIntIntPtrType and
75+
cast.getExpr().getType() instanceof ResolvesToVoidPointerType
6676
or
67-
cast.getType() instanceof VoidPointerType and
68-
cast.getExpr().getType() instanceof StdIntIntPtrType
77+
cast.getType() instanceof ResolvesToVoidPointerType and
78+
cast.getExpr().getType() instanceof ResolvesToStdIntIntPtrType
6979
)
7080
}
7181

c/misra/src/rules/RULE-22-12/NonstandardUseOfThreadingObject.ql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
import cpp
1717
import codingstandards.c.misra
1818
import codingstandards.cpp.Concurrency
19-
import codingstandards.cpp.Type
19+
import codingstandards.cpp.types.Resolve
2020

21-
predicate isThreadingObject(Type t) { t instanceof PossiblySpecified<C11ThreadingObjectType>::Type }
21+
predicate isThreadingObject(Type t) {
22+
t instanceof ResolvesTo<C11ThreadingObjectType>::IgnoringSpecifiers
23+
}
2224

2325
predicate validUseOfStdThreadObject(Expr e) {
2426
e.getParent() instanceof AddressOfExpr

c/misra/src/rules/RULE-22-13/ThreadingObjectWithInvalidStorageDuration.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ import cpp
1717
import codingstandards.c.misra
1818
import codingstandards.c.Objects
1919
import codingstandards.cpp.Concurrency
20-
import codingstandards.cpp.Type
20+
import codingstandards.cpp.types.Resolve
2121

2222
from ObjectIdentity obj, StorageDuration storageDuration, Type type
2323
where
2424
not isExcluded(obj, Concurrency8Package::threadingObjectWithInvalidStorageDurationQuery()) and
2525
storageDuration = obj.getStorageDuration() and
2626
not storageDuration.isStatic() and
2727
type = obj.getASubObjectType() and
28-
type instanceof PossiblySpecified<C11ThreadingObjectType>::Type
28+
type instanceof ResolvesTo<C11ThreadingObjectType>::IgnoringSpecifiers
2929
select obj,
3030
"Object of type '" + obj.getType().getName() + "' has invalid storage duration type '" +
3131
storageDuration.getStorageTypeName() + "'."

c/misra/src/rules/RULE-22-14/MutexNotInitializedBeforeUse.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import cpp
1717
import codingstandards.c.misra
1818
import codingstandards.c.Objects
1919
import codingstandards.cpp.Concurrency
20-
import codingstandards.cpp.Type
20+
import codingstandards.cpp.types.Resolve
2121
import codingstandards.c.initialization.GlobalInitializationAnalysis
2222

2323
module MutexInitializationConfig implements GlobalInitializationAnalysisConfigSig {
@@ -68,8 +68,8 @@ where
6868
) and
6969
(
7070
if
71-
obj.getType() instanceof PossiblySpecified<C11MutexType>::Type or
72-
obj.getType() instanceof PossiblySpecified<C11ConditionType>::Type
71+
obj.getType() instanceof ResolvesTo<C11MutexType>::IgnoringSpecifiers or
72+
obj.getType() instanceof ResolvesTo<C11ConditionType>::IgnoringSpecifiers
7373
then description = typeString
7474
else description = typeString + " in object"
7575
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- `INT36-C` - `ConvertingAPointerToIntegerOrIntegerToPointer.ql`:
2+
- Integrated new type resolution modules to fully handle typedefs and ignore cv-qualifiers during type comparisons, such as in detecting int types, pointer types, (u)intptr_t types, and void pointer types.
3+
- `RULE-22-12`, `RULE-22-13`, `RULE-22-14` - `NonstandardUseOfThreadingObject.ql`, `ThreadingObjectWithInvalidStorageDuration.ql`, `MutexNotInitializedBeforeUse.ql`:
4+
- Integrated new type resolution modules to handle typedefs when identifying threading object types.
5+
- `RULE-9-5-1` - `LegacyForStatementsShouldBeSimple.ql`:
6+
- Refactor to integrate new type resolution, no change in functionality expected.
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
private import cpp
2+
private import qtil.Qtil
3+
private import codingstandards.cpp.types.Specifiers
4+
private import codeql.util.Boolean
5+
6+
Type typeResolvesToTypeStep(Type type) {
7+
result = type.(Decltype).getBaseType()
8+
or
9+
result = type.(TypedefType).getBaseType()
10+
}
11+
12+
module PointerTo<Qtil::Signature<Type>::Type PointeeType> {
13+
/**
14+
* A pointer type that points to a type that resolves to the module's `PointeeType` type parameter.
15+
*/
16+
class Type extends Qtil::Final<PointerType>::Type {
17+
Type() { getBaseType() instanceof PointeeType }
18+
}
19+
}
20+
21+
module ReferenceOf<Qtil::Signature<Type>::Type ReferencedType> {
22+
/**
23+
* A reference type that refers to a type that resolves to the module's `ReferencedType` type
24+
* parameter.
25+
*/
26+
class Type extends Qtil::Final<ReferenceType>::Type {
27+
Type() { getBaseType() instanceof ReferencedType }
28+
}
29+
}
30+
31+
/**
32+
* A module for handling complex type resolution in c++, such as a decltype of a const typedef
33+
* of a struct type, which resolves to a const struct type.
34+
*
35+
* Basic usage:
36+
* - `ResolvesTo<ClassType>::Exactly` is the set of class types, and typedefs/decltypes that resolve
37+
* to class types exactly (without specifiers).
38+
* - `resolvedType.resolve()` gets the fully resolved class type for the above.
39+
* - `ResolvesTo<ClassType>::Specified` is the set of types that resolve to a specified class type.
40+
* - `ResolvesTo<ClassType>::Ref` is the set of types that resolve to a reference to a class type.
41+
* - `ResolvesTo<ClassType>::Const` is the set of types that resolve to a const class type.
42+
* - `ResolvesTo<ClassType>::IgnoringSpecifiers` is the set of types that resolve to a class type
43+
* ignoring specifiers.
44+
* - `ResolvesTo<ClassType>::ExactlyOrRef` is the set of types that resolve to a class type and
45+
* unwraps references.
46+
*
47+
* These module classes are preferred to the member predicates on `Type` such as `resolveTypedefs`,
48+
* `stripSpecifiers`, `getUnderlyingType()`, etc, for the following reasons:
49+
* - Hopefully the API is clearer and easier to use correctly.
50+
* - Unlike `resolveTypedefs`, these classes can find types that resolve to other typedefs (via
51+
* `ResolvesType<SomeTypedefType>::Exactly` etc.), instead of always resolving all typedefs.
52+
* - The member predicates on `Type` have cases with no result. For instance, if there's a const
53+
* typedef `const T = F`, but `const F` is never explicitly written in the code, then there is no
54+
* matching extracted `Type` for `resolveTypedefs` to return, and it will have no result.
55+
*/
56+
module ResolvesTo<Qtil::Signature<Type>::Type ResolvedType> {
57+
private import cpp as cpp
58+
59+
final class CppType = cpp::Type;
60+
61+
/**
62+
* A type that resolve exactly to the module's `ResolvedType` type parameter.
63+
*
64+
* For example, `ResolvesTo<FooType>::Type` is the set of all `FooType`s and types that resolve
65+
* (through typedefs * and/or decltypes) to `FooType`s. This does _not_ include types that resolve
66+
* to a const `FooType` (though `FooType` itself may be const). To perform type resolution and
67+
* check or strip specifiers, see module classes `IgnoringSpecifiers`, `Specified`, `Const`, etc.
68+
*
69+
* ```
70+
* // Example `ResolvesTo<FooType>::Exactly` types:
71+
* FooType f; // matches (a FooType)
72+
* decltype(f); // matches (a decltype of FooType)
73+
* typedef FooType FT; // matches (a typedef of FooType)
74+
* FT f2; // matches (a typedef of FooType)
75+
* decltype(f2); // matches (a decltype of typedef of FooType)
76+
* typedef FT FT2; // matches (a typedef of typedef of FooType)
77+
*
78+
* // Examples types that are not `ResolvesTo<FooType>::Exactly` types:
79+
* const FooType cf; // does not match (specified FooTypes)
80+
* FooType& rf = f; // does not match (references to FooTypes)
81+
* NotFooType nf; // does not match (non FooTypes)
82+
* ```
83+
*/
84+
class Exactly extends CppType {
85+
ResolvedType resolvedType;
86+
87+
Exactly() { resolvedType = typeResolvesToTypeStep*(this) }
88+
89+
ResolvedType resolve() { result = resolvedType }
90+
}
91+
92+
/**
93+
* A type that resolves to a const type that in turn resolves to the module's `ResolvedType` type.
94+
*
95+
* For example, `ResolvesTo<FooType>::CvConst` is the set of all const `FooType`s and types that
96+
* resolve (through typedefs and/or decltypes) to const `FooType`s, including cases involving
97+
* `const` typedefs, etc.
98+
*
99+
* Volatile specifiers are ignored, since const volatile types are still const.
100+
*
101+
* For matching both const and non-const types that resolve to `FooType`, see
102+
* `IgnoringSpecifiers`.
103+
*
104+
* ```
105+
* // Note that this does NOT match `FooType`s that are not const.
106+
* FooType f; // does not match (non-const)
107+
* decltype(f) df; // does not match (non-const)
108+
* typedef TF = FooType; // does not match (non-const)
109+
*
110+
* // Example `ResolvesTo<FooType>::Const` types:
111+
* const FooType cf; // matches (a const FooType)
112+
* const volatile FooType cf; // matches (a const FooType, volatile is allowed and ignored)
113+
* decltype(cf); // matches (a decltype of a const FooType)
114+
* const decltype(f); // matches (a const decltype of FooType)
115+
* const decltype(cf); // matches (a const decltype of a const FooType)
116+
* typedef const FooType CFT; // matches (a typedef of const FooType)
117+
* const TF ctf; // matches (a const typedef of FooType)
118+
*
119+
* // Additional examples types that are not `ResolvesTo<FooType>::Const` types:
120+
* const FooType &f; // does not match (reference to const FooType)
121+
* ```
122+
*/
123+
class CvConst extends Specified {
124+
CvConst() { getASpecifier() instanceof ConstSpecifier }
125+
}
126+
127+
/**
128+
* A type that resolves to a cv-qualified (or otherwise specified) type that in turn resolves to
129+
* the module's `ResolvedType` type parameter.
130+
*
131+
* For example, `ResolvesTo<FooType>::Specified` is the set of all specified `FooType`s and types
132+
* that resolve (through typedefs and/or decltypes) to specified `FooType`s, including cases
133+
* involving `const` and `volatile` typedefs, etc.
134+
*
135+
* ```
136+
* // Note that this does NOT match `FooType`s that are not specified.
137+
* FooType f; // does not match (not specified)
138+
* decltype(f) df; // does not match (not specified)
139+
* typedef TF = FooType; // does not match (not specified)
140+
*
141+
* // Example `ResolvesTo<FooType>::Specified` types:
142+
* const FooType cf; // matches (a const FooType)
143+
* volatile FooType vf; // matches (a volatile FooType)
144+
* const volatile FooType cvf; // matches (a const volatile FooType)
145+
* decltype(cf); // matches (a decltype of a const FooType)
146+
* volatile decltype(f); // matches (a volatile decltype of FooType)
147+
* const decltype(vf); // matches (a const decltype of volatile FooType)
148+
* const decltype(cf); // matches (a const decltype of const FooType)
149+
* typedef const FooType CFT; // matches (a typedef of const FooType)
150+
* const TF ctf; // matches (a const typedef of FooType)
151+
* volatile TF ctf; // matches (a volatile typedef of FooType)
152+
*
153+
* // Additional examples types that are not `ResolvesTo<FooType>::Specified` types:
154+
* const FooType &f; // does not match (reference to const FooType)
155+
* ```
156+
*/
157+
class Specified extends CppType {
158+
ResolvedType resolved;
159+
160+
Specified() {
161+
resolved = typeResolvesToTypeStep*(this).(SpecifiedType).getBaseType().(Exactly).resolve()
162+
}
163+
164+
ResolvedType resolve() { result = resolved }
165+
}
166+
167+
/**
168+
* A class that resolves to the module's `ResolvedType` type parameter, ignoring specifiers.
169+
*/
170+
class IgnoringSpecifiers extends CppType {
171+
ResolvedType resolved;
172+
173+
IgnoringSpecifiers() {
174+
resolved = this.(Specified).resolve()
175+
or
176+
resolved = this.(Exactly).resolve()
177+
}
178+
179+
ResolvedType resolve() { result = resolved }
180+
}
181+
182+
/**
183+
* A type that resolves to a reference to that resolves to the module's `ResolvedType` type
184+
* parameter.
185+
*
186+
* For example, `ResolvesTo<FooType>::Ref` is the set of all references to `FooType`s and types
187+
* that resolve (through typedefs and/or decltypes) to references to `FooType`s.
188+
*
189+
* ```
190+
* // Example `ResolvesTo<FooType>::Ref` types:
191+
* FooType &f; // matches (a reference to FooType)
192+
* decltype(f); // matches (a decltype of reference to FooType)
193+
* typedef FooType &FT; // matches (a typedef of ref to FooType)
194+
* FT f2; // matches (a typedef of ref to FooType)
195+
* decltype(f2); // matches (a decltype of typedef of ref to FooType)
196+
* typedef FT FT2; // matches (a typedef of typedef of ref to FooType)
197+
*
198+
* // Examples types that are not `ResolvesTo<FooType>::Ref` types:
199+
* const FooType &cf; // does not match (specified references to FooTypes)
200+
* FooType rf = f; // does not match (non-rerefence FooTypes)
201+
* NotFooType &nf; // does not match (non FooTypes)
202+
* ```
203+
*/
204+
class Ref extends CppType {
205+
ResolvedType resolved;
206+
207+
Ref() {
208+
// Note that the extractor appears to perform reference collapsing, so cases like
209+
// `const T& &` are treated as `const T&`.
210+
resolved = typeResolvesToTypeStep*(this).(ReferenceType).getBaseType().(Exactly).resolve()
211+
}
212+
213+
ResolvedType resolve() { result = resolved }
214+
}
215+
216+
//class Ref =
217+
//Impl::ResolveRefType;
218+
/**
219+
* A type that resolves to a const reference of (or reference to const of) the module's
220+
* `ResolvedType` type parameter.
221+
*
222+
* For example, `ResolvesTo<FooType>::CvConstRef` is the set of all const references to `FooType`s
223+
* and types that resolve (through typedefs and/or decltypes) to const references to `FooType`s.
224+
*
225+
* Volatile specifiers are ignored, since const volatile types are still const.
226+
*
227+
* ```
228+
* FooType &f; // does not match (not const)
229+
* const FooType f; // does not match (not a reference)
230+
*
231+
* // Example `ResolvesTo<FooType>::CvConstRef` types:
232+
* const FooType &cf; // matches (a const reference to FooType)
233+
* const volatile FooType &cf; // matches (a const reference to FooType, volatile is ignored)
234+
* const decltype(f) cdf; // matches (a const decltype of reference to FooType)
235+
* decltype(f)& dcf; // matches (a decltype of const reference to FooType)
236+
* typedef const FooType &CFT; // matches (a typedef of const ref to FooType)
237+
* const CFT ctf; // matches due to const collapse
238+
* CFT &ctf; // matches due to reference collapse
239+
* ```
240+
*/
241+
class CvConstRef extends CppType {
242+
ResolvedType resolved;
243+
244+
CvConstRef() {
245+
exists(ReferenceType refType |
246+
// A type can be a reference to const, but a const type cannot contain a reference.
247+
// Therefore, we only need to find reference types that resolve to const types.
248+
// Note that the extractor appears to perform reference collapsing, so cases like
249+
// `const T& &` are treated as `const T&`.
250+
refType = typeResolvesToTypeStep*(this) and
251+
refType.getBaseType() instanceof CvConst
252+
)
253+
}
254+
255+
ResolvedType resolve() { result = resolved }
256+
}
257+
258+
/**
259+
* A type that resolves to either a reference to that resolves to the module's `ResolvedType` type
260+
* parameter, or exactly to the `ResolvedType`.
261+
*/
262+
class ExactlyOrRef extends CppType {
263+
ResolvedType resolved;
264+
265+
ExactlyOrRef() {
266+
resolved = this.(Ref).resolve()
267+
or
268+
resolved = this.(Exactly).resolve()
269+
}
270+
271+
ResolvedType resolve() { result = resolved }
272+
}
273+
}

0 commit comments

Comments
 (0)