FIR DFA: take all statements from ?. if result is non-null.

I.e. a?.f(b as T) != null => b is T.

This also allows to remove the copyAllInformationFrom hack by moving the
edge directly in the control flow graph.
This commit is contained in:
pyos
2022-11-19 19:13:27 +01:00
committed by teamcity
parent 3392e066df
commit 16b8811697
14 changed files with 278 additions and 245 deletions
@@ -40,51 +40,116 @@ digraph safeCalls_kt {
10 [label="Access variable R|<local>/x|"]; 10 [label="Access variable R|<local>/x|"];
11 [label="Enter safe call"]; 11 [label="Enter safe call"];
12 [label="Function call: $subj$.R|/A.foo|()"]; 12 [label="Function call: $subj$.R|/A.foo|()"];
13 [label="Exit safe call"]; 13 [label="Enter safe call"];
14 [label="Enter safe call"]; 14 [label="Const: String()"];
15 [label="Function call: $subj$.R|/A.bar|()"]; 15 [label="Function call: $subj$.R|/A.bar|(...)"];
16 [label="Exit safe call"]; 16 [label="Exit safe call"];
17 [label="Exit block"]; 17 [label="Exit safe call"];
18 [label="Exit block"];
} }
18 [label="Exit function test_1" style="filled" fillcolor=red]; 19 [label="Exit function test_1" style="filled" fillcolor=red];
} }
8 -> {9}; 8 -> {9};
9 -> {10}; 9 -> {10};
10 -> {11 13}; 10 -> {11 16};
11 -> {12}; 11 -> {12};
12 -> {13}; 12 -> {16 13};
13 -> {14 16}; 13 -> {14};
14 -> {15}; 14 -> {15};
15 -> {16}; 15 -> {17};
16 -> {17}; 16 -> {17};
17 -> {18}; 17 -> {18};
18 -> {19};
subgraph cluster_6 { subgraph cluster_6 {
color=red color=red
19 [label="Enter function test_2" style="filled" fillcolor=red]; 20 [label="Enter function test_2" style="filled" fillcolor=red];
subgraph cluster_7 { subgraph cluster_7 {
color=blue color=blue
20 [label="Enter block"]; 21 [label="Enter block"];
21 [label="Access variable R|<local>/x|"]; 22 [label="Access variable R|<local>/x|"];
22 [label="Enter safe call"]; 23 [label="Enter safe call"];
23 [label="Access variable R|/B.foo|"]; 24 [label="Access variable R|/B.foo|"];
24 [label="Exit safe call"];
25 [label="Enter safe call"]; 25 [label="Enter safe call"];
26 [label="Access variable R|/B.bar|"]; 26 [label="Access variable R|/B.bar|"];
27 [label="Exit safe call"]; 27 [label="Exit safe call"];
28 [label="Exit block"]; 28 [label="Exit safe call"];
29 [label="Exit block"];
} }
29 [label="Exit function test_2" style="filled" fillcolor=red]; 30 [label="Exit function test_2" style="filled" fillcolor=red];
} }
19 -> {20};
20 -> {21}; 20 -> {21};
21 -> {22 24}; 21 -> {22};
22 -> {23}; 22 -> {23 27};
23 -> {24}; 23 -> {24};
24 -> {25 27}; 24 -> {27 25};
25 -> {26}; 25 -> {26};
26 -> {27}; 26 -> {28};
27 -> {28}; 27 -> {28};
28 -> {29}; 28 -> {29};
29 -> {30};
subgraph cluster_8 {
color=red
31 [label="Enter function test_3" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
32 [label="Enter block"];
subgraph cluster_10 {
color=blue
33 [label="Enter when"];
subgraph cluster_11 {
color=blue
34 [label="Enter when branch condition "];
35 [label="Access variable R|<local>/x|"];
36 [label="Enter safe call"];
37 [label="Access variable R|<local>/y|"];
38 [label="Type operator: (R|<local>/y| as R|kotlin/String|)"];
39 [label="Function call: $subj$.R|/A.bar|(...)"];
40 [label="Exit safe call"];
41 [label="Const: Null(null)"];
42 [label="Equality operator !="];
43 [label="Exit when branch condition"];
}
44 [label="Synthetic else branch"];
45 [label="Enter when branch result"];
subgraph cluster_12 {
color=blue
46 [label="Enter block"];
47 [label="Access variable R|<local>/y|"];
48 [label="Smart cast: R|<local>/y|"];
49 [label="Access variable R|kotlin/String.length|"];
50 [label="Exit block"];
}
51 [label="Exit when branch result"];
52 [label="Exit when"];
}
53 [label="Exit block"];
}
54 [label="Exit function test_3" style="filled" fillcolor=red];
}
31 -> {32};
32 -> {33};
33 -> {34};
34 -> {35};
35 -> {36 40};
36 -> {37};
37 -> {38};
38 -> {39};
39 -> {40};
40 -> {41};
41 -> {42};
42 -> {43};
43 -> {45 44};
44 -> {52};
45 -> {46};
46 -> {47};
47 -> {48};
48 -> {49};
49 -> {50};
50 -> {51};
51 -> {52};
52 -> {53};
53 -> {54};
} }
@@ -2,7 +2,7 @@ FILE: safeCalls.kt
public abstract interface A : R|kotlin/Any| { public abstract interface A : R|kotlin/Any| {
public abstract fun foo(): R|A| public abstract fun foo(): R|A|
public abstract fun bar(): R|A| public abstract fun bar(x: R|kotlin/String|): R|A|
} }
public abstract interface B : R|kotlin/Any| { public abstract interface B : R|kotlin/Any| {
@@ -14,8 +14,16 @@ FILE: safeCalls.kt
} }
public final fun test_1(x: R|A?|): R|kotlin/Unit| { public final fun test_1(x: R|A?|): R|kotlin/Unit| {
R|<local>/x|?.{ $subj$.R|/A.foo|() }?.{ $subj$.R|/A.bar|() } R|<local>/x|?.{ $subj$.R|/A.foo|() }?.{ $subj$.R|/A.bar|(String()) }
} }
public final fun test_2(x: R|B?|): R|kotlin/Unit| { public final fun test_2(x: R|B?|): R|kotlin/Unit| {
R|<local>/x|?.{ $subj$.R|/B.foo| }?.{ $subj$.R|/B.bar| } R|<local>/x|?.{ $subj$.R|/B.foo| }?.{ $subj$.R|/B.bar| }
} }
public final fun test_3(x: R|A?|, y: R|kotlin/String?|): R|kotlin/Unit| {
when () {
!=(R|<local>/x|?.{ $subj$.R|/A.bar|((R|<local>/y| as R|kotlin/String|)) }, Null(null)) -> {
R|<local>/y|.R|kotlin/String.length|
}
}
}
@@ -1,7 +1,7 @@
// !DUMP_CFG // !DUMP_CFG
interface A { interface A {
fun foo(): A fun foo(): A
fun bar(): A fun bar(x: String): A
} }
interface B { interface B {
@@ -10,9 +10,15 @@ interface B {
} }
fun test_1(x: A?) { fun test_1(x: A?) {
x?.foo()?.bar() x?.foo()?.bar("")
} }
fun test_2(x: B?) { fun test_2(x: B?) {
x?.foo?.bar x?.foo?.bar
} }
fun test_3(x: A?, y: String?) {
if (x?.bar(y as String) != null) {
y.length
}
}
@@ -62,9 +62,7 @@ digraph elvis_kt {
5 -> {6}; 5 -> {6};
6 -> {7}; 6 -> {7};
7 -> {8}; 7 -> {8};
8 -> {9}; 8 -> {9 13};
8 -> {11} [color=red];
8 -> {13} [color=green];
9 -> {10}; 9 -> {10};
10 -> {11}; 10 -> {11};
11 -> {12}; 11 -> {12};
@@ -490,9 +490,7 @@ digraph returns_kt {
161 -> {162}; 161 -> {162};
162 -> {163}; 162 -> {163};
163 -> {164}; 163 -> {164};
164 -> {165}; 164 -> {165 169};
164 -> {167} [color=red];
164 -> {169} [color=green];
165 -> {166}; 165 -> {166};
166 -> {167}; 166 -> {167};
167 -> {168}; 167 -> {168};
@@ -484,12 +484,12 @@ digraph nullability_kt {
165 [label="Access variable R|<local>/q|"]; 165 [label="Access variable R|<local>/q|"];
166 [label="Enter safe call"]; 166 [label="Enter safe call"];
167 [label="Access variable R|/Q.data|"]; 167 [label="Access variable R|/Q.data|"];
168 [label="Exit safe call"]; 168 [label="Enter safe call"];
169 [label="Enter safe call"]; 169 [label="Access variable R|/MyData.s|"];
170 [label="Access variable R|/MyData.s|"]; 170 [label="Enter safe call"];
171 [label="Exit safe call"]; 171 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
172 [label="Enter safe call"]; 172 [label="Exit safe call"];
173 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 173 [label="Exit safe call"];
174 [label="Exit safe call"]; 174 [label="Exit safe call"];
175 [label="Const: Null(null)"]; 175 [label="Const: Null(null)"];
176 [label="Equality operator !="]; 176 [label="Equality operator !="];
@@ -528,13 +528,13 @@ digraph nullability_kt {
162 -> {163}; 162 -> {163};
163 -> {164}; 163 -> {164};
164 -> {165}; 164 -> {165};
165 -> {166 168}; 165 -> {166 172};
166 -> {167}; 166 -> {167};
167 -> {168}; 167 -> {172 168};
168 -> {169 171}; 168 -> {169};
169 -> {170}; 169 -> {173 170};
170 -> {171}; 170 -> {171};
171 -> {172 174}; 171 -> {174};
172 -> {173}; 172 -> {173};
173 -> {174}; 173 -> {174};
174 -> {175}; 174 -> {175};
@@ -573,18 +573,18 @@ digraph nullability_kt {
203 [label="Access variable R|<local>/q|"]; 203 [label="Access variable R|<local>/q|"];
204 [label="Enter safe call"]; 204 [label="Enter safe call"];
205 [label="Access variable R|/Q.data|"]; 205 [label="Access variable R|/Q.data|"];
206 [label="Exit safe call"]; 206 [label="Enter safe call"];
207 [label="Enter safe call"]; 207 [label="Access variable R|/MyData.s|"];
208 [label="Access variable R|/MyData.s|"]; 208 [label="Enter safe call"];
209 [label="Exit safe call"]; 209 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
210 [label="Enter safe call"]; 210 [label="Exit safe call"];
211 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 211 [label="Exit lhs of ?:"];
212 [label="Exit safe call"]; 212 [label="Lhs of ?: is not null"];
213 [label="Exit lhs of ?:"]; 213 [label="Exit safe call"];
214 [label="Enter rhs of ?:"]; 214 [label="Exit safe call"];
215 [label="Jump: ^test_6 Unit"]; 215 [label="Enter rhs of ?:"];
216 [label="Stub" style="filled" fillcolor=gray]; 216 [label="Jump: ^test_6 Unit"];
217 [label="Lhs of ?: is not null"]; 217 [label="Stub" style="filled" fillcolor=gray];
218 [label="Exit ?:"]; 218 [label="Exit ?:"];
219 [label="Access variable R|<local>/q|"]; 219 [label="Access variable R|<local>/q|"];
220 [label="Smart cast: R|<local>/q|"]; 220 [label="Smart cast: R|<local>/q|"];
@@ -607,24 +607,22 @@ digraph nullability_kt {
} }
201 -> {202}; 201 -> {202};
202 -> {203}; 202 -> {203};
203 -> {204 206}; 203 -> {204 213};
204 -> {205}; 204 -> {205};
205 -> {206}; 205 -> {213 206};
206 -> {207 209}; 206 -> {207};
207 -> {208}; 207 -> {214 208};
208 -> {209}; 208 -> {209};
209 -> {210}; 209 -> {210};
209 -> {212} [color=red];
209 -> {214} [color=green];
210 -> {211}; 210 -> {211};
211 -> {212}; 211 -> {212 215};
212 -> {213}; 212 -> {218};
213 -> {217 214}; 213 -> {214};
214 -> {215}; 214 -> {215};
215 -> {235}; 215 -> {216};
215 -> {216} [style=dotted]; 216 -> {235};
216 -> {218} [style=dotted]; 216 -> {217} [style=dotted];
217 -> {218}; 217 -> {218} [style=dotted];
218 -> {219}; 218 -> {219};
219 -> {220}; 219 -> {220};
220 -> {221}; 220 -> {221};
@@ -658,12 +656,12 @@ digraph nullability_kt {
240 [label="Access variable R|<local>/q|"]; 240 [label="Access variable R|<local>/q|"];
241 [label="Enter safe call"]; 241 [label="Enter safe call"];
242 [label="Function call: $subj$.R|/Q.fdata|()"]; 242 [label="Function call: $subj$.R|/Q.fdata|()"];
243 [label="Exit safe call"]; 243 [label="Enter safe call"];
244 [label="Enter safe call"]; 244 [label="Function call: $subj$.R|/MyData.fs|()"];
245 [label="Function call: $subj$.R|/MyData.fs|()"]; 245 [label="Enter safe call"];
246 [label="Exit safe call"]; 246 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
247 [label="Enter safe call"]; 247 [label="Exit safe call"];
248 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 248 [label="Exit safe call"];
249 [label="Exit safe call"]; 249 [label="Exit safe call"];
250 [label="Const: Null(null)"]; 250 [label="Const: Null(null)"];
251 [label="Equality operator !="]; 251 [label="Equality operator !="];
@@ -699,13 +697,13 @@ digraph nullability_kt {
237 -> {238}; 237 -> {238};
238 -> {239}; 238 -> {239};
239 -> {240}; 239 -> {240};
240 -> {241 243}; 240 -> {241 247};
241 -> {242}; 241 -> {242};
242 -> {243}; 242 -> {247 243};
243 -> {244 246}; 243 -> {244};
244 -> {245}; 244 -> {248 245};
245 -> {246}; 245 -> {246};
246 -> {247 249}; 246 -> {249};
247 -> {248}; 247 -> {248};
248 -> {249}; 248 -> {249};
249 -> {250}; 249 -> {250};
@@ -1164,12 +1162,12 @@ digraph nullability_kt {
436 [label="Access variable R|<local>/q|"]; 436 [label="Access variable R|<local>/q|"];
437 [label="Enter safe call"]; 437 [label="Enter safe call"];
438 [label="Access variable R|/QImpl.data|"]; 438 [label="Access variable R|/QImpl.data|"];
439 [label="Exit safe call"]; 439 [label="Enter safe call"];
440 [label="Enter safe call"]; 440 [label="Access variable R|/MyData.s|"];
441 [label="Access variable R|/MyData.s|"]; 441 [label="Enter safe call"];
442 [label="Exit safe call"]; 442 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
443 [label="Enter safe call"]; 443 [label="Exit safe call"];
444 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 444 [label="Exit safe call"];
445 [label="Exit safe call"]; 445 [label="Exit safe call"];
446 [label="Const: Null(null)"]; 446 [label="Const: Null(null)"];
447 [label="Equality operator !="]; 447 [label="Equality operator !="];
@@ -1248,13 +1246,13 @@ digraph nullability_kt {
433 -> {434}; 433 -> {434};
434 -> {435}; 434 -> {435};
435 -> {436}; 435 -> {436};
436 -> {437 439}; 436 -> {437 443};
437 -> {438}; 437 -> {438};
438 -> {439}; 438 -> {443 439};
439 -> {440 442}; 439 -> {440};
440 -> {441}; 440 -> {444 441};
441 -> {442}; 441 -> {442};
442 -> {443 445}; 442 -> {445};
443 -> {444}; 443 -> {444};
444 -> {445}; 444 -> {445};
445 -> {446}; 445 -> {446};
@@ -1330,12 +1328,12 @@ digraph nullability_kt {
507 [label="Access variable R|<local>/q|"]; 507 [label="Access variable R|<local>/q|"];
508 [label="Enter safe call"]; 508 [label="Enter safe call"];
509 [label="Access variable R|/QImplWithCustomGetter.data|"]; 509 [label="Access variable R|/QImplWithCustomGetter.data|"];
510 [label="Exit safe call"]; 510 [label="Enter safe call"];
511 [label="Enter safe call"]; 511 [label="Access variable R|/MyData.s|"];
512 [label="Access variable R|/MyData.s|"]; 512 [label="Enter safe call"];
513 [label="Exit safe call"]; 513 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
514 [label="Enter safe call"]; 514 [label="Exit safe call"];
515 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 515 [label="Exit safe call"];
516 [label="Exit safe call"]; 516 [label="Exit safe call"];
517 [label="Const: Null(null)"]; 517 [label="Const: Null(null)"];
518 [label="Equality operator !="]; 518 [label="Equality operator !="];
@@ -1374,13 +1372,13 @@ digraph nullability_kt {
504 -> {505}; 504 -> {505};
505 -> {506}; 505 -> {506};
506 -> {507}; 506 -> {507};
507 -> {508 510}; 507 -> {508 514};
508 -> {509}; 508 -> {509};
509 -> {510}; 509 -> {514 510};
510 -> {511 513}; 510 -> {511};
511 -> {512}; 511 -> {515 512};
512 -> {513}; 512 -> {513};
513 -> {514 516}; 513 -> {516};
514 -> {515}; 514 -> {515};
515 -> {516}; 515 -> {516};
516 -> {517}; 516 -> {517};
@@ -1425,12 +1423,12 @@ digraph nullability_kt {
547 [label="Access variable R|<local>/q|"]; 547 [label="Access variable R|<local>/q|"];
548 [label="Enter safe call"]; 548 [label="Enter safe call"];
549 [label="Access variable R|/QImplMutable.data|"]; 549 [label="Access variable R|/QImplMutable.data|"];
550 [label="Exit safe call"]; 550 [label="Enter safe call"];
551 [label="Enter safe call"]; 551 [label="Access variable R|/MyData.s|"];
552 [label="Access variable R|/MyData.s|"]; 552 [label="Enter safe call"];
553 [label="Exit safe call"]; 553 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
554 [label="Enter safe call"]; 554 [label="Exit safe call"];
555 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; 555 [label="Exit safe call"];
556 [label="Exit safe call"]; 556 [label="Exit safe call"];
557 [label="Const: Null(null)"]; 557 [label="Const: Null(null)"];
558 [label="Equality operator !="]; 558 [label="Equality operator !="];
@@ -1469,13 +1467,13 @@ digraph nullability_kt {
544 -> {545}; 544 -> {545};
545 -> {546}; 545 -> {546};
546 -> {547}; 546 -> {547};
547 -> {548 550}; 547 -> {548 554};
548 -> {549}; 548 -> {549};
549 -> {550}; 549 -> {554 550};
550 -> {551 553}; 550 -> {551};
551 -> {552}; 551 -> {555 552};
552 -> {553}; 552 -> {553};
553 -> {554 556}; 553 -> {556};
554 -> {555}; 554 -> {555};
555 -> {556}; 555 -> {556};
556 -> {557}; 556 -> {557};
@@ -142,15 +142,13 @@ digraph safeCalls_kt {
50 [label="Access variable R|<local>/x|"]; 50 [label="Access variable R|<local>/x|"];
51 [label="Smart cast: R|<local>/x|"]; 51 [label="Smart cast: R|<local>/x|"];
52 [label="Function call: $subj$.R|/A.bar|(...)"]; 52 [label="Function call: $subj$.R|/A.bar|(...)"];
53 [label="Exit safe call"]; 53 [label="Enter safe call"];
54 [label="Enter safe call"]; 54 [label="Access variable R|<local>/x|"];
55 [label="Access variable R|<local>/x|"]; 55 [label="Smart cast: R|<local>/x|"];
56 [label="Smart cast: R|<local>/x|"]; 56 [label="Function call: R|<local>/x|.R|/A.bool|()"];
57 [label="Function call: R|<local>/x|.R|/A.bool|()"]; 57 [label="Function call: $subj$.R|/foo|(...)"];
58 [label="Function call: $subj$.R|/foo|(...)"]; 58 [label="Enter safe call"];
59 [label="Exit safe call"]; 59 [label="Postponed enter to lambda"];
60 [label="Enter safe call"];
61 [label="Postponed enter to lambda"];
subgraph cluster_14 { subgraph cluster_14 {
color=blue color=blue
69 [label="Enter function anonymousFunction" style="filled" fillcolor=red]; 69 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
@@ -164,8 +162,10 @@ digraph safeCalls_kt {
} }
75 [label="Exit function anonymousFunction" style="filled" fillcolor=red]; 75 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
} }
62 [label="Postponed exit from lambda"]; 60 [label="Postponed exit from lambda"];
63 [label="Function call: $subj$.R|/let|(...)"]; 61 [label="Function call: $subj$.R|/let|(...)"];
62 [label="Exit safe call"];
63 [label="Exit safe call"];
64 [label="Exit safe call"]; 64 [label="Exit safe call"];
65 [label="Access variable R|<local>/x|"]; 65 [label="Access variable R|<local>/x|"];
66 [label="Function call: R|<local>/x|.<Unresolved name: bool>#()"]; 66 [label="Function call: R|<local>/x|.<Unresolved name: bool>#()"];
@@ -176,21 +176,21 @@ digraph safeCalls_kt {
45 -> {46}; 45 -> {46};
46 -> {47}; 46 -> {47};
47 -> {48}; 47 -> {48};
48 -> {49 53}; 48 -> {49 62};
49 -> {50}; 49 -> {50};
50 -> {51}; 50 -> {51};
51 -> {52}; 51 -> {52};
52 -> {53}; 52 -> {62 53};
53 -> {54 59}; 53 -> {54};
54 -> {55}; 54 -> {55};
55 -> {56}; 55 -> {56};
56 -> {57}; 56 -> {57};
57 -> {58}; 57 -> {63 58};
58 -> {59}; 58 -> {59};
59 -> {60 64}; 59 -> {60 69};
59 -> {69} [style=dashed];
60 -> {61}; 60 -> {61};
61 -> {62 69}; 61 -> {64};
61 -> {69} [style=dashed];
62 -> {63}; 62 -> {63};
63 -> {64}; 63 -> {64};
64 -> {65}; 64 -> {65};
@@ -213,9 +213,9 @@ digraph safeCalls_kt {
78 [label="Access variable R|<local>/x|"]; 78 [label="Access variable R|<local>/x|"];
79 [label="Enter safe call"]; 79 [label="Enter safe call"];
80 [label="Function call: $subj$.R|/A.id|()"]; 80 [label="Function call: $subj$.R|/A.id|()"];
81 [label="Exit safe call"]; 81 [label="Enter safe call"];
82 [label="Enter safe call"]; 82 [label="Function call: $subj$.R|/A.bool|()"];
83 [label="Function call: $subj$.R|/A.bool|()"]; 83 [label="Exit safe call"];
84 [label="Exit safe call"]; 84 [label="Exit safe call"];
85 [label="Access variable R|<local>/x|"]; 85 [label="Access variable R|<local>/x|"];
86 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"]; 86 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"];
@@ -225,11 +225,11 @@ digraph safeCalls_kt {
} }
76 -> {77}; 76 -> {77};
77 -> {78}; 77 -> {78};
78 -> {79 81}; 78 -> {79 83};
79 -> {80}; 79 -> {80};
80 -> {81}; 80 -> {83 81};
81 -> {82 84}; 81 -> {82};
82 -> {83}; 82 -> {84};
83 -> {84}; 83 -> {84};
84 -> {85}; 84 -> {85};
85 -> {86}; 85 -> {86};
@@ -275,12 +275,12 @@ digraph safeCalls_kt {
99 [label="Postponed exit from lambda" style="filled" fillcolor=gray]; 99 [label="Postponed exit from lambda" style="filled" fillcolor=gray];
100 [label="Function call: $subj$.R|kotlin/let|<R|A|, R|kotlin/Nothing|>(...)" style="filled" fillcolor=gray]; 100 [label="Function call: $subj$.R|kotlin/let|<R|A|, R|kotlin/Nothing|>(...)" style="filled" fillcolor=gray];
101 [label="Stub" style="filled" fillcolor=gray]; 101 [label="Stub" style="filled" fillcolor=gray];
102 [label="Exit safe call"]; 102 [label="Enter safe call" style="filled" fillcolor=gray];
103 [label="Enter safe call"]; 103 [label="Access variable R|<local>/x|" style="filled" fillcolor=gray];
104 [label="Access variable R|<local>/x|"]; 104 [label="Smart cast: R|<local>/x|" style="filled" fillcolor=gray];
105 [label="Smart cast: R|<local>/x|"]; 105 [label="Function call: R|<local>/x|.R|/A.bool|()" style="filled" fillcolor=gray];
106 [label="Function call: R|<local>/x|.R|/A.bool|()"]; 106 [label="Function call: $subj$.R|/boo|(...)" style="filled" fillcolor=gray];
107 [label="Function call: $subj$.R|/boo|(...)"]; 107 [label="Exit safe call"];
108 [label="Exit safe call"]; 108 [label="Exit safe call"];
109 [label="Access variable R|<local>/x|"]; 109 [label="Access variable R|<local>/x|"];
110 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"]; 110 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"];
@@ -290,7 +290,7 @@ digraph safeCalls_kt {
} }
93 -> {94}; 93 -> {94};
94 -> {95}; 94 -> {95};
95 -> {96 102}; 95 -> {96 107};
96 -> {97}; 96 -> {97};
97 -> {113}; 97 -> {113};
97 -> {99} [color=red]; 97 -> {99} [color=red];
@@ -299,12 +299,12 @@ digraph safeCalls_kt {
99 -> {100} [style=dotted]; 99 -> {100} [style=dotted];
100 -> {101} [style=dotted]; 100 -> {101} [style=dotted];
100 -> {112} [style=dotted] [label=onUncaughtException]; 100 -> {112} [style=dotted] [label=onUncaughtException];
101 -> {102} [style=dotted]; 101 -> {107 102} [style=dotted];
102 -> {103 108}; 102 -> {103} [style=dotted];
103 -> {104}; 103 -> {104} [style=dotted];
104 -> {105}; 104 -> {105} [style=dotted];
105 -> {106}; 105 -> {106} [style=dotted];
106 -> {107}; 106 -> {108} [style=dotted];
107 -> {108}; 107 -> {108};
108 -> {109}; 108 -> {109};
109 -> {110}; 109 -> {110};
@@ -139,9 +139,7 @@ digraph smartcastInByClause_kt {
} }
24 -> {25}; 24 -> {25};
25 -> {26}; 25 -> {26};
26 -> {27}; 26 -> {27 31};
26 -> {29} [color=red];
26 -> {31} [color=green];
27 -> {28}; 27 -> {28};
28 -> {29}; 28 -> {29};
29 -> {30}; 29 -> {30};
@@ -812,14 +812,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
} }
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) { fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
val node = graphBuilder.enterSafeCall(safeCall) val flow = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow()
val flow = node.mergeIncomingFlow()
// When calling `c` in `a?.b?.c`, all type information obtained after calling `b` is valid as we know `a`
// is non-null. In theory, this should be unnecessary if the TODOs below are implemented.
val flowFromPreviousSafeCall = (node.firstPreviousNode as? ExitSafeCallNode)?.lastNodeInNotNullCase?.flow
if (flowFromPreviousSafeCall != null) {
flow.copyAllInformationFrom(flowFromPreviousSafeCall)
}
val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return
flow.commitOperationStatement(receiverVariable notEq null) flow.commitOperationStatement(receiverVariable notEq null)
} }
@@ -828,17 +821,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitSafeCall() val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitSafeCall()
val flow = node.mergeIncomingFlow() val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow() mergePostponedLambdaExitsNode?.mergeIncomingFlow()
// If there is only 1 previous node, then this is LHS of `a?.b ?: c`; then the null-case
val receiverLastNode = node.firstPreviousNode // edge from `a` goes directly to `c` and this node's flow already assumes `b` executed.
val receiverVariable = variableStorage.getOrCreateIfRealAndUnchanged(receiverLastNode.flow, flow, safeCall.receiver) ?: return if (node.previousNodes.size < 2) return
// Otherwise if the result is non-null, then `b` executed, which implies `a` is not null
// and every statement from `b` holds.
val expressionVariable = variableStorage.getOrCreate(flow, safeCall) val expressionVariable = variableStorage.getOrCreate(flow, safeCall)
// TODO? if the callee has non-null return type, then safe-call == null => receiver == null // TODO? if the callee has non-null return type, then safe-call == null => receiver == null
// if (x?.toString() == null) { /* x == null */ } // if (x?.toString() == null) { /* x == null */ }
// TODO? all new statements in previous node's flow are valid here if receiver != null
// if (x?.whatever(y as String) != null) { /* y is String */ }
// TODO? all new implications in previous node's flow are valid here if receiver != null // TODO? all new implications in previous node's flow are valid here if receiver != null
// (that requires a second level of implications: receiver != null => condition => effect). // (that requires a second level of implications: receiver != null => condition => effect).
flow.addImplication((expressionVariable notEq null) implies (receiverVariable notEq null)) flow.addAllConditionally(expressionVariable notEq null, node.lastPreviousNode.flow)
} }
fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) { fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) {
@@ -1193,22 +1186,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
} }
fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean) { fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean) {
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis() val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis(isLhsNotNull)
val flow = node.mergeIncomingFlow() val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow() mergePostponedLambdaExitsNode?.mergeIncomingFlow()
// If LHS is never null, then the edge from RHS is dead and this node's flow already contains
val rhs = elvisExpression.rhs // all statements from LHS unconditionally.
// No need to check for reassignments - if we can make any statements about LHS, then RHS has not executed. if (isLhsNotNull) return
val lhsVariable = variableStorage.getOrCreateIfReal(flow, elvisExpression.lhs) ?: return // For any predicate P(x), if P(v) != P(u ?: v) then u != null. In general this requires two levels of
if (isLhsNotNull) { // implications, but for constant v the logic system can handle some basic cases of P(x).
flow.commitOperationStatement(lhsVariable notEq null) val rhs = (elvisExpression.rhs as? FirConstExpression<*>)?.value as? Boolean ?: return
} else if (rhs is FirConstExpression<*> && rhs.kind == ConstantValueKind.Boolean) { val elvisVariable = variableStorage.createSynthetic(elvisExpression)
// (a ?: x) != x -> a != null. The logic system can only handle cases where x is Boolean. flow.addAllConditionally(elvisVariable eq !rhs, node.firstPreviousNode.flow)
// Or null...but nobody would write that, right?
val elvisVariable = variableStorage.createSynthetic(elvisExpression)
val value = rhs.value as Boolean
flow.addImplication((elvisVariable eq !value) implies (lhsVariable notEq null))
}
} }
// Callable reference // Callable reference
@@ -1291,13 +1279,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
} }
} }
private fun FLOW.copyAllInformationFrom(other: FLOW) {
if (this === currentReceiverState) {
updateAllReceivers(this, other)
}
logicSystem.copyAllInformation(other, this)
}
private fun FLOW.addImplication(statement: Implication) { private fun FLOW.addImplication(statement: Implication) {
logicSystem.addImplication(this, statement) logicSystem.addImplication(this, statement)
} }
@@ -1348,31 +1348,29 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Safe calls ----------------------------------- // ----------------------------------- Safe calls -----------------------------------
fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode { fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode {
/*
* We create
* lastNode -> enterNode
* lastNode -> exitNode
* instead of
* lastNode -> enterNode -> exitNode
* because we need to fork flow before `enterNode`, so `exitNode`
* will have unchanged flow from `lastNode`
* which corresponds to a path with nullable receiver.
*/
val lastNode = lastNodes.pop()
val enterNode = createEnterSafeCallNode(safeCall) val enterNode = createEnterSafeCallNode(safeCall)
lastNodes.push(enterNode)
val exitNode = createExitSafeCallNode(safeCall) val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode) exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode) val lastNode = lastNodes.pop()
if (elvisRhsEnterNodes.topOrNull()?.fir?.lhs === safeCall) { if (lastNode is ExitSafeCallNode) {
//if this is safe call in lhs of elvis, we make two edges // Only the non-null branch of the previous safe call can enter this one.
// 1. Df-only edge to exit node, to get not null implications there // a ----> a.b -----> a?.b.c ------> a?.b?.c
// 2. Cf-only edge to elvis rhs // \-----\-> a?.b (null) ---^
addEdge(lastNode, exitNode, preferredKind = EdgeKind.DfgForward) addEdge(lastNode.lastPreviousNode, enterNode)
addEdge(lastNode, elvisRhsEnterNodes.top(), preferredKind = EdgeKind.CfgForward) } else {
addEdge(lastNode, enterNode)
}
val nextElvisRHS = elvisRhsEnterNodes.topOrNull()
if (nextElvisRHS?.fir?.lhs === safeCall) {
// Can skip the null edge directly to elvis RHS.
// /-----------v
// a ----> a.b ----> a?.b ----> c ----> a?.b ?: c
// \------------------------^
addEdge(lastNode, nextElvisRHS)
} else { } else {
addEdge(lastNode, exitNode) addEdge(lastNode, exitNode)
} }
lastNodes.push(enterNode)
splitDataFlowForPostponedLambdas() splitDataFlowForPostponedLambdas()
return enterNode return enterNode
} }
@@ -1403,34 +1401,28 @@ class ControlFlowGraphBuilder {
exitElvisExpressionNodes.push(it) exitElvisExpressionNodes.push(it)
} }
val typedFir = lastNodes.topOrNull()?.fir as? FirExpression
val type = typedFir?.typeRef?.coneTypeSafe<ConeKotlinType>()
val lhsExitNode = createElvisLhsExitNode(elvisExpression).also { val lhsExitNode = createElvisLhsExitNode(elvisExpression).also {
popAndAddEdge(it) popAndAddEdge(it)
} }
val lhsIsNotNullNode = createElvisLhsIsNotNullNode(elvisExpression).also { val lhsIsNotNullNode = createElvisLhsIsNotNullNode(elvisExpression).also {
val preferredKind = if (type?.isNullableNothing == true) { val lhsIsNull = elvisExpression.lhs.typeRef.coneTypeSafe<ConeKotlinType>()?.isNullableNothing == true
EdgeKind.DeadForward addEdge(lhsExitNode, it, isDead = lhsIsNull)
} else {
EdgeKind.Forward
}
addEdge(lhsExitNode, it, preferredKind = preferredKind)
addEdge(it, exitNode, propagateDeadness = false) addEdge(it, exitNode, propagateDeadness = false)
} }
val rhsEnterNode = elvisRhsEnterNodes.pop().also { val rhsEnterNode = elvisRhsEnterNodes.pop().also {
addEdge(lhsExitNode, it) // Can only have a previous node if the LHS is a safe call, in which case it's the safe
// call's receiver - then RHS is not dead unless said receiver is dead (or never null).
addEdge(lhsExitNode, it, propagateDeadness = it.previousNodes.isEmpty())
} }
lastNodes.push(rhsEnterNode) lastNodes.push(rhsEnterNode)
return Triple(lhsExitNode, lhsIsNotNullNode, rhsEnterNode) return Triple(lhsExitNode, lhsIsNotNullNode, rhsEnterNode)
} }
fun exitElvis(): Pair<ElvisExitNode, MergePostponedLambdaExitsNode?> { fun exitElvis(lhsIsNotNull: Boolean): Pair<ElvisExitNode, MergePostponedLambdaExitsNode?> {
val exitNode = exitElvisExpressionNodes.pop() val exitNode = exitElvisExpressionNodes.pop()
addNewSimpleNode(exitNode) addNewSimpleNode(exitNode, isDead = lhsIsNotNull)
exitNode.updateDeadStatus() exitNode.updateDeadStatus()
return exitNode to joinDataFlowFromPostponedLambdasWith(exitNode) return exitNode to joinDataFlowFromPostponedLambdasWith(exitNode)
} }
@@ -21,7 +21,6 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
abstract fun addImplication(flow: FLOW, implication: Implication) abstract fun addImplication(flow: FLOW, implication: Implication)
abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable) abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable)
abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int) abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int)
abstract fun copyAllInformation(from: FLOW, to: FLOW)
abstract fun isSameValueIn(a: FLOW, b: FLOW, variable: RealVariable): Boolean abstract fun isSameValueIn(a: FLOW, b: FLOW, variable: RealVariable): Boolean
abstract fun translateVariableFromConditionInStatements( abstract fun translateVariableFromConditionInStatements(
@@ -79,14 +79,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
override fun forkFlow(flow: PersistentFlow): PersistentFlow = override fun forkFlow(flow: PersistentFlow): PersistentFlow =
PersistentFlow(flow) PersistentFlow(flow)
override fun copyAllInformation(from: PersistentFlow, to: PersistentFlow) {
to.approvedTypeStatements = from.approvedTypeStatements
to.logicStatements = from.logicStatements
to.directAliasMap = from.directAliasMap
to.backwardsAliasMap = from.backwardsAliasMap
to.assignmentIndex = from.assignmentIndex
}
override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow = override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow =
foldFlow(flows, allExecute = false) foldFlow(flows, allExecute = false)
@@ -781,8 +781,6 @@ class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallEx
} }
} }
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) { class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
val lastNodeInNotNullCase: CFGNode<*>? get() = previousNodes.getOrNull(1)
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R { override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitSafeCallNode(this, data) return visitor.visitExitSafeCallNode(this, data)
} }
+14 -14
View File
@@ -634,9 +634,8 @@ digraph kt44814_kt {
210 [label="Type operator: (this@R|/FirModifierList.Companion.getModifierList|.R|/FirPsiSourceElement.psi| as? R|KtModifierListOwner|) [6]"]; 210 [label="Type operator: (this@R|/FirModifierList.Companion.getModifierList|.R|/FirPsiSourceElement.psi| as? R|KtModifierListOwner|) [6]"];
211 [label="Enter safe call [6]"]; 211 [label="Enter safe call [6]"];
212 [label="Access variable R|/KtModifierListOwner.modifierList| [6]"]; 212 [label="Access variable R|/KtModifierListOwner.modifierList| [6]"];
213 [label="Exit safe call [6]"]; 213 [label="Enter safe call [6]"];
214 [label="Enter safe call [6]"]; 214 [label="Postponed enter to lambda [7]"];
215 [label="Postponed enter to lambda [7]"];
subgraph cluster_65 { subgraph cluster_65 {
color=blue color=blue
233 [label="Enter function anonymousFunction [8]" style="filled" fillcolor=red]; 233 [label="Enter function anonymousFunction [8]" style="filled" fillcolor=red];
@@ -649,8 +648,9 @@ digraph kt44814_kt {
} }
238 [label="Exit function anonymousFunction [8]" style="filled" fillcolor=red]; 238 [label="Exit function anonymousFunction [8]" style="filled" fillcolor=red];
} }
216 [label="Postponed exit from lambda [7]"]; 215 [label="Postponed exit from lambda [7]"];
217 [label="Function call: $subj$.R|kotlin/let|<R|KtModifierList|, R|FirModifierList.FirPsiModifierList|>(...) [6]"]; 216 [label="Function call: $subj$.R|kotlin/let|<R|KtModifierList|, R|FirModifierList.FirPsiModifierList|>(...) [6]"];
217 [label="Exit safe call [6]"];
218 [label="Exit safe call [6]"]; 218 [label="Exit safe call [6]"];
219 [label="Exit block [6]"]; 219 [label="Exit block [6]"];
} }
@@ -734,15 +734,15 @@ digraph kt44814_kt {
207 -> {208}; 207 -> {208};
208 -> {209}; 208 -> {209};
209 -> {210}; 209 -> {210};
210 -> {211 213}; 210 -> {211 217};
211 -> {212}; 211 -> {212};
212 -> {213}; 212 -> {217 213};
213 -> {214 218}; 213 -> {214};
214 -> {215}; 214 -> {233};
215 -> {233}; 214 -> {215} [color=red];
215 -> {216} [color=red]; 214 -> {233} [style=dashed];
215 -> {233} [style=dashed]; 215 -> {216};
216 -> {217}; 216 -> {218};
217 -> {218}; 217 -> {218};
218 -> {221 219}; 218 -> {221 219};
219 -> {220}; 219 -> {220};
@@ -764,7 +764,7 @@ digraph kt44814_kt {
236 -> {237}; 236 -> {237};
237 -> {238}; 237 -> {238};
238 -> {221} [color=red]; 238 -> {221} [color=red];
238 -> {216} [color=green]; 238 -> {215} [color=green];
239 -> {249 240}; 239 -> {249 240};
240 -> {241}; 240 -> {241};
241 -> {242 244}; 241 -> {242 244};