FIR DFA: do not generate nodes (and flows) for contracts on calls
The code was already duplicated between FirDataFlowAnalyzer and FirReturnsImpliesAnalyzer, so might as well use the latter to slightly speed up the former.
This commit is contained in:
Vendored
+180
-278
@@ -12,19 +12,12 @@ digraph conditionalEffects_kt {
|
|||||||
2 [label="Access variable R|<local>/x|"];
|
2 [label="Access variable R|<local>/x|"];
|
||||||
3 [label="Type operator: (R|<local>/x| is R|kotlin/Int|)"];
|
3 [label="Type operator: (R|<local>/x| is R|kotlin/Int|)"];
|
||||||
4 [label="Function call: R|kotlin/require|(...)"];
|
4 [label="Function call: R|kotlin/require|(...)"];
|
||||||
subgraph cluster_2 {
|
5 [label="Access variable R|<local>/x|"];
|
||||||
color=blue
|
6 [label="Smart cast: R|<local>/x|"];
|
||||||
5 [label="Enter contract"];
|
7 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
||||||
6 [label="Access variable R|<local>/x|"];
|
8 [label="Exit block"];
|
||||||
7 [label="Type operator: (R|<local>/x| is R|kotlin/Int|)"];
|
|
||||||
8 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
9 [label="Access variable R|<local>/x|"];
|
|
||||||
10 [label="Smart cast: R|<local>/x|"];
|
|
||||||
11 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
|
|
||||||
12 [label="Exit block"];
|
|
||||||
}
|
}
|
||||||
13 [label="Exit function test_1" style="filled" fillcolor=red];
|
9 [label="Exit function test_1" style="filled" fillcolor=red];
|
||||||
}
|
}
|
||||||
0 -> {1};
|
0 -> {1};
|
||||||
1 -> {2};
|
1 -> {2};
|
||||||
@@ -35,39 +28,48 @@ digraph conditionalEffects_kt {
|
|||||||
6 -> {7};
|
6 -> {7};
|
||||||
7 -> {8};
|
7 -> {8};
|
||||||
8 -> {9};
|
8 -> {9};
|
||||||
9 -> {10};
|
|
||||||
|
subgraph cluster_2 {
|
||||||
|
color=red
|
||||||
|
10 [label="Enter function test_2" style="filled" fillcolor=red];
|
||||||
|
subgraph cluster_3 {
|
||||||
|
color=blue
|
||||||
|
11 [label="Enter block"];
|
||||||
|
12 [label="Access variable R|<local>/x|"];
|
||||||
|
13 [label="Function call: R|kotlin/requireNotNull|<R|kotlin/String|>(...)"];
|
||||||
|
14 [label="Access variable R|<local>/x|"];
|
||||||
|
15 [label="Smart cast: R|<local>/x|"];
|
||||||
|
16 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
17 [label="Exit block"];
|
||||||
|
}
|
||||||
|
18 [label="Exit function test_2" style="filled" fillcolor=red];
|
||||||
|
}
|
||||||
10 -> {11};
|
10 -> {11};
|
||||||
11 -> {12};
|
11 -> {12};
|
||||||
12 -> {13};
|
12 -> {13};
|
||||||
|
13 -> {14};
|
||||||
subgraph cluster_3 {
|
|
||||||
color=red
|
|
||||||
14 [label="Enter function test_2" style="filled" fillcolor=red];
|
|
||||||
subgraph cluster_4 {
|
|
||||||
color=blue
|
|
||||||
15 [label="Enter block"];
|
|
||||||
16 [label="Access variable R|<local>/x|"];
|
|
||||||
17 [label="Function call: R|kotlin/requireNotNull|<R|kotlin/String|>(...)"];
|
|
||||||
subgraph cluster_5 {
|
|
||||||
color=blue
|
|
||||||
18 [label="Enter contract"];
|
|
||||||
19 [label="Access variable R|<local>/x|"];
|
|
||||||
20 [label="Const: Null(null)"];
|
|
||||||
21 [label="Equality operator !="];
|
|
||||||
22 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
23 [label="Access variable R|<local>/x|"];
|
|
||||||
24 [label="Smart cast: R|<local>/x|"];
|
|
||||||
25 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
26 [label="Exit block"];
|
|
||||||
}
|
|
||||||
27 [label="Exit function test_2" style="filled" fillcolor=red];
|
|
||||||
}
|
|
||||||
14 -> {15};
|
14 -> {15};
|
||||||
15 -> {16};
|
15 -> {16};
|
||||||
16 -> {17};
|
16 -> {17};
|
||||||
17 -> {18};
|
17 -> {18};
|
||||||
18 -> {19};
|
|
||||||
|
subgraph cluster_4 {
|
||||||
|
color=red
|
||||||
|
19 [label="Enter function test_3" style="filled" fillcolor=red];
|
||||||
|
subgraph cluster_5 {
|
||||||
|
color=blue
|
||||||
|
20 [label="Enter block"];
|
||||||
|
21 [label="Access variable R|<local>/x|"];
|
||||||
|
22 [label="Const: Null(null)"];
|
||||||
|
23 [label="Equality operator !="];
|
||||||
|
24 [label="Function call: R|kotlin/require|(...)"];
|
||||||
|
25 [label="Access variable R|<local>/x|"];
|
||||||
|
26 [label="Smart cast: R|<local>/x|"];
|
||||||
|
27 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
28 [label="Exit block"];
|
||||||
|
}
|
||||||
|
29 [label="Exit function test_3" style="filled" fillcolor=red];
|
||||||
|
}
|
||||||
19 -> {20};
|
19 -> {20};
|
||||||
20 -> {21};
|
20 -> {21};
|
||||||
21 -> {22};
|
21 -> {22};
|
||||||
@@ -76,40 +78,44 @@ digraph conditionalEffects_kt {
|
|||||||
24 -> {25};
|
24 -> {25};
|
||||||
25 -> {26};
|
25 -> {26};
|
||||||
26 -> {27};
|
26 -> {27};
|
||||||
|
27 -> {28};
|
||||||
|
28 -> {29};
|
||||||
|
|
||||||
subgraph cluster_6 {
|
subgraph cluster_6 {
|
||||||
color=red
|
color=red
|
||||||
28 [label="Enter function test_3" style="filled" fillcolor=red];
|
30 [label="Enter function test_4" style="filled" fillcolor=red];
|
||||||
subgraph cluster_7 {
|
subgraph cluster_7 {
|
||||||
color=blue
|
color=blue
|
||||||
29 [label="Enter block"];
|
31 [label="Enter block"];
|
||||||
30 [label="Access variable R|<local>/x|"];
|
|
||||||
31 [label="Const: Null(null)"];
|
|
||||||
32 [label="Equality operator !="];
|
|
||||||
33 [label="Function call: R|kotlin/require|(...)"];
|
|
||||||
subgraph cluster_8 {
|
subgraph cluster_8 {
|
||||||
color=blue
|
color=blue
|
||||||
34 [label="Enter contract"];
|
32 [label="Enter &&"];
|
||||||
35 [label="Access variable R|<local>/x|"];
|
33 [label="Access variable R|<local>/x|"];
|
||||||
36 [label="Const: Null(null)"];
|
34 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
||||||
37 [label="Equality operator !="];
|
35 [label="Exit left part of &&"];
|
||||||
38 [label="Exit contract"];
|
36 [label="Enter right part of &&"];
|
||||||
|
37 [label="Access variable R|<local>/y|"];
|
||||||
|
38 [label="Const: Null(null)"];
|
||||||
|
39 [label="Equality operator !="];
|
||||||
|
40 [label="Exit &&"];
|
||||||
}
|
}
|
||||||
39 [label="Access variable R|<local>/x|"];
|
41 [label="Function call: R|kotlin/require|(...)"];
|
||||||
40 [label="Smart cast: R|<local>/x|"];
|
42 [label="Access variable R|<local>/x|"];
|
||||||
41 [label="Access variable R|kotlin/String.length|"];
|
43 [label="Smart cast: R|<local>/x|"];
|
||||||
42 [label="Exit block"];
|
44 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
45 [label="Access variable R|<local>/y|"];
|
||||||
|
46 [label="Smart cast: R|<local>/y|"];
|
||||||
|
47 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
48 [label="Exit block"];
|
||||||
}
|
}
|
||||||
43 [label="Exit function test_3" style="filled" fillcolor=red];
|
49 [label="Exit function test_4" style="filled" fillcolor=red];
|
||||||
}
|
}
|
||||||
28 -> {29};
|
|
||||||
29 -> {30};
|
|
||||||
30 -> {31};
|
30 -> {31};
|
||||||
31 -> {32};
|
31 -> {32};
|
||||||
32 -> {33};
|
32 -> {33};
|
||||||
33 -> {34};
|
33 -> {34};
|
||||||
34 -> {35};
|
34 -> {35};
|
||||||
35 -> {36};
|
35 -> {40 36};
|
||||||
36 -> {37};
|
36 -> {37};
|
||||||
37 -> {38};
|
37 -> {38};
|
||||||
38 -> {39};
|
38 -> {39};
|
||||||
@@ -117,73 +123,77 @@ digraph conditionalEffects_kt {
|
|||||||
40 -> {41};
|
40 -> {41};
|
||||||
41 -> {42};
|
41 -> {42};
|
||||||
42 -> {43};
|
42 -> {43};
|
||||||
|
43 -> {44};
|
||||||
subgraph cluster_9 {
|
|
||||||
color=red
|
|
||||||
44 [label="Enter function test_4" style="filled" fillcolor=red];
|
|
||||||
subgraph cluster_10 {
|
|
||||||
color=blue
|
|
||||||
45 [label="Enter block"];
|
|
||||||
subgraph cluster_11 {
|
|
||||||
color=blue
|
|
||||||
46 [label="Enter &&"];
|
|
||||||
47 [label="Access variable R|<local>/x|"];
|
|
||||||
48 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
49 [label="Exit left part of &&"];
|
|
||||||
50 [label="Enter right part of &&"];
|
|
||||||
51 [label="Access variable R|<local>/y|"];
|
|
||||||
52 [label="Const: Null(null)"];
|
|
||||||
53 [label="Equality operator !="];
|
|
||||||
54 [label="Exit &&"];
|
|
||||||
}
|
|
||||||
55 [label="Function call: R|kotlin/require|(...)"];
|
|
||||||
subgraph cluster_12 {
|
|
||||||
color=blue
|
|
||||||
56 [label="Enter contract"];
|
|
||||||
subgraph cluster_13 {
|
|
||||||
color=blue
|
|
||||||
57 [label="Enter &&"];
|
|
||||||
58 [label="Access variable R|<local>/x|"];
|
|
||||||
59 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
60 [label="Exit left part of &&"];
|
|
||||||
61 [label="Enter right part of &&"];
|
|
||||||
62 [label="Access variable R|<local>/y|"];
|
|
||||||
63 [label="Const: Null(null)"];
|
|
||||||
64 [label="Equality operator !="];
|
|
||||||
65 [label="Exit &&"];
|
|
||||||
}
|
|
||||||
66 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
67 [label="Access variable R|<local>/x|"];
|
|
||||||
68 [label="Smart cast: R|<local>/x|"];
|
|
||||||
69 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
70 [label="Access variable R|<local>/y|"];
|
|
||||||
71 [label="Smart cast: R|<local>/y|"];
|
|
||||||
72 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
73 [label="Exit block"];
|
|
||||||
}
|
|
||||||
74 [label="Exit function test_4" style="filled" fillcolor=red];
|
|
||||||
}
|
|
||||||
44 -> {45};
|
44 -> {45};
|
||||||
45 -> {46};
|
45 -> {46};
|
||||||
46 -> {47};
|
46 -> {47};
|
||||||
47 -> {48};
|
47 -> {48};
|
||||||
48 -> {49};
|
48 -> {49};
|
||||||
49 -> {54 50};
|
|
||||||
|
subgraph cluster_9 {
|
||||||
|
color=red
|
||||||
|
50 [label="Enter function test_5" style="filled" fillcolor=red];
|
||||||
|
subgraph cluster_10 {
|
||||||
|
color=blue
|
||||||
|
51 [label="Enter block"];
|
||||||
|
subgraph cluster_11 {
|
||||||
|
color=blue
|
||||||
|
52 [label="Enter when"];
|
||||||
|
subgraph cluster_12 {
|
||||||
|
color=blue
|
||||||
|
53 [label="Enter when branch condition "];
|
||||||
|
54 [label="Access variable R|<local>/b|"];
|
||||||
|
55 [label="Exit when branch condition"];
|
||||||
|
}
|
||||||
|
subgraph cluster_13 {
|
||||||
|
color=blue
|
||||||
|
56 [label="Enter when branch condition else"];
|
||||||
|
57 [label="Exit when branch condition"];
|
||||||
|
}
|
||||||
|
58 [label="Enter when branch result"];
|
||||||
|
subgraph cluster_14 {
|
||||||
|
color=blue
|
||||||
|
59 [label="Enter block"];
|
||||||
|
60 [label="Access variable R|<local>/x|"];
|
||||||
|
61 [label="Access variable <Unresolved name: length>#"];
|
||||||
|
62 [label="Exit block"];
|
||||||
|
}
|
||||||
|
63 [label="Exit when branch result"];
|
||||||
|
64 [label="Enter when branch result"];
|
||||||
|
subgraph cluster_15 {
|
||||||
|
color=blue
|
||||||
|
65 [label="Enter block"];
|
||||||
|
66 [label="Access variable R|<local>/x|"];
|
||||||
|
67 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
||||||
|
68 [label="Function call: R|kotlin/require|(...)"];
|
||||||
|
69 [label="Access variable R|<local>/x|"];
|
||||||
|
70 [label="Smart cast: R|<local>/x|"];
|
||||||
|
71 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
72 [label="Exit block"];
|
||||||
|
}
|
||||||
|
73 [label="Exit when branch result"];
|
||||||
|
74 [label="Exit when"];
|
||||||
|
}
|
||||||
|
75 [label="Access variable R|<local>/x|"];
|
||||||
|
76 [label="Access variable <Unresolved name: length>#"];
|
||||||
|
77 [label="Exit block"];
|
||||||
|
}
|
||||||
|
78 [label="Exit function test_5" style="filled" fillcolor=red];
|
||||||
|
}
|
||||||
50 -> {51};
|
50 -> {51};
|
||||||
51 -> {52};
|
51 -> {52};
|
||||||
52 -> {53};
|
52 -> {53};
|
||||||
53 -> {54};
|
53 -> {54};
|
||||||
54 -> {55};
|
54 -> {55};
|
||||||
55 -> {56};
|
55 -> {64 56};
|
||||||
56 -> {57};
|
56 -> {57};
|
||||||
57 -> {58};
|
57 -> {58};
|
||||||
58 -> {59};
|
58 -> {59};
|
||||||
59 -> {60};
|
59 -> {60};
|
||||||
60 -> {65 61};
|
60 -> {61};
|
||||||
61 -> {62};
|
61 -> {62};
|
||||||
62 -> {63};
|
62 -> {63};
|
||||||
63 -> {64};
|
63 -> {74};
|
||||||
64 -> {65};
|
64 -> {65};
|
||||||
65 -> {66};
|
65 -> {66};
|
||||||
66 -> {67};
|
66 -> {67};
|
||||||
@@ -194,78 +204,76 @@ digraph conditionalEffects_kt {
|
|||||||
71 -> {72};
|
71 -> {72};
|
||||||
72 -> {73};
|
72 -> {73};
|
||||||
73 -> {74};
|
73 -> {74};
|
||||||
|
74 -> {75};
|
||||||
subgraph cluster_14 {
|
|
||||||
color=red
|
|
||||||
75 [label="Enter function test_5" style="filled" fillcolor=red];
|
|
||||||
subgraph cluster_15 {
|
|
||||||
color=blue
|
|
||||||
76 [label="Enter block"];
|
|
||||||
subgraph cluster_16 {
|
|
||||||
color=blue
|
|
||||||
77 [label="Enter when"];
|
|
||||||
subgraph cluster_17 {
|
|
||||||
color=blue
|
|
||||||
78 [label="Enter when branch condition "];
|
|
||||||
79 [label="Access variable R|<local>/b|"];
|
|
||||||
80 [label="Exit when branch condition"];
|
|
||||||
}
|
|
||||||
subgraph cluster_18 {
|
|
||||||
color=blue
|
|
||||||
81 [label="Enter when branch condition else"];
|
|
||||||
82 [label="Exit when branch condition"];
|
|
||||||
}
|
|
||||||
83 [label="Enter when branch result"];
|
|
||||||
subgraph cluster_19 {
|
|
||||||
color=blue
|
|
||||||
84 [label="Enter block"];
|
|
||||||
85 [label="Access variable R|<local>/x|"];
|
|
||||||
86 [label="Access variable <Unresolved name: length>#"];
|
|
||||||
87 [label="Exit block"];
|
|
||||||
}
|
|
||||||
88 [label="Exit when branch result"];
|
|
||||||
89 [label="Enter when branch result"];
|
|
||||||
subgraph cluster_20 {
|
|
||||||
color=blue
|
|
||||||
90 [label="Enter block"];
|
|
||||||
91 [label="Access variable R|<local>/x|"];
|
|
||||||
92 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
93 [label="Function call: R|kotlin/require|(...)"];
|
|
||||||
subgraph cluster_21 {
|
|
||||||
color=blue
|
|
||||||
94 [label="Enter contract"];
|
|
||||||
95 [label="Access variable R|<local>/x|"];
|
|
||||||
96 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
97 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
98 [label="Access variable R|<local>/x|"];
|
|
||||||
99 [label="Smart cast: R|<local>/x|"];
|
|
||||||
100 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
101 [label="Exit block"];
|
|
||||||
}
|
|
||||||
102 [label="Exit when branch result"];
|
|
||||||
103 [label="Exit when"];
|
|
||||||
}
|
|
||||||
104 [label="Access variable R|<local>/x|"];
|
|
||||||
105 [label="Access variable <Unresolved name: length>#"];
|
|
||||||
106 [label="Exit block"];
|
|
||||||
}
|
|
||||||
107 [label="Exit function test_5" style="filled" fillcolor=red];
|
|
||||||
}
|
|
||||||
75 -> {76};
|
75 -> {76};
|
||||||
76 -> {77};
|
76 -> {77};
|
||||||
77 -> {78};
|
77 -> {78};
|
||||||
78 -> {79};
|
|
||||||
|
subgraph cluster_16 {
|
||||||
|
color=red
|
||||||
|
79 [label="Enter function test_6" style="filled" fillcolor=red];
|
||||||
|
subgraph cluster_17 {
|
||||||
|
color=blue
|
||||||
|
80 [label="Enter block"];
|
||||||
|
subgraph cluster_18 {
|
||||||
|
color=blue
|
||||||
|
81 [label="Enter when"];
|
||||||
|
subgraph cluster_19 {
|
||||||
|
color=blue
|
||||||
|
82 [label="Enter when branch condition "];
|
||||||
|
83 [label="Access variable R|<local>/b|"];
|
||||||
|
84 [label="Exit when branch condition"];
|
||||||
|
}
|
||||||
|
subgraph cluster_20 {
|
||||||
|
color=blue
|
||||||
|
85 [label="Enter when branch condition else"];
|
||||||
|
86 [label="Exit when branch condition"];
|
||||||
|
}
|
||||||
|
87 [label="Enter when branch result"];
|
||||||
|
subgraph cluster_21 {
|
||||||
|
color=blue
|
||||||
|
88 [label="Enter block"];
|
||||||
|
89 [label="Access variable R|<local>/x|"];
|
||||||
|
90 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
||||||
|
91 [label="Function call: R|kotlin/require|(...)"];
|
||||||
|
92 [label="Access variable R|<local>/x|"];
|
||||||
|
93 [label="Smart cast: R|<local>/x|"];
|
||||||
|
94 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
95 [label="Exit block"];
|
||||||
|
}
|
||||||
|
96 [label="Exit when branch result"];
|
||||||
|
97 [label="Enter when branch result"];
|
||||||
|
subgraph cluster_22 {
|
||||||
|
color=blue
|
||||||
|
98 [label="Enter block"];
|
||||||
|
99 [label="Access variable R|<local>/x|"];
|
||||||
|
100 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
||||||
|
101 [label="Function call: R|kotlin/require|(...)"];
|
||||||
|
102 [label="Access variable R|<local>/x|"];
|
||||||
|
103 [label="Smart cast: R|<local>/x|"];
|
||||||
|
104 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
105 [label="Exit block"];
|
||||||
|
}
|
||||||
|
106 [label="Exit when branch result"];
|
||||||
|
107 [label="Exit when"];
|
||||||
|
}
|
||||||
|
108 [label="Access variable R|<local>/x|"];
|
||||||
|
109 [label="Smart cast: R|<local>/x|"];
|
||||||
|
110 [label="Access variable R|kotlin/String.length|"];
|
||||||
|
111 [label="Exit block"];
|
||||||
|
}
|
||||||
|
112 [label="Exit function test_6" style="filled" fillcolor=red];
|
||||||
|
}
|
||||||
79 -> {80};
|
79 -> {80};
|
||||||
80 -> {89 81};
|
80 -> {81};
|
||||||
81 -> {82};
|
81 -> {82};
|
||||||
82 -> {83};
|
82 -> {83};
|
||||||
83 -> {84};
|
83 -> {84};
|
||||||
84 -> {85};
|
84 -> {97 85};
|
||||||
85 -> {86};
|
85 -> {86};
|
||||||
86 -> {87};
|
86 -> {87};
|
||||||
87 -> {88};
|
87 -> {88};
|
||||||
88 -> {103};
|
88 -> {89};
|
||||||
89 -> {90};
|
89 -> {90};
|
||||||
90 -> {91};
|
90 -> {91};
|
||||||
91 -> {92};
|
91 -> {92};
|
||||||
@@ -273,7 +281,7 @@ digraph conditionalEffects_kt {
|
|||||||
93 -> {94};
|
93 -> {94};
|
||||||
94 -> {95};
|
94 -> {95};
|
||||||
95 -> {96};
|
95 -> {96};
|
||||||
96 -> {97};
|
96 -> {107};
|
||||||
97 -> {98};
|
97 -> {98};
|
||||||
98 -> {99};
|
98 -> {99};
|
||||||
99 -> {100};
|
99 -> {100};
|
||||||
@@ -284,116 +292,10 @@ digraph conditionalEffects_kt {
|
|||||||
104 -> {105};
|
104 -> {105};
|
||||||
105 -> {106};
|
105 -> {106};
|
||||||
106 -> {107};
|
106 -> {107};
|
||||||
|
107 -> {108};
|
||||||
subgraph cluster_22 {
|
|
||||||
color=red
|
|
||||||
108 [label="Enter function test_6" style="filled" fillcolor=red];
|
|
||||||
subgraph cluster_23 {
|
|
||||||
color=blue
|
|
||||||
109 [label="Enter block"];
|
|
||||||
subgraph cluster_24 {
|
|
||||||
color=blue
|
|
||||||
110 [label="Enter when"];
|
|
||||||
subgraph cluster_25 {
|
|
||||||
color=blue
|
|
||||||
111 [label="Enter when branch condition "];
|
|
||||||
112 [label="Access variable R|<local>/b|"];
|
|
||||||
113 [label="Exit when branch condition"];
|
|
||||||
}
|
|
||||||
subgraph cluster_26 {
|
|
||||||
color=blue
|
|
||||||
114 [label="Enter when branch condition else"];
|
|
||||||
115 [label="Exit when branch condition"];
|
|
||||||
}
|
|
||||||
116 [label="Enter when branch result"];
|
|
||||||
subgraph cluster_27 {
|
|
||||||
color=blue
|
|
||||||
117 [label="Enter block"];
|
|
||||||
118 [label="Access variable R|<local>/x|"];
|
|
||||||
119 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
120 [label="Function call: R|kotlin/require|(...)"];
|
|
||||||
subgraph cluster_28 {
|
|
||||||
color=blue
|
|
||||||
121 [label="Enter contract"];
|
|
||||||
122 [label="Access variable R|<local>/x|"];
|
|
||||||
123 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
124 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
125 [label="Access variable R|<local>/x|"];
|
|
||||||
126 [label="Smart cast: R|<local>/x|"];
|
|
||||||
127 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
128 [label="Exit block"];
|
|
||||||
}
|
|
||||||
129 [label="Exit when branch result"];
|
|
||||||
130 [label="Enter when branch result"];
|
|
||||||
subgraph cluster_29 {
|
|
||||||
color=blue
|
|
||||||
131 [label="Enter block"];
|
|
||||||
132 [label="Access variable R|<local>/x|"];
|
|
||||||
133 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
134 [label="Function call: R|kotlin/require|(...)"];
|
|
||||||
subgraph cluster_30 {
|
|
||||||
color=blue
|
|
||||||
135 [label="Enter contract"];
|
|
||||||
136 [label="Access variable R|<local>/x|"];
|
|
||||||
137 [label="Type operator: (R|<local>/x| is R|kotlin/String|)"];
|
|
||||||
138 [label="Exit contract"];
|
|
||||||
}
|
|
||||||
139 [label="Access variable R|<local>/x|"];
|
|
||||||
140 [label="Smart cast: R|<local>/x|"];
|
|
||||||
141 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
142 [label="Exit block"];
|
|
||||||
}
|
|
||||||
143 [label="Exit when branch result"];
|
|
||||||
144 [label="Exit when"];
|
|
||||||
}
|
|
||||||
145 [label="Access variable R|<local>/x|"];
|
|
||||||
146 [label="Smart cast: R|<local>/x|"];
|
|
||||||
147 [label="Access variable R|kotlin/String.length|"];
|
|
||||||
148 [label="Exit block"];
|
|
||||||
}
|
|
||||||
149 [label="Exit function test_6" style="filled" fillcolor=red];
|
|
||||||
}
|
|
||||||
108 -> {109};
|
108 -> {109};
|
||||||
109 -> {110};
|
109 -> {110};
|
||||||
110 -> {111};
|
110 -> {111};
|
||||||
111 -> {112};
|
111 -> {112};
|
||||||
112 -> {113};
|
|
||||||
113 -> {130 114};
|
|
||||||
114 -> {115};
|
|
||||||
115 -> {116};
|
|
||||||
116 -> {117};
|
|
||||||
117 -> {118};
|
|
||||||
118 -> {119};
|
|
||||||
119 -> {120};
|
|
||||||
120 -> {121};
|
|
||||||
121 -> {122};
|
|
||||||
122 -> {123};
|
|
||||||
123 -> {124};
|
|
||||||
124 -> {125};
|
|
||||||
125 -> {126};
|
|
||||||
126 -> {127};
|
|
||||||
127 -> {128};
|
|
||||||
128 -> {129};
|
|
||||||
129 -> {144};
|
|
||||||
130 -> {131};
|
|
||||||
131 -> {132};
|
|
||||||
132 -> {133};
|
|
||||||
133 -> {134};
|
|
||||||
134 -> {135};
|
|
||||||
135 -> {136};
|
|
||||||
136 -> {137};
|
|
||||||
137 -> {138};
|
|
||||||
138 -> {139};
|
|
||||||
139 -> {140};
|
|
||||||
140 -> {141};
|
|
||||||
141 -> {142};
|
|
||||||
142 -> {143};
|
|
||||||
143 -> {144};
|
|
||||||
144 -> {145};
|
|
||||||
145 -> {146};
|
|
||||||
146 -> {147};
|
|
||||||
147 -> {148};
|
|
||||||
148 -> {149};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -12,10 +12,10 @@ var Any?.isNotNull: Boolean
|
|||||||
return this != null
|
return this != null
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
contract {
|
<!WRONG_IMPLIES_CONDITION!>contract {
|
||||||
returns() implies (this@isNotNull != null)
|
returns() implies (this@isNotNull != null)
|
||||||
<!ERROR_IN_CONTRACT_DESCRIPTION!>require(<!SENSELESS_COMPARISON!>this != null<!>)<!>
|
<!ERROR_IN_CONTRACT_DESCRIPTION!>require(<!SENSELESS_COMPARISON!>this != null<!>)<!>
|
||||||
}
|
}<!>
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test_1(a: A?) {
|
fun test_1(a: A?) {
|
||||||
|
|||||||
+61
-105
@@ -7,12 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||||
import org.jetbrains.kotlin.fir.BuiltinTypes
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.previousCfgNodes
|
import org.jetbrains.kotlin.fir.analysis.cfa.util.previousCfgNodes
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
||||||
import org.jetbrains.kotlin.fir.contracts.coneEffects
|
import org.jetbrains.kotlin.fir.contracts.coneEffects
|
||||||
@@ -27,10 +25,11 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
|||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.JumpNode
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.JumpNode
|
||||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
|
||||||
|
|
||||||
object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
||||||
|
|
||||||
@@ -47,11 +46,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
|||||||
|
|
||||||
val logicSystem = object : PersistentLogicSystem(context.session.typeContext) {
|
val logicSystem = object : PersistentLogicSystem(context.session.typeContext) {
|
||||||
override val variableStorage: VariableStorageImpl
|
override val variableStorage: VariableStorageImpl
|
||||||
get() = dataFlowInfo.variableStorage as VariableStorageImpl
|
get() = throw IllegalStateException("shouldn't be called")
|
||||||
|
|
||||||
override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean {
|
override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean =
|
||||||
return !isNullableNothing
|
!isNullableNothing
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
effects.forEach { effect ->
|
effects.forEach { effect ->
|
||||||
@@ -89,94 +87,70 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: create separate variable storage and don't modify existing one
|
|
||||||
val variableStorage = dataFlowInfo.variableStorage as VariableStorageImpl
|
|
||||||
val flow = dataFlowInfo.flowOnNodes.getValue(node) as PersistentFlow
|
val flow = dataFlowInfo.flowOnNodes.getValue(node) as PersistentFlow
|
||||||
var typeStatements: TypeStatements = flow.approvedTypeStatements
|
var typeStatements: TypeStatements = flow.approvedTypeStatements
|
||||||
|
|
||||||
if (effect.value != ConeConstantReference.WILDCARD) {
|
val operation = effect.value.toOperation()
|
||||||
val operation = effect.value.toOperation()
|
if (operation != null) {
|
||||||
if (expressionType != null && expressionType.isInapplicableWith(operation, context.session)) return false
|
|
||||||
|
|
||||||
if (resultExpression is FirConstExpression<*>) {
|
if (resultExpression is FirConstExpression<*>) {
|
||||||
if (!resultExpression.isApplicableWith(operation)) return false
|
if (!operation.isTrueFor(resultExpression.value)) return false
|
||||||
} else {
|
} else {
|
||||||
val resultVar = variableStorage.getOrCreate(flow, resultExpression)
|
if (expressionType != null && !operation.canBeTrueFor(context.session, expressionType)) return false
|
||||||
typeStatements = logicSystem.andForTypeStatements(
|
// TODO: avoid modifying the storage
|
||||||
typeStatements,
|
val variableStorage = dataFlowInfo.variableStorage as VariableStorageImpl
|
||||||
logicSystem.approveOperationStatement(flow, OperationStatement(resultVar, operation))
|
val resultVar = variableStorage.getOrCreateIfReal(flow, resultExpression)
|
||||||
)
|
if (resultVar != null) {
|
||||||
}
|
typeStatements = logicSystem.andForTypeStatements(
|
||||||
}
|
typeStatements,
|
||||||
|
logicSystem.approveOperationStatement(flow, OperationStatement(resultVar, operation))
|
||||||
val conditionStatements = effectDeclaration.condition.buildTypeStatements(
|
)
|
||||||
function, logicSystem, variableStorage, context
|
|
||||||
) ?: return false
|
|
||||||
|
|
||||||
for ((realVar, requiredTypeStatement) in conditionStatements) {
|
|
||||||
val fixedRealVar = typeStatements.keys.find { it.identifier == realVar.identifier } ?: realVar
|
|
||||||
val originalType = function.getParameterType(fixedRealVar.identifier.symbol, context) ?: continue
|
|
||||||
val resultType = typeStatements[fixedRealVar]?.exactType.intersectWith(typeContext, originalType)
|
|
||||||
val requiredType = typeContext.intersectTypesOrNull(requiredTypeStatement.exactType.toList())
|
|
||||||
if (requiredType != null && !requiredType.isSupertypeOf(typeContext, resultType)) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ConeBooleanExpression.buildTypeStatements(
|
|
||||||
function: FirFunction,
|
|
||||||
logicSystem: LogicSystem<*>,
|
|
||||||
variableStorage: VariableStorage,
|
|
||||||
context: CheckerContext
|
|
||||||
): TypeStatements? {
|
|
||||||
fun ConeValueParameterReference.toVariable(): RealVariable {
|
|
||||||
val parameterSymbol = function.getParameterSymbol(parameterIndex, context)
|
|
||||||
return variableStorage.getLocalVariable(parameterSymbol)
|
|
||||||
?: RealVariable(
|
|
||||||
Identifier(parameterSymbol, null, null),
|
|
||||||
parameterIndex < 0, null, parameterIndex + 1, PropertyStability.STABLE_VALUE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ConeBooleanExpression.toTypeStatements(inverted: Boolean): TypeStatements? = when (this) {
|
|
||||||
is ConeBinaryLogicExpression -> {
|
|
||||||
val left = left.toTypeStatements(inverted)
|
|
||||||
val right = right.toTypeStatements(inverted)
|
|
||||||
when {
|
|
||||||
left == null -> right
|
|
||||||
right == null -> left
|
|
||||||
(kind == LogicOperationKind.AND) == !inverted -> logicSystem.andForTypeStatements(left, right)
|
|
||||||
else -> logicSystem.orForTypeStatements(left, right)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ConeIsInstancePredicate ->
|
|
||||||
if (isNegated == inverted) (arg.toVariable() typeEq type).singleton() else mapOf()
|
|
||||||
is ConeIsNullPredicate ->
|
|
||||||
arg.toVariable().nullabilityStatement(context.session.builtinTypes, isNull = isNegated == inverted).singleton()
|
|
||||||
is ConeLogicalNot -> arg.toTypeStatements(!inverted)
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toTypeStatements(inverted = false)
|
// TODO: if this is not a top-level function, `FirDataFlowAnalyzer` has erased its value parameters
|
||||||
|
// from `dataFlowInfo.variableStorage` for some reason, so its `getLocalVariable` doesn't work.
|
||||||
|
val knownVariables = typeStatements.keys.associateBy { it.identifier }
|
||||||
|
val argumentVariables = Array(function.valueParameters.size + 1) { i ->
|
||||||
|
val parameterSymbol = if (i > 0) {
|
||||||
|
function.valueParameters[i - 1].symbol
|
||||||
|
} else {
|
||||||
|
if (function.symbol is FirPropertyAccessorSymbol) {
|
||||||
|
context.containingProperty?.symbol
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} ?: function.symbol
|
||||||
|
}
|
||||||
|
val identifier = Identifier(parameterSymbol, null, null)
|
||||||
|
// Might be unknown if there are no statements made about that parameter, but it's still possible that trivial
|
||||||
|
// contracts are valid. E.g. `returns() implies (x is String)` when `x`'s *original type* is already `String`.
|
||||||
|
knownVariables[identifier] ?: RealVariable(identifier, i == 0, null, i, PropertyStability.STABLE_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val conditionStatements = logicSystem.approveContractStatement(
|
||||||
|
flow, effectDeclaration.condition, argumentVariables, substitutor = null
|
||||||
|
) ?: return true
|
||||||
|
|
||||||
|
return !conditionStatements.values.all { requirement ->
|
||||||
|
val originalType = requirement.variable.identifier.symbol.correspondingParameterType ?: return@all true
|
||||||
|
val requiredType = requirement.smartCastedType(typeContext, originalType)
|
||||||
|
val actualType = typeStatements[requirement.variable].smartCastedType(typeContext, originalType)
|
||||||
|
actualType.isSubtypeOf(typeContext, requiredType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealVariable.nullabilityStatement(builtinTypes: BuiltinTypes, isNull: Boolean) =
|
private fun Operation.canBeTrueFor(session: FirSession, type: ConeKotlinType): Boolean = when (this) {
|
||||||
this typeEq (if (isNull) builtinTypes.nullableNothingType.type else builtinTypes.anyType.type)
|
Operation.EqTrue, Operation.EqFalse ->
|
||||||
|
AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, type)
|
||||||
private fun TypeStatement.singleton(): TypeStatements =
|
Operation.EqNull -> type.canBeNull
|
||||||
mapOf(variable to this)
|
Operation.NotEqNull -> !type.isNullableNothing
|
||||||
|
|
||||||
private fun ConeKotlinType.isInapplicableWith(operation: Operation, session: FirSession): Boolean {
|
|
||||||
return (operation == Operation.EqFalse || operation == Operation.EqTrue)
|
|
||||||
&& !AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, this)
|
|
||||||
|| operation == Operation.EqNull && !isNullable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FirConstExpression<*>.isApplicableWith(operation: Operation): Boolean = when {
|
private fun Operation.isTrueFor(value: Any?) = when (this) {
|
||||||
kind == ConstantValueKind.Null -> operation == Operation.EqNull
|
Operation.EqTrue -> value == true
|
||||||
kind == ConstantValueKind.Boolean && operation == Operation.EqTrue -> (value as Boolean)
|
Operation.EqFalse -> value == false
|
||||||
kind == ConstantValueKind.Boolean && operation == Operation.EqFalse -> !(value as Boolean)
|
Operation.EqNull -> value == null
|
||||||
else -> true
|
Operation.NotEqNull -> value != null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CFGNode<*>.collectBranchExits(nodes: MutableList<CFGNode<*>> = mutableListOf()): List<CFGNode<*>> {
|
private fun CFGNode<*>.collectBranchExits(nodes: MutableList<CFGNode<*>> = mutableListOf()): List<CFGNode<*>> {
|
||||||
@@ -189,28 +163,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
|||||||
private val CheckerContext.containingProperty: FirProperty?
|
private val CheckerContext.containingProperty: FirProperty?
|
||||||
get() = (containingDeclarations.lastOrNull { it is FirProperty } as? FirProperty)
|
get() = (containingDeclarations.lastOrNull { it is FirProperty } as? FirProperty)
|
||||||
|
|
||||||
private fun FirFunction.getParameterType(symbol: FirBasedSymbol<*>, context: CheckerContext): ConeKotlinType? {
|
private val FirBasedSymbol<*>.correspondingParameterType: ConeKotlinType?
|
||||||
val typeRef = if (this.symbol == symbol) {
|
get() = when (this) {
|
||||||
if (symbol is FirPropertyAccessorSymbol) {
|
is FirValueParameterSymbol -> resolvedReturnType
|
||||||
context.containingProperty?.receiverParameter?.typeRef
|
is FirCallableSymbol<*> -> resolvedReceiverTypeRef?.coneType
|
||||||
} else {
|
else -> null
|
||||||
receiverParameter?.typeRef
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
valueParameters.find { it.symbol == symbol }?.returnTypeRef
|
|
||||||
}
|
}
|
||||||
return typeRef?.coneType
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun FirFunction.getParameterSymbol(index: Int, context: CheckerContext): FirBasedSymbol<*> {
|
|
||||||
return if (index == -1) {
|
|
||||||
if (symbol !is FirPropertyAccessorSymbol) {
|
|
||||||
symbol
|
|
||||||
} else {
|
|
||||||
context.containingProperty?.symbol ?: symbol
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.valueParameters[index].symbol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-61
@@ -11,19 +11,16 @@ import org.jetbrains.kotlin.descriptors.Modality
|
|||||||
import org.jetbrains.kotlin.fir.*
|
import org.jetbrains.kotlin.fir.*
|
||||||
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration
|
import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
|
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration
|
import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
|
||||||
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
|
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
|
||||||
import org.jetbrains.kotlin.fir.resolve.*
|
import org.jetbrains.kotlin.fir.resolve.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
|
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.contracts.buildContractFir
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.contracts.createArgumentsMapping
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
||||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
|
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
|
||||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
|
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
|
||||||
@@ -35,7 +32,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
|||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.fir.visitors.transformSingle
|
|
||||||
import org.jetbrains.kotlin.name.StandardClassIds
|
import org.jetbrains.kotlin.name.StandardClassIds
|
||||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
import org.jetbrains.kotlin.types.ConstantValueKind
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
@@ -99,7 +95,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) {
|
override fun receiverUpdated(symbol: FirBasedSymbol<*>, info: TypeStatement?) {
|
||||||
val index = receiverStack.getReceiverIndex(symbol) ?: return
|
val index = receiverStack.getReceiverIndex(symbol) ?: return
|
||||||
val originalType = receiverStack.getOriginalType(index)
|
val originalType = receiverStack.getOriginalType(index)
|
||||||
receiverStack.replaceReceiverType(index, info?.exactType.intersectWith(typeContext, originalType))
|
receiverStack.replaceReceiverType(index, info.smartCastedType(typeContext, originalType))
|
||||||
}
|
}
|
||||||
|
|
||||||
override val logicSystem: PersistentLogicSystem =
|
override val logicSystem: PersistentLogicSystem =
|
||||||
@@ -139,8 +135,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
private val graphBuilder get() = context.graphBuilder
|
private val graphBuilder get() = context.graphBuilder
|
||||||
private val variableStorage get() = context.variableStorage
|
private val variableStorage get() = context.variableStorage
|
||||||
|
|
||||||
private var contractDescriptionVisitingMode = false
|
|
||||||
|
|
||||||
private val any = components.session.builtinTypes.anyType.type
|
private val any = components.session.builtinTypes.anyType.type
|
||||||
private val nullableNothing = components.session.builtinTypes.nullableNothingType.type
|
private val nullableNothing = components.session.builtinTypes.nullableNothingType.type
|
||||||
|
|
||||||
@@ -393,7 +387,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type))
|
flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type))
|
||||||
}
|
}
|
||||||
if (!type.canBeNull) {
|
if (!type.canBeNull) {
|
||||||
|
// x is (T & Any) => x != null
|
||||||
flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null))
|
flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null))
|
||||||
|
} else {
|
||||||
|
// TODO? (KT-22996) x !is T? => x != null; don't forget to change `approveContractStatement`
|
||||||
|
// flow.addImplication((expressionVariable eq !isType) implies (operandVariable notEq null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,7 +406,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
} else {
|
} else {
|
||||||
val expressionVariable = variableStorage.createSynthetic(typeOperatorCall)
|
val expressionVariable = variableStorage.createSynthetic(typeOperatorCall)
|
||||||
flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null))
|
flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null))
|
||||||
// TODO? flow.addImplication((expressionVariable eq null) implies (operandVariable eq null))
|
// TODO? (x as T?) == null => x == null
|
||||||
|
// flow.addImplication((expressionVariable eq null) implies (operandVariable eq null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,8 +770,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
fun enterQualifiedAccessExpression() {}
|
fun enterQualifiedAccessExpression() {}
|
||||||
|
|
||||||
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
|
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
|
||||||
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
|
val flow = graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
|
||||||
processConditionalContract(qualifiedAccessExpression)
|
processConditionalContract(flow, qualifiedAccessExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitSmartCastExpression(smartCastExpression: FirSmartCastExpression) {
|
fun exitSmartCastExpression(smartCastExpression: FirSmartCastExpression) {
|
||||||
@@ -843,10 +842,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
val (functionCallNode, unionNode) = graphBuilder.exitFunctionCall(functionCall, callCompleted)
|
val (functionCallNode, unionNode) = graphBuilder.exitFunctionCall(functionCall, callCompleted)
|
||||||
unionNode?.unionFlowFromArguments()
|
unionNode?.unionFlowFromArguments()
|
||||||
val flow = functionCallNode.mergeIncomingFlow()
|
val flow = functionCallNode.mergeIncomingFlow()
|
||||||
if (functionCall.isBooleanNot()) {
|
processConditionalContract(flow, functionCall)
|
||||||
exitBooleanNot(flow, functionCall, functionCallNode)
|
|
||||||
}
|
|
||||||
processConditionalContract(functionCall)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
|
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
|
||||||
@@ -865,69 +861,74 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
flow = logicSystem.unionFlow(previousNodes.map { it.flow })
|
flow = logicSystem.unionFlow(previousNodes.map { it.flow })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) {
|
private fun FirQualifiedAccess.orderedArguments(callee: FirFunction): Array<out FirExpression?>? {
|
||||||
val owner: FirContractDescriptionOwner? = when (qualifiedAccess) {
|
val receiver = extensionReceiver.takeIf { it != FirNoReceiverExpression }
|
||||||
|
?: dispatchReceiver.takeIf { it != FirNoReceiverExpression }
|
||||||
|
return when (this) {
|
||||||
|
is FirFunctionCall -> {
|
||||||
|
val argumentToParameter = resolvedArgumentMapping ?: return null
|
||||||
|
val parameterToArgument = argumentToParameter.entries.associate { it.value to it.key.unwrapArgument() }
|
||||||
|
Array(callee.valueParameters.size + 1) { i ->
|
||||||
|
if (i > 0) parameterToArgument[callee.valueParameters[i - 1]] else receiver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FirQualifiedAccessExpression -> arrayOf(receiver)
|
||||||
|
is FirVariableAssignment -> arrayOf(receiver, rValue)
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processConditionalContract(flow: FLOW, qualifiedAccess: FirQualifiedAccess) {
|
||||||
|
val callee = when (qualifiedAccess) {
|
||||||
is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction
|
is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction
|
||||||
is FirQualifiedAccessExpression -> {
|
is FirQualifiedAccessExpression -> (qualifiedAccess.calleeReference.resolvedSymbol?.fir as? FirProperty)?.getter
|
||||||
val property = qualifiedAccess.calleeReference.resolvedSymbol?.fir as? FirProperty
|
is FirVariableAssignment -> (qualifiedAccess.lValue.resolvedSymbol?.fir as? FirProperty)?.setter
|
||||||
property?.getter
|
|
||||||
}
|
|
||||||
is FirVariableAssignment -> {
|
|
||||||
val property = qualifiedAccess.lValue.resolvedSymbol?.fir as? FirProperty
|
|
||||||
property?.setter
|
|
||||||
}
|
|
||||||
else -> null
|
else -> null
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
if (callee.symbol.callableId == StandardClassIds.Callables.not) {
|
||||||
|
// Special hardcoded contract for Boolean.not():
|
||||||
|
// returns(true) implies (this == false)
|
||||||
|
// returns(false) implies (this == true)
|
||||||
|
return exitBooleanNot(flow, qualifiedAccess as FirFunctionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
val contractDescription = owner?.contractDescription as? FirResolvedContractDescription ?: return
|
val contractDescription = callee.contractDescription as? FirResolvedContractDescription ?: return
|
||||||
val conditionalEffects = contractDescription.effects.map { it.effect }.filterIsInstance<ConeConditionalEffectDeclaration>()
|
val conditionalEffects = contractDescription.effects.mapNotNull { it.effect as? ConeConditionalEffectDeclaration }
|
||||||
if (conditionalEffects.isEmpty()) return
|
if (conditionalEffects.isEmpty()) return
|
||||||
val argumentsMapping = createArgumentsMapping(qualifiedAccess) ?: return
|
|
||||||
|
|
||||||
val typeParameters = (owner as? FirTypeParameterRefsOwner)?.typeParameters
|
val arguments = qualifiedAccess.orderedArguments(callee) ?: return
|
||||||
val substitutor = if (!typeParameters.isNullOrEmpty()) {
|
val argumentVariables = Array(arguments.size) { i -> arguments[i]?.let { variableStorage.getOrCreateIfReal(flow, it) } }
|
||||||
|
if (argumentVariables.all { it == null }) return
|
||||||
|
|
||||||
|
val typeParameters = callee.typeParameters
|
||||||
|
val substitutor = if (typeParameters.isNotEmpty()) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val substitutionFromArguments = typeParameters.zip(qualifiedAccess.typeArguments).map { (typeParameterRef, typeArgument) ->
|
val substitutionFromArguments = typeParameters.zip(qualifiedAccess.typeArguments).map { (typeParameterRef, typeArgument) ->
|
||||||
typeParameterRef.symbol to typeArgument.toConeTypeProjection().type
|
typeParameterRef.symbol to typeArgument.toConeTypeProjection().type
|
||||||
}.filter { it.second != null }.toMap() as Map<FirTypeParameterSymbol, ConeKotlinType>
|
}.filter { it.second != null }.toMap() as Map<FirTypeParameterSymbol, ConeKotlinType>
|
||||||
ConeSubstitutorByMap(substitutionFromArguments, components.session)
|
ConeSubstitutorByMap(substitutionFromArguments, components.session)
|
||||||
} else {
|
} else {
|
||||||
ConeSubstitutor.Empty
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
contractDescriptionVisitingMode = true
|
|
||||||
graphBuilder.enterContract(qualifiedAccess).mergeIncomingFlow()
|
|
||||||
val lastFlow = graphBuilder.lastNode.flow
|
|
||||||
val functionCallVariable = variableStorage.getOrCreate(lastFlow, qualifiedAccess)
|
|
||||||
for (conditionalEffect in conditionalEffects) {
|
for (conditionalEffect in conditionalEffects) {
|
||||||
val fir = conditionalEffect.buildContractFir(argumentsMapping, substitutor) ?: continue
|
|
||||||
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
|
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
|
||||||
fir.transformSingle(components.transformer, ResolutionMode.ContextDependent)
|
val operation = effect.value.toOperation()
|
||||||
val argumentVariable = variableStorage.get(lastFlow, fir) ?: continue
|
val statements = logicSystem.approveContractStatement(
|
||||||
val lastNode = graphBuilder.lastNode
|
flow, conditionalEffect.condition, argumentVariables, substitutor, removeApprovedOrImpossible = operation == null
|
||||||
when (val value = effect.value) {
|
) ?: continue // TODO: do what if the result is known to be false?
|
||||||
ConeConstantReference.WILDCARD -> {
|
if (operation == null) {
|
||||||
lastNode.flow.commitOperationStatement(argumentVariable eq true)
|
statements.values.forEach { flow.addTypeStatement(it) }
|
||||||
}
|
} else {
|
||||||
|
val functionCallVariable = variableStorage.getOrCreate(flow, qualifiedAccess)
|
||||||
else -> {
|
flow.addAllConditionally(OperationStatement(functionCallVariable, operation), statements)
|
||||||
logicSystem.translateVariableFromConditionInStatements(
|
|
||||||
lastNode.flow,
|
|
||||||
argumentVariable,
|
|
||||||
functionCallVariable,
|
|
||||||
shouldRemoveOriginalStatements = true,
|
|
||||||
filter = { it.condition.operation == Operation.EqTrue },
|
|
||||||
transform = { OperationStatement(it.condition.variable, value.toOperation()) implies it.effect }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graphBuilder.exitContract(qualifiedAccess).mergeIncomingFlow()
|
|
||||||
contractDescriptionVisitingMode = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitConstExpression(constExpression: FirConstExpression<*>) {
|
fun exitConstExpression(constExpression: FirConstExpression<*>) {
|
||||||
if (constExpression.resultType is FirResolvedTypeRef && !contractDescriptionVisitingMode) return
|
if (constExpression.resultType is FirResolvedTypeRef) return
|
||||||
graphBuilder.exitConstExpression(constExpression).mergeIncomingFlow()
|
graphBuilder.exitConstExpression(constExpression).mergeIncomingFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,7 +950,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
logicSystem.recordNewAssignment(flow, variable, context.newAssignmentIndex())
|
logicSystem.recordNewAssignment(flow, variable, context.newAssignmentIndex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processConditionalContract(assignment)
|
processConditionalContract(flow, assignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exitVariableInitialization(
|
private fun exitVariableInitialization(
|
||||||
@@ -1089,9 +1090,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exitBooleanNot(flow: FLOW, functionCall: FirFunctionCall, node: FunctionCallNode) {
|
private fun exitBooleanNot(flow: FLOW, expression: FirFunctionCall) {
|
||||||
val argumentVariable = variableStorage.get(flow, node.firstPreviousNode.fir) ?: return
|
val argumentVariable = variableStorage.get(flow, expression.dispatchReceiver) ?: return
|
||||||
val expressionVariable = variableStorage.createSynthetic(functionCall)
|
val expressionVariable = variableStorage.createSynthetic(expression)
|
||||||
// Alternatively: (expression == true => argument == false) && (expression == false => argument == true)
|
// Alternatively: (expression == true => argument == false) && (expression == false => argument == true)
|
||||||
// Which implementation is faster and/or consumes less memory is an open question.
|
// Which implementation is faster and/or consumes less memory is an open question.
|
||||||
logicSystem.translateVariableFromConditionInStatements(flow, argumentVariable, expressionVariable) {
|
logicSystem.translateVariableFromConditionInStatements(flow, argumentVariable, expressionVariable) {
|
||||||
|
|||||||
-8
@@ -888,14 +888,6 @@ class ControlFlowGraphBuilder {
|
|||||||
return leftExitNode to rightExitNode
|
return leftExitNode to rightExitNode
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enterContract(qualifiedAccess: FirQualifiedAccess): EnterContractNode {
|
|
||||||
return createEnterContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exitContract(qualifiedAccess: FirQualifiedAccess): ExitContractNode {
|
|
||||||
return createExitContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
|
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
|
||||||
assert(binaryLogicExpression.kind == LogicOperationKind.OR)
|
assert(binaryLogicExpression.kind == LogicOperationKind.OR)
|
||||||
levelCounter--
|
levelCounter--
|
||||||
|
|||||||
-6
@@ -166,12 +166,6 @@ fun ControlFlowGraphBuilder.createElvisExitNode(fir: FirElvisExpression): ElvisE
|
|||||||
fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode =
|
fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode =
|
||||||
VariableDeclarationNode(currentGraph, fir, levelCounter, createId())
|
VariableDeclarationNode(currentGraph, fir, levelCounter, createId())
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createExitContractNode(fir: FirQualifiedAccess): ExitContractNode =
|
|
||||||
ExitContractNode(currentGraph, fir, levelCounter, createId())
|
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirQualifiedAccess): EnterContractNode =
|
|
||||||
EnterContractNode(currentGraph, fir, levelCounter, createId())
|
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode =
|
fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode =
|
||||||
ConstExpressionNode(currentGraph, fir, levelCounter, createId())
|
ConstExpressionNode(currentGraph, fir, levelCounter, createId())
|
||||||
|
|
||||||
|
|||||||
-116
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.fir.resolve.dfa.contracts
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.*
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
|
||||||
import org.jetbrains.kotlin.fir.expressions.builder.*
|
|
||||||
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
|
|
||||||
import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
|
||||||
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
|
|
||||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
|
||||||
|
|
||||||
private class ConeConditionalEffectToFirVisitor(
|
|
||||||
val valueParametersMapping: Map<Int, FirExpression>,
|
|
||||||
val substitutor: ConeSubstitutor
|
|
||||||
) : ConeContractDescriptionVisitor<FirExpression?, Nothing?>() {
|
|
||||||
override fun visitConditionalEffectDeclaration(conditionalEffect: ConeConditionalEffectDeclaration, data: Nothing?): FirExpression? {
|
|
||||||
return conditionalEffect.condition.accept(this, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitConstantDescriptor(constantReference: ConeConstantReference, data: Nothing?): FirExpression? {
|
|
||||||
return when (constantReference) {
|
|
||||||
ConeBooleanConstantReference.TRUE -> buildConstExpression(null, ConstantValueKind.Boolean, true)
|
|
||||||
ConeBooleanConstantReference.FALSE -> buildConstExpression(null, ConstantValueKind.Boolean, false)
|
|
||||||
ConeConstantReference.NULL -> createConstNull()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitLogicalBinaryOperationContractExpression(
|
|
||||||
binaryLogicExpression: ConeBinaryLogicExpression,
|
|
||||||
data: Nothing?
|
|
||||||
): FirExpression? {
|
|
||||||
val leftExpression = binaryLogicExpression.left.accept(this, data) ?: return null
|
|
||||||
val rightExpression = binaryLogicExpression.right.accept(this, data) ?: return null
|
|
||||||
return buildBinaryLogicExpression {
|
|
||||||
leftOperand = leftExpression
|
|
||||||
rightOperand = rightExpression
|
|
||||||
kind = binaryLogicExpression.kind
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Nothing?): FirExpression? {
|
|
||||||
val explicitReceiver = logicalNot.arg.accept(this, data) ?: return null
|
|
||||||
return buildFunctionCall {
|
|
||||||
calleeReference = buildSimpleNamedReference { name = OperatorNameConventions.NOT }
|
|
||||||
this.explicitReceiver = explicitReceiver
|
|
||||||
origin = FirFunctionCallOrigin.Operator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitIsInstancePredicate(isInstancePredicate: ConeIsInstancePredicate, data: Nothing?): FirExpression? {
|
|
||||||
val argument = isInstancePredicate.arg.accept(this@ConeConditionalEffectToFirVisitor, data) ?: return null
|
|
||||||
return buildTypeOperatorCall {
|
|
||||||
argumentList = buildUnaryArgumentList(argument)
|
|
||||||
operation = if (isInstancePredicate.isNegated) {
|
|
||||||
FirOperation.NOT_IS
|
|
||||||
} else {
|
|
||||||
FirOperation.IS
|
|
||||||
}
|
|
||||||
conversionTypeRef = buildResolvedTypeRef { type = substitutor.substituteOrSelf(isInstancePredicate.type) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Nothing?): FirExpression? {
|
|
||||||
val argument = isNullPredicate.arg.accept(this, data) ?: return null
|
|
||||||
return buildEqualityOperatorCall {
|
|
||||||
operation = if (isNullPredicate.isNegated) {
|
|
||||||
FirOperation.NOT_EQ
|
|
||||||
} else {
|
|
||||||
FirOperation.EQ
|
|
||||||
}
|
|
||||||
argumentList = buildBinaryArgumentList(argument, createConstNull())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitValueParameterReference(valueParameterReference: ConeValueParameterReference, data: Nothing?): FirExpression? {
|
|
||||||
return valueParametersMapping[valueParameterReference.parameterIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createConstNull(): FirConstExpression<*> = buildConstExpression(null, ConstantValueKind.Null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ConeConditionalEffectDeclaration.buildContractFir(
|
|
||||||
argumentMapping: Map<Int, FirExpression>,
|
|
||||||
substitutor: ConeSubstitutor
|
|
||||||
): FirExpression? {
|
|
||||||
return condition.accept(ConeConditionalEffectToFirVisitor(argumentMapping, substitutor), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createArgumentsMapping(qualifiedAccess: FirQualifiedAccess): Map<Int, FirExpression>? {
|
|
||||||
val argumentsMapping = mutableMapOf<Int, FirExpression>()
|
|
||||||
qualifiedAccess.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
|
||||||
?: qualifiedAccess.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
|
||||||
when (qualifiedAccess) {
|
|
||||||
is FirFunctionCall -> {
|
|
||||||
val function = qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null
|
|
||||||
val parameterToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter to index }.toMap()
|
|
||||||
val callArgumentMapping = qualifiedAccess.resolvedArgumentMapping ?: return null
|
|
||||||
for (argument in qualifiedAccess.arguments) {
|
|
||||||
argumentsMapping[parameterToIndex.getValue(callArgumentMapping.getValue(argument))] = argument.unwrapArgument()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is FirVariableAssignment -> {
|
|
||||||
argumentsMapping[0] = qualifiedAccess.rValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return argumentsMapping
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
|||||||
originalVariable: DataFlowVariable,
|
originalVariable: DataFlowVariable,
|
||||||
newVariable: DataFlowVariable,
|
newVariable: DataFlowVariable,
|
||||||
shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(),
|
shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(),
|
||||||
filter: (Implication) -> Boolean = { true },
|
|
||||||
transform: (Implication) -> Implication? = { it },
|
transform: (Implication) -> Implication? = { it },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -237,12 +237,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
|||||||
originalVariable: DataFlowVariable,
|
originalVariable: DataFlowVariable,
|
||||||
newVariable: DataFlowVariable,
|
newVariable: DataFlowVariable,
|
||||||
shouldRemoveOriginalStatements: Boolean,
|
shouldRemoveOriginalStatements: Boolean,
|
||||||
filter: (Implication) -> Boolean,
|
|
||||||
transform: (Implication) -> Implication?
|
transform: (Implication) -> Implication?
|
||||||
) {
|
) {
|
||||||
with(flow) {
|
with(flow) {
|
||||||
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
|
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
|
||||||
val newStatements = statements.filter(filter).mapNotNull {
|
val newStatements = statements.mapNotNull {
|
||||||
val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
|
val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
|
||||||
transform(newStatement)
|
transform(newStatement)
|
||||||
}.toPersistentList()
|
}.toPersistentList()
|
||||||
|
|||||||
@@ -761,19 +761,6 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
|
|
||||||
EnterNodeMarker {
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
|
||||||
return visitor.visitEnterContractNode(this, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
|
|
||||||
ExitNodeMarker {
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
|
||||||
return visitor.visitExitContractNode(this, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
|
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
|
||||||
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.visitEnterSafeCallNode(this, data)
|
return visitor.visitEnterSafeCallNode(this, data)
|
||||||
|
|||||||
@@ -92,9 +92,6 @@ fun CFGNode<*>.render(): String =
|
|||||||
is AnnotationEnterNode -> "Enter annotation"
|
is AnnotationEnterNode -> "Enter annotation"
|
||||||
is AnnotationExitNode -> "Exit annotation"
|
is AnnotationExitNode -> "Exit annotation"
|
||||||
|
|
||||||
is EnterContractNode -> "Enter contract"
|
|
||||||
is ExitContractNode -> "Exit contract"
|
|
||||||
|
|
||||||
is EnterSafeCallNode -> "Enter safe call"
|
is EnterSafeCallNode -> "Enter safe call"
|
||||||
is ExitSafeCallNode -> "Exit safe call"
|
is ExitSafeCallNode -> "Exit safe call"
|
||||||
|
|
||||||
|
|||||||
-8
@@ -339,14 +339,6 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
|||||||
return visitNode(node, data)
|
return visitNode(node, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun visitEnterContractNode(node: EnterContractNode, data: D): R {
|
|
||||||
return visitNode(node, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun visitExitContractNode(node: ExitContractNode, data: D): R {
|
|
||||||
return visitNode(node, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: D): R {
|
open fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: D): R {
|
||||||
return visitNode(node, data)
|
return visitNode(node, data)
|
||||||
}
|
}
|
||||||
|
|||||||
-16
@@ -273,14 +273,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
|||||||
visitNode(node)
|
visitNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun visitEnterContractNode(node: EnterContractNode) {
|
|
||||||
visitNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun visitExitContractNode(node: ExitContractNode) {
|
|
||||||
visitNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
|
open fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
|
||||||
visitNode(node)
|
visitNode(node)
|
||||||
}
|
}
|
||||||
@@ -570,14 +562,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
|||||||
visitVariableAssignmentNode(node)
|
visitVariableAssignmentNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
final override fun visitEnterContractNode(node: EnterContractNode, data: Nothing?) {
|
|
||||||
visitEnterContractNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
final override fun visitExitContractNode(node: ExitContractNode, data: Nothing?) {
|
|
||||||
visitExitContractNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
final override fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: Nothing?) {
|
final override fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: Nothing?) {
|
||||||
visitEnterSafeCallNode(node)
|
visitEnterSafeCallNode(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.contracts.description.*
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.LogicOperationKind
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||||
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
|
|
||||||
|
fun ConeConstantReference.toOperation(): Operation? = when (this) {
|
||||||
|
ConeConstantReference.WILDCARD -> null
|
||||||
|
ConeConstantReference.NULL -> Operation.EqNull
|
||||||
|
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
|
||||||
|
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
|
||||||
|
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
|
||||||
|
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns `null` if the statement is always false.
|
||||||
|
fun <F : Flow> LogicSystem<F>.approveContractStatement(
|
||||||
|
flow: F,
|
||||||
|
statement: ConeBooleanExpression,
|
||||||
|
arguments: Array<out DataFlowVariable?>, // 0 = receiver (null if doesn't exist)
|
||||||
|
substitutor: ConeSubstitutor?,
|
||||||
|
removeApprovedOrImpossible: Boolean = false,
|
||||||
|
): TypeStatements? {
|
||||||
|
fun OperationStatement.approve() =
|
||||||
|
approveOperationStatement(flow, this, removeApprovedOrImpossible)
|
||||||
|
|
||||||
|
fun DataFlowVariable.processEqNull(isEq: Boolean): TypeStatements =
|
||||||
|
OperationStatement(this, if (isEq) Operation.EqNull else Operation.NotEqNull).approve()
|
||||||
|
|
||||||
|
fun ConeBooleanExpression.visit(inverted: Boolean): TypeStatements? = when (this) {
|
||||||
|
is ConeBooleanConstantReference ->
|
||||||
|
if (inverted == (this == ConeBooleanConstantReference.TRUE)) null else mapOf()
|
||||||
|
is ConeLogicalNot -> arg.visit(inverted = !inverted)
|
||||||
|
is ConeIsInstancePredicate ->
|
||||||
|
arguments.getOrNull(arg.parameterIndex + 1)?.let {
|
||||||
|
val isType = inverted == isNegated
|
||||||
|
val substitutedType = substitutor?.substituteOrNull(type) ?: type
|
||||||
|
when {
|
||||||
|
substitutedType.isAny -> it.processEqNull(!isType)
|
||||||
|
substitutedType.isNullableNothing -> it.processEqNull(isType)
|
||||||
|
else -> {
|
||||||
|
// x is (T & Any) => x != null
|
||||||
|
// TODO? (KT-22996) x !is T? => x != null: change `&&` to `==`
|
||||||
|
val fromNullability = if (isType && !type.canBeNull) it.processEqNull(false) else mapOf()
|
||||||
|
if (isType && it is RealVariable) {
|
||||||
|
andForTypeStatements(fromNullability, mapOf(it to (it typeEq substitutedType)))
|
||||||
|
} else {
|
||||||
|
fromNullability
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: mapOf()
|
||||||
|
is ConeIsNullPredicate ->
|
||||||
|
arguments.getOrNull(arg.parameterIndex + 1)?.processEqNull(inverted == isNegated) ?: mapOf()
|
||||||
|
is ConeBooleanValueParameterReference ->
|
||||||
|
arguments.getOrNull(parameterIndex + 1)?.let {
|
||||||
|
OperationStatement(it, if (inverted) Operation.EqFalse else Operation.EqTrue).approve()
|
||||||
|
} ?: mapOf()
|
||||||
|
is ConeBinaryLogicExpression -> {
|
||||||
|
val a = left.visit(inverted)
|
||||||
|
val b = right.visit(inverted)
|
||||||
|
val isAnd = inverted != (kind == LogicOperationKind.AND)
|
||||||
|
when {
|
||||||
|
a == null -> b.takeIf { !isAnd } // false || b == b; false && b = false
|
||||||
|
b == null -> a.takeIf { !isAnd } // a || false == a; a && false = false
|
||||||
|
isAnd -> andForTypeStatements(a, b)
|
||||||
|
else -> orForTypeStatements(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> mapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
return statement.visit(inverted = false)
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa
|
|||||||
|
|
||||||
import kotlinx.collections.immutable.PersistentMap
|
import kotlinx.collections.immutable.PersistentMap
|
||||||
import org.jetbrains.kotlin.fir.FirElement
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
|
|
||||||
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||||
@@ -16,12 +14,8 @@ import org.jetbrains.kotlin.fir.references.FirThisReference
|
|||||||
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
|
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
|
||||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.fir.types.ConeTypeContext
|
|
||||||
import org.jetbrains.kotlin.fir.types.coneType
|
|
||||||
import org.jetbrains.kotlin.name.StandardClassIds
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@@ -58,9 +52,9 @@ internal inline fun <K, V> PersistentMap<K, V>.put(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Set<ConeKotlinType>?.intersectWith(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
|
fun TypeStatement?.smartCastedType(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
|
||||||
if (!isNullOrEmpty()) {
|
if (this != null && exactType.isNotEmpty()) {
|
||||||
context.intersectTypes(toMutableList().also { it += originalType })
|
context.intersectTypes(exactType.toMutableList().also { it += originalType })
|
||||||
} else {
|
} else {
|
||||||
originalType
|
originalType
|
||||||
}
|
}
|
||||||
@@ -74,19 +68,6 @@ fun FirOperation.isEq(): Boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FirFunctionCall.isBooleanNot(): Boolean {
|
|
||||||
val symbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false
|
|
||||||
return symbol.callableId == StandardClassIds.Callables.not
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ConeConstantReference.toOperation(): Operation = when (this) {
|
|
||||||
ConeConstantReference.NULL -> Operation.EqNull
|
|
||||||
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
|
|
||||||
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
|
|
||||||
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
|
|
||||||
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
@DfaInternals
|
@DfaInternals
|
||||||
val FirExpression.coneType: ConeKotlinType
|
val FirExpression.coneType: ConeKotlinType
|
||||||
get() = typeRef.coneType
|
get() = typeRef.coneType
|
||||||
|
|||||||
Reference in New Issue
Block a user