From 39a60158da75bbf7b718902347200ff6406cb944 Mon Sep 17 00:00:00 2001 From: snowyukitty <270071858+snowyukitty@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:30:40 +0900 Subject: [PATCH] compile: fix panic and silent corruption when assigning to a field of a non-table value The object of an assignment target (the `t` in `t.k = v`) becomes operand A of OP_SETTABLE/OP_SETTABLEKS, which the VM always reads as a register R(A). But compileAssignStmtLeft compiled that object with KMV propagation, which folds a constant object such as ("s").k = v or (5).x = v into an RK (constant) operand. Its truncated index then aliases an unrelated register, so the assignment either silently wrote to the wrong value or, when that register was unset, panicked with a nil pointer dereference (e.g. after debug.setmetatable("string", {})). Compile the object with MV propagation instead, matching the read path, so operand A is always a materialised register and the existing "attempt to index a non-table object" error is raised with the correct type. Fixes #524 --- _glua-tests/issues.lua | 34 ++++++++++++++++++++++++++++++++++ compile.go | 8 +++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/_glua-tests/issues.lua b/_glua-tests/issues.lua index 6d6343ab..5af8d2f9 100644 --- a/_glua-tests/issues.lua +++ b/_glua-tests/issues.lua @@ -490,3 +490,37 @@ function test() assert(b == nil) end test() + +-- issue #524 +-- Assigning to a field or index of a non-table value must raise a Lua error, +-- never panic the VM or silently corrupt an unrelated value. The object of an +-- assignment target is operand A of OP_SETTABLE(KS), which the VM reads as a +-- register; a constant object such as ("s").k = v used to be folded into an RK +-- operand that aliased another register. +function test() + -- (1) silent corruption: the write must not fall through into a nearby local. + -- Compiled as a top-level chunk to exercise the register layout in which the + -- bug corrupted rather than erroring. + local chunk = assert(loadstring("local t = {}; (5).field = 1; return t")) + local ok = pcall(chunk) + assert(not ok, "assigning a field of a number must error, not corrupt a local") + + -- (2) VM panic: the reported crash, with the string metatable replaced by one + -- that has no __newindex. + local savedmt = debug.getmetatable("") + local ok2, msg2 = pcall(assert(loadstring([[debug.setmetatable("", {}); ("Lua").key = 1]]))) + debug.setmetatable("", savedmt) + assert(not ok2) + assert(string.find(msg2, "attempt to index a non%-table object"), msg2) + + -- (3) the raised error reports the correct type of the indexed value. + local ok3, msg3 = pcall(function() (5).field = 1 end) + assert(not ok3) + assert(string.find(msg3, "non%-table object%(number%)"), msg3) + + -- (4) a valid parenthesised table assignment target keeps working. + local t = {} + ;(t).x = 1 + assert(t.x == 1) +end +test() diff --git a/compile.go b/compile.go index f9fbf576..ef5d564a 100644 --- a/compile.go +++ b/compile.go @@ -705,7 +705,13 @@ func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []* acs = append(acs, &assigncontext{ec, 0, 0, false, false}) case *ast.AttrGetExpr: ac := &assigncontext{&expcontext{ecTable, regNotDefined, 0}, 0, 0, false, false} - compileExprWithKMVPropagation(context, st.Object, ®, &ac.ec.reg) + // The assignment target object becomes operand A of OP_SETTABLE(KS), + // which the VM always reads as a register R(A), never as a constant. + // Use MV (not KMV) propagation: KMV would fold a constant object such + // as ("s").k = v into an RK operand, whose truncated index then aliases + // an unrelated register - silently writing to the wrong value, or + // panicking when that register is unset. This mirrors the read path. + compileExprWithMVPropagation(context, st.Object, ®, &ac.ec.reg) ac.keyrk = reg reg += compileExpr(context, reg, st.Key, ecnone(0)) if _, ok := st.Key.(*ast.StringExpr); ok {