mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Track getClass() calls (IDEA-195220)
GitOrigin-RevId: 2927b603e5192a14c421e33d5cf1a9c3235015df
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4b18e94734
commit
e77596602d
@@ -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)) {
|
||||
|
||||
+68
@@ -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) {
|
||||
|
||||
+21
-1
@@ -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;
|
||||
}
|
||||
|
||||
+6
-2
@@ -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>) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -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(); }
|
||||
}
|
||||
Reference in New Issue
Block a user