Skip to content

Commit 8014db7

Browse files
committed
Propagate node.js exceptions into Java.
1 parent c3f157c commit 8014db7

File tree

8 files changed

+95
-19
lines changed

8 files changed

+95
-19
lines changed
354 Bytes
Binary file not shown.

src-java/node/NodeJsException.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package node;
2+
3+
/**
4+
* @author Christopher Ng
5+
*/
6+
public class NodeJsException extends RuntimeException {
7+
public NodeJsException(String message) {
8+
super(message);
9+
}
10+
}

src/java.cpp

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
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
16+
#define DYNAMIC_PROXY_JS_ERROR -4
1917

2018
long v8ThreadId;
2119

@@ -999,6 +997,15 @@ NAN_METHOD(Java::instanceOf) {
999997
void EIO_CallJs(uv_work_t* req) {
1000998
}
1001999

1000+
jthrowable newThrowable(JNIEnv* env, const char * excClassName, std::string msg) {
1001+
jclass newExcCls = env->FindClass(excClassName);
1002+
jthrowable throwable = env->ExceptionOccurred();
1003+
if (throwable != NULL) {
1004+
return throwable; // this should only be Errors, according to the docs
1005+
}
1006+
env->ThrowNew(newExcCls, msg.c_str());
1007+
}
1008+
10021009
#if NODE_MINOR_VERSION >= 10
10031010
void EIO_AfterCallJs(uv_work_t* req, int status) {
10041011
#else
@@ -1013,9 +1020,10 @@ void EIO_AfterCallJs(uv_work_t* req) {
10131020
JNIEnv* env;
10141021
int ret = dynamicProxyData->java->getJvm()->GetEnv((void**)&env, JNI_VERSION_1_6);
10151022
if (ret != JNI_OK) {
1016-
printf("ERROR: jvm->GetEnv returned %d\n", ret);
1017-
dynamicProxyData->done = DYNAMIC_PROXY_NO_ENV_ERROR;
1018-
return;
1023+
dynamicProxyData->throwableClass = "java/lang/IllegalStateException";
1024+
dynamicProxyData->throwableMessage = "Could not retrieve JNIEnv: jvm->GetEnv returned " + ret;
1025+
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
1026+
return;
10191027
}
10201028

10211029
NanScope();
@@ -1030,11 +1038,15 @@ void EIO_AfterCallJs(uv_work_t* req) {
10301038
v8::Local<v8::Object> dynamicProxyDataFunctions = NanNew(dynamicProxyData->functions);
10311039
v8::Local<v8::Value> fnObj = dynamicProxyDataFunctions->Get(NanNew<v8::String>(dynamicProxyData->methodName.c_str()));
10321040
if(fnObj->IsUndefined() || fnObj->IsNull()) {
1033-
dynamicProxyData->done = DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR;
1041+
dynamicProxyData->throwableClass = "java/lang/NoSuchMethodError";
1042+
dynamicProxyData->throwableMessage = "Could not find js function " + dynamicProxyData->methodName;
1043+
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
10341044
return;
10351045
}
10361046
if(!fnObj->IsFunction()) {
1037-
dynamicProxyData->done = DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR;
1047+
dynamicProxyData->throwableClass = "java/lang/IllegalStateException";
1048+
dynamicProxyData->throwableMessage = dynamicProxyData->methodName + " is not a function";
1049+
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
10381050
return;
10391051
}
10401052

@@ -1050,8 +1062,24 @@ void EIO_AfterCallJs(uv_work_t* req) {
10501062
for(i=0; i<argc; i++) {
10511063
argv[i] = v8Args->Get(i);
10521064
}
1065+
1066+
v8::TryCatch tryCatch;
10531067
v8Result = fn->Call(dynamicProxyDataFunctions, argc, argv);
10541068
delete[] argv;
1069+
if (tryCatch.HasCaught()) {
1070+
dynamicProxyData->throwableClass = "node/NodeJsException";
1071+
v8::String::Utf8Value stackTrace(tryCatch.StackTrace());
1072+
if (stackTrace.length() > 0) {
1073+
dynamicProxyData->throwableMessage = std::string(*stackTrace);
1074+
} else {
1075+
v8::String::Utf8Value exception(tryCatch.Exception());
1076+
dynamicProxyData->throwableMessage = std::string(*exception);
1077+
}
1078+
tryCatch.Reset();
1079+
dynamicProxyData->done = DYNAMIC_PROXY_JS_ERROR;
1080+
return;
1081+
}
1082+
10551083
if(!dynamicProxyDataVerify(dynamicProxyData)) {
10561084
return;
10571085
}
@@ -1084,6 +1112,8 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
10841112
dynamicProxyData->args = args;
10851113
dynamicProxyData->done = false;
10861114
dynamicProxyData->result = NULL;
1115+
dynamicProxyData->throwableClass = "";
1116+
dynamicProxyData->throwableMessage = "";
10871117

10881118
jclass methodClazz = env->FindClass("java/lang/reflect/Method");
10891119
jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;");
@@ -1119,16 +1149,8 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
11191149
env->DeleteGlobalRef(dynamicProxyData->args);
11201150
}
11211151

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;
1152+
if (dynamicProxyData->done == DYNAMIC_PROXY_JS_ERROR) {
1153+
throwNewThrowable(env, dynamicProxyData->throwableClass.c_str(), dynamicProxyData->throwableMessage);
11321154
}
11331155

11341156
jobject result = NULL;

src/utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ struct DynamicProxyData {
3939
std::string methodName;
4040
jobjectArray args;
4141
jobject result;
42+
std::string throwableClass;
43+
std::string throwableMessage;
4244
int done;
4345
unsigned int markerEnd;
4446
};

test/RunInterface$1.class

0 Bytes
Binary file not shown.

test/RunInterface.class

171 Bytes
Binary file not shown.

test/RunInterface.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public void run1Args(Interface1Arg r) {
2424
}
2525

2626
public int runWithReturn(InterfaceWithReturn r) {
27-
return r.run(42);
27+
try {
28+
return r.run(42);
29+
} catch (RuntimeException e) {
30+
throw new RuntimeException(e);
31+
}
2832
}
2933

3034
public int runInAnotherThread(final InterfaceWithReturn r) throws InterruptedException {

test/dynamicProxy-test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,44 @@ exports['Dynamic Proxy'] = nodeunit.testCase({
201201

202202
test.equals(result, "myRunInterface");
203203

204+
test.done();
205+
},
206+
207+
"js string error": function (test) {
208+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
209+
run: function (i) {
210+
throw 'myError';
211+
}
212+
});
213+
214+
var runInterface = java.newInstanceSync("RunInterface");
215+
try {
216+
runInterface.runWithReturnSync(myProxy);
217+
test.fail("Exception was not thrown");
218+
} catch (e) {
219+
test.equals(e.cause.getClassSync().getNameSync(), "java.lang.RuntimeException");
220+
test.ok(/Caused by: node\.NodeJsException: myError/.test(e.message));
221+
}
222+
223+
test.done();
224+
},
225+
226+
"js Error": function (test) {
227+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
228+
run: function (i) {
229+
throw new Error('newError');
230+
}
231+
});
232+
233+
var runInterface = java.newInstanceSync("RunInterface");
234+
try {
235+
runInterface.runWithReturnSync(myProxy);
236+
test.fail("Exception was not thrown");
237+
} catch (e) {
238+
test.equals(e.cause.getClassSync().getNameSync(), "java.lang.RuntimeException");
239+
test.ok(/Caused by: node\.NodeJsException: Error: newError/.test(e.message));
240+
}
241+
204242
test.done();
205243
}
206244
});

0 commit comments

Comments
 (0)