Source purity inference: do not infer purity for methods which read volatile field (own field within the same class only)

This commit is contained in:
Tagir Valeev
2018-01-19 17:52:23 +07:00
parent 87c90ac84b
commit fc0d240488
3 changed files with 43 additions and 2 deletions
@@ -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)
}
@@ -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<LighterASTNode> mutatedRefs = new ArrayList<>();
private boolean hasReturns;
private boolean hasVolatileReads;
private List<LighterASTNode> 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)),
@@ -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