From 63ecb6d3e1e1fad5b9969abc728f995021eb7c66 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 13 Apr 2021 18:05:42 +0700 Subject: [PATCH] [java-dfa] Decouple DfaValueFactory from Java PSI GitOrigin-RevId: ccd98b024398cad958cd63c6ff5e232b981239dd --- .../dataFlow/TrackingRunner.java | 3 +- .../dataFlow/controlTransfer.kt | 6 +- .../dataFlow/java/ControlFlowAnalyzer.java | 15 +- .../dataFlow/java/DfaExpressionFactory.java | 45 ++++- .../dataFlow/jvm/FieldChecker.java | 118 ++++++++++++ .../dataFlow/jvm/SpecialField.java | 5 +- .../descriptors/ArrayElementDescriptor.java | 3 +- .../jvm/descriptors/GetterDescriptor.java | 4 +- .../jvm/descriptors/PlainDescriptor.java | 7 +- .../jvm/descriptors/PsiVarDescriptor.java | 9 +- .../dataFlow/value/DfaValueFactory.java | 173 ++---------------- .../dataFlow/value/DfaVariableValue.java | 2 +- .../dataFlow/value/VariableDescriptor.java | 4 +- 13 files changed, 213 insertions(+), 181 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/FieldChecker.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java index 9fea86712882..d7dcbe836973 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java @@ -9,6 +9,7 @@ import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.FactExtractor import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.MemoryStateChange; import com.intellij.codeInspection.dataFlow.TrackingDfaMemoryState.Relation; import com.intellij.codeInspection.dataFlow.java.JavaDfaInstructionVisitor; +import com.intellij.codeInspection.dataFlow.jvm.FieldChecker; import com.intellij.codeInspection.dataFlow.jvm.SpecialField; import com.intellij.codeInspection.dataFlow.lang.DfaInterceptor; import com.intellij.codeInspection.dataFlow.lang.ir.inst.*; @@ -1226,7 +1227,7 @@ public final class TrackingRunner extends DataFlowRunner { } return new CauseItem(message, anchor); } - if (owner instanceof PsiField && getFactory().canTrustFieldInitializer((PsiField)owner)) { + if (owner instanceof PsiField && FieldChecker.getChecker(getFactory().getContext()).canTrustFieldInitializer((PsiField)owner)) { Pair fieldNullability = NullabilityUtil.getNullabilityFromFieldInitializers((PsiField)owner); if (fieldNullability.second == DfaNullability.toNullability(nullability)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt index 937275accea5..49b80d245a15 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt @@ -22,6 +22,7 @@ import com.intellij.codeInspection.dataFlow.lang.ir.ControlFlow import com.intellij.codeInspection.dataFlow.lang.ir.inst.ControlTransferInstruction import com.intellij.codeInspection.dataFlow.value.DfaValue import com.intellij.codeInspection.dataFlow.value.DfaValueFactory +import com.intellij.codeInspection.dataFlow.value.VariableDescriptor import com.intellij.psi.* import com.intellij.util.containers.FList @@ -46,9 +47,10 @@ interface TransferTarget { data class ExceptionTransfer(val throwable: TypeConstraint) : TransferTarget { override fun toString(): String = "Exception($throwable)" } -data class InstructionTransfer(val offset: ControlFlow.ControlFlowOffset, private val block: PsiElement?) : TransferTarget { +data class InstructionTransfer(val offset: ControlFlow.ControlFlowOffset, private val varsToFlush: List) : TransferTarget { override fun dispatch(state: DfaMemoryState, runner: DataFlowRunner): List { - runner.factory.getVariablesInBlock(block).forEach(state::flushVariable) + val varFactory = runner.factory.varFactory + varsToFlush.forEach { desc -> state.flushVariable(varFactory.createVariableValue(desc)) } return listOf(DfaInstructionState(runner.getInstruction(offset.instructionOffset), state)) } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index 26b79df5a1e8..e2d14a47bf6a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -413,7 +413,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { private void jumpOut(PsiElement exitedStatement) { if (exitedStatement != null && PsiTreeUtil.isAncestor(myCodeFragment, exitedStatement, false)) { - controlTransfer(new InstructionTransfer(getEndOffset(exitedStatement), exitedStatement), + controlTransfer(createTransfer(exitedStatement, exitedStatement), getTrapsInsideElement(exitedStatement)); } else { // Jumping out of analyzed code fragment @@ -435,7 +435,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { PsiStatement continuedStatement = statement.findContinuedStatement(); if (continuedStatement instanceof PsiLoopStatement && PsiTreeUtil.isAncestor(myCodeFragment, continuedStatement, true)) { PsiStatement body = ((PsiLoopStatement)continuedStatement).getBody(); - controlTransfer(new InstructionTransfer(getEndOffset(body), body), getTrapsInsideElement(body)); + controlTransfer(createTransfer(body, body), getTrapsInsideElement(body)); } else { // Jumping out of analyzed code fragment controlTransfer(ReturnTransfer.INSTANCE, getTrapsInsideElement(myCodeFragment)); @@ -1078,7 +1078,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { processTryWithResources(resourceList, tryBlock); - InstructionTransfer gotoEnd = new InstructionTransfer(getEndOffset(statement), tryBlock); + InstructionTransfer gotoEnd = createTransfer(statement, tryBlock); FList singleFinally = FList.createFromReversed(ContainerUtil.createMaybeSingletonList(finallyDescriptor)); controlTransfer(gotoEnd, singleFinally); @@ -1107,6 +1107,13 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { finishElement(statement); } + @NotNull + private InstructionTransfer createTransfer(PsiElement exitedStatement, PsiElement blockToFlush) { + List varsToFlush = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(blockToFlush, PsiVariable.class), + variable -> new PlainDescriptor(variable)); + return new InstructionTransfer(getEndOffset(exitedStatement), varsToFlush); + } + void pushTrap(Trap elem) { myTrapStack = myTrapStack.prepend(elem); } @@ -1136,7 +1143,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } if (twrFinallyDescriptor != null) { - InstructionTransfer gotoEnd = new InstructionTransfer(getEndOffset(resourceList), tryBlock); + InstructionTransfer gotoEnd = createTransfer(resourceList, tryBlock); controlTransfer(gotoEnd, FList.createFromReversed(ContainerUtil.createMaybeSingletonList(twrFinallyDescriptor))); popTrap(TwrFinally.class); pushTrap(new InsideFinally(resourceList)); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/DfaExpressionFactory.java index 1f90f5b0ec5f..39c4416443ae 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/DfaExpressionFactory.java @@ -17,6 +17,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.VariableDescriptor; import com.intellij.psi.*; import com.intellij.psi.impl.JavaConstantExpressionEvaluator; +import com.intellij.psi.impl.source.PsiFieldImpl; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PropertyUtilBase; import com.intellij.psi.util.PsiTreeUtil; @@ -25,6 +26,7 @@ import com.intellij.util.ObjectUtils; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.ExpressionUtils; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -107,7 +109,7 @@ public class DfaExpressionFactory { if (target instanceof PsiVariable) { PsiVariable variable = (PsiVariable)target; if (!PsiUtil.isAccessedForWriting(refExpr)) { - DfaValue constValue = factory.getConstantFromVariable(variable); + DfaValue constValue = getConstantFromVariable(factory, variable); if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) return constValue; } } @@ -227,4 +229,45 @@ public class DfaExpressionFactory { } return loopElement == null ? factory.getUnknown() : DfaUtil.boxUnbox(loopElement, targetType); } + + /** + * @param factory factory to create new values + * @param variable variable to create a constant based on its value + * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant + */ + @Nullable + private static DfaValue getConstantFromVariable(DfaValueFactory factory, PsiVariable variable) { + if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) return null; + Object value = variable.computeConstantValue(); + PsiType type = variable.getType(); + if (value == null) { + Boolean boo = computeJavaLangBooleanFieldReference(variable); + if (boo != null) { + DfaValue unboxed = factory.getBoolean(boo); + return factory.getWrapperFactory().createWrapper(DfTypes.typedObject(type, Nullability.NOT_NULL), SpecialField.UNBOX, unboxed); + } + if (DfaUtil.isEmptyCollectionConstantField(variable)) { + return factory.getConstant(variable, type); + } + PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); + initializer = PsiUtil.skipParenthesizedExprDown(initializer); + if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) { + return factory.getNull(); + } + if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) { + return factory.getConstant(variable, type); + } + return null; + } + return factory.getConstant(value, type); + } + + @Nullable + private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) { + if (!(variable instanceof PsiField)) return null; + PsiClass psiClass = ((PsiField)variable).getContainingClass(); + if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) return null; + @NonNls String name = variable.getName(); + return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/FieldChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/FieldChecker.java new file mode 100644 index 000000000000..4a0140dff754 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/FieldChecker.java @@ -0,0 +1,118 @@ +// Copyright 2000-2021 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.codeInspection.dataFlow.jvm; + +import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.psi.*; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class FieldChecker { + private final boolean myTrustDirectFieldInitializers; + private final boolean myTrustFieldInitializersInConstructors; + private final boolean myCanInstantiateItself; + private final PsiClass myClass; + + FieldChecker(PsiElement context) { + PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); + PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass ? (PsiClass)context : null; + myClass = contextClass; + if (method == null || myClass == null) { + myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; + return; + } + // Indirect instantiation via other class is still possible, but hopefully unlikely + ClassInitializationInfo info = CachedValuesManager.getProjectPsiDependentCache(contextClass, ClassInitializationInfo::new); + myCanInstantiateItself = info.myCanInstantiateItself; + if (method.hasModifierProperty(PsiModifier.STATIC) || method.isConstructor()) { + myTrustDirectFieldInitializers = true; + myTrustFieldInitializersInConstructors = false; + return; + } + myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; + myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; + } + + public boolean canTrustFieldInitializer(PsiField field) { + if (field.hasInitializer()) { + boolean staticField = field.hasModifierProperty(PsiModifier.STATIC); + if (staticField && myClass != null && field.getContainingClass() != myClass) return true; + return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); + } + return myTrustFieldInitializersInConstructors; + } + + public static FieldChecker getChecker(@Nullable PsiElement context) { + if (context == null) return new FieldChecker(null); + return CachedValuesManager.getProjectPsiDependentCache(context, FieldChecker::new); + } + + private static class ClassInitializationInfo { + private static final CallMatcher SAFE_CALLS = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); + + final boolean myCanInstantiateItself; + final boolean myCtorsCallMethods; + final boolean mySuperCtorsCallMethods; + + ClassInitializationInfo(@NotNull PsiClass psiClass) { + // Indirect instantiation via other class is still possible, but hopefully unlikely + boolean canInstantiateItself = false; + for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiMember && ((PsiMember)child).hasModifierProperty(PsiModifier.STATIC) && + SyntaxTraverser.psiTraverser(child).filter(PsiNewExpression.class) + .filterMap(PsiNewExpression::getClassReference) + .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { + canInstantiateItself = true; + break; + } + } + myCanInstantiateItself = canInstantiateItself; + mySuperCtorsCallMethods = + !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); + myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); + } + + private static boolean canCallMethodsInConstructors(@NotNull PsiClass aClass, boolean virtual) { + boolean inByteCode = false; + if (aClass instanceof PsiCompiledElement) { + inByteCode = true; + PsiElement navigationElement = aClass.getNavigationElement(); + if (navigationElement instanceof PsiClass) { + aClass = (PsiClass)navigationElement; + } + } + for (PsiMethod constructor : aClass.getConstructors()) { + if (inByteCode && JavaMethodContractUtil.isPure(constructor) && + !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { + // While pure constructor may call pure overridable method, our current implementation + // of bytecode inference will not infer the constructor purity in this case. + // So if we inferred a constructor purity from bytecode we can currently rely that + // no overridable methods are called there. + continue; + } + if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) return true; + + PsiCodeBlock body = constructor.getBody(); + if (body == null) continue; + + for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { + PsiReferenceExpression methodExpression = call.getMethodExpression(); + if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) continue; + if (SAFE_CALLS.test(call)) continue; + if (!virtual) return true; + + PsiMethod target = call.resolveMethod(); + if (target != null && PsiUtil.canBeOverridden(target)) return true; + } + } + + return false; + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java index 896f61ea130e..fbfbff1ab668 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java @@ -179,7 +179,8 @@ public enum SpecialField implements VariableDescriptor { } @Override - public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue) { + public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue, + @Nullable PsiElement context) { return getDfType(thisValue.getQualifier()).meet(DfaNullability.NULLABLE.asDfType()); } @@ -290,7 +291,7 @@ public enum SpecialField implements VariableDescriptor { DfaVariableValue variableValue = (DfaVariableValue)qualifier; PsiField psiVariable = ObjectUtils.tryCast(variableValue.getPsiVariable(), PsiField.class); if (psiVariable != null && - factory.canTrustFieldInitializer(psiVariable) && + FieldChecker.getChecker(factory.getContext()).canTrustFieldInitializer(psiVariable) && psiVariable.hasModifierProperty(PsiModifier.STATIC) && psiVariable.hasModifierProperty(PsiModifier.FINAL)) { PsiExpression initializer = psiVariable.getInitializer(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java index 99aac7b568dd..6c2674f7cd5f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java @@ -38,7 +38,8 @@ public final class ArrayElementDescriptor implements VariableDescriptor { } @Override - public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue) { + public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue, + @Nullable PsiElement context) { DfaVariableValue qualifier = thisValue.getQualifier(); DfType dfType = getDfType(qualifier); if (qualifier != null && dfType instanceof DfReferenceType) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/GetterDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/GetterDescriptor.java index 9f3478435cd3..57ef7e6fae6f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/GetterDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/GetterDescriptor.java @@ -92,7 +92,9 @@ public final class GetterDescriptor extends PsiVarDescriptor { } @Override - @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, @NotNull DfaVariableValue value) { + @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, + @NotNull DfaVariableValue value, + @Nullable PsiElement context) { PsiType type = getType(value.getQualifier()); return DfaNullability.fromNullability(DfaPsiUtil.getElementNullabilityIgnoringParameterInference(type, var)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PlainDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PlainDescriptor.java index 86292448e878..01a64aceeb34 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PlainDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PlainDescriptor.java @@ -7,6 +7,7 @@ import com.intellij.codeInspection.dataFlow.DfaNullability; import com.intellij.codeInspection.dataFlow.DfaPsiUtil; import com.intellij.codeInspection.dataFlow.DfaUtil; import com.intellij.codeInspection.dataFlow.NullabilityUtil; +import com.intellij.codeInspection.dataFlow.jvm.FieldChecker; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; @@ -88,7 +89,9 @@ public final class PlainDescriptor extends PsiVarDescriptor { } @Override - @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, @NotNull DfaVariableValue value) { + @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, + @NotNull DfaVariableValue value, + @Nullable PsiElement context) { if (var instanceof PsiField && DfaUtil.hasInitializationHacks((PsiField)var)) { return DfaNullability.FLUSHED; } @@ -109,7 +112,7 @@ public final class PlainDescriptor extends PsiVarDescriptor { } } - if (var instanceof PsiField && value.getFactory().canTrustFieldInitializer((PsiField)var)) { + if (var instanceof PsiField && FieldChecker.getChecker(context).canTrustFieldInitializer((PsiField)var)) { return DfaNullability.fromNullability(NullabilityUtil.getNullabilityFromFieldInitializers((PsiField)var).second); } return DfaNullability.UNKNOWN; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PsiVarDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PsiVarDescriptor.java index 3c5478c66b16..c8cdb65f5ae4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PsiVarDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/PsiVarDescriptor.java @@ -61,7 +61,8 @@ abstract class PsiVarDescriptor implements VariableDescriptor { } @Override - public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue) { + public @NotNull DfType getInitialDfType(@NotNull DfaVariableValue thisValue, + @Nullable PsiElement context) { DfType dfType = getDfType(thisValue.getQualifier()); PsiModifierListOwner psi = ObjectUtils.tryCast(getPsiElement(), PsiModifierListOwner.class); if (psi == null) return dfType; @@ -70,12 +71,14 @@ abstract class PsiVarDescriptor implements VariableDescriptor { } if (dfType instanceof DfReferenceType) { dfType = dfType.meet(Mutability.getMutability(psi).asDfType()); - dfType = dfType.meet(calcCanBeNull(psi, thisValue).asDfType()); + dfType = dfType.meet(calcCanBeNull(psi, thisValue, context).asDfType()); } return dfType; } - @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, @NotNull DfaVariableValue value) { + @NotNull DfaNullability calcCanBeNull(@NotNull PsiModifierListOwner var, + @NotNull DfaVariableValue value, + @Nullable PsiElement context) { return DfaNullability.UNKNOWN; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index 05ea5e8f022f..30dfcf1729ac 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -3,23 +3,19 @@ package com.intellij.codeInspection.dataFlow.value; import com.intellij.codeInsight.Nullability; -import com.intellij.codeInspection.dataFlow.*; -import com.intellij.codeInspection.dataFlow.jvm.SpecialField; -import com.intellij.codeInspection.dataFlow.jvm.descriptors.PlainDescriptor; +import com.intellij.codeInspection.dataFlow.DfaControlTransferValue; +import com.intellij.codeInspection.dataFlow.TransferTarget; +import com.intellij.codeInspection.dataFlow.Trap; import com.intellij.codeInspection.dataFlow.types.DfType; import com.intellij.codeInspection.dataFlow.types.DfTypes; -import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.PsiFieldImpl; -import com.intellij.psi.util.*; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiEnumConstant; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiType; import com.intellij.util.containers.FList; import com.intellij.util.containers.FactoryMap; -import com.siyeh.ig.callMatcher.CallMatcher; -import com.siyeh.ig.psiutils.ExpressionUtils; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,12 +23,9 @@ import java.util.*; public class DfaValueFactory { private final @NotNull List myValues = new ArrayList<>(); - private final @NotNull FieldChecker myFieldChecker; private final @NotNull Project myProject; + private final @Nullable PsiElement myContext; private @Nullable DfaVariableValue myAssertionDisabled; - private final @NotNull Map> myVariablesInBlock = - FactoryMap.create(block -> ContainerUtil.map(PsiTreeUtil.findChildrenOfType(block, PsiVariable.class), - variable -> PlainDescriptor.createVariableValue(this, variable))); /** * @param project a project in which context the analysis is performed @@ -40,7 +33,7 @@ public class DfaValueFactory { */ public DfaValueFactory(@NotNull Project project, @Nullable PsiElement context) { myProject = project; - myFieldChecker = new FieldChecker(context); + myContext = context; myValues.add(null); myVarFactory = new DfaVariableValue.Factory(this); myBoxedFactory = new DfaWrappedValue.Factory(this); @@ -48,8 +41,8 @@ public class DfaValueFactory { myTypeValueFactory = new DfaTypeValue.Factory(this); } - public boolean canTrustFieldInitializer(PsiField field) { - return myFieldChecker.canTrustFieldInitializer(field); + public @Nullable PsiElement getContext() { + return myContext; } @NotNull @@ -71,10 +64,6 @@ public class DfaValueFactory { return myValues.size() - 1; } - public @NotNull List getVariablesInBlock(@Nullable PsiElement block) { - return block == null ? Collections.emptyList() : myVariablesInBlock.get(block); - } - public DfaValue getValue(int id) { return myValues.get(id); } @@ -133,51 +122,11 @@ public class DfaValueFactory { return fromDfType(DfTypes.constant(value, type)); } - /** - * @param variable variable to create a constant based on its value - * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant - */ - @Nullable - public DfaValue getConstantFromVariable(PsiVariable variable) { - if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) return null; - Object value = variable.computeConstantValue(); - PsiType type = variable.getType(); - if (value == null) { - Boolean boo = computeJavaLangBooleanFieldReference(variable); - if (boo != null) { - DfaValue unboxed = getBoolean(boo); - return getWrapperFactory().createWrapper(DfTypes.typedObject(type, Nullability.NOT_NULL), SpecialField.UNBOX, unboxed); - } - if (DfaUtil.isEmptyCollectionConstantField(variable)) { - return getConstant(variable, type); - } - PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); - initializer = PsiUtil.skipParenthesizedExprDown(initializer); - if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) { - return getNull(); - } - if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) { - return getConstant(variable, type); - } - return null; - } - return getConstant(value, type); - } - @NotNull public Project getProject() { return myProject; } - @Nullable - private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) { - if (!(variable instanceof PsiField)) return null; - PsiClass psiClass = ((PsiField)variable).getContainingClass(); - if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) return null; - @NonNls String name = variable.getName(); - return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; - } - @NotNull public DfaTypeValue fromDfType(@NotNull DfType dfType) { return myTypeValueFactory.create(dfType); @@ -226,104 +175,4 @@ public class DfaValueFactory { return myBinOpFactory; } - private static class ClassInitializationInfo { - private static final CallMatcher SAFE_CALLS = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); - - final boolean myCanInstantiateItself; - final boolean myCtorsCallMethods; - final boolean mySuperCtorsCallMethods; - - ClassInitializationInfo(@NotNull PsiClass psiClass) { - // Indirect instantiation via other class is still possible, but hopefully unlikely - boolean canInstantiateItself = false; - for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiMember && ((PsiMember)child).hasModifierProperty(PsiModifier.STATIC) && - SyntaxTraverser.psiTraverser(child).filter(PsiNewExpression.class) - .filterMap(PsiNewExpression::getClassReference) - .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { - canInstantiateItself = true; - break; - } - } - myCanInstantiateItself = canInstantiateItself; - mySuperCtorsCallMethods = - !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); - myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); - } - - private static boolean canCallMethodsInConstructors(@NotNull PsiClass aClass, boolean virtual) { - boolean inByteCode = false; - if (aClass instanceof PsiCompiledElement) { - inByteCode = true; - PsiElement navigationElement = aClass.getNavigationElement(); - if (navigationElement instanceof PsiClass) { - aClass = (PsiClass)navigationElement; - } - } - for (PsiMethod constructor : aClass.getConstructors()) { - if (inByteCode && JavaMethodContractUtil.isPure(constructor) && - !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { - // While pure constructor may call pure overridable method, our current implementation - // of bytecode inference will not infer the constructor purity in this case. - // So if we inferred a constructor purity from bytecode we can currently rely that - // no overridable methods are called there. - continue; - } - if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) return true; - - PsiCodeBlock body = constructor.getBody(); - if (body == null) continue; - - for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { - PsiReferenceExpression methodExpression = call.getMethodExpression(); - if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) continue; - if (SAFE_CALLS.test(call)) continue; - if (!virtual) return true; - - PsiMethod target = call.resolveMethod(); - if (target != null && PsiUtil.canBeOverridden(target)) return true; - } - } - - return false; - } - } - - private static class FieldChecker { - private final boolean myTrustDirectFieldInitializers; - private final boolean myTrustFieldInitializersInConstructors; - private final boolean myCanInstantiateItself; - private final PsiClass myClass; - - FieldChecker(PsiElement context) { - PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); - PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass ? (PsiClass)context : null; - myClass = contextClass; - if (method == null || myClass == null) { - myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; - return; - } - // Indirect instantiation via other class is still possible, but hopefully unlikely - ClassInitializationInfo info = CachedValuesManager.getCachedValue(contextClass, () -> CachedValueProvider.Result - .create(new ClassInitializationInfo(contextClass), PsiModificationTracker.MODIFICATION_COUNT)); - myCanInstantiateItself = info.myCanInstantiateItself; - if (method.hasModifierProperty(PsiModifier.STATIC) || method.isConstructor()) { - myTrustDirectFieldInitializers = true; - myTrustFieldInitializersInConstructors = false; - return; - } - myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; - myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; - } - - boolean canTrustFieldInitializer(PsiField field) { - if (field.hasInitializer()) { - boolean staticField = field.hasModifierProperty(PsiModifier.STATIC); - if (staticField && myClass != null && field.getContainingClass() != myClass) return true; - return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); - } - return myTrustFieldInitializersInConstructors; - } - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index 2cbbb27c42c0..41d2693a72e0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -152,7 +152,7 @@ public final class DfaVariableValue extends DfaValue { public DfType getInherentType() { if(myInherentType == null) { - myInherentType = myDescriptor.getInitialDfType(this); + myInherentType = myDescriptor.getInitialDfType(this, getFactory().getContext()); } return myInherentType; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java index fa1cb6997497..5c372f8ad4c3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/VariableDescriptor.java @@ -82,12 +82,14 @@ public interface VariableDescriptor { /** * Returns the DfType the value with this descriptor has at the beginning of the interpretation * @param thisValue DfaVariableValue representing the current variable + * @param context * @return Initial DfType of the value. May differ from {@link #getDfType(DfaVariableValue)} result, * as additional information like initial nullability or range may be known from annotations, which * may change later if the value is reassigned. */ @NotNull - default DfType getInitialDfType(@NotNull DfaVariableValue thisValue) { + default DfType getInitialDfType(@NotNull DfaVariableValue thisValue, + @Nullable PsiElement context) { return getDfType(thisValue.getQualifier()); } }