Why does luaV_execute use runtime_check instead of luaG_runerror in OP_SETLIST?

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Why does luaV_execute use runtime_check instead of luaG_runerror in OP_SETLIST?

Peter Cawley
For OP_FORPREP, luaG_runerror is called if any of the values aren't
numbers, and luaG_runerror is used elsewhere in the VM to indicate a
runtime error to the caller. However, OP_SETLIST calls runtime_check
if the value isn't a table, and then silently skips to the next
instruction without an error. Why is this the only place where
runtime_check is used, and why is it used instead of luaG_runerror?

Reply | Threaded
Open this post in threaded view
|

Re: Why does luaV_execute use runtime_check instead of luaG_runerror in OP_SETLIST?

Mike Pall-90
Peter Cawley wrote:
> For OP_FORPREP, luaG_runerror is called if any of the values aren't
> numbers, and luaG_runerror is used elsewhere in the VM to indicate a
> runtime error to the caller. However, OP_SETLIST calls runtime_check
> if the value isn't a table, and then silently skips to the next
> instruction without an error. Why is this the only place where
> runtime_check is used, and why is it used instead of luaG_runerror?

OP_SETLIST is used to initialize table elements for table
constructors. The parser only ever generates bytecode sequences
where the result of OP_NEWTABLE flows into OP_SETLIST. So the
object pointed to by RA can never be anything else than a table.

But maliciously crafted bytecode could abuse this and crash the VM
if the runtime check were omitted. Unfortunately the bytecode
verifier can't catch this statically when the bytecode is loaded,
since it doesn't perform a full data-flow analysis. This would be
both expensive and rather pointless: all other bytecodes happily
deal with arbitrary object types. Either by directly using them,
coercing them, calling metamethods or throwing an error.

Arguably the silent runtime_check() is surprising. Maybe for
consistency luaG_runerror(L, "bad bytecode") would be better.

[
If all error functions would be marked as not-returning, the C
compiler could optimize calls to them much better and keep them
out of the fast path:

#if defined(__GNUC__)
#define LUAI_NORET      __attribute__((noreturn))
#elif defined(_MSC_VER)
#define LUAI_NORET      __declspec(noreturn)
#else
#define LUAI_NORET

#define LUAI_FUNC_NORET LUAI_FUNC LUAI_NORET

...
LUAI_FUNC_NORET void luaG_runerror(lua_State *L, const char *fmt, ...);
...
]

--Mike