Track getClass() calls (IDEA-195220)

GitOrigin-RevId: 2927b603e5192a14c421e33d5cf1a9c3235015df
This commit is contained in:
Tagir Valeev
2019-07-24 05:51:18 +03:00
committed by intellij-monorepo-bot
parent 4b18e94734
commit e77596602d
10 changed files with 203 additions and 3 deletions
@@ -38,6 +38,7 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.BitUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -409,6 +410,8 @@ public class GuessManagerImpl extends GuessManager {
}
private static class GuessTypeVisitor extends JavaElementVisitor {
private static final CallMatcher OBJECT_GET_CLASS =
CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0);
private final @NotNull PsiExpression myPlace;
PsiType mySpecificType;
private boolean myNeedDfa;
@@ -462,6 +465,17 @@ public class GuessManagerImpl extends GuessManager {
super.visitTypeCastExpression(expression);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
if (OBJECT_GET_CLASS.test(call)) {
PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression());
if (qualifier != null && ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) {
myNeedDfa = true;
}
}
super.visitMethodCallExpression(call);
}
@Override
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
if (ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) {
@@ -14,8 +14,11 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PropertyUtilBase;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import gnu.trove.THashMap;
@@ -1050,6 +1053,21 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return !isNegated || (dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls());
}
if (dfaLeft instanceof DfaVariableValue && (dfaRight instanceof DfaVariableValue || dfaRight instanceof DfaConstValue) &&
(type == RelationType.NE || type == RelationType.EQ)) {
DfaConstValue leftConstant = getConstantValue(dfaLeft, false);
DfaConstValue rightConstant = getConstantValue(dfaRight, false);
if (leftConstant != null && leftConstant.getValue() instanceof PsiType && rightConstant == null) {
assert dfaRight instanceof DfaVariableValue; // otherwise rightConstant is not-null
ThreeState result = processGetClass((DfaVariableValue)dfaRight, (PsiType)leftConstant.getValue(), isNegated);
if (result != ThreeState.UNSURE) return result.toBoolean();
}
if (rightConstant != null && rightConstant.getValue() instanceof PsiType && leftConstant == null) {
ThreeState result = processGetClass((DfaVariableValue)dfaLeft, (PsiType)rightConstant.getValue(), isNegated);
if (result != ThreeState.UNSURE) return result.toBoolean();
}
}
if (isNull(dfaLeft) && isNotNull(dfaRight) || isNull(dfaRight) && isNotNull(dfaLeft)) {
return isNegated;
}
@@ -1077,6 +1095,56 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return applyUnboxedRelation(dfaLeft, dfaRight, isNegated);
}
@NotNull
private ThreeState processGetClass(DfaVariableValue variable, PsiType value, boolean negated) {
EqClass eqClass = getEqClass(variable);
List<DfaVariableValue> variables = eqClass == null ? Collections.singletonList(variable)
: eqClass.getVariables(false);
boolean hasUnprocessed = false;
for (DfaVariableValue var : variables) {
PsiModifierListOwner psi = var.getPsiVariable();
DfaVariableValue qualifier = var.getQualifier();
if (psi instanceof PsiMethod && PsiTypesUtil.isGetClass((PsiMethod)psi) && qualifier != null) {
switch (applyGetClassRelation(qualifier, value, negated)) {
case NO:
return ThreeState.NO;
case YES:
continue;
case UNSURE:
break;
}
}
hasUnprocessed = true;
}
return hasUnprocessed ? ThreeState.UNSURE : ThreeState.YES;
}
@NotNull
private ThreeState applyGetClassRelation(@NotNull DfaVariableValue qualifier, @NotNull PsiType value, boolean negated) {
DfaPsiType dfaType = myFactory.createDfaType(value);
TypeConstraint constraint = TypeConstraint.exact(dfaType);
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(value);
if (!negated) {
if (psiClass != null && (psiClass.isInterface() || psiClass.hasModifierProperty(PsiModifier.ABSTRACT))) {
// getClass() result cannot be an interface or an abstract class
return ThreeState.NO;
}
return ThreeState.fromBoolean(applyFact(qualifier, DfaFactType.TYPE_CONSTRAINT, constraint));
}
if (psiClass != null && (psiClass.isInterface() || psiClass.hasModifierProperty(PsiModifier.ABSTRACT))) {
return ThreeState.YES;
}
TypeConstraint existingConstraint = getValueFact(qualifier, DfaFactType.TYPE_CONSTRAINT);
if (existingConstraint != null && existingConstraint.isExact()) {
return ThreeState.fromBoolean(!existingConstraint.equals(constraint));
}
if (dfaType.asConstraint().isExact()) { // final class
return ThreeState.fromBoolean(
applyFact(qualifier, DfaFactType.TYPE_CONSTRAINT, TypeConstraint.empty().withNotInstanceofValue(dfaType)));
}
return ThreeState.UNSURE;
}
private boolean applyRangeToRelatedValues(DfaValue value, LongRangeSet appliedRange) {
EqClass eqClass = getEqClass(value);
if (eqClass != null) {
@@ -10,6 +10,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ThreeState;
@@ -518,7 +519,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaMemoryState state, DfaValueFactory factory) {
DfaValue precalculated = instruction.getPrecalculatedReturnValue();
if (precalculated != null) {
return precalculated;
return getPrecalculatedResult(qualifierValue, state, factory, precalculated);
}
PsiType type = instruction.getResultType();
@@ -570,6 +571,25 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return DfaUnknownValue.getInstance();
}
private static DfaValue getPrecalculatedResult(@Nullable DfaValue qualifierValue,
DfaMemoryState state,
DfaValueFactory factory, DfaValue precalculated) {
if (precalculated instanceof DfaVariableValue && qualifierValue != null) {
PsiModifierListOwner psi = ((DfaVariableValue)precalculated).getPsiVariable();
// Perform constant folding for getClass() call.
if (psi instanceof PsiMethod && PsiTypesUtil.isGetClass((PsiMethod)psi)) {
TypeConstraint fact = state.getValueFact(qualifierValue, DfaFactType.TYPE_CONSTRAINT);
if (fact != null && fact.isExact()) {
PsiType javaLangClass = precalculated.getType();
if (javaLangClass != null) {
return factory.getConstFactory().createFromValue(fact.getPsiType(), javaLangClass);
}
}
}
}
return precalculated;
}
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
boolean notNullable = state.checkNotNullable(value);
if (notNullable && problem != null && problem.thrownException() != null) {
@@ -494,6 +494,7 @@ public abstract class TypeConstraint {
return new Exact(type);
}
@NotNull
public static TypeConstraint empty() {
return Constrained.EMPTY;
}
@@ -369,8 +369,12 @@ public class DfaExpressionFactory {
GetterDescriptor(@NotNull PsiMethod getter) {
myGetter = getter;
PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter);
myStable = field != null && field.hasModifierProperty(PsiModifier.FINAL);
if (PsiTypesUtil.isGetClass(getter)) {
myStable = true;
} else {
PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter);
myStable = field != null && field.hasModifierProperty(PsiModifier.FINAL);
}
}
@NotNull
@@ -0,0 +1,11 @@
// IDEA-218260
class Foo {
static void foo(Object bar) {
if (!bar.getClass().equals(Foo.class)) {
return;
}
bar.fo<caret>;
}
void foo() {
}
}
@@ -0,0 +1,11 @@
// IDEA-218260
class Foo {
static void foo(Object bar) {
if (!bar.getClass().equals(Foo.class)) {
return;
}
((Foo) bar).foo();<caret>
}
void foo() {
}
}
@@ -0,0 +1,69 @@
import java.util.*;
class GetClass {
native void unknown();
void testStability(Object obj, Class<?> c) {
if (obj.getClass().equals(c)) {
unknown();
if (<warning descr="Condition 'obj.getClass().equals(c)' is always 'true'">obj.getClass().equals(c)</warning>) { }
}
if (obj.getClass().equals(ArrayList.class)) {
unknown();
if (<warning descr="Condition 'obj.getClass().equals(ArrayList.class)' is always 'true'">obj.getClass().equals(ArrayList.class)</warning>) { }
}
}
void testFinalClass(String s) {
if (<warning descr="Condition 's.getClass() == String.class' is always 'true'">s.getClass() == String.class</warning>) { }
if (<warning descr="Condition 'String.class.equals(s.getClass())' is always 'true'">String.class.equals(s.getClass())</warning>) {}
}
void testInstanceOfInterop(Object obj) {
if (obj instanceof CharSequence) {
if (<warning descr="Condition 'obj.getClass() == Integer.class' is always 'false'">obj.getClass() == Integer.class</warning>) {}
}
if (obj.getClass() == HashSet.class) {
if (<warning descr="Condition 'obj instanceof Set' is always 'true'">obj instanceof Set</warning>) {}
if (<warning descr="Condition 'obj instanceof LinkedHashSet' is always 'false'">obj instanceof LinkedHashSet</warning>) {}
}
if (obj instanceof HashSet) {
if (obj.getClass() == HashSet.class) {} // possible but not always
if (obj.getClass() == LinkedHashSet.class) {} // also possible
}
}
void testInterfaceAbstract(Object obj, Class<?> c) {
if (<warning descr="Condition 'obj.getClass() == CharSequence.class' is always 'false'">obj.getClass() == CharSequence.class</warning>) {}
if (<warning descr="Condition 'obj.getClass().equals(Number.class)' is always 'false'">obj.getClass().equals(Number.class)</warning>) {}
if (c == Number.class || c == CharSequence.class) {
if (<warning descr="Condition 'obj.getClass() == c' is always 'false'">obj.getClass() == c</warning>) {}
}
}
void testIntermediateVar(Object obj) {
Class<?> c = obj.getClass();
if (<warning descr="Condition 'c == Number.class' is always 'false'">c == Number.class</warning>) {}
if (c == HashSet.class) {
if (<warning descr="Condition 'obj instanceof CharSequence' is always 'false'">obj instanceof CharSequence</warning>) {}
}
if (obj instanceof CharSequence) {
if (<warning descr="Condition 'c == HashSet.class' is always 'false'">c == HashSet.class</warning>) {}
}
}
void testTwoObjects(Object o1, Object o2) {
if (o1 instanceof CharSequence && o2 instanceof Integer) {
if (<warning descr="Condition 'o1.getClass() == o2.getClass()' is always 'false'">o1.getClass() == o2.getClass()</warning>) {}
}
if (o1.getClass() == o2.getClass()) {
if (o1 instanceof String) {
if(<warning descr="Condition 'o2.getClass() == Integer.class' is always 'false'">o2.getClass() == Integer.class</warning>) {}
}
if (o1 instanceof CharSequence) {
if (<warning descr="Condition 'o2 instanceof Integer' is always 'false'">o2 instanceof Integer</warning>) {}
}
}
}
}
@@ -70,6 +70,7 @@ class NormalCompletionDfaTest extends NormalCompletionTestCase {
void testInstanceOfDisjunction2() { doTest() }
void testInstanceOfDisjunctionDeep() { doTest() }
void testInstanceOfDisjunctionCircular() { doTest() }
void testAfterGetClass() { doTest() }
void testComplexInstanceOfDfa() {
configureByTestName()
myFixture.assertPreferredCompletionItems 0, 'methodFromX', 'methodFromX2', 'methodFromY', 'methodFromY2'
@@ -247,4 +247,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testLambdaWritesArrayInTry() { doTest(); }
public void testManyNestedOptionals() { doTest(); }
public void testGetClass() { doTest(); }
}