Skip to content

Commit e3fcf2c

Browse files
Mike Palligormunkin
Mike Pall
authored andcommitted
Add NaN check to IR_NEWREF.
Thanks to Peter Cawley. (cherry-picked from commit 7f9907b) When emitting IR NEWREF, there is no check for a non-NaN stored key value. Thus, when the NaN number value is given to trace, it may be stored as a key. This patch adds the corresponding check. If fold optimization is enabled, this IR EQ check is dropped if it references CONV IR from any (unsigned) integer type since NaN can be created via conversion from an integer. Sergey Kaplun: * added the description and the test for the problem Part of tarantool/tarantool#9145
1 parent 3c8dbe2 commit e3fcf2c

File tree

3 files changed

+166
-4
lines changed

3 files changed

+166
-4
lines changed

src/lj_opt_fold.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -1936,7 +1936,10 @@ LJFOLD(NE any any)
19361936
LJFOLDF(comm_equal)
19371937
{
19381938
/* For non-numbers only: x == x ==> drop; x ~= x ==> fail */
1939-
if (fins->op1 == fins->op2 && !irt_isnum(fins->t))
1939+
if (fins->op1 == fins->op2 &&
1940+
(!irt_isnum(fins->t) ||
1941+
(fleft->o == IR_CONV && /* Converted integers cannot be NaN. */
1942+
(uint32_t)(fleft->op2 & IRCONV_SRCMASK) - (uint32_t)IRT_I8 <= (uint32_t)(IRT_U64 - IRT_U8))))
19401943
return CONDFOLD(fins->o == IR_EQ);
19411944
return fold_comm_swap(J);
19421945
}

src/lj_record.c

