From ea4f1670917580ebaf14ea261b77e9fa8a30c5b3 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Wed, 25 Nov 2015 20:21:31 +0300 Subject: [PATCH] Calls to non-@JvmStatic protected members of companion objects from subclasses are now errors (unsupported yet) --- ...otectedInSuperClassCompanionCallChecker.kt | 51 +++++++++++ .../diagnostics/DefaultErrorMessagesJvm.java | 1 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 1 + .../jvm/platform/JvmPlatformConfigurator.kt | 3 +- ...classCallsProtectedInheritedByCompanion.kt | 11 +++ ...DerivedClassCallsProtectedFromCompanion.kt | 10 +++ .../tests/scopes/VisibilityInClassObject.kt | 2 +- .../CallCompanionProtectedNonStatic.kt | 87 +++++++++++++++++++ .../CallCompanionProtectedNonStatic.txt | 87 +++++++++++++++++++ .../DiagnosticsTestWithStdLibGenerated.java | 6 ++ .../BlackBoxCodegenTestGenerated.java | 12 +++ 11 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ProtectedInSuperClassCompanionCallChecker.kt create mode 100644 compiler/testData/codegen/box/objects/classCallsProtectedInheritedByCompanion.kt create mode 100644 compiler/testData/codegen/box/objects/nestedDerivedClassCallsProtectedFromCompanion.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.txt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ProtectedInSuperClassCompanionCallChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ProtectedInSuperClassCompanionCallChecker.kt new file mode 100644 index 00000000000..6c15bdf0575 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ProtectedInSuperClassCompanionCallChecker.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2015 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.jvm.checkers + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm + +class ProtectedInSuperClassCompanionCallChecker : CallChecker { + + override fun check(resolvedCall: ResolvedCall, context: BasicCallResolutionContext) { + val targetDescriptor = resolvedCall.resultingDescriptor.original + // Protected non-JVM static + if (targetDescriptor.visibility != Visibilities.PROTECTED) return + if (targetDescriptor.hasJvmStaticAnnotation()) return + val containerDescriptor = targetDescriptor.containingDeclaration + // Declared in companion object + if (containerDescriptor is ClassDescriptor && containerDescriptor.isCompanionObject) { + val companionDescriptor = containerDescriptor + val companionOwnerDescriptor = companionDescriptor.containingDeclaration as? ClassDescriptor ?: return + val parentClassDescriptors = context.scope.ownerDescriptor.parentsWithSelf.filterIsInstance() + // Called from within a derived class + if (!parentClassDescriptors.any { DescriptorUtils.isSubclass(it, companionOwnerDescriptor) }) return + // Called not within the same companion object or its owner class + if (companionDescriptor !in parentClassDescriptors && companionOwnerDescriptor !in parentClassDescriptors) { + context.trace.report(ErrorsJvm.SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC.on(resolvedCall.call.callElement)); + } + } + } +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 6f5ee083e49..68ded5b251a 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -81,6 +81,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { "Expected type does not accept nulls in {0}, but the value may be null in {1}", Renderers.TO_STRING, Renderers.TO_STRING); MAP.put(ErrorsJvm.INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER, "Interfaces can't call Java default methods via super"); + MAP.put(ErrorsJvm.SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC, "Using non-JVM static members protected in the superclass companion is unsupported yet"); MAP.put(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA, "Enum argument can be null in Java, but exhaustive when contains no null branch"); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 0b8158673ac..39af7b89d0e 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -68,6 +68,7 @@ public interface ErrorsJvm { DiagnosticFactory1 ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory0.create(WARNING); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index b08a38a88e3..2511c1fb9a0 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -46,7 +46,8 @@ public object JvmPlatformConfigurator : PlatformConfigurator( NeedSyntheticChecker(), JavaAnnotationCallChecker(), TraitDefaultMethodCallChecker(), - JavaClassOnCompanionChecker() + JavaClassOnCompanionChecker(), + ProtectedInSuperClassCompanionCallChecker() ), additionalTypeCheckers = listOf( diff --git a/compiler/testData/codegen/box/objects/classCallsProtectedInheritedByCompanion.kt b/compiler/testData/codegen/box/objects/classCallsProtectedInheritedByCompanion.kt new file mode 100644 index 00000000000..454a3815e24 --- /dev/null +++ b/compiler/testData/codegen/box/objects/classCallsProtectedInheritedByCompanion.kt @@ -0,0 +1,11 @@ +open class A { + protected fun foo() = "OK" +} + +class B { + companion object : A() + + fun bar() = foo() +} + +fun box() = B().bar() diff --git a/compiler/testData/codegen/box/objects/nestedDerivedClassCallsProtectedFromCompanion.kt b/compiler/testData/codegen/box/objects/nestedDerivedClassCallsProtectedFromCompanion.kt new file mode 100644 index 00000000000..a9a05baa90e --- /dev/null +++ b/compiler/testData/codegen/box/objects/nestedDerivedClassCallsProtectedFromCompanion.kt @@ -0,0 +1,10 @@ +open class A { + companion object { + protected fun foo() = "OK" + } + class B : A() { + fun bar() = foo() + } +} + +fun box() = A.B().bar() diff --git a/compiler/testData/diagnostics/tests/scopes/VisibilityInClassObject.kt b/compiler/testData/diagnostics/tests/scopes/VisibilityInClassObject.kt index 0fa69d95043..41591ef908c 100644 --- a/compiler/testData/diagnostics/tests/scopes/VisibilityInClassObject.kt +++ b/compiler/testData/diagnostics/tests/scopes/VisibilityInClassObject.kt @@ -28,6 +28,6 @@ class B: A() { devNull(A.internal_val) devNull(A.public_val) devNull(A.private_val) - devNull(A.protected_val) + devNull(A.protected_val) } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.kt b/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.kt new file mode 100644 index 00000000000..2da2f08a17e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.kt @@ -0,0 +1,87 @@ +open class VeryBase { + protected fun baz() {} +} + +open class Base { + protected fun foo() { + bar() // Ok + baz() // Ok + } + + inner class Inner { + fun fromInner() { + foo() // Ok + bar() // Ok + gav() // Ok + baz() // Ok + } + } + + class NestedDerived : Base() { + fun fromNestedDerived() { + foo() // Ok + bar() // Ok + gav() // Ok + baz() // Ok + } + } + + companion object : VeryBase() { + protected fun bar() {} + + @JvmStatic protected fun gav() {} + + class Nested { + fun fromNested() { + bar() // Ok + gav() // Ok + } + } + } +} + +class Derived : Base() { + fun test() { + foo() // Ok + gav() // Ok + bar() + baz() + } + + inner class DerivedInner { + fun fromDerivedInner() { + foo() // Ok + gav() // Ok + bar() + baz() + } + } + + companion object { + fun test2() { + gav() // Ok + bar() + baz() + } + } +} + +class Other { + fun test(base: Base, derived: Derived) { + base.foo() + base.gav() + base.bar() + derived.foo() + derived.gav() + derived.bar() + } +} + +fun top(base: Base, derived: Derived) { + base.foo() + base.bar() + base.gav() + derived.foo() + derived.bar() + derived.gav() +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.txt b/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.txt new file mode 100644 index 00000000000..6e98801e08c --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.txt @@ -0,0 +1,87 @@ +package + +public fun top(/*0*/ base: Base, /*1*/ derived: Derived): kotlin.Unit + +public open class Base { + public constructor Base() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected 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 + + public companion object Companion : VeryBase { + private constructor Companion() + protected final fun bar(): kotlin.Unit + protected final override /*1*/ /*fake_override*/ fun baz(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.jvm.JvmStatic() protected final fun gav(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class Nested { + public constructor Nested() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun fromNested(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + + public final inner class Inner { + public constructor Inner() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun fromInner(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class NestedDerived : Base { + public constructor NestedDerived() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected final override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit + public final fun fromNestedDerived(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class Derived : Base { + public constructor Derived() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected final override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final fun test(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + 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 final fun test2(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final inner class DerivedInner { + public constructor DerivedInner() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun fromDerivedInner(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class Other { + public constructor Other() + 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 final fun test(/*0*/ base: Base, /*1*/ derived: Derived): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class VeryBase { + public constructor VeryBase() + protected final fun baz(): kotlin.Unit + 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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index fb4a1205d87..96fffd0a38c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -35,6 +35,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("CallCompanionProtectedNonStatic.kt") + public void testCallCompanionProtectedNonStatic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/CallCompanionProtectedNonStatic.kt"); + doTest(fileName); + } + @TestMetadata("CallToMainRedeclaredInMultiFile.kt") public void testCallToMainRedeclaredInMultiFile() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/CallToMainRedeclaredInMultiFile.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java index 665af4441b7..371277bd778 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -5545,6 +5545,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("classCallsProtectedInheritedByCompanion.kt") + public void testClassCallsProtectedInheritedByCompanion() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/objects/classCallsProtectedInheritedByCompanion.kt"); + doTest(fileName); + } + @TestMetadata("flist.kt") public void testFlist() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/objects/flist.kt"); @@ -5671,6 +5677,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("nestedDerivedClassCallsProtectedFromCompanion.kt") + public void testNestedDerivedClassCallsProtectedFromCompanion() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/objects/nestedDerivedClassCallsProtectedFromCompanion.kt"); + doTest(fileName); + } + @TestMetadata("nestedObjectWithSuperclass.kt") public void testNestedObjectWithSuperclass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/objects/nestedObjectWithSuperclass.kt");