Rename ReadOrWriteActionInServiceInitializationInspection to PotentialDeadlockInServiceInitializationInspection

GitOrigin-RevId: bcf700c2c1a8c7afa9d3d257f010413e6a0e95fb
This commit is contained in:
Karol Lewandowski
2024-09-13 16:40:23 +02:00
committed by intellij-monorepo-bot
parent 1897480db8
commit 2beb8da5c2
8 changed files with 62 additions and 66 deletions

View File

@@ -1,6 +1,6 @@
<html>
<body>
Reports read and write actions run from the scope of service initialization:
Reports read/write actions and <code>invokeAndWait</code> called from the scope of service initialization:
<ul>
<li>service constructors and initialization blocks (including static)</li>
<li>service companion object's initialization blocks (Kotlin)</li>
@@ -13,7 +13,7 @@ Reports read and write actions run from the scope of service initialization:
</ul>
</ul>
<p>Running a read or write action during service initialization may cause deadlocks.</p>
<p>Running a read/write action or calling <code>invokeAndWait</code> during service initialization may cause deadlocks.</p>
<p><b>Examples:</b></p>
<p>Kotlin:</p>

View File

@@ -459,14 +459,14 @@
implementationClass="org.jetbrains.idea.devkit.inspections.ListenerImplementationMustNotBeDisposableInspection"/>
<localInspection language="UAST"
key="inspection.read.or.write.action.during.service.init.display.name"
shortName="ReadOrWriteActionInServiceInitialization"
key="inspection.potential.deadlock.during.service.init.display.name"
shortName="PotentialDeadlockInServiceInitialization"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path"
groupKey="inspections.group.code"
enabledByDefault="false"
level="ERROR"
implementationClass="org.jetbrains.idea.devkit.inspections.ReadOrWriteActionInServiceInitializationInspection"/>
implementationClass="org.jetbrains.idea.devkit.inspections.PotentialDeadlockInServiceInitializationInspection"/>
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>

View File

@@ -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

View File

@@ -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<UClass>() 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<UObjectLiteralExpression>() != null ||
readOrWriteActionCall.getParentOfType<ULambdaExpression>() != null
return forbiddenActionCall.getParentOfType<UObjectLiteralExpression>() != null ||
forbiddenActionCall.getParentOfType<ULambdaExpression>() != 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<UClassInitializer>() != null
private fun isCalledInInitBlock(forbiddenCall: UCallExpression): Boolean {
return forbiddenCall.getParentOfType<UClassInitializer>() != null
}
private fun isCalledInFieldAssignment(readOrWriteActionCall: UCallExpression): Boolean {
return readOrWriteActionCall.getParentOfType<UField>() != null
private fun isCalledInFieldAssignment(forbiddenCall: UCallExpression): Boolean {
return forbiddenCall.getParentOfType<UField>() != 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<UElement> = 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<UMethod> = 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)
}

View File

@@ -31,7 +31,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
"ThreadingConcurrency",
"CallingMethodShouldBeRequiresBlockingContext",
"IncorrectProcessCanceledExceptionHandling",
"ReadOrWriteActionInServiceInitialization"
"PotentialDeadlockInServiceInitialization"
).sorted().toList();
/**

View File

@@ -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))

View File

@@ -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))

View File

@@ -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())
}
}