From 3b9e217ea83af3babb3e58b14ee6c4cc665f6eac Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Tue, 22 Dec 2015 19:55:40 +0100 Subject: [PATCH] new "AtomicFieldUpdater issues" inspection --- .../src/META-INF/InspectionGadgets.xml | 4 + .../siyeh/InspectionGadgetsBundle.properties | 10 +- .../AtomicFieldUpdaterIssuesInspection.java | 170 ++++++++++++++++++ .../AtomicFieldUpdaterIssues.html | 16 ++ ...tomicFieldUpdaterIssuesInspectionTest.java | 108 +++++++++++ 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspection.java create mode 100644 plugins/InspectionGadgets/src/inspectionDescriptions/AtomicFieldUpdaterIssues.html create mode 100644 plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspectionTest.java diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index aa6c9619a944..12bb98488a5f 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -2529,6 +2529,10 @@ + Throwable.#ref()#ref is not declared ''static final'' #loc \ No newline at end of file +atomic.field.updater.not.static.final.problem.descriptor={0} field #ref is not declared ''static final'' #loc +atomic.field.updater.issues.display.name=AtomicFieldUpdater issues +field.not.found.in.class.problem.descriptor=No field named ''{0}'' found in class ''{1}'' +field.incorrect.type.problem.descriptor=Field ''{0}'' does not have type ''{1}'' +field.missing.volatile.modifier.problem.descriptor=Field ''{0}'' does not have ''volatile'' modifier +field.has.static.modifier.problem.descriptor=Field ''{0}'' has ''static'' modifier +private.field.not.accessible.problem.descriptor=''private'' field ''{0}'' is not accessible from here +package.local.field.not.accessible=package local field ''{0}'' is not accessible from here +protected.field.not.accessible.problem.descriptor=''protected'' field ''{0}'' is not accessible from here \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspection.java new file mode 100644 index 000000000000..fa07adabb4dd --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspection.java @@ -0,0 +1,170 @@ +/* + * 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.psi.*; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.psiutils.ClassUtils; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.TypeUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class AtomicFieldUpdaterIssuesInspection extends BaseInspection { + + @Nls + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message("atomic.field.updater.issues.display.name"); + } + + @NotNull + @Override + protected String buildErrorString(Object... infos) { + final String message = (String)infos[0]; + return message; + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new AtomicFieldUpdaterIssuesVisitor(); + } + + private static class AtomicFieldUpdaterIssuesVisitor extends BaseInspectionVisitor { + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!"newUpdater".equals(name)) { + return; + } + final PsiExpressionList argumentList = expression.getArgumentList(); + final PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length < 2) { + return; + } + final PsiExpression lastArgument = arguments[arguments.length - 1]; + final Object value = ExpressionUtils.computeConstantExpression(lastArgument); + if (!(value instanceof String)) { + return; + } + final String fieldName = (String)value; + final PsiExpression firstArgument = arguments[0]; + if (!(firstArgument instanceof PsiClassObjectAccessExpression)) { + return; + } + final PsiClassObjectAccessExpression classObjectAccessExpression = (PsiClassObjectAccessExpression)firstArgument; + final PsiType operandType = classObjectAccessExpression.getOperand().getType(); + if (!(operandType instanceof PsiClassType)) { + return; + } + final PsiClassType classType = (PsiClassType)operandType; + final PsiClass target = classType.resolve(); + if (target == null) { + return; + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return; + } + final String typeString = TypeUtils.expressionHasTypeOrSubtype(expression, + "java.util.concurrent.atomic.AtomicLongFieldUpdater", + "java.util.concurrent.atomic.AtomicIntegerFieldUpdater", + "java.util.concurrent.atomic.AtomicReferenceFieldUpdater"); + if (typeString == null) { + return; + } + final PsiField field = target.findFieldByName(fieldName, false); + if (field == null) { + registerError(lastArgument, + InspectionGadgetsBundle.message("field.not.found.in.class.problem.descriptor", fieldName, target.getName())); + return; + } + else if (typeString.equals("java.util.concurrent.atomic.AtomicLongFieldUpdater")) { + if (arguments.length != 2) { + return; + } + if (!PsiType.LONG.equals(field.getType())) { + registerError(lastArgument, InspectionGadgetsBundle.message("field.incorrect.type.problem.descriptor", fieldName, "long")); + return; + } + } + else if (typeString.equals("java.util.concurrent.atomic.AtomicIntegerFieldUpdater")) { + if (arguments.length != 2) { + return; + } + if (!PsiType.INT.equals(field.getType())) { + registerError(lastArgument, InspectionGadgetsBundle.message("field.incorrect.type.problem.descriptor", fieldName, "int")); + return; + } + } + else if (typeString.equals("java.util.concurrent.atomic.AtomicReferenceFieldUpdater")) { + if (arguments.length != 3) { + return; + } + final PsiExpression argument2 = arguments[1]; + if (!(argument2 instanceof PsiClassObjectAccessExpression)) { + return; + } + final PsiClassObjectAccessExpression objectAccessExpression = (PsiClassObjectAccessExpression)argument2; + final PsiType type = objectAccessExpression.getOperand().getType(); + if (!field.getType().equals(type)) { + registerError(lastArgument, InspectionGadgetsBundle.message("field.incorrect.type.problem.descriptor", + fieldName, type.getPresentableText())); + return; + } + } + else { + assert false; + } + if (!field.hasModifierProperty(PsiModifier.VOLATILE)) { + registerError(lastArgument, InspectionGadgetsBundle.message("field.missing.volatile.modifier.problem.descriptor", fieldName)); + } + else if (field.hasModifierProperty(PsiModifier.STATIC)) { + registerError(lastArgument, InspectionGadgetsBundle.message("field.has.static.modifier.problem.descriptor", fieldName)); + } + else if (!field.hasModifierProperty(PsiModifier.PUBLIC)) { + if (ClassUtils.getContainingClass(expression) != field.getContainingClass()) { + if (field.hasModifierProperty(PsiModifier.PRIVATE)) { + registerError(lastArgument, InspectionGadgetsBundle.message("private.field.not.accessible.problem.descriptor", fieldName)); + } + } + else if (!ClassUtils.inSamePackage(expression, field)) { + if (field.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + registerError(lastArgument, InspectionGadgetsBundle.message("package.local.field.not.accessible", fieldName)); + } + } + else { + final PsiClass expressionClass = ClassUtils.getContainingClass(expression); + final PsiClass fieldClass = field.getContainingClass(); + if (expressionClass != null && fieldClass != null && !expressionClass.isInheritor(fieldClass, true)) { + if (field.hasModifierProperty(PsiModifier.PROTECTED)) { + registerError(lastArgument, InspectionGadgetsBundle.message("protected.field.not.accessible.problem.descriptor", fieldName)); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/AtomicFieldUpdaterIssues.html b/plugins/InspectionGadgets/src/inspectionDescriptions/AtomicFieldUpdaterIssues.html new file mode 100644 index 000000000000..23d6c8e30e9d --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/AtomicFieldUpdaterIssues.html @@ -0,0 +1,16 @@ + + + + +Reports issues on fields of type java.util.concurrent.atomic.AtomicLongFieldUpdater, +java.util.concurrent.atomic.AtomicIntegerFieldUpdater or java.util.concurrent.atomic.AtomicReferenceFieldUpdater. +The issues reported are identical to the runtime problems that can happen with atomic field updaters: +specified field not found, specified field not accessible, specified field of the wrong type, etc. + + +

