diff options
Diffstat (limited to 'tools')
60 files changed, 2232 insertions, 3597 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/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/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/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp index 1121eadca967..14eead853f50 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -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) - |