summaryrefslogtreecommitdiff
path: root/tests/modules/for_loop/test.lua
diff options
context:
space:
mode:
authorruki <[email protected]>2026-04-15 00:45:16 +0800
committerruki <[email protected]>2026-04-15 00:45:16 +0800
commit088cae88b4600bde070c73b9e218712195fe77ee (patch)
treeb74497d98c3ac90ce1182ca3fdc69c5cbf188d01 /tests/modules/for_loop/test.lua
parent56b93f6181438ace9764d280b5d906d75cfc5883 (diff)
improve test
Diffstat (limited to 'tests/modules/for_loop/test.lua')
-rw-r--r--tests/modules/for_loop/test.lua50
1 files changed, 35 insertions, 15 deletions
diff --git a/tests/modules/for_loop/test.lua b/tests/modules/for_loop/test.lua
index 926d7e700..231337a29 100644
--- a/tests/modules/for_loop/test.lua
+++ b/tests/modules/for_loop/test.lua
@@ -1,9 +1,34 @@
--- Regression tests for Lua 5.4 for-loop customizations in xmake:
--- 1. RDKCONST is relaxed on the control variable (parser patch), so
--- reassigning `i` in a numeric-for or `k` in a generic-for compiles.
--- 2. Sandbox `pairs` snapshots keys so reassigning the first loop
--- variable cannot corrupt the iterator state — otherwise `next`
--- would later fail with "invalid key to 'next'".
+-- Regression tests for the Lua 5.4+ for-loop hazard: the parser merges
+-- the generic-for control slot with the user's first loop variable and
+-- marks it `RDKCONST`, so code like
+--
+-- for k, v in pairs(t) do
+-- k = k:gsub("_", "-")
+-- ...
+-- end
+--
+-- normally fails to compile with "attempt to assign to const variable",
+-- and if we only relax the const check the runtime silently corrupts the
+-- iterator state (next iteration gets the modified key and `next` bails
+-- with "invalid key to 'next'" — or worse, silently skips entries).
+--
+-- xmake works around this in two coordinated places:
+--
+-- 1. Compile-time: `core/src/lua/xmake.lua` (and xmake.sh) replace
+-- `RDKCONST);` with `VDKREG);` in lparser.c so assignment to the
+-- first loop variable is allowed through.
+--
+-- 2. Runtime: the sandbox iterators (`pairs`, `ipairs`, `irpairs`) and
+-- the base containers (`list:items/ritems`, `hashset:items/keys`)
+-- are implemented as stateful closures keeping their cursor in an
+-- upvalue, so user writes to the first loop variable cannot reach
+-- the iterator's internal state.
+--
+-- These tests pin both behaviors: that reassigning the first loop
+-- variable (a) compiles and (b) produces correct, non-lossy iteration.
+
+import("core.base.list")
+import("core.base.hashset")
function test_numeric_for_reassign(t)
-- Just needs to compile & run without "attempt to assign to const".
@@ -31,8 +56,8 @@ function test_generic_for_reassign_key(t)
end
function test_generic_for_reassign_many_keys(t)
- -- Stress the snapshot path with enough keys that a broken iterator
- -- would deterministically trip `next` on the second iteration.
+ -- Stress the iterator with enough keys that a broken stateless
+ -- iterator would deterministically trip `next` on the second round.
local tbl = {}
for i = 1, 64 do
tbl["key_" .. i] = i
@@ -47,22 +72,18 @@ function test_generic_for_reassign_many_keys(t)
end
function test_list_items_reassign(t)
- import("core.base.list")
local l = list.new()
for i = 1, 5 do
l:push({name = "n" .. i})
end
- local names = {}
for item in l:items() do
item = nil -- would corrupt list:next on the next iteration
- -- re-fetch to prove we don't rely on `item`
end
- local count = 0
+ local names = {}
for item in l:items() do
- count = count + 1
table.insert(names, item.name)
end
- t:require(count == 5)
+ t:require(#names == 5)
t:require(names[1] == "n1" and names[5] == "n5")
end
@@ -79,7 +100,6 @@ function test_irpairs_reassign_index(t)
end
function test_hashset_items_reassign(t)
- import("core.base.hashset")
local set = hashset.from({"key_1", "key_2", "key_3", "key_4", "key_5"})
local seen = {}
for item in set:items() do