Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6583c72

Browse files
committed
Restrict pattern type guards to account for nested record matching failures
1 parent d40311e commit 6583c72

6 files changed

Lines changed: 115 additions & 17 deletions

File tree

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,4 +2658,22 @@ class RecordPatternExpr extends Expr, @recordpatternexpr {
26582658
* Gets the `i`th subpattern of this record pattern.
26592659
*/
26602660
PatternExpr getSubPattern(int i) { result.isNthChildOf(this, i) }
2661+
2662+
/**
2663+
* Holds if this record pattern matches any record of its type.
2664+
*
2665+
* For example, for `record R(Object o) { }`, pattern `R(Object o)` is unrestricted, whereas
2666+
* pattern `R(String s)` is not because it matches a subset of `R` instances, those containing `String`s.
2667+
*/
2668+
predicate isUnrestricted() {
2669+
forall(PatternExpr subPattern, int idx | subPattern = this.getSubPattern(idx) |
2670+
subPattern.getType() =
2671+
this.getType().(Record).getCanonicalConstructor().getParameter(idx).getType() and
2672+
(
2673+
subPattern instanceof LocalVariableDeclExpr
2674+
or
2675+
subPattern.(RecordPatternExpr).isUnrestricted()
2676+
)
2677+
)
2678+
}
26612679
}

java/ql/lib/semmle/code/java/controlflow/Guards.qll

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,38 @@ class TypeTestGuard extends Guard {
194194
)
195195
}
196196

197-
/** Holds if this guard tests whether `e` has type `t`. */
198-
predicate appliesTypeTest(Expr e, Type t) { e = testedExpr and t = testedType }
197+
/**
198+
* Gets the record pattern this type test binds to, if any.
199+
*/
200+
PatternExpr getPattern() {
201+
result = this.(InstanceOfExpr).getPattern()
202+
or
203+
result = this.(PatternCase).getPattern()
204+
}
205+
206+
/**
207+
* Holds if this guard tests whether `e` has type `t`.
208+
*
209+
* Note that record patterns that make at least one tighter restriction than the record's definition
210+
* (e.g. matching `record R(Object)` with `case R(String)`) means this only guarantees the tested type
211+
* on the true branch (i.e., entering such a case guarantees `testedExpr` is a `testedType`, but failing
212+
* the type test could mean a nested record or binding pattern didn't match but `testedExpr` is still
213+
* of type `testedType`.)
214+
*/
215+
predicate appliesTypeTest(Expr e, Type t, boolean testedBranch) {
216+
e = testedExpr and
217+
t = testedType and
218+
(
219+
testedBranch = true
220+
or
221+
testedBranch = false and
222+
(
223+
this.getPattern().asRecordPattern().isUnrestricted()
224+
or
225+
not this.getPattern() instanceof RecordPatternExpr
226+
)
227+
)
228+
}
199229
}
200230

