upload android base code part7

This commit is contained in:
August 2018-08-08 18:09:17 +08:00
parent 4e516ec6ed
commit 841ae54672
25229 changed files with 1709508 additions and 0 deletions

View file

@ -0,0 +1,33 @@
# Copyright (C) 2010 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.
LOCAL_PATH := $(call my-dir)
# the execution script
# ============================================================
include $(CLEAR_VARS)
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE := apkcheck
LOCAL_SRC_FILES := etc/apkcheck
LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_OUT_JAVA_LIBRARIES)/apkcheck$(COMMON_JAVA_PACKAGE_SUFFIX)
include $(BUILD_PREBUILT)
# the other stuff
# ============================================================
subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
src \
))
include $(subdirs)

View file

@ -0,0 +1,179 @@
Android APK Checker
This compares the set of classes, fields, and methods used by an Android
application against the published API. It identifies and reports the
use of any unpublished members or methods.
The public API description files live in the source tree, in
frameworks/base/api/. The tip-of-tree version is in "current.xml",
and each officially released API has a numbered file (e.g. "6.xml").
They're generated from the sources, and can take into acount javadoc
annotations like "@hide" in comments.
The dependency set for an APK can be generated with "dexdeps". It finds
all classes, fields, and methods that are referenced by classes.dex but not
defined locally. The tool can't easily tell anything about a dependency
beyond the name (e.g. whether a class is a static or non-static inner
class), so while the output from dexdeps is similar in structure to the
API XML file, it has much less detail.
==== Usage ====
% apkcheck [options] public-api.xml apk1.xml ...
Provide the public API data file of choice, and one or more XML files
generated by dexdeps. The time required to parse and manipulate the
public API XML file is generally much larger than the time required to
analyze the APK, so if you have a large set of APKs it's best to run them
through in large batches.
Options:
--help
Show options summary.
--uses-library=<lib.xml>
Load additional public API list. This is intended for APKs that
use "uses-library" directives to pull in external libraries. Since
the external libraries are not part of the public API, their use
would otherwise be flagged as illegal by apkcheck.
--ignore-package=<package-name>
Ignore errors generated by references to the named package (e.g.
"com.google.android.maps"). Warnings will be generated instead.
Useful for ignoring references to shared library content when
XML API data is not available.
--[no-]warn
Enable or disable warning messages. These are disabled by default.
--[no-]error
Enable or disable error messages. These are enabled by default. If
you disable both warnings and errors you will only see a summary.
In some cases involving generic signatures it may not be possible
to accurately reconstruct the public API. Some popular cases have
been hard-coded into the program. They can be included by specifying
"--uses-library=BUILTIN".
Example use:
% dexdeps out/target/product/sapphire/system/app/Gmail.apk > Gmail.apk.xml
% apkcheck --uses-library=BUILTIN frameworks/base/api/current.xml Gmail.apk.xml
Gmail.apk.xml: summary: 0 errors, 15 warnings
==== Limitations ====
The API XML files have some ambiguous entries and are missing important
pieces. A summary of the issues follows.
(1) Class names are not in binary form
Example:
type="android.os.Parcelable.Creator"
This could be a Creator class in the package android.os.Parcelable,
or Parcelable.Creator in the package android.os. We can guess based on
capitalization, but that's unreliable.
The API XML does specify each package in a <package> tag, so we should have
the full set of packages available. From this we can remove one element
at a time from the right until we match a known package. This will work
unless "android.os" and "android.os.Parcelable" are both valid packages.
(2) Public enums are not enumerated
Enumeration classes are included, and always have two methods ("valueOf"
and "values"). What isn't included are entries for the fields representing
the enumeration values. This makes it look like an APK is referring
to non-public fields in the class.
If apkcheck sees a reference to an unknown field, and the field's defining
class appears to be an Enum (the superclass is java.lang.Enum), we emit
a warning instead of an error.
(3) Public annotation methods are not listed
Annotation classes have trivial entries that show only the class name
and "implements java.lang.annotation.Annotation". It is not possible
to verify that a method call on an annotation is valid.
If apkcheck sees a method call to an unknown method, and the class appears
to be an annotation (extends Object, implements Annotation, defines no
fields or methods), we emit a warning instead of an error.
(4) Covariant return types
Suppose a class defines a method "public Foo gimmeFoo()". Any subclass
that overrides that method must also return Foo, so it would seem that
there's no need to emit a method entry for gimmeFoo() in the subclasses.
However, it's possible to override gimmeFoo with "public MegaFoo
gimmeFoo()" so long as MegaFoo is an instance of Foo. In that case it
is necessary to emit a new method entry, but the public API XML generator
does not.
If apkcheck can't find an exact match for a method reference, but can
find a method that matches on everything but the return type, it will
emit a warning instead of an error. (We could be more thorough and try
to verify that the return types are related, but that's more trouble than
it's worth.)
(5) Generic signatures
When generic signatures are used, the public API file will contain
entries like these:
<parameter name="key" type="K">
<parameter name="others" type="E...">
<parameter name="map" type="java.util.Map&lt;? extends K, ? extends V&gt;">
The generic types are generally indistinguishable from classes in the
default package (i.e. that have no package name). In most cases they're
a single letter, so apkcheck includes a kluge that converts single-letter
class names to java.lang.Object.
This often works, but falls apart in a few cases. For example:
public <T extends Parcelable> T getParcelableExtra(String name) {
return mExtras == null ? null : mExtras.<T>getParcelable(name);
}
This is emitted as:
<method name="getParcelableExtra" return="T">
which gets converted to java.lang.Object. Unfortunately the APK wants
a method with a more specific return type (android.os.Parcelable), so
the lookup fails.
There is no way to recover the actual type, because the generic signature
details are not present in the XML. This particular case will be handled
as a covariant return type. When the generic type is in the parameter
list, though, this isn't handled so easily.
These cases are relatively few, so they were handled by baking the
signatures into the code (--uses-library=BUILTIN). (At some point it
may be worthwhile to try a little harder here.)
(6) Use of opaque non-public types
Some classes are not meant for public consumption, but are still referred
to by application code. For example, an opaque type might be passed to
the app as a cookie.
Another example is the Dalvik annotation classes, like
dalvik.annotation.InnerClass. These are emitted by "dx", and referenced
from the DEX file, but not intended to be used by application code.
If an APK refers to a non-public class, but doesn't access any fields
or methods, a warning is emitted instead of an error.

View file

@ -0,0 +1,46 @@
#!/bin/bash
#
# Copyright (C) 2010 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.
# Set up prog to be the path of this script, including following symlinks,
# and set up progdir to be the fully-qualified pathname of its directory.
prog="$0"
while [ -h "${prog}" ]; do
newProg=`/bin/ls -ld "${prog}"`
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
if expr "x${newProg}" : 'x/' >/dev/null; then
prog="${newProg}"
else
progdir=`dirname "${prog}"`
prog="${progdir}/${newProg}"
fi
done
oldwd=`pwd`
progdir=`dirname "${prog}"`
cd "${progdir}"
progdir=`pwd`
prog="${progdir}"/`basename "${prog}"`
cd "${oldwd}"
libdir=`dirname $progdir`/framework
javaOpts=""
while expr "x$1" : 'x-J' >/dev/null; do
opt=`expr "$1" : '-J\(.*\)'`
javaOpts="${javaOpts} -${opt}"
shift
done
exec java $javaOpts -jar $libdir/apkcheck.jar "$@"

View file

@ -0,0 +1,2 @@
Manifest-Version: 1.0
Main-Class: com.android.apkcheck.ApkCheck

View file

@ -0,0 +1,28 @@
# Copyright (C) 2010 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.
LOCAL_PATH := $(call my-dir)
# apkcheck java library
# ============================================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAR_MANIFEST := ../etc/manifest.txt
LOCAL_MODULE:= apkcheck
include $(BUILD_HOST_JAVA_LIBRARY)

View file

@ -0,0 +1,147 @@
/*
* Copyright (C) 2010 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.apkcheck;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
/**
* Provides implementation for SAX parser.
*/
class ApiDescrHandler extends DefaultHandler {
/*
* Uber-container.
*/
private ApiList mApiList;
/*
* Temporary objects, used as containers while we accumulate the
* innards.
*/
private PackageInfo mCurrentPackage = null;
private ClassInfo mCurrentClass = null;
private MethodInfo mCurrentMethod = null;
/**
* Constructs an ApiDescrHandler.
*
* @param fileName Source file name, used for debugging.
*/
public ApiDescrHandler(ApiList apiList) {
mApiList = apiList;
}
/**
* Returns the ApiList in its current state. Generally only
* makes sense to call here after parsing is completed.
*/
public ApiList getApiList() {
return mApiList;
}
/**
* Processes start tags. If the file is malformed we will likely
* NPE, but this is captured by the caller.
*
* We currently assume that packages and classes only appear once,
* so all classes associated with a package are wrapped in a singular
* instance of &lt;package&gt;. We may want to remove this assumption
* by attempting to find an existing package/class with the same name.
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) {
if (qName.equals("package")) {
/* top-most element */
mCurrentPackage = mApiList.getOrCreatePackage(
attributes.getValue("name"));
} else if (qName.equals("class") || qName.equals("interface")) {
/* get class, gather fields/methods and interfaces */
mCurrentClass = mCurrentPackage.getOrCreateClass(
attributes.getValue("name"),
attributes.getValue("extends"),
attributes.getValue("static"));
} else if (qName.equals("implements")) {
/* add name of interface to current class */
mCurrentClass.addInterface(attributes.getValue("name"));
} else if (qName.equals("method")) {
/* hold object while we gather parameters */
mCurrentMethod = new MethodInfo(attributes.getValue("name"),
attributes.getValue("return"));
} else if (qName.equals("constructor")) {
/* like "method", but has no name or return type */
mCurrentMethod = new MethodInfo("<init>", "void");
/*
* If this is a non-static inner class, we want to add the
* "hidden" outer class parameter as the first parameter.
* We can tell if it's an inner class because the class name
* will include a '$' (it has been normalized already).
*/
String staticClass = mCurrentClass.getStatic();
if (staticClass == null) {
/*
* We're parsing an APK file, which means we can't know
* if the class we're referencing is static or not. We
* also already have the "secret" first parameter
* represented in the method parameter list, so we don't
* need to insert it here.
*/
} else if ("false".equals(staticClass)) {
String className = mCurrentClass.getName();
int dollarIndex = className.indexOf('$');
if (dollarIndex >= 0) {
String outerClass = className.substring(0, dollarIndex);
//System.out.println("--- inserting " +
// mCurrentPackage.getName() + "." + outerClass +
// " into constructor for " + className);
mCurrentMethod.addParameter(mCurrentPackage.getName() +
"." + outerClass);
}
}
} else if (qName.equals("field")) {
/* add to current class */
FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
attributes.getValue("type"));
mCurrentClass.addField(fInfo);
} else if (qName.equals("parameter")) {
/* add to current method */
mCurrentMethod.addParameter(attributes.getValue("type"));
}
}
/**
* Processes end tags. Generally these add the under-construction
* item to the appropriate container.
*/
@Override
public void endElement(String uri, String localName, String qName) {
if (qName.equals("method") || qName.equals("constructor")) {
/* add method to class */
mCurrentClass.addMethod(mCurrentMethod);
mCurrentMethod = null;
} else if (qName.equals("class") || qName.equals("interface")) {
mCurrentClass = null;
} else if (qName.equals("package")) {
mCurrentPackage = null;
}
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.util.HashMap;
import java.util.Iterator;
/**
* Holds a list of API members, including classes, fields, and methods.
*/
public class ApiList {
private HashMap<String,PackageInfo> mPackageList;
private String mDebugString;
private int mWarnings, mErrors;
/**
* Constructs an ApiList.
*
* @param debugString Identification string useful for debugging.
*/
public ApiList(String debugString) {
mPackageList = new HashMap<String,PackageInfo>();
mDebugString = debugString;
}
/**
* Returns the source filename. Useful for debug messages only.
*/
public String getDebugString() {
return mDebugString;
}
/**
* Increment the number of warnings associated with this API list.
*/
public void incrWarnings() {
mWarnings++;
}
/**
* Increment the errors of warnings associated with this API list.
*/
public void incrErrors() {
mErrors++;
}
/**
* Returns the number of warnings associated with this API list.
*/
public int getWarningCount() {
return mWarnings;
}
/**
* Returns the number of errors associated with this API list.
*/
public int getErrorCount() {
return mErrors;
}
/**
* Retrieves the named package.
*
* @return the package, or null if no match was found
*/
public PackageInfo getPackage(String name) {
return mPackageList.get(name);
}
/**
* Retrieves the named package, creating it if it doesn't already
* exist.
*/
public PackageInfo getOrCreatePackage(String name) {
PackageInfo pkgInfo = mPackageList.get(name);
if (pkgInfo == null) {
pkgInfo = new PackageInfo(name);
mPackageList.put(name, pkgInfo);
}
return pkgInfo;
}
/**
* Returns an iterator for the set of known packages.
*/
public Iterator<PackageInfo> getPackageIterator() {
return mPackageList.values().iterator();
}
}

View file

@ -0,0 +1,431 @@
/*
* Copyright (C) 2010 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.apkcheck;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
/**
* Checks an APK's dependencies against the published API specification.
*
* We need to read two XML files (spec and APK) and perform some operations
* on the elements. The file formats are similar but not identical, so
* we distill it down to common elements.
*
* We may also want to read some additional API lists representing
* libraries that would be included with a "uses-library" directive.
*
* For performance we want to allow processing of multiple APKs so
* we don't have to re-parse the spec file each time.
*/
public class ApkCheck {
/* keep track of current APK file name, for error messages */
private static ApiList sCurrentApk;
/* show warnings? */
private static boolean sShowWarnings = false;
/* show errors? */
private static boolean sShowErrors = true;
/* names of packages we're allowed to ignore */
private static HashSet<String> sIgnorablePackages = new HashSet<String>();
/**
* Program entry point.
*/
public static void main(String[] args) {
ApiList apiDescr = new ApiList("public-api");
if (args.length < 2) {
usage();
return;
}
/* process args */
int idx;
for (idx = 0; idx < args.length; idx++) {
if (args[idx].equals("--help")) {
usage();
return;
} else if (args[idx].startsWith("--uses-library=")) {
String libName = args[idx].substring(args[idx].indexOf('=')+1);
if ("BUILTIN".equals(libName)) {
Reader reader = Builtin.getReader();
if (!parseXml(apiDescr, reader, "BUILTIN"))
return;
} else {
if (!parseApiDescr(apiDescr, libName))
return;
}
} else if (args[idx].startsWith("--ignore-package=")) {
String pkgName = args[idx].substring(args[idx].indexOf('=')+1);
sIgnorablePackages.add(pkgName);
} else if (args[idx].equals("--warn")) {
sShowWarnings = true;
} else if (args[idx].equals("--no-warn")) {
sShowWarnings = false;
} else if (args[idx].equals("--error")) {
sShowErrors = true;
} else if (args[idx].equals("--no-error")) {
sShowErrors = false;
} else if (args[idx].startsWith("--")) {
if (args[idx].equals("--")) {
// remainder are filenames, even if they start with "--"
idx++;
break;
} else {
// unknown option specified
System.err.println("ERROR: unknown option " +
args[idx] + " (use \"--help\" for usage info)");
return;
}
} else {
break;
}
}
if (idx > args.length - 2) {
usage();
return;
}
/* parse base API description */
if (!parseApiDescr(apiDescr, args[idx++]))
return;
/* "flatten" superclasses and interfaces */
sCurrentApk = apiDescr;
flattenInherited(apiDescr);
/* walk through list of libs we want to scan */
for ( ; idx < args.length; idx++) {
ApiList apkDescr = new ApiList(args[idx]);
sCurrentApk = apkDescr;
boolean success = parseApiDescr(apkDescr, args[idx]);
if (!success) {
if (idx < args.length-1)
System.err.println("Skipping...");
continue;
}
check(apiDescr, apkDescr);
System.out.println(args[idx] + ": summary: " +
apkDescr.getErrorCount() + " errors, " +
apkDescr.getWarningCount() + " warnings\n");
}
}
/**
* Prints usage statement.
*/
static void usage() {
System.err.println("Android APK checker v1.0");
System.err.println("Copyright (C) 2010 The Android Open Source Project\n");
System.err.println("Usage: apkcheck [options] public-api.xml apk1.xml ...\n");
System.err.println("Options:");
System.err.println(" --help show this message");
System.err.println(" --uses-library=lib.xml load additional public API list");
System.err.println(" --ignore-package=pkg don't show errors for references to this package");
System.err.println(" --[no-]warn enable or disable display of warnings");
System.err.println(" --[no-]error enable or disable display of errors");
}
/**
* Opens the file and passes it to parseXml.
*
* TODO: allow '-' as an alias for stdin?
*/
static boolean parseApiDescr(ApiList apiList, String fileName) {
boolean result = false;
try {
FileReader fileReader = new FileReader(fileName);
result = parseXml(apiList, fileReader, fileName);
fileReader.close();
} catch (IOException ioe) {
System.err.println("Error opening " + fileName);
}
return result;
}
/**
* Parses an XML file holding an API description.
*
* @param fileReader Data source.
* @param apiList Container to add stuff to.
* @param fileName Input file name, only used for debug messages.
*/
static boolean parseXml(ApiList apiList, Reader reader,
String fileName) {
//System.out.println("--- parsing " + fileName);
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
ApiDescrHandler handler = new ApiDescrHandler(apiList);
xmlReader.setContentHandler(handler);
xmlReader.setErrorHandler(handler);
xmlReader.parse(new InputSource(reader));
//System.out.println("--- parsing complete");
//dumpApi(apiList);
return true;
} catch (SAXParseException ex) {
System.err.println("Error parsing " + fileName + " line " +
ex.getLineNumber() + ": " + ex.getMessage());
} catch (Exception ex) {
System.err.println("Error while reading " + fileName + ": " +
ex.getMessage());
ex.printStackTrace();
}
// failed
return false;
}
/**
* Expands lists of fields and methods to recursively include superclass
* and interface entries.
*
* The API description files have entries for every method a class
* declares, even if it's present in the superclass (e.g. toString()).
* Removal of one of these methods doesn't constitute an API change,
* though, so if we don't find a method in a class we need to hunt
* through its superclasses.
*
* We can walk up the hierarchy while analyzing the target APK,
* or we can "flatten" the methods declared by the superclasses and
* interfaces before we begin the analysis. Expanding up front can be
* beneficial if we're analyzing lots of APKs in one go, but detrimental
* to startup time if we just want to look at one small APK.
*
* It also means filling the field/method hash tables with lots of
* entries that never get used, possibly worsening the hash table
* hit rate.
*
* We only need to do this for the public API list. The dexdeps output
* doesn't have this sort of information anyway.
*/
static void flattenInherited(ApiList pubList) {
Iterator<PackageInfo> pkgIter = pubList.getPackageIterator();
while (pkgIter.hasNext()) {
PackageInfo pubPkgInfo = pkgIter.next();
Iterator<ClassInfo> classIter = pubPkgInfo.getClassIterator();
while (classIter.hasNext()) {
ClassInfo pubClassInfo = classIter.next();
pubClassInfo.flattenClass(pubList);
}
}
}
/**
* Checks the APK against the public API.
*
* Run through and find the mismatches.
*
* @return true if all is well
*/
static boolean check(ApiList pubList, ApiList apkDescr) {
Iterator<PackageInfo> pkgIter = apkDescr.getPackageIterator();
while (pkgIter.hasNext()) {
PackageInfo apkPkgInfo = pkgIter.next();
PackageInfo pubPkgInfo = pubList.getPackage(apkPkgInfo.getName());
boolean badPackage = false;
if (pubPkgInfo == null) {
// "illegal package" not a tremendously useful message
//apkError("Illegal package ref: " + apkPkgInfo.getName());
badPackage = true;
}
Iterator<ClassInfo> classIter = apkPkgInfo.getClassIterator();
while (classIter.hasNext()) {
ClassInfo apkClassInfo = classIter.next();
if (badPackage) {
/*
* The package is not present in the public API file,
* but simply saying "bad package" isn't all that
* useful, so we emit the names of each of the classes.
*/
if (isIgnorable(apkPkgInfo)) {
apkWarning("Ignoring class ref: " +
apkPkgInfo.getName() + "." + apkClassInfo.getName());
} else {
apkError("Illegal class ref: " +
apkPkgInfo.getName() + "." + apkClassInfo.getName());
}
} else {
checkClass(pubPkgInfo, apkClassInfo);
}
}
}
return true;
}
/**
* Checks the class against the public API. We check the class
* itself and then any fields and methods.
*/
static boolean checkClass(PackageInfo pubPkgInfo, ClassInfo classInfo) {
ClassInfo pubClassInfo = pubPkgInfo.getClass(classInfo.getName());
if (pubClassInfo == null) {
if (isIgnorable(pubPkgInfo)) {
apkWarning("Ignoring class ref: " +
pubPkgInfo.getName() + "." + classInfo.getName());
} else if (classInfo.hasNoFieldMethod()) {
apkWarning("Hidden class referenced: " +
pubPkgInfo.getName() + "." + classInfo.getName());
} else {
apkError("Illegal class ref: " +
pubPkgInfo.getName() + "." + classInfo.getName());
// could list specific fields/methods used
}
return false;
}
/*
* Check the contents of classInfo against pubClassInfo.
*/
Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
while (fieldIter.hasNext()) {
FieldInfo apkFieldInfo = fieldIter.next();
String nameAndType = apkFieldInfo.getNameAndType();
FieldInfo pubFieldInfo = pubClassInfo.getField(nameAndType);
if (pubFieldInfo == null) {
if (pubClassInfo.isEnum()) {
apkWarning("Enum field ref: " + pubPkgInfo.getName() +
"." + classInfo.getName() + "." + nameAndType);
} else {
apkError("Illegal field ref: " + pubPkgInfo.getName() +
"." + classInfo.getName() + "." + nameAndType);
}
}
}
Iterator<MethodInfo> methodIter = classInfo.getMethodIterator();
while (methodIter.hasNext()) {
MethodInfo apkMethodInfo = methodIter.next();
String nameAndDescr = apkMethodInfo.getNameAndDescriptor();
MethodInfo pubMethodInfo = pubClassInfo.getMethod(nameAndDescr);
if (pubMethodInfo == null) {
pubMethodInfo = pubClassInfo.getMethodIgnoringReturn(nameAndDescr);
if (pubMethodInfo == null) {
if (pubClassInfo.isAnnotation()) {
apkWarning("Annotation method ref: " +
pubPkgInfo.getName() + "." + classInfo.getName() +
"." + nameAndDescr);
} else {
apkError("Illegal method ref: " + pubPkgInfo.getName() +
"." + classInfo.getName() + "." + nameAndDescr);
}
} else {
apkWarning("Possibly covariant method ref: " +
pubPkgInfo.getName() + "." + classInfo.getName() +
"." + nameAndDescr);
}
}
}
return true;
}
/**
* Returns true if the package is in the "ignored" list.
*/
static boolean isIgnorable(PackageInfo pkgInfo) {
return sIgnorablePackages.contains(pkgInfo.getName());
}
/**
* Prints a warning message about an APK problem.
*/
public static void apkWarning(String msg) {
if (sShowWarnings) {
System.out.println("(warn) " + sCurrentApk.getDebugString() +
": " + msg);
}
sCurrentApk.incrWarnings();
}
/**
* Prints an error message about an APK problem.
*/
public static void apkError(String msg) {
if (sShowErrors) {
System.out.println(sCurrentApk.getDebugString() + ": " + msg);
}
sCurrentApk.incrErrors();
}
/**
* Recursively dumps the contents of the API. Sort order is not
* specified.
*/
private static void dumpApi(ApiList apiList) {
Iterator<PackageInfo> iter = apiList.getPackageIterator();
while (iter.hasNext()) {
PackageInfo pkgInfo = iter.next();
dumpPackage(pkgInfo);
}
}
private static void dumpPackage(PackageInfo pkgInfo) {
Iterator<ClassInfo> iter = pkgInfo.getClassIterator();
System.out.println("PACKAGE " + pkgInfo.getName());
while (iter.hasNext()) {
ClassInfo classInfo = iter.next();
dumpClass(classInfo);
}
}
private static void dumpClass(ClassInfo classInfo) {
System.out.println(" CLASS " + classInfo.getName());
Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
while (fieldIter.hasNext()) {
FieldInfo fieldInfo = fieldIter.next();
dumpField(fieldInfo);
}
Iterator<MethodInfo> methIter = classInfo.getMethodIterator();
while (methIter.hasNext()) {
MethodInfo methInfo = methIter.next();
dumpMethod(methInfo);
}
}
private static void dumpMethod(MethodInfo methInfo) {
System.out.println(" METHOD " + methInfo.getNameAndDescriptor());
}
private static void dumpField(FieldInfo fieldInfo) {
System.out.println(" FIELD " + fieldInfo.getNameAndType());
}
}

