Added checks for an initialization of properties using backing fields, not using setters

This commit is contained in:
svtk
2011-11-01 18:59:53 +04:00
parent 080f5d6ad2
commit 41c9b7e414
6 changed files with 375 additions and 166 deletions
@@ -3,18 +3,18 @@ package org.jetbrains.jet.lang.cfg;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.cfg.pseudocode.*;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.LocalVariableDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.diagnostics.Errors;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.types.JetStandardClasses;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.*;
@@ -259,13 +259,16 @@ public class JetFlowInformationProvider {
// }
// }
public void markUninitializedVariables(@NotNull JetElement subroutine, List<? extends VariableDescriptor> initializedVariables) {
Pseudocode pseudocode = pseudocodeMap.get(subroutine);
public void markUninitializedVariables(
@NotNull JetElement subroutine,
final boolean inAnonymousInitializers) {
final Pseudocode pseudocode = pseudocodeMap.get(subroutine);
assert pseudocode != null;
JetControlFlowGraphTraverser.InstructionsMergeStrategy<Map<VariableDescriptor, InitializationPoints>> instructionsMergeStrategy = new JetControlFlowGraphTraverser.InstructionsMergeStrategy<Map<VariableDescriptor, InitializationPoints>>() {
JetControlFlowGraphTraverser.InstructionsMergeStrategy<Map<VariableDescriptor, InitializationPoints>> instructionsMergeStrategy =
new JetControlFlowGraphTraverser.InstructionsMergeStrategy<Map<VariableDescriptor, InitializationPoints>>() {
@Override
public Map<VariableDescriptor, InitializationPoints> execute(
public Pair<Map<VariableDescriptor, InitializationPoints>, Map<VariableDescriptor, InitializationPoints>> execute(
Instruction instruction,
Collection<Map<VariableDescriptor, InitializationPoints>> incomingEdgesData) {
@@ -274,7 +277,7 @@ public class JetFlowInformationProvider {
variablesInScope.addAll(edgePointsMap.keySet());
}
Map<VariableDescriptor, InitializationPoints> pointsMap = Maps.newHashMap();
Map<VariableDescriptor, InitializationPoints> enterInstructionPointsMap = Maps.newHashMap();
for (VariableDescriptor variable : variablesInScope) {
Set<InitializationPoints> edgesDataForVariable = Sets.newHashSet();
for (Map<VariableDescriptor, InitializationPoints> edgePointsMap : incomingEdgesData) {
@@ -283,43 +286,64 @@ public class JetFlowInformationProvider {
edgesDataForVariable.add(points);
}
}
pointsMap.put(variable, new InitializationPoints(edgesDataForVariable));
enterInstructionPointsMap.put(variable, new InitializationPoints(edgesDataForVariable));
}
Map<VariableDescriptor, InitializationPoints> exitInstructionPointsMap = Maps.newHashMap(enterInstructionPointsMap);
if (instruction instanceof WriteValueInstruction) {
VariableDescriptor variableDescriptor = extractVariableDescriptorIfAny(instruction);
InitializationPoints initializationAtThisPoint = new InitializationPoints(((WriteValueInstruction) instruction).getElement());
pointsMap.put(variableDescriptor, initializationAtThisPoint);
VariableDescriptor variable = extractVariableDescriptorIfAny(instruction);
InitializationPoints initializationAtThisPoint = new InitializationPoints(((WriteValueInstruction) instruction).getElement());
exitInstructionPointsMap.put(variable, initializationAtThisPoint);
}
return pointsMap;
return Pair.create(enterInstructionPointsMap, exitInstructionPointsMap);
}
};
Set<VariableDescriptor> localVariables = collectAllLocalVariables(pseudocode);
Collection<VariableDescriptor> usedVariables = collectUsedVariables(pseudocode);
Collection<VariableDescriptor> declaredVariables = collectDeclaredVariables(subroutine);
Map<VariableDescriptor, InitializationPoints> initialMapForStartInstruction = Maps.newHashMap();
InitializationPoints initialPointsForLocalVariable = new InitializationPoints(true);
for (VariableDescriptor variable : localVariables) {
initialMapForStartInstruction.put(variable, initialPointsForLocalVariable);
InitializationPoints initialPointsForDeclaredVariable = new InitializationPoints(true);
InitializationPoints initialPointsForExternalVariable = new InitializationPoints(false);
for (VariableDescriptor variable : usedVariables) {
if (declaredVariables.contains(variable)) {
initialMapForStartInstruction.put(variable, initialPointsForDeclaredVariable);
}
else {
initialMapForStartInstruction.put(variable, initialPointsForExternalVariable);
}
}
InitializationPoints initialPointsForParameter = new InitializationPoints(false);
for (VariableDescriptor initializedVariable : initializedVariables) {
initialMapForStartInstruction.put(initializedVariable, initialPointsForParameter);
}
final Map<Instruction, Map<VariableDescriptor, InitializationPoints>> dataMap =
JetControlFlowGraphTraverser.traverseInstructionGraphUntilFactsStabilization(pseudocode, instructionsMergeStrategy, Collections.<VariableDescriptor, InitializationPoints>emptyMap(), initialMapForStartInstruction, true);
final Map<Instruction, Pair<Map<VariableDescriptor, InitializationPoints>, Map<VariableDescriptor, InitializationPoints>>> dataMap =
JetControlFlowGraphTraverser.traverseInstructionGraphUntilFactsStabilization(pseudocode,
instructionsMergeStrategy,
Collections.<VariableDescriptor, InitializationPoints>emptyMap(),
initialMapForStartInstruction,
true);
JetControlFlowGraphTraverser.traverseAndAnalyzeInstructionGraph(pseudocode, new JetControlFlowGraphTraverser.InstructionDataAnalyzeStrategy() {
@Override
public void execute(Instruction instruction) {
Map<VariableDescriptor, InitializationPoints> variablesData = dataMap.get(instruction);
Pair<Map<VariableDescriptor, InitializationPoints>, Map<VariableDescriptor, InitializationPoints>> variablesData = dataMap.get(instruction);
Map<VariableDescriptor, InitializationPoints> enterData = variablesData.getFirst();
Map<VariableDescriptor, InitializationPoints> exitData = variablesData.getSecond();
if (instruction instanceof ReadValueInstruction) {
VariableDescriptor variableDescriptor = extractVariableDescriptorIfAny(instruction);
JetElement element = ((ReadValueInstruction) instruction).getElement();
if (element instanceof JetSimpleNameExpression && variableDescriptor instanceof LocalVariableDescriptor) {
InitializationPoints initializationPoints = variablesData.get(variableDescriptor);
assert initializationPoints != null;
if (initializationPoints.canBeUninitialized) {
if (element instanceof JetSimpleNameExpression && variableDescriptor != null &&
(inAnonymousInitializers || variableDescriptor instanceof LocalVariableDescriptor)) {
InitializationPoints exitInitializationPoints = exitData.get(variableDescriptor);
assert exitInitializationPoints != null;
boolean canBeUnitialized = exitInitializationPoints.canBeUninitialized();
if (variableDescriptor instanceof PropertyDescriptor) {
if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor)) {
canBeUnitialized = false;
}
}
if (canBeUnitialized) {
trace.report(Errors.UNINITIALIZED_VARIABLE.on((JetSimpleNameExpression) element, variableDescriptor));
}
}
@@ -328,15 +352,43 @@ public class JetFlowInformationProvider {
JetElement element = ((WriteValueInstruction) instruction).getlValue();
VariableDescriptor variableDescriptor = extractVariableDescriptorIfAny(instruction);
if (element instanceof JetSimpleNameExpression && variableDescriptor != null) {
InitializationPoints initializationPoints = variablesData.get(variableDescriptor);
assert initializationPoints != null;
if (initializationPoints.hasPossibleInitializers() && !variableDescriptor.isVar()) {
trace.report(Errors.VAL_REASSIGNMENT.on((JetSimpleNameExpression) element, variableDescriptor));
InitializationPoints enterInitializationPoints = enterData.get(variableDescriptor);
assert enterInitializationPoints != null;
InitializationPoints exitInitializationPoints = exitData.get(variableDescriptor);
assert exitInitializationPoints != null;
Set<JetElement> possiblePoints = enterInitializationPoints.getPossiblePoints();
boolean hasAnotherInitializer = !possiblePoints.isEmpty() || !enterInitializationPoints.canBeUninitialized;
if (possiblePoints.size() == 1) {
JetElement initializer = possiblePoints.iterator().next();
if (initializer == element.getParent()) {
hasAnotherInitializer = false;
}
}
JetSimpleNameExpression expression = (JetSimpleNameExpression) element;
if (hasAnotherInitializer && !variableDescriptor.isVar()) {
trace.report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor));
}
if (inAnonymousInitializers && variableDescriptor instanceof PropertyDescriptor &&
enterInitializationPoints.canBeUninitialized && !exitInitializationPoints.canBeUninitialized) {
if (expression.getReferencedNameElementType() != JetTokens.FIELD_IDENTIFIER) {
trace.report(Errors.INITIALIZATION_USING_BACKING_FIELD.on(expression, variableDescriptor));
}
}
}
}
}
});
if (inAnonymousInitializers) {
Map<VariableDescriptor, InitializationPoints> lastInfo = dataMap.get(pseudocode.getExitInstruction()).getFirst();
for (Map.Entry<VariableDescriptor, InitializationPoints> entry : lastInfo.entrySet()) {
VariableDescriptor variable = entry.getKey();
if (variable instanceof PropertyDescriptor) {
InitializationPoints initializationPoints = entry.getValue();
trace.record(BindingContext.IS_INITIALIZED, (PropertyDescriptor) variable, !initializationPoints.canBeUninitialized);
}
}
}
}
public void markNotOnlyInvokedFunctionVariables(@NotNull JetElement subroutine, List<? extends VariableDescriptor> variables) {
@@ -395,19 +447,43 @@ public class JetFlowInformationProvider {
return variableDescriptor;
}
private Set<VariableDescriptor> collectAllLocalVariables(Pseudocode pseudocode) {
final Set<VariableDescriptor> localVariables = Sets.newHashSet();
JetControlFlowGraphTraverser.InstructionDataAnalyzeStrategy analyzeStrategy = new JetControlFlowGraphTraverser.InstructionDataAnalyzeStrategy() {
private Collection<VariableDescriptor> collectUsedVariables(Pseudocode pseudocode) {
final Set<VariableDescriptor> usedVariables = Sets.newHashSet();
JetControlFlowGraphTraverser.traverseAndAnalyzeInstructionGraph(pseudocode, new JetControlFlowGraphTraverser.InstructionDataAnalyzeStrategy() {
@Override
public void execute(Instruction instruction) {
VariableDescriptor variableDescriptor = extractVariableDescriptorIfAny(instruction);
if (variableDescriptor != null) {
localVariables.add(variableDescriptor);
usedVariables.add(variableDescriptor);
}
}
};
JetControlFlowGraphTraverser.traverseAndAnalyzeInstructionGraph(pseudocode, analyzeStrategy);
return localVariables;
});
return usedVariables;
}
private Collection<VariableDescriptor> collectDeclaredVariables(JetElement element) {
final Set<VariableDescriptor> concernedVariables = Sets.newHashSet();
element.accept(new JetTreeVisitor<Void>() {
@Override
public Void visitProperty(JetProperty property, Void data) {
DeclarationDescriptor descriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property);
assert descriptor instanceof VariableDescriptor;
concernedVariables.add((VariableDescriptor) descriptor);
return null;
}
@Override
public Void visitForExpression(JetForExpression expression, Void data) {
JetParameter loopParameter = expression.getLoopParameter();
if (loopParameter != null) {
DeclarationDescriptor descriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, loopParameter);
assert descriptor instanceof VariableDescriptor;
concernedVariables.add((VariableDescriptor) descriptor);
}
return null;
}
}, null);
return concernedVariables;
}
private static class InitializationPoints {
@@ -433,8 +509,12 @@ public class JetFlowInformationProvider {
}
}
public boolean hasPossibleInitializers() {
return !possiblePoints.isEmpty();
public Set<JetElement> getPossiblePoints() {
return possiblePoints;
}
public boolean canBeUninitialized() {
return canBeUninitialized;
}
@Override
@@ -144,8 +144,9 @@ public interface Errors {
PsiElementOnlyDiagnosticFactory1<JetSimpleNameExpression, DeclarationDescriptor> UNINITIALIZED_VARIABLE = PsiElementOnlyDiagnosticFactory1.create(ERROR, "Variable ''{0}'' must be initialized", NAME);
PsiElementOnlyDiagnosticFactory1<JetSimpleNameExpression, DeclarationDescriptor> VAL_REASSIGNMENT = PsiElementOnlyDiagnosticFactory1.create(ERROR, "Val can not be reassigned", NAME);
PsiElementOnlyDiagnosticFactory1<JetSimpleNameExpression, DeclarationDescriptor> INITIALIZATION_USING_BACKING_FIELD = PsiElementOnlyDiagnosticFactory1.create(ERROR, "Initialization using backing field required", NAME);
PsiElementOnlyDiagnosticFactory1<JetSimpleNameExpression, DeclarationDescriptor> FUNCTION_PARAMETERS_OF_INLINE_FUNCTION = PsiElementOnlyDiagnosticFactory1.create(ERROR, "Funciton parameters of inline funciton can only be invoked", NAME);
PsiElementOnlyDiagnosticFactory1<JetSimpleNameExpression, DeclarationDescriptor> FUNCTION_PARAMETERS_OF_INLINE_FUNCTION = PsiElementOnlyDiagnosticFactory1.create(ERROR, "Function parameters of inline function can only be invoked", NAME);
SimpleDiagnosticFactory UNREACHABLE_CODE = SimpleDiagnosticFactory.create(ERROR, "Unreachable code");
@@ -296,7 +296,7 @@ public class BodyResolver {
ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
assert primaryConstructor != null;
final JetScope scopeForConstructor = getInnerScopeForConstructor(primaryConstructor, classDescriptor.getScopeForMemberResolution(), true);
ExpressionTypingServices typeInferrer = context.getSemanticServices().getTypeInferrerServices(createFieldAssignTrackingTrace()); // TODO : flow
ExpressionTypingServices typeInferrer = context.getSemanticServices().getTypeInferrerServices(traceForConstructors);
for (JetClassInitializer anonymousInitializer : anonymousInitializers) {
typeInferrer.getType(scopeForConstructor, anonymousInitializer.getBody(), NO_EXPECTED_TYPE);
}
@@ -506,22 +506,6 @@ public class BodyResolver {
});
}
private ObservableBindingTrace createFieldAssignTrackingTrace() {
return new ObservableBindingTrace(traceForConstructors).addHandler(BindingContext.VARIABLE_ASSIGNMENT, new ObservableBindingTrace.RecordHandler<JetExpression, DeclarationDescriptor>() {
@Override
public void handleRecord(WritableSlice<JetExpression, DeclarationDescriptor> jetExpressionBooleanWritableSlice, JetExpression expression, DeclarationDescriptor descriptor) {
if (expression instanceof JetSimpleNameExpression) {
JetSimpleNameExpression variable = (JetSimpleNameExpression) expression;
if (variable.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) {
if (descriptor instanceof PropertyDescriptor) {
traceForMembers.record(BindingContext.IS_INITIALIZED, (PropertyDescriptor) descriptor);
}
}
}
}
});
}
private void resolvePropertyInitializer(JetProperty property, PropertyDescriptor propertyDescriptor, JetExpression initializer, JetScope scope) {
//JetFlowInformationProvider flowInformationProvider = context.getClassDescriptorResolver().computeFlowData(property, initializer); // TODO : flow JET-15
ExpressionTypingServices typeInferrer = context.getSemanticServices().getTypeInferrerServices(traceForConstructors);
@@ -42,8 +42,8 @@ public class TopDownAnalyzer {
new DelegationResolver(context).process();
new OverrideResolver(context).process();
new BodyResolver(context).resolveBehaviorDeclarationBodies();
new DeclarationsChecker(context).process();
new ControlFlowAnalyzer(context, flowDataTraceFactory, declaredLocally).process();
new DeclarationsChecker(context).process();
}
public static void processStandardLibraryNamespace(
@@ -1,106 +0,0 @@
namespace uninitialized_vals {
fun t1(b : Boolean) {
val v : Int
if (<!UNINITIALIZED_VARIABLE!>v<!> == 0) {}
var u: String
if (b) {
u = "s"
}
doSmth(<!UNINITIALIZED_VARIABLE!>u<!>)
var r: String
if (b) {
r = "s"
}
else {
r = "tg"
}
doSmth(r)
var t: String
if (b)
doSmth(<!UNINITIALIZED_VARIABLE!>t<!>)
else
t = "ss"
doSmth(<!UNINITIALIZED_VARIABLE!>t<!>)
val i = 3
doSmth(i)
if (b) {
return;
}
doSmth(i)
if (i is Int) {
return;
}
}
fun t2() {
val s = "ss"
for (i in 0..2) {
doSmth(s)
}
}
fun doSmth(s: String) {}
fun doSmth(i: Int) {}
class A() {}
fun t4(a: A, val b: A, var c: A) {
<!VAL_REASSIGNMENT!>a<!> = A()
<!VAL_REASSIGNMENT!>b<!> = A()
c = A()
}
}
namespace reassigned_vals {
fun t1() {
val a : Int = 1
<!VAL_REASSIGNMENT!>a<!> = 2
var b : Int = 1
b = 3
}
abstract enum class ProtocolState {
WAITING {
override fun signal() = ProtocolState.TALKING
}
TALKING {
override fun signal() = ProtocolState.WAITING
}
abstract fun signal() : ProtocolState
}
fun t2() {
val x: ProtocolState = ProtocolState.WAITING
<!VAL_REASSIGNMENT!>x<!> = x.signal()
<!VAL_REASSIGNMENT!>x<!> = x.signal()
}
fun t3() {
val x = 1
<!VAL_REASSIGNMENT!>x<!> += 2
val y = 3
<!VAL_REASSIGNMENT!>y<!> *= 4
var z = 5
z -= y
}
fun t4() {
for (i in 0..2) {
<!VAL_REASSIGNMENT!>i<!> += 1
fun t5() {
<!VAL_REASSIGNMENT!>i<!> += 3
}
}
}
}
@@ -0,0 +1,250 @@
namespace uninitialized_reassigned_variables
fun doSmth(s: String) {}
fun doSmth(i: Int) {}
// ------------------------------------------------
// uninitialized variables
fun t1(b : Boolean) {
val v : Int
if (<!UNINITIALIZED_VARIABLE!>v<!> == 0) {}
var u: String
if (b) {
u = "s"
}
doSmth(<!UNINITIALIZED_VARIABLE!>u<!>)
var r: String
if (b) {
r = "s"
}
else {
r = "tg"
}
doSmth(r)
var t: String
if (b)
doSmth(<!UNINITIALIZED_VARIABLE!>t<!>)
else
t = "ss"
doSmth(<!UNINITIALIZED_VARIABLE!>t<!>)
val i = 3
doSmth(i)
if (b) {
return;
}
doSmth(i)
if (i is Int) {
return;
}
}
fun t2() {
val s = "ss"
for (i in 0..2) {
doSmth(s)
}
}
class A() {}
fun t4(a: A, val b: A, var c: A) {
<!VAL_REASSIGNMENT!>a<!> = A()
<!VAL_REASSIGNMENT!>b<!> = A()
c = A()
}
// ------------------------------------------------
// reassigned vals
fun t1() {
val a : Int = 1
<!VAL_REASSIGNMENT!>a<!> = 2
var b : Int = 1
b = 3
}
abstract enum class ProtocolState {
WAITING {
override fun signal() = ProtocolState.TALKING
}
TALKING {
override fun signal() = ProtocolState.WAITING
}
abstract fun signal() : ProtocolState
}
fun t2() {
val x: ProtocolState = ProtocolState.WAITING
<!VAL_REASSIGNMENT!>x<!> = x.signal()
<!VAL_REASSIGNMENT!>x<!> = x.signal()
}
fun t3() {
val x = 1
<!VAL_REASSIGNMENT!>x<!> += 2
val y = 3
<!VAL_REASSIGNMENT!>y<!> *= 4
var z = 5
z -= y
}
fun t4() {
for (i in 0..2) {
<!VAL_REASSIGNMENT!>i<!> += 1
fun t5() {
<!VAL_REASSIGNMENT!>i<!> += 3
}
}
}
// ------------------------------------------------
// backing fields
var x = 10
val y = 10
val z = 10
class AnonymousInitializers(var a: String, val b: String) {
{
$a = "30"
a = "s"
<!VAL_REASSIGNMENT!>$b<!> = "3"
<!VAL_REASSIGNMENT!>b<!> = "tt"
}
val i: Int
{
$i = 121
}
{
x = 11
<!UNRESOLVED_REFERENCE!>$y<!> = 11
<!VAL_REASSIGNMENT!>z<!> = 10
}
val j: Int
get() = 20
{
<!VAL_REASSIGNMENT!>$i<!> = 13
<!NO_BACKING_FIELD!>$j<!> = 30
<!VAL_REASSIGNMENT!>j<!> = 34
}
val k: String
{
if (1 < 3) {
<!INITIALIZATION_USING_BACKING_FIELD!>k<!> = "a"
}
else {
$k = "b"
}
}
val l: String
{
if (1 < 3) {
$l = "a"
}
else {
$l = "b"
}
}
val <!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>o<!>: String
{
if (1 < 3) {
$o = "a"
}
}
var m: Int = 30
{
$m = 400
}
val n: Int
{
while (<!UNINITIALIZED_VARIABLE!>n<!> == 0) {
}
$n = 10
while (n == 0) {
}
}
var p = 1
{
p++
}
}
fun reassignFunParams(val a: Int) {
<!VAL_REASSIGNMENT!>a<!> = 1
}
open class Open(a: Int, w: Int) {}
class LocalValsVsProperties(val a: Int, w: Int) : Open(a, w) {
val x : Int
val <!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>y<!> : Int
{
$x = 1
val b = x
}
val b = a
fun foo() {
val r : Int
doSmth(x)
doSmth(y)
doSmth(<!UNINITIALIZED_VARIABLE!>r<!>)
doSmth(a)
}
var xx = w
var yy : Int
{
<!VAL_REASSIGNMENT!>w<!> += 1
$yy = w
}
}
class Outer() {
val a : Int
var b : Int
{
$a = 1
$b = 1
}
class Inner() {
{
<!VAL_REASSIGNMENT!>a<!>++
b++
}
}
fun foo() {
<!VAL_REASSIGNMENT!>a<!>++
b++
}
}
class ForwardAccessToBackingField() { //kt-147
val a = <!UNRESOLVED_REFERENCE, UNINITIALIZED_VARIABLE!>$a<!> // error
val b = <!UNINITIALIZED_VARIABLE!>$c<!> // error
val c = 1
}