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