Prohibit protected constructor calls that are not super-calls in subtypes
#KT-11649 Fixed
This commit is contained in:
@@ -73,6 +73,8 @@ public interface Errors {
|
|||||||
DiagnosticFactory3.create(ERROR);
|
DiagnosticFactory3.create(ERROR);
|
||||||
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_MEMBER = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
|
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
|
// Exposed visibility group
|
||||||
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_PROPERTY_TYPE = DiagnosticFactory3.create(ERROR);
|
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_PROPERTY_TYPE = DiagnosticFactory3.create(ERROR);
|
||||||
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_FUNCTION_RETURN_TYPE = DiagnosticFactory3.create(ERROR);
|
DiagnosticFactory3<PsiElement, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility> EXPOSED_FUNCTION_RETURN_TYPE = DiagnosticFactory3.create(ERROR);
|
||||||
|
|||||||
+2
@@ -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_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(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_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_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);
|
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(),
|
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker(),
|
||||||
SafeCallChecker(), InvokeConventionChecker(), CallReturnsArrayOfNothingChecker(),
|
SafeCallChecker(), InvokeConventionChecker(), CallReturnsArrayOfNothingChecker(),
|
||||||
ConstructorHeaderCallChecker)
|
ConstructorHeaderCallChecker, ProtectedConstructorCallChecker)
|
||||||
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
||||||
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator())
|
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator())
|
||||||
|
|
||||||
|
|||||||
+59
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -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
|
||||||
|
}
|
||||||
+32
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -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
|
||||||
|
}
|
||||||
+30
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -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);
|
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")
|
@TestMetadata("protectedCallOnSubClass.kt")
|
||||||
public void testProtectedCallOnSubClass() throws Exception {
|
public void testProtectedCallOnSubClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/protectedCallOnSubClass.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/scopes/protectedVisibility/protectedCallOnSubClass.kt");
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ public class Visibilities {
|
|||||||
@NotNull DeclarationDescriptorWithVisibility whatDeclaration,
|
@NotNull DeclarationDescriptorWithVisibility whatDeclaration,
|
||||||
@NotNull ClassDescriptor fromClass
|
@NotNull ClassDescriptor fromClass
|
||||||
) {
|
) {
|
||||||
|
//noinspection deprecation
|
||||||
|
if (receiver == FALSE_IF_PROTECTED) return false;
|
||||||
|
|
||||||
// Do not check receiver for non-callable declarations
|
// Do not check receiver for non-callable declarations
|
||||||
if (!(whatDeclaration instanceof CallableMemberDescriptor)) return true;
|
if (!(whatDeclaration instanceof CallableMemberDescriptor)) return true;
|
||||||
// Constructor accessibility check is performed manually
|
// Constructor accessibility check is performed manually
|
||||||
@@ -282,7 +285,6 @@ public class Visibilities {
|
|||||||
return findInvisibleMember(IRRELEVANT_RECEIVER, what, from) == null;
|
return findInvisibleMember(IRRELEVANT_RECEIVER, what, from) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static DeclarationDescriptorWithVisibility findInvisibleMember(
|
public static DeclarationDescriptorWithVisibility findInvisibleMember(
|
||||||
@Nullable ReceiverValue receiver,
|
@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) {
|
public static boolean isPrivate(@NotNull Visibility visibility) {
|
||||||
return visibility == PRIVATE || visibility == PRIVATE_TO_THIS;
|
return visibility == PRIVATE || visibility == PRIVATE_TO_THIS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user