diff options
Diffstat (limited to 'tools/dump-coverage/dump_coverage.cc')
-rw-r--r-- | tools/dump-coverage/dump_coverage.cc | 205 |
1 files changed, 205 insertions, 0 deletions
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 |