|
| 1 | +#!/usr/bin/env tarantool |
| 2 | + |
| 3 | +tap = require('tap') |
| 4 | + |
| 5 | +test = tap.test("494") |
| 6 | +test:plan(1) |
| 7 | + |
| 8 | +-- Test file to demonstrate Lua table hash chain bugs discussed in |
| 9 | +-- https://github.com/LuaJIT/LuaJIT/issues/494 |
| 10 | +-- Credit: prepared by Peter Cawley here with minor edits: |
| 11 | +-- https://gist.github.com/corsix/1fc9b13a2dd5f3659417b62dd54d4500 |
| 12 | + |
| 13 | +--- Plumbing |
| 14 | +ffi = require"ffi" |
| 15 | +ffi.cdef"char* strstr(const char*, const char*)" |
| 16 | +strstr = ffi.C.strstr |
| 17 | +cast = ffi.cast |
| 18 | +str_hash_offset = cast("uint32_t*", strstr("*", ""))[-2] == 1 and 3 or 2 |
| 19 | +function str_hash(s) |
| 20 | + return cast("uint32_t*", strstr(s, "")) - str_hash_offset |
| 21 | +end |
| 22 | +table_new = require"table.new" |
| 23 | + |
| 24 | +--- Prepare some objects |
| 25 | +victims = {} |
| 26 | +orig_hash = {} |
| 27 | +for c in ("abcdef"):gmatch"." do |
| 28 | + v = c .. "{09add58a-13a4-44e0-a52c-d44d0f9b2b95}" |
| 29 | + victims[c] = v |
| 30 | + orig_hash[c] = str_hash(v)[0] |
| 31 | +end |
| 32 | +collectgarbage() |
| 33 | + |
| 34 | +do --- Basic version of the problem |
| 35 | + for k, v in pairs(victims) do |
| 36 | + str_hash(v)[0] = 0 |
| 37 | + end |
| 38 | + t = table_new(0, 8) |
| 39 | + -- Make chain a -> b -> c -> d, all with a as primary |
| 40 | + t[victims.a] = true |
| 41 | + t[victims.d] = true |
| 42 | + t[victims.c] = true |
| 43 | + t[victims.b] = true |
| 44 | + -- Change c's primary to b, and d's primary to c |
| 45 | + t[victims.d] = nil |
| 46 | + t[victims.c] = nil |
| 47 | + str_hash(victims.c)[0] = 5 |
| 48 | + str_hash(victims.d)[0] = 6 |
| 49 | + t[victims.c] = true |
| 50 | + t[victims.d] = true |
| 51 | + -- Insert something with b as primary |
| 52 | + str_hash(victims.e)[0] = 5 |
| 53 | + t[victims.e] = true |
| 54 | + -- Check for consistency |
| 55 | + for c in ("abcde"):gmatch"." do |
| 56 | + assert(t[victims[c]], c) |
| 57 | + end |
| 58 | +end |
| 59 | +collectgarbage() |
| 60 | + |
| 61 | +do --- Just `mn != freenode` can lead to infinite loops |
| 62 | + for k, v in pairs(victims) do |
| 63 | + str_hash(v)[0] = 0 |
| 64 | + end |
| 65 | + t = table_new(0, 8) |
| 66 | + -- Make chain a -> b -> c -> d, all with a as primary |
| 67 | + t[victims.a] = true |
| 68 | + t[victims.d] = true |
| 69 | + t[victims.c] = true |
| 70 | + t[victims.b] = true |
| 71 | + -- Change c's primary to b, and d's primary to d |
| 72 | + t[victims.d] = nil |
| 73 | + t[victims.c] = nil |
| 74 | + str_hash(victims.c)[0] = 5 |
| 75 | + str_hash(victims.d)[0] = 7 |
| 76 | + t[victims.c] = true |
| 77 | + t[victims.d] = true |
| 78 | + -- Insert something with b as primary |
| 79 | + str_hash(victims.e)[0] = 5 |
| 80 | + t[victims.e] = true |
| 81 | + -- Insert something with d as primary (infinite lookup loop) |
| 82 | + str_hash(victims.f)[0] = 7 |
| 83 | + t[victims.f] = true |
| 84 | +end |
| 85 | +collectgarbage() |
| 86 | + |
| 87 | +do --- Just `mn != nn` can lead to infinite loops |
| 88 | + for k, v in pairs(victims) do |
| 89 | + str_hash(v)[0] = 0 |
| 90 | + end |
| 91 | + t = table_new(0, 8) |
| 92 | + -- Make chain a -> b -> c -> d -> e, all with a as primary |
| 93 | + t[victims.a] = true |
| 94 | + t[victims.e] = true |
| 95 | + t[victims.d] = true |
| 96 | + t[victims.c] = true |
| 97 | + t[victims.b] = true |
| 98 | + -- Change c's primary to b, d's primary to d, and e's primary to d |
| 99 | + t[victims.e] = nil |
| 100 | + t[victims.d] = nil |
| 101 | + t[victims.c] = nil |
| 102 | + str_hash(victims.c)[0] = 4 |
| 103 | + str_hash(victims.d)[0] = 6 |
| 104 | + str_hash(victims.e)[0] = 6 |
| 105 | + t[victims.c] = true |
| 106 | + t[victims.d] = true |
| 107 | + t[victims.e] = true |
| 108 | + -- Insert something with b as primary (infinite rechaining loop) |
| 109 | + str_hash(victims.f)[0] = 4 |
| 110 | + t[victims.f] = true |
| 111 | +end |
| 112 | + |
| 113 | +for i = 0, 10 do --- Non-strings can need rechaining too |
| 114 | + collectgarbage() |
| 115 | + |
| 116 | + k = tonumber((("0x%xp-1074"):format(i))) |
| 117 | + str_hash(victims.a)[0] = 0 |
| 118 | + str_hash(victims.b)[0] = 0 |
| 119 | + t = table_new(0, 4) |
| 120 | + -- a -> b, both with a as primary |
| 121 | + t[victims.a] = true |
| 122 | + t[victims.b] = true |
| 123 | + -- Change b's primary to b |
| 124 | + t[victims.b] = nil |
| 125 | + str_hash(victims.b)[0] = 3 |
| 126 | + t[victims.b] = true |
| 127 | + -- Might get a -> b -> k, with k's primary as b |
| 128 | + t[k] = true |
| 129 | + -- Change b's primary to a |
| 130 | + t[victims.b] = nil |
| 131 | + str_hash(victims.b)[0] = 0 |
| 132 | + t[victims.b] = true |
| 133 | + -- Insert something with b as primary |
| 134 | + str_hash(victims.c)[0] = 3 |
| 135 | + t[victims.c] = true |
| 136 | + -- Check for consistency |
| 137 | + assert(t[k], i) |
| 138 | +end |
| 139 | + |
| 140 | +for i = 0, 10 do --- Non-strings can be moved to freenode |
| 141 | + collectgarbage() |
| 142 | + |
| 143 | + k = false |
| 144 | + str_hash(victims.a)[0] = 0 |
| 145 | + str_hash(victims.b)[0] = 0 |
| 146 | + t = table_new(0, 4) |
| 147 | + -- a -> k -> b, all with a as primary |
| 148 | + t[victims.a] = true |
| 149 | + t[victims.b] = true |
| 150 | + t[k] = true |
| 151 | + -- Change b's primary to k |
| 152 | + t[victims.b] = nil |
| 153 | + str_hash(victims.b)[0] = 2 |
| 154 | + t[victims.b] = true |
| 155 | + -- Insert a non-string with primary of k |
| 156 | + t[tonumber((("0x%xp-1074"):format(i)))] = true |
| 157 | + -- Check for consistency |
| 158 | + assert(t[victims.b], i) |
| 159 | +end |
| 160 | +collectgarbage() |
| 161 | + |
| 162 | +do --- Do not forget to advance freenode in the not-string case |
| 163 | + t = table_new(0, 4) |
| 164 | + -- Chain of colliding numbers |
| 165 | + t[0x0p-1074] = true |
| 166 | + t[0x4p-1074] = true |
| 167 | + t[0x8p-1074] = true |
| 168 | + -- Steal middle node of the chain to be a main node (infinite walking loop) |
| 169 | + t[0x2p-1074] = true |
| 170 | +end |
| 171 | +collectgarbage() |
| 172 | + |
| 173 | +--- Restore interpreter invariants, just in case |
| 174 | +for c, v in pairs(victims) do |
| 175 | + str_hash(v)[0] = orig_hash[c] |
| 176 | +end |
| 177 | + |
| 178 | +test:ok("PASS") |
0 commit comments