[ANN] Lua 5.5.0 (rc1) now available

765 views
Skip to first unread message

Luiz Henrique de Figueiredo

unread,
Nov 14, 2025, 8:08:44 PM11/14/25
to lua-l
Lua 5.5 is the next version of Lua.

Lua 5.5.0 (rc1) is now available for testing at
https://www.lua.org/work/lua-5.5.0-rc1.tar.gz

The SHA256 checksum is
34a4dcca0c04877fbce4baff54054b9793b70bca5d8c676ca4d3504dd47c3772  -

The Git commit ID is
4cf498210e6a60637a7abb06d32460ec21efdbdc

The main changes in Lua 5.5.0 are listed at
https://www.lua.org/work/doc/#changes

An updated reference manual is included and also available at
https://www.lua.org/work/doc

The complete diffs from beta to rc1 are available at
https://www.lua.org/work/diffs-lua-5.5.0-beta-rc1.html.html
https://www.lua.org/work/diffu-lua-5.5.0-beta-rc1.html.html

A test suite is available at
https://www.lua.org/work/lua-5.5.0-tests.tar.gz

All feedback welcome. Thanks.
--lhf

Luiz Henrique de Figueiredo

unread,
Nov 14, 2025, 8:37:45 PM11/14/25
to lu...@googlegroups.com
The complete diffs from beta to rc1 are available at
https://www.lua.org/work/diffs-lua-5.5.0-beta-rc1.html
https://www.lua.org/work/diffu-lua-5.5.0-beta-rc1.html

Sorry about the typo in the announcement.
--lhf

Bas Groothedde

unread,
Nov 15, 2025, 6:12:14 AM11/15/25
to lu...@googlegroups.com

Thank you, I have processed those changes on luac.nl

~b

Xmilia Hermit

unread,
Nov 15, 2025, 6:46:13 AM11/15/25
to lu...@googlegroups.com
I already reported all the problems I found in the new features and they
are already fixed.
Personally, I like the new `...name` syntax to name varargs.
However, I would have liked to see a `varargtable...` operator as the
reverse of the named vararg `...varargtable`.
This should behave like `table.unpack(varargtable, 1, varargtable.n)`
and should not have parsing problems since constructs such as
`...(funcarg)` or `...[1]=1` are not valid.
In my opinion this would make the use of `...` cleaner in case the
function uses a named vararg parameter as in:

local function test(...args)
return ...
end

vs

local function test(...args)
return args...
end

and could allow for constructs such as

local function test(...args)
return functon() return args... end
end

I tried to implement this myself by using OP_VARARG and the unused K and
RB parameters to encode if this is the `a...` version and what the slot
of `a` is.
The following is the patch in case someone is interested:

index 95ef900c..fe9e4454 100644
--- a/lcode.c
+++ b/lcode.c
@@ -1951,6 +1951,11 @@ void luaK_finish (FuncState *fs) {
SET_OPCODE(*pc, OP_GETTABLE); /* must get vararg there */
break;
}
+ case OP_VARARG: {
+ if ((p->flag & (PF_VATAB | PF_ISVARARG)) == PF_ISVARARG &&
GETARG_B(*pc) == fs->f->numparams)
+ SETARG_k(*pc, 0); /* Don't use the spread function in case
the vararg table is not created */
+ break;
+ }
case OP_JMP: { /* to optimize jumps to jumps */
int target = finaltarget(p->code, i);
fixjump(fs, i, target); /* jump directly to final target */
diff --git a/lparser.c b/lparser.c
index 77141e79..4ecaa670 100644
--- a/lparser.c
+++ b/lparser.c
@@ -1247,6 +1247,21 @@ static void suffixedexp (LexState *ls, expdesc *v) {
funcargs(ls, v);
break;
}
+ case TK_DOTS: {
+ if (v->k == VVARGVAR && v->t == v->f) {
+ init_exp(v, VVARARG, luaK_codeABCk(fs, OP_VARARG, 0,
v->u.var.ridx, 1, 1));
+ } else {
+ luaK_exp2anyreg(fs, v);
+ if (v->k == VNONRELOC && v->u.info >= luaY_nvarstack(fs)) {
+ fs->freereg--;
+ lua_assert(v->u.info == fs->freereg);
+ }
+ init_exp(v, VVARARG, luaK_codeABCk(fs, OP_VARARG, 0,
v->u.info, 1, 1));
+ }
+ luaK_checkstack(fs, 2);
+ luaX_next(ls);
+ return;
+ }
default: return;
}
}
diff --git a/ltablib.c b/ltablib.c
index 46ecb5e0..0c0aefac 100644
--- a/ltablib.c
+++ b/ltablib.c
@@ -424,3 +424,13 @@ LUAMOD_API int luaopen_table (lua_State *L) {
return 1;
}

