Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
integration:
name: Sentry integration / ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -112,13 +112,31 @@ jobs:
bash sentry/build-linux.sh "$TEST_BIN"
cd testdata
go test -mod=readonly -ldflags='-checklinkname=0 -s=false -w=false' -c -o "$TEST_BIN/ixgo.test" ./ixgo
go test -mod=readonly -ldflags='-checklinkname=0 -s=false -w=false' -c -o "$TEST_BIN/ixgo-upstream.test" ./ixgo/upstream
go test -mod=readonly -ldflags='-checklinkname=0 -s=false -w=false' -c -o "$TEST_BIN/reflect.test" ./reflect
go build -mod=readonly -ldflags='-checklinkname=0 -s=false -w=false' -o "$TEST_BIN/smoke" .
cd llar
go test -mod=readonly -ldflags='-checklinkname=0 -s=false -w=false' -c -o "$TEST_BIN/llar.test" .
- name: Test ixgo round trips
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
working-directory: testdata/ixgo
run: sudo env GLIBC_TUNABLES="$GLIBC_TUNABLES" SANDBOX_TEST_LIBRARY="$SANDBOX_TEST_LIBRARY" "$TEST_BIN/ixgo.test" -test.v -test.timeout=10m
- name: Test Go reflect in Sentry
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
working-directory: testdata
run: |
sudo env GLIBC_TUNABLES="$GLIBC_TUNABLES" SANDBOX_TEST_LIBRARY="$SANDBOX_TEST_LIBRARY" \
PATH="$PATH" GOTOOLCHAIN="$GOTOOLCHAIN" GOROOT="$(go env GOROOT)" \
GOPATH="$(go env GOPATH)" GOMODCACHE="$(go env GOMODCACHE)" GOCACHE="$(go env GOCACHE)" \
TMPDIR=/tmp "$TEST_BIN/reflect.test" -test.v -test.timeout=20m
- name: Test upstream ixgo interpreter state round trips
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
working-directory: testdata
run: |
sudo env GLIBC_TUNABLES="$GLIBC_TUNABLES" SANDBOX_TEST_LIBRARY="$SANDBOX_TEST_LIBRARY" \
PATH="$PATH" GOTOOLCHAIN="$GOTOOLCHAIN" GOROOT="$(go env GOROOT)" \
GOPATH="$(go env GOPATH)" GOMODCACHE="$(go env GOMODCACHE)" GOCACHE="$(go env GOCACHE)" \
TMPDIR=/tmp "$TEST_BIN/ixgo-upstream.test" -test.v -test.timeout=40m
- name: Test LLAR formulas
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
working-directory: testdata/llar
Expand Down
5 changes: 3 additions & 2 deletions testdata/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ go 1.26.6
require (
github.com/goplus/ixgo v1.1.6
github.com/xgo-dev/sandbox v0.0.0
golang.org/x/mod v0.39.0
golang.org/x/sys v0.47.0
golang.org/x/tools v0.49.0
)

