Skip to content

Commit b46f4bb

Browse files
committed
Merge pull request joeferner#152 from facboy/master
Fix joeferner#143
2 parents 58c59b8 + c3f157c commit b46f4bb

11 files changed

Lines changed: 257 additions & 29 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ node_modules
66
npm-debug.log
77
.idea
88
node-java.cbp
9+
*.iml

binding.gyp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
{
22
'variables': {
33
'arch%': 'amd64', # linux JVM architecture. See $(JAVA_HOME)/jre/lib/<@(arch)/server/
4+
'uname_m': '',
45
'conditions': [
56
['target_arch=="ia32"', {
67
'arch%': 'i386'
78
}],
8-
['"<!(uname -m)"=="s390" or "<!(uname -m)"=="s390x"', {
9+
['OS!="win"', {
10+
'uname_m': '<!(uname -m)'
11+
}],
12+
['uname_m=="s390" or uname_m=="s390x"', {
913
'target_arch': 's390'
1014
}],
11-
['"<!(uname -m)"=="ppc64" or "<!(uname -m)"=="ppc64le"', {
15+
['uname_m=="ppc64" or uname_m=="ppc64le"', {
1216
'target_arch': 'ppc64'
1317
}],
1418
['OS=="win"', {
1.46 KB
Binary file not shown.

src-java/node/NodeDynamicProxyClass.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package node;
22

3-
import java.util.HashSet;
3+
import java.lang.reflect.Method;
4+
import java.lang.reflect.Proxy;
45

56
public class NodeDynamicProxyClass implements java.lang.reflect.InvocationHandler {
7+
private static final Method EQUALS;
8+
private static final Method HASHCODE;
9+
static {
10+
try {
11+
EQUALS = Object.class.getMethod("equals", Object.class);
12+
HASHCODE = Object.class.getMethod("hashCode");
13+
} catch (NoSuchMethodException e) {
14+
throw new ExceptionInInitializerError(e);
15+
}
16+
}
17+
618
private native Object callJs(long ptr, java.lang.reflect.Method m, Object[] args) throws Throwable;
719
private native void unref(long ptr) throws Throwable;
8-
public long ptr;
20+
public final long ptr;
921

1022
public NodeDynamicProxyClass(String path, long ptr) {
1123
try{
@@ -18,13 +30,25 @@ public NodeDynamicProxyClass(String path, long ptr) {
1830

1931
public Object invoke(Object proxy, java.lang.reflect.Method m, Object[] args) throws Throwable
2032
{
21-
Object result = callJs(this.ptr, m, args);
22-
//if(result == null) {
23-
// System.out.println("invoke: null");
24-
//} else {
25-
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
26-
//}
27-
return result;
33+
try {
34+
Object result = callJs(this.ptr, m, args);
35+
//if(result == null) {
36+
// System.out.println("invoke: null");
37+
//} else {
38+
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
39+
//}
40+
return result;
41+
} catch (NoSuchMethodError e) {
42+
// use 'vanilla' implementations otherwise - the object that persists between multiple invocations is
43+
// 'this', not the 'proxy' argument, so we operate on this.
44+
if (EQUALS.equals(m)) {
45+
// need to check if the arg is a Proxy, and if so, if its invocation handler == this!
46+
return Proxy.isProxyClass(args[0].getClass()) && Proxy.getInvocationHandler(args[0]) == this;
47+
} else if (HASHCODE.equals(m)) {
48+
return System.identityHashCode(this);
49+
}
50+
throw e;
51+
}
2852
}
2953

3054
public void unref() throws Throwable {

src/java.cpp

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#include <sstream>
1414
#include <nan.h>
1515

16+
#define DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR -1
17+
#define DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR -2
18+
#define DYNAMIC_PROXY_NO_ENV_ERROR -3
19+
1620
long v8ThreadId;
1721

1822
/*static*/ v8::Persistent<v8::FunctionTemplate> Java::s_ct;
@@ -1006,7 +1010,13 @@ void EIO_AfterCallJs(uv_work_t* req) {
10061010
}
10071011
dynamicProxyData->result = NULL;
10081012

1009-
JNIEnv* env = dynamicProxyData->env;
1013+
JNIEnv* env;
1014+
int ret = dynamicProxyData->java->getJvm()->GetEnv((void**)&env, JNI_VERSION_1_6);
1015+
if (ret != JNI_OK) {
1016+
printf("ERROR: jvm->GetEnv returned %d\n", ret);
1017+
dynamicProxyData->done = DYNAMIC_PROXY_NO_ENV_ERROR;
1018+
return;
1019+
}
10101020

10111021
NanScope();
10121022
v8::Array* v8Args;
@@ -1020,12 +1030,12 @@ void EIO_AfterCallJs(uv_work_t* req) {
10201030
v8::Local<v8::Object> dynamicProxyDataFunctions = NanNew(dynamicProxyData->functions);
10211031
v8::Local<v8::Value> fnObj = dynamicProxyDataFunctions->Get(NanNew<v8::String>(dynamicProxyData->methodName.c_str()));
10221032
if(fnObj->IsUndefined() || fnObj->IsNull()) {
1023-
printf("ERROR: Could not find method %s\n", dynamicProxyData->methodName.c_str());
1024-
goto CleanUp;
1033+
dynamicProxyData->done = DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR;
1034+
return;
10251035
}
10261036
if(!fnObj->IsFunction()) {
1027-
printf("ERROR: %s is not a function.\n", dynamicProxyData->methodName.c_str());
1028-
goto CleanUp;
1037+
dynamicProxyData->done = DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR;
1038+
return;
10291039
}
10301040

10311041
fn = v8::Function::Cast(*fnObj);
@@ -1049,25 +1059,31 @@ void EIO_AfterCallJs(uv_work_t* req) {
10491059
javaResult = v8ToJava(env, v8Result);
10501060
if(javaResult == NULL) {
10511061
dynamicProxyData->result = NULL;
1052-
dynamicProxyData->resultGlobalRef = NULL;
10531062
} else {
1054-
dynamicProxyData->result = javaResult;
1055-
dynamicProxyData->resultGlobalRef = env->NewGlobalRef(javaResult);
1063+
dynamicProxyData->result = env->NewGlobalRef(javaResult);
10561064
}
10571065

1058-
CleanUp:
10591066
dynamicProxyData->done = true;
10601067
}
10611068

1069+
void throwNewThrowable(JNIEnv* env, const char * excClassName, std::string msg) {
1070+
jclass newExcCls = env->FindClass(excClassName);
1071+
jthrowable throwable = env->ExceptionOccurred();
1072+
if (throwable != NULL) {
1073+
env->Throw(throwable); // this should only be Errors, according to the docs
1074+
}
1075+
env->ThrowNew(newExcCls, msg.c_str());
1076+
}
1077+
10621078
JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, jobjectArray args) {
10631079
long myThreadId = my_getThreadId();
1080+
bool hasArgsGlobalRef = false;
10641081

1082+
// args needs to be global, you can't send env across thread boundaries
10651083
DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr;
1066-
dynamicProxyData->env = env;
10671084
dynamicProxyData->args = args;
10681085
dynamicProxyData->done = false;
10691086
dynamicProxyData->result = NULL;
1070-
dynamicProxyData->resultGlobalRef = NULL;
10711087

10721088
jclass methodClazz = env->FindClass("java/lang/reflect/Method");
10731089
jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;");
@@ -1083,6 +1099,12 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
10831099
EIO_AfterCallJs(req);
10841100
#endif
10851101
} else {
1102+
if (args) {
1103+
// if args is not null and we have to kick this across the thread boundary, make it a global ref
1104+
dynamicProxyData->args = (jobjectArray) env->NewGlobalRef(args);
1105+
hasArgsGlobalRef = true;
1106+
}
1107+
10861108
uv_queue_work(uv_default_loop(), req, EIO_CallJs, (uv_after_work_cb)EIO_AfterCallJs);
10871109

10881110
while(!dynamicProxyData->done) {
@@ -1091,12 +1113,31 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
10911113
}
10921114

10931115
if(!dynamicProxyDataVerify(dynamicProxyData)) {
1094-
return NULL;
1116+
throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted");
1117+
}
1118+
if(hasArgsGlobalRef) {
1119+
env->DeleteGlobalRef(dynamicProxyData->args);
10951120
}
1121+
1122+
switch (dynamicProxyData->done) {
1123+
case DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR:
1124+
throwNewThrowable(env, "java/lang/NoSuchMethodError", "Could not find js function " + dynamicProxyData->methodName);
1125+
break;
1126+
case DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR:
1127+
throwNewThrowable(env, "java/lang/IllegalStateException", dynamicProxyData->methodName + " is not a function");
1128+
break;
1129+
case DYNAMIC_PROXY_NO_ENV_ERROR:
1130+
throwNewThrowable(env, "java/lang/IllegalStateException", "Could not retrieve JNIEnv");
1131+
break;
1132+
}
1133+
1134+
jobject result = NULL;
10961135
if(dynamicProxyData->result) {
1097-
env->DeleteGlobalRef(dynamicProxyData->resultGlobalRef);
1136+
// need to retain a local ref so that we can return it, otherwise the returned object gets corrupted
1137+
result = env->NewLocalRef(dynamicProxyData->result);
1138+
env->DeleteGlobalRef(dynamicProxyData->result);
10981139
}
1099-
return dynamicProxyData->result;
1140+
return result;
11001141
}
11011142

11021143
JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *env, jobject src, jlong ptr) {

src/java.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Java : public node::ObjectWrap {
1212
public:
1313
static void Init(v8::Handle<v8::Object> target);
1414
JavaVM* getJvm() { return m_jvm; }
15-
JNIEnv* getJavaEnv() { return m_env; }
15+
JNIEnv* getJavaEnv() { return m_env; } // can only be used safely by the main thread as this is the thread it belongs to
1616
jobject getClassLoader() { return m_classLoader; }
1717

1818
private:
@@ -47,7 +47,7 @@ class Java : public node::ObjectWrap {
4747

4848
static v8::Persistent<v8::FunctionTemplate> s_ct;
4949
JavaVM* m_jvm;
50-
JNIEnv* m_env;
50+
JNIEnv* m_env; // can only be used safely by the main thread as this is the thread it belongs to
5151
jobject m_classLoader;
5252
std::string m_classPath;
5353
static std::string s_nativeBindingLocation;

src/utils.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ typedef enum _jvalueType {
3232

3333
struct DynamicProxyData {
3434
unsigned int markerStart;
35-
JNIEnv* env;
3635
Java* java;
3736
std::string interfaceName;
3837
v8::Persistent<v8::Object> functions;
3938
v8::Persistent<v8::Value> jsObject;
4039
std::string methodName;
4140
jobjectArray args;
4241
jobject result;
43-
jobject resultGlobalRef;
4442
int done;
4543
unsigned int markerEnd;
4644
};

test/RunInterface$1.class

1007 Bytes
Binary file not shown.

test/RunInterface.class

1.61 KB
Binary file not shown.

test/RunInterface.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import java.util.concurrent.CountDownLatch;
12

23
public class RunInterface {
34
public static interface Interface0Arg {
@@ -25,4 +26,41 @@ public void run1Args(Interface1Arg r) {
2526
public int runWithReturn(InterfaceWithReturn r) {
2627
return r.run(42);
2728
}
29+
30+
public int runInAnotherThread(final InterfaceWithReturn r) throws InterruptedException {
31+
final int[] result = {0};
32+
final CountDownLatch latch = new CountDownLatch(1);
33+
Thread t = new Thread(new Runnable() {
34+
@Override
35+
public void run() {
36+
result[0] = r.run(46);
37+
latch.countDown();
38+
}
39+
});
40+
t.start();
41+
latch.await();
42+
return result[0];
43+
}
44+
45+
public boolean runEquals(final InterfaceWithReturn r) {
46+
return r.equals(Boolean.FALSE);
47+
}
48+
49+
public int runHashCode(final InterfaceWithReturn r) {
50+
return r.hashCode();
51+
}
52+
53+
private InterfaceWithReturn prev;
54+
55+
public void setInstance(final InterfaceWithReturn r) {
56+
prev = r;
57+
}
58+
59+
public boolean runEqualsInstance(final InterfaceWithReturn r) {
60+
return r.equals(prev);
61+
}
62+
63+
public String runToString(final InterfaceWithReturn r) {
64+
return r.toString();
65+
}
2866
}

0 commit comments

Comments
 (0)