Compare commits

...

13 Commits

Author SHA1 Message Date
Mikhail Glukhikh
af3ac1b02b [FIR] Run diagnostics in modularized test 2020-05-19 12:50:40 +03:00
Mikhail Glukhikh
340b1163a3 Modularized test: set root prefixes to 'C:/' 2020-05-19 11:35:27 +03:00
Mikhail Glukhikh
4b13909f92 Dump FIR off 2020-05-19 11:35:27 +03:00
Mikhail Glukhikh
5e5d7632b7 FIR: do not allow unresolved types in smoke diagnostics test 2020-05-19 11:35:27 +03:00
Mikhail Glukhikh
61d09d9222 Change resolve modularized test to match M.G. configuration 2020-05-19 11:35:26 +03:00
Mikhail Glukhikh
310372f32c Change FIR tmp directory 2020-05-19 11:35:26 +03:00
vovasq
947ef3ff24 [Leaking_This]: resolve of 'this' passing and local fun support added 2020-05-19 11:34:53 +03:00
vovasq
a7fc2a2219 [Leaking_This]: refactoring and traverse on cfg on second time added 2020-05-19 11:34:52 +03:00
vovasq
250a569d82 [Leaking_This]: switched to forward traverse, remove infinite looping 2020-05-19 11:34:52 +03:00
vovasq
c656fb490c [Leaking_This]: experiments with lambda and safeCall added 2020-05-19 11:34:52 +03:00
vovasq
114ba52b91 [Leaking_This]: some bugfixes in collecting initContext 2020-05-19 11:34:52 +03:00
vovasq
1d3175cbe9 [Leaking_This]: fixed duplication reporting on multiple cfg branches 2020-05-19 11:34:51 +03:00
vovasq
2243fcaecd [Leaking_This]: cherry picked from leaking_this_backup 2020-05-19 11:34:51 +03:00
40 changed files with 2015 additions and 21 deletions

View File

