mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-15 03:53:59 +00:00
268 lines
7.6 KiB
Go
268 lines
7.6 KiB
Go
package lua
|
|
|
|
import (
|
|
"net"
|
|
"reflect"
|
|
"testing"
|
|
|
|
glua "github.com/yuin/gopher-lua"
|
|
luar "layeh.com/gopher-luar"
|
|
)
|
|
|
|
func TestSlicePusher(t *testing.T) {
|
|
L := glua.NewState()
|
|
defer L.Close()
|
|
push := NewSlicePusher[int](L)
|
|
values := []int{3, 5}
|
|
L.SetGlobal("getValues", L.NewFunction(func(L *glua.LState) int {
|
|
push(L, values)
|
|
return 1
|
|
}))
|
|
if err := L.DoString(`
|
|
local values = getValues()
|
|
assert(#values == 2 and values[1] == 3 and values[2] == 5)
|
|
values[2] = 7
|
|
local co = coroutine.create(function()
|
|
local values = getValues()
|
|
assert(#values == 2 and values[1] == 3 and values[2] == 7)
|
|
return true
|
|
end)
|
|
local ok, result = coroutine.resume(co)
|
|
assert(ok and result == true)
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if values[1] != 7 {
|
|
t.Fatal("slice storage was copied")
|
|
}
|
|
push(L, nil)
|
|
if L.Get(-1) != glua.LNil {
|
|
t.Fatal("nil slice must push Lua nil")
|
|
}
|
|
L.Pop(1)
|
|
push(L, []int{})
|
|
L.SetGlobal("empty", L.Get(-1))
|
|
L.Pop(1)
|
|
if err := L.DoString(`assert(type(empty) == "userdata" and #empty == 0)`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
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 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) {
|
|
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)
|
|
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)
|
|
}
|
|
readOnly := luar.MT(L, ips[0]).RawGetString("__newindex")
|
|
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()
|
|
second := glua.NewState()
|
|
defer second.Close()
|
|
NewSlicePusher[int](first)(first, []int{1})
|
|
NewSlicePusher[int](second)(second, []int{1})
|
|
if first.Get(-1).(*glua.LUserData).Metatable == second.Get(-1).(*glua.LUserData).Metatable {
|
|
t.Fatal("independent states share a slice metatable")
|
|
}
|
|
}
|
|
|
|
func BenchmarkSlicePusher(b *testing.B) {
|
|
L := glua.NewState()
|
|
defer L.Close()
|
|
ips := []net.IP{net.ParseIP("127.0.0.1")}
|
|
pushIPs := NewSlicePusher[net.IP](L)
|
|
for _, benchmark := range []struct {
|
|
name string
|
|
push func(*glua.LState, []net.IP)
|
|
}{
|
|
{"bare", func(L *glua.LState, ips []net.IP) { PushUserData(L, ips) }},
|
|
{"luar", func(L *glua.LState, ips []net.IP) { L.Push(luar.New(L, ips)) }},
|
|
{"cached", pushIPs},
|
|
} {
|
|
b.Run(benchmark.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
benchmark.push(L, ips)
|
|
L.Pop(1)
|
|
}
|
|
})
|
|
}
|
|
}
|