require (
Expand All @@ -15,8 +17,7 @@ require (
github.com/visualfc/goembed v0.3.4 // indirect
github.com/visualfc/xtype v0.3.3 // indirect
golang.org/x/arch v0.28.0 // indirect
golang.org/x/mod v0.39.0 // indirect
golang.org/x/tools v0.49.0 // indirect
golang.org/x/sync v0.22.0 // indirect
)

replace github.com/xgo-dev/sandbox => ..
6 changes: 6 additions & 0 deletions testdata/goroot/upstream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ func init() {
gorootTestSkips["fixedbugs/issue30116u.go"] = "BUG, slice bound check"
gorootTestSkips["chan/select5.go"] = "bug, select case expr call order"

// Sandbox-specific: ixgo Goexit compares the current goroutine ID with the
// interpreter's host mainid. Exclude these cases until IDs are rebound.
gorootTestSkips["fixedbugs/issue5963.go"] = "ixgo Goexit depends on a process-local goroutine ID"
gorootTestSkips["fixedbugs/issue8158.go"] = "ixgo Goexit depends on a process-local goroutine ID"
gorootTestSkips["fixedbugs/issue11256.go"] = "ixgo Goexit depends on a process-local goroutine ID"

// fixedbugs/issue7740.go
// const ulp = (1.0 + (2.0 / 3.0)) - (5.0 / 3.0)
// Go 1.14 1.15 1.16 ulp = 1.4916681462400413e-154
Expand Down
210 changes: 210 additions & 0 deletions testdata/ixgo/reflect_public_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
//go:build linux && (amd64 || arm64) && cgo

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in testdata/LICENSE.

package ixgo_test

import (
"reflect"
"testing"
)

// These cases adapt public API checks from Go 1.26.6 reflect/all_test.go,
// type_test.go and set_test.go. Objects are created before Run; assertions
// also check aliases and mutations after the guest returns.
type reflectCounter struct{ N int }

func (c *reflectCounter) Add(n int) int { c.N += n; return c.N }

type reflectAdder interface{ Add(int) int }
type reflectIntPointer *int
type reflectOtherIntPointer *int

func publicReflectTypes() []reflect.Type {
number := reflect.TypeFor[int]()
return []reflect.Type{
number, reflect.TypeFor[any](), reflect.TypeFor[reflectAdder](),
reflect.PointerTo(number), reflect.ArrayOf(3, number), reflect.SliceOf(number),
reflect.MapOf(reflect.TypeFor[string](), number),
reflect.ChanOf(reflect.BothDir, number),
reflect.ChanOf(reflect.RecvDir, number),
reflect.ChanOf(reflect.SendDir, number),
reflect.StructOf([]reflect.StructField{{Name: "Count", Type: number, Tag: `json:"count"`}}),
reflect.FuncOf([]reflect.Type{number}, []reflect.Type{number}, false),
reflect.FuncOf([]reflect.Type{number, reflect.SliceOf(number)}, []reflect.Type{number}, true),
}
}

func TestReflectPublicTypes(t *testing.T) {
types := publicReflectTypes()
var returned []reflect.Type
if err := runSandbox(t, func() {
local := publicReflectTypes()
for i, typ := range types {
if typ != local[i] {
panic("restored type differs from the guest constructor")
}
}
field := types[10].Field(0)
if field.Name != "Count" || field.Tag.Get("json") != "count" || field.Type != types[0] {
panic("StructOf field metadata changed")
}
if !types[12].IsVariadic() || types[12].In(1) != types[5] || types[12].Out(0) != types[0] {
panic("FuncOf signature changed")
Comment on lines +50 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Brittle hardcoded indices couple assertions to constructor order

types[10] (StructOf) and types[12] (variadic FuncOf), plus types[0]/types[5], reach into the slice from publicReflectTypes() using magic indices that must stay manually in sync with the constructor's element order. They are correct today, but inserting or reordering a type (e.g. a new ChanOf variant) would silently retarget these checks at the wrong element without any compile-time or test-time signal. Consider binding the interesting types to named locals and asserting against the names, or at minimum add a comment mapping each index to its constructor line.

}
returned = local
}); err != nil {
t.Fatal(err)
}
for i, typ := range types {
if i >= len(returned) || returned[i] != typ {
t.Fatalf("returned type %d lost host identity", i)
}
}
}

func TestReflectPublicStructValue(t *testing.T) {
typ := reflect.StructOf([]reflect.StructField{
{Name: "Count", Type: reflect.TypeFor[int](), Tag: `json:"count"`},
{Name: "Name", Type: reflect.TypeFor[string]()},
})
value := reflect.New(typ).Elem()
value.Field(0).SetInt(41)
value.Field(1).SetString("host")
count := value.Field(0).Addr().Interface().(*int)
if err := runSandbox(t, func() {
if value.Type() != typ || !value.CanSet() || value.Field(0).Addr().Interface().(*int) != count {
panic("reflected struct lost its type, access or field alias")
}
value.FieldByName("Count").SetInt(42)
value.FieldByName("Name").SetString("guest")
if *count != 42 {
panic("reflected field mutation lost alias")
}
}); err != nil {
t.Fatal(err)
}
if *count != 42 || value.Field(0).Int() != 42 || value.Field(1).String() != "guest" || value.Type() != typ {
t.Fatal("reflected struct mutation was not written back")
}
}

func TestReflectPublicContainers(t *testing.T) {
array := [3]int{1, 2, 3}
slice := array[:]
arrayValue, sliceValue := reflect.ValueOf(&array).Elem(), reflect.ValueOf(slice)
mapping := reflect.MakeMap(reflect.TypeFor[map[string][]int]())
mapping.SetMapIndex(reflect.ValueOf("shared"), sliceValue)
if err := runSandbox(t, func() {
arrayValue.Index(1).SetInt(20)
fromMap := mapping.MapIndex(reflect.ValueOf("shared"))
if sliceValue.Index(1).Int() != 20 || fromMap.Index(1).Int() != 20 || slice[1] != 20 {
panic("array, slice and map lost their shared backing storage")
}
fromMap.Index(2).SetInt(30)
mapping.SetMapIndex(reflect.ValueOf("grown"), reflect.Append(sliceValue, reflect.ValueOf(4)))
}); err != nil {
t.Fatal(err)
}
if array != [3]int{1, 20, 30} || &slice[0] != &array[0] {
t.Fatal("array or slice identity was not written back")
}
shared := mapping.MapIndex(reflect.ValueOf("shared")).Interface().([]int)
grown := mapping.MapIndex(reflect.ValueOf("grown")).Interface().([]int)
if &shared[0] != &array[0] || !reflect.DeepEqual(grown, []int{1, 20, 30, 4}) {
t.Fatal("reflected map or appended slice was not written back")
}
}

func TestReflectPublicMethods(t *testing.T) {
counter := &reflectCounter{N: 10}
var boxed any = counter
value := reflect.ValueOf(&boxed).Elem()
method := reflect.ValueOf(counter).MethodByName("Add")
var result int
if err := runSandbox(t, func() {
if value.Elem().Interface().(*reflectCounter) != counter {
panic("interface lost receiver identity")
}
if method.Call([]reflect.Value{reflect.ValueOf(2)})[0].Int() != 12 {
panic("bound reflected method")
}
m, ok := reflect.TypeOf(counter).MethodByName("Add")
if !ok || m.Func.Call([]reflect.Value{reflect.ValueOf(counter), reflect.ValueOf(3)})[0].Int() != 15 {
panic("reflected method expression")
}
result = value.Elem().Interface().(reflectAdder).Add(4)
}); err != nil {
t.Fatal(err)
}
if result != 19 || counter.N != 19 || boxed.(*reflectCounter) != counter {
t.Fatal("method receiver mutation was not written back")
}
if got := method.Call([]reflect.Value{reflect.ValueOf(1)})[0].Int(); got != 20 || counter.N != 20 {
t.Fatal("original reflected method lost its receiver")
}
}

func TestReflectPublicMakeFunc(t *testing.T) {
n := 10
typ := reflect.FuncOf([]reflect.Type{reflect.TypeFor[int](), reflect.TypeFor[[]int]()}, []reflect.Type{reflect.TypeFor[int]()}, true)
value := reflect.MakeFunc(typ, func(in []reflect.Value) []reflect.Value {
n += int(in[0].Int())
for i := 0; i < in[1].Len(); i++ {
n += int(in[1].Index(i).Int())
}
return []reflect.Value{reflect.ValueOf(n)}
})
fn := value.Interface().(func(int, ...int) int)
var results [3]int
if err := runSandbox(t, func() {
if value.Type() != typ {
panic("MakeFunc signature changed")
}
results[0] = fn(1, 2, 3)
results[1] = int(value.Call([]reflect.Value{reflect.ValueOf(4), reflect.ValueOf(5)})[0].Int())
results[2] = int(value.CallSlice([]reflect.Value{reflect.ValueOf(6), reflect.ValueOf([]int{7, 8})})[0].Int())
}); err != nil {
t.Fatal(err)
}
if results != [3]int{16, 25, 46} || n != 46 {
t.Fatalf("MakeFunc results=%v capture=%d", results, n)
}
if got := fn(1); got != 47 || n != 47 {
t.Fatal("original MakeFunc lost its captured variable")
}
}

func TestReflectPublicAssignableTo(t *testing.T) {
cases := []struct {
from, to reflect.Type
want bool
}{
{reflect.TypeFor[chan int](), reflect.TypeFor[<-chan int](), true},
{reflect.TypeFor[<-chan int](), reflect.TypeFor[chan int](), false},
{reflect.TypeFor[*int](), reflect.TypeFor[reflectIntPointer](), true},
{reflect.TypeFor[reflectIntPointer](), reflect.TypeFor[*int](), true},
{reflect.TypeFor[reflectIntPointer](), reflect.TypeFor[reflectOtherIntPointer](), false},
{reflect.TypeFor[*reflectCounter](), reflect.TypeFor[reflectAdder](), true},
{reflect.TypeFor[reflectCounter](), reflect.TypeFor[reflectAdder](), false},
}
var checked int
if err := runSandbox(t, func() {
for _, test := range cases {
if test.from.AssignableTo(test.to) != test.want {
panic("AssignableTo changed after transfer")
}
if test.to.Kind() == reflect.Interface && test.from.Implements(test.to) != test.want {
panic("Implements changed after transfer")
}
checked++
}
}); err != nil {
t.Fatal(err)
}
if checked != len(cases) {
t.Fatalf("checked %d assignability cases, want %d", checked, len(cases))
}
}
Loading
Loading