+9-3
Original file line numberDiff line numberDiff line change
@@ -1509,10 +1509,16 @@ TRef lj_record_idx(jit_State *J, RecordIndex *ix)
15091509
lj_assertJ(!hasmm, "inconsistent metamethod handling");
15101510
if (oldv == niltvg(J2G(J))) { /* Need to insert a new key. */
15111511
TRef key = ix->key;
1512-
if (tref_isinteger(key)) /* NEWREF needs a TValue as a key. */
1512+
if (tref_isinteger(key)) { /* NEWREF needs a TValue as a key. */
15131513
key = emitir(IRTN(IR_CONV), key, IRCONV_NUM_INT);
1514-
else if (tref_isnumber(key) && tref_isk(key) && tvismzero(&ix->keyv))
1515-
key = lj_ir_knum_zero(J); /* Canonicalize -0.0 to +0.0. */
1514+
} else if (tref_isnum(key)) {
1515+
if (tref_isk(key)) {
1516+
if (tvismzero(&ix->keyv))
1517+
key = lj_ir_knum_zero(J); /* Canonicalize -0.0 to +0.0. */
1518+
} else {
1519+
emitir(IRTG(IR_EQ, IRT_NUM), key, key); /* Check for !NaN. */
1520+
}
1521+
}
15161522
xref = emitir(IRT(IR_NEWREF, IRT_PGC), ix->tab, key);
15171523
keybarrier = 0; /* NEWREF already takes care of the key barrier. */
15181524
#ifdef LUAJIT_ENABLE_TABLE_BUMP
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
local tap = require('tap')
2+
3+
-- Test file to demonstrate LuaJIT misbehaviour for NEWREF IR
4+
-- taken NaN value.
5+
-- See also, https://github.com/LuaJIT/LuaJIT/issues/1069.
6+
7+
local test = tap.test('lj-1069-newref-nan-key'):skipcond({
8+
['Test requires JIT enabled'] = not jit.status(),
9+
})
10+
11+
local table_new = require('table.new')
12+
local ffi = require('ffi')
13+
14+
local NaN = tonumber('nan')
15+
16+
test:plan(4)
17+
18+
test:test('NaN on trace in the non-constant IR', function(subtest)
19+
local NKEYS = 3
20+
21+
-- XXX: NaN isn't stored, so the number of tests is:
22+
-- (NKEYS - 1) + test status + test error message + test keys
23+
-- amount.
24+
subtest:plan(NKEYS + 2)
25+
26+
local tset_table = table_new(0, NKEYS)
27+
28+
local function tset(t, k)
29+
-- Value doesn't matter.
30+
t[k] = true
31+
end
32+
33+
-- Compile the function.
34+
jit.opt.start('hotloop=1')
35+
36+
-- Use number keys to emit NEWREF.
37+
tset(tset_table, 0.1)
38+
tset(tset_table, 0.2)
39+
40+
-- Insert NaN on the trace.
41+
local ok, err = pcall(tset, tset_table, NaN)
42+
43+
subtest:ok(not ok, 'function returns an error')
44+
subtest:like(err, 'table index is NaN', 'correct error message')
45+
46+
local nkeys = 0
47+
for k in pairs(tset_table) do
48+
nkeys = nkeys + 1
49+
subtest:ok(k == k, ('not NaN key by number %d'):format(nkeys))
50+
end
51+
subtest:is(nkeys, NKEYS - 1, 'correct amount of keys')
52+
end)
53+
54+
test:test('NaN on trace in the non-constant IR CONV', function(subtest)
55+
-- XXX: simplify `jit.dump()` output.
56+
local tonumber = tonumber
57+
58+
local NKEYS = 3
59+
60+
-- XXX: NaN isn't stored, so the number of tests is:
61+
-- (NKEYS - 1) + test status + test error message + test keys
62+
-- amount.
63+
subtest:plan(NKEYS + 2)
64+
65+
local tset_table = table_new(0, NKEYS)
66+
67+
local function tset(t, k)
68+
-- XXX: Emit CONV to number type. Value doesn't matter.
69+
t[tonumber(k)] = true
70+
end
71+
72+
-- Compile the function.
73+
jit.on()
74+
jit.opt.start('hotloop=1')
75+
76+
-- Use number keys to emit NEWREF.
77+
tset(tset_table, ffi.new('float', 0.1))
78+
tset(tset_table, ffi.new('float', 0.2))
79+
80+
-- Insert NaN on the trace.
81+
local ok, err = pcall(tset, tset_table, ffi.new('float', NaN))
82+
83+
subtest:ok(not ok, 'function returns an error')
84+
subtest:like(err, 'table index is NaN', 'correct error message')
85+
86+
local nkeys = 0
87+
for k in pairs(tset_table) do
88+
nkeys = nkeys + 1
89+
subtest:ok(k == k, ('not NaN key by number %d'):format(nkeys))
90+
end
91+
subtest:is(nkeys, NKEYS - 1, 'correct amount of keys')
92+
end)
93+
94+
-- Test the constant IR NaN value on the trace.
95+
96+
test:test('constant NaN on the trace', function(subtest)
97+
-- Test the status and the error message.
98+
subtest:plan(2)
99+
local function protected()
100+
local counter = 0
101+
-- Use a number key to emit NEWREF.
102+
local key = 0.1
103+
104+
jit.opt.start('hotloop=1')
105+
106+
while counter < 2 do
107+
counter = counter + 1
108+
-- luacheck: ignore
109+
local tab = {_ = 'unused'}
110+
tab[key] = 'NaN'
111+
-- XXX: Use the constant NaN value on the trace.
112+
key = 0/0
113+
end
114+
end
115+
116+
local ok, err = pcall(protected)
117+
subtest:ok(not ok, 'function returns an error')
118+
subtest:like(err, 'table index is NaN', 'NaN index error message')
119+
end)
120+
121+
test:test('constant NaN on the trace and rehash of the table', function(subtest)
122+
-- A little bit different test case: after rehashing the table,
123+
-- the node is lost, and a hash part of the table isn't freed.
124+
-- This leads to the assertion failure, which checks memory
125+
-- leaks on shutdown.
126+
-- XXX: The test checks didn't fail even before the patch. Check
127+
-- the same values as in the previous test for consistency.
128+
subtest:plan(2)
129+
local function protected()
130+
local counter = 0
131+
-- Use a number key to emit NEWREF.
132+
local key = 0.1
133+
134+
jit.opt.start('hotloop=1')
135+
136+
while counter < 2 do
137+
counter = counter + 1
138+
-- luacheck: ignore
139+
local tab = {_ = 'unused'}
140+
tab[key] = 'NaN'
141+
-- Rehash the table.
142+
tab[counter] = 'unused'
143+
-- XXX: Use the constant NaN value on the trace.
144+
key = 0/0
145+
end
146+
end
147+
148+
local ok, err = pcall(protected)
149+
subtest:ok(not ok, 'function returns an error')
150+
subtest:like(err, 'table index is NaN', 'NaN index error message')
151+
end)
152+
153+
test:done(true)

0 commit comments

Comments
 (0)