Skip to content

Commit 30a9aa3

Browse files
Mike Palligormunkin
Mike Pall
authored andcommitted
LJ_GC64: Fix lua_concat().
Reported by Mathias Westerdahl. (cherry picked from commit 633f265) Lua 5.1 Reference Manual [1] defines a function `lua_concat`, that: > void lua_concat (lua_State *L, int n); > > Concatenates the n values at the top of the stack, pops them, and leaves > the result at the top. Without the patch, `lua_concat()` behaved incorrectly with userdata with the defined `__concat` metamethod. The problem is GC64-specific. Assuming we have three literals and a userdata with the defined `__concat` metamethod on top of the Lua stack: 1 [string] 2 [string] 3 [string] 4 [string] 5 [userdata] <--- top On attempt to concatenate *two* items on top of the Lua stack, `lua_concat()` concatenates *four* items and leaves the result on top of the Lua stack: 1 [string] 2 [string][string][string][userdata] <--- top The problem is in the incorrect calculation of `n` counter in the loop in the implementation of function `lua_concat`. Without the fix, `n` is equal to 3 at the end of the first iteration, and therefore it goes to the next iteration of concatenation. In the fixed implementation of `lua_concat()`, `n` is equal to 1 at the end of the first loop iteration, decremented in a loop postcondition, and breaks the loop. For details see implementation of `lj_meta_cat()` in <src/lj_meta.c>. The patch fixes incorrect behaviour. 1. https://www.lua.org/manual/5.1/manual.html Sergey Bronnikov: * added the description and the test for the problem Part of tarantool/tarantool#9145 Reviewed-by: Maxim Kokryashkin <[email protected]> Reviewed-by: Sergey Kaplun <[email protected]> Signed-off-by: Igor Munkin <[email protected]> (cherry picked from commit 3526ebb)
1 parent eb7cf72 commit 30a9aa3

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/lj_api.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ LUA_API void lua_concat(lua_State *L, int n)
801801
L->top -= n;
802802
break;
803803
}
804-
n -= (int)(L->top - top);
804+
n -= (int)(L->top - (top - 2*LJ_FR2));
805805
L->top = top+2;
806806
jit_secure_call(L, top, 1+1);
807807
L->top -= 1+LJ_FR2;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include <string.h>
2+
3+
#include "lua.h"
4+
#include "lauxlib.h"
5+
6+
#include "test.h"
7+
#include "utils.h"
8+
9+
/*
10+
* This test demonstrates LuaJIT's incorrect behaviour when
11+
* calling `lua_concat()` with userdata with the __concat metamethod.
12+
* See https://github.com/LuaJIT/LuaJIT/issues/881 for details.
13+
*/
14+
15+
#define TYPE_NAME "int"
16+
17+
#define TEST_VALUE 100
18+
#define TOSTR(s) #s
19+
#define CONCAT(A, B) A TOSTR(B)
20+
21+
static int __concat(lua_State *L)
22+
{
23+
const char *s = luaL_checkstring(L, 1);
24+
int *n = (int *)luaL_checkudata(L, 2, TYPE_NAME);
25+
/* Do non-default concatenation. */
26+
lua_pushfstring(L, "%s + %d", s, *n);
27+
return 1;
28+
}
29+
30+
static const luaL_Reg mt[] = {
31+
{ "__concat", __concat },
32+
{ NULL, NULL}
33+
};
34+
35+
static int lua_concat_testcase(void *test_state)
36+
{
37+
/* Setup. */
38+
lua_State *L = test_state;
39+
const int top = 4;
40+
41+
/* Create metatable and put it to the Lua registry. */
42+
luaL_newmetatable(L, TYPE_NAME);
43+
/* Fill metatable. */
44+
luaL_register(L, 0, mt);
45+
lua_pop(L, 1);
46+
47+
assert_int_equal(lua_gettop(L), 0);
48+
49+
lua_pushliteral(L, "C");
50+
lua_pushliteral(L, "B");
51+
lua_pushliteral(L, "A");
52+
53+
int *n = (int *)lua_newuserdata(L, sizeof(*n));
54+
*n = 100;
55+
56+
luaL_getmetatable(L, TYPE_NAME);
57+
lua_setmetatable(L, -2);
58+
59+
assert_int_equal(lua_gettop(L), top);
60+
61+
/* Test body. */
62+
63+
/*
64+
* void lua_concat (lua_State *L, int n);
65+
*
66+
* Concatenates the n values at the top of the stack,
67+
* pops them, and leaves the result at the top. If n is 1,
68+
* the result is the single value on the stack; if n is 0,
69+
* the result is the empty string [1].
70+
*
71+
* 1. https://www.lua.org/manual/5.1/manual.html
72+
*/
73+
74+
/* Concatenate two elements on the top. */
75+
lua_concat(L, 2);
76+
77+
const char *str = lua_tostring(L, -1);
78+
assert_int_equal(lua_gettop(L), top - 2 + 1);
79+
const char expected_str[] = CONCAT("A + ", TEST_VALUE);
80+
assert_str_equal(str, expected_str);
81+
82+
/* Teardown. */
83+
lua_settop(L, 0);
84+
85+
return TEST_EXIT_SUCCESS;
86+
}
87+
88+
int main(void)
89+
{
90+
lua_State *L = utils_lua_init();
91+
const struct test_unit tgroup[] = {
92+
test_unit_def(lua_concat_testcase),
93+
};
94+
const int test_result = test_run_group(tgroup, L);
95+
utils_lua_close(L);
96+
97+
return test_result;
98+
}

0 commit comments

Comments
 (0)