diff --git a/plugins/devkit/devkit-core/resources/inspectionDescriptions/ReadOrWriteActionInServiceInitialization.html b/plugins/devkit/devkit-core/resources/inspectionDescriptions/PotentialDeadlockInServiceInitialization.html similarity index 91% rename from plugins/devkit/devkit-core/resources/inspectionDescriptions/ReadOrWriteActionInServiceInitialization.html rename to plugins/devkit/devkit-core/resources/inspectionDescriptions/PotentialDeadlockInServiceInitialization.html index b05eae075069..872ae84ca2ae 100644 --- a/plugins/devkit/devkit-core/resources/inspectionDescriptions/ReadOrWriteActionInServiceInitialization.html +++ b/plugins/devkit/devkit-core/resources/inspectionDescriptions/PotentialDeadlockInServiceInitialization.html @@ -1,6 +1,6 @@ -Reports read and write actions run from the scope of service initialization: +Reports read/write actions and invokeAndWait called from the scope of service initialization: -

Running a read or write action during service initialization may cause deadlocks.

+

Running a read/write action or calling invokeAndWait during service initialization may cause deadlocks.

Examples:

Kotlin:

diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index 1a0d95bb3484..b218c8dd2041 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -459,14 +459,14 @@ implementationClass="org.jetbrains.idea.devkit.inspections.ListenerImplementationMustNotBeDisposableInspection"/> + implementationClass="org.jetbrains.idea.devkit.inspections.PotentialDeadlockInServiceInitializationInspection"/> diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index 1bf7dc2bb009..e4928a4ceb4b 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -683,16 +683,16 @@ inspections.static.initialization.in.extensions.message=Extension point implemen inspections.listener.implementation.must.not.be.disposable.name=Listener implementation implements 'Disposable' inspections.listener.implementation.must.not.implement.disposable=Listener implementation must not implement 'Disposable' -inspection.read.or.write.action.during.service.init.display.name=Read or Write Action run during service initialization -inspection.read.or.write.action.during.service.init.message.read=Do not run read actions during service initialization{0} -inspection.read.or.write.action.during.service.init.message.write=Do not run write actions during service initialization{0} -inspection.read.or.write.action.during.service.init.message.invoke.and.wait=Do not run ''invokeAndWait'' during service initialization{0} -inspection.read.or.write.action.during.service.init.message.context=\ ({0} is called in {1}) -inspection.read.or.write.action.during.service.init.message.context.field=''{0}'' field initializer -inspection.read.or.write.action.during.service.init.message.context.method=''{0}'' method -inspection.read.or.write.action.during.service.init.message.context.constructor=''{0}'' constructor or init block -inspection.read.or.write.action.during.service.init.message.context.static.initializer=static initialization block -inspection.read.or.write.action.during.service.init.message.context.instance.initializer=instance initialization block +inspection.potential.deadlock.during.service.init.display.name=Read or Write Action run during service initialization +inspection.potential.deadlock.during.service.init.message.read=Do not run read actions during service initialization{0} +inspection.potential.deadlock.during.service.init.message.write=Do not run write actions during service initialization{0} +inspection.potential.deadlock.during.service.init.message.invoke.and.wait=Do not run ''invokeAndWait'' during service initialization{0} +inspection.potential.deadlock.during.service.init.message.context=\ ({0} is called in {1}) +inspection.potential.deadlock.during.service.init.message.context.field=''{0}'' field initializer +inspection.potential.deadlock.during.service.init.message.context.method=''{0}'' method +inspection.potential.deadlock.during.service.init.message.context.constructor=''{0}'' constructor or init block +inspection.potential.deadlock.during.service.init.message.context.static.initializer=static initialization block +inspection.potential.deadlock.during.service.init.message.context.instance.initializer=instance initialization block inspection.plugin.xml.registration.check.display.name=Plugin.xml registration check diff --git a/plugins/devkit/devkit-core/src/inspections/ReadOrWriteActionInServiceInitializationInspection.kt b/plugins/devkit/devkit-core/src/inspections/PotentialDeadlockInServiceInitializationInspection.kt similarity index 65% rename from plugins/devkit/devkit-core/src/inspections/ReadOrWriteActionInServiceInitializationInspection.kt rename to plugins/devkit/devkit-core/src/inspections/PotentialDeadlockInServiceInitializationInspection.kt index f18b7a033287..9dfb5d9d37c3 100644 --- a/plugins/devkit/devkit-core/src/inspections/ReadOrWriteActionInServiceInitializationInspection.kt +++ b/plugins/devkit/devkit-core/src/inspections/PotentialDeadlockInServiceInitializationInspection.kt @@ -43,7 +43,7 @@ private val PERSISTENT_STATE_COMPONENT_INIT_METHOD_NAMES = arrayOf( private const val VISIT_CHILDREN = false private const val SKIP_CHILDREN = true -internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastInspectionBase() { +internal class PotentialDeadlockInServiceInitializationInspection : DevKitUastInspectionBase() { override fun buildInternalVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return UastHintedVisitorAdapter.create( @@ -71,12 +71,12 @@ internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastIn } } - private fun isCalledDuringServiceInitialization(readOrWriteActionCall: UCallExpression, callContextHolder: CallContextHolder): Boolean { - val uClass = readOrWriteActionCall.getContainingNonCompanionObjectClass() ?: return false + private fun isCalledDuringServiceInitialization(forbiddenCall: UCallExpression, callContextHolder: CallContextHolder): Boolean { + val uClass = forbiddenCall.getContainingNonCompanionObjectClass() ?: return false return isService(uClass) && - (isCalledDuringInit(readOrWriteActionCall) || - isInMethodCalledDuringInit(uClass, readOrWriteActionCall, callContextHolder) || - isCalledDuringPersistentStateComponentInit(uClass, readOrWriteActionCall, callContextHolder)) + (isCalledDuringInit(forbiddenCall) || + isInMethodCalledDuringInit(uClass, forbiddenCall, callContextHolder) || + isCalledDuringPersistentStateComponentInit(uClass, forbiddenCall, callContextHolder)) } private fun UElement.getContainingNonCompanionObjectClass(): UClass? { @@ -84,41 +84,37 @@ internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastIn return if (uClass.javaPsi.name == "Companion") uClass.getParentOfType() else uClass } - private fun isCalledDuringInit(readOrWriteActionCall: UCallExpression): Boolean { - return !isCalledInAnonymousClassOrLambda(readOrWriteActionCall) && - (isCalledInConstructor(readOrWriteActionCall) || - isCalledInInitBlock(readOrWriteActionCall) || - isCalledInFieldAssignment(readOrWriteActionCall)) + private fun isCalledDuringInit(forbiddenCall: UCallExpression): Boolean { + return !isCalledInAnonymousClassOrLambda(forbiddenCall) && + (isCalledInConstructor(forbiddenCall) || + isCalledInInitBlock(forbiddenCall) || + isCalledInFieldAssignment(forbiddenCall)) } - private fun isCalledInAnonymousClassOrLambda(readOrWriteActionCall: UCallExpression): Boolean { + private fun isCalledInAnonymousClassOrLambda(forbiddenActionCall: UCallExpression): Boolean { // do not report calls in listener, alarm, etc. registration - return readOrWriteActionCall.getParentOfType() != null || - readOrWriteActionCall.getParentOfType() != null + return forbiddenActionCall.getParentOfType() != null || + forbiddenActionCall.getParentOfType() != null } - private fun isCalledInConstructor(readOrWriteActionCall: UCallExpression): Boolean { - return readOrWriteActionCall.getContainingUMethod()?.isConstructor == true + private fun isCalledInConstructor(forbiddenCall: UCallExpression): Boolean { + return forbiddenCall.getContainingUMethod()?.isConstructor == true } - private fun isCalledInInitBlock(readOrWriteActionCall: UCallExpression): Boolean { - return readOrWriteActionCall.getParentOfType() != null + private fun isCalledInInitBlock(forbiddenCall: UCallExpression): Boolean { + return forbiddenCall.getParentOfType() != null } - private fun isCalledInFieldAssignment(readOrWriteActionCall: UCallExpression): Boolean { - return readOrWriteActionCall.getParentOfType() != null + private fun isCalledInFieldAssignment(forbiddenCall: UCallExpression): Boolean { + return forbiddenCall.getParentOfType() != null } - private fun isInMethodCalledDuringInit( - serviceClass: UClass, - readOrWriteActionCall: UCallExpression, - callContextHolder: CallContextHolder, - ): Boolean { - if (isCalledInAnonymousClassOrLambda(readOrWriteActionCall)) return false + private fun isInMethodCalledDuringInit(serviceClass: UClass, forbiddenCall: UCallExpression, callContextHolder: CallContextHolder): Boolean { + if (isCalledInAnonymousClassOrLambda(forbiddenCall)) return false val companionObject = serviceClass.innerClasses.firstOrNull { it.javaPsi.name == "Companion" } val initializationElements: List = serviceClass.getInitializationElements() + (companionObject?.getInitializationElements() ?: emptyList()) - val containingMethod = readOrWriteActionCall.getContainingUMethod() ?: return false + val containingMethod = forbiddenCall.getContainingUMethod() ?: return false return containingMethod.isCalledInAnyOf(initializationElements, callContextHolder) } @@ -154,30 +150,30 @@ internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastIn private fun getContextText(calledMethod: UMethod, caller: UElement): @Nls String? { val callerName = when (caller) { is UMethod -> - if (caller.isConstructor) message("inspection.read.or.write.action.during.service.init.message.context.constructor", caller.name) - else message("inspection.read.or.write.action.during.service.init.message.context.method", caller.name) + if (caller.isConstructor) message("inspection.potential.deadlock.during.service.init.message.context.constructor", caller.name) + else message("inspection.potential.deadlock.during.service.init.message.context.method", caller.name) is UField -> - message("inspection.read.or.write.action.during.service.init.message.context.field", @Suppress("UElementAsPsi") caller.name) + message("inspection.potential.deadlock.during.service.init.message.context.field", @Suppress("UElementAsPsi") caller.name) is UClassInitializer -> // there is no API in UAST to distinguish companion object context, but we can live with it - if (caller.isStatic) message("inspection.read.or.write.action.during.service.init.message.context.static.initializer") - else message("inspection.read.or.write.action.during.service.init.message.context.instance.initializer") + if (caller.isStatic) message("inspection.potential.deadlock.during.service.init.message.context.static.initializer") + else message("inspection.potential.deadlock.during.service.init.message.context.instance.initializer") else -> return null } - return message("inspection.read.or.write.action.during.service.init.message.context", "'${calledMethod.name}'", callerName) + return message("inspection.potential.deadlock.during.service.init.message.context", "'${calledMethod.name}'", callerName) } private fun isCalledDuringPersistentStateComponentInit( serviceClass: UClass, - readOrWriteActionCall: UCallExpression, + forbiddenCall: UCallExpression, callContextHolder: CallContextHolder, ): Boolean { return isPersistentStateComponent(serviceClass) && - (isCalledDuringPersistentStateComponentInitMethods(readOrWriteActionCall) || - isInMethodCalledDuringPersistentStateComponentInit(serviceClass, readOrWriteActionCall, callContextHolder)) + (isCalledDuringPersistentStateComponentInitMethods(forbiddenCall) || + isInMethodCalledDuringPersistentStateComponentInit(serviceClass, forbiddenCall, callContextHolder)) } private fun isPersistentStateComponent(serviceClass: UClass): Boolean { @@ -185,19 +181,19 @@ internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastIn return JvmInheritanceUtil.isInheritor(servicePsiClass, PersistentStateComponent::class.java.canonicalName) } - private fun isCalledDuringPersistentStateComponentInitMethods(readOrWriteActionCall: UCallExpression): Boolean { - if (isCalledInAnonymousClassOrLambda(readOrWriteActionCall)) return false - val containingMethod = readOrWriteActionCall.getContainingUMethod() ?: return false + private fun isCalledDuringPersistentStateComponentInitMethods(forbiddenCall: UCallExpression): Boolean { + if (isCalledInAnonymousClassOrLambda(forbiddenCall)) return false + val containingMethod = forbiddenCall.getContainingUMethod() ?: return false return PERSISTENT_STATE_COMPONENT_INIT_METHOD_NAMES.contains(containingMethod.name) } private fun isInMethodCalledDuringPersistentStateComponentInit( - serviceClass: UClass, readOrWriteActionCall: UCallExpression, + serviceClass: UClass, forbiddenCall: UCallExpression, callContextHolder: CallContextHolder, ): Boolean { - if (isCalledInAnonymousClassOrLambda(readOrWriteActionCall)) return false + if (isCalledInAnonymousClassOrLambda(forbiddenCall)) return false val lifecycleMethods: List = serviceClass.methods.filter { PERSISTENT_STATE_COMPONENT_INIT_METHOD_NAMES.contains(it.name) } - val containingMethod = readOrWriteActionCall.getContainingUMethod() ?: return false + val containingMethod = forbiddenCall.getContainingUMethod() ?: return false return containingMethod.isCalledInAnyOf(lifecycleMethods, callContextHolder) } @@ -210,9 +206,9 @@ internal class ReadOrWriteActionInServiceInitializationInspection : DevKitUastIn val anchor = uCallExpression.methodIdentifier?.sourcePsi ?: return val callContext = callContextHolder.value ?: "" val message = when (actionType) { - CallType.READ -> message("inspection.read.or.write.action.during.service.init.message.read", callContext) - CallType.WRITE -> message("inspection.read.or.write.action.during.service.init.message.write", callContext) - CallType.INVOKE_AND_WAIT -> message("inspection.read.or.write.action.during.service.init.message.invoke.and.wait", callContext) + CallType.READ -> message("inspection.potential.deadlock.during.service.init.message.read", callContext) + CallType.WRITE -> message("inspection.potential.deadlock.during.service.init.message.write", callContext) + CallType.INVOKE_AND_WAIT -> message("inspection.potential.deadlock.during.service.init.message.invoke.and.wait", callContext) } holder.registerProblem(anchor, message) } diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java index 5e29387eab66..86c47941711e 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java @@ -31,7 +31,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase "ThreadingConcurrency", "CallingMethodShouldBeRequiresBlockingContext", "IncorrectProcessCanceledExceptionHandling", - "ReadOrWriteActionInServiceInitialization" + "PotentialDeadlockInServiceInitialization" ).sorted().toList(); /** diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTest.kt similarity index 99% rename from plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTest.kt rename to plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTest.kt index b350f2052ba2..c1503bcccace 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTest.kt +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTest.kt @@ -1,7 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.inspections -class ReadOrWriteActionInServiceInitializationInspectionTest : ReadOrWriteActionInServiceInitializationInspectionTestBase() { +class PotentialDeadlockInServiceInitializationInspectionTest : PotentialDeadlockInServiceInitializationInspectionTestBase() { fun `test read and write actions are reported in a light service`() { myFixture.configureByText("TestService.java", getServiceWithReadAndWriteActionsCalledDuringInit(true)) diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtReadOrWriteActionInServiceInitializationInspectionTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtPotentialDeadlockInServiceInitializationInspectionTest.kt similarity index 98% rename from plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtReadOrWriteActionInServiceInitializationInspectionTest.kt rename to plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtPotentialDeadlockInServiceInitializationInspectionTest.kt index 79d163f409e3..ec34208c395e 100644 --- a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtReadOrWriteActionInServiceInitializationInspectionTest.kt +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtPotentialDeadlockInServiceInitializationInspectionTest.kt @@ -1,9 +1,9 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.kotlin.inspections -import org.jetbrains.idea.devkit.inspections.ReadOrWriteActionInServiceInitializationInspectionTestBase +import org.jetbrains.idea.devkit.inspections.PotentialDeadlockInServiceInitializationInspectionTestBase -class KtReadOrWriteActionInServiceInitializationInspectionTest : ReadOrWriteActionInServiceInitializationInspectionTestBase() { +class KtPotentialDeadlockInServiceInitializationInspectionTest : PotentialDeadlockInServiceInitializationInspectionTestBase() { fun `test read and write actions are reported in a light service`() { myFixture.configureByText("TestService.kt", getServiceWithReadAndWriteActionsCalledDuringInit(true)) diff --git a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTestBase.kt b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTestBase.kt similarity index 94% rename from plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTestBase.kt rename to plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTestBase.kt index d7e5ba330711..b8de3dbc815d 100644 --- a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/ReadOrWriteActionInServiceInitializationInspectionTestBase.kt +++ b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/inspections/PotentialDeadlockInServiceInitializationInspectionTestBase.kt @@ -17,7 +17,7 @@ import com.intellij.util.Alarm import com.intellij.util.PathUtil import org.jetbrains.idea.devkit.inspections.quickfix.DevKitInspectionFixTestBase -abstract class ReadOrWriteActionInServiceInitializationInspectionTestBase : DevKitInspectionFixTestBase() { +abstract class PotentialDeadlockInServiceInitializationInspectionTestBase : DevKitInspectionFixTestBase() { override fun tuneFixture(moduleBuilder: JavaModuleFixtureBuilder<*>) { // too many classes to add manually via addClass, so using the slower libraries approach: @@ -39,7 +39,7 @@ abstract class ReadOrWriteActionInServiceInitializationInspectionTestBase : DevK KotlinTester.configureKotlinStdLib(it) } IndexingTestUtil.waitUntilIndexesAreReady(project) - myFixture.enableInspections(ReadOrWriteActionInServiceInitializationInspection()) + myFixture.enableInspections(PotentialDeadlockInServiceInitializationInspection()) } }