+New in 15.1 + + + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspectionTest.java new file mode 100644 index 000000000000..88b74be2444f --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/AtomicFieldUpdaterIssuesInspectionTest.java @@ -0,0 +1,108 @@ +/* + * 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.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class AtomicFieldUpdaterIssuesInspectionTest extends LightInspectionTestCase { + + public void testAllGood() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class A {" + + " private volatile int value = 0;" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(A.class, \"value\");" + + "}"); + } + + public void testStatic() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class A {" + + " private static volatile int value = 0;" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(A.class, /*Field 'value' has 'static' modifier*/\"value\"/**/);" + + "}"); + } + + public void testNotVolatile() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class A {" + + " private int value = 0;" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(A.class, /*Field 'value' does not have 'volatile' modifier*/\"value\"/**/);" + + "}"); + } + + public void testFieldNotFound() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class Z {" + + " volatile int value = 0;" + + "}" + + "class A extends Z {" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(A.class, /*No field named 'value' found in class 'A'*/\"value\"/**/);" + + "}"); + } + + public void testWrongType1() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class A {" + + " private volatile long value = 0;" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(A.class, /*Field 'value' does not have type 'int'*/\"value\"/**/);" + + "}"); + } + + public void testWrongType2() { + doTest("import java.util.concurrent.atomic.AtomicLongFieldUpdater;" + + "class A {" + + " private volatile int value = 0;" + + " private static final AtomicLongFieldUpdater updater = " + + " AtomicLongFieldUpdater.newUpdater(A.class, /*Field 'value' does not have type 'long'*/\"value\"/**/);" + + "}"); + } + + public void testWrongType3() { + doTest("import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;" + + "class A {" + + " private volatile int[] value = new int[]{0};" + + " private static final AtomicReferenceFieldUpdater updater = " + + " AtomicReferenceFieldUpdater.newUpdater(A.class, long[].class, /*Field 'value' does not have type 'long[]'*/\"value\"/**/);" + + "}"); + } + + public void testNotAccessible1() { + doTest("import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;" + + "class Z {" + + " private volatile int value = 0;" + + "}" + + "class A {" + + " private static final AtomicIntegerFieldUpdater updater = " + + " AtomicIntegerFieldUpdater.newUpdater(Z.class, /*'private' field 'value' is not accessible from here*/\"value\"/**/);" + + "}"); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new AtomicFieldUpdaterIssuesInspection(); + } +} \ No newline at end of file