new "AtomicFieldUpdater issues" inspection

This commit is contained in:
Bas Leijdekkers
2015-12-22 22:17:40 +01:00
parent 9a57da8ddd
commit 3b9e217ea8
5 changed files with 307 additions and 1 deletions
@@ -2529,6 +2529,10 @@
<localInspection groupPath="Java" language="JAVA" shortName="AwaitNotInLoop" bundle="com.siyeh.InspectionGadgetsBundle" key="await.not.in.loop.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.threading.issues" enabledByDefault="false"
level="WARNING" implementationClass="com.siyeh.ig.threading.AwaitNotInLoopInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="AtomicFieldUpdaterIssues" bundle="com.siyeh.InspectionGadgetsBundle"
key="atomic.field.updater.issues.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.threading.issues" enabledByDefault="false" level="WARNING"
implementationClass="com.siyeh.ig.threading.AtomicFieldUpdaterIssuesInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="AtomicFieldUpdaterNotStaticFinal" bundle="com.siyeh.InspectionGadgetsBundle"
key="atomic.field.updater.not.static.final.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.threading.issues" enabledByDefault="false" level="WARNING"
@@ -2145,4 +2145,12 @@ unnecessary.initcause.problem.descriptor=Unnecessary <code>Throwable.#ref()</cod
unnecessary.initcause.quickfix=Remove 'Throwable.initCause()' call
consider.static.final.fields.constant.option=Consider 'static final' fields constant
atomic.field.updater.not.static.final.display.name=AtomicFieldUpdater field not declared 'static final'
atomic.field.updater.not.static.final.problem.descriptor={0} field <code>#ref</code> is not declared ''static final'' #loc
atomic.field.updater.not.static.final.problem.descriptor={0} field <code>#ref</code> 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
@@ -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));
}
}
}
}
}
}
}
@@ -0,0 +1,16 @@
<html>
<body>
<html>
<body>
Reports issues on fields of type <b>java.util.concurrent.atomic.AtomicLongFieldUpdater</b>,
<b>java.util.concurrent.atomic.AtomicIntegerFieldUpdater</b> or <b>java.util.concurrent.atomic.AtomicReferenceFieldUpdater</b>.
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.
<!-- tooltip end -->
<p>
<small>New in 15.1</small>
</body>
</html>
</body>
</html>
@@ -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();
}
}