blob: 0808e776f190cccb077c2b105720ef7e7ad80a7b [file] [log] [blame]
Kavi Gupta2d84ae72019-06-11 09:19:35 -07001// 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 Gupta2d84ae72019-06-11 09:19:35 -070021#include <fstream>
Kavi Gupta2d84ae72019-06-11 09:19:35 -070022
23using std::get;
24using std::tuple;
Kavi Gupta2d84ae72019-06-11 09:19:35 -070025
26namespace 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
32static JavaVM* java_vm = nullptr;
33
34// Get the current JNI environment.
35static 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.
45static 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.
66static 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 Nguyen05b1c062019-07-16 14:09:38 -070080// 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 Gupta2d84ae72019-06-11 09:19:35 -070083// returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
Oliver Nguyen05b1c062019-07-16 14:09:38 -070084static jint WriteFile(const char* data, int length, const std::string& filename) {
Kavi Gupta2d84ae72019-06-11 09:19:35 -070085 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 Nguyen05b1c062019-07-16 14:09:38 -0700105// Grabs execution data and writes it to a file.
106// filename: file to write coverage data to.
Kavi Gupta2d84ae72019-06-11 09:19:35 -0700107// 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 Nguyen05b1c062019-07-16 14:09:38 -0700109static jint Dump(const std::string& filename) {
Kavi Gupta2d84ae72019-06-11 09:19:35 -0700110 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 Nguyen05b1c062019-07-16 14:09:38 -0700121 return WriteFile((const char*) result_ptr, result_len, filename);
Kavi Gupta2d84ae72019-06-11 09:19:35 -0700122}
123
124// Resets execution data, performing the equivalent of
125// Agent.getInstance().reset();
Oliver Nguyen05b1c062019-07-16 14:09:38 -0700126// args: should be empty.
Kavi Gupta2d84ae72019-06-11 09:19:35 -0700127// returns JNI_ERR if the arguments are invalid.
128// Will crash if the Agent isn't found or if any Java Exception occurs.
129static 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>, "").
149static 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".
164static 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
183static 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').
191extern "C" JNIEXPORT jint JNICALL
192Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
193 return AgentStart(vm, options);
194}
195
196// Early attachment.
197extern "C" JNIEXPORT jint JNICALL
198Agent_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