View file

@ -0,0 +1,111 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.io.StringReader;
/**
* Class containing "built-in" API description entries.
*
* There are some bugs in the API description file that we can't work around
* (notably some ambiguity with generic types). The easiest way to cope
* is to supply the correct definitions in an add-on file. Rather than
* cart around an extra file, we bake them in here.
*/
public class Builtin {
private Builtin() {}
private static final String BUILTIN =
"<api>\n" +
" <package name=\"java.util\">\n" +
" <class name=\"EnumSet\"\n" +
" extends=\"java.util.AbstractSet\">\n" +
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
" <parameter name=\"e\" type=\"java.lang.Enum\"/>\n" +
" </method>\n" +
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
" </method>\n" +
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
" </method>\n" +
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
" </method>\n" +
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
" <parameter name=\"e5\" type=\"java.lang.Enum\"/>\n" +
" </method>\n" +
" </class>\n" +
" </package>\n" +
" <package name=\"android.os\">\n" +
" <class name=\"RemoteCallbackList\"\n" +
" extends=\"java.lang.Object\">\n" +
" <method name=\"register\" return=\"boolean\">\n" +
" <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
" </method>\n" +
" <method name=\"unregister\" return=\"boolean\">\n" +
" <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
" </method>\n" +
" </class>\n" +
" <class name=\"AsyncTask\"\n" +
" extends=\"java.lang.Object\">\n" +
" <method name=\"onPostExecute\" return=\"void\">\n" +
" <parameter name=\"result\" type=\"java.lang.Object\"/>\n" +
" </method>\n" +
" <method name=\"onProgressUpdate\" return=\"void\">\n" +
" <parameter name=\"values\" type=\"java.lang.Object[]\"/>\n" +
" </method>\n" +
" <method name=\"execute\" return=\"android.os.AsyncTask\">\n" +
" <parameter name=\"params\" type=\"java.lang.Object[]\"/>\n" +
" </method>\n" +
" </class>\n" +
" </package>\n" +
" <package name=\"android.widget\">\n" +
" <class name=\"AutoCompleteTextView\"\n" +
" extends=\"android.widget.EditText\">\n" +
" <method name=\"setAdapter\" return=\"void\">\n" +
" <parameter name=\"adapter\" type=\"android.widget.ListAdapter\"/>\n" +
" </method>\n" +
" </class>\n" +
" </package>\n" +
"</api>\n"
;
/**
* Returns the built-in definition "file".
*/
public static StringReader getReader() {
return new StringReader(BUILTIN);
}
}