+LUAI_FUNC int luaT_spread(lua_State *L);
+int luaT_spread(lua_State *L) {
+ int isnum;
+ lua_settop(L, 1);
+ lua_pushinteger(L, 1);
+ lua_getfield(L, 1, "n");
+ lua_tointegerx(L, -1, &isnum);
+ if (l_unlikely(!isnum)) return luaL_error(L, "'n' not an integer");
+ return tunpack(L);
+}
\ No newline at end of file
diff --git a/lvm.c b/lvm.c
index 2c868c21..77ad8e2e 100644
--- a/lvm.c
+++ b/lvm.c
@@ -31,6 +31,7 @@
#include "ltm.h"
#include "lvm.h"

+LUAI_FUNC int luaT_spread(lua_State *L);

/*
** By default, use jump tables in the main interpreter loop on gcc
@@ -1936,6 +1937,13 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmcase(OP_VARARG) {
StkId ra = RA(i);
int n = GETARG_C(i) - 1; /* required results */
+ if (TESTARG_k(i)) {
+ StkId rb = RB(i);
+ setobjs2s(L, ra+1, rb);
+ setfvalue(s2v(ra), luaT_spread);
+ L->top.p = ra + 2;
+ ProtectNT(luaD_call(L, ra, n));
+ } else
Protect(luaT_getvarargs(L, ci, ra, n));
vmbreak;
}

Regards,
Xmilia

Luiz Henrique de Figueiredo

unread,
Nov 15, 2025, 7:13:29 AM11/15/25
to lu...@googlegroups.com
> Thank you, I have processed those changes on luac.nl.

In that example, try also the new vararg feature:
https://luac.nl/s/19dfbcdf6bb8f25a7cb86416bdf

local function print_all(...a)
for i=1,a.n do
print(i, a[i]);
end
end

print_all(1, 2, 3, 4, 5, false, true, {}, "hello");

Bas Groothedde

unread,
Nov 15, 2025, 8:44:01 AM11/15/25
to lu...@googlegroups.com


> On 15 Nov 2025, at 13:13, Luiz Henrique de Figueiredo <l...@tecgraf.puc-rio.br> wrote:
>
> 
Thank you, I will work that into the signatures.

~b

Scott Morgan

unread,
Nov 15, 2025, 8:48:31 AM11/15/25
to lu...@googlegroups.com
On 15/11/2025 11:46, Xmilia Hermit wrote:
> I already reported all the problems I found in the new features and they
> are already fixed.
> Personally, I like the new `...name` syntax to name varargs.
> However, I would have liked to see a `varargtable...` operator as the
> reverse of the named vararg `...varargtable`.
> This should behave like `table.unpack(varargtable, 1, varargtable.n)`
> and should not have parsing problems since constructs such as `...
> (funcarg)` or `...[1]=1` are not valid.
> In my opinion this would make the use of `...` cleaner in case the
> function uses a named vararg parameter as in:
...
> and could allow for constructs such as
>
> local function test(...args)
>    return functon() return args... end
> end
>

Would it make sense to also work for any table?

e.g.

function test()
local t = { 1,2,3 } -- Does it need n setting?
return t...
end

Scott

Xmilia Hermit

unread,
Nov 15, 2025, 9:03:01 AM11/15/25
to lu...@googlegroups.com

> Would it make sense to also work for any table?
>
> e.g.
>
> function test()
>   local t = { 1,2,3 } -- Does it need n setting?
>   return t...
> end

In case the table does not have a `n` key the length with `#` could
certainly be used before failing. I just wanted to give a small
motivating example.

Regards,
Xmilia

Martin Eden

unread,
Nov 15, 2025, 9:14:50 AM11/15/25
to lu...@googlegroups.com
On 2025-11-15 14:13, Luiz Henrique de Figueiredo wrote:
> In that example, try also the new vararg feature:
> https://luac.nl/s/19dfbcdf6bb8f25a7cb86416bdf
>
> local function print_all(...a)
> for i=1,a.n do
> print(i, a[i]);
> end
> end

It's nice to see a new release. But I'm here for grumping..

Just tested vararg aliasing on Lua 5.5.0.

Alias feature is nice but we still can't have named sequence in Lua in
general case.

My use case is wrapping Lua functions. I want to print args before call
and results after call.

