mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
DFA: support of Class.isInstance, isAssignableFrom and class literal tracking
Fixes IDEA-189865 Support class.isAssignableFrom and class.isInstance in DFA
This commit is contained in:
+2
-2
@@ -520,13 +520,13 @@ public class GuessManagerImpl extends GuessManager {
|
||||
@Override
|
||||
public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
PsiExpression psiOperand = instruction.getLeft();
|
||||
if (!isInteresting(psiOperand)) {
|
||||
if (!isInteresting(psiOperand) || instruction.isClassObjectCheck()) {
|
||||
return super.visitInstanceof(instruction, runner, memState);
|
||||
}
|
||||
DfaValue type = memState.pop();
|
||||
DfaValue operand = memState.pop();
|
||||
DfaValue relation = runner.getFactory().createCondition(operand, DfaRelationValue.RelationType.IS, type);
|
||||
memState.push(new DfaInstanceofValue(runner.getFactory(), psiOperand, instruction.getCastType(), relation, false));
|
||||
memState.push(new DfaInstanceofValue(runner.getFactory(), psiOperand, Objects.requireNonNull(instruction.getCastType()), relation, false));
|
||||
return new DfaInstructionState[]{new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState)};
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,35 @@ public class CFGBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to replace class value on top of stack with the corresponding object value
|
||||
* <p>
|
||||
* Stack before: ... class_value
|
||||
* <p>
|
||||
* Stack after: ... object_value
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder objectOf() {
|
||||
myAnalyzer.addInstruction(new ObjectOfInstruction());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to perform an Class.isInstance operation
|
||||
* <p>
|
||||
* Stack before: ... object class_object
|
||||
* <p>
|
||||
* Stack after: ... result
|
||||
*
|
||||
* @param anchor element to bind this instruction to
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder isInstance(PsiMethodCallExpression anchor) {
|
||||
myAnalyzer.addInstruction(new InstanceofInstruction(anchor));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to invoke the method associated with given method call assuming that method arguments and qualifier
|
||||
* are already on stack. If vararg call is specified, vararg arguments should be placed as is, without packing into array,
|
||||
|
||||
+4
-2
@@ -1511,7 +1511,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
@Override public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) {
|
||||
startElement(expression);
|
||||
addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL), expression));
|
||||
PsiTypeElement operand = expression.getOperand();
|
||||
DfaConstValue classConstant = myFactory.getConstFactory().createFromValue(operand.getType(), expression.getType(), null);
|
||||
addInstruction(new PushInstruction(classConstant, expression));
|
||||
finishElement(expression);
|
||||
}
|
||||
|
||||
@@ -2059,6 +2061,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
static final CallInliner[] INLINERS = {new OptionalChainInliner(), new LambdaInliner(), new CollectionFactoryInliner(),
|
||||
new StreamChainInliner(), new MapUpdateInliner(), new AssumeInliner()};
|
||||
new StreamChainInliner(), new MapUpdateInliner(), new AssumeInliner(), new ClassMethodsInliner()};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -638,7 +638,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
BranchingInstruction instruction) {
|
||||
PsiElement psiAnchor = instruction.getPsiAnchor();
|
||||
if (instruction instanceof InstanceofInstruction && visitor.isInstanceofRedundant((InstanceofInstruction)instruction)) {
|
||||
if (visitor.canBeNull((BinopInstruction)instruction)) {
|
||||
if (visitor.canBeNull((InstanceofInstruction)instruction)) {
|
||||
holder.registerProblem(psiAnchor,
|
||||
InspectionsBundle.message("dataflow.message.redundant.instanceof"),
|
||||
new RedundantInstanceofFix());
|
||||
|
||||
+12
-4
@@ -16,12 +16,10 @@
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.instructions.*;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.psi.PsiArrayAccessExpression;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -99,6 +97,16 @@ public abstract class InstructionVisitor {
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
}
|
||||
|
||||
public DfaInstructionState[] visitObjectOfInstruction(ObjectOfInstruction instruction, DataFlowRunner runner, DfaMemoryState state) {
|
||||
DfaValue value = state.pop();
|
||||
DfaConstValue constant = value instanceof DfaConstValue ? (DfaConstValue)value :
|
||||
value instanceof DfaVariableValue ? state.getConstantValue((DfaVariableValue)value) :
|
||||
null;
|
||||
PsiType type = constant == null ? null : ObjectUtils.tryCast(constant.getValue(), PsiType.class);
|
||||
state.push(runner.getFactory().createTypeValue(type, Nullness.NOT_NULL));
|
||||
return nextInstruction(instruction, runner, state);
|
||||
}
|
||||
|
||||
public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
memState.pop();
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
|
||||
+86
-57
@@ -26,6 +26,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
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.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
@@ -43,8 +44,8 @@ import java.util.stream.Stream;
|
||||
public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor");
|
||||
|
||||
private final Set<BinopInstruction> myReachable = new THashSet<>();
|
||||
private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<>();
|
||||
private final Set<InstanceofInstruction> myReachable = new THashSet<>();
|
||||
private final Set<InstanceofInstruction> myCanBeNullInInstanceof = new THashSet<>();
|
||||
private final Set<InstanceofInstruction> myUsefulInstanceofs = new THashSet<>();
|
||||
|
||||
@Override
|
||||
@@ -496,10 +497,11 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
private <T extends PsiElement> DfaValue dereference(DfaMemoryState memState,
|
||||
DfaValue value,
|
||||
@Nullable NullabilityProblemKind.NullabilityProblem<T> problem) {
|
||||
if (checkNotNullable(memState, value, problem)) return value;
|
||||
boolean ok = checkNotNullable(memState, value, problem);
|
||||
if (value instanceof DfaFactMapValue) {
|
||||
return ((DfaFactMapValue)value).withFact(DfaFactType.CAN_BE_NULL, false);
|
||||
}
|
||||
if (ok) return value;
|
||||
if (memState.isNull(value) && NullabilityProblemKind.nullableFunctionReturn.isMyProblem(problem)) {
|
||||
return value.getFactory().getFactValue(DfaFactType.CAN_BE_NULL, false);
|
||||
}
|
||||
@@ -613,8 +615,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
myReachable.add(instruction);
|
||||
|
||||
DfaValue dfaRight = memState.pop();
|
||||
DfaValue dfaLeft = memState.pop();
|
||||
|
||||
@@ -641,13 +641,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
if (JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
|
||||
result = runner.getFactory().createTypeValue(type, Nullness.NOT_NULL);
|
||||
}
|
||||
else if (instruction instanceof InstanceofInstruction) {
|
||||
handleInstanceof((InstanceofInstruction)instruction, dfaRight, dfaLeft);
|
||||
}
|
||||
if (result == null && JavaTokenType.PLUS == opSign && TypeUtils.isJavaLangString(type)) {
|
||||
result = runner.getFactory().createTypeValue(type, Nullness.NOT_NULL);
|
||||
}
|
||||
memState.push(result == null ? DfaUnknownValue.getInstance() : result);
|
||||
|
||||
@@ -658,15 +653,13 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
|
||||
DataFlowRunner runner,
|
||||
DfaMemoryState memState,
|
||||
DfaValue dfaRight,
|
||||
DfaValue dfaLeft,
|
||||
RelationType relationType) {
|
||||
private static DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
|
||||
DataFlowRunner runner,
|
||||
DfaMemoryState memState,
|
||||
DfaValue dfaRight,
|
||||
DfaValue dfaLeft,
|
||||
RelationType relationType) {
|
||||
DfaValueFactory factory = runner.getFactory();
|
||||
final Instruction next = runner.getInstruction(instruction.getIndex() + 1);
|
||||
|
||||
RelationType[] relations = splitRelation(relationType);
|
||||
|
||||
ArrayList<DfaInstructionState> states = new ArrayList<>(relations.length);
|
||||
@@ -681,20 +674,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
final DfaMemoryState copy = i == relations.length - 1 ? memState : memState.createCopy();
|
||||
if (copy.applyCondition(condition)) {
|
||||
boolean isTrue = relationType.isSubRelation(relation);
|
||||
copy.push(factory.getBoolean(isTrue));
|
||||
if (isTrue) {
|
||||
instruction.setTrueReachable();
|
||||
}
|
||||
else {
|
||||
if (instruction instanceof InstanceofInstruction && !copy.isNull(dfaLeft)) {
|
||||
myUsefulInstanceofs.add((InstanceofInstruction)instruction);
|
||||
}
|
||||
instruction.setFalseReachable();
|
||||
}
|
||||
states.add(new DfaInstructionState(next, copy));
|
||||
states.add(makeBooleanResult(instruction, runner, copy, ThreeState.fromBoolean(isTrue)));
|
||||
}
|
||||
}
|
||||
myCanBeNullInInstanceof.add(instruction);
|
||||
|
||||
return states.toArray(DfaInstructionState.EMPTY_ARRAY);
|
||||
}
|
||||
@@ -712,20 +694,66 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInstanceof(InstanceofInstruction instruction, DfaValue dfaRight, DfaValue dfaLeft) {
|
||||
if (dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) {
|
||||
DfaFactMapValue left = (DfaFactMapValue)dfaLeft;
|
||||
DfaFactMapValue right = (DfaFactMapValue)dfaRight;
|
||||
@Override
|
||||
public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
myReachable.add(instruction);
|
||||
|
||||
if (!Boolean.FALSE.equals(left.get(DfaFactType.CAN_BE_NULL))) {
|
||||
myCanBeNullInInstanceof.add(instruction);
|
||||
}
|
||||
|
||||
if (right.getFacts().with(DfaFactType.CAN_BE_NULL, null).isSuperStateOf(left.getFacts())) {
|
||||
return;
|
||||
DfaValue dfaRight = memState.pop();
|
||||
DfaValue dfaLeft = memState.pop();
|
||||
DfaValueFactory factory = runner.getFactory();
|
||||
if (!memState.isNotNull(dfaLeft)) {
|
||||
myCanBeNullInInstanceof.add(instruction);
|
||||
}
|
||||
boolean unknownTargetType = false;
|
||||
DfaValue condition = null;
|
||||
if (instruction.isClassObjectCheck()) {
|
||||
DfaConstValue constant = dfaRight instanceof DfaConstValue ? (DfaConstValue)dfaRight :
|
||||
dfaRight instanceof DfaVariableValue ? memState.getConstantValue((DfaVariableValue)dfaRight) :
|
||||
null;
|
||||
PsiType type = constant == null ? null : ObjectUtils.tryCast(constant.getValue(), PsiType.class);
|
||||
if (type == null || type instanceof PsiPrimitiveType) {
|
||||
// Unknown/primitive class: just execute contract "null -> false"
|
||||
DfaConstValue aNull = factory.getConstFactory().getNull();
|
||||
condition = factory.createCondition(dfaLeft, RelationType.NE, aNull);
|
||||
unknownTargetType = true;
|
||||
} else {
|
||||
dfaRight = factory.createTypeValue(type, Nullness.NOT_NULL);
|
||||
}
|
||||
}
|
||||
myUsefulInstanceofs.add(instruction);
|
||||
if (condition == null) {
|
||||
condition = factory.createCondition(dfaLeft, RelationType.IS, dfaRight);
|
||||
}
|
||||
|
||||
boolean useful;
|
||||
ArrayList<DfaInstructionState> states = new ArrayList<>(2);
|
||||
if (condition instanceof DfaUnknownValue) {
|
||||
if (dfaLeft instanceof DfaFactMapValue && dfaRight instanceof DfaFactMapValue) {
|
||||
DfaFactMapValue left = (DfaFactMapValue)dfaLeft;
|
||||
DfaFactMapValue right = (DfaFactMapValue)dfaRight;
|
||||
useful = !right.getFacts().with(DfaFactType.CAN_BE_NULL, null).isSuperStateOf(left.getFacts());
|
||||
} else {
|
||||
useful = true;
|
||||
}
|
||||
states.add(makeBooleanResult(instruction, runner, memState, ThreeState.UNSURE));
|
||||
}
|
||||
else {
|
||||
final DfaMemoryState trueState = memState.createCopy();
|
||||
useful = unknownTargetType;
|
||||
if (trueState.applyCondition(condition)) {
|
||||
states.add(makeBooleanResult(instruction, runner, trueState, unknownTargetType ? ThreeState.UNSURE : ThreeState.YES));
|
||||
}
|
||||
if (memState.applyCondition(condition.createNegated())) {
|
||||
if (unknownTargetType) {
|
||||
memState.markEphemeral();
|
||||
}
|
||||
states.add(makeBooleanResult(instruction, runner, memState, ThreeState.NO));
|
||||
useful |= !memState.isNull(dfaLeft);
|
||||
}
|
||||
}
|
||||
if (useful) {
|
||||
myUsefulInstanceofs.add(instruction);
|
||||
}
|
||||
return states.toArray(DfaInstructionState.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -764,10 +792,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
dfaLeft == runner.getFactory().getConstFactory().getContractFail() ||
|
||||
dfaRight == runner.getFactory().getConstFactory().getContractFail()) {
|
||||
boolean negated = (relationType == RelationType.NE) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
|
||||
if (dfaLeft == dfaRight ^ negated) {
|
||||
return alwaysTrue(instruction, runner, memState);
|
||||
}
|
||||
return alwaysFalse(instruction, runner, memState);
|
||||
boolean result = dfaLeft == dfaRight ^ negated;
|
||||
return makeBooleanResultArray(instruction, runner, memState, result);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -820,7 +846,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
return result ? alwaysTrue(instruction, runner, memState) : alwaysFalse(instruction, runner, memState);
|
||||
return makeBooleanResultArray(instruction, runner, memState, result);
|
||||
}
|
||||
|
||||
private static int compare(Number a, Number b) {
|
||||
@@ -831,23 +857,26 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
return Double.compare(a.doubleValue(), b.doubleValue());
|
||||
}
|
||||
|
||||
private static DfaInstructionState[] alwaysFalse(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
memState.push(runner.getFactory().getConstFactory().getFalse());
|
||||
instruction.setFalseReachable();
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
private static DfaInstructionState[] makeBooleanResultArray(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, boolean result) {
|
||||
return new DfaInstructionState[]{makeBooleanResult(instruction, runner, memState, ThreeState.fromBoolean(result))};
|
||||
}
|
||||
|
||||
private static DfaInstructionState[] alwaysTrue(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
memState.push(runner.getFactory().getConstFactory().getTrue());
|
||||
instruction.setTrueReachable();
|
||||
return nextInstruction(instruction, runner, memState);
|
||||
private static DfaInstructionState makeBooleanResult(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, @NotNull ThreeState result) {
|
||||
memState.push(result == ThreeState.UNSURE ? DfaUnknownValue.getInstance() : runner.getFactory().getBoolean(result.toBoolean()));
|
||||
if (result != ThreeState.NO) {
|
||||
instruction.setTrueReachable();
|
||||
}
|
||||
if (result != ThreeState.YES) {
|
||||
instruction.setFalseReachable();
|
||||
}
|
||||
return new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState);
|
||||
}
|
||||
|
||||
public boolean isInstanceofRedundant(InstanceofInstruction instruction) {
|
||||
return !myUsefulInstanceofs.contains(instruction) && !instruction.isConditionConst() && myReachable.contains(instruction);
|
||||
}
|
||||
|
||||
public boolean canBeNull(BinopInstruction instruction) {
|
||||
public boolean canBeNull(InstanceofInstruction instruction) {
|
||||
return myCanBeNullInInstanceof.contains(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -21,6 +21,8 @@ import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -37,8 +39,14 @@ public class RedundantInstanceofFix implements LocalQuickFix {
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement psiElement = descriptor.getPsiElement();
|
||||
String replacement;
|
||||
CommentTracker ct = new CommentTracker();
|
||||
if (psiElement instanceof PsiInstanceOfExpression) {
|
||||
replacement = ((PsiInstanceOfExpression)psiElement).getOperand().getText() + " != null";
|
||||
replacement = ct.text(((PsiInstanceOfExpression)psiElement).getOperand()) + " != null";
|
||||
}
|
||||
else if (psiElement instanceof PsiMethodCallExpression) {
|
||||
PsiExpression arg = ArrayUtil.getFirstElement(((PsiMethodCallExpression)psiElement).getArgumentList().getExpressions());
|
||||
if (arg == null) return;
|
||||
replacement = ct.text(arg) + " != null";
|
||||
}
|
||||
else if (psiElement instanceof PsiMethodReferenceExpression) {
|
||||
replacement = CommonClassNames.JAVA_UTIL_OBJECTS + "::nonNull";
|
||||
@@ -46,7 +54,6 @@ public class RedundantInstanceofFix implements LocalQuickFix {
|
||||
else {
|
||||
return;
|
||||
}
|
||||
PsiExpression compareToNull = JavaPsiFacade.getElementFactory(project).createExpressionFromText(replacement, psiElement.getParent());
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(psiElement.replace(compareToNull));
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(ct.replaceAndRestoreComments(psiElement, replacement));
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright 2000-2018 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.inliner;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CFGBuilder;
|
||||
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.psi.CommonClassNames.JAVA_LANG_CLASS;
|
||||
import static com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
/**
|
||||
* Inline class.isInstance(obj) and class.isAssignableFrom(obj.getClass())
|
||||
*/
|
||||
public class ClassMethodsInliner implements CallInliner {
|
||||
private static final CallMatcher IS_ASSIGNABLE_FROM = CallMatcher.instanceCall(JAVA_LANG_CLASS, "isAssignableFrom").parameterTypes(
|
||||
JAVA_LANG_CLASS);
|
||||
private static final CallMatcher IS_INSTANCE = CallMatcher.instanceCall(JAVA_LANG_CLASS, "isInstance").parameterTypes(
|
||||
JAVA_LANG_OBJECT);
|
||||
private static final CallMatcher OBJECT_GET_CLASS = CallMatcher.instanceCall(JAVA_LANG_OBJECT, "getClass").parameterCount(0);
|
||||
|
||||
|
||||
@Override
|
||||
public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier == null) return false;
|
||||
if (IS_ASSIGNABLE_FROM.matches(call)) {
|
||||
PsiExpression arg = call.getArgumentList().getExpressions()[0];
|
||||
PsiMethodCallExpression nestedCall = tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class);
|
||||
PsiExpression getClassQualifier =
|
||||
OBJECT_GET_CLASS.matches(nestedCall) ? nestedCall.getMethodExpression().getQualifierExpression() : null;
|
||||
if (getClassQualifier != null) {
|
||||
builder.pushExpression(qualifier)
|
||||
.pushExpression(getClassQualifier)
|
||||
.checkNotNull(nestedCall, NullabilityProblemKind.callNPE);
|
||||
}
|
||||
else {
|
||||
builder.pushExpression(qualifier)
|
||||
.pushExpression(arg)
|
||||
.checkNotNull(arg, NullabilityProblemKind.passingNullableToNotNullParameter)
|
||||
.objectOf();
|
||||
}
|
||||
builder.swap()
|
||||
.checkNotNull(call, NullabilityProblemKind.callNPE)
|
||||
.isInstance(call);
|
||||
return true;
|
||||
}
|
||||
else if (IS_INSTANCE.matches(call)) {
|
||||
builder.pushExpression(qualifier)
|
||||
.pushExpression(call.getArgumentList().getExpressions()[0])
|
||||
.swap()
|
||||
.checkNotNull(call, NullabilityProblemKind.callNPE)
|
||||
.isInstance(call);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+21
-6
@@ -19,10 +19,7 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner;
|
||||
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -31,7 +28,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class InstanceofInstruction extends BinopInstruction {
|
||||
@Nullable private final PsiExpression myLeft;
|
||||
@NotNull private final PsiType myCastType;
|
||||
@Nullable private final PsiType myCastType;
|
||||
|
||||
public InstanceofInstruction(PsiElement psiAnchor, @Nullable PsiExpression left, @NotNull PsiType castType) {
|
||||
super(JavaTokenType.INSTANCEOF_KEYWORD, psiAnchor, PsiType.BOOLEAN);
|
||||
@@ -39,6 +36,16 @@ public class InstanceofInstruction extends BinopInstruction {
|
||||
myCastType = castType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a class object instanceof check (e.g. from Class.isInstance call); castType is not known
|
||||
* @param psiAnchor anchor call
|
||||
*/
|
||||
public InstanceofInstruction(PsiMethodCallExpression psiAnchor) {
|
||||
super(JavaTokenType.INSTANCEOF_KEYWORD, psiAnchor, PsiType.BOOLEAN);
|
||||
myLeft = null;
|
||||
myCastType = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
|
||||
return visitor.visitInstanceof(this, runner, stateBefore);
|
||||
@@ -53,8 +60,16 @@ public class InstanceofInstruction extends BinopInstruction {
|
||||
return myLeft;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
public PsiType getCastType() {
|
||||
return myCastType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this instanceof instruction checks against Class object (e.g. Class.isInstance() call). In this case
|
||||
* class object is located on the stack and cast type is not known
|
||||
*/
|
||||
public boolean isClassObjectCheck() {
|
||||
return myCastType == null;
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2000-2018 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.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowRunner;
|
||||
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
|
||||
|
||||
/**
|
||||
* Instruction which transforms class constant on top of stack to the object value which is instanceof that class
|
||||
*/
|
||||
public class ObjectOfInstruction extends Instruction {
|
||||
@Override
|
||||
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
|
||||
return visitor.visitObjectOfInstruction(this, runner, stateBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OBJECT_OF";
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with a null check" "true"
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class Test {
|
||||
void test(String s) {
|
||||
if(s != null) {
|
||||
System.out.println("not null s");
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with a null check" "true"
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class Test {
|
||||
void test(String s) {
|
||||
if(String.class.isInsta<caret>nce(s)) {
|
||||
System.out.println("not null s");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
class ClassMethodsInlining {
|
||||
void assignableTest3(Class<?> c, List<?> list) {
|
||||
if(c.isAssignableFrom(list.get(0).getClass())) {
|
||||
System.out.println("possible");
|
||||
}
|
||||
}
|
||||
|
||||
void instanceTest(Class<?> c, Object o) {
|
||||
if(c.isInstance(o)) {
|
||||
System.out.println(o.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
void instanceNotNullTest(Class<?> c, Object o) {
|
||||
if(o != null && c.isInstance(o)) {
|
||||
System.out.println(o.hashCode());
|
||||
}
|
||||
if(c.isInstance(new Object())) {
|
||||
System.out.println("possible");
|
||||
}
|
||||
}
|
||||
|
||||
void assignableTest(Class<?> c1, Class<?> c2) {
|
||||
if(c1.isAssignableFrom(c2)) {
|
||||
System.out.println("possible");
|
||||
}
|
||||
}
|
||||
|
||||
void assignableTest2(Class<?> c) {
|
||||
if(c.isAssignableFrom(String.class)) {
|
||||
System.out.println("possible");
|
||||
}
|
||||
if(String.class.isAssignableFrom(c)) {
|
||||
System.out.println("possible");
|
||||
}
|
||||
}
|
||||
|
||||
void testNullFalse(@Nullable Object s, Class<?> c) {
|
||||
if(c.isInstance(s)) {
|
||||
System.out.println(s.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
void limitedClass(List<String> list, Class<?> c) {
|
||||
assert c == Integer.class || c == String.class || c == Boolean.class;
|
||||
|
||||
if(<warning descr="Condition 'c.isInstance(list)' is always 'false'">c.isInstance(list)</warning>) {
|
||||
System.out.println("oops");
|
||||
}
|
||||
}
|
||||
|
||||
void primitiveClass(Object obj) {
|
||||
// Not supported yet
|
||||
if(int.class.isInstance(obj)) {
|
||||
System.out.println("impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void objectClass(Object obj, Object obj2) {
|
||||
Class<?> cls = Object.class;
|
||||
if(<warning descr="Condition 'cls.isInstance(obj)' is redundant and can be replaced with a null check">cls.isInstance(obj)</warning>) {
|
||||
System.out.println("for every non-null");
|
||||
}
|
||||
if(<warning descr="Condition 'cls.isAssignableFrom(obj2.getClass())' is always 'true'">cls.isAssignableFrom(obj2.getClass())</warning>) {
|
||||
System.out.println("redundant");
|
||||
}
|
||||
}
|
||||
|
||||
void incompatibleClasses() {
|
||||
Class<?> c1 = Integer.class;
|
||||
Class<?> c2 = Double.class;
|
||||
if(<warning descr="Condition 'c1.isAssignableFrom(c2)' is always 'false'">c1.isAssignableFrom(c2)</warning>) {
|
||||
System.out.println("impossible");
|
||||
}
|
||||
}
|
||||
|
||||
void testEphemeral(Object obj, Class<?> c) {
|
||||
if(c.isInstance(obj)) {
|
||||
System.out.println("yeah!");
|
||||
}
|
||||
System.out.println(obj.hashCode());
|
||||
}
|
||||
|
||||
native Object next(Object prev);
|
||||
|
||||
void testLoop(Object o) {
|
||||
while(o != null && !(o instanceof String)) {
|
||||
o = next(o);
|
||||
}
|
||||
if(<warning descr="Condition 'o instanceof String' is redundant and can be replaced with a null check">o instanceof String</warning>) {
|
||||
System.out.println("String");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,4 +598,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
public void testMergedInitializerAndConstructor() { doTest(); }
|
||||
public void testClassMethodsInlining() { doTest(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user