// 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 #include #include #include #include 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(&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 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 ":" returns (, ). // Given a string that doesn't contain a colon, returns (, ""). static tuple 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 // ":" where 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