Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 1 | // Copyright (C) 2018 The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | // |
| 15 | |
| 16 | #include <android-base/logging.h> |
| 17 | #include <jni.h> |
| 18 | #include <jvmti.h> |
| 19 | #include <string.h> |
| 20 | |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 21 | #include <fstream> |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 22 | |
| 23 | using std::get; |
| 24 | using std::tuple; |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 25 | |
| 26 | namespace dump_coverage { |
| 27 | |
| 28 | #define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE) |
| 29 | #define CHECK_NOTNULL(x) CHECK((x) != nullptr) |
| 30 | #define CHECK_NO_EXCEPTION(env) CHECK(!(env)->ExceptionCheck()); |
| 31 | |
| 32 | static JavaVM* java_vm = nullptr; |
| 33 | |
| 34 | // Get the current JNI environment. |
| 35 | static JNIEnv* GetJNIEnv() { |
| 36 | JNIEnv* env = nullptr; |
| 37 | CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6), |
| 38 | JNI_OK); |
| 39 | return env; |
| 40 | } |
| 41 | |
| 42 | // Get the JaCoCo Agent class and an instance of the class, given a JNI |
| 43 | // environment. |
| 44 | // Will crash if the Agent isn't found or if any Java Exception occurs. |
| 45 | static tuple<jclass, jobject> GetJavaAgent(JNIEnv* env) { |
| 46 | jclass java_agent_class = |
| 47 | env->FindClass("org/jacoco/agent/rt/internal/Agent"); |
| 48 | CHECK_NOTNULL(java_agent_class); |
| 49 | |
| 50 | jmethodID java_agent_get_instance = |
| 51 | env->GetStaticMethodID(java_agent_class, "getInstance", |
| 52 | "()Lorg/jacoco/agent/rt/internal/Agent;"); |
| 53 | CHECK_NOTNULL(java_agent_get_instance); |
| 54 | |
| 55 | jobject java_agent_instance = |
| 56 | env->CallStaticObjectMethod(java_agent_class, java_agent_get_instance); |
| 57 | CHECK_NO_EXCEPTION(env); |
| 58 | CHECK_NOTNULL(java_agent_instance); |
| 59 | |
| 60 | return tuple(java_agent_class, java_agent_instance); |
| 61 | } |
| 62 | |
| 63 | // Runs equivalent of Agent.getInstance().getExecutionData(false) and returns |
| 64 | // the result. |
| 65 | // Will crash if the Agent isn't found or if any Java Exception occurs. |
| 66 | static jbyteArray GetExecutionData(JNIEnv* env) { |
| 67 | auto java_agent = GetJavaAgent(env); |
| 68 | jmethodID java_agent_get_execution_data = |
| 69 | env->GetMethodID(get<0>(java_agent), "getExecutionData", "(Z)[B"); |
| 70 | CHECK_NO_EXCEPTION(env); |
| 71 | CHECK_NOTNULL(java_agent_get_execution_data); |
| 72 | |
| 73 | jbyteArray java_result_array = (jbyteArray)env->CallObjectMethod( |
| 74 | get<1>(java_agent), java_agent_get_execution_data, false); |
| 75 | CHECK_NO_EXCEPTION(env); |
| 76 | |
| 77 | return java_result_array; |
| 78 | } |
| 79 | |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 80 | // Writes the execution data to a file. |
| 81 | // data, length: represent the data, as a sequence of bytes. |
| 82 | // filename: file to write coverage data to. |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 83 | // returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK. |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 84 | static jint WriteFile(const char* data, int length, const std::string& filename) { |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 85 | LOG(INFO) << "Writing file of length " << length << " to '" << filename |
| 86 | << "'"; |
| 87 | std::ofstream file(filename, std::ios::binary); |
| 88 | |
| 89 | if (!file.is_open()) { |
| 90 | LOG(ERROR) << "Could not open file: '" << filename << "'"; |
| 91 | return JNI_ERR; |
| 92 | } |
| 93 | file.write(data, length); |
| 94 | file.close(); |
| 95 | |
| 96 | if (!file) { |
| 97 | LOG(ERROR) << "I/O error in reading file"; |
| 98 | return JNI_ERR; |
| 99 | } |
| 100 | |
| 101 | LOG(INFO) << "Done writing file"; |
| 102 | return JNI_OK; |
| 103 | } |
| 104 | |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 105 | // Grabs execution data and writes it to a file. |
| 106 | // filename: file to write coverage data to. |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 107 | // returns JNI_ERR if there is an error writing the file. |
| 108 | // Will crash if the Agent isn't found or if any Java Exception occurs. |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 109 | static jint Dump(const std::string& filename) { |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 110 | LOG(INFO) << "Dumping file"; |
| 111 | |
| 112 | JNIEnv* env = GetJNIEnv(); |
| 113 | jbyteArray java_result_array = GetExecutionData(env); |
| 114 | CHECK_NOTNULL(java_result_array); |
| 115 | |
| 116 | jbyte* result_ptr = env->GetByteArrayElements(java_result_array, 0); |
| 117 | CHECK_NOTNULL(result_ptr); |
| 118 | |
| 119 | int result_len = env->GetArrayLength(java_result_array); |
| 120 | |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 121 | return WriteFile((const char*) result_ptr, result_len, filename); |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | // Resets execution data, performing the equivalent of |
| 125 | // Agent.getInstance().reset(); |
Oliver Nguyen | 05b1c06 | 2019-07-16 14:09:38 -0700 | [diff] [blame] | 126 | // args: should be empty. |
Kavi Gupta | 2d84ae7 | 2019-06-11 09:19:35 -0700 | [diff] [blame] | 127 | // returns JNI_ERR if the arguments are invalid. |
| 128 | // Will crash if the Agent isn't found or if any Java Exception occurs. |
| 129 | static jint Reset(const std::string& args) { |
| 130 | if (args != "") { |
| 131 | LOG(ERROR) << "reset takes no arguments, but received '" << args << "'"; |
| 132 | return JNI_ERR; |
| 133 | } |
| 134 | |
| 135 | JNIEnv* env = GetJNIEnv(); |
| 136 | auto java_agent = GetJavaAgent(env); |
| 137 | |
| 138 | jmethodID java_agent_reset = |
| 139 | env->GetMethodID(get<0>(java_agent), "reset", "()V"); |
| 140 | CHECK_NOTNULL(java_agent_reset); |
| 141 | |
| 142 | env->CallVoidMethod(get<1>(java_agent), java_agent_reset); |
| 143 | CHECK_NO_EXCEPTION(env); |
| 144 | return JNI_OK; |
| 145 | } |
| 146 | |
| 147 | // Given a string of the form "<a>:<b>" returns (<a>, <b>). |
| 148 | // Given a string <a> that doesn't contain a colon, returns (<a>, ""). |
| 149 | static tuple<std::string, std::string> SplitOnColon(const std::string& options) { |
| 150 | size_t loc_delim = options.find(':'); |
| 151 | std::string command, args; |
| 152 | |
| 153 | if (loc_delim == std::string::npos) { |
| 154 | command = options; |
| 155 | } else { |
| 156 | command = options.substr(0, loc_delim); |
| 157 | args = options.substr(loc_delim + 1, options.length()); |
| 158 | } |
| 159 | return tuple(command, args); |
| 160 | } |
| 161 | |
| 162 | // Parses and executes a command specified by options of the form |
| 163 | // "<command>:<args>" where <command> is either "dump" or "reset". |
| 164 | static jint ParseOptionsAndExecuteCommand(const std::string& options) { |
| 165 | auto split = SplitOnColon(options); |
| 166 | auto command = get<0>(split), args = get<1>(split); |
| 167 | |
| 168 | LOG(INFO) << "command: '" << command << "' args: '" << args << "'"; |
| 169 | |
| 170 | if (command == "dump") { |
| 171 | return Dump(args); |
| 172 | } |
| 173 | |
| 174 | if (command == "reset") { |
| 175 | return Reset(args); |
| 176 | } |
| 177 | |
| 178 | LOG(ERROR) << "Invalid command: expected 'dump' or 'reset' but was '" |
| 179 | << command << "'"; |
| 180 | return JNI_ERR; |
| 181 | } |
| 182 | |
| 183 | static jint AgentStart(JavaVM* vm, char* options) { |
| 184 | android::base::InitLogging(/* argv= */ nullptr); |
| 185 | java_vm = vm; |
| 186 | |
| 187 | return ParseOptionsAndExecuteCommand(options); |
| 188 | } |
| 189 | |
| 190 | // Late attachment (e.g. 'am attach-agent'). |
| 191 | extern "C" JNIEXPORT jint JNICALL |
| 192 | Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) { |
| 193 | return AgentStart(vm, options); |
| 194 | } |
| 195 | |
| 196 | // Early attachment. |
| 197 | extern "C" JNIEXPORT jint JNICALL |
| 198 | Agent_OnLoad(JavaVM* jvm ATTRIBUTE_UNUSED, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) { |
| 199 | LOG(ERROR) |
| 200 | << "The dumpcoverage agent will not work on load," |
| 201 | << " as it does not have access to the runtime."; |
| 202 | return JNI_ERR; |
| 203 | } |
| 204 | |
| 205 | } // namespace dump_coverage |