diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index 4ede569d165b..dac257e9bf7e 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -2700,6 +2700,10 @@ key="while.loop.spins.on.field.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.threading.issues" enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.threading.WhileLoopSpinsOnFieldInspection"/> + #ref may be annotated with @FunctionalInterface only.report.public.methods.option=Only report 'public' methods lambda.parameter.hides.member.variable.display.name=Lambda parameter hides field +static.initializer.references.subclass.display.name=Static initializer references subclass lambda.parameter.hides.member.variable.problem.descriptor=Lambda parameter #ref hides field in class ''{0}'' #loc shared.thread.local.random.display.name='ThreadLocalRandom' instance might be shared shared.thread.local.random.problem.descriptor='ThreadLocalRandom' instance might be shared between threads diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspection.java new file mode 100644 index 000000000000..3cc2178e793f --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspection.java @@ -0,0 +1,105 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.siyeh.ig.threading; + +import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * see https://bugs.openjdk.java.net/browse/JDK-8037567 + * @author peter + */ +public class StaticInitializerReferencesSubClassInspection extends BaseJavaBatchLocalInspectionTool { + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + + @Override + public void visitField(PsiField field) { + checkSubClassReferences(field); + } + + @Override + public void visitClassInitializer(PsiClassInitializer initializer) { + checkSubClassReferences(initializer); + } + + private void checkSubClassReferences(PsiMember scope) { + if (!scope.hasModifierProperty(PsiModifier.STATIC)) return; + + PsiClass containingClass = scope.getContainingClass(); + Pair pair = findSubClassReference(scope, containingClass); + if (pair != null) { + holder.registerProblem(pair.first, + "Referencing subclass " + pair.second.getName() + " from superclass " + containingClass.getName() + " initializer might lead to class loading deadlock"); + } + } + }; + } + + @Nullable + private static Pair findSubClassReference(@NotNull PsiElement scope, @Nullable final PsiClass baseClass) { + if (baseClass == null || baseClass.isInterface()) return null; + + final Ref> result = Ref.create(); + scope.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (element instanceof PsiClass || element instanceof PsiReferenceParameterList || element instanceof PsiTypeElement) return; + + PsiClass targetClass = extractClass(element); + if (targetClass != null && !(targetClass instanceof PsiAnonymousClass) && targetClass.isInheritor(baseClass, true)) { + PsiElement problemElement = calcProblemElement(element); + if (problemElement != null) { + result.set(Pair.create(problemElement, targetClass)); + } + } + + super.visitElement(element); + } + }); + return result.get(); + } + + @Nullable + private static PsiElement calcProblemElement(PsiElement element) { + if (element instanceof PsiNewExpression) return calcProblemElement(((PsiNewExpression)element).getClassOrAnonymousClassReference()); + if (element instanceof PsiMethodCallExpression) return calcProblemElement(((PsiMethodCallExpression)element).getMethodExpression()); + if (element instanceof PsiJavaCodeReferenceElement) return ((PsiJavaCodeReferenceElement)element).getReferenceNameElement(); + return element; + } + + @Nullable + private static PsiClass extractClass(PsiElement element) { + if (element instanceof PsiReferenceExpression) { + PsiElement target = ((PsiReferenceExpression)element).resolve(); + if (target instanceof PsiClass) { + return (PsiClass)target; + } + } + if (element instanceof PsiExpression) { + return PsiUtil.resolveClassInClassTypeOnly(((PsiExpression)element).getType()); + } + return null; + } +} diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/threading/StaticInitializerReferencesSubClass/StaticInitializer.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/threading/StaticInitializerReferencesSubClass/StaticInitializer.java new file mode 100644 index 000000000000..21abd1631691 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/threading/StaticInitializerReferencesSubClass/StaticInitializer.java @@ -0,0 +1,31 @@ +class Super { + static Super C1 = new Sub(); + static Object C2 = Sub.create(); + static final Sub C3; + static final Sub C4 = SubFactory.create(); + static final String C5 = SubFactory.create().toString(); + + static Object OK_INSIDE_ANONYMOUS = new Object() {{ Sub s = new Sub(); }}; + static Object OK_UNRELATED = "abc"; + static Super OK_SAME = new Super(); + static Super OK_SAME_ANONYMOUS = new Super(){}; + static Sub[] OK_ARRAY = new Sub[3]; + static java.util.List OK_GENERICS = new java.util.ArrayList(); + + static { + C3 = new Sub(); + } +} + +class Sub extends Super implements Intf { + static native Object create(); +} + +class SubFactory { + static native Sub create(); + +} + +interface Intf { + Sub ok = new Sub(); +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspectionTest.java new file mode 100644 index 000000000000..cfa1614beb5d --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/StaticInitializerReferencesSubClassInspectionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.siyeh.ig.threading; + +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; + +/** + * @author peter + */ +public class StaticInitializerReferencesSubClassInspectionTest extends LightCodeInsightFixtureTestCase { + @Override + protected String getTestDataPath() { + return PluginPathManager.getPluginHomePath("InspectionGadgets") + "/test/com/siyeh/igtest/threading/StaticInitializerReferencesSubClass"; + } + + public void testStaticInitializer() { + myFixture.enableInspections(new StaticInitializerReferencesSubClassInspection()); + myFixture.testHighlighting(true, false, false, getTestName(false) + ".java"); + } + +} diff --git a/resources-en/src/inspectionDescriptions/StaticInitializerReferencesSubClass.html b/resources-en/src/inspectionDescriptions/StaticInitializerReferencesSubClass.html new file mode 100644 index 000000000000..4665189340db --- /dev/null +++ b/resources-en/src/inspectionDescriptions/StaticInitializerReferencesSubClass.html @@ -0,0 +1,7 @@ + + +This inspection reports classes that refer to their own subclasses in their static initializers or in static fields. +Such references can cause JVM-level deadlocks in multithreaded environment, when one thread tries to load superclass +and another thread tries to load subclass at the same time. + + \ No newline at end of file