Prevent circular secondary constructors delegation

This commit is contained in:
Denis Zharkov
2015-02-12 18:40:53 +03:00
parent 5388bd7cdc
commit 525ddf9eb0
6 changed files with 153 additions and 0 deletions
@@ -153,6 +153,10 @@ public interface Errors {
DiagnosticFactory0<JetNullableType> NULLABLE_SUPERTYPE = DiagnosticFactory0.create(ERROR, NULLABLE_TYPE);
DiagnosticFactory0<JetTypeReference> DYNAMIC_SUPERTYPE = DiagnosticFactory0.create(ERROR);
// Secondary constructors
DiagnosticFactory0<JetConstructorDelegationCall> CYCLIC_CONSTRUCTOR_DELEGATION_CALL = DiagnosticFactory0.create(ERROR);
// Trait-specific
DiagnosticFactory0<JetModifierListOwner> ABSTRACT_MODIFIER_IN_TRAIT = DiagnosticFactory0
@@ -413,6 +413,8 @@ public class DefaultErrorMessages {
MAP.put(FINAL_SUPERTYPE, "This type is final, so it cannot be inherited from");
MAP.put(SINGLETON_IN_SUPERTYPE, "Cannot inherit from a singleton");
MAP.put(CYCLIC_CONSTRUCTOR_DELEGATION_CALL, "There's a cycle in the delegation calls chain");
MAP.put(ILLEGAL_SELECTOR, "Expression ''{0}'' cannot be a selector (occur after a dot)", STRING);
MAP.put(NO_TAIL_CALLS_FOUND, "A function is marked as tail-recursive but no tail calls are found");
@@ -127,6 +127,11 @@ public class BodyResolver {
for (Map.Entry<JetSecondaryConstructor, ConstructorDescriptor> entry : c.getSecondaryConstructors().entrySet()) {
resolveSecondaryConstructorBody(c, entry.getKey(), entry.getValue());
}
if (c.getSecondaryConstructors().isEmpty()) return;
Set<ConstructorDescriptor> visitedConstructors = Sets.newHashSet();
for (Map.Entry<JetSecondaryConstructor, ConstructorDescriptor> entry : c.getSecondaryConstructors().entrySet()) {
checkCyclicConstructorDelegationCall(entry.getValue(), visitedConstructors);
}
}
private void resolveSecondaryConstructorBody(
@@ -174,6 +179,58 @@ public class BodyResolver {
}
}
private void checkCyclicConstructorDelegationCall(
@NotNull ConstructorDescriptor constructorDescriptor,
@NotNull Set<ConstructorDescriptor> visitedConstructors
) {
if (visitedConstructors.contains(constructorDescriptor)) return;
// if visit constructor that is already in current chain
// such constructor is on cycle
Set<ConstructorDescriptor> visitedInCurrentChain = Sets.newHashSet();
ConstructorDescriptor currentConstructorDescriptor = constructorDescriptor;
while (true) {
visitedInCurrentChain.add(currentConstructorDescriptor);
ConstructorDescriptor delegatedConstructorDescriptor = getDelegatedConstructor(currentConstructorDescriptor);
if (delegatedConstructorDescriptor == null) break;
// if next delegation call is super or primary constructor or already visited
if (!constructorDescriptor.getContainingDeclaration().equals(delegatedConstructorDescriptor.getContainingDeclaration()) ||
delegatedConstructorDescriptor.isPrimary() ||
visitedConstructors.contains(delegatedConstructorDescriptor)) {
break;
}
if (visitedInCurrentChain.contains(delegatedConstructorDescriptor)) {
reportEachConstructorOnCycle(delegatedConstructorDescriptor);
break;
}
currentConstructorDescriptor = delegatedConstructorDescriptor;
}
visitedConstructors.addAll(visitedInCurrentChain);
}
private void reportEachConstructorOnCycle(@NotNull ConstructorDescriptor startConstructor) {
ConstructorDescriptor currentConstructor = startConstructor;
do {
PsiElement constructorToReport = DescriptorToSourceUtils.descriptorToDeclaration(currentConstructor);
if (constructorToReport != null) {
JetConstructorDelegationCall call = ((JetSecondaryConstructor) constructorToReport).getDelegationCall();
assert call != null : "resolved call can't be null";
trace.report(CYCLIC_CONSTRUCTOR_DELEGATION_CALL.on(call));
}
currentConstructor = getDelegatedConstructor(currentConstructor);
assert currentConstructor != null : "Delegated constructor should not be null in cycle";
} while (startConstructor != currentConstructor);
}
@Nullable
private ConstructorDescriptor getDelegatedConstructor(@NotNull ConstructorDescriptor constructor) {
ResolvedCall<ConstructorDescriptor> call = trace.get(CONSTRUCTOR_RESOLVED_DELEGATION_CALL, constructor);
return call == null ? null : call.getResultingDescriptor();
}
public void resolveBodies(@NotNull BodiesResolveContext c) {
resolveBehaviorDeclarationBodies(c);
controlFlowAnalyzer.process(c);
@@ -0,0 +1,39 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class A1 {
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this()<!> {}
}
class A2(x: Byte) {
constructor(x1: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, 1)<!> {}
constructor(x1: Int, x2: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, 2)<!> {}
constructor(x1: Int, x2: Int, x3: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1)<!> {}
// delegating to previously declared cycle
constructor(x1: Double): this(1) {}
// delegating to cycle declared after
constructor(x1: String): this(1L) {}
constructor(x1: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, 1L)<!> {}
constructor(x1: Long, x2: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, 2L)<!> {}
constructor(x1: Long, x2: Long, x3: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1)<!> {}
// no cycle, just call to primary constuctor
constructor(x1: Double, x2: Double): this(x1, x2, 1.0) {}
constructor(x1: Double, x2: Double, x3: Double): this(x1, x2, x3, 1.0) {}
constructor(x1: Double, x2: Double, x3: Double, x4: Double): this(1.toByte()) {}
constructor(): this("x", "y") {}
constructor(x1: String, x2: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, "")<!> {}
constructor(x1: String, x2: String, x3: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2)<!> {}
}
open class B(x: Byte)
class A : B {
// no cycle, just call to super constuctor
constructor(x1: Double, x2: Double): this(x1, x2, 1.0) {}
constructor(x1: Double, x2: Double, x3: Double): this(x1, x2, x3, 1.0) {}
constructor(x1: Double, x2: Double, x3: Double, x4: Double): super(1.toByte()) {}
}
@@ -0,0 +1,45 @@
package
internal final class A : B {
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double)
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double)
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double, /*3*/ x4: kotlin.Double)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
internal final class A1 {
public constructor A1()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
internal final class A2 {
public constructor A2()
public constructor A2(/*0*/ x: kotlin.Byte)
public constructor A2(/*0*/ x1: kotlin.Double)
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double)
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double)
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double, /*3*/ x4: kotlin.Double)
public constructor A2(/*0*/ x1: kotlin.Int)
public constructor A2(/*0*/ x1: kotlin.Int, /*1*/ x2: kotlin.Int)
public constructor A2(/*0*/ x1: kotlin.Int, /*1*/ x2: kotlin.Int, /*2*/ x3: kotlin.Int)
public constructor A2(/*0*/ x1: kotlin.Long)
public constructor A2(/*0*/ x1: kotlin.Long, /*1*/ x2: kotlin.Long)
public constructor A2(/*0*/ x1: kotlin.Long, /*1*/ x2: kotlin.Long, /*2*/ x3: kotlin.Long)
public constructor A2(/*0*/ x1: kotlin.String)
public constructor A2(/*0*/ x1: kotlin.String, /*1*/ x2: kotlin.String)
public constructor A2(/*0*/ x1: kotlin.String, /*1*/ x2: kotlin.String, /*2*/ x3: kotlin.String)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
internal open class B {
public constructor B(/*0*/ x: kotlin.Byte)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -10415,6 +10415,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("cyclicDelegationCalls.kt")
public void testCyclicDelegationCalls() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/cyclicDelegationCalls.kt");
doTest(fileName);
}
@TestMetadata("generics.kt")
public void testGenerics() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/generics.kt");