/*
* Copyright (C) 2017 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 <jni.h>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include "jvmti.h"
#include <slicer/dex_ir.h>
#include <slicer/writer.h>
#include <slicer/reader.h>
using namespace dex;
namespace com_android_dx_mockito_inline_tests {
static jvmtiEnv *localJvmtiEnv;
// Converts a class name to a type descriptor
// (ex. "java.lang.String" to "Ljava/lang/String;")
static std::string
ClassNameToDescriptor(const char* class_name) {
std::stringstream ss;
ss << "L";
for (auto p = class_name; *p != '\0'; ++p) {
ss << (*p == '.' ? '/' : *p);
}
ss << ";";
return ss.str();
}
static void
Transform(jvmtiEnv *jvmti_env,
JNIEnv *env,
jclass classBeingRedefined,
jobject loader,
const char *name,
jobject protectionDomain,
jint classDataLen,
const unsigned char *classData,
jint *newClassDataLen,
unsigned char **newClassData) {
// Isolate byte code of class class. This is needed as Android usually gives us more
// than the class we need.
// Then just return the isolated byte code without modification.
Reader reader(classData, (size_t) classDataLen);
u4 index = reader.FindClassIndex(ClassNameToDescriptor(name).c_str());
reader.CreateClassIr(index);
std::shared_ptr<ir::DexFile> ir = reader.GetIr();
class Allocator : public Writer::Allocator {
jvmtiEnv *jvmti_env;
public:
Allocator(jvmtiEnv *jvmti_env) : Writer::Allocator(), jvmti_env(jvmti_env) {
}
virtual void *Allocate(size_t size) {
unsigned char *mem;
jvmti_env->Allocate(size, &mem);
return mem;
}
virtual void Free(void *ptr) { ::free(ptr); }
};
Allocator allocator(jvmti_env);
Writer writer(ir);
size_t newClassLen;
*newClassData = writer.CreateImage(&allocator, &newClassLen);
*newClassDataLen = (jint) newClassLen;
}
// Initializes the agent
extern "C" jint Agent_OnAttach(JavaVM *vm,
char *options,
void *reserved) {
jint jvmError = vm->GetEnv(reinterpret_cast<void **>(&localJvmtiEnv), JVMTI_VERSION_1_2);
if (jvmError != JNI_OK) {
return jvmError;
}
jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_retransform_classes = 1;
jvmtiError error = localJvmtiEnv->AddCapabilities(&caps);
if (error != JVMTI_ERROR_NONE) {
return error;
}
jvmtiEventCallbacks cb;
memset(&cb, 0, sizeof(cb));
cb.ClassFileLoadHook = Transform;
error = localJvmtiEnv->SetEventCallbacks(&cb, sizeof(cb));
if (error != JVMTI_ERROR_NONE) {
return error;
}
error = localJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
NULL);
if (error != JVMTI_ERROR_NONE) {
return error;
}
return JVMTI_ERROR_NONE;
}
// Triggers retransformation of classes via this file's Transform method
extern "C" JNIEXPORT jint JNICALL
Java_com_android_dx_mockito_inline_tests_MultipleJvmtiAgentsInterference_nativeRetransformClasses(
JNIEnv *env,
jobject thiz,
jobjectArray classes) {
jsize numTransformedClasses = env->GetArrayLength(classes);
jclass *transformedClasses = (jclass *) malloc(numTransformedClasses * sizeof(jclass));
for (int i = 0; i < numTransformedClasses; i++) {
transformedClasses[i] = (jclass) env->NewGlobalRef(env->GetObjectArrayElement(classes,
i));
}
jvmtiError error = localJvmtiEnv->RetransformClasses(numTransformedClasses,
transformedClasses);
for (int i = 0; i < numTransformedClasses; i++) {
env->DeleteGlobalRef(transformedClasses[i]);
}
free(transformedClasses);
return error;
}
// Disable hook to not slow down test
extern "C" JNIEXPORT jint JNICALL
Java_com_android_dx_mockito_inline_tests_MultipleJvmtiAgentsInterference_disableRetransformHook(
JNIEnv *env,
jclass ignored) {
return localJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
NULL);
}
}