/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.android.tools.klint.client.api;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.Detector.JavaPsiScanner;
import com.android.tools.klint.detector.api.Detector.UastScanner;
import com.android.tools.klint.detector.api.Detector.XmlScanner;
import com.android.tools.klint.detector.api.JavaContext;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.uast.*;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.*;
import static com.android.SdkConstants.ANDROID_PKG;
/**
* Specialized visitor for running detectors on a Java AST.
* It operates in three phases:
* <ol>
* <li> First, it computes a set of maps where it generates a map from each
* significant AST attribute (such as method call names) to a list
* of detectors to consult whenever that attribute is encountered.
* Examples of "attributes" are method names, Android resource identifiers,
* and general AST node types such as "cast" nodes etc. These are
* defined on the {@link JavaPsiScanner} interface.
* <li> Second, it iterates over the document a single time, delegating to
* the detectors found at each relevant AST attribute.
* <li> Finally, it calls the remaining visitors (those that need to process a
* whole document on their own).
* </ol>
* It also notifies all the detectors before and after the document is processed
* such that they can do pre- and post-processing.
*/
public class UElementVisitor {
/** Default size of lists holding detectors of the same type for a given node type */
private static final int SAME_TYPE_COUNT = 8;
private final Map<String, List<VisitingDetector>> mMethodDetectors =
Maps.newHashMapWithExpectedSize(80);
private final Map<String, List<VisitingDetector>> mConstructorDetectors =
Maps.newHashMapWithExpectedSize(12);
private final Map<String, List<VisitingDetector>> mReferenceDetectors =
Maps.newHashMapWithExpectedSize(10);
private Set<String> mConstructorSimpleNames;
private final List<VisitingDetector> mResourceFieldDetectors =
new ArrayList<VisitingDetector>();
private final List<VisitingDetector> mAllDetectors;
private final List<VisitingDetector> mFullTreeDetectors;
private final Map<Class<? extends UElement>, List<VisitingDetector>> mNodePsiTypeDetectors =
new HashMap<Class<? extends UElement>, List<VisitingDetector>>(16);
private final JavaParser mParser;
private final Map<String, List<VisitingDetector>> mSuperClassDetectors =
new HashMap<String, List<VisitingDetector>>();
/**
* Number of fatal exceptions (internal errors, usually from ECJ) we've
* encountered; we don't log each and every one to avoid massive log spam
* in code which triggers this condition
*/
private static int sExceptionCount;
/** Max number of logs to include */
private static final int MAX_REPORTED_CRASHES = 20;
UElementVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) {
mParser = parser;
mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
for (Detector detector : detectors) {
UastScanner uastScanner = (UastScanner) detector;
VisitingDetector v = new VisitingDetector(detector, uastScanner);
mAllDetectors.add(v);
List<String> names = detector.getApplicableMethodNames();
if (names != null) {
// not supported in Java visitors; adding a method invocation node is trivial
// for that case.
assert names != XmlScanner.ALL;
for (String name : names) {
List<VisitingDetector> list = mMethodDetectors.get(name);
if (list == null) {
list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
mMethodDetectors.put(name, list);
}
list.add(v);
}
}
List<String> applicableSuperClasses = detector.applicableSuperClasses();
if (applicableSuperClasses != null) {
for (String fqn : applicableSuperClasses) {
List<VisitingDetector> list = mSuperClassDetectors.get(fqn);
if (list == null) {
list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
mSuperClassDetectors.put(fqn, list);
}
list.add(v);
}
continue;
}
List<Class<? extends UElement>> nodePsiTypes = detector.getApplicableUastTypes();
if (nodePsiTypes != null) {
for (Class<? extends UElement> type : nodePsiTypes) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(type);
if (list == null) {
list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
mNodePsiTypeDetectors.put(type, list);
}
list.add(v);
}
}
List<String> types = detector.getApplicableConstructorTypes();
if (types != null) {
// not supported in Java visitors; adding a method invocation node is trivial
// for that case.
assert types != XmlScanner.ALL;
if (mConstructorSimpleNames == null) {
mConstructorSimpleNames = Sets.newHashSet();
}
for (String type : types) {
List<VisitingDetector> list = mConstructorDetectors.get(type);
if (list == null) {
list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
mConstructorDetectors.put(type, list);
mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1));
}
list.add(v);
}
}
List<String> referenceNames = detector.getApplicableReferenceNames();
if (referenceNames != null) {
// not supported in Java visitors; adding a method invocation node is trivial
// for that case.
assert referenceNames != XmlScanner.ALL;
for (String name : referenceNames) {
List<VisitingDetector> list = mReferenceDetectors.get(name);
if (list == null) {
list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
mReferenceDetectors.put(name, list);
}
list.add(v);
}
}
if (detector.appliesToResourceRefs()) {
mResourceFieldDetectors.add(v);
} else if ((referenceNames == null || referenceNames.isEmpty())
&& (nodePsiTypes == null || nodePsiTypes.isEmpty())
&& (types == null || types.isEmpty())) {
mFullTreeDetectors.add(v);
}
}
}
void visitFile(@NonNull final JavaContext context) {
try {
Project ideaProject = context.getParser().getIdeaProject();
if (ideaProject == null) {
return;
}
VirtualFile virtualFile = StandardFileSystems.local()
.findFileByPath(context.file.getAbsolutePath());
if (virtualFile == null) {
return;
}
PsiFile psiFile = PsiManager.getInstance(ideaProject).findFile(virtualFile);
if (psiFile == null) {
return;
}
UElement uElement = context.getUastContext().convertElementWithParent(psiFile, UFile.class);
if (!(uElement instanceof UFile)) {
// No need to log this; the parser should be reporting
// a full warning (such as IssueRegistry#PARSER_ERROR)
// with details, location, etc.
return;
}
final UFile uFile = (UFile) uElement;
try {
context.setUFile(uFile);
mParser.runReadAction(new Runnable() {
@Override
public void run() {
for (VisitingDetector v : mAllDetectors) {
v.setContext(context);
v.getDetector().beforeCheckFile(context);
}
}
});
if (!mSuperClassDetectors.isEmpty()) {
mParser.runReadAction(new Runnable() {
@Override
public void run() {
SuperclassPsiVisitor visitor = new SuperclassPsiVisitor(context);
uFile.accept(visitor);
}
});
}
for (final VisitingDetector v : mFullTreeDetectors) {
mParser.runReadAction(new Runnable() {
@Override
public void run() {
UastVisitor visitor = v.getVisitor();
ProgressManager.checkCanceled();
uFile.accept(visitor);
}
});
}
if (!mMethodDetectors.isEmpty()
|| !mResourceFieldDetectors.isEmpty()
|| !mConstructorDetectors.isEmpty()
|| !mReferenceDetectors.isEmpty()) {
mParser.runReadAction(new Runnable() {
@Override
public void run() {
// TODO: Do we need to break this one up into finer grain
// locking units
UastVisitor visitor = new DelegatingPsiVisitor(context);
uFile.accept(visitor);
}
});
} else {
if (!mNodePsiTypeDetectors.isEmpty()) {
mParser.runReadAction(new Runnable() {
@Override
public void run() {
// TODO: Do we need to break this one up into finer grain
// locking units
UastVisitor visitor = new DispatchPsiVisitor();
uFile.accept(visitor);
}
});
}
}
mParser.runReadAction(new Runnable() {
@Override
public void run() {
for (VisitingDetector v : mAllDetectors) {
ProgressManager.checkCanceled();
v.getDetector().afterCheckFile(context);
}
}
});
} finally {
mParser.dispose(context, uFile);
context.setUFile(null);
}
} catch (ProcessCanceledException ignore) {
// Cancelling inspections in the IDE
} catch (RuntimeException e) {
if (sExceptionCount++ > MAX_REPORTED_CRASHES) {
// No need to keep spamming the user that a lot of the files
// are tripping up ECJ, they get the picture.
return;
}
if (e.getClass().getSimpleName().equals("IndexNotReadyException")) {
// Attempting to access PSI during startup before indices are ready; ignore these.
// See http://b.android.com/176644 for an example.
return;
}
// Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
// Don't allow lint bugs to take down the whole build. TRY to log this as a
// lint error instead!
StringBuilder sb = new StringBuilder(100);
sb.append("Unexpected failure during lint analysis of ");
sb.append(context.file.getName());
sb.append(" (this is a bug in lint or one of the libraries it depends on)\n");
sb.append(e.getClass().getSimpleName());
sb.append(':');
StackTraceElement[] stackTrace = e.getStackTrace();
int count = 0;
for (StackTraceElement frame : stackTrace) {
if (count > 0) {
sb.append("<-");
}
String className = frame.getClassName();
sb.append(className.substring(className.lastIndexOf('.') + 1));
sb.append('.').append(frame.getMethodName());
sb.append('(');
sb.append(frame.getFileName()).append(':').append(frame.getLineNumber());
sb.append(')');
count++;
// Only print the top 3-4 frames such that we can identify the bug
if (count == 4) {
break;
}
}
Throwable throwable = null; // NOT e: this makes for very noisy logs
//noinspection ConstantConditions
context.log(throwable, sb.toString());
}
}
/**
* For testing only: returns the number of exceptions thrown during Java AST analysis
*
* @return the number of internal errors found
*/
@VisibleForTesting
public static int getCrashCount() {
return sExceptionCount;
}
/**
* For testing only: clears the crash counter
*/
@VisibleForTesting
public static void clearCrashCount() {
sExceptionCount = 0;
}
public void prepare(@NonNull List<JavaContext> contexts) {
mParser.prepareJavaParse(contexts);
}
public void dispose() {
mParser.dispose();
}
@Nullable
private static Set<String> getInterfaceNames(
@Nullable Set<String> addTo,
@NonNull PsiClass cls) {
for (PsiClass resolvedInterface : cls.getInterfaces()) {
String name = resolvedInterface.getQualifiedName();
if (addTo == null) {
addTo = Sets.newHashSet();
} else if (addTo.contains(name)) {
// Superclasses can explicitly implement the same interface,
// so keep track of visited interfaces as we traverse up the
// super class chain to avoid checking the same interface
// more than once.
continue;
}
addTo.add(name);
getInterfaceNames(addTo, resolvedInterface);
}
return addTo;
}
private static class VisitingDetector {
private UastVisitor mVisitor;
private JavaContext mContext;
public final Detector mDetector;
public final UastScanner mUastScanner;
public VisitingDetector(@NonNull Detector detector, @NonNull UastScanner uastScanner) {
mDetector = detector;
mUastScanner = uastScanner;
}
@NonNull
public Detector getDetector() {
return mDetector;
}
@Nullable
public UastScanner getUastScanner() {
return mUastScanner;
}
public void setContext(@NonNull JavaContext context) {
mContext = context;
// The visitors are one-per-context, so clear them out here and construct
// lazily only if needed
mVisitor = null;
}
@NonNull
UastVisitor getVisitor() {
if (mVisitor == null) {
mVisitor = mDetector.createUastVisitor(mContext);
if (mVisitor == null) {
mVisitor = new AbstractUastVisitor() {};
}
}
return mVisitor;
}
}
private class SuperclassPsiVisitor extends AbstractUastVisitor {
private JavaContext mContext;
public SuperclassPsiVisitor(@NonNull JavaContext context) {
mContext = context;
}
@Override
public boolean visitClass(UClass node) {
boolean result = super.visitClass(node);
checkClass(node);
return result;
}
private void checkClass(@NonNull UClass node) {
ProgressManager.checkCanceled();
if (node instanceof PsiTypeParameter) {
// Not included: explained in javadoc for JavaPsiScanner#checkClass
return;
}
UClass cls = node;
int depth = 0;
while (cls != null) {
List<VisitingDetector> list = mSuperClassDetectors.get(cls.getQualifiedName());
if (list != null) {
for (VisitingDetector v : list) {
UastScanner uastScanner = v.getUastScanner();
if (uastScanner != null) {
uastScanner.checkClass(mContext, node);
}
}
}
// Check interfaces too
Set<String> interfaceNames = getInterfaceNames(null, cls);
if (interfaceNames != null) {
for (String name : interfaceNames) {
list = mSuperClassDetectors.get(name);
if (list != null) {
for (VisitingDetector v : list) {
UastScanner javaPsiScanner = v.getUastScanner();
if (javaPsiScanner != null) {
javaPsiScanner.checkClass(mContext, node);
}
}
}
}
}
cls = cls.getSuperClass();
depth++;
if (depth == 500) {
// Shouldn't happen in practice; this prevents the IDE from
// hanging if the user has accidentally typed in an incorrect
// super class which creates a cycle.
break;
}
}
}
}
private class DispatchPsiVisitor extends AbstractUastVisitor {
@Override
public boolean visitAnnotation(UAnnotation node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UAnnotation.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitAnnotation(node);
}
}
return super.visitAnnotation(node);
}
@Override
public boolean visitCatchClause(UCatchClause node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCatchClause.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitCatchClause(node);
}
}
return super.visitCatchClause(node);
}
@Override
public boolean visitMethod(UMethod node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UMethod.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitMethod(node);
}
}
return super.visitMethod(node);
}
@Override
public boolean visitVariable(UVariable node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UVariable.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitVariable(node);
}
}
return super.visitVariable(node);
}
@Override
public boolean visitFile(UFile node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UFile.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitFile(node);
}
}
return super.visitFile(node);
}
@Override
public boolean visitImportStatement(UImportStatement node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UImportStatement.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitImportStatement(node);
}
}
return super.visitImportStatement(node);
}
@Override
public boolean visitElement(UElement node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UElement.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitElement(node);
}
}
return super.visitElement(node);
}
@Override
public boolean visitClass(UClass node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClass.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitClass(node);
}
}
return super.visitClass(node);
}
@Override
public boolean visitInitializer(UClassInitializer node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClassInitializer.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitInitializer(node);
}
}
return super.visitInitializer(node);
}
@Override
public boolean visitLabeledExpression(ULabeledExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULabeledExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitLabeledExpression(node);
}
}
return super.visitLabeledExpression(node);
}
@Override
public boolean visitBlockExpression(UBlockExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBlockExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitBlockExpression(node);
}
}
return super.visitBlockExpression(node);
}
@Override
public boolean visitCallExpression(UCallExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCallExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitCallExpression(node);
}
}
return super.visitCallExpression(node);
}
@Override
public boolean visitBinaryExpression(UBinaryExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBinaryExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitBinaryExpression(node);
}
}
return super.visitBinaryExpression(node);
}
@Override
public boolean visitBinaryExpressionWithType(UBinaryExpressionWithType node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBinaryExpressionWithType.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitBinaryExpressionWithType(node);
}
}
return super.visitBinaryExpressionWithType(node);
}
@Override
public boolean visitParenthesizedExpression(UParenthesizedExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UParenthesizedExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitParenthesizedExpression(node);
}
}
return super.visitParenthesizedExpression(node);
}
@Override
public boolean visitUnaryExpression(UUnaryExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UUnaryExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitUnaryExpression(node);
}
}
return super.visitUnaryExpression(node);
}
@Override
public boolean visitPrefixExpression(UPrefixExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UPrefixExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitPrefixExpression(node);
}
}
return super.visitPrefixExpression(node);
}
@Override
public boolean visitPostfixExpression(UPostfixExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UPostfixExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitPostfixExpression(node);
}
}
return super.visitPostfixExpression(node);
}
@Override
public boolean visitIfExpression(UIfExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UIfExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitIfExpression(node);
}
}
return super.visitIfExpression(node);
}
@Override
public boolean visitSwitchExpression(USwitchExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(USwitchExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitSwitchExpression(node);
}
}
return super.visitSwitchExpression(node);
}
@Override
public boolean visitSwitchClauseExpression(USwitchClauseExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(USwitchClauseExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitSwitchClauseExpression(node);
}
}
return super.visitSwitchClauseExpression(node);
}
@Override
public boolean visitWhileExpression(UWhileExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UWhileExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitWhileExpression(node);
}
}
return super.visitWhileExpression(node);
}
@Override
public boolean visitDoWhileExpression(UDoWhileExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UDoWhileExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitDoWhileExpression(node);
}
}
return super.visitDoWhileExpression(node);
}
@Override
public boolean visitForExpression(UForExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UForExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitForExpression(node);
}
}
return super.visitForExpression(node);
}
@Override
public boolean visitForEachExpression(UForEachExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UForEachExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitForEachExpression(node);
}
}
return super.visitForEachExpression(node);
}
@Override
public boolean visitTryExpression(UTryExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UTryExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitTryExpression(node);
}
}
return super.visitTryExpression(node);
}
@Override
public boolean visitLiteralExpression(ULiteralExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULiteralExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitLiteralExpression(node);
}
}
return super.visitLiteralExpression(node);
}
@Override
public boolean visitThisExpression(UThisExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UThisExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitThisExpression(node);
}
}
return super.visitThisExpression(node);
}
@Override
public boolean visitSuperExpression(USuperExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(USuperExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitSuperExpression(node);
}
}
return super.visitSuperExpression(node);
}
@Override
public boolean visitReturnExpression(UReturnExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UReturnExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitReturnExpression(node);
}
}
return super.visitReturnExpression(node);
}
@Override
public boolean visitBreakExpression(UBreakExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBreakExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitBreakExpression(node);
}
}
return super.visitBreakExpression(node);
}
@Override
public boolean visitContinueExpression(UContinueExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UContinueExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitContinueExpression(node);
}
}
return super.visitContinueExpression(node);
}
@Override
public boolean visitThrowExpression(UThrowExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UThrowExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitThrowExpression(node);
}
}
return super.visitThrowExpression(node);
}
@Override
public boolean visitArrayAccessExpression(UArrayAccessExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UArrayAccessExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitArrayAccessExpression(node);
}
}
return super.visitArrayAccessExpression(node);
}
@Override
public boolean visitCallableReferenceExpression(UCallableReferenceExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCallableReferenceExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitCallableReferenceExpression(node);
}
}
return super.visitCallableReferenceExpression(node);
}
@Override
public boolean visitClassLiteralExpression(UClassLiteralExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClassLiteralExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitClassLiteralExpression(node);
}
}
return super.visitClassLiteralExpression(node);
}
@Override
public boolean visitLambdaExpression(ULambdaExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULambdaExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitLambdaExpression(node);
}
}
return super.visitLambdaExpression(node);
}
@Override
public boolean visitObjectLiteralExpression(UObjectLiteralExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UObjectLiteralExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitObjectLiteralExpression(node);
}
}
return super.visitObjectLiteralExpression(node);
}
@Override
public boolean visitExpressionList(UExpressionList node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UExpressionList.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitExpressionList(node);
}
}
return super.visitExpressionList(node);
}
@Override
public boolean visitTypeReferenceExpression(UTypeReferenceExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UTypeReferenceExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitTypeReferenceExpression(node);
}
}
return super.visitTypeReferenceExpression(node);
}
@Override
public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(USimpleNameReferenceExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitSimpleNameReferenceExpression(node);
}
}
return super.visitSimpleNameReferenceExpression(node);
}
@Override
public boolean visitQualifiedReferenceExpression(UQualifiedReferenceExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UQualifiedReferenceExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitQualifiedReferenceExpression(node);
}
}
return super.visitQualifiedReferenceExpression(node);
}
@Override
public boolean visitDeclarationsExpression(UDeclarationsExpression node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UDeclarationsExpression.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitDeclarationsExpression(node);
}
}
return super.visitDeclarationsExpression(node);
}
}
/** Performs common AST searches for method calls and R-type-field references.
* Note that this is a specialized form of the {@link DispatchPsiVisitor}. */
private class DelegatingPsiVisitor extends DispatchPsiVisitor {
private final JavaContext mContext;
private final boolean mVisitResources;
private final boolean mVisitMethods;
private final boolean mVisitConstructors;
private final boolean mVisitReferences;
DelegatingPsiVisitor(JavaContext context) {
mContext = context;
mVisitMethods = !mMethodDetectors.isEmpty();
mVisitConstructors = !mConstructorDetectors.isEmpty();
mVisitResources = !mResourceFieldDetectors.isEmpty();
mVisitReferences = !mReferenceDetectors.isEmpty();
}
@Override
public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) {
if (mVisitReferences || mVisitResources) {
ProgressManager.checkCanceled();
}
if (mVisitReferences) {
List<VisitingDetector> list = mReferenceDetectors.get(node.getIdentifier());
if (list != null) {
PsiElement referenced = node.resolve();
if (referenced != null) {
for (VisitingDetector v : list) {
UastScanner uastScanner = v.getUastScanner();
if (uastScanner != null) {
uastScanner.visitReference(mContext, v.getVisitor(),
node, referenced);
}
}
}
}
}
if (mVisitResources) {
AndroidReference androidReference = UastLintUtils.toAndroidReferenceViaResolve(node);
if (androidReference != null) {
for (VisitingDetector v : mResourceFieldDetectors) {
UastScanner uastScanner = v.getUastScanner();
if (uastScanner != null) {
uastScanner.visitResourceReference(mContext, v.getVisitor(),
androidReference.node,
androidReference.getType(),
androidReference.getName(),
androidReference.getPackage().equals(ANDROID_PKG));
}
}
}
}
return super.visitSimpleNameReferenceExpression(node);
}
@Override
public boolean visitCallExpression(UCallExpression node) {
boolean result = super.visitCallExpression(node);
ProgressManager.checkCanceled();
if (UastExpressionUtils.isMethodCall(node)) {
visitMethodCallExpression(node);
} else if (UastExpressionUtils.isConstructorCall(node)) {
visitNewExpression(node);
}
return result;
}
private void visitMethodCallExpression(UCallExpression node) {
if (mVisitMethods) {
String methodName = node.getMethodName();
if (methodName != null) {
List<VisitingDetector> list = mMethodDetectors.get(methodName);
if (list != null) {
PsiMethod function = node.resolve();
if (function != null) {
for (VisitingDetector v : list) {
UastScanner scanner = v.getUastScanner();
if (scanner != null) {
scanner.visitMethod(mContext, v.getVisitor(), node,
mContext.getUastContext().getMethod(function));
}
}
}
}
}
}
}
private void visitNewExpression(UCallExpression node) {
if (mVisitConstructors) {
PsiMethod resolvedConstructor = node.resolve();
if (resolvedConstructor == null) {
return;
}
PsiClass resolvedClass = resolvedConstructor.getContainingClass();
if (resolvedClass != null) {
List<VisitingDetector> list = mConstructorDetectors.get(
resolvedClass.getQualifiedName());
if (list != null) {
for (VisitingDetector v : list) {
UastScanner javaPsiScanner = v.getUastScanner();
if (javaPsiScanner != null) {
javaPsiScanner.visitConstructor(mContext,
v.getVisitor(), node,
mContext.getUastContext().getMethod(resolvedConstructor));
}
}
}
}
}
}
}
}