@@ -0,0 +1,182 @@
digraph inlineAndLambdas_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
49 [label="Enter function inLineCatch" style="filled" fillcolor=red];
51 [label="Access variable R|/A.p2|"];
52 [label="Access variable R|kotlin/String.length|"];
53 [label="Function call: R|<local>/f|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Int|>|()"];
50 [label="Exit function inLineCatch" style="filled" fillcolor=red];
}
49 -> {51};
51 -> {52};
52 -> {53};
53 -> {50};
subgraph cluster_2 {
color=red
54 [label="Enter function notInline" style="filled" fillcolor=red];
56 [label="Function call: this@R|/A|.R|/A.memberCall1|()"];
57 [label="Function call: R|<local>/f|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Int|>|()"];
58 [label="Function call: this@R|/A|.R|/A.memberCall1|()"];
55 [label="Exit function notInline" style="filled" fillcolor=red];
}
54 -> {56};
56 -> {57};
57 -> {58};
58 -> {55};
subgraph cluster_3 {
color=red
59 [label="Enter function memberCall1" style="filled" fillcolor=red];
60 [label="Exit function memberCall1" style="filled" fillcolor=red];
}
59 -> {60};
subgraph cluster_4 {
color=red
61 [label="Enter class A" style="filled" fillcolor=red];
subgraph cluster_5 {
color=blue
15 [label="Enter function getter" style="filled" fillcolor=red];
16 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_6 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_7 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_8 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Const: String(asa)"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_9 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_10 {
color=blue
17 [label="Enter property" style="filled" fillcolor=red];
19 [label="Postponed enter to lambda"];
subgraph cluster_11 {
color=blue
12 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
14 [label="Const: Int(3)"];
13 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
20 [label="Postponed exit from lambda"];
21 [label="Function call: this@R|/A|.R|/A.notInline|(...)"];
18 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_12 {
color=blue
33 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_13 {
color=blue
35 [label="Enter block"];
36 [label="Postponed enter to lambda"];
subgraph cluster_14 {
color=blue
22 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
24 [label="Access variable R|/A.p1|"];
25 [label="Access variable R|kotlin/String.length|"];
23 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
37 [label="Postponed exit from lambda"];
38 [label="Function call: this@R|/A|.R|/A.inLineCatch|(...)"];
39 [label="Postponed enter to lambda"];
subgraph cluster_15 {
color=blue
26 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
28 [label="Const: Int(1)"];
27 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
40 [label="Postponed exit from lambda"];
41 [label="Function call: this@R|/A|.R|/A.notInline|(...)"];
42 [label="Variable declaration: lval local1: R|kotlin/Unit|"];
43 [label="Const: String(dsadsa)"];
44 [label="Assignment: R|/A.p2|"];
45 [label="Postponed enter to lambda"];
subgraph cluster_16 {
color=blue
29 [label="Enter function anonymousFunction" style="filled" fillcolor=red];
31 [label="Access variable R|/A.p1|"];
32 [label="Access variable R|kotlin/String.length|"];
30 [label="Exit function anonymousFunction" style="filled" fillcolor=red];
}
46 [label="Postponed exit from lambda"];
47 [label="Function call: this@R|/A|.R|/A.notInline|(...)"];
48 [label="Exit block"];
}
34 [label="Exit init block" style="filled" fillcolor=red];
}
62 [label="Exit class A" style="filled" fillcolor=red];
}
61 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {17} [color=green];
8 -> {9};
17 -> {19};
18 -> {33} [color=green];
19 -> {20};
19 -> {12} [color=red];
20 -> {21};
21 -> {18};
12 -> {14};
14 -> {13};
15 -> {16};
33 -> {35};
34 -> {62} [color=green];
35 -> {36};
36 -> {22};
36 -> {37} [color=red];
37 -> {38};
38 -> {39};
39 -> {40};
39 -> {26} [color=red];
40 -> {41};
41 -> {42};
42 -> {43};
43 -> {44};
44 -> {45};
45 -> {46};
45 -> {29} [color=red];
46 -> {47};
47 -> {48};
48 -> {34};
22 -> {23 24};
23 -> {22};
23 -> {37} [color=green];
24 -> {25};
25 -> {23};
26 -> {28};
28 -> {27};
29 -> {31};
31 -> {32};
32 -> {30};
}

View File

@@ -0,0 +1,30 @@
// !DUMP_CFG
class A {
val p1 = "asa"
val p2: String
val p3 = notInline { 3 }
init {
inLineCatch { p1.length }
val local1 = notInline { 1 }
p2 = "dsadsa"
notInline { p1.length }
}
private inline fun inLineCatch(f: () -> Int){
<!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p2<!>.length
f()
}
// todo
private fun notInline(f: () -> Int){
memberCall1()
f()
memberCall1()
}
private fun memberCall1(){
}
}

View File

@@ -0,0 +1,49 @@
FILE: inlineAndLambdas.kt
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = String(asa)
public get(): R|kotlin/String|
public final val p2: R|kotlin/String|
public get(): R|kotlin/String|
public final val p3: R|kotlin/Unit| = this@R|/A|.R|/A.notInline|(<L> = notInline@fun <anonymous>(): R|kotlin/Int| {
^ Int(3)
}
)
public get(): R|kotlin/Unit|
init {
this@R|/A|.R|/A.inLineCatch|(<L> = inLineCatch@fun <anonymous>(): R|kotlin/Int| <kind=UNKNOWN> {
^ this@R|/A|.R|/A.p1|.R|kotlin/String.length|
}
)
lval local1: R|kotlin/Unit| = this@R|/A|.R|/A.notInline|(<L> = notInline@fun <anonymous>(): R|kotlin/Int| {
^ Int(1)
}
)
this@R|/A|.R|/A.p2| = String(dsadsa)
this@R|/A|.R|/A.notInline|(<L> = notInline@fun <anonymous>(): R|kotlin/Int| {
^ this@R|/A|.R|/A.p1|.R|kotlin/String.length|
}
)
}
private final inline fun inLineCatch(f: R|() -> kotlin/Int|): R|kotlin/Unit| {
this@R|/A|.R|/A.p2|.R|kotlin/String.length|
R|<local>/f|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Int|>|()
}
private final fun notInline(f: R|() -> kotlin/Int|): R|kotlin/Unit| {
this@R|/A|.R|/A.memberCall1|()
R|<local>/f|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Int|>|()
this@R|/A|.R|/A.memberCall1|()
}
private final fun memberCall1(): R|kotlin/Unit| {
}
}

View File

@@ -0,0 +1,201 @@
digraph manyInitSections_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
31 [label="Enter function checkConst" style="filled" fillcolor=red];
33 [label="Access variable R|<local>/s|"];
34 [label="Variable declaration: lval x: R|kotlin/String|"];
35 [label="Const: String(empty)"];
36 [label="Variable declaration: lvar y: R|kotlin/String|"];
subgraph cluster_2 {
color=blue
37 [label="Enter when"];
subgraph cluster_3 {
color=blue
39 [label="Enter when branch condition "];
40 [label="Access variable R|<local>/x|"];
41 [label="Const: String(const1)"];
42 [label="Operator =="];
43 [label="Exit when branch condition"];
}
50 [label="Synthetic else branch"];
44 [label="Enter when branch result"];
subgraph cluster_4 {
color=blue
45 [label="Enter block"];
46 [label="Const: String(yes)"];
47 [label="Assignment: R|<local>/y|"];
48 [label="Exit block"];
}
49 [label="Exit when branch result"];
38 [label="Exit when"];
}
subgraph cluster_5 {
color=blue
51 [label="Enter when"];
subgraph cluster_6 {
color=blue
53 [label="Enter when branch condition "];
54 [label="Access variable R|<local>/x|"];
55 [label="Const: String(const2)"];
56 [label="Operator =="];
57 [label="Exit when branch condition"];
}
64 [label="Synthetic else branch"];
58 [label="Enter when branch result"];
subgraph cluster_7 {
color=blue
59 [label="Enter block"];
60 [label="Const: String(no)"];
61 [label="Assignment: R|<local>/y|"];
62 [label="Exit block"];
}
63 [label="Exit when branch result"];
52 [label="Exit when"];
}
65 [label="Access variable R|<local>/y|"];
66 [label="Jump: ^checkConst R|<local>/y|"];
67 [label="Stub" style="filled" fillcolor=gray];
32 [label="Exit function checkConst" style="filled" fillcolor=red];
}
31 -> {33};
33 -> {34};
34 -> {35};
35 -> {36};
36 -> {37};
37 -> {39};
38 -> {51};
39 -> {40};
40 -> {41};
41 -> {42};
42 -> {43};
43 -> {44 50};
44 -> {45};
45 -> {46};
46 -> {47};
47 -> {48};
48 -> {49};
49 -> {38};
50 -> {38};
51 -> {53};
52 -> {65};
53 -> {54};
54 -> {55};
55 -> {56};
56 -> {57};
57 -> {58 64};
58 -> {59};
59 -> {60};
60 -> {61};
61 -> {62};
62 -> {63};
63 -> {52};
64 -> {52};
65 -> {66};
66 -> {32};
66 -> {67} [style=dotted];
67 -> {32} [style=dotted];
subgraph cluster_8 {
color=red
68 [label="Enter class A" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
12 [label="Enter function getter" style="filled" fillcolor=red];
13 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_10 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_11 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_12 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Const: String(kek)"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_13 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_14 {
color=blue
14 [label="Enter property" style="filled" fillcolor=red];
15 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_15 {
color=blue
16 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_16 {
color=blue
18 [label="Enter block"];
19 [label="Access variable R|/A.p1|"];
20 [label="Access variable R|kotlin/String.length|"];
21 [label="Access variable R|/A.p3|"];
22 [label="Access variable R|kotlin/String.length|"];
23 [label="Assignment: R|/A.p2|"];
24 [label="Exit block"];
}
17 [label="Exit init block" style="filled" fillcolor=red];
}
subgraph cluster_17 {
color=blue
25 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_18 {
color=blue
27 [label="Enter block"];
28 [label="Const: String(asa)"];
29 [label="Assignment: R|/A.p3|"];
30 [label="Exit block"];
}
26 [label="Exit init block" style="filled" fillcolor=red];
}
69 [label="Exit class A" style="filled" fillcolor=red];
}
68 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {14} [color=green];
8 -> {9};
14 -> {15};
15 -> {16} [color=green];
12 -> {13};
16 -> {18};
17 -> {25} [color=green];
18 -> {19};
19 -> {20};
20 -> {21};
21 -> {22};
22 -> {23};
23 -> {24};
24 -> {17};
25 -> {27};
26 -> {69} [color=green];
27 -> {28};
28 -> {29};
29 -> {30};
30 -> {26};
}

View File

@@ -0,0 +1,31 @@
// !DUMP_CFG
class A {
val p1 = "kek"
val p2: Int
val p3: String
init {
p1.length
p2 = <!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p3<!>.length
}
init {
p3 = "asa"
}
fun checkConst(s: String) {
val x = s
var y = "empty"
if (x == "const1") {
y = "yes"
}
if (x == "const2") {
y = "no"
}
return y
}
}

View File

@@ -0,0 +1,43 @@
FILE: manyInitSections.kt
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = String(kek)
public get(): R|kotlin/String|
public final val p2: R|kotlin/Int|
public get(): R|kotlin/Int|
public final val p3: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/A|.R|/A.p1|.R|kotlin/String.length|
this@R|/A|.R|/A.p2| = this@R|/A|.R|/A.p3|.R|kotlin/String.length|
}
init {
this@R|/A|.R|/A.p3| = String(asa)
}
public final fun checkConst(s: R|kotlin/String|): R|kotlin/Unit| {
lval x: R|kotlin/String| = R|<local>/s|
lvar y: R|kotlin/String| = String(empty)
when () {
==(R|<local>/x|, String(const1)) -> {
R|<local>/y| = String(yes)
}
}
when () {
==(R|<local>/x|, String(const2)) -> {
R|<local>/y| = String(no)
}
}
^checkConst R|<local>/y|
}
}

View File

@@ -0,0 +1,32 @@
class C {
val p1 = "kek"
val p2: Int
val p3: String
val p4: String
init {
memberCall1()
p3 = "ura"
p2 = p3.length
p4 = "intiniada"
}
fun memberCall1() {
memberCall2()
<!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p3<!>.length
}
fun memberCall2() {
fun localCallable(){
<!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p4<!>.length
}
localCallable()
// TODO memberCall3()
}
fun memberCall3() {
memberCall2()
}
}

View File

@@ -0,0 +1,43 @@
FILE: memberCallInInitPos.kt
public final class C : R|kotlin/Any| {
public constructor(): R|C| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = String(kek)
public get(): R|kotlin/String|
public final val p2: R|kotlin/Int|
public get(): R|kotlin/Int|
public final val p3: R|kotlin/String|
public get(): R|kotlin/String|
public final val p4: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/C|.R|/C.memberCall1|()
this@R|/C|.R|/C.p3| = String(ura)
this@R|/C|.R|/C.p2| = this@R|/C|.R|/C.p3|.R|kotlin/String.length|
this@R|/C|.R|/C.p4| = String(intiniada)
}
public final fun memberCall1(): R|kotlin/Unit| {
this@R|/C|.R|/C.memberCall2|()
this@R|/C|.R|/C.p3|.R|kotlin/String.length|
}
public final fun memberCall2(): R|kotlin/Unit| {
local final fun localCallable(): R|kotlin/Unit| {
this@R|/C|.R|/C.p4|.R|kotlin/String.length|
}
R|<local>/localCallable|()
}
public final fun memberCall3(): R|kotlin/Unit| {
this@R|/C|.R|/C.memberCall2|()
}
}

