aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-11-28 17:42:16 -0800
committerGerrit Code Review <noreply-gerritcodereview@google.com>2012-11-28 17:42:16 -0800
commitf654fe41fea466e2461bca58c04f2b0d5a3981f9 (patch)
tree91c6fcaba898be4ac6eeabfb81a9759e0e42d5d8 /lint
parent64c7db8b1dc6e35116d45f2b6b1c230bf9039e8e (diff)
parent8d67a4b7628a10175026ac019ef3acce2cce6f64 (diff)
downloadsdk-f654fe41fea466e2461bca58c04f2b0d5a3981f9.zip
sdk-f654fe41fea466e2461bca58c04f2b0d5a3981f9.tar.gz
sdk-f654fe41fea466e2461bca58c04f2b0d5a3981f9.tar.bz2
Merge "Check for unsupported Java packages"
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiClass.java11
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java1
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java134
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java3
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/InvalidPackageDetector.java279
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiLookupTest.java13
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InvalidPackageDetectorTest.java56
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/unsupported.jar.databin0 -> 1196 bytes
8 files changed, 480 insertions, 17 deletions
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiClass.java
index b534b2f..365de80 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiClass.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiClass.java
@@ -18,6 +18,7 @@ package com.android.tools.lint.checks;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import com.android.annotations.Nullable;
import com.android.utils.Pair;
import com.google.common.collect.Lists;
@@ -229,6 +230,16 @@ public class ApiClass {
}
+ @Nullable
+ public String getPackage() {
+ int index = mName.lastIndexOf('/');
+ if (index != -1) {
+ return mName.substring(0, index);
+ }
+
+ return null;
+ }
+
@Override
public String toString() {
return mName;
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
index ba93d01..fd7bba9 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
@@ -312,7 +312,6 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
className.substring(className.lastIndexOf('/') + 1), null,
SearchHints.create(NEAREST).matchJavaSymbol());
}
-
}
}
}
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 9d661a8..17578b8 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
@@ -24,7 +24,9 @@ import com.android.annotations.VisibleForTesting;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.LintUtils;
import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.google.common.primitives.UnsignedBytes;
@@ -74,12 +76,12 @@ public class ApiLookup {
/** Relative path to the api-versions.xml database file within the Lint installation */
private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$
private static final String FILE_HEADER = "API database used by Android lint\000";
- private static final int BINARY_FORMAT_VERSION = 4;
+ private static final int BINARY_FORMAT_VERSION = 5;
private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
private static final boolean DEBUG_SEARCH = false;
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;
+ private static final int BYTES_PER_ENTRY = 36;
private final LintClient mClient;
private final File mXmlFile;
@@ -89,6 +91,7 @@ public class ApiLookup {
private int[] mIndices;
private int mClassCount;
private int mMethodCount;
+ private String[] mJavaPackages;
private static WeakReference<ApiLookup> sInstance =
new WeakReference<ApiLookup>(null);
@@ -237,16 +240,20 @@ public class ApiLookup {
* version, it can ignore it (and regenerate the cache from XML).
* 3. The number of classes [1 int]
* 4. The number of members (across all classes) [1 int].
- * 5. Class offset table (one integer per class, pointing to the byte offset in the
+ * 5. The number of java/javax packages [1 int]
+ * 6. The java/javax package name table. Each item consists of a byte count for
+ * the package string (as 1 byte) followed by the UTF-8 encoded bytes for each package.
+ * These are in sorted order.
+ * 7. Class offset table (one integer per class, pointing to the byte offset in the
* file (relative to the beginning of the file) where each class begins.
* The classes are always sorted alphabetically by fully qualified name.
- * 6. Member offset table (one integer per member, pointing to the byte offset in the
+ * 8. Member offset table (one integer per member, pointing to the byte offset in the
* file (relative to the beginning of the file) where each member entry begins.
* The members are always sorted alphabetically.
- * 7. Class entry table. Each class entry consists of the fully qualified class name,
+ * 9. Class entry table. Each class entry consists of the fully qualified class name,
* in JVM format (using / instead of . in package names and $ for inner classes),
* followed by the byte 0 as a terminator, followed by the API version as a byte.
- * 8. Member entry table. Each member entry consists of the class number (as a short),
+ * 10. Member entry table. Each member entry consists of the class number (as a short),
* followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
* signature terminator, followed by the API level as a byte.
* <p>
@@ -290,6 +297,16 @@ public class ApiLookup {
mClassCount = buffer.getInt();
mMethodCount = buffer.getInt();
+ int javaPackageCount = buffer.getInt();
+ // Read in the Java packages
+ mJavaPackages = new String[javaPackageCount];
+ for (int i = 0; i < javaPackageCount; i++) {
+ int count = UnsignedBytes.toInt(buffer.get());
+ byte[] bytes = new byte[count];
+ buffer.get(bytes, 0, count);
+ mJavaPackages[i] = new String(bytes, Charsets.UTF_8);
+ }
+
// Read in the class table indices;
int count = mClassCount + mMethodCount;
int[] offsets = new int[count];
@@ -345,10 +362,17 @@ public class ApiLookup {
Map<ApiClass, List<String>> memberMap =
Maps.newHashMapWithExpectedSize(classMap.size());
int memberCount = 0;
+ Set<String> javaPackageSet = Sets.newHashSetWithExpectedSize(70);
for (Map.Entry<String, ApiClass> entry : classMap.entrySet()) {
String className = entry.getKey();
ApiClass apiClass = entry.getValue();
+ if (className.startsWith("java/") //$NON-NLS-1$
+ || className.startsWith("javax/")) { //$NON-NLS-1$
+ String pkg = apiClass.getPackage();
+ javaPackageSet.add(pkg);
+ }
+
Set<String> allMethods = apiClass.getAllMethods(info);
Set<String> allFields = apiClass.getAllFields(info);
@@ -399,6 +423,10 @@ public class ApiLookup {
}
Collections.sort(classes);
+ List<String> javaPackages = Lists.newArrayList(javaPackageSet);
+ Collections.sort(javaPackages);
+ int javaPackageCount = javaPackages.size();
+
int entryCount = classMap.size() + memberCount;
int capacity = entryCount * BYTES_PER_ENTRY;
ByteBuffer buffer = ByteBuffer.allocate(capacity);
@@ -413,14 +441,26 @@ public class ApiLookup {
// version, it can ignore it (and regenerate the cache from XML).
buffer.put((byte) BINARY_FORMAT_VERSION);
-
-
// 3. The number of classes [1 int]
buffer.putInt(classes.size());
+
// 4. The number of members (across all classes) [1 int].
buffer.putInt(memberCount);
- // 5. Class offset table (one integer per class, pointing to the byte offset in the
+ // 5. The number of Java packages [1 int].
+ buffer.putInt(javaPackageCount);
+
+ // 6. The Java package table. There are javaPackage.size() entries, where each entry
+ // consists of a string length, as a byte, followed by the bytes in the package.
+ // There is no terminating 0.
+ for (String pkg : javaPackages) {
+ byte[] bytes = pkg.getBytes(Charsets.UTF_8);
+ assert bytes.length < 255 : pkg;
+ buffer.put((byte) bytes.length);
+ buffer.put(bytes);
+ }
+
+ // 7. Class offset table (one integer per class, pointing to the byte offset in the
// file (relative to the beginning of the file) where each class begins.
// The classes are always sorted alphabetically by fully qualified name.
int classOffsetTable = buffer.position();
@@ -431,7 +471,7 @@ public class ApiLookup {
buffer.putInt(0);
}
- // 6. Member offset table (one integer per member, pointing to the byte offset in the
+ // 8. Member offset table (one integer per member, pointing to the byte offset in the
// file (relative to the beginning of the file) where each member entry begins.
// The members are always sorted alphabetically.
int methodOffsetTable = buffer.position();
@@ -442,7 +482,7 @@ public class ApiLookup {
int nextEntry = buffer.position();
int nextOffset = classOffsetTable;
- // 7. Class entry table. Each class entry consists of the fully qualified class name,
+ // 9. Class entry table. Each class entry consists of the fully qualified class name,
// in JVM format (using / instead of . in package names and $ for inner classes),
// followed by the byte 0 as a terminator, followed by the API version as a byte.
for (String clz : classes) {
@@ -462,7 +502,7 @@ public class ApiLookup {
nextEntry = buffer.position();
}
- // 8. Member entry table. Each member entry consists of the class number (as a short),
+ // 10. Member entry table. Each member entry consists of the class number (as a short),
// followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
// signature terminator, followed by the API level as a byte.
assert nextOffset == methodOffsetTable;
@@ -715,6 +755,70 @@ public class ApiLookup {
return -1;
}
+ /**
+ * Returns true if the given owner (in VM format) is a valid Java package supported
+ * in any version of Android.
+ *
+ * @param owner the package, in VM format
+ * @return true if the package is included in one or more versions of Android
+ */
+ public boolean isValidJavaPackage(@NonNull String owner) {
+ int packageLength = owner.lastIndexOf('/');
+ if (packageLength == -1) {
+ return false;
+ }
+
+ // The index array contains class indexes from 0 to classCount and
+ // member indices from classCount to mIndices.length.
+ int low = 0;
+ int high = mJavaPackages.length - 1;
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ int offset = middle;
+
+ if (DEBUG_SEARCH) {
+ System.out.println("Comparing string " + owner + " with entry at " + offset
+ + ": " + mJavaPackages[offset]);
+ }
+
+ // Compare the api info at the given index.
+ int compare = comparePackage(mJavaPackages[offset], owner, packageLength);
+ if (compare == 0) {
+ return true;
+ }
+
+ if (compare < 0) {
+ low = middle + 1;
+ } else if (compare > 0) {
+ high = middle - 1;
+ } else {
+ assert false; // compare == 0 already handled above
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private int comparePackage(String s1, String s2, int max) {
+ for (int i = 0; i < max; i++) {
+ if (i == s1.length()) {
+ return -1;
+ }
+ char c1 = s1.charAt(i);
+ char c2 = s2.charAt(i);
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ }
+
+ if (s1.length() > max) {
+ return 1;
+ }
+
+ return 0;
+ }
+
/** Returns the class number of the given class, or -1 if it is unknown */
private int findClass(@NonNull String owner) {
assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
@@ -723,17 +827,17 @@ public class ApiLookup {
// member indices from classCount to mIndices.length.
int low = 0;
int high = mClassCount - 1;
+ // Compare the api info at the given index.
+ int classNameLength = owner.length();
while (low <= high) {
int middle = (low + high) >>> 1;
int offset = mIndices[middle];
if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + owner +" with entry at " + offset
+ System.out.println("Comparing string " + owner + " with entry at " + offset
+ ": " + dumpEntry(offset));
}
- // Compare the api info at the given index.
- int classNameLength = owner.length();
int compare = compare(mData, offset, (byte) 0, owner, classNameLength);
if (compare == 0) {
return middle;
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 52c89d4..7e7916e 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -55,7 +55,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
private static final List<Issue> sIssues;
static {
- final int initialCapacity = 131;
+ final int initialCapacity = 132;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -64,6 +64,7 @@ public class BuiltinIssueRegistry extends IssueRegistry {
issues.add(FieldGetterDetector.ISSUE);
issues.add(SdCardDetector.ISSUE);
issues.add(ApiDetector.UNSUPPORTED);
+ issues.add(InvalidPackageDetector.ISSUE);
issues.add(DuplicateIdDetector.CROSS_LAYOUT);
issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
issues.add(DuplicateResourceDetector.ISSUE);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InvalidPackageDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InvalidPackageDetector.java
new file mode 100644
index 0000000..460995d
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InvalidPackageDetector.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2012 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.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.google.common.collect.Sets;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.ast.libs.org.parboiled.google.collect.Lists;
+
+/**
+ * Looks for usages of Java packages that are not included in Android.
+ */
+public class InvalidPackageDetector extends Detector implements Detector.ClassScanner {
+ /** Accessing an invalid package */
+ public static final Issue ISSUE = Issue.create("InvalidPackage", //$NON-NLS-1$
+ "Finds API accesses to APIs that are not supported in Android",
+
+ "This check scans through libraries looking for calls to APIs that are not included " +
+ "in Android.\n" +
+ "\n" +
+ "When you create Android projects, the classpath is set up such that you can only " +
+ "access classes in the API packages that are included in Android. However, if you " +
+ "add other projects to your libs/ folder, there is no guarantee that those .jar " +
+ "files were built with an Android specific classpath, and in particular, they " +
+ "could be accessing unsupported APIs such as java.applet.\n" +
+ "\n" +
+ "This check scans through library jars and looks for references to API packages " +
+ "that are not included in Android and flags these. This is only an error if your " +
+ "code calls one of the library classes which wind up referencing the unsupported " +
+ "package.",
+
+ Category.CORRECTNESS,
+ 6,
+ Severity.ERROR,
+ InvalidPackageDetector.class,
+ EnumSet.of(Scope.JAVA_LIBRARIES));
+
+ private static final String JAVA_PKG_PREFIX = "java/"; //$NON-NLS-1$
+ private static final String JAVAX_PKG_PREFIX = "javax/"; //$NON-NLS-1$
+
+ private ApiLookup mApiDatabase;
+
+ /**
+ * List of candidates that are potential package violations. These are
+ * recorded as candidates rather than flagged immediately such that we can
+ * filter out hits for classes that are also defined as libraries (possibly
+ * encountered later in the library traversal).
+ */
+ private List<Candidate> mCandidates;
+ /**
+ * Set of Java packages defined in the libraries; this means that if the
+ * user has added libraries in this package namespace (such as the
+ * null annotations jars) we don't flag these.
+ */
+ private Set<String> mJavaxLibraryClasses = Sets.newHashSetWithExpectedSize(64);
+
+ /** Constructs a new package check */
+ public InvalidPackageDetector() {
+ }
+
+ @Override
+ public @NonNull Speed getSpeed() {
+ return Speed.SLOW;
+ }
+
+ @Override
+ public void beforeCheckProject(@NonNull Context context) {
+ mApiDatabase = ApiLookup.get(context.getClient());
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @SuppressWarnings("rawtypes") // ASM API
+ @Override
+ public void checkClass(final @NonNull ClassContext context, @NonNull ClassNode classNode) {
+ if (!context.isFromClassLibrary() || shouldSkip(context.file)) {
+ return;
+ }
+
+ if (mApiDatabase == null) {
+ return;
+ }
+
+ if (classNode.name.startsWith(JAVAX_PKG_PREFIX)) {
+ mJavaxLibraryClasses.add(classNode.name);
+ }
+
+ List methodList = classNode.methods;
+ for (Object m : methodList) {
+ MethodNode method = (MethodNode) m;
+
+ InsnList nodes = method.instructions;
+
+ // Check return type
+ // The parameter types are already handled as local variables so we can skip
+ // right to the return type.
+ // Check types in parameter list
+ String signature = method.desc;
+ if (signature != null) {
+ int args = signature.indexOf(')');
+ if (args != -1 && signature.charAt(args + 1) == 'L') {
+ String type = signature.substring(args + 2, signature.length() - 1);
+ if (isInvalidPackage(type)) {
+ AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
+ record(context, method, first, type);
+ }
+ }
+ }
+
+ for (int i = 0, n = nodes.size(); i < n; i++) {
+ AbstractInsnNode instruction = nodes.get(i);
+ int type = instruction.getType();
+ if (type == AbstractInsnNode.METHOD_INSN) {
+ MethodInsnNode node = (MethodInsnNode) instruction;
+ String owner = node.owner;
+
+ // No need to check methods in this local class; we know they
+ // won't be an API match
+ if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && owner.equals(classNode.name)) {
+ owner = classNode.superName;
+ }
+
+ while (owner != null) {
+ if (isInvalidPackage(owner)) {
+ record(context, method, instruction, owner);
+ }
+
+ // For virtual dispatch, walk up the inheritance chain checking
+ // each inherited method
+ if (owner.startsWith("android/") //$NON-NLS-1$
+ || owner.startsWith(JAVA_PKG_PREFIX)
+ || owner.startsWith(JAVAX_PKG_PREFIX)) {
+ owner = null;
+ } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
+ owner = context.getDriver().getSuperClass(owner);
+ } else if (node.getOpcode() == Opcodes.INVOKESTATIC) {
+ // Inherit through static classes as well
+ owner = context.getDriver().getSuperClass(owner);
+ } else {
+ owner = null;
+ }
+ }
+ } else if (type == AbstractInsnNode.FIELD_INSN) {
+ FieldInsnNode node = (FieldInsnNode) instruction;
+ String owner = node.owner;
+ if (isInvalidPackage(owner)) {
+ record(context, method, instruction, owner);
+ }
+ } else if (type == AbstractInsnNode.LDC_INSN) {
+ LdcInsnNode node = (LdcInsnNode) instruction;
+ if (node.cst instanceof Type) {
+ Type t = (Type) node.cst;
+ String className = t.getInternalName();
+ if (isInvalidPackage(className)) {
+ record(context, method, instruction, className);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isInvalidPackage(String owner) {
+ if (owner.startsWith(JAVA_PKG_PREFIX)
+ || owner.startsWith(JAVAX_PKG_PREFIX)) {
+ return !mApiDatabase.isValidJavaPackage(owner);
+ }
+
+ return false;
+ }
+
+ private void record(ClassContext context, MethodNode method,
+ AbstractInsnNode instruction, String owner) {
+ if (owner.indexOf('$') != -1) {
+ // Don't report inner classes too; there will pretty much always be an outer class
+ // reference as well
+ return;
+ }
+
+ if (mCandidates == null) {
+ mCandidates = Lists.newArrayList();
+ }
+ mCandidates.add(new Candidate(owner, context.getClassNode().name, context.getJarFile()));
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mCandidates == null) {
+ return;
+ }
+
+ for (Candidate candidate : mCandidates) {
+ String type = candidate.mClass;
+ if (mJavaxLibraryClasses.contains(type)) {
+ continue;
+ }
+ File jarFile = candidate.mJarFile;
+ String referencedIn = candidate.mReferencedIn;
+
+ Location location = Location.create(jarFile);
+ Object pkg = getPackageName(type);
+ String message = String.format(
+ "Invalid package reference in library; not included in Android: %1$s. " +
+ "Referenced from %2$s.", pkg, ClassContext.getFqcn(referencedIn));
+ context.report(ISSUE, location, message, null);
+ }
+ }
+
+ private Object getPackageName(String owner) {
+ String pkg = owner;
+ int index = pkg.lastIndexOf('/');
+ if (index != -1) {
+ pkg = pkg.substring(0, index);
+ }
+
+ return ClassContext.getFqcn(pkg);
+ }
+
+ private boolean shouldSkip(File file) {
+ // No need to do work on this library, which is included in pretty much all new ADT
+ // projects
+ if (file.getPath().endsWith("android-support-v4.jar")) { //$NON-NLS-1$
+ return true;
+ }
+
+ return false;
+ }
+
+ private static class Candidate {
+ private final String mReferencedIn;
+ private final File mJarFile;
+ private final String mClass;
+
+ public Candidate(String className, String referencedIn, File jarFile) {
+ mClass = className;
+ mReferencedIn = referencedIn;
+ mJarFile = jarFile;
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiLookupTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiLookupTest.java
index 2e55533..252ec5e 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiLookupTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiLookupTest.java
@@ -87,6 +87,16 @@ public class ApiLookupTest extends AbstractCheckTest {
"(Landroid/preference/PreferenceFragment;Landroid/preference/Preference;)"));
}
+ public void testIsValidPackage() {
+ assertTrue(mDb.isValidJavaPackage("java/lang/Integer"));
+ assertTrue(mDb.isValidJavaPackage("javax/crypto/Cipher"));
+ assertTrue(mDb.isValidJavaPackage("java/awt/font/NumericShaper"));
+
+ assertFalse(mDb.isValidJavaPackage("javax/swing/JButton"));
+ assertFalse(mDb.isValidJavaPackage("java/rmi/Naming"));
+ assertFalse(mDb.isValidJavaPackage("java/lang/instrument/Instrumentation"));
+ }
+
@Override
protected Detector getDetector() {
fail("This is not used in the ApiDatabase test");
@@ -129,6 +139,9 @@ public class ApiLookupTest extends AbstractCheckTest {
raf.close();
lookup = ApiLookup.get(new LookupTestClient());
String message = mLogBuffer.toString();
+ // NOTE: This test is incompatible with the DEBUG_FORCE_REGENERATE_BINARY and WRITE_STATS
+ // flags in the ApiLookup class, so if the test fails during development and those are
+ // set, clear them.
assertTrue(message.contains("Please delete the file and restart the IDE/lint:"));
assertTrue(message.contains(mCacheDir.getPath()));
ApiLookup.dispose();
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InvalidPackageDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
new file mode 100644
index 0000000..b634255
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InvalidPackageDetectorTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class InvalidPackageDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new InvalidPackageDetector();
+ }
+
+ public void testUnsupportedJavaLibraryCode() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=39109
+ assertEquals(
+ "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: java.awt. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
+ "libs/unsupported.jar: Error: Invalid package reference in library; not included in Android: javax.swing. Referenced from test.pkg.LibraryClass. [InvalidPackage]\n" +
+ "2 errors, 0 warnings\n",
+
+ lintProject(
+ "apicheck/minsdk14.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml",
+ "apicheck/unsupported.jar.data=>libs/unsupported.jar"
+ ));
+ }
+
+ public void testOk() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk2.xml=>AndroidManifest.xml",
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class",
+ "bytecode/GetterTest.jar.data=>libs/GetterTest.jar",
+ "bytecode/classes.jar=>libs/classes.jar"
+ ));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data
new file mode 100644
index 0000000..7cbedc1
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/unsupported.jar.data
Binary files differ