diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java index 766b81292dd3..771bc4cf5b7c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/guess/impl/GuessManagerImpl.java @@ -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)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index 56c09feb4420..a2caee180af3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -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 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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 0745cac3596e..a3218fdc854d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java index d68c3e85a6c5..b2c1473ab37f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java @@ -494,6 +494,7 @@ public abstract class TypeConstraint { return new Exact(type); } + @NotNull public static TypeConstraint empty() { return Constrained.EMPTY; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java index 63187a39e99a..e57cd473cd5f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -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 diff --git a/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass.java b/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass.java new file mode 100644 index 000000000000..c171c41a264b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass.java @@ -0,0 +1,11 @@ +// IDEA-218260 +class Foo { + static void foo(Object bar) { + if (!bar.getClass().equals(Foo.class)) { + return; + } + bar.fo; + } + void foo() { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass_after.java b/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass_after.java new file mode 100644 index 000000000000..ce364514cea4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/AfterGetClass_after.java @@ -0,0 +1,11 @@ +// IDEA-218260 +class Foo { + static void foo(Object bar) { + if (!bar.getClass().equals(Foo.class)) { + return; + } + ((Foo) bar).foo(); + } + void foo() { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/GetClass.java b/java/java-tests/testData/inspection/dataFlow/fixture/GetClass.java new file mode 100644 index 000000000000..6753139d595b --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/GetClass.java @@ -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 (obj.getClass().equals(c)) { } + } + if (obj.getClass().equals(ArrayList.class)) { + unknown(); + if (obj.getClass().equals(ArrayList.class)) { } + } + } + + void testFinalClass(String s) { + if (s.getClass() == String.class) { } + if (String.class.equals(s.getClass())) {} + + } + + void testInstanceOfInterop(Object obj) { + if (obj instanceof CharSequence) { + if (obj.getClass() == Integer.class) {} + } + if (obj.getClass() == HashSet.class) { + if (obj instanceof Set) {} + if (obj instanceof LinkedHashSet) {} + } + 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 (obj.getClass() == CharSequence.class) {} + if (obj.getClass().equals(Number.class)) {} + if (c == Number.class || c == CharSequence.class) { + if (obj.getClass() == c) {} + } + } + + void testIntermediateVar(Object obj) { + Class c = obj.getClass(); + if (c == Number.class) {} + if (c == HashSet.class) { + if (obj instanceof CharSequence) {} + } + if (obj instanceof CharSequence) { + if (c == HashSet.class) {} + } + } + + void testTwoObjects(Object o1, Object o2) { + if (o1 instanceof CharSequence && o2 instanceof Integer) { + if (o1.getClass() == o2.getClass()) {} + } + if (o1.getClass() == o2.getClass()) { + if (o1 instanceof String) { + if(o2.getClass() == Integer.class) {} + } + if (o1 instanceof CharSequence) { + if (o2 instanceof Integer) {} + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy index 695f4905ad0d..d99cfecfca87 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy @@ -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' diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index 359e77503c54..ee0da645cd6b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -247,4 +247,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { public void testLambdaWritesArrayInTry() { doTest(); } public void testManyNestedOptionals() { doTest(); } + public void testGetClass() { doTest(); } } \ No newline at end of file