View File

@@ -0,0 +1,158 @@
digraph propertyInit_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
18 [label="Enter function memberCall1" style="filled" fillcolor=red];
20 [label="Function call: this@R|/D|.R|/D.memberCall2|()"];
21 [label="Jump: ^memberCall1 this@R|/D|.R|/D.memberCall2|()"];
22 [label="Stub" style="filled" fillcolor=gray];
19 [label="Exit function memberCall1" style="filled" fillcolor=red];
}
18 -> {20};
20 -> {21};
21 -> {19};
21 -> {22} [style=dotted];
22 -> {19} [style=dotted];
subgraph cluster_2 {
color=red
23 [label="Enter function memberCall2" style="filled" fillcolor=red];
subgraph cluster_3 {
color=blue
25 [label="Enter when"];
subgraph cluster_4 {
color=blue
27 [label="Enter when branch condition "];
28 [label="Access variable R|/D.p2|"];
29 [label="Access variable R|kotlin/String.length|"];
30 [label="Const: Int(0)"];
31 [label="Operator !="];
32 [label="Exit when branch condition"];
}
subgraph cluster_5 {
color=blue
42 [label="Enter when branch condition else"];
43 [label="Exit when branch condition"];
}
44 [label="Enter when branch result"];
subgraph cluster_6 {
color=blue
45 [label="Enter block"];
46 [label="Const: String(empty)"];
47 [label="Jump: ^memberCall2 String(empty)"];
48 [label="Stub" style="filled" fillcolor=gray];
49 [label="Exit block" style="filled" fillcolor=gray];
}
50 [label="Exit when branch result" style="filled" fillcolor=gray];
33 [label="Enter when branch result"];
subgraph cluster_7 {
color=blue
34 [label="Enter block"];
35 [label="Access variable R|/D.p2|"];
36 [label="Const: String(sadsa)"];
37 [label="Function call: this@R|/D|.R|/D.p2|.R|kotlin/String.plus|(...)"];
38 [label="Jump: ^memberCall2 this@R|/D|.R|/D.p2|.R|kotlin/String.plus|(String(sadsa))"];
39 [label="Stub" style="filled" fillcolor=gray];
40 [label="Exit block" style="filled" fillcolor=gray];
}
41 [label="Exit when branch result" style="filled" fillcolor=gray];
26 [label="Exit when" style="filled" fillcolor=gray];
}
24 [label="Exit function memberCall2" style="filled" fillcolor=red];
}
23 -> {25};
25 -> {27};
26 -> {24} [style=dotted];
27 -> {28};
28 -> {29};
29 -> {30};
30 -> {31};
31 -> {32};
32 -> {33 42};
33 -> {34};
34 -> {35};
35 -> {36};
36 -> {37};
37 -> {38};
38 -> {24};
38 -> {39} [style=dotted];
39 -> {40} [style=dotted];
40 -> {41} [style=dotted];
41 -> {26} [style=dotted];
42 -> {43};
43 -> {44};
44 -> {45};
45 -> {46};
46 -> {47};
47 -> {24};
47 -> {48} [style=dotted];
48 -> {49} [style=dotted];
49 -> {50} [style=dotted];
50 -> {26} [style=dotted];
subgraph cluster_8 {
color=red
51 [label="Enter class D" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_10 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_11 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Function call: this@R|/D|.R|/D.memberCall1|()"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_12 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_13 {
color=blue
12 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_14 {
color=blue
14 [label="Enter block"];
15 [label="Const: String(dsadsa)"];
16 [label="Assignment: R|/D.p2|"];
17 [label="Exit block"];
}
13 [label="Exit init block" style="filled" fillcolor=red];
}
52 [label="Exit class D" style="filled" fillcolor=red];
}
51 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {12} [color=green];
8 -> {9};
12 -> {14};
13 -> {52} [color=green];
14 -> {15};
15 -> {16};
16 -> {17};
17 -> {13};
}

View File

@@ -0,0 +1,22 @@
// !DUMP_CFG
class D {
val p1 = memberCall1()
val p2: String
init {
p2 = "dsadsa"
}
private fun memberCall1(): String {
return memberCall2()
}
private fun memberCall2(): String {
if ( <!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p2<!>.length != 0){
return p2 + "sadsa"
} else {
return "empty"
}
}
}

View File

@@ -0,0 +1,33 @@
FILE: propertyInit.kt
public final class D : R|kotlin/Any| {
public constructor(): R|D| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = this@R|/D|.R|/D.memberCall1|()
public get(): R|kotlin/String|
public final val p2: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/D|.R|/D.p2| = String(dsadsa)
}
private final fun memberCall1(): R|kotlin/String| {
^memberCall1 this@R|/D|.R|/D.memberCall2|()
}
private final fun memberCall2(): R|kotlin/String| {
when () {
!=(this@R|/D|.R|/D.p2|.R|kotlin/String.length|, Int(0)) -> {
^memberCall2 this@R|/D|.R|/D.p2|.R|kotlin/String.plus|(String(sadsa))
}
else -> {
^memberCall2 String(empty)
}
}
}
}

View File

@@ -0,0 +1,97 @@
digraph simpleInitNeg_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
30 [label="Enter class A" style="filled" fillcolor=red];
subgraph cluster_2 {
color=blue
12 [label="Enter function getter" style="filled" fillcolor=red];
13 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_3 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_4 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_5 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Const: String(kek)"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_6 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_7 {
color=blue
14 [label="Enter property" style="filled" fillcolor=red];
15 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_8 {
color=blue
16 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
18 [label="Enter block"];
19 [label="Const: Int(1)"];
20 [label="Variable declaration: lval localP1: R|kotlin/Int|"];
21 [label="Access variable R|/A.p1|"];
22 [label="Access variable R|kotlin/String.length|"];
23 [label="Const: String(asa)"];
24 [label="Function call: R|java/lang/String.String|(...)"];
25 [label="Assignment: R|/A.p3|"];
26 [label="Access variable R|/A.p3|"];
27 [label="Access variable R|kotlin/CharSequence.length|"];
28 [label="Assignment: R|/A.p2|"];
29 [label="Exit block"];
}
17 [label="Exit init block" style="filled" fillcolor=red];
}
31 [label="Exit class A" style="filled" fillcolor=red];
}
30 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {14} [color=green];
8 -> {9};
14 -> {15};
15 -> {16} [color=green];
12 -> {13};
16 -> {18};
17 -> {31} [color=green];
18 -> {19};
19 -> {20};
20 -> {21};
21 -> {22};
22 -> {23};
23 -> {24};
24 -> {25};
25 -> {26};
26 -> {27};
27 -> {28};
28 -> {29};
29 -> {17};
}

View File

@@ -0,0 +1,15 @@
// PROBLEM: none
// !DUMP_CFG
class A {
val p1 = "kek"
val p2: Int
val p3: String
init {
val localP1 = 1
p1.length
p3 = String("asa")
p2 = p3.length
}
}

View File

@@ -0,0 +1,23 @@
FILE: simpleInitNeg.kt
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = String(kek)
public get(): R|kotlin/String|
public final val p2: R|kotlin/Int|
public get(): R|kotlin/Int|
public final val p3: R|kotlin/String|
public get(): R|kotlin/String|
init {
lval localP1: R|kotlin/Int| = Int(1)
this@R|/A|.R|/A.p1|.R|kotlin/String.length|
this@R|/A|.R|/A.p3| = R|java/lang/String.String|(String(asa))
this@R|/A|.R|/A.p2| = this@R|/A|.R|/A.p3|.R|kotlin/CharSequence.length|
}
}

View File

