mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 02:09:59 +07:00
[blocking calls detection] IDEA-310256: handle implicit constructor calls
GitOrigin-RevId: d550a9ad10d014116378fd4800cff7b72b7b51c1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
573f7f3268
commit
1555d664b6
@@ -5,7 +5,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface Blocking {
|
||||
}
|
||||
@@ -5,6 +5,6 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface NonBlocking {}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NonBlocking;
|
||||
|
||||
public class TestBlockingConstructor {
|
||||
static class BlockingConstructor {
|
||||
@Blocking
|
||||
BlockingConstructor() {
|
||||
}
|
||||
|
||||
@Blocking
|
||||
BlockingConstructor(String url) {
|
||||
}
|
||||
}
|
||||
|
||||
static class Intermediate extends BlockingConstructor {}
|
||||
|
||||
static class NonBlockingConstructor extends BlockingConstructor {
|
||||
@NonBlocking
|
||||
<warning descr="Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation">NonBlockingConstructor</warning>() {
|
||||
|
||||
}
|
||||
|
||||
@NonBlocking
|
||||
NonBlockingConstructor(String url) {
|
||||
<warning descr="Possibly blocking call in non-blocking context could lead to thread starvation">super</warning>(url);
|
||||
}
|
||||
}
|
||||
|
||||
static class NonBlockingConstructorIntermediate extends Intermediate {
|
||||
@NonBlocking
|
||||
<warning descr="Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation">NonBlockingConstructorIntermediate</warning>() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ jvm.inspections.must.already.be.removed.api.earlier.version.description=API must
|
||||
jvm.inspections.must.already.be.removed.api.current.version.description=API must be removed in the current version {0}
|
||||
jvm.inspections.blocking.method.problem.descriptor=Possibly blocking call in non-blocking context could lead to thread starvation
|
||||
jvm.inspections.blocking.method.problem.wildcard.descriptor=Possibly blocking call in {0} could lead to thread starvation
|
||||
jvm.inspections.blocking.method.in.implicit.ctr.problem.descriptor=Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation
|
||||
jvm.inspections.blocking.method.in.implicit.ctr.problem.wildcard.descriptor=Possibly blocking call from implict constructor call in {0} could lead to thread starvation
|
||||
jvm.inspections.blocking.method.display.name=Possibly blocking call in non-blocking context
|
||||
jvm.inspections.blocking.method.annotation.blocking=Blocking annotations
|
||||
jvm.inspections.blocking.method.annotation.non-blocking=Non-blocking annotations
|
||||
|
||||
@@ -40,10 +40,17 @@ public final class AnnotationBasedNonBlockingContextChecker implements NonBlocki
|
||||
|
||||
@Override
|
||||
public ContextType computeContextType(@NotNull ElementContext elementContext) {
|
||||
UCallExpression callExpression = UastContextKt.toUElement(elementContext.getElement(), UCallExpression.class);
|
||||
if (callExpression == null) return Unsure.INSTANCE;
|
||||
UMethod callingMethod;
|
||||
var method = UastContextKt.toUElement(elementContext.getElement(), UMethod.class);
|
||||
if (method != null) {
|
||||
callingMethod = method;
|
||||
}
|
||||
else {
|
||||
UCallExpression callExpression = UastContextKt.toUElement(elementContext.getElement(), UCallExpression.class);
|
||||
if (callExpression == null) return Unsure.INSTANCE;
|
||||
callingMethod = UastUtils.getParentOfType(callExpression, UMethod.class);
|
||||
}
|
||||
|
||||
UMethod callingMethod = UastUtils.getParentOfType(callExpression, UMethod.class);
|
||||
if (callingMethod == null) return Unsure.INSTANCE;
|
||||
PsiMethod psiCallingMethod = callingMethod.getJavaPsi();
|
||||
|
||||
|
||||
@@ -11,13 +11,12 @@ import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.uast.UCallExpression;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.uast.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -52,12 +51,15 @@ public final class BlockingMethodInNonBlockingContextInspection extends Abstract
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("myConsiderUnknownContextBlocking", JvmAnalysisBundle.message("jvm.inspections.blocking.method.consider.unknown.context.blocking")),
|
||||
checkbox("myConsiderUnknownContextBlocking",
|
||||
JvmAnalysisBundle.message("jvm.inspections.blocking.method.consider.unknown.context.blocking")),
|
||||
stringSet("myBlockingAnnotations", JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.blocking"),
|
||||
new JavaClassValidator().withTitle(JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.configure.add.blocking.title"))
|
||||
new JavaClassValidator().withTitle(
|
||||
JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.configure.add.blocking.title"))
|
||||
.annotationsOnly()),
|
||||
stringSet("myNonBlockingAnnotations", JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.non-blocking"),
|
||||
new JavaClassValidator().withTitle(JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.configure.add.non-blocking.title"))
|
||||
new JavaClassValidator().withTitle(
|
||||
JvmAnalysisBundle.message("jvm.inspections.blocking.method.annotation.configure.add.non-blocking.title"))
|
||||
.annotationsOnly())
|
||||
);
|
||||
}
|
||||
@@ -126,11 +128,18 @@ public final class BlockingMethodInNonBlockingContextInspection extends Abstract
|
||||
@Override
|
||||
public void visitElement(@NotNull PsiElement element) {
|
||||
super.visitElement(element);
|
||||
if (visitConstructor(element)) return;
|
||||
|
||||
UCallExpression callExpression = AnalysisUastUtil.getUCallExpression(element);
|
||||
if (callExpression == null) return;
|
||||
PsiElement elementToHighLight = AnalysisUastUtil.getMethodIdentifierSourcePsi(callExpression);
|
||||
if (elementToHighLight == null) return;
|
||||
|
||||
// implicit delegating constructor, check it in visitConstructor
|
||||
if (callExpression.getKind() == UastCallKind.CONSTRUCTOR_CALL && elementToHighLight.getTextRange().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextType contextType = isContextNonBlockingFor(element, myNonBlockingContextCheckers, mySettings);
|
||||
if (contextType instanceof ContextType.Blocking) {
|
||||
return;
|
||||
@@ -168,8 +177,69 @@ public final class BlockingMethodInNonBlockingContextInspection extends Abstract
|
||||
}
|
||||
myHolder.registerProblem(elementToHighLight, message, fixesStream.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private boolean visitConstructor(@NotNull PsiElement element) {
|
||||
var method = UastContextKt.toUElement(element, UMethod.class);
|
||||
if (method == null || !method.isConstructor()) return false;
|
||||
|
||||
var anchor = method.getUastAnchor();
|
||||
if (anchor == null) return false;
|
||||
var elementToHighlight = anchor.getSourcePsi();
|
||||
if (elementToHighlight == null) return false;
|
||||
|
||||
if (!(method.getUastParent() instanceof UClass containingClass)) return false;
|
||||
if (containingClass.getJavaPsi().getSuperClass() == null) return false;
|
||||
if (!(method.getUastBody() instanceof UBlockExpression body)) return false;
|
||||
|
||||
var firstExpression = ContainerUtil.getFirstItem(body.getExpressions());
|
||||
if (firstExpression != null && isExplicitSuperCall(firstExpression)) return false;
|
||||
|
||||
ContextType contextType = isContextNonBlockingFor(element, myNonBlockingContextCheckers, mySettings);
|
||||
if (contextType instanceof ContextType.Blocking) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(contextType instanceof ContextType.NonBlocking nonBlockingContext)) return true;
|
||||
|
||||
var implicitlyCalledCtr = findFirstExplicitNoArgConstructor(containingClass.getJavaPsi().getSuperClass());
|
||||
if (implicitlyCalledCtr == null) return true;
|
||||
if (!isMethodBlocking(implicitlyCalledCtr, myBlockingMethodCheckers, mySettings)) return true;
|
||||
|
||||
String message;
|
||||
if (StringUtil.isNotEmpty(nonBlockingContext.getDescription())) {
|
||||
String contextDescription = nonBlockingContext.getDescription();
|
||||
message = JvmAnalysisBundle.message("jvm.inspections.blocking.method.in.implicit.ctr.problem.wildcard.descriptor", contextDescription);
|
||||
}
|
||||
else {
|
||||
message = JvmAnalysisBundle.message("jvm.inspections.blocking.method.in.implicit.ctr.problem.descriptor");
|
||||
}
|
||||
myHolder.registerProblem(elementToHighlight, message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static @Nullable PsiMethod findFirstExplicitNoArgConstructor(@NotNull PsiClass currentClass) {
|
||||
while (currentClass != null) {
|
||||
var explicitEmptyArgCtr = ContainerUtil.find(currentClass.getConstructors(), ctr -> !ctr.hasParameters());
|
||||
if (explicitEmptyArgCtr != null) {
|
||||
return explicitEmptyArgCtr;
|
||||
}
|
||||
currentClass = currentClass.getSuperClass();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isExplicitSuperCall(@NotNull UExpression expression) {
|
||||
if (!(expression instanceof USuperExpression) &&
|
||||
!(expression instanceof UCallExpression call && call.getKind() == UastCallKind.CONSTRUCTOR_CALL)) return true;
|
||||
var sourcePsi = expression.getSourcePsi();
|
||||
if (sourcePsi == null) return false;
|
||||
return !sourcePsi.getTextRange().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean isMethodOrSupersBlocking(PsiMethod referencedMethod,
|
||||
List<BlockingMethodChecker> checkers,
|
||||
BlockingCallInspectionSettings settings) {
|
||||
|
||||
@@ -84,4 +84,9 @@ public class JavaBlockingMethodInNonBlockingContextInspectionTest extends Useful
|
||||
myFixture.configureByFiles("TestThrowsTypeDetection.java", "Blocking.java", "NonBlocking.java");
|
||||
myFixture.testHighlighting(true, false, true, "TestThrowsTypeDetection.java");
|
||||
}
|
||||
|
||||
public void testBlockingConstructor() {
|
||||
myFixture.configureByFiles("TestBlockingConstructor.java", "Blocking.java", "NonBlocking.java");
|
||||
myFixture.testHighlighting(true, false, true, "TestBlockingConstructor.java");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,55 @@ public class KotlinBlockingCallDetectionTest extends JavaCodeInsightFixtureTestC
|
||||
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
}
|
||||
|
||||
public void testDelegatingConstructor() {
|
||||
myFixture.addClass("package org.jetbrains.annotations;\n" +
|
||||
"public @interface Blocking {}");
|
||||
myFixture.addClass("package org.jetbrains.annotations;\n" +
|
||||
"public @interface NonBlocking {}");
|
||||
|
||||
myFixture.addClass("""
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
public class BlockingCtrClass {
|
||||
@Blocking
|
||||
BlockingCtrClass() {
|
||||
}
|
||||
|
||||
public static class Intermediate extends BlockingCtrClass {}
|
||||
}
|
||||
"""
|
||||
);
|
||||
|
||||
myFixture.configureByText("file.kt",
|
||||
"""
|
||||
import org.jetbrains.annotations.*
|
||||
|
||||
class NonBlockingCtrClass : BlockingCtrClass {
|
||||
@NonBlocking
|
||||
<warning descr="Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation">constructor</warning>() {}
|
||||
}
|
||||
|
||||
class NonBlockingCtrClassWithIntermediate : BlockingCtrClass.Intermediate {
|
||||
@NonBlocking
|
||||
<warning descr="Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation">constructor</warning>() {}
|
||||
}
|
||||
|
||||
class NonBlockingCtrClassExplicit @NonBlocking constructor() : <warning descr="Possibly blocking call in non-blocking context could lead to thread starvation">BlockingCtrClass</warning>()
|
||||
|
||||
class NonBlockingCtrClassExplicit2 : BlockingCtrClass {
|
||||
@NonBlocking
|
||||
constructor() : <warning descr="Possibly blocking call in non-blocking context could lead to thread starvation">super</warning>()
|
||||
}
|
||||
|
||||
open class KotlinBlockingCtr @Blocking constructor()
|
||||
|
||||
class NonBlockingCtr : KotlinBlockingCtr {
|
||||
@NonBlocking
|
||||
<warning descr="Possibly blocking call from implicit constructor call in non-blocking context could lead to thread starvation">constructor</warning>() {}
|
||||
}
|
||||
""");
|
||||
|
||||
myFixture.testHighlighting(true, false, true, "file.kt");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user