Files
zandGitHub c45b8a7eeb Fix Memory64 scalar overflow traps and require real >4 GiB AOT certification (#1)
* Trap memory64 scalar out-of-bounds/overflow before native pointer use: internal/codegen/emit_memops.go

* Trap memory64 scalar out-of-bounds/overflow before native pointer use: internal/codegen/helpers/helpers.go

* Regression: memory64 scalar loads/stores must trap on u64 overflow

* Make greater-than-4GiB memory certification fail closed when required

* Add required real >4 GiB Memory64 CI gate

* Repair Memory64 CI workflow YAML after broken replacement

* CLI: add strict -require-memory64 mode for native AOT builds

* Test strict memory64 CLI accepts wasm64 and refuses wasm32

* Document strict Memory64 AOT mode and real 4 GiB proof requirements

* Register memory64 bounds helper in generator dependency closure

* Test memory64 bounds helper and keep extractor-only function referenced

* Align compiler-source lint/vet with generated benchmark policy (Makefile)

* Align compiler-source lint/vet with generated benchmark policy (internal/testfixture/fixture.go)

* Correct Memory64 scalar boundary regression to actually test out-of-range address

* Fix Go formatting of memory64 bounds test table

* Fix escaped newline expectations in Memory64 scalar runtime regression

* Fix trailing comma in Memory64 test composite literal
2026-10-08 19:21:51 -07:00

138 lines
3.7 KiB
Go

// Package testfixture compiles .wat test fixtures to wasm on demand.
// It is a non-test package so that any test binary in the module can import it.
package testfixture
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
)
var (
cacheMu sync.Mutex
cache = map[string][]byte{}
watFlagsOnce sync.Once
watFlags []string
)
func getWatFlags() []string {
watFlagsOnce.Do(func() {
out, err := exec.Command("wat2wasm", "--help").CombinedOutput()
if err != nil {
// The later Wasm() invocation will report the real compiler
// failure. Do not infer unsupported flags from a broken help run.
return
}
help := string(out)
var flags []string
if strings.Contains(help, "--enable-exceptions") {
flags = append(flags, "--enable-exceptions")
}
if strings.Contains(help, "--enable-threads") {
flags = append(flags, "--enable-threads")
}
if strings.Contains(help, "--enable-memory64") {
flags = append(flags, "--enable-memory64")
}
watFlags = flags
})
return watFlags
}
// Wasm compiles testdata/<basename>.wat via wat2wasm and returns the wasm bytes.
// name may include or omit the .wasm or .wat extension — both are accepted and
// stripped to derive the bare basename.
// Results are cached per-process so each fixture is compiled at most once.
func Wasm(t testing.TB, name string) []byte {
t.Helper()
// Strip known extensions to get the bare basename.
base := name
if strings.HasSuffix(base, ".wasm") {
base = strings.TrimSuffix(base, ".wasm")
} else if strings.HasSuffix(base, ".wat") {
base = strings.TrimSuffix(base, ".wat")
}
cacheMu.Lock()
if data, ok := cache[base]; ok {
cacheMu.Unlock()
return data
}
cacheMu.Unlock()
root := repoRoot()
watPath := filepath.Join(root, "testdata", base+".wat")
if _, err := os.Stat(watPath); err != nil {
t.Fatalf("testfixture: wat source not found: %s", watPath)
}
args := append([]string{}, getWatFlags()...)
args = append(args, "--output=-", watPath)
cmd := exec.Command("wat2wasm", args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("testfixture: wat2wasm %s: %v\n%s", base, err, stderr.String())
}
data := stdout.Bytes()
cacheMu.Lock()
cache[base] = data
cacheMu.Unlock()
return data
}
// WasmPath compiles testdata/<basename>.wat and writes the result to a
// temporary file whose path is returned. The file is owned by t.TempDir()
// and is cleaned up when the test ends. This is useful for tests that must
// hand a file path to an external binary (e.g. the wasm2go CLI).
func WasmPath(t testing.TB, name string) string {
t.Helper()
data := Wasm(t, name)
// Derive a clean basename for the temp-file name.
base := name
if strings.HasSuffix(base, ".wasm") {
base = strings.TrimSuffix(base, ".wasm")
} else if strings.HasSuffix(base, ".wat") {
base = strings.TrimSuffix(base, ".wat")
}
dir := t.TempDir()
p := filepath.Join(dir, base+".wasm")
if err := os.WriteFile(p, data, 0600); err != nil {
t.Fatalf("testfixture: write temp wasm %s: %v", base, err)
}
return p
}
// repoRoot locates the repository root by walking up from this source file
// until it finds a directory containing go.mod.
func repoRoot() string {
// runtime.Caller(0) gives the absolute path of this source file.
_, file, _, ok := runtime.Caller(0)
if !ok {
panic("testfixture: runtime.Caller failed")
}
dir := filepath.Dir(file)
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
panic(fmt.Sprintf("testfixture: go.mod not found above %s", filepath.Dir(file)))
}
dir = parent
}
}