mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 21:41:24 +07:00
Java: Take into account writes to the field done via AtomicFieldUpdater (IDEA-152262)
This commit is contained in:
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user