So I'm writing wrapper that returns function with embedded calls
of printers around call of main function:

  local Wrap =
    function(Main, Before, After)
      return
        function(...)
          Before(...)
          local Result = table.pack(Main(...))
          After(table.unpack(Result, 1, Result.n))
          return table.unpack(Result, 1, Result.n)
        end
    end

Naming "...Args" here won't help, we're not using select()'s.
Note a hoops jumping at using table.pack() and table.unpack().

Please make sequences first-class citizens.

Then I can write something like

  local Wrap =
    function(Main, Before, After)
      return
        function(...)
          Before(...)
          local Result... = Main(...)
          After(Result...)
          return Result...
        end
    end

  -- (And still be able to do "Result[i]" and "Result.n" if needed.)

-- Martin

Andrey Dobrovolsky

unread,
Nov 15, 2025, 10:38:31 AM11/15/25
to lu...@googlegroups.com
Huge thanks for the new Lua version!

There is the typo "varag" instead of "vararg" in manual.hml line 2925:

> The presence of a varag table in a variadic function is indicated

And I have a small question: if I use the named vararg list under
ipairs(), will the interpreter transform this list into the
full-featured table?

Regards,
-- Andrew

сб, 15 лист. 2025 р. о 16:14 'Martin Eden' via lua-l
<lu...@googlegroups.com> пише:
> --
> You received this message because you are subscribed to the Google Groups "lua-l" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/lua-l/06095d9a-c0e7-4d96-b4f6-a4656d90a44c%40disroot.org.

Roberto Ierusalimschy

unread,
Nov 15, 2025, 2:46:31 PM11/15/25
to lu...@googlegroups.com
> There is the typo "varag" instead of "vararg" in manual.hml line 2925:
> > The presence of a varag table in a variadic function is indicated

Thanks.


> And I have a small question: if I use the named vararg list under
> ipairs(), will the interpreter transform this list into the
> full-featured table?

Yes. Any use not syntactically in the form 't[exp]' or 't.name' forces a
real table. (Among other issues, the name 'ipairs' can refer to a
different function.)

-- Roberto

Andrey Dobrovolsky

unread,
Nov 16, 2025, 6:14:43 AM11/16/25
to lu...@googlegroups.com
сб, 15 лист. 2025 р. о 21:46 Roberto Ierusalimschy
<rob...@inf.puc-rio.br> пише:
>
> Yes. Any use not syntactically in the form 't[exp]' or 't.name' forces a
> real table. (Among other issues, the name 'ipairs' can refer to a
> different function.)

Thanks! Got it.

May I ask one more silly question?
Are actually passed varargs allowed to assign new values or they are a
kind of <const>? Seems that

function(i, new_value, ... t)
if i < 0 and i <= t.n then
t[i] = new_value
end
end

is not violating the above mentioned rule? Or varargs appear to be
<const> locals, unlike the named parameters?

-- Andrew

Bas Groothedde

unread,
Nov 16, 2025, 6:42:24 AM11/16/25
to lu...@googlegroups.com

See https://luac.nl/s/19e61f2fc9bbf557b1093781d35 - test1 mutates the vararg, thus Lua emits code for a vararg table. (forces a real table)

test2 does not mutate it, thus it generates code for accessing the vararg pack through GETVARG

~b

Andrey Dobrovolsky

unread,
Nov 16, 2025, 7:09:50 AM11/16/25
to lu...@googlegroups.com
Hi Bas,

Thanks for Your answer. Sorry for the noise, it appeared not to be hard to test.
So varargs are <const>, good to have a confirmed answer.

-- Andrew

нд, 16 лист. 2025 р. о 13:42 'Bas Groothedde' via lua-l
<lu...@googlegroups.com> пише:
> --
> You received this message because you are subscribed to the Google Groups "lua-l" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/lua-l/a7f57c549115eeae9517e42b393072ea%40xoru.net.

Bas Groothedde

unread,
Nov 16, 2025, 10:12:00 AM11/16/25
to lu...@googlegroups.com
--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
In luac.c, line 651:
 
   case OP_ERRNNIL:
    printf("%d %d",a,bx);
    printf(COMMENT); PrintConstant(f,bx);
    break;
 
Should this not include a test for bx?
 
l_noret luaG_errnnil (lua_State *L, LClosure *cl, int k) {
  const char *globalname = "?";  /* default name if k == 0 */
  if (k > 0)
    kname(cl->p, k - 1, &globalname);
  luaG_runerror(L, "global '%s' already defined", globalname);
}
 
