mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-26 13:06:58 +00:00
lua: defer read-only slice element bindings until first access
This commit is contained in:
+18
-1
@@ -28,6 +28,7 @@ func NewSlicePusher[T any](L *glua.LState) func(*glua.LState, []T) {
|
|||||||
// NewReadOnlySlicePusher borrows slices without copying and recursively makes
|
// NewReadOnlySlicePusher borrows slices without copying and recursively makes
|
||||||
// nested slice types read-only through their shared metatables. IP byte views
|
// nested slice types read-only through their shared metatables. IP byte views
|
||||||
// and masks are also read-only. Other referenced elements retain their mutability.
|
// and masks are also read-only. Other referenced elements retain their mutability.
|
||||||
|
// Element bindings are initialized on first indexing or iteration.
|
||||||
func NewReadOnlySlicePusher[T any](L *glua.LState) func(*glua.LState, []T) {
|
func NewReadOnlySlicePusher[T any](L *glua.LState) func(*glua.LState, []T) {
|
||||||
sliceMetatable := luar.MT(L, []T{})
|
sliceMetatable := luar.MT(L, []T{})
|
||||||
if sliceMetatable.RawGetString("__readonly") != glua.LTrue {
|
if sliceMetatable.RawGetString("__readonly") != glua.LTrue {
|
||||||
@@ -66,7 +67,23 @@ func setSliceReadOnly[S ~[]E, E any](L *glua.LState, readOnly *glua.LFunction) {
|
|||||||
})
|
})
|
||||||
visit(reflect.TypeFor[net.IPMask]())
|
visit(reflect.TypeFor[net.IPMask]())
|
||||||
}
|
}
|
||||||
visit(typ.Elem())
|
if element := typ.Elem(); element.Kind() == reflect.Slice {
|
||||||
|
index := metatable.RawGetString("__index").(*glua.LFunction)
|
||||||
|
call := metatable.RawGetString("__call").(*glua.LFunction)
|
||||||
|
initialize := func() {
|
||||||
|
visit(element)
|
||||||
|
metatable.RawSetString("__index", index)
|
||||||
|
metatable.RawSetString("__call", call)
|
||||||
|
}
|
||||||
|
metatable.RawSetString("__index", L.NewFunction(func(L *glua.LState) int {
|
||||||
|
initialize()
|
||||||
|
return index.GFunction(L)
|
||||||
|
}))
|
||||||
|
metatable.RawSetString("__call", L.NewFunction(func(L *glua.LState) int {
|
||||||
|
initialize()
|
||||||
|
return call.GFunction(L)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
visit(reflect.TypeFor[S]())
|
visit(reflect.TypeFor[S]())
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-1
@@ -2,6 +2,7 @@ package lua
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
glua "github.com/yuin/gopher-lua"
|
glua "github.com/yuin/gopher-lua"
|
||||||
@@ -117,6 +118,53 @@ end
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadOnlyIPSlicePusherLazy(t *testing.T) {
|
||||||
|
for _, tc := range []struct{ name, access string }{
|
||||||
|
{"index", `local ip = ips[1]`},
|
||||||
|
{"iterate", `local _, ip = ips()()`},
|
||||||
|
{"coroutine", `local co = coroutine.create(function() return ips[1] end); local ok, ip = coroutine.resume(co); assert(ok)`},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
L := glua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
methods := 0
|
||||||
|
luar.GetConfig(L).MethodNames = func(_ reflect.Type, method reflect.Method) []string {
|
||||||
|
methods++
|
||||||
|
return []string{method.Name}
|
||||||
|
}
|
||||||
|
NewReadOnlySlicePusher[net.IP](L)(L, []net.IP{{192, 0, 2, 1}})
|
||||||
|
L.SetGlobal("ips", L.Get(-1))
|
||||||
|
L.Pop(1)
|
||||||
|
if err := L.DoString(`assert(#ips == 1); assert(not pcall(function() ips[1] = nil end))`); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if methods != 0 {
|
||||||
|
t.Fatal("IP methods initialized without reading an element")
|
||||||
|
}
|
||||||
|
source := tc.access + `
|
||||||
|
assert(ip:String() == "192.0.2.1" and ip.AppendText == nil)
|
||||||
|
assert(not pcall(function() ip[1] = 0 end))
|
||||||
|
assert(not pcall(function() ip:To4()[1] = 0 end))
|
||||||
|
assert(not pcall(function() ip:To16()[1] = 0 end))
|
||||||
|
assert(not pcall(function() ip:DefaultMask()[1] = 0 end))
|
||||||
|
`
|
||||||
|
if err := L.DoString(source); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
initialized := methods
|
||||||
|
if initialized == 0 {
|
||||||
|
t.Fatal("IP access did not initialize its methods")
|
||||||
|
}
|
||||||
|
if err := L.DoString(source); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if methods != initialized {
|
||||||
|
t.Fatal("IP methods initialized more than once")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadOnlyNestedSlicePusher(t *testing.T) {
|
func TestReadOnlyNestedSlicePusher(t *testing.T) {
|
||||||
L := glua.NewState()
|
L := glua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
@@ -128,7 +176,6 @@ func TestReadOnlyNestedSlicePusher(t *testing.T) {
|
|||||||
NewReadOnlySlicePusher[[]net.IP](L)(L, ips)
|
NewReadOnlySlicePusher[[]net.IP](L)(L, ips)
|
||||||
L.SetGlobal("ips", L.Get(-1))
|
L.SetGlobal("ips", L.Get(-1))
|
||||||
L.Pop(1)
|
L.Pop(1)
|
||||||
readOnly := luar.MT(L, ips[0]).RawGetString("__newindex")
|
|
||||||
if err := L.DoString(`
|
if err := L.DoString(`
|
||||||
local function readOnly(value)
|
local function readOnly(value)
|
||||||
assert(not pcall(function() value[1] = value[1] end))
|
assert(not pcall(function() value[1] = value[1] end))
|
||||||
@@ -152,6 +199,7 @@ assert(ip.AppendText == nil and ip.appendText == nil)
|
|||||||
`); err != nil {
|
`); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
readOnly := luar.MT(L, ips[0]).RawGetString("__newindex")
|
||||||
NewReadOnlySlicePusher[net.IP](L)
|
NewReadOnlySlicePusher[net.IP](L)
|
||||||
if luar.MT(L, ips[0]).RawGetString("__newindex") != readOnly {
|
if luar.MT(L, ips[0]).RawGetString("__newindex") != readOnly {
|
||||||
t.Fatal("nested slice metatable was initialized again")
|
t.Fatal("nested slice metatable was initialized again")
|
||||||
|
|||||||
Reference in New Issue
Block a user