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 
21 #include <fstream>
22 
23 using std::get;
24 using std::tuple;
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.
GetJNIEnv()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.
GetJavaAgent(JNIEnv * env)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.
GetExecutionData(JNIEnv * env)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 
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.
83 //  returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
WriteFile(const char * data,int length,const std::string & filename)84 static jint WriteFile(const char* data, int length, const std::string& filename) {
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 
105 // Grabs execution data and writes it to a file.
106 //  filename: file to write coverage data to.
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.
Dump(const std::string & filename)109 static jint Dump(const std::string& filename) {
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 
121   return WriteFile((const char*) result_ptr, result_len, filename);
122 }
123 
124 // Resets execution data, performing the equivalent of
125 //  Agent.getInstance().reset();
126 //  args: should be empty.
127 //  returns JNI_ERR if the arguments are invalid.
128 // Will crash if the Agent isn't found or if any Java Exception occurs.
Reset(const std::string & args)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>, "").
SplitOnColon(const std::string & options)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".
ParseOptionsAndExecuteCommand(const std::string & options)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 
AgentStart(JavaVM * vm,char * options)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
Agent_OnAttach(JavaVM * vm,char * options,void * reserved ATTRIBUTE_UNUSED)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
Agent_OnLoad(JavaVM * jvm ATTRIBUTE_UNUSED,char * options ATTRIBUTE_UNUSED,void * reserved ATTRIBUTE_UNUSED)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
206