diff options
author | Tor Norbye <tnorbye@google.com> | 2012-02-24 15:51:10 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-02-24 15:54:50 -0800 |
commit | 8a74e947c8ec79456d4f8011ee73b5fcefc03440 (patch) | |
tree | aac66ceb1ba9f1ae918206152ac760a0a6b8f04f /lint/libs/lint_api | |
parent | 73013b8947af3d04f8c48031b51e013d05cb1b2e (diff) | |
download | sdk-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.java | 82 | ||||
-rw-r--r-- | lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java | 27 |
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); } |