diff --git a/quickjs-android-wrapper/src/androidTest/java/com/whl/quickjs/wrapper/QuickJSTest.java b/quickjs-android-wrapper/src/androidTest/java/com/whl/quickjs/wrapper/QuickJSTest.java index bbe98d1..90a575e 100644 --- a/quickjs-android-wrapper/src/androidTest/java/com/whl/quickjs/wrapper/QuickJSTest.java +++ b/quickjs-android-wrapper/src/androidTest/java/com/whl/quickjs/wrapper/QuickJSTest.java @@ -10,7 +10,8 @@ public class QuickJSTest { @Test public void createQuickJSContextTest() { - QuickJSContext.create(); + QuickJSContext context = QuickJSContext.create(); + context.destroyContext(); } @Test @@ -34,6 +35,8 @@ public void evalReturnTypeTest() { assertEquals(123, context.evaluate("123;")); assertEquals(1.23, context.evaluate("1.23;")); assertEquals("hello wrapper", context.evaluate("\"hello wrapper\";")); + + context.destroyContext(); } @Test @@ -54,6 +57,8 @@ public void getPropertiesTest() { assertEquals(true, globalObject.getProperty("booleanValue")); JSFunction function = (JSFunction) globalObject.getProperty("testFunc"); assertEquals("hello, yonglan-whl", function.call("yonglan-whl")); + + context.destroyContext(); } @Test @@ -65,6 +70,8 @@ public void getJSArrayTest() { "\n" + "test(3);"); assertEquals(3, ret.get(2)); + + context.destroyContext(); } @Test @@ -76,6 +83,8 @@ public void JSFunctionArgsTest() { JSObject globalObject = context.getGlobalObject(); JSFunction func = (JSFunction) globalObject.getProperty("test"); assertEquals("hello, 1string123.11true", func.call(1, "string", 123.11, true)); + + context.destroyContext(); } @Test @@ -87,6 +96,8 @@ public void JSFunctionNullArgsTest() { JSObject globalObject = context.getGlobalObject(); JSFunction func = (JSFunction) globalObject.getProperty("test"); assertEquals("hello, undefined-13", func.call(null, -1, 3)); + + context.destroyContext(); } @Test @@ -104,6 +115,7 @@ public void JSFunctionArgsTestWithUnSupportType() { assertTrue(e.toString().contains("Unsupported Java type")); } + context.destroyContext(); } @Test @@ -149,6 +161,8 @@ public Object call(Object... args) { }); context.evaluate("console.log(123)"); + + context.destroyContext(); } @Test @@ -190,6 +204,8 @@ public Object call(Object... args) { JSObject jsObj = (JSObject) context.evaluate("render();"); JSFunction jsFunction = (JSFunction) jsObj.getProperty("func"); jsFunction.call(); + + context.destroyContext(); } @Test @@ -199,6 +215,7 @@ public void setPropertyWithJSObjectTest() { context.getGlobalObject().setProperty("test1", context.getGlobalObject().getProperty("test")); assertEquals("{\"count\":0}", context.getGlobalObject().getJSObjectProperty("test1").stringify()); + context.destroyContext(); } @Test @@ -209,6 +226,42 @@ public void jsonParseTest() { JSObject result = context.parseJSON(text); assertEquals("270", result.getProperty("leadsId")); + context.getGlobalObject().setProperty("test", result); + Log.d("__quickjs__", "123------------------" + context.getGlobalObject().getJSObjectProperty("test").stringify()); + + + context.destroyContext(); + } + + @Test + public void jsonParseTest2() { + String text = "{\"phoneNumber\":\"呼叫 18505815627\",\"leadsId\":\"270\",\"leadsBizId\":\"xxx\",\"options\":[{\"type\":\"aliyun\",\"avatarUrl\":\"https://gw.alicdn.com/tfs/TB1BYz0vpYqK1RjSZLeXXbXppXa-187-187.png\",\"personName\":\"老板\",\"storeName\":\"小店名称\",\"title\":\"智能办公电话\",\"content\":\"免费拨打\"},{\"type\":\"direct\",\"title\":\"普通电话\",\"content\":\"运营商拨打\"}]}\n"; + + QuickJSContext context = QuickJSContext.create(); + JSFunction log = (JSFunction) context.evaluate("console.log"); + JSObject jsonObj = context.parseJSON(text); + log.call(jsonObj); + + context.destroyContext(); + } + + @Test + public void jsonParseTest3() { + String text = "{\"phoneNumber\":\"呼叫 18505815627\",\"leadsId\":\"270\",\"leadsBizId\":\"xxx\",\"options\":[{\"type\":\"aliyun\",\"avatarUrl\":\"https://gw.alicdn.com/tfs/TB1BYz0vpYqK1RjSZLeXXbXppXa-187-187.png\",\"personName\":\"老板\",\"storeName\":\"小店名称\",\"title\":\"智能办公电话\",\"content\":\"免费拨打\"},{\"type\":\"direct\",\"title\":\"普通电话\",\"content\":\"运营商拨打\"}]}\n"; + QuickJSContext context = QuickJSContext.create(); + JSObject a = (JSObject) context.evaluate("var a = {}; a;"); + a.setProperty("test", context.parseJSON(text)); + context.evaluate("console.log(a.test.leadsId);"); + context.destroyContext(); + } + + @Test + public void jsonParseTest4() { + String text = "{\"phoneNumber\":\"呼叫 18505815627\",\"leadsId\":\"270\",\"leadsBizId\":\"xxx\",\"options\":[{\"type\":\"aliyun\",\"avatarUrl\":\"https://gw.alicdn.com/tfs/TB1BYz0vpYqK1RjSZLeXXbXppXa-187-187.png\",\"personName\":\"老板\",\"storeName\":\"小店名称\",\"title\":\"智能办公电话\",\"content\":\"免费拨打\"},{\"type\":\"direct\",\"title\":\"普通电话\",\"content\":\"运营商拨打\"}]}\n"; + QuickJSContext context = QuickJSContext.create(); + JSObject a = (JSObject) context.evaluate("var a = {b: {}}; a;"); + a.getJSObjectProperty("b").setProperty("test", context.parseJSON(text)); + context.evaluate("console.log(a.b.test.leadsId);"); context.destroyContext(); } @@ -249,6 +302,8 @@ public void testGetOwnPropertyNames() { assertEquals("ff", item); } } + + context.destroyContext(); } @Test diff --git a/quickjs-android-wrapper/src/main/java/com/whl/quickjs/wrapper/QuickJSContext.java b/quickjs-android-wrapper/src/main/java/com/whl/quickjs/wrapper/QuickJSContext.java index 6c7d2d7..8a42288 100644 --- a/quickjs-android-wrapper/src/main/java/com/whl/quickjs/wrapper/QuickJSContext.java +++ b/quickjs-android-wrapper/src/main/java/com/whl/quickjs/wrapper/QuickJSContext.java @@ -74,6 +74,12 @@ public Object getProperty(JSObject jsObj, String name) { public void setProperty(JSObject jsObj, String name, Object value) { setProperty(context, jsObj.getPointer(), name, value); + + // Value 为 JSObject 类型,需要手动增加引用计数,不然 QuickJS 垃圾回收会报 + // assertion "p->ref_count > 0" 的错误。 + if (value instanceof JSObject) { + ((JSObject) value).dupValue(); + } } public void freeValue(JSObject jsObj) { @@ -104,6 +110,11 @@ Object call(JSObject func, long objPointer, Object... args) { return obj; } + /** + * Automatically manage the release of objects, + * the hold method is equivalent to call the + * dupValue and freeDupValue methods with NativeCleaner. + */ public void hold(JSObject jsObj) { jsObj.dupValue(); nativeCleaner.register(jsObj, jsObj.getPointer()); diff --git a/remarks.md b/remarks.md index ba79b14..e9239a9 100644 --- a/remarks.md +++ b/remarks.md @@ -36,4 +36,23 @@ - globalObject 的引用计数默认是 2,暂时未弄清楚具体逻辑,目前的释放逻辑是每次 getGlobalObject 后都会 Free 下。待后续优化 -- String/Atom 内存泄漏问题:虽然调用了 JSCString 的释放方法,但是因为其所在的对象是 GlobalObject,无法被释放,导致一直会被持有,这里需要进一步优化. \ No newline at end of file +- String/Atom 内存泄漏问题:虽然调用了 JSCString 的释放方法,但是因为其所在的对象是 GlobalObject,无法被释放,导致一直会被持有,这里需要进一步优化. + +- `void gc_decref_child(JSRuntime *, JSGCObjectHeader *): assertion "p->ref_count > 0" failed` + - 原因:某个对象引用计数为未加一,导致减一的时候校验失败 + - 排查方式:先打印出 `gc_decref_child` 里 `p->ref_count <= 0` 的对象信息,因为执行到方法时,对象一般已经释放,会显示为 `null`,但是可以看到指针信息,可以选择以下任一方式定位到具体对象信息 + - 方式1:打开 `quickjs.c` 里的 DUMP_FREE 开关,打印出所有的 `free` 对象,然后指针去匹配查找到释放前的对象信息 + - 方式2:找到 `__JS_FreeValueRT` 方法,并注释以下代码,不释放对象,这样在 `gc_decref_child` 里就可以看到对象的具体信息。 + + + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + { + // JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + // if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + // list_del(&p->link); + // list_add(&p->link, &rt->gc_zero_ref_count_list); + // if (rt->gc_phase == JS_GC_PHASE_NONE) { + // free_zero_refcount(rt); + // } + // } \ No newline at end of file