luaG_errnil subtracts 1 from k when k is greater than 0, but PrintConstant in luac.c does not - resulting in an out of bounds access,
as PrintConstant simply reads from f->k[i] even if i is not a valid index. 
 
   case OP_ERRNNIL:
    printf("%d %d",a,bx);
    if (bx == 0) {
        printf(COMMENT); printf("?");
    } else {
        printf(COMMENT); PrintConstant(f,bx - 1);
    }
    break;
 
 
~b

Luiz Henrique de Figueiredo

unread,
Nov 16, 2025, 2:28:41 PM11/16/25
to lu...@googlegroups.com
> In luac.c, line 651:
> Should this not include a test for bx?

It seems you're right. Thanks for the report.

André Naef

unread,
Nov 16, 2025, 2:47:37 PM11/16/25
to lua-l
Minor: the lua_pushexternalstring documentation does not describe the return value of the function.

sur-behoffski

unread,
Nov 17, 2025, 5:11:21 AM11/17/25
to lu...@googlegroups.com
On 2025-11-15 11:38, Luiz Henrique de Figueiredo wrote:
> Lua 5.5 is the next version of Lua.
>
> Lua 5.5.0 (rc1) is now available for testing at
> https://www.lua.org/work/lua-5.5.0-rc1.tar.gz [...]

Have tried building from source on LinuxMint 22.2, using my
GNU/Linux "lglicua" framework. Results are as expected.

Distro: LinuxMint 22.2
Kernel: 6.14.0-35-generic
GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

Lua: lua-5.5.0-rc1
LuaRocks: 3.12.2

Lua compiles and installs without problem (for all four combinations
of {static, dynamic}x{gcc, g++}).

LuaRocks fails (as expected). From previous chat when looking at
the -beta, there is a newer version waiting in the wings (not sure,
but is it written in Rust?). (Hisham noted that things can change
quite a bit between -beta and -rc1, and so any LuaRocks release
would not occur while Lua 5.5.0 was in beta.)

install$ ./i luarocks-install
'LuaRocks entire install' failed (2):
'Configure+make LuaRocks for 'Lua5.5'' failed (1):
'Configure LuaRocks for 5.5' failed (1): Invalid Lua version in flag --lua-version.

----

So far so good, but, personally, I'd like to see LuaRocks working
(and a set of Rocks ported to 5.5) before signing off on a Lua -rc.



cheers, s-b etc.

Roberto Ierusalimschy

unread,
Nov 17, 2025, 8:15:06 AM11/17/25
to lu...@googlegroups.com
> Thanks for Your answer. Sorry for the noise, it appeared not to be hard to test.
> So varargs are <const>, good to have a confirmed answer.

The manual says that:

When present, a vararg table behaves like a read-only local variable
with the given name that is initialized with a table.

and later:

the code behaves as if the function started with the following
statement, assuming the standard behavior of table.pack:

local <const> name = table.pack(...)

-- Roberto

Roberto Ierusalimschy

unread,
Nov 17, 2025, 8:18:03 AM11/17/25
to lu...@googlegroups.com
> Minor: the lua_pushexternalstring documentation does not describe the
> return value of the function.

Thanks for the feedback.

-- Roberto

Andrey Dobrovolsky

unread,
Nov 18, 2025, 9:02:49 AM11/18/25
to lu...@googlegroups.com
Named vararg lists are a very attractive feature in the new Lua
version. Let me speculate a little about it.

"... name" in function declaration supposes the true list of the known
(at the runtime, not during code loading) and fixed size being already
present on the Lua stack, which may wake the full-featured table in
certain circumstances. Those circumstances are described in the
manual.

In the current Lua release candidate such named lists may be used in
the functions' declarations.

But there is one more case, when some true list of the known at the
runtime and fixed size is placed into the Lua stack. It is the case of
another great Lua feature - the function can return the list.

Xmilia Hermit proposed the syntax:

name ...

It supposes that there may be more than one object, behaving like
named varargs "...".

For example

local f = function()
return 1, 2, 3
end

.... t = f() -- declaration of local t <const> = table.pack(f())

where t may behave in the same way as named varargs. In case t will
become the full-featured table, the original function returned values
may be accessed unchanged as "t ...".

Is it possible for these sweet dreams to come true?

Thanks for the exciting Lua improvements, and best regards,
-- Andrew

Matěj Cepl

unread,
Nov 18, 2025, 4:28:09 PM11/18/25
to lu...@googlegroups.com
On Sat Nov 15, 2025 at 2:08 AM CET, Luiz Henrique de Figueiredo wrote:
> Lua 5.5 is the next version of Lua.
>
> Lua 5.5.0 (rc1) is now available for testing at
> https://www.lua.org/work/lua-5.5.0-rc1.tar.gz

