Skip to content

Commit 86389f8

Browse files
Mike PallBuristan
Mike Pall
authored andcommitted
Prevent loop in snap_usedef().
Reported by XmiliaH. (cherry picked from commit 0e66fc9) It is possible to get an infinite loop in a function `snap_usedef` when a `UCLO` makes a tight loop. This infinite loop could happen when `snap_usedef()` called during trace recording (more precisely, on the creation of the snapshot for the guarded trace check) processes UCLO bytecode instruction, and this instruction attempts a jump back with a negative offset value. The patch fixes the problem by checking a number of slots in a jump argument and replacing this value by `maxslot` if a value is negative, this means that no values will be purged from the snapshot. Sergey Bronnikov: * added the description and the test for the problem Part of tarantool/tarantool#10709 Reviewed-by: Sergey Kaplun <[email protected]> Signed-off-by: Sergey Kaplun <[email protected]> (cherry picked from commit baa7554)
1 parent 02b66d8 commit 86389f8

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

src/lj_snap.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,12 @@ static BCReg snap_usedef(jit_State *J, uint8_t *udf,
251251
BCReg minslot = bc_a(ins);
252252
if (op >= BC_FORI && op <= BC_JFORL) minslot += FORL_EXT;
253253
else if (op >= BC_ITERL && op <= BC_JITERL) minslot += bc_b(pc[-2])-1;
254-
else if (op == BC_UCLO) { pc += bc_j(ins); break; }
254+
else if (op == BC_UCLO) {
255+
ptrdiff_t delta = bc_j(ins);
256+
if (delta < 0) return maxslot; /* Prevent loop. */
257+
pc += delta;
258+
break;
259+
}
255260
for (s = minslot; s < maxslot; s++) DEF_SLOT(s);
256261
return minslot < maxslot ? minslot : maxslot;
257262
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
local tap = require('tap')
2+
-- Test file to demonstrate the infinite loop in LuaJIT during the
3+
-- use-def analysis for upvalues.
4+
-- See details in https://github.com/LuaJIT/LuaJIT/issues/736.
5+
local test = tap.test('lj-736-BC_UCLO-triggers-infinite-loop'):skipcond({
6+
['Test requires JIT enabled'] = not jit.status(),
7+
})
8+
test:plan(2)
9+
10+
-- Before the patch, the code flow like in the `testcase()` below
11+
-- may cause the problem -- use-def analysis for the 0019 UCLO
12+
-- creates an infinite loop in 0014 - 0019:
13+
-- | 0008 FORI base: 4 jump: => 0013
14+
-- | 0009 ISNEN var: 7 num: 0 ; number 2
15+
-- | 0010 JMP rbase: 8 jump: => 0012
16+
-- | 0011 UCLO rbase: 2 jump: => 0014
17+
-- | 0012 FORL base: 4 jump: => 0009
18+
-- | 0013 UCLO rbase: 2 jump: => 0014
19+
-- | 0014 KPRI dst: 2 pri: 0 ; Start of `assert()` line.
20+
-- | ...
21+
-- | 0019 UCLO rbase: 2 jump: => 0014
22+
23+
jit.opt.start('hotloop=1')
24+
25+
local assert_msg = 'Infinite loop is not reproduced.'
26+
local assert = assert
27+
28+
local function testcase()
29+
-- The code in the first scope `do`/`end` is a prerequisite.
30+
-- It contains the UCLO instruction for the `uv1`. The use-def
31+
-- analysis for it escapes this `do`/`end` scope.
32+
do
33+
local uv1 -- luacheck: no unused
34+
local _ = function() return uv1 end
35+
36+
-- Records the trace for which use-def analysis is applied.
37+
for i = 1, 2 do
38+
-- This condition triggers snapshoting and use-def analysis.
39+
-- Before the patch this triggers the infinite loop in the
40+
-- `snap_usedef()`, so the `goto` is never taken.
41+
if i == 2 then
42+
goto x
43+
end
44+
end
45+
end
46+
47+
::x::
48+
do
49+
local uv2 -- luacheck: no unused
50+
51+
-- Create a tight loop for the one more upvalue (`uv2`).
52+
-- Before the patch, use-def analysis gets stuck in this code
53+
-- flow.
54+
assert(nil, assert_msg)
55+
goto x
56+
-- This code is unreachable by design.
57+
local _ = function() return uv2 end -- luacheck: ignore
58+
end
59+
end
60+
61+
local ok, err = pcall(testcase)
62+
63+
test:is(ok, false, 'assertion is triggered in a function with testcase')
64+
test:ok(err:match(assert_msg), 'BC_UCLO does not trigger an infinite loop')
65+
66+
test:done(true)

0 commit comments

Comments
 (0)