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:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user