blob: 0e9d4651db8ecdf66859143582797f8df22db18d [file] [log] [blame]
/*
* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "com_apple_jobjc_Subclassing.h"
#include <math.h>
#include <assert.h>
#include <errno.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <ffi/ffi.h>
#include <sys/mman.h>
#include <JavaNativeFoundation/JavaNativeFoundation.h>
// Subclassing of Obj-C classes in Java
//
// See:
// - Objective-C Runtime documentation
// - man ffi_prep_closure
// - Subclassing.java
#pragma mark Accessing object in IVar
#define JOBJ_IVAR_NAME "jObjWrapper"
static jobject getJObjectFromIVar(id obj);
jobject getJObjectFromIVar(id obj)
{
JNFJObjectWrapper *wrapper = NULL;
object_getInstanceVariable(obj, JOBJ_IVAR_NAME, (void**) &wrapper);
return wrapper ? [wrapper jObject] : NULL;
}
JNIEXPORT jobject JNICALL Java_com_apple_jobjc_Subclassing_getJObjectFromIVar
(JNIEnv *env, jclass jClass, jlong jPtr)
{
id obj = (id) jlong_to_ptr(jPtr);
if(obj == NULL){
(*env)->ThrowNew(env, (*env)->FindClass(env,
"java/lang/NullPointerException"), "obj");
return NULL;
}
JNFJObjectWrapper *wrapper;
if(!object_getInstanceVariable(obj, JOBJ_IVAR_NAME, (void**) &wrapper)){
NSLog(@"IVar '%s' not found. obj: %@", JOBJ_IVAR_NAME, obj);
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"),
"Could not find instance variable that holds Java object.");
return NULL;
}
return wrapper ? [wrapper jObject] : NULL;
}
JNIEXPORT void JNICALL Java_com_apple_jobjc_Subclassing_initJObjectToIVar
(JNIEnv *env, jclass jClass, jlong jPtr, jobject jObject)
{
id obj = (id) jlong_to_ptr(jPtr);
JNFJObjectWrapper *wrapper = [[JNFJObjectWrapper alloc]
initWithJObject:jObject withEnv:env];
[wrapper retain];
if(!object_setInstanceVariable(obj, JOBJ_IVAR_NAME, wrapper)){
NSLog(@"IVar '%s' not found. obj: %@", JOBJ_IVAR_NAME, obj);
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"),
"Could not find instance variable that holds Java object.");
return;
}
}
#pragma mark Registering class
JNIEXPORT jlong JNICALL Java_com_apple_jobjc_Subclassing_allocateClassPair
(JNIEnv *env, jclass clazz, jlong jSuperClass, jstring jName)
{
const Class superClass = (Class)jlong_to_ptr(jSuperClass);
assert(superClass);
const char *name = (*env)->GetStringUTFChars(env, jName, JNI_FALSE);
const Class newClass = objc_allocateClassPair(superClass, name, 0);
(*env)->ReleaseStringUTFChars(env, jName, name);
return ptr_to_jlong(newClass);
}
JNIEXPORT jboolean JNICALL Java_com_apple_jobjc_Subclassing_addIVarForJObj
(JNIEnv *env, jclass clazz, jlong jSynthClass)
{
return class_addIvar(
jlong_to_ptr(jSynthClass),
JOBJ_IVAR_NAME,
sizeof(id),
(uint8_t)log2((double)sizeof(id)),
"@");
}
JNIEXPORT void JNICALL Java_com_apple_jobjc_Subclassing_registerClassPair
(JNIEnv *env, jclass clazz, jlong jClass)
{
Class c = jlong_to_ptr(jClass);
// NSLog(@"Registering class pair %p / %s", c, class_getName(c));
objc_registerClassPair(c);
}
#pragma mark Patching +alloc
static id patchedAllocIMP(id obj, SEL sel);
static void addJavaInstance(id obj);
JNIEXPORT jboolean JNICALL Java_com_apple_jobjc_Subclassing_patchAlloc
(JNIEnv *env, jclass clazz, jlong jNativeClass)
{
Class metaClass = object_getClass(jlong_to_ptr(jNativeClass));
return class_addMethod(metaClass,
sel_registerName("alloc"),
(IMP) patchedAllocIMP,
"@@:");
}
static id patchedAllocIMP(id cls, SEL sel){
id inst = class_createInstance(cls, 0);
addJavaInstance(inst);
return inst;
}
static void addJavaInstance(id obj){
// NSLog(@"addJavaInstance %p", obj);
// NSLog(@"... calling up to Java");
static JNF_CLASS_CACHE(jc_Subclassing, "com/apple/jobjc/Subclassing");
static JNF_STATIC_MEMBER_CACHE(jm_Subclassing_initJObject,
jc_Subclassing,
"initJObject",
"(J)V");
JNFThreadContext threadWasAttached = JNFThreadDetachOnThreadDeath;
JNIEnv *env = JNFObtainEnv(&threadWasAttached);
JNFCallStaticVoidMethod(env, jm_Subclassing_initJObject,
ptr_to_jlong(obj));
JNFReleaseEnv(env, &threadWasAttached);
}
#pragma mark Adding methods
static ffi_closure *make_closure(ffi_cif *cif, void *user_data);
static void sel_closure_call(ffi_cif* cif, void* result, void** args, void* user_data);
typedef struct closure_data_t{
JNFJObjectWrapper *jMethod;
JNFJObjectWrapper *jCIF;
} closure_data_t;
static ffi_closure *make_closure(ffi_cif *cif, void *user_data){
// Allocate a page to hold the closure with read and write permissions.
ffi_closure *closure;
if ((closure = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, (off_t) 0)) == (void*)-1)
{
fprintf(stderr, "mmap failed with errno: %d", errno);
return NULL;
}
// Prepare the ffi_closure structure.
ffi_status status;
if ((status = ffi_prep_closure(closure, cif, sel_closure_call, (void *)user_data)) != FFI_OK)
{
fprintf(stderr, "ffi_prep_closure failed with ffi_status: %d", status);
munmap(closure, sizeof(ffi_closure));
return NULL;
}
// Ensure that the closure will execute on all architectures.
if (mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1)
{
fprintf(stderr, "mprotect failed with errno: %d", errno);
munmap(closure, sizeof(ffi_closure));
return NULL;
}
return closure;
}
JNIEXPORT jboolean JNICALL Java_com_apple_jobjc_Subclassing_addMethod
(JNIEnv *env, jclass clazz, jlong jClass, jstring jSelName, jobject jMethod,
jobject jCIF, jlong jCIFPtr, jstring jObjCEncodedType)
{
ffi_cif *cif = jlong_to_ptr(jCIFPtr);
closure_data_t *user_data = malloc(sizeof(closure_data_t));
user_data->jMethod = [[JNFJObjectWrapper alloc] initWithJObject:jMethod withEnv:env];
user_data->jCIF = [[JNFJObjectWrapper alloc] initWithJObject:jCIF withEnv:env];
ffi_closure *closure;;
if(!(closure = make_closure(cif, user_data))){
[user_data->jMethod release];
[user_data->jCIF release];
free(user_data);
return NO;
}
const Class objcClass = (Class)jlong_to_ptr(jClass);
const char *selName = (*env)->GetStringUTFChars(env, jSelName, JNI_FALSE);
const char *objCEncodedType = (*env)->GetStringUTFChars(env, jObjCEncodedType, JNI_FALSE);
// NSLog(@"Adding method '%s' :: '%s' to '%s' / %p",
// selName,
// objCEncodedType,
// class_getName(objcClass),
// objcClass);
BOOL ret = class_addMethod(objcClass, sel_registerName(selName), (IMP) closure, objCEncodedType);
(*env)->ReleaseStringUTFChars(env, jSelName, selName);
(*env)->ReleaseStringUTFChars(env, jObjCEncodedType, objCEncodedType);
if(!ret){
NSLog(@"class_addMethod failed");
munmap(closure, sizeof(ffi_closure));
[user_data->jMethod release];
[user_data->jCIF release];
free(user_data);
return NO;
}
return ret;
}
static void sel_closure_call(ffi_cif* cif, void* result, void** args, void* user_data)
{
id obj = *(id*) args[0];
// SEL sel = *(SEL*) args[1];
// NSLog(@"Subclassing: sel_closure_call: %p %p", obj, sel);
// NSLog(@"Subclassing: sel_closure_call: obj class: %@ sel name: %s", object_getClass(obj), sel_getName(sel));
jobject jObj = getJObjectFromIVar(obj);
if(!jObj){
addJavaInstance(obj);
jObj = getJObjectFromIVar(obj);
}
closure_data_t *jmeta = user_data;
jobject jMethod = [jmeta->jMethod jObject];
jobject jCIF = [jmeta->jCIF jObject];
JNFThreadContext threadWasAttached = JNFThreadDetachOnThreadDeath;
JNIEnv *env = JNFObtainEnv(&threadWasAttached);
if((*env)->ExceptionOccurred(env)) goto bail;
static JNF_CLASS_CACHE(jc, "com/apple/jobjc/Subclassing");
static JNF_STATIC_MEMBER_CACHE(jm_invokeFromJNI, jc, "invokeFromJNI",
"(Lcom/apple/jobjc/ID;Ljava/lang/reflect/Method;Lcom/apple/jobjc/CIF;JJ)V");
JNFCallStaticVoidMethod(env, jm_invokeFromJNI,
jObj,
jMethod,
jCIF,
ptr_to_jlong(result),
ptr_to_jlong(args));
bail:
JNFReleaseEnv(env, &threadWasAttached);
if((*env)->ExceptionOccurred(env)){
NSLog(@"Exception!");
(*env)->ExceptionDescribe(env);
}
JNFReleaseEnv(env, &threadWasAttached);
}