@@ -0,0 +1,97 @@
digraph simpleInitPos_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
30 [label="Enter class A" style="filled" fillcolor=red];
subgraph cluster_2 {
color=blue
12 [label="Enter function getter" style="filled" fillcolor=red];
13 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_3 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_4 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_5 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Const: String(kek)"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_6 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_7 {
color=blue
14 [label="Enter property" style="filled" fillcolor=red];
15 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_8 {
color=blue
16 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_9 {
color=blue
18 [label="Enter block"];
19 [label="Access variable R|/A.p1|"];
20 [label="Access variable R|kotlin/String.length|"];
21 [label="Access variable R|/A.p3|"];
22 [label="Access variable R|kotlin/String.length|"];
23 [label="Access variable R|/A.p3|"];
24 [label="Access variable R|kotlin/String.length|"];
25 [label="Assignment: R|/A.p2|"];
26 [label="Const: String(asa)"];
27 [label="Function call: R|java/lang/String.String|(...)"];
28 [label="Assignment: R|/A.p3|"];
29 [label="Exit block"];
}
17 [label="Exit init block" style="filled" fillcolor=red];
}
31 [label="Exit class A" style="filled" fillcolor=red];
}
30 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {14} [color=green];
8 -> {9};
14 -> {15};
15 -> {16} [color=green];
12 -> {13};
16 -> {18};
17 -> {31} [color=green];
18 -> {19};
19 -> {20};
20 -> {21};
21 -> {22};
22 -> {23};
23 -> {24};
24 -> {25};
25 -> {26};
26 -> {27};
27 -> {28};
28 -> {29};
29 -> {17};
}

View File

@@ -0,0 +1,14 @@
// !DUMP_CFG
class A {
val p1 = "kek"
val p2: Int
val p3: String
init {
p1.length
<!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p3<!>.length
p2 = p3.length
p3 = String("asa")
}
}

View File

@@ -0,0 +1,23 @@
FILE: simpleInitPos.kt
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String| = String(kek)
public get(): R|kotlin/String|
public final val p2: R|kotlin/Int|
public get(): R|kotlin/Int|
public final val p3: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/A|.R|/A.p1|.R|kotlin/String.length|
this@R|/A|.R|/A.p3|.R|kotlin/String.length|
this@R|/A|.R|/A.p2| = this@R|/A|.R|/A.p3|.R|kotlin/String.length|
this@R|/A|.R|/A.p3| = R|java/lang/String.String|(String(asa))
}
}

View File

@@ -0,0 +1,84 @@
digraph simplePropertyInit_kt {
graph [nodesep=3]
node [shape=box penwidth=2]
edge [penwidth=2]
subgraph cluster_0 {
color=red
0 [label="Enter function <init>" style="filled" fillcolor=red];
2 [label="Delegated constructor call: super<R|kotlin/Any|>()"];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {2};
2 -> {1};
subgraph cluster_1 {
color=red
18 [label="Enter function memberCall1" style="filled" fillcolor=red];
20 [label="Access variable R|/D.p2|"];
21 [label="Access variable R|kotlin/String.length|"];
22 [label="Jump: ^memberCall1 this@R|/D|.R|/D.p2|.R|kotlin/String.length|"];
23 [label="Stub" style="filled" fillcolor=gray];
19 [label="Exit function memberCall1" style="filled" fillcolor=red];
}
18 -> {20};
20 -> {21};
21 -> {22};
22 -> {19};
22 -> {23} [style=dotted];
23 -> {19} [style=dotted];
subgraph cluster_2 {
color=red
24 [label="Enter class D" style="filled" fillcolor=red];
subgraph cluster_3 {
color=blue
8 [label="Enter function getter" style="filled" fillcolor=red];
9 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_4 {
color=blue
3 [label="Enter function getter" style="filled" fillcolor=red];
4 [label="Exit function getter" style="filled" fillcolor=red];
}
subgraph cluster_5 {
color=blue
5 [label="Enter property" style="filled" fillcolor=red];
7 [label="Function call: this@R|/D|.R|/D.memberCall1|()"];
6 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_6 {
color=blue
10 [label="Enter property" style="filled" fillcolor=red];
11 [label="Exit property" style="filled" fillcolor=red];
}
subgraph cluster_7 {
color=blue
12 [label="Enter init block" style="filled" fillcolor=red];
subgraph cluster_8 {
color=blue
14 [label="Enter block"];
15 [label="Const: String(dsadsa)"];
16 [label="Assignment: R|/D.p2|"];
17 [label="Exit block"];
}
13 [label="Exit init block" style="filled" fillcolor=red];
}
25 [label="Exit class D" style="filled" fillcolor=red];
}
24 -> {5} [color=green];
5 -> {7};
6 -> {10} [color=green];
7 -> {6};
3 -> {4};
10 -> {11};
11 -> {12} [color=green];
8 -> {9};
12 -> {14};
13 -> {25} [color=green];
14 -> {15};
15 -> {16};
16 -> {17};
17 -> {13};
}

View File

@@ -0,0 +1,14 @@
// !DUMP_CFG
class D {
val p1 = memberCall1()
val p2: String
init {
p2 = "dsadsa"
}
private fun memberCall1(): Int {
return <!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p2<!>.length
}
}

View File

@@ -0,0 +1,21 @@
FILE: simplePropertyInit.kt
public final class D : R|kotlin/Any| {
public constructor(): R|D| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/Int| = this@R|/D|.R|/D.memberCall1|()
public get(): R|kotlin/Int|
public final val p2: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/D|.R|/D.p2| = String(dsadsa)
}
private final fun memberCall1(): R|kotlin/Int| {
^memberCall1 this@R|/D|.R|/D.p2|.R|kotlin/String.length|
}
}

View File

@@ -0,0 +1,33 @@
class A{
val p1 : String
val p2 : Int
val p3 : String
init {
p1 = "P1"
memberCall1()
p2 = p1.length
p3 = "danklds"
memberCall2()
}
fun memberCall1 (){
B().foreighnCall1(this)
}
fun memberCall2(){
B().foreighnCall2(this.p3)
}
}
class B{
fun foreighnCall1(a: A){
a.p1.length
a.<!POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR!>p3<!>.length
}
fun foreighnCall2(s: String){
s.length
}
}

View File

@@ -0,0 +1,47 @@
FILE: thisPassing.kt
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final val p1: R|kotlin/String|
public get(): R|kotlin/String|
public final val p2: R|kotlin/Int|
public get(): R|kotlin/Int|
public final val p3: R|kotlin/String|
public get(): R|kotlin/String|
init {
this@R|/A|.R|/A.p1| = String(P1)
this@R|/A|.R|/A.memberCall1|()
this@R|/A|.R|/A.p2| = this@R|/A|.R|/A.p1|.R|kotlin/String.length|
this@R|/A|.R|/A.p3| = String(danklds)
this@R|/A|.R|/A.memberCall2|()
}
public final fun memberCall1(): R|kotlin/Unit| {
R|/B.B|().R|/B.foreighnCall1|(this@R|/A|)
}
public final fun memberCall2(): R|kotlin/Unit| {
R|/B.B|().R|/B.foreighnCall2|(this@R|/A|.R|/A.p3|)
}
}
public final class B : R|kotlin/Any| {
public constructor(): R|B| {
super<R|kotlin/Any|>()
}
public final fun foreighnCall1(a: R|A|): R|kotlin/Unit| {
R|<local>/a|.R|/A.p1|.R|kotlin/String.length|
R|<local>/a|.R|/A.p3|.R|kotlin/String.length|
}
public final fun foreighnCall2(s: R|kotlin/String|): R|kotlin/Unit| {
R|<local>/s|.R|kotlin/String.length|
}
}

View File

@@ -875,6 +875,59 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
public void testValOnAnnotationParameter() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LeakingThis extends AbstractFirDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLeakingThis() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("inlineAndLambdas.kt")
public void testInlineAndLambdas() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/inlineAndLambdas.kt");
}
@TestMetadata("manyInitSections.kt")
public void testManyInitSections() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/manyInitSections.kt");
}
@TestMetadata("memberCallInInitPos.kt")
public void testMemberCallInInitPos() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/memberCallInInitPos.kt");
}
@TestMetadata("propertyInit.kt")
public void testPropertyInit() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/propertyInit.kt");
}
@TestMetadata("simpleInitNeg.kt")
public void testSimpleInitNeg() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simpleInitNeg.kt");
}
@TestMetadata("simpleInitPos.kt")
public void testSimpleInitPos() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simpleInitPos.kt");
}
@TestMetadata("simplePropertyInit.kt")
public void testSimplePropertyInit() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simplePropertyInit.kt");
}
@TestMetadata("thisPassing.kt")
public void testThisPassing() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/thisPassing.kt");
}
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/expresssions")

