From 0c336ffda4950282b5dba5dc9d0e67404ce4b08d Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 25 May 2020 15:59:46 +0700 Subject: [PATCH] IDEA-240404 Analyze dataflow to here: stack-trace based scope GitOrigin-RevId: 33b4b1cdf2198e9c15fc2d52e0113ffe97c3dde9 --- .../DataflowExceptionAnalysisProvider.java | 8 +- ...ceValueFilter.java => DfaBasedFilter.java} | 22 ++- .../com/intellij/slicer/JavaSliceBuilder.java | 70 +++++---- .../intellij/slicer/JavaSliceProvider.java | 12 +- .../com/intellij/slicer/JavaValueFilter.java | 100 ++++++++++++ .../slicer/SliceUsageCellRenderer.java | 4 +- .../src/com/intellij/slicer/SliceUtil.java | 87 +++++----- .../src/com/intellij/slicer/StackFilter.java | 148 ++++++++++++++++++ ...iddenMethodsInThisClassHierarchyParam.java | 21 +++ ...enMethodsInThisClassHierarchyParamDfa.java | 22 +++ .../slice/backward/StackFilterSimple.java | 20 +++ .../java/slicer/SliceBackwardTest.java | 24 ++- .../execution/filters/ExceptionWorker.java | 9 +- 13 files changed, 438 insertions(+), 109 deletions(-) rename java/java-impl/src/com/intellij/slicer/{JavaDfaSliceValueFilter.java => DfaBasedFilter.java} (89%) create mode 100644 java/java-impl/src/com/intellij/slicer/JavaValueFilter.java create mode 100644 java/java-impl/src/com/intellij/slicer/StackFilter.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParam.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParamDfa.java create mode 100644 java/java-tests/testData/codeInsight/slice/backward/StackFilterSimple.java diff --git a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java index f6959cb79726..feb398f57911 100644 --- a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java +++ b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java @@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.AnAction; 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.util.ClassUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -318,17 +319,18 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi private @Nullable AnAction createAction(@Nullable AnalysisStartingPoint analysis, @NotNull Supplier> nextFramesSupplier) { if (analysis == null) return null; - String text = JavaDfaSliceValueFilter.getPresentationText(analysis.myDfType, analysis.myAnchor.getType()); + String text = DfaBasedFilter.getPresentationText(analysis.myDfType, analysis.myAnchor.getType()); if (text.isEmpty()) return null; return new AnAction(null, JavaBundle.message("action.dfa.from.stacktrace.text", analysis.myAnchor.getText(), text), null) { @Override public void actionPerformed(@NotNull AnActionEvent e) { List nextFrames = nextFramesSupplier.get(); + StackFilter stackFilter = StackFilter.from(nextFrames); SliceAnalysisParams params = new SliceAnalysisParams(); params.dataFlowToThis = true; - params.scope = new AnalysisScope(myProject); + params.scope = new AnalysisScope(GlobalSearchScope.allScope(myProject), myProject); params.scope.setSearchInLibraries(true); - params.valueFilter = new JavaDfaSliceValueFilter(analysis.myDfType); + params.valueFilter = new JavaValueFilter(new DfaBasedFilter(analysis.myDfType), stackFilter); SliceManager.getInstance(myProject).createToolWindow(analysis.myAnchor, params); } }; diff --git a/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java b/java/java-impl/src/com/intellij/slicer/DfaBasedFilter.java similarity index 89% rename from java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java rename to java/java-impl/src/com/intellij/slicer/DfaBasedFilter.java index 0223fe7cf03c..5dde09c4744d 100644 --- a/java/java-impl/src/com/intellij/slicer/JavaDfaSliceValueFilter.java +++ b/java/java-impl/src/com/intellij/slicer/DfaBasedFilter.java @@ -17,17 +17,17 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; -public class JavaDfaSliceValueFilter implements SliceValueFilter { - private final @Nullable JavaDfaSliceValueFilter myNextFilter; +final class DfaBasedFilter { + private final @Nullable DfaBasedFilter myNextFilter; private final @NotNull DfType myDfType; - private JavaDfaSliceValueFilter(@Nullable JavaDfaSliceValueFilter nextFilter, @NotNull DfType type) { + private DfaBasedFilter(@Nullable DfaBasedFilter nextFilter, @NotNull DfType type) { myNextFilter = nextFilter; myDfType = type; } - public JavaDfaSliceValueFilter(@NotNull DfType type) { + DfaBasedFilter(@NotNull DfType type) { this(null, type); } @@ -35,16 +35,15 @@ public class JavaDfaSliceValueFilter implements SliceValueFilter { return myDfType; } - JavaDfaSliceValueFilter wrap() { - return new JavaDfaSliceValueFilter(this, DfTypes.TOP); + DfaBasedFilter wrap() { + return new DfaBasedFilter(this, DfTypes.TOP); } - JavaDfaSliceValueFilter unwrap() { + DfaBasedFilter unwrap() { return myNextFilter; } - @Override - public boolean allowed(@NotNull PsiElement element) { + boolean allowed(@NotNull PsiElement element) { return allowed(element, true); } @@ -64,7 +63,7 @@ public class JavaDfaSliceValueFilter implements SliceValueFilter { return dfType.meet(myDfType) != DfTypes.BOTTOM; } - @Nullable JavaDfaSliceValueFilter mergeFilter(@NotNull PsiElement element) { + @Nullable DfaBasedFilter mergeFilter(@NotNull PsiElement element) { DfType type = getElementDfType(element, true); if (type instanceof DfReferenceType) { type = ((DfReferenceType)type).dropLocality().dropMutability(); @@ -72,7 +71,7 @@ public class JavaDfaSliceValueFilter implements SliceValueFilter { DfType meet = type.meet(myDfType); if (meet == DfTypes.TOP && myNextFilter == null) return null; if (meet == DfTypes.BOTTOM || meet.equals(myDfType)) return this; - return new JavaDfaSliceValueFilter(myNextFilter, meet); + return new DfaBasedFilter(myNextFilter, meet); } private @NotNull DfType getElementDfType(@NotNull PsiElement element, boolean assertionsDisabled) { @@ -96,7 +95,6 @@ public class JavaDfaSliceValueFilter implements SliceValueFilter { return myDfType.toString(); } - @Override public @NotNull @Nls String getPresentationText(@NotNull PsiElement element) { if (element instanceof PsiLiteralExpression || element instanceof PsiExpression && JavaPsiMathUtil.getNumberFromLiteral((PsiExpression)element) != null) { diff --git a/java/java-impl/src/com/intellij/slicer/JavaSliceBuilder.java b/java/java-impl/src/com/intellij/slicer/JavaSliceBuilder.java index cd14f57c88ec..23ae8132803a 100644 --- a/java/java-impl/src/com/intellij/slicer/JavaSliceBuilder.java +++ b/java/java-impl/src/com/intellij/slicer/JavaSliceBuilder.java @@ -1,11 +1,13 @@ // 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.analysis.AnalysisScope; import com.intellij.codeInspection.dataFlow.types.DfTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.impl.source.DummyHolder; +import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiUtil; import com.intellij.util.Processor; import gnu.trove.THashMap; @@ -16,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; import java.util.Map; +import java.util.function.UnaryOperator; /** * A helper class to build new {@link JavaSliceUsage} and pass it to the processor @@ -25,30 +28,32 @@ final class JavaSliceBuilder { private final @NotNull PsiSubstitutor mySubstitutor; private final @Range(from = 0, to = Integer.MAX_VALUE) int myIndexNesting; private final @NotNull String mySyntheticField; - private final @NotNull SliceAnalysisParams myAnalysisParams; + private final @NotNull JavaValueFilter myFilter; private JavaSliceBuilder(@NotNull SliceUsage parent, @NotNull PsiSubstitutor substitutor, @Range(from = 0, to = Integer.MAX_VALUE) int indexNesting, @NotNull String syntheticField, - @NotNull SliceAnalysisParams analysisParams) { + @Nullable SliceValueFilter filter) { assert indexNesting >= 0 : indexNesting; myParent = parent; mySubstitutor = substitutor; myIndexNesting = indexNesting; mySyntheticField = syntheticField; - myAnalysisParams = analysisParams; + myFilter = filter instanceof JavaValueFilter + ? (JavaValueFilter)filter + : JavaValueFilter.ALLOW_EVERYTHING; } @Contract(pure = true) @NotNull JavaSliceBuilder withSubstitutor(@NotNull PsiSubstitutor substitutor) { - return new JavaSliceBuilder(myParent, substitutor, myIndexNesting, mySyntheticField, myAnalysisParams); + return new JavaSliceBuilder(myParent, substitutor, myIndexNesting, mySyntheticField, myFilter); } @Contract(pure = true) @NotNull JavaSliceBuilder withSyntheticField(@NotNull String syntheticField) { if (syntheticField.equals(mySyntheticField)) return this; - return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting, syntheticField, myAnalysisParams); + return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting, syntheticField, myFilter); } @Contract(pure = true) @@ -59,28 +64,16 @@ final class JavaSliceBuilder { @Contract(pure = true) @NotNull JavaSliceBuilder dropNesting() { if (myIndexNesting == 0) return this; - return new JavaSliceBuilder(myParent, mySubstitutor, 0, mySyntheticField, myAnalysisParams) - .withFilter(null); - } - - @Contract(pure = true) - @NotNull JavaSliceBuilder withFilter(@Nullable SliceValueFilter filter) { - if (filter == myAnalysisParams.valueFilter) return this; - SliceAnalysisParams params = new SliceAnalysisParams(myAnalysisParams); - params.valueFilter = filter; - return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting, mySyntheticField, params); + return new JavaSliceBuilder(myParent, mySubstitutor, 0, mySyntheticField, myFilter.withType(DfTypes.TOP)); } boolean process(PsiElement element, Processor processor) { final PsiElement realExpression = element.getParent() instanceof DummyHolder ? element.getParent().getContext() : element; assert realExpression != null; if (!(realExpression instanceof PsiCompiledElement)) { - JavaDfaSliceValueFilter curFilter = myAnalysisParams.valueFilter instanceof JavaDfaSliceValueFilter - ? (JavaDfaSliceValueFilter)myAnalysisParams.valueFilter - : new JavaDfaSliceValueFilter(DfTypes.TOP); - JavaDfaSliceValueFilter filter = curFilter.mergeFilter(realExpression); - SliceAnalysisParams params = myAnalysisParams; - if (filter != curFilter) { + JavaValueFilter filter = myFilter.mergeFilter(realExpression); + SliceAnalysisParams params = myParent.params; + if (filter != params.valueFilter) { params = new SliceAnalysisParams(params); params.valueFilter = filter; } @@ -106,20 +99,26 @@ final class JavaSliceBuilder { return result; } + @Contract(pure = true) + @NotNull JavaSliceBuilder withFilter(UnaryOperator filterTransformer) { + JavaValueFilter filter = filterTransformer.apply(myFilter); + if (filter == myFilter) return this; + return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting, mySyntheticField, filter); + } + + @NotNull SearchScope getSearchScope() { + AnalysisScope scope = myParent.getScope(); + return myFilter.correctScope(scope.getProject(), scope.toSearchScope()); + } + @Contract(pure = true) @NotNull JavaSliceBuilder incrementNesting() { - SliceValueFilter filter = myAnalysisParams.valueFilter; - filter = filter instanceof JavaDfaSliceValueFilter ? ((JavaDfaSliceValueFilter)filter).wrap() : null; - return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting + 1, mySyntheticField, myAnalysisParams) - .withFilter(filter); + return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting + 1, mySyntheticField, myFilter.wrap()); } @Contract(pure = true) @NotNull JavaSliceBuilder decrementNesting() { - SliceValueFilter filter = myAnalysisParams.valueFilter; - filter = filter instanceof JavaDfaSliceValueFilter ? ((JavaDfaSliceValueFilter)filter).unwrap() : null; - return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting - 1, mySyntheticField, myAnalysisParams) - .withFilter(filter); + return new JavaSliceBuilder(myParent, mySubstitutor, myIndexNesting - 1, mySyntheticField, myFilter.unwrap()); } boolean hasNesting() { @@ -136,6 +135,7 @@ final class JavaSliceBuilder { return mySubstitutor; } + @Contract(pure = true) PsiType substitute(@Nullable PsiType type) { return mySubstitutor.substitute(type); } @@ -145,17 +145,23 @@ final class JavaSliceBuilder { return mySyntheticField; } + @Contract(pure = true) + @NotNull JavaValueFilter getFilter() { + return myFilter; + } + /** * @param parent parent usage * @return new JavaSliceBuilder that inherits the parent properties */ @Contract(pure = true) - static @NotNull JavaSliceBuilder create(SliceUsage parent) { + static @NotNull JavaSliceBuilder create(@NotNull SliceUsage parent) { + SliceValueFilter filter = parent.params.valueFilter; if (parent instanceof JavaSliceUsage) { JavaSliceUsage javaParent = (JavaSliceUsage)parent; - return new JavaSliceBuilder(parent, javaParent.getSubstitutor(), javaParent.indexNesting, javaParent.syntheticField, parent.params); + return new JavaSliceBuilder(parent, javaParent.getSubstitutor(), javaParent.indexNesting, javaParent.syntheticField, filter); } - return new JavaSliceBuilder(parent, PsiSubstitutor.EMPTY, 0, "", parent.params); + return new JavaSliceBuilder(parent, PsiSubstitutor.EMPTY, 0, "", filter); } @Nullable JavaSliceBuilder combineSubstitutor(@NotNull PsiSubstitutor substitutor, @NotNull Project project) { diff --git a/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java b/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java index 9aa76ea4d49d..dca14f7d9c24 100644 --- a/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java +++ b/java/java-impl/src/com/intellij/slicer/JavaSliceProvider.java @@ -128,14 +128,14 @@ public class JavaSliceProvider implements SliceLanguageSupportProvider, SliceUsa throw new SliceFilterParseException( JavaBundle.message("slice.filter.parse.error.null.filter.not.applicable.for.primitive.type", type.getPresentableText())); } - return new JavaDfaSliceValueFilter(DfTypes.NULL); + return new JavaValueFilter(DfTypes.NULL); } if (filter.equals("!null")) { if (type instanceof PsiPrimitiveType) { throw new SliceFilterParseException( JavaBundle.message("slice.filter.parse.error.not.null.filter.not.applicable.for.primitive.type", type.getPresentableText())); } - return new JavaDfaSliceValueFilter(DfTypes.NOT_NULL_OBJECT); + return new JavaValueFilter(DfTypes.NOT_NULL_OBJECT); } RelationType relationType = RelationType.EQ; if (PsiType.BYTE.equals(type) || @@ -155,7 +155,7 @@ public class JavaSliceProvider implements SliceLanguageSupportProvider, SliceUsa if (psiClass != null && psiClass.isEnum()) { PsiField enumConstant = psiClass.findFieldByName(filter, false); if (enumConstant instanceof PsiEnumConstant) { - return new JavaDfaSliceValueFilter(DfTypes.constant(enumConstant, type)); + return new JavaValueFilter(DfTypes.constant(enumConstant, type)); } else { throw new SliceFilterParseException(JavaBundle.message("slice.filter.parse.error.enum.constant.not.found", filter)); } @@ -181,12 +181,12 @@ public class JavaSliceProvider implements SliceLanguageSupportProvider, SliceUsa } if (PsiType.LONG.equals(type)) { LongRangeSet rangeSet = LongRangeSet.point(((Number)o).longValue()).fromRelation(relationType); - return new JavaDfaSliceValueFilter(DfTypes.longRange(rangeSet)); + return new JavaValueFilter(DfTypes.longRange(rangeSet)); } LongRangeSet rangeSet = LongRangeSet.point(((Number)o).intValue()).fromRelation(relationType); - return new JavaDfaSliceValueFilter(DfTypes.intRangeClamped(rangeSet)); + return new JavaValueFilter(DfTypes.intRangeClamped(rangeSet)); } - return new JavaDfaSliceValueFilter(DfTypes.constant(o, type)); + return new JavaValueFilter(DfTypes.constant(o, type)); } private static @Nullable PsiType getType(@NotNull PsiElement expression) { diff --git a/java/java-impl/src/com/intellij/slicer/JavaValueFilter.java b/java/java-impl/src/com/intellij/slicer/JavaValueFilter.java new file mode 100644 index 000000000000..1dd9f26d29e1 --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/JavaValueFilter.java @@ -0,0 +1,100 @@ +// 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.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.types.DfTypes; +import com.intellij.execution.filters.ExceptionAnalysisProvider; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.SearchScope; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class JavaValueFilter implements SliceValueFilter { + public static final JavaValueFilter ALLOW_EVERYTHING = new JavaValueFilter(null, null); + + private final @Nullable DfaBasedFilter myDfaFilter; + private final @Nullable StackFilter myStackFilter; + + JavaValueFilter(@Nullable DfaBasedFilter filter, + @Nullable StackFilter stackFilter) { + myDfaFilter = filter; + myStackFilter = stackFilter; + } + + JavaValueFilter(@NotNull DfType dfType) { + this(new DfaBasedFilter(dfType), null); + } + + @Override + public boolean allowed(@NotNull PsiElement element) { + return (myDfaFilter == null || myDfaFilter.allowed(element)) && + (myStackFilter == null || myStackFilter.isAcceptable(element)); + } + + public @NotNull JavaValueFilter withStack(List lines) { + return new JavaValueFilter(myDfaFilter, StackFilter.from(lines)); + } + + @NotNull JavaValueFilter withType(DfType type) { + return new JavaValueFilter(new DfaBasedFilter(type), myStackFilter); + } + + @NotNull JavaValueFilter pushFrame() { + if (myStackFilter == null) return this; + return new JavaValueFilter(myDfaFilter, myStackFilter.pushFrame()); + } + + @NotNull JavaValueFilter popFrame() { + if (myStackFilter == null) return this; + return new JavaValueFilter(myDfaFilter, myStackFilter.popFrame()); + } + + @NotNull JavaValueFilter dropFrameFilter() { + if (myStackFilter == null) return this; + return new JavaValueFilter(myDfaFilter, null); + } + + @NotNull JavaValueFilter wrap() { + if (myDfaFilter == null) return this; + return new JavaValueFilter(myDfaFilter.wrap(), myStackFilter); + } + + @NotNull JavaValueFilter unwrap() { + if (myDfaFilter == null) return this; + return new JavaValueFilter(myDfaFilter.unwrap(), myStackFilter); + } + + @NotNull SearchScope correctScope(@NotNull Project project, @NotNull SearchScope scope) { + return myStackFilter == null ? scope : myStackFilter.correctScope(project, scope); + } + + @Override + public @NotNull @Nls String getPresentationText(@NotNull PsiElement element) { + // For now, stack filter doesn't contribute to the presentation text + return myDfaFilter == null ? "" : myDfaFilter.getPresentationText(element); + } + + JavaValueFilter mergeFilter(PsiElement expression) { + DfaBasedFilter dfaFilter = myDfaFilter == null ? new DfaBasedFilter(DfTypes.TOP) : myDfaFilter; + DfaBasedFilter newFilter = dfaFilter.mergeFilter(expression); + return dfaFilter == newFilter ? this : new JavaValueFilter(newFilter, myStackFilter); + } + + DfType getDfType() { + return myDfaFilter == null ? DfTypes.TOP : myDfaFilter.getDfType(); + } + + @Override + public String toString() { + return ((myDfaFilter == null ? "" : myDfaFilter.toString()) + + " " + (myStackFilter == null ? "" : myStackFilter.toString())).trim(); + } + + public boolean requiresAssertionViolation(PsiElement element) { + return myDfaFilter != null && myDfaFilter.requiresAssertionViolation(element); + } +} diff --git a/java/java-impl/src/com/intellij/slicer/SliceUsageCellRenderer.java b/java/java-impl/src/com/intellij/slicer/SliceUsageCellRenderer.java index f903cf69b52b..44d5397e51cc 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceUsageCellRenderer.java +++ b/java/java-impl/src/com/intellij/slicer/SliceUsageCellRenderer.java @@ -99,8 +99,8 @@ class SliceUsageCellRenderer extends SliceUsageCellRendererBase { String message = LangBundle.message("slice.analysis.title.filter", filterText); append(" " + message, SimpleTextAttributes.GRAY_ATTRIBUTES); } - if (filter instanceof JavaDfaSliceValueFilter && element != null && - ((JavaDfaSliceValueFilter)filter).requiresAssertionViolation(element)) { + if (filter instanceof JavaValueFilter && element != null && + ((JavaValueFilter)filter).requiresAssertionViolation(element)) { append(" " + JavaBundle.message("slice.usage.message.assertion.violated"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } } diff --git a/java/java-impl/src/com/intellij/slicer/SliceUtil.java b/java/java-impl/src/com/intellij/slicer/SliceUtil.java index b0f5dd2c0d5c..9c17d46cb68a 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceUtil.java +++ b/java/java-impl/src/com/intellij/slicer/SliceUtil.java @@ -17,9 +17,10 @@ package com.intellij.slicer; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInspection.dataFlow.DfaPsiUtil; -import com.intellij.codeInspection.dataFlow.DfaUtil; -import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil; +import com.intellij.codeInsight.JavaTargetElementEvaluator; +import com.intellij.codeInspection.dataFlow.*; +import com.intellij.codeInspection.dataFlow.types.DfType; +import com.intellij.codeInspection.dataFlow.types.DfTypes; import com.intellij.lang.Language; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.progress.ProgressManager; @@ -35,19 +36,21 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.psi.util.PsiUtil; 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 com.siyeh.ig.psiutils.ExpressionUtils; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.intellij.lang.annotations.Flow; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; /** * @author cdr @@ -106,6 +109,9 @@ class SliceUtil { addContainerReferences((PsiVariable)resolved, processor, builder); needToReportDeclaration = true; + if (resolved instanceof PsiField || StackFilter.getElementContext(resolved) != StackFilter.getElementContext(expression)) { + builder = builder.withFilter(JavaValueFilter::dropFrameFilter); + } expression = resolved; } if (expression instanceof PsiVariable) { @@ -138,7 +144,7 @@ class SliceUtil { return processFieldUsages((PsiField)variable, builder.dropSyntheticField(), processor); } else if (variable instanceof PsiParameter) { - return processParameterUsages((PsiParameter)variable, builder, processor); + return processParameterUsages((PsiParameter)variable, builder.withFilter(JavaValueFilter::popFrame), processor); } } if (expression instanceof PsiMethodCallExpression) { // ctr call can't return value or be container get, so don't use PsiCall here @@ -182,11 +188,11 @@ 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()); + DfType filterDfType = builder.getFilter().getDfType(); + if (filterDfType != DfTypes.TOP && expression instanceof PsiExpression) { + AnalysisStartingPoint analysis = AnalysisStartingPoint.propagateThroughExpression(expression, filterDfType); if (analysis != null) { - return builder.withFilter(new JavaDfaSliceValueFilter(analysis.myDfType)).process(analysis.myAnchor, processor); + return builder.withFilter(filter -> filter.withType(analysis.myDfType)).process(analysis.myAnchor, processor); } } @@ -253,7 +259,7 @@ class SliceUtil { final PsiType parentType = builder.substitute(methodCallExpr.getType()); final PsiSubstitutor substitutor = resolved.getSubstitutor().putAll(builder.getSubstitutor()); return processMethodReturnValue(processor, builder.getSubstitutor(), qualifierClass, methodCalled, parentType, - builder.withSubstitutor(substitutor).dropSyntheticField()); + builder.withSubstitutor(substitutor).dropSyntheticField().withFilter(JavaValueFilter::pushFrame)); } private static boolean processMethodReturnValue(@NotNull Processor processor, @@ -263,15 +269,11 @@ class SliceUtil { @Nullable PsiType parentType, @NotNull JavaSliceBuilder builder) { Collection overrides = new THashSet<>(); - AnalysisScope parentScope = builder.getParent().getScope(); - OverridingMethodsSearch.search(methodCalled, parentScope.toSearchScope(), true).forEach((PsiMethod override) -> { - PsiClass containingClass = override.getContainingClass(); - if (containingClass == null) return true; - if (qualifierClass == null || containingClass.isInheritor(qualifierClass, true)) { - overrides.add(override); - } - return true; - }); + SearchScope scope = builder.getSearchScope(); + if (qualifierClass != null && qualifierClass != methodCalled.getContainingClass()) { + scope = JavaTargetElementEvaluator.getHierarchyScope(qualifierClass, scope); + } + overrides.addAll(OverridingMethodsSearch.search(methodCalled, scope, true).findAll()); overrides.add(methodCalled); final boolean[] result = {true}; @@ -280,7 +282,7 @@ class SliceUtil { if (override instanceof PsiCompiledElement) { override = (PsiMethod)override.getNavigationElement(); } - if (!parentScope.contains(override)) continue; + if (!builder.getSearchScope().contains(PsiUtil.preferCompiledElement(override).getContainingFile().getVirtualFile())) continue; Language language = override.getLanguage(); if (language != JavaLanguage.INSTANCE) { @@ -322,28 +324,17 @@ class SliceUtil { } private static PsiClass resolveQualifier(@NotNull PsiMethodCallExpression expr) { - PsiExpression qualifier = expr.getMethodExpression().getQualifierExpression(); - if (qualifier == null) { - PsiMethodCallExpression copy = (PsiMethodCallExpression)expr.copy(); - PsiReferenceExpression methodExpression = copy.getMethodExpression(); - - PsiThisExpression thisExpression = RefactoringChangeUtil.createThisExpression(expr.getManager(), null); - methodExpression.setQualifierExpression(thisExpression); - qualifier = methodExpression.getQualifierExpression(); + PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(expr.getMethodExpression()); + if (qualifier == null) return null; + PsiType psiType = null; + CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(qualifier); + if (result != null) { + psiType = TypeConstraint.fromDfType(result.getDfTypeNoAssertions(qualifier)).getPsiType(qualifier.getProject()); } - if (qualifier != null) { - if (qualifier instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression)qualifier).resolve(); - if (resolved instanceof PsiClass) return (PsiClass)resolved; - } - else if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { - PsiType type = qualifier.getType(); - if (type instanceof PsiClassType) { - return ((PsiClassType)type).resolve(); - } - } + if (psiType == null) { + psiType = qualifier.getType(); } - return null; + return PsiUtil.resolveClassInClassTypeOnly(psiType); } private static boolean processFieldUsages(@NotNull final PsiField field, @@ -356,7 +347,7 @@ class SliceUtil { } } AnalysisScope scope = builder.getParent().getScope(); - SearchScope searchScope = scope.toSearchScope(); + SearchScope searchScope = builder.getSearchScope(); return ReferencesSearch.search(field, searchScope).forEach(reference -> { ProgressManager.checkCanceled(); PsiElement element = reference.getElement(); @@ -370,10 +361,10 @@ class SliceUtil { if (PsiUtil.isOnAssignmentLeftHand(referenceExpression)) { PsiExpression rExpression = ((PsiAssignmentExpression)parentExpr).getRExpression(); if (rExpression != null) { - PsiType rtype = rExpression.getType(); - PsiType ftype = field.getType(); - PsiType subFType = builder.substitute(ftype); - PsiType subRType = builder.substitute(rtype); + PsiType rType = rExpression.getType(); + PsiType fType = field.getType(); + PsiType subFType = builder.substitute(fType); + PsiType subRType = builder.substitute(rType); if (subFType != null && subRType != null && TypeConversionUtil.isAssignable(subFType, subRType)) { return builder.process(rExpression, processor); } @@ -422,7 +413,7 @@ class SliceUtil { final Set processed = new THashSet<>(); //usages of super method and overridden method can overlap for (final PsiMethod superMethod : superMethods) { - if (!MethodReferencesSearch.search(superMethod, builder.getParent().getScope().toSearchScope(), true).forEach(reference -> { + if (!MethodReferencesSearch.search(superMethod, builder.getSearchScope(), true).forEach(reference -> { ProgressManager.checkCanceled(); synchronized (processed) { if (!processed.add(reference)) return true; diff --git a/java/java-impl/src/com/intellij/slicer/StackFilter.java b/java/java-impl/src/com/intellij/slicer/StackFilter.java new file mode 100644 index 000000000000..7c9c3c4ae55d --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/StackFilter.java @@ -0,0 +1,148 @@ +// 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.execution.filters.ExceptionAnalysisProvider; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.ig.psiutils.ClassUtils; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Allows to narrow dataflow-to-here results based on known stacktrace. + * TODO: support bridge methods + */ +class StackFilter { + final int myExtraFrames; + final @NotNull String myClassName; + final @NotNull String myMethodName; + final @Nullable StackFilter myNext; + + private StackFilter(int frames, + @NotNull String className, + @NotNull String methodName, + @Nullable StackFilter next) { + myExtraFrames = frames; + myClassName = className; + myMethodName = methodName; + myNext = next; + } + + SearchScope correctScope(Project project, SearchScope base) { + if (base instanceof GlobalSearchScope && myExtraFrames == 0) { + PsiManager instance = PsiManager.getInstance(project); + String packageName = StringUtil.getPackageName(myClassName); + return new GlobalSearchScope() { + @Override + public boolean isSearchInModuleContent(@NotNull Module aModule) { + return ((GlobalSearchScope)base).isSearchInModuleContent(aModule); + } + + @Override + public boolean isSearchInLibraries() { + return ((GlobalSearchScope)base).isSearchInLibraries(); + } + + @Override + public boolean contains(@NotNull VirtualFile file) { + if (!base.contains(file)) return false; + PsiFile psiFile = instance.findFile(file); + if (!(psiFile instanceof PsiClassOwner)) return false; + // Do not filter by exact class for now + return ((PsiClassOwner)psiFile).getPackageName().equals(packageName); + } + }; + } + return base; + } + + boolean isAcceptable(PsiElement element) { + if (myExtraFrames > 0) return true; + PsiElement parent = getElementContext(element); + if (parent instanceof PsiMember) { + return myMethodName.equals(getExpectedName((PsiMember)parent)) && classMatches(((PsiMember)parent).getContainingClass()); + } + if (parent instanceof PsiLambdaExpression) { + return myMethodName.startsWith("lambda$") && classMatches(ClassUtils.getContainingClass(parent)); + } + return false; + } + + static PsiElement getElementContext(PsiElement element) { + PsiElement parent; + while(true) { + parent = PsiTreeUtil.getParentOfType(element, PsiMember.class, PsiLambdaExpression.class); + if (parent instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)parent).getArgumentList(), element, true)) { + element = parent; + } else { + break; + } + } + return parent; + } + + private static @Nullable String getExpectedName(PsiMember member) { + if (member instanceof PsiMethod) { + return ((PsiMethod)member).isConstructor() ? "" : member.getName(); + } + if (member instanceof PsiField || member instanceof PsiClassInitializer) { + return member.hasModifierProperty(PsiModifier.STATIC) ? "" : ""; + } + return null; + } + + private boolean classMatches(PsiClass aClass) { + if (aClass == null) return false; + PsiFile file = aClass.getContainingFile(); + if (!(file instanceof PsiClassOwner)) return false; + String packageName = StringUtil.getPackageName(myClassName); + if (!((PsiClassOwner)file).getPackageName().matches(packageName)) return false; + String shortName = StringUtil.getShortName(myClassName); + return classNameMatches(aClass, shortName); + } + + private static boolean classNameMatches(PsiClass aClass, String shortName) { + String actualName = aClass.getName(); + if (shortName.equals(actualName)) return true; + String afterDollar = StringUtil.getShortName(shortName, '$'); + if (actualName != null) { + if (!actualName.equals(afterDollar)) return false; + } else { + if (!afterDollar.matches("\\d+")) return false; + } + PsiClass containingClass = ClassUtils.getContainingClass(aClass); + String prefix = StringUtil.substringBefore(shortName, "$"); + if (prefix == null) { + return containingClass == null; + } + return containingClass != null && classNameMatches(containingClass, prefix); + } + + @NotNull StackFilter pushFrame() { + return new StackFilter(myExtraFrames + 1, myClassName, myMethodName, myNext); + } + + @Nullable StackFilter popFrame() { + return myExtraFrames == 0 ? myNext : + new StackFilter(myExtraFrames - 1, myClassName, myMethodName, myNext); + } + + static @Nullable StackFilter from(List list) { + return StreamEx.of(list).foldRight(null, (line, prev) -> + new StackFilter(0, line.getClassName(), line.getMethodName(), prev)); + } + + @Override + public String toString() { + return (myExtraFrames == 0 ? "" : myExtraFrames + "+") + myClassName + "." + myMethodName; + } +} diff --git a/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParam.java b/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParam.java new file mode 100644 index 000000000000..1bd75d409373 --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParam.java @@ -0,0 +1,21 @@ +interface I { + String getValue(); +} + +interface J extends I { + +} + +class X implements I { + public String getValue() {return "X";} +} + +class Y implements J { + public String getValue() {return "Y";} +} + +class Test { + void foo(J j) { + String s = j.getValue(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParamDfa.java b/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParamDfa.java new file mode 100644 index 000000000000..d20956c63d85 --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/SearchOverriddenMethodsInThisClassHierarchyParamDfa.java @@ -0,0 +1,22 @@ +interface I { + String getValue(); +} + +interface J extends I { + +} + +class X implements I { + public String getValue() {return "X";} +} + +class Y implements J { + public String getValue() {return "Y";} +} + +class Test { + void foo(I i) { + if (!(i instanceof J)) return; + String s = i.getValue(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/slice/backward/StackFilterSimple.java b/java/java-tests/testData/codeInsight/slice/backward/StackFilterSimple.java new file mode 100644 index 000000000000..4ea4797bc27c --- /dev/null +++ b/java/java-tests/testData/codeInsight/slice/backward/StackFilterSimple.java @@ -0,0 +1,20 @@ +class MainTest { + static void test(String str) { + System.out.println(str.trim()); + } + + public static void main(String[] args) { + foo("xyz"); + foo(null); + bar("xyz"); + bar(null); + } + + private static void bar(String s) { + test(s); + } + + private static void foo(String s) { + test(s); + } +} \ No newline at end of file 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 3220f099323f..dc4078efdd67 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/SliceBackwardTest.java @@ -17,13 +17,17 @@ package com.intellij.java.slicer; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.execution.filters.ExceptionAnalysisProvider; import com.intellij.openapi.editor.RangeMarker; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.slicer.*; +import com.intellij.util.ArrayUtil; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import java.util.Collection; +import java.util.List; import java.util.Map; /** @@ -35,6 +39,10 @@ public class SliceBackwardTest extends SliceTestCase { } private void doTest(@NotNull String filter) throws Exception { + doTest(filter, ArrayUtil.EMPTY_STRING_ARRAY); + } + + private void doTest(@NotNull String filter, @NotNull String @NotNull... stack) throws Exception { configureByFile("/codeInsight/slice/backward/"+getTestName(false)+".java"); Map sliceUsageName2Offset = SliceTestUtil.extractSliceOffsetsFromDocument(getEditor().getDocument()); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); @@ -47,9 +55,13 @@ public class SliceBackwardTest extends SliceTestCase { params.scope = new AnalysisScope(getProject()); params.dataFlowToThis = true; SliceLanguageSupportProvider provider = LanguageSlicing.getProvider(element); - if (!filter.isEmpty()) { - params.valueFilter = provider.parseFilter(element, filter); - } + params.valueFilter = filter.isEmpty() ? JavaValueFilter.ALLOW_EVERYTHING : provider.parseFilter(element, filter); + List lines = StreamEx.of(stack).map(line -> { + String[] parts = line.split(":"); + return new ExceptionAnalysisProvider.StackLine(parts[0], parts[1]); + }).toList(); + assertTrue(params.valueFilter instanceof JavaValueFilter); + params.valueFilter = ((JavaValueFilter)params.valueFilter).withStack(lines); SliceUsage usage = provider.createRootUsage(element, params); SliceTestUtil.checkUsages(usage, tree); @@ -90,8 +102,11 @@ public class SliceBackwardTest extends SliceTestCase { public void testFinalVarAssignedBeforePassingToAnonymous() throws Exception { doTest();} public void testLocalVarDeclarationAndAssignment() throws Exception { doTest();} public void testSearchOverriddenMethodsInThisClassHierarchy() throws Exception { doTest();} + public void testSearchOverriddenMethodsInThisClassHierarchyParam() throws Exception { doTest();} + public void testSearchOverriddenMethodsInThisClassHierarchyParamDfa() throws Exception { doTest();} public void testAppend() throws Exception { doTest();} public void testRequireNonNull() throws Exception { doTest();} + public void testFilterIntRange() throws Exception { doTest(">=0");} public void testFilterIntRangeArray() throws Exception { doTest(">=0");} public void testFilterNull() throws Exception { doTest("null");} @@ -101,4 +116,7 @@ public class SliceBackwardTest extends SliceTestCase { public void testFilterPropagateBoolean2() throws Exception { doTest("false");} public void testFilterAssertionViolation() throws Exception { doTest("-1");} public void testReturnParameter() throws Exception { doTest(); } + + public void testStackFilterSimple() throws Exception { doTest("null", + "MainTest:test", "MainTest:foo", "MainTest:main");} } diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index 5b16af4cd5ab..9a0c2c338123 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -438,14 +438,17 @@ public class ExceptionWorker { if (originalEditor != null) { Document origDocument = originalEditor.getDocument(); supplier = () -> { - List nextLines = new ArrayList<>(); int stackLineNumber = origDocument.getLineNumber(myTextEndOffset); + if (stackLineNumber < 1) return Collections.emptyList(); int lineCount = Math.min(origDocument.getLineCount(), stackLineNumber + 100); - for (int i = stackLineNumber + 1; i < lineCount; i++) { + List nextLines = new ArrayList<>(); + for (int i = stackLineNumber - 1; i < lineCount; i++) { String traceLine = origDocument.getText(TextRange.create(origDocument.getLineStartOffset(i), origDocument.getLineEndOffset(i))); ParsedLine line = parseExceptionLine(traceLine); if (line == null) break; - StackLine stackLine = new StackLine(line.classFqnRange.substring(traceLine), line.methodNameRange.substring(traceLine)); + String methodName = line.methodNameRange.substring(traceLine); + if (methodName.startsWith("access$")) continue; + StackLine stackLine = new StackLine(line.classFqnRange.substring(traceLine), methodName); nextLines.add(stackLine); } return nextLines;