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|"];
11 [label="Enter safe call"];
12 [label="Function call: $subj$.R|/A.foo|()"];
13 [label="Exit safe call"];
14 [label="Enter safe call"];
15 [label="Function call: $subj$.R|/A.bar|()"];
13 [label="Enter safe call"];
14 [label="Const: String()"];
15 [label="Function call: $subj$.R|/A.bar|(...)"];
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};
9 -> {10};
10 -> {11 13};
10 -> {11 16};
11 -> {12};
12 -> {13};
13 -> {14 16};
12 -> {16 13};
13 -> {14};
14 -> {15};
15 -> {16};
15 -> {17};
16 -> {17};
17 -> {18};
18 -> {19};
subgraph cluster_6 {
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 {
color=blue
20 [label="Enter block"];
21 [label="Access variable R|<local>/x|"];
22 [label="Enter safe call"];
23 [label="Access variable R|/B.foo|"];
24 [label="Exit safe call"];
21 [label="Enter block"];
22 [label="Access variable R|<local>/x|"];
23 [label="Enter safe call"];
24 [label="Access variable R|/B.foo|"];
25 [label="Enter safe call"];
26 [label="Access variable R|/B.bar|"];
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};
21 -> {22 24};
22 -> {23};
21 -> {22};
22 -> {23 27};
23 -> {24};
24 -> {25 27};
24 -> {27 25};
25 -> {26};
26 -> {27};
26 -> {28};
27 -> {28};
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 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| {
@@ -14,8 +14,16 @@ FILE: safeCalls.kt
}
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| {
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
interface A {
fun foo(): A
fun bar(): A
fun bar(x: String): A
}
interface B {
@@ -10,9 +10,15 @@ interface B {
}
fun test_1(x: A?) {
x?.foo()?.bar()
x?.foo()?.bar("")
}
fun test_2(x: B?) {
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};
6 -> {7};
7 -> {8};
8 -> {9};
8 -> {11} [color=red];
8 -> {13} [color=green];
8 -> {9 13};
9 -> {10};
10 -> {11};
11 -> {12};
@@ -490,9 +490,7 @@ digraph returns_kt {
161 -> {162};
162 -> {163};
163 -> {164};
164 -> {165};
164 -> {167} [color=red];
164 -> {169} [color=green];
164 -> {165 169};
165 -> {166};
166 -> {167};
167 -> {168};
@@ -484,12 +484,12 @@ digraph nullability_kt {
165 [label="Access variable R|<local>/q|"];
166 [label="Enter safe call"];
167 [label="Access variable R|/Q.data|"];
168 [label="Exit safe call"];
169 [label="Enter safe call"];
170 [label="Access variable R|/MyData.s|"];
171 [label="Exit safe call"];
172 [label="Enter safe call"];
173 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
168 [label="Enter safe call"];
169 [label="Access variable R|/MyData.s|"];
170 [label="Enter safe call"];
171 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
172 [label="Exit safe call"];
173 [label="Exit safe call"];
174 [label="Exit safe call"];
175 [label="Const: Null(null)"];
176 [label="Equality operator !="];
@@ -528,13 +528,13 @@ digraph nullability_kt {
162 -> {163};
163 -> {164};
164 -> {165};
165 -> {166 168};
165 -> {166 172};
166 -> {167};
167 -> {168};
168 -> {169 171};
169 -> {170};
167 -> {172 168};
168 -> {169};
169 -> {173 170};
170 -> {171};
171 -> {172 174};
171 -> {174};
172 -> {173};
173 -> {174};
174 -> {175};
@@ -573,18 +573,18 @@ digraph nullability_kt {
203 [label="Access variable R|<local>/q|"];
204 [label="Enter safe call"];
205 [label="Access variable R|/Q.data|"];
206 [label="Exit safe call"];
207 [label="Enter safe call"];
208 [label="Access variable R|/MyData.s|"];
209 [label="Exit safe call"];
210 [label="Enter safe call"];
211 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
212 [label="Exit safe call"];
213 [label="Exit lhs of ?:"];
214 [label="Enter rhs of ?:"];
215 [label="Jump: ^test_6 Unit"];
216 [label="Stub" style="filled" fillcolor=gray];
217 [label="Lhs of ?: is not null"];
206 [label="Enter safe call"];
207 [label="Access variable R|/MyData.s|"];
208 [label="Enter safe call"];
209 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
210 [label="Exit safe call"];
211 [label="Exit lhs of ?:"];
212 [label="Lhs of ?: is not null"];
213 [label="Exit safe call"];
214 [label="Exit safe call"];
215 [label="Enter rhs of ?:"];
216 [label="Jump: ^test_6 Unit"];
217 [label="Stub" style="filled" fillcolor=gray];
218 [label="Exit ?:"];
219 [label="Access variable R|<local>/q|"];
220 [label="Smart cast: R|<local>/q|"];
@@ -607,24 +607,22 @@ digraph nullability_kt {
}
201 -> {202};
202 -> {203};
203 -> {204 206};
203 -> {204 213};
204 -> {205};
205 -> {206};
206 -> {207 209};
207 -> {208};
205 -> {213 206};
206 -> {207};
207 -> {214 208};
208 -> {209};
209 -> {210};
209 -> {212} [color=red];
209 -> {214} [color=green];
210 -> {211};
211 -> {212};
212 -> {213};
213 -> {217 214};
211 -> {212 215};
212 -> {218};
213 -> {214};
214 -> {215};
215 -> {235};
215 -> {216} [style=dotted];
216 -> {218} [style=dotted];
217 -> {218};
215 -> {216};
216 -> {235};
216 -> {217} [style=dotted];
217 -> {218} [style=dotted];
218 -> {219};
219 -> {220};
220 -> {221};
@@ -658,12 +656,12 @@ digraph nullability_kt {
240 [label="Access variable R|<local>/q|"];
241 [label="Enter safe call"];
242 [label="Function call: $subj$.R|/Q.fdata|()"];
243 [label="Exit safe call"];
244 [label="Enter safe call"];
245 [label="Function call: $subj$.R|/MyData.fs|()"];
246 [label="Exit safe call"];
247 [label="Enter safe call"];
248 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
243 [label="Enter safe call"];
244 [label="Function call: $subj$.R|/MyData.fs|()"];
245 [label="Enter safe call"];
246 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
247 [label="Exit safe call"];
248 [label="Exit safe call"];
249 [label="Exit safe call"];
250 [label="Const: Null(null)"];
251 [label="Equality operator !="];
@@ -699,13 +697,13 @@ digraph nullability_kt {
237 -> {238};
238 -> {239};
239 -> {240};
240 -> {241 243};
240 -> {241 247};
241 -> {242};
242 -> {243};
243 -> {244 246};
244 -> {245};
242 -> {247 243};
243 -> {244};
244 -> {248 245};
245 -> {246};
246 -> {247 249};
246 -> {249};
247 -> {248};
248 -> {249};
249 -> {250};
@@ -1164,12 +1162,12 @@ digraph nullability_kt {
436 [label="Access variable R|<local>/q|"];
437 [label="Enter safe call"];
438 [label="Access variable R|/QImpl.data|"];
439 [label="Exit safe call"];
440 [label="Enter safe call"];
441 [label="Access variable R|/MyData.s|"];
442 [label="Exit safe call"];
443 [label="Enter safe call"];
444 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
439 [label="Enter safe call"];
440 [label="Access variable R|/MyData.s|"];
441 [label="Enter safe call"];
442 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
443 [label="Exit safe call"];
444 [label="Exit safe call"];
445 [label="Exit safe call"];
446 [label="Const: Null(null)"];
447 [label="Equality operator !="];
@@ -1248,13 +1246,13 @@ digraph nullability_kt {
433 -> {434};
434 -> {435};
435 -> {436};
436 -> {437 439};
436 -> {437 443};
437 -> {438};
438 -> {439};
439 -> {440 442};
440 -> {441};
438 -> {443 439};
439 -> {440};
440 -> {444 441};
441 -> {442};
442 -> {443 445};
442 -> {445};
443 -> {444};
444 -> {445};
445 -> {446};
@@ -1330,12 +1328,12 @@ digraph nullability_kt {
507 [label="Access variable R|<local>/q|"];
508 [label="Enter safe call"];
509 [label="Access variable R|/QImplWithCustomGetter.data|"];
510 [label="Exit safe call"];
511 [label="Enter safe call"];
512 [label="Access variable R|/MyData.s|"];
513 [label="Exit safe call"];
514 [label="Enter safe call"];
515 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
510 [label="Enter safe call"];
511 [label="Access variable R|/MyData.s|"];
512 [label="Enter safe call"];
513 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
514 [label="Exit safe call"];
515 [label="Exit safe call"];
516 [label="Exit safe call"];
517 [label="Const: Null(null)"];
518 [label="Equality operator !="];
@@ -1374,13 +1372,13 @@ digraph nullability_kt {
504 -> {505};
505 -> {506};
506 -> {507};
507 -> {508 510};
507 -> {508 514};
508 -> {509};
509 -> {510};
510 -> {511 513};
511 -> {512};
509 -> {514 510};
510 -> {511};
511 -> {515 512};
512 -> {513};
513 -> {514 516};
513 -> {516};
514 -> {515};
515 -> {516};
516 -> {517};
@@ -1425,12 +1423,12 @@ digraph nullability_kt {
547 [label="Access variable R|<local>/q|"];
548 [label="Enter safe call"];
549 [label="Access variable R|/QImplMutable.data|"];
550 [label="Exit safe call"];
551 [label="Enter safe call"];
552 [label="Access variable R|/MyData.s|"];
553 [label="Exit safe call"];
554 [label="Enter safe call"];
555 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
550 [label="Enter safe call"];
551 [label="Access variable R|/MyData.s|"];
552 [label="Enter safe call"];
553 [label="Function call: $subj$.R|kotlin/Int.inc|()"];
554 [label="Exit safe call"];
555 [label="Exit safe call"];
556 [label="Exit safe call"];
557 [label="Const: Null(null)"];
558 [label="Equality operator !="];
@@ -1469,13 +1467,13 @@ digraph nullability_kt {
544 -> {545};
545 -> {546};
546 -> {547};
547 -> {548 550};
547 -> {548 554};
548 -> {549};
549 -> {550};
550 -> {551 553};
551 -> {552};
549 -> {554 550};
550 -> {551};
551 -> {555 552};
552 -> {553};
553 -> {554 556};
553 -> {556};
554 -> {555};
555 -> {556};
556 -> {557};
@@ -142,15 +142,13 @@ digraph safeCalls_kt {
50 [label="Access variable R|<local>/x|"];
51 [label="Smart cast: R|<local>/x|"];
52 [label="Function call: $subj$.R|/A.bar|(...)"];
53 [label="Exit safe call"];
54 [label="Enter safe call"];
55 [label="Access variable R|<local>/x|"];
56 [label="Smart cast: R|<local>/x|"];
57 [label="Function call: R|<local>/x|.R|/A.bool|()"];
58 [label="Function call: $subj$.R|/foo|(...)"];
59 [label="Exit safe call"];
60 [label="Enter safe call"];
61 [label="Postponed enter to lambda"];
53 [label="Enter safe call"];
54 [label="Access variable R|<local>/x|"];
55 [label="Smart cast: R|<local>/x|"];
56 [label="Function call: R|<local>/x|.R|/A.bool|()"];
57 [label="Function call: $subj$.R|/foo|(...)"];
58 [label="Enter safe call"];
59 [label="Postponed enter to lambda"];
subgraph cluster_14 {
color=blue
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];
}
62 [label="Postponed exit from lambda"];
63 [label="Function call: $subj$.R|/let|(...)"];
60 [label="Postponed exit from lambda"];
61 [label="Function call: $subj$.R|/let|(...)"];
62 [label="Exit safe call"];
63 [label="Exit safe call"];
64 [label="Exit safe call"];
65 [label="Access variable R|<local>/x|"];
66 [label="Function call: R|<local>/x|.<Unresolved name: bool>#()"];
@@ -176,21 +176,21 @@ digraph safeCalls_kt {
45 -> {46};
46 -> {47};
47 -> {48};
48 -> {49 53};
48 -> {49 62};
49 -> {50};
50 -> {51};
51 -> {52};
52 -> {53};
53 -> {54 59};
52 -> {62 53};
53 -> {54};
54 -> {55};
55 -> {56};
56 -> {57};
57 -> {58};
57 -> {63 58};
58 -> {59};
59 -> {60 64};
59 -> {60 69};
59 -> {69} [style=dashed];
60 -> {61};
61 -> {62 69};
61 -> {69} [style=dashed];
61 -> {64};
62 -> {63};
63 -> {64};
64 -> {65};
@@ -213,9 +213,9 @@ digraph safeCalls_kt {
78 [label="Access variable R|<local>/x|"];
79 [label="Enter safe call"];
80 [label="Function call: $subj$.R|/A.id|()"];
81 [label="Exit safe call"];
82 [label="Enter safe call"];
83 [label="Function call: $subj$.R|/A.bool|()"];
81 [label="Enter safe call"];
82 [label="Function call: $subj$.R|/A.bool|()"];
83 [label="Exit safe call"];
84 [label="Exit safe call"];
85 [label="Access variable R|<local>/x|"];
86 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"];
@@ -225,11 +225,11 @@ digraph safeCalls_kt {
}
76 -> {77};
77 -> {78};
78 -> {79 81};
78 -> {79 83};
79 -> {80};
80 -> {81};
81 -> {82 84};
82 -> {83};
80 -> {83 81};
81 -> {82};
82 -> {84};
83 -> {84};
84 -> {85};
85 -> {86};
@@ -275,12 +275,12 @@ digraph safeCalls_kt {
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];
101 [label="Stub" style="filled" fillcolor=gray];
102 [label="Exit safe call"];
103 [label="Enter safe call"];
104 [label="Access variable R|<local>/x|"];
105 [label="Smart cast: R|<local>/x|"];
106 [label="Function call: R|<local>/x|.R|/A.bool|()"];
107 [label="Function call: $subj$.R|/boo|(...)"];
102 [label="Enter safe call" style="filled" fillcolor=gray];
103 [label="Access variable R|<local>/x|" style="filled" fillcolor=gray];
104 [label="Smart cast: R|<local>/x|" style="filled" fillcolor=gray];
105 [label="Function call: R|<local>/x|.R|/A.bool|()" style="filled" fillcolor=gray];
106 [label="Function call: $subj$.R|/boo|(...)" style="filled" fillcolor=gray];
107 [label="Exit safe call"];
108 [label="Exit safe call"];
109 [label="Access variable R|<local>/x|"];
110 [label="Function call: R|<local>/x|.<Inapplicable(UNSAFE_CALL): /A.id>#()"];
@@ -290,7 +290,7 @@ digraph safeCalls_kt {
}
93 -> {94};
94 -> {95};
95 -> {96 102};
95 -> {96 107};
96 -> {97};
97 -> {113};
97 -> {99} [color=red];
@@ -299,12 +299,12 @@ digraph safeCalls_kt {
99 -> {100} [style=dotted];
100 -> {101} [style=dotted];
100 -> {112} [style=dotted] [label=onUncaughtException];
101 -> {102} [style=dotted];
102 -> {103 108};
103 -> {104};
104 -> {105};
105 -> {106};
106 -> {107};
101 -> {107 102} [style=dotted];
102 -> {103} [style=dotted];
103 -> {104} [style=dotted];
104 -> {105} [style=dotted];
105 -> {106} [style=dotted];
106 -> {108} [style=dotted];
107 -> {108};
108 -> {109};
109 -> {110};
@@ -139,9 +139,7 @@ digraph smartcastInByClause_kt {
}
24 -> {25};
25 -> {26};
26 -> {27};
26 -> {29} [color=red];
26 -> {31} [color=green];
26 -> {27 31};
27 -> {28};
28 -> {29};
29 -> {30};
@@ -812,14 +812,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
val node = graphBuilder.enterSafeCall(safeCall)
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 flow = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow()
val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return
flow.commitOperationStatement(receiverVariable notEq null)
}
@@ -828,17 +821,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitSafeCall()
val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow()
val receiverLastNode = node.firstPreviousNode
val receiverVariable = variableStorage.getOrCreateIfRealAndUnchanged(receiverLastNode.flow, flow, safeCall.receiver) ?: return
// If there is only 1 previous node, then this is LHS of `a?.b ?: c`; then the null-case
// edge from `a` goes directly to `c` and this node's flow already assumes `b` executed.
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)
// TODO? if the callee has non-null return type, then safe-call == null => receiver == 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
// (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) {
@@ -1193,22 +1186,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean) {
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis()
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis(isLhsNotNull)
val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow()
val rhs = elvisExpression.rhs
// No need to check for reassignments - if we can make any statements about LHS, then RHS has not executed.
val lhsVariable = variableStorage.getOrCreateIfReal(flow, elvisExpression.lhs) ?: return
if (isLhsNotNull) {
flow.commitOperationStatement(lhsVariable notEq null)
} else if (rhs is FirConstExpression<*> && rhs.kind == ConstantValueKind.Boolean) {
// (a ?: x) != x -> a != null. The logic system can only handle cases where x is Boolean.
// 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))
}
// If LHS is never null, then the edge from RHS is dead and this node's flow already contains
// all statements from LHS unconditionally.
if (isLhsNotNull) return
// For any predicate P(x), if P(v) != P(u ?: v) then u != null. In general this requires two levels of
// implications, but for constant v the logic system can handle some basic cases of P(x).
val rhs = (elvisExpression.rhs as? FirConstExpression<*>)?.value as? Boolean ?: return
val elvisVariable = variableStorage.createSynthetic(elvisExpression)
flow.addAllConditionally(elvisVariable eq !rhs, node.firstPreviousNode.flow)
}
// 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) {
logicSystem.addImplication(this, statement)
}
@@ -1348,31 +1348,29 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Safe calls -----------------------------------
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)
lastNodes.push(enterNode)
val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode)
if (elvisRhsEnterNodes.topOrNull()?.fir?.lhs === safeCall) {
//if this is safe call in lhs of elvis, we make two edges
// 1. Df-only edge to exit node, to get not null implications there
// 2. Cf-only edge to elvis rhs
addEdge(lastNode, exitNode, preferredKind = EdgeKind.DfgForward)
addEdge(lastNode, elvisRhsEnterNodes.top(), preferredKind = EdgeKind.CfgForward)
val lastNode = lastNodes.pop()
if (lastNode is ExitSafeCallNode) {
// Only the non-null branch of the previous safe call can enter this one.
// a ----> a.b -----> a?.b.c ------> a?.b?.c
// \-----\-> a?.b (null) ---^
addEdge(lastNode.lastPreviousNode, enterNode)
} 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 {
addEdge(lastNode, exitNode)
}
lastNodes.push(enterNode)
splitDataFlowForPostponedLambdas()
return enterNode
}
@@ -1403,34 +1401,28 @@ class ControlFlowGraphBuilder {
exitElvisExpressionNodes.push(it)
}
val typedFir = lastNodes.topOrNull()?.fir as? FirExpression
val type = typedFir?.typeRef?.coneTypeSafe<ConeKotlinType>()
val lhsExitNode = createElvisLhsExitNode(elvisExpression).also {
popAndAddEdge(it)
}
val lhsIsNotNullNode = createElvisLhsIsNotNullNode(elvisExpression).also {
val preferredKind = if (type?.isNullableNothing == true) {
EdgeKind.DeadForward
} else {
EdgeKind.Forward
}
addEdge(lhsExitNode, it, preferredKind = preferredKind)
val lhsIsNull = elvisExpression.lhs.typeRef.coneTypeSafe<ConeKotlinType>()?.isNullableNothing == true
addEdge(lhsExitNode, it, isDead = lhsIsNull)
addEdge(it, exitNode, propagateDeadness = false)
}
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)
return Triple(lhsExitNode, lhsIsNotNullNode, rhsEnterNode)
}
fun exitElvis(): Pair<ElvisExitNode, MergePostponedLambdaExitsNode?> {
fun exitElvis(lhsIsNotNull: Boolean): Pair<ElvisExitNode, MergePostponedLambdaExitsNode?> {
val exitNode = exitElvisExpressionNodes.pop()
addNewSimpleNode(exitNode)
addNewSimpleNode(exitNode, isDead = lhsIsNotNull)
exitNode.updateDeadStatus()
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 addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable)
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 translateVariableFromConditionInStatements(
@@ -79,14 +79,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
override fun forkFlow(flow: PersistentFlow): PersistentFlow =
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 =
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) {
val lastNodeInNotNullCase: CFGNode<*>? get() = previousNodes.getOrNull(1)
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
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]"];
211 [label="Enter safe call [6]"];
212 [label="Access variable R|/KtModifierListOwner.modifierList| [6]"];
213 [label="Exit safe call [6]"];
214 [label="Enter safe call [6]"];
215 [label="Postponed enter to lambda [7]"];
213 [label="Enter safe call [6]"];
214 [label="Postponed enter to lambda [7]"];
subgraph cluster_65 {
color=blue
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];
}
216 [label="Postponed exit from lambda [7]"];
217 [label="Function call: $subj$.R|kotlin/let|<R|KtModifierList|, R|FirModifierList.FirPsiModifierList|>(...) [6]"];
215 [label="Postponed exit from lambda [7]"];
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]"];
219 [label="Exit block [6]"];
}
@@ -734,15 +734,15 @@ digraph kt44814_kt {
207 -> {208};
208 -> {209};
209 -> {210};
210 -> {211 213};
210 -> {211 217};
211 -> {212};
212 -> {213};
213 -> {214 218};
214 -> {215};
215 -> {233};
215 -> {216} [color=red];
215 -> {233} [style=dashed];
216 -> {217};
212 -> {217 213};
213 -> {214};
214 -> {233};
214 -> {215} [color=red];
214 -> {233} [style=dashed];
215 -> {216};
216 -> {218};
217 -> {218};
218 -> {221 219};
219 -> {220};
@@ -764,7 +764,7 @@ digraph kt44814_kt {
236 -> {237};
237 -> {238};
238 -> {221} [color=red];
238 -> {216} [color=green];
238 -> {215} [color=green];
239 -> {249 240};
240 -> {241};
241 -> {242 244};