Unfortunately failure to build on i586 reported for beta [1] is still
present. Complete log is available on [2].

Best,

Matěj


[1] https://groups.google.com/u/1/g/lua-l/c/So-oIbHbIIE/m/6_GMzGv7AAAJ
[2] https://mcepl.fedorapeople.org/tmp/lua55-rc1-i586-FTBFS-log.txt
--
http://matej.ceplovi.cz/blog/, @mc...@en.osm.town
GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8

If in desperation, read the documentation!
-- Brian D. Ripley, on R-help list

E09FEF25D96484AC.asc
E09FEF25D96484AC.asc
application_pgp-keys_11340884770069117365
signature.asc

Roberto Ierusalimschy

unread,
Nov 19, 2025, 8:48:34 AM11/19/25
to lu...@googlegroups.com
> On Sat Nov 15, 2025 at 2:08 AM CET, Luiz Henrique de Figueiredo wrote:
> > Lua 5.5 is the next version of Lua.
> >
> > Lua 5.5.0 (rc1) is now available for testing at
> > https://www.lua.org/work/lua-5.5.0-rc1.tar.gz
>
> Unfortunately failure to build on i586 reported for beta [1] is still
> present. Complete log is available on [2].

Maybe I am misreading something, but apparently [1] mentions a
Segmentation fault while running strings.lua, while [2] ends with a
regular Lua error while running tpack.lua. Are they really about the
same issue?

-- Roberto

Mitchell

unread,
Nov 24, 2025, 1:19:47 PM11/24/25
to lu...@googlegroups.com
Hi,
I noticed that in the reference manual for `math.random()`, the “range [0,1)” was changed to “range [0, 1)” (insertion of a space), but a couple of sentences later, “math.random(1,n)” still doesn’t have a space before ’n’. Perhaps you’d like to make that change too for consistency.

Thanks for this new release!

Cheers,
Mitchell

Roberto Ierusalimschy

unread,
Nov 24, 2025, 2:51:01 PM11/24/25
to lu...@googlegroups.com
> I noticed that in the reference manual for `math.random()`, the “range [0,1)” was changed to “range [0, 1)” (insertion of a space), but a couple of sentences later, “math.random(1,n)” still doesn’t have a space before ’n’. Perhaps you’d like to make that change too for consistency.

Note that "range [0, 1)" is regular text, while "math.random(1,n)" is code
(written with a different font), so they don't need to be consistent.

However, our code style uses a space after commas in argument lists,
so in the end "math.random(1,n)" should have a space too.

-- Roberto

最萌 小汐

unread,
Nov 27, 2025, 5:37:33 AM11/27/25
to lu...@googlegroups.com
Is this as expected?

```lua
print(_VERSION)

do
    global X = 1
end
global X = 2 -- global 'X' already defined
```


-- sumneko

发件人: lu...@googlegroups.com <lu...@googlegroups.com> 代表 Roberto Ierusalimschy <rob...@inf.puc-rio.br>
发送时间: 2025年11月25日 3:50
收件人: lu...@googlegroups.com <lu...@googlegroups.com>
主题: Re: [ANN] Lua 5.5.0 (rc1) now available
 
--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Luiz Henrique de Figueiredo

unread,
Nov 27, 2025, 8:06:56 AM11/27/25
to lu...@googlegroups.com
> Is this as expected?
> do
> global X = 1
> end
> global X = 2 -- global 'X' already defined

What do you expect from the code below?
do
global X = 1
end
print(X)

最萌 小汐

unread,
Nov 27, 2025, 9:23:35 AM11/27/25
to lu...@googlegroups.com
It should be 1, because there is a global * outside.

Let us see this code:


do
    global <const> X = 1
end

X = 2 — ok. There is no const X
global X = 3 — error: global 'X' already defined . Where is the defined X?


I am really confused by the scope of the global keyword

-- sumneko

发件人: lu...@googlegroups.com <lu...@googlegroups.com> 代表 Luiz Henrique de Figueiredo <l...@tecgraf.puc-rio.br>
发送时间: 2025年11月27日 21:06

收件人: lu...@googlegroups.com <lu...@googlegroups.com>
主题: Re: [ANN] Lua 5.5.0 (rc1) now available
--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Roberto Ierusalimschy

unread,
Nov 27, 2025, 9:43:20 AM11/27/25
to lu...@googlegroups.com
> Let us see this code:
>
> do
> global <const> X = 1
> end
>
> X = 2 — ok. There is no const X
> global X = 3 — error: global 'X' already defined . Where is the defined X?
>
> I am really confused by the scope of the global keyword

Despite the scope of the global keyword, both 'X' refer to the same variable.

