From 9740a1498a77079b4a412b101a6d29b1d0a80979 Mon Sep 17 00:00:00 2001 From: LepilkinaElena Date: Fri, 6 Mar 2020 14:58:09 +0300 Subject: [PATCH] Added Native-specific checker for properties of top level singletons (#3172) --- .../nativeTests/topLevelSingleton.kt | 114 +++++++++ .../nativeTests/topLevelSingleton.txt | 217 ++++++++++++++++++ .../DiagnosticsNativeTestGenerated.java | 5 + .../diagnostics/DefaultErrorMessagesNative.kt | 6 + .../resolve/konan/diagnostics/ErrorsNative.kt | 7 +- .../NativeTopLevelSingletonChecker.kt | 50 ++++ .../platform/NativePlatformConfigurator.kt | 6 +- 7 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt create mode 100644 compiler/testData/diagnostics/nativeTests/topLevelSingleton.txt create mode 100644 native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt diff --git a/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt new file mode 100644 index 0000000000000..6b15a4a9dac33 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt @@ -0,0 +1,114 @@ +// FILE: annotation.kt +package kotlin.native.concurrent + +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class ThreadLocal + +// FILE: test.kt +import kotlin.native.concurrent.ThreadLocal + +import kotlin.reflect.KProperty + +class Delegate { + val value: Int = 10 + operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return value + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + } +} + +class AtomicInt(var value: Int) +object Foo { + var field1: Int = 10 + val backer2 = AtomicInt(0) + var field2: Int + get() = backer2.value + set(value: Int) { backer2.value = value } +} + +object Foo1 { + var field1: Int = 10 + set(value: Int) { backer2.value = value } + val backer2 = AtomicInt(0) +} + +object WithDelegate { + var field1: Int by Delegate() +} + +@ThreadLocal +object Bar { + var field1: Int = 10 + var field2: String? = null +} + +class Foo2 { + companion object { + var field1: Int = 10 + val backer2 = AtomicInt(0) + var field2: Int + get() = backer2.value + set(value: Int) { + backer2.value = value + } + } +} + +class Bar2 { + @ThreadLocal + companion object { + var field1: Int = 10 + var field2: String? = null + } +} + +@ThreadLocal +enum class Color(var rgb: Int) { + RED(0xFF0000), + GREEN(0x00FF00), + BLUE(0x0000FF) +} + +enum class Color1(var rgb: Int) { + RED(0xFF0000), + GREEN(0x00FF00), + BLUE(0x0000FF); + + init { this.rgb += 1 } +} + +@ThreadLocal +var a = 3 +enum class Color2() { + RED(), + GREEN(), + BLUE(); + + var rgb: Int = 2 + set(value: Int) { + a = value + } +} + +enum class Color3() { + RED(), + GREEN(), + BLUE(); + + var field1: Int by Delegate() +} + +enum class Color4 { + RED { + var a = 2 + override fun foo() { a = 42 } + }, + GREEN, + BLUE; + open fun foo() {} +} + +var topLevelProperty = "Global var" \ No newline at end of file diff --git a/compiler/testData/diagnostics/nativeTests/topLevelSingleton.txt b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.txt new file mode 100644 index 0000000000000..00506d6b6b0d7 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.txt @@ -0,0 +1,217 @@ +package + +@kotlin.native.concurrent.ThreadLocal public var a: kotlin.Int +public var topLevelProperty: kotlin.String + +public final class AtomicInt { + public constructor AtomicInt(/*0*/ value: kotlin.Int) + public final var value: 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 +} + +@kotlin.native.concurrent.ThreadLocal public object Bar { + private constructor Bar() + public final var field1: kotlin.Int + public final var field2: 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 Bar2 { + public constructor Bar2() + 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 + + @kotlin.native.concurrent.ThreadLocal public companion object Companion { + private constructor Companion() + public final var field1: kotlin.Int + public final var field2: 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 + } +} + +@kotlin.native.concurrent.ThreadLocal public final enum class Color : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color(/*0*/ rgb: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var rgb: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class Color1 : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color1(/*0*/ rgb: kotlin.Int) + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var rgb: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color1): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color1 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class Color2 : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color2() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + public final var rgb: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color2): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color2 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class Color3 : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color3() + public final var field1: kotlin.Int + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color3): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color3 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final enum class Color4 : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color4() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color4): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun foo(): kotlin.Unit + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color4 + public final /*synthesized*/ fun values(): kotlin.Array +} + +public final class Delegate { + public constructor Delegate() + public final val value: kotlin.Int = 10 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun getValue(/*0*/ thisRef: kotlin.Any?, /*1*/ property: kotlin.reflect.KProperty<*>): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final operator fun setValue(/*0*/ thisRef: kotlin.Any?, /*1*/ property: kotlin.reflect.KProperty<*>, /*2*/ value: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public object Foo { + private constructor Foo() + public final val backer2: AtomicInt + public final var field1: kotlin.Int + public final var field2: 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 object Foo1 { + private constructor Foo1() + public final val backer2: AtomicInt + public final var field1: 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 Foo2 { + public constructor Foo2() + 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 companion object Companion { + private constructor Companion() + public final val backer2: AtomicInt + public final var field1: kotlin.Int + public final var field2: 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 object WithDelegate { + private constructor WithDelegate() + public final var field1: 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 +} + +package kotlin { + + package kotlin.native { + + package kotlin.native.concurrent { + + @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.CLASS}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class ThreadLocal : kotlin.Annotation { + public constructor ThreadLocal() + 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 index 0ffca1fc5b898..301f66c51d515 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java @@ -37,4 +37,9 @@ public void testSharedImmutable() throws Exception { public void testThrows() throws Exception { runTest("compiler/testData/diagnostics/nativeTests/throws.kt"); } + + @TestMetadata("topLevelSingleton.kt") + public void testTopLevelSingleton() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt"); + } } 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 c31aa1a0b72a4..751364906514c 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 @@ -25,6 +25,12 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { "@SharedImmutable is applicable only to val with backing field or to property with delegation" ) put(ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL, "@SharedImmutable is applicable only to top level declarations") + put( + ErrorsNative.VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL, + "Variable in singleton without @ThreadLocal can't be changed after initialization" + ) + put(ErrorsNative.ENUM_THREAD_LOCAL_INAPPLICABLE, "@ThreadLocal isn't applicable to enum classes") + put(ErrorsNative.VARIABLE_IN_ENUM, "Variable in enum class can't be changed after initialization") } } 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 8c5102bc9d1d1..bd258685231e6 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 @@ -24,7 +24,12 @@ object ErrorsNative { val INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY = DiagnosticFactory0.create(Severity.ERROR) @JvmField val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL = DiagnosticFactory0.create(Severity.ERROR) - + @JvmField + val VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL = DiagnosticFactory0.create(Severity.WARNING) + @JvmField + val ENUM_THREAD_LOCAL_INAPPLICABLE = DiagnosticFactory0.create(Severity.ERROR) + @JvmField + val VARIABLE_IN_ENUM = DiagnosticFactory0.create(Severity.WARNING) init { Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java) } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt new file mode 100644 index 0000000000000..f2fb983645ab6 --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt @@ -0,0 +1,50 @@ +/* + * 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.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.hasBackingField + +object NativeTopLevelSingletonChecker : DeclarationChecker { + private val threadLocalFqName = FqName("kotlin.native.concurrent.ThreadLocal") + + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + // @ThreadLocal on enum has no effect. + if (descriptor is ClassDescriptor && DescriptorUtils.isEnumClass(descriptor)) { + descriptor.annotations.findAnnotation(threadLocalFqName)?.let { + val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration + context.trace.report(ErrorsNative.ENUM_THREAD_LOCAL_INAPPLICABLE.on(reportLocation)) + } + } + + // Check variables inside singletons. + if (descriptor !is PropertyDescriptor) return + (descriptor.containingDeclaration as? ClassDescriptor)?.let { parent -> + val hasBackingFieldWithDefaultSetter = descriptor.hasBackingField(context.trace.bindingContext) && + descriptor.setter?.isDefault == true + val hasDelegate = if (declaration is KtProperty) declaration.delegate != null else false + + if (descriptor.isVar && (DescriptorUtils.isEnumClass(parent) || DescriptorUtils.isEnumEntry(parent)) && + hasBackingFieldWithDefaultSetter && !hasDelegate) { + context.trace.report(ErrorsNative.VARIABLE_IN_ENUM.on(declaration)) + } else if (parent.kind.isSingleton) { + parent.annotations.findAnnotation(threadLocalFqName) ?: run { + if (descriptor.isVar && !hasDelegate && hasBackingFieldWithDefaultSetter) { + context.trace.report(ErrorsNative.VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL.on(declaration)) + } + } + } + } + } +} 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 47d3af98d55d6..7e2078624cc3e 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 @@ -16,10 +16,14 @@ import org.jetbrains.kotlin.resolve.inline.ReasonableInlineRule import org.jetbrains.kotlin.resolve.jvm.checkers.SuperCallWithDefaultArgumentsChecker import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeSharedImmutableChecker import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeThrowsChecker +import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeTopLevelSingletonChecker object NativePlatformConfigurator : PlatformConfiguratorBase( additionalCallCheckers = listOf(SuperCallWithDefaultArgumentsChecker()), - additionalDeclarationCheckers = listOf(NativeThrowsChecker, NativeSharedImmutableChecker) + additionalDeclarationCheckers = listOf( + NativeThrowsChecker, NativeSharedImmutableChecker, + NativeTopLevelSingletonChecker + ) ) { override fun configureModuleComponents(container: StorageComponentContainer) { container.useInstance(NativeInliningRule)