diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 556fff57b..ed8ae02e3 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -304,6 +304,8 @@ void mScriptValueWrap(struct mScriptValue* val, struct mScriptValue* out); struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* val); const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* val); +void mScriptValueFollowPointer(struct mScriptValue* ptr, struct mScriptValue* out); + struct mScriptValue* mScriptStringCreateEmpty(size_t size); struct mScriptValue* mScriptStringCreateFromBytes(const void* string, size_t size); struct mScriptValue* mScriptStringCreateFromUTF8(const char* string); diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 3c7266a2a..db7a70075 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -693,6 +693,31 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v return true; } } + struct mScriptValue derefPtr; + if (value->type->base == mSCRIPT_TYPE_OPAQUE) { + if (!value->type->details.type) { + return false; + } + mScriptValueFollowPointer(value, &derefPtr); + switch (derefPtr.type->base) { + case mSCRIPT_TYPE_VOID: + case mSCRIPT_TYPE_SINT: + case mSCRIPT_TYPE_UINT: + case mSCRIPT_TYPE_FLOAT: + value = &derefPtr; + break; + case mSCRIPT_TYPE_OBJECT: + value = mScriptValueAlloc(derefPtr.type); + value->value.opaque = derefPtr.value.opaque; + weakref = mScriptContextSetWeakref(luaContext->d.context, value); + needsWeakref = true; + mScriptContextDisownWeakref(luaContext->d.context, weakref); + mScriptValueDeref(value); + break; + default: + return false; + } + } if (value->type == mSCRIPT_TYPE_MS_WEAKREF) { weakref = value->value.u32; value = mScriptContextAccessWeakref(luaContext->d.context, value); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 2c047ec39..5731b0705 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -22,6 +22,7 @@ struct Test { void (*vfn0)(struct Test*); void (*vfn1)(struct Test*, int); int32_t (*icfn0)(const struct Test*); + struct Test* next; }; static int identityInt(int in) { @@ -83,6 +84,7 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(Test, v1, testV1, 1, S32, b); mSCRIPT_DEFINE_STRUCT(Test) mSCRIPT_DEFINE_STRUCT_MEMBER(Test, S32, i) + mSCRIPT_DEFINE_STRUCT_MEMBER(Test, PS(Test), next) mSCRIPT_DEFINE_STRUCT_METHOD(Test, ifn0) mSCRIPT_DEFINE_STRUCT_METHOD(Test, ifn1) mSCRIPT_DEFINE_STRUCT_METHOD(Test, icfn0) @@ -726,6 +728,42 @@ M_TEST_DEFINE(callList) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(linkedList) { + SETUP_LUA; + + struct Test first = { + .i = 1 + }; + struct Test second = { + .i = 2 + }; + struct mScriptValue a = mSCRIPT_MAKE_S(Test, &first); + + assert_true(lua->setGlobal(lua, "l", &a)); + TEST_PROGRAM("assert(l)"); + TEST_PROGRAM("assert(l.i == 1)"); + TEST_PROGRAM("assert(not l.next)"); + + first.next = &second; + TEST_PROGRAM("assert(l)"); + TEST_PROGRAM("assert(l.i == 1)"); + TEST_PROGRAM("assert(l.next)"); + TEST_PROGRAM("assert(l.next.i == 2)"); + TEST_PROGRAM("assert(not l.next.next)"); + + TEST_PROGRAM( + "n = l.next\n" + "function readN()\n" + " assert(n)\n" + " assert(n.i or not n.i)\n" + "end\n" + "assert(pcall(readN))\n"); + // The weakref stored in `n` gets pruned between executions to avoid stale pointers + TEST_PROGRAM("assert(not pcall(readN))"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(create), cmocka_unit_test(loadGood), @@ -744,4 +782,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableLookup), cmocka_unit_test(tableIterate), cmocka_unit_test(callList), + cmocka_unit_test(linkedList) ) diff --git a/src/script/types.c b/src/script/types.c index 0b35fc58a..279351535 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -853,6 +853,21 @@ const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* va return NULL; } +void mScriptValueFollowPointer(struct mScriptValue* ptr, struct mScriptValue* out) { + if (ptr->type->base != mSCRIPT_TYPE_OPAQUE || !ptr->type->details.type) { + return; + } + + out->value.opaque = *(void**) ptr->value.opaque; + if (out->value.opaque) { + out->type = ptr->type->details.type; + } else { + out->type = mSCRIPT_TYPE_MS_VOID; + } + out->refs = mSCRIPT_VALUE_UNREF; + out->flags = 0; +} + struct mScriptValue* mScriptStringCreateEmpty(size_t size) { struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_STR); struct mScriptString* internal = val->value.opaque;