diff --git a/client/space_lua/runtime.ts b/client/space_lua/runtime.ts index a947055f..55ac2f08 100644 --- a/client/space_lua/runtime.ts +++ b/client/space_lua/runtime.ts @@ -927,6 +927,20 @@ export class LuaTable implements ILuaSettable, ILuaGettable { const errSf = sf || LuaStackFrame.lostFrame; const ctx = sf?.astCtx ?? EMPTY_CTX; + if (key === null || key === undefined) { + throw new LuaRuntimeError( + "table index is nil", + errSf, + ); + } + + if (typeof key === "number" && isNaN(key)) { + throw new LuaRuntimeError( + "table index is NaN", + errSf, + ); + } + if (this.has(key)) { return this.rawSet(key, value, numType); } diff --git a/client/space_lua/stdlib/table_test.lua b/client/space_lua/stdlib/table_test.lua index 38db391b..8ef17920 100644 --- a/client/space_lua/stdlib/table_test.lua +++ b/client/space_lua/stdlib/table_test.lua @@ -63,6 +63,38 @@ do assertError(function() table.remove(t, #t + 2) end) end +-- nil and NaN keys are forbidden +do + -- nil key at assignment + assertError(function() + local t = {} + t[nil] = "foo" + end) + + -- nil key in table constructor (dynamic field) + assertError(function() + local k = nil + local _ = { [k] = "foo" } + end) + + -- NaN key at assignment + assertError(function() + local t = {} + t[0/0] = "bar" + end) + + -- NaN key in table constructor + assertError(function() + local _ = { [0/0] = "bar" } + end) + + -- reading with nil key is also an error + assertError(function() + local t = {} + return t[nil] + end) +end + -- Reference semantics / aliasing (tables are mutable references) do local a = { x = 1 }