Java: Take into account writes to the field done via AtomicFieldUpdater (IDEA-152262)

This commit is contained in:
Pavel Dolgov
2018-06-21 19:23:58 +03:00
parent 9789fa1b18
commit 6caa2e4236
18 changed files with 387 additions and 0 deletions

View File

@@ -166,6 +166,7 @@
<fileTypeFactory implementation="com.intellij.spi.SPIFileTypeFactory"/>
<writingAccessProvider implementation="com.intellij.refactoring.util.ClsElementWritingAccessProvider"/>
<psi.referenceContributor language="JAVA" implementation="com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceContributor"/>
<implicitUsageProvider implementation="com.intellij.psi.impl.source.resolve.reference.impl.AtomicReferenceImplicitUsageProvider"/>
<projectTemplateParameterFactory implementation="com.intellij.openapi.module.BasePackageParameterFactory"/>
<java.elementFinder implementation="com.intellij.psi.impl.migration.MigrationElementFinder"/>
<java.elementFinder implementation="com.intellij.psi.impl.PackagePrefixElementFinder"/>

View File

@@ -0,0 +1,150 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*;
import static com.intellij.psi.search.PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES;
import static com.intellij.psi.util.PsiUtil.skipParenthesizedExprDown;
import static com.intellij.psi.util.PsiUtil.skipParenthesizedExprUp;
/**
* @author Pavel.Dolgov
*/
public class AtomicReferenceImplicitUsageProvider implements ImplicitUsageProvider {
private static final Set<String> ourUpdateMethods = ContainerUtil.set(
"compareAndSet", "weakCompareAndSet", "set", "lazySet", "getAndSet", "getAndIncrement", "getAndDecrement", "getAndAdd",
"incrementAndGet", "decrementAndGet", "addAndGet", "getAndUpdate", "updateAndGet", "getAndAccumulate", "accumulateAndGet");
@Override
public boolean isImplicitUsage(PsiElement element) {
return false;
}
@Override
public boolean isImplicitRead(PsiElement element) {
return false;
}
@Override
public boolean isImplicitWrite(PsiElement element) {
if (element instanceof PsiField) {
PsiField field = (PsiField)element;
if (!field.hasModifierProperty(PsiModifier.VOLATILE)) {
return false;
}
PsiType type = field.getType();
if (PsiType.INT.equals(type)) {
return isAtomicWrite(field, ATOMIC_INTEGER_FIELD_UPDATER);
}
if (PsiType.LONG.equals(type)) {
return isAtomicWrite(field, ATOMIC_LONG_FIELD_UPDATER);
}
if (!(type instanceof PsiPrimitiveType)) {
return isAtomicWrite(field, ATOMIC_REFERENCE_FIELD_UPDATER);
}
}
return false;
}
private static boolean isAtomicWrite(@NotNull PsiField field, @NonNls String updaterName) {
SearchScope scope = getCheapSearchScope(field);
if (scope == null) {
return false;
}
Ref<Boolean> isUpdated = new Ref<>(Boolean.FALSE);
Query<PsiReference> fieldQuery = ReferencesSearch.search(field, scope);
fieldQuery.forEach((PsiReference reference) -> findAtomicUpdaters(reference, updaterName, isUpdated));
return isUpdated.get();
}
private static boolean findAtomicUpdaters(@NotNull PsiReference reference,
@NotNull String updaterName,
@NotNull Ref<Boolean> isUpdated) {
if (!(reference instanceof JavaLangClassMemberReference)) { // optimization
return true;
}
PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(reference.getElement(), PsiMethodCallExpression.class);
if (methodCall == null || !isCallToMethod(methodCall, updaterName, NEW_UPDATER)) {
return true;
}
PsiElement callParent = skipParenthesizedExprUp(methodCall.getParent());
PsiVariable updaterVariable = null;
if (callParent instanceof PsiVariable && skipParenthesizedExprDown(((PsiVariable)callParent).getInitializer()) == methodCall) {
updaterVariable = (PsiVariable)callParent;
}
else if (callParent instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignment = (PsiAssignmentExpression)callParent;
if (assignment.getOperationTokenType() == JavaTokenType.EQ && skipParenthesizedExprDown(assignment.getRExpression()) == methodCall) {
PsiExpression lExpression = skipParenthesizedExprDown(assignment.getLExpression());
if (lExpression instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression)lExpression).resolve();
if (resolved instanceof PsiVariable) {
updaterVariable = (PsiVariable)resolved;
}
}
}
}
if (updaterVariable != null && InheritanceUtil.isInheritor(updaterVariable.getType(), updaterName)) {
Query<PsiReference> updaterQuery = ReferencesSearch.search(updaterVariable);
updaterQuery.forEach((PsiReference updaterReference) -> findWrites(updaterReference, isUpdated));
if (isUpdated.get()) {
return false;
}
}
return true;
}
private static boolean findWrites(@NotNull PsiReference reference, @NotNull Ref<Boolean> isUpdated) {
PsiElement element = reference.getElement();
PsiReferenceExpression methodExpression = ObjectUtils.tryCast(skipParenthesizedExprUp(element.getParent()), PsiReferenceExpression.class);
if (methodExpression != null &&
(methodExpression instanceof PsiMethodReferenceExpression || methodExpression.getParent() instanceof PsiMethodCallExpression) &&
ourUpdateMethods.contains(methodExpression.getReferenceName()) &&
skipParenthesizedExprDown(methodExpression.getQualifierExpression()) == element) {
isUpdated.set(Boolean.TRUE);
return false;
}
return true;
}
@Nullable
private static SearchScope getCheapSearchScope(@NotNull PsiField field) {
String name = field.getName();
if (name == null) {
return null;
}
Project project = field.getProject();
PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(project);
SearchScope scope = searchHelper.getUseScope(field);
if (scope instanceof LocalSearchScope || RefResolveService.getInstance(project).isUpToDate()) {
return scope;
}
if (scope instanceof GlobalSearchScope &&
searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope)scope, null, null) == FEW_OCCURRENCES) {
return scope;
}
return null;
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Atomics {
private volatile int num;
private static final AtomicIntegerFieldUpdater<Atomics> updater =
AtomicIntegerFieldUpdater.newUpdater(Atomics.class, "num");
public void init(int n) {
(updater).compareAndSet(this, 0, n);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Atomics {
private volatile int <warning descr="Private field 'num' is never assigned">num</warning>;
private static final AtomicIntegerFieldUpdater<Atomics> updater =
AtomicIntegerFieldUpdater.newUpdater(Atomics.class, "num");
public int getInt() {
return updater.get(this);
}
}

View File

@@ -0,0 +1,20 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.BiFunction;
class Atomics {
private volatile int num;
private static final AtomicIntegerFieldUpdater<Atomics> updater =
AtomicIntegerFieldUpdater.newUpdater(Atomics.class, "num");
public int getInt() {
return updater.get(this);
}
public int getAndSet(int n) {
return update(updater::getAndSet, n);
}
private int update(BiFunction<Atomics, Integer, Integer> f, int n) {
return f.apply(this, n);
}
}

View File

@@ -0,0 +1,14 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Atomics {
private volatile int num;
private static final AtomicIntegerFieldUpdater<Atomics> updater;
static {
updater = (AtomicIntegerFieldUpdater.newUpdater(Atomics.class, "num"));
}
public int increment() {
return updater.incrementAndGet(this);
}
}

View File

@@ -0,0 +1,10 @@
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Atomics {
private volatile int num;
public void set(int n) {
AtomicIntegerFieldUpdater<Atomics> updater = AtomicIntegerFieldUpdater.newUpdater(Atomics.class, "num");
updater.set(this, n);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
class Atomics {
private volatile long num;
private static final AtomicLongFieldUpdater<Atomics> updater =
(AtomicLongFieldUpdater.newUpdater(Atomics.class, "num"));
public void init(long n) {
updater.compareAndSet(this, 0, n);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
class Atomics {
private volatile long <warning descr="Private field 'num' is never assigned">num</warning>;
private static final AtomicLongFieldUpdater<Atomics> updater =
AtomicLongFieldUpdater.newUpdater(Atomics.class, "num");
public long getLong() {
return updater.get(this);
}
}

View File

@@ -0,0 +1,20 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.function.BiFunction;
class Atomics {
private volatile long num;
private static final AtomicLongFieldUpdater<Atomics> updater =
AtomicLongFieldUpdater.newUpdater(Atomics.class, "num");
public long getLong() {
return updater.get(this);
}
public long getAndSet(long n) {
return update(updater::getAndSet, n);
}
private long update(BiFunction<Atomics, Long, Long> f, long n) {
return f.apply(this, n);
}
}

View File

@@ -0,0 +1,14 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
class Atomics {
private volatile long num;
private static final AtomicLongFieldUpdater<Atomics> updater;
static {
(updater) = AtomicLongFieldUpdater.newUpdater(Atomics.class, "num");
}
public long increment() {
return updater.incrementAndGet(this);
}
}

View File

@@ -0,0 +1,10 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
class Atomics {
private volatile long num;
public void set(long n) {
AtomicLongFieldUpdater<Atomics> updater = AtomicLongFieldUpdater.newUpdater(Atomics.class, "num");
updater.set(this, n);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class Atomics {
private String <warning descr="Private field 'str' is never assigned">str</warning>;
private static final AtomicReferenceFieldUpdater<Atomics, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Atomics.class, String.class, "str");
public void init(String s) {
updater.compareAndSet(this, null, s);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class Atomics {
private volatile String str;
private static final AtomicReferenceFieldUpdater<Atomics, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Atomics.class, String.class, "str");
public void init(String s) {
updater.compareAndSet(this, null, s);
}
}

View File

@@ -0,0 +1,11 @@
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class Atomics {
private volatile String <warning descr="Private field 'str' is never assigned">str</warning>;
private static final AtomicReferenceFieldUpdater<Atomics, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Atomics.class, String.class, "str");
public String getStr() {
return updater.get(this);
}
}

View File

@@ -0,0 +1,16 @@
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiFunction;
class Atomics {
private volatile String str;
private static final AtomicReferenceFieldUpdater<Atomics, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Atomics.class, String.class, "str");
public String getAndSet(String s) {
return update(updater::getAndSet, s);
}
private String update(BiFunction<Atomics, String, String> f, String s) {
return f.apply(this, s);
}
}

View File

@@ -0,0 +1,10 @@
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class Atomics {
private volatile String str;
public void set(String s) {
AtomicReferenceFieldUpdater<Atomics, String> updater = AtomicReferenceFieldUpdater.newUpdater(Atomics.class, String.class, "str");
updater.set(this, s);
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.highlighting
import com.intellij.JavaTestUtil
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
/**
* @author Pavel.Dolgov
*/
class AtomicReferenceImplicitUsageTest : LightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = JAVA_8
override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/atomicReferenceImplicitUsage/"
override fun setUp() {
super.setUp()
myFixture.enableInspections(UnusedDeclarationInspection())
}
fun testReferenceGet() = doTest()
fun testIntegerGet() = doTest()
fun testLongGet() = doTest()
fun testReferenceCAS() = doTest()
fun testIntegerCAS() = doTest()
fun testLongCAS() = doTest()
fun testReferenceGetAndSet() = doTest()
fun testIntegerGetAndSet() = doTest()
fun testLongGetAndSet() = doTest()
fun testReferenceSet() = doTest()
fun testIntegerSet() = doTest()
fun testLongSet() = doTest()
fun testIntegerIncrement() = doTest()
fun testLongIncrement() = doTest()
fun testNonVolatile() = doTest()
private fun doTest() {
myFixture.configureByFile(getTestName(false) + ".java")
myFixture.checkHighlighting()
}
}