Prohibit protected constructor calls that are not super-calls in subtypes

#KT-11649 Fixed
This commit is contained in:
Denis Zharkov
2016-03-28 18:22:08 +03:00
parent 816f14a927
commit 92be4aee9c
12 changed files with 228 additions and 2 deletions
@@ -73,6 +73,8 @@ public interface Errors {
DiagnosticFactory3.create(ERROR);
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_MEMBER = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
DiagnosticFactory1<KtExpression, ConstructorDescriptor> PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL = DiagnosticFactory1.create(ERROR);
// Exposed visibility group
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_PROPERTY_TYPE = DiagnosticFactory3.create(ERROR);
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_FUNCTION_RETURN_TYPE = DiagnosticFactory3.create(ERROR);
@@ -97,6 +97,8 @@ public class DefaultErrorMessages {
MAP.put(INVISIBLE_REFERENCE, "Cannot access ''{0}'': it is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
MAP.put(INVISIBLE_MEMBER, "Cannot access ''{0}'': it is ''{1}'' in {2}", NAME, TO_STRING, NAME_OF_PARENT_OR_FILE);
MAP.put(PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL, "Protected constructor ''{0}'' from other classes can only be used in super-call", Renderers.SHORT_NAMES_IN_TYPES);
MAP.put(EXPOSED_PROPERTY_TYPE, "''{0}'' property exposes its ''{2}'' type{1}", TO_STRING, TO_STRING, TO_STRING);
MAP.put(EXPOSED_FUNCTION_RETURN_TYPE, "''{0}'' function exposes its ''{2}'' return type{1}", TO_STRING, TO_STRING, TO_STRING);
MAP.put(EXPOSED_PARAMETER_TYPE, "''{0}'' function exposes its ''{2}'' parameter type{1}", TO_STRING, TO_STRING, TO_STRING);
@@ -69,7 +69,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker(),
SafeCallChecker(), InvokeConventionChecker(), CallReturnsArrayOfNothingChecker(),
ConstructorHeaderCallChecker)
ConstructorHeaderCallChecker, ProtectedConstructorCallChecker)
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator())
@@ -0,0 +1,59 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls.checkers
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression
import org.jetbrains.kotlin.psi.KtConstructorDelegationReferenceExpression
import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
object ProtectedConstructorCallChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, context: BasicCallResolutionContext) {
val descriptor = resolvedCall.resultingDescriptor as? ConstructorDescriptor ?: return
val constructorOwner = descriptor.containingDeclaration.original
val scopeOwner = context.scope.ownerDescriptor
if (descriptor.visibility.normalize() != Visibilities.PROTECTED) return
// Error already reported
if (!Visibilities.isVisibleWithAnyReceiver(descriptor, scopeOwner)) return
val calleeExpression = resolvedCall.call.calleeExpression ?: return
// Permit constructor super-calls
when (calleeExpression) {
is KtConstructorCalleeExpression -> if (calleeExpression.parent is KtSuperTypeCallEntry) return
is KtConstructorDelegationReferenceExpression -> return
}
// Permit calls within class
if (scopeOwner.parentsWithSelf.any { it.original === constructorOwner }) return
// Using FALSE_IF_PROTECTED helps us to check that descriptor doesn't meet conditions of java package/static-protected
// (i.e. being in the same package)
// And without ProtectedConstructorCallChecker such calls would be allowed only because they are performed within subclass
// of constructor owner
@Suppress("DEPRECATION")
if (Visibilities.findInvisibleMember(Visibilities.FALSE_IF_PROTECTED, descriptor, scopeOwner) == descriptor) {
context.trace.report(Errors.PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL.on(calleeExpression, descriptor))
}
}
}
@@ -0,0 +1,26 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
open class A protected constructor(x: Int) {
protected constructor() : this(1)
public constructor(x: Double) : this(3)
}
class B4 : A(1) {
init {
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>()
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>(1)
A(5.0)
}
fun foo() {
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>()
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>(1)
A(5.0)
object : A() {}
object : A(1) {}
object : A(5.0) {}
class Local : A()
}
}
@@ -0,0 +1,18 @@
package
public open class A {
protected constructor A()
public constructor A(/*0*/ x: kotlin.Double)
protected constructor A(/*0*/ x: kotlin.Int)
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
}
public final class B4 : A {
public constructor B4()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,32 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: abc/A.java
package abc;
public class A {
protected A() {}
protected A(int x) {}
public A(double x) {}
}
// FILE: main.kt
import abc.*
class B4 : A(1) {
init {
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>()
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>(1)
A(5.0)
}
fun foo() {
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>()
<!PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL!>A<!>(1)
A(5.0)
object : A() {}
object : A(1) {}
object : A(5.0) {}
class Local : A()
}
}
@@ -0,0 +1,9 @@
package
public final class B4 : abc.A {
public constructor B4()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,30 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FILE: A.java
public class A {
protected A() {}
protected A(int x) {}
public A(double x) {}
}
// FILE: main.kt
class B4 : A(1) {
init {
A()
A(1)
A(5.0)
}
fun foo() {
A()
A(1)
A(5.0)
object : A() {}
object : A(1) {}
object : A(5.0) {}
class Local : A()
}
}
@@ -0,0 +1,18 @@
package
public open class A {
protected/*protected and package*/ constructor A()
public constructor A(/*0*/ x: kotlin.Double)
protected/*protected and package*/ constructor A(/*0*/ x: kotlin.Int)
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
}
public final class B4 : A {
public constructor B4()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -15506,6 +15506,24 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("nonSuperCallConstructor.kt")
public void testNonSuperCallConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/nonSuperCallConstructor.kt");
doTest(fileName);
}
@TestMetadata("nonSuperCallConstructorJavaDifferentPackage.kt")
public void testNonSuperCallConstructorJavaDifferentPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/nonSuperCallConstructorJavaDifferentPackage.kt");
doTest(fileName);
}
@TestMetadata("nonSuperCallConstructorJavaSamePackage.kt")
public void testNonSuperCallConstructorJavaSamePackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/nonSuperCallConstructorJavaSamePackage.kt");
doTest(fileName);
}
@TestMetadata("protectedCallOnSubClass.kt")
public void testProtectedCallOnSubClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/protectedCallOnSubClass.kt");
@@ -160,6 +160,9 @@ public class Visibilities {
@NotNull DeclarationDescriptorWithVisibility whatDeclaration,
@NotNull ClassDescriptor fromClass
) {
//noinspection deprecation
if (receiver == FALSE_IF_PROTECTED) return false;
// Do not check receiver for non-callable declarations
if (!(whatDeclaration instanceof CallableMemberDescriptor)) return true;
// Constructor accessibility check is performed manually
@@ -282,7 +285,6 @@ public class Visibilities {
return findInvisibleMember(IRRELEVANT_RECEIVER, what, from) == null;
}
@Nullable
public static DeclarationDescriptorWithVisibility findInvisibleMember(
@Nullable ReceiverValue receiver,
@@ -362,6 +364,16 @@ public class Visibilities {
}
};
// This constant is not intended to use somewhere else from
@Deprecated
public static final ReceiverValue FALSE_IF_PROTECTED = new ReceiverValue() {
@NotNull
@Override
public KotlinType getType() {
throw new IllegalStateException("This method should not be called");
}
};
public static boolean isPrivate(@NotNull Visibility visibility) {
return visibility == PRIVATE || visibility == PRIVATE_TO_THIS;
}