From d99cf43b8b1e2a3bbfd79b139d15f199771c5927 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 28 Feb 2014 15:25:56 +0100 Subject: [PATCH] new "'ThreadLocalRandom' instance might be shared" inspection --- .../src/META-INF/InspectionGadgets.xml | 3 + .../siyeh/InspectionGadgetsBundle.properties | 2 + .../SharedThreadLocalRandomInspection.java | 122 ++++++++++++++++++ .../SharedThreadLocalRandom.html | 15 +++ ...SharedThreadLocalRandomInspectionTest.java | 77 +++++++++++ 5 files changed, 219 insertions(+) create mode 100644 plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/SharedThreadLocalRandomInspection.java create mode 100644 plugins/InspectionGadgets/src/inspectionDescriptions/SharedThreadLocalRandom.html create mode 100644 plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/SharedThreadLocalRandomInspectionTest.java diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index eca27bdaf661..4becc9723a65 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -2489,6 +2489,9 @@ + #ref#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/SharedThreadLocalRandomInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/SharedThreadLocalRandomInspection.java new file mode 100644 index 000000000000..97adc033c5b1 --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/SharedThreadLocalRandomInspection.java @@ -0,0 +1,122 @@ +/* + * Copyright 2000-2014 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.psi.*; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.psiutils.VariableAccessUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class SharedThreadLocalRandomInspection extends BaseInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message("shared.thread.local.random.display.name"); + } + + @NotNull + @Override + protected String buildErrorString(Object... infos) { + return InspectionGadgetsBundle.message("shared.thread.local.random.problem.descriptor"); + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new SharedThreadLocalRandomVisitor(); + } + + private static class SharedThreadLocalRandomVisitor extends BaseInspectionVisitor { + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + @NonNls final String name = methodExpression.getReferenceName(); + if (!"current".equals(name)) { + return; + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (!InheritanceUtil.isInheritor(aClass, "java.util.concurrent.ThreadLocalRandom")) { + return; + } + if (isArgumentToMethodCall(expression)) { + registerMethodCallError(expression); + } + else { + final PsiVariable variable = assignedToVariable(expression); + if (variable instanceof PsiField) { + registerMethodCallError(expression); + } + else if (variable instanceof PsiLocalVariable) { + final PsiCodeBlock context = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); + if (VariableAccessUtils.variableIsPassedAsMethodArgument(variable, context) || + VariableAccessUtils.variableIsUsedInInnerClass(variable, context)) { + registerMethodCallError(expression); + } + } + } + } + + private static boolean isArgumentToMethodCall(PsiExpression expression) { + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression); + if (!(parent instanceof PsiExpressionList)) { + return false; + } + final PsiElement grandParent = parent.getParent(); + return grandParent instanceof PsiMethodCallExpression; + } + + private static PsiVariable assignedToVariable(PsiMethodCallExpression expression) { + final PsiElement parent = PsiTreeUtil.skipParentsOfType(expression, PsiParenthesizedExpression.class); + if (parent instanceof PsiVariable) { + return (PsiVariable)parent; + } + if (!(parent instanceof PsiAssignmentExpression)) { + return null; + } + final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent; + final PsiExpression rhs = assignmentExpression.getRExpression(); + if (!PsiTreeUtil.isAncestor(rhs, expression, false)) { + return null; + } + final PsiExpression lhs = ParenthesesUtils.stripParentheses(assignmentExpression.getLExpression()); + if (!(lhs instanceof PsiReferenceExpression)) { + return null; + } + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lhs; + final PsiElement target = referenceExpression.resolve(); + if (!(target instanceof PsiVariable)) { + return null; + } + return (PsiVariable)target; + } + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/SharedThreadLocalRandom.html b/plugins/InspectionGadgets/src/inspectionDescriptions/SharedThreadLocalRandom.html new file mode 100644 index 000000000000..07ebccab4653 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/SharedThreadLocalRandom.html @@ -0,0 +1,15 @@ + + +Reports java.util.concurrent.ThreadLocalRandom instances which might be shared between threads. +A ThreadLocalRandom might be shared between threads and is reported when it is assigned to a field, +used as a method argument or assigned to a local variable and used in anonymous or nested classes. +A ThreadLocalRandom should not be shared between threads because that is not thread-safe. +

+Usages of ThreadLocalRandom should typically look like ThreadLocalRandom.current().nextInt(...) +(or nextDouble(...) etc.). +When all usages are in this form, ThreadLocalRandom instances can not be used accidentally by multiple threads. + +

+New in 13.1 + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/SharedThreadLocalRandomInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/SharedThreadLocalRandomInspectionTest.java new file mode 100644 index 000000000000..b15bc350fd6b --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/SharedThreadLocalRandomInspectionTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2014 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.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; + +/** + * @author Bas Leijdekkers + */ +public class SharedThreadLocalRandomInspectionTest extends LightInspectionTestCase { + @Override + protected InspectionProfileEntry getInspection() { + return new SharedThreadLocalRandomInspection(); + } + + @Override + protected String[] getEnvironmentClasses() { + return new String[] { + "package java.util.concurrent;\n" + + "import java.util.Random;" + + "public class ThreadLocalRandom extends Random {" + + " public static ThreadLocalRandom current() {" + + " return null;" + + " }" + + "}" + }; + } + + public void testNoWarn() { + doStatementTest("System.out.println(java.util.concurrent.ThreadLocalRandom.current().nextInt());"); + } + + public void testArgument() { + doStatementTest("System.out.println(java.util.concurrent.ThreadLocalRandom./*'ThreadLocalRandom' instance might be shared between threads*/current/**/());"); + } + + public void testNoWarn2() { + doMemberTest("void m() {" + + " java.util.concurrent.ThreadLocalRandom r = java.util.concurrent.ThreadLocalRandom.current();" + + "}"); + } + + public void testNestedClass() { + doMemberTest("void m() {" + + " java.util.concurrent.ThreadLocalRandom r = java.util.concurrent.ThreadLocalRandom./*'ThreadLocalRandom' instance might be shared between threads*/current/**/();" + + " class Z extends Thread {" + + " public void run() {" + + " System.out.println(r.nextInt(1));" + + " }" + + " }" + + "}"); + } + + public void testAssignmentToField() { + doTest("import java.util.concurrent.ThreadLocalRandom;" + + "class A {" + + " private ThreadLocalRandom r;" + + " void m() {" + + " r = ThreadLocalRandom./*'ThreadLocalRandom' instance might be shared between threads*/current/**/();" + + " }" + + "}"); + } +}