diff --git a/jni/com_eclipsesource_v8_V8Impl.cpp b/jni/com_eclipsesource_v8_V8Impl.cpp index e2c648b19..c3a71a3ad 100644 --- a/jni/com_eclipsesource_v8_V8Impl.cpp +++ b/jni/com_eclipsesource_v8_V8Impl.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "com_eclipsesource_v8_V8Impl.h" @@ -20,6 +21,7 @@ class V8Runtime { }; std::map v8Isolates; +JavaVM* jvm = NULL; void throwError( JNIEnv *env, const char *message ); void throwExecutionException( JNIEnv *env, const char *message ); @@ -27,9 +29,78 @@ void throwResultUndefinedException( JNIEnv *env, const char *message ); Isolate* getIsolate(JNIEnv *env, int handle); void setupJNIContext(int v8RuntimeHandle, JNIEnv *env, jobject v8 ); +void debugHandler() { + JNIEnv * g_env; + // double check it's all ok + int getEnvStat = jvm->GetEnv((void **) &g_env, JNI_VERSION_1_6); + if (getEnvStat == JNI_EDETACHED) { + if (jvm->AttachCurrentThread((void **) &g_env, NULL) != 0) { + std::cout << "Failed to attach" << std::endl; + } + } else if (getEnvStat == JNI_OK) { + // + } else if (getEnvStat == JNI_EVERSION) { + std::cout << "GetEnv: version not supported" << std::endl; + } + + jclass cls = g_env->FindClass("com/eclipsesource/v8/V8"); + jmethodID processDebugMessage = g_env->GetStaticMethodID(cls, "debugMessageReceived", "()V"); + + g_env->CallStaticVoidMethod(cls, processDebugMessage); + + if (g_env->ExceptionCheck()) { + g_env->ExceptionDescribe(); + } + + jvm->DetachCurrentThread(); +} + +JNIEXPORT jboolean JNICALL Java_com_eclipsesource_v8_V8__1enableDebugSupport + (JNIEnv *env, jobject, jint v8RuntimeHandle, jint port, jboolean waitForConnection) { + Isolate* isolate = getIsolate(env, v8RuntimeHandle); + if ( isolate == NULL ) { + return false; + } + v8::Isolate::Scope isolateScope(isolate); + HandleScope handle_scope(isolate); + v8::Local context = v8::Local::New(isolate,v8Isolates[v8RuntimeHandle]->context_); + Context::Scope context_scope(context); + bool result = v8::Debug::EnableAgent("j2v8", port, waitForConnection); + v8::Debug::SetDebugMessageDispatchHandler(&debugHandler); + return result; +} + +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1disableDebugSupport + (JNIEnv *env, jobject, jint v8RuntimeHandle) { + Isolate* isolate = getIsolate(env, v8RuntimeHandle); + if ( isolate == NULL ) { + return; + } + v8::Isolate::Scope isolateScope(isolate); + HandleScope handle_scope(isolate); + v8::Local context = v8::Local::New(isolate,v8Isolates[v8RuntimeHandle]->context_); + Context::Scope context_scope(context); + v8::Debug::DisableAgent(); +} + +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1processDebugMessages + (JNIEnv *env, jobject, jint v8RuntimeHandle) { + Isolate* isolate = getIsolate(env, v8RuntimeHandle); + if ( isolate == NULL ) { + return; + } + v8::Isolate::Scope isolateScope(isolate); + HandleScope handle_scope(isolate); + v8::Local context = v8::Local::New(isolate,v8Isolates[v8RuntimeHandle]->context_); + Context::Scope context_scope(context); + v8::Debug::ProcessDebugMessages(); +} JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1createIsolate - (JNIEnv *, jobject, jint handle) { + (JNIEnv *env, jobject, jint handle) { + if (jvm == NULL ) { + env->GetJavaVM(&jvm); + } v8Isolates[handle] = new V8Runtime(); v8Isolates[handle]->isolate = Isolate::New(); v8Isolates[handle]->isolate_scope = new Isolate::Scope(v8Isolates[handle]->isolate); @@ -613,7 +684,6 @@ JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1executeVoidFunction v8::Local context = v8::Local::New(isolate,v8Isolates[v8RuntimeHandle]->context_); Context::Scope context_scope(context); Handle parentObject = Local::New(isolate, *v8Isolates[v8RuntimeHandle]->objects[objectHandle]); - int size = 0; Handle* args = NULL; if ( parameterHandle >= 0 ) { @@ -624,8 +694,7 @@ JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1executeVoidFunction args[i] = parameters->Get(i); } } - - Handle value = parentObject->Get(v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), functionName)); + Handle value = parentObject->Get(v8::String::NewFromUtf8(isolate, functionName)); Handle func = v8::Handle::Cast(value); func->Call(parentObject, size, args); env->ReleaseStringUTFChars(jfunctionName, functionName); diff --git a/jni/com_eclipsesource_v8_V8Impl.h b/jni/com_eclipsesource_v8_V8Impl.h index 05792d28f..475e0930a 100644 --- a/jni/com_eclipsesource_v8_V8Impl.h +++ b/jni/com_eclipsesource_v8_V8Impl.h @@ -437,6 +437,30 @@ JNIEXPORT jint JNICALL Java_com_eclipsesource_v8_V8__1getType__III JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1setPrototype (JNIEnv *, jobject, jint, jint, jint); +/* + * Class: com_eclipsesource_v8_V8 + * Method: _enableDebugSupport + * Signature: (IIZ)Z + */ +JNIEXPORT jboolean JNICALL Java_com_eclipsesource_v8_V8__1enableDebugSupport + (JNIEnv *, jobject, jint, jint, jboolean); + +/* + * Class: com_eclipsesource_v8_V8 + * Method: _disableDebugSupport + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1disableDebugSupport + (JNIEnv *, jobject, jint); + +/* + * Class: com_eclipsesource_v8_V8 + * Method: _processDebugMessages + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1processDebugMessages + (JNIEnv *, jobject, jint); + #ifdef __cplusplus } #endif diff --git a/src/main/java/com/eclipsesource/v8/V8.java b/src/main/java/com/eclipsesource/v8/V8.java index eeaedd206..4c64f8668 100644 --- a/src/main/java/com/eclipsesource/v8/V8.java +++ b/src/main/java/com/eclipsesource/v8/V8.java @@ -2,17 +2,22 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class V8 extends V8Object { private static int v8InstanceCounter; - private int methodReferenceCounter = 0; + private static Thread thread = null; + private static List runtimes = new ArrayList<>(); + private static Runnable debugHandler = null; - private int v8RuntimeHandle; - static Thread thread = null; - long objectReferences = 0; + private int methodReferenceCounter = 0; + private int v8RuntimeHandle; + private boolean debugEnabled = false; + long objectReferences = 0; class MethodDescriptor { Object object; @@ -29,15 +34,51 @@ public synchronized static V8 createV8Runtime() { if (thread == null) { thread = Thread.currentThread(); } - return new V8(); + V8 runtime = new V8(); + runtimes.add(runtime); + return runtime; } - private V8() { + protected V8() { checkThread(); v8RuntimeHandle = v8InstanceCounter++; _createIsolate(v8RuntimeHandle); } + public boolean enableDebugSupport(final int port, final boolean waitForConnection) { + checkThread(); + debugEnabled = _enableDebugSupport(getHandle(), port, waitForConnection); + return debugEnabled; + } + + public boolean enableDebugSupport(final int port) { + checkThread(); + debugEnabled = true; + debugEnabled = _enableDebugSupport(getV8RuntimeHandle(), port, false); + return debugEnabled; + } + + public void disableDebugSupport() { + checkThread(); + _disableDebugSupport(getV8RuntimeHandle()); + debugEnabled = false; + } + + public static void processDebugMessages() { + checkThread(); + for (V8 v8 : runtimes) { + v8._processDebugMessages(v8.getV8RuntimeHandle()); + } + } + + public static int getActiveRuntimes() { + return runtimes.size(); + } + + public static void registerDebugHandler(final Runnable handler) { + debugHandler = handler; + } + public int getV8RuntimeHandle() { return v8RuntimeHandle; } @@ -45,12 +86,17 @@ public int getV8RuntimeHandle() { @Override public void release() { checkThread(); + if (debugEnabled) { + disableDebugSupport(); + } + runtimes.remove(this); + _releaseRuntime(v8RuntimeHandle); if (objectReferences > 0) { throw new IllegalStateException(objectReferences + " Object(s) still exist in runtime"); } - _releaseRuntime(v8RuntimeHandle); } + public int executeIntScript(final String script) throws V8RuntimeException { checkThread(); return _executeIntScript(v8RuntimeHandle, script); @@ -101,7 +147,7 @@ public void executeVoidScript(final String script) throws V8RuntimeException { } static void checkThread() { - if (thread != Thread.currentThread()) { + if ((thread != null) && (thread != Thread.currentThread())) { throw new Error("Invalid V8 thread access."); } } @@ -163,6 +209,12 @@ private Object getArrayItem(final V8Array array, final int index) { return null; } + protected static void debugMessageReceived() { + if (debugHandler != null) { + debugHandler.run(); + } + } + protected native void _initExistingV8Object(int v8RuntimeHandle, int parentHandle, String objectKey, int objectHandle); @@ -275,6 +327,12 @@ protected native void _arrayGetObject(final int v8RuntimeHandle, final int array protected native void _setPrototype(int v8RuntimeHandle, int objectHandle, int prototypeHandle); + protected native boolean _enableDebugSupport(int v8RuntimeHandle, int port, boolean waitForConnection); + + protected native void _disableDebugSupport(int v8RuntimeHandle); + + protected native void _processDebugMessages(int v8RuntimeHandle); + void addObjRef() { objectReferences++; } diff --git a/src/test/java/com/eclipsesource/v8/tests/V8ArrayTest.java b/src/test/java/com/eclipsesource/v8/tests/V8ArrayTest.java index 140e089ad..c93cfd5e5 100644 --- a/src/test/java/com/eclipsesource/v8/tests/V8ArrayTest.java +++ b/src/test/java/com/eclipsesource/v8/tests/V8ArrayTest.java @@ -26,6 +26,9 @@ public void seutp() { public void tearDown() { try { v8.release(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released."); + } } catch (IllegalStateException e) { System.out.println(e.getMessage()); } diff --git a/src/test/java/com/eclipsesource/v8/tests/V8CallbackTest.java b/src/test/java/com/eclipsesource/v8/tests/V8CallbackTest.java index 5bb10c810..e2977f306 100644 --- a/src/test/java/com/eclipsesource/v8/tests/V8CallbackTest.java +++ b/src/test/java/com/eclipsesource/v8/tests/V8CallbackTest.java @@ -31,6 +31,9 @@ public void seutp() { public void tearDown() { try { v8.release(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released."); + } } catch (IllegalStateException e) { System.out.println(e.getMessage()); } diff --git a/src/test/java/com/eclipsesource/v8/tests/V8JSFunctionCallTest.java b/src/test/java/com/eclipsesource/v8/tests/V8JSFunctionCallTest.java index a7e40c107..09aec95a0 100644 --- a/src/test/java/com/eclipsesource/v8/tests/V8JSFunctionCallTest.java +++ b/src/test/java/com/eclipsesource/v8/tests/V8JSFunctionCallTest.java @@ -25,6 +25,9 @@ public void seutp() { public void tearDown() { try { v8.release(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released."); + } } catch (IllegalStateException e) { System.out.println(e.getMessage()); } diff --git a/src/test/java/com/eclipsesource/v8/tests/V8ObjectTest.java b/src/test/java/com/eclipsesource/v8/tests/V8ObjectTest.java index 92abe7d28..908e36e10 100644 --- a/src/test/java/com/eclipsesource/v8/tests/V8ObjectTest.java +++ b/src/test/java/com/eclipsesource/v8/tests/V8ObjectTest.java @@ -28,6 +28,9 @@ public void seutp() { public void tearDown() { try { v8.release(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released."); + } } catch (IllegalStateException e) { System.out.println(e.getMessage()); } diff --git a/src/test/java/com/eclipsesource/v8/tests/V8Test.java b/src/test/java/com/eclipsesource/v8/tests/V8Test.java index e1ff77b4b..2728c7fed 100644 --- a/src/test/java/com/eclipsesource/v8/tests/V8Test.java +++ b/src/test/java/com/eclipsesource/v8/tests/V8Test.java @@ -1,5 +1,8 @@ package com.eclipsesource.v8.tests; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; import java.util.Arrays; import java.util.List; @@ -17,6 +20,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class V8Test { @@ -31,6 +36,9 @@ public void seutp() { public void tearDown() { try { v8.release(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released."); + } } catch (IllegalStateException e) { System.out.println(e.getMessage()); } @@ -692,6 +700,7 @@ public void testGetBooleanDoesNotExist() { v8.getBoolean("x"); } + @Test public void testAddGet() { v8.add("string", "string"); @@ -889,4 +898,73 @@ public void testWindowAliasForGlobalScope() { assertTrue(v8.executeBooleanScript("window.hasOwnProperty( \"Object\" )")); } + /*** Debug Tests ***/ + @Test + public void testSetupDebugHandler() { + int port = 9991; + + v8.enableDebugSupport(port); + + assertTrue(debugEnabled(port)); + } + + @Test + public void testRemoveDebugHandler() { + int port = 9991; + v8.enableDebugSupport(port); + + v8.disableDebugSupport(); + + assertFalse(debugEnabled(port)); + } + + @Test + public void testMultipleDebugHandlers() { + V8 v8_2 = V8.createV8Runtime(); + + v8.enableDebugSupport(9991); + v8_2.enableDebugSupport(9992); + + assertTrue(debugEnabled(9991)); + assertTrue(debugEnabled(9992)); + v8_2.disableDebugSupport(); + v8_2.release(); + assertTrue(debugEnabled(9991)); + } + + static class SubV8 extends V8 { + public static void debugMessageReceived() { + V8.debugMessageReceived(); + } + } + + @Test + public void testHandlerCalled() { + Runnable runnable = mock(Runnable.class); + V8.registerDebugHandler(runnable); + + SubV8.debugMessageReceived(); + + verify(runnable).run(); + V8.registerDebugHandler(null); + } + + private boolean debugEnabled(final int port) { + Socket socket = new Socket(); + InetSocketAddress endPoint = new InetSocketAddress("localhost", port); + try { + socket.connect(endPoint); + return true; + } catch (IOException e) { + return false; + } finally { + try { + socket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + } + } \ No newline at end of file