lua: add recursive read-only bindings for borrowed slices

This commit is contained in:
Meo597
2026-10-15 06:36:12 +08:00
parent ef4f4888df
commit b408f7f0c7
5 changed files with 187 additions and 3 deletions
+135
View File
@@ -47,6 +47,141 @@ assert(ok and result == true)
}
}
func TestReadOnlySlicePusher(t *testing.T) {
L := glua.NewState()
defer L.Close()
values := []int{3, 5}
NewReadOnlySlicePusher[int](L)(L, values)
L.SetGlobal("values", L.Get(-1))
L.Pop(1)
if err := L.DoString(`
assert(#values == 2 and values[1] == 3 and values[2] == 5)
assert(not pcall(function() values[1] = 7 end))
assert(not pcall(function() return values + 7 end))
local sum = 0
for _, value in values() do sum = sum + value end
assert(sum == 8)
`); err != nil {
t.Fatal(err)
}
got := L.GetGlobal("values").(*glua.LUserData).Value.([]int)
if &got[0] != &values[0] || values[0] != 3 || values[1] != 5 {
t.Fatal("read-only binding copied or modified the slice")
}
}
func TestReadOnlyIPSlicePusher(t *testing.T) {
L := glua.NewState()
defer L.Close()
ips := []net.IP{{192, 0, 2, 1}, net.ParseIP("2001:db8::1")}
NewReadOnlySlicePusher[net.IP](L)(L, ips)
L.SetGlobal("ips", L.Get(-1))
L.Pop(1)
readOnly := luar.MT(L, ips).RawGetString("__newindex")
NewReadOnlySlicePusher[net.IP](L)
if luar.MT(L, ips).RawGetString("__newindex") != readOnly {
t.Fatal("IP metatables were initialized more than once")
}
if err := L.DoString(`
local function readOnly(value)
assert(not pcall(function() value[1] = value[1] end))
assert(not pcall(function() return value + value[1] end))
end
assert(#ips == 2 and ips[1]:String() == "192.0.2.1")
readOnly(ips)
for _, ip in ips() do
readOnly(ip)
readOnly(ip:To16())
if ip:To4() then
readOnly(ip:To4())
readOnly(ip:DefaultMask())
end
assert(ip:Equal(ip:To16()) and type(ip:String()) == "string")
assert(ip.AppendText == nil and ip.appendText == nil)
local text, err = ip:MarshalText()
assert(not err)
text[1] = 88 -- Independently owned byte buffers remain writable.
assert(not pcall(function() return ips + text end))
end
`); err != nil {
t.Fatal(err)
}
got := L.GetGlobal("ips").(*glua.LUserData).Value.([]net.IP)
if &got[0] != &ips[0] || &got[0][0] != &ips[0][0] || &got[1][0] != &ips[1][0] {
t.Fatal("read-only binding copied the IP data")
}
for i, want := range []string{"192.0.2.1", "2001:db8::1"} {
if got[i].String() != want {
t.Fatalf("IP %d changed to %s; want %s", i, got[i], want)
}
}
}
func TestReadOnlyNestedSlicePusher(t *testing.T) {
L := glua.NewState()
defer L.Close()
values := [][][]byte{{{3, 5}}}
NewReadOnlySlicePusher[[][]byte](L)(L, values)
L.SetGlobal("values", L.Get(-1))
L.Pop(1)
ips := [][]net.IP{{{192, 0, 2, 1}}}
NewReadOnlySlicePusher[[]net.IP](L)(L, ips)
L.SetGlobal("ips", L.Get(-1))
L.Pop(1)
readOnly := luar.MT(L, ips[0]).RawGetString("__newindex")
if err := L.DoString(`
local function readOnly(value)
assert(not pcall(function() value[1] = value[1] end))
assert(not pcall(function() return value + value[1] end))
end
assert(values[1][1][1] == 3 and values[1][1][2] == 5)
readOnly(values)
for _, group in values() do
readOnly(group)
for _, bytes in group() do readOnly(bytes) end
end
readOnly(ips)
readOnly(ips[1])
local ip = ips[1][1]
assert(ip:String() == "192.0.2.1")
readOnly(ip)
readOnly(ip:To4())
readOnly(ip:To16())
readOnly(ip:DefaultMask())
assert(ip.AppendText == nil and ip.appendText == nil)
`); err != nil {
t.Fatal(err)
}
NewReadOnlySlicePusher[net.IP](L)
if luar.MT(L, ips[0]).RawGetString("__newindex") != readOnly {
t.Fatal("nested slice metatable was initialized again")
}
got := L.GetGlobal("values").(*glua.LUserData).Value.([][][]byte)
if &got[0][0][0] != &values[0][0][0] || values[0][0][0] != 3 || values[0][0][1] != 5 {
t.Fatal("read-only binding copied or modified the nested slice")
}
}
type recursiveReadOnlySlice []recursiveReadOnlySlice
func TestReadOnlyRecursiveSlicePusher(t *testing.T) {
L := glua.NewState()
defer L.Close()
values := make(recursiveReadOnlySlice, 1)
values[0] = values
NewReadOnlySlicePusher[recursiveReadOnlySlice](L)(L, values)
L.SetGlobal("values", L.Get(-1))
L.Pop(1)
if err := L.DoString(`
local nested = values[1][1][1]
assert(#nested == 1)
assert(not pcall(function() nested[1] = nested end))
assert(not pcall(function() return nested + nested end))
`); err != nil {
t.Fatal(err)
}
}
func TestSlicePusherMetatablePerState(t *testing.T) {
first := glua.NewState()
defer first.Close()