Skip to content

Commit 08e7499

Browse files
authored
Merge pull request #1011 from hvitved/csharp/implements-performance
Approved by calumgrant
2 parents eb4efc4 + 6135b5b commit 08e7499

File tree

2 files changed

+169
-97
lines changed

2 files changed

+169
-97
lines changed

csharp/ql/src/semmle/code/csharp/Implements.qll

Lines changed: 166 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ private Virtualizable getAnImplementedInterfaceMemberForSubType(Virtualizable m,
7777
)
7878
}
7979

80-
// predicate folding for proper join order
8180
pragma[noinline]
8281
private predicate hasMemberCompatibleWithInterfaceMember(ValueOrRefType t, Virtualizable m) {
8382
m = getACompatibleInterfaceMember(t.getAMember())
@@ -128,7 +127,6 @@ private DeclarationWithAccessors getACompatibleInterfaceAccessorCandidate(Declar
128127
d.isPublic()
129128
}
130129

131-
// predicate folding for proper join-order
132130
pragma[noinline]
133131
private predicate getACompatibleInterfaceAccessorAux(
134132
DeclarationWithAccessors d, ValueOrRefType t, string name
@@ -162,47 +160,51 @@ ValueOrRefType getAPossibleImplementor(Interface i) {
162160
not result instanceof Interface
163161
}
164162

163+
private Indexer getACompatibleInterfaceIndexer0(Indexer i, int j) {
164+
result = getACompatibleInterfaceIndexerCandidate(i) and
165+
convIdentity(i.getType(), result.getType()) and
166+
j = -1
167+
or
168+
result = getACompatibleInterfaceIndexer0(i, j - 1) and
169+
convIdentity(i.getParameter(j).getType(), result.getParameter(j).getType())
170+
}
171+
165172
/**
166173
* Gets an interface indexer whose signature matches `i`'s
167174
* signature, and where `i` can potentially be accessed when
168175
* the interface indexer is accessed.
169176
*/
170177
private Indexer getACompatibleInterfaceIndexer(Indexer i) {
171-
result = getACompatibleInterfaceIndexerCandidate(i) and
172-
convIdentity(i.getType(), result.getType()) and
173-
forex(int j | j in [0 .. i.getNumberOfParameters() - 1] |
174-
convIdentity(i.getParameter(j).getType(), result.getParameter(j).getType())
175-
)
178+
result = getACompatibleInterfaceIndexer0(i, i.getNumberOfParameters() - 1)
176179
}
177180

178181
private Indexer getACompatibleInterfaceIndexerCandidate(Indexer i) {
179182
getACompatibleInterfaceIndexerAux(result, i.getDeclaringType()) and
180183
i.isPublic()
181184
}
182185

183-
// predicate folding for proper join-order
184186
pragma[noinline]
185187
private predicate getACompatibleInterfaceIndexerAux(Indexer i, ValueOrRefType t) {
186188
t = getAPossibleImplementor(i.getDeclaringType())
187189
}
188190

189-
private Method getACompatibleInterfaceMethod(Method m) {
191+
private Method getACompatibleInterfaceMethod0(Method m, int i) {
190192
result = getAnInterfaceMethodCandidate(m) and
191-
forex(int i | i in [0 .. m.getNumberOfParameters()] |
192-
exists(Type t1, Type t2 |
193-
t1 = getArgumentOrReturnType(m, i) and
194-
t2 = getArgumentOrReturnType(result, i)
195-
|
196-
convIdentity(t1, t2)
197-
or
198-
// In the case where both `m` and `result` are unbound generic methods,
199-
// we need to do check for structural equality modulo the method type
200-
// parameters
201-
structurallyCompatible(m, result, t1, t2)
202-
)
193+
i = -1
194+
or
195+
result = getACompatibleInterfaceMethod0(m, i - 1) and
196+
exists(Type t1, Type t2 |
197+
t1 = getArgumentOrReturnType(m, i) and
198+
t2 = getArgumentOrReturnType(result, i)
199+
|
200+
Gvn::getGlobalValueNumber(t1) = Gvn::getGlobalValueNumber(t2)
203201
)
204202
}
205203

204+
private Method getACompatibleInterfaceMethod(Method m) {
205+
result = getACompatibleInterfaceMethod0(m, m.getNumberOfParameters())
206+
}
207+
206208
/**
207209
* Gets an interface method that may potentially be implemented by `m`.
208210
*
@@ -215,7 +217,6 @@ private Method getAnInterfaceMethodCandidate(Method m) {
215217
m.isPublic()
216218
}
217219

218-
// predicate folding for proper join-order
219220
pragma[nomagic]
220221
private predicate getAPotentialInterfaceMethodAux(
221222
Method m, ValueOrRefType t, string name, int params
@@ -232,90 +233,158 @@ private Type getArgumentOrReturnType(Method m, int i) {
232233
}
233234

234235
/**
235-
* Holds if `m2` is an interface method candidate for `m1`
236-
* (`m2 = getAnInterfaceMethodCandidate(m1)`), both `m1` and `m2` are
237-
* unbound generic methods, `t1` is a structural sub term of a
238-
* parameter type of `m1`, `t2` is a structural sub term of a parameter
239-
* (at the same index) type of `m2`, and `t1` and `t2` are compatible.
236+
* INTERNAL: Do not use.
240237
*
241-
* Here, compatibility means that the two types are structurally equal
242-
* modulo identity conversions and method type parameters.
238+
* Provides an implementation of Global Value Numbering for types
239+
* (see https://en.wikipedia.org/wiki/Global_value_numbering), where
240+
* types are considered equal modulo identity conversions and method
241+
* type parameters (at the same index).
243242
*/
244-
private predicate structurallyCompatible(
245-
UnboundGenericMethod m1, UnboundGenericMethod m2, Type t1, Type t2
246-
) {
247-
candidateTypes(m1, m2, t1, t2) and
248-
(
249-
// Base case: identity convertible
250-
convIdentity(t1, t2)
251-
or
252-
// Base case: method type parameter (at same index)
253-
exists(int i | structurallyCompatibleTypeParameter(m1, m2, i, t1, m2.getTypeParameter(i)))
254-
or
255-
// Recursive case
256-
(
257-
t1 instanceof PointerType and t2 instanceof PointerType
243+
module Gvn {
244+
private newtype TCompoundTypeKind =
245+
TPointerTypeKind() or
246+
TNullableTypeKind() or
247+
TArrayTypeKind(int dim, int rnk) {
248+
exists(ArrayType at | dim = at.getDimension() and rnk = at.getRank())
249+
} or
250+
TConstructedType(UnboundGenericType ugt)
251+
252+
/** A type kind for a compound type. */
253+
class CompoundTypeKind extends TCompoundTypeKind {
254+
int getNumberOfTypeParameters() {
255+
this = TPointerTypeKind() and result = 1
258256
or
259-
t1 instanceof NullableType and t2 instanceof NullableType
257+
this = TNullableTypeKind() and result = 1
260258
or
261-
t1.(ArrayType).hasSameShapeAs(t2.(ArrayType))
259+
this = TArrayTypeKind(_, _) and result = 1
262260
or
263-
t1.(ConstructedType).getUnboundGeneric() = t2.(ConstructedType).getUnboundGeneric()
264-
) and
265-
structurallyCompatibleChildren(m1, m2, t1, t2, t1.getNumberOfChildren() - 1)
266-
)
267-
}
261+
exists(UnboundGenericType ugt | this = TConstructedType(ugt) |
262+
result = ugt.getNumberOfTypeParameters()
263+
)
264+
}
268265

269-
// Predicate folding to force joining on `candidateTypes` first
270-
// to prevent `private#structurallyCompatibleTypeParameter#fbbfb`
271-
pragma[nomagic]
272-
private predicate structurallyCompatibleTypeParameter(
273-
UnboundGenericMethod m1, UnboundGenericMethod m2, int i, Type t1, Type t2
274-
) {
275-
candidateTypes(m1, m2, t1, t2) and
276-
t1 = m1.getTypeParameter(i)
277-
}
266+
string toString() {
267+
this = TPointerTypeKind() and result = "*"
268+
or
269+
this = TNullableTypeKind() and result = "?"
270+
or
271+
exists(int dim, int rnk | this = TArrayTypeKind(dim, rnk) |
272+
result = "[" + dim + ", " + rnk + "]"
273+
)
274+
or
275+
exists(UnboundGenericType ugt | this = TConstructedType(ugt) |
276+
result = ugt.getNameWithoutBrackets()
277+
)
278+
}
278279

279-
/**
280-
* Holds if the `i+1` first children of types `x` and `y` are compatible.
281-
*/
282-
private predicate structurallyCompatibleChildren(
283-
UnboundGenericMethod m1, UnboundGenericMethod m2, Type t1, Type t2, int i
284-
) {
285-
exists(Type t3, Type t4 |
286-
i = 0 and
287-
candidateChildren(t1, t2, i, t3, t4) and
288-
structurallyCompatible(m1, m2, t3, t4)
289-
)
290-
or
291-
exists(Type t3, Type t4 |
292-
structurallyCompatibleChildren(m1, m2, t1, t2, i - 1) and
293-
candidateChildren(t1, t2, i, t3, t4) and
294-
structurallyCompatible(m1, m2, t3, t4)
295-
)
296-
}
280+
Location getLocation() { result instanceof EmptyLocation }
281+
}
297282

298-
// manual magic predicate used to enumerate types relevant for structural comparison
299-
private predicate candidateTypes(UnboundGenericMethod m1, UnboundGenericMethod m2, Type t1, Type t2) {
300-
m2 = getAnInterfaceMethodCandidate(m1) and
301-
(
302-
exists(int i |
303-
t1 = getArgumentOrReturnType(m1, i) and
304-
t2 = getArgumentOrReturnType(m2, i)
305-
)
283+
/** Gets the type kind for type `t`, if any. */
284+
CompoundTypeKind getTypeKind(Type t) {
285+
result = TPointerTypeKind() and t instanceof PointerType
286+
or
287+
result = TNullableTypeKind() and t instanceof NullableType
288+
or
289+
t = any(ArrayType at | result = TArrayTypeKind(at.getDimension(), at.getRank()))
306290
or
307-
exists(Type t3, Type t4, int j |
308-
candidateTypes(m1, m2, t3, t4) and
309-
t1 = t3.getChild(j) and
310-
t2 = t4.getChild(j)
291+
result = TConstructedType(t.(ConstructedType).getUnboundGeneric())
292+
}
293+
294+
private class MethodTypeParameter extends TypeParameter {
295+
MethodTypeParameter() { this = any(UnboundGenericMethod ugm).getATypeParameter() }
296+
}
297+
298+
private class LeafType extends Type {
299+
LeafType() {
300+
not exists(this.getAChild()) and
301+
not this instanceof MethodTypeParameter
302+
}
303+
}
304+
305+
private predicate id(LeafType t, int i) = equivalenceRelation(convIdentity/2)(t, i)
306+
307+
private newtype TGvnType =
308+
TLeafGvnType(int i) { id(_, i) } or
309+
TMethodTypeParameterGvnType(int i) { i = any(MethodTypeParameter p).getIndex() } or
310+
TConstructedGvnType(TConstructedGvnType0 t)
311+
312+
private newtype TConstructedGvnType0 =
313+
TConstructedGvnTypeNil(CompoundTypeKind k) or
314+
TConstructedGvnTypeCons(TGvnType head, TConstructedGvnType0 tail) {
315+
gvnConstructedCons(_, _, head, tail)
316+
}
317+
318+
private TConstructedGvnType0 gvnConstructed(Type t, int i) {
319+
result = TConstructedGvnTypeNil(getTypeKind(t)) and i = -1
320+
or
321+
exists(TGvnType head, TConstructedGvnType0 tail | gvnConstructedCons(t, i, head, tail) |
322+
result = TConstructedGvnTypeCons(head, tail)
311323
)
312-
)
313-
}
324+
}
314325

315-
// predicate folding for proper join-order
316-
pragma[noinline]
317-
private predicate candidateChildren(Type t1, Type t2, int i, Type t3, Type t4) {
318-
candidateTypes(_, _, t1, t2) and
319-
t3 = t1.getChild(i) and
320-
t4 = t2.getChild(i)
326+
pragma[noinline]
327+
private TGvnType gvnTypeChild(Type t, int i) { result = getGlobalValueNumber(t.getChild(i)) }
328+
329+
pragma[noinline]
330+
private predicate gvnConstructedCons(Type t, int i, TGvnType head, TConstructedGvnType0 tail) {
331+
tail = gvnConstructed(t, i - 1) and
332+
head = gvnTypeChild(t, i)
333+
}
334+
335+
/** Gets the global value number for a given type. */
336+
GvnType getGlobalValueNumber(Type t) {
337+
result = TLeafGvnType(any(int i | id(t, i)))
338+
or
339+
result = TMethodTypeParameterGvnType(t.(MethodTypeParameter).getIndex())
340+
or
341+
result = TConstructedGvnType(gvnConstructed(t, getTypeKind(t).getNumberOfTypeParameters() - 1))
342+
}
343+
344+
/** A global value number for a type. */
345+
class GvnType extends TGvnType {
346+
string toString() {
347+
exists(int i | this = TLeafGvnType(i) | result = i.toString())
348+
or
349+
exists(int i | this = TMethodTypeParameterGvnType(i) | result = "M!" + i)
350+
or
351+
exists(GvnConstructedType t | this = TConstructedGvnType(t) | result = t.toString())
352+
}
353+
354+
Location getLocation() { result instanceof EmptyLocation }
355+
}
356+
357+
/** A global value number for a constructed type. */
358+
class GvnConstructedType extends TConstructedGvnType0 {
359+
private CompoundTypeKind getKind() {
360+
this = TConstructedGvnTypeNil(result)
361+
or
362+
exists(GvnConstructedType tail | this = TConstructedGvnTypeCons(_, tail) |
363+
result = tail.getKind()
364+
)
365+
}
366+
367+
private GvnType getArg(int i) {
368+
this = TConstructedGvnTypeCons(result, TConstructedGvnTypeNil(_)) and
369+
i = 0
370+
or
371+
exists(GvnConstructedType tail | this = TConstructedGvnTypeCons(result, tail) |
372+
exists(tail.getArg(i - 1))
373+
)
374+
}
375+
376+
language[monotonicAggregates]
377+
string toString() {
378+
exists(CompoundTypeKind k | k = this.getKind() |
379+
result = k + "<" +
380+
concat(int i |
381+
i in [0 .. k.getNumberOfTypeParameters() - 1]
382+
|
383+
this.getArg(i).toString(), ", "
384+
) + ">"
385+
)
386+
}
387+
388+
Location getLocation() { result instanceof EmptyLocation }
389+
}
321390
}

csharp/ql/test/library-tests/frameworks/test/Test.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
| VisualStudio.cs:9:11:9:21 | MyTestSuite | TestClass | LeafType |
12
| VisualStudio.cs:9:11:9:21 | MyTestSuite | TestClass | VSTestClass |
23
| VisualStudio.cs:12:21:12:25 | Test1 | TestMethod | InstanceCallable |
34
| VisualStudio.cs:12:21:12:25 | Test1 | TestMethod | VSTestMethod |
45
| VisualStudio.cs:17:21:17:25 | Test2 | TestMethod | InstanceCallable |
56
| VisualStudio.cs:17:21:17:25 | Test2 | TestMethod | VSTestMethod |
7+
| XUnit.cs:22:11:22:21 | MyTestSuite | TestClass | LeafType |
68
| XUnit.cs:22:11:22:21 | MyTestSuite | TestClass | XUnitTestClass |
79
| XUnit.cs:25:21:25:25 | Test1 | TestMethod | InstanceCallable |
810
| XUnit.cs:25:21:25:25 | Test1 | TestMethod | XUnitTestMethod |
911
| XUnit.cs:30:21:30:25 | Test2 | TestMethod | InstanceCallable |
1012
| XUnit.cs:30:21:30:25 | Test2 | TestMethod | XUnitTestMethod |
13+
| nunit.cs:42:11:42:21 | MyTestSuite | TestClass | LeafType |
1114
| nunit.cs:42:11:42:21 | MyTestSuite | TestClass | NUnitFixture |
1215
| nunit.cs:52:21:52:25 | Test1 | TestMethod | InstanceCallable |
1316
| nunit.cs:52:21:52:25 | Test1 | TestMethod | NUnitTestMethod |

0 commit comments

Comments
 (0)