View file

@ -0,0 +1,358 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Container representing a class or interface with fields and methods.
*/
public class ClassInfo {
private String mName;
// methods are hashed on name:descriptor
private HashMap<String,MethodInfo> mMethodList;
// fields are hashed on name:type
private HashMap<String,FieldInfo> mFieldList;
private String mSuperclassName;
// is this a static inner class?
private String mIsStatic;
// holds the name of the superclass and all declared interfaces
private ArrayList<String> mSuperNames;
// is this an enumerated type?
private boolean mIsEnum;
// is this an annotation type?
private boolean mIsAnnotation;
private boolean mFlattening = false;
private boolean mFlattened = false;
/**
* Constructs a new ClassInfo with the provided class name.
*
* @param className Binary class name without the package name,
* e.g. "AlertDialog$Builder".
* @param superclassName Fully-qualified binary or non-binary superclass
* name (e.g. "java.lang.Enum").
* @param isStatic Class static attribute, may be "true", "false", or null.
*/
public ClassInfo(String className, String superclassName, String isStatic) {
mName = className;
mMethodList = new HashMap<String,MethodInfo>();
mFieldList = new HashMap<String,FieldInfo>();
mSuperNames = new ArrayList<String>();
mIsStatic = isStatic;
/*
* Record the superclass name, and add it to the interface list
* since we'll need to do the same "flattening" work on it.
*
* Interfaces and java.lang.Object have a null value.
*/
if (superclassName != null) {
mSuperclassName = superclassName;
mSuperNames.add(superclassName);
}
}
/**
* Returns the name of the class.
*/
public String getName() {
return mName;
}
/**
* Returns the name of the superclass.
*/
public String getSuperclassName() {
return mSuperclassName;
}
/**
* Returns the "static" attribute.
*
* This is actually tri-state:
* "true" means it is static
* "false" means it's not static
* null means it's unknown
*
* The "unknown" state is associated with the APK input, while the
* known states are from the public API definition.
*
* This relates to the handling of the "secret" first parameter to
* constructors of non-static inner classes.
*/
public String getStatic() {
return mIsStatic;
}
/**
* Returns whether or not this class is an enumerated type.
*/
public boolean isEnum() {
assert mFlattened;
return mIsEnum;
}
/**
* Returns whether or not this class is an annotation type.
*/
public boolean isAnnotation() {
assert mFlattened;
return mIsAnnotation;
}
/**
* Adds a field to the list.
*/
public void addField(FieldInfo fieldInfo) {
mFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
}
/**
* Retrives a field from the list.
*
* @param nameAndType fieldName:type
*/
public FieldInfo getField(String nameAndType) {
return mFieldList.get(nameAndType);
}
/**
* Returns an iterator over all known fields.
*/
public Iterator<FieldInfo> getFieldIterator() {
return mFieldList.values().iterator();
}
/**
* Adds a method to the list.
*/
public void addMethod(MethodInfo methInfo) {
mMethodList.put(methInfo.getNameAndDescriptor(), methInfo);
}
/**
* Returns an iterator over all known methods.
*/
public Iterator<MethodInfo> getMethodIterator() {
return mMethodList.values().iterator();
}
/**
* Retrieves a method from the list.
*
* @param nameAndDescr methodName:descriptor
*/
public MethodInfo getMethod(String nameAndDescr) {
return mMethodList.get(nameAndDescr);
}
/**
* Retrieves a method from the list, matching on the part of the key
* before the return type.
*
* The API file doesn't include an entry for a method that overrides
* a method in the superclass. Ordinarily this is a good thing, but
* if the override uses a covariant return type then the reference
* to it in the APK won't match.
*
* @param nameAndDescr methodName:descriptor
*/
public MethodInfo getMethodIgnoringReturn(String nameAndDescr) {
String shortKey = nameAndDescr.substring(0, nameAndDescr.indexOf(')')+1);
Iterator<MethodInfo> iter = getMethodIterator();
while (iter.hasNext()) {
MethodInfo methInfo = iter.next();
String nad = methInfo.getNameAndDescriptor();
if (nad.startsWith(shortKey))
return methInfo;
}
return null;
}
/**
* Returns true if the method and field lists are empty.
*/
public boolean hasNoFieldMethod() {
return mMethodList.size() == 0 && mFieldList.size() == 0;
}
/**
* Adds an interface to the list of classes implemented by this class.
*/
public void addInterface(String interfaceName) {
mSuperNames.add(interfaceName);
}
/**
* Flattens a class. This involves copying all methods and fields
* declared by the superclass and interfaces (and, recursively, their
* superclasses and interfaces) into the local structure.
*
* The public API file must be fully parsed before calling here.
*
* This also detects if we're an Enum or Annotation.
*/
public void flattenClass(ApiList apiList) {
if (mFlattened)
return;
/*
* Recursive class definitions aren't allowed in Java code, but
* there could be one in the API definition file.
*/
if (mFlattening) {
throw new RuntimeException("Recursive invoke; current class is "
+ mName);
}
mFlattening = true;
/*
* Normalize the ambiguous types. This requires regenerating the
* field and method lists, because the signature is used as the
* hash table key.
*/
normalizeTypes(apiList);
/*
* Figure out if this class is an enumerated type.
*/
mIsEnum = "java.lang.Enum".equals(mSuperclassName);
/*
* Figure out if this class is an annotation type. We expect it
* to extend Object, implement java.lang.annotation.Annotation,
* and declare no fields or methods. (If the API XML file is
* fixed, it will declare methods; but at that point having special
* handling for annotations will be unnecessary.)
*/
if ("java.lang.Object".equals(mSuperclassName) &&
mSuperNames.contains("java.lang.annotation.Annotation") &&
hasNoFieldMethod())
{
mIsAnnotation = true;
}
/*
* Flatten our superclass and interfaces.
*/
for (int i = 0; i < mSuperNames.size(); i++) {
/*
* The contents of mSuperNames are in an ambiguous form.
* Normalize it to binary form before working with it.
*/
String interfaceName = TypeUtils.ambiguousToBinaryName(mSuperNames.get(i),
apiList);
ClassInfo classInfo = lookupClass(interfaceName, apiList);
if (classInfo == null) {
ApkCheck.apkWarning("Class " + interfaceName +
" not found (super of " + mName + ")");
continue;
}
/* flatten it */
classInfo.flattenClass(apiList);
/* copy everything from it in here */
mergeFrom(classInfo);
}
mFlattened = true;
}
/**
* Normalizes the type names used in field and method descriptors.
*
* We call the field/method normalization function, which updates how
* it thinks of itself (and may be called multiple times from different
* classes). We then have to re-add it to the hash map because the
* key may have changed. (We're using an iterator, so we create a
* new hashmap and replace the old.)
*/
private void normalizeTypes(ApiList apiList) {
Iterator<String> keyIter;
HashMap<String,FieldInfo> tmpFieldList = new HashMap<String,FieldInfo>();
keyIter = mFieldList.keySet().iterator();
while (keyIter.hasNext()) {
String key = keyIter.next();
FieldInfo fieldInfo = mFieldList.get(key);
fieldInfo.normalizeType(apiList);
tmpFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
}
mFieldList = tmpFieldList;
HashMap<String,MethodInfo> tmpMethodList = new HashMap<String,MethodInfo>();
keyIter = mMethodList.keySet().iterator();
while (keyIter.hasNext()) {
String key = keyIter.next();
MethodInfo methodInfo = mMethodList.get(key);
methodInfo.normalizeTypes(apiList);
tmpMethodList.put(methodInfo.getNameAndDescriptor(), methodInfo);
}
mMethodList = tmpMethodList;
}
/**
* Merges the fields and methods from "otherClass" into this class.
*
* Redundant entries will be merged. We don't specify who the winner
* will be.
*/
private void mergeFrom(ClassInfo otherClass) {
/*System.out.println("merging into " + getName() + ": fields=" +
mFieldList.size() + "/" + otherClass.mFieldList.size() +
", methods=" +
mMethodList.size() + "/" + otherClass.mMethodList.size());*/
mFieldList.putAll(otherClass.mFieldList);
mMethodList.putAll(otherClass.mMethodList);
/*System.out.println(" now fields=" + mFieldList.size() +
", methods=" + mMethodList.size());*/
}
/**
* Finds the named class in the ApiList.
*
* @param className Fully-qualified dot notation (e.g. "java.lang.String")
* @param apiList The hierarchy to search in.
* @return The class or null if not found.
*/
private static ClassInfo lookupClass(String fullname, ApiList apiList) {
String packageName = TypeUtils.packageNameOnly(fullname);
String className = TypeUtils.classNameOnly(fullname);
PackageInfo pkg = apiList.getPackage(packageName);
if (pkg == null)
return null;
return pkg.getClass(className);
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (C) 2010 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.apkcheck;
/**
* Container representing a method with parameters.
*/
public class FieldInfo {
private String mName;
private String mType;
private String mNameAndType;
private boolean mTypeNormalized;
/**
* Constructs a FieldInfo.
*
* @param name Field name.
* @param type Fully-qualified binary or non-binary type name.
*/
public FieldInfo(String name, String type) {
mName = name;
mType = type;
}
/**
* Returns the combined name and type. This value is used as a hash
* table key.
*/
public String getNameAndType() {
if (mNameAndType == null)
mNameAndType = mName + ":" + TypeUtils.typeToDescriptor(mType);
return mNameAndType;
}
/**
* Normalize the type used in fields.
*/
public void normalizeType(ApiList apiList) {
if (!mTypeNormalized) {
String type = TypeUtils.ambiguousToBinaryName(mType, apiList);
if (!type.equals(mType)) {
/* name changed, force regen on name+type */
mType = type;
mNameAndType = null;
}
mTypeNormalized = true;
}
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Container representing a method with parameters.
*/
public class MethodInfo {
private String mName;
private String mReturn;
private String mNameAndDescriptor;
private ArrayList<String> mParameters;
private boolean mParametersNormalized;
/**
* Constructs MethodInfo. Tuck the method return type away for
* later construction of the signature.
*/
public MethodInfo(String name, String returnType) {
mName = name;
mReturn = returnType;
mParameters = new ArrayList<String>();
}
/**
* Returns the method signature. This is generated when needed.
*/
public String getNameAndDescriptor() {
if (mNameAndDescriptor == null) {
StringBuilder newSig = new StringBuilder(mName);
newSig.append(":(");
for (int i = 0; i < mParameters.size(); i++) {
String humanType = mParameters.get(i);
String sigType = TypeUtils.typeToDescriptor(humanType);
newSig.append(sigType);
}
newSig.append(")");
newSig.append(TypeUtils.typeToDescriptor(mReturn));
mNameAndDescriptor = newSig.toString();
}
return mNameAndDescriptor;
}
/**
* Adds a parameter to the method. The "type" is a primitive or
* object type, formatted in human-centric form. For now we just
* store it.
*/
public void addParameter(String type) {
mParameters.add(type);
if (mNameAndDescriptor != null) {
System.err.println("WARNING: late add of params to method");
mNameAndDescriptor = null; // force regen
}
}
/**
* Normalizes the types in parameter lists to unambiguous binary form.
*
* The public API file must be fully parsed before calling here,
* because we need the full set of package names.
*/
public void normalizeTypes(ApiList apiList) {
if (!mParametersNormalized) {
mReturn = TypeUtils.ambiguousToBinaryName(mReturn, apiList);
for (int i = 0; i < mParameters.size(); i++) {
String fixed = TypeUtils.ambiguousToBinaryName(mParameters.get(i),
apiList);
mParameters.set(i, fixed);
}
mNameAndDescriptor = null; // force regen
mParametersNormalized = true;
}
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.util.HashMap;
import java.util.Iterator;
/**
* Container representing a package of classes and interfaces.
*/
public class PackageInfo {
private String mName;
private HashMap<String,ClassInfo> mClassList;
public PackageInfo(String name) {
mName = name;
mClassList = new HashMap<String,ClassInfo>();
}
public String getName() {
return mName;
}
/**
* Retrieves the named class.
*
* @return the package, or null if no match was found
*/
public ClassInfo getClass(String name) {
return mClassList.get(name);
}
/**
* Retrieves the named class, creating it if it doesn't already
* exist.
*
* @param className Binary or non-binary class name without the
* package name, e.g. "AlertDialog.Builder".
* @param superclassName Fully-qualified binary or non-binary superclass
* name (e.g. "java.lang.Enum").
* @param isStatic Class static attribute, may be "true", "false", or null.
*/
public ClassInfo getOrCreateClass(String className, String superclassName,
String isStatic) {
String fixedName = TypeUtils.simpleClassNameToBinary(className);
ClassInfo classInfo = mClassList.get(fixedName);
if (classInfo == null) {
//System.out.println("--- creating entry for class " + fixedName +
// " (super=" + superclassName + ")");
classInfo = new ClassInfo(fixedName, superclassName, isStatic);
mClassList.put(fixedName, classInfo);
} else {
//System.out.println("--- returning existing class " + name);
}
return classInfo;
}
/**
* Returns an iterator for the set of classes in this package.
*/
public Iterator<ClassInfo> getClassIterator() {
return mClassList.values().iterator();
}
}

View file

@ -0,0 +1,279 @@
/*
* Copyright (C) 2010 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.apkcheck;
import java.util.HashMap;
public class TypeUtils {
private void TypeUtils() {}
/*
* Conversions for the primitive types, as well as a few things
* that show up a lot so we can avoid the string manipulation.
*/
private static final HashMap<String,String> sQuickConvert;
static {
sQuickConvert = new HashMap<String,String>();
sQuickConvert.put("boolean", "Z");
sQuickConvert.put("byte", "B");
sQuickConvert.put("char", "C");
sQuickConvert.put("short", "S");
sQuickConvert.put("int", "I");
sQuickConvert.put("float", "F");
sQuickConvert.put("long", "J");
sQuickConvert.put("double", "D");
sQuickConvert.put("void", "V");
sQuickConvert.put("java.lang.Object", "Ljava/lang/Object;");
sQuickConvert.put("java.lang.String", "Ljava/lang/String;");
sQuickConvert.put("java.util.ArrayList", "Ljava/util/ArrayList;");
sQuickConvert.put("java.util.HashMap", "Ljava/util/HashMap;");
};
/*
* Convert a human-centric type into something suitable for a method
* signature. Examples:
*
* int --> I
* float[] --> [F
* java.lang.String --> Ljava/lang/String;
*/
public static String typeToDescriptor(String type) {
String quick = sQuickConvert.get(type);
if (quick != null)
return quick;
int arrayDepth = 0;
int firstPosn = -1;
int posn = -1;
while ((posn = type.indexOf('[', posn+1)) != -1) {
if (firstPosn == -1)
firstPosn = posn;
arrayDepth++;
}
/* if we found an array, strip the brackets off */
if (firstPosn != -1)
type = type.substring(0, firstPosn);
StringBuilder builder = new StringBuilder();
while (arrayDepth-- > 0)
builder.append("[");
/* retry quick convert */
quick = sQuickConvert.get(type);
if (quick != null) {
builder.append(quick);
} else {
builder.append("L");
builder.append(type.replace('.', '/'));
builder.append(";");
}
return builder.toString();
}
/**
* Converts a "simple" class name into a "binary" class name. For
* example:
*
* SharedPreferences.Editor --&gt; SharedPreferences$Editor
*
* Do not use this on fully-qualified class names.
*/
public static String simpleClassNameToBinary(String className) {
return className.replace('.', '$');
}
/**
* Returns the class name portion of a fully-qualified binary class name.
*/
static String classNameOnly(String typeName) {
int start = typeName.lastIndexOf(".");
if (start < 0) {
return typeName;
} else {
return typeName.substring(start+1);
}
}
/**
* Returns the package portion of a fully-qualified binary class name.
*/
static String packageNameOnly(String typeName) {
int end = typeName.lastIndexOf(".");
if (end < 0) {
/* lives in default package */
return "";
} else {
return typeName.substring(0, end);
}
}
/**
* Normalizes a full class name to binary form.
*
* For example, "android.view.View.OnClickListener" could be in
* the "android.view" package or the "android.view.View" package.
* Checking capitalization is unreliable. We do have a full list
* of package names from the file though, so there's an excellent
* chance that we can identify the package that way. (Of course, we
* can only do this after we have finished parsing the file.)
*
* If the name has two or more dots, we need to compare successively
* shorter strings until we find a match in the package list.
*
* Do not call this on previously-returned output, as that may
* confuse the code that deals with generic signatures.
*/
public static String ambiguousToBinaryName(String typeName, ApiList apiList) {
/*
* In some cases this can be a generic signature:
* <parameter name="collection" type="java.util.Collection&lt;? extends E&gt;">
* <parameter name="interfaces" type="java.lang.Class&lt;?&gt;[]">
* <parameter name="object" type="E">
*
* If we see a '<', strip out everything from it to the '>'. That
* does pretty much the right thing, though we have to deal with
* nested stuff like "<? extends Map<String>>".
*
* Handling the third item is ugly. If the string is a single
* character, change it to java.lang.Object. This is generally
* insufficient and also ambiguous with respect to classes in the
* default package, but we don't have much choice here, and it gets
* us through the standard collection classes. Note this is risky
* if somebody tries to normalize a string twice, since we could be
* "promoting" a primitive type.
*/
typeName = stripAngleBrackets(typeName);
if (typeName.length() == 1) {
//System.out.println("converting X to Object: " + typeName);
typeName = "java.lang.Object";
} else if (typeName.length() == 3 &&
typeName.substring(1, 3).equals("[]")) {
//System.out.println("converting X[] to Object[]: " + typeName);
typeName = "java.lang.Object[]";
} else if (typeName.length() == 4 &&
typeName.substring(1, 4).equals("...")) {
//System.out.println("converting X... to Object[]: " + typeName);
typeName = "java.lang.Object[]";
}
/*
* Catch-all for varargs, which come in different varieties:
* java.lang.Object...
* java.lang.Class...
* java.lang.CharSequence...
* int...
* Progress...
*
* The latter is a generic type that we didn't catch above because
* it's not using a single-character descriptor.
*
* The method reference for "java.lang.Class..." will be looking
* for java.lang.Class[], not java.lang.Object[], so we don't want
* to do a blanket conversion. Similarly, "int..." turns into int[].
*
* There's not much we can do with "Progress...", unless we want
* to write off the default package and filter out primitive types.
* Probably easier to fix it up elsewhere.
*/
int ellipsisIndex = typeName.indexOf("...");
if (ellipsisIndex >= 0) {
String newTypeName = typeName.substring(0, ellipsisIndex) + "[]";
//System.out.println("vararg " + typeName + " --> " + newTypeName);
typeName = newTypeName;
}
/*
* It's possible the code that generates API definition files
* has been fixed. If we see a '$', just return the original.
*/
if (typeName.indexOf('$') >= 0)
return typeName;
int lastDot = typeName.lastIndexOf('.');
if (lastDot < 0)
return typeName;
/*
* What we have looks like some variation of these:
* package.Class
* Class.InnerClass
* long.package.name.Class
* long.package.name.Class.InnerClass
*
* We cut it off at the last '.' and test to see if it's a known
* package name. If not, keep moving left until we run out of dots.
*/
int nextDot = lastDot;
while (nextDot >= 0) {
String testName = typeName.substring(0, nextDot);
if (apiList.getPackage(testName) != null) {
break;
}
nextDot = typeName.lastIndexOf('.', nextDot-1);
}
if (nextDot < 0) {
/* no package name found, convert all dots */
System.out.println("+++ no pkg name found on " + typeName + typeName.length());
typeName = typeName.replace('.', '$');
} else if (nextDot == lastDot) {
/* class name is last element; original string is fine */
} else {
/* in the middle; zap the dots in the inner class name */
String oldClassName = typeName;
typeName = typeName.substring(0, nextDot+1) +
typeName.substring(nextDot+1).replace('.', '$');
//System.out.println("+++ " + oldClassName + " --> " + typeName);
}
return typeName;
}
/**
* Strips out everything between '<' and '>'. This will handle
* nested brackets, but we're not expecting to see multiple instances
* in series (i.e. "&lt;foo&lt;bar&gt;&gt;" is expected, but
* "&lt;foo&gt;STUFF&lt;bar&gt; is not).
*
* @return the stripped string
*/
private static String stripAngleBrackets(String str) {
/*
* Since we only expect to see one "run", we can just find the
* first '<' and the last '>'. Ideally we'd verify that they're
* not mismatched, but we're assuming the input file is generally
* correct.
*/
int ltIndex = str.indexOf('<');
if (ltIndex < 0)
return str;
int gtIndex = str.lastIndexOf('>');
if (gtIndex < 0) {
System.err.println("ERROR: found '<' without '>': " + str);
return str; // not much we can do
}
return str.substring(0, ltIndex) + str.substring(gtIndex+1);
}
}