View File

@@ -875,6 +875,59 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
public void testValOnAnnotationParameter() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LeakingThis extends AbstractFirDiagnosticsWithLightTreeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLeakingThis() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("inlineAndLambdas.kt")
public void testInlineAndLambdas() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/inlineAndLambdas.kt");
}
@TestMetadata("manyInitSections.kt")
public void testManyInitSections() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/manyInitSections.kt");
}
@TestMetadata("memberCallInInitPos.kt")
public void testMemberCallInInitPos() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/memberCallInInitPos.kt");
}
@TestMetadata("propertyInit.kt")
public void testPropertyInit() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/propertyInit.kt");
}
@TestMetadata("simpleInitNeg.kt")
public void testSimpleInitNeg() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simpleInitNeg.kt");
}
@TestMetadata("simpleInitPos.kt")
public void testSimpleInitPos() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simpleInitPos.kt");
}
@TestMetadata("simplePropertyInit.kt")
public void testSimplePropertyInit() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/simplePropertyInit.kt");
}
@TestMetadata("thisPassing.kt")
public void testThisPassing() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/leakingThis/thisPassing.kt");
}
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/expresssions")

View File

@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.cfa
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.ControlFlowGraphVisitor
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
enum class TraverseDirection {
Forward, Backward
@@ -18,7 +15,9 @@ enum class TraverseDirection {
fun <D> ControlFlowGraph.traverse(
direction: TraverseDirection,
visitor: ControlFlowGraphVisitor<*, D>,
data: D
data: D,
acceptPrevious: (CFGNode<*>, CFGNode<*>) -> Boolean = { _: CFGNode<*>, _: CFGNode<*> -> true },
acceptFollowing: (CFGNode<*>, CFGNode<*>) -> Boolean = { _: CFGNode<*>, _: CFGNode<*> -> true }
) {
val visitedNodes = mutableSetOf<CFGNode<*>>()
// used to prevent infinite cycle
@@ -35,7 +34,9 @@ fun <D> ControlFlowGraph.traverse(
TraverseDirection.Forward -> node.previousNodes
TraverseDirection.Backward -> node.followingNodes
}
if (!previousNodes.all { it in visitedNodes }) {
if (node != initialNode
&& previousNodes.all { acceptPrevious(node, it) } && !previousNodes.all { it in visitedNodes }
) {
if (!delayedNodes.add(node)) {
throw IllegalArgumentException("Infinite loop")
}
@@ -43,6 +44,7 @@ fun <D> ControlFlowGraph.traverse(
}
node.accept(visitor, data)
visitedNodes.add(node)
val followingNodes = when (direction) {
TraverseDirection.Forward -> node.followingNodes
@@ -50,7 +52,8 @@ fun <D> ControlFlowGraph.traverse(
}
followingNodes.forEach {
stack.addFirst(it)
if (acceptFollowing(node, it))
stack.addFirst(it)
}
}
}
@@ -59,5 +62,36 @@ fun ControlFlowGraph.traverse(
direction: TraverseDirection,
visitor: ControlFlowGraphVisitorVoid
) {
traverse(direction, visitor, null)
traverse(
direction, visitor, null,
{ node, _ -> node !is StubNode },
{ cur, next ->
cur !is StubNode
&& !(cur is ExitSafeCallNode && next is QualifiedAccessNode)
&& !(cur is FunctionEnterNode && next is FunctionExitNode)
})
}
@OptIn(ExperimentalStdlibApi::class)
fun ControlFlowGraph.traverseForwardWithoutLoops(
visitor: ControlFlowGraphVisitorVoid,
analyze: (CFGNode<*>) -> Unit = { _: CFGNode<*> -> },
loopHandler: (CFGNode<*>) -> Unit = { _: CFGNode<*> -> },
acceptFollowing: (CFGNode<*>, CFGNode<*>) -> Boolean = { _: CFGNode<*>, _: CFGNode<*> -> true }
) {
val visitedNodes = mutableSetOf<CFGNode<*>>()
val stack = ArrayDeque<CFGNode<*>>()
stack.addFirst(enterNode)
while (stack.isNotEmpty()) {
val node = stack.removeFirst()
if (visitedNodes.add(node)) {
analyze(node)
node.accept(visitor, null)
node.followingNodes.forEach {
if (acceptFollowing(node, it))
stack.addFirst(it)
}
}
}
}

View File

@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.leak.LeakingThisChecker
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
object DeclarationCheckers {
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf(
@@ -21,4 +23,7 @@ object DeclarationCheckers {
val CONSTRUCTORS: List<FirDeclarationChecker<FirConstructor>> = MEMBER_DECLARATIONS + listOf(
FirConstructorChecker
)
val CLASS_DECLARATIONS: List<FirDeclarationChecker<FirRegularClass>> = listOf(LeakingThisChecker)
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2010-2020 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.analysis.checkers.declaration.leak
import org.jetbrains.kotlin.fir.analysis.cfa.traverseForwardWithoutLoops
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.name.ClassId
import java.util.*
internal class BaseClassInitContext(private val classDeclaration: FirRegularClass) {
val classId: ClassId
get() = classDeclaration.symbol.classId
val classInitContextNodesMap = mutableMapOf<CFGNode<*>, InitContextNode>()
val isCfgAvailable: Boolean
get() = classDeclaration.controlFlowGraphReference.controlFlowGraph != null
val classCfg: ControlFlowGraph
get() = classDeclaration.controlFlowGraphReference.controlFlowGraph!!
private val propsDeclList = mutableListOf<FirProperty>()
private val anonymousInitializer = mutableListOf<FirAnonymousInitializer>()
// private val highOrderFunctions
init {
if (isCfgAvailable)
for (subGraph in classCfg.subGraphs) {
when (val declaration = subGraph.declaration) {
is FirAnonymousInitializer -> anonymousInitializer.add(declaration)
is FirProperty -> propsDeclList.add(declaration)
}
}
val visitor = ForwardCfgVisitor(classDeclaration.symbol.classId)
classCfg.traverseForwardWithoutLoops(visitor)
classInitContextNodesMap.putAll(visitor.initContextNodesMap)
}
}
internal class ForwardCfgVisitor(
private val classId: ClassId,
) : ControlFlowGraphVisitorVoid() {
val initContextNodesMap = mutableMapOf<CFGNode<*>, InitContextNode>()
private var currentAffectingNodes = Stack<InitContextNode>()
private var isInPropertyInitializer = false
private var lastPropertyInitializerAffectingNodes = mutableListOf<InitContextNode>()
private var lastPropertyInitializerContextNode: InitContextNode? = null
// for safeCall detection
private var lastQualifiedAccessContextNode: InitContextNode? = null
private val isLastNodeThisReceiver: Boolean
get() = if (currentAffectingNodes.isEmpty()) false
else currentAffectingNodes.peek().nodeType == ContextNodeType.RESOLVABLE_THIS_RECEIVER_CALL
override fun visitNode(node: CFGNode<*>) {
val context = checkAndBuildNodeContext(cfgNode = node)
initContextNodesMap[node] = context
}
override fun visitFunctionCallNode(node: FunctionCallNode) {
val accessedMembers = mutableListOf<FirCallableSymbol<*>>()
val accessedProperties = mutableListOf<FirVariableSymbol<*>>()
var nodeType = ContextNodeType.UNRESOLVABLE_FUN_CALL
if (node.fir.calleeReference.isCallResolvable(classId)) {
accessedMembers.add(node.fir.calleeReference.resolvedSymbolAsNamedFunction!!)
nodeType = ContextNodeType.RESOLVABLE_CALL
}
for (argument in node.fir.argumentList.arguments)
when {
argument is FirThisReceiverExpression -> {
nodeType = ContextNodeType.RESOLVABLE_THIS_RECEIVER_CALL
}
argument is FirQualifiedAccessExpression && argument.calleeReference.isMemberOfTheClass(classId) -> {
accessedMembers.add(argument.calleeReference.resolvedSymbolAsCallable!!)
accessedProperties.add(argument.calleeReference.resolvedSymbolAsProperty ?: continue)
}
else -> continue
}
val context = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedMembers,
accessedProperties = accessedProperties,
nodeType = nodeType
)
initContextNodesMap[node] = context
}
override fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
val accessedProperties = mutableListOf<FirVariableSymbol<*>>()
when {
node.fir.calleeReference.isMemberOfTheClass(classId) -> {
if (isLastNodeThisReceiver) {// case: "this.property"
currentAffectingNodes.peek().nodeType = ContextNodeType.NOT_AFFECTED
}
// if it is a member and it is qualifiedAccess then it is property :)
val member = node.fir.calleeReference.resolvedSymbolAsProperty!!
accessedProperties.add(member)
lastQualifiedAccessContextNode = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedProperties,
accessedProperties = accessedProperties,
nodeType = ContextNodeType.PROPERTY_QUALIFIED_ACCESS
)
initContextNodesMap[node] = lastQualifiedAccessContextNode!!
}
else -> {
val context = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedProperties,
accessedProperties = accessedProperties,
nodeType = ContextNodeType.NOT_MEMBER_QUALIFIED_ACCESS
)
initContextNodesMap[node] = context
}
}
}
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
if (node.fir.lValue.resolvedSymbolAsProperty?.callableId?.classId == classId) {
val symbol = node.fir.lValue.resolvedSymbolAsProperty
val accessedProperties = mutableListOf<FirVariableSymbol<*>>()
accessedProperties.add(symbol!!)
val assignNodeContext = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedProperties,
accessedProperties = accessedProperties,
initCandidates = accessedProperties,
nodeType = ContextNodeType.ASSIGNMENT_OR_INITIALIZER
)
updateAffectedNodesAfterAssignmentNode(assignNodeContext)
initContextNodesMap[node] = assignNodeContext
} else {
visitNode(node)
}
}
override fun visitPropertyInitializerEnterNode(node: PropertyInitializerEnterNode) {
if (node.fir.isClassPropertyWithInitializer) {
isInPropertyInitializer = true
currentAffectingNodes = Stack()
val accessedProperties = mutableListOf<FirVariableSymbol<*>>(node.fir.symbol)
lastPropertyInitializerContextNode = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedProperties,
accessedProperties = accessedProperties,
initCandidates = accessedProperties,
nodeType = ContextNodeType.ASSIGNMENT_OR_INITIALIZER
)
initContextNodesMap[node] = lastPropertyInitializerContextNode!!
lastPropertyInitializerAffectingNodes = mutableListOf()
} else {
visitNode(node)
}
}
override fun visitPropertyInitializerExitNode(node: PropertyInitializerExitNode) {
if (node.fir.isClassPropertyWithInitializer && isInPropertyInitializer) {
isInPropertyInitializer = false
val accessedProperties = mutableListOf<FirVariableSymbol<*>>(node.fir.symbol)
// TODO affected by itself also :)))
val context = checkAndBuildNodeContext(
cfgNode = node,
accessedMembers = accessedProperties,
accessedProperties = accessedProperties,
initCandidates = accessedProperties,
affectingNodes = currentAffectingNodes,
nodeType = ContextNodeType.ASSIGNMENT_OR_INITIALIZER
)
initContextNodesMap[node] = context
} else {
isInPropertyInitializer = false
visitNode(node)
}
}
// TODO: lambda
override fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
if (lastQualifiedAccessContextNode == currentAffectingNodes.peek()) {
lastQualifiedAccessContextNode?.nodeType = ContextNodeType.PROPERTY_SAFE_QUALIFIED_ACCESS
}
visitNode(node)
}
private fun checkAndBuildNodeContext(
cfgNode: CFGNode<*>,
accessedMembers: List<FirCallableSymbol<*>> = emptyList(),
accessedProperties: List<FirVariableSymbol<*>> = emptyList(),
initCandidates: MutableList<FirVariableSymbol<*>> = mutableListOf(),
affectedNodes: MutableList<InitContextNode> = mutableListOf(),
affectingNodes: MutableList<InitContextNode> = mutableListOf(),
nodeType: ContextNodeType = ContextNodeType.NOT_AFFECTED,
isInitialized: Boolean = false
): InitContextNode {
val context = InitContextNode(
cfgNode,
accessedMembers,
accessedProperties,
initCandidates,
affectedNodes,
affectingNodes,
nodeType,
isInitialized
)
currentAffectingNodes.push(context)
if (checkIfInPropertyInitializer(context)) {
context.affectedNodes.add(lastPropertyInitializerContextNode!!)
}
return context
}
private fun updateAffectedNodesAfterAssignmentNode(assignNodeContext: InitContextNode) {
val rValue = (assignNodeContext.cfgNode as VariableAssignmentNode).fir.rValue
currentAffectingNodes.pop() // remove assignNodeContext itself
var node = currentAffectingNodes.pop()
while (node.cfgNode.fir != rValue) {
node.affectedNodes.add(assignNodeContext)
assignNodeContext.affectingNodes.add(node)
node = currentAffectingNodes.pop()
}
currentAffectingNodes = Stack()
}
private fun checkIfInPropertyInitializer(context: InitContextNode) =
if (context.cfgNode !is PropertyInitializerEnterNode && isInPropertyInitializer) {
lastPropertyInitializerAffectingNodes.add(context)
true
} else false
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2010-2020 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.analysis.checkers.declaration.leak
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.name.ClassId
// always contains at least kotlin/Any as a parent
internal fun FirRegularClass.hasClassSomeParents() = this.superTypeRefs.size > 1
internal val FirReference.resolvedSymbolAsProperty: FirPropertySymbol?
get() = (this as? FirResolvedNamedReference)?.resolvedSymbol as? FirPropertySymbol
internal val FirReference.resolvedSymbolAsNamedFunction: FirNamedFunctionSymbol?
get() = (this as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol
internal val FirReference.resolvedSymbolAsCallable: FirCallableSymbol<*>?
get() = when {
(resolvedSymbolAsNamedFunction != null) -> resolvedSymbolAsNamedFunction
(resolvedSymbolAsProperty != null) -> resolvedSymbolAsProperty
else -> null
}
internal fun FirReference.isCallResolvable(expectedClassId: ClassId): Boolean {
val resolvedSymbol = resolvedSymbolAsCallable ?: return false
return resolvedSymbol.callableId.isLocal || resolvedSymbol.callableId.isTopLevel || expectedClassId == resolvedSymbol.callableId.classId
}
internal fun FirReference.isMemberOfTheClass(expectedClassId: ClassId): Boolean {
val resolvedSymbol = resolvedSymbolAsCallable ?: return false
val classId = resolvedSymbol.callableId.classId
// call member - fun in constructor
if (classId != null && classId == expectedClassId)
return true
return false
}
internal val FirProperty.isClassPropertyWithInitializer: Boolean
get() = !this.isLocal && this.initializer != null

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2010-2020 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.analysis.checkers.declaration.leak
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.cfa.traverseForwardWithoutLoops
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ConstExpressionNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.FunctionCallNode
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.name.ClassId
internal class InitContextAnalyzer(
private val classInitContext: BaseClassInitContext,
private val reporter: DiagnosticReporter,
private val maxResolvedCallLevel: Int
) {
private val initializedProperties = mutableSetOf<FirVariableSymbol<*>>()
private val reportedProperties = mutableSetOf<FirVariableSymbol<*>>()
private var callLevel: Int = 0
private val resolvedCalls = mutableSetOf<FirCallableSymbol<*>>()
private val classId: ClassId
get() = classInitContext.classId
fun analyze() {
if (classInitContext.isCfgAvailable)
classInitContext.classCfg.traverseForwardWithoutLoops(ForwardCfgVisitor(classInitContext.classId), analyze = this::analyze)
}
private fun analyze(node: CFGNode<*>) {
val contextNode = classInitContext.classInitContextNodesMap[node] ?: return
when (contextNode.nodeType) {
ContextNodeType.ASSIGNMENT_OR_INITIALIZER -> {
if (contextNode.isSuccessfullyInitNode()) {
contextNode.confirmInitForCandidate()
initializedProperties.add(contextNode.initCandidate)
}
}
ContextNodeType.PROPERTY_QUALIFIED_ACCESS -> {
if (contextNode.accessedProperties.any {
it !in initializedProperties && it !in reportedProperties
}
) {
reporter.report(contextNode.cfgNode.fir.source)
reportedProperties.add(contextNode.firstAccessedProperty)
}
}
ContextNodeType.RESOLVABLE_THIS_RECEIVER_CALL, ContextNodeType.RESOLVABLE_CALL -> {
if (callLevel < maxResolvedCallLevel) {
callLevel += 1
resolveCall(contextNode)
}
}
else -> return
}
}
private fun resolveCall(contextNode: InitContextNode) {
// try {
val callableCfg = contextNode.callableCFG ?: return
val callableBodyVisitor = ForwardCfgVisitor(classId)
if (resolvedCalls.add(contextNode.callableSymbol ?: return)) {
callableCfg.traverseForwardWithoutLoops(callableBodyVisitor)
classInitContext.classInitContextNodesMap.putAll(callableBodyVisitor.initContextNodesMap)
}
callableCfg.traverseForwardWithoutLoops(callableBodyVisitor, analyze = this::analyze)
// } catch (e: Exception) {
// }
}
private fun DiagnosticReporter.report(source: FirSourceElement?) {
source?.let { report(FirErrors.LEAKING_THIS_IN_CONSTRUCTOR.on(it, "Possible leaking this in constructor")) }
}
private fun InitContextNode.isSuccessfullyInitNode(): Boolean =
affectingNodes.all {
it.cfgNode is ConstExpressionNode
|| (it.nodeType == ContextNodeType.UNRESOLVABLE_FUN_CALL)
|| (it.nodeType == ContextNodeType.PROPERTY_QUALIFIED_ACCESS
&& it.firstAccessedProperty.callableId.classId == classId
&& initializedProperties.contains(it.firstAccessedProperty))
}
private val InitContextNode.callableCFG: ControlFlowGraph?
get() = (cfgNode as FunctionCallNode).fir.calleeReference.resolvedSymbolAsNamedFunction?.fir?.controlFlowGraphReference?.controlFlowGraph
private val InitContextNode.callableSymbol: FirCallableSymbol<*>?
get() = (cfgNode as FunctionCallNode).fir.calleeReference.resolvedSymbolAsNamedFunction
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010-2020 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.analysis.checkers.declaration.leak
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
internal class InitContextNode(
val cfgNode: CFGNode<*>,
val accessedMembers: List<FirCallableSymbol<*>>,
val accessedProperties: List<FirVariableSymbol<*>>,
val initCandidates: MutableList<FirVariableSymbol<*>>,
var affectedNodes: MutableList<InitContextNode>,
var affectingNodes: MutableList<InitContextNode>,
var nodeType: ContextNodeType,
var isInitialized: Boolean // if true => initCandidate contains initialized property
) {
val initCandidate: FirVariableSymbol<*>
get() = initCandidates[0]
val firstAccessedProperty: FirVariableSymbol<*>
get() = accessedProperties[0]
fun confirmInitForCandidate() {
isInitialized = true
}
}
internal enum class ContextNodeType {
ASSIGNMENT_OR_INITIALIZER,
PROPERTY_QUALIFIED_ACCESS,
PROPERTY_SAFE_QUALIFIED_ACCESS,
NOT_MEMBER_QUALIFIED_ACCESS,
NOT_AFFECTED,
RESOLVABLE_CALL, // top-level, local and members calls
RESOLVABLE_THIS_RECEIVER_CALL,
UNRESOLVABLE_FUN_CALL,
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2010-2020 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.analysis.checkers.declaration.leak
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.modality
object LeakingThisChecker : FirDeclarationChecker<FirRegularClass>() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
when (declaration.modality) {
Modality.FINAL -> {
if (!declaration.hasClassSomeParents())
runCheck(
collectDataForSimpleClassAnalysis(declaration),
reporter
)
}
else -> {
}
}
}
private fun collectDataForSimpleClassAnalysis(classDeclaration: FirRegularClass): BaseClassInitContext =
BaseClassInitContext(
classDeclaration
)
private fun runCheck(classInitContext: BaseClassInitContext, reporter: DiagnosticReporter) {
val analyzer = InitContextAnalyzer(classInitContext, reporter, 100)
analyzer.analyze()
}
}

View File

@@ -19,6 +19,7 @@ class DeclarationCheckersDiagnosticComponent(collector: AbstractDiagnosticCollec
override fun visitRegularClass(regularClass: FirRegularClass, data: CheckerContext) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(regularClass, data, it) }
runCheck { DeclarationCheckers.CLASS_DECLARATIONS.check(regularClass, data, it) }
}
override fun visitSealedClass(sealedClass: FirSealedClass, data: CheckerContext) {

View File

@@ -72,6 +72,9 @@ object FirErrors {
val REDUNDANT_MODIFIER by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val DEPRECATED_MODIFIER_PAIR by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val INCOMPATIBLE_MODIFIERS by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val LEAKING_THIS_IN_CONSTRUCTOR by existing<FirSourceElement, PsiElement, String>(Errors.POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR)
}

View File

@@ -30,6 +30,12 @@ data class CallableId(
return field
}
val isTopLevel: Boolean
get() = !isLocal && className == null
val isLocal: Boolean
get() = packageName == PACKAGE_FQ_NAME_FOR_LOCAL
constructor(classId: ClassId, callableName: Name) : this(classId.packageFqName, classId.relativeClassName, callableName) {
this.classId = classId
}

View File

@@ -33,9 +33,9 @@ data class ModuleData(
val qualifiedName get() = if (name in qualifier) qualifier else "$name.$qualifier"
val outputDir = File(ROOT_PATH_PREFIX, rawOutputDir.removePrefix("/"))
val classpath = rawClasspath.map { File(ROOT_PATH_PREFIX, it.removePrefix("/")) }
val sources = rawSources.map { File(ROOT_PATH_PREFIX, it.removePrefix("/")) }
val javaSourceRoots = rawJavaSourceRoots.map { File(ROOT_PATH_PREFIX, it.removePrefix("/")) }
val classpath = rawClasspath.map { File(ROOT_PATH_PREFIX, it.removePrefix("C:/")) }
val sources = rawSources.map { File(ROOT_PATH_PREFIX, it.removePrefix("C:/")) }
val javaSourceRoots = rawJavaSourceRoots.map { File(ROOT_PATH_PREFIX, it.removePrefix("C:/")) }
}
private fun NodeList.toList(): List<Node> {
@@ -49,7 +49,7 @@ private fun NodeList.toList(): List<Node> {
private val Node.childNodesList get() = childNodes.toList()
private val ROOT_PATH_PREFIX = System.getProperty("fir.bench.prefix", "/")
private val ROOT_PATH_PREFIX = System.getProperty("fir.bench.prefix", "C:/")
abstract class AbstractModularizedTest : KtUsefulTestCase() {
private val folderDateFormat = SimpleDateFormat("yyyy-MM-dd")
@@ -139,7 +139,7 @@ abstract class AbstractModularizedTest : KtUsefulTestCase() {
protected fun runTestOnce(pass: Int) {
beforePass()
val testDataPath = System.getProperty("fir.bench.jps.dir")?.toString() ?: "/Users/jetbrains/jps"
val testDataPath = "C:/jps_script"
val root = File(testDataPath)
println("BASE PATH: ${root.absolutePath}")

View File

@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.search.GlobalSearchScope
@@ -15,6 +16,9 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.dump.MultiModuleHtmlFirDump
@@ -23,6 +27,7 @@ import org.jetbrains.kotlin.fir.resolve.firProvider
import org.jetbrains.kotlin.fir.resolve.impl.FirProviderImpl
import org.jetbrains.kotlin.fir.resolve.transformers.FirTotalResolveTransformer
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import java.io.File
import java.io.FileOutputStream
import java.io.PrintStream
@@ -30,11 +35,11 @@ import java.io.PrintStream
private const val FAIL_FAST = true
private const val FIR_DUMP_PATH = "tmp/firDump"
private const val FIR_HTML_DUMP_PATH = "tmp/firDump-html"
const val FIR_LOGS_PATH = "tmp/fir-logs"
private const val FIR_DUMP_PATH = "C:/fir_tmp/firDump"
private const val FIR_HTML_DUMP_PATH = "C:/fir_tmp/firDump-html"
const val FIR_LOGS_PATH = "C:/fir_tmp/fir-logs"
private val DUMP_FIR = System.getProperty("fir.bench.dump", "true").toBooleanLenient()!!
private const val DUMP_FIR = false
internal val PASSES = System.getProperty("fir.bench.passes")?.toInt() ?: 3
internal val SEPARATE_PASS_DUMP = System.getProperty("fir.bench.dump.separate_pass", "false").toBooleanLenient()!!
private val APPEND_ERROR_REPORTS = System.getProperty("fir.bench.report.errors.append", "false").toBooleanLenient()!!
@@ -69,12 +74,47 @@ class FirResolveModularizedTotalKotlinTest : AbstractModularizedTest() {
//println("Raw FIR up, files: ${firFiles.size}")
bench.processFiles(firFiles, totalTransformer.transformers)
val diagnostics = collectDiagnostics(firFiles)
val fileDocumentManager = FileDocumentManager.getInstance()
for ((file, list) in diagnostics) {
if (list.isNotEmpty()) {
println("For ${file.name}: ")
for (diagnostic in list) {
val element = diagnostic.element as FirPsiSourceElement<*>
val psi = element.psi
val document = fileDocumentManager.getDocument(psi.containingFile.virtualFile)
val line = (document?.getLineNumber(psi.startOffset) ?: 0)
val char = psi.startOffset - (document?.getLineStartOffset(line) ?: 0)
println("$line:$char: ${diagnostic.factory.name}")
}
}
}
val disambiguatedName = moduleData.disambiguatedName()
dumpFir(disambiguatedName, moduleData, firFiles)
dumpFirHtml(disambiguatedName, moduleData, firFiles)
}
private fun collectDiagnostics(firFiles: List<FirFile>): Map<FirFile, List<FirDiagnostic<*>>> {
val collectors = mutableMapOf<FirSession, AbstractDiagnosticCollector>()
val result = mutableMapOf<FirFile, List<FirDiagnostic<*>>>()
for (firFile in firFiles) {
val session = firFile.session
val collector = collectors.computeIfAbsent(session) { createCollector(session) }
try {
result[firFile] = collector.collectDiagnostics(firFile).toList()
} catch (e: Throwable) {
println("Exception in file: ${firFile.name}")
e.printStackTrace()
}
}
return result
}
private fun createCollector(session: FirSession): AbstractDiagnosticCollector {
return FirDiagnosticsCollector.create(session)
}
private fun dumpFir(disambiguatedName: String, moduleData: ModuleData, firFiles: List<FirFile>) {
if (!DUMP_FIR) return
val dumpRoot = File(FIR_DUMP_PATH).resolve(disambiguatedName)

View File

@@ -339,6 +339,7 @@ public interface Errors {
DiagnosticFactory0<PsiElement> NON_PRIVATE_CONSTRUCTOR_IN_ENUM = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NON_PRIVATE_CONSTRUCTOR_IN_SEALED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR = DiagnosticFactory1.create(WARNING);
// Inline classes

View File

@@ -685,6 +685,8 @@ public class DefaultErrorMessages {
MAP.put(NON_PRIVATE_CONSTRUCTOR_IN_ENUM, "Constructor must be private in enum class");
MAP.put(NON_PRIVATE_CONSTRUCTOR_IN_SEALED, "Constructor must be private in sealed class");
MAP.put(POSSIBLE_LEAKING_THIS_IN_CONSTRUCTOR, "Possible leaking this in constructor {0}", STRING);
MAP.put(INLINE_CLASS_NOT_TOP_LEVEL, "Inline classes are only allowed on top level");
MAP.put(INLINE_CLASS_NOT_FINAL, "Inline classes can be only final");

View File

@@ -32,7 +32,7 @@ abstract class AbstractFirOldFrontendDiagnosticsTest : AbstractFirDiagnosticsTes
override fun runAnalysis(testDataFile: File, testFiles: List<TestFile>, firFilesPerSession: Map<FirSession, List<FirFile>>) {
val failure: FirRuntimeException? = try {
for ((_, firFiles) in firFilesPerSession) {
doFirResolveTestBench(firFiles, FirTotalResolveTransformer().transformers, gc = false)
doFirResolveTestBench(firFiles, FirTotalResolveTransformer().transformers, gc = false, unresolvedTypesAllowed = false)
}
null
} catch (e: FirRuntimeException) {

View File

@@ -30,6 +30,7 @@ import java.text.DecimalFormat
import kotlin.math.max
import kotlin.reflect.KClass
import kotlin.system.measureNanoTime
import kotlin.test.assertEquals
fun checkFirProvidersConsistency(firFiles: List<FirFile>) {
@@ -330,7 +331,8 @@ fun doFirResolveTestBench(
transformers: List<FirTransformer<Nothing?>>,
gc: Boolean = true,
withProgress: Boolean = false,
silent: Boolean = true
silent: Boolean = true,
unresolvedTypesAllowed: Boolean = true
) {
if (gc) {
@@ -339,7 +341,7 @@ fun doFirResolveTestBench(
val bench = FirResolveBench(withProgress)
bench.processFiles(firFiles, transformers)
if (!silent) bench.getTotalStatistics().report(System.out, "")
if (!silent) bench.getTotalStatistics().report(System.out, "", unresolvedTypesAllowed = unresolvedTypesAllowed)
bench.throwFailure()
}
@@ -380,7 +382,12 @@ fun FirResolveBench.TotalStatistics.reportErrors(stream: PrintStream) {
}
}
fun FirResolveBench.TotalStatistics.report(stream: PrintStream, header: String) {
fun FirResolveBench.TotalStatistics.report(
stream: PrintStream,
header: String,
errorTypeReports: Boolean = true,
unresolvedTypesAllowed: Boolean = true
) {
with(stream) {
infix fun Int.percentOf(other: Int): String {
return String.format("%.1f%%", this * 100.0 / other)
@@ -411,6 +418,10 @@ fun FirResolveBench.TotalStatistics.report(stream: PrintStream, header: String)
printMeasureAsTable(totalMeasure, this@report, "Total time")
}
}
if (!unresolvedTypesAllowed) {
assertEquals(0, unresolvedTypes)
}
}
}