diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 3a0a80000f0..508d1d4bc08 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -16543,6 +16543,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/inlineClasses/reservedMembersAndConstructsInsideInlineClass.kt"); } + @Test + @TestMetadata("synchronizedForbidden.kt") + public void testSynchronizedForbidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt"); + } + @Test @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 3ae6431e0a2..0009b947ca8 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -16543,6 +16543,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/inlineClasses/reservedMembersAndConstructsInsideInlineClass.kt"); } + @Test + @TestMetadata("synchronizedForbidden.kt") + public void testSynchronizedForbidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt"); + } + @Test @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index a048ebd3835..cbba43406b1 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -16543,6 +16543,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/inlineClasses/reservedMembersAndConstructsInsideInlineClass.kt"); } + @Test + @TestMetadata("synchronizedForbidden.kt") + public void testSynchronizedForbidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt"); + } + @Test @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt index 1971095c342..a004600dabf 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/declarationCheckers.kt @@ -199,6 +199,8 @@ class SynchronizedAnnotationChecker : DeclarationChecker { context.trace.report(ErrorsJvm.SYNCHRONIZED_ON_INLINE.on(annotationEntry)) } else if (descriptor.isSuspend) { context.trace.report(ErrorsJvm.SYNCHRONIZED_ON_SUSPEND.on(annotationEntry)) + } else if (descriptor.containingDeclaration.let { it is ClassDescriptor && it.isValue }) { + context.trace.report(ErrorsJvm.SYNCHRONIZED_ON_VALUE_CLASS.on(annotationEntry)) } } } 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 edde8630323..499d79b18a6 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 @@ -62,6 +62,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(VOLATILE_ON_DELEGATE, "'@Volatile' annotation cannot be used on delegated properties"); MAP.put(SYNCHRONIZED_ON_ABSTRACT, "'@Synchronized' annotation cannot be used on abstract functions"); MAP.put(SYNCHRONIZED_ON_INLINE, "'@Synchronized' annotation has no effect on inline functions"); + MAP.put(SYNCHRONIZED_ON_VALUE_CLASS, "'@Synchronized' annotation has no effect on value classes"); MAP.put(SYNCHRONIZED_IN_INTERFACE, "'@Synchronized' annotation cannot be used on interface members"); MAP.put(EXTERNAL_DECLARATION_CANNOT_BE_ABSTRACT, "External declaration can not be abstract"); MAP.put(EXTERNAL_DECLARATION_CANNOT_HAVE_BODY, "External declaration can not have a body"); 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 eb0f58def8d..35748778626 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 @@ -48,6 +48,7 @@ public interface ErrorsJvm { DiagnosticFactory0 VOLATILE_ON_DELEGATE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SYNCHRONIZED_ON_ABSTRACT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SYNCHRONIZED_ON_INLINE = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 SYNCHRONIZED_ON_VALUE_CLASS = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 SYNCHRONIZED_ON_SUSPEND = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 SYNCHRONIZED_IN_INTERFACE = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index e8b2395832e..e955a7be508 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -874,6 +874,7 @@ public interface Errors { DiagnosticFactory2 DEPRECATED_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 IMPLICIT_BOXING_IN_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 FORBIDDEN_IDENTITY_EQUALS = DiagnosticFactory2.create(ERROR); + DiagnosticFactory1 FORBIDDEN_SYNCHRONIZED_BY_VALUE_CLASSES_OR_PRIMITIVES = DiagnosticFactory1.create(WARNING); DiagnosticFactory2 DEPRECATED_BINARY_MOD = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 FORBIDDEN_BINARY_MOD = DiagnosticFactory2.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 1b19ee0f3aa..71f824f2490 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -460,6 +460,7 @@ public class DefaultErrorMessages { MAP.put(DEPRECATED_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is deprecated", RENDER_TYPE, RENDER_TYPE); MAP.put(IMPLICIT_BOXING_IN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} can be unstable because of implicit boxing", RENDER_TYPE, RENDER_TYPE); MAP.put(FORBIDDEN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is forbidden", RENDER_TYPE, RENDER_TYPE); + MAP.put(FORBIDDEN_SYNCHRONIZED_BY_VALUE_CLASSES_OR_PRIMITIVES, "Synchronizing by {0} is forbidden", RENDER_TYPE); MAP.put(DEPRECATED_BINARY_MOD, "Deprecated convention for ''{0}''. Use ''{1}''", NAME, STRING); MAP.put(FORBIDDEN_BINARY_MOD, "Convention for ''{0}'' is forbidden. Use ''{1}''", NAME, STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 2e70e2535d3..996c27e2678 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -50,7 +50,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf( ) private val DEFAULT_CALL_CHECKERS = listOf( - CapturingInClosureChecker(), InlineCheckerWrapper(), SafeCallChecker(), TrailingCommaCallChecker, + CapturingInClosureChecker(), InlineCheckerWrapper(), SynchronizedByValueChecker(), SafeCallChecker(), TrailingCommaCallChecker, DeprecatedCallChecker, CallReturnsArrayOfNothingChecker(), InfixCallChecker(), OperatorCallChecker(), ConstructorHeaderCallChecker, ProtectedConstructorCallChecker, ApiVersionCallChecker, CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/SynchronizedByValueChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/SynchronizedByValueChecker.kt new file mode 100644 index 00000000000..8087c14129a --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/SynchronizedByValueChecker.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2021 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.calls.checkers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.isTopLevelInPackage +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.getType +import org.jetbrains.kotlin.resolve.constants.IntegerLiteralTypeConstructor +import org.jetbrains.kotlin.types.KotlinType + +class SynchronizedByValueChecker : CallChecker { + private fun KotlinType.isValueOrPrimitive(): Boolean = KotlinBuiltIns.isPrimitiveType(this) + || constructor.declarationDescriptor.let { it is ClassDescriptor && it.isValue } + || constructor.let { manyTypes -> manyTypes is IntegerLiteralTypeConstructor && manyTypes.possibleTypes.any { it.isValueOrPrimitive() } } + + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + if (resolvedCall.resultingDescriptor?.isTopLevelInPackage("synchronized", "kotlin") != true) return + val argument = resolvedCall.valueArgumentsByIndex?.get(0)?.arguments?.firstOrNull() ?: return + val type = argument.getArgumentExpression()?.getType(context.trace.bindingContext) ?: return + if (type.isValueOrPrimitive()) { + context.trace.report(Errors.FORBIDDEN_SYNCHRONIZED_BY_VALUE_CLASSES_OR_PRIMITIVES.on(reportOn, type)) + } + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.fir.kt b/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.fir.kt new file mode 100644 index 00000000000..b5dc2a5d867 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.fir.kt @@ -0,0 +1,88 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// SKIP_TXT +// KT-49339 + +@JvmInline +value class A(val a: Int) { + @get:Synchronized + val f0 + get() = Unit + + @Synchronized + fun f1() = Unit + + @Synchronized + fun String.f2() = Unit + + @get:Synchronized + val String.f3 + get() = Unit + + @get:Synchronized + val A.f4 + get() = Unit + + @Synchronized + fun A.f5() = Unit + + val f6 + @Synchronized + get() = Unit + + val A.f7 + @Synchronized + get() = Unit + + val String.f8 + @Synchronized + get() = Unit +} + +class Usual { + + @get:Synchronized + val A.f9 + get() = Unit + + @Synchronized + fun A.f10() = Unit + + val A.f11 + @Synchronized + get() = Unit +} + +@Synchronized +fun A.f12() = Unit + +@get:Synchronized +val A.f13 + get() = Unit + +val A.f14 + @Synchronized + get() = Unit + +fun main() { + val a = A(2) + synchronized(a) {} + synchronized(2) {} + synchronized(0x2) {} + synchronized(2U) {} + synchronized(true) {} + synchronized(2L) {} + synchronized(2.to(1).first) {} + synchronized(2.toByte()) {} + synchronized(2UL) {} + synchronized(2F) {} + synchronized(2.0) {} + synchronized('2') {} + synchronized(block={}, lock='2') + synchronized(block={}, lock=a) + for (b in listOf(a)) { + synchronized(b) {} + synchronized(b.to(1).first) {} + synchronized(block={}, lock=a) + } +} diff --git a/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt b/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt new file mode 100644 index 00000000000..6fa88646629 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt @@ -0,0 +1,88 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// SKIP_TXT +// KT-49339 + +@JvmInline +value class A(val a: Int) { + @get:Synchronized + val f0 + get() = Unit + + @Synchronized + fun f1() = Unit + + @Synchronized + fun String.f2() = Unit + + @get:Synchronized + val String.f3 + get() = Unit + + @get:Synchronized + val A.f4 + get() = Unit + + @Synchronized + fun A.f5() = Unit + + val f6 + @Synchronized + get() = Unit + + val A.f7 + @Synchronized + get() = Unit + + val String.f8 + @Synchronized + get() = Unit +} + +class Usual { + + @get:Synchronized + val A.f9 + get() = Unit + + @Synchronized + fun A.f10() = Unit + + val A.f11 + @Synchronized + get() = Unit +} + +@Synchronized +fun A.f12() = Unit + +@get:Synchronized +val A.f13 + get() = Unit + +val A.f14 + @Synchronized + get() = Unit + +fun main() { + val a = A(2) + synchronized(a) {} + synchronized(2) {} + synchronized(0x2) {} + synchronized(2U) {} + synchronized(true) {} + synchronized(2L) {} + synchronized(2.to(1).first) {} + synchronized(2.toByte()) {} + synchronized(2UL) {} + synchronized(2F) {} + synchronized(2.0) {} + synchronized('2') {} + synchronized(block={}, lock='2') + synchronized(block={}, lock=a) + for (b in listOf(a)) { + synchronized(b) {} + synchronized(b.to(1).first) {} + synchronized(block={}, lock=a) + } +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 740c8b276d2..48d6684d09a 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -16549,6 +16549,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/inlineClasses/reservedMembersAndConstructsInsideInlineClass.kt"); } + @Test + @TestMetadata("synchronizedForbidden.kt") + public void testSynchronizedForbidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt"); + } + @Test @TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt") public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {