lua_dump vs Lua 5.4 and Lua 5.5

183 views
Skip to first unread message

Benoit Germain

unread,
Jul 18, 2025, 10:52:57 AMJul 18
to lu...@googlegroups.com
Hello,

I'm having some issues "porting" Lanes to Lua 5.5 wrt lua_dump. I'm using the following writer, inspired from lstrlib.c:

static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_)
{
    luaL_Buffer* const _B{ static_cast<luaL_Buffer*>(ud_) };
    if (!_B->L) {
        luaL_buffinit(L_, _B);
    }
    if (b_ && size_) {
        luaL_addlstring(_B, static_cast<char const*>(b_), size_);
    }
    return 0;
}

The difference is that I don't wrap the lua_Buffer in an additional structure to check for first-time init, but use the buffer's own L pointer which is null on initialization.

Then I dump a function like so:

    luaL_Buffer B{};
    lua_dump(L, buf_writer, &B, U->stripFunctions);
    // pushes dumped string on 'L', (and removes the light userdata when we are Lua 5.4)
    luaL_pushresult(&B);


First, something I noticed with Lua 5.4. For all Lua 5 versions, documentation says that lua_dump isn't supposed to push anything on the stack (it reads [-0,+0,...]). However, with Lua 5.4.8, a light userdata is pushed on the stack. This is the placeholder pushed by luaL_buffinit, which will be later removed by my subsequent call to luaL_pushresult. So I suppose that the documentation for Lua 5.4 should be fixed accordingly.

With Lua 5.5, the documentation says: "After calling luaL_pushresult, the stack is back to its level when the buffer was initialized, plus the final string on its top."
However, my observation is that the closure that was on top of the stack when I called lua_dump, is removed by luaL_pushresult and replaced with the bytecode string. Is the error in the documentation or the implementation?

Regards,


--
Benoit.

Roberto Ierusalimschy

unread,
Jul 18, 2025, 1:24:26 PMJul 18
to lu...@googlegroups.com
> luaL_Buffer B{};
> lua_dump(L, buf_writer, &B, U->stripFunctions);
> // pushes dumped string on 'L', (and removes the light userdata when we
> are Lua 5.4)
> luaL_pushresult(&B);

> First, something I noticed with Lua 5.4. For all Lua 5 versions,
> documentation says that lua_dump isn't supposed to push anything on the
> stack (it reads [-0,+0,...]). However, with Lua 5.4.8, a light userdata is
> pushed on the stack. This is the placeholder pushed by luaL_buffinit, which
> will be later removed by my subsequent call to luaL_pushresult. So I
> suppose that the documentation for Lua 5.4 should be fixed accordingly.

I'm afraid you are mixing two different things. The documentation
[-0, +0,...] is for the caller of the API function, not for occasional
functions being called by it. The documentation before 5.5 was mute
about this other side---what was on the stack when lua_dump calls the
writer. The documentation of 5.5 mentions that in the Incompatibilities
Section:

The function lua_dump changed the way it keeps the stack through the
calls to the writer function. (That was not specified in previous
versions.)


> With Lua 5.5, the documentation says: "After calling luaL_pushresult
> <https://www.lua.org/work/doc/manual.html#luaL_pushresult>, the stack is
> back to its level when the buffer was initialized, plus the final string on
> its top."
> However, my observation is that the closure that was on top of the stack
> when I called lua_dump, is removed by luaL_pushresult and replaced with the
> bytecode string. Is the error in the documentation or the implementation?

The documentation for auxiliar buffers is clear that you can only call
buffer functions when the stack is in the same level it was when you
initialized it:

You can use the stack between successive calls to buffer operations
as long as that use is balanced; that is, when you call a buffer
operation, the stack is at the same level it was immediately after
the previous buffer operation. (The only exception to this rule is
luaL_addvalue.)

Are you sure you are keeping that true through all the life of the buffer,
in particular when you call luaL_pushresult?
The documentation of lua_dump now is clear that the stack level around
the call to lua_dump and inside the calls to the writer function may not
be the same.

-- Roberto

Benoit Germain

