IDEA-240404 Analyze dataflow to here: stack-trace based scope

GitOrigin-RevId: 33b4b1cdf2198e9c15fc2d52e0113ffe97c3dde9
This commit is contained in:
Tagir Valeev
2020-05-25 12:59:28 +03:00
committed by intellij-monorepo-bot
parent 19bed5ea77
commit 0c336ffda4
13 changed files with 438 additions and 109 deletions
@@ -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<List<StackLine>> 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<StackLine> 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);
}
};
@@ -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) {
@@ -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<? super SliceUsage> 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<JavaValueFilter> 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) {
@@ -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) {
@@ -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<ExceptionAnalysisProvider.StackLine> 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);
}
}
@@ -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);
}
}
@@ -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<? super SliceUsage> processor,
@@ -263,15 +269,11 @@ class SliceUtil {
@Nullable PsiType parentType,
@NotNull JavaSliceBuilder builder) {
Collection<PsiMethod> 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<PsiReference> 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;
@@ -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() ? "<init>" : member.getName();
}
if (member instanceof PsiField || member instanceof PsiClassInitializer) {
return member.hasModifierProperty(PsiModifier.STATIC) ? "<clinit>" : "<init>";
}
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<ExceptionAnalysisProvider.StackLine> 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;
}
}
@@ -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 <flown11>"Y";}
}
class Test {
void foo(J j) {
String <caret>s = <flown1>j.getValue();
}
}
@@ -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 <flown11>"Y";}
}
class Test {
void foo(I i) {
if (!(i instanceof J)) return;
String <caret>s = <flown1>i.getValue();
}
}
@@ -0,0 +1,20 @@
class MainTest {
static void test(String <flown1>str) {
System.out.println(<caret>str.trim());
}
public static void main(String[] args) {
foo("xyz");
foo(<flown1111>null);
bar("xyz");
bar(null);
}
private static void bar(String s) {
test(s);
}
private static void foo(String <flown111>s) {
test(<flown11>s);
}
}
@@ -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<String, RangeMarker> 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<ExceptionAnalysisProvider.StackLine> 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");}
}
@@ -438,14 +438,17 @@ public class ExceptionWorker {
if (originalEditor != null) {
Document origDocument = originalEditor.getDocument();
supplier = () -> {
List<StackLine> 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<StackLine> 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;