aboutsummaryrefslogtreecommitdiffstats
path: root/lint/libs/lint_api
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-02-24 15:51:10 -0800
committerTor Norbye <tnorbye@google.com>2012-02-24 15:54:50 -0800
commit8a74e947c8ec79456d4f8011ee73b5fcefc03440 (patch)
treeaac66ceb1ba9f1ae918206152ac760a0a6b8f04f /lint/libs/lint_api
parent73013b8947af3d04f8c48031b51e013d05cb1b2e (diff)
downloadsdk-8a74e947c8ec79456d4f8011ee73b5fcefc03440.zip
sdk-8a74e947c8ec79456d4f8011ee73b5fcefc03440.tar.gz
sdk-8a74e947c8ec79456d4f8011ee73b5fcefc03440.tar.bz2
Fix SuppressLint annotations in outer classes
This changeset makes @SuppressLint work for classfile based detectors when the error is found in an inner class and the SuppressLint annotations is on an outer class. Innerclasses are actually separate classes from their outer classes, so they are processed separately (each .class file is read and analyzed independently). This changeset processes the class files in alphabetical* order such that it can maintain a stack of outerclasses when processing a class. The suppress lint check can then visit the outer class' annotations to see if the error should be suppressed. (*: The order isn't exactly alphabetical: We want Foo$Bar.class to come after Foo.class) This changeset also tweaks the Add Annotation quickfix such that it only offers per-method or per-class annotations, since class files do not maintain annotation info for other granularities (such as on variable declarations, so you cannot suppress classfile based issues with annotations there.) We could make a lint check which ensures that you don't try to put these annotations there :-) (This is related to issue http://b.android.com/25948) Change-Id: Ia9dbc39b1adc73a1b60e375edbf9b5618c7d2353
Diffstat (limited to 'lint/libs/lint_api')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java82
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java27
2 files changed, 108 insertions, 1 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
index a749ace..d328ab8 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
@@ -31,6 +31,7 @@ import static org.objectweb.asm.Opcodes.ASM4;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.client.api.LintListener.EventType;
import com.android.tools.lint.detector.api.Category;
@@ -66,10 +67,12 @@ import org.w3c.dom.Element;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -860,6 +863,7 @@ public class LintDriver {
if (libraries.size() > 0) {
libraryEntries = new ArrayList<ClassEntry>(64);
findClasses(libraryEntries, libraries);
+ Collections.sort(libraryEntries);
} else {
libraryEntries = Collections.emptyList();
}
@@ -877,6 +881,7 @@ public class LintDriver {
} else {
classEntries = new ArrayList<ClassEntry>(64);
findClasses(classEntries, classFolders);
+ Collections.sort(classEntries);
}
if (getPhase() == 1) {
@@ -894,15 +899,35 @@ public class LintDriver {
runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
}
+ /**
+ * Stack of {@link ClassNode} nodes for outer classes of the currently
+ * processed class, including that class itself. Populated by
+ * {@link #runClassDetectors(Scope, List, Project, Project)} and used by
+ * {@link #getOuterClassNode(ClassNode)}
+ */
+ private Deque<ClassNode> mOuterClasses;
+
private void runClassDetectors(Scope scope, List<ClassEntry> entries,
Project project, Project main) {
if (mScope.contains(scope)) {
List<Detector> classDetectors = mScopeDetectors.get(scope);
if (classDetectors != null && classDetectors.size() > 0 && entries.size() > 0) {
+ mOuterClasses = new ArrayDeque<ClassNode>();
for (ClassEntry entry : entries) {
ClassReader reader = new ClassReader(entry.bytes);
ClassNode classNode = new ClassNode();
reader.accept(classNode, 0 /* flags */);
+
+ ClassNode peek;
+ while ((peek = mOuterClasses.peek()) != null) {
+ if (classNode.name.startsWith(peek.name)) {
+ break;
+ } else {
+ mOuterClasses.pop();
+ }
+ }
+ mOuterClasses.push(classNode);
+
if (isSuppressed(null, classNode)) {
// Class was annotated with suppress all -- no need to look any further
continue;
@@ -917,8 +942,31 @@ public class LintDriver {
return;
}
}
+
+ mOuterClasses = null;
+ }
+ }
+ }
+
+ /** Returns the outer class node of the given class node
+ * @param classNode the inner class node
+ * @return the outer class node */
+ public ClassNode getOuterClassNode(@NonNull ClassNode classNode) {
+ String outerName = classNode.outerClass;
+
+ Iterator<ClassNode> iterator = mOuterClasses.iterator();
+ while (iterator.hasNext()) {
+ ClassNode node = iterator.next();
+ if (outerName != null) {
+ if (node.name.equals(outerName)) {
+ return node;
+ }
+ } else if (node == classNode) {
+ return iterator.hasNext() ? iterator.next() : null;
}
}
+
+ return null;
}
private Map<String, String> getSuperMap(List<ClassEntry> libraryEntries,
@@ -1665,7 +1713,8 @@ public class LintDriver {
}
/** A pending class to be analyzed by {@link #checkClasses} */
- private static class ClassEntry {
+ @VisibleForTesting
+ static class ClassEntry implements Comparable<ClassEntry> {
public final File file;
public final File jarFile;
public final File binDir;
@@ -1678,5 +1727,36 @@ public class LintDriver {
this.binDir = binDir;
this.bytes = bytes;
}
+
+ @Override
+ public int compareTo(ClassEntry other) {
+ String p1 = file.getPath();
+ String p2 = other.file.getPath();
+ int m1 = p1.length();
+ int m2 = p2.length();
+ int m = Math.min(m1, m2);
+
+ for (int i = 0; i < m; i++) {
+ char c1 = p1.charAt(i);
+ char c2 = p2.charAt(i);
+ if (c1 != c2) {
+ // Sort Foo$Bar.class *after* Foo.class, even though $ < .
+ if (c1 == '.' && c2 == '$') {
+ return -1;
+ }
+ if (c1 == '$' && c2 == '.') {
+ return 1;
+ }
+ return c1 - c2;
+ }
+ }
+
+ return (m == m1) ? -1 : 1;
+ }
+
+ @Override
+ public String toString() {
+ return file.getPath();
+ }
}
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
index 9de6dc8..ff05270 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -257,6 +257,33 @@ public class ClassContext extends Context {
if (mDriver.isSuppressed(issue, mClassNode)) {
return;
}
+ ClassNode curr = mClassNode;
+ while (curr != null) {
+ curr = mDriver.getOuterClassNode(curr);
+ if (curr != null) {
+ if (mClassNode.outerMethod != null) {
+ @SuppressWarnings("rawtypes") // ASM API
+ List methods = curr.methods;
+ for (Object m : methods) {
+ MethodNode method = (MethodNode) m;
+ if (method.name.equals(mClassNode.outerMethod)
+ && method.desc.equals(mClassNode.outerMethodDesc)) {
+ // Found the outer method for this anonymous class; continue
+ // reporting on it (which will also work its way up the parent
+ // class hierarchy)
+ if (method != null && mDriver.isSuppressed(issue, method)) {
+ return;
+ }
+ break;
+ }
+ }
+ }
+ if (mDriver.isSuppressed(issue, curr)) {
+ return;
+ }
+ }
+ }
+
super.report(issue, location, message, data);
}