[java-dfa] Decouple DfaValueFactory from Java PSI

GitOrigin-RevId: ccd98b024398cad958cd63c6ff5e232b981239dd
This commit is contained in:
Tagir Valeev
2021-04-15 08:58:58 +03:00
committed by intellij-monorepo-bot
parent 7b0c37213d
commit 63ecb6d3e1
13 changed files with 213 additions and 181 deletions
@@ -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))
}
@@ -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));
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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();
@@ -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) {
@@ -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));
}
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
}
@@ -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());
}
}