mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-22 09:14:25 +00:00
lua: add recursive read-only bindings for borrowed slices
This commit is contained in:
+1
-1
@@ -52,7 +52,7 @@ func luaServers(s *DNS) []luaDNSServer {
|
|||||||
|
|
||||||
func registerLua(L *lua.LState, servers []luaDNSServer, client featureDNS.Client) {
|
func registerLua(L *lua.LState, servers []luaDNSServer, client featureDNS.Client) {
|
||||||
L.PreloadModule("xray.dns", func(L *lua.LState) int {
|
L.PreloadModule("xray.dns", func(L *lua.LState) int {
|
||||||
pushIPs := xlua.NewSlicePusher[net.IP](L)
|
pushIPs := xlua.NewReadOnlySlicePusher[net.IP](L)
|
||||||
|
|
||||||
serverList := L.CreateTable(len(servers), 0)
|
serverList := L.CreateTable(len(servers), 0)
|
||||||
for i, client := range servers {
|
for i, client := range servers {
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ func (r *Router) RegisterLua(L *lua.LState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerLuaContext(L *lua.LState) {
|
func registerLuaContext(L *lua.LState) {
|
||||||
pushIPs := xlua.NewSlicePusher[net.IP](L)
|
pushIPs := xlua.NewReadOnlySlicePusher[net.IP](L)
|
||||||
attributes := L.NewTypeMetatable(luaAttributesType)
|
attributes := L.NewTypeMetatable(luaAttributesType)
|
||||||
L.SetField(attributes, "__index", L.NewFunction(func(L *lua.LState) int {
|
L.SetField(attributes, "__index", L.NewFunction(func(L *lua.LState) int {
|
||||||
values := L.CheckUserData(1).Value.(map[string]string)
|
values := L.CheckUserData(1).Value.(map[string]string)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func RegisterLua(L *lua.LState) {
|
|||||||
"Match": luaIPMatch,
|
"Match": luaIPMatch,
|
||||||
"AnyMatch": luaIPAnyMatch,
|
"AnyMatch": luaIPAnyMatch,
|
||||||
"Matches": luaIPMatches,
|
"Matches": luaIPMatches,
|
||||||
"FilterIPs": newLuaIPFilterIPs(xlua.NewSlicePusher[net.IP](L)),
|
"FilterIPs": newLuaIPFilterIPs(xlua.NewReadOnlySlicePusher[net.IP](L)),
|
||||||
}, "SetReverse", "ToggleReverse")
|
}, "SetReverse", "ToggleReverse")
|
||||||
return 1
|
return 1
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package lua
|
package lua
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
glua "github.com/yuin/gopher-lua"
|
glua "github.com/yuin/gopher-lua"
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
)
|
)
|
||||||
@@ -22,6 +25,52 @@ func NewSlicePusher[T any](L *glua.LState) func(*glua.LState, []T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReadOnlySlicePusher borrows slices without copying and recursively makes
|
||||||
|
// nested slice types read-only through their shared metatables. IP byte views
|
||||||
|
// and masks are also read-only. Other referenced elements retain their mutability.
|
||||||
|
func NewReadOnlySlicePusher[T any](L *glua.LState) func(*glua.LState, []T) {
|
||||||
|
sliceMetatable := luar.MT(L, []T{})
|
||||||
|
if sliceMetatable.RawGetString("__readonly") != glua.LTrue {
|
||||||
|
readOnly := L.NewFunction(func(L *glua.LState) int {
|
||||||
|
L.RaiseError("slice is read-only")
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
setSliceReadOnly[[]T](L, readOnly)
|
||||||
|
}
|
||||||
|
return NewSlicePusher[T](L)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSliceReadOnly[S ~[]E, E any](L *glua.LState, readOnly *glua.LFunction) {
|
||||||
|
var visit func(reflect.Type)
|
||||||
|
visit = func(typ reflect.Type) {
|
||||||
|
if typ.Kind() != reflect.Slice {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metatable := luar.MT(L, reflect.Zero(typ).Interface())
|
||||||
|
if metatable.RawGetString("__readonly") == glua.LTrue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Mark before descending because named slice types can be recursive.
|
||||||
|
metatable.RawSetString("__readonly", glua.LTrue)
|
||||||
|
metatable.RawSetString("__newindex", readOnly)
|
||||||
|
// Reject explicitly so Lua cannot fall back to the other operand.
|
||||||
|
metatable.RawSetString("__add", readOnly)
|
||||||
|
if typ == reflect.TypeFor[net.IP]() {
|
||||||
|
// A nil receiver lets AppendText return its argument as a writable []byte.
|
||||||
|
methods := metatable.RawGetString("methods").(*glua.LTable)
|
||||||
|
appendText := methods.RawGetString("AppendText")
|
||||||
|
methods.ForEach(func(key, method glua.LValue) {
|
||||||
|
if method == appendText {
|
||||||
|
methods.RawSet(key, glua.LNil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
visit(reflect.TypeFor[net.IPMask]())
|
||||||
|
}
|
||||||
|
visit(typ.Elem())
|
||||||
|
}
|
||||||
|
visit(reflect.TypeFor[S]())
|
||||||
|
}
|
||||||
|
|
||||||
// DirectMethod handles a Lua call without luar's reflected method invocation.
|
// DirectMethod handles a Lua call without luar's reflected method invocation.
|
||||||
// It returns the result count and whether it handled the arguments. On false,
|
// It returns the result count and whether it handled the arguments. On false,
|
||||||
// it must leave the stack unchanged for the original luar wrapper.
|
// it must leave the stack unchanged for the original luar wrapper.
|
||||||
|
|||||||
@@ -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) {
|
func TestSlicePusherMetatablePerState(t *testing.T) {
|
||||||
first := glua.NewState()
|
first := glua.NewState()
|
||||||
defer first.Close()
|
defer first.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user