From 7d4c5ea4d8d572ccbe0b1d7d9ad308f466ca987a Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 15 May 2020 18:12:09 +0700 Subject: [PATCH] IDEA-240403 Analyze dataflow to here: perform backpropagation through simple expressions GitOrigin-RevId: 6c7d2e9f30121748c624a20ab14cbcf7dd7c71ec --- .../slicer/AnalysisStartingPoint.java | 291 ++++++++++++++++++ .../DataflowExceptionAnalysisProvider.java | 284 ++--------------- .../slicer/JavaDfaSliceValueFilter.java | 7 +- .../src/com/intellij/slicer/SliceUtil.java | 14 +- .../backward/FilterPropagateBoolean.java | 13 + .../backward/FilterPropagateBoolean2.java | 13 + .../slice/backward/FilterPropagateMath.java | 27 ++ ...DataflowExceptionAnalysisProviderTest.java | 2 +- .../java/slicer/SliceBackwardTest.java | 3 + 9 files changed, 397 insertions(+), 257 deletions(-) create mode 100644 java/java-impl/src/com/intellij/slicer/AnalysisStartingPoint.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean2.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/FilterPropagateMath.java diff --git a/java/java-impl/src/com/intellij/slicer/AnalysisStartingPoint.java b/java/java-impl/src/com/intellij/slicer/AnalysisStartingPoint.java new file mode 100644 index 000000000000..5d55b661d3a0 --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/AnalysisStartingPoint.java @@ -0,0 +1,291 @@ +// Copyright 2000-2020 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.slicer; + +import com.intellij.codeInsight.Nullability; +import com.intellij.codeInspection.dataFlow.CommonDataflow; +import com.intellij.codeInspection.dataFlow.NullabilityProblemKind; +import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; +import com.intellij.codeInspection.dataFlow.types.DfIntegralType; +import com.intellij.codeInspection.dataFlow.types.DfLongType; +import com.intellij.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.codeInspection.dataFlow.value.RelationType; +import com.intellij.psi.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.ConstantExpressionUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; +import com.siyeh.ig.psiutils.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.Objects; + +class AnalysisStartingPoint { + final DfType myDfType; + final PsiExpression myAnchor; + + AnalysisStartingPoint(DfType type, PsiExpression anchor) { + myDfType = type; + myAnchor = anchor; + } + + @Nullable AnalysisStartingPoint tryMeet(@NotNull AnalysisStartingPoint next) { + if (!EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(this.myAnchor, next.myAnchor)) return null; + DfType meet = this.myDfType.meet(next.myDfType); + if (meet == DfTypes.BOTTOM) return null; + return new AnalysisStartingPoint(meet, this.myAnchor); + } + + @Nullable AnalysisStartingPoint tryJoin(@NotNull AnalysisStartingPoint next) { + if (!EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(this.myAnchor, next.myAnchor)) return null; + DfType meet = this.myDfType.join(next.myDfType); + if (meet == DfTypes.TOP) return null; + return new AnalysisStartingPoint(meet, this.myAnchor); + } + + static @Nullable AnalysisStartingPoint create(@NotNull DfType type, @Nullable PsiExpression anchor) { + anchor = extractAnchor(anchor); + if (anchor == null) return null; + if (DfTypes.typedObject(anchor.getType(), Nullability.UNKNOWN).meet(type) == DfTypes.BOTTOM) return null; + return new AnalysisStartingPoint(type, anchor); + } + + @Nullable + static PsiExpression extractAnchor(@Nullable PsiExpression target) { + target = PsiUtil.skipParenthesizedExprDown(target); + if (target instanceof PsiReferenceExpression || + target instanceof PsiMethodCallExpression || + target != null && propagateThroughExpression(target, DfTypes.LONG) != null) { + return target; + } + return null; + } + + static @Nullable AnalysisStartingPoint fromCondition(@Nullable PsiExpression cond) { + cond = PsiUtil.skipParenthesizedExprDown(cond); + if (cond == null) return null; + if (cond instanceof PsiPolyadicExpression) { + IElementType tokenType = ((PsiPolyadicExpression)cond).getOperationTokenType(); + if (tokenType.equals(JavaTokenType.ANDAND)) { + AnalysisStartingPoint analysis = null; + for (PsiExpression operand : ((PsiPolyadicExpression)cond).getOperands()) { + AnalysisStartingPoint next = fromCondition(operand); + if (next == null) return null; + if (analysis == null) { + analysis = next; + } + else { + analysis = analysis.tryMeet(next); + if (analysis == null) return null; + } + } + return analysis; + } + if (tokenType.equals(JavaTokenType.OROR)) { + AnalysisStartingPoint analysis = null; + for (PsiExpression operand : ((PsiPolyadicExpression)cond).getOperands()) { + AnalysisStartingPoint next = fromCondition(operand); + if (next == null) return null; + if (analysis == null) { + analysis = next; + } + else { + analysis = analysis.tryJoin(next); + if (analysis == null) return null; + } + } + return analysis; + } + } + if (cond instanceof PsiBinaryExpression) { + PsiBinaryExpression binop = (PsiBinaryExpression)cond; + PsiExpression left = PsiUtil.skipParenthesizedExprDown(binop.getLOperand()); + PsiExpression right = PsiUtil.skipParenthesizedExprDown(binop.getROperand()); + AnalysisStartingPoint analysis = fromBinOp(left, binop.getOperationTokenType(), right); + if (analysis != null) return analysis; + return fromBinOp(right, binop.getOperationTokenType(), left); + } + if (cond instanceof PsiInstanceOfExpression) { + PsiTypeElement checkType = ((PsiInstanceOfExpression)cond).getCheckType(); + if (checkType == null) return null; + PsiExpression anchor = extractAnchor(((PsiInstanceOfExpression)cond).getOperand()); + if (anchor != null) { + DfType typedObject = DfTypes.typedObject(checkType.getType(), Nullability.NOT_NULL); + return new AnalysisStartingPoint(typedObject, anchor); + } + } + if (cond instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)cond; + if (MethodCallUtils.isEqualsCall(call)) { + PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()); + PsiExpression argument = PsiUtil.skipParenthesizedExprDown(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); + if (qualifier != null && argument != null) { + DfType type = fromConstant(qualifier); + PsiExpression anchor = extractAnchor(argument); + if (type == null) { + type = fromConstant(argument); + anchor = extractAnchor(qualifier); + } + if (type != null && anchor != null) { + PsiType anchorType = anchor.getType(); + if (anchorType == null || DfTypes.typedObject(anchorType, Nullability.NOT_NULL).meet(type) == DfTypes.BOTTOM) return null; + return new AnalysisStartingPoint(type, anchor); + } + } + } + } + if (BoolUtils.isNegation(cond)) { + AnalysisStartingPoint negatedAnalysis = fromCondition(BoolUtils.getNegated(cond)); + return tryNegate(negatedAnalysis); + } + PsiExpression anchor = extractAnchor(cond); + if (anchor != null) { + return new AnalysisStartingPoint(DfTypes.TRUE, anchor); + } + return null; + } + + static @Nullable AnalysisStartingPoint tryNegate(AnalysisStartingPoint analysis) { + if (analysis == null) return null; + DfType type = analysis.myDfType.tryNegate(); + if (type == null) return null; + NullabilityProblemKind.NullabilityProblem problem = NullabilityProblemKind.fromContext(analysis.myAnchor, Collections.emptyMap()); + if (problem != null && CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION.equals(problem.thrownException())) { + type = type.meet(DfTypes.NOT_NULL_OBJECT); + } + return new AnalysisStartingPoint(type, analysis.myAnchor); + } + + static @Nullable DfType fromConstant(@NotNull PsiExpression constant) { + if (constant instanceof PsiClassObjectAccessExpression) { + PsiClassObjectAccessExpression classObject = (PsiClassObjectAccessExpression)constant; + PsiTypeElement operand = classObject.getOperand(); + return DfTypes.constant(operand.getType(), classObject.getType()); + } + if (constant instanceof PsiReferenceExpression) { + PsiElement target = ((PsiReferenceExpression)constant).resolve(); + if (target instanceof PsiEnumConstant) { + return DfTypes.constant(target, Objects.requireNonNull(constant.getType())); + } + } + if (ExpressionUtils.isNullLiteral(constant)) { + return DfTypes.NULL; + } + Object value = ExpressionUtils.computeConstantExpression(constant); + if (value != null) { + return DfTypes.constant(value, Objects.requireNonNull(constant.getType())); + } + return null; + } + + private static @Nullable AnalysisStartingPoint fromBinOp(@Nullable PsiExpression target, + @NotNull IElementType type, + @Nullable PsiExpression constant) { + if (constant == null) return null; + DfType constantType = fromConstant(constant); + if (constantType == null) { + return null; + } + PsiExpression anchor = extractAnchor(target); + if (anchor != null) { + PsiType anchorType = anchor.getType(); + if (anchorType == null || TypeUtils.isJavaLangString(anchorType)) return null; + if (anchorType.equals(PsiType.BYTE) || anchorType.equals(PsiType.CHAR) || anchorType.equals(PsiType.SHORT)) { + anchorType = PsiType.INT; + } + if (constantType == DfTypes.NULL || DfTypes.typedObject(anchorType, Nullability.NOT_NULL).meet(constantType) != DfTypes.BOTTOM) { + if (type.equals(JavaTokenType.EQEQ)) { + return new AnalysisStartingPoint(constantType, anchor); + } + if (type.equals(JavaTokenType.NE)) { + return tryNegate(new AnalysisStartingPoint(constantType, anchor)); + } + } + } + RelationType relationType = RelationType.fromElementType(type); + if (relationType != null) { + LongRangeSet set = DfLongType.extractRange(constantType).fromRelation(relationType); + if (anchor == null) return null; + PsiType anchorType = anchor.getType(); + if (PsiType.LONG.equals(anchorType)) { + return new AnalysisStartingPoint(DfTypes.longRange(set), anchor); + } + if (PsiType.INT.equals(anchorType) || + PsiType.SHORT.equals(anchorType) || + PsiType.BYTE.equals(anchorType) || + PsiType.CHAR.equals(anchorType)) { + set = set.intersect(Objects.requireNonNull(LongRangeSet.fromType(anchorType))); + return new AnalysisStartingPoint(DfTypes.intRangeClamped(set), anchor); + } + } + return null; + } + + static AnalysisStartingPoint propagateThroughExpression(@NotNull PsiElement expression, @NotNull DfType origType) { + AnalysisStartingPoint analysis = null; + if (origType == DfTypes.TRUE) { + analysis = fromCondition((PsiExpression)expression); + } + else if (origType == DfTypes.FALSE) { + analysis = tryNegate(fromCondition((PsiExpression)expression)); + } + DfIntegralType dfType = ObjectUtils.tryCast(origType, DfIntegralType.class); + if (dfType != null) { + boolean isLong = dfType instanceof DfLongType; + LongRangeSet origRange = dfType.getRange(); + LongRangeSet newRange = null; + PsiExpression anchor = null; + if (expression instanceof PsiPrefixExpression) { + anchor = ((PsiPrefixExpression)expression).getOperand(); + IElementType type = ((PsiPrefixExpression)expression).getOperationTokenType(); + if (type.equals(JavaTokenType.MINUS)) { + newRange = origRange.negate(isLong); + } + else if (type.equals(JavaTokenType.TILDE)) { + newRange = origRange.negate(isLong).minus(LongRangeSet.point(1), isLong); + } + } + if (expression instanceof PsiBinaryExpression) { + PsiBinaryExpression binOp = (PsiBinaryExpression)expression; + IElementType type = binOp.getOperationTokenType(); + LongRangeSet leftRange = CommonDataflow.getExpressionRange(binOp.getLOperand()); + LongRangeSet rightRange = CommonDataflow.getExpressionRange(binOp.getROperand()); + Long left = leftRange == null ? null : leftRange.getConstantValue(); + Long right = rightRange == null ? null : rightRange.getConstantValue(); + if (type.equals(JavaTokenType.PERC)) { + if (right != null) { + newRange = LongRangeSet.fromRemainder(right, origRange); + anchor = binOp.getLOperand(); + } + } + if (type.equals(JavaTokenType.PLUS)) { + if (right != null) { + newRange = origRange.minus(LongRangeSet.point(right), isLong); + anchor = binOp.getLOperand(); + } + else if (left != null) { + newRange = origRange.minus(LongRangeSet.point(left), isLong); + anchor = binOp.getROperand(); + } + } + if (type.equals(JavaTokenType.MINUS)) { + if (right != null) { + newRange = origRange.plus(LongRangeSet.point(right), isLong); + anchor = binOp.getLOperand(); + } + else if (left != null) { + newRange = LongRangeSet.point(left).minus(origRange, isLong); + anchor = binOp.getROperand(); + } + } + } + if (newRange != null && anchor != null && !(anchor instanceof PsiLiteralExpression)) { + analysis = new AnalysisStartingPoint(isLong ? DfTypes.longRange(newRange) : DfTypes.intRangeClamped(newRange), anchor); + } + } + return analysis; + } +} diff --git a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java index dab40adfa16d..3a4239a1284a 100644 --- a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java +++ b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java @@ -4,9 +4,7 @@ package com.intellij.slicer; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInsight.Nullability; import com.intellij.codeInspection.dataFlow.*; -import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.types.*; -import com.intellij.codeInspection.dataFlow.value.RelationType; import com.intellij.execution.filters.*; import com.intellij.java.JavaBundle; import com.intellij.openapi.actionSystem.AnAction; @@ -14,11 +12,9 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.*; import org.jetbrains.annotations.NotNull; @@ -38,19 +34,19 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi @Override public @Nullable AnAction getAnalysisAction(@NotNull PsiElement anchor, @NotNull ExceptionInfo info) { - Analysis analysis = getAnalysis(anchor, info); + AnalysisStartingPoint analysis = getAnalysis(anchor, info); return createAction(analysis); } @Override public @Nullable AnAction getIntermediateRowAnalysisAction(@NotNull PsiElement anchor) { - Analysis analysis = getIntermediateRowAnalysis(anchor); + AnalysisStartingPoint analysis = getIntermediateRowAnalysis(anchor); return createAction(analysis); } - private static @Nullable Analysis getIntermediateRowAnalysis(@NotNull PsiElement anchor) { + private static @Nullable AnalysisStartingPoint getIntermediateRowAnalysis(@NotNull PsiElement anchor) { if (anchor instanceof PsiExpression) { - return new Analysis(DfTypes.NULL, (PsiExpression)anchor); + return new AnalysisStartingPoint(DfTypes.NULL, (PsiExpression)anchor); } if (!(anchor instanceof PsiIdentifier)) return null; PsiReferenceExpression ref = tryCast(anchor.getParent(), PsiReferenceExpression.class); @@ -70,19 +66,19 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi PsiExpression[] args = call.getArgumentList().getExpressions(); PsiExpression arg = getArgFromContract(args, condition, ContractValue.nullValue(), true); if (arg != null) { - return Analysis.create(DfTypes.NULL, arg); + return AnalysisStartingPoint.create(DfTypes.NULL, arg); } arg = getArgFromContract(args, condition, ContractValue.nullValue(), false); if (arg != null) { - return Analysis.create(DfTypes.NOT_NULL_OBJECT, arg); + return AnalysisStartingPoint.create(DfTypes.NOT_NULL_OBJECT, arg); } arg = getArgFromContract(args, condition, ContractValue.booleanValue(true), true); if (arg != null) { - return fromCondition(arg); + return AnalysisStartingPoint.fromCondition(arg); } arg = getArgFromContract(args, condition, ContractValue.booleanValue(false), true); if (arg != null) { - return tryNegate(fromCondition(arg)); + return AnalysisStartingPoint.tryNegate(AnalysisStartingPoint.fromCondition(arg)); } return null; } @@ -94,8 +90,8 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi return args[pos]; } - private @Nullable Analysis getAnalysis(@NotNull PsiElement anchor, - @NotNull ExceptionInfo info) { + private @Nullable AnalysisStartingPoint getAnalysis(@NotNull PsiElement anchor, + @NotNull ExceptionInfo info) { if (anchor instanceof PsiKeyword && anchor.textMatches(PsiKeyword.NEW)) { PsiNewExpression exceptionConstructor = tryCast(anchor.getParent(), PsiNewExpression.class); if (exceptionConstructor != null && !exceptionConstructor.isArrayCreation()) { @@ -111,19 +107,19 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi else if (info instanceof ArrayIndexOutOfBoundsExceptionInfo) { Integer index = ((ArrayIndexOutOfBoundsExceptionInfo)info).getIndex(); if (index != null && anchor instanceof PsiExpression) { - return Analysis.create(DfTypes.intValue(index), (PsiExpression)anchor); + return AnalysisStartingPoint.create(DfTypes.intValue(index), (PsiExpression)anchor); } } else if (info instanceof ClassCastExceptionInfo) { return fromClassCastException(anchor, ((ClassCastExceptionInfo)info).getActualClass()); } else if (info instanceof NullPointerExceptionInfo || info instanceof JetBrainsNotNullInstrumentationExceptionInfo) { - return Analysis.create(DfTypes.NULL, tryCast(anchor, PsiExpression.class)); + return AnalysisStartingPoint.create(DfTypes.NULL, tryCast(anchor, PsiExpression.class)); } else if (info instanceof NegativeArraySizeExceptionInfo) { Integer size = ((NegativeArraySizeExceptionInfo)info).getSuppliedSize(); if (size != null && size < 0 && anchor instanceof PsiExpression) { - return Analysis.create(DfTypes.intValue(size), (PsiExpression)anchor); + return AnalysisStartingPoint.create(DfTypes.intValue(size), (PsiExpression)anchor); } } else if (info instanceof ArithmeticExceptionInfo) { @@ -131,27 +127,27 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi } else if (info instanceof ArrayCopyIndexOutOfBoundsExceptionInfo) { if (anchor instanceof PsiExpression) { - return Analysis.create(DfTypes.intValue(((ArrayCopyIndexOutOfBoundsExceptionInfo)info).getValue()), (PsiExpression)anchor); + return AnalysisStartingPoint.create(DfTypes.intValue(((ArrayCopyIndexOutOfBoundsExceptionInfo)info).getValue()), (PsiExpression)anchor); } } return null; } - private static Analysis fromArithmeticException(PsiElement anchor) { + private static AnalysisStartingPoint fromArithmeticException(PsiElement anchor) { if (anchor instanceof PsiExpression) { PsiExpression divisor = (PsiExpression)anchor; PsiType type = divisor.getType(); if (PsiType.LONG.equals(type)) { - return Analysis.create(DfTypes.longValue(0), divisor); + return AnalysisStartingPoint.create(DfTypes.longValue(0), divisor); } else if (TypeConversionUtil.isIntegralNumberType(type)) { - return Analysis.create(DfTypes.intValue(0), divisor); + return AnalysisStartingPoint.create(DfTypes.intValue(0), divisor); } } return null; } - private @Nullable static Analysis fromThrowStatement(PsiThrowStatement throwStatement) { + private @Nullable static AnalysisStartingPoint fromThrowStatement(PsiThrowStatement throwStatement) { PsiElement parent = throwStatement.getParent(); if (parent instanceof PsiCodeBlock) { PsiElement statement = throwStatement.getPrevSibling(); @@ -163,8 +159,8 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi boolean elseExits = ifStatement.getElseBranch() != null && !ControlFlowUtils.statementMayCompleteNormally(ifStatement.getElseBranch()); if (thenExits != elseExits) { - Analysis analysis = fromCondition(ifStatement.getCondition()); - return thenExits ? tryNegate(analysis) : analysis; + AnalysisStartingPoint analysis = AnalysisStartingPoint.fromCondition(ifStatement.getCondition()); + return thenExits ? AnalysisStartingPoint.tryNegate(analysis) : analysis; } } if (statement instanceof PsiSwitchLabelStatement) { @@ -179,7 +175,7 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi } if (parent instanceof PsiIfStatement && PsiTreeUtil.isAncestor(((PsiIfStatement)parent).getThenBranch(), throwStatement, false)) { PsiExpression cond = PsiUtil.skipParenthesizedExprDown(((PsiIfStatement)parent).getCondition()); - return fromCondition(cond); + return AnalysisStartingPoint.fromCondition(cond); } if (parent instanceof PsiSwitchLabeledRuleStatement) { return fromSwitchLabel((PsiSwitchLabeledRuleStatement)parent); @@ -187,7 +183,7 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi return null; } - private static @Nullable Analysis fromSwitchLabel(PsiSwitchLabelStatementBase label) { + private static @Nullable AnalysisStartingPoint fromSwitchLabel(PsiSwitchLabelStatementBase label) { PsiSwitchBlock block = label.getEnclosingSwitchBlock(); if (block == null) return null; boolean hasDefault = false; @@ -231,13 +227,13 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi labels = allLabels; } PsiExpression selector = block.getExpression(); - Analysis result = null; + AnalysisStartingPoint result = null; for (PsiExpression labelValue : labels) { - DfType type = fromConstant(labelValue); + DfType type = AnalysisStartingPoint.fromConstant(labelValue); if (type == null) return null; - Analysis next = Analysis.create(type, selector); + AnalysisStartingPoint next = AnalysisStartingPoint.create(type, selector); if (hasDefault) { - next = tryNegate(next); + next = AnalysisStartingPoint.tryNegate(next); } if (next == null) return null; if (result == null) { @@ -250,36 +246,36 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi return result; } - private @Nullable Analysis fromClassCastException(@NotNull PsiElement anchor, @Nullable String actualClass) { + private @Nullable AnalysisStartingPoint fromClassCastException(@NotNull PsiElement anchor, @Nullable String actualClass) { if (!(anchor instanceof PsiTypeElement)) return null; PsiTypeCastExpression castExpression = tryCast(anchor.getParent(), PsiTypeCastExpression.class); if (castExpression == null) return null; - PsiExpression ref = extractAnchor(castExpression.getOperand()); + PsiExpression ref = AnalysisStartingPoint.extractAnchor(castExpression.getOperand()); if (ref == null) return null; if (actualClass != null) { // TODO: support arrays, primitive arrays, inner classes PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(actualClass, GlobalSearchScope.allScope(myProject)); if (classes.length == 1) { - return new Analysis( + return new AnalysisStartingPoint( DfTypes.typedObject(JavaPsiFacade.getElementFactory(myProject).createType(classes[0]), Nullability.NOT_NULL), ref); } } PsiType castType = castExpression.getType(); if (castType != null) { - return tryNegate(new Analysis(DfTypes.typedObject(castType, Nullability.NULLABLE), ref)); + return AnalysisStartingPoint.tryNegate(new AnalysisStartingPoint(DfTypes.typedObject(castType, Nullability.NULLABLE), ref)); } return null; } @Nullable - private static Analysis fromAssertionError(@NotNull PsiElement anchor) { + private static AnalysisStartingPoint fromAssertionError(@NotNull PsiElement anchor) { if (anchor instanceof PsiAssertStatement) { - return tryNegate(fromCondition(((PsiAssertStatement)anchor).getAssertCondition())); + return AnalysisStartingPoint.tryNegate(AnalysisStartingPoint.fromCondition(((PsiAssertStatement)anchor).getAssertCondition())); } return null; } - private @Nullable AnAction createAction(@Nullable Analysis analysis) { + private @Nullable AnAction createAction(@Nullable AnalysisStartingPoint analysis) { if (analysis == null) return null; String text = JavaDfaSliceValueFilter.getPresentationText(analysis.myDfType, analysis.myAnchor.getType()); if (text.isEmpty()) return null; @@ -295,218 +291,4 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi } }; } - - private static @Nullable Analysis fromCondition(@Nullable PsiExpression cond) { - cond = PsiUtil.skipParenthesizedExprDown(cond); - if (cond == null) return null; - if (cond instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression)cond).getOperationTokenType(); - if (tokenType.equals(JavaTokenType.ANDAND)) { - Analysis analysis = null; - for (PsiExpression operand : ((PsiPolyadicExpression)cond).getOperands()) { - Analysis next = fromCondition(operand); - if (next == null) return null; - if (analysis == null) { - analysis = next; - } - else { - analysis = analysis.tryMeet(next); - if (analysis == null) return null; - } - } - return analysis; - } - if (tokenType.equals(JavaTokenType.OROR)) { - Analysis analysis = null; - for (PsiExpression operand : ((PsiPolyadicExpression)cond).getOperands()) { - Analysis next = fromCondition(operand); - if (next == null) return null; - if (analysis == null) { - analysis = next; - } - else { - analysis = analysis.tryJoin(next); - if (analysis == null) return null; - } - } - return analysis; - } - } - if (cond instanceof PsiBinaryExpression) { - PsiBinaryExpression binop = (PsiBinaryExpression)cond; - PsiExpression left = PsiUtil.skipParenthesizedExprDown(binop.getLOperand()); - PsiExpression right = PsiUtil.skipParenthesizedExprDown(binop.getROperand()); - Analysis analysis = fromBinOp(left, binop.getOperationTokenType(), right); - if (analysis != null) return analysis; - return fromBinOp(right, binop.getOperationTokenType(), left); - } - if (cond instanceof PsiInstanceOfExpression) { - PsiTypeElement checkType = ((PsiInstanceOfExpression)cond).getCheckType(); - if (checkType == null) return null; - PsiExpression anchor = extractAnchor(((PsiInstanceOfExpression)cond).getOperand()); - if (anchor != null) { - DfType typedObject = DfTypes.typedObject(checkType.getType(), Nullability.NOT_NULL); - return new Analysis(typedObject, anchor); - } - } - if (cond instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression)cond; - if (MethodCallUtils.isEqualsCall(call)) { - PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()); - PsiExpression argument = PsiUtil.skipParenthesizedExprDown(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); - if (qualifier != null && argument != null) { - DfType type = fromConstant(qualifier); - PsiExpression anchor = extractAnchor(argument); - if (type == null) { - type = fromConstant(argument); - anchor = extractAnchor(qualifier); - } - if (type != null && anchor != null) { - PsiType anchorType = anchor.getType(); - if (anchorType == null || DfTypes.typedObject(anchorType, Nullability.NOT_NULL).meet(type) == DfTypes.BOTTOM) return null; - return new Analysis(type, anchor); - } - } - } - } - if (BoolUtils.isNegation(cond)) { - Analysis negatedAnalysis = fromCondition(BoolUtils.getNegated(cond)); - return tryNegate(negatedAnalysis); - } - PsiExpression anchor = extractAnchor(cond); - if (anchor != null) { - return new Analysis(DfTypes.TRUE, anchor); - } - return null; - } - - private static @Nullable Analysis tryNegate(Analysis analysis) { - if (analysis == null) return null; - DfType type = analysis.myDfType.tryNegate(); - if (type == null) return null; - NullabilityProblemKind.NullabilityProblem problem = NullabilityProblemKind.fromContext(analysis.myAnchor, Collections.emptyMap()); - if (problem != null && CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION.equals(problem.thrownException())) { - type = type.meet(DfTypes.NOT_NULL_OBJECT); - } - return new Analysis(type, analysis.myAnchor); - } - - private static @Nullable DfType fromConstant(@NotNull PsiExpression constant) { - if (constant instanceof PsiClassObjectAccessExpression) { - PsiClassObjectAccessExpression classObject = (PsiClassObjectAccessExpression)constant; - PsiTypeElement operand = classObject.getOperand(); - return DfTypes.constant(operand.getType(), classObject.getType()); - } - if (constant instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression)constant).resolve(); - if (target instanceof PsiEnumConstant) { - return DfTypes.constant(target, Objects.requireNonNull(constant.getType())); - } - } - if (ExpressionUtils.isNullLiteral(constant)) { - return DfTypes.NULL; - } - Object value = ExpressionUtils.computeConstantExpression(constant); - if (value != null) { - return DfTypes.constant(value, Objects.requireNonNull(constant.getType())); - } - return null; - } - - private static @Nullable Analysis fromBinOp(@Nullable PsiExpression target, - @NotNull IElementType type, - @Nullable PsiExpression constant) { - if (constant == null) return null; - DfType constantType = fromConstant(constant); - if (constantType == null) { - return null; - } - PsiExpression anchor = extractAnchor(target); - if (anchor != null) { - PsiType anchorType = anchor.getType(); - if (anchorType == null || TypeUtils.isJavaLangString(anchorType)) return null; - if (anchorType.equals(PsiType.BYTE) || anchorType.equals(PsiType.CHAR) || anchorType.equals(PsiType.SHORT)) { - anchorType = PsiType.INT; - } - if (constantType == DfTypes.NULL || DfTypes.typedObject(anchorType, Nullability.NOT_NULL).meet(constantType) != DfTypes.BOTTOM) { - if (type.equals(JavaTokenType.EQEQ)) { - return new Analysis(constantType, anchor); - } - if (type.equals(JavaTokenType.NE)) { - return tryNegate(new Analysis(constantType, anchor)); - } - } - } - RelationType relationType = RelationType.fromElementType(type); - if (relationType != null) { - LongRangeSet set = DfLongType.extractRange(constantType).fromRelation(relationType); - if (anchor == null) { - if (target instanceof PsiBinaryExpression) { - PsiBinaryExpression binOp = (PsiBinaryExpression)target; - IElementType tokenType = binOp.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.PERC)) { - anchor = extractAnchor(binOp.getLOperand()); - if (anchor != null) { - Object divisor = ExpressionUtils.computeConstantExpression(binOp.getROperand()); - if (!(divisor instanceof Integer) && !(divisor instanceof Long)) return null; - set = LongRangeSet.fromRemainder(((Number)divisor).longValue(), set); - } - } - } - if (anchor == null) return null; - } - PsiType anchorType = anchor.getType(); - if (PsiType.LONG.equals(anchorType)) { - return new Analysis(DfTypes.longRange(set), anchor); - } - if (PsiType.INT.equals(anchorType) || - PsiType.SHORT.equals(anchorType) || - PsiType.BYTE.equals(anchorType) || - PsiType.CHAR.equals(anchorType)) { - set = set.intersect(Objects.requireNonNull(LongRangeSet.fromType(anchorType))); - return new Analysis(DfTypes.intRangeClamped(set), anchor); - } - } - return null; - } - - @Nullable - private static PsiExpression extractAnchor(@Nullable PsiExpression target) { - target = PsiUtil.skipParenthesizedExprDown(target); - if (target instanceof PsiReferenceExpression || target instanceof PsiMethodCallExpression) { - return target; - } - return null; - } - - private static class Analysis { - final DfType myDfType; - final PsiExpression myAnchor; - - private Analysis(DfType type, PsiExpression anchor) { - myDfType = type; - myAnchor = anchor; - } - - private @Nullable Analysis tryMeet(@NotNull Analysis next) { - if (!EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(this.myAnchor, next.myAnchor)) return null; - DfType meet = this.myDfType.meet(next.myDfType); - if (meet == DfTypes.BOTTOM) return null; - return new Analysis(meet, this.myAnchor); - } - - private @Nullable Analysis tryJoin(@NotNull Analysis next) { - if (!EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(this.myAnchor, next.myAnchor)) return null; - DfType meet = this.myDfType.join(next.myDfType); - if (meet == DfTypes.TOP) return null; - return new Analysis(meet, this.myAnchor); - } - - private static @Nullable Analysis create(@NotNull DfType type, @Nullable PsiExpression anchor) { - anchor = extractAnchor(anchor); - if (anchor == null) return null; - if (DfTypes.typedObject(anchor.getType(), Nullability.UNKNOWN).meet(type) == DfTypes.BOTTOM) return null; - return new Analysis(type, anchor); - } - } } diff --git a/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java b/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java index 20a931c7cf1c..21369c071d40 100644 --- a/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java +++ b/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java @@ -18,16 +18,21 @@ import java.util.Objects; public class JavaDfaSliceValueFilter implements SliceValueFilter { private final @Nullable JavaDfaSliceValueFilter myNextFilter; + private final @NotNull DfType myDfType; private JavaDfaSliceValueFilter(@Nullable JavaDfaSliceValueFilter nextFilter, @NotNull DfType type) { myNextFilter = nextFilter; myDfType = type; } - + public JavaDfaSliceValueFilter(@NotNull DfType type) { this(null, type); } + + @NotNull DfType getDfType() { + return myDfType; + } JavaDfaSliceValueFilter wrap() { return new JavaDfaSliceValueFilter(this, DfTypes.TOP); diff --git a/java/java-impl/src/com/intellij/slicer/SliceUtil.java b/java/java-impl/src/com/intellij/slicer/SliceUtil.java index d9616e3d738d..b0f5dd2c0d5c 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceUtil.java +++ b/java/java-impl/src/com/intellij/slicer/SliceUtil.java @@ -38,6 +38,7 @@ import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.CommonProcessors; +import com.intellij.util.ObjectUtils; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; @@ -46,10 +47,7 @@ import org.intellij.lang.annotations.Flow; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author cdr @@ -184,6 +182,14 @@ class SliceUtil { return processUsagesFlownDownTo(rExpression, processor, builder); } } + JavaDfaSliceValueFilter filter = ObjectUtils.tryCast(builder.getParent().params.valueFilter, JavaDfaSliceValueFilter.class); + if (filter != null && expression instanceof PsiExpression) { + AnalysisStartingPoint analysis = AnalysisStartingPoint.propagateThroughExpression(expression, filter.getDfType()); + if (analysis != null) { + return builder.withFilter(new JavaDfaSliceValueFilter(analysis.myDfType)).process(analysis.myAnchor, processor); + } + } + if (builder.hasNesting()) { // consider container creation PsiElement initializer = expression instanceof PsiNewExpression ? ((PsiNewExpression)expression).getArrayInitializer() : expression; diff --git a/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean.java b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean.java new file mode 100644 index 000000000000..0a8e4509256a --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean.java @@ -0,0 +1,13 @@ +class Test { + void test() { + foo("xyz"); + foo(123); + } + + void foo(Object obj) { + boolean b = obj instanceof String; + if (b) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean2.java b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean2.java new file mode 100644 index 000000000000..72736d21774f --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateBoolean2.java @@ -0,0 +1,13 @@ +class Test { + void test() { + foo("xyz"); + foo(123); + } + + void foo(Object obj) { + boolean b = obj instanceof String; + if (b) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateMath.java b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateMath.java new file mode 100644 index 000000000000..5bb8388ad332 --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/FilterPropagateMath.java @@ -0,0 +1,27 @@ +import java.util.Random; + +class Test { + public static void main(String[] args) { + int[] data = new int[10]; + int i = getIndex() % data.length; + System.out.println(data[i]); + } + + private static int abs(int x) { + return x < 0 ? -x : x; + } + + private static int getIndex() { + return abs(getCode()); + } + + private static int getCode() { + switch (new Random().nextInt() % 4) { + case 0: return 10; + case 1: return Integer.MAX_VALUE; + case 2: return -10; + case 3: return 0x80000000; + default: return 12345; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java index 290445c65078..34c508d1713d 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java @@ -62,7 +62,7 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT public void testAssertDivisibility() { doTest("java.lang.AssertionError", - "Find why 'i' could be odd", + "Find why 'i % 2' could be != 0", "class X {static void test(int i) {assert i % 2 == 0;}}"); } diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java index 59bff402a260..e7c26abd29ff 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java @@ -96,4 +96,7 @@ public class SliceBackwardTest extends SliceTestCase { public void testFilterIntRangeArray() throws Exception { doTest(">=0");} public void testFilterNull() throws Exception { doTest("null");} public void testNarrowFilter() throws Exception { doTest();} + public void testFilterPropagateMath() throws Exception { doTest("-8");} + public void testFilterPropagateBoolean() throws Exception { doTest("true");} + public void testFilterPropagateBoolean2() throws Exception { doTest("false");} }