mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-dfa] Decouple DfaValueFactory from Java PSI
GitOrigin-RevId: ccd98b024398cad958cd63c6ff5e232b981239dd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7b0c37213d
commit
63ecb6d3e1
+2
-1
@@ -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<PsiExpression, Nullability> fieldNullability =
|
||||
NullabilityUtil.getNullabilityFromFieldInitializers((PsiField)owner);
|
||||
if (fieldNullability.second == DfaNullability.toNullability(nullability)) {
|
||||
|
||||
@@ -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<VariableDescriptor>) : TransferTarget {
|
||||
override fun dispatch(state: DfaMemoryState, runner: DataFlowRunner): List<DfaInstructionState> {
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -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<Trap> 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<PlainDescriptor> 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));
|
||||
|
||||
+44
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+118
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+3
-1
@@ -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));
|
||||
}
|
||||
|
||||
+5
-2
@@ -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;
|
||||
|
||||
+6
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-162
@@ -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<DfaValue> 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<PsiElement, List<DfaVariableValue>> 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<DfaVariableValue> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user