summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Android.bp2
-rw-r--r--tools/aapt2/Main.cpp6
-rw-r--r--tools/aapt2/ResourceUtils.cpp11
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp13
-rw-r--r--tools/aapt2/SdkConstants.cpp11
-rw-r--r--tools/aapt2/SdkConstants.h2
-rw-r--r--tools/aapt2/cmd/Convert_test.cpp7
-rw-r--r--tools/aapt2/io/ZipArchive.cpp10
-rwxr-xr-xtools/apilint/apilint147
-rw-r--r--tools/apilint/apilint.py2347
-rwxr-xr-xtools/apilint/apilint_sha.sh4
-rwxr-xr-xtools/apilint/apilint_sha_system.sh23
-rwxr-xr-xtools/apilint/apilint_stats.sh7
-rw-r--r--tools/apilint/apilint_test.py414
-rw-r--r--tools/dump-coverage/Android.bp29
-rw-r--r--tools/dump-coverage/README.md50
-rw-r--r--tools/dump-coverage/dump_coverage.cc205
-rwxr-xr-xtools/hiddenapi/generate_hiddenapi_lists.py2
-rw-r--r--tools/lock_agent/Android.bp68
-rw-r--r--tools/lock_agent/agent.cpp555
-rw-r--r--tools/lock_agent/crasher.cpp (renamed from tools/preload2/src/com/android/preload/actions/ClearTableAction.java)33
-rw-r--r--tools/lock_agent/java/com/android/lock_checker/LockHook.java324
-rw-r--r--tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java390
-rwxr-xr-xtools/lock_agent/start_with_lockagent.sh13
-rw-r--r--tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java6
-rw-r--r--tools/preload-check/Android.bp23
-rw-r--r--tools/preload-check/AndroidTest.xml24
-rw-r--r--tools/preload-check/TEST_MAPPING7
-rw-r--r--tools/preload-check/device/Android.bp26
-rw-r--r--tools/preload-check/device/src/com/android/preload/check/Initialized.java (renamed from tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java)18
-rw-r--r--tools/preload-check/device/src/com/android/preload/check/IntegrityCheck.java40
-rw-r--r--tools/preload-check/device/src/com/android/preload/check/NotInitialized.java (renamed from tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java)32
-rw-r--r--tools/preload-check/device/src/com/android/preload/check/NotInitializedRegex.java66
-rw-r--r--tools/preload-check/device/src/com/android/preload/check/Util.java125
-rw-r--r--tools/preload-check/src/com/android/preload/check/PreloadCheck.java127
-rw-r--r--tools/preload2/Android.mk30
-rw-r--r--tools/preload2/preload-tool37
-rw-r--r--tools/preload2/src/com/android/preload/ClientUtils.java224
-rw-r--r--tools/preload2/src/com/android/preload/DeviceUtils.java420
-rw-r--r--tools/preload2/src/com/android/preload/DumpData.java91
-rw-r--r--tools/preload2/src/com/android/preload/DumpDataIO.java141
-rw-r--r--tools/preload2/src/com/android/preload/DumpTableModel.java93
-rw-r--r--tools/preload2/src/com/android/preload/Main.java341
-rw-r--r--tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java39
-rw-r--r--tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java45
-rw-r--r--tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java148
-rw-r--r--tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java41
-rw-r--r--tools/preload2/src/com/android/preload/actions/DeviceSpecific.java38
-rw-r--r--tools/preload2/src/com/android/preload/actions/ExportAction.java62
-rw-r--r--tools/preload2/src/com/android/preload/actions/ImportAction.java68
-rw-r--r--tools/preload2/src/com/android/preload/actions/ReloadListAction.java68
-rw-r--r--tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java124
-rw-r--r--tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java63
-rw-r--r--tools/preload2/src/com/android/preload/actions/ScanPackageAction.java97
-rw-r--r--tools/preload2/src/com/android/preload/actions/ShowDataAction.java94
-rw-r--r--tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java60
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java70
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java228
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java221
-rw-r--r--tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java40
-rw-r--r--tools/preload2/src/com/android/preload/ui/IUI.java45
-rw-r--r--tools/preload2/src/com/android/preload/ui/SequenceUI.java222
-rw-r--r--tools/preload2/src/com/android/preload/ui/SwingUI.java291
-rw-r--r--tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java54
-rw-r--r--tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java72
-rw-r--r--tools/stats_log_api_gen/Android.bp2
-rw-r--r--tools/streaming_proto/Android.bp17
-rw-r--r--tools/streaming_proto/Android.mk28
68 files changed, 2237 insertions, 6544 deletions
diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp
index f36739730775..a594e5bf0ce1 100644
--- a/tools/aapt/Android.bp
+++ b/tools/aapt/Android.bp
@@ -71,8 +71,6 @@ cc_library_host_static {
cflags: [
"-Wno-format-y2k",
"-DSTATIC_ANDROIDFW_FOR_TOOLS",
- // Allow implicit fallthroughs in AaptAssets.cpp until they are fixed.
- "-Wno-error=implicit-fallthrough",
],
srcs: [
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 213bdd2372ec..8a43bb4ede35 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -169,12 +169,12 @@ int MainImpl(int argc, char** argv) {
aapt::text::Printer printer(&fout);
aapt::StdErrDiagnostics diagnostics;
- auto main_command = new aapt::MainCommand(&printer, &diagnostics);
+ aapt::MainCommand main_command(&printer, &diagnostics);
// Add the daemon subcommand here so it cannot be called while executing the daemon
- main_command->AddOptionalSubcommand(
+ main_command.AddOptionalSubcommand(
aapt::util::make_unique<aapt::DaemonCommand>(&fout, &diagnostics));
- return main_command->Execute(args, &std::cerr);
+ return main_command.Execute(args, &std::cerr);
}
int main(int argc, char** argv) {
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index a571aee61546..e0040e486a23 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -534,17 +534,18 @@ Maybe<int> ParseSdkVersion(const StringPiece& str) {
}
// Try parsing the code name.
- std::pair<StringPiece, int> entry = GetDevelopmentSdkCodeNameAndVersion();
- if (entry.first == trimmed_str) {
- return entry.second;
+ Maybe<int> entry = GetDevelopmentSdkCodeNameVersion(trimmed_str);
+ if (entry) {
+ return entry.value();
}
// Try parsing codename from "[codename].[preview_sdk_fingerprint]" value.
const StringPiece::const_iterator begin = std::begin(trimmed_str);
const StringPiece::const_iterator end = std::end(trimmed_str);
const StringPiece::const_iterator codename_end = std::find(begin, end, '.');
- if (codename_end != end && entry.first == trimmed_str.substr(begin, codename_end)) {
- return entry.second;
+ entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end));
+ if (entry) {
+ return entry.value();
}
return {};
}
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 3b77135a09eb..c016cb44af00 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -214,14 +214,15 @@ TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) {
}
TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) {
- const android::StringPiece codename =
- GetDevelopmentSdkCodeNameAndVersion().first;
- const int version = GetDevelopmentSdkCodeNameAndVersion().second;
+ EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(Maybe<int>(10000)));
+ EXPECT_THAT(
+ ResourceUtils::ParseSdkVersion("Q.fingerprint"),
+ Eq(Maybe<int>(10000)));
- EXPECT_THAT(ResourceUtils::ParseSdkVersion(codename), Eq(Maybe<int>(version)));
+ EXPECT_THAT(ResourceUtils::ParseSdkVersion("R"), Eq(Maybe<int>(10000)));
EXPECT_THAT(
- ResourceUtils::ParseSdkVersion(codename.to_string() + ".fingerprint"),
- Eq(Maybe<int>(version)));
+ ResourceUtils::ParseSdkVersion("R.fingerprint"),
+ Eq(Maybe<int>(10000)));
}
TEST(ResourceUtilsTest, StringBuilderWhitespaceRemoval) {
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index f4b0124abcda..b4b6ff1daaaa 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -18,15 +18,17 @@
#include <algorithm>
#include <string>
-#include <unordered_map>
+#include <unordered_set>
#include <vector>
using android::StringPiece;
namespace aapt {
-static const char* sDevelopmentSdkCodeName = "Q";
static ApiVersion sDevelopmentSdkLevel = 10000;
+static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>({
+ "Q", "R"
+});
static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
{0x021c, 1},
@@ -72,8 +74,9 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
return iter->second;
}
-std::pair<StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion() {
- return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel);
+Maybe<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) {
+ return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
+ ? Maybe<ApiVersion>() : sDevelopmentSdkLevel;
}
} // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 580daabbd577..a00d978565ad 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -58,7 +58,7 @@ enum : ApiVersion {
};
ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-std::pair<android::StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion();
+Maybe<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name);
} // namespace aapt
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 3c0fe370c516..f35237ec25e3 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -123,8 +123,7 @@ TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
void* cookie = nullptr;
- ZipString prefix("res/theme/10");
- int32_t result = StartIteration(handle, &cookie, &prefix, nullptr);
+ int32_t result = StartIteration(handle, &cookie, "res/theme/10", "");
// If this is -5, that means we've found a duplicate entry and this test has failed
EXPECT_THAT(result, Eq(0));
@@ -133,7 +132,7 @@ TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
int count = 0;
// Can't pass nullptrs into Next()
- ZipString zip_name;
+ std::string zip_name;
ZipEntry zip_data;
while ((result = Next(cookie, &zip_data, &zip_name)) == 0) {
@@ -145,4 +144,4 @@ TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
EXPECT_THAT(count, Eq(1));
}
-} // namespace aapt \ No newline at end of file
+} // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index f6aaa1280a61..4380586b1d3c 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -114,7 +114,7 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
}
void* cookie = nullptr;
- result = StartIteration(collection->handle_, &cookie, nullptr, nullptr);
+ result = StartIteration(collection->handle_, &cookie);
if (result != 0) {
if (out_error) *out_error = ErrorCodeString(result);
return {};
@@ -123,13 +123,9 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
IterationEnder iteration_ender(cookie, EndIteration);
- ZipString zip_entry_name;
+ std::string zip_entry_path;
ZipEntry zip_data;
- while ((result = Next(cookie, &zip_data, &zip_entry_name)) == 0) {
- std::string zip_entry_path =
- std::string(reinterpret_cast<const char*>(zip_entry_name.name),
- zip_entry_name.name_length);
-
+ while ((result = Next(cookie, &zip_data, &zip_entry_path)) == 0) {
// Do not add folders to the file collection
if (util::EndsWith(zip_entry_path, "/")) {
continue;
diff --git a/tools/apilint/apilint b/tools/apilint/apilint
deleted file mode 100755
index e42857f1a190..000000000000
--- a/tools/apilint/apilint
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2019 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.
-
-if [ "$1" == "--help" -o "$1" == "-h" ]; then
-echo "Usage: apilint [FILTERS...]"
-echo " Shows lint from currently open files (as diffed from HEAD), i.e. errors"
-echo " you will receive if you upload this CL."
-echo
-echo "Usage: apilint --all [FILTERS...]"
-echo " Shows all lint errors present in the current working directory, regardless"
-echo " of when they were added."
-echo
-echo "Usage: apilint --level API_LEVEL [FILTERS...]"
-echo " Shows lint as it stands in API_LEVEL"
-echo
-echo "Usage: apilint --shal SHA [FILTERS...]"
-echo " Shows lint from locally commited git change SHA."
-echo
-echo "Usage: apilint --unreleased [FILTERS...]"
-echo " Shows all lint errors in the current working directory directory added since"
-echo " the last released SDK version."
-echo
-echo "FILTERS"
-echo " List of class or package names by which to filter the results."
-echo
-exit
-fi
-
-if [ \( -z "$ANDROID_BUILD_TOP" \) \
- -a \( ! -f frameworks/base/api/current.txt \) \
- -a \( ! -f frameworks/base/api/system-current.txt \) \
- ]; then
- echo "apilint must be run either with ANDROID_BUILD_TOP set or from the" 1>&2
- echo "root of the android source tree" 1>&2
- exit 1
-fi
-
-if [ ${ANDROID_BUILD_TOP:0:1} != "/" ]; then
- echo "ANDROID_BUILD_TOP must be an absolute path, not: $ANDROID_BUILD_TOP" 1>&2
- exit 1
-fi
-
-if [ -z "$ANDROID_BUILD_TOP" ]; then
- ANDROID_BUILD_TOP=$(pwd)
-fi
-
-FW_BASE=$ANDROID_BUILD_TOP/frameworks/base
-
-MODE=open
-
-OPTIONS=$(getopt -n apilint -o "" -l "all,sha:,unreleased" -- "$@")
-
-[ $? -eq 0 ] || {
- exit 1
-}
-
-eval set -- "$OPTIONS"
-while true; do
- case "$1" in
- --all)
- MODE=all
- ;;
- --sha)
- shift; # The arg is next in position args
- MODE=sha
- SHA=$1
- ;;
- --unreleased)
- MODE=unreleased
- ;;
- --)
- shift
- break
- ;;
- esac
- shift
-done
-FILTERS=
-for var in "$@"
-do
- FILTERS="$FILTERS --filter $var"
-done
-
-if [ $MODE = "all" ]; then
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SDK" \
- $FILTERS \
- $ANDROID_BUILD_TOP/frameworks/base/api/current.txt
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SystemApi" \
- $FILTERS \
- --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
- $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt
-elif [ $MODE = "open" ]; then
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SDK" \
- $FILTERS \
- $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
- <(cd $FW_BASE ; git show HEAD:api/current.txt)
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SystemApi" \
- $FILTERS \
- --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
- --base-previous <(cd $FW_BASE ; git show HEAD:api/current.txt) \
- $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt \
- <(cd $FW_BASE ; git show HEAD:api/system-current.txt)
-elif [ $MODE = "sha" ]; then
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SDK" \
- $FILTERS \
- <(cd $FW_BASE ; git show $SHA:api/current.txt) \
- <(cd $FW_BASE ; git show $SHA^:api/current.txt)
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SystemApi" \
- $FILTERS \
- --base-current <(cd $FW_BASE ; git show $SHA:api/current.txt) \
- --base-previous <(cd $FW_BASE ; git show $SHA^:api/current.txt) \
- <(cd $FW_BASE ; git show $SHA:api/system-current.txt) \
- <(cd $FW_BASE ; git show $SHA^:api/system-current.txt)
-elif [ $MODE = "unreleased" ]; then
- LAST_SDK=$(ls $ANDROID_BUILD_TOP/prebuilts/sdk | grep "^[0-9][0-9]*$" | sort -n | tail -n 1)
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SDK" \
- $FILTERS \
- $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
- $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/public/api/android.txt
- python2.7 -B $ANDROID_BUILD_TOP/frameworks/base/tools/apilint/apilint.py \
- --title "SystemApi" \
- $FILTERS \
- --base-current $ANDROID_BUILD_TOP/frameworks/base/api/current.txt \
- --base-previous $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/public/api/android.txt \
- $ANDROID_BUILD_TOP/frameworks/base/api/system-current.txt \
- $ANDROID_BUILD_TOP/prebuilts/sdk/$LAST_SDK/system/api/android.txt
-fi
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
deleted file mode 100644
index 9e42c044e209..000000000000
--- a/tools/apilint/apilint.py
+++ /dev/null
@@ -1,2347 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2014 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.
-
-"""
-Enforces common Android public API design patterns. It ignores lint messages from
-a previous API level, if provided.
-
-Usage: apilint.py current.txt
-Usage: apilint.py current.txt previous.txt
-
-You can also splice in blame details like this:
-$ git blame api/current.txt -t -e > /tmp/currentblame.txt
-$ apilint.py /tmp/currentblame.txt previous.txt --no-color
-"""
-
-import re, sys, collections, traceback, argparse, itertools
-
-
-BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
-
-ALLOW_GOOGLE = False
-USE_COLOR = True
-
-def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
- # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
- if not USE_COLOR: return ""
- codes = []
- if reset: codes.append("0")
- else:
- if not fg is None: codes.append("3%d" % (fg))
- if not bg is None:
- if not bright: codes.append("4%d" % (bg))
- else: codes.append("10%d" % (bg))
- if bold: codes.append("1")
- elif dim: codes.append("2")
- else: codes.append("22")
- return "\033[%sm" % (";".join(codes))
-
-
-class Field():
- def __init__(self, clazz, line, raw, blame, sig_format = 1):
- self.clazz = clazz
- self.line = line
- self.raw = raw.strip(" {;")
- self.blame = blame
-
- if sig_format == 2:
- V2LineParser(raw).parse_into_field(self)
- elif sig_format == 1:
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
-
- raw = raw.split()
- self.split = list(raw)
-
- for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
- while r in raw: raw.remove(r)
-
- # ignore annotations for now
- raw = [ r for r in raw if not r.startswith("@") ]
-
- self.typ = raw[0]
- self.name = raw[1].strip(";")
- if len(raw) >= 4 and raw[2] == "=":
- self.value = raw[3].strip(';"')
- else:
- self.value = None
- self.annotations = []
-
- self.ident = "-".join((self.typ, self.name, self.value or ""))
-
- def __hash__(self):
- return hash(self.raw)
-
- def __repr__(self):
- return self.raw
-
-
-class Argument(object):
-
- __slots__ = ["type", "annotations", "name", "default"]
-
- def __init__(self, type):
- self.type = type
- self.annotations = []
- self.name = None
- self.default = None
-
-
-class Method():
- def __init__(self, clazz, line, raw, blame, sig_format = 1):
- self.clazz = clazz
- self.line = line
- self.raw = raw.strip(" {;")
- self.blame = blame
-
- if sig_format == 2:
- V2LineParser(raw).parse_into_method(self)
- elif sig_format == 1:
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
-
- # handle each clause differently
- raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
-
- # parse prefixes
- raw = re.split("[\s]+", raw_prefix)
- for r in ["", ";"]:
- while r in raw: raw.remove(r)
- self.split = list(raw)
-
- for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
- while r in raw: raw.remove(r)
-
- self.typ = raw[0]
- self.name = raw[1]
-
- # parse args
- self.detailed_args = []
- for arg in re.split(",\s*", raw_args):
- arg = re.split("\s", arg)
- # ignore annotations for now
- arg = [ a for a in arg if not a.startswith("@") ]
- if len(arg[0]) > 0:
- self.detailed_args.append(Argument(arg[0]))
-
- # parse throws
- self.throws = []
- for throw in re.split(",\s*", raw_throws):
- self.throws.append(throw)
-
- self.annotations = []
- else:
- raise ValueError("Unknown signature format: " + sig_format)
-
- self.args = map(lambda a: a.type, self.detailed_args)
- self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
-
- def sig_matches(self, typ, name, args):
- return typ == self.typ and name == self.name and args == self.args
-
- def __hash__(self):
- return hash(self.raw)
-
- def __repr__(self):
- return self.raw
-
-
-class Class():
- def __init__(self, pkg, line, raw, blame, sig_format = 1):
- self.pkg = pkg
- self.line = line
- self.raw = raw.strip(" {;")
- self.blame = blame
- self.ctors = []
- self.fields = []
- self.methods = []
- self.annotations = []
-
- if sig_format == 2:
- V2LineParser(raw).parse_into_class(self)
- elif sig_format == 1:
- # drop generics for now; may need multiple passes
- raw = re.sub("<[^<]+?>", "", raw)
- raw = re.sub("<[^<]+?>", "", raw)
-
- raw = raw.split()
- self.split = list(raw)
- if "class" in raw:
- self.fullname = raw[raw.index("class")+1]
- elif "interface" in raw:
- self.fullname = raw[raw.index("interface")+1]
- elif "@interface" in raw:
- self.fullname = raw[raw.index("@interface")+1]
- else:
- raise ValueError("Funky class type %s" % (self.raw))
-
- if "extends" in raw:
- self.extends = raw[raw.index("extends")+1]
- else:
- self.extends = None
-
- if "implements" in raw:
- self.implements = raw[raw.index("implements")+1]
- self.implements_all = [self.implements]
- else:
- self.implements = None
- self.implements_all = []
- else:
- raise ValueError("Unknown signature format: " + sig_format)
-
- self.fullname = self.pkg.name + "." + self.fullname
- self.fullname_path = self.fullname.split(".")
-
- if self.extends is not None:
- self.extends_path = self.extends.split(".")
- else:
- self.extends_path = []
-
- self.name = self.fullname[self.fullname.rindex(".")+1:]
-
- def merge_from(self, other):
- self.ctors.extend(other.ctors)
- self.fields.extend(other.fields)
- self.methods.extend(other.methods)
-
- def __hash__(self):
- return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
-
- def __repr__(self):
- return self.raw
-
-
-class Package():
- NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
-
- def __init__(self, line, raw, blame):
- self.line = line
- self.raw = raw.strip(" {;")
- self.blame = blame
-
- self.name = Package.NAME.match(raw).group(1)
- self.name_path = self.name.split(".")
-
- def __repr__(self):
- return self.raw
-
-class V2Tokenizer(object):
- __slots__ = ["raw"]
-
- SIGNATURE_PREFIX = "// Signature format: "
- DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
- STRING_SPECIAL = re.compile(r'["\\]')
-
- def __init__(self, raw):
- self.raw = raw
-
- def tokenize(self):
- tokens = []
- current = 0
- raw = self.raw
- length = len(raw)
-
- while current < length:
- while current < length:
- start = current
- match = V2Tokenizer.DELIMITER.search(raw, start)
- if match is not None:
- match_start = match.start()
- if match_start == current:
- end = match.end()
- else:
- end = match_start
- else:
- end = length
-
- token = raw[start:end]
- current = end
-
- if token == "" or token[0] == " ":
- continue
- else:
- break
-
- if token == "@":
- if raw[start:start+11] == "@interface ":
- current = start + 11
- tokens.append("@interface")
- continue
- elif token == '/':
- if raw[start:start+2] == "//":
- current = length
- continue
- elif token == '"':
- current, string_token = self.tokenize_string(raw, length, current)
- tokens.append(token + string_token)
- continue
-
- tokens.append(token)
-
- return tokens
-
- def tokenize_string(self, raw, length, current):
- start = current
- end = length
- while start < end:
- match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
- if match:
- if match.group() == '"':
- end = match.end()
- break
- elif match.group() == '\\':
- # ignore whatever is after the slash
- start += 2
- else:
- raise ValueError("Unexpected match: `%s`" % (match.group()))
- else:
- raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
-
- token = raw[current:end]
- return end, token
-
-class V2LineParser(object):
- __slots__ = ["tokenized", "current", "len"]
-
- FIELD_KINDS = ("field", "property", "enum_constant")
- MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
- JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
-
- def __init__(self, raw):
- self.tokenized = V2Tokenizer(raw).tokenize()
- self.current = 0
- self.len = len(self.tokenized)
-
- def parse_into_method(self, method):
- method.split = []
- kind = self.parse_one_of("ctor", "method")
- method.split.append(kind)
- method.annotations = self.parse_annotations()
- method.split.extend(self.parse_modifiers())
- self.parse_matching_paren("<", ">")
- if "@Deprecated" in method.annotations:
- method.split.append("deprecated")
- if kind == "ctor":
- method.typ = "ctor"
- else:
- method.typ = self.parse_type()
- method.split.append(method.typ)
- method.name = self.parse_name()
- method.split.append(method.name)
- self.parse_token("(")
- method.detailed_args = self.parse_args()
- self.parse_token(")")
- method.throws = self.parse_throws()
- if "@interface" in method.clazz.split:
- self.parse_annotation_default()
- self.parse_token(";")
- self.parse_eof()
-
- def parse_into_class(self, clazz):
- clazz.split = []
- clazz.annotations = self.parse_annotations()
- if "@Deprecated" in clazz.annotations:
- clazz.split.append("deprecated")
- clazz.split.extend(self.parse_modifiers())
- kind = self.parse_one_of("class", "interface", "@interface", "enum")
- if kind == "enum":
- # enums are implicitly final
- clazz.split.append("final")
- clazz.split.append(kind)
- clazz.fullname = self.parse_name()
- self.parse_matching_paren("<", ">")
- extends = self.parse_extends()
- clazz.extends = extends[0] if extends else None
- clazz.implements_all = self.parse_implements()
- # The checks assume that interfaces are always found in implements, which isn't true for
- # subinterfaces.
- if not clazz.implements_all and "interface" in clazz.split:
- clazz.implements_all = [clazz.extends]
- clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
- self.parse_token("{")
- self.parse_eof()
-
- def parse_into_field(self, field):
- kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
- field.split = [kind]
- field.annotations = self.parse_annotations()
- if "@Deprecated" in field.annotations:
- field.split.append("deprecated")
- field.split.extend(self.parse_modifiers())
- field.typ = self.parse_type()
- field.split.append(field.typ)
- field.name = self.parse_name()
- field.split.append(field.name)
- if self.parse_if("="):
- field.value = self.parse_value_stripped()
- else:
- field.value = None
-
- self.parse_token(";")
- self.parse_eof()
-
- def lookahead(self):
- return self.tokenized[self.current]
-
- def parse_one_of(self, *options):
- found = self.lookahead()
- if found not in options:
- raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
- return self.parse_token()
-
- def parse_token(self, tok = None):
- found = self.lookahead()
- if tok is not None and found != tok:
- raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
- self.current += 1
- return found
-
- def eof(self):
- return self.current == self.len
-
- def parse_eof(self):
- if not self.eof():
- raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
-
- def parse_if(self, tok):
- if not self.eof() and self.lookahead() == tok:
- self.parse_token()
- return True
- return False
-
- def parse_annotations(self):
- ret = []
- while self.lookahead() == "@":
- ret.append(self.parse_annotation())
- return ret
-
- def parse_annotation(self):
- ret = self.parse_token("@") + self.parse_token()
- self.parse_matching_paren("(", ")")
- return ret
-
- def parse_matching_paren(self, open, close):
- start = self.current
- if not self.parse_if(open):
- return
- length = len(self.tokenized)
- count = 1
- while count > 0:
- if self.current == length:
- raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
- t = self.parse_token()
- if t == open:
- count += 1
- elif t == close:
- count -= 1
- return self.tokenized[start:self.current]
-
- def parse_modifiers(self):
- ret = []
- while self.lookahead() in V2LineParser.MODIFIERS:
- ret.append(self.parse_token())
- return ret
-
- def parse_kotlin_nullability(self):
- t = self.lookahead()
- if t == "?" or t == "!":
- return self.parse_token()
- return None
-
- def parse_type(self):
- self.parse_annotations()
- type = self.parse_token()
- if type[-1] == '.':
- self.parse_annotations()
- type += self.parse_token()
- if type in V2LineParser.JAVA_LANG_TYPES:
- type = "java.lang." + type
- self.parse_matching_paren("<", ">")
- while True:
- t = self.lookahead()
- if t == "@":
- self.parse_annotation()
- elif t == "[]":
- type += self.parse_token()
- elif self.parse_kotlin_nullability() is not None:
- pass # discard nullability for now
- else:
- break
- return type
-
- def parse_arg_type(self):
- type = self.parse_type()
- if self.parse_if("..."):
- type += "..."
- self.parse_kotlin_nullability() # discard nullability for now
- return type
-
- def parse_name(self):
- return self.parse_token()
-
- def parse_args(self):
- args = []
- if self.lookahead() == ")":
- return args
-
- while True:
- args.append(self.parse_arg())
- if self.lookahead() == ")":
- return args
- self.parse_token(",")
-
- def parse_arg(self):
- self.parse_if("vararg") # kotlin vararg
- annotations = self.parse_annotations()
- arg = Argument(self.parse_arg_type())
- arg.annotations = annotations
- l = self.lookahead()
- if l != "," and l != ")":
- if self.lookahead() != '=':
- arg.name = self.parse_token() # kotlin argument name
- if self.parse_if('='): # kotlin default value
- arg.default = self.parse_expression()
- return arg
-
- def parse_expression(self):
- while not self.lookahead() in [')', ',', ';']:
- (self.parse_matching_paren('(', ')') or
- self.parse_matching_paren('{', '}') or
- self.parse_token())
-
- def parse_throws(self):
- ret = []
- if self.parse_if("throws"):
- ret.append(self.parse_type())
- while self.parse_if(","):
- ret.append(self.parse_type())
- return ret
-
- def parse_extends(self):
- if self.parse_if("extends"):
- return self.parse_space_delimited_type_list()
- return []
-
- def parse_implements(self):
- if self.parse_if("implements"):
- return self.parse_space_delimited_type_list()
- return []
-
- def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
- types = []
- while True:
- types.append(self.parse_type())
- if self.lookahead() in terminals:
- return types
-
- def parse_annotation_default(self):
- if self.parse_if("default"):
- self.parse_expression()
-
- def parse_value(self):
- if self.lookahead() == "{":
- return " ".join(self.parse_matching_paren("{", "}"))
- elif self.lookahead() == "(":
- return " ".join(self.parse_matching_paren("(", ")"))
- else:
- return self.parse_token()
-
- def parse_value_stripped(self):
- value = self.parse_value()
- if value[0] == '"':
- return value[1:-1]
- return value
-
-
-def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
- in_classes_with_base=[]):
- api = {}
- in_classes_with_base = _retry_iterator(in_classes_with_base)
-
- if base_f:
- base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
- else:
- base_classes = []
-
- def handle_class(clazz):
- if clazz_cb:
- clazz_cb(clazz)
- else: # In callback mode, don't keep track of the full API
- api[clazz.fullname] = clazz
-
- def handle_missed_classes_with_base(clazz):
- for c in _yield_until_matching_class(in_classes_with_base, clazz):
- base_class = _skip_to_matching_class(base_classes, c)
- if base_class:
- handle_class(base_class)
-
- for clazz in _parse_stream_to_generator(f):
- # Before looking at clazz, let's see if there's some classes that were not present, but
- # may have an entry in the base stream.
- handle_missed_classes_with_base(clazz)
-
- base_class = _skip_to_matching_class(base_classes, clazz)
- if base_class:
- clazz.merge_from(base_class)
- if out_classes_with_base is not None:
- out_classes_with_base.append(clazz)
- handle_class(clazz)
-
- handle_missed_classes_with_base(None)
-
- return api
-
-def _parse_stream_to_generator(f):
- line = 0
- pkg = None
- clazz = None
- blame = None
- sig_format = 1
-
- re_blame = re.compile(r"^(\^?[a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
-
- field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
- def startsWithFieldPrefix(raw):
- for prefix in field_prefixes:
- if raw.startswith(prefix):
- return True
- return False
-
- for raw in f:
- line += 1
- raw = raw.rstrip()
- match = re_blame.match(raw)
- if match is not None:
- blame = match.groups()[0:2]
- if blame[0].startswith("^"): # Outside of blame range
- blame = None
- raw = match.groups()[2]
- else:
- blame = None
-
- if line == 1 and V2Tokenizer.SIGNATURE_PREFIX in raw:
- sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
- if sig_format_string in ["2.0", "3.0"]:
- sig_format = 2
- else:
- raise ValueError("Unknown format: %s" % (sig_format_string,))
- elif raw.startswith("package"):
- pkg = Package(line, raw, blame)
- elif raw.startswith(" ") and raw.endswith("{"):
- clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
- elif raw.startswith(" ctor"):
- clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
- elif raw.startswith(" method"):
- clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
- elif startsWithFieldPrefix(raw):
- clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
- elif raw.startswith(" }") and clazz:
- yield clazz
-
-def _retry_iterator(it):
- """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
- for e in it:
- while True:
- retry = yield e
- if not retry:
- break
- # send() was called, asking us to redeliver clazz on next(). Still need to yield
- # a dummy value to the send() first though.
- if (yield "Returning clazz on next()"):
- raise TypeError("send() must be followed by next(), not send()")
-
-def _skip_to_matching_class(classes, needle):
- """Takes a classes iterator and consumes entries until it returns the class we're looking for
-
- This relies on classes being sorted by package and class name."""
-
- for clazz in classes:
- if clazz.pkg.name < needle.pkg.name:
- # We haven't reached the right package yet
- continue
- if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
- # We're in the right package, but not the right class yet
- continue
- if clazz.fullname == needle.fullname:
- return clazz
- # We ran past the right class. Send it back into the generator, then report failure.
- classes.send(clazz)
- return None
-
-def _yield_until_matching_class(classes, needle):
- """Takes a class iterator and yields entries it until it reaches the class we're looking for.
-
- This relies on classes being sorted by package and class name."""
-
- for clazz in classes:
- if needle is None:
- yield clazz
- elif clazz.pkg.name < needle.pkg.name:
- # We haven't reached the right package yet
- yield clazz
- elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
- # We're in the right package, but not the right class yet
- yield clazz
- elif clazz.fullname == needle.fullname:
- # Class found, abort.
- return
- else:
- # We ran past the right class. Send it back into the iterator, then abort.
- classes.send(clazz)
- return
-
-class Failure():
- def __init__(self, sig, clazz, detail, error, rule, msg):
- self.clazz = clazz
- self.sig = sig
- self.error = error
- self.rule = rule
- self.msg = msg
-
- if error:
- self.head = "Error %s" % (rule) if rule else "Error"
- dump = "%s%s:%s %s" % (format(fg=RED, bg=BLACK, bold=True), self.head, format(reset=True), msg)
- else:
- self.head = "Warning %s" % (rule) if rule else "Warning"
- dump = "%s%s:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), self.head, format(reset=True), msg)
-
- self.line = clazz.line
- blame = clazz.blame
- if detail is not None:
- dump += "\n in " + repr(detail)
- self.line = detail.line
- blame = detail.blame
- dump += "\n in " + repr(clazz)
- dump += "\n in " + repr(clazz.pkg)
- dump += "\n at line " + repr(self.line)
- if blame is not None:
- dump += "\n last modified by %s in %s" % (blame[1], blame[0])
-
- self.dump = dump
-
- def __repr__(self):
- return self.dump
-
-
-failures = {}
-
-def _fail(clazz, detail, error, rule, msg):
- """Records an API failure to be processed later."""
- global failures
-
- sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
- sig = sig.replace(" deprecated ", " ")
-
- failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
-
-
-def warn(clazz, detail, rule, msg):
- _fail(clazz, detail, False, rule, msg)
-
-def error(clazz, detail, rule, msg):
- _fail(clazz, detail, True, rule, msg)
-
-
-noticed = {}
-
-def notice(clazz):
- global noticed
-
- noticed[clazz.fullname] = hash(clazz)
-
-
-verifiers = {}
-
-def verifier(f):
- verifiers[f.__name__] = f
- return f
-
-
-@verifier
-def verify_constants(clazz):
- """All static final constants must be FOO_NAME style."""
- if re.match("android\.R\.[a-z]+", clazz.fullname): return
- if clazz.fullname.startswith("android.os.Build"): return
- if clazz.fullname == "android.system.OsConstants": return
-
- req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
- for f in clazz.fields:
- if "static" in f.split and "final" in f.split:
- if re.match("[A-Z0-9_]+", f.name) is None:
- error(clazz, f, "C2", "Constant field names must be FOO_NAME")
- if f.typ != "java.lang.String":
- if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
- warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
- if f.typ in req and f.value is None:
- error(clazz, f, None, "All constants must be defined at compile time")
-
-@verifier
-def verify_enums(clazz):
- """Enums are bad, mmkay?"""
- if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
- error(clazz, None, "F5", "Enums are not allowed")
-
-@verifier
-def verify_class_names(clazz):
- """Try catching malformed class names like myMtp or MTPUser."""
- if clazz.fullname.startswith("android.opengl"): return
- if clazz.fullname.startswith("android.renderscript"): return
- if re.match("android\.R\.[a-z]+", clazz.fullname): return
-
- if re.search("[A-Z]{2,}", clazz.name) is not None:
- warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
- if re.match("[^A-Z]", clazz.name):
- error(clazz, None, "S1", "Class must start with uppercase char")
- if clazz.name.endswith("Impl"):
- error(clazz, None, None, "Don't expose your implementation details")
-
-
-@verifier
-def verify_method_names(clazz):
- """Try catching malformed method names, like Foo() or getMTU()."""
- if clazz.fullname.startswith("android.opengl"): return
- if clazz.fullname.startswith("android.renderscript"): return
- if clazz.fullname == "android.system.OsConstants": return
-
- for m in clazz.methods:
- if re.search("[A-Z]{2,}", m.name) is not None:
- warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
- if re.match("[^a-z]", m.name):
- error(clazz, m, "S1", "Method name must start with lowercase char")
-
-
-@verifier
-def verify_callbacks(clazz):
- """Verify Callback classes.
- All methods must follow onFoo() naming style."""
- if clazz.fullname == "android.speech.tts.SynthesisCallback": return
-
- if clazz.name.endswith("Callbacks"):
- error(clazz, None, "L1", "Callback class names should be singular")
- if clazz.name.endswith("Observer"):
- warn(clazz, None, "L1", "Class should be named FooCallback")
-
- if clazz.name.endswith("Callback"):
- for m in clazz.methods:
- if not re.match("on[A-Z][a-z]*", m.name):
- error(clazz, m, "L1", "Callback method names must be onFoo() style")
-
-
-@verifier
-def verify_listeners(clazz):
- """Verify Listener classes.
- All Listener classes must be interface.
- All methods must follow onFoo() naming style.
- If only a single method, it must match class name:
- interface OnFooListener { void onFoo() }"""
-
- if clazz.name.endswith("Listener"):
- if "abstract" in clazz.split and "class" in clazz.split:
- error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
-
- for m in clazz.methods:
- if not re.match("on[A-Z][a-z]*", m.name):
- error(clazz, m, "L1", "Listener method names must be onFoo() style")
-
- if len(clazz.methods) == 1 and clazz.name.startswith("On"):
- m = clazz.methods[0]
- if (m.name + "Listener").lower() != clazz.name.lower():
- error(clazz, m, "L1", "Single listener method name must match class name")
-
-
-@verifier
-def verify_actions(clazz):
- """Verify intent actions.
- All action names must be named ACTION_FOO.
- All action values must be scoped by package and match name:
- package android.foo {
- String ACTION_BAR = "android.foo.action.BAR";
- }"""
- for f in clazz.fields:
- if f.value is None: continue
- if f.name.startswith("EXTRA_"): continue
- if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
- if "INTERACTION" in f.name: continue
-
- if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
- if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
- if not f.name.startswith("ACTION_"):
- error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
- else:
- if clazz.fullname == "android.content.Intent":
- prefix = "android.intent.action"
- elif clazz.fullname == "android.provider.Settings":
- prefix = "android.settings"
- elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
- prefix = "android.app.action"
- else:
- prefix = clazz.pkg.name + ".action"
- expected = prefix + "." + f.name[7:]
- if f.value != expected:
- error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
-
-
-@verifier
-def verify_extras(clazz):
- """Verify intent extras.
- All extra names must be named EXTRA_FOO.
- All extra values must be scoped by package and match name:
- package android.foo {
- String EXTRA_BAR = "android.foo.extra.BAR";
- }"""
- if clazz.fullname == "android.app.Notification": return
- if clazz.fullname == "android.appwidget.AppWidgetManager": return
-
- for f in clazz.fields:
- if f.value is None: continue
- if f.name.startswith("ACTION_"): continue
-
- if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
- if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
- if not f.name.startswith("EXTRA_"):
- error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
- else:
- if clazz.pkg.name == "android.content" and clazz.name == "Intent":
- prefix = "android.intent.extra"
- elif clazz.pkg.name == "android.app.admin":
- prefix = "android.app.extra"
- else:
- prefix = clazz.pkg.name + ".extra"
- expected = prefix + "." + f.name[6:]
- if f.value != expected:
- error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
-
-
-@verifier
-def verify_equals(clazz):
- """Verify that equals() and hashCode() must be overridden together."""
- eq = False
- hc = False
- for m in clazz.methods:
- if "static" in m.split: continue
- if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
- if m.sig_matches("int", "hashCode", []): hc = True
- if eq != hc:
- error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
-
-
-@verifier
-def verify_parcelable(clazz):
- """Verify that Parcelable objects aren't hiding required bits."""
- if clazz.implements == "android.os.Parcelable":
- creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
- write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
- describe = [ i for i in clazz.methods if i.name == "describeContents" ]
-
- if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
- error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
-
- if "final" not in clazz.split:
- error(clazz, None, "FW8", "Parcelable classes must be final")
-
- for c in clazz.ctors:
- if c.args == ["android.os.Parcel"]:
- error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
-
-
-@verifier
-def verify_protected(clazz):
- """Verify that no protected methods or fields are allowed."""
- for m in clazz.methods:
- if m.name == "finalize": continue
- if "protected" in m.split:
- error(clazz, m, "M7", "Protected methods not allowed; must be public")
- for f in clazz.fields:
- if "protected" in f.split:
- error(clazz, f, "M7", "Protected fields not allowed; must be public")
-
-
-@verifier
-def verify_fields(clazz):
- """Verify that all exposed fields are final.
- Exposed fields must follow myName style.
- Catch internal mFoo objects being exposed."""
-
- IGNORE_BARE_FIELDS = [
- "android.app.ActivityManager.RecentTaskInfo",
- "android.app.Notification",
- "android.content.pm.ActivityInfo",
- "android.content.pm.ApplicationInfo",
- "android.content.pm.ComponentInfo",
- "android.content.pm.ResolveInfo",
- "android.content.pm.FeatureGroupInfo",
- "android.content.pm.InstrumentationInfo",
- "android.content.pm.PackageInfo",
- "android.content.pm.PackageItemInfo",
- "android.content.res.Configuration",
- "android.graphics.BitmapFactory.Options",
- "android.os.Message",
- "android.system.StructPollfd",
- ]
-
- for f in clazz.fields:
- if not "final" in f.split:
- if clazz.fullname in IGNORE_BARE_FIELDS:
- pass
- elif clazz.fullname.endswith("LayoutParams"):
- pass
- elif clazz.fullname.startswith("android.util.Mutable"):
- pass
- else:
- error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
-
- if "static" not in f.split and "property" not in f.split:
- if not re.match("[a-z]([a-zA-Z]+)?", f.name):
- error(clazz, f, "S1", "Non-static fields must be named using myField style")
-
- if re.match("[ms][A-Z]", f.name):
- error(clazz, f, "F1", "Internal objects must not be exposed")
-
- if re.match("[A-Z_]+", f.name):
- if "static" not in f.split or "final" not in f.split:
- error(clazz, f, "C2", "Constants must be marked static final")
-
-
-@verifier
-def verify_register(clazz):
- """Verify parity of registration methods.
- Callback objects use register/unregister methods.
- Listener objects use add/remove methods."""
- methods = [ m.name for m in clazz.methods ]
- for m in clazz.methods:
- if "Callback" in m.raw:
- if m.name.startswith("register"):
- other = "unregister" + m.name[8:]
- if other not in methods:
- error(clazz, m, "L2", "Missing unregister method")
- if m.name.startswith("unregister"):
- other = "register" + m.name[10:]
- if other not in methods:
- error(clazz, m, "L2", "Missing register method")
-
- if m.name.startswith("add") or m.name.startswith("remove"):
- error(clazz, m, "L3", "Callback methods should be named register/unregister")
-
- if "Listener" in m.raw:
- if m.name.startswith("add"):
- other = "remove" + m.name[3:]
- if other not in methods:
- error(clazz, m, "L2", "Missing remove method")
- if m.name.startswith("remove") and not m.name.startswith("removeAll"):
- other = "add" + m.name[6:]
- if other not in methods:
- error(clazz, m, "L2", "Missing add method")
-
- if m.name.startswith("register") or m.name.startswith("unregister"):
- error(clazz, m, "L3", "Listener methods should be named add/remove")
-
-
-@verifier
-def verify_sync(clazz):
- """Verify synchronized methods aren't exposed."""
- for m in clazz.methods:
- if "synchronized" in m.split:
- error(clazz, m, "M5", "Internal locks must not be exposed")
-
-
-@verifier
-def verify_intent_builder(clazz):
- """Verify that Intent builders are createFooIntent() style."""
- if clazz.name == "Intent": return
-
- for m in clazz.methods:
- if m.typ == "android.content.Intent":
- if m.name.startswith("create") and m.name.endswith("Intent"):
- pass
- else:
- warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
-
-
-@verifier
-def verify_helper_classes(clazz):
- """Verify that helper classes are named consistently with what they extend.
- All developer extendable methods should be named onFoo()."""
- test_methods = False
- if clazz.extends == "android.app.Service":
- test_methods = True
- if not clazz.name.endswith("Service"):
- error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
-
- found = False
- for f in clazz.fields:
- if f.name == "SERVICE_INTERFACE":
- found = True
- if f.value != clazz.fullname:
- error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
-
- if clazz.extends == "android.content.ContentProvider":
- test_methods = True
- if not clazz.name.endswith("Provider"):
- error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
-
- found = False
- for f in clazz.fields:
- if f.name == "PROVIDER_INTERFACE":
- found = True
- if f.value != clazz.fullname:
- error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
-
- if clazz.extends == "android.content.BroadcastReceiver":
- test_methods = True
- if not clazz.name.endswith("Receiver"):
- error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
-
- if clazz.extends == "android.app.Activity":
- test_methods = True
- if not clazz.name.endswith("Activity"):
- error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
-
- if test_methods:
- for m in clazz.methods:
- if "final" in m.split: continue
- if not re.match("on[A-Z]", m.name):
- if "abstract" in m.split:
- warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
- else:
- warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
-
-
-@verifier
-def verify_builder(clazz):
- """Verify builder classes.
- Methods should return the builder to enable chaining."""
- if clazz.extends: return
- if not clazz.name.endswith("Builder"): return
-
- if clazz.name != "Builder":
- warn(clazz, None, None, "Builder should be defined as inner class")
-
- has_build = False
- for m in clazz.methods:
- if m.name == "build":
- has_build = True
- continue
-
- if m.name.startswith("get"): continue
- if m.name.startswith("clear"): continue
-
- if m.name.startswith("with"):
- warn(clazz, m, None, "Builder methods names should use setFoo() style")
-
- if m.name.startswith("set"):
- if not m.typ.endswith(clazz.fullname):
- warn(clazz, m, "M4", "Methods must return the builder object")
-
- if not has_build:
- warn(clazz, None, None, "Missing build() method")
-
- if "final" not in clazz.split:
- error(clazz, None, None, "Builder should be final")
-
-
-@verifier
-def verify_aidl(clazz):
- """Catch people exposing raw AIDL."""
- if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
- error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
-
-
-@verifier
-def verify_internal(clazz):
- """Catch people exposing internal classes."""
- if clazz.pkg.name.startswith("com.android"):
- error(clazz, None, None, "Internal classes must not be exposed")
-
-def layering_build_ranking(ranking_list):
- r = {}
- for rank, ps in enumerate(ranking_list):
- if not isinstance(ps, list):
- ps = [ps]
- for p in ps:
- rs = r
- for n in p.split('.'):
- if n not in rs:
- rs[n] = {}
- rs = rs[n]
- rs['-rank'] = rank
- return r
-
-LAYERING_PACKAGE_RANKING = layering_build_ranking([
- ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
- "android.app",
- "android.widget",
- "android.view",
- "android.animation",
- "android.provider",
- ["android.content","android.graphics.drawable"],
- "android.database",
- "android.text",
- "android.graphics",
- "android.os",
- "android.util"
-])
-
-@verifier
-def verify_layering(clazz):
- """Catch package layering violations.
- For example, something in android.os depending on android.app."""
-
- def rank(p):
- r = None
- l = LAYERING_PACKAGE_RANKING
- for n in p.split('.'):
- if n in l:
- l = l[n]
- if '-rank' in l:
- r = l['-rank']
- else:
- break
- return r
-
- cr = rank(clazz.pkg.name)
- if cr is None: return
-
- for f in clazz.fields:
- ir = rank(f.typ)
- if ir is not None and ir < cr:
- warn(clazz, f, "FW6", "Field type violates package layering")
-
- for m in itertools.chain(clazz.methods, clazz.ctors):
- ir = rank(m.typ)
- if ir is not None and ir < cr:
- warn(clazz, m, "FW6", "Method return type violates package layering")
- for arg in m.args:
- ir = rank(arg)
- if ir is not None and ir < cr:
- warn(clazz, m, "FW6", "Method argument type violates package layering")
-
-
-@verifier
-def verify_boolean(clazz):
- """Verifies that boolean accessors are named correctly.
- For example, hasFoo() and setHasFoo()."""
-
- def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
- def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
-
- gets = [ m for m in clazz.methods if is_get(m) ]
- sets = [ m for m in clazz.methods if is_set(m) ]
-
- def error_if_exists(methods, trigger, expected, actual):
- for m in methods:
- if m.name == actual:
- error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
-
- for m in clazz.methods:
- if is_get(m):
- if re.match("is[A-Z]", m.name):
- target = m.name[2:]
- expected = "setIs" + target
- error_if_exists(sets, m.name, expected, "setHas" + target)
- elif re.match("has[A-Z]", m.name):
- target = m.name[3:]
- expected = "setHas" + target
- error_if_exists(sets, m.name, expected, "setIs" + target)
- error_if_exists(sets, m.name, expected, "set" + target)
- elif re.match("get[A-Z]", m.name):
- target = m.name[3:]
- expected = "set" + target
- error_if_exists(sets, m.name, expected, "setIs" + target)
- error_if_exists(sets, m.name, expected, "setHas" + target)
-
- if is_set(m):
- if re.match("set[A-Z]", m.name):
- target = m.name[3:]
- expected = "get" + target
- error_if_exists(sets, m.name, expected, "is" + target)
- error_if_exists(sets, m.name, expected, "has" + target)
-
-
-@verifier
-def verify_collections(clazz):
- """Verifies that collection types are interfaces."""
- if clazz.fullname == "android.os.Bundle": return
- if clazz.fullname == "android.os.Parcel": return
-
- bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
- "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
- for m in clazz.methods:
- if m.typ in bad:
- error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
- for arg in m.args:
- if arg in bad:
- error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
-
-
-@verifier
-def verify_uris(clazz):
- bad = ["java.net.URL", "java.net.URI", "android.net.URL"]
-
- for f in clazz.fields:
- if f.typ in bad:
- error(clazz, f, None, "Field must be android.net.Uri instead of " + f.typ)
-
- for m in clazz.methods + clazz.ctors:
- if m.typ in bad:
- error(clazz, m, None, "Must return android.net.Uri instead of " + m.typ)
- for arg in m.args:
- if arg in bad:
- error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg)
-
-
-@verifier
-def verify_flags(clazz):
- """Verifies that flags are non-overlapping."""
- known = collections.defaultdict(int)
- for f in clazz.fields:
- if "FLAG_" in f.name:
- try:
- val = int(f.value)
- except:
- continue
-
- scope = f.name[0:f.name.index("FLAG_")]
- if val & known[scope]:
- warn(clazz, f, "C1", "Found overlapping flag constant value")
- known[scope] |= val
-
-
-@verifier
-def verify_exception(clazz):
- """Verifies that methods don't throw generic exceptions."""
- for m in clazz.methods:
- for t in m.throws:
- if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
- error(clazz, m, "S1", "Methods must not throw generic exceptions")
-
- if t in ["android.os.RemoteException"]:
- if clazz.fullname == "android.content.ContentProviderClient": continue
- if clazz.fullname == "android.os.Binder": continue
- if clazz.fullname == "android.os.IBinder": continue
-
- error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
-
- if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
- warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
-
-
-GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
-
-# Not marked as @verifier, because it is only conditionally applied.
-def verify_google(clazz):
- """Verifies that APIs never reference Google."""
-
- if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
- error(clazz, None, None, "Must never reference Google")
-
- for test in clazz.ctors, clazz.fields, clazz.methods:
- for t in test:
- if GOOGLE_IGNORECASE.search(t.raw) is not None:
- error(clazz, t, None, "Must never reference Google")
-
-
-@verifier
-def verify_bitset(clazz):
- """Verifies that we avoid using heavy BitSet."""
-
- for f in clazz.fields:
- if f.typ == "java.util.BitSet":
- error(clazz, f, None, "Field type must not be heavy BitSet")
-
- for m in clazz.methods:
- if m.typ == "java.util.BitSet":
- error(clazz, m, None, "Return type must not be heavy BitSet")
- for arg in m.args:
- if arg == "java.util.BitSet":
- error(clazz, m, None, "Argument type must not be heavy BitSet")
-
-
-@verifier
-def verify_manager(clazz):
- """Verifies that FooManager is only obtained from Context."""
-
- if not clazz.name.endswith("Manager"): return
-
- for c in clazz.ctors:
- error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
-
- for m in clazz.methods:
- if m.typ == clazz.fullname:
- error(clazz, m, None, "Managers must always be obtained from Context")
-
-
-@verifier
-def verify_boxed(clazz):
- """Verifies that methods avoid boxed primitives."""
-
- boxed = ["java.lang.Number","java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
-
- for c in clazz.ctors:
- for arg in c.args:
- if arg in boxed:
- error(clazz, c, "M11", "Must avoid boxed primitives")
-
- for f in clazz.fields:
- if f.typ in boxed:
- error(clazz, f, "M11", "Must avoid boxed primitives")
-
- for m in clazz.methods:
- if m.typ in boxed:
- error(clazz, m, "M11", "Must avoid boxed primitives")
- for arg in m.args:
- if arg in boxed:
- error(clazz, m, "M11", "Must avoid boxed primitives")
-
-
-@verifier
-def verify_static_utils(clazz):
- """Verifies that helper classes can't be constructed."""
- if clazz.fullname.startswith("android.opengl"): return
- if clazz.fullname.startswith("android.R"): return
-
- # Only care about classes with default constructors
- if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
- test = []
- test.extend(clazz.fields)
- test.extend(clazz.methods)
-
- if len(test) == 0: return
- for t in test:
- if "static" not in t.split:
- return
-
- error(clazz, None, None, "Fully-static utility classes must not have constructor")
-
-
-# @verifier # Disabled for now
-def verify_overload_args(clazz):
- """Verifies that method overloads add new arguments at the end."""
- if clazz.fullname.startswith("android.opengl"): return
-
- overloads = collections.defaultdict(list)
- for m in clazz.methods:
- if "deprecated" in m.split: continue
- overloads[m.name].append(m)
-
- for name, methods in overloads.items():
- if len(methods) <= 1: continue
-
- # Look for arguments common across all overloads
- def cluster(args):
- count = collections.defaultdict(int)
- res = set()
- for i in range(len(args)):
- a = args[i]
- res.add("%s#%d" % (a, count[a]))
- count[a] += 1
- return res
-
- common_args = cluster(methods[0].args)
- for m in methods:
- common_args = common_args & cluster(m.args)
-
- if len(common_args) == 0: continue
-
- # Require that all common arguments are present at start of signature
- locked_sig = None
- for m in methods:
- sig = m.args[0:len(common_args)]
- if not common_args.issubset(cluster(sig)):
- warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
- elif not locked_sig:
- locked_sig = sig
- elif locked_sig != sig:
- error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
-
-
-@verifier
-def verify_callback_handlers(clazz):
- """Verifies that methods adding listener/callback have overload
- for specifying delivery thread."""
-
- # Ignore UI packages which assume main thread
- skip = [
- "animation",
- "view",
- "graphics",
- "transition",
- "widget",
- "webkit",
- ]
- for s in skip:
- if s in clazz.pkg.name_path: return
- if s in clazz.extends_path: return
-
- # Ignore UI classes which assume main thread
- if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
- for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
- if s in clazz.fullname: return
- if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
- for s in ["Loader"]:
- if s in clazz.fullname: return
-
- found = {}
- by_name = collections.defaultdict(list)
- examine = clazz.ctors + clazz.methods
- for m in examine:
- if m.name.startswith("unregister"): continue
- if m.name.startswith("remove"): continue
- if re.match("on[A-Z]+", m.name): continue
-
- by_name[m.name].append(m)
-
- for a in m.args:
- if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
- found[m.name] = m
-
- for f in found.values():
- takes_handler = False
- takes_exec = False
- for m in by_name[f.name]:
- if "android.os.Handler" in m.args:
- takes_handler = True
- if "java.util.concurrent.Executor" in m.args:
- takes_exec = True
- if not takes_exec:
- warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
-
-
-@verifier
-def verify_context_first(clazz):
- """Verifies that methods accepting a Context keep it the first argument."""
- examine = clazz.ctors + clazz.methods
- for m in examine:
- if len(m.args) > 1 and m.args[0] != "android.content.Context":
- if "android.content.Context" in m.args[1:]:
- error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
- if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
- if "android.content.ContentResolver" in m.args[1:]:
- error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
-
-
-@verifier
-def verify_listener_last(clazz):
- """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
- examine = clazz.ctors + clazz.methods
- for m in examine:
- if "Listener" in m.name or "Callback" in m.name: continue
- found = False
- for a in m.args:
- if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
- found = True
- elif found:
- warn(clazz, m, "M3", "Listeners should always be at end of argument list")
-
-
-@verifier
-def verify_resource_names(clazz):
- """Verifies that resource names have consistent case."""
- if not re.match("android\.R\.[a-z]+", clazz.fullname): return
-
- # Resources defined by files are foo_bar_baz
- if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
- for f in clazz.fields:
- if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
- if f.name.startswith("config_"):
- error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
-
- if re.match("[a-z1-9_]+$", f.name): continue
- error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
-
- # Resources defined inside files are fooBarBaz
- if clazz.name in ["array","attr","id","bool","fraction","integer"]:
- for f in clazz.fields:
- if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
- if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
- if re.match("state_[a-z_]*$", f.name): continue
-
- if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
- error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
-
- # Styles are FooBar_Baz
- if clazz.name in ["style"]:
- for f in clazz.fields:
- if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
- error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
-
-
-@verifier
-def verify_files(clazz):
- """Verifies that methods accepting File also accept streams."""
-
- has_file = set()
- has_stream = set()
-
- test = []
- test.extend(clazz.ctors)
- test.extend(clazz.methods)
-
- for m in test:
- if "java.io.File" in m.args:
- has_file.add(m)
- if "java.io.FileDescriptor" in m.args or "android.os.ParcelFileDescriptor" in m.args or "java.io.InputStream" in m.args or "java.io.OutputStream" in m.args:
- has_stream.add(m.name)
-
- for m in has_file:
- if m.name not in has_stream:
- warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
-
-
-@verifier
-def verify_manager_list(clazz):
- """Verifies that managers return List<? extends Parcelable> instead of arrays."""
-
- if not clazz.name.endswith("Manager"): return
-
- for m in clazz.methods:
- if m.typ.startswith("android.") and m.typ.endswith("[]"):
- warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
-
-
-@verifier
-def verify_abstract_inner(clazz):
- """Verifies that abstract inner classes are static."""
-
- if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
- if "abstract" in clazz.split and "static" not in clazz.split:
- warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
-
-
-@verifier
-def verify_runtime_exceptions(clazz):
- """Verifies that runtime exceptions aren't listed in throws."""
-
- banned = [
- "java.lang.NullPointerException",
- "java.lang.ClassCastException",
- "java.lang.IndexOutOfBoundsException",
- "java.lang.reflect.UndeclaredThrowableException",
- "java.lang.reflect.MalformedParametersException",
- "java.lang.reflect.MalformedParameterizedTypeException",
- "java.lang.invoke.WrongMethodTypeException",
- "java.lang.EnumConstantNotPresentException",
- "java.lang.IllegalMonitorStateException",
- "java.lang.SecurityException",
- "java.lang.UnsupportedOperationException",
- "java.lang.annotation.AnnotationTypeMismatchException",
- "java.lang.annotation.IncompleteAnnotationException",
- "java.lang.TypeNotPresentException",
- "java.lang.IllegalStateException",
- "java.lang.ArithmeticException",
- "java.lang.IllegalArgumentException",
- "java.lang.ArrayStoreException",
- "java.lang.NegativeArraySizeException",
- "java.util.MissingResourceException",
- "java.util.EmptyStackException",
- "java.util.concurrent.CompletionException",
- "java.util.concurrent.RejectedExecutionException",
- "java.util.IllformedLocaleException",
- "java.util.ConcurrentModificationException",
- "java.util.NoSuchElementException",
- "java.io.UncheckedIOException",
- "java.time.DateTimeException",
- "java.security.ProviderException",
- "java.nio.BufferUnderflowException",
- "java.nio.BufferOverflowException",
- ]
-
- examine = clazz.ctors + clazz.methods
- for m in examine:
- for t in m.throws:
- if t in banned:
- error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
-
-
-@verifier
-def verify_error(clazz):
- """Verifies that we always use Exception instead of Error."""
- if not clazz.extends: return
- if clazz.extends.endswith("Error"):
- error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
- if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
- error(clazz, None, None, "Exceptions must be named FooException")
-
-
-@verifier
-def verify_units(clazz):
- """Verifies that we use consistent naming for units."""
-
- # If we find K, recommend replacing with V
- bad = {
- "Ns": "Nanos",
- "Ms": "Millis or Micros",
- "Sec": "Seconds", "Secs": "Seconds",
- "Hr": "Hours", "Hrs": "Hours",
- "Mo": "Months", "Mos": "Months",
- "Yr": "Years", "Yrs": "Years",
- "Byte": "Bytes", "Space": "Bytes",
- }
-
- for m in clazz.methods:
- if m.typ not in ["short","int","long"]: continue
- for k, v in bad.iteritems():
- if m.name.endswith(k):
- error(clazz, m, None, "Expected method name units to be " + v)
- if m.name.endswith("Nanos") or m.name.endswith("Micros"):
- warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
- if m.name.endswith("Seconds"):
- error(clazz, m, None, "Returned time values must be in milliseconds")
-
- for m in clazz.methods:
- typ = m.typ
- if typ == "void":
- if len(m.args) != 1: continue
- typ = m.args[0]
-
- if m.name.endswith("Fraction") and typ != "float":
- error(clazz, m, None, "Fractions must use floats")
- if m.name.endswith("Percentage") and typ != "int":
- error(clazz, m, None, "Percentage must use ints")
-
-
-@verifier
-def verify_closable(clazz):
- """Verifies that classes are AutoClosable."""
- if "java.lang.AutoCloseable" in clazz.implements_all: return
- if "java.io.Closeable" in clazz.implements_all: return
-
- for m in clazz.methods:
- if len(m.args) > 0: continue
- if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
- warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
- return
-
-
-@verifier
-def verify_member_name_not_kotlin_keyword(clazz):
- """Prevent method names which are keywords in Kotlin."""
-
- # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
- # This list does not include Java keywords as those are already impossible to use.
- keywords = [
- 'as',
- 'fun',
- 'in',
- 'is',
- 'object',
- 'typealias',
- 'val',
- 'var',
- 'when',
- ]
-
- for m in clazz.methods:
- if m.name in keywords:
- error(clazz, m, None, "Method name must not be a Kotlin keyword")
- for f in clazz.fields:
- if f.name in keywords:
- error(clazz, f, None, "Field name must not be a Kotlin keyword")
-
-
-@verifier
-def verify_method_name_not_kotlin_operator(clazz):
- """Warn about method names which become operators in Kotlin."""
-
- binary = set()
-
- def unique_binary_op(m, op):
- if op in binary:
- error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
- binary.add(op)
-
- for m in clazz.methods:
- if 'static' in m.split or 'operator' in m.split:
- continue
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
- if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
- warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
- if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
- # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
- # practical way of checking that relationship here.
- warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
- if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
- warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
- unique_binary_op(m, m.name)
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#in
- if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
- warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
- if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
- warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
- if m.name == 'invoke':
- warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
-
- # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
- if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
- and len(m.args) == 1 \
- and m.typ == 'void':
- warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
- unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
-
-
-@verifier
-def verify_collections_over_arrays(clazz):
- """Warn that [] should be Collections."""
-
- if "@interface" in clazz.split:
- return
-
- safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
- for m in clazz.methods:
- if m.typ.endswith("[]") and m.typ not in safe:
- warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
- for arg in m.args:
- if arg.endswith("[]") and arg not in safe:
- warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
-
-
-@verifier
-def verify_user_handle(clazz):
- """Methods taking UserHandle should be ForUser or AsUser."""
- if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
- if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
- if clazz.fullname == "android.content.pm.LauncherApps": return
- if clazz.fullname == "android.os.UserHandle": return
- if clazz.fullname == "android.os.UserManager": return
-
- for m in clazz.methods:
- if re.match("on[A-Z]+", m.name): continue
-
- has_arg = "android.os.UserHandle" in m.args
- has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
-
- if clazz.fullname.endswith("Manager") and has_arg:
- warn(clazz, m, None, "When a method overload is needed to target a specific "
- "UserHandle, callers should be directed to use "
- "Context.createPackageContextAsUser() and re-obtain the relevant "
- "Manager, and no new API should be added")
- elif has_arg and not has_name:
- warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
- "or 'queryFooForUser'")
-
-
-@verifier
-def verify_params(clazz):
- """Parameter classes should be 'Params'."""
- if clazz.name.endswith("Params"): return
- if clazz.fullname == "android.app.ActivityOptions": return
- if clazz.fullname == "android.app.BroadcastOptions": return
- if clazz.fullname == "android.os.Bundle": return
- if clazz.fullname == "android.os.BaseBundle": return
- if clazz.fullname == "android.os.PersistableBundle": return
-
- bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
- for b in bad:
- if clazz.name.endswith(b):
- error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
-
-
-@verifier
-def verify_services(clazz):
- """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
- if clazz.fullname != "android.content.Context": return
-
- for f in clazz.fields:
- if f.typ != "java.lang.String": continue
- found = re.match(r"([A-Z_]+)_SERVICE", f.name)
- if found:
- expected = found.group(1).lower()
- if f.value != expected:
- error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
-
-
-@verifier
-def verify_tense(clazz):
- """Verify tenses of method names."""
- if clazz.fullname.startswith("android.opengl"): return
-
- for m in clazz.methods:
- if m.name.endswith("Enable"):
- warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
-
-
-@verifier
-def verify_icu(clazz):
- """Verifies that richer ICU replacements are used."""
- better = {
- "java.util.TimeZone": "android.icu.util.TimeZone",
- "java.util.Calendar": "android.icu.util.Calendar",
- "java.util.Locale": "android.icu.util.ULocale",
- "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
- "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
- "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
- "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
- "java.lang.Character": "android.icu.lang.UCharacter",
- "java.text.BreakIterator": "android.icu.text.BreakIterator",
- "java.text.Collator": "android.icu.text.Collator",
- "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
- "java.text.NumberFormat": "android.icu.text.NumberFormat",
- "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
- "java.text.DateFormat": "android.icu.text.DateFormat",
- "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
- "java.text.MessageFormat": "android.icu.text.MessageFormat",
- "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
- }
-
- for m in clazz.ctors + clazz.methods:
- types = []
- types.extend(m.typ)
- types.extend(m.args)
- for arg in types:
- if arg in better:
- warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
-
-
-@verifier
-def verify_clone(clazz):
- """Verify that clone() isn't implemented; see EJ page 61."""
- for m in clazz.methods:
- if m.name == "clone":
- error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
-
-
-@verifier
-def verify_pfd(clazz):
- """Verify that android APIs use PFD over FD."""
- if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
- return
-
- examine = clazz.ctors + clazz.methods
- for m in examine:
- if m.typ == "java.io.FileDescriptor":
- error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
- if m.typ == "int":
- if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
- error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
- for arg in m.args:
- if arg == "java.io.FileDescriptor":
- error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
-
- for f in clazz.fields:
- if f.typ == "java.io.FileDescriptor":
- error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
-
-
-@verifier
-def verify_numbers(clazz):
- """Discourage small numbers types like short and byte."""
-
- discouraged = ["short","byte"]
-
- for c in clazz.ctors:
- for arg in c.args:
- if arg in discouraged:
- warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
-
- for f in clazz.fields:
- if f.typ in discouraged:
- warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
-
- for m in clazz.methods:
- if m.typ in discouraged:
- warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
- for arg in m.args:
- if arg in discouraged:
- warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
-
-
-PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
-
-@verifier
-def verify_nullability(clazz):
- """Catches missing nullability annotations"""
-
- for f in clazz.fields:
- if f.value is not None and 'static' in f.split and 'final' in f.split:
- continue # Nullability of constants can be inferred.
- if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
- error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
-
- for c in clazz.ctors:
- verify_nullability_args(clazz, c)
-
- for m in clazz.methods:
- if m.name == "writeToParcel" or m.name == "onReceive":
- continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
-
- if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
- error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
- verify_nullability_args(clazz, m)
-
-def verify_nullability_args(clazz, m):
- for i, arg in enumerate(m.detailed_args):
- if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
- error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
-
-def has_nullability(annotations):
- return "@NonNull" in annotations or "@Nullable" in annotations
-
-
-@verifier
-def verify_intdef(clazz):
- """intdefs must be @hide, because the constant names cannot be stored in
- the stubs (only the values are, which is not useful)"""
- if "@interface" not in clazz.split:
- return
- if "@IntDef" in clazz.annotations or "@LongDef" in clazz.annotations:
- error(clazz, None, None, "@IntDef and @LongDef annotations must be @hide")
-
-
-@verifier
-def verify_singleton(clazz):
- """Catch singleton objects with constructors."""
-
- singleton = False
- for m in clazz.methods:
- if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
- singleton = True
-
- if singleton:
- for c in clazz.ctors:
- error(clazz, c, None, "Singleton classes should use getInstance() methods")
-
-
-
-def is_interesting(clazz):
- """Test if given class is interesting from an Android PoV."""
-
- if clazz.pkg.name.startswith("java"): return False
- if clazz.pkg.name.startswith("junit"): return False
- if clazz.pkg.name.startswith("org.apache"): return False
- if clazz.pkg.name.startswith("org.xml"): return False
- if clazz.pkg.name.startswith("org.json"): return False
- if clazz.pkg.name.startswith("org.w3c"): return False
- if clazz.pkg.name.startswith("android.icu."): return False
- return True
-
-
-def examine_clazz(clazz):
- """Find all style issues in the given class."""
-
- notice(clazz)
-
- if not is_interesting(clazz): return
-
- for v in verifiers.itervalues():
- v(clazz)
-
- if not ALLOW_GOOGLE: verify_google(clazz)
-
-
-def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
- """Find all style issues in the given API stream."""
- global failures, noticed
- failures = {}
- noticed = {}
- _parse_stream(stream, examine_clazz, base_f=base_stream,
- in_classes_with_base=in_classes_with_base,
- out_classes_with_base=out_classes_with_base)
- return (failures, noticed)
-
-
-def examine_api(api):
- """Find all style issues in the given parsed API."""
- global failures
- failures = {}
- for key in sorted(api.keys()):
- examine_clazz(api[key])
- return failures
-
-
-def verify_compat(cur, prev):
- """Find any incompatible API changes between two levels."""
- global failures
-
- def class_exists(api, test):
- return test.fullname in api
-
- def ctor_exists(api, clazz, test):
- for m in clazz.ctors:
- if m.ident == test.ident: return True
- return False
-
- def all_methods(api, clazz):
- methods = list(clazz.methods)
- if clazz.extends is not None:
- methods.extend(all_methods(api, api[clazz.extends]))
- return methods
-
- def method_exists(api, clazz, test):
- methods = all_methods(api, clazz)
- for m in methods:
- if m.ident == test.ident: return True
- return False
-
- def field_exists(api, clazz, test):
- for f in clazz.fields:
- if f.ident == test.ident: return True
- return False
-
- failures = {}
- for key in sorted(prev.keys()):
- prev_clazz = prev[key]
-
- if not class_exists(cur, prev_clazz):
- error(prev_clazz, None, None, "Class removed or incompatible change")
- continue
-
- cur_clazz = cur[key]
-
- for test in prev_clazz.ctors:
- if not ctor_exists(cur, cur_clazz, test):
- error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
-
- methods = all_methods(prev, prev_clazz)
- for test in methods:
- if not method_exists(cur, cur_clazz, test):
- error(prev_clazz, test, None, "Method removed or incompatible change")
-
- for test in prev_clazz.fields:
- if not field_exists(cur, cur_clazz, test):
- error(prev_clazz, test, None, "Field removed or incompatible change")
-
- return failures
-
-
-def match_filter(filters, fullname):
- for f in filters:
- if fullname == f:
- return True
- if fullname.startswith(f + '.'):
- return True
- return False
-
-
-def show_deprecations_at_birth(cur, prev):
- """Show API deprecations at birth."""
- global failures
-
- # Remove all existing things so we're left with new
- for prev_clazz in prev.values():
- if prev_clazz.fullname not in cur:
- # The class was removed this release; we can safely ignore it.
- continue
-
- cur_clazz = cur[prev_clazz.fullname]
- if not is_interesting(cur_clazz): continue
-
- sigs = { i.ident: i for i in prev_clazz.ctors }
- cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
- sigs = { i.ident: i for i in prev_clazz.methods }
- cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
- sigs = { i.ident: i for i in prev_clazz.fields }
- cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
-
- # Forget about class entirely when nothing new
- if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
- del cur[prev_clazz.fullname]
-
- for clazz in cur.values():
- if not is_interesting(clazz): continue
-
- if "deprecated" in clazz.split and not clazz.fullname in prev:
- error(clazz, None, None, "Found API deprecation at birth")
-
- for i in clazz.ctors + clazz.methods + clazz.fields:
- if "deprecated" in i.split:
- error(clazz, i, None, "Found API deprecation at birth")
-
- print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
- format(reset=True)))
- for f in sorted(failures):
- print failures[f]
- print
-
-
-def show_stats(cur, prev):
- """Show API stats."""
-
- stats = collections.defaultdict(int)
- for cur_clazz in cur.values():
- if not is_interesting(cur_clazz): continue
-
- if cur_clazz.fullname not in prev:
- stats['new_classes'] += 1
- stats['new_ctors'] += len(cur_clazz.ctors)
- stats['new_methods'] += len(cur_clazz.methods)
- stats['new_fields'] += len(cur_clazz.fields)
- else:
- prev_clazz = prev[cur_clazz.fullname]
-
- sigs = { i.ident: i for i in prev_clazz.ctors }
- ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
- sigs = { i.ident: i for i in prev_clazz.methods }
- methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
- sigs = { i.ident: i for i in prev_clazz.fields }
- fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
-
- if ctors + methods + fields > 0:
- stats['extend_classes'] += 1
- stats['extend_ctors'] += ctors
- stats['extend_methods'] += methods
- stats['extend_fields'] += fields
-
- print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
- print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
-
-
-def main():
- parser = argparse.ArgumentParser(description="Enforces common Android public API design \
- patterns. It ignores lint messages from a previous API level, if provided.")
- parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
- help="The base current.txt to use when examining system-current.txt or"
- " test-current.txt")
- parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
- help="The base previous.txt to use when examining system-previous.txt or"
- " test-previous.txt")
- parser.add_argument("--no-color", action='store_const', const=True,
- help="Disable terminal colors")
- parser.add_argument("--color", action='store_const', const=True,
- help="Use terminal colors")
- parser.add_argument("--allow-google", action='store_const', const=True,
- help="Allow references to Google")
- parser.add_argument("--show-noticed", action='store_const', const=True,
- help="Show API changes noticed")
- parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
- help="Show API deprecations at birth")
- parser.add_argument("--show-stats", action='store_const', const=True,
- help="Show API stats")
- parser.add_argument("--title", action='store', default=None,
- help="Title to put in for display purposes")
- parser.add_argument("--filter", action="append",
- help="If provided, only show lint for the given packages or classes.")
- parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
- parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
- help="previous.txt")
- args = vars(parser.parse_args())
-
- if args['no_color']:
- USE_COLOR = False
- elif args['color']:
- USE_COLOR = True
- else:
- USE_COLOR = sys.stdout.isatty()
-
- if args['allow_google']:
- ALLOW_GOOGLE = True
-
- current_file = args['current.txt']
- base_current_file = args['base_current']
- previous_file = args['previous.txt']
- base_previous_file = args['base_previous']
- filters = args['filter']
- if not filters:
- filters = []
- title = args['title']
- if not title:
- title = current_file.name
-
- if args['show_deprecations_at_birth']:
- with current_file as f:
- cur = _parse_stream(f)
- with previous_file as f:
- prev = _parse_stream(f)
- show_deprecations_at_birth(cur, prev)
- sys.exit()
-
- if args['show_stats']:
- with current_file as f:
- cur = _parse_stream(f)
- with previous_file as f:
- prev = _parse_stream(f)
- show_stats(cur, prev)
- sys.exit()
-
- classes_with_base = []
-
- with current_file as f:
- if base_current_file:
- with base_current_file as base_f:
- cur_fail, cur_noticed = examine_stream(f, base_f,
- out_classes_with_base=classes_with_base)
- else:
- cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
-
- if not previous_file is None:
- with previous_file as f:
- if base_previous_file:
- with base_previous_file as base_f:
- prev_fail, prev_noticed = examine_stream(f, base_f,
- in_classes_with_base=classes_with_base)
- else:
- prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
-
- # ignore errors from previous API level
- for p in prev_fail:
- if p in cur_fail:
- del cur_fail[p]
-
- # ignore classes unchanged from previous API level
- for k, v in prev_noticed.iteritems():
- if k in cur_noticed and v == cur_noticed[k]:
- del cur_noticed[k]
-
- """
- # NOTE: disabled because of memory pressure
- # look for compatibility issues
- compat_fail = verify_compat(cur, prev)
-
- print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
- for f in sorted(compat_fail):
- print compat_fail[f]
- print
- """
-
- # ignore everything but the given filters, if provided
- if filters:
- cur_fail = dict([(key, failure) for key, failure in cur_fail.iteritems()
- if match_filter(filters, failure.clazz.fullname)])
-
- if args['show_noticed'] and len(cur_noticed) != 0:
- print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
- for f in sorted(cur_noticed.keys()):
- print f
- print
-
- if len(cur_fail) != 0:
- print "%s API style issues: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
- title, format(reset=True)))
- for f in filters:
- print "%s filter: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
- f, format(reset=True)))
- print
- for f in sorted(cur_fail):
- print cur_fail[f]
- print
- print "%d errors" % len(cur_fail)
- sys.exit(77)
-
-if __name__ == "__main__":
- try:
- main()
- except KeyboardInterrupt:
- sys.exit(1)
diff --git a/tools/apilint/apilint_sha.sh b/tools/apilint/apilint_sha.sh
deleted file mode 100755
index 2a45b10392d7..000000000000
--- a/tools/apilint/apilint_sha.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-if git show --name-only --pretty=format: $1 | grep api/ > /dev/null; then
- python tools/apilint/apilint.py <(git show $1:api/current.txt) <(git show $1^:api/current.txt)
-fi
diff --git a/tools/apilint/apilint_sha_system.sh b/tools/apilint/apilint_sha_system.sh
deleted file mode 100755
index 8538a3d904f5..000000000000
--- a/tools/apilint/apilint_sha_system.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2018 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.
-
-if git show --name-only --pretty=format: $1 | grep api/ > /dev/null; then
- python tools/apilint/apilint.py \
- --base-current <(git show $1:api/current.txt) \
- --base-previous <(git show $1^:api/current.txt) \
- <(git show $1:api/system-current.txt) \
- <(git show $1^:api/system-current.txt)
-fi
diff --git a/tools/apilint/apilint_stats.sh b/tools/apilint/apilint_stats.sh
deleted file mode 100755
index 052d9a5265fe..000000000000
--- a/tools/apilint/apilint_stats.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-API=28
-while [ $API -gt 14 ]; do
- echo "# Changes in API $((API))"
- python tools/apilint/apilint.py --show-stats ../../prebuilts/sdk/$((API))/public/api/android.txt ../../prebuilts/sdk/$((API-1))/public/api/android.txt
- let API=API-1
-done
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
deleted file mode 100644
index 811cb9aa23d5..000000000000
--- a/tools/apilint/apilint_test.py
+++ /dev/null
@@ -1,414 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2018 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.
-
-import unittest
-
-import apilint
-
-def cls(pkg, name):
- return apilint.Class(apilint.Package(999, "package %s {" % pkg, None), 999,
- "public final class %s {" % name, None)
-
-_ri = apilint._retry_iterator
-
-c1 = cls("android.app", "ActivityManager")
-c2 = cls("android.app", "Notification")
-c3 = cls("android.app", "Notification.Action")
-c4 = cls("android.graphics", "Bitmap")
-
-class UtilTests(unittest.TestCase):
- def test_retry_iterator(self):
- it = apilint._retry_iterator([1, 2, 3, 4])
- self.assertEqual(it.next(), 1)
- self.assertEqual(it.next(), 2)
- self.assertEqual(it.next(), 3)
- it.send("retry")
- self.assertEqual(it.next(), 3)
- self.assertEqual(it.next(), 4)
- with self.assertRaises(StopIteration):
- it.next()
-
- def test_retry_iterator_one(self):
- it = apilint._retry_iterator([1])
- self.assertEqual(it.next(), 1)
- it.send("retry")
- self.assertEqual(it.next(), 1)
- with self.assertRaises(StopIteration):
- it.next()
-
- def test_retry_iterator_one(self):
- it = apilint._retry_iterator([1])
- self.assertEqual(it.next(), 1)
- it.send("retry")
- self.assertEqual(it.next(), 1)
- with self.assertRaises(StopIteration):
- it.next()
-
- def test_skip_to_matching_class_found(self):
- it = _ri([c1, c2, c3, c4])
- self.assertEquals(apilint._skip_to_matching_class(it, c3),
- c3)
- self.assertEqual(it.next(), c4)
-
- def test_skip_to_matching_class_not_found(self):
- it = _ri([c1, c2, c3, c4])
- self.assertEquals(apilint._skip_to_matching_class(it, cls("android.content", "ContentProvider")),
- None)
- self.assertEqual(it.next(), c4)
-
- def test_yield_until_matching_class_found(self):
- it = _ri([c1, c2, c3, c4])
- self.assertEquals(list(apilint._yield_until_matching_class(it, c3)),
- [c1, c2])
- self.assertEqual(it.next(), c4)
-
- def test_yield_until_matching_class_not_found(self):
- it = _ri([c1, c2, c3, c4])
- self.assertEquals(list(apilint._yield_until_matching_class(it, cls("android.content", "ContentProvider"))),
- [c1, c2, c3])
- self.assertEqual(it.next(), c4)
-
- def test_yield_until_matching_class_None(self):
- it = _ri([c1, c2, c3, c4])
- self.assertEquals(list(apilint._yield_until_matching_class(it, None)),
- [c1, c2, c3, c4])
-
-
-faulty_current_txt = """
-// Signature format: 2.0
-package android.app {
- public final class Activity {
- }
-
- public final class WallpaperColors implements android.os.Parcelable {
- ctor public WallpaperColors(@NonNull android.os.Parcel);
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
- }
-}
-""".strip().split('\n')
-
-ok_current_txt = """
-// Signature format: 2.0
-package android.app {
- public final class Activity {
- }
-
- public final class WallpaperColors implements android.os.Parcelable {
- ctor public WallpaperColors();
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
- }
-}
-""".strip().split('\n')
-
-system_current_txt = """
-// Signature format: 2.0
-package android.app {
- public final class WallpaperColors implements android.os.Parcelable {
- method public int getSomething();
- }
-}
-""".strip().split('\n')
-
-
-
-class BaseFileTests(unittest.TestCase):
- def test_base_file_avoids_errors(self):
- failures, _ = apilint.examine_stream(system_current_txt, ok_current_txt)
- self.assertEquals(failures, {})
-
- def test_class_with_base_finds_same_errors(self):
- failures_with_classes_with_base, _ = apilint.examine_stream("", faulty_current_txt,
- in_classes_with_base=[cls("android.app", "WallpaperColors")])
- failures_with_system_txt, _ = apilint.examine_stream(system_current_txt, faulty_current_txt)
-
- self.assertEquals(failures_with_classes_with_base.keys(), failures_with_system_txt.keys())
-
- def test_classes_with_base_is_emited(self):
- classes_with_base = []
- _, _ = apilint.examine_stream(system_current_txt, faulty_current_txt,
- out_classes_with_base=classes_with_base)
- self.assertEquals(map(lambda x: x.fullname, classes_with_base), ["android.app.WallpaperColors"])
-
-class ParseV2Stream(unittest.TestCase):
- def test_field_kinds(self):
- api = apilint._parse_stream("""
-// Signature format: 2.0
-package android {
- public enum SomeEnum {
- enum_constant public static final android.SomeEnum ENUM_CONST;
- field public static final int FIELD_CONST;
- property public final int someProperty;
- ctor public SomeEnum();
- method public Object? getObject();
- }
-}
- """.strip().split('\n'))
-
- self.assertEquals(api['android.SomeEnum'].fields[0].split[0], 'enum_constant')
- self.assertEquals(api['android.SomeEnum'].fields[1].split[0], 'field')
- self.assertEquals(api['android.SomeEnum'].fields[2].split[0], 'property')
- self.assertEquals(api['android.SomeEnum'].ctors[0].split[0], 'ctor')
- self.assertEquals(api['android.SomeEnum'].methods[0].split[0], 'method')
-
-class ParseV3Stream(unittest.TestCase):
- def test_field_kinds(self):
- api = apilint._parse_stream("""
-// Signature format: 3.0
-package a {
-
- public final class ContextKt {
- method public static inline <reified T> T! getSystemService(android.content.Context);
- method public static inline void withStyledAttributes(android.content.Context, android.util.AttributeSet? set = null, int[] attrs, @AttrRes int defStyleAttr = 0, @StyleRes int defStyleRes = 0, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
- }
-}
- """.strip().split('\n'))
- self.assertEquals(api['a.ContextKt'].methods[0].name, 'getSystemService')
- self.assertEquals(api['a.ContextKt'].methods[0].split[:4], ['method', 'public', 'static', 'inline'])
- self.assertEquals(api['a.ContextKt'].methods[1].name, 'withStyledAttributes')
- self.assertEquals(api['a.ContextKt'].methods[1].split[:4], ['method', 'public', 'static', 'inline'])
-
-class V2TokenizerTests(unittest.TestCase):
- def _test(self, raw, expected):
- self.assertEquals(apilint.V2Tokenizer(raw).tokenize(), expected)
-
- def test_simple(self):
- self._test(" method public some.Type someName(some.Argument arg, int arg);",
- ['method', 'public', 'some.Type', 'someName', '(', 'some.Argument',
- 'arg', ',', 'int', 'arg', ')', ';'])
- self._test("class Some.Class extends SomeOther {",
- ['class', 'Some.Class', 'extends', 'SomeOther', '{'])
-
- def test_varargs(self):
- self._test("name(String...)",
- ['name', '(', 'String', '...', ')'])
-
- def test_kotlin(self):
- self._test("String? name(String!...)",
- ['String', '?', 'name', '(', 'String', '!', '...', ')'])
-
- def test_annotation(self):
- self._test("method @Nullable public void name();",
- ['method', '@', 'Nullable', 'public', 'void', 'name', '(', ')', ';'])
-
- def test_annotation_args(self):
- self._test("@Some(val=1, other=2) class Class {",
- ['@', 'Some', '(', 'val', '=', '1', ',', 'other', '=', '2', ')',
- 'class', 'Class', '{'])
- def test_comment(self):
- self._test("some //comment", ['some'])
-
- def test_strings(self):
- self._test(r'"" "foo" "\"" "\\"', ['""', '"foo"', r'"\""', r'"\\"'])
-
- def test_at_interface(self):
- self._test("public @interface Annotation {",
- ['public', '@interface', 'Annotation', '{'])
-
- def test_array_type(self):
- self._test("int[][]", ['int', '[]', '[]'])
-
- def test_generics(self):
- self._test("<>foobar<A extends Object>",
- ['<', '>', 'foobar', '<', 'A', 'extends', 'Object', '>'])
-
-class V2ParserTests(unittest.TestCase):
- def _cls(self, raw):
- pkg = apilint.Package(999, "package pkg {", None)
- return apilint.Class(pkg, 1, raw, '', sig_format=2)
-
- def _method(self, raw, cls=None):
- if not cls:
- cls = self._cls("class Class {")
- return apilint.Method(cls, 1, raw, '', sig_format=2)
-
- def _field(self, raw):
- cls = self._cls("class Class {")
- return apilint.Field(cls, 1, raw, '', sig_format=2)
-
- def test_parse_package(self):
- pkg = apilint.Package(999, "package wifi.p2p {", None)
- self.assertEquals("wifi.p2p", pkg.name)
-
- def test_class(self):
- cls = self._cls("@Deprecated @IntRange(from=1, to=2) public static abstract class Some.Name extends Super<Class> implements Interface<Class> {")
- self.assertTrue('deprecated' in cls.split)
- self.assertTrue('static' in cls.split)
- self.assertTrue('abstract' in cls.split)
- self.assertTrue('class' in cls.split)
- self.assertEquals('Super', cls.extends)
- self.assertEquals('Interface', cls.implements)
- self.assertEquals('pkg.Some.Name', cls.fullname)
-
- def test_enum(self):
- cls = self._cls("public enum Some.Name {")
- self._field("enum_constant public static final android.ValueType COLOR;")
-
- def test_interface(self):
- cls = self._cls("@Deprecated @IntRange(from=1, to=2) public interface Some.Name extends Interface<Class> {")
- self.assertTrue('deprecated' in cls.split)
- self.assertTrue('interface' in cls.split)
- self.assertEquals('Interface', cls.extends)
- self.assertEquals('Interface', cls.implements)
- self.assertEquals('pkg.Some.Name', cls.fullname)
-
- def test_at_interface(self):
- cls = self._cls("@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SuppressLint {")
- self.assertTrue('@interface' in cls.split)
- self.assertEquals('pkg.SuppressLint', cls.fullname)
-
- def test_parse_method(self):
- m = self._method("method @Deprecated public static native <T> Class<T>[][] name("
- + "Class<T[]>[][], Class<T[][][]>[][]...) throws Exception, T;")
- self.assertTrue('static' in m.split)
- self.assertTrue('public' in m.split)
- self.assertTrue('method' in m.split)
- self.assertTrue('native' in m.split)
- self.assertTrue('deprecated' in m.split)
- self.assertEquals('java.lang.Class[][]', m.typ)
- self.assertEquals('name', m.name)
- self.assertEquals(['java.lang.Class[][]', 'java.lang.Class[][]...'], m.args)
- self.assertEquals(['java.lang.Exception', 'T'], m.throws)
-
- def test_ctor(self):
- m = self._method("ctor @Deprecated <T> ClassName();")
- self.assertTrue('ctor' in m.split)
- self.assertTrue('deprecated' in m.split)
- self.assertEquals('ctor', m.typ)
- self.assertEquals('ClassName', m.name)
-
- def test_parse_annotation_method(self):
- cls = self._cls("@interface Annotation {")
- self._method('method abstract String category() default "";', cls=cls)
- self._method('method abstract boolean deepExport() default false;', cls=cls)
- self._method('method abstract ViewDebug.FlagToString[] flagMapping() default {};', cls=cls)
- self._method('method abstract ViewDebug.FlagToString[] flagMapping() default (double)java.lang.Float.NEGATIVE_INFINITY;', cls=cls)
-
- def test_parse_string_field(self):
- f = self._field('field @Deprecated public final String SOME_NAME = "value";')
- self.assertTrue('field' in f.split)
- self.assertTrue('deprecated' in f.split)
- self.assertTrue('final' in f.split)
- self.assertEquals('java.lang.String', f.typ)
- self.assertEquals('SOME_NAME', f.name)
- self.assertEquals('value', f.value)
-
- def test_parse_field(self):
- f = self._field('field public Object SOME_NAME;')
- self.assertTrue('field' in f.split)
- self.assertEquals('java.lang.Object', f.typ)
- self.assertEquals('SOME_NAME', f.name)
- self.assertEquals(None, f.value)
-
- def test_parse_int_field(self):
- f = self._field('field public int NAME = 123;')
- self.assertTrue('field' in f.split)
- self.assertEquals('int', f.typ)
- self.assertEquals('NAME', f.name)
- self.assertEquals('123', f.value)
-
- def test_parse_quotient_field(self):
- f = self._field('field public int NAME = (0.0/0.0);')
- self.assertTrue('field' in f.split)
- self.assertEquals('int', f.typ)
- self.assertEquals('NAME', f.name)
- self.assertEquals('( 0.0 / 0.0 )', f.value)
-
- def test_kotlin_types(self):
- self._field('field public List<Integer[]?[]!>?[]![]? NAME;')
- self._method("method <T?> Class<T!>?[]![][]? name(Type!, Type argname,"
- + "Class<T?>[][]?[]!...!) throws Exception, T;")
- self._method("method <T> T name(T a = 1, T b = A(1), Lambda f = { false }, N? n = null, "
- + """double c = (1/0), float d = 1.0f, String s = "heyo", char c = 'a');""")
-
- def test_kotlin_operator(self):
- self._method('method public operator void unaryPlus(androidx.navigation.NavDestination);')
- self._method('method public static operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);')
- self._method('method public static operator <T> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);')
-
- def test_kotlin_property(self):
- self._field('property public VM value;')
- self._field('property public final String? action;')
-
- def test_kotlin_varargs(self):
- self._method('method public void error(int p = "42", Integer int2 = "null", int p1 = "42", vararg String args);')
-
- def test_kotlin_default_values(self):
- self._method('method public void foo(String! = null, String! = "Hello World", int = 42);')
- self._method('method void method(String, String firstArg = "hello", int secondArg = "42", String thirdArg = "world");')
- self._method('method void method(String, String firstArg = "hello", int secondArg = "42");')
- self._method('method void method(String, String firstArg = "hello");')
- self._method('method void edit(android.Type, boolean commit = false, Function1<? super Editor,kotlin.Unit> action);')
- self._method('method <K, V> LruCache<K,V> lruCache(int maxSize, Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, Function1<? extends V> create = { (V)null }, Function4<kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });')
- self._method('method android.Bitmap? drawToBitmap(android.View, android.Config config = android.graphics.Bitmap.Config.ARGB_8888);')
- self._method('method void emptyLambda(Function0<kotlin.Unit> sizeOf = {});')
- self._method('method void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);')
- self._method('method void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);')
- self._method('method void method3(String str, int p, int int2 = double(int) + str.length);')
- self._method('method void print(test.pkg.Foo foo = test.pkg.Foo());')
-
- def test_type_use_annotation(self):
- self._method('method public static int codePointAt(char @NonNull [], int);')
- self._method('method @NonNull public java.util.Set<java.util.Map.@NonNull Entry<K,V>> entrySet();')
-
- m = self._method('method @NonNull public java.lang.annotation.@NonNull Annotation @NonNull [] getAnnotations();')
- self.assertEquals('java.lang.annotation.Annotation[]', m.typ)
-
- m = self._method('method @NonNull public abstract java.lang.annotation.@NonNull Annotation @NonNull [] @NonNull [] getParameterAnnotations();')
- self.assertEquals('java.lang.annotation.Annotation[][]', m.typ)
-
- m = self._method('method @NonNull public @NonNull String @NonNull [] split(@NonNull String, int);')
- self.assertEquals('java.lang.String[]', m.typ)
-
-class PackageTests(unittest.TestCase):
- def _package(self, raw):
- return apilint.Package(123, raw, "blame")
-
- def test_regular_package(self):
- p = self._package("package an.pref.int {")
- self.assertEquals('an.pref.int', p.name)
-
- def test_annotation_package(self):
- p = self._package("package @RestrictTo(a.b.C) an.pref.int {")
- self.assertEquals('an.pref.int', p.name)
-
- def test_multi_annotation_package(self):
- p = self._package("package @Rt(a.b.L_G_P) @RestrictTo(a.b.C) an.pref.int {")
- self.assertEquals('an.pref.int', p.name)
-
-class FilterTests(unittest.TestCase):
- def test_filter_match_prefix(self):
- self.assertTrue(apilint.match_filter(["a"], "a.B"))
- self.assertTrue(apilint.match_filter(["a.B"], "a.B.C"))
-
- def test_filter_dont_match_prefix(self):
- self.assertFalse(apilint.match_filter(["c"], "a.B"))
- self.assertFalse(apilint.match_filter(["a."], "a.B"))
- self.assertFalse(apilint.match_filter(["a.B."], "a.B.C"))
-
- def test_filter_match_exact(self):
- self.assertTrue(apilint.match_filter(["a.B"], "a.B"))
-
- def test_filter_dont_match_exact(self):
- self.assertFalse(apilint.match_filter([""], "a.B"))
- self.assertFalse(apilint.match_filter(["a.C"], "a.B"))
- self.assertFalse(apilint.match_filter(["a.C"], "a.B"))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tools/dump-coverage/Android.bp b/tools/dump-coverage/Android.bp
new file mode 100644
index 000000000000..4519ce3636bf
--- /dev/null
+++ b/tools/dump-coverage/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// Build variants {target,host} x {32,64}
+cc_library {
+ name: "libdumpcoverage",
+ srcs: ["dump_coverage.cc"],
+ header_libs: [
+ "libopenjdkjvmti_headers",
+ ],
+
+ host_supported: true,
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/tools/dump-coverage/README.md b/tools/dump-coverage/README.md
new file mode 100644
index 000000000000..d1c10bc2e520
--- /dev/null
+++ b/tools/dump-coverage/README.md
@@ -0,0 +1,50 @@
+# dumpcoverage
+
+libdumpcoverage.so is a JVMTI agent designed to dump coverage information for a process, where the binaries have been instrumented by JaCoCo. JaCoCo automatically starts recording data on process start, and we need a way to trigger the resetting or dumping of this data.
+
+The JVMTI agent is used to make the calls to JaCoCo in its process.
+
+# Usage
+
+Note that these examples assume you have an instrumented build (userdebug_coverage). Here is, for example, how to dump coverage information regarding the default clock app. First some setup is necessary:
+
+```
+adb root # necessary to copy files in/out of the /data/data/{package} folder
+adb shell 'mkdir /data/data/com.android.deskclock/folder-to-use'
+```
+
+Then we can run the command to dump the data:
+
+```
+adb shell 'am attach-agent com.android.deskclock /system/lib/libdumpcoverage.so=dump:/data/data/com.android.deskclock/folder-to-use/coverage-file.ec'
+```
+
+We can also reset the coverage information with
+
+```
+adb shell 'am attach-agent com.android.deskclock /system/lib/libdumpcoverage.so=reset'
+```
+
+then perform more actions, then dump the data again. To get the files, we can get
+
+```
+adb pull /data/data/com.android.deskclock/folder-to-use/coverage-file.ec ~/path-on-your-computer
+```
+
+And you should have `coverage-file.ec` on your machine under the folder `~/path-on-your-computer`
+
+# Details
+
+In dump mode, the agent makes JNI calls equivalent to
+
+```
+Agent.getInstance().getExecutionData(/*reset = */ false);
+```
+
+and then saves the result to a file specified by the passed in directory
+
+In reset mode, it makes a JNI call equivalent to
+
+```
+Agent.getInstance().reset();
+```
diff --git a/tools/dump-coverage/dump_coverage.cc b/tools/dump-coverage/dump_coverage.cc
new file mode 100644
index 000000000000..0808e776f190
--- /dev/null
+++ b/tools/dump-coverage/dump_coverage.cc
@@ -0,0 +1,205 @@
+// Copyright (C) 2018 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.
+//
+
+#include <android-base/logging.h>
+#include <jni.h>
+#include <jvmti.h>
+#include <string.h>
+
+#include <fstream>
+
+using std::get;
+using std::tuple;
+
+namespace dump_coverage {
+
+#define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE)
+#define CHECK_NOTNULL(x) CHECK((x) != nullptr)
+#define CHECK_NO_EXCEPTION(env) CHECK(!(env)->ExceptionCheck());
+
+static JavaVM* java_vm = nullptr;
+
+// Get the current JNI environment.
+static JNIEnv* GetJNIEnv() {
+ JNIEnv* env = nullptr;
+ CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6),
+ JNI_OK);
+ return env;
+}
+
+// Get the JaCoCo Agent class and an instance of the class, given a JNI
+// environment.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static tuple<jclass, jobject> GetJavaAgent(JNIEnv* env) {
+ jclass java_agent_class =
+ env->FindClass("org/jacoco/agent/rt/internal/Agent");
+ CHECK_NOTNULL(java_agent_class);
+
+ jmethodID java_agent_get_instance =
+ env->GetStaticMethodID(java_agent_class, "getInstance",
+ "()Lorg/jacoco/agent/rt/internal/Agent;");
+ CHECK_NOTNULL(java_agent_get_instance);
+
+ jobject java_agent_instance =
+ env->CallStaticObjectMethod(java_agent_class, java_agent_get_instance);
+ CHECK_NO_EXCEPTION(env);
+ CHECK_NOTNULL(java_agent_instance);
+
+ return tuple(java_agent_class, java_agent_instance);
+}
+
+// Runs equivalent of Agent.getInstance().getExecutionData(false) and returns
+// the result.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jbyteArray GetExecutionData(JNIEnv* env) {
+ auto java_agent = GetJavaAgent(env);
+ jmethodID java_agent_get_execution_data =
+ env->GetMethodID(get<0>(java_agent), "getExecutionData", "(Z)[B");
+ CHECK_NO_EXCEPTION(env);
+ CHECK_NOTNULL(java_agent_get_execution_data);
+
+ jbyteArray java_result_array = (jbyteArray)env->CallObjectMethod(
+ get<1>(java_agent), java_agent_get_execution_data, false);
+ CHECK_NO_EXCEPTION(env);
+
+ return java_result_array;
+}
+
+// Writes the execution data to a file.
+// data, length: represent the data, as a sequence of bytes.
+// filename: file to write coverage data to.
+// returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
+static jint WriteFile(const char* data, int length, const std::string& filename) {
+ LOG(INFO) << "Writing file of length " << length << " to '" << filename
+ << "'";
+ std::ofstream file(filename, std::ios::binary);
+
+ if (!file.is_open()) {
+ LOG(ERROR) << "Could not open file: '" << filename << "'";
+ return JNI_ERR;
+ }
+ file.write(data, length);
+ file.close();
+
+ if (!file) {
+ LOG(ERROR) << "I/O error in reading file";
+ return JNI_ERR;
+ }
+
+ LOG(INFO) << "Done writing file";
+ return JNI_OK;
+}
+
+// Grabs execution data and writes it to a file.
+// filename: file to write coverage data to.
+// returns JNI_ERR if there is an error writing the file.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jint Dump(const std::string& filename) {
+ LOG(INFO) << "Dumping file";
+
+ JNIEnv* env = GetJNIEnv();
+ jbyteArray java_result_array = GetExecutionData(env);
+ CHECK_NOTNULL(java_result_array);
+
+ jbyte* result_ptr = env->GetByteArrayElements(java_result_array, 0);
+ CHECK_NOTNULL(result_ptr);
+
+ int result_len = env->GetArrayLength(java_result_array);
+
+ return WriteFile((const char*) result_ptr, result_len, filename);
+}
+
+// Resets execution data, performing the equivalent of
+// Agent.getInstance().reset();
+// args: should be empty.
+// returns JNI_ERR if the arguments are invalid.
+// Will crash if the Agent isn't found or if any Java Exception occurs.
+static jint Reset(const std::string& args) {
+ if (args != "") {
+ LOG(ERROR) << "reset takes no arguments, but received '" << args << "'";
+ return JNI_ERR;
+ }
+
+ JNIEnv* env = GetJNIEnv();
+ auto java_agent = GetJavaAgent(env);
+
+ jmethodID java_agent_reset =
+ env->GetMethodID(get<0>(java_agent), "reset", "()V");
+ CHECK_NOTNULL(java_agent_reset);
+
+ env->CallVoidMethod(get<1>(java_agent), java_agent_reset);
+ CHECK_NO_EXCEPTION(env);
+ return JNI_OK;
+}
+
+// Given a string of the form "<a>:<b>" returns (<a>, <b>).
+// Given a string <a> that doesn't contain a colon, returns (<a>, "").
+static tuple<std::string, std::string> SplitOnColon(const std::string& options) {
+ size_t loc_delim = options.find(':');
+ std::string command, args;
+
+ if (loc_delim == std::string::npos) {
+ command = options;
+ } else {
+ command = options.substr(0, loc_delim);
+ args = options.substr(loc_delim + 1, options.length());
+ }
+ return tuple(command, args);
+}
+
+// Parses and executes a command specified by options of the form
+// "<command>:<args>" where <command> is either "dump" or "reset".
+static jint ParseOptionsAndExecuteCommand(const std::string& options) {
+ auto split = SplitOnColon(options);
+ auto command = get<0>(split), args = get<1>(split);
+
+ LOG(INFO) << "command: '" << command << "' args: '" << args << "'";
+
+ if (command == "dump") {
+ return Dump(args);
+ }
+
+ if (command == "reset") {
+ return Reset(args);
+ }
+
+ LOG(ERROR) << "Invalid command: expected 'dump' or 'reset' but was '"
+ << command << "'";
+ return JNI_ERR;
+}
+
+static jint AgentStart(JavaVM* vm, char* options) {
+ android::base::InitLogging(/* argv= */ nullptr);
+ java_vm = vm;
+
+ return ParseOptionsAndExecuteCommand(options);
+}
+
+// Late attachment (e.g. 'am attach-agent').
+extern "C" JNIEXPORT jint JNICALL
+Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
+ return AgentStart(vm, options);
+}
+
+// Early attachment.
+extern "C" JNIEXPORT jint JNICALL
+Agent_OnLoad(JavaVM* jvm ATTRIBUTE_UNUSED, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
+ LOG(ERROR)
+ << "The dumpcoverage agent will not work on load,"
+ << " as it does not have access to the runtime.";
+ return JNI_ERR;
+}
+
+} // namespace dump_coverage
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index c856cc36d6f6..e883c6bed755 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -29,6 +29,7 @@ FLAG_GREYLIST = "greylist"
FLAG_BLACKLIST = "blacklist"
FLAG_GREYLIST_MAX_O = "greylist-max-o"
FLAG_GREYLIST_MAX_P = "greylist-max-p"
+FLAG_GREYLIST_MAX_Q = "greylist-max-q"
FLAG_CORE_PLATFORM_API = "core-platform-api"
FLAG_PUBLIC_API = "public-api"
FLAG_SYSTEM_API = "system-api"
@@ -41,6 +42,7 @@ FLAGS_API_LIST = [
FLAG_BLACKLIST,
FLAG_GREYLIST_MAX_O,
FLAG_GREYLIST_MAX_P,
+ FLAG_GREYLIST_MAX_Q,
]
ALL_FLAGS = FLAGS_API_LIST + [
FLAG_CORE_PLATFORM_API,
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
new file mode 100644
index 000000000000..79dce4a8ce09
--- /dev/null
+++ b/tools/lock_agent/Android.bp
@@ -0,0 +1,68 @@
+cc_library {
+ name: "liblockagent",
+ host_supported: false,
+ srcs: ["agent.cpp"],
+ static_libs: [
+ "libbase_ndk",
+ "slicer_ndk_no_rtti",
+ ],
+ shared_libs: [
+ "libz", // for slicer (using adler32).
+ "liblog",
+ ],
+ sdk_version: "current",
+ stl: "c++_static",
+ header_libs: [
+ // Use ScopedUtfChars.
+ "libnativehelper_header_only",
+ "libopenjdkjvmti_headers",
+ ],
+ compile_multilib: "both",
+}
+
+cc_binary_host {
+ name: "lockagenttest",
+ srcs: ["agent.cpp"],
+ static_libs: [
+ "libbase",
+ "libz",
+ "slicer",
+ ],
+ header_libs: [
+ // Use ScopedUtfChars.
+ "libnativehelper_header_only",
+ "libopenjdkjvmti_headers",
+ ],
+}
+
+java_library {
+ name: "lockagent",
+ srcs: ["java/**/*.java"],
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ installable: true,
+}
+
+cc_binary {
+ name: "lockagent_crasher",
+ srcs: ["crasher.cpp"],
+ static_libs: ["libbase_ndk"],
+ shared_libs: ["liblog"],
+ sdk_version: "current",
+ stl: "c++_static",
+ compile_multilib: "first",
+}
+
+sh_binary {
+ name: "start_with_lockagent",
+ src: "start_with_lockagent.sh",
+ required: [
+ "liblockagent",
+ "lockagent",
+ "lockagent_crasher",
+ ],
+}
diff --git a/tools/lock_agent/agent.cpp b/tools/lock_agent/agent.cpp
new file mode 100644
index 000000000000..40293b690180
--- /dev/null
+++ b/tools/lock_agent/agent.cpp
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <cstring>
+#include <iostream>
+#include <memory>
+#include <sstream>
+
+#include <unistd.h>
+
+#include <jni.h>
+
+#include <jvmti.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <nativehelper/scoped_utf_chars.h>
+
+// We need dladdr.
+#if !defined(__APPLE__) && !defined(_WIN32)
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#define DEFINED_GNU_SOURCE
+#endif
+#include <dlfcn.h>
+#ifdef DEFINED_GNU_SOURCE
+#undef _GNU_SOURCE
+#undef DEFINED_GNU_SOURCE
+#endif
+#endif
+
+// Slicer's headers have code that triggers these warnings. b/65298177
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wsign-compare"
+
+#include <slicer/dex_ir.h>
+#include <slicer/code_ir.h>
+#include <slicer/dex_bytecode.h>
+#include <slicer/dex_ir_builder.h>
+#include <slicer/writer.h>
+#include <slicer/reader.h>
+
+#pragma clang diagnostic pop
+
+namespace {
+
+JavaVM* gJavaVM = nullptr;
+bool gForkCrash = false;
+bool gJavaCrash = false;
+
+// Converts a class name to a type descriptor
+// (ex. "java.lang.String" to "Ljava/lang/String;")
+std::string classNameToDescriptor(const char* className) {
+ std::stringstream ss;
+ ss << "L";
+ for (auto p = className; *p != '\0'; ++p) {
+ ss << (*p == '.' ? '/' : *p);
+ }
+ ss << ";";
+ return ss.str();
+}
+
+using namespace dex;
+using namespace lir;
+
+class Transformer {
+public:
+ explicit Transformer(std::shared_ptr<ir::DexFile> dexIr) : dexIr_(dexIr) {}
+
+ bool transform() {
+ bool classModified = false;
+
+ std::unique_ptr<ir::Builder> builder;
+
+ for (auto& method : dexIr_->encoded_methods) {
+ // Do not look into abstract/bridge/native/synthetic methods.
+ if ((method->access_flags & (kAccAbstract | kAccBridge | kAccNative | kAccSynthetic))
+ != 0) {
+ continue;
+ }
+
+ struct HookVisitor: public Visitor {
+ HookVisitor(Transformer* transformer, CodeIr* c_ir)
+ : transformer(transformer), cIr(c_ir) {
+ }
+
+ bool Visit(Bytecode* bytecode) override {
+ if (bytecode->opcode == OP_MONITOR_ENTER) {
+ insertHook(bytecode, true,
+ reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
+ return true;
+ }
+ if (bytecode->opcode == OP_MONITOR_EXIT) {
+ insertHook(bytecode, false,
+ reinterpret_cast<VReg*>(bytecode->operands[0])->reg);
+ return true;
+ }
+ return false;
+ }
+
+ void insertHook(lir::Instruction* before, bool pre, u4 reg) {
+ transformer->preparePrePost();
+ transformer->addCall(cIr, before, OP_INVOKE_STATIC_RANGE,
+ transformer->hookType_, pre ? "preLock" : "postLock",
+ transformer->voidType_, transformer->objectType_, reg);
+ myModified = true;
+ }
+
+ Transformer* transformer;
+ CodeIr* cIr;
+ bool myModified = false;
+ };
+
+ CodeIr c(method.get(), dexIr_);
+ bool methodModified = false;
+
+ HookVisitor visitor(this, &c);
+ for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
+ lir::Instruction* fi = *it;
+ fi->Accept(&visitor);
+ }
+ methodModified |= visitor.myModified;
+
+ if (methodModified) {
+ classModified = true;
+ c.Assemble();
+ }
+ }
+
+ return classModified;
+ }
+
+private:
+ void preparePrePost() {
+ // Insert "void LockHook.(pre|post)(Object o)."
+
+ prepareBuilder();
+
+ if (voidType_ == nullptr) {
+ voidType_ = builder_->GetType("V");
+ }
+ if (hookType_ == nullptr) {
+ hookType_ = builder_->GetType("Lcom/android/lock_checker/LockHook;");
+ }
+ if (objectType_ == nullptr) {
+ objectType_ = builder_->GetType("Ljava/lang/Object;");
+ }
+ }
+
+ void prepareBuilder() {
+ if (builder_ == nullptr) {
+ builder_ = std::unique_ptr<ir::Builder>(new ir::Builder(dexIr_));
+ }
+ }
+
+ static void addInst(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode,
+ const std::list<Operand*>& operands) {
+ auto instruction = cIr->Alloc<Bytecode>();
+
+ instruction->opcode = opcode;
+
+ for (auto it = operands.begin(); it != operands.end(); it++) {
+ instruction->operands.push_back(*it);
+ }
+
+ cIr->instructions.InsertBefore(instructionAfter, instruction);
+ }
+
+ void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
+ const char* methodName, ir::Type* returnType,
+ const std::vector<ir::Type*>& types, const std::list<int>& regs) {
+ auto proto = builder_->GetProto(returnType, builder_->GetTypeList(types));
+ auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
+
+ VRegList* paramRegs = cIr->Alloc<VRegList>();
+ for (auto it = regs.begin(); it != regs.end(); it++) {
+ paramRegs->registers.push_back(*it);
+ }
+
+ addInst(cIr, instructionAfter, opcode,
+ { paramRegs, cIr->Alloc<Method>(method, method->orig_index) });
+ }
+
+ void addCall(CodeIr* cIr, lir::Instruction* instructionAfter, Opcode opcode, ir::Type* type,
+ const char* methodName, ir::Type* returnType, ir::Type* paramType,
+ u4 paramVReg) {
+ auto proto = builder_->GetProto(returnType, builder_->GetTypeList( { paramType }));
+ auto method = builder_->GetMethodDecl(builder_->GetAsciiString(methodName), proto, type);
+
+ VRegRange* args = cIr->Alloc<VRegRange>(paramVReg, 1);
+
+ addInst(cIr, instructionAfter, opcode,
+ { args, cIr->Alloc<Method>(method, method->orig_index) });
+ }
+
+ std::shared_ptr<ir::DexFile> dexIr_;
+ std::unique_ptr<ir::Builder> builder_;
+
+ ir::Type* voidType_ = nullptr;
+ ir::Type* hookType_ = nullptr;
+ ir::Type* objectType_ = nullptr;
+};
+
+std::pair<dex::u1*, size_t> maybeTransform(const char* name, size_t classDataLen,
+ const unsigned char* classData, dex::Writer::Allocator* allocator) {
+ // Isolate byte code of class class. This is needed as Android usually gives us more
+ // than the class we need.
+ dex::Reader reader(classData, classDataLen);
+
+ dex::u4 index = reader.FindClassIndex(classNameToDescriptor(name).c_str());
+ CHECK_NE(index, kNoIndex);
+ reader.CreateClassIr(index);
+ std::shared_ptr<ir::DexFile> ir = reader.GetIr();
+
+ {
+ Transformer transformer(ir);
+ if (!transformer.transform()) {
+ return std::make_pair(nullptr, 0);
+ }
+ }
+
+ size_t new_size;
+ dex::Writer writer(ir);
+ dex::u1* newClassData = writer.CreateImage(allocator, &new_size);
+ return std::make_pair(newClassData, new_size);
+}
+
+void transformHook(jvmtiEnv* jvmtiEnv, JNIEnv* env ATTRIBUTE_UNUSED,
+ jclass classBeingRedefined ATTRIBUTE_UNUSED, jobject loader, const char* name,
+ jobject protectionDomain ATTRIBUTE_UNUSED, jint classDataLen,
+ const unsigned char* classData, jint* newClassDataLen, unsigned char** newClassData) {
+ // Even reading the classData array is expensive as the data is only generated when the
+ // memory is touched. Hence call JvmtiAgent#shouldTransform to check if we need to transform
+ // the class.
+
+ // Skip bootclasspath classes. TODO: Make this configurable.
+ if (loader == nullptr) {
+ return;
+ }
+
+ // Do not look into java.* classes. Should technically be filtered by above, but when that's
+ // configurable have this.
+ if (strncmp("java", name, 4) == 0) {
+ return;
+ }
+
+ // Do not look into our Java classes.
+ if (strncmp("com/android/lock_checker", name, 24) == 0) {
+ return;
+ }
+
+ class JvmtiAllocator: public dex::Writer::Allocator {
+ public:
+ explicit JvmtiAllocator(::jvmtiEnv* jvmti) :
+ jvmti_(jvmti) {
+ }
+
+ void* Allocate(size_t size) override {
+ unsigned char* res = nullptr;
+ jvmti_->Allocate(size, &res);
+ return res;
+ }
+
+ void Free(void* ptr) override {
+ jvmti_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+ }
+
+ private:
+ ::jvmtiEnv* jvmti_;
+ };
+ JvmtiAllocator allocator(jvmtiEnv);
+ std::pair<dex::u1*, size_t> result = maybeTransform(name, classDataLen, classData,
+ &allocator);
+
+ if (result.second > 0) {
+ *newClassData = result.first;
+ *newClassDataLen = static_cast<jint>(result.second);
+ }
+}
+
+void dataDumpRequestHook(jvmtiEnv* jvmtiEnv ATTRIBUTE_UNUSED) {
+ if (gJavaVM == nullptr) {
+ LOG(ERROR) << "No JavaVM for dump";
+ return;
+ }
+ JNIEnv* env;
+ if (gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG(ERROR) << "Could not get env for dump";
+ return;
+ }
+ jclass lockHookClass = env->FindClass("com/android/lock_checker/LockHook");
+ if (lockHookClass == nullptr) {
+ env->ExceptionClear();
+ LOG(ERROR) << "Could not find LockHook class";
+ return;
+ }
+ jmethodID dumpId = env->GetStaticMethodID(lockHookClass, "dump", "()V");
+ if (dumpId == nullptr) {
+ env->ExceptionClear();
+ LOG(ERROR) << "Could not find LockHook.dump";
+ return;
+ }
+ env->CallStaticVoidMethod(lockHookClass, dumpId);
+ env->ExceptionClear();
+}
+
+// A function for dladdr to search.
+extern "C" __attribute__ ((visibility ("default"))) void lock_agent_tag_fn() {
+}
+
+bool fileExists(const std::string& path) {
+ struct stat statBuf;
+ int rc = stat(path.c_str(), &statBuf);
+ return rc == 0;
+}
+
+std::string findLockAgentJar() {
+ // Check whether the jar is located next to the agent's so.
+#ifndef __APPLE__
+ {
+ Dl_info info;
+ if (dladdr(reinterpret_cast<const void*>(&lock_agent_tag_fn), /* out */ &info) != 0) {
+ std::string lockAgentSoPath = info.dli_fname;
+ std::string dir = android::base::Dirname(lockAgentSoPath);
+ std::string lockAgentJarPath = dir + "/" + "lockagent.jar";
+ if (fileExists(lockAgentJarPath)) {
+ return lockAgentJarPath;
+ }
+ } else {
+ LOG(ERROR) << "dladdr failed";
+ }
+ }
+#endif
+
+ std::string sysFrameworkPath = "/system/framework/lockagent.jar";
+ if (fileExists(sysFrameworkPath)) {
+ return sysFrameworkPath;
+ }
+
+ std::string relPath = "lockagent.jar";
+ if (fileExists(relPath)) {
+ return relPath;
+ }
+
+ return "";
+}
+
+void prepareHook(jvmtiEnv* env) {
+ // Inject the agent Java code.
+ {
+ std::string path = findLockAgentJar();
+ if (path.empty()) {
+ LOG(FATAL) << "Could not find lockagent.jar";
+ }
+ LOG(INFO) << "Will load Java parts from " << path;
+ jvmtiError res = env->AddToBootstrapClassLoaderSearch(path.c_str());
+ if (res != JVMTI_ERROR_NONE) {
+ LOG(FATAL) << "Could not add lockagent from " << path << " to boot classpath: " << res;
+ }
+ }
+
+ jvmtiCapabilities caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.can_retransform_classes = 1;
+
+ if (env->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
+ LOG(FATAL) << "Could not add caps";
+ }
+
+ jvmtiEventCallbacks cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.ClassFileLoadHook = transformHook;
+ cb.DataDumpRequest = dataDumpRequestHook;
+
+ if (env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
+ LOG(FATAL) << "Could not set cb";
+ }
+
+ if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr)
+ != JVMTI_ERROR_NONE) {
+ LOG(FATAL) << "Could not enable events";
+ }
+ if (env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr)
+ != JVMTI_ERROR_NONE) {
+ LOG(FATAL) << "Could not enable events";
+ }
+}
+
+jint attach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
+ gJavaVM = vm;
+
+ jvmtiEnv* env;
+ jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_2);
+ if (jvmError != JNI_OK) {
+ return jvmError;
+ }
+
+ prepareHook(env);
+
+ std::vector<std::string> config = android::base::Split(options, ",");
+ for (const std::string& c : config) {
+ if (c == "native_crash") {
+ gForkCrash = true;
+ } else if (c == "java_crash") {
+ gJavaCrash = true;
+ }
+ }
+
+ return JVMTI_ERROR_NONE;
+}
+
+extern "C" JNIEXPORT
+jboolean JNICALL Java_com_android_lock_1checker_LockHook_getNativeHandlingConfig(JNIEnv*, jclass) {
+ return gForkCrash ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_android_lock_1checker_LockHook_getSimulateCrashConfig(JNIEnv*, jclass) {
+ return gJavaCrash ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_lock_1checker_LockHook_nWtf(JNIEnv* env, jclass,
+ jstring msg) {
+ if (!gForkCrash || msg == nullptr) {
+ return;
+ }
+
+ // Create a native crash with the given message. Decouple from the current crash to create a
+ // tombstone but continue on.
+ //
+ // TODO: Once there are not so many reports, consider making this fatal for the calling process.
+ ScopedUtfChars utf(env, msg);
+ if (utf.c_str() == nullptr) {
+ return;
+ }
+ const char* args[] = {
+ "/system/bin/lockagent_crasher",
+ utf.c_str(),
+ nullptr
+ };
+ pid_t pid = fork();
+ if (pid < 0) {
+ return;
+ }
+ if (pid == 0) {
+ // Double fork so we return quickly. Leave init to deal with the zombie.
+ pid_t pid2 = fork();
+ if (pid2 == 0) {
+ execv(args[0], const_cast<char* const*>(args));
+ _exit(1);
+ __builtin_unreachable();
+ }
+ _exit(0);
+ __builtin_unreachable();
+ }
+ int status;
+ waitpid(pid, &status, 0); // Ignore any results.
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
+ return attach(vm, options, reserved);
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
+ return attach(vm, options, reserved);
+}
+
+int locktest_main(int argc, char *argv[]) {
+ if (argc != 3) {
+ LOG(FATAL) << "Need two arguments: dex-file class-name";
+ }
+ struct stat statBuf;
+ int rc = stat(argv[1], &statBuf);
+ if (rc != 0) {
+ PLOG(FATAL) << "Could not get file size for " << argv[1];
+ }
+ std::unique_ptr<char[]> data(new char[statBuf.st_size]);
+ {
+ android::base::unique_fd fd(open(argv[1], O_RDONLY));
+ if (fd.get() == -1) {
+ PLOG(FATAL) << "Could not open file " << argv[1];
+ }
+ if (!android::base::ReadFully(fd.get(), data.get(), statBuf.st_size)) {
+ PLOG(FATAL) << "Could not read file " << argv[1];
+ }
+ }
+
+ class NewDeleteAllocator: public dex::Writer::Allocator {
+ public:
+ explicit NewDeleteAllocator() {
+ }
+
+ void* Allocate(size_t size) override {
+ return new char[size];
+ }
+
+ void Free(void* ptr) override {
+ delete[] reinterpret_cast<char*>(ptr);
+ }
+ };
+ NewDeleteAllocator allocator;
+
+ std::pair<dex::u1*, size_t> result = maybeTransform(argv[2], statBuf.st_size,
+ reinterpret_cast<unsigned char*>(data.get()), &allocator);
+
+ if (result.second == 0) {
+ LOG(INFO) << "No transformation";
+ return 0;
+ }
+
+ std::string newName(argv[1]);
+ newName.append(".new");
+
+ {
+ android::base::unique_fd fd(
+ open(newName.c_str(), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR));
+ if (fd.get() == -1) {
+ PLOG(FATAL) << "Could not open file " << newName;
+ }
+ if (!android::base::WriteFully(fd.get(), result.first, result.second)) {
+ PLOG(FATAL) << "Could not write file " << newName;
+ }
+ }
+ LOG(INFO) << "Transformed file written to " << newName;
+
+ return 0;
+}
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ return locktest_main(argc, argv);
+}
diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/lock_agent/crasher.cpp
index c0e4795b6d90..2829d6d9447c 100644
--- a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java
+++ b/tools/lock_agent/crasher.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,24 +14,17 @@
* limitations under the License.
*/
-package com.android.preload.actions;
+#include <android-base/logging.h>
-import com.android.preload.DumpTableModel;
-
-import java.awt.event.ActionEvent;
-
-import javax.swing.AbstractAction;
-
-public class ClearTableAction extends AbstractAction {
- private final DumpTableModel dataTableModel;
-
- public ClearTableAction(DumpTableModel dataTableModel) {
- super("Clear");
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- dataTableModel.clear();
+// Simple binary that will just crash with the message given as the first parameter.
+//
+// This is helpful in cases the caller does not want to crash itself, e.g., fork+crash
+// instead, as LOG(FATAL) might not be safe (for example in a multi-threaded environment).
+int main(int argc, char *argv[]) {
+ if (argc != 2) {
+ LOG(FATAL) << "Need one argument for abort message";
+ __builtin_unreachable();
}
-} \ No newline at end of file
+ LOG(FATAL) << argv[1];
+ __builtin_unreachable();
+}
diff --git a/tools/lock_agent/java/com/android/lock_checker/LockHook.java b/tools/lock_agent/java/com/android/lock_checker/LockHook.java
new file mode 100644
index 000000000000..35c75cbbae7c
--- /dev/null
+++ b/tools/lock_agent/java/com/android/lock_checker/LockHook.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2019 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.lock_checker;
+
+import android.app.ActivityThread;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+import android.util.LogWriter;
+
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.StatLogger;
+
+import dalvik.system.AnnotatedStackTraceElement;
+
+import libcore.util.HexEncoding;
+
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Entry class for lock inversion infrastructure. The agent will inject calls to preLock
+ * and postLock, and the hook will call the checker, and store violations.
+ */
+public class LockHook {
+ private static final String TAG = "LockHook";
+
+ private static final Charset sFilenameCharset = Charset.forName("UTF-8");
+
+ private static final HandlerThread sHandlerThread;
+ private static final WtfHandler sHandler;
+
+ private static final AtomicInteger sTotalObtainCount = new AtomicInteger();
+ private static final AtomicInteger sTotalReleaseCount = new AtomicInteger();
+ private static final AtomicInteger sDeepestNest = new AtomicInteger();
+
+ /**
+ * Whether to do the lock check on this thread.
+ */
+ private static final ThreadLocal<Boolean> sDoCheck = ThreadLocal.withInitial(() -> true);
+
+ interface Stats {
+ int ON_THREAD = 0;
+ }
+
+ static final StatLogger sStats = new StatLogger(new String[] { "on-thread", });
+
+ private static final ConcurrentLinkedQueue<Violation> sViolations =
+ new ConcurrentLinkedQueue<>();
+ private static final int MAX_VIOLATIONS = 50;
+
+ private static final LockChecker[] sCheckers;
+
+ private static boolean sNativeHandling = false;
+ private static boolean sSimulateCrash = false;
+
+ static {
+ sHandlerThread = new HandlerThread("LockHook:wtf", Process.THREAD_PRIORITY_BACKGROUND);
+ sHandlerThread.start();
+ sHandler = new WtfHandler(sHandlerThread.getLooper());
+
+ sCheckers = new LockChecker[] { new OnThreadLockChecker() };
+
+ sNativeHandling = getNativeHandlingConfig();
+ sSimulateCrash = getSimulateCrashConfig();
+ }
+
+ private static native boolean getNativeHandlingConfig();
+ private static native boolean getSimulateCrashConfig();
+
+ static <T> boolean shouldDumpStacktrace(StacktraceHasher hasher, Map<String, T> dumpedSet,
+ T val, AnnotatedStackTraceElement[] st, int from, int to) {
+ final String stacktraceHash = hasher.stacktraceHash(st, from, to);
+ if (dumpedSet.containsKey(stacktraceHash)) {
+ return false;
+ }
+ dumpedSet.put(stacktraceHash, val);
+ return true;
+ }
+
+ static void updateDeepestNest(int nest) {
+ for (;;) {
+ final int knownDeepest = sDeepestNest.get();
+ if (knownDeepest >= nest) {
+ return;
+ }
+ if (sDeepestNest.compareAndSet(knownDeepest, nest)) {
+ return;
+ }
+ }
+ }
+
+ static void wtf(Violation v) {
+ sHandler.wtf(v);
+ }
+
+ static void doCheckOnThisThread(boolean check) {
+ sDoCheck.set(check);
+ }
+
+ /**
+ * This method is called when a lock is about to be held. (Except if it's a
+ * synchronized, the lock is already held.)
+ */
+ public static void preLock(Object lock) {
+ if (Thread.currentThread() != sHandlerThread && sDoCheck.get()) {
+ sDoCheck.set(false);
+ try {
+ sTotalObtainCount.incrementAndGet();
+ for (LockChecker checker : sCheckers) {
+ checker.pre(lock);
+ }
+ } finally {
+ sDoCheck.set(true);
+ }
+ }
+ }
+
+ /**
+ * This method is called when a lock is about to be released.
+ */
+ public static void postLock(Object lock) {
+ if (Thread.currentThread() != sHandlerThread && sDoCheck.get()) {
+ sDoCheck.set(false);
+ try {
+ sTotalReleaseCount.incrementAndGet();
+ for (LockChecker checker : sCheckers) {
+ checker.post(lock);
+ }
+ } finally {
+ sDoCheck.set(true);
+ }
+ }
+ }
+
+ private static class WtfHandler extends Handler {
+ private static final int MSG_WTF = 1;
+
+ WtfHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void wtf(Violation v) {
+ sDoCheck.set(false);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = v;
+ obtainMessage(MSG_WTF, args).sendToTarget();
+ sDoCheck.set(true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WTF:
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleViolation((Violation) args.arg1);
+ args.recycle();
+ break;
+ }
+ }
+ }
+
+ private static void handleViolation(Violation v) {
+ String msg = v.toString();
+ Log.wtf(TAG, msg);
+ if (sNativeHandling) {
+ nWtf(msg); // Also send to native.
+ }
+ if (sSimulateCrash) {
+ RuntimeInit.logUncaught("LockAgent",
+ ActivityThread.isSystem() ? "system_server"
+ : ActivityThread.currentProcessName(),
+ Process.myPid(), v.getException());
+ }
+ }
+
+ private static native void nWtf(String msg);
+
+ /**
+ * Generates a hash for a given stacktrace of a {@link Throwable}.
+ */
+ static class StacktraceHasher {
+ private byte[] mLineNumberBuffer = new byte[4];
+ private final MessageDigest mHash;
+
+ StacktraceHasher() {
+ try {
+ mHash = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String stacktraceHash(Throwable t) {
+ mHash.reset();
+ for (StackTraceElement e : t.getStackTrace()) {
+ hashStackTraceElement(e);
+ }
+ return HexEncoding.encodeToString(mHash.digest());
+ }
+
+ public String stacktraceHash(AnnotatedStackTraceElement[] annotatedStack, int from,
+ int to) {
+ mHash.reset();
+ for (int i = from; i <= to; i++) {
+ hashStackTraceElement(annotatedStack[i].getStackTraceElement());
+ }
+ return HexEncoding.encodeToString(mHash.digest());
+ }
+
+ private void hashStackTraceElement(StackTraceElement e) {
+ if (e.getFileName() != null) {
+ mHash.update(sFilenameCharset.encode(e.getFileName()).array());
+ } else {
+ if (e.getClassName() != null) {
+ mHash.update(sFilenameCharset.encode(e.getClassName()).array());
+ }
+ if (e.getMethodName() != null) {
+ mHash.update(sFilenameCharset.encode(e.getMethodName()).array());
+ }
+ }
+
+ final int line = e.getLineNumber();
+ mLineNumberBuffer[0] = (byte) ((line >> 24) & 0xff);
+ mLineNumberBuffer[1] = (byte) ((line >> 16) & 0xff);
+ mLineNumberBuffer[2] = (byte) ((line >> 8) & 0xff);
+ mLineNumberBuffer[3] = (byte) ((line >> 0) & 0xff);
+ mHash.update(mLineNumberBuffer);
+ }
+ }
+
+ static void addViolation(Violation v) {
+ wtf(v);
+
+ sViolations.offer(v);
+ while (sViolations.size() > MAX_VIOLATIONS) {
+ sViolations.poll();
+ }
+ }
+
+ /**
+ * Dump stats to the given PrintWriter.
+ */
+ public static void dump(PrintWriter pw, String indent) {
+ final int oc = LockHook.sTotalObtainCount.get();
+ final int rc = LockHook.sTotalReleaseCount.get();
+ final int dn = LockHook.sDeepestNest.get();
+ pw.print("Lock stats: oc=");
+ pw.print(oc);
+ pw.print(" rc=");
+ pw.print(rc);
+ pw.print(" dn=");
+ pw.print(dn);
+ pw.println();
+
+ for (LockChecker checker : sCheckers) {
+ pw.print(indent);
+ pw.print(" ");
+ checker.dump(pw);
+ pw.println();
+ }
+
+ sStats.dump(pw, indent);
+
+ pw.print(indent);
+ pw.println("Violations:");
+ for (Object v : sViolations) {
+ pw.print(indent); // This won't really indent a multiline string,
+ // though.
+ pw.println(v);
+ }
+ }
+
+ /**
+ * Dump stats to logcat.
+ */
+ public static void dump() {
+ // Dump to logcat.
+ PrintWriter out = new PrintWriter(new LogWriter(Log.WARN, TAG), true);
+ dump(out, "");
+ out.close();
+ }
+
+ interface LockChecker {
+ void pre(Object lock);
+
+ void post(Object lock);
+
+ int getNumDetected();
+
+ int getNumDetectedUnique();
+
+ String getCheckerName();
+
+ void dump(PrintWriter pw);
+ }
+
+ interface Violation {
+ Throwable getException();
+ }
+}
diff --git a/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java b/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java
new file mode 100644
index 000000000000..e74ccf9d5069
--- /dev/null
+++ b/tools/lock_agent/java/com/android/lock_checker/OnThreadLockChecker.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2019 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.lock_checker;
+
+import android.util.Log;
+
+import dalvik.system.AnnotatedStackTraceElement;
+import dalvik.system.VMStack;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+class OnThreadLockChecker implements LockHook.LockChecker {
+ private static final String TAG = "LockCheckOnThread";
+
+ private static final boolean SKIP_RECURSIVE = true;
+
+ private final Thread mChecker;
+
+ private final AtomicInteger mNumDetected = new AtomicInteger();
+
+ private final AtomicInteger mNumDetectedUnique = new AtomicInteger();
+
+ // Queue for possible violations, to handle them on the sChecker thread.
+ private final LinkedBlockingQueue<Violation> mQueue = new LinkedBlockingQueue<>();
+
+ // The stack of locks held on the current thread.
+ private final ThreadLocal<List<Object>> mHeldLocks = ThreadLocal
+ .withInitial(() -> new ArrayList<>(10));
+
+ // A cached stacktrace hasher for each thread. The hasher caches internal objects and is not
+ // thread-safe.
+ private final ThreadLocal<LockHook.StacktraceHasher> mStacktraceHasher = ThreadLocal
+ .withInitial(() -> new LockHook.StacktraceHasher());
+
+ // A map of stacktrace hashes we have seen.
+ private final ConcurrentMap<String, Boolean> mDumpedStacktraceHashes =
+ new ConcurrentHashMap<>();
+
+ OnThreadLockChecker() {
+ mChecker = new Thread(() -> checker());
+ mChecker.setName(TAG);
+ mChecker.setPriority(Thread.MIN_PRIORITY);
+ mChecker.start();
+ }
+
+ private static class LockPair {
+ // Consider WeakReference. It will require also caching the String
+ // description for later reporting, though.
+ Object mFirst;
+ Object mSecond;
+
+ private int mCachedHashCode;
+
+ LockPair(Object first, Object second) {
+ mFirst = first;
+ mSecond = second;
+ computeHashCode();
+ }
+
+ public void set(Object newFirst, Object newSecond) {
+ mFirst = newFirst;
+ mSecond = newSecond;
+ computeHashCode();
+ }
+
+ private void computeHashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mFirst == null) ? 0 : System.identityHashCode(mFirst));
+ result = prime * result + ((mSecond == null) ? 0 : System.identityHashCode(mSecond));
+ mCachedHashCode = result;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCachedHashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ LockPair other = (LockPair) obj;
+ return mFirst == other.mFirst && mSecond == other.mSecond;
+ }
+ }
+
+ private static class OrderData {
+ final int mTid;
+ final String mThreadName;
+ final AnnotatedStackTraceElement[] mStack;
+
+ OrderData(int tid, String threadName, AnnotatedStackTraceElement[] stack) {
+ this.mTid = tid;
+ this.mThreadName = threadName;
+ this.mStack = stack;
+ }
+ }
+
+ private static ConcurrentMap<LockPair, OrderData> sLockOrderMap = new ConcurrentHashMap<>();
+
+ @Override
+ public void pre(Object lock) {
+ handlePre(Thread.currentThread(), lock);
+ }
+
+ @Override
+ public void post(Object lock) {
+ handlePost(Thread.currentThread(), lock);
+ }
+
+ private void handlePre(Thread self, Object lock) {
+ List<Object> heldLocks = mHeldLocks.get();
+
+ LockHook.updateDeepestNest(heldLocks.size() + 1);
+
+ heldLocks.add(lock);
+ if (heldLocks.size() == 1) {
+ return;
+ }
+
+ // Data about this location. Cached and lazily initialized.
+ AnnotatedStackTraceElement[] annotatedStack = null;
+ OrderData orderData = null;
+
+ // Reused tmp pair;
+ LockPair tmp = new LockPair(lock, lock);
+
+ int size = heldLocks.size() - 1;
+ for (int i = 0; i < size; i++) {
+ Object alreadyHeld = heldLocks.get(i);
+ if (SKIP_RECURSIVE && lock == alreadyHeld) {
+ return;
+ }
+
+ // Check if we've already seen alreadyHeld -> lock.
+ tmp.set(alreadyHeld, lock);
+ if (sLockOrderMap.containsKey(tmp)) {
+ continue; // Already seen.
+ }
+
+ // Note: could insert the OrderData now. This would mean we only
+ // report one instance for each order violation, but it avoids
+ // the expensive hashing in handleViolation for duplicate stacks.
+
+ // Locking alreadyHeld -> lock, check whether the inverse exists.
+ tmp.set(lock, alreadyHeld);
+
+ // We technically need a critical section here. Add synchronized and
+ // skip
+ // instrumenting this class. For now, a concurrent hash map is good
+ // enough.
+
+ OrderData oppositeData = sLockOrderMap.getOrDefault(tmp, null);
+ if (oppositeData != null) {
+ if (annotatedStack == null) {
+ annotatedStack = VMStack.getAnnotatedThreadStackTrace(self);
+ }
+ postViolation(self, alreadyHeld, lock, annotatedStack, oppositeData);
+ continue;
+ }
+
+ // Enter our occurrence.
+ if (annotatedStack == null) {
+ annotatedStack = VMStack.getAnnotatedThreadStackTrace(self);
+ }
+ if (orderData == null) {
+ orderData = new OrderData((int) self.getId(), self.getName(), annotatedStack);
+ }
+ sLockOrderMap.putIfAbsent(new LockPair(alreadyHeld, lock), orderData);
+
+ // Check again whether we might have raced with the opposite.
+ oppositeData = sLockOrderMap.getOrDefault(tmp, null);
+ if (oppositeData != null) {
+ postViolation(self, alreadyHeld, lock, annotatedStack, oppositeData);
+ }
+ }
+ }
+
+ private void handlePost(Thread self, Object lock) {
+ List<Object> heldLocks = mHeldLocks.get();
+ if (heldLocks.isEmpty()) {
+ Log.wtf("LockCheckMine", "Empty thread list on post()");
+ return;
+ }
+ int index = heldLocks.size() - 1;
+ if (heldLocks.get(index) != lock) {
+ Log.wtf("LockCheckMine", "post(" + Violation.describeLock(lock) + ") vs [..., "
+ + Violation.describeLock(heldLocks.get(index)) + "]");
+ return;
+ }
+ heldLocks.remove(index);
+ }
+
+ private static class Violation implements LockHook.Violation {
+ int mSelfTid;
+ String mSelfName;
+ Object mAlreadyHeld;
+ Object mLock;
+ AnnotatedStackTraceElement[] mStack;
+ OrderData mOppositeData;
+
+ private static final int STACK_OFFSET = 4;
+
+ Violation(Thread self, Object alreadyHeld, Object lock,
+ AnnotatedStackTraceElement[] stack, OrderData oppositeData) {
+ this.mSelfTid = (int) self.getId();
+ this.mSelfName = self.getName();
+ this.mAlreadyHeld = alreadyHeld;
+ this.mLock = lock;
+ this.mStack = stack;
+ this.mOppositeData = oppositeData;
+ }
+
+ private static String getAnnotatedStackString(AnnotatedStackTraceElement[] stackTrace,
+ int skip, String extra, int prefixAfter, String prefix) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = skip; i < stackTrace.length; i++) {
+ AnnotatedStackTraceElement element = stackTrace[i];
+ sb.append(" ").append(i >= prefixAfter ? prefix : "").append("at ")
+ .append(element.getStackTraceElement()).append('\n');
+ if (i == skip && extra != null) {
+ sb.append(" ").append(extra).append('\n');
+ }
+ if (element.getHeldLocks() != null) {
+ for (Object held : element.getHeldLocks()) {
+ sb.append(" ").append(i >= prefixAfter ? prefix : "")
+ .append(describeLocking(held, "locked")).append('\n');
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String describeLocking(Object lock, String action) {
+ return String.format("- %s %s", action, describeLock(lock));
+ }
+
+ private static int getTo(AnnotatedStackTraceElement[] stack, Object searchFor) {
+ // Extract the range of the annotated stack.
+ int to = stack.length - 1;
+ for (int i = 0; i < stack.length; i++) {
+ Object[] locks = stack[i].getHeldLocks();
+ if (locks != null) {
+ for (Object heldLock : locks) {
+ if (heldLock == searchFor) {
+ to = i;
+ break;
+ }
+ }
+ }
+ }
+ return to;
+ }
+
+ private static String describeLock(Object lock) {
+ return String.format("<0x%08x> (a %s)", System.identityHashCode(lock),
+ lock.getClass().getName());
+ }
+
+ // Synthesize an exception.
+ public Throwable getException() {
+ RuntimeException inner = new RuntimeException("Previously locked");
+ inner.setStackTrace(synthesizeStackTrace(mOppositeData.mStack));
+
+ RuntimeException outer = new RuntimeException(toString(), inner);
+ outer.setStackTrace(synthesizeStackTrace(mStack));
+
+ return outer;
+ }
+
+ private StackTraceElement[] synthesizeStackTrace(AnnotatedStackTraceElement[] stack) {
+
+ StackTraceElement[] out = new StackTraceElement[stack.length - STACK_OFFSET];
+ for (int i = 0; i < out.length; i++) {
+ out[i] = stack[i + STACK_OFFSET].getStackTraceElement();
+ }
+ return out;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Lock inversion detected!\n");
+ sb.append(" Locked ");
+ sb.append(describeLock(mLock));
+ sb.append(" -> ");
+ sb.append(describeLock(mAlreadyHeld));
+ sb.append(" on thread ").append(mOppositeData.mTid).append(" (")
+ .append(mOppositeData.mThreadName).append(")");
+ sb.append(" at:\n");
+ sb.append(getAnnotatedStackString(mOppositeData.mStack, STACK_OFFSET,
+ describeLocking(mAlreadyHeld, "will lock"), getTo(mOppositeData.mStack, mLock)
+ + 1, " | "));
+ sb.append(" Locking ");
+ sb.append(describeLock(mAlreadyHeld));
+ sb.append(" -> ");
+ sb.append(describeLock(mLock));
+ sb.append(" on thread ").append(mSelfTid).append(" (").append(mSelfName).append(")");
+ sb.append(" at:\n");
+ sb.append(getAnnotatedStackString(mStack, STACK_OFFSET,
+ describeLocking(mLock, "will lock"),
+ getTo(mStack, mAlreadyHeld) + 1, " | "));
+
+ return sb.toString();
+ }
+ }
+
+ private void postViolation(Thread self, Object alreadyHeld, Object lock,
+ AnnotatedStackTraceElement[] annotatedStack, OrderData oppositeData) {
+ mQueue.offer(new Violation(self, alreadyHeld, lock, annotatedStack, oppositeData));
+ }
+
+ private void handleViolation(Violation v) {
+ mNumDetected.incrementAndGet();
+ // Extract the range of the annotated stack.
+ int to = Violation.getTo(v.mStack, v.mAlreadyHeld);
+
+ if (LockHook.shouldDumpStacktrace(mStacktraceHasher.get(), mDumpedStacktraceHashes,
+ Boolean.TRUE, v.mStack, 0, to)) {
+ mNumDetectedUnique.incrementAndGet();
+ LockHook.addViolation(v);
+ }
+ }
+
+ private void checker() {
+ LockHook.doCheckOnThisThread(false);
+
+ for (;;) {
+ try {
+ Violation v = mQueue.take();
+ handleViolation(v);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public int getNumDetected() {
+ return mNumDetected.get();
+ }
+
+ @Override
+ public int getNumDetectedUnique() {
+ return mNumDetectedUnique.get();
+ }
+
+ @Override
+ public String getCheckerName() {
+ return "Standard LockChecker";
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.print(getCheckerName());
+ pw.print(": d=");
+ pw.print(getNumDetected());
+ pw.print(" du=");
+ pw.print(getNumDetectedUnique());
+ }
+}
diff --git a/tools/lock_agent/start_with_lockagent.sh b/tools/lock_agent/start_with_lockagent.sh
new file mode 100755
index 000000000000..70ed5c5e1dd7
--- /dev/null
+++ b/tools/lock_agent/start_with_lockagent.sh
@@ -0,0 +1,13 @@
+#!/system/bin/sh
+
+AGENT_OPTIONS=
+if [[ "$1" == --agent-options ]] ; then
+ shift
+ AGENT_OPTIONS="=$1"
+ shift
+fi
+
+APP=$1
+shift
+
+$APP -Xplugin:libopenjdkjvmti.so "-agentpath:liblockagent.so$AGENT_OPTIONS" $@
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
index edb9a49f4106..828cce72dda9 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
@@ -13,6 +13,9 @@
*/
package lockedregioncodeinjection;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -24,8 +27,6 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassWriter;
public class Main {
public static void main(String[] args) throws IOException {
@@ -74,6 +75,7 @@ public class Main {
while (srcEntries.hasMoreElements()) {
ZipEntry entry = srcEntries.nextElement();
ZipEntry newEntry = new ZipEntry(entry.getName());
+ newEntry.setTime(entry.getTime());
zos.putNextEntry(newEntry);
BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry));
diff --git a/tools/preload-check/Android.bp b/tools/preload-check/Android.bp
new file mode 100644
index 000000000000..87b31d22af32
--- /dev/null
+++ b/tools/preload-check/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 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.
+
+java_test_host {
+ name: "PreloadCheck",
+ srcs: ["src/**/*.java"],
+ java_resources: [":preloaded-classes-blacklist"],
+ libs: ["tradefed"],
+ test_suites: ["general-tests"],
+ required: ["preload-check-device"],
+ data: [":preload-check-device"],
+}
diff --git a/tools/preload-check/AndroidTest.xml b/tools/preload-check/AndroidTest.xml
new file mode 100644
index 000000000000..a0645d5b1051
--- /dev/null
+++ b/tools/preload-check/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for PreloadCheck">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="preload-check-device.jar->/data/local/tmp/preload-check-device.jar" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.preload.check.PreloadCheck" />
+ </test>
+</configuration>
diff --git a/tools/preload-check/TEST_MAPPING b/tools/preload-check/TEST_MAPPING
new file mode 100644
index 000000000000..d09805ec08b6
--- /dev/null
+++ b/tools/preload-check/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "PreloadCheck"
+ }
+ ]
+}
diff --git a/tools/preload-check/device/Android.bp b/tools/preload-check/device/Android.bp
new file mode 100644
index 000000000000..f40d8ba5a287
--- /dev/null
+++ b/tools/preload-check/device/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 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.
+
+java_test_helper_library {
+ name: "preload-check-device",
+ host_supported: false,
+ device_supported: true,
+ compile_dex: true,
+
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ dex_preopt: {
+ enabled: false,
+ },
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload-check/device/src/com/android/preload/check/Initialized.java
index f04360fc1942..81c074c04d4c 100644
--- a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java
+++ b/tools/preload-check/device/src/com/android/preload/check/Initialized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-package com.android.preload.classdataretrieval;
-
-import com.android.ddmlib.Client;
-
-import java.util.Map;
+package com.android.preload.check;
/**
- * Retrieve a class-to-classloader map for loaded classes from the client.
+ * Test that the given boot classpath class is initialized.
*/
-public interface ClassDataRetriever {
-
- public Map<String, String> getClassData(Client client);
+public class Initialized {
+ public static void main(String[] args) throws Exception {
+ Util.assertInitialized(args[0], null);
+ System.out.println("OK");
+ }
}
diff --git a/tools/preload-check/device/src/com/android/preload/check/IntegrityCheck.java b/tools/preload-check/device/src/com/android/preload/check/IntegrityCheck.java
new file mode 100644
index 000000000000..1c1e927c54c4
--- /dev/null
+++ b/tools/preload-check/device/src/com/android/preload/check/IntegrityCheck.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.preload.check;
+
+/**
+ * Test that a helper class is first seen as uninitialized, then initialized after forced.
+ */
+public class IntegrityCheck {
+ public static void main(String[] args) throws Exception {
+ ClassLoader loader = IntegrityCheck.class.getClassLoader();
+
+ Util.assertNotInitialized("com.android.preload.check.IntegrityCheck$StatusHelper", loader);
+
+ Class.forName("com.android.preload.check.IntegrityCheck$StatusHelper",
+ /* initialize */ true, loader);
+
+ Util.assertInitialized("com.android.preload.check.IntegrityCheck$StatusHelper", loader);
+
+ System.out.println("OK");
+ }
+
+ @SuppressWarnings("unused")
+ private static class StatusHelper {
+ private final static Object defer = new Object();
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload-check/device/src/com/android/preload/check/NotInitialized.java
index f45aad06ac6b..c3d2c7737c7d 100644
--- a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java
+++ b/tools/preload-check/device/src/com/android/preload/check/NotInitialized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,26 +14,14 @@
* limitations under the License.
*/
-package com.android.preload.ui;
+package com.android.preload.check;
-import com.android.ddmlib.SyncService.ISyncProgressMonitor;
-
-public class NullProgressMonitor implements ISyncProgressMonitor {
-
- @Override
- public void advance(int arg0) {}
-
- @Override
- public boolean isCanceled() {
- return false;
+/**
+ * Test that the given boot classpath class is not initialized.
+ */
+public class NotInitialized {
+ public static void main(String[] args) throws Exception {
+ Util.assertNotInitialized(args[0], null);
+ System.out.println("OK");
}
-
- @Override
- public void start(int arg0) {}
-
- @Override
- public void startSubTask(String arg0) {}
-
- @Override
- public void stop() {}
-} \ No newline at end of file
+}
diff --git a/tools/preload-check/device/src/com/android/preload/check/NotInitializedRegex.java b/tools/preload-check/device/src/com/android/preload/check/NotInitializedRegex.java
new file mode 100644
index 000000000000..d942bad9b6a8
--- /dev/null
+++ b/tools/preload-check/device/src/com/android/preload/check/NotInitializedRegex.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.preload.check;
+
+import dalvik.system.DexFile;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Test boot classpath classes that satisfy a given regular expression to be not initialized.
+ * Optionally check that at least one class was matched.
+ */
+public class NotInitializedRegex {
+ /**
+ * First arg (mandatory): regular exception. Second arg (optional): boolean to denote a
+ * required match.
+ */
+ public static void main(String[] args) throws Exception {
+ Matcher m = Pattern.compile(args[0]).matcher("");
+ boolean requiresMatch = args.length > 1 ? Boolean.parseBoolean(args[1]) : false;
+
+ Collection<DexFile> dexFiles = Util.getBootDexFiles();
+ int matched = 0, notMatched = 0;
+ for (DexFile dexFile : dexFiles) {
+ Enumeration<String> entries = dexFile.entries();
+ while (entries.hasMoreElements()) {
+ String entry = entries.nextElement();
+ m.reset(entry);
+ if (m.matches()) {
+ System.out.println(entry + ": match");
+ matched++;
+ check(entry);
+ } else {
+ System.out.println(entry + ": no match");
+ notMatched++;
+ }
+ }
+ }
+ System.out.println("Matched: " + matched + " Not-Matched: " + notMatched);
+ if (requiresMatch && matched == 0) {
+ throw new RuntimeException("Did not find match");
+ }
+ System.out.println("OK");
+ }
+
+ private static void check(String name) {
+ Util.assertNotInitialized(name, null);
+ }
+}
diff --git a/tools/preload-check/device/src/com/android/preload/check/Util.java b/tools/preload-check/device/src/com/android/preload/check/Util.java
new file mode 100644
index 000000000000..fccea0a0c107
--- /dev/null
+++ b/tools/preload-check/device/src/com/android/preload/check/Util.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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.preload.check;
+
+import dalvik.system.DexFile;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Util {
+ private static Field statusField;
+
+ static {
+ try {
+ Class<?> klass = Class.class;
+ statusField = klass.getDeclaredField("status");
+ statusField.setAccessible(true);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ // Reset the framework's kill handler.
+ Thread.setDefaultUncaughtExceptionHandler(null);
+ }
+
+ public static Collection<DexFile> getBootDexFiles() throws Exception {
+ Class<?> vmClassLoaderClass = Class.forName("java.lang.VMClassLoader");
+ Method getResources = vmClassLoaderClass.getDeclaredMethod("getResources", String.class);
+ getResources.setAccessible(true);
+ LinkedList<DexFile> res = new LinkedList<>();
+ for (int i = 1;; i++) {
+ try {
+ String name = "classes" + (i > 1 ? String.valueOf(i) : "") + ".dex";
+ @SuppressWarnings("unchecked")
+ List<URL> urls = (List<URL>) getResources.invoke(null, name);
+ if (urls.isEmpty()) {
+ break;
+ }
+ for (URL url : urls) {
+ // Make temp copy, so we can use public API. Would be nice to use in-memory, but
+ // those are unstable.
+ String tmp = "/data/local/tmp/tmp.dex";
+ try (BufferedInputStream in = new BufferedInputStream(url.openStream());
+ BufferedOutputStream out = new BufferedOutputStream(
+ new FileOutputStream(tmp))) {
+ byte[] buf = new byte[4096];
+ for (;;) {
+ int r = in.read(buf);
+ if (r == -1) {
+ break;
+ }
+ out.write(buf, 0, r);
+ }
+ }
+ try {
+ res.add(new DexFile(tmp));
+ } catch (Exception dexError) {
+ dexError.printStackTrace(System.out);
+ }
+ new File(tmp).delete();
+ }
+ } catch (Exception ignored) {
+ break;
+ }
+ }
+ return res;
+ }
+
+ public static boolean isInitialized(Class<?> klass) throws Exception {
+ Object val = statusField.get(klass);
+ if (val == null || !(val instanceof Integer)) {
+ throw new IllegalStateException(String.valueOf(val));
+ }
+ int intVal = (int)val;
+ intVal = (intVal >> (32-4)) & 0xf;
+ return intVal >= 14;
+ }
+
+ public static void assertTrue(boolean val, String msg) {
+ if (!val) {
+ throw new RuntimeException(msg);
+ }
+ }
+
+ public static void assertInitializedState(String className, boolean expected,
+ ClassLoader loader) {
+ boolean initialized;
+ try {
+ Class<?> klass = Class.forName(className, /* initialize */ false, loader);
+ initialized = isInitialized(klass);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ assertTrue(expected == initialized, className);
+ }
+
+ public static void assertNotInitialized(String className, ClassLoader loader) {
+ assertInitializedState(className, false, loader);
+ }
+
+ public static void assertInitialized(String className, ClassLoader loader) {
+ assertInitializedState(className, true, loader);
+ }
+}
diff --git a/tools/preload-check/src/com/android/preload/check/PreloadCheck.java b/tools/preload-check/src/com/android/preload/check/PreloadCheck.java
new file mode 100644
index 000000000000..00fd414e3ee2
--- /dev/null
+++ b/tools/preload-check/src/com/android/preload/check/PreloadCheck.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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.preload.check;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PreloadCheck implements IDeviceTest {
+ private ITestDevice mTestDevice;
+
+ private static final String TEST_CLASSPATH = "/data/local/tmp/preload-check-device.jar";
+
+ @Override
+ public void setDevice(ITestDevice testDevice) {
+ mTestDevice = testDevice;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mTestDevice;
+ }
+
+ /**
+ * Test that checks work as expected.
+ */
+ @Test
+ public void testStatus() throws Exception {
+ run("com.android.preload.check.IntegrityCheck");
+ }
+
+ /**
+ * b/130206915.
+ */
+ @Test
+ public void testAsyncTask() throws Exception {
+ run("com.android.preload.check.NotInitialized", "android.os.AsyncTask");
+ }
+
+ /**
+ * Just a check for something we expect to see initialized.
+ */
+ @Test
+ public void testAnimator() throws Exception {
+ run("com.android.preload.check.Initialized", "android.animation.Animator");
+ }
+
+ /**
+ * Test the classes mentioned in the embedded preloaded-classes blacklist.
+ */
+ @Test
+ public void testBlackList() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass()
+ .getResourceAsStream("/preloaded-classes-blacklist")))) {
+ String s;
+ while ((s = br.readLine()) != null) {
+ s = s.trim();
+ if (s.startsWith("#") || s.isEmpty()) {
+ continue;
+ }
+ try {
+ run("com.android.preload.check.NotInitialized", s);
+ } catch (Throwable t) {
+ if (sb.length() > 0) {
+ sb.append('\n');
+ }
+ sb.append(t.getMessage());
+ }
+ }
+ }
+ if (sb.length() > 0) {
+ throw new RuntimeException(sb.toString());
+ }
+ }
+
+ /**
+ * Test the classes ending in NoPreloadHolder are not initialized.
+ */
+ @Test
+ public void testNoPreloadHolder() throws Exception {
+ run("com.android.preload.check.NotInitializedRegex", ".*NoPreloadHolder$", "true");
+ }
+
+ private void run(String cmd, String... args) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ sb.append("app_process ")
+ .append("-cp ").append(TEST_CLASSPATH)
+ .append(" /system/bin ")
+ .append(cmd);
+ for (String arg : args) {
+ sb.append(' ').append(escape(arg));
+ }
+ String res = mTestDevice.executeShellCommand(sb.toString());
+ assertTrue(sb.toString() + "\n===\n" + res, res.trim().endsWith("OK"));
+ }
+
+ private static String escape(String input) {
+ if (input.indexOf('$') == -1) {
+ return input;
+ }
+ return input.replace("$", "\\$");
+ }
+}
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
deleted file mode 100644
index d3ee1d370855..000000000000
--- a/tools/preload2/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-# To connect to devices (and take hprof dumps).
-LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt tools-common-prebuilt
-
-# To process hprof dumps.
-LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib
-
-# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
-# convenience (and to not depend on internal JDK APIs).
-LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit-host
-
-LOCAL_MODULE:= preload2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-# Copy to build artifacts
-$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):$(LOCAL_MODULE).jar)
-
-# Copy the preload-tool shell script to the host's bin directory.
-include $(CLEAR_VARS)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE := preload-tool
-LOCAL_SRC_FILES := preload-tool
-LOCAL_REQUIRED_MODULES := preload2
-include $(BUILD_PREBUILT)
diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool
deleted file mode 100644
index 322b62fda071..000000000000
--- a/tools/preload2/preload-tool
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2015 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.
-
-# This script is used on the host only. It uses a common subset
-# shell dialect that should work well. It is partially derived
-# from art/tools/art.
-
-function follow_links() {
- if [ z"$BASH_SOURCE" != z ]; then
- file="$BASH_SOURCE"
- else
- file="$0"
- fi
- while [ -h "$file" ]; do
- # On Mac OS, readlink -f doesn't work.
- file="$(readlink "$file")"
- done
- echo "$file"
-}
-
-
-PROG_NAME="$(follow_links)"
-PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
-ANDROID_ROOT=$PROG_DIR/..
-
-java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main $@
diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java
deleted file mode 100644
index 71ef025d6587..000000000000
--- a/tools/preload2/src/com/android/preload/ClientUtils.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-
-/**
- * Helper class for common communication with a Client (the ddms name for a running application).
- *
- * Instances take a default timeout parameter that's applied to all functions without explicit
- * timeout. Timeouts are in milliseconds.
- */
-public class ClientUtils {
-
- private int defaultTimeout;
-
- public ClientUtils() {
- this(10000);
- }
-
- public ClientUtils(int defaultTimeout) {
- this.defaultTimeout = defaultTimeout;
- }
-
- /**
- * Shortcut for findClient with default timeout.
- */
- public Client findClient(IDevice device, String processName, int processPid) {
- return findClient(device, processName, processPid, defaultTimeout);
- }
-
- /**
- * Find the client with the given process name or process id. The name takes precedence over
- * the process id (if valid). Stop looking after the given timeout.
- *
- * @param device The device to communicate with.
- * @param processName The name of the process. May be null.
- * @param processPid The pid of the process. Values less than or equal to zero are ignored.
- * @param timeout The amount of milliseconds to wait, at most.
- * @return The client, if found. Otherwise null.
- */
- public Client findClient(IDevice device, String processName, int processPid, int timeout) {
- WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
- return wfc.get();
- }
-
- /**
- * Shortcut for findAllClients with default timeout.
- */
- public Client[] findAllClients(IDevice device) {
- return findAllClients(device, defaultTimeout);
- }
-
- /**
- * Retrieve all clients known to the given device. Wait at most the given timeout.
- *
- * @param device The device to investigate.
- * @param timeout The amount of milliseconds to wait, at most.
- * @return An array of clients running on the given device. May be null depending on the
- * device implementation.
- */
- public Client[] findAllClients(IDevice device, int timeout) {
- if (device.hasClients()) {
- return device.getClients();
- }
- WaitForClients wfc = new WaitForClients(device, timeout);
- return wfc.get();
- }
-
- private static class WaitForClient implements IClientChangeListener {
-
- private IDevice device;
- private String processName;
- private int processPid;
- private long timeout;
- private Client result;
-
- public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
- this.device = device;
- this.processName = processName;
- this.processPid = processPid;
- this.timeout = timeout;
- this.result = null;
- }
-
- public Client get() {
- synchronized (this) {
- AndroidDebugBridge.addClientChangeListener(this);
-
- // Maybe it's already there.
- if (result == null) {
- result = searchForClient(device);
- }
-
- if (result == null) {
- try {
- wait(timeout);
- } catch (InterruptedException e) {
- // Note: doesn't guard for spurious wakeup.
- }
- }
- }
-
- AndroidDebugBridge.removeClientChangeListener(this);
- return result;
- }
-
- private Client searchForClient(IDevice device) {
- if (processName != null) {
- Client tmp = device.getClient(processName);
- if (tmp != null) {
- return tmp;
- }
- }
- if (processPid > 0) {
- String name = device.getClientName(processPid);
- if (name != null && !name.isEmpty()) {
- Client tmp = device.getClient(name);
- if (tmp != null) {
- return tmp;
- }
- }
- }
- if (processPid > 0) {
- // Try manual search.
- for (Client cl : device.getClients()) {
- if (cl.getClientData().getPid() == processPid
- && cl.getClientData().getClientDescription() != null) {
- return cl;
- }
- }
- }
- return null;
- }
-
- private boolean isTargetClient(Client c) {
- if (processPid > 0 && c.getClientData().getPid() == processPid) {
- return true;
- }
- if (processName != null
- && processName.equals(c.getClientData().getClientDescription())) {
- return true;
- }
- return false;
- }
-
- @Override
- public void clientChanged(Client arg0, int arg1) {
- synchronized (this) {
- if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
- if (isTargetClient(arg0)) {
- result = arg0;
- notifyAll();
- }
- }
- }
- }
- }
-
- private static class WaitForClients implements IClientChangeListener {
-
- private IDevice device;
- private long timeout;
-
- public WaitForClients(IDevice device, long timeout) {
- this.device = device;
- this.timeout = timeout;
- }
-
- public Client[] get() {
- synchronized (this) {
- AndroidDebugBridge.addClientChangeListener(this);
-
- if (device.hasClients()) {
- return device.getClients();
- }
-
- try {
- wait(timeout); // Note: doesn't guard for spurious wakeup.
- } catch (InterruptedException exc) {
- }
-
- // We will be woken up when the first client data arrives. Sleep a little longer
- // to give (hopefully all of) the rest of the clients a chance to become available.
- // Note: a loop with timeout is brittle as well and complicated, just accept this
- // for now.
- try {
- Thread.sleep(500);
- } catch (InterruptedException exc) {
- }
- }
-
- AndroidDebugBridge.removeClientChangeListener(this);
-
- return device.getClients();
- }
-
- @Override
- public void clientChanged(Client arg0, int arg1) {
- synchronized (this) {
- if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
- notifyAll();
- }
- }
- }
- }
-}
diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java
deleted file mode 100644
index 18cab7bee12d..000000000000
--- a/tools/preload2/src/com/android/preload/DeviceUtils.java
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
-import com.android.preload.classdataretrieval.hprof.Hprof;
-import com.android.ddmlib.DdmPreferences;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.ddmlib.SyncException;
-import com.android.ddmlib.TimeoutException;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for some device routines.
- */
-public class DeviceUtils {
-
- // Locations
- private static final String PRELOADED_CLASSES_FILE = "/etc/preloaded-classes";
- // Shell commands
- private static final String CREATE_EMPTY_PRELOADED_CMD = "touch " + PRELOADED_CLASSES_FILE;
- private static final String DELETE_CACHE_CMD = "rm /data/dalvik-cache/*/*boot.art";
- private static final String DELETE_PRELOADED_CMD = "rm " + PRELOADED_CLASSES_FILE;
- private static final String READ_PRELOADED_CMD = "cat " + PRELOADED_CLASSES_FILE;
- private static final String START_SHELL_CMD = "start";
- private static final String STOP_SHELL_CMD = "stop";
- private static final String REMOUNT_SYSTEM_CMD = "mount -o rw,remount /system";
- private static final String UNSET_BOOTCOMPLETE_CMD = "setprop dev.bootcomplete \"0\"";
-
- public static void init(int debugPort) {
- DdmPreferences.setSelectedDebugPort(debugPort);
-
- Hprof.init();
-
- AndroidDebugBridge.init(true);
-
- AndroidDebugBridge.createBridge();
- }
-
- /**
- * Run a command in the shell on the device.
- */
- public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
- doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
- }
-
- /**
- * Run a command in the shell on the device. Collects and returns the console output.
- */
- public static String doShellReturnString(IDevice device, String cmdline, long timeout,
- TimeUnit unit) {
- CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
- doShell(device, cmdline, rec, timeout, unit);
- return rec.toString();
- }
-
- /**
- * Run a command in the shell on the device, directing all output to the given receiver.
- */
- public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
- long timeout, TimeUnit unit) {
- try {
- device.executeShellCommand(cmdline, receiver, timeout, unit);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Run am start on the device.
- */
- public static void doAMStart(IDevice device, String name, String activity) {
- doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
- }
-
- /**
- * Find the device with the given serial. Give up after the given timeout (in milliseconds).
- */
- public static IDevice findDevice(String serial, int timeout) {
- WaitForDevice wfd = new WaitForDevice(serial, timeout);
- return wfd.get();
- }
-
- /**
- * Get all devices ddms knows about. Wait at most for the given timeout.
- */
- public static IDevice[] findDevices(int timeout) {
- WaitForDevice wfd = new WaitForDevice(null, timeout);
- wfd.get();
- return AndroidDebugBridge.getBridge().getDevices();
- }
-
- /**
- * Return the build type of the given device. This is the value of the "ro.build.type"
- * system property.
- */
- public static String getBuildType(IDevice device) {
- try {
- Future<String> buildType = device.getSystemProperty("ro.build.type");
- return buildType.get(500, TimeUnit.MILLISECONDS);
- } catch (Exception e) {
- }
- return null;
- }
-
- /**
- * Check whether the given device has a pre-optimized boot image. More precisely, checks
- * whether /system/framework/ * /boot.art exists.
- */
- public static boolean hasPrebuiltBootImage(IDevice device) {
- String ret =
- doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
-
- return !ret.contains("No such file or directory");
- }
-
- /**
- * Write over the preloaded-classes file with an empty or existing file and regenerate the boot
- * image as necessary.
- *
- * @param device
- * @param pcFile
- * @param bootTimeout
- * @throws AdbCommandRejectedException
- * @throws IOException
- * @throws TimeoutException
- * @throws SyncException
- * @return true if successfully overwritten, false otherwise
- */
- public static boolean overwritePreloaded(IDevice device, File pcFile, long bootTimeout)
- throws AdbCommandRejectedException, IOException, TimeoutException, SyncException {
- boolean writeEmpty = (pcFile == null);
- if (writeEmpty) {
- // Check if the preloaded-classes file is already empty.
- String oldContent =
- doShellReturnString(device, READ_PRELOADED_CMD, 1, TimeUnit.SECONDS);
- if (oldContent.trim().equals("")) {
- System.out.println("Preloaded-classes already empty.");
- return true;
- }
- }
-
- // Stop the system server etc.
- doShell(device, STOP_SHELL_CMD, 1, TimeUnit.SECONDS);
- // Remount the read-only system partition
- doShell(device, REMOUNT_SYSTEM_CMD, 1, TimeUnit.SECONDS);
- // Delete the preloaded-classes file
- doShell(device, DELETE_PRELOADED_CMD, 1, TimeUnit.SECONDS);
- // Delete the dalvik cache files
- doShell(device, DELETE_CACHE_CMD, 1, TimeUnit.SECONDS);
- if (writeEmpty) {
- // Write an empty preloaded-classes file
- doShell(device, CREATE_EMPTY_PRELOADED_CMD, 500, TimeUnit.MILLISECONDS);
- } else {
- // Push the new preloaded-classes file
- device.pushFile(pcFile.getAbsolutePath(), PRELOADED_CLASSES_FILE);
- }
- // Manually reset the boot complete flag
- doShell(device, UNSET_BOOTCOMPLETE_CMD, 1, TimeUnit.SECONDS);
- // Restart system server on the device
- doShell(device, START_SHELL_CMD, 1, TimeUnit.SECONDS);
- // Wait for the boot complete flag and return the outcome.
- return waitForBootComplete(device, bootTimeout);
- }
-
- private static boolean waitForBootComplete(IDevice device, long timeout) {
- // Do a loop checking each second whether bootcomplete. Wait for at most the given
- // threshold.
- Date startDate = new Date();
- for (;;) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // Ignore spurious wakeup.
- }
- // Check whether bootcomplete.
- String ret =
- doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
- if (ret.trim().equals("1")) {
- break;
- }
- System.out.println("Still not booted: " + ret);
-
- // Check whether we timed out. This is a simplistic check that doesn't take into account
- // things like switches in time.
- Date endDate = new Date();
- long seconds =
- TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
- if (seconds > timeout) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Enable method-tracing on device. The system should be restarted after this.
- */
- public static void enableTracing(IDevice device) {
- // Disable selinux.
- doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
-
- // Make the profile directory world-writable.
- doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
-
- // Enable streaming method tracing with a small 1K buffer.
- doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
- doShell(device, "setprop dalvik.vm.method-trace-file "
- + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
- doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
- doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
- }
-
- private static class NullShellOutputReceiver implements IShellOutputReceiver {
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- @Override
- public void flush() {}
-
- @Override
- public void addOutput(byte[] arg0, int arg1, int arg2) {}
- }
-
- private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
-
- private StringBuilder builder = new StringBuilder();
-
- @Override
- public String toString() {
- String ret = builder.toString();
- // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
- while (ret.endsWith("\r") || ret.endsWith("\n")) {
- ret = ret.substring(0, ret.length() - 1);
- }
- return ret;
- }
-
- @Override
- public void addOutput(byte[] arg0, int arg1, int arg2) {
- builder.append(new String(arg0, arg1, arg2));
- }
-
- @Override
- public void flush() {}
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- }
-
- private static class WaitForDevice {
-
- private String serial;
- private long timeout;
- private IDevice device;
-
- public WaitForDevice(String serial, long timeout) {
- this.serial = serial;
- this.timeout = timeout;
- device = null;
- }
-
- public IDevice get() {
- if (device == null) {
- WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
- synchronized (wfdl) {
- AndroidDebugBridge.addDeviceChangeListener(wfdl);
-
- // Check whether we already know about this device.
- IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
- if (serial != null) {
- for (IDevice d : devices) {
- if (serial.equals(d.getSerialNumber())) {
- // Only accept if there are clients already. Else wait for the callback informing
- // us that we now have clients.
- if (d.hasClients()) {
- device = d;
- }
-
- break;
- }
- }
- } else {
- if (devices.length > 0) {
- device = devices[0];
- }
- }
-
- if (device == null) {
- try {
- wfdl.wait(timeout);
- } catch (InterruptedException e) {
- // Ignore spurious wakeups.
- }
- device = wfdl.getDevice();
- }
-
- AndroidDebugBridge.removeDeviceChangeListener(wfdl);
- }
- }
-
- if (device != null) {
- // Wait for clients.
- WaitForClientsListener wfcl = new WaitForClientsListener(device);
- synchronized (wfcl) {
- AndroidDebugBridge.addDeviceChangeListener(wfcl);
-
- if (!device.hasClients()) {
- try {
- wfcl.wait(timeout);
- } catch (InterruptedException e) {
- // Ignore spurious wakeups.
- }
- }
-
- AndroidDebugBridge.removeDeviceChangeListener(wfcl);
- }
- }
-
- return device;
- }
-
- private static class WaitForDeviceListener implements IDeviceChangeListener {
-
- private String serial;
- private IDevice device;
-
- public WaitForDeviceListener(String serial) {
- this.serial = serial;
- }
-
- public IDevice getDevice() {
- return device;
- }
-
- @Override
- public void deviceChanged(IDevice arg0, int arg1) {
- // We may get a device changed instead of connected. Handle like a connection.
- deviceConnected(arg0);
- }
-
- @Override
- public void deviceConnected(IDevice arg0) {
- if (device != null) {
- // Ignore updates.
- return;
- }
-
- if (serial == null || serial.equals(arg0.getSerialNumber())) {
- device = arg0;
- synchronized (this) {
- notifyAll();
- }
- }
- }
-
- @Override
- public void deviceDisconnected(IDevice arg0) {
- // Ignore disconnects.
- }
-
- }
-
- private static class WaitForClientsListener implements IDeviceChangeListener {
-
- private IDevice myDevice;
-
- public WaitForClientsListener(IDevice myDevice) {
- this.myDevice = myDevice;
- }
-
- @Override
- public void deviceChanged(IDevice arg0, int arg1) {
- if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
- // Got a client list, done here.
- synchronized (this) {
- notifyAll();
- }
- }
- }
-
- @Override
- public void deviceConnected(IDevice arg0) {
- }
-
- @Override
- public void deviceDisconnected(IDevice arg0) {
- }
-
- }
- }
-
-}
diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java
deleted file mode 100644
index d99722416a1d..000000000000
--- a/tools/preload2/src/com/android/preload/DumpData.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Holds the collected data for a process.
- */
-public class DumpData {
- /**
- * Name of the package (=application).
- */
- String packageName;
-
- /**
- * A map of class name to a string for the classloader. This may be a toString equivalent,
- * or just a unique ID.
- */
- Map<String, String> dumpData;
-
- /**
- * The Date when this data was captured. Mostly for display purposes.
- */
- Date date;
-
- /**
- * A cached value for the number of boot classpath classes (classloader value in dumpData is
- * null).
- */
- int bcpClasses;
-
- public DumpData(String packageName, Map<String, String> dumpData, Date date) {
- this.packageName = packageName;
- this.dumpData = dumpData;
- this.date = date;
-
- countBootClassPath();
- }
-
- public String getPackageName() {
- return packageName;
- }
-
- public Date getDate() {
- return date;
- }
-
- public Map<String, String> getDumpData() {
- return dumpData;
- }
-
- public void countBootClassPath() {
- bcpClasses = 0;
- for (Map.Entry<String, String> e : dumpData.entrySet()) {
- if (e.getValue() == null) {
- bcpClasses++;
- }
- }
- }
-
- // Return an inverted mapping.
- public Map<String, Set<String>> invertData() {
- Map<String, Set<String>> ret = new HashMap<>();
- for (Map.Entry<String, String> e : dumpData.entrySet()) {
- if (!ret.containsKey(e.getValue())) {
- ret.put(e.getValue(), new HashSet<String>());
- }
- ret.get(e.getValue()).add(e.getKey());
- }
- return ret;
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java
deleted file mode 100644
index 28625c5531e4..000000000000
--- a/tools/preload2/src/com/android/preload/DumpDataIO.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.File;
-import java.io.FileReader;
-import java.text.DateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Helper class for serialization and deserialization of a collection of DumpData objects to XML.
- */
-public class DumpDataIO {
-
- /**
- * Serialize the given collection to an XML document. Returns the produced string.
- */
- public static String serialize(Collection<DumpData> data) {
- // We'll do this by hand, constructing a DOM or similar is too complicated for our simple
- // use case.
-
- StringBuilder sb = new StringBuilder();
- sb.append("<preloaded-classes-data>\n");
-
- for (DumpData d : data) {
- serialize(d, sb);
- }
-
- sb.append("</preloaded-classes-data>\n");
- return sb.toString();
- }
-
- private static void serialize(DumpData d, StringBuilder sb) {
- sb.append("<data package=\"" + d.packageName + "\" date=\"" +
- DateFormat.getDateTimeInstance().format(d.date) +"\">\n");
-
- for (Map.Entry<String, String> e : d.dumpData.entrySet()) {
- sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n");
- }
-
- sb.append("</data>\n");
- }
-
- /**
- * Load a collection of DumpData objects from the given file.
- */
- public static Collection<DumpData> deserialize(File f) throws Exception {
- // Use SAX parsing. Our format is very simple. Don't do any schema validation or such.
-
- SAXParserFactory spf = SAXParserFactory.newInstance();
- spf.setNamespaceAware(false);
- SAXParser saxParser = spf.newSAXParser();
-
- XMLReader xmlReader = saxParser.getXMLReader();
- DumpDataContentHandler ddch = new DumpDataContentHandler();
- xmlReader.setContentHandler(ddch);
- xmlReader.parse(new InputSource(new FileReader(f)));
-
- return ddch.data;
- }
-
- private static class DumpDataContentHandler extends DefaultHandler {
- Collection<DumpData> data = new LinkedList<DumpData>();
- DumpData openData = null;
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
- if (qName.equals("data")) {
- if (openData != null) {
- throw new IllegalStateException();
- }
- String pkg = attributes.getValue("package");
- String dateString = attributes.getValue("date");
-
- if (pkg == null || dateString == null) {
- throw new IllegalArgumentException();
- }
-
- try {
- Date date = DateFormat.getDateTimeInstance().parse(dateString);
- openData = new DumpData(pkg, new HashMap<String, String>(), date);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- } else if (qName.equals("class")) {
- if (openData == null) {
- throw new IllegalStateException();
- }
- String className = attributes.getValue("name");
- String classLoader = attributes.getValue("classloader");
-
- if (className == null || classLoader == null) {
- throw new IllegalArgumentException();
- }
-
- openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- if (qName.equals("data")) {
- if (openData == null) {
- throw new IllegalStateException();
- }
- openData.countBootClassPath();
-
- data.add(openData);
- openData = null;
- }
- }
- }
-}
diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java
deleted file mode 100644
index d97cbf0df5e5..000000000000
--- a/tools/preload2/src/com/android/preload/DumpTableModel.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.table.AbstractTableModel;
-
-/**
- * A table model for collected DumpData. This is both the internal storage as well as the model
- * for display.
- */
-public class DumpTableModel extends AbstractTableModel {
-
- private List<DumpData> data = new ArrayList<DumpData>();
-
- public void addData(DumpData d) {
- data.add(d);
- fireTableRowsInserted(data.size() - 1, data.size() - 1);
- }
-
- public void clear() {
- int size = data.size();
- if (size > 0) {
- data.clear();
- fireTableRowsDeleted(0, size - 1);
- }
- }
-
- public List<DumpData> getData() {
- return data;
- }
-
- @Override
- public int getRowCount() {
- return data.size();
- }
-
- @Override
- public int getColumnCount() {
- return 4;
- }
-
- @Override
- public String getColumnName(int column) {
- switch (column) {
- case 0:
- return "Package";
- case 1:
- return "Date";
- case 2:
- return "# All Classes";
- case 3:
- return "# Boot Classpath Classes";
-
- default:
- throw new IndexOutOfBoundsException(String.valueOf(column));
- }
- }
-
- @Override
- public Object getValueAt(int rowIndex, int columnIndex) {
- DumpData d = data.get(rowIndex);
- switch (columnIndex) {
- case 0:
- return d.packageName;
- case 1:
- return d.date;
- case 2:
- return d.dumpData.size();
- case 3:
- return d.bcpClasses;
-
- default:
- throw new IndexOutOfBoundsException(String.valueOf(columnIndex));
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
deleted file mode 100644
index 2265e9557c4b..000000000000
--- a/tools/preload2/src/com/android/preload/Main.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.actions.ClearTableAction;
-import com.android.preload.actions.ComputeThresholdAction;
-import com.android.preload.actions.ComputeThresholdXAction;
-import com.android.preload.actions.DeviceSpecific;
-import com.android.preload.actions.ExportAction;
-import com.android.preload.actions.ImportAction;
-import com.android.preload.actions.ReloadListAction;
-import com.android.preload.actions.RunMonkeyAction;
-import com.android.preload.actions.ScanAllPackagesAction;
-import com.android.preload.actions.ScanPackageAction;
-import com.android.preload.actions.ShowDataAction;
-import com.android.preload.actions.WritePreloadedClassesAction;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-import com.android.preload.classdataretrieval.hprof.Hprof;
-import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
-import com.android.preload.ui.IUI;
-import com.android.preload.ui.SequenceUI;
-import com.android.preload.ui.SwingUI;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import javax.swing.Action;
-import javax.swing.DefaultListModel;
-
-public class Main {
-
- /**
- * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
- * off for now.
- */
- public final static boolean ENABLE_TRACING = false;
-
- /**
- * Ten-second timeout.
- */
- public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
-
- /**
- * Hprof timeout. Two minutes.
- */
- public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
-
- private IDevice device;
- private static ClientUtils clientUtils;
-
- private DumpTableModel dataTableModel;
- private DefaultListModel<Client> clientListModel;
-
- private IUI ui;
-
- // Actions that need to be updated once a device is selected.
- private Collection<DeviceSpecific> deviceSpecificActions;
-
- // Current main instance.
- private static Main top;
- private static boolean useJdwpClassDataRetriever = false;
-
- public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
- + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
- + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
-
-
- // Threads
- "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
- + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
- + "(.*\\$NoPreloadHolder$)";
-
- public final static String SCAN_ALL_CMD = "scan-all";
- public final static String SCAN_PACKAGE_CMD = "scan";
- public final static String COMPUTE_FILE_CMD = "comp";
- public final static String EXPORT_CMD = "export";
- public final static String IMPORT_CMD = "import";
- public final static String WRITE_CMD = "write";
-
- /**
- * @param args
- */
- public static void main(String[] args) {
- Main m;
- if (args.length > 0 && args[0].equals("--seq")) {
- m = createSequencedMain(args);
- } else {
- m = new Main(new SwingUI());
- }
-
- top = m;
- m.startUp();
- }
-
- public Main(IUI ui) {
- this.ui = ui;
-
- clientListModel = new DefaultListModel<Client>();
- dataTableModel = new DumpTableModel();
-
- clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout.
-
- List<Action> actions = new ArrayList<Action>();
- actions.add(new ReloadListAction(clientUtils, null, clientListModel));
- actions.add(new ClearTableAction(dataTableModel));
- actions.add(new RunMonkeyAction(null, dataTableModel));
- actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
- actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
- actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
- CLASS_PRELOAD_BLACKLIST));
- actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
- null));
- actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
- CLASS_PRELOAD_BLACKLIST));
- actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel));
- actions.add(new ShowDataAction(dataTableModel));
- actions.add(new ImportAction(dataTableModel));
- actions.add(new ExportAction(dataTableModel));
-
- deviceSpecificActions = new ArrayList<DeviceSpecific>();
- for (Action a : actions) {
- if (a instanceof DeviceSpecific) {
- deviceSpecificActions.add((DeviceSpecific)a);
- }
- }
-
- ui.prepare(clientListModel, dataTableModel, actions);
- }
-
- /**
- * @param args
- * @return
- */
- private static Main createSequencedMain(String[] args) {
- SequenceUI ui = new SequenceUI();
- Main main = new Main(ui);
-
- Iterator<String> it = Arrays.asList(args).iterator();
- it.next(); // --seq
- // Setup
- ui.choice("#" + it.next()); // Device.
- ui.confirmNo(); // Prepare: no.
- // Actions
- try {
- while (it.hasNext()) {
- String op = it.next();
- // Operation: Scan a single package
- if (SCAN_PACKAGE_CMD.equals(op)) {
- System.out.println("Scanning package.");
- ui.action(ScanPackageAction.class);
- ui.client(it.next());
- // Operation: Scan all packages
- } else if (SCAN_ALL_CMD.equals(op)) {
- System.out.println("Scanning all packages.");
- ui.action(ScanAllPackagesAction.class);
- // Operation: Export the output to a file
- } else if (EXPORT_CMD.equals(op)) {
- System.out.println("Exporting data.");
- ui.action(ExportAction.class);
- ui.output(new File(it.next()));
- // Operation: Import the input from a file or directory
- } else if (IMPORT_CMD.equals(op)) {
- System.out.println("Importing data.");
- File file = new File(it.next());
- if (!file.exists()) {
- throw new RuntimeException(
- String.format("File does not exist, %s.", file.getAbsolutePath()));
- } else if (file.isFile()) {
- ui.action(ImportAction.class);
- ui.input(file);
- } else if (file.isDirectory()) {
- for (File content : file.listFiles()) {
- ui.action(ImportAction.class);
- ui.input(content);
- }
- }
- // Operation: Compute preloaded classes with specific threshold
- } else if (COMPUTE_FILE_CMD.equals(op)) {
- System.out.println("Compute preloaded classes.");
- ui.action(ComputeThresholdXAction.class);
- ui.input(it.next());
- ui.confirmYes();
- ui.output(new File(it.next()));
- // Operation: Write preloaded classes from a specific file
- } else if (WRITE_CMD.equals(op)) {
- System.out.println("Writing preloaded classes.");
- ui.action(WritePreloadedClassesAction.class);
- ui.input(new File(it.next()));
- }
- }
- } catch (NoSuchElementException e) {
- System.out.println("Failed to parse action sequence correctly.");
- throw e;
- }
-
- return main;
- }
-
- public static IUI getUI() {
- return top.ui;
- }
-
- public static ClassDataRetriever getClassDataRetriever() {
- if (useJdwpClassDataRetriever) {
- return new JDWPClassDataRetriever();
- } else {
- return new Hprof(HPROF_TIMEOUT_MILLIS);
- }
- }
-
- public IDevice getDevice() {
- return device;
- }
-
- public void setDevice(IDevice device) {
- this.device = device;
- for (DeviceSpecific ds : deviceSpecificActions) {
- ds.setDevice(device);
- }
- }
-
- public DefaultListModel<Client> getClientListModel() {
- return clientListModel;
- }
-
- static class DeviceWrapper {
- IDevice device;
-
- public DeviceWrapper(IDevice d) {
- device = d;
- }
-
- @Override
- public String toString() {
- return device.getName() + " (#" + device.getSerialNumber() + ")";
- }
- }
-
- private void startUp() {
- getUI().showWaitDialog();
- initDevice();
-
- // Load clients.
- new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
-
- getUI().hideWaitDialog();
- getUI().ready();
- }
-
- private void initDevice() {
- DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
-
- IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
- if (devices == null || devices.length == 0) {
- throw new RuntimeException("Could not find any devices...");
- }
-
- getUI().hideWaitDialog();
-
- DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
- for (int i = 0; i < devices.length; i++) {
- deviceWrappers[i] = new DeviceWrapper(devices[i]);
- }
-
- DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
- deviceWrappers);
- if (ret != null) {
- setDevice(ret.device);
- } else {
- System.exit(0);
- }
-
- boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
- "Do you want to prepare the device? This is highly recommended.");
- if (prepare) {
- String buildType = DeviceUtils.getBuildType(device);
- if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
- Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
- + ")");
- return;
- }
- if (DeviceUtils.hasPrebuiltBootImage(device)) {
- Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
- + "image!");
- return;
- }
-
- if (ENABLE_TRACING) {
- DeviceUtils.enableTracing(device);
- }
-
- Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
- + "long time. Please be patient.");
- boolean success = false;
- try {
- success = DeviceUtils.overwritePreloaded(device, null, 15 * 60);
- } catch (Exception e) {
- System.err.println(e);
- } finally {
- if (!success) {
- Main.getUI().showMessageDialog(
- "Removing preloaded-classes failed unexpectedly!");
- }
- }
- }
- }
-
- public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
- throws Exception {
- Client client = clientUtils.findClient(device, packageName, -1);
- if (client == null) {
- throw new RuntimeException("Could not find client...");
- }
- System.out.println("Found client: " + client);
-
- return getClassDataRetriever().getClassData(client);
- }
-
-}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
deleted file mode 100644
index 5787d8507230..000000000000
--- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.Main;
-import java.awt.event.ActionEvent;
-
-import javax.swing.AbstractAction;
-
-public abstract class AbstractThreadedAction extends AbstractAction implements Runnable {
-
- protected AbstractThreadedAction(String title) {
- super(title);
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- if (Main.getUI().isSingleThreaded()) {
- run();
- } else {
- new Thread(this).start();
- }
- }
-
-}
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
deleted file mode 100644
index 7906417b7a8d..000000000000
--- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.IDevice;
-
-import java.awt.event.ActionEvent;
-
-public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction
- implements DeviceSpecific {
-
- protected IDevice device;
-
- protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) {
- super(title);
- this.device = device;
- }
-
- @Override
- public void setDevice(IDevice device) {
- this.device = device;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- if (device == null) {
- return;
- }
- super.actionPerformed(e);
- }
-}
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
deleted file mode 100644
index 3a7f7f74d755..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-import javax.swing.AbstractAction;
-
-/**
- * Compute an intersection of classes from the given data. A class is in the intersection if it
- * appears in at least the number of threshold given packages. An optional blacklist can be
- * used to filter classes from the intersection.
- */
-public class ComputeThresholdAction extends AbstractThreadedAction {
- protected int threshold;
- private Pattern blacklist;
- private DumpTableModel dataTableModel;
-
- /**
- * Create an action with the given parameters. The blacklist is a regular expression
- * that filters classes.
- */
- public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold,
- String blacklist) {
- super(name);
- this.dataTableModel = dataTableModel;
- this.threshold = threshold;
- if (blacklist != null) {
- this.blacklist = Pattern.compile(blacklist);
- }
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- List<DumpData> data = dataTableModel.getData();
- if (data.size() == 0) {
- Main.getUI().showMessageDialog("No data available, please scan packages or run "
- + "monkeys.");
- return;
- }
- if (data.size() == 1) {
- Main.getUI().showMessageDialog("Cannot compute list from only one data set, please "
- + "scan packages or run monkeys.");
- return;
- }
-
- super.actionPerformed(e);
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- Map<String, Set<String>> uses = new HashMap<String, Set<String>>();
- for (DumpData d : dataTableModel.getData()) {
- Main.getUI().updateWaitDialog("Merging " + d.getPackageName());
- updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData()));
- }
-
- Main.getUI().updateWaitDialog("Computing thresholded set");
- Set<String> result = fromThreshold(uses, blacklist, threshold);
- Main.getUI().hideWaitDialog();
-
- boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
- + " classes, would you like to save to disk?", "Save?");
- if (ret) {
- File f = Main.getUI().showSaveDialog();
- if (f != null) {
- saveSet(result, f);
- }
- }
- }
-
- private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist,
- int threshold) {
- TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name.
-
- for (Map.Entry<String, Set<String>> e : classUses.entrySet()) {
- if (e.getValue().size() >= threshold) {
- if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) {
- ret.add(e.getKey());
- }
- }
- }
-
- return ret;
- }
-
- private static void updateClassUse(String pkg, Map<String, Set<String>> classUses,
- Set<String> classes) {
- for (String className : classes) {
- Set<String> old = classUses.get(className);
- if (old == null) {
- classUses.put(className, new HashSet<String>());
- }
- classUses.get(className).add(pkg);
- }
- }
-
- private static Set<String> getBootClassPathClasses(Map<String, String> source) {
- Set<String> ret = new HashSet<>();
- for (Map.Entry<String, String> e : source.entrySet()) {
- if (e.getValue() == null) {
- ret.add(e.getKey());
- }
- }
- return ret;
- }
-
- private static void saveSet(Set<String> result, File f) {
- try {
- PrintWriter out = new PrintWriter(f);
- for (String s : result) {
- out.println(s);
- }
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
deleted file mode 100644
index 3ec0a4c18db1..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-public class ComputeThresholdXAction extends ComputeThresholdAction {
-
- public ComputeThresholdXAction(String name, DumpTableModel dataTableModel,
- String blacklist) {
- super(name, dataTableModel, 1, blacklist);
- }
-
- @Override
- public void run() {
- String value = Main.getUI().showInputDialog("Threshold?");
-
- if (value != null) {
- try {
- threshold = Integer.parseInt(value);
- super.run();
- } catch (Exception exc) {
- }
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
deleted file mode 100644
index 35a8f26a99fe..000000000000
--- a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.IDevice;
-
-/**
- * Marks an action as being device-specific. The user must set the device through the specified
- * method if the device selection changes.
- *
- * Implementors must tolerate a null device (for example, with a no-op). This includes calling
- * any methods before setDevice has been called.
- */
-public interface DeviceSpecific {
-
- /**
- * Set the device that should be used. Note that there is no restriction on calling other
- * methods of the implementor before a setDevice call. Neither is device guaranteed to be
- * non-null.
- *
- * @param device The device to use going forward.
- */
- public void setDevice(IDevice device);
-}
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
deleted file mode 100644
index 848a56826788..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ExportAction.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.DumpDataIO;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.PrintWriter;
-
-public class ExportAction extends AbstractThreadedAction {
- private File lastSaveFile;
- private DumpTableModel dataTableModel;
-
- public ExportAction(DumpTableModel dataTableModel) {
- super("Export data");
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- lastSaveFile = Main.getUI().showSaveDialog();
- if (lastSaveFile != null) {
- super.actionPerformed(e);
- }
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- String serialized = DumpDataIO.serialize(dataTableModel.getData());
-
- if (serialized != null) {
- try {
- PrintWriter out = new PrintWriter(lastSaveFile);
- out.println(serialized);
- out.close();
-
- Main.getUI().hideWaitDialog();
- } catch (Exception e) {
- Main.getUI().hideWaitDialog();
- Main.getUI().showMessageDialog("Failed writing: " + e.getMessage());
- }
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
deleted file mode 100644
index bfeeb836fd45..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ImportAction.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpDataIO;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.util.Collection;
-
-import javax.swing.AbstractAction;
-
-public class ImportAction extends AbstractThreadedAction {
- private File[] lastOpenFiles;
- private DumpTableModel dataTableModel;
-
- public ImportAction(DumpTableModel dataTableModel) {
- super("Import data");
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- lastOpenFiles = Main.getUI().showOpenDialog(true);
- if (lastOpenFiles != null) {
- super.actionPerformed(e);
- }
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- try {
- for (File f : lastOpenFiles) {
- try {
- Collection<DumpData> data = DumpDataIO.deserialize(f);
-
- for (DumpData d : data) {
- dataTableModel.addData(d);
- }
- } catch (Exception e) {
- Main.getUI().showMessageDialog("Failed reading: " + e.getMessage());
- }
- }
- } finally {
- Main.getUI().hideWaitDialog();
- }
-
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
deleted file mode 100644
index 29f055700dfa..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-import javax.swing.DefaultListModel;
-
-public class ReloadListAction extends AbstractThreadedDeviceSpecificAction {
-
- private ClientUtils clientUtils;
- private final DefaultListModel<Client> clientListModel;
-
- public ReloadListAction(ClientUtils utils, IDevice device,
- DefaultListModel<Client> clientListModel) {
- super("Reload", device);
- this.clientUtils = utils;
- this.clientListModel = clientListModel;
- }
-
- @Override
- public void run() {
- Client[] clients = clientUtils.findAllClients(device);
- if (clients != null) {
- Arrays.sort(clients, new ClientComparator());
- }
- clientListModel.removeAllElements();
- for (Client c : clients) {
- clientListModel.addElement(c);
- }
- }
-
- private static class ClientComparator implements Comparator<Client> {
-
- @Override
- public int compare(Client o1, Client o2) {
- String s1 = o1.getClientData().getClientDescription();
- String s2 = o2.getClientData().getClientDescription();
-
- if (s1 == null || s2 == null) {
- // Not good, didn't get all data?
- return (s1 == null) ? -1 : 1;
- }
-
- return s1.compareTo(s2);
- }
-
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
deleted file mode 100644
index 29464fc7abdf..000000000000
--- a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.IDevice;
-import com.android.preload.DeviceUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.AbstractAction;
-
-public class RunMonkeyAction extends AbstractAction implements DeviceSpecific {
-
- private final static String DEFAULT_MONKEY_PACKAGES =
- "com.android.calendar,com.android.gallery3d";
-
- private IDevice device;
- private DumpTableModel dataTableModel;
-
- public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) {
- super("Run monkey");
- this.device = device;
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void setDevice(IDevice device) {
- this.device = device;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- String packages = Main.getUI().showInputDialog("Please enter packages name to run with"
- + " the monkey, or leave empty for default.");
- if (packages == null) {
- return;
- }
- if (packages.isEmpty()) {
- packages = DEFAULT_MONKEY_PACKAGES;
- }
- Runnable r = new RunMonkeyRunnable(packages);
- if (Main.getUI().isSingleThreaded()) {
- r.run();
- } else {
- new Thread(r).start();
- }
- }
-
- private class RunMonkeyRunnable implements Runnable {
-
- private String packages;
- private final static int ITERATIONS = 1000;
-
- public RunMonkeyRunnable(String packages) {
- this.packages = packages;
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- try {
- String pkgs[] = packages.split(",");
-
- for (String pkg : pkgs) {
- Main.getUI().updateWaitDialog("Running monkey on " + pkg);
-
- try {
- // Stop running app.
- forceStop(pkg);
-
- // Little bit of breather here.
- try {
- Thread.sleep(1000);
- } catch (Exception e) {
- }
-
- DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1,
- TimeUnit.MINUTES);
-
- Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
- Map<String, String> data = Main.findAndGetClassData(device, pkg);
- DumpData dumpData = new DumpData(pkg, data, new Date());
- dataTableModel.addData(dumpData);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // Stop running app.
- forceStop(pkg);
- }
- }
- } finally {
- Main.getUI().hideWaitDialog();
- }
- }
-
- private void forceStop(String packageName) {
- // Stop running app.
- DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS);
- DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS);
- DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS);
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
deleted file mode 100644
index d74b8a3f6cfb..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.util.Date;
-import java.util.Map;
-
-public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction {
-
- private ClientUtils clientUtils;
- private DumpTableModel dataTableModel;
-
- public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
- super("Scan all packages", device);
- this.clientUtils = utils;
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- try {
- Client[] clients = clientUtils.findAllClients(device);
- for (Client c : clients) {
- String pkg = c.getClientData().getClientDescription();
- Main.getUI().showWaitDialog();
- Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
-
- try {
- Map<String, String> data = Main.getClassDataRetriever().getClassData(c);
- DumpData dumpData = new DumpData(pkg, data, new Date());
- dataTableModel.addData(dumpData);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- } finally {
- Main.getUI().hideWaitDialog();
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
deleted file mode 100644
index 98492bd951bf..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.util.Date;
-import java.util.Map;
-
-public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction {
-
- private ClientUtils clientUtils;
- private DumpTableModel dataTableModel;
-
- public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
- super("Scan package", device);
- this.clientUtils = utils;
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
-
- try {
- Client client = Main.getUI().getSelectedClient();
- if (client != null) {
- work(client);
- } else {
- Client[] clients = clientUtils.findAllClients(device);
- if (clients.length > 0) {
- ClientWrapper[] clientWrappers = new ClientWrapper[clients.length];
- for (int i = 0; i < clientWrappers.length; i++) {
- clientWrappers[i] = new ClientWrapper(clients[i]);
- }
- Main.getUI().hideWaitDialog();
-
- ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan",
- "Choose package",
- clientWrappers);
- if (ret != null) {
- work(ret.client);
- }
- }
- }
- } finally {
- Main.getUI().hideWaitDialog();
- }
- }
-
- private void work(Client c) {
- String pkg = c.getClientData().getClientDescription();
- Main.getUI().showWaitDialog();
- Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg);
-
- try {
- Map<String, String> data = Main.findAndGetClassData(device, pkg);
- DumpData dumpData = new DumpData(pkg, data, new Date());
- dataTableModel.addData(dumpData);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- private static class ClientWrapper {
- private Client client;
-
- public ClientWrapper(Client c) {
- client = c;
- }
-
- @Override
- public String toString() {
- return client.getClientData().getClientDescription() + " (pid "
- + client.getClientData().getPid() + ")";
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
deleted file mode 100644
index 2bb175f60772..000000000000
--- a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.BorderLayout;
-import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.AbstractAction;
-import javax.swing.JFrame;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-
-public class ShowDataAction extends AbstractAction {
- private DumpTableModel dataTableModel;
-
- public ShowDataAction(DumpTableModel dataTableModel) {
- super("Show data");
- this.dataTableModel = dataTableModel;
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- // TODO(agampe): Auto-generated method stub
- int selRow = Main.getUI().getSelectedDataTableRow();
- if (selRow != -1) {
- DumpData data = dataTableModel.getData().get(selRow);
- Map<String, Set<String>> inv = data.invertData();
-
- StringBuilder builder = new StringBuilder();
-
- // First bootclasspath.
- add(builder, "Boot classpath:", inv.get(null));
-
- // Now everything else.
- for (String k : inv.keySet()) {
- if (k != null) {
- builder.append("==================\n\n");
- add(builder, k, inv.get(k));
- }
- }
-
- JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate());
- newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
- newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())),
- BorderLayout.CENTER);
- newFrame.setSize(800, 600);
- newFrame.setLocationRelativeTo(null);
- newFrame.setVisible(true);
- }
- }
-
- private void add(StringBuilder builder, String head, Set<String> set) {
- builder.append(head);
- builder.append('\n');
- addSet(builder, set);
- builder.append('\n');
- }
-
- private void addSet(StringBuilder builder, Set<String> set) {
- if (set == null) {
- builder.append(" NONE\n");
- return;
- }
- List<String> sorted = new ArrayList<>(set);
- Collections.sort(sorted);
- for (String s : sorted) {
- builder.append(s);
- builder.append('\n');
- }
- }
-} \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java b/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
deleted file mode 100644
index 9b97f1168df9..000000000000
--- a/tools/preload2/src/com/android/preload/actions/WritePreloadedClassesAction.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.actions;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.IDevice;
-import com.android.preload.ClientUtils;
-import com.android.preload.DeviceUtils;
-import com.android.preload.DumpData;
-import com.android.preload.DumpTableModel;
-import com.android.preload.Main;
-
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.util.Date;
-import java.util.Map;
-
-public class WritePreloadedClassesAction extends AbstractThreadedDeviceSpecificAction {
- private File preloadedClassFile;
-
- public WritePreloadedClassesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) {
- super("Write preloaded classes action", device);
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- File[] files = Main.getUI().showOpenDialog(true);
- if (files != null && files.length > 0) {
- preloadedClassFile = files[0];
- super.actionPerformed(e);
- }
- }
-
- @Override
- public void run() {
- Main.getUI().showWaitDialog();
- try {
- // Write the new file with a 5-minute timeout
- DeviceUtils.overwritePreloaded(device, preloadedClassFile, 5 * 60);
- } catch (Exception e) {
- System.err.println(e);
- } finally {
- Main.getUI().hideWaitDialog();
- }
- }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
deleted file mode 100644
index 8d797ee00128..000000000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.classdataretrieval.hprof;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData.IHprofDumpHandler;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class GeneralHprofDumpHandler implements IHprofDumpHandler {
-
- private List<IHprofDumpHandler> handlers = new ArrayList<>();
-
- public void addHandler(IHprofDumpHandler h) {
- synchronized (handlers) {
- handlers.add(h);
- }
- }
-
- public void removeHandler(IHprofDumpHandler h) {
- synchronized (handlers) {
- handlers.remove(h);
- }
- }
-
- private List<IHprofDumpHandler> getIterationList() {
- synchronized (handlers) {
- return new ArrayList<>(handlers);
- }
- }
-
- @Override
- public void onEndFailure(Client arg0, String arg1) {
- List<IHprofDumpHandler> iterList = getIterationList();
- for (IHprofDumpHandler h : iterList) {
- h.onEndFailure(arg0, arg1);
- }
- }
-
- @Override
- public void onSuccess(String arg0, Client arg1) {
- List<IHprofDumpHandler> iterList = getIterationList();
- for (IHprofDumpHandler h : iterList) {
- h.onSuccess(arg0, arg1);
- }
- }
-
- @Override
- public void onSuccess(byte[] arg0, Client arg1) {
- List<IHprofDumpHandler> iterList = getIterationList();
- for (IHprofDumpHandler h : iterList) {
- h.onSuccess(arg0, arg1);
- }
- }
- } \ No newline at end of file
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
deleted file mode 100644
index 84ec8b7d0fdd..000000000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.classdataretrieval.hprof;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-import com.android.ddmlib.ClientData.IHprofDumpHandler;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-import com.android.preload.ui.NullProgressMonitor;
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Queries;
-import com.android.tools.perflib.heap.Snapshot;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class Hprof implements ClassDataRetriever {
-
- private static GeneralHprofDumpHandler hprofHandler;
-
- public static void init() {
- synchronized(Hprof.class) {
- if (hprofHandler == null) {
- ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
- }
- }
- }
-
- public static File doHprof(Client client, int timeout) {
- GetHprof gh = new GetHprof(client, timeout);
- return gh.get();
- }
-
- /**
- * Return a map of class names to class-loader names derived from the hprof dump.
- *
- * @param hprofLocalFile
- */
- public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
- Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
-
- Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
- Map<String, String> retValue = new HashMap<String, String>();
- for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
- for (ClassObj c : e.getValue()) {
- String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
- String cName = c.getClassName();
- int aDepth = 0;
- while (cName.endsWith("[]")) {
- cName = cName.substring(0, cName.length()-2);
- aDepth++;
- }
- String newName = transformPrimitiveClass(cName);
- if (aDepth > 0) {
- // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
- if (newName.equals(cName)) {
- newName = "L" + newName + ";";
- }
- for (int i = 0; i < aDepth; i++) {
- newName = "[" + newName;
- }
- }
- retValue.put(newName, cl);
- }
- }
-
- // Free up memory.
- snapshot.dispose();
-
- return retValue;
- }
-
- private static Map<String, String> primitiveMapping;
-
- static {
- primitiveMapping = new HashMap<>();
- primitiveMapping.put("boolean", "Z");
- primitiveMapping.put("byte", "B");
- primitiveMapping.put("char", "C");
- primitiveMapping.put("double", "D");
- primitiveMapping.put("float", "F");
- primitiveMapping.put("int", "I");
- primitiveMapping.put("long", "J");
- primitiveMapping.put("short", "S");
- primitiveMapping.put("void", "V");
- }
-
- private static String transformPrimitiveClass(String name) {
- String rep = primitiveMapping.get(name);
- if (rep != null) {
- return rep;
- }
- return name;
- }
-
- private static class GetHprof implements IHprofDumpHandler {
-
- private File target;
- private long timeout;
- private Client client;
-
- public GetHprof(Client client, long timeout) {
- this.client = client;
- this.timeout = timeout;
- }
-
- public File get() {
- synchronized (this) {
- hprofHandler.addHandler(this);
- client.dumpHprof();
- if (target == null) {
- try {
- wait(timeout);
- } catch (Exception e) {
- System.out.println(e);
- }
- }
- }
-
- hprofHandler.removeHandler(this);
- return target;
- }
-
- private void wakeUp() {
- synchronized (this) {
- notifyAll();
- }
- }
-
- @Override
- public void onEndFailure(Client arg0, String arg1) {
- System.out.println("GetHprof.onEndFailure");
- if (client == arg0) {
- wakeUp();
- }
- }
-
- private static File createTargetFile() {
- try {
- return File.createTempFile("ddms", ".hprof");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void onSuccess(String arg0, Client arg1) {
- System.out.println("GetHprof.onSuccess");
- if (client == arg1) {
- try {
- target = createTargetFile();
- arg1.getDevice().getSyncService().pullFile(arg0,
- target.getAbsoluteFile().toString(), new NullProgressMonitor());
- } catch (Exception e) {
- if (target != null) {
- target.delete();
- }
- e.printStackTrace();
- target = null;
- }
- wakeUp();
- }
- }
-
- @Override
- public void onSuccess(byte[] arg0, Client arg1) {
- System.out.println("GetHprof.onSuccess");
- if (client == arg1) {
- try {
- target = createTargetFile();
- BufferedOutputStream out =
- new BufferedOutputStream(new FileOutputStream(target));
- out.write(arg0);
- out.close();
- } catch (Exception e) {
- if (target != null) {
- target.delete();
- }
- e.printStackTrace();
- target = null;
- }
- wakeUp();
- }
- }
- }
-
- private int timeout;
-
- public Hprof(int timeout) {
- this.timeout = timeout;
- }
-
- @Override
- public Map<String, String> getClassData(Client client) {
- File hprofLocalFile = Hprof.doHprof(client, timeout);
- if (hprofLocalFile == null) {
- throw new RuntimeException("Failed getting dump...");
- }
- System.out.println("Dump file is " + hprofLocalFile);
-
- try {
- return analyzeHprof(hprofLocalFile);
- } catch (Exception e) {
- throw new RuntimeException(e);
- } finally {
- hprofLocalFile.delete();
- }
- }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
deleted file mode 100644
index dbd4c89b27cf..000000000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.classdataretrieval.jdwp;
-
-import com.android.ddmlib.Client;
-import com.android.preload.classdataretrieval.ClassDataRetriever;
-
-import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
-import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
-import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
-import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
-import org.apache.harmony.jpda.tests.share.JPDALogWriter;
-import org.apache.harmony.jpda.tests.share.JPDATestOptions;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
-
- private final Client client;
-
- public JDWPClassDataRetriever() {
- this(null);
- }
-
- public JDWPClassDataRetriever(Client client) {
- this.client = client;
- }
-
-
- @Override
- protected String getDebuggeeClassName() {
- return "<unset>";
- }
-
- @Override
- public Map<String, String> getClassData(Client client) {
- return new JDWPClassDataRetriever(client).retrieve();
- }
-
- private Map<String, String> retrieve() {
- if (client == null) {
- throw new IllegalStateException();
- }
-
- settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
- settings.setDebuggeeSuspend("n");
-
- logWriter = new JPDALogWriter(System.out, "", false);
-
- try {
- internalSetUp();
-
- return retrieveImpl();
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- } finally {
- internalTearDown();
- }
- }
-
- private Map<String, String> retrieveImpl() {
- try {
- // Suspend the app.
- {
- CommandPacket packet = new CommandPacket(
- JDWPCommands.VirtualMachineCommandSet.CommandSetID,
- JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
- ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
- if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
- return null;
- }
- }
-
- // List all classes.
- CommandPacket packet = new CommandPacket(
- JDWPCommands.VirtualMachineCommandSet.CommandSetID,
- JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
- ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
-
- if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
- return null;
- }
-
- int classCount = reply.getNextValueAsInt();
- System.out.println("Runtime reported " + classCount + " classes.");
-
- Map<Long, String> classes = new HashMap<Long, String>();
- Map<Long, String> arrayClasses = new HashMap<Long, String>();
-
- for (int i = 0; i < classCount; i++) {
- byte refTypeTag = reply.getNextValueAsByte();
- long typeID = reply.getNextValueAsReferenceTypeID();
- String signature = reply.getNextValueAsString();
- /* int status = */ reply.getNextValueAsInt();
-
- switch (refTypeTag) {
- case JDWPConstants.TypeTag.CLASS:
- case JDWPConstants.TypeTag.INTERFACE:
- classes.put(typeID, signature);
- break;
-
- case JDWPConstants.TypeTag.ARRAY:
- arrayClasses.put(typeID, signature);
- break;
- }
- }
-
- Map<String, String> result = new HashMap<String, String>();
-
- // Parse all classes.
- for (Map.Entry<Long, String> entry : classes.entrySet()) {
- long typeID = entry.getKey();
- String signature = entry.getValue();
-
- if (!checkClass(typeID, signature, result)) {
- System.err.println("Issue investigating " + signature);
- }
- }
-
- // For arrays, look at the leaf component type.
- for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
- long typeID = entry.getKey();
- String signature = entry.getValue();
-
- if (!checkArrayClass(typeID, signature, result)) {
- System.err.println("Issue investigating " + signature);
- }
- }
-
- return result;
- } finally {
- // Resume the app.
- {
- CommandPacket packet = new CommandPacket(
- JDWPCommands.VirtualMachineCommandSet.CommandSetID,
- JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
- /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
- }
- }
- }
-
- private boolean checkClass(long typeID, String signature, Map<String, String> result) {
- CommandPacket packet = new CommandPacket(
- JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
- JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
- packet.setNextValueAsReferenceTypeID(typeID);
- ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
- if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
- return false;
- }
-
- long classLoaderID = reply.getNextValueAsObjectID();
-
- // TODO: Investigate the classloader to have a better string?
- String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
-
- result.put(getClassName(signature), classLoaderString);
-
- return true;
- }
-
- private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
- // Classloaders of array classes are the same as the component class'.
- CommandPacket packet = new CommandPacket(
- JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
- JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
- packet.setNextValueAsReferenceTypeID(typeID);
- ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
- if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
- return false;
- }
-
- long classLoaderID = reply.getNextValueAsObjectID();
-
- // TODO: Investigate the classloader to have a better string?
- String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
-
- // For array classes, we *need* the signature directly.
- result.put(signature, classLoaderString);
-
- return true;
- }
-
- private static String getClassName(String signature) {
- String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
- return withoutLAndSemicolon.replace('/', '.');
- }
-
-
- private static JPDATestOptions createTestOptions(String address) {
- JPDATestOptions options = new JPDATestOptions();
- options.setAttachConnectorKind();
- options.setTimeout(1000);
- options.setWaitingTime(1000);
- options.setTransportAddress(address);
- return options;
- }
-
- @Override
- protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
- return new PreloadDebugeeWrapper(settings, logWriter);
- }
-}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
deleted file mode 100644
index b9df6d0aeb93..000000000000
--- a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.classdataretrieval.jdwp;
-
-import org.apache.harmony.jpda.tests.framework.LogWriter;
-import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper;
-import org.apache.harmony.jpda.tests.share.JPDATestOptions;
-
-import java.io.IOException;
-
-public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper {
-
- public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) {
- super(options, writer);
- }
-
- @Override
- protected Process launchProcess(String cmdLine) throws IOException {
- return null;
- }
-
- @Override
- protected void WaitForProcessExit(Process process) {
- }
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/IUI.java b/tools/preload2/src/com/android/preload/ui/IUI.java
deleted file mode 100644
index 9371463e9a79..000000000000
--- a/tools/preload2/src/com/android/preload/ui/IUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.android.preload.ui;
-
-import com.android.ddmlib.Client;
-import java.io.File;
-import java.util.List;
-import javax.swing.Action;
-import javax.swing.ListModel;
-import javax.swing.table.TableModel;
-
-/**
- * UI abstraction for the tool. This allows a graphical mode, command line mode,
- * or silent mode.
- */
-public interface IUI {
-
- void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
- List<Action> actions);
-
- void ready();
-
- boolean isSingleThreaded();
-
- Client getSelectedClient();
-
- int getSelectedDataTableRow();
-
- void showWaitDialog();
-
- void updateWaitDialog(String s);
-
- void hideWaitDialog();
-
- void showMessageDialog(String s);
-
- boolean showConfirmDialog(String title, String message);
-
- String showInputDialog(String message);
-
- <T> T showChoiceDialog(String title, String message, T[] choices);
-
- File showSaveDialog();
-
- File[] showOpenDialog(boolean multi);
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/SequenceUI.java b/tools/preload2/src/com/android/preload/ui/SequenceUI.java
deleted file mode 100644
index dc6a4f389b10..000000000000
--- a/tools/preload2/src/com/android/preload/ui/SequenceUI.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package com.android.preload.ui;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
-import javax.swing.Action;
-import javax.swing.ListModel;
-import javax.swing.table.TableModel;
-
-public class SequenceUI implements IUI {
-
- private ListModel<Client> clientListModel;
- @SuppressWarnings("unused")
- private TableModel dataTableModel;
- private List<Action> actions;
-
- private List<Object> sequence = new LinkedList<>();
-
- public SequenceUI() {
- }
-
- @Override
- public boolean isSingleThreaded() {
- return true;
- }
-
- @Override
- public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
- List<Action> actions) {
- this.clientListModel = clientListModel;
- this.dataTableModel = dataTableModel;
- this.actions = actions;
- }
-
- public SequenceUI action(Action a) {
- sequence.add(a);
- return this;
- }
-
- public SequenceUI action(Class<? extends Action> actionClass) {
- for (Action a : actions) {
- if (actionClass.equals(a.getClass())) {
- sequence.add(a);
- return this;
- }
- }
- throw new IllegalArgumentException("No action of class " + actionClass + " found.");
- }
-
- public SequenceUI confirmYes() {
- sequence.add(Boolean.TRUE);
- return this;
- }
-
- public SequenceUI confirmNo() {
- sequence.add(Boolean.FALSE);
- return this;
- }
-
- public SequenceUI input(String input) {
- sequence.add(input);
- return this;
- }
-
- public SequenceUI input(File... f) {
- sequence.add(f);
- return this;
- }
-
- public SequenceUI output(File f) {
- sequence.add(f);
- return this;
- }
-
- public SequenceUI tableRow(int i) {
- sequence.add(i);
- return this;
- }
-
- private class ClientSelector {
- private String pkg;
-
- public ClientSelector(String pkg) {
- this.pkg = pkg;
- }
-
- public Client getClient() {
- for (int i = 0; i < clientListModel.getSize(); i++) {
- ClientData cd = clientListModel.getElementAt(i).getClientData();
- if (cd != null) {
- String s = cd.getClientDescription();
- if (pkg.equals(s)) {
- return clientListModel.getElementAt(i);
- }
- }
- }
- throw new RuntimeException("Didn't find client " + pkg);
- }
- }
-
- public SequenceUI client(String pkg) {
- sequence.add(new ClientSelector(pkg));
- return this;
- }
-
- public SequenceUI choice(String pattern) {
- sequence.add(pattern);
- return this;
- }
-
- @Override
- public void ready() {
- // Run the actions.
- // No iterator or foreach loop as the sequence will be emptied while running.
- try {
- while (!sequence.isEmpty()) {
- Object next = sequence.remove(0);
- if (next instanceof Action) {
- ((Action)next).actionPerformed(null);
- } else {
- throw new IllegalStateException("Didn't expect a non-action: " + next);
- }
- }
- } catch (Exception e) {
- e.printStackTrace(System.out);
- }
-
- // Now shut down.
- System.exit(0);
- }
-
- @Override
- public Client getSelectedClient() {
- Object next = sequence.remove(0);
- if (next instanceof ClientSelector) {
- return ((ClientSelector)next).getClient();
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public int getSelectedDataTableRow() {
- Object next = sequence.remove(0);
- if (next instanceof Integer) {
- return ((Integer)next).intValue();
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public void showWaitDialog() {
- }
-
- @Override
- public void updateWaitDialog(String s) {
- System.out.println(s);
- }
-
- @Override
- public void hideWaitDialog() {
- }
-
- @Override
- public void showMessageDialog(String s) {
- System.out.println(s);
- }
-
- @Override
- public boolean showConfirmDialog(String title, String message) {
- Object next = sequence.remove(0);
- if (next instanceof Boolean) {
- return ((Boolean)next).booleanValue();
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public String showInputDialog(String message) {
- Object next = sequence.remove(0);
- if (next instanceof String) {
- return (String)next;
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public <T> T showChoiceDialog(String title, String message, T[] choices) {
- Object next = sequence.remove(0);
- if (next instanceof String) {
- String s = (String)next;
- for (T t : choices) {
- if (t.toString().contains(s)) {
- return t;
- }
- }
- return null;
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public File showSaveDialog() {
- Object next = sequence.remove(0);
- if (next instanceof File) {
- System.out.println(next);
- return (File)next;
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
- @Override
- public File[] showOpenDialog(boolean multi) {
- Object next = sequence.remove(0);
- if (next instanceof File[]) {
- return (File[])next;
- }
- throw new IllegalStateException("Unexpected: " + next);
- }
-
-}
diff --git a/tools/preload2/src/com/android/preload/ui/SwingUI.java b/tools/preload2/src/com/android/preload/ui/SwingUI.java
deleted file mode 100644
index cab3744ad74c..000000000000
--- a/tools/preload2/src/com/android/preload/ui/SwingUI.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2015 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.preload.ui;
-
-import com.android.ddmlib.Client;
-import com.android.ddmlib.ClientData;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.io.File;
-import java.util.List;
-
-import javax.swing.Action;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JProgressBar;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.JToolBar;
-import javax.swing.ListModel;
-import javax.swing.SwingUtilities;
-import javax.swing.table.TableModel;
-
-public class SwingUI extends JFrame implements IUI {
-
- private JList<Client> clientList;
- private JTable dataTable;
-
- // Shared file chooser, means the directory is retained.
- private JFileChooser jfc;
-
- public SwingUI() {
- super("Preloaded-classes computation");
- }
-
- @Override
- public boolean isSingleThreaded() {
- return false;
- }
-
- @Override
- public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
- List<Action> actions) {
- getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
- BorderLayout.WEST);
- clientList.setCellRenderer(new ClientListCellRenderer());
- // clientList.addListSelectionListener(listener);
-
- dataTable = new JTable(dataTableModel);
- getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER);
-
- JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
- for (Action a : actions) {
- if (a == null) {
- toolbar.addSeparator();
- } else {
- toolbar.add(a);
- }
- }
- getContentPane().add(toolbar, BorderLayout.PAGE_START);
-
- setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- setBounds(100, 100, 800, 600);
-
- setVisible(true);
- }
-
- @Override
- public void ready() {
- }
-
- @Override
- public Client getSelectedClient() {
- return clientList.getSelectedValue();
- }
-
- @Override
- public int getSelectedDataTableRow() {
- return dataTable.getSelectedRow();
- }
-
- private JDialog currentWaitDialog = null;
-
- @Override
- public void showWaitDialog() {
- if (currentWaitDialog == null) {
- currentWaitDialog = new JDialog(this, "Please wait...", true);
- currentWaitDialog.getContentPane().add(new JLabel("Please be patient."),
- BorderLayout.CENTER);
- JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL);
- progress.setIndeterminate(true);
- currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH);
- currentWaitDialog.setSize(200, 100);
- currentWaitDialog.setLocationRelativeTo(null);
- showWaitDialogLater();
- }
- }
-
- private void showWaitDialogLater() {
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(true); // This is blocking.
- }
- }
- });
- }
-
- @Override
- public void updateWaitDialog(String s) {
- if (currentWaitDialog != null) {
- ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
- Dimension prefSize = currentWaitDialog.getPreferredSize();
- Dimension curSize = currentWaitDialog.getSize();
- if (prefSize.width > curSize.width || prefSize.height > curSize.height) {
- currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width),
- Math.max(prefSize.height, curSize.height));
- currentWaitDialog.invalidate();
- }
- }
- }
-
- @Override
- public void hideWaitDialog() {
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- currentWaitDialog = null;
- }
- }
-
- @Override
- public void showMessageDialog(String s) {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try {
- JOptionPane.showMessageDialog(this, s);
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- @Override
- public boolean showConfirmDialog(String title, String message) {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try {
- return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION)
- == JOptionPane.YES_OPTION;
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- @Override
- public String showInputDialog(String message) {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try {
- return JOptionPane.showInputDialog(message);
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public <T> T showChoiceDialog(String title, String message, T[] choices) {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try{
- return (T)JOptionPane.showInputDialog(this,
- title,
- message,
- JOptionPane.QUESTION_MESSAGE,
- null,
- choices,
- choices[0]);
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- @Override
- public File showSaveDialog() {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try{
- if (jfc == null) {
- jfc = new JFileChooser();
- }
-
- int ret = jfc.showSaveDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION) {
- return jfc.getSelectedFile();
- } else {
- return null;
- }
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- @Override
- public File[] showOpenDialog(boolean multi) {
- // Hide the wait dialog...
- if (currentWaitDialog != null) {
- currentWaitDialog.setVisible(false);
- }
-
- try{
- if (jfc == null) {
- jfc = new JFileChooser();
- }
-
- jfc.setMultiSelectionEnabled(multi);
- int ret = jfc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION) {
- return jfc.getSelectedFiles();
- } else {
- return null;
- }
- } finally {
- // And reshow it afterwards...
- if (currentWaitDialog != null) {
- showWaitDialogLater();
- }
- }
- }
-
- private class ClientListCellRenderer extends DefaultListCellRenderer {
-
- @Override
- public Component getListCellRendererComponent(JList<?> list, Object value, int index,
- boolean isSelected, boolean cellHasFocus) {
- ClientData cd = ((Client) value).getClientData();
- String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")";
- return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus);
- }
- }
-}
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
index ef2914610f80..5a5703ed520c 100644
--- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
@@ -20,12 +20,13 @@ import static javax.lang.model.element.ElementKind.PACKAGE;
import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.WARNING;
-import android.annotation.UnsupportedAppUsage;
-
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.sun.tools.javac.code.Type;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -69,6 +70,7 @@ public class SignatureBuilder {
public SignatureBuilderException(String message) {
super(message);
}
+
public void report(Element offendingElement) {
mMessager.printMessage(ERROR, getMessage(), offendingElement);
}
@@ -153,7 +155,7 @@ public class SignatureBuilder {
/**
* Get the signature for an executable, either a method or a constructor.
*
- * @param name "<init>" for constructor, else the method name
+ * @param name "<init>" for constructor, else the method name
* @param method The executable element in question.
*/
private String getExecutableSignature(CharSequence name, ExecutableElement method)
@@ -191,8 +193,13 @@ public class SignatureBuilder {
return sig.toString();
}
- public String buildSignature(Element element) {
- UnsupportedAppUsage uba = element.getAnnotation(UnsupportedAppUsage.class);
+ /**
+ * Creates the signature for an annotated element.
+ *
+ * @param annotationType type of annotation being processed.
+ * @param element element for which we want to create a signature.
+ */
+ public String buildSignature(Class<? extends Annotation> annotationType, Element element) {
try {
String signature;
switch (element.getKind()) {
@@ -208,18 +215,35 @@ public class SignatureBuilder {
default:
return null;
}
- // if we have an expected signature on the annotation, warn if it doesn't match.
- if (!Strings.isNullOrEmpty(uba.expectedSignature())) {
- if (!signature.equals(uba.expectedSignature())) {
- mMessager.printMessage(
- WARNING,
- String.format("Expected signature doesn't match generated signature.\n"
- + " Expected: %s\n Generated: %s",
- uba.expectedSignature(), signature),
- element);
+ // Obtain annotation objects
+ Annotation annotation = element.getAnnotation(annotationType);
+ if (annotation == null) {
+ throw new IllegalStateException(
+ "Element doesn't have any UnsupportedAppUsage annotation");
+ }
+ try {
+ Method expectedSignatureMethod = annotationType.getMethod("expectedSignature");
+ // If we have an expected signature on the annotation, warn if it doesn't match.
+ String expectedSignature = expectedSignatureMethod.invoke(annotation).toString();
+ if (!Strings.isNullOrEmpty(expectedSignature)) {
+ if (!signature.equals(expectedSignature)) {
+ mMessager.printMessage(
+ WARNING,
+ String.format(
+ "Expected signature doesn't match generated signature.\n"
+ + " Expected: %s\n Generated: %s",
+ expectedSignature, signature),
+ element);
+ }
}
+ return signature;
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(
+ "Annotation type does not have expectedSignature parameter", e);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException(
+ "Could not get expectedSignature parameter for annotation", e);
}
- return signature;
} catch (SignatureBuilderException problem) {
problem.report(element);
return null;
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
index d368136c7081..5bb956a1fea2 100644
--- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
@@ -18,9 +18,8 @@ package android.processor.unsupportedappusage;
import static javax.tools.StandardLocation.CLASS_OUTPUT;
-import android.annotation.UnsupportedAppUsage;
-
import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.Pair;
@@ -28,6 +27,7 @@ import com.sun.tools.javac.util.Position;
import java.io.IOException;
import java.io.PrintStream;
+import java.lang.annotation.Annotation;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Set;
@@ -47,14 +47,14 @@ import javax.lang.model.element.TypeElement;
/**
* Annotation processor for {@link UnsupportedAppUsage} annotations.
*
- * This processor currently outputs two things:
- * 1. A greylist.txt containing dex signatures of all annotated elements.
- * 2. A CSV file with a mapping of dex signatures to corresponding source positions.
+ * This processor currently outputs a CSV file with a mapping of dex signatures to corresponding
+ * source positions.
*
- * The first will be used at a later stage of the build to add access flags to the dex file. The
- * second is used for automating updates to the annotations themselves.
+ * This is used for automating updates to the annotations themselves.
*/
-@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage"})
+@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage",
+ "dalvik.annotation.compat.UnsupportedAppUsage"
+})
public class UnsupportedAppUsageProcessor extends AbstractProcessor {
// Package name for writing output. Output will be written to the "class output" location within
@@ -62,6 +62,13 @@ public class UnsupportedAppUsageProcessor extends AbstractProcessor {
private static final String PACKAGE = "unsupportedappusage";
private static final String INDEX_CSV = "unsupportedappusage_index.csv";
+ private static final ImmutableSet<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS =
+ ImmutableSet.of(android.annotation.UnsupportedAppUsage.class,
+ dalvik.annotation.compat.UnsupportedAppUsage.class);
+ private static final ImmutableSet<String> SUPPORTED_ANNOTATION_NAMES =
+ SUPPORTED_ANNOTATIONS.stream().map(annotation -> annotation.getCanonicalName()).collect(
+ ImmutableSet.toImmutableSet());
+
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
@@ -92,8 +99,7 @@ public class UnsupportedAppUsageProcessor extends AbstractProcessor {
private AnnotationMirror getUnsupportedAppUsageAnnotationMirror(Element e) {
for (AnnotationMirror m : e.getAnnotationMirrors()) {
TypeElement type = (TypeElement) m.getAnnotationType().asElement();
- if (type.getQualifiedName().toString().equals(
- UnsupportedAppUsage.class.getCanonicalName())) {
+ if (SUPPORTED_ANNOTATION_NAMES.contains(type.getQualifiedName().toString())) {
return m;
}
}
@@ -133,12 +139,12 @@ public class UnsupportedAppUsageProcessor extends AbstractProcessor {
/**
* Maps an annotated element to the source position of the @UnsupportedAppUsage annotation
* attached to it. It returns CSV in the format:
- * dex-signature,filename,start-line,start-col,end-line,end-col
+ * dex-signature,filename,start-line,start-col,end-line,end-col
*
* The positions refer to the annotation itself, *not* the annotated member. This can therefore
* be used to read just the annotation from the file, and to perform in-place edits on it.
*
- * @param signature the dex signature for the element.
+ * @param signature the dex signature for the element.
* @param annotatedElement The annotated element
* @return A single line of CSV text
*/
@@ -164,28 +170,34 @@ public class UnsupportedAppUsageProcessor extends AbstractProcessor {
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(
- UnsupportedAppUsage.class);
- if (annotated.size() == 0) {
- return true;
- }
- // build signatures for each annotated member, and put them in a map of signature to member
Map<String, Element> signatureMap = new TreeMap<>();
SignatureBuilder sb = new SignatureBuilder(processingEnv.getMessager());
- for (Element e : annotated) {
- String sig = sb.buildSignature(e);
- if (sig != null) {
- signatureMap.put(sig, e);
+ for (Class<? extends Annotation> supportedAnnotation : SUPPORTED_ANNOTATIONS) {
+ Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(
+ supportedAnnotation);
+ if (annotated.size() == 0) {
+ continue;
+ }
+ // Build signatures for each annotated member and put them in a map from signature to
+ // member.
+ for (Element e : annotated) {
+ String sig = sb.buildSignature(supportedAnnotation, e);
+ if (sig != null) {
+ signatureMap.put(sig, e);
+ }
}
}
- try {
- writeToFile(INDEX_CSV,
- getCsvHeaders(),
- signatureMap.entrySet()
- .stream()
- .map(e -> getAnnotationIndex(e.getKey() ,e.getValue())));
- } catch (IOException e) {
- throw new RuntimeException("Failed to write output", e);
+
+ if (!signatureMap.isEmpty()) {
+ try {
+ writeToFile(INDEX_CSV,
+ getCsvHeaders(),
+ signatureMap.entrySet()
+ .stream()
+ .map(e -> getAnnotationIndex(e.getKey(), e.getValue())));
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write output", e);
+ }
}
return true;
}
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 4ce4406211ce..c08f9b04f0df 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -95,7 +95,7 @@ genrule {
],
}
-cc_library_shared {
+cc_library {
name: "libstatslog",
host_supported: true,
generated_sources: ["statslog.cpp"],
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 1121eadca967..1390f63248f9 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -29,7 +29,7 @@ cc_defaults {
"-Werror",
],
- shared_libs: ["libprotoc"],
+ static_libs: ["libprotoc"],
}
cc_binary_host {
@@ -49,3 +49,18 @@ cc_binary_host {
defaults: ["protoc-gen-stream-defaults"],
}
+
+// ==========================================================
+// Build the java test
+// ==========================================================
+java_library {
+ name: "StreamingProtoTest",
+ srcs: [
+ "test/**/*.java",
+ "test/**/*.proto",
+ ],
+ proto: {
+ plugin: "javastream",
+ },
+ static_libs: ["libprotobuf-java-lite"],
+}
diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk
deleted file mode 100644
index ebb77a197883..000000000000
--- a/tools/streaming_proto/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2015 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)
-
-# ==========================================================
-# Build the java test
-# ==========================================================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, test) \
- $(call all-proto-files-under, test)
-LOCAL_MODULE := StreamingProtoTest
-LOCAL_PROTOC_OPTIMIZE_TYPE := stream
-include $(BUILD_JAVA_LIBRARY)
-