From 2fe3ee616e2880e8900a8fb30dfe198f0ea17281 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 24 May 2018 12:06:50 +0700 Subject: [PATCH] ContractInferenceIndex: cache volatile fields to reduce resolve in purity inference Part of IDEA-191287 Intellij IDEA Ultimate [Linux] loses keystrokes --- .../inference/ContractInferenceIndex.kt | 108 ++++++++++-------- .../inference/PurityInferenceVisitor.java | 24 ++-- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/ContractInferenceIndex.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/ContractInferenceIndex.kt index 43ede709a3c3..1fbe3826f869 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/ContractInferenceIndex.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/ContractInferenceIndex.kt @@ -3,7 +3,9 @@ package com.intellij.codeInspection.dataFlow.inference import com.intellij.lang.LighterAST import com.intellij.lang.LighterASTNode +import com.intellij.psi.JavaTokenType import com.intellij.psi.impl.source.JavaLightStubBuilder +import com.intellij.psi.impl.source.JavaLightTreeUtil import com.intellij.psi.impl.source.PsiMethodImpl import com.intellij.psi.impl.source.tree.JavaElementType.* import com.intellij.psi.impl.source.tree.LightTreeUtil @@ -21,62 +23,78 @@ private val gist = GistManager.getInstance().newPsiFileGist("contractInference", } private fun indexFile(tree: LighterAST): Map { + val visitor = InferenceVisitor(tree) + visitor.visitNode(tree.root) + return visitor.result +} + +private class InferenceVisitor(val tree : LighterAST) : RecursiveLighterASTNodeWalkingVisitor(tree) { + var methodIndex = 0 + val volatileFieldNames = HashSet() val result = HashMap() - object : RecursiveLighterASTNodeWalkingVisitor(tree) { - var methodIndex = 0 - - override fun visitNode(element: LighterASTNode) { - if (element.tokenType === METHOD) { - calcData(tree, element)?.let { data -> result[methodIndex] = data } + override fun visitNode(element: LighterASTNode) { + when(element.tokenType) { + CLASS -> gatherFields(element) + METHOD -> { + calcData(element)?.let { data -> result[methodIndex] = data } methodIndex++ } - - if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return - - super.visitNode(element) } - }.visitNode(tree.root) - return result -} - -private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? { - val body = LightTreeUtil.firstChildOfType(tree, method, CODE_BLOCK) ?: return null - val statements = ContractInferenceInterpreter.getStatements(body, tree) - - val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements) - - val nullityVisitor = MethodReturnInferenceVisitor(tree, body) - val purityVisitor = PurityInferenceVisitor(tree, body) - for (statement in statements) { - walkMethodBody(tree, statement) { nullityVisitor.visitNode(it); purityVisitor.visitNode(it) } + if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return + super.visitNode(element) } - val notNullParams = inferNotNullParameters(tree, method, statements) - return createData(body, contracts, nullityVisitor.result, purityVisitor.result, notNullParams) -} - -private fun walkMethodBody(tree: LighterAST, root: LighterASTNode, processor: (LighterASTNode) -> Unit) { - object : RecursiveLighterASTNodeWalkingVisitor(tree) { - override fun visitNode(element: LighterASTNode) { - val type = element.tokenType - if (type === CLASS || type === FIELD || type === METHOD || type === ANNOTATION_METHOD || type === LAMBDA_EXPRESSION) return - - processor(element) - super.visitNode(element) + private fun gatherFields(aClass: LighterASTNode) { + val fields = LightTreeUtil.getChildrenOfType(tree, aClass, FIELD) + for (field in fields) { + val modifierList = LightTreeUtil.firstChildOfType(tree, field, MODIFIER_LIST) + val fieldName = JavaLightTreeUtil.getNameIdentifierText(tree, field) + if (modifierList != null && fieldName != null && + tree.getChildren(modifierList).any { modifier -> modifier.tokenType === JavaTokenType.VOLATILE_KEYWORD }) { + volatileFieldNames.add(fieldName) + } } - }.visitNode(root) -} + } -private fun createData(body: LighterASTNode, - contracts: List, - methodReturn: MethodReturnInferenceResult?, - purity: PurityInferenceResult?, - notNullParams: BitSet): MethodData? { - if (methodReturn == null && purity == null && contracts.isEmpty() && notNullParams.isEmpty) return null + private fun calcData(method: LighterASTNode): MethodData? { + val body = LightTreeUtil.firstChildOfType(tree, method, CODE_BLOCK) ?: return null + val statements = ContractInferenceInterpreter.getStatements(body, tree) - return MethodData(methodReturn, purity, contracts, notNullParams, body.startOffset, body.endOffset) + val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts(statements) + + val nullityVisitor = MethodReturnInferenceVisitor(tree, body) + val purityVisitor = PurityInferenceVisitor(tree, body, volatileFieldNames) + for (statement in statements) { + walkMethodBody(statement) { nullityVisitor.visitNode(it); purityVisitor.visitNode(it) } + } + val notNullParams = inferNotNullParameters(tree, method, statements) + + return createData(body, contracts, nullityVisitor.result, purityVisitor.result, notNullParams) + } + + private fun walkMethodBody(root: LighterASTNode, processor: (LighterASTNode) -> Unit) { + object : RecursiveLighterASTNodeWalkingVisitor(tree) { + override fun visitNode(element: LighterASTNode) { + val type = element.tokenType + if (type === CLASS || type === FIELD || type === METHOD || type === ANNOTATION_METHOD || type === LAMBDA_EXPRESSION) return + + processor(element) + super.visitNode(element) + } + }.visitNode(root) + } + + private fun createData(body: LighterASTNode, + contracts: List, + methodReturn: MethodReturnInferenceResult?, + purity: PurityInferenceResult?, + notNullParams: BitSet): MethodData? { + if (methodReturn == null && purity == null && contracts.isEmpty() && notNullParams.isEmpty) return null + + return MethodData(methodReturn, purity, contracts, notNullParams, body.startOffset, body.endOffset) + } } fun getIndexedData(method: PsiMethodImpl): MethodData? = gist.getFileData(method.containingFile)?.get(JavaStubImplUtil.getMethodStubIndex(method)) \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/PurityInferenceVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/PurityInferenceVisitor.java index 1fdecb312bfc..f09e4e2f5c96 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/PurityInferenceVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inference/PurityInferenceVisitor.java @@ -14,20 +14,23 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Set; import static com.intellij.psi.impl.source.tree.JavaElementType.*; class PurityInferenceVisitor { private final LighterAST tree; private final LighterASTNode body; + private final Set myVolatileFieldNames; private final List mutatedRefs = new ArrayList<>(); private boolean hasReturns; private boolean hasVolatileReads; private final List calls = new ArrayList<>(); - PurityInferenceVisitor(LighterAST tree, LighterASTNode body) { + PurityInferenceVisitor(LighterAST tree, LighterASTNode body, Set volatileFieldNames) { this.tree = tree; this.body = body; + myVolatileFieldNames = volatileFieldNames; } void visitNode(LighterASTNode element) { @@ -44,19 +47,16 @@ class PurityInferenceVisitor { else if (isCall(element, type)) { calls.add(element); } - else if (type == REFERENCE_EXPRESSION) { + else if (type == REFERENCE_EXPRESSION && !myVolatileFieldNames.isEmpty()) { 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 (modifier.getTokenType() == JavaTokenType.VOLATILE_KEYWORD) { - hasVolatileReads = true; - break; - } - } + if (myVolatileFieldNames.contains(JavaLightTreeUtil.getNameIdentifierText(tree, element))) { + LighterASTNode target = new FileLocalResolver(tree).resolveLocally(element).getTarget(); + if (target != null && target.getTokenType() == FIELD) { + LighterASTNode modifierList = LightTreeUtil.firstChildOfType(tree, target, MODIFIER_LIST); + hasVolatileReads |= modifierList != null && + tree.getChildren(modifierList).stream() + .anyMatch(modifier -> modifier.getTokenType() == JavaTokenType.VOLATILE_KEYWORD); } } }