The whole point of this error is when you try to define a global already
defined somewhere else. So, as explained in the manual, it does not rely
on visible global definitions, but on the runtime value of the global:

Moreover, for global variables, the initialization will raise a
runtime error if the variable is already defined, that is, it has a
non-nil value.

-- Roberto

最萌 小汐

unread,
Nov 27, 2025, 10:19:38 AM11/27/25
to lu...@googlegroups.com
Thank you for your explanation! I never noticed that this was a runtime error, so it's much clearer now.

-- sumneko

发件人: lu...@googlegroups.com <lu...@googlegroups.com> 代表 Roberto Ierusalimschy <rob...@inf.puc-rio.br>
发送时间: 2025年11月27日 22:43
收件人: lu...@googlegroups.com <lu...@googlegroups.com>
主题: Re: 回复: [ANN] Lua 5.5.0 (rc1) now available
 
--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Matěj Cepl

unread,
Dec 31, 2025, 8:29:20 AM12/31/25
to lu...@googlegroups.com, Matěj Cepl
On 32-bit platforms, the overflow check in `str_rep` (string repetition)
could be bypassed when the repetition count `n` is sufficiently large.
Specifically, the expression `MAX_SIZE / n` could underflow to 0,
allowing allocation attempts that exceed memory limits and causing
segmentation faults.

This patch fixes the issue by:
1. Reordering the overflow check in `str_rep` to safely validate that `n
* (len + lsep)` does not exceed `MAX_SIZE`, avoiding division
underflow.
2. Updating `str_pack` to check total size against `MAX_SIZE` instead of
`LUA_MAXINTEGER`, ensuring the result fits within Lua's string
limits.

This resolves crashes observed on 32-bit architectures (i586, armv7l)
running the `strings.lua` and `tpack.lua` test suites.

