Skip to content

Commit 7f6b32d

Browse files
Try new approach to reduce search set in type equivalence, some new join orders.
More pragmas added to encourage the join ordering pipeline to make function comparisons more efficient. New approach in type equivalence assumes that all types are trivially equivalent to themselves. Therefore, only type comparisons between non-identical types need to be considered as interesting roots. The types that are reachable in the type graph from these roots are the ones considered by the recursive type equivalence predicate.
1 parent 472c93a commit 7f6b32d

8 files changed

+199
-80
lines changed

c/misra/src/rules/RULE-23-5/DangerousDefaultSelectionForPointerInGeneric.ql

+8-12
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,14 @@ import codingstandards.cpp.types.LvalueConversion
2020
import codingstandards.cpp.types.SimpleAssignment
2121

2222
predicate typesCompatible(Type t1, Type t2) {
23-
TypeEquivalence<TypesCompatibleConfig, TypeFromGeneric>::equalTypes(t1, t2)
23+
TypeEquivalence<TypesCompatibleConfig, relevantTypes/2>::equalTypes(t1, t2)
2424
}
2525

26-
class TypeFromGeneric extends Type {
27-
TypeFromGeneric() {
28-
exists(C11GenericExpr g |
29-
(
30-
this = g.getAssociationType(_) or
31-
this = g.getControllingExpr().getFullyConverted().getType()
32-
)
33-
)
34-
}
26+
predicate relevantTypes(Type a, Type b) {
27+
exists(C11GenericExpr g |
28+
a = g.getAnAssociationType() and
29+
b = getLvalueConverted(g.getControllingExpr().getFullyConverted().getType())
30+
)
3531
}
3632

3733
predicate missesOnPointerConversion(Type provided, Type expected) {
@@ -40,11 +36,11 @@ predicate missesOnPointerConversion(Type provided, Type expected) {
4036
// But 6.5.16.1 simple assignment constraints would have been satisfied:
4137
(
4238
// Check as if the controlling expr is assigned to the expected type:
43-
SimpleAssignment<TypeFromGeneric>::satisfiesSimplePointerAssignment(expected, provided)
39+
SimpleAssignment<relevantTypes/2>::satisfiesSimplePointerAssignment(expected, provided)
4440
or
4541
// Since developers typically rely on the compiler to catch const/non-const assignment
4642
// errors, don't assume a const-to-non-const generic selection miss was intentional.
47-
SimpleAssignment<TypeFromGeneric>::satisfiesSimplePointerAssignment(provided, expected)
43+
SimpleAssignment<relevantTypes/2>::satisfiesSimplePointerAssignment(provided, expected)
4844
)
4945
}
5046

c/misra/src/rules/RULE-8-3/DeclarationsOfAFunctionSameNameAndType.ql

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import cpp
1616
import codingstandards.c.misra
1717
import codingstandards.cpp.types.Compatible
1818

19+
predicate interestedInFunctions(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
20+
f1.getDeclaration() = f2.getDeclaration() and
21+
not f1 = f2 and
22+
f1.getDeclaration() = f2.getDeclaration()
23+
}
24+
1925
from FunctionDeclarationEntry f1, FunctionDeclarationEntry f2, string case, string pluralDo
2026
where
2127
not isExcluded(f1, Declarations4Package::declarationsOfAFunctionSameNameAndTypeQuery()) and
@@ -24,12 +30,12 @@ where
2430
f1.getDeclaration() = f2.getDeclaration() and
2531
//return type check
2632
(
27-
not FunctionDeclarationTypeEquivalence<TypeNamesMatchConfig>::equalReturnTypes(f1, f2) and
33+
not FunctionDeclarationTypeEquivalence<TypeNamesMatchConfig, interestedInFunctions/2>::equalReturnTypes(f1, f2) and
2834
case = "return type" and
2935
pluralDo = "does"
3036
or
3137
//parameter type check
32-
not FunctionDeclarationTypeEquivalence<TypeNamesMatchConfig>::equalParameterTypes(f1, f2) and
38+
not FunctionDeclarationTypeEquivalence<TypeNamesMatchConfig, interestedInFunctions/2>::equalParameterTypes(f1, f2) and
3339
case = "parameter types" and
3440
pluralDo = "do"
3541
or

c/misra/src/rules/RULE-8-3/DeclarationsOfAnObjectSameNameAndType.ql

+9-10
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,6 @@ import cpp
1616
import codingstandards.c.misra
1717
import codingstandards.cpp.types.Compatible
1818

19-
class RelevantType extends Type {
20-
RelevantType() {
21-
exists(VariableDeclarationEntry decl |
22-
(relevantPair(decl, _) or relevantPair(_, decl)) and
23-
decl.getType() = this
24-
)
25-
}
26-
}
27-
2819
predicate relevantPair(VariableDeclarationEntry decl1, VariableDeclarationEntry decl2) {
2920
not decl1 = decl2 and
3021
not decl1.getVariable().getDeclaringType().isAnonymous() and
@@ -43,12 +34,20 @@ predicate relevantPair(VariableDeclarationEntry decl1, VariableDeclarationEntry
4334
)
4435
}
4536

37+
predicate relevantTypes(Type a, Type b) {
38+
exists(VariableDeclarationEntry varA, VariableDeclarationEntry varB |
39+
a = varA.getType() and
40+
b = varB.getType() and
41+
relevantPair(varA, varB)
42+
)
43+
}
44+
4645
from VariableDeclarationEntry decl1, VariableDeclarationEntry decl2
4746
where
4847
not isExcluded(decl1, Declarations4Package::declarationsOfAnObjectSameNameAndTypeQuery()) and
4948
not isExcluded(decl2, Declarations4Package::declarationsOfAnObjectSameNameAndTypeQuery()) and
5049
relevantPair(decl1, decl2) and
51-
not TypeEquivalence<TypeNamesMatchConfig, RelevantType>::equalTypes(decl1.getType(),
50+
not TypeEquivalence<TypeNamesMatchConfig, relevantTypes/2>::equalTypes(decl1.getType(),
5251
decl2.getType())
5352
select decl1,
5453
"The object $@ of type " + decl1.getType().toString() +

c/misra/src/rules/RULE-8-4/CompatibleDeclarationFunctionDefined.ql

+14-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ import codingstandards.c.misra
1919
import codingstandards.cpp.Identifiers
2020
import codingstandards.cpp.types.Compatible
2121

22+
predicate interestedInFunctions(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
23+
f1.getDeclaration() instanceof ExternalIdentifiers and
24+
f1.isDefinition() and
25+
f1.getName() = f2.getName() and
26+
f1.getDeclaration() = f2.getDeclaration() and
27+
not f2.isDefinition() and
28+
not f1.isFromTemplateInstantiation(_) and
29+
not f2.isFromTemplateInstantiation(_)
30+
}
31+
2232
from FunctionDeclarationEntry f1
2333
where
2434
not isExcluded(f1, Declarations4Package::compatibleDeclarationFunctionDefinedQuery()) and
@@ -38,10 +48,12 @@ where
3848
f2.getDeclaration() = f1.getDeclaration() and
3949
(
4050
//return types differ
41-
not FunctionDeclarationTypeEquivalence<TypesCompatibleConfig>::equalReturnTypes(f1, f2)
51+
not FunctionDeclarationTypeEquivalence<TypesCompatibleConfig, interestedInFunctions/2>::equalReturnTypes(f1,
52+
f2)
4253
or
4354
//parameter types differ
44-
not FunctionDeclarationTypeEquivalence<TypesCompatibleConfig>::equalParameterTypes(f1, f2)
55+
not FunctionDeclarationTypeEquivalence<TypesCompatibleConfig, interestedInFunctions/2>::equalParameterTypes(f1,
56+
f2)
4557
or
4658
//parameter names differ
4759
parameterNamesUnmatched(f1, f2)

c/misra/src/rules/RULE-8-4/CompatibleDeclarationObjectDefined.ql

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import codingstandards.c.misra
1919
import codingstandards.cpp.Identifiers
2020
import codingstandards.cpp.types.Compatible
2121

22-
class RelevantType extends Type {
23-
RelevantType() {
24-
exists(VariableDeclarationEntry decl |
25-
count(VariableDeclarationEntry others | others.getDeclaration() = decl.getDeclaration()) > 1 and
26-
decl.getType() = this
27-
)
28-
}
22+
predicate relevantTypes(Type a, Type b) {
23+
exists(VariableDeclarationEntry varA, VariableDeclarationEntry varB |
24+
not varA = varB and
25+
varA.getDeclaration() = varB.getDeclaration() and
26+
a = varA.getType() and
27+
b = varB.getType()
28+
)
2929
}
3030

3131
from VariableDeclarationEntry decl1
@@ -37,7 +37,7 @@ where
3737
not exists(VariableDeclarationEntry decl2 |
3838
not decl2.isDefinition() and
3939
decl1.getDeclaration() = decl2.getDeclaration() and
40-
TypeEquivalence<TypesCompatibleConfig, RelevantType>::equalTypes(decl1.getType(),
40+
TypeEquivalence<TypesCompatibleConfig, relevantTypes/2>::equalTypes(decl1.getType(),
4141
decl2.getType())
4242
)
4343
select decl1, "No separate compatible declaration found for this definition."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- `RULE-8-3`, `RULE-8-4`, `DCL40-C`, `RULE-23-5`: `DeclarationsOfAFunctionSameNameAndType.ql`, `DeclarationsOfAnObjectSameNameAndType.ql`, `CompatibleDeclarationOfFunctionDefined.ql`, `CompatibleDeclarationObjectDefined.ql`, `IncompatibleFunctionDeclarations.ql`, `DangerousDefaultSelectionForPointerInGeneric.ql`:
2+
- Added pragmas to alter join order on function parameter equivalence (names and types).
3+
- Refactored expression which the optimizer was confused by, and compiled into a cartesian product.
4+
- Altered the module `Compatible.qll` to only perform expensive equality checks on types that are compared to a non identical other type, and those reachable from those types in the type graph. Types that are identical will trivially be considered equivalent.
5+
- `RULE-23-5`: `DangerousDefaultSelectionForPointerInGeneric.ql`:
6+
- Altered the module `SimpleAssignment.qll` in accordance with the changes to `Compatible.qll`.

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

+101-26
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class VariableType extends Type {
2525
}
2626

2727
predicate parameterNamesUnmatched(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
28-
f1.getDeclaration() = f2.getDeclaration() and
28+
pragma[only_bind_into](f1).getDeclaration() = pragma[only_bind_into](f2).getDeclaration() and
2929
exists(string p1Name, string p2Name, int i |
3030
p1Name = f1.getParameterDeclarationEntry(i).getName() and
3131
p2Name = f2.getParameterDeclarationEntry(i).getName()
@@ -208,34 +208,64 @@ signature module TypeEquivalenceSig {
208208
module DefaultEquivalence implements TypeEquivalenceSig { }
209209

210210
/**
211-
* A signature class used to restrict the set of types considered by `TypeEquivalence`, for
211+
* A signature predicate used to restrict the set of types considered by `TypeEquivalence`, for
212212
* performance reasons.
213213
*/
214-
signature class TypeSubset extends Type;
214+
signature predicate interestedInEquality(Type a, Type b);
215215

216216
/**
217217
* A module to check the equivalence of two types, as defined by the provided `TypeEquivalenceSig`.
218218
*
219-
* For performance reasons, this module is designed to be used with a `TypeSubset` that restricts
220-
* the set of considered types. All types reachable (in the type graph) from a type in the subset
221-
* will be considered. (See `RelevantType`.)
219+
* For performance reasons, this module is designed to be used with a predicate
220+
* `interestedInEquality` that restricts the set of considered types.
222221
*
223222
* To use this module, define a `TypeEquivalenceSig` module and implement a subset of `Type` that
224223
* selects the relevant root types to be considered. Then use the predicate `equalTypes(a, b)`.
224+
* Note that `equalTypes(a, b)` only holds if `interestedIn(a, b)` holds. A type is always
225+
* considered to be equal to itself, and this module does not support configurations that declare
226+
* otherwise.
227+
*
228+
* Further, `interestedInEquality(a, a)` is treated differently from `interestedInEquality(a, b)`,
229+
* assuming that `a` and `b` are not identical. This is so that we can construct a set of types
230+
* that are not identical, but still may be equivalent by the specified configuration. We also must
231+
* consider all types that are reachable from these types, as the equivalence relation is
232+
* recursive. Therefore, this module is more performant when most comparisons are identical, and
233+
* only a few are not.
225234
*/
226-
module TypeEquivalence<TypeEquivalenceSig Config, TypeSubset T> {
235+
module TypeEquivalence<TypeEquivalenceSig Config, interestedInEquality/2 interestedIn> {
227236
/**
228237
* Check whether two types are equivalent, as defined by the `TypeEquivalenceSig` module.
238+
*
239+
* This only holds if the specified predicate `interestedIn` holds for the types, and always
240+
* holds if `t1` and `t2` are identical.
229241
*/
230-
predicate equalTypes(RelevantType t1, RelevantType t2) {
242+
predicate equalTypes(Type t1, Type t2) {
243+
interestedInUnordered(t1, t2) and
244+
(
245+
// If the types are identical, they are trivially equal.
246+
t1 = t2
247+
or
248+
not t1 = t2 and
249+
equalTypesImpl(t1, t2)
250+
)
251+
}
252+
253+
/**
254+
* This implementation handles only the slow and complex cases of type equivalence, where the
255+
* types are not identical.
256+
*
257+
* Assuming that types a, b must be compared where `a` and `b` are not identical, we wish to
258+
* search only the smallest set of possible relevant types. See `RelevantType` for more.
259+
*/
260+
private predicate equalTypesImpl(RelevantType t1, RelevantType t2) {
231261
if Config::overrideTypeComparison(t1, t2, _)
232262
then Config::overrideTypeComparison(t1, t2, true)
233263
else
234264
if t1 instanceof TypedefType and Config::resolveTypedefs()
235-
then equalTypes(t1.(TypedefType).getBaseType(), t2)
265+
then equalTypesImpl(t1.(TypedefType).getBaseType(), t2)
236266
else
237267
if t2 instanceof TypedefType and Config::resolveTypedefs()
238-
then equalTypes(t1, t2.(TypedefType).getBaseType())
268+
then equalTypesImpl(t1, t2.(TypedefType).getBaseType())
239269
else (
240270
not t1 instanceof DerivedType and
241271
not t2 instanceof DerivedType and
@@ -251,13 +281,36 @@ module TypeEquivalence<TypeEquivalenceSig Config, TypeSubset T> {
251281
)
252282
}
253283

284+
/** Whether two types will be compared, regardless of order (a, b) or (b, a). */
285+
private predicate interestedInUnordered(Type t1, Type t2) {
286+
interestedIn(t1, t2) or
287+
interestedIn(t2, t1) }
288+
289+
final private class FinalType = Type;
290+
254291
/**
255-
* A type that is either part of the type subset, or that is reachable from a type in the subset.
292+
* A type that is compared to another type that is not identical. This is the set of types that
293+
* form the roots of our more expensive type equivalence analysis.
256294
*/
257-
private class RelevantType instanceof Type {
258-
RelevantType() { exists(T t | typeGraph*(t, this)) }
295+
private class InterestingType extends FinalType {
296+
InterestingType() {
297+
exists(Type inexactCompare |
298+
interestedInUnordered(this, _) and
299+
not inexactCompare = this
300+
)
301+
}
302+
}
259303

260-
string toString() { result = this.(Type).toString() }
304+
/**
305+
* A type that is reachable from an `InterestingType` (a type that is compared to a non-identical
306+
* type).
307+
*
308+
* Since type equivalence is recursive, CodeQL will consider the equality of these types in a
309+
* bottom-up evaluation, with leaf nodes first. Therefore, this set must be as small as possible
310+
* in order to be efficient.
311+
*/
312+
private class RelevantType extends FinalType {
313+
RelevantType() { exists(InterestingType t | typeGraph*(t, this)) }
261314
}
262315

263316
private class RelevantDerivedType extends RelevantType instanceof DerivedType {
@@ -296,7 +349,7 @@ module TypeEquivalence<TypeEquivalenceSig Config, TypeSubset T> {
296349
bindingset[t1, t2]
297350
private predicate equalDerivedTypes(RelevantDerivedType t1, RelevantDerivedType t2) {
298351
exists(Boolean baseTypesEqual |
299-
(baseTypesEqual = true implies equalTypes(t1.getBaseType(), t2.getBaseType())) and
352+
(baseTypesEqual = true implies equalTypesImpl(t1.getBaseType(), t2.getBaseType())) and
300353
(
301354
Config::equalPointerTypes(t1, t2, baseTypesEqual)
302355
or
@@ -310,20 +363,20 @@ module TypeEquivalence<TypeEquivalenceSig Config, TypeSubset T> {
310363
// Note that this case is different from the above, in that we don't merely get the base
311364
// type (as that could be a TypedefType that points to another SpecifiedType). We need to
312365
// unspecify the type to see if the base types are equal.
313-
(unspecifiedTypesEqual = true implies equalTypes(unspecify(t1), unspecify(t2))) and
366+
(unspecifiedTypesEqual = true implies equalTypesImpl(unspecify(t1), unspecify(t2))) and
314367
Config::equalSpecifiedTypes(t1, t2, unspecifiedTypesEqual)
315368
)
316369
}
317370

318371
bindingset[t1, t2]
319372
private predicate equalFunctionTypes(RelevantFunctionType t1, RelevantFunctionType t2) {
320373
exists(Boolean returnTypeEqual, Boolean parameterTypesEqual |
321-
(returnTypeEqual = true implies equalTypes(t1.getReturnType(), t2.getReturnType())) and
374+
(returnTypeEqual = true implies equalTypesImpl(t1.getReturnType(), t2.getReturnType())) and
322375
(
323376
parameterTypesEqual = true
324377
implies
325378
forall(int i | exists([t1, t2].getParameterType(i)) |
326-
equalTypes(t1.getParameterType(i), t2.getParameterType(i))
379+
equalTypesImpl(t1.getParameterType(i), t2.getParameterType(i))
327380
)
328381
) and
329382
(
@@ -337,30 +390,52 @@ module TypeEquivalence<TypeEquivalenceSig Config, TypeSubset T> {
337390
bindingset[t1, t2]
338391
private predicate equalTypedefTypes(RelevantTypedefType t1, RelevantTypedefType t2) {
339392
exists(Boolean baseTypesEqual |
340-
(baseTypesEqual = true implies equalTypes(t1.getBaseType(), t2.getBaseType())) and
393+
(baseTypesEqual = true implies equalTypesImpl(t1.getBaseType(), t2.getBaseType())) and
341394
Config::equalTypedefTypes(t1, t2, baseTypesEqual)
342395
)
343396
}
344397
}
345398

346-
module FunctionDeclarationTypeEquivalence<TypeEquivalenceSig Config> {
399+
signature predicate interestedInFunctionDeclarations(
400+
FunctionDeclarationEntry f1, FunctionDeclarationEntry f2
401+
);
402+
403+
module FunctionDeclarationTypeEquivalence<
404+
TypeEquivalenceSig Config, interestedInFunctionDeclarations/2 interestedInFunctions>
405+
{
406+
private predicate interestedInReturnTypes(Type a, Type b) {
407+
exists(FunctionDeclarationEntry aFun, FunctionDeclarationEntry bFun |
408+
interestedInFunctions(aFun, bFun) and
409+
a = aFun.getType() and
410+
b = bFun.getType()
411+
)
412+
}
413+
414+
private predicate interestedInParameterTypes(Type a, Type b) {
415+
exists(FunctionDeclarationEntry aFun, FunctionDeclarationEntry bFun, int i |
416+
interestedInFunctions(pragma[only_bind_into](aFun), pragma[only_bind_into](bFun)) and
417+
a = aFun.getParameterDeclarationEntry(i).getType() and
418+
b = bFun.getParameterDeclarationEntry(i).getType()
419+
)
420+
}
421+
347422
predicate equalReturnTypes(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
348-
TypeEquivalence<Config, FunctionSignatureType>::equalTypes(f1.getType(), f2.getType())
423+
TypeEquivalence<Config, interestedInReturnTypes/2>::equalTypes(f1.getType(), f2.getType())
349424
}
350425

351426
predicate equalParameterTypes(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
427+
interestedInFunctions(f1, f2) and
352428
f1.getDeclaration() = f2.getDeclaration() and
353429
forall(int i | exists([f1, f2].getParameterDeclarationEntry(i)) |
354430
equalParameterTypesAt(f1, f2, pragma[only_bind_into](i))
355431
)
356432
}
357433

358434
predicate equalParameterTypesAt(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2, int i) {
359-
pragma[only_bind_out](f1.getDeclaration()) = pragma[only_bind_out](f2.getDeclaration()) and
360-
TypeEquivalence<Config, FunctionSignatureType>::equalTypes(
361-
f1.getParameterDeclarationEntry(i).getType(),
362-
f2.getParameterDeclarationEntry(i).getType()
363-
)
435+
interestedInFunctions(f1, f2) and
436+
f1.getDeclaration() = f2.getDeclaration() and
437+
TypeEquivalence<Config, interestedInParameterTypes/2>::equalTypes(f1.getParameterDeclarationEntry(pragma[only_bind_into](i))
438+
.getType(), f2.getParameterDeclarationEntry(pragma[only_bind_into](i)).getType())
364439
}
365440
}
366441

0 commit comments

Comments
 (0)