blob: a689e181f3df4737cfad35a286db2f2384b3adf7 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.slicer;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSubstitutorImpl;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
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.util.ArrayUtilRt;
import com.intellij.util.Processor;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* @author cdr
*/
public class SliceUtil {
public static boolean processUsagesFlownDownTo(@NotNull PsiElement expression,
@NotNull Processor<SliceUsage> processor,
@NotNull SliceUsage parent,
@NotNull PsiSubstitutor parentSubstitutor) {
expression = simplify(expression);
PsiElement original = expression;
if (expression instanceof PsiReferenceExpression) {
PsiElement element = SliceForwardUtil.complexify(expression);
if (element instanceof PsiExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)element)) {
PsiExpression rightSide = ((PsiAssignmentExpression)element.getParent()).getRExpression();
return rightSide == null || handToProcessor(rightSide, processor, parent, parentSubstitutor);
}
PsiReferenceExpression ref = (PsiReferenceExpression)expression;
JavaResolveResult result = ref.advancedResolve(false);
parentSubstitutor = result.getSubstitutor().putAll(parentSubstitutor);
PsiElement resolved = result.getElement();
if (resolved instanceof PsiCompiledElement) {
resolved = resolved.getNavigationElement();
}
if (resolved instanceof PsiMethod && expression.getParent() instanceof PsiMethodCallExpression) {
return processUsagesFlownDownTo(expression.getParent(), processor, parent, parentSubstitutor);
}
if (!(resolved instanceof PsiVariable)) return true;
expression = resolved;
}
if (expression instanceof PsiVariable) {
PsiVariable variable = (PsiVariable)expression;
Collection<PsiExpression> values = DfaUtil.getCachedVariableValues(variable, original);
if (values == null) {
SliceUsage stopUsage = createTooComplexDFAUsage(expression, parent, parentSubstitutor);
return processor.process(stopUsage);
}
final Set<PsiExpression> expressions = new THashSet<PsiExpression>(values);
PsiExpression initializer = variable.getInitializer();
if (initializer != null && expressions.isEmpty()) expressions.add(initializer);
for (PsiExpression exp : expressions) {
if (!handToProcessor(exp, processor, parent, parentSubstitutor)) return false;
}
if (variable instanceof PsiField) {
return processFieldUsages((PsiField)variable, processor, parent, parentSubstitutor);
}
else if (variable instanceof PsiParameter) {
return processParameterUsages((PsiParameter)variable, processor, parent, parentSubstitutor);
}
}
if (expression instanceof PsiMethodCallExpression) {
return processMethodReturnValue((PsiMethodCallExpression)expression, processor, parent, parentSubstitutor);
}
if (expression instanceof PsiConditionalExpression) {
PsiConditionalExpression conditional = (PsiConditionalExpression)expression;
PsiExpression thenE = conditional.getThenExpression();
PsiExpression elseE = conditional.getElseExpression();
if (thenE != null && !handToProcessor(thenE, processor, parent, parentSubstitutor)) return false;
if (elseE != null && !handToProcessor(elseE, processor, parent, parentSubstitutor)) return false;
}
if (expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression;
IElementType tokenType = assignment.getOperationTokenType();
PsiExpression rExpression = assignment.getRExpression();
if (tokenType == JavaTokenType.EQ && rExpression != null) {
return processUsagesFlownDownTo(rExpression, processor, parent, parentSubstitutor);
}
}
return true;
}
private static PsiElement simplify(@NotNull PsiElement expression) {
if (expression instanceof PsiParenthesizedExpression) {
return simplify(((PsiParenthesizedExpression)expression).getExpression());
}
if (expression instanceof PsiTypeCastExpression) {
return simplify(((PsiTypeCastExpression)expression).getOperand());
}
return expression;
}
private static boolean handToProcessor(@NotNull PsiExpression exp,
@NotNull Processor<SliceUsage> processor,
@NotNull SliceUsage parent,
@NotNull PsiSubstitutor substitutor) {
final PsiExpression realExpression =
exp.getParent() instanceof DummyHolder ? (PsiExpression)exp.getParent().getContext() : exp;
assert realExpression != null;
if (!(realExpression instanceof PsiCompiledElement)) {
SliceUsage usage = createSliceUsage(realExpression, parent, substitutor);
if (!processor.process(usage)) return false;
}
return true;
}
private static boolean processMethodReturnValue(@NotNull final PsiMethodCallExpression methodCallExpr,
@NotNull final Processor<SliceUsage> processor,
@NotNull final SliceUsage parent,
@NotNull final PsiSubstitutor parentSubstitutor) {
final JavaResolveResult resolved = methodCallExpr.resolveMethodGenerics();
PsiElement r = resolved.getElement();
if (r instanceof PsiCompiledElement) {
r = r.getNavigationElement();
}
if (!(r instanceof PsiMethod)) return true;
PsiMethod methodCalled = (PsiMethod)r;
PsiType returnType = methodCalled.getReturnType();
if (returnType == null) return true;
final PsiType parentType = parentSubstitutor.substitute(methodCallExpr.getType());
final PsiSubstitutor substitutor = resolved.getSubstitutor().putAll(parentSubstitutor);
Collection<PsiMethod> overrides = new THashSet<PsiMethod>(OverridingMethodsSearch.search(methodCalled, parent.getScope().toSearchScope(), true).findAll());
overrides.add(methodCalled);
final boolean[] result = {true};
for (PsiMethod override : overrides) {
if (!result[0]) break;
if (override instanceof PsiCompiledElement) {
override = (PsiMethod)override.getNavigationElement();
}
if (!parent.getScope().contains(override)) continue;
final PsiCodeBlock body = override.getBody();
if (body == null) continue;
final PsiSubstitutor s = methodCalled == override ? substitutor :
MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodCalled.getSignature(substitutor), override.getSignature(substitutor));
final PsiSubstitutor superSubstitutor = s == null ? parentSubstitutor : s;
body.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitAnonymousClass(PsiAnonymousClass aClass) {
// do not look for returns there
}
@Override
public void visitReturnStatement(final PsiReturnStatement statement) {
PsiExpression returnValue = statement.getReturnValue();
if (returnValue == null) return;
PsiType right = superSubstitutor.substitute(superSubstitutor.substitute(returnValue.getType()));
if (right == null || !TypeConversionUtil.isAssignable(parentType, right)) return;
if (!handToProcessor(returnValue, processor, parent, substitutor)) {
stopWalking();
result[0] = false;
}
}
});
}
return result[0];
}
private static boolean processFieldUsages(@NotNull final PsiField field, @NotNull final Processor<SliceUsage> processor, @NotNull final SliceUsage parent,
@NotNull final PsiSubstitutor parentSubstitutor) {
if (field.hasInitializer()) {
PsiExpression initializer = field.getInitializer();
if (initializer != null && !(field instanceof PsiCompiledElement)) {
if (!handToProcessor(initializer, processor, parent, parentSubstitutor)) return false;
}
}
SearchScope searchScope = parent.getScope().toSearchScope();
return ReferencesSearch.search(field, searchScope).forEach(new Processor<PsiReference>() {
@Override
public boolean process(final PsiReference reference) {
SliceManager.getInstance(field.getProject()).checkCanceled();
PsiElement element = reference.getElement();
if (!(element instanceof PsiReferenceExpression)) return true;
if (element instanceof PsiCompiledElement) {
element = element.getNavigationElement();
if (!parent.getScope().contains(element)) return true;
}
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)element;
PsiElement parentExpr = referenceExpression.getParent();
if (PsiUtil.isOnAssignmentLeftHand(referenceExpression)) {
PsiExpression rExpression = ((PsiAssignmentExpression)parentExpr).getRExpression();
PsiType rtype = rExpression.getType();
PsiType ftype = field.getType();
if (TypeConversionUtil.isAssignable(parentSubstitutor.substitute(ftype), parentSubstitutor.substitute(rtype))) {
return handToProcessor(rExpression, processor, parent, parentSubstitutor);
}
}
if (parentExpr instanceof PsiPrefixExpression && ((PsiPrefixExpression)parentExpr).getOperand() == referenceExpression && ( ((PsiPrefixExpression)parentExpr).getOperationTokenType() == JavaTokenType.PLUSPLUS || ((PsiPrefixExpression)parentExpr).getOperationTokenType() == JavaTokenType.MINUSMINUS)) {
PsiPrefixExpression prefixExpression = (PsiPrefixExpression)parentExpr;
return handToProcessor(prefixExpression, processor, parent, parentSubstitutor);
}
if (parentExpr instanceof PsiPostfixExpression && ((PsiPostfixExpression)parentExpr).getOperand() == referenceExpression && ( ((PsiPostfixExpression)parentExpr).getOperationTokenType() == JavaTokenType.PLUSPLUS || ((PsiPostfixExpression)parentExpr).getOperationTokenType() == JavaTokenType.MINUSMINUS)) {
PsiPostfixExpression postfixExpression = (PsiPostfixExpression)parentExpr;
return handToProcessor(postfixExpression, processor, parent, parentSubstitutor);
}
return true;
}
});
}
@NotNull
public static SliceUsage createSliceUsage(@NotNull PsiElement element, @NotNull SliceUsage parent, @NotNull PsiSubstitutor substitutor) {
return new SliceUsage(simplify(element), parent, substitutor);
}
@NotNull
public static SliceUsage createTooComplexDFAUsage(@NotNull PsiElement element, @NotNull SliceUsage parent, @NotNull PsiSubstitutor substitutor) {
return new SliceTooComplexDFAUsage(simplify(element), parent, substitutor);
}
static boolean processParameterUsages(@NotNull final PsiParameter parameter, @NotNull final Processor<SliceUsage> processor, @NotNull final SliceUsage parent,
@NotNull final PsiSubstitutor parentSubstitutor) {
PsiElement declarationScope = parameter.getDeclarationScope();
if (!(declarationScope instanceof PsiMethod)) return true;
final PsiMethod method = (PsiMethod)declarationScope;
final PsiType actualType = parameter.getType();
final PsiParameter[] actualParameters = method.getParameterList().getParameters();
final int paramSeqNo = ArrayUtilRt.find(actualParameters, parameter);
assert paramSeqNo != -1;
Collection<PsiMethod> superMethods = new THashSet<PsiMethod>(Arrays.asList(method.findDeepestSuperMethods()));
superMethods.add(method);
final Set<PsiReference> processed = new THashSet<PsiReference>(); //usages of super method and overridden method can overlap
for (final PsiMethod superMethod : superMethods) {
if (!MethodReferencesSearch.search(superMethod, parent.getScope().toSearchScope(), true).forEach(new Processor<PsiReference>() {
@Override
public boolean process(final PsiReference reference) {
SliceManager.getInstance(parameter.getProject()).checkCanceled();
synchronized (processed) {
if (!processed.add(reference)) return true;
}
PsiElement refElement = reference.getElement();
PsiExpressionList argumentList;
JavaResolveResult result;
if (refElement instanceof PsiCall) {
// the case of enum constant decl
PsiCall call = (PsiCall)refElement;
argumentList = call.getArgumentList();
result = call.resolveMethodGenerics();
}
else {
PsiElement element = refElement.getParent();
if (element instanceof PsiCompiledElement) return true;
if (element instanceof PsiAnonymousClass) {
PsiAnonymousClass anon = (PsiAnonymousClass)element;
argumentList = anon.getArgumentList();
PsiElement callExp = element.getParent();
if (!(callExp instanceof PsiCallExpression)) return true;
result = ((PsiCall)callExp).resolveMethodGenerics();
}
else {
if (!(element instanceof PsiCall)) return true;
PsiCall call = (PsiCall)element;
argumentList = call.getArgumentList();
result = call.resolveMethodGenerics();
}
}
PsiSubstitutor substitutor = result.getSubstitutor();
PsiExpression[] expressions = argumentList.getExpressions();
if (paramSeqNo >= expressions.length) {
return true;
}
PsiExpression passExpression = expressions[paramSeqNo];
Project project = argumentList.getProject();
PsiElement element = result.getElement();
if (element instanceof PsiCompiledElement) {
element = element.getNavigationElement();
}
// for erased method calls for which we cannot determine target substitutor,
// rely on call argument types. I.e. new Pair(1,2) -> Pair<Integer, Integer>
if (element instanceof PsiTypeParameterListOwner && PsiUtil.isRawSubstitutor((PsiTypeParameterListOwner)element, substitutor)) {
PsiTypeParameter[] typeParameters = substitutor.getSubstitutionMap().keySet().toArray(new PsiTypeParameter[0]);
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(project).getResolveHelper();
substitutor = resolveHelper.inferTypeArguments(typeParameters, actualParameters, expressions, parentSubstitutor, argumentList,
DefaultParameterTypeInferencePolicy.INSTANCE);
}
substitutor = removeRawMappingsLeftFromResolve(substitutor);
PsiSubstitutor combined = unify(substitutor, parentSubstitutor, project);
if (combined == null) return true;
PsiType substituted = combined.substitute(passExpression.getType());
if (substituted instanceof PsiPrimitiveType) {
final PsiClassType boxedType = ((PsiPrimitiveType)substituted).getBoxedType(argumentList);
substituted = boxedType != null ? boxedType : substituted;
}
if (substituted == null || !TypeConversionUtil.areTypesConvertible(substituted, actualType)) return true;
return handToProcessor(passExpression, processor, parent, combined);
}
})) {
return false;
}
}
return true;
}
@NotNull
private static PsiSubstitutor removeRawMappingsLeftFromResolve(@NotNull PsiSubstitutor substitutor) {
Map<PsiTypeParameter, PsiType> map = null;
for (Map.Entry<PsiTypeParameter, PsiType> entry : substitutor.getSubstitutionMap().entrySet()) {
if (entry.getValue() == null) {
if (map == null) map = new THashMap<PsiTypeParameter, PsiType>();
map.put(entry.getKey(), entry.getValue());
}
}
if (map == null) return substitutor;
Map<PsiTypeParameter, PsiType> newmap = new THashMap<PsiTypeParameter, PsiType>(substitutor.getSubstitutionMap());
newmap.keySet().removeAll(map.keySet());
return PsiSubstitutorImpl.createSubstitutor(newmap);
}
@Nullable
private static PsiSubstitutor unify(@NotNull PsiSubstitutor substitutor, @NotNull PsiSubstitutor parentSubstitutor, @NotNull Project project) {
Map<PsiTypeParameter,PsiType> newMap = new THashMap<PsiTypeParameter, PsiType>(substitutor.getSubstitutionMap());
for (Map.Entry<PsiTypeParameter, PsiType> entry : substitutor.getSubstitutionMap().entrySet()) {
PsiTypeParameter typeParameter = entry.getKey();
PsiType type = entry.getValue();
PsiClass resolved = PsiUtil.resolveClassInType(type);
if (!parentSubstitutor.getSubstitutionMap().containsKey(typeParameter)) continue;
PsiType parentType = parentSubstitutor.substitute(parentSubstitutor.substitute(typeParameter));
if (resolved instanceof PsiTypeParameter) {
PsiTypeParameter res = (PsiTypeParameter)resolved;
newMap.put(res, parentType);
}
else if (!Comparing.equal(type, parentType)) {
return null; // cannot unify
}
}
return JavaPsiFacade.getElementFactory(project).createSubstitutor(newMap);
}
}