From 65597c3b57ee6b9775fb3890f15eaf927766c5db Mon Sep 17 00:00:00 2001 From: vmutafov Date: Thu, 31 Jan 2019 17:56:19 +0200 Subject: [PATCH] Add releaseNativeCounterpart JS function for releasing the native part of a Java proxy object in JS --- test-app/app/src/main/assets/app/mainpage.js | 3 +- .../app/tests/testReleaseNativeCounterpart.js | 81 ++++++ test-app/runtime/build.gradle | 9 +- .../runtime/src/main/cpp/CallbackHandlers.cpp | 45 ++- .../runtime/src/main/cpp/CallbackHandlers.h | 6 + .../runtime/src/main/cpp/ObjectManager.cpp | 267 +++++++++++------- test-app/runtime/src/main/cpp/ObjectManager.h | 8 +- test-app/runtime/src/main/cpp/Runtime.cpp | 1 + .../src/main/java/com/tns/Runtime.java | 69 ++++- .../src/test/java/com/tns/RuntimeTest.java | 90 ++++++ 10 files changed, 457 insertions(+), 122 deletions(-) create mode 100644 test-app/app/src/main/assets/app/tests/testReleaseNativeCounterpart.js create mode 100644 test-app/runtime/src/test/java/com/tns/RuntimeTest.java diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 1af0835a1..ae814e00e 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -52,4 +52,5 @@ require("./tests/byte-buffer-test"); require("./tests/dex-interface-implementation"); require("./tests/testInterfaceImplementation"); require("./tests/testRuntimeImplementedAPIs"); -require("./tests/testsInstanceOfOperator"); \ No newline at end of file +require("./tests/testsInstanceOfOperator"); +require("./tests/testReleaseNativeCounterpart"); \ No newline at end of file diff --git a/test-app/app/src/main/assets/app/tests/testReleaseNativeCounterpart.js b/test-app/app/src/main/assets/app/tests/testReleaseNativeCounterpart.js new file mode 100644 index 000000000..4bd78ce65 --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testReleaseNativeCounterpart.js @@ -0,0 +1,81 @@ +describe("Test native counterpart release", function () { + + var myCustomEquality = function(first, second) { + return first == second; + }; + + beforeEach(function() { + jasmine.addCustomEqualityTester(myCustomEquality); + }); + + it("Calling a method on a released object should throw exception", function () { + + var errorMessage = ""; + + try{ + var object1 = new java.lang.Object(); + + global.__releaseNativeCounterpart(object1); + + object1.toString(); + } catch(e){ + errorMessage = e.message; + } + + expect(errorMessage).toBe("Failed calling toString on a java/lang/Object instance. The JavaScript instance no longer has available Java instance counterpart."); + }); + + it("Calling release on a non native object should throw exception", function () { + + var errorMessage = ""; + + try{ + var object2 = {prop: "test"}; + global.__releaseNativeCounterpart(object2); + } catch(e){ + errorMessage = e.message; + } + + expect(errorMessage).toBe("Trying to release a non native object!"); + }); + + + it("Calling release on a non native primitive type should throw exception", function () { + + var errorMessage = ""; + + try{ + global.__releaseNativeCounterpart(42); + } catch(e){ + errorMessage = e.message; + } + + expect(errorMessage).toBe("Argument is not an object!"); + }); + + it("Calling the __releaseNativeCounterpart function with 0 arguments should throw exception", function(){ + var errorMessage = ""; + + try{ + global.__releaseNativeCounterpart(); + } catch(e){ + errorMessage = e.message; + } + + expect(errorMessage).toBe("Unexpected arguments count!"); + }); + + it("Calling the __releaseNativeCounterpart function with more than 1 arguments should throw exception", function(){ + var errorMessage = ""; + + try{ + global.__releaseNativeCounterpart({},{}); + } catch(e){ + errorMessage = e.message; + } + + expect(errorMessage).toBe("Unexpected arguments count!"); + }); + + +}); \ No newline at end of file diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index 6c2c4bcdd..80336ce4c 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -61,7 +61,7 @@ android { arguments.push("-DOPTIMIZED_WITH_INSPECTOR_BUILD=true") } - if(useCCache) { + if (useCCache) { arguments.push("-DUSE_CCACHE=true") } @@ -104,6 +104,8 @@ allprojects { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.10.19' } tasks.whenTaskAdded { task -> @@ -112,6 +114,11 @@ tasks.whenTaskAdded { task -> setRuntimeCommit.dependsOn(setPackageVersion) task.dependsOn(setRuntimeCommit) } + + if(taskName.contains("bundleReleaseAar")){ + task.dependsOn("testDebugUnitTest") + } + if (taskName.contains("Strip")) { task.finalizedBy(revertVersionFile) } diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp index dcd0c0ff4..318ab6590 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp @@ -573,7 +573,9 @@ CallbackHandlers::GetImplementedInterfaces(JEnv &env, const Local &imple auto interfacesArr = prop->ToObject(isolate); auto context = isolate->GetCurrentContext(); - int length = interfacesArr->Get(v8::String::NewFromUtf8(isolate, "length"))->ToObject(isolate)->Uint32Value(context).ToChecked(); + int length = interfacesArr->Get( + v8::String::NewFromUtf8(isolate, "length"))->ToObject(isolate)->Uint32Value( + context).ToChecked(); if (length > 0) { for (int i = 0; i < length; i++) { @@ -670,6 +672,38 @@ void CallbackHandlers::TimeCallback(const v8::FunctionCallbackInfo &a args.GetReturnValue().Set(duration); } +void CallbackHandlers::ReleaseNativeCounterpartCallback( + const v8::FunctionCallbackInfo &info) { + try { + SET_PROFILER_FRAME(); + + validateProvidedArgumentsLength(info, 1); + + auto isolate = info.GetIsolate(); + Handle obj = info[0].As(); + + auto runtime = Runtime::GetRuntime(isolate); + auto objectManager = runtime->GetObjectManager(); + objectManager->ReleaseNativeCounterpart(obj); + } catch (NativeScriptException &e) { + e.ReThrowToV8(); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToV8(); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToV8(); + } +} + +void CallbackHandlers::validateProvidedArgumentsLength(const v8::FunctionCallbackInfo &args, int expectedSize) { + if(args.Length() != expectedSize){ + throw NativeScriptException("Unexpected arguments count!"); + } +} + void CallbackHandlers::DumpReferenceTablesMethodCallback( const v8::FunctionCallbackInfo &args) { DumpReferenceTablesMethod(); @@ -887,8 +921,11 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfoGetIsolate(); - auto currentExecutingScriptName = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName)->GetFrame(isolate, 0)->GetScriptName(); - auto currentExecutingScriptNameStr = ArgConverter::ConvertToString(currentExecutingScriptName); + auto currentExecutingScriptName = StackTrace::CurrentStackTrace(isolate, 1, + StackTrace::kScriptName)->GetFrame( + isolate, 0)->GetScriptName(); + auto currentExecutingScriptNameStr = ArgConverter::ConvertToString( + currentExecutingScriptName); auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); auto currentDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); string fileSchema("file://"); @@ -1444,6 +1481,8 @@ jmethodID CallbackHandlers::ENABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; jmethodID CallbackHandlers::DISABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; jmethodID CallbackHandlers::INIT_WORKER_METHOD_ID = nullptr; + + NumericCasts CallbackHandlers::castFunctions; ArrayElementAccessor CallbackHandlers::arrayElementAccessor; FieldAccessor CallbackHandlers::fieldAccessor; diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.h b/test-app/runtime/src/main/cpp/CallbackHandlers.h index 72fb2567b..ebf8ca9e1 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.h +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.h @@ -111,6 +111,8 @@ namespace tns { static void DisableVerboseLoggingMethodCallback(const v8::FunctionCallbackInfo &args); + static void ReleaseNativeCounterpartCallback(const v8::FunctionCallbackInfo &info); + static v8::Local FindClass(v8::Isolate *isolate, const std::string &className); static void NewThreadCallback(const v8::FunctionCallbackInfo &args); @@ -194,6 +196,8 @@ namespace tns { */ static jobjectArray GetJavaStringArray(JEnv &env, int length); + static void validateProvidedArgumentsLength(const v8::FunctionCallbackInfo &args, int expectedSize); + static short MAX_JAVA_STRING_ARRAY_LENGTH; static jclass RUNTIME_CLASS; @@ -235,6 +239,8 @@ namespace tns { jfieldID _fieldID; jobject _runtime; }; + + }; } diff --git a/test-app/runtime/src/main/cpp/ObjectManager.cpp b/test-app/runtime/src/main/cpp/ObjectManager.cpp index 5a6bbe99c..1feb802d3 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.cpp +++ b/test-app/runtime/src/main/cpp/ObjectManager.cpp @@ -18,28 +18,40 @@ using namespace std; using namespace tns; ObjectManager::ObjectManager(jobject javaRuntimeObject) : - m_javaRuntimeObject(javaRuntimeObject), - m_env(JEnv()), - m_numberOfGC(0), - m_currentObjectId(0), - m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) { + m_javaRuntimeObject(javaRuntimeObject), + m_env(JEnv()), + m_numberOfGC(0), + m_currentObjectId(0), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) { auto runtimeClass = m_env.FindClass("com/tns/Runtime"); assert(runtimeClass != nullptr); - GET_JAVAOBJECT_BY_ID_METHOD_ID = m_env.GetMethodID(runtimeClass, "getJavaObjectByID", "(I)Ljava/lang/Object;"); + GET_JAVAOBJECT_BY_ID_METHOD_ID = m_env.GetMethodID(runtimeClass, "getJavaObjectByID", + "(I)Ljava/lang/Object;"); assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); - GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = m_env.GetMethodID(runtimeClass, "getOrCreateJavaObjectID", "(Ljava/lang/Object;)I"); + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = m_env.GetMethodID(runtimeClass, + "getOrCreateJavaObjectID", + "(Ljava/lang/Object;)I"); assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); - MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V"); + MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(Ljava/nio/ByteBuffer;IZ)V"); assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); - MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z"); + MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, + "makeInstanceWeakAndCheckIfAlive", + "(I)Z"); assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr); - CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); + RELEASE_NATIVE_INSTANCE_METHOD_ID = m_env.GetMethodID(runtimeClass, "releaseNativeCounterpart", + "(I)V"); + assert(RELEASE_NATIVE_INSTANCE_METHOD_ID != nullptr); + + CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, + "checkWeakObjectAreAlive", + "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr); JAVA_LANG_CLASS = m_env.FindClass("java/lang/Class"); @@ -54,18 +66,20 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject) : auto useGlobalRefs = m_env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID); m_useGlobalRefs = useGlobalRefs == JNI_TRUE; - auto getMarkingModeOrdinalMethodID = m_env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", "()I"); + auto getMarkingModeOrdinalMethodID = m_env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", + "()I"); jint markingMode = m_env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID); m_markingMode = static_cast(markingMode); } -void ObjectManager::SetInstanceIsolate(Isolate* isolate) { +void ObjectManager::SetInstanceIsolate(Isolate *isolate) { m_isolate = isolate; } -void ObjectManager::Init(Isolate* isolate) { +void ObjectManager::Init(Isolate *isolate) { auto jsWrapperFuncTemplate = FunctionTemplate::New(isolate, JSWrapperConstructorCallback); - jsWrapperFuncTemplate->InstanceTemplate()->SetInternalFieldCount(static_cast(MetadataNodeKeys::END)); + jsWrapperFuncTemplate->InstanceTemplate()->SetInternalFieldCount( + static_cast(MetadataNodeKeys::END)); auto jsWrapperFunc = jsWrapperFuncTemplate->GetFunction(); m_poJsWrapperFunc = new Persistent(isolate, jsWrapperFunc); @@ -76,15 +90,16 @@ void ObjectManager::Init(Isolate* isolate) { } -JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local& object) { - JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(object); +JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local &object) { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); if (jsInstanceInfo != nullptr) { if (m_useGlobalRefs) { JniLocalRef javaObject(GetJavaObjectByID(jsInstanceInfo->JavaObjectID), true); return javaObject; } else { - JniLocalRef javaObject(m_env.NewLocalRef(GetJavaObjectByID(jsInstanceInfo->JavaObjectID))); + JniLocalRef javaObject( + m_env.NewLocalRef(GetJavaObjectByID(jsInstanceInfo->JavaObjectID))); return javaObject; } } @@ -92,15 +107,16 @@ JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local& object) return JniLocalRef(); } -ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfo(const Local& object) { - JSInstanceInfo* jsInstanceInfo = nullptr; +ObjectManager::JSInstanceInfo *ObjectManager::GetJSInstanceInfo(const Local &object) { + JSInstanceInfo *jsInstanceInfo = nullptr; if (IsJsRuntimeObject(object)) { return GetJSInstanceInfoFromRuntimeObject(object); } return nullptr; } -ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local& object) { +ObjectManager::JSInstanceInfo * +ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local &object) { HandleScope handleScope(m_isolate); const int jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); @@ -110,7 +126,8 @@ ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfoFromRuntimeObject auto prototypeObject = object->GetPrototype().As(); if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) { - DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash()); + DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", + prototypeObject->GetIdentityHash()); if (IsJsRuntimeObject(prototypeObject)) { jsInfo = prototypeObject->GetInternalField(jsInfoIdx); } @@ -119,13 +136,13 @@ ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfoFromRuntimeObject if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) { auto external = jsInfo.As(); - return static_cast(external->Value()); + return static_cast(external->Value()); } return nullptr; } -bool ObjectManager::IsJsRuntimeObject(const v8::Local& object) { +bool ObjectManager::IsJsRuntimeObject(const v8::Local &object) { int internalFieldCount = object->InternalFieldCount(); const int count = static_cast(MetadataNodeKeys::END); return internalFieldCount == count; @@ -138,7 +155,8 @@ jweak ObjectManager::GetJavaObjectByID(uint32_t javaObjectID) { } jobject ObjectManager::GetJavaObjectByIDImpl(uint32_t javaObjectID) { - jobject object = m_env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, javaObjectID); + jobject object = m_env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, + javaObjectID); return object; } @@ -146,22 +164,23 @@ void ObjectManager::UpdateCache(int objectID, jobject obj) { m_cache.update(objectID, obj); } -jclass ObjectManager::GetJavaClass(const Local& instance) { +jclass ObjectManager::GetJavaClass(const Local &instance) { DEBUG_WRITE("GetClass called"); - JSInstanceInfo* jsInfo = GetJSInstanceInfo(instance); + JSInstanceInfo *jsInfo = GetJSInstanceInfo(instance); jclass clazz = jsInfo->ObjectClazz; return clazz; } -void ObjectManager::SetJavaClass(const Local& instance, jclass clazz) { - JSInstanceInfo* jsInfo = GetJSInstanceInfo(instance); +void ObjectManager::SetJavaClass(const Local &instance, jclass clazz) { + JSInstanceInfo *jsInfo = GetJSInstanceInfo(instance); jsInfo->ObjectClazz = clazz; } int ObjectManager::GetOrCreateObjectId(jobject object) { - jint javaObjectID = m_env.CallIntMethod(m_javaRuntimeObject, GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); + jint javaObjectID = m_env.CallIntMethod(m_javaRuntimeObject, + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); return javaObjectID; } @@ -175,24 +194,26 @@ Local ObjectManager::GetJsObjectByJavaObject(int javaObjectID) { return handleScope.Escape(Local()); } - Persistent* jsObject = it->second; + Persistent *jsObject = it->second; auto localObject = Local::New(isolate, *jsObject); return handleScope.Escape(localObject); } -Local ObjectManager::CreateJSWrapper(jint javaObjectID, const string& typeName) { +Local ObjectManager::CreateJSWrapper(jint javaObjectID, const string &typeName) { return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); } -Local ObjectManager::CreateJSWrapper(jint javaObjectID, const string& typeName, jobject instance) { +Local +ObjectManager::CreateJSWrapper(jint javaObjectID, const string &typeName, jobject instance) { JniLocalRef clazz(m_env.GetObjectClass(instance)); return CreateJSWrapperHelper(javaObjectID, typeName, clazz); } -Local ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const string& typeName, jclass clazz) { +Local +ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const string &typeName, jclass clazz) { auto isolate = m_isolate; auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; @@ -212,7 +233,7 @@ Local ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const stri /* * * Link the JavaScript object and it's java counterpart with an ID */ -void ObjectManager::Link(const Local& object, uint32_t javaObjectID, jclass clazz) { +void ObjectManager::Link(const Local &object, uint32_t javaObjectID, jclass clazz) { if (!IsJsRuntimeObject(object)) { string errMsg("Trying to link invalid 'this' to a Java object"); throw NativeScriptException(errMsg); @@ -220,7 +241,8 @@ void ObjectManager::Link(const Local& object, uint32_t javaObjectID, jcl auto isolate = m_isolate; - DEBUG_WRITE("Linking js object: %d and java instance id: %d", object->GetIdentityHash(), javaObjectID); + DEBUG_WRITE("Linking js object: %d and java instance id: %d", object->GetIdentityHash(), + javaObjectID); auto jsInstanceInfo = new JSInstanceInfo(false/*isJavaObjWeak*/, javaObjectID, clazz); @@ -244,7 +266,7 @@ void ObjectManager::Link(const Local& object, uint32_t javaObjectID, jcl m_idToObject.insert(make_pair(javaObjectID, objectHandle)); } -bool ObjectManager::CloneLink(const Local& src, const Local& dest) { +bool ObjectManager::CloneLink(const Local &src, const Local &dest) { auto jsInfo = GetJSInstanceInfo(src); auto success = jsInfo != nullptr; @@ -274,29 +296,30 @@ string ObjectManager::GetClassName(jclass clazz) { return className; } -void ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfo& data) { - ObjectWeakCallbackState* callbackState = data.GetParameter(); +void +ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfo &data) { + ObjectWeakCallbackState *callbackState = data.GetParameter(); - ObjectManager* thisPtr = callbackState->thisPtr; + ObjectManager *thisPtr = callbackState->thisPtr; auto isolate = data.GetIsolate(); thisPtr->JSObjectWeakCallback(isolate, callbackState); } -void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo& data) { - ObjectWeakCallbackState* callbackState = data.GetParameter(); +void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo &data) { + ObjectWeakCallbackState *callbackState = data.GetParameter(); - ObjectManager* thisPtr = callbackState->thisPtr; + ObjectManager *thisPtr = callbackState->thisPtr; auto isolate = data.GetIsolate(); thisPtr->JSObjectFinalizer(isolate, callbackState); } -void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* callbackState) { +void ObjectManager::JSObjectFinalizer(Isolate *isolate, ObjectWeakCallbackState *callbackState) { HandleScope handleScope(m_isolate); - Persistent* po = callbackState->target; + Persistent *po = callbackState->target; auto jsInstanceInfo = GetJSInstanceInfoFromRuntimeObject(po->Get(m_isolate)); if (jsInstanceInfo == nullptr) { @@ -305,7 +328,9 @@ void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* } auto javaObjectID = jsInstanceInfo->JavaObjectID; - jboolean isJavaInstanceAlive = m_env.CallBooleanMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, javaObjectID); + jboolean isJavaInstanceAlive = m_env.CallBooleanMethod(m_javaRuntimeObject, + MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, + javaObjectID); if (isJavaInstanceAlive) { // If the Java instance is alive, keep the JavaScript instance alive. po->SetWeak(callbackState, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer); @@ -326,10 +351,10 @@ void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* * -ones called for the second or next time which are already weak in java too * These objects are categorized by "regular" and "callback" and saved in different arrays for performance optimizations during GC * */ -void ObjectManager::JSObjectWeakCallback(Isolate* isolate, ObjectWeakCallbackState* callbackState) { +void ObjectManager::JSObjectWeakCallback(Isolate *isolate, ObjectWeakCallbackState *callbackState) { HandleScope handleScope(isolate); - Persistent* po = callbackState->target; + Persistent *po = callbackState->target; auto itFound = m_visitedPOs.find(po); @@ -337,24 +362,27 @@ void ObjectManager::JSObjectWeakCallback(Isolate* isolate, ObjectWeakCallbackSta m_visitedPOs.insert(po); auto obj = Local::New(isolate, *po); - JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); - int javaObjectID = jsInstanceInfo->JavaObjectID; + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(obj); + + if(jsInstanceInfo != nullptr){ + int javaObjectID = jsInstanceInfo->JavaObjectID; - bool hasImplObj = HasImplObject(isolate, obj); + bool hasImplObj = HasImplObject(isolate, obj); - DEBUG_WRITE("JSObjectWeakCallback objectId: %d, hasImplObj=%d", javaObjectID, hasImplObj); + DEBUG_WRITE("JSObjectWeakCallback objectId: %d, hasImplObj=%d", javaObjectID, hasImplObj); - if (hasImplObj) { - if (jsInstanceInfo->IsJavaObjectWeak) { - m_implObjWeak.push_back(PersistentObjectIdPair(po, javaObjectID)); + if (hasImplObj) { + if (jsInstanceInfo->IsJavaObjectWeak) { + m_implObjWeak.push_back(PersistentObjectIdPair(po, javaObjectID)); + } else { + m_implObjStrong.insert(make_pair(javaObjectID, po)); + jsInstanceInfo->IsJavaObjectWeak = true; + } } else { - m_implObjStrong.insert(make_pair(javaObjectID, po)); - jsInstanceInfo->IsJavaObjectWeak = true; + assert(!m_markedForGC.empty()); + auto &topGCInfo = m_markedForGC.top(); + topGCInfo.markedForGC.push_back(po); } - } else { - assert(!m_markedForGC.empty()); - auto& topGCInfo = m_markedForGC.top(); - topGCInfo.markedForGC.push_back(po); } } @@ -368,7 +396,7 @@ int ObjectManager::GenerateNewObjectID() { return oldValue; } -void ObjectManager::ReleaseJSInstance(Persistent* po, JSInstanceInfo* jsInstanceInfo) { +void ObjectManager::ReleaseJSInstance(Persistent *po, JSInstanceInfo *jsInstanceInfo) { int javaObjectID = jsInstanceInfo->JavaObjectID; auto it = m_idToObject.find(javaObjectID); @@ -399,10 +427,11 @@ void ObjectManager::ReleaseRegularObjects() { HandleScope handleScope(m_isolate); - auto propName = String::NewFromUtf8(m_isolate, "t::gcNum", NewStringType::kNormal).ToLocalChecked(); + auto propName = String::NewFromUtf8(m_isolate, "t::gcNum", + NewStringType::kNormal).ToLocalChecked(); - auto& topGCInfo = m_markedForGC.top(); - auto& marked = topGCInfo.markedForGC; + auto &topGCInfo = m_markedForGC.top(); + auto &marked = topGCInfo.markedForGC; int numberOfGC = topGCInfo.numberOfGC; for (auto po : marked) { @@ -427,7 +456,7 @@ void ObjectManager::ReleaseRegularObjects() { isReachableFromImplementationObject = objGcNum >= numberOfGC; } - JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(obj); if (!isReachableFromImplementationObject) { if (!jsInstanceInfo->IsJavaObjectWeak) { @@ -441,7 +470,7 @@ void ObjectManager::ReleaseRegularObjects() { marked.clear(); } -bool ObjectManager::HasImplObject(Isolate* isolate, const Local& obj) { +bool ObjectManager::HasImplObject(Isolate *isolate, const Local &obj) { auto implObject = MetadataNode::GetImplementationObject(isolate, obj); bool hasImplObj = !implObject.IsEmpty(); @@ -453,7 +482,7 @@ bool ObjectManager::HasImplObject(Isolate* isolate, const Local& obj) { * When "MarkReachableObjects" is called V8 has marked all JS objects that can be released. * This method builds on top of V8s marking phase, because we need to consider the JAVA counterpart objects (is it "regular" or "callback"), when marking JS ones. * */ -void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& obj) { +void ObjectManager::MarkReachableObjects(Isolate *isolate, const Local &obj) { tns::instrumentation::Frame frame; stack> s; @@ -463,7 +492,7 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& auto propName = String::NewFromUtf8(isolate, "t::gcNum"); assert(!m_markedForGC.empty()); - auto& topGCInfo = m_markedForGC.top(); + auto &topGCInfo = m_markedForGC.top(); int numberOfGC = topGCInfo.numberOfGC; auto fromJsInfo = GetJSInstanceInfo(obj); auto fromId = fromJsInfo->JavaObjectID; @@ -489,7 +518,8 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& if (itFound != m_visited.end()) { continue; } - m_visited.insert(addr); // set as processed only if the current object is not the object we are starting from + m_visited.insert( + addr); // set as processed only if the current object is not the object we are starting from } auto jsInfo = GetJSInstanceInfo(o); @@ -509,9 +539,10 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& auto func = o.As(); int closureObjectLength; - auto closureObjects = NativeScriptExtension::GetClosureObjects(isolate, func, &closureObjectLength); + auto closureObjects = NativeScriptExtension::GetClosureObjects(isolate, func, + &closureObjectLength); for (int i = 0; i < closureObjectLength; i++) { - auto& curV = *(closureObjects + i); + auto &curV = *(closureObjects + i); if (!curV.IsEmpty() && curV->IsObject()) { s.push(curV); } @@ -546,9 +577,10 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& if (!getter.IsEmpty() && getter->IsFunction()) { int getterClosureObjectLength = 0; - auto getterClosureObjects = NativeScriptExtension::GetClosureObjects(isolate, getter.As(), &getterClosureObjectLength); + auto getterClosureObjects = NativeScriptExtension::GetClosureObjects( + isolate, getter.As(), &getterClosureObjectLength); for (int i = 0; i < getterClosureObjectLength; i++) { - auto& curV = *(getterClosureObjects + i); + auto &curV = *(getterClosureObjects + i); if (!curV.IsEmpty() && curV->IsObject()) { s.push(curV); } @@ -558,9 +590,10 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& if (!setter.IsEmpty() && setter->IsFunction()) { int setterClosureObjectLength = 0; - auto setterClosureObjects = NativeScriptExtension::GetClosureObjects(isolate, setter.As(), &setterClosureObjectLength); + auto setterClosureObjects = NativeScriptExtension::GetClosureObjects( + isolate, setter.As(), &setterClosureObjectLength); for (int i = 0; i < setterClosureObjectLength; i++) { - auto& curV = *(setterClosureObjects + i); + auto &curV = *(setterClosureObjects + i); if (!curV.IsEmpty() && curV->IsObject()) { s.push(curV); } @@ -587,7 +620,7 @@ void ObjectManager::MarkReachableObjects(Isolate* isolate, const Local& } } -void ObjectManager::MarkReachableArrayElements(Local& o, stack>& s) { +void ObjectManager::MarkReachableArrayElements(Local &o, stack> &s) { auto arr = o.As(); int arrEnclosedObjectsLength = arr->Length(); @@ -600,12 +633,12 @@ void ObjectManager::MarkReachableArrayElements(Local& o, stackGetObjectManager(); objectManager->OnGcStarted(type, flags); - } catch (NativeScriptException& e) { + } catch (NativeScriptException &e) { e.ReThrowToV8(); } catch (std::exception e) { stringstream ss; @@ -618,12 +651,12 @@ void ObjectManager::OnGcStartedStatic(Isolate* isolate, GCType type, GCCallbackF } } -void ObjectManager::OnGcFinishedStatic(Isolate* isolate, GCType type, GCCallbackFlags flags) { +void ObjectManager::OnGcFinishedStatic(Isolate *isolate, GCType type, GCCallbackFlags flags) { try { auto runtime = Runtime::GetRuntime(isolate); auto objectManager = runtime->GetObjectManager(); objectManager->OnGcFinished(type, flags); - } catch (NativeScriptException& e) { + } catch (NativeScriptException &e) { e.ReThrowToV8(); } catch (std::exception e) { stringstream ss; @@ -655,8 +688,8 @@ void ObjectManager::OnGcFinished(GCType type, GCCallbackFlags flags) { auto obj = Local::New(isolate, *weakObj.po); MarkReachableObjects(isolate, obj); } - for (const auto& kv : m_implObjStrong) { - Persistent* po = kv.second; + for (const auto &kv : m_implObjStrong) { + Persistent *po = kv.second; if (po != nullptr) { auto obj = Local::New(isolate, *po); MarkReachableObjects(isolate, obj); @@ -688,7 +721,7 @@ void ObjectManager::OnGcFinished(GCType type, GCCallbackFlags flags) { * We have all the JS "regular" objects that JS has made weak and ready to by GC'd, * so we tell java to take the JAVA objects out of strong reference so they can be collected by JAVA GC * */ -void ObjectManager::MakeRegularObjectsWeak(const set& instances, DirectBuffer& inputBuff) { +void ObjectManager::MakeRegularObjectsWeak(const set &instances, DirectBuffer &inputBuff) { jboolean keepAsWeak = JNI_FALSE; for (auto javaObjectId : instances) { @@ -696,7 +729,8 @@ void ObjectManager::MakeRegularObjectsWeak(const set& instances, DirectBuff if (!success) { int length = inputBuff.Length(); - m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, (jobject) inputBuff, length, keepAsWeak); + m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, + (jobject) inputBuff, length, keepAsWeak); inputBuff.Reset(); success = inputBuff.Write(javaObjectId); assert(success); @@ -704,7 +738,8 @@ void ObjectManager::MakeRegularObjectsWeak(const set& instances, DirectBuff } int size = inputBuff.Size(); if (size > 0) { - m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, (jobject) inputBuff, size, keepAsWeak); + m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, + (jobject) inputBuff, size, keepAsWeak); } inputBuff.Reset(); @@ -715,10 +750,11 @@ void ObjectManager::MakeRegularObjectsWeak(const set& instances, DirectBuff * so we tell java to take the JAVA objects out of strong, BUT KEEP THEM AS WEEK REFERENCES, * so that if java needs to release them, it can, on a later stage. * */ -void ObjectManager::MakeImplObjectsWeak(const map*>& instances, DirectBuffer& inputBuff) { +void ObjectManager::MakeImplObjectsWeak(const map *> &instances, + DirectBuffer &inputBuff) { jboolean keepAsWeak = JNI_TRUE; - for (const auto& kv : instances) { + for (const auto &kv : instances) { if (kv.second != nullptr) { int javaObjectId = kv.first; @@ -727,7 +763,8 @@ void ObjectManager::MakeImplObjectsWeak(const map*>& ins if (!success) { int length = inputBuff.Length(); jboolean keepAsWeak = JNI_TRUE; - m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, (jobject) inputBuff, length, keepAsWeak); + m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, + (jobject) inputBuff, length, keepAsWeak); inputBuff.Reset(); success = inputBuff.Write(javaObjectId); assert(success); @@ -737,7 +774,8 @@ void ObjectManager::MakeImplObjectsWeak(const map*>& ins int size = inputBuff.Size(); if (size > 0) { jboolean keepAsWeak = JNI_TRUE; - m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, (jobject) inputBuff, size, keepAsWeak); + m_env.CallVoidMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_BATCH_METHOD_ID, + (jobject) inputBuff, size, keepAsWeak); } inputBuff.Reset(); @@ -747,24 +785,26 @@ void ObjectManager::MakeImplObjectsWeak(const map*>& ins * Consult with JAVA world to check if a java object is still in kept as a strong or weak reference * If the JAVA objects are released, we can release the their counterpart JS objects * */ -void ObjectManager::CheckWeakObjectsAreAlive(const vector& instances, DirectBuffer& inputBuff, DirectBuffer& outputBuff) { +void ObjectManager::CheckWeakObjectsAreAlive(const vector &instances, + DirectBuffer &inputBuff, DirectBuffer &outputBuff) { TNSPERF(); - for (const auto& poIdPair : instances) { + for (const auto &poIdPair : instances) { int javaObjectId = poIdPair.javaObjectId; bool success = inputBuff.Write(javaObjectId); if (!success) { int length = inputBuff.Length(); - m_env.CallVoidMethod(m_javaRuntimeObject, CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID, (jobject) inputBuff, (jobject) outputBuff, length); + m_env.CallVoidMethod(m_javaRuntimeObject, CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID, + (jobject) inputBuff, (jobject) outputBuff, length); // - int* released = outputBuff.GetData(); + int *released = outputBuff.GetData(); for (int i = 0; i < length; i++) { bool isReleased = *released++ != 0; if (isReleased) { - Persistent* po = instances[i].po; + Persistent *po = instances[i].po; po->Reset(); } } @@ -776,21 +816,22 @@ void ObjectManager::CheckWeakObjectsAreAlive(const vector 0) { - m_env.CallVoidMethod(m_javaRuntimeObject, CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID, (jobject) inputBuff, (jobject) outputBuff, size); - int* released = outputBuff.GetData(); + m_env.CallVoidMethod(m_javaRuntimeObject, CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID, + (jobject) inputBuff, (jobject) outputBuff, size); + int *released = outputBuff.GetData(); for (int i = 0; i < size; i++) { bool isReleased = *released++ != 0; if (isReleased) { - Persistent* po = instances[i].po; + Persistent *po = instances[i].po; po->Reset(); } } } } -jweak ObjectManager::NewWeakGlobalRefCallback(const int& javaObjectID, void* state) { - auto objManager = reinterpret_cast(state); +jweak ObjectManager::NewWeakGlobalRefCallback(const int &javaObjectID, void *state) { + auto objManager = reinterpret_cast(state); JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); @@ -799,13 +840,13 @@ jweak ObjectManager::NewWeakGlobalRefCallback(const int& javaObjectID, void* sta return weakRef; } -void ObjectManager::DeleteWeakGlobalRefCallback(const jweak& object, void* state) { - auto objManager = reinterpret_cast(state); +void ObjectManager::DeleteWeakGlobalRefCallback(const jweak &object, void *state) { + auto objManager = reinterpret_cast(state); objManager->m_env.DeleteWeakGlobalRef(object); } -Local ObjectManager::GetEmptyObject(Isolate* isolate) { +Local ObjectManager::GetEmptyObject(Isolate *isolate) { auto emptyObjCtorFunc = Local::New(isolate, *m_poJsWrapperFunc); auto val = emptyObjCtorFunc->CallAsConstructor(isolate->GetCurrentContext(), 0, nullptr); if (val.IsEmpty()) { @@ -817,6 +858,26 @@ Local ObjectManager::GetEmptyObject(Isolate* isolate) { return obj; } -void ObjectManager::JSWrapperConstructorCallback(const v8::FunctionCallbackInfo& info) { +void ObjectManager::JSWrapperConstructorCallback(const v8::FunctionCallbackInfo &info) { assert(info.IsConstructCall()); } + +void ObjectManager::ReleaseNativeCounterpart(v8::Local &object) { + + if(!object->IsObject()){ + throw NativeScriptException("Argument is not an object!"); + } + + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + + if(jsInstanceInfo == nullptr){ + throw NativeScriptException("Trying to release a non native object!"); + } + + m_env.CallVoidMethod(m_javaRuntimeObject, RELEASE_NATIVE_INSTANCE_METHOD_ID, + jsInstanceInfo->JavaObjectID); + + delete jsInstanceInfo; + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + object->SetInternalField(jsInfoIdx, Undefined(m_isolate)); +} diff --git a/test-app/runtime/src/main/cpp/ObjectManager.h b/test-app/runtime/src/main/cpp/ObjectManager.h index b3f50c4c7..16a3e0768 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.h +++ b/test-app/runtime/src/main/cpp/ObjectManager.h @@ -37,8 +37,12 @@ class ObjectManager { void Link(const v8::Local& object, uint32_t javaObjectID, jclass clazz); + void ReleaseNativeCounterpart(v8::Local& object); + bool CloneLink(const v8::Local& src, const v8::Local& dest); + bool IsJsRuntimeObject(const v8::Local& object); + std::string GetClassName(jobject javaObject); std::string GetClassName(jclass clazz); @@ -139,7 +143,7 @@ class ObjectManager { int javaObjectId; }; - bool IsJsRuntimeObject(const v8::Local& object); + JSInstanceInfo* GetJSInstanceInfo(const v8::Local& object); @@ -231,6 +235,8 @@ class ObjectManager { jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID; + jmethodID RELEASE_NATIVE_INSTANCE_METHOD_ID; + jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID; v8::Persistent* m_poJsWrapperFunc; diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index 3fbad51f7..cbeab89c9 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -578,6 +578,7 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__exit"), FunctionTemplate::New(isolate, CallbackHandlers::ExitMethodCallback)); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__runtimeVersion"), ArgConverter::ConvertToV8String(isolate, NATIVE_SCRIPT_RUNTIME_VERSION), readOnlyFlags); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__time"), FunctionTemplate::New(isolate, CallbackHandlers::TimeCallback)); + globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__releaseNativeCounterpart"), FunctionTemplate::New(isolate, CallbackHandlers::ReleaseNativeCounterpartCallback)); /* * Attach `Worker` object constructor only to the main thread (isolate)'s global object diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index 51d778cbb..521255217 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -48,7 +48,9 @@ private native void initNativeScript(int runtimeId, String filesPath, String nat private native int generateNewObjectId(int runtimeId); private native boolean notifyGc(int runtimeId); + private native void lock(int runtimeId); + private native void unlock(int runtimeId); private native void passExceptionToJsNative(int runtimeId, Throwable ex, String stackTrace, boolean isDiscarded); @@ -98,13 +100,13 @@ public static void passSuppressedExceptionToJs(Throwable ex, String methodName) "Primitive types need to be manually wrapped in their respective Object wrappers.\n" + "If you are creating an instance of an inner class, make sure to always provide reference to the outer `this` as the first argument."; - private final HashMap strongInstances = new HashMap<>(); + private HashMap strongInstances = new HashMap<>(); - private final HashMap> weakInstances = new HashMap<>(); + private HashMap> weakInstances = new HashMap<>(); - private final NativeScriptHashMap strongJavaObjectToID = new NativeScriptHashMap(); + private NativeScriptHashMap strongJavaObjectToID = new NativeScriptHashMap(); - private final NativeScriptWeakHashMap weakJavaObjectToID = new NativeScriptWeakHashMap(); + private NativeScriptWeakHashMap weakJavaObjectToID = new NativeScriptWeakHashMap(); private final Map, JavaScriptImplementation> loadedJavaScriptExtends = new HashMap, JavaScriptImplementation>(); @@ -120,8 +122,14 @@ public static void passSuppressedExceptionToJs(Throwable ex, String methodName) private Logger logger; private boolean isLiveSyncStarted; - public boolean getIsLiveSyncStarted() { return this.isLiveSyncStarted; } - public void setIsLiveSyncStarted(boolean value) { this.isLiveSyncStarted = value; } + + public boolean getIsLiveSyncStarted() { + return this.isLiveSyncStarted; + } + + public void setIsLiveSyncStarted(boolean value) { + this.isLiveSyncStarted = value; + } public Logger getLogger() { return this.logger; @@ -171,6 +179,19 @@ public int compare(Method lhs, Method rhs) { */ private Map workerIdToHandler = new HashMap<>(); + public Runtime(ClassResolver classResolver, GcListener gcListener, StaticConfiguration config, DynamicConfiguration dynamicConfig, int runtimeId, int workerId, HashMap strongInstances, HashMap> weakInstances, NativeScriptHashMap strongJavaObjectToId, NativeScriptWeakHashMap weakJavaObjectToId) { + this.classResolver = classResolver; + this.gcListener = gcListener; + this.config = config; + this.dynamicConfig = dynamicConfig; + this.runtimeId = runtimeId; + this.workerId = workerId; + this.strongInstances = strongInstances; + this.weakInstances = weakInstances; + this.strongJavaObjectToID = strongJavaObjectToId; + this.weakJavaObjectToID = weakJavaObjectToId; + } + public Runtime(StaticConfiguration config, DynamicConfiguration dynamicConfiguration) { synchronized (Runtime.currentRuntime) { ManualInstrumentation.Frame frame = ManualInstrumentation.start("new Runtime"); @@ -256,7 +277,7 @@ public int getMarkingModeOrdinal() { if (staticConfiguration != null && staticConfiguration.appConfig != null) { return staticConfiguration.appConfig.getMarkingMode().ordinal(); } else { - return ((MarkingMode)AppConfig.KnownKeys.MarkingMode.getDefaultValue()).ordinal(); + return ((MarkingMode) AppConfig.KnownKeys.MarkingMode.getDefaultValue()).ordinal(); } } @@ -280,6 +301,20 @@ public void ResetDateTimeConfigurationCache() { } } + public void releaseNativeCounterpart(int nativeObjectId) { + Object strongRef = strongInstances.get(nativeObjectId); + if (strongRef != null) { + strongInstances.remove(nativeObjectId); + strongJavaObjectToID.remove(strongRef); + } + + WeakReference weakRef = weakInstances.get(nativeObjectId); + if (weakRef != null) { + weakInstances.remove(nativeObjectId); + weakJavaObjectToID.remove(weakRef); + } + } + private static class WorkerThreadHandler extends Handler { @Override public void handleMessage(Message msg) { @@ -652,9 +687,17 @@ private long getUsedMemory() { return usedMemory; } - public void notifyGc() { notifyGc(runtimeId); } - public void lock() { lock(runtimeId); } - public void unlock() { unlock(runtimeId); } + public void notifyGc() { + notifyGc(runtimeId); + } + + public void lock() { + lock(runtimeId); + } + + public void unlock() { + unlock(runtimeId); + } public static void initInstance(Object instance) { ManualInstrumentation.Frame frame = ManualInstrumentation.start("Runtime.initInstance"); @@ -1128,7 +1171,7 @@ private Object[] extendConstructorArgs(String methodName, boolean isConstructor, if (methodName.equals("init")) { if (args == null) { arr = new Object[] - {isConstructor}; + {isConstructor}; } else { arr = new Object[args.length + 1]; System.arraycopy(args, 0, arr, 0, args.length); @@ -1159,7 +1202,7 @@ private Object dispatchCallJSMethodNative(final int javaObjectID, final String m try { ret = callJSMethodNative(getRuntimeId(), javaObjectID, methodName, returnType, isConstructor, packagedArgs); } catch (NativeScriptException e) { - if(discardUncaughtJsExceptions) { + if (discardUncaughtJsExceptions) { String errorMessage = "Error on \"" + Thread.currentThread().getName() + "\" thread for callJSMethodNative\n"; android.util.Log.w("Warning", "NativeScript discarding uncaught JS exception!"); passDiscardedExceptionToJs(e, errorMessage); @@ -1180,7 +1223,7 @@ public void run() { final Object[] packagedArgs = packageArgs(tmpArgs); arr[0] = callJSMethodNative(getRuntimeId(), javaObjectID, methodName, returnType, isCtor, packagedArgs); } catch (NativeScriptException e) { - if(discardUncaughtJsExceptions) { + if (discardUncaughtJsExceptions) { String errorMessage = "Error on \"" + Thread.currentThread().getName() + "\" thread for callJSMethodNative\n"; passDiscardedExceptionToJs(e, errorMessage); android.util.Log.w("Warning", "NativeScript discarding uncaught JS exception!"); diff --git a/test-app/runtime/src/test/java/com/tns/RuntimeTest.java b/test-app/runtime/src/test/java/com/tns/RuntimeTest.java new file mode 100644 index 000000000..265062ced --- /dev/null +++ b/test-app/runtime/src/test/java/com/tns/RuntimeTest.java @@ -0,0 +1,90 @@ +package com.tns; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RuntimeTest { + + private static final int TESTING_NATIVE_OBJECT_ID = 666; + private static final Object TESTING_NATIVE_OBJECT = new Object(); + + private Runtime runtime; + + @Mock + private HashMap strongInstances; + + @Mock + private HashMap> weakInstances; + + @Mock + private NativeScriptHashMap strongJavaObjectToId; + + @Mock + private NativeScriptWeakHashMap weakJavaObjectToId; + + @Mock + private WeakReference testingObjectWeakRefMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + runtime = new Runtime(mock(ClassResolver.class), + mock(GcListener.class), + mock(StaticConfiguration.class), + mock(DynamicConfiguration.class), + 0, + 0, + strongInstances, + weakInstances, + strongJavaObjectToId, + weakJavaObjectToId); + } + + @Test + public void testStrongInstanceIsSuccessfullyRemoved() { + when(strongInstances.get(TESTING_NATIVE_OBJECT_ID)).thenReturn(TESTING_NATIVE_OBJECT); + + runtime.releaseNativeCounterpart(TESTING_NATIVE_OBJECT_ID); + + verify(strongInstances).remove(TESTING_NATIVE_OBJECT_ID); + verify(strongJavaObjectToId).remove(TESTING_NATIVE_OBJECT); + } + + @Test + public void testWeakInstanceIsSuccessfullyRemoved(){ + when(weakInstances.get(TESTING_NATIVE_OBJECT_ID)).thenReturn(testingObjectWeakRefMock); + + runtime.releaseNativeCounterpart(TESTING_NATIVE_OBJECT_ID); + + verify(weakInstances).remove(TESTING_NATIVE_OBJECT_ID); + verify(weakJavaObjectToId).remove(testingObjectWeakRefMock); + } + + @Test + public void testStrongAndWeakInstancesAreSuccessfullyRemoved(){ + when(strongInstances.get(TESTING_NATIVE_OBJECT_ID)).thenReturn(TESTING_NATIVE_OBJECT); + when(weakInstances.get(TESTING_NATIVE_OBJECT_ID)).thenReturn(testingObjectWeakRefMock); + + runtime.releaseNativeCounterpart(TESTING_NATIVE_OBJECT_ID); + + verify(strongInstances).remove(TESTING_NATIVE_OBJECT_ID); + verify(strongJavaObjectToId).remove(TESTING_NATIVE_OBJECT); + verify(weakInstances).remove(TESTING_NATIVE_OBJECT_ID); + verify(weakJavaObjectToId).remove(testingObjectWeakRefMock); + } +}