FIR DFA: fork flow everywhere

In theory, forking persistent flows should be cheap because of object
reuse, so the proposal here is to start from scratch and prove
redundancy of forks on a case-by-case basis. Something something better
safe than sorry.

^KT-28333 tag fixed-in-k2
^KT-28489 tag fixed-in-k2
This commit is contained in:
pyos
2022-11-08 21:33:40 +01:00
committed by teamcity
parent 757921e63e
commit edaca59d83
17 changed files with 554 additions and 683 deletions
@@ -481,11 +481,12 @@ digraph flowFromInplaceLambda2_kt {
color=blue
197 [label="Enter block"];
198 [label="Access variable R|<local>/p|"];
199 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
200 [label="Const: Int(123)"];
201 [label="Exit block"];
199 [label="Smart cast: R|<local>/p|"];
200 [label="Access variable <Inapplicable(UNSTABLE_SMARTCAST): kotlin/String.length>#"];
201 [label="Const: Int(123)"];
202 [label="Exit block"];
}
202 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
203 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
172 [label="Postponed exit from lambda"];
173 [label="Function call: R|kotlin/run|<R|kotlin/Int|>(...)"];
@@ -577,329 +578,329 @@ digraph flowFromInplaceLambda2_kt {
199 -> {200};
200 -> {201};
201 -> {202};
202 -> {175} [color=red];
202 -> {172} [color=green];
202 -> {203};
203 -> {175} [color=red];
203 -> {172} [color=green];
subgraph cluster_47 {
color=red
203 [label="Enter function test5" style="filled" fillcolor=red];
204 [label="Enter function test5" style="filled" fillcolor=red];
subgraph cluster_48 {
color=blue
204 [label="Enter block"];
205 [label="Access variable R|<local>/y|"];
206 [label="Enter safe call"];
207 [label="Postponed enter to lambda"];
205 [label="Enter block"];
206 [label="Access variable R|<local>/y|"];
207 [label="Enter safe call"];
208 [label="Postponed enter to lambda"];
subgraph cluster_49 {
color=blue
222 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
223 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_50 {
color=blue
223 [label="Enter block"];
224 [label="Access variable R|<local>/x|"];
225 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
226 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
227 [label="Exit block"];
224 [label="Enter block"];
225 [label="Access variable R|<local>/x|"];
226 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
227 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
228 [label="Exit block"];
}
228 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
229 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
208 [label="Postponed exit from lambda"];
209 [label="Function call: $subj$.R|kotlin/let|<R|kotlin/String|, R|kotlin/Nothing?|>(...)"];
210 [label="Exit safe call"];
211 [label="Const: Int(1)"];
212 [label="Postponed enter to lambda"];
209 [label="Postponed exit from lambda"];
210 [label="Function call: $subj$.R|kotlin/let|<R|kotlin/String|, R|kotlin/Nothing?|>(...)"];
211 [label="Exit safe call"];
212 [label="Const: Int(1)"];
213 [label="Postponed enter to lambda"];
subgraph cluster_51 {
color=blue
229 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
230 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_52 {
color=blue
230 [label="Enter block"];
231 [label="Const: String()"];
232 [label="Exit block"];
231 [label="Enter block"];
232 [label="Const: String()"];
233 [label="Exit block"];
}
233 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
234 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
213 [label="Postponed exit from lambda"];
214 [label="Function call: R|kotlin/run|<R|kotlin/String|>(...)"];
215 [label="Merge postponed lambda exits"];
216 [label="Call arguments union" style="filled" fillcolor=yellow];
217 [label="Function call: R|/foo|<R|kotlin/String|>(...)"];
218 [label="Access variable R|<local>/x|"];
219 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
220 [label="Exit block"];
214 [label="Postponed exit from lambda"];
215 [label="Function call: R|kotlin/run|<R|kotlin/String|>(...)"];
216 [label="Merge postponed lambda exits"];
217 [label="Call arguments union" style="filled" fillcolor=yellow];
218 [label="Function call: R|/foo|<R|kotlin/String|>(...)"];
219 [label="Access variable R|<local>/x|"];
220 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
221 [label="Exit block"];
}
221 [label="Exit function test5" style="filled" fillcolor=red];
222 [label="Exit function test5" style="filled" fillcolor=red];
}
203 -> {204};
204 -> {205};
205 -> {206 210};
206 -> {207};
207 -> {222};
207 -> {208} [color=red];
207 -> {222} [style=dashed];
208 -> {209};
205 -> {206};
206 -> {207 211};
207 -> {208};
208 -> {223};
208 -> {209} [color=red];
208 -> {223} [style=dashed];
209 -> {210};
210 -> {215 211};
211 -> {212};
212 -> {229};
212 -> {213} [color=red];
212 -> {229} [style=dashed];
213 -> {214};
214 -> {216};
215 -> {216} [color=red];
216 -> {217};
210 -> {211};
211 -> {216 212};
212 -> {213};
213 -> {230};
213 -> {214} [color=red];
213 -> {230} [style=dashed];
214 -> {215};
215 -> {217};
216 -> {217} [color=red];
217 -> {218};
218 -> {219};
219 -> {220};
220 -> {221};
222 -> {223};
221 -> {222};
223 -> {224};
224 -> {225};
225 -> {226};
226 -> {227};
227 -> {228};
228 -> {215} [color=red];
228 -> {208} [color=green];
229 -> {230};
228 -> {229};
229 -> {216} [color=red];
229 -> {209} [color=green];
230 -> {231};
231 -> {232};
232 -> {233};
233 -> {216} [color=red];
233 -> {213} [color=green];
233 -> {234};
234 -> {217} [color=red];
234 -> {214} [color=green];
subgraph cluster_53 {
color=red
234 [label="Enter function test6" style="filled" fillcolor=red];
235 [label="Enter function test6" style="filled" fillcolor=red];
subgraph cluster_54 {
color=blue
235 [label="Enter block"];
236 [label="Enter block"];
subgraph cluster_55 {
color=blue
236 [label="Enter when"];
237 [label="Enter when"];
subgraph cluster_56 {
color=blue
237 [label="Enter when branch condition "];
238 [label="Const: Boolean(true)"];
239 [label="Exit when branch condition"];
238 [label="Enter when branch condition "];
239 [label="Const: Boolean(true)"];
240 [label="Exit when branch condition"];
}
subgraph cluster_57 {
color=blue
240 [label="Enter when branch condition else"];
241 [label="Exit when branch condition"];
241 [label="Enter when branch condition else"];
242 [label="Exit when branch condition"];
}
242 [label="Enter when branch result"];
243 [label="Enter when branch result"];
subgraph cluster_58 {
color=blue
243 [label="Enter block"];
244 [label="Postponed enter to lambda"];
244 [label="Enter block"];
245 [label="Postponed enter to lambda"];
subgraph cluster_59 {
color=blue
276 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
277 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_60 {
color=blue
277 [label="Enter block"];
278 [label="Access variable R|<local>/x|"];
279 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
280 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
281 [label="Exit block"];
278 [label="Enter block"];
279 [label="Access variable R|<local>/x|"];
280 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
281 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
282 [label="Exit block"];
}
282 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
283 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
245 [label="Postponed exit from lambda"];
246 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
247 [label="Exit block"];
246 [label="Postponed exit from lambda"];
247 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
248 [label="Exit block"];
}
248 [label="Exit when branch result"];
249 [label="Enter when branch result"];
249 [label="Exit when branch result"];
250 [label="Enter when branch result"];
subgraph cluster_61 {
color=blue
250 [label="Enter block"];
251 [label="Postponed enter to lambda"];
251 [label="Enter block"];
252 [label="Postponed enter to lambda"];
subgraph cluster_62 {
color=blue
269 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
270 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_63 {
color=blue
270 [label="Enter block"];
271 [label="Access variable R|<local>/x|"];
272 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
273 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
274 [label="Exit block"];
271 [label="Enter block"];
272 [label="Access variable R|<local>/x|"];
273 [label="Type operator: (R|<local>/x| as R|kotlin/String|)"];
274 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
275 [label="Exit block"];
}
275 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
276 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
252 [label="Postponed exit from lambda"];
253 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
254 [label="Exit block"];
253 [label="Postponed exit from lambda"];
254 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
255 [label="Exit block"];
}
255 [label="Exit when branch result"];
256 [label="Exit when"];
256 [label="Exit when branch result"];
257 [label="Exit when"];
}
257 [label="Function call: R|/id|<R|kotlin/Nothing?|>(...)"];
258 [label="Const: Int(1)"];
259 [label="Postponed enter to lambda"];
258 [label="Function call: R|/id|<R|kotlin/Nothing?|>(...)"];
259 [label="Const: Int(1)"];
260 [label="Postponed enter to lambda"];
subgraph cluster_64 {
color=blue
283 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
284 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_65 {
color=blue
284 [label="Enter block"];
285 [label="Access variable R|<local>/x|"];
286 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
287 [label="Const: Int(123)"];
288 [label="Exit block"];
285 [label="Enter block"];
286 [label="Access variable R|<local>/x|"];
287 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
288 [label="Const: Int(123)"];
289 [label="Exit block"];
}
289 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
290 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
260 [label="Postponed exit from lambda"];
261 [label="Function call: R|kotlin/run|<R|kotlin/Int|>(...)"];
262 [label="Merge postponed lambda exits"];
263 [label="Call arguments union" style="filled" fillcolor=yellow];
264 [label="Function call: R|/foo|<R|kotlin/Int|>(...)"];
265 [label="Access variable R|<local>/x|"];
266 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
267 [label="Exit block"];
261 [label="Postponed exit from lambda"];
262 [label="Function call: R|kotlin/run|<R|kotlin/Int|>(...)"];
263 [label="Merge postponed lambda exits"];
264 [label="Call arguments union" style="filled" fillcolor=yellow];
265 [label="Function call: R|/foo|<R|kotlin/Int|>(...)"];
266 [label="Access variable R|<local>/x|"];
267 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
268 [label="Exit block"];
}
268 [label="Exit function test6" style="filled" fillcolor=red];
269 [label="Exit function test6" style="filled" fillcolor=red];
}
234 -> {235};
235 -> {236};
236 -> {237};
237 -> {238};
238 -> {239};
239 -> {249 240};
240 -> {241};
239 -> {240};
240 -> {250 241};
241 -> {242};
242 -> {243};
243 -> {244};
244 -> {276};
244 -> {245} [color=red];
244 -> {276} [style=dashed];
245 -> {246};
244 -> {245};
245 -> {277};
245 -> {246} [color=red];
245 -> {277} [style=dashed];
246 -> {247};
247 -> {248};
248 -> {256};
249 -> {250};
248 -> {249};
249 -> {257};
250 -> {251};
251 -> {269};
251 -> {252} [color=red];
251 -> {269} [style=dashed];
252 -> {253};
251 -> {252};
252 -> {270};
252 -> {253} [color=red];
252 -> {270} [style=dashed];
253 -> {254};
254 -> {255};
255 -> {256};
256 -> {262 257};
257 -> {258};
256 -> {257};
257 -> {263 258};
258 -> {259};
259 -> {283};
259 -> {260} [color=red];
259 -> {283} [style=dashed];
260 -> {261};
261 -> {263};
262 -> {263} [color=red];
263 -> {264};
259 -> {260};
260 -> {284};
260 -> {261} [color=red];
260 -> {284} [style=dashed];
261 -> {262};
262 -> {264};
263 -> {264} [color=red];
264 -> {265};
265 -> {266};
266 -> {267};
267 -> {268};
269 -> {270};
268 -> {269};
270 -> {271};
271 -> {272};
272 -> {273};
273 -> {274};
274 -> {275};
275 -> {262} [color=red];
275 -> {252} [color=green];
276 -> {277};
275 -> {276};
276 -> {263} [color=red];
276 -> {253} [color=green];
277 -> {278};
278 -> {279};
279 -> {280};
280 -> {281};
281 -> {282};
282 -> {262} [color=red];
282 -> {245} [color=green];
283 -> {284};
282 -> {283};
283 -> {263} [color=red];
283 -> {246} [color=green];
284 -> {285};
285 -> {286};
286 -> {287};
287 -> {288};
288 -> {289};
289 -> {263} [color=red];
289 -> {260} [color=green];
289 -> {290};
290 -> {264} [color=red];
290 -> {261} [color=green];
subgraph cluster_66 {
color=red
290 [label="Enter function test7" style="filled" fillcolor=red];
291 [label="Enter function test7" style="filled" fillcolor=red];
subgraph cluster_67 {
color=blue
291 [label="Enter block"];
292 [label="Access variable R|<local>/x|"];
293 [label="Variable declaration: lvar p: R|kotlin/String?|"];
292 [label="Enter block"];
293 [label="Access variable R|<local>/x|"];
294 [label="Variable declaration: lvar p: R|kotlin/String?|"];
subgraph cluster_68 {
color=blue
294 [label="Enter when"];
295 [label="Enter when"];
subgraph cluster_69 {
color=blue
295 [label="Enter when branch condition "];
296 [label="Access variable R|<local>/p|"];
297 [label="Const: Null(null)"];
298 [label="Equality operator !="];
299 [label="Exit when branch condition"];
296 [label="Enter when branch condition "];
297 [label="Access variable R|<local>/p|"];
298 [label="Const: Null(null)"];
299 [label="Equality operator !="];
300 [label="Exit when branch condition"];
}
300 [label="Synthetic else branch"];
301 [label="Enter when branch result"];
301 [label="Synthetic else branch"];
302 [label="Enter when branch result"];
subgraph cluster_70 {
color=blue
302 [label="Enter block"];
303 [label="Postponed enter to lambda"];
303 [label="Enter block"];
304 [label="Postponed enter to lambda"];
subgraph cluster_71 {
color=blue
321 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
322 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_72 {
color=blue
322 [label="Enter block"];
323 [label="Const: Null(null)"];
324 [label="Assignment: R|<local>/p|"];
325 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
326 [label="Exit block"];
323 [label="Enter block"];
324 [label="Const: Null(null)"];
325 [label="Assignment: R|<local>/p|"];
326 [label="Function call: R|/n|<R|kotlin/Nothing?|>()"];
327 [label="Exit block"];
}
327 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
328 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
304 [label="Postponed exit from lambda"];
305 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
306 [label="Function call: R|/id|<R|kotlin/Nothing?|>(...)"];
307 [label="Const: Int(1)"];
308 [label="Postponed enter to lambda"];
305 [label="Postponed exit from lambda"];
306 [label="Function call: R|kotlin/run|<R|kotlin/Nothing?|>(...)"];
307 [label="Function call: R|/id|<R|kotlin/Nothing?|>(...)"];
308 [label="Const: Int(1)"];
309 [label="Postponed enter to lambda"];
subgraph cluster_73 {
color=blue
328 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
329 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_74 {
color=blue
329 [label="Enter block"];
330 [label="Access variable R|<local>/p|"];
331 [label="Smart cast: R|<local>/p|"];
332 [label="Access variable <Inapplicable(UNSTABLE_SMARTCAST): kotlin/String.length>#"];
333 [label="Const: Int(123)"];
334 [label="Exit block"];
330 [label="Enter block"];
331 [label="Access variable R|<local>/p|"];
332 [label="Smart cast: R|<local>/p|"];
333 [label="Access variable <Inapplicable(UNSTABLE_SMARTCAST): kotlin/String.length>#"];
334 [label="Const: Int(123)"];
335 [label="Exit block"];
}
335 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
336 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
309 [label="Postponed exit from lambda"];
310 [label="Function call: R|kotlin/run|<R|kotlin/Int|>(...)"];
311 [label="Call arguments union" style="filled" fillcolor=yellow];
312 [label="Function call: R|/foo|<R|kotlin/Int|>(...)"];
313 [label="Access variable R|<local>/p|"];
314 [label="Smart cast: R|<local>/p|"];
315 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
316 [label="Exit block"];
310 [label="Postponed exit from lambda"];
311 [label="Function call: R|kotlin/run|<R|kotlin/Int|>(...)"];
312 [label="Call arguments union" style="filled" fillcolor=yellow];
313 [label="Function call: R|/foo|<R|kotlin/Int|>(...)"];
314 [label="Access variable R|<local>/p|"];
315 [label="Smart cast: R|<local>/p|"];
316 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
317 [label="Exit block"];
}
317 [label="Exit when branch result"];
318 [label="Exit when"];
318 [label="Exit when branch result"];
319 [label="Exit when"];
}
319 [label="Exit block"];
320 [label="Exit block"];
}
320 [label="Exit function test7" style="filled" fillcolor=red];
321 [label="Exit function test7" style="filled" fillcolor=red];
}
290 -> {291};
291 -> {292};
292 -> {293};
293 -> {294};
@@ -908,21 +909,21 @@ digraph flowFromInplaceLambda2_kt {
296 -> {297};
297 -> {298};
298 -> {299};
299 -> {301 300};
300 -> {318};
301 -> {302};
299 -> {300};
300 -> {302 301};
301 -> {319};
302 -> {303};
303 -> {321};
303 -> {304} [color=red];
303 -> {321} [style=dashed];
304 -> {305};
303 -> {304};
304 -> {322};
304 -> {305} [color=red];
304 -> {322} [style=dashed];
305 -> {306};
306 -> {307};
307 -> {308};
308 -> {328};
308 -> {309} [color=red];
308 -> {328} [style=dashed];
309 -> {310};
308 -> {309};
309 -> {329};
309 -> {310} [color=red];
309 -> {329} [style=dashed];
310 -> {311};
311 -> {312};
312 -> {313};
@@ -933,22 +934,23 @@ digraph flowFromInplaceLambda2_kt {
317 -> {318};
318 -> {319};
319 -> {320};
321 -> {322};
320 -> {321};
322 -> {323};
323 -> {324};
324 -> {325};
325 -> {326};
326 -> {327};
327 -> {311} [color=red];
327 -> {304} [color=green];
328 -> {329};
327 -> {328};
328 -> {312} [color=red];
328 -> {305} [color=green];
329 -> {330};
330 -> {331};
331 -> {332};
332 -> {333};
333 -> {334};
334 -> {335};
335 -> {311} [color=red];
335 -> {309} [color=green];
335 -> {336};
336 -> {312} [color=red];
336 -> {310} [color=green];
}
@@ -74,7 +74,7 @@ FILE: flowFromInplaceLambda2.kt
}
}
), Int(1), R|kotlin/run|<R|kotlin/Int|>(<L> = run@fun <anonymous>(): R|kotlin/Int| <inline=Inline, kind=EXACTLY_ONCE> {
R|<local>/p|.<Inapplicable(UNSAFE_CALL): kotlin/String.length>#
R|<local>/p|.<Inapplicable(UNSTABLE_SMARTCAST): kotlin/String.length>#
^ Int(123)
}
))
@@ -41,7 +41,7 @@ fun test4(x: String?) {
foo(
id(if (true) run { p = null; n() } else run { n() }),
1,
run { p<!UNSAFE_CALL!>.<!>length; 123 } // Bad (p = null possible)
run { <!SMARTCAST_IMPOSSIBLE!>p<!>.length; 123 } // Bad (p = null possible)
)
p<!UNSAFE_CALL!>.<!>length // Bad (p = null possible)
}
@@ -80,27 +80,25 @@ digraph flowFromInplaceLambda3_kt {
27 [label="Postponed enter to lambda"];
subgraph cluster_10 {
color=blue
38 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
36 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_11 {
color=blue
39 [label="Enter block"];
40 [label="Const: Int(1)"];
41 [label="Assignment: R|<local>/x|"];
42 [label="Exit block"];
37 [label="Enter block"];
38 [label="Const: Int(1)"];
39 [label="Assignment: R|<local>/x|"];
40 [label="Exit block"];
}
43 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
41 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
28 [label="Postponed exit from lambda"];
29 [label="Function call: R|/unknown|(...)"];
30 [label="Access variable R|<local>/x|"];
31 [label="Smart cast: R|<local>/x|"];
32 [label="Access variable <Unresolved name: length>#"];
33 [label="Access variable R|<local>/x|"];
34 [label="Smart cast: R|<local>/x|"];
35 [label="Function call: R|<local>/x|.<Unresolved name: inc>#()"];
36 [label="Exit block"];
31 [label="Access variable <Unresolved name: length>#"];
32 [label="Access variable R|<local>/x|"];
33 [label="Function call: R|<local>/x|.<Unresolved name: inc>#()"];
34 [label="Exit block"];
}
37 [label="Exit function test1" style="filled" fillcolor=red];
35 [label="Exit function test1" style="filled" fillcolor=red];
}
19 -> {20};
20 -> {21};
@@ -110,8 +108,8 @@ digraph flowFromInplaceLambda3_kt {
24 -> {25};
25 -> {26};
26 -> {27};
27 -> {28 38};
27 -> {38} [style=dashed];
27 -> {28 36};
27 -> {36} [style=dashed];
28 -> {29};
29 -> {30};
30 -> {31};
@@ -119,199 +117,199 @@ digraph flowFromInplaceLambda3_kt {
32 -> {33};
33 -> {34};
34 -> {35};
35 -> {36};
36 -> {37};
38 -> {43 39};
36 -> {41 37};
37 -> {38};
38 -> {39};
39 -> {40};
40 -> {41};
41 -> {42};
42 -> {43};
43 -> {28};
43 -> {38} [color=green style=dashed];
41 -> {28};
41 -> {36} [color=green style=dashed];
subgraph cluster_12 {
color=red
44 [label="Enter function test2" style="filled" fillcolor=red];
42 [label="Enter function test2" style="filled" fillcolor=red];
subgraph cluster_13 {
color=blue
45 [label="Enter block"];
46 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
47 [label="Const: String()"];
48 [label="Assignment: R|<local>/x|"];
49 [label="Access variable R|<local>/x|"];
50 [label="Smart cast: R|<local>/x|"];
51 [label="Access variable R|kotlin/String.length|"];
52 [label="Postponed enter to lambda"];
43 [label="Enter block"];
44 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
45 [label="Const: String()"];
46 [label="Assignment: R|<local>/x|"];
47 [label="Access variable R|<local>/x|"];
48 [label="Smart cast: R|<local>/x|"];
49 [label="Access variable R|kotlin/String.length|"];
50 [label="Postponed enter to lambda"];
subgraph cluster_14 {
color=blue
64 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
62 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_15 {
color=blue
65 [label="Enter block"];
66 [label="Const: Int(1)"];
67 [label="Assignment: R|<local>/x|"];
68 [label="Exit block"];
63 [label="Enter block"];
64 [label="Const: Int(1)"];
65 [label="Assignment: R|<local>/x|"];
66 [label="Exit block"];
}
69 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
67 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
53 [label="Call arguments union" style="filled" fillcolor=yellow];
54 [label="Postponed exit from lambda"];
55 [label="Function call: R|/atLeastOnce|(...)"];
56 [label="Access variable R|<local>/x|"];
57 [label="Smart cast: R|<local>/x|"];
58 [label="Access variable <Unresolved name: length>#"];
59 [label="Access variable R|<local>/x|"];
60 [label="Smart cast: R|<local>/x|"];
61 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
62 [label="Exit block"];
51 [label="Call arguments union" style="filled" fillcolor=yellow];
52 [label="Postponed exit from lambda"];
53 [label="Function call: R|/atLeastOnce|(...)"];
54 [label="Access variable R|<local>/x|"];
55 [label="Smart cast: R|<local>/x|"];
56 [label="Access variable <Unresolved name: length>#"];
57 [label="Access variable R|<local>/x|"];
58 [label="Smart cast: R|<local>/x|"];
59 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
60 [label="Exit block"];
}
63 [label="Exit function test2" style="filled" fillcolor=red];
61 [label="Exit function test2" style="filled" fillcolor=red];
}
42 -> {43};
43 -> {44};
44 -> {45};
45 -> {46};
46 -> {47};
47 -> {48};
48 -> {49};
49 -> {50};
50 -> {51};
51 -> {52};
52 -> {64};
52 -> {54} [color=red];
52 -> {64} [style=dashed];
53 -> {55} [color=red];
54 -> {55} [color=green];
50 -> {62};
50 -> {52} [color=red];
50 -> {62} [style=dashed];
51 -> {53} [color=red];
52 -> {53} [color=green];
53 -> {54};
54 -> {55};
55 -> {56};
56 -> {57};
57 -> {58};
58 -> {59};
59 -> {60};
60 -> {61};
61 -> {62};
62 -> {63};
63 -> {64};
64 -> {65};
65 -> {66};
66 -> {67};
67 -> {68};
68 -> {69};
69 -> {53} [color=red];
69 -> {54} [color=green];
69 -> {64} [color=green style=dashed];
67 -> {51} [color=red];
67 -> {52} [color=green];
67 -> {62} [color=green style=dashed];
subgraph cluster_16 {
color=red
70 [label="Enter function test3" style="filled" fillcolor=red];
68 [label="Enter function test3" style="filled" fillcolor=red];
subgraph cluster_17 {
color=blue
71 [label="Enter block"];
72 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
73 [label="Const: String()"];
74 [label="Assignment: R|<local>/x|"];
75 [label="Access variable R|<local>/x|"];
76 [label="Smart cast: R|<local>/x|"];
77 [label="Access variable R|kotlin/String.length|"];
78 [label="Postponed enter to lambda"];
69 [label="Enter block"];
70 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
71 [label="Const: String()"];
72 [label="Assignment: R|<local>/x|"];
73 [label="Access variable R|<local>/x|"];
74 [label="Smart cast: R|<local>/x|"];
75 [label="Access variable R|kotlin/String.length|"];
76 [label="Postponed enter to lambda"];
subgraph cluster_18 {
color=blue
90 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
88 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_19 {
color=blue
91 [label="Enter block"];
92 [label="Const: Int(1)"];
93 [label="Assignment: R|<local>/x|"];
94 [label="Exit block"];
89 [label="Enter block"];
90 [label="Const: Int(1)"];
91 [label="Assignment: R|<local>/x|"];
92 [label="Exit block"];
}
95 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
93 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
79 [label="Call arguments union" style="filled" fillcolor=yellow];
80 [label="Postponed exit from lambda"];
81 [label="Function call: R|/exactlyOnce|(...)"];
82 [label="Access variable R|<local>/x|"];
83 [label="Smart cast: R|<local>/x|"];
84 [label="Access variable <Unresolved name: length>#"];
85 [label="Access variable R|<local>/x|"];
86 [label="Smart cast: R|<local>/x|"];
87 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
88 [label="Exit block"];
77 [label="Call arguments union" style="filled" fillcolor=yellow];
78 [label="Postponed exit from lambda"];
79 [label="Function call: R|/exactlyOnce|(...)"];
80 [label="Access variable R|<local>/x|"];
81 [label="Smart cast: R|<local>/x|"];
82 [label="Access variable <Unresolved name: length>#"];
83 [label="Access variable R|<local>/x|"];
84 [label="Smart cast: R|<local>/x|"];
85 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
86 [label="Exit block"];
}
89 [label="Exit function test3" style="filled" fillcolor=red];
87 [label="Exit function test3" style="filled" fillcolor=red];
}
68 -> {69};
69 -> {70};
70 -> {71};
71 -> {72};
72 -> {73};
73 -> {74};
74 -> {75};
75 -> {76};
76 -> {77};
77 -> {78};
78 -> {90};
78 -> {80} [color=red];
78 -> {90} [style=dashed];
79 -> {81} [color=red];
80 -> {81} [color=green];
76 -> {88};
76 -> {78} [color=red];
76 -> {88} [style=dashed];
77 -> {79} [color=red];
78 -> {79} [color=green];
79 -> {80};
80 -> {81};
81 -> {82};
82 -> {83};
83 -> {84};
84 -> {85};
85 -> {86};
86 -> {87};
87 -> {88};
88 -> {89};
89 -> {90};
90 -> {91};
91 -> {92};
92 -> {93};
93 -> {94};
94 -> {95};
95 -> {79} [color=red];
95 -> {80} [color=green];
95 -> {90} [color=green style=dashed];
93 -> {77} [color=red];
93 -> {78} [color=green];
93 -> {88} [color=green style=dashed];
subgraph cluster_20 {
color=red
96 [label="Enter function test4" style="filled" fillcolor=red];
94 [label="Enter function test4" style="filled" fillcolor=red];
subgraph cluster_21 {
color=blue
97 [label="Enter block"];
98 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
99 [label="Const: String()"];
100 [label="Assignment: R|<local>/x|"];
101 [label="Access variable R|<local>/x|"];
102 [label="Smart cast: R|<local>/x|"];
103 [label="Access variable R|kotlin/String.length|"];
104 [label="Postponed enter to lambda"];
95 [label="Enter block"];
96 [label="Variable declaration: lvar x: R|kotlin/Any?|"];
97 [label="Const: String()"];
98 [label="Assignment: R|<local>/x|"];
99 [label="Access variable R|<local>/x|"];
100 [label="Smart cast: R|<local>/x|"];
101 [label="Access variable R|kotlin/String.length|"];
102 [label="Postponed enter to lambda"];
subgraph cluster_22 {
color=blue
115 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
113 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
subgraph cluster_23 {
color=blue
116 [label="Enter block"];
117 [label="Const: Int(1)"];
118 [label="Assignment: R|<local>/x|"];
119 [label="Exit block"];
114 [label="Enter block"];
115 [label="Const: Int(1)"];
116 [label="Assignment: R|<local>/x|"];
117 [label="Exit block"];
}
120 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
118 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
105 [label="Postponed exit from lambda"];
106 [label="Function call: R|/atMostOnce|(...)"];
107 [label="Access variable R|<local>/x|"];
108 [label="Smart cast: R|<local>/x|"];
109 [label="Access variable <Unresolved name: length>#"];
110 [label="Access variable R|<local>/x|"];
111 [label="Smart cast: R|<local>/x|"];
112 [label="Function call: R|<local>/x|.<Unresolved name: inc>#()"];
113 [label="Exit block"];
103 [label="Postponed exit from lambda"];
104 [label="Function call: R|/atMostOnce|(...)"];
105 [label="Access variable R|<local>/x|"];
106 [label="Smart cast: R|<local>/x|"];
107 [label="Access variable <Unresolved name: length>#"];
108 [label="Access variable R|<local>/x|"];
109 [label="Smart cast: R|<local>/x|"];
110 [label="Function call: R|<local>/x|.<Unresolved name: inc>#()"];
111 [label="Exit block"];
}
114 [label="Exit function test4" style="filled" fillcolor=red];
112 [label="Exit function test4" style="filled" fillcolor=red];
}
94 -> {95};
95 -> {96};
96 -> {97};
97 -> {98};
98 -> {99};
99 -> {100};
100 -> {101};
101 -> {102};
102 -> {103};
102 -> {103 113};
102 -> {113} [style=dashed];
103 -> {104};
104 -> {105 115};
104 -> {115} [style=dashed];
104 -> {105};
105 -> {106};
106 -> {107};
107 -> {108};
@@ -319,13 +317,11 @@ digraph flowFromInplaceLambda3_kt {
109 -> {110};
110 -> {111};
111 -> {112};
112 -> {113};
113 -> {114};
115 -> {120 116};
113 -> {118 114};
114 -> {115};
115 -> {116};
116 -> {117};
117 -> {118};
118 -> {119};
119 -> {120};
120 -> {105};
118 -> {103};
}
@@ -237,9 +237,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked.
getOrCreateLocalVariableAssignmentAnalyzer(function)?.enterLocalFunction(function)
val (functionEnterNode, localFunctionNode, previousNode) = graphBuilder.enterFunction(function)
val (functionEnterNode, localFunctionNode) = graphBuilder.enterFunction(function)
localFunctionNode?.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow(shouldForkFlow = previousNode != null)
functionEnterNode.mergeIncomingFlow()
}
fun exitFunction(function: FirFunction): FirControlFlowGraphReference? {
@@ -262,8 +262,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (graphBuilder.isTopLevel()) {
context.reset()
} else {
logicSystem.updateAllReceivers(graph.enterNode.flow)
}
logicSystem.updateAllReceivers(graph.enterNode.computeIncomingFlow().first)
return FirControlFlowGraphReferenceImpl(graph, DataFlowInfo(variableStorage, flowOnNodes))
}
@@ -275,9 +276,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
enterLocalFunction(anonymousFunction)
}
val (postponedLambdaEnterNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
// TODO: questionable
postponedLambdaEnterNode?.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow(shouldForkFlow = true)
val flowOnEntry = functionEnterNode.mergeIncomingFlow()
when (anonymousFunction.invocationKind) {
EventOccurrencesRange.AT_LEAST_ONCE,
EventOccurrencesRange.MORE_THAN_ONCE,
@@ -285,7 +285,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
enterCapturingStatement(functionEnterNode, anonymousFunction)
else -> {}
}
logicSystem.updateAllReceivers(functionEnterNode.flow)
logicSystem.updateAllReceivers(flowOnEntry)
}
private fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): FirControlFlowGraphReference {
@@ -300,10 +300,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
exitCapturingStatement(anonymousFunction)
else -> {}
}
// TODO: questionable
functionExitNode.mergeIncomingFlow()
postponedLambdaExitNode?.mergeIncomingFlow()
logicSystem.updateAllReceivers(graph.enterNode.computeIncomingFlow().first)
logicSystem.updateAllReceivers(graph.enterNode.flow)
return FirControlFlowGraphReferenceImpl(graph)
}
@@ -313,7 +312,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val (enterNode, exitNode) = graphBuilder.visitPostponedAnonymousFunction(anonymousFunctionExpression)
enterNode.mergeIncomingFlow()
exitNode.mergeIncomingFlow()
enterNode.flow = enterNode.flow.fork()
}
fun exitAnonymousFunctionExpression(anonymousFunctionExpression: FirAnonymousFunctionExpression) {
@@ -359,7 +357,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Value parameters (and it's defaults) -----------------------------------
fun enterValueParameter(valueParameter: FirValueParameter) {
graphBuilder.enterValueParameter(valueParameter)?.mergeIncomingFlow(shouldForkFlow = true)
graphBuilder.enterValueParameter(valueParameter)?.mergeIncomingFlow()
}
fun exitValueParameter(valueParameter: FirValueParameter): ControlFlowGraph? {
@@ -415,11 +413,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) {
val node = graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow()
val node = graphBuilder.exitTypeOperatorCall(typeOperatorCall)
val flow = node.mergeIncomingFlow()
if (typeOperatorCall.operation !in FirOperation.TYPES) return
val type = typeOperatorCall.conversionTypeRef.coneType
val operandVariable = variableStorage.getOrCreateVariable(node.previousFlow, typeOperatorCall.argument)
val flow = node.flow
when (val operation = typeOperatorCall.operation) {
FirOperation.IS, FirOperation.NOT_IS -> {
@@ -446,7 +444,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
flow.addTypeStatement(operandVariable typeEq type)
}
if (!type.canBeNull) {
flow.assumeNotNull(operandVariable, shouldForkFlow = false, shouldRemoveSynthetics = true)
flow.assumeNotNull(operandVariable, shouldRemoveSynthetics = true)
} else {
val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall)
flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null))
@@ -471,7 +469,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun exitEqualityOperatorCall(equalityOperatorCall: FirEqualityOperatorCall) {
val node = graphBuilder.exitEqualityOperatorCall(equalityOperatorCall).mergeIncomingFlow()
val node = graphBuilder.exitEqualityOperatorCall(equalityOperatorCall)
node.mergeIncomingFlow()
val operation = equalityOperatorCall.operation
val leftOperand = equalityOperatorCall.arguments[0]
val rightOperand = equalityOperatorCall.arguments[1]
@@ -669,20 +668,17 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall, callCompleted: Boolean) {
// Add `Any` to the set of possible types; the intersection type `T? & Any` will be reduced to `T` after smartcast.
val (node, unionNode) = graphBuilder.exitCheckNotNullCall(checkNotNullCall, callCompleted)
node.mergeIncomingFlow()
val argumentVariable = variableStorage.getOrCreateVariable(node.previousFlow, checkNotNullCall.argument)
node.flow.assumeNotNull(argumentVariable, shouldForkFlow = false, shouldRemoveSynthetics = false)
unionNode?.let { unionFlowFromArguments(it) }
node.mergeIncomingFlow().assumeNotNull(argumentVariable, shouldRemoveSynthetics = false)
unionNode?.unionFlowFromArguments()
}
private fun FLOW.assumeNotNull(variable: DataFlowVariable, shouldForkFlow: Boolean, shouldRemoveSynthetics: Boolean): FLOW =
logicSystem.approveStatementsInsideFlow(this, variable notEq null, shouldForkFlow, shouldRemoveSynthetics).also {
if (variable is RealVariable) {
it.addTypeStatement(variable typeEq any)
}
private fun FLOW.assumeNotNull(variable: DataFlowVariable, shouldRemoveSynthetics: Boolean) {
logicSystem.commitOperationStatement(this, variable notEq null, shouldRemoveSynthetics)
if (variable is RealVariable) {
addTypeStatement(variable typeEq any)
}
}
// ----------------------------------- When -----------------------------------
@@ -691,32 +687,23 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun enterWhenBranchCondition(whenBranch: FirWhenBranch) {
val node = graphBuilder.enterWhenBranchCondition(whenBranch).mergeIncomingFlow(updateReceivers = true)
val node = graphBuilder.enterWhenBranchCondition(whenBranch)
val flow = node.mergeIncomingFlow(updateReceivers = true)
val previousNode = node.previousNodes.single()
if (previousNode is WhenBranchConditionExitNode) {
val conditionVariable = context.variablesForWhenConditions.remove(previousNode)!!
node.flow = logicSystem.approveStatementsInsideFlow(
node.flow,
conditionVariable eq false,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
logicSystem.commitOperationStatement(flow, conditionVariable eq false, shouldRemoveSynthetics = true)
}
}
fun exitWhenBranchCondition(whenBranch: FirWhenBranch) {
val (conditionExitNode, branchEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch)
conditionExitNode.mergeIncomingFlow()
val conditionExitFlow = conditionExitNode.flow
val conditionExitFlow = conditionExitNode.mergeIncomingFlow()
val conditionVariable = variableStorage.getOrCreateVariable(conditionExitFlow, whenBranch.condition)
context.variablesForWhenConditions[conditionExitNode] = conditionVariable
branchEnterNode.flow = logicSystem.approveStatementsInsideFlow(
conditionExitFlow,
conditionVariable eq true,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
branchEnterNode.flow = conditionExitFlow.fork().also {
logicSystem.commitOperationStatement(it, conditionVariable eq true, shouldRemoveSynthetics = false)
}
}
fun exitWhenBranchResult(whenBranch: FirWhenBranch) {
@@ -731,12 +718,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// in that case there will be when enter or subject access node
if (previousConditionExitNode != null) {
val conditionVariable = context.variablesForWhenConditions.remove(previousConditionExitNode)!!
syntheticElseNode.flow = logicSystem.approveStatementsInsideFlow(
previousConditionExitNode.flow,
conditionVariable eq false,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
syntheticElseNode.flow = previousConditionExitNode.flow.fork().also {
logicSystem.commitOperationStatement(it, conditionVariable eq false, shouldRemoveSynthetics = true)
}
} else {
syntheticElseNode.mergeIncomingFlow()
}
@@ -755,12 +739,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val singlePreviousNode = exitNode.previousNodes.singleOrNull { !it.isDead }
if (singlePreviousNode is LoopConditionExitNode) {
val variable = variableStorage.getOrCreateVariable(exitNode.previousFlow, singlePreviousNode.fir)
exitNode.flow = logicSystem.approveStatementsInsideFlow(
exitNode.flow,
variable eq false,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
logicSystem.commitOperationStatement(exitNode.flow, variable eq false, shouldRemoveSynthetics = true)
}
exitCapturingStatement(exitNode.fir)
}
@@ -774,16 +753,13 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitWhileLoopCondition(loop: FirLoop) {
val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop)
loopConditionExitNode.mergeIncomingFlow()
val conditionExitFlow = loopConditionExitNode.flow
loopBlockEnterNode.flow = variableStorage.getVariable(conditionExitFlow, loop.condition)?.let { conditionVariable ->
logicSystem.approveStatementsInsideFlow(
conditionExitFlow,
conditionVariable eq true,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
} ?: logicSystem.forkFlow(conditionExitFlow)
val conditionExitFlow = loopConditionExitNode.mergeIncomingFlow()
loopBlockEnterNode.flow = conditionExitFlow.fork().also {
val conditionVariable = variableStorage.getVariable(conditionExitFlow, loop.condition)
if (conditionVariable != null) {
logicSystem.commitOperationStatement(it, conditionVariable eq true, shouldRemoveSynthetics = false)
}
}
}
fun exitWhileLoop(loop: FirLoop) {
@@ -838,9 +814,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun enterTryExpression(tryExpression: FirTryExpression) {
val (tryExpressionEnterNode, tryMainBlockEnterNode) = graphBuilder.enterTryExpression(tryExpression)
tryExpressionEnterNode.mergeIncomingFlow()
// NB: fork to isolate effects inside the try main block
// Otherwise, changes in the try main block could affect the try expression enter node as well as its previous nodes.
tryMainBlockEnterNode.mergeIncomingFlow(shouldForkFlow = true)
tryMainBlockEnterNode.mergeIncomingFlow()
}
fun exitTryMainBlock() {
@@ -848,9 +822,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun enterCatchClause(catch: FirCatch) {
// NB: fork to isolate effects inside the catch clause
// Otherwise, changes in the catch clause could affect the previous node: try main block.
graphBuilder.enterCatchClause(catch).mergeIncomingFlow(updateReceivers = true, shouldForkFlow = true)
graphBuilder.enterCatchClause(catch).mergeIncomingFlow(updateReceivers = true)
}
fun exitCatchClause(catch: FirCatch) {
@@ -858,9 +830,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun enterFinallyBlock() {
// NB: fork to isolate effects inside the finally block
// Otherwise, changes in the finally block could affect the previous nodes: try main block and catch clauses.
graphBuilder.enterFinallyBlock().mergeIncomingFlow(updateReceivers = true, shouldForkFlow = true)
graphBuilder.enterFinallyBlock().mergeIncomingFlow(updateReceivers = true)
}
fun exitFinallyBlock() {
@@ -869,10 +839,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitTryExpression(callCompleted: Boolean) {
val (tryExpressionExitNode, unionNode) = graphBuilder.exitTryExpression(callCompleted)
// NB: fork to prevent effects after the try expression from being flown into the try expression
// Otherwise, changes in any following nodes could affect the previous nodes, including try main block and finally block if any.
tryExpressionExitNode.mergeIncomingFlow(shouldForkFlow = true)
unionNode?.let { unionFlowFromArguments(it) }
tryExpressionExitNode.mergeIncomingFlow()
unionNode?.unionFlowFromArguments()
}
// ----------------------------------- Resolvable call -----------------------------------
@@ -890,48 +858,31 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
val node = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow()
val previousNode = node.firstPreviousNode
val shouldFork: Boolean
var flow = if (previousNode is ExitSafeCallNode) {
shouldFork = false
previousNode.secondPreviousNode?.flow ?: node.flow
} else {
shouldFork = true
node.flow
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) {
logicSystem.copyAllInformation(flowFromPreviousSafeCall, flow)
}
safeCall.receiver.let { receiver ->
val type = receiver.coneType.takeIf { it.isMarkedNullable }
?.makeConeTypeDefinitelyNotNullOrNotNull(components.session.typeContext)
?: return@let
val variable = variableStorage.getOrCreateVariable(flow, receiver)
if (variable.isReal()) {
if (shouldFork) {
flow = logicSystem.forkFlow(flow)
}
flow.addTypeStatement(variable typeEq type)
}
flow = flow.assumeNotNull(variable, shouldFork, shouldRemoveSynthetics = false)
}
node.flow = flow
val receiverVariable = variableStorage.getOrCreateVariable(node.flow, safeCall.receiver)
flow.assumeNotNull(receiverVariable, shouldRemoveSynthetics = true)
}
fun exitSafeCall(safeCall: FirSafeCallExpression) {
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitSafeCall()
val flow = node.mergeIncomingFlow().flow
val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow()
val variable = variableStorage.getOrCreateVariable(flow, safeCall)
val receiverVariable = when (variable) {
// There is some bug with invokes. See KT-36014
is RealVariable -> variable.explicitReceiverVariable ?: return
is SyntheticVariable -> variableStorage.getOrCreateVariable(flow, safeCall.receiver)
}
val receiverVariable = variableStorage.getOrCreateVariable(flow, safeCall.receiver)
// TODO? if the callee has non-null return type, then (variable eq null) implies (receiverVariable eq null)
// if (x?.toString() == null) { /* x == null */ }
// TODO? all new statements in previous node's flow are valid here if receiverVariable != null
// if (x?.whatever(y as String) != null) { /* y is String */ }
// TODO? all new implications in previous node's flow are valid here if receiverVariable != null
// (that requires a second level of implications: receiverVariable != null => condition => effect).
flow.addImplication((variable notEq null) implies (receiverVariable notEq null))
}
@@ -968,7 +919,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
return
}
val (functionCallNode, unionNode) = graphBuilder.exitFunctionCall(functionCall, callCompleted)
unionNode?.let { unionFlowFromArguments(it) }
unionNode?.unionFlowFromArguments()
functionCallNode.mergeIncomingFlow()
if (functionCall.isBooleanNot()) {
exitBooleanNot(functionCall, functionCallNode)
@@ -978,19 +929,18 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
val (callNode, unionNode) = graphBuilder.exitDelegatedConstructorCall(call, callCompleted)
unionNode?.let { unionFlowFromArguments(it) }
unionNode?.unionFlowFromArguments()
callNode.mergeIncomingFlow()
}
fun exitStringConcatenationCall(call: FirStringConcatenationCall) {
val (callNode, unionNode) = graphBuilder.exitStringConcatenationCall(call)
unionNode?.let { unionFlowFromArguments(it) }
unionNode?.unionFlowFromArguments()
callNode.mergeIncomingFlow()
}
private fun unionFlowFromArguments(node: UnionFunctionCallArgumentsNode) {
node.flow = logicSystem.unionFlow(node.previousNodes.map { it.flow }).also {
private fun UnionFunctionCallArgumentsNode.unionFlowFromArguments() {
flow = logicSystem.unionFlow(previousNodes.map { it.flow }).also {
logicSystem.updateAllReceivers(it)
}
}
@@ -1037,12 +987,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val lastNode = graphBuilder.lastNode
when (val value = effect.value) {
ConeConstantReference.WILDCARD -> {
lastNode.flow = logicSystem.approveStatementsInsideFlow(
lastNode.flow,
argumentVariable eq true,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
logicSystem.commitOperationStatement(lastNode.flow, argumentVariable eq true, shouldRemoveSynthetics = true)
}
is ConeBooleanConstantReference -> {
@@ -1084,34 +1029,33 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
fun exitLocalVariableDeclaration(variable: FirProperty, hadExplicitType: Boolean) {
val node = graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow()
val flow = graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow()
val initializer = variable.initializer ?: return
exitVariableInitialization(node, initializer, variable, assignment = null, hadExplicitType)
exitVariableInitialization(flow, initializer, variable, assignment = null, hadExplicitType)
}
fun exitVariableAssignment(assignment: FirVariableAssignment) {
val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
val flow = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
val property = assignment.lValue.resolvedSymbol?.fir as? FirProperty ?: return
if (property.isLocal || property.isVal) {
exitVariableInitialization(node, assignment.rValue, property, assignment, hasExplicitType = false)
exitVariableInitialization(flow, assignment.rValue, property, assignment, hasExplicitType = false)
} else {
// TODO: add unstable smartcast for non-local var
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(node.flow, property.symbol, assignment)
val variable = variableStorage.getRealVariableWithoutUnwrappingAlias(flow, property.symbol, assignment)
if (variable != null) {
logicSystem.recordNewAssignment(node.flow, variable, context.newAssignmentIndex())
logicSystem.recordNewAssignment(flow, variable, context.newAssignmentIndex())
}
}
processConditionalContract(assignment)
}
private fun exitVariableInitialization(
node: CFGNode<*>,
flow: FLOW,
initializer: FirExpression,
property: FirProperty,
assignment: FirVariableAssignment?,
hasExplicitType: Boolean,
) {
val flow = node.flow
val propertyVariable = variableStorage.getOrCreateRealVariableWithoutUnwrappingAlias(
flow,
property.symbol,
@@ -1198,14 +1142,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
private fun exitLeftArgumentOfBinaryBooleanOperator(leftNode: CFGNode<*>, rightNode: CFGNode<*>, isAnd: Boolean) {
val parentFlow = leftNode.firstPreviousNode.flow
leftNode.flow = logicSystem.forkFlow(parentFlow)
val leftOperandVariable = variableStorage.getOrCreateVariable(parentFlow, leftNode.firstPreviousNode.fir)
rightNode.flow = logicSystem.approveStatementsInsideFlow(
parentFlow,
leftOperandVariable eq isAnd,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
leftNode.flow = parentFlow.fork()
rightNode.flow = parentFlow.fork().also {
logicSystem.commitOperationStatement(it, leftOperandVariable eq isAnd, shouldRemoveSynthetics = false)
}
}
private fun exitBinaryBooleanOperator(
@@ -1220,8 +1161,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// Naming for all variables was chosen in assumption that we processing && expression
val flowFromLeft = node.leftOperandNode.flow
val flowFromRight = node.rightOperandNode.flow
val flow = node.mergeIncomingFlow().flow
val flow = node.mergeIncomingFlow()
/*
* TODO: Here we should handle case when one of arguments is dead (e.g. in cases `false && expr` or `true || expr`)
@@ -1234,12 +1174,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (!node.leftOperandNode.isDead && node.rightOperandNode.isDead) {
// If the right operand does not terminate, then we know that the value of the entire expression
// has to be `onlyLeftEvaluated`, and it has to be produced by the left operand.
logicSystem.approveStatementsInsideFlow(
flow,
leftVariable eq onlyLeftEvaluated,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
logicSystem.commitOperationStatement(flow, leftVariable eq onlyLeftEvaluated, shouldRemoveSynthetics = true)
} else {
val (conditionalFromLeft, conditionalFromRight, approvedFromRight) =
logicSystem.collectInfoForBooleanOperator(flowFromLeft, leftVariable, flowFromRight, rightVariable)
@@ -1299,7 +1234,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun enterInitBlock(initBlock: FirAnonymousInitializer) {
graphBuilder.enterInitBlock(initBlock).let { (node, prevNode) ->
if (prevNode != null) {
node.flow = logicSystem.forkFlow(prevNode.flow)
node.flow = prevNode.flow.fork()
} else {
node.mergeIncomingFlow()
}
@@ -1330,27 +1265,22 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
fun exitElvisLhs(elvisExpression: FirElvisExpression) {
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
lhsExitNode.mergeIncomingFlow()
val flow = lhsExitNode.flow
val flow = lhsExitNode.mergeIncomingFlow()
val lhsVariable = variableStorage.getOrCreateVariable(flow, elvisExpression.lhs)
lhsIsNotNullNode.flow = flow.assumeNotNull(lhsVariable, shouldForkFlow = true, shouldRemoveSynthetics = false)
rhsEnterNode.flow = logicSystem.approveStatementsInsideFlow(
flow,
lhsVariable eq null,
shouldForkFlow = true,
shouldRemoveSynthetics = false
).also {
lhsIsNotNullNode.flow = flow.fork().also { it.assumeNotNull(lhsVariable, shouldRemoveSynthetics = false) }
rhsEnterNode.flow = flow.fork().also {
logicSystem.commitOperationStatement(it, lhsVariable eq null, shouldRemoveSynthetics = false)
logicSystem.updateAllReceivers(it)
}
}
fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean) {
val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis()
node.mergeIncomingFlow()
val flow = node.mergeIncomingFlow()
mergePostponedLambdaExitsNode?.mergeIncomingFlow()
if (isLhsNotNull) {
val lhsVariable = variableStorage.getOrCreateVariable(node.previousFlow, elvisExpression.lhs)
node.flow.assumeNotNull(lhsVariable, shouldForkFlow = false, shouldRemoveSynthetics = true)
flow.assumeNotNull(lhsVariable, shouldRemoveSynthetics = true)
}
if (!components.session.languageVersionSettings.supportsFeature(LanguageFeature.BooleanElvisBoundSmartCasts)) return
@@ -1359,7 +1289,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val lhs = elvisExpression.lhs
if (lhs.typeRef.coneType.classId != StandardClassIds.Boolean) return
val flow = node.flow
// a ?: false == true -> a != null
// a ?: true == false -> a != null
val elvisVariable = variableStorage.getOrCreateVariable(flow, elvisExpression)
@@ -1400,38 +1329,27 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
private val CFGNode<*>.origin: CFGNode<*> get() = if (this is StubNode) firstPreviousNode else this
private fun <T : CFGNode<*>> T.computeIncomingFlow(): Pair<FLOW, Int> {
private fun CFGNode<*>.mergeIncomingFlow(
// This flag should be set true if we're changing flow branches from one to another (e.g. in when, try->catch)
updateReceivers: Boolean = false
): FLOW {
val previousFlows = mutableListOf<FLOW>()
var deadForwardCount = 0
for (previousNode in previousNodes) {
val incomingEdgeKind = incomingEdges.getValue(previousNode).kind
if (isDead && incomingEdgeKind.usedInDeadDfa) {
previousFlows += previousNode.flow
} else if (incomingEdgeKind.usedInDfa) {
previousFlows += previousNode.flow
}
if (incomingEdgeKind == EdgeKind.DeadForward) {
deadForwardCount++
}
if (incomingEdgeKind.usedInDfa || (isDead && incomingEdgeKind.usedInDeadDfa)) {
previousFlows += previousNode.flow
}
}
return logicSystem.joinFlow(previousFlows) to (previousFlows.size + deadForwardCount)
}
private fun <T : CFGNode<*>> T.mergeIncomingFlow(
// This flag should be set true if we're changing flow branches from one to another (e.g. in when, try->catch)
updateReceivers: Boolean = false,
shouldForkFlow: Boolean = false
): T = this.also { node ->
var (flow, incomingEdges) = computeIncomingFlow()
if (updateReceivers || incomingEdges > 1) {
logicSystem.updateAllReceivers(flow)
return logicSystem.joinFlow(previousFlows).also {
flow = it
if (updateReceivers || previousFlows.size + deadForwardCount > 1) {
logicSystem.updateAllReceivers(it)
}
}
if (shouldForkFlow) {
flow = flow.fork()
}
node.flow = flow
}
private fun FLOW.addImplication(statement: Implication) {
@@ -182,7 +182,7 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Regular function -----------------------------------
fun enterFunction(function: FirFunction): Triple<FunctionEnterNode, LocalFunctionDeclarationNode?, CFGNode<*>?> {
fun enterFunction(function: FirFunction): Pair<FunctionEnterNode, LocalFunctionDeclarationNode?> {
require(function !is FirAnonymousFunction)
val name = when (function) {
is FirSimpleFunction -> function.name.asString()
@@ -226,7 +226,7 @@ class ControlFlowGraphBuilder {
exitTargetsForTry.push(it)
}
return Triple(enterNode, localFunctionNode, previousNode)
return Pair(enterNode, localFunctionNode)
}
fun exitFunction(function: FirFunction): Pair<FunctionExitNode, ControlFlowGraph> {
@@ -30,17 +30,14 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
transform: (Implication) -> Implication? = { it },
)
abstract fun approveStatementsInsideFlow(
flow: FLOW,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean,
): FLOW
abstract fun commitOperationStatement(flow: FLOW, statement: OperationStatement, shouldRemoveSynthetics: Boolean)
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)
protected abstract fun getImplicationsWithVariable(flow: FLOW, variable: DataFlowVariable): Collection<Implication>
protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean
@@ -26,7 +26,6 @@ class PersistentFlow : Flow {
var approvedTypeStatements: PersistentApprovedTypeStatements
var logicStatements: PersistentImplications
val level: Int
var approvedTypeStatementsDiff: PersistentApprovedTypeStatements = persistentHashMapOf()
/*
* val x = a
@@ -75,12 +74,18 @@ class PersistentFlow : Flow {
abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSystem<PersistentFlow>(context) {
abstract val variableStorage: VariableStorageImpl
override fun createEmptyFlow(): PersistentFlow {
return PersistentFlow()
}
override fun createEmptyFlow(): PersistentFlow =
PersistentFlow()
override fun forkFlow(flow: PersistentFlow): PersistentFlow {
return PersistentFlow(flow)
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 =
@@ -92,10 +97,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
private fun foldFlow(flows: Collection<PersistentFlow>, allExecute: Boolean): PersistentFlow {
when (flows.size) {
0 -> return createEmptyFlow()
1 -> return flows.first()
1 -> return forkFlow(flows.first())
}
val commonFlow = flows.reduce(::lowestCommonFlow)
val commonFlow = forkFlow(flows.reduce(::lowestCommonFlow))
// Aliases that have occurred before branching should already be in `commonFlow`,
// but it might be useful to also add aliases that happen in all branches, e.g.
// the aliasing of `y` to `a.x` after `if (p) { y = a.x } else { y = a.x }`.
@@ -137,9 +142,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
commonFlow.approvedTypeStatements += statements
if (commonFlow.previousFlow != null) {
commonFlow.approvedTypeStatementsDiff += statements
}
for ((alias, underlyingVariable) in commonAliases) {
addLocalVariableAlias(commonFlow, alias, underlyingVariable)
@@ -168,7 +170,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
assert(variable !in backwardsAliasMap)
assert(variable !in logicStatements)
assert(variable !in approvedTypeStatements)
assert(variable !in approvedTypeStatementsDiff)
}
// `variable.dependentVariables` is not separated by flow, so it may be non-empty if aliasing of this variable
// was broken in another flow. However, in *this* flow dependent variables should have no information attached to them.
@@ -194,7 +195,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
logicStatements = logicStatements.replaceVariable(variable, replacementOrNext)
approvedTypeStatements = approvedTypeStatements.replaceVariable(variable, replacementOrNext)
approvedTypeStatementsDiff = approvedTypeStatementsDiff.replaceVariable(variable, replacementOrNext)
if (aliases != null) {
backwardsAliasMap -= variable
if (replacementOrNext != null) {
@@ -214,24 +214,23 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement) {
if (statement.isEmpty) return
with(flow) {
approvedTypeStatements = approvedTypeStatements.addTypeStatement(statement)
if (previousFlow != null) {
approvedTypeStatementsDiff = approvedTypeStatementsDiff.addTypeStatement(statement)
}
if (statement.variable.isThisReference) {
processUpdatedReceiverVariable(flow, statement.variable)
}
if (statement.exactType.isEmpty()) return
val variable = statement.variable
val oldExactType = flow.approvedTypeStatements[variable]?.exactType
val newExactType = oldExactType?.addAll(statement.exactType) ?: statement.exactType.toPersistentSet()
if (newExactType === oldExactType) return
flow.approvedTypeStatements = flow.approvedTypeStatements.put(variable, PersistentTypeStatement(variable, newExactType))
if (variable.isThisReference) {
processUpdatedReceiverVariable(flow, variable)
}
}
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
put(info.variable, { info.toPersistent() }, { and(listOf(it, info)).toPersistent() })
override fun addImplication(flow: PersistentFlow, implication: Implication) {
if ((implication.effect as? TypeStatement)?.isEmpty == true) return
if (implication.condition == implication.effect) return
val effect = implication.effect
if (effect == implication.condition) return
if (effect is TypeStatement && (effect.isEmpty ||
flow.approvedTypeStatements[effect.variable]?.exactType?.containsAll(effect.exactType) == true)
) return
val variable = implication.condition.variable
flow.logicStatements = flow.logicStatements.put(variable, { persistentListOf(implication) }, { it + implication })
}
@@ -257,35 +256,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
}
override fun approveStatementsInsideFlow(
flow: PersistentFlow,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): PersistentFlow {
val approvedFacts = approveOperationStatementsInternal(
flow,
approvedStatement,
initialStatements = null,
shouldRemoveSynthetics
)
val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow
if (approvedFacts.isEmpty) return resultFlow
val updatedReceivers = mutableSetOf<RealVariable>()
approvedFacts.asMap().forEach { (variable, infos) ->
if (variable.isThisReference) {
updatedReceivers += variable
}
addTypeStatement(resultFlow, and(infos))
override fun commitOperationStatement(flow: PersistentFlow, statement: OperationStatement, shouldRemoveSynthetics: Boolean) {
approveOperationStatementsInternal(flow, statement, initialStatements = null, shouldRemoveSynthetics).values.forEach {
addTypeStatement(flow, it)
}
updatedReceivers.forEach {
processUpdatedReceiverVariable(resultFlow, it)
}
return resultFlow
}
private fun approveOperationStatementsInternal(
@@ -293,55 +267,40 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
approvedStatement: OperationStatement,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean
): ArrayListMultimap<RealVariable, TypeStatement> {
val approvedFacts: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val approvedStatements = LinkedList<OperationStatement>().apply { this += approvedStatement }
approveOperationStatementsInternal(flow, approvedStatements, initialStatements, shouldRemoveSynthetics, approvedFacts)
return approvedFacts
}
private fun approveOperationStatementsInternal(
flow: PersistentFlow,
approvedStatements: LinkedList<OperationStatement>,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean,
approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement>
) {
if (approvedStatements.isEmpty()) return
val approvedOperationStatements = mutableSetOf<OperationStatement>()
): TypeStatements {
val approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val queue = LinkedList<OperationStatement>().apply { this += approvedStatement }
val approved = mutableSetOf<OperationStatement>()
var firstIteration = true
while (approvedStatements.isNotEmpty()) {
@Suppress("NAME_SHADOWING")
val approvedStatement: OperationStatement = approvedStatements.removeFirst()
while (queue.isNotEmpty()) {
val next: OperationStatement = queue.removeFirst()
// Defense from cycles in facts
if (!approvedOperationStatements.add(approvedStatement)) {
continue
}
if (!approved.add(next)) continue
val variable = next.variable
val statements = initialStatements?.takeIf { firstIteration }
?: flow.logicStatements[approvedStatement.variable]?.takeIf { it.isNotEmpty() }
?: flow.logicStatements[variable]?.takeIf { it.isNotEmpty() }
?: continue
if (shouldRemoveSynthetics && approvedStatement.variable.isSynthetic()) {
flow.logicStatements -= approvedStatement.variable
if (shouldRemoveSynthetics && variable.isSynthetic()) {
flow.logicStatements -= variable
}
for (statement in statements) {
if (statement.condition == approvedStatement) {
if (statement.condition == next) {
when (val effect = statement.effect) {
is OperationStatement -> approvedStatements += effect
is OperationStatement -> queue += effect
is TypeStatement -> approvedTypeStatements.put(effect.variable, effect)
}
}
}
firstIteration = false
}
return approvedTypeStatements.asMap().mapValues { and(it.value) }
}
override fun approveOperationStatement(
flow: PersistentFlow,
approvedStatement: OperationStatement,
statementsForVariable: Collection<Implication>?
): TypeStatements =
approveOperationStatementsInternal(flow, approvedStatement, statementsForVariable, shouldRemoveSynthetics = false)
.asMap().mapValues { and(it.value) }
): TypeStatements = approveOperationStatementsInternal(flow, approvedStatement, statementsForVariable, shouldRemoveSynthetics = false)
override fun collectInfoForBooleanOperator(
leftFlow: PersistentFlow,
@@ -352,7 +311,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
return InfoForBooleanOperator(
leftFlow.logicStatements[leftVariable] ?: emptyList(),
rightFlow.logicStatements[rightVariable] ?: emptyList(),
rightFlow.approvedTypeStatementsDiff
rightFlow.approvedTypeStatements
)
}
@@ -780,8 +780,7 @@ 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 lastPreviousNode: CFGNode<*> get() = previousNodes.last()
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
val lastNodeInNotNullCase: CFGNode<*>? get() = previousNodes.getOrNull(1)
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitSafeCallNode(this, data)
@@ -8,7 +8,7 @@ fun calc(x: String?, y: String?): Int {
// x is not null in condition but we do not see it yet
} while (x.length > 0)
// y is nullable because of break
y.length
y<!UNSAFE_CALL!>.<!>length
// x is not null, at least in theory
return x.length
}
@@ -7,5 +7,5 @@ fun test(foo: Foo?) {
// Unnecessary
foo<!UNNECESSARY_SAFE_CALL!>?.<!>bar?.length
}
foo.bar<!UNNECESSARY_SAFE_CALL!>?.<!>length
foo.bar?.length
}
@@ -3,7 +3,7 @@ fun foo(y: String?) {
if (x != null) {
with(y?.let { x = null; it }) {
this<!UNSAFE_CALL!>.<!>length
x<!UNSAFE_CALL!>.<!>length
<!SMARTCAST_IMPOSSIBLE!>x<!>.length
}
x<!UNSAFE_CALL!>.<!>length
}
@@ -61,8 +61,8 @@ fun case_4(x: Boolean?) {
} while (x!!)
} while (true)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 5
@@ -73,8 +73,8 @@ fun case_5(x: Boolean?) {
} while (x!!)
} while (true)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 6
@@ -85,8 +85,8 @@ fun case_6(x: Boolean?) {
} while (true)
} while (x!!)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 7
@@ -126,8 +126,8 @@ fun case_9(x: Boolean?) {
}
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -204,8 +204,8 @@ fun case_16(x: Boolean?) {
break
} while (x!!)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -343,8 +343,8 @@ fun case_26(x: Boolean?) {
}
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -357,8 +357,8 @@ fun case_27(x: Int?, y: Class) {
y[break, x!!]
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -371,6 +371,6 @@ fun case_28(x: Int?, y: List<List<Int>>) {
y[break][x!!]
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
@@ -61,8 +61,8 @@ fun case_4(x: Boolean?) {
} while (x as Boolean)
} while (true)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 5
@@ -73,8 +73,8 @@ fun case_5(x: Boolean?) {
} while (x as Boolean)
} while (true)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 6
@@ -85,8 +85,8 @@ fun case_6(x: Boolean?) {
} while (true)
} while (x as Boolean)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 7
@@ -126,8 +126,8 @@ fun case_9(x: Boolean?) {
}
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -204,8 +204,8 @@ fun case_16(x: Boolean?) {
break
} while (x as Boolean)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -343,8 +343,8 @@ fun case_26(x: Boolean?) {
}
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
/*
@@ -357,8 +357,8 @@ fun case_27(x: Int?, y: Class) {
y[break, x as Int]
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -371,8 +371,8 @@ fun case_28(x: Int?, y: List<List<Int>>) {
y[break][x as Int]
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -386,6 +386,6 @@ fun case_29(x: Boolean?, y: MutableList<Int>) {
x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
@@ -68,8 +68,8 @@ fun case_5(x: Boolean?, y: Boolean?) {
x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 6
@@ -79,8 +79,8 @@ fun case_6(x: Boolean?, y: ((x: Nothing) -> Unit)?) {
x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 7
@@ -120,8 +120,8 @@ fun case_9(x: Int?) {
y = break as Int + x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -135,8 +135,8 @@ fun case_10(x: Int?) {
break as Int + x as Int
} while (true)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -152,8 +152,8 @@ fun case_11(x: Int?) {
break(x!!)
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -169,8 +169,8 @@ fun case_12(x: Int?) {
<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>break[x!!]<!>
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -186,8 +186,8 @@ fun case_13(x: Int?) {
break[x!!] = 10
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
/*
@@ -203,8 +203,8 @@ fun case_14(x: Int?) {
break[10] = x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!><!UNSAFE_CALL!>.<!>inv()
}
// TESTCASE NUMBER: 15
@@ -23,8 +23,8 @@ fun case_2(x: Boolean?, y: Int?) {
x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 3
@@ -92,8 +92,8 @@ fun case_7(x: Boolean?, y: Boolean?) {
x!!
}
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Boolean")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 8
@@ -105,8 +105,8 @@ fun case_9() {
x = null
break
} while (x!!.length > 1)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing")!>x<!>.length
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>length
}
// TESTCASE NUMBER: 10
@@ -116,8 +116,8 @@ fun case_10() {
x = null
break
} while ((x as String).length > 1)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing")!>x<!>.length
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>length
}
// TESTCASE NUMBER: 11
@@ -127,8 +127,8 @@ fun case_11() {
x = null
break
} while (x!!)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing")!>x<!>.not()
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>not()
}
// TESTCASE NUMBER: 12