unread,
Jul 22, 2025, 9:59:19 AMJul 22
to lu...@googlegroups.com
Le ven. 18 juil. 2025 à 19:24, Roberto Ierusalimschy <rob...@inf.puc-rio.br> a écrit :

Are you sure you are keeping that true through all the life of the buffer,
in particular when you call luaL_pushresult?
The documentation of lua_dump now is clear that the stack level around
the call to lua_dump and inside the calls to the writer function may not
be the same.

 Yes. Code is visible on github. See buf_writer() and PushFunctionBytecode() in https://github.com/LuaLanes/lanes/blob/master/src/tools.cpp.

I call PushFunctionBytecode() with the Lua function at the top of the stack (let N be the index), where I call lua_dump, followed by luaL_pushresult.
The first call to buf_writer() from lua_dump calls luaL_buffinit, which happens to push a table on the stack.
Then buf_writer() is called again a bunch of times from inside lua_dump, and lua_dump eventually returns. At that point, the table is removed from the stack, which is back to its state before lua_dump was called (therefore the function I am dumping is back at the top of the stack, which is still N). So far, everything works as expected.
Then I immediately call luaL_pushresult, which I observe causing the Lua function to be removed from the stack, and the bytecode string to be pushed in its place. This contradicts the documentation saying:
"After calling luaL_pushresult, the stack is back to its level when the buffer was initialized, plus the final string on its top."
Because when the buffer was initialized, the stack had N entries, and it still has N entries after calling luaL_pushresult, when it should have N+1 according to that sentence. Or did I get it wrong?


--
Benoit.

Benoit Germain

unread,
Jul 22, 2025, 10:28:33 AMJul 22
to lu...@googlegroups.com
Sorry, my observations about what happens when I call lua_dump are slightly wrong. It should read instead:
When I call lua_dump with the Lua function at the top of the stack, I see that there is an additional table present on top the first time I enter buf_writer. When buf_writer calls luaL_buffinit, it pushes an additional light userdata on the stack. Then lua_dump repeatedly calls buf_writer, and returns. At that point, when lua_dump returns the stack is back to its original state before the call, with the Lua function on top.

Also, another issue that I have is that depending on the Lua function I attempt to dump, I can crash in luaL_pushresult:

LUALIB_API void luaL_pushresult (luaL_Buffer *B) {
  lua_State *L = B->L;
  checkbufferlevel(B, -1);
  if (!buffonstack(B))  /* using static buffer? */
    lua_pushlstring(L, B->b, B->n);  /* save result as regular string */
  else {  /* reuse buffer already allocated */
    UBox *box = (UBox *)lua_touserdata(L, -1); <--- BOX IS NULL BECAUSE THE TOP OF THE STACK IS THE DUMPED FUNCTION, NOT THE LIGHT USERDATA PREPARED BY luaL_buffinit
    void *ud;
    lua_Alloc allocf = lua_getallocf(L, &ud);  /* function to free buffer */
    size_t len = B->n;  /* final string length */
    char *s;
    resizebox(L, -1, len + 1);  /* adjust box size to content size */ <--- BOX IS OBTAINED AGAIN INSIDE resizebox(), WHICH CAUSES A NULL POINTER DEREFERENCE
--
Benoit.

Roberto Ierusalimschy

unread,
Jul 23, 2025, 8:35:43 AMJul 23
to lu...@googlegroups.com
> Sorry, my observations about what happens when I call lua_dump are
> slightly wrong. It should read instead:
> When I call lua_dump with the Lua function at the top of the stack, I see
> that there is an additional table present on top the first time I enter
> buf_writer. When buf_writer calls luaL_buffinit, it pushes an additional
> light userdata on the stack. Then lua_dump repeatedly calls buf_writer, and
> returns. At that point, when lua_dump returns the stack is back to its
> original state before the call, with the Lua function on top.

So, when you call buffinit, the stack has an additional table, which is
not present when you call pushresult.

Similarly, when you call pushresult, the stack has "the Lua function on
top", which was not present when you called buffinit.

Both things violate the contract with Lua buffers.

-- Roberto
Reply all
Reply to author
Forward
0 new messages