More precise value reassignment analysis. #KT-7418 Fixed. #KT-6902 Fixed.

Variable can now be "exactly initialized", "may be initialized" and "not initialized".
A set of relevant tests. Some fixed tests.
This commit is contained in:
Mikhail Glukhikh
2015-09-01 19:16:27 +03:00
parent c59b118b09
commit 285d5d06c7
26 changed files with 269 additions and 53 deletions
@@ -372,14 +372,14 @@ public class JetFlowInformationProvider {
if (!(element instanceof JetSimpleNameExpression)) return;
boolean isInitialized = ctxt.exitInitState.isInitialized;
boolean isDefinitelyInitialized = ctxt.exitInitState.definitelyInitialized();
VariableDescriptor variableDescriptor = ctxt.variableDescriptor;
if (variableDescriptor instanceof PropertyDescriptor) {
if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor)) {
isInitialized = true;
isDefinitelyInitialized = true;
}
}
if (!isInitialized && !varWithUninitializedErrorGenerated.contains(variableDescriptor)) {
if (!isDefinitelyInitialized && !varWithUninitializedErrorGenerated.contains(variableDescriptor)) {
if (!(variableDescriptor instanceof PropertyDescriptor)) {
varWithUninitializedErrorGenerated.add(variableDescriptor);
}
@@ -412,7 +412,7 @@ public class JetFlowInformationProvider {
}
}
boolean isInitializedNotHere = ctxt.enterInitState.isInitialized;
boolean mayBeInitializedNotHere = ctxt.enterInitState.mayBeInitialized();
boolean hasBackingField = true;
if (variableDescriptor instanceof PropertyDescriptor) {
hasBackingField = trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor);
@@ -437,7 +437,7 @@ public class JetFlowInformationProvider {
}
boolean isThisOrNoDispatchReceiver =
PseudocodeUtil.isThisOrNoDispatchReceiver(writeValueInstruction, trace.getBindingContext());
if ((isInitializedNotHere || !hasBackingField || !isThisOrNoDispatchReceiver) && !variableDescriptor.isVar()) {
if ((mayBeInitializedNotHere || !hasBackingField || !isThisOrNoDispatchReceiver) && !variableDescriptor.isVar()) {
boolean hasReassignMethodReturningUnit = false;
JetSimpleNameExpression operationReference = null;
PsiElement parent = expression.getParent();
@@ -483,7 +483,7 @@ public class JetFlowInformationProvider {
private boolean checkAssignmentBeforeDeclaration(@NotNull VariableInitContext ctxt, @NotNull JetExpression expression) {
if (!ctxt.enterInitState.isDeclared && !ctxt.exitInitState.isDeclared
&& !ctxt.enterInitState.isInitialized && ctxt.exitInitState.isInitialized) {
&& !ctxt.enterInitState.mayBeInitialized() && ctxt.exitInitState.mayBeInitialized()) {
report(Errors.INITIALIZATION_BEFORE_DECLARATION.on(expression, ctxt.variableDescriptor), ctxt);
return true;
}
@@ -492,7 +492,8 @@ public class JetFlowInformationProvider {
private boolean checkInitializationUsingBackingField(@NotNull VariableInitContext ctxt, @NotNull JetExpression expression) {
VariableDescriptor variableDescriptor = ctxt.variableDescriptor;
if (variableDescriptor instanceof PropertyDescriptor && !ctxt.enterInitState.isInitialized && ctxt.exitInitState.isInitialized) {
if (variableDescriptor instanceof PropertyDescriptor
&& !ctxt.enterInitState.mayBeInitialized() && ctxt.exitInitState.mayBeInitialized()) {
if (!variableDescriptor.isVar()) return false;
if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor)) return false;
PsiElement property = DescriptorToSourceUtils.descriptorToDeclaration(variableDescriptor);
@@ -590,7 +591,7 @@ public class JetFlowInformationProvider {
for (VariableDescriptor variable : declaredVariables) {
if (variable instanceof PropertyDescriptor) {
PseudocodeVariablesData.VariableInitState variableInitState = initializers.getIncoming().get(variable);
if (variableInitState != null && variableInitState.isInitialized) continue;
if (variableInitState != null && variableInitState.definitelyInitialized()) continue;
trace.record(BindingContext.IS_UNINITIALIZED, (PropertyDescriptor) variable);
}
}
@@ -155,7 +155,7 @@ public class PseudocodeVariablesData {
boolean declaredOutsideThisDeclaration =
declaredIn == null //declared outside this pseudocode
|| declaredIn.getLexicalScopeForContainingDeclaration() != instruction.getLexicalScope().getLexicalScopeForContainingDeclaration();
return VariableInitState.create(/*isInitialized=*/declaredOutsideThisDeclaration);
return VariableInitState.create(/*initState=*/declaredOutsideThisDeclaration);
}
@NotNull
@@ -169,20 +169,21 @@ public class PseudocodeVariablesData {
Map<VariableDescriptor, VariableInitState> enterInstructionData = Maps.newHashMap();
for (VariableDescriptor variable : variablesInScope) {
boolean isInitialized = true;
TriInitState initState = null;
boolean isDeclared = true;
for (Map<VariableDescriptor, VariableInitState> edgeData : incomingEdgesData) {
VariableInitState initState = edgeData.get(variable);
if (initState != null) {
if (!initState.isInitialized) {
isInitialized = false;
}
if (!initState.isDeclared) {
VariableInitState varControlFlowState = edgeData.get(variable);
if (varControlFlowState != null) {
initState = initState != null ? initState.merge(varControlFlowState.initState) : varControlFlowState.initState;
if (!varControlFlowState.isDeclared) {
isDeclared = false;
}
}
}
enterInstructionData.put(variable, VariableInitState.create(isInitialized, isDeclared));
if (initState == null) {
throw new AssertionError("An empty set of incoming edges data");
}
enterInstructionData.put(variable, VariableInitState.create(initState, isDeclared));
}
return enterInstructionData;
}
@@ -217,8 +218,8 @@ public class PseudocodeVariablesData {
if (enterInitState == null) {
enterInitState = getDefaultValueForInitializers(variable, instruction, lexicalScopeVariableInfo);
}
if (enterInitState == null || !enterInitState.isInitialized || !enterInitState.isDeclared) {
boolean isInitialized = enterInitState != null && enterInitState.isInitialized;
if (enterInitState == null || !enterInitState.mayBeInitialized() || !enterInitState.isDeclared) {
boolean isInitialized = enterInitState != null && enterInitState.mayBeInitialized();
VariableInitState variableDeclarationInfo = VariableInitState.create(isInitialized, true);
exitInstructionData.put(variable, variableDeclarationInfo);
}
@@ -279,28 +280,54 @@ public class PseudocodeVariablesData {
);
}
private enum TriInitState {
INITIALIZED("I"), UNKNOWN("I?"), NOT_INITIALIZED("");
private final String s;
TriInitState(String s) {
this.s = s;
}
private TriInitState merge(@NotNull TriInitState other) {
if (this == other) return this;
return UNKNOWN;
}
@Override
public String toString() {
return s;
}
}
public static class VariableInitState {
public final boolean isInitialized;
public final TriInitState initState;
public final boolean isDeclared;
private VariableInitState(boolean isInitialized, boolean isDeclared) {
this.isInitialized = isInitialized;
private VariableInitState(TriInitState initState, boolean isDeclared) {
this.initState = initState;
this.isDeclared = isDeclared;
}
private static final VariableInitState VS_TT = new VariableInitState(true, true);
private static final VariableInitState VS_TF = new VariableInitState(true, false);
private static final VariableInitState VS_FT = new VariableInitState(false, true);
private static final VariableInitState VS_FF = new VariableInitState(false, false);
private static final VariableInitState VS_IT = new VariableInitState(TriInitState.INITIALIZED, true);
private static final VariableInitState VS_IF = new VariableInitState(TriInitState.INITIALIZED, false);
private static final VariableInitState VS_UT = new VariableInitState(TriInitState.UNKNOWN, true);
private static final VariableInitState VS_UF = new VariableInitState(TriInitState.UNKNOWN, false);
private static final VariableInitState VS_NT = new VariableInitState(TriInitState.NOT_INITIALIZED, true);
private static final VariableInitState VS_NF = new VariableInitState(TriInitState.NOT_INITIALIZED, false);
private static VariableInitState create(TriInitState initState, boolean isDeclared) {
switch (initState) {
case INITIALIZED: return isDeclared ? VS_IT : VS_IF;
case UNKNOWN: return isDeclared ? VS_UT : VS_UF;
default: return isDeclared ? VS_NT : VS_NF;
}
}
private static VariableInitState create(boolean isInitialized, boolean isDeclared) {
if (isInitialized) {
if (isDeclared) return VS_TT;
return VS_TF;
}
if (isDeclared) return VS_FT;
return VS_FF;
return create(isInitialized ? TriInitState.INITIALIZED : TriInitState.NOT_INITIALIZED, isDeclared);
}
private static VariableInitState create(boolean isInitialized) {
@@ -311,14 +338,22 @@ public class PseudocodeVariablesData {
return create(true, isDeclaredHere || (mergedEdgesData != null && mergedEdgesData.isDeclared));
}
public boolean definitelyInitialized() {
return initState == TriInitState.INITIALIZED;
}
public boolean mayBeInitialized() {
return initState != TriInitState.NOT_INITIALIZED;
}
@Override
public String toString() {
if (!isInitialized && !isDeclared) return "-";
return (isInitialized ? "I" : "") + (isDeclared ? "D" : "");
if (initState == TriInitState.NOT_INITIALIZED && !isDeclared) return "-";
return initState + (isDeclared ? "D" : "");
}
}
public static enum VariableUseState {
public enum VariableUseState {
READ(3),
WRITTEN_AFTER_READ(2),
ONLY_WRITTEN_NEVER_READ(1),
@@ -10,7 +10,7 @@ fun foo() {
}
---------------------
L0:
1 <START> INIT: in: {} out: {} USE: in: {} out: {}
1 <START> INIT: in: {} out: {} USE: in: {} out: {}
2 mark({ val b: Boolean if (1 < 2) { use(b) } else { b = true } })
v(val b: Boolean) INIT: in: {} out: {b=D}
mark(if (1 < 2) { use(b) } else { b = true }) INIT: in: {b=D} out: {b=D}
@@ -19,23 +19,23 @@ L0:
mark(1 < 2)
call(1 < 2, compareTo|<v0>, <v1>) -> <v2>
jf(L2|<v2>)
3 mark({ use(b) }) USE: in: {b=READ} out: {b=READ}
r(b) -> <v3> USE: in: {} out: {b=READ}
3 mark({ use(b) }) USE: in: {b=READ} out: {b=READ}
r(b) -> <v3> USE: in: {} out: {b=READ}
mark(use(b))
call(use(b), use|<v3>) -> <v4>
2 jmp(L3) USE: in: {} out: {}
2 jmp(L3) USE: in: {} out: {}
L2 [else branch]:
3 mark({ b = true })
r(true) -> <v5> USE: in: {b=ONLY_WRITTEN_NEVER_READ} out: {b=ONLY_WRITTEN_NEVER_READ}
w(b|<v5>) INIT: in: {b=D} out: {b=ID} USE: in: {} out: {b=ONLY_WRITTEN_NEVER_READ}
r(true) -> <v5> USE: in: {b=ONLY_WRITTEN_NEVER_READ} out: {b=ONLY_WRITTEN_NEVER_READ}
w(b|<v5>) INIT: in: {b=D} out: {b=ID} USE: in: {} out: {b=ONLY_WRITTEN_NEVER_READ}
L3 ['if' expression result]:
2 merge(if (1 < 2) { use(b) } else { b = true }|<v4>, !<v6>) -> <v7> INIT: in: {b=D} out: {b=D}
2 merge(if (1 < 2) { use(b) } else { b = true }|<v4>, !<v6>) -> <v7> INIT: in: {b=I?D} out: {b=I?D}
L1:
1 <END> INIT: in: {} out: {}
error:
<ERROR>
sink:
<SINK> USE: in: {} out: {}
<SINK> USE: in: {} out: {}
=====================
== use ==
fun use(vararg a: Any?) = a
@@ -10,7 +10,7 @@ L0:
v(val sum: (Int)->Int = { (x: Int) -> sum(x - 1) + x }) INIT: in: {} out: {sum=D}
mark({ (x: Int) -> sum(x - 1) + x }) INIT: in: {sum=D} out: {sum=D}
jmp?(L2)
d({ (x: Int) -> sum(x - 1) + x }) USE: in: {sum=READ} out: {sum=READ}
d({ (x: Int) -> sum(x - 1) + x }) USE: in: {sum=READ} out: {sum=READ}
L2 [after local declaration]:
r({ (x: Int) -> sum(x - 1) + x }) -> <v0>
w(sum|<v0>) INIT: in: {sum=D} out: {sum=ID}
@@ -19,7 +19,7 @@ L1:
error:
<ERROR> INIT: in: {} out: {}
sink:
<SINK> INIT: in: {sum=D} out: {sum=D} USE: in: {} out: {}
<SINK> INIT: in: {sum=I?D} out: {sum=I?D} USE: in: {} out: {}
=====================
== anonymous_0 ==
{ (x: Int) ->
@@ -101,7 +101,7 @@ L1:
error:
<ERROR> INIT: in: {} out: {}
sink:
<SINK> INIT: in: {obj=D} out: {obj=D} USE: in: {} out: {}
<SINK> INIT: in: {obj=I?D} out: {obj=I?D} USE: in: {} out: {}
=====================
== foo ==
fun foo() {
@@ -23,9 +23,9 @@ L0:
v(i) INIT: in: {numbers=ID} out: {i=D, numbers=ID}
L2 [loop entry point]:
L6 [condition entry point]:
jmp?(L3) INIT: in: {i=D, numbers=ID} out: {i=D, numbers=ID}
jmp?(L3) INIT: in: {i=I?D, numbers=ID} out: {i=I?D, numbers=ID}
magic[LOOP_RANGE_ITERATION](numbers|<v1>) -> <v2>
w(i|<v2>) INIT: in: {i=D, numbers=ID} out: {i=ID, numbers=ID}
w(i|<v2>) INIT: in: {i=I?D, numbers=ID} out: {i=ID, numbers=ID}
mark(for (i in numbers) { val b: Boolean if (1 < 2) { b = false } else { b = true } use(b) continue }) INIT: in: {i=ID, numbers=ID} out: {i=ID, numbers=ID} USE: in: {} out: {}
L4 [body entry point]:
4 mark({ val b: Boolean if (1 < 2) { b = false } else { b = true } use(b) continue })
@@ -53,7 +53,7 @@ L8 ['if' expression result]:
- 3 jmp(L2)
L3 [loop exit point]:
L5 [body exit point]:
read (Unit) INIT: in: {i=D, numbers=ID} out: {i=D, numbers=ID}
read (Unit) INIT: in: {i=I?D, numbers=ID} out: {i=I?D, numbers=ID}
L1:
1 <END> INIT: in: {numbers=ID} out: {numbers=ID}
error:
@@ -19,9 +19,9 @@ L0:
v(i) INIT: in: {} out: {i=D}
L2 [loop entry point]:
L6 [condition entry point]:
jmp?(L3) INIT: in: {i=D} out: {i=D}
jmp?(L3) INIT: in: {i=I?D} out: {i=I?D}
magic[LOOP_RANGE_ITERATION](1..10|<v3>) -> <v4>
w(i|<v4>) INIT: in: {i=D} out: {i=ID}
w(i|<v4>) INIT: in: {i=I?D} out: {i=ID}
mark(for (i in 1..10) { val a = i }) INIT: in: {i=ID} out: {i=ID}
L4 [body entry point]:
4 mark({ val a = i })
@@ -31,7 +31,7 @@ L4 [body entry point]:
3 jmp(L2) INIT: in: {i=ID} out: {i=ID} USE: in: {i=READ} out: {i=READ}
L3 [loop exit point]:
L5 [body exit point]:
read (Unit) INIT: in: {i=D} out: {i=D}
read (Unit) INIT: in: {i=I?D} out: {i=I?D}
2 mark("after") INIT: in: {} out: {}
r("after") -> <v6>
L1:
@@ -0,0 +1,10 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(k: Int): Int {
val i: Int
for (j in 1..k) {
<!VAL_REASSIGNMENT!>i<!> = j
}
i = 6
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ k: kotlin.Int): kotlin.Int
@@ -0,0 +1,10 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(): Int {
val i: Int
var j = 0
do {
<!VAL_REASSIGNMENT!>i<!> = ++j
} while (j < 5)
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Int
@@ -0,0 +1,11 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(f: Boolean): Int {
val i: Int
if (f) {}
else {
i = 2
}
<!VAL_REASSIGNMENT!>i<!> = 3
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ f: kotlin.Boolean): kotlin.Int
@@ -0,0 +1,9 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(k: Int): Int {
val i: Int
for (j in 1..k) {
<!VAL_REASSIGNMENT!>i<!> = j
}
return <!UNINITIALIZED_VARIABLE!>i<!>
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ k: kotlin.Int): kotlin.Int
+10
View File
@@ -0,0 +1,10 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(f: Boolean): Int {
val i: Int
if (f) {
i = 1
}
<!VAL_REASSIGNMENT!>i<!> = 3
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ f: kotlin.Boolean): kotlin.Int
@@ -0,0 +1,13 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(f: Boolean): Int {
val i: Int
if (f) {
i = 1
}
else {
i = 2
}
<!VAL_REASSIGNMENT!>i<!> = 3
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ f: kotlin.Boolean): kotlin.Int
@@ -0,0 +1,6 @@
fun foo(f: Boolean): Int {
val i: Int
if (f) {}
i = 3
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ f: kotlin.Boolean): kotlin.Int
@@ -0,0 +1,10 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(f: Boolean): Int {
val i: Int
when (f) {
true -> i = 1
}
<!VAL_REASSIGNMENT!>i<!> = 3
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ f: kotlin.Boolean): kotlin.Int
@@ -0,0 +1,11 @@
// !DIAGNOSTICS: -UNUSED_VALUE
fun foo(): Int {
val i: Int
var j = 0
while (true) {
<!VAL_REASSIGNMENT!>i<!> = ++j
if (j > 5) break
}
return i
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Int
@@ -10912,6 +10912,69 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
}
}
@TestMetadata("compiler/testData/diagnostics/tests/reassignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Reassignment extends AbstractJetDiagnosticsTest {
@TestMetadata("afterfor.kt")
public void testAfterfor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/afterfor.kt");
doTest(fileName);
}
public void testAllFilesPresentInReassignment() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/reassignment"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("dowhile.kt")
public void testDowhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/dowhile.kt");
doTest(fileName);
}
@TestMetadata("else.kt")
public void testElse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/else.kt");
doTest(fileName);
}
@TestMetadata("foronly.kt")
public void testForonly() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/foronly.kt");
doTest(fileName);
}
@TestMetadata("if.kt")
public void testIf() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/if.kt");
doTest(fileName);
}
@TestMetadata("ifelse.kt")
public void testIfelse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/ifelse.kt");
doTest(fileName);
}
@TestMetadata("noifelse.kt")
public void testNoifelse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/noifelse.kt");
doTest(fileName);
}
@TestMetadata("when.kt")
public void testWhen() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/when.kt");
doTest(fileName);
}
@TestMetadata("whiletrue.kt")
public void testWhiletrue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reassignment/whiletrue.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/diagnostics/tests/recovery")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
+1 -1
View File
@@ -10,7 +10,7 @@ object FileRead {
val fstream = FileInputStream()
val `in` = DataInputStream(fstream)
val br = BufferedReader(InputStreamReader(`in`))
val strLine: String
var strLine: String
while ((strLine = br.readLine()) != null) {
println(strLine)
}