Merge "APK checker."
This commit is contained in:
committed by
Android (Google) Code Review
commit
07efa81059
42
tools/apkcheck/Android.mk
Normal file
42
tools/apkcheck/Android.mk
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
# We use copy-file-to-new-target so that the installed
|
||||||
|
# script file's timestamp is at least as new as the
|
||||||
|
# .jar file it wraps.
|
||||||
|
|
||||||
|
# the execution script
|
||||||
|
# ============================================================
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_IS_HOST_MODULE := true
|
||||||
|
LOCAL_MODULE_CLASS := EXECUTABLES
|
||||||
|
LOCAL_MODULE := apkcheck
|
||||||
|
|
||||||
|
include $(BUILD_SYSTEM)/base_rules.mk
|
||||||
|
|
||||||
|
$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/apkcheck$(COMMON_JAVA_PACKAGE_SUFFIX)
|
||||||
|
$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/apkcheck | $(ACP)
|
||||||
|
@echo "Copy: $(PRIVATE_MODULE) ($@)"
|
||||||
|
$(copy-file-to-new-target)
|
||||||
|
$(hide) chmod 755 $@
|
||||||
|
|
||||||
|
# the other stuff
|
||||||
|
# ============================================================
|
||||||
|
subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
|
||||||
|
src \
|
||||||
|
))
|
||||||
|
|
||||||
|
include $(subdirs)
|
||||||
39
tools/apkcheck/README.txt
Normal file
39
tools/apkcheck/README.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
APK Checker
|
||||||
|
|
||||||
|
This compares the set of classes, fields, and methods used by an Android
|
||||||
|
application against the published API.
|
||||||
|
|
||||||
|
The public API description files live in the source tree, in
|
||||||
|
frameworks/base/api/. The dependency set for an APK can be generated with
|
||||||
|
"dexdeps".
|
||||||
|
|
||||||
|
Use "apkcheck --help" to see a list of available options.
|
||||||
|
|
||||||
|
|
||||||
|
Due to limitations and omissions in the API description files, there may
|
||||||
|
be false-positives and false-negatives. When possible these are emitted
|
||||||
|
as warnings rather than errors. (You may need to specify "--warn" to
|
||||||
|
see them.)
|
||||||
|
|
||||||
|
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 adding the following
|
||||||
|
to the command line:
|
||||||
|
|
||||||
|
--uses-library=BUILTIN
|
||||||
|
|
||||||
|
The "--uses-library" option allows you to specify additional API source
|
||||||
|
material. In the future this may be useful for applications that include
|
||||||
|
libraries with the "uses-library" directive.
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
By using the numbered API files (1.xml, 2.xml) instead of current.xml you
|
||||||
|
can test the APK against a specific release.
|
||||||
|
|
||||||
46
tools/apkcheck/etc/apkcheck
Normal file
46
tools/apkcheck/etc/apkcheck
Normal 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 "$@"
|
||||||
2
tools/apkcheck/etc/manifest.txt
Normal file
2
tools/apkcheck/etc/manifest.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: com.android.apkcheck.ApkCheck
|
||||||
28
tools/apkcheck/src/Android.mk
Normal file
28
tools/apkcheck/src/Android.mk
Normal 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)
|
||||||
|
|
||||||
147
tools/apkcheck/src/com/android/apkcheck/ApiDescrHandler.java
Normal file
147
tools/apkcheck/src/com/android/apkcheck/ApiDescrHandler.java
Normal 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 <package>. 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
104
tools/apkcheck/src/com/android/apkcheck/ApiList.java
Normal file
104
tools/apkcheck/src/com/android/apkcheck/ApiList.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
396
tools/apkcheck/src/com/android/apkcheck/ApkCheck.java
Normal file
396
tools/apkcheck/src/com/android/apkcheck/ApkCheck.java
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* 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.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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].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(" --[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) {
|
||||||
|
/* list the offending classes */
|
||||||
|
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 (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 ("java.lang.Enum".equals(pubClassInfo.getSuperclassName())) {
|
||||||
|
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) {
|
||||||
|
apkError("Illegal method ref: " + pubPkgInfo.getName() +
|
||||||
|
"." + classInfo.getName() + "." + nameAndDescr);
|
||||||
|
} else {
|
||||||
|
apkWarning("Possibly covariant method ref: " +
|
||||||
|
pubPkgInfo.getName() + "." + classInfo.getName() +
|
||||||
|
"." + nameAndDescr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
111
tools/apkcheck/src/com/android/apkcheck/Builtin.java
Normal file
111
tools/apkcheck/src/com/android/apkcheck/Builtin.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
316
tools/apkcheck/src/com/android/apkcheck/ClassInfo.java
Normal file
316
tools/apkcheck/src/com/android/apkcheck/ClassInfo.java
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
64
tools/apkcheck/src/com/android/apkcheck/FieldInfo.java
Normal file
64
tools/apkcheck/src/com/android/apkcheck/FieldInfo.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
95
tools/apkcheck/src/com/android/apkcheck/MethodInfo.java
Normal file
95
tools/apkcheck/src/com/android/apkcheck/MethodInfo.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
79
tools/apkcheck/src/com/android/apkcheck/PackageInfo.java
Normal file
79
tools/apkcheck/src/com/android/apkcheck/PackageInfo.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
253
tools/apkcheck/src/com/android/apkcheck/TypeUtils.java
Normal file
253
tools/apkcheck/src/com/android/apkcheck/TypeUtils.java
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* 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 --> 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<? extends E>">
|
||||||
|
* <parameter name="object" type="E">
|
||||||
|
*
|
||||||
|
* If we see a '<', truncate the string at that point. That does
|
||||||
|
* pretty much the right thing.
|
||||||
|
*
|
||||||
|
* Handling the second 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.
|
||||||
|
*/
|
||||||
|
int ltOffset = typeName.indexOf('<');
|
||||||
|
if (ltOffset >= 0) {
|
||||||
|
//System.out.println("stripping: " + typeName);
|
||||||
|
typeName = typeName.substring(0, ltOffset);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user