[Tarantool-patches] [PATCH luajit] FFI: Fix constructor index resolution in JIT compiler.

Sergey Kaplun via Tarantool-patches
2026-03-16T10:47:58Z

From: Mike Pall <mike>

Reported by Vladimir Davydov and Sergey Kaplun.

(cherry picked from commit 02e29995581cb568a784a9b996cd5e9139cd0737)

This patch is a follow-up for the commit
6cee1333ef2b340e1b23e3051b9f433fc803d2ff ("FFI: Resolve metamethods for
constructors, too."). It allows the use of metamethods of the objects to
be called on their constructors. Unfortunately, the JIT part isn't
updated and may cause inconsistencies since it is indexing the field of
the structure referenced by the given CType.

This patch makes the semantics of JIT the same as for the VM.

Sergey Kaplun:
* added the description and the test for the problem

Part of tarantool/tarantool#12134
---

Branch: https://github.com/tarantool/luajit/tree/skaplun/lj-1441-record-constructor-metamethod
Related issues:
* https://github.com/LuaJIT/LuaJIT/issues/1441
* https://github.com/tarantool/tarantool/issues/12134

src/lj_crecord.c | 2 +
...441-record-constructor-metamethod.test.lua | 79 +++++++++++++++++++
2 files changed, 81 insertions(+)
create mode 100644 test/tarantool-tests/lj-1441-record-constructor-metamethod.test.lua

diff --git a/src/lj_crecord.c b/src/lj_crecord.c
index b016eaec..d486ee85 100644
--- a/src/lj_crecord.c
+++ b/src/lj_crecord.c
@@ -881,6 +881,8 @@ again:
}
J->base[0] = lj_ir_kint(J, (int32_t)fct->size);
return; /* Interpreter will throw for newindex. */
+ } else if (cd && cd->ctypeid == CTID_CTYPEID) {
+ /* Only resolve constants and metamethods for constructors. */
} else if (ctype_isbitfield(fct->info)) {
if (ofs)
ptr = emitir(IRT(IR_ADD, IRT_PTR), ptr, lj_ir_kintp(J, ofs));
diff --git a/test/tarantool-tests/lj-1441-record-constructor-metamethod.test.lua b/test/tarantool-tests/lj-1441-record-constructor-metamethod.test.lua
new file mode 100644
index 00000000..370e49e0
--- /dev/null
+++ b/test/tarantool-tests/lj-1441-record-constructor-metamethod.test.lua
@@ -0,0 +1,79 @@
+local tap = require('tap')
+
+-- The test file to demonstrate LuaJIT's incorrect recording of
+-- the __index metamethod invocation on the cdata's constructor.
+-- See also: https://github.com/LuaJIT/LuaJIT/issues/1441.
+
+local test = tap.test('lj-1441-record-constructor-metamethod'):skipcond({
+ ['Test requires JIT enabled'] = not jit.status(),
+})
+
+test:plan(3)
+
+local ffi = require('ffi')
+
+ffi.cdef[[
+ struct test_recursive {int a;};
+ struct test_finite {int a;};
+]]
+
+local recursive_t = ffi.typeof('struct test_recursive')
+local finite_t = ffi.typeof('struct test_finite')
+
+local MAGIC = 42
+
+local function new_recursive()
+ return ffi.new(recursive_t, 0)
+end
+
+local function new_finite()
+ return ffi.new(finite_t, 0)
+end
+
+local function index_func_recursive(v)
+ -- Should raise an error (stack overflow).
+ return ffi.typeof(v).a
+end
+
+-- Special object to invoke metamethod on the cdata<ctypeid>.
+local one_more_step = new_finite()
+
+local function index_func_finite(v)
+ if v == one_more_step then
+ -- XXX: Avoid tail-calls.
+ local x = ffi.typeof(v).a
+ return x
+ else
+ return MAGIC
+ end
+end
+
+ffi.metatype(recursive_t, {
+ __index = index_func_recursive,
+})
+
+ffi.metatype(finite_t, {
+ __index = index_func_finite,
+})
+
+jit.opt.start('hotloop=1')
+
+-- Test the recursive call. Expect the stack overflow error.
+local o_rec = new_recursive()
+local result, errmsg
+for _ = 1, 4 do
+ result, errmsg = pcall(index_func_recursive, o_rec)
+end
+
+test:ok(not result, 'correct status for recursive call')
+test:like(errmsg, 'stack overflow', 'correct error message for recursive call')
+
+-- Test the finite call. Expect the specific value.
+local got
+for _ = 1, 4 do
+ got = index_func_finite(one_more_step)
+end
+
+test:is(got, MAGIC, 'correct result value on trace for finite call')
+
+test:done(true)
--
2.53.0