diff options
Diffstat (limited to 'lint/libs')
11 files changed, 221 insertions, 2 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); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java index 0c3aaee..5f9a1e3 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java @@ -76,7 +76,7 @@ public class ApiLookup { private static final int BINARY_FORMAT_VERSION = 1; private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false; private static final boolean DEBUG_SEARCH = false; - private static final boolean WRITE_STATS = LintUtils.assertionsEnabled(); + private static final boolean WRITE_STATS = false; /** Default size to reserve for each API entry when creating byte buffer to build up data */ private static final int BYTES_PER_ENTRY = 40; diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java index 797059c..eb35e1d 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java @@ -236,6 +236,26 @@ public class ApiDetectorTest extends AbstractCheckTest { )); } + public void testSuppressInnerClasses() throws Exception { + assertEquals( + // These errors are correctly -not- suppressed because they + // appear outside the middle inner class suppressing its own errors + // and its child's errors + "ApiCallTest4.java:38: Error: Call requires API level 14 (current min is 1): android.widget.GridLayout#<init>\n" + + "ApiCallTest4.java:9: Error: Call requires API level 14 (current min is 1): android.widget.GridLayout#<init>", + + lintProject( + "apicheck/classpath=>.classpath", + "apicheck/minsdk1.xml=>AndroidManifest.xml", + "apicheck/ApiCallTest4.java.txt=>src/test/pkg/ApiCallTest4.java", + "apicheck/ApiCallTest4.class.data=>bin/classes/test/pkg/ApiCallTest4.class", + "apicheck/ApiCallTest4$1.class.data=>bin/classes/test/pkg/ApiCallTest4$1.class", + "apicheck/ApiCallTest4$InnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1.class", + "apicheck/ApiCallTest4$InnerClass2.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass2.class", + "apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data=>bin/classes/test/pkg/ApiCallTest4$InnerClass1$InnerInnerClass1.class" + )); + } + public void testApiTargetAnnotation() throws Exception { assertEquals( "ApiTargetTest.java:13: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler\n" + diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data Binary files differnew file mode 100644 index 0000000..8d3fbf7 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$1.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data Binary files differnew file mode 100644 index 0000000..940b83d --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1$InnerInnerClass1.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data Binary files differnew file mode 100644 index 0000000..4da3d3b --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass1.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data Binary files differnew file mode 100644 index 0000000..c8e914b --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4$InnerClass2.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data Binary files differnew file mode 100644 index 0000000..b51d04a --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt new file mode 100644 index 0000000..de6be04 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest4.java.txt @@ -0,0 +1,41 @@ +package test.pkg; + +import android.annotation.SuppressLint; +import android.widget.GridLayout; + +@SuppressWarnings("unused") +public class ApiCallTest4 { + public void foo() { + new GridLayout(null, null, 0); + } + + @SuppressLint("NewApi") + void foo2() { + // Inner class suppressed via a method in outer class + new Runnable() { + @Override + public void run() { + new GridLayout(null, null, 0); + } + }; + } + + @SuppressLint("NewApi") + private class InnerClass1 { + void foo() { + new GridLayout(null, null, 0); + } + + private class InnerInnerClass1 { + public void foo() { + new GridLayout(null, null, 0); + } + } + } + + private class InnerClass2 { + public void foo() { + new GridLayout(null, null, 0); + } + } +}
\ No newline at end of file diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintDriverTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintDriverTest.java new file mode 100644 index 0000000..0fbc221 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/client/api/LintDriverTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.lint.client.api; + +import com.android.tools.lint.client.api.LintDriver.ClassEntry; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class LintDriverTest extends TestCase { + public void testClassEntryCompare() throws Exception { + ClassEntry c0 = new ClassEntry(new File("/a1/Foo.class"), null, null, null); + ClassEntry c1 = new ClassEntry(new File("/a1/Foo.clazz"), null, null, null); + ClassEntry c2 = new ClassEntry(new File("/a1/Foo$Inner1.class"), null, null, null); + ClassEntry c3 = new ClassEntry(new File("/a1/Foo$Inner1$Inner.class"), null, null, null); + ClassEntry c4 = new ClassEntry(new File("/a2/Foo$Inner2.clas"), null, null, null); + ClassEntry c5 = new ClassEntry(new File("/a2/Foo$Inner2.class"), null, null, null); + + List<ClassEntry> expected = Arrays.asList(c0, c1, c2, c3, c4, c5); + List<ClassEntry> list = new ArrayList<ClassEntry>(expected); + Collections.sort(list); + assertEquals(list, list); + + List<ClassEntry> list2 = Arrays.asList(c5, c4, c3, c2, c1, c0); + Collections.sort(list2); + assertEquals(expected, list2); + + List<ClassEntry> list3 = Arrays.asList(c3, c0, c1, c5, c2, c4); + Collections.sort(list3); + assertEquals(expected, list3); + } +} |