diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceIndex.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceIndex.kt index c629423c5ffa..2a498d674e7d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceIndex.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceIndex.kt @@ -30,7 +30,7 @@ import java.util.* * @author peter */ -private val gist = GistManager.getInstance().newPsiFileGist("contractInference", 5, MethodDataExternalizer) { file -> +private val gist = GistManager.getInstance().newPsiFileGist("contractInference", 6, MethodDataExternalizer) { file -> indexFile(file.node.lighterAST) } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java index 3cffb4271b11..62e4eec086ed 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PurityInference.java @@ -19,7 +19,10 @@ import com.intellij.lang.LighterAST; import com.intellij.lang.LighterASTNode; import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.JavaTokenType; +import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiType; +import com.intellij.psi.impl.cache.RecordUtil; +import com.intellij.psi.impl.source.FileLocalResolver; import com.intellij.psi.impl.source.JavaLightTreeUtil; import com.intellij.psi.impl.source.PsiMethodImpl; import com.intellij.psi.impl.source.tree.LightTreeUtil; @@ -60,6 +63,7 @@ public class PurityInference { private final LighterASTNode body; private List mutatedRefs = new ArrayList<>(); private boolean hasReturns; + private boolean hasVolatileReads; private List calls = new ArrayList<>(); PurityInferenceVisitor(LighterAST tree, LighterASTNode body) { @@ -81,6 +85,23 @@ public class PurityInference { else if (isCall(element, type)) { calls.add(element); } + else if (type == REFERENCE_EXPRESSION) { + LighterASTNode qualifier = JavaLightTreeUtil.findExpressionChild(tree, element); + if (qualifier == null || qualifier.getTokenType() == THIS_EXPRESSION) { + LighterASTNode target = new FileLocalResolver(tree).resolveLocally(element).getTarget(); + if (target != null && target.getTokenType() == FIELD) { + LighterASTNode modifierList = LightTreeUtil.firstChildOfType(tree, target, MODIFIER_LIST); + if (modifierList != null) { + for (LighterASTNode modifier : tree.getChildren(modifierList)) { + if (RecordUtil.intern(tree.getCharTable(), modifier).equals(PsiModifier.VOLATILE)) { + hasVolatileReads = true; + break; + } + } + } + } + } + } } private boolean isCall(@NotNull LighterASTNode element, IElementType type) { @@ -95,7 +116,7 @@ public class PurityInference { @Nullable PurityInferenceResult getResult() { - if (calls.size() > 1 || !hasReturns) return null; + if (calls.size() > 1 || !hasReturns || hasVolatileReads) return null; int bodyStart = body.getStartOffset(); return new PurityInferenceResult(ContainerUtil.map(mutatedRefs, node -> ExpressionRange.create(node, bodyStart)), diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/PurityInferenceFromSourceTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInspection/PurityInferenceFromSourceTest.groovy index 8a35fa76f7e5..331868b34087 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/PurityInferenceFromSourceTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/PurityInferenceFromSourceTest.groovy @@ -231,6 +231,26 @@ class Another { """ } + void "test plain field read"() { + assertPure true, """ +int x; + +int get() { + return x; +} +""" + } + + void "test volatile field read"() { + assertPure false, """ +volatile int x; + +int get() { + return x; +} +""" + } + private void assertPure(boolean expected, String classBody) { def clazz = myFixture.addClass("final class Foo { $classBody }") assert !((PsiFileImpl) clazz.containingFile).contentsLoaded