201231
private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {

java/ql/lib/semmle/code/java/dataflow/TypeFlow.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ private predicate downcastSuccessor(VarAccess va, RefType t) {
418418
*/
419419
private predicate typeTestGuarded(VarAccess va, RefType t) {
420420
exists(TypeTestGuard typeTest, BaseSsaVariable v |
421-
typeTest.appliesTypeTest(v.getAUse(), t) and
421+
typeTest.appliesTypeTest(v.getAUse(), t, true) and
422422
va = v.getAUse() and
423423
guardControls_v1(typeTest, va.getBasicBlock(), true)
424424
)
@@ -429,7 +429,7 @@ private predicate typeTestGuarded(VarAccess va, RefType t) {
429429
*/
430430
predicate arrayTypeTestGuarded(ArrayAccess aa, RefType t) {
431431
exists(TypeTestGuard typeTest, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
432-
typeTest.appliesTypeTest(aa1, t) and
432+
typeTest.appliesTypeTest(aa1, t, true) and
433433
aa1.getArray() = v1.getAUse() and
434434
aa1.getIndexExpr() = v2.getAUse() and
435435
aa.getArray() = v1.getAUse() and

java/ql/lib/semmle/code/java/dispatch/VirtualDispatch.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ private module Dispatch {
197197
exists(TypeTestGuard typeTest, BaseSsaVariable v, Expr q, RefType t |
198198
source.getQualifier() = q and
199199
v.getAUse() = q and
200-
typeTest.appliesTypeTest(v.getAUse(), t) and
200+
typeTest.appliesTypeTest(v.getAUse(), t, false) and
201201
guardControls_v1(typeTest, q.getBasicBlock(), false) and
202202
tgt.getDeclaringType().getSourceDeclaration().getASourceSupertype*() = t.getErasure()
203203
)

java/ql/test/library-tests/switch-default-impossible-dispatch/Test.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,51 @@ public static void sink(int x) { }
66
interface I { void take(int x); }
77
static class C1 implements I { public void take(int x) { sink(x); } }
88
static class C2 implements I { public void take(int x) { sink(x); } }
9+
record Wrapper(Object o) implements I { public void take(int x) { sink(x); } }
10+
record WrapperWrapper(Wrapper w) implements I { public void take(int x) { sink(x); } }
911

10-
public static void test(boolean unknown, int alsoUnknown) {
12+
public static void test(int unknown, int alsoUnknown) {
1113

12-
I c1or2 = unknown ? new C1() : new C2();
14+
I i = unknown == 0 ? new C1() : unknown == 1 ? new C2() : unknown == 2 ? new Wrapper(new Object()) : new WrapperWrapper(new Wrapper(new Object()));
1315

14-
switch(c1or2) {
16+
switch(i) {
1517
case C1 c1 when alsoUnknown == 1 -> { }
16-
default -> c1or2.take(source()); // Could call either implementation
18+
default -> i.take(source()); // Could call any implementation
1719
}
1820

19-
switch(c1or2) {
21+
switch(i) {
2022
case C1 c1 -> { }
21-
default -> c1or2.take(source()); // Can't call C1.take
23+
default -> i.take(source()); // Can't call C1.take
2224
}
2325

24-
switch(c1or2) {
26+
switch(i) {
2527
case C1 c1 -> { }
26-
case null, default -> c1or2.take(source()); // Can't call C1.take
28+
case null, default -> i.take(source()); // Can't call C1.take
29+
}
30+
31+
switch(i) {
32+
case Wrapper w -> { }
33+
default -> i.take(source()); // Can't call Wrapper.take
34+
}
35+
36+
switch(i) {
37+
case Wrapper(Object o) -> { }
38+
default -> i.take(source()); // Can't call Wrapper.take
39+
}
40+
41+
switch(i) {
42+
case Wrapper(String s) -> { }
43+
default -> i.take(source()); // Could call any implementation, because this might be a Wrapper(Integer) for example.
44+
}
45+
46+
switch(i) {
47+
case WrapperWrapper(Wrapper(Object o)) -> { }
48+
default -> i.take(source()); // Can't call WrapperWrapper.take
49+
}
50+
51+
switch(i) {
52+
case WrapperWrapper(Wrapper(String s)) -> { }
53+
default -> i.take(source()); // Could call any implementation, because this might be a WrapperWrapper(Wrapper((Integer)) for example.
2754
}
2855

2956
}
Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
1-
| Test.java:16:29:16:36 | source(...) | Test.java:7:65:7:65 | x |
2-
| Test.java:16:29:16:36 | source(...) | Test.java:8:65:8:65 | x |
3-
| Test.java:21:29:21:36 | source(...) | Test.java:8:65:8:65 | x |
4-
| Test.java:26:40:26:47 | source(...) | Test.java:8:65:8:65 | x |
1+
| Test.java:18:25:18:32 | source(...) | Test.java:7:65:7:65 | x |
2+
| Test.java:18:25:18:32 | source(...) | Test.java:8:65:8:65 | x |
3+
| Test.java:18:25:18:32 | source(...) | Test.java:9:74:9:74 | x |
4+
| Test.java:18:25:18:32 | source(...) | Test.java:10:82:10:82 | x |
5+
| Test.java:23:25:23:32 | source(...) | Test.java:8:65:8:65 | x |
6+
| Test.java:23:25:23:32 | source(...) | Test.java:9:74:9:74 | x |
7+
| Test.java:23:25:23:32 | source(...) | Test.java:10:82:10:82 | x |
8+
| Test.java:28:36:28:43 | source(...) | Test.java:8:65:8:65 | x |
9+
| Test.java:28:36:28:43 | source(...) | Test.java:9:74:9:74 | x |
10+
| Test.java:28:36:28:43 | source(...) | Test.java:10:82:10:82 | x |
11+
| Test.java:33:25:33:32 | source(...) | Test.java:7:65:7:65 | x |
12+
| Test.java:33:25:33:32 | source(...) | Test.java:8:65:8:65 | x |
13+
| Test.java:33:25:33:32 | source(...) | Test.java:10:82:10:82 | x |
14+
| Test.java:38:25:38:32 | source(...) | Test.java:7:65:7:65 | x |
15+
| Test.java:38:25:38:32 | source(...) | Test.java:8:65:8:65 | x |
16+
| Test.java:38:25:38:32 | source(...) | Test.java:10:82:10:82 | x |
17+
| Test.java:43:25:43:32 | source(...) | Test.java:7:65:7:65 | x |
18+
| Test.java:43:25:43:32 | source(...) | Test.java:8:65:8:65 | x |
19+
| Test.java:43:25:43:32 | source(...) | Test.java:9:74:9:74 | x |
20+
| Test.java:43:25:43:32 | source(...) | Test.java:10:82:10:82 | x |
21+
| Test.java:48:25:48:32 | source(...) | Test.java:7:65:7:65 | x |
22+
| Test.java:48:25:48:32 | source(...) | Test.java:8:65:8:65 | x |
23+
| Test.java:48:25:48:32 | source(...) | Test.java:9:74:9:74 | x |
24+
| Test.java:53:25:53:32 | source(...) | Test.java:7:65:7:65 | x |
25+
| Test.java:53:25:53:32 | source(...) | Test.java:8:65:8:65 | x |
26+
| Test.java:53:25:53:32 | source(...) | Test.java:9:74:9:74 | x |
27+
| Test.java:53:25:53:32 | source(...) | Test.java:10:82:10:82 | x |

0 commit comments

Comments
 (0)