Skip to content

Commit 1efe1e0

Browse files
committed
Java: Improve algorithm for subtyping of parameterized types.
1 parent e9b1146 commit 1efe1e0

File tree

1 file changed

+130
-97
lines changed

1 file changed

+130
-97
lines changed

java/ql/lib/semmle/code/java/Type.qll

Lines changed: 130 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ predicate hasSubtype(RefType t, Type sub) {
3131
arraySubtype(t, sub) and t != sub
3232
or
3333
// Type parameter containment for parameterized types.
34-
parContainmentSubtype(t, sub) and t != sub
34+
parContainmentSubtype(t, sub)
3535
or
3636
// Type variables are subtypes of their upper bounds.
3737
typeVarSubtypeBound(t, sub) and t != sub
@@ -59,19 +59,23 @@ private predicate arraySubtype(Array sup, Array sub) {
5959
* )
6060
* ```
6161
* For performance several transformations are made. First, the `forex` is
62-
* written as a loop where `typeArgumentsContain(_, pt, psub, n)` encode that
63-
* the `forex` holds for `i in [0..n]`. Second, the relation is split into two
64-
* cases depending on whether `pt.getNumberOfTypeArguments()` is 1 or 2+, as
65-
* this allows us to unroll the loop and collapse the first two iterations. The
66-
* base case for `typeArgumentsContain` is therefore `n=1` and this allows an
67-
* improved join order implemented by `contains01`.
62+
* written as a loop where `typePrefixContains(ppt, ppsub)` encode that
63+
* `ppt` and `ppsub` are prefixes of `pt` and `ptsub` and that
64+
* the `forex` holds for `i in [0..n-1]` where `n` is the length of the prefixes.
65+
* Second, the recursive case that determines containment of length `n+1`
66+
* prefixes is split into three cases depending on whether there is
67+
* non-reflexive type parameter containment:
68+
* - only in the length `n` prefix,
69+
* - only in the `n`th position,
70+
* - both in the length `n` prefix and the `n`th position.
6871
*/
6972

7073
private predicate parContainmentSubtype(ParameterizedType pt, ParameterizedType psub) {
71-
pt != psub and
72-
typeArgumentsContain(_, pt, psub, pt.getNumberOfTypeArguments() - 1)
73-
or
74-
typeArgumentsContain0(_, pt, psub)
74+
exists(ParameterizedPrefix ppt, ParameterizedPrefix ppsub |
75+
typePrefixContains(ppt, ppsub) and
76+
ppt.equals(pt) and
77+
ppsub.equals(psub)
78+
)
7579
}
7680

7781
/**
@@ -94,100 +98,115 @@ private RefType parameterisationTypeArgumentVarianceCand(
9498
varianceCandidate(t)
9599
}
96100

97-
/**
98-
* Holds if every type argument of `s` (up to `n` with `n >= 1`) contains the
99-
* corresponding type argument of `t`. Both `s` and `t` are constrained to
100-
* being parameterizations of `g`.
101-
*/
102-
pragma[nomagic]
103-
private predicate typeArgumentsContain(
104-
GenericType g, ParameterizedType s, ParameterizedType t, int n
105-
) {
106-
contains01(g, s, t) and n = 1
101+
private newtype TParameterizedPrefix =
102+
TGenericType(GenericType g) or
103+
TTypeParam(ParameterizedPrefix pp, RefType t) { prefixMatches(pp, t, _, _) }
104+
105+
/** Holds if `pp` is a length `n` prefix of `pt`. */
106+
private predicate prefixMatches(ParameterizedPrefix pp, ParameterizedType pt, int n) {
107+
pp = TGenericType(pt.getGenericType()) and n = 0
107108
or
108-
contains(g, s, t, n) and
109-
typeArgumentsContain(g, s, t, n - 1)
109+
exists(ParameterizedPrefix pp0, RefType t |
110+
pp = TTypeParam(pp0, t) and prefixMatches(pp0, t, pt, n - 1)
111+
)
110112
}
111113

112-
private predicate typeArgumentsContain0(
113-
GenericType g, ParameterizedType sParm, ParameterizedType tParm
114-
) {
115-
exists(RefType s, RefType t |
116-
containsAux0(g, tParm, s, t) and
117-
s = parameterisationTypeArgument(g, sParm, 0) and
118-
s != t
119-
)
114+
/**
115+
* Holds if `pp` is a length `n` prefix of `pt` and `t` is the `n`th type
116+
* argument of `pt`.
117+
*/
118+
private predicate prefixMatches(ParameterizedPrefix pp, RefType t, ParameterizedType pt, int n) {
119+
prefixMatches(pp, pt, n) and
120+
t = pt.getTypeArgument(n)
120121
}
121122

122123
/**
123-
* Holds if the `n`-th type argument of `sParm` contain the `n`-th type
124-
* argument of `tParm` for both `n = 0` and `n = 1`, where both `sParm` and
125-
* `tParm` are parameterizations of the same generic type `g`.
126-
*
127-
* This is equivalent to
128-
* ```
129-
* contains(g, sParm, tParm, 0) and
130-
* contains(g, sParm, tParm, 1)
131-
* ```
132-
* except `contains` is restricted to only include `n >= 2`.
124+
* A prefix of a `ParameterizedType`. This encodes the corresponding
125+
* `GenericType` and the first `n` type arguments where `n` is the prefix
126+
* length.
133127
*/
134-
private predicate contains01(GenericType g, ParameterizedType sParm, ParameterizedType tParm) {
135-
exists(RefType s0, RefType t0, RefType s1, RefType t1 |
136-
contains01Aux0(g, tParm, s0, t0, t1) and
137-
contains01Aux1(g, sParm, s0, s1, t1)
138-
)
128+
private class ParameterizedPrefix extends TParameterizedPrefix {
129+
string toString() { result = "ParameterizedPrefix" }
130+
131+
predicate equals(ParameterizedType pt) { prefixMatches(this, pt, pt.getNumberOfTypeArguments()) }
132+
133+
/** Holds if this prefix has length `n`, applies to `g`, and equals `TTypeParam(pp, t)`. */
134+
predicate split(GenericType g, ParameterizedPrefix pp, RefType t, int n) {
135+
this = TTypeParam(pp, t) and
136+
(
137+
pp = TGenericType(g) and n = 0
138+
or
139+
pp.split(g, _, _, n - 1)
140+
)
141+
}
139142
}
140143

144+
/**
145+
* Holds if every type argument of `pps` contains the corresponding type
146+
* argument of `ppt`. Both `pps` and `ppt` are constrained to be equal-length
147+
* prefixes of parameterizations of the same `GenericType`.
148+
*/
141149
pragma[nomagic]
142-
private predicate contains01Aux0(
143-
GenericType g, ParameterizedType tParm, RefType s0, RefType t0, RefType t1
144-
) {
145-
typeArgumentContains(g, s0, t0, 0) and
146-
t0 = parameterisationTypeArgument(g, tParm, 0) and
147-
t1 = parameterisationTypeArgument(g, tParm, 1)
150+
private predicate typePrefixContains(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
151+
// Let `pps = TTypeParam(pps0, s)` and `ppt = TTypeParam(ppt0, t)`.
152+
// Case 1: pps0 = ppt0 and typeArgumentContains(_, s, t, _)
153+
typePrefixContains_base(pps, ppt)
154+
or
155+
// Case 2: typePrefixContains(pps0, ppt0) and s = t
156+
typePrefixContains_ext_eq(pps, ppt)
157+
or
158+
// Case 3: typePrefixContains(pps0, ppt0) and typeArgumentContains(_, s, t, _)
159+
typePrefixContains_ext_neq(pps, ppt)
148160
}
149161

150-
pragma[nomagic]
151-
private predicate contains01Aux1(
152-
GenericType g, ParameterizedType sParm, RefType s0, RefType s1, RefType t1
153-
) {
154-
typeArgumentContains(g, s1, t1, 1) and
155-
s0 = parameterisationTypeArgumentVarianceCand(g, sParm, 0) and
156-
s1 = parameterisationTypeArgumentVarianceCand(g, sParm, 1)
162+
private predicate typePrefixContains_base(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
163+
exists(ParameterizedPrefix pp, RefType s |
164+
pps = TTypeParam(pp, s) and
165+
typePrefixContainsAux2(ppt, pp, s)
166+
)
157167
}
158168

159-
pragma[nomagic]
160-
private predicate containsAux0(GenericType g, ParameterizedType tParm, RefType s, RefType t) {
161-
typeArgumentContains(g, s, t, 0) and
162-
t = parameterisationTypeArgument(g, tParm, 0) and
163-
g.getNumberOfTypeParameters() = 1
169+
private predicate typePrefixContains_ext_eq(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
170+
exists(ParameterizedPrefix pps0, ParameterizedPrefix ppt0, RefType t |
171+
typePrefixContains(pragma[only_bind_into](pps0), pragma[only_bind_into](ppt0)) and
172+
pps = TTypeParam(pragma[only_bind_into](pps0), t) and
173+
ppt = TTypeParam(ppt0, t)
174+
)
164175
}
165176

166-
/**
167-
* Holds if the `n`-th type argument of `sParm` contain the `n`-th type
168-
* argument of `tParm`, where both `sParm` and `tParm` are parameterizations of
169-
* the same generic type `g`. The index `n` is restricted to `n >= 2`, the
170-
* cases `n < 2` are handled by `contains01`.
171-
*
172-
* See JLS 4.5.1, Type Arguments of Parameterized Types.
173-
*/
174-
private predicate contains(GenericType g, ParameterizedType sParm, ParameterizedType tParm, int n) {
175-
exists(RefType s, RefType t |
176-
containsAux(g, tParm, n, s, t) and
177-
s = parameterisationTypeArgumentVarianceCand(g, sParm, n)
177+
private predicate typePrefixContains_ext_neq(ParameterizedPrefix pps, ParameterizedPrefix ppt) {
178+
exists(ParameterizedPrefix ppt0, RefType s |
179+
typePrefixContainsAux1(pps, ppt0, s) and
180+
typePrefixContainsAux2(ppt, ppt0, s)
178181
)
179182
}
180183

181184
pragma[nomagic]
182-
private predicate containsAux(GenericType g, ParameterizedType tParm, int n, RefType s, RefType t) {
183-
typeArgumentContains(g, s, t, n) and
184-
t = parameterisationTypeArgument(g, tParm, n) and
185-
n >= 2
185+
private predicate typePrefixContainsAux1(
186+
ParameterizedPrefix pps, ParameterizedPrefix ppt0, RefType s
187+
) {
188+
exists(ParameterizedPrefix pps0 |
189+
typePrefixContains(pps0, ppt0) and
190+
pps = TTypeParam(pps0, s) and
191+
s instanceof Wildcard
192+
)
193+
}
194+
195+
pragma[nomagic]
196+
private predicate typePrefixContainsAux2(
197+
ParameterizedPrefix ppt, ParameterizedPrefix ppt0, RefType s
198+
) {
199+
exists(GenericType g, int n, RefType t |
200+
ppt.split(g, ppt0, t, n) and
201+
typeArgumentContains(g, s, t, n)
202+
)
186203
}
187204

188205
/**
189206
* Holds if the type argument `s` contains the type argument `t`, where both
190207
* type arguments occur as index `n` in an instantiation of `g`.
208+
*
209+
* The case `s = t` is not included.
191210
*/
192211
pragma[noinline]
193212
private predicate typeArgumentContains(GenericType g, RefType s, RefType t, int n) {
@@ -205,26 +224,26 @@ private predicate typeArgumentContainsAux2(GenericType g, RefType s, RefType t,
205224
* Holds if the type argument `s` contains the type argument `t`, where both
206225
* type arguments occur as index `n` in some parameterized types.
207226
*
227+
* The case `s = t` is not included.
228+
*
208229
* See JLS 4.5.1, Type Arguments of Parameterized Types.
209230
*/
210231
private predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
211-
exists(int i |
212-
s = parameterisationTypeArgumentVarianceCand(_, _, i) and
213-
t = parameterisationTypeArgument(_, _, n) and
214-
i <= n and
215-
n <= i
216-
|
232+
s = parameterisationTypeArgumentVarianceCand(_, _, pragma[only_bind_into](n)) and
233+
t = parameterisationTypeArgument(_, _, pragma[only_bind_into](n)) and
234+
s != t and
235+
(
217236
exists(RefType tUpperBound | tUpperBound = t.(Wildcard).getUpperBound().getType() |
218237
// ? extends T <= ? extends S if T <: S
219-
hasSubtypeStar(s.(Wildcard).getUpperBound().getType(), tUpperBound)
238+
hasSubtypeStar1(s.(Wildcard).getUpperBound().getType(), tUpperBound)
220239
or
221240
// ? extends T <= ?
222241
s.(Wildcard).isUnconstrained()
223242
)
224243
or
225244
exists(RefType tLowerBound | tLowerBound = t.(Wildcard).getLowerBound().getType() |
226245
// ? super T <= ? super S if s <: T
227-
hasSubtypeStar(tLowerBound, s.(Wildcard).getLowerBound().getType())
246+
hasSubtypeStar2(tLowerBound, s.(Wildcard).getLowerBound().getType())
228247
or
229248
// ? super T <= ?
230249
s.(Wildcard).isUnconstrained()
@@ -233,14 +252,14 @@ private predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
233252
wildcardExtendsObject(s)
234253
)
235254
or
236-
// T <= T
237-
s = t
238-
or
239255
// T <= ? extends T
240-
hasSubtypeStar(s.(Wildcard).getUpperBound().getType(), t)
256+
hasSubtypeStar1(s.(Wildcard).getUpperBound().getType(), t)
241257
or
242258
// T <= ? super T
243-
hasSubtypeStar(t, s.(Wildcard).getLowerBound().getType())
259+
hasSubtypeStar2(t, s.(Wildcard).getLowerBound().getType())
260+
// or
261+
// T <= T
262+
// but this case is handled directly in `typePrefixContains`
244263
)
245264
}
246265

@@ -249,12 +268,26 @@ private predicate wildcardExtendsObject(Wildcard wc) {
249268
wc.getUpperBound().getType() instanceof TypeObject
250269
}
251270

252-
private predicate hasSubtypeStar(RefType t, RefType sub) {
253-
sub = t
271+
private predicate subtypeStarMagic1(RefType t) { t = any(Wildcard w).getUpperBound().getType() }
272+
273+
private predicate subtypeStarMagic2(RefType t) { t = any(Wildcard w).getLowerBound().getType() }
274+
275+
pragma[nomagic]
276+
private predicate hasSubtypeStar1(RefType t, RefType sub) {
277+
sub = t and subtypeStarMagic1(t)
278+
or
279+
hasSubtype(t, sub) and subtypeStarMagic1(t)
280+
or
281+
exists(RefType mid | hasSubtypeStar1(t, mid) and hasSubtype(mid, sub))
282+
}
283+
284+
pragma[nomagic]
285+
private predicate hasSubtypeStar2(RefType t, RefType sub) {
286+
sub = t and subtypeStarMagic2(sub)
254287
or
255-
hasSubtype(t, sub)
288+
hasSubtype(t, sub) and subtypeStarMagic2(sub)
256289
or
257-
exists(RefType mid | hasSubtypeStar(t, mid) and hasSubtype(mid, sub))
290+
exists(RefType mid | hasSubtype(t, mid) and hasSubtypeStar2(mid, sub))
258291
}
259292

260293
/** Holds if type `t` declares member `m`. */

0 commit comments

Comments
 (0)