From 5eedba390306f52b375751fb291d81ac5a8a3881 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 3 Feb 2020 11:21:40 +0300 Subject: [PATCH] Add Native-specific frontend checker for `@Throws` --- .../diagnostics/nativeTests/throws.kt | 76 ++++++++++ .../diagnostics/nativeTests/throws.txt | 143 ++++++++++++++++++ .../DiagnosticsNativeTestGenerated.java | 35 +++++ .../generators/tests/GenerateCompilerTests.kt | 4 + .../diagnostics/DefaultErrorMessagesNative.kt | 8 +- .../resolve/konan/diagnostics/ErrorsNative.kt | 12 ++ .../konan/diagnostics/NativeThrowsChecker.kt | 63 ++++++++ .../platform/NativePlatformConfigurator.kt | 4 +- 8 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/diagnostics/nativeTests/throws.kt create mode 100644 compiler/testData/diagnostics/nativeTests/throws.txt create mode 100644 compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java create mode 100644 native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt diff --git a/compiler/testData/diagnostics/nativeTests/throws.kt b/compiler/testData/diagnostics/nativeTests/throws.kt new file mode 100644 index 00000000000..b5cc8972322 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/throws.kt @@ -0,0 +1,76 @@ +// FILE: annotation.kt +package kotlin.native + +annotation class Throws(vararg val exceptionClasses: kotlin.reflect.KClass) + +// FILE: test.kt +class Exception1 : Throwable() +class Exception2 : Throwable() +class Exception3 : Throwable() + +@Throws +fun foo() {} + +interface Base1 { + @Throws(Exception1::class) fun foo() +} + +class HasThrowsOnOverride : Base1 { + @Throws(Exception1::class) override fun foo() {} +} + +class HasThrowsWithEmptyListOnOverride : Base1 { + @Throws override fun foo() {} +} + +interface Base2 { + @Throws(Exception2::class) fun foo() +} + +open class InheritsDifferentThrows1 : Base1, Base2 { + override fun foo() {} +} + +class InheritsDifferentThrowsThroughSameClass1 : InheritsDifferentThrows1() { + override fun foo() {} +} + +interface Base3 { + @Throws(Exception3::class) fun foo() +} + +class InheritsDifferentThrows2 : InheritsDifferentThrows1(), Base3 { + override fun foo() {} +} + +interface Base4 { + @Throws(Exception1::class) fun foo() +} + +class InheritsSameThrows : Base1, Base4 { + override fun foo() {} +} + +interface Base5 { + @Throws(Exception1::class, Exception2::class) fun foo() +} + +interface Base6 { + @Throws(Exception2::class, Exception1::class) fun foo() +} + +class InheritsSameThrowsMultiple : Base5, Base6 { + override fun foo() {} +} + +fun withLocalClass() { + class LocalException : Throwable() + + abstract class Base7 { + @Throws(Exception1::class, LocalException::class) abstract fun foo() + } + + class InheritsDifferentThrowsLocal : Base1, Base7() { + override fun foo() {} + } +} diff --git a/compiler/testData/diagnostics/nativeTests/throws.txt b/compiler/testData/diagnostics/nativeTests/throws.txt new file mode 100644 index 00000000000..8ec6afac55d --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/throws.txt @@ -0,0 +1,143 @@ +package + +@kotlin.native.Throws(exceptionClasses = {}) public fun foo(): kotlin.Unit +public fun withLocalClass(): kotlin.Unit + +public interface Base1 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception1::class}) public abstract 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 interface Base2 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception2::class}) public abstract 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 interface Base3 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception3::class}) public abstract 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 interface Base4 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception1::class}) public abstract 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 interface Base5 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception1::class, Exception2::class}) public abstract 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 interface Base6 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception2::class, Exception1::class}) public abstract 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 final class Exception1 : kotlin.Throwable { + public constructor Exception1() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: 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 +} + +public final class Exception2 : kotlin.Throwable { + public constructor Exception2() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: 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 +} + +public final class Exception3 : kotlin.Throwable { + public constructor Exception3() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: 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 +} + +public final class HasThrowsOnOverride : Base1 { + public constructor HasThrowsOnOverride() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {Exception1::class}) public open override /*1*/ 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 final class HasThrowsWithEmptyListOnOverride : Base1 { + public constructor HasThrowsWithEmptyListOnOverride() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.native.Throws(exceptionClasses = {}) public open override /*1*/ 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 open class InheritsDifferentThrows1 : Base1, Base2 { + public constructor InheritsDifferentThrows1() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class InheritsDifferentThrows2 : InheritsDifferentThrows1, Base3 { + public constructor InheritsDifferentThrows2() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class InheritsDifferentThrowsThroughSameClass1 : InheritsDifferentThrows1 { + public constructor InheritsDifferentThrowsThroughSameClass1() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ 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 final class InheritsSameThrows : Base1, Base4 { + public constructor InheritsSameThrows() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class InheritsSameThrowsMultiple : Base5, Base6 { + public constructor InheritsSameThrowsMultiple() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +package kotlin { + + package kotlin.native { + + public final annotation class Throws : kotlin.Annotation { + public constructor Throws(/*0*/ vararg exceptionClasses: kotlin.reflect.KClass /*kotlin.Array>*/) + public final val exceptionClasses: kotlin.Array> + 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/DiagnosticsNativeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java new file mode 100644 index 00000000000..6da036c98b4 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.checkers; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/testData/diagnostics/nativeTests") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class DiagnosticsNativeTestGenerated extends AbstractDiagnosticsNativeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInNativeTests() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/nativeTests"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("throws.kt") + public void testThrows() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/throws.kt"); + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt index 44560bdeb48..b6e68270084 100644 --- a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt +++ b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt @@ -106,6 +106,10 @@ fun main(args: Array) { model("diagnostics/testsWithJsStdLibAndBackendCompilation") } + testClass { + model("diagnostics/nativeTests") + } + testClass { model("diagnostics/testWithModifiedMockJdk") } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt index f0e1a127917..ea27dbd2a7c 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt @@ -7,10 +7,16 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.rendering.Renderers private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { DiagnosticFactoryToRendererMap("Native").apply { - + put(ErrorsNative.THROWS_LIST_EMPTY, "@Throws must have non-empty class list") + put(ErrorsNative.THROWS_ON_OVERRIDE, "@Throws is prohibited for overridden members") + put( + ErrorsNative.INCOMPATIBLE_THROWS_INHERITED, "Member inherits different @Throws filters from {0}", + Renderers.commaSeparated(Renderers.NAME) + ) } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt index d1dd17dfb5c..37cb2ecd181 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt @@ -5,9 +5,21 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement object ErrorsNative { + @JvmField + val THROWS_LIST_EMPTY = DiagnosticFactory0.create(Severity.ERROR) + @JvmField + val THROWS_ON_OVERRIDE = DiagnosticFactory0.create(Severity.ERROR) + @JvmField + val INCOMPATIBLE_THROWS_INHERITED = DiagnosticFactory1.create>(Severity.ERROR) init { Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java) diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt new file mode 100644 index 00000000000..203b208be37 --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.konan.diagnostics + + +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.constants.ArrayValue +import org.jetbrains.kotlin.resolve.constants.ConstantValue +import org.jetbrains.kotlin.resolve.descriptorUtil.firstArgument +import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors + +object NativeThrowsChecker : DeclarationChecker { + private val throwsFqName = FqName("kotlin.native.Throws") + + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + checkForIncompatibleMultipleInheritance(declaration, descriptor, context) + + val throwsAnnotation = descriptor.annotations.findAnnotation(throwsFqName) ?: return + val element = DescriptorToSourceUtils.getSourceFromAnnotation(throwsAnnotation) ?: declaration + + if (descriptor is CallableMemberDescriptor && descriptor.overriddenDescriptors.isNotEmpty()) { + context.trace.report(ErrorsNative.THROWS_ON_OVERRIDE.on(element)) + return + } + + if (throwsAnnotation.getVariadicArguments().isEmpty()) { + context.trace.report(ErrorsNative.THROWS_LIST_EMPTY.on(element)) + } + } + + private fun checkForIncompatibleMultipleInheritance( + declaration: KtDeclaration, + descriptor: DeclarationDescriptor, + context: DeclarationCheckerContext + ) { + if (descriptor !is CallableMemberDescriptor) return + + if (descriptor.overriddenDescriptors.size < 2) return // No multiple inheritance here. + + val incompatible = descriptor.findOriginalTopMostOverriddenDescriptors() + .distinctBy { it.annotations.findAnnotation(throwsFqName)?.getVariadicArguments()?.toSet() } + + if (incompatible.size < 2) return + + context.trace.report(ErrorsNative.INCOMPATIBLE_THROWS_INHERITED.on(declaration, incompatible.map { it.containingDeclaration })) + } + + private fun AnnotationDescriptor.getVariadicArguments(): List> { + val argument = this.firstArgument() as? ArrayValue ?: return emptyList() + return argument.value + } + +} diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt index bc6c35913c5..b5ee4c23142 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt @@ -14,9 +14,11 @@ import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.resolve.inline.ReasonableInlineRule import org.jetbrains.kotlin.resolve.jvm.checkers.SuperCallWithDefaultArgumentsChecker +import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeThrowsChecker object NativePlatformConfigurator : PlatformConfiguratorBase( - additionalCallCheckers = listOf(SuperCallWithDefaultArgumentsChecker()) + additionalCallCheckers = listOf(SuperCallWithDefaultArgumentsChecker()), + additionalDeclarationCheckers = listOf(NativeThrowsChecker) ) { override fun configureModuleComponents(container: StorageComponentContainer) { container.useInstance(NativeInliningRule)