Fixes: https://groups.google.com/g/lua-l/c/So-oIbHbIIE/m/6_GMzGv7AAAJ
References: DEC515V1NHA2....@cepl.eu
References: https://groups.google.com/g/lua-l/c/qGKCWe2sLq8/m/tdf3so2dAAAJ
Signed-off-by: Matěj Cepl <mc...@cepl.eu>
---
lstrlib.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lstrlib.c b/lstrlib.c
index 23df839e..c30d0030 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -144,7 +144,7 @@ static int str_rep (lua_State *L) {
if (n <= 0)
lua_pushliteral(L, "");
else if (l_unlikely(len > MAX_SIZE - lsep ||
- cast_st2S(len + lsep) > cast_st2S(MAX_SIZE) / n))
+ (len + lsep > 0 && n > cast_st2S(MAX_SIZE) / cast_st2S(len + lsep))))
return luaL_error(L, "resulting string too large");
else {
size_t totallen = (cast_sizet(n) * (len + lsep)) - lsep;
@@ -1726,7 +1726,7 @@ static int str_packsize (lua_State *L) {
luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1,
"variable-length format");
size += ntoalign; /* total space used by option */
- luaL_argcheck(L, totalsize <= LUA_MAXINTEGER - size,
+ luaL_argcheck(L, totalsize <= MAX_SIZE - size,
1, "format result too large");
totalsize += size;
}
--
2.52.0

Roberto Ierusalimschy

unread,
Dec 31, 2025, 9:28:22 AM12/31/25
to lu...@googlegroups.com, Matěj Cepl
> On 32-bit platforms, the overflow check in `str_rep` (string repetition)
> could be bypassed when the repetition count `n` is sufficiently large.
> Specifically, the expression `MAX_SIZE / n` could underflow to 0,
> allowing allocation attempts that exceed memory limits and causing
> segmentation faults.

There is a note about that in the commit of this code, e3716ee16. The
test is this:

else if (l_unlikely(len > MAX_SIZE - lsep ||
cast_st2S(len + lsep) > cast_st2S(MAX_SIZE) / n))
return luaL_error(L, "resulting string too large");

The first operand of the '||' ensures that the addition len+lsep does
not overflow. Next, if MAX_SIZE / n undeflows to zero, the second operand
of the '||' becomes 'len + lsep > 0', so it will raise an error unless
len+lsep is zero, which implies in both len and lsep being zero.

So, I fail to see how this could exceed memory limits. Could you provide
more information? Thanks,

-- Roberto

Roberto Ierusalimschy

unread,
Dec 31, 2025, 9:54:50 AM12/31/25
to lu...@googlegroups.com
> 2. Updating `str_pack` to check total size against `MAX_SIZE` instead of
> `LUA_MAXINTEGER`, ensuring the result fits within Lua's string
> limits.
> [...]
> - luaL_argcheck(L, totalsize <= LUA_MAXINTEGER - size,
> + luaL_argcheck(L, totalsize <= MAX_SIZE - size,
> 1, "format result too large");
> totalsize += size;

If 'totalsize+size' is larger than MAX_SIZE by smaller than LUA_MAXINTEGER,
the addition will wrap-around (as both totalsize and size are size_t),
giving the wrong result.

The result will fit in Lua's string limits, but for the wrong reasons. :-)

Matěj Cepl

unread,
Dec 31, 2025, 10:07:03 AM12/31/25
to Roberto Ierusalimschy, lu...@googlegroups.com
On Wed Dec 31, 2025 at 3:28 PM CET, Roberto Ierusalimschy wrote:
> So, I fail to see how this could exceed memory limits. Could you provide
> more information? Thanks,

I don’t know what to say: without this patch the test on 32bit
architectures (i586 and arvm7l) still fails (log is still on
https://mcepl.fedorapeople.org/tmp/lua55-rc1-i586-FTBFS-log.txt) and
with it they pass.

Gemini generated for me this report [1], which may be helpful?

This is the `%check` section of my `lua55.spec` [2]:

```
cd testes
pushd libs
%make_build all LUA_DIR=%{_includedir}/lua%{major_version}
cp *.so ..
popd
LD_LIBRARY_PATH=%{_libdir} %{_bindir}/lua%{major_version} all.lua
%endif
```

Is it possible to make running of tests more verbose?

Happy New Year!

Matěj

[1] https://paste.sr.ht/~mcepl/404dd1f515df5168b28989f499fa4e392f4b3552
[2] https://src.opensuse.org/lua/lua55/src/branch/main/lua55.spec
--
http://matej.ceplovi.cz/blog/, @mc...@en.osm.town
GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8

A conclusion is simply the place where someone got tired of
thinking.

E09FEF25D96484AC.asc
signature.asc

Matěj Cepl

unread,
Dec 31, 2025, 10:11:02 AM12/31/25
to Roberto Ierusalimschy, lu...@googlegroups.com
On Wed Dec 31, 2025 at 3:28 PM CET, Roberto Ierusalimschy wrote:
> So, I fail to see how this could exceed memory limits. Could you provide
> more information? Thanks,

Also, perhaps, this tests script generated by Gemini can be helpful?

```lua
local pack = string.pack
local packsize = string.packsize
local unpack = string.unpack

local function checkerror(msg, f, ...)
local status, err = pcall(f, ...)
if status then
print("Expected error '" .. msg .. "', but got success")
return false
end
if not string.find(err, msg) then
print("Expected error containing '" .. msg .. "', but got '" .. err .. "'")
return false
end
return true
end

print("Testing 32-bit fixes...")

-- 1. string.rep overflow check (original issue)
local maxi = math.maxinteger
local status = pcall(string.rep, "aa", maxi // 2 + 10)
if status then
print("FAIL: string.rep should fail for large n")
else
print("PASS: string.rep failed as expected")
end

-- 2. string.rep division by zero check (regression 1)
local status, res = pcall(string.rep, "", 10)
if status and res == "" then
print("PASS: string.rep('', 10) works")
else
print("FAIL: string.rep('', 10) failed: " .. tostring(res))
end

-- 3. string.packsize overflow check (regression 2)
-- simulate 32-bit environment constraints if we are on 64-bit
-- MAX_SIZE on 32-bit is 0xFFFFFFFF (4294967295)
local MAX_SIZE_32 = 4294967295
-- We can't easily force 32-bit behavior on 64-bit build for this specific internal check
-- unless we are actually running on 32-bit or we trigger the logic logic.

-- Test case from tpack.lua that failed on 32-bit
-- This requires crafting a format string that results in size > MAX_SIZE
-- On 64-bit, MAX_SIZE is huge, so this test passes (no error) unless we hit memory limit.
-- But on 32-bit, it hit the overflow check in packsize.

-- If we construct a format string that asks for > MAX_SIZE
-- packsize should error.
local huge_size = MAX_SIZE_32 + 20
-- c<huge_size>
local fmt = string.format("c%d", huge_size)
-- This might fail to create the format string itself if huge_size is too big for string.format?
-- No, %d handles integers.

-- However, on 64-bit, this size is valid.
-- So we can't easily reproduce the 32-bit failure on 64-bit machine.
-- But the fix (changing LUA_MAXINTEGER to MAX_SIZE) is logically correct for 32-bit.

print("Done.")
```

Does it help?

Matěj

--
http://matej.ceplovi.cz/blog/, @mc...@en.osm.town
GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8

Every developer’s hunt for the best editor ends up with Vim,
Emacs or a management position.
-- Rolf Bjaanes

E09FEF25D96484AC.asc
signature.asc

Roberto Ierusalimschy

unread,
Dec 31, 2025, 4:26:01 PM12/31/25
to lu...@googlegroups.com
> On Wed Dec 31, 2025 at 3:28 PM CET, Roberto Ierusalimschy wrote:
> > So, I fail to see how this could exceed memory limits. Could you provide
> > more information? Thanks,
>
> I don’t know what to say: without this patch the test on 32bit
> architectures (i586 and arvm7l) still fails (log is still on
> https://mcepl.fedorapeople.org/tmp/lua55-rc1-i586-FTBFS-log.txt) and
> with it they pass.
>
> Gemini generated for me this report [1], which may be helpful?

The report talks about segmentation fault, but it does not show any
actual segmentation fault hapenning. It also says "memory corruption during
the subsequent memcpy operations", but it does not point to exactly what
subsequent memcpy operation would be corrupting memory.

Are you sure there is some test that generates a seg. fault? Or is it
just some test failing that Gemini decided could corrupt memory?

If it is just a failed test, please read [1], in particular: "The test
suite is not a product; it is just a tool for our internal use." Maybe the
problem is with the test, not with Lua.

[1] https://www.lua.org/tests/

-- Roberto

Roberto Ierusalimschy

unread,
Jan 1, 2026, 11:53:56 AMJan 1
to Matěj Cepl, lu...@googlegroups.com
Gemini says:

https://paste.sr.ht/~mcepl/404dd1f515df5168b28989f499fa4e392f4b3552
"Segmentation fault in strings.lua"

However, the log you sent [2] shows nothing wrong with strings.lua.

https://mcepl.fedorapeople.org/tmp/lua55-rc1-i586-FTBFS-log.txt
[ 14s] .testing strings and string library
[ 14s] testing 'format %a %A'
[ 14s] 'collate' locale not found
[ 14s] 'ctype' locale not found
[ 14s]
[ 14s] >>> testC not active: skipping 'pushfstring' tests <<<
[ 14s]
[ 14s]
[ 14s] >>> testC not active: skipping external strings tests <<<
[ 14s]
[ 14s] OK

This looks like some kind of allucination.


> A conclusion is simply the place where someone got tired of
> thinking.

That explains a lot.

-- Roberto

Matěj Cepl

unread,
Jan 6, 2026, 6:21:37 AMJan 6
to lu...@googlegroups.com
On Thu Jan 1, 2026 at 5:53 PM CET, Roberto Ierusalimschy wrote:
> This looks like some kind of allucination.

What is not hallucination is what without the patch the test suite fails
on 32bit archs, and it doesn’t with it.

Let me add to the previous collection of New Year wishes with this one
in Czech:

Přeji Vám Všem šťastný nový rok 2026!

Matěj

--
http://matej.ceplovi.cz/blog/, @mc...@en.osm.town
GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8

Find the dependencies -- and eliminate them.
-- according to http://is.gd/oeYpcI the motto of the MS Excel team

E09FEF25D96484AC.asc
signature.asc

Roberto Ierusalimschy

unread,
Jan 6, 2026, 7:56:42 AMJan 6
to lu...@googlegroups.com
> On Thu Jan 1, 2026 at 5:53 PM CET, Roberto Ierusalimschy wrote:
> > This looks like some kind of allucination.
>
> What is not hallucination is what without the patch the test suite fails
> on 32bit archs, and it doesn’t with it.

The patch was "two-in-one", one patch for two bugs. More exactly,
the patch changed two functions, one for each bug.

One bug was real (but did not cause any seg. fault, only a failed test),
the other was the allucination. Without the patch, the real bug failed
the suite.

-- Roberto

Matěj Cepl

unread,
Jan 7, 2026, 4:21:42 AMJan 7
to lu...@googlegroups.com
On Tue Jan 6, 2026 at 1:56 PM CET, Roberto Ierusalimschy wrote:
> One bug was real (but did not cause any seg. fault, only a failed test),
> the other was the allucination. Without the patch, the real bug failed
> the suite.

That's possible. Thank you, so we can call this patch submission
resolved?

Matěj

--
http://matej.ceplovi.cz/blog/, @mc...@en.osm.town
GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8

music was alot better when ugly people were allowed to make it.
-- Comment under “Locomotive Breath” by Jethro Tull

E09FEF25D96484AC.asc
signature.asc
Reply all
Reply to author
Forward
0 new messages