mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
f16697624d
When the interpreter encountered a load that was out of bounds of the
object, it panicked with "interp: load out of bounds", crashing the
compiler. This can happen for valid Go programs, for example when
dereferencing the pointer returned by unsafe.SliceData on a
zero-capacity slice, which points to a zero-sized object:
package main
import "unsafe"
var p = unsafe.SliceData([]int{})
var v = *p
func main() {}
Return nil for an out-of-bounds load, the same as for an external
global, so the caller defers the load to runtime instead of crashing.
This matches what regular Go does, where the load reads from the
runtime zero-base at runtime.
Fixes #4214
128 lines
3.0 KiB
Go
128 lines
3.0 KiB
Go
package interp
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
func TestInterp(t *testing.T) {
|
|
for _, name := range []string{
|
|
"basic",
|
|
"phi",
|
|
"consteval",
|
|
"intrinsics",
|
|
"copy",
|
|
"interface",
|
|
"revert",
|
|
"store",
|
|
"alloc",
|
|
"slicedata",
|
|
} {
|
|
name := name // make local to this closure
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
runTest(t, "testdata/"+name)
|
|
})
|
|
}
|
|
}
|
|
|
|
func runTest(t *testing.T, pathPrefix string) {
|
|
// Read the input IR.
|
|
ctx := llvm.NewContext()
|
|
defer ctx.Dispose()
|
|
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
|
|
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
|
|
if err != nil {
|
|
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
|
|
}
|
|
mod, err := ctx.ParseIR(buf)
|
|
if err != nil {
|
|
t.Fatalf("could not load module:\n%v", err)
|
|
}
|
|
defer mod.Dispose()
|
|
|
|
// Perform the transform.
|
|
err = Run(mod, 10*time.Minute, DefaultMaxInterpBlockEntries, false)
|
|
if err != nil {
|
|
if err, match := err.(*Error); match {
|
|
println(err.Error())
|
|
if len(err.Inst) != 0 {
|
|
println(err.Inst)
|
|
}
|
|
if len(err.Traceback) > 0 {
|
|
println("\ntraceback:")
|
|
for _, line := range err.Traceback {
|
|
println(line.Pos.String() + ":")
|
|
println(line.Inst)
|
|
}
|
|
}
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// To be sure, verify that the module is still valid.
|
|
if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil {
|
|
t.FailNow()
|
|
}
|
|
|
|
// Run some cleanup passes to get easy-to-read outputs.
|
|
to := llvm.NewPassBuilderOptions()
|
|
defer to.Dispose()
|
|
mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to)
|
|
|
|
// Read the expected output IR.
|
|
out, err := os.ReadFile(pathPrefix + ".out.ll")
|
|
if err != nil {
|
|
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
|
|
}
|
|
|
|
// See whether the transform output matches with the expected output IR.
|
|
expected := string(out)
|
|
actual := mod.String()
|
|
if !fuzzyEqualIR(expected, actual) {
|
|
t.Logf("output does not match expected output:\n%s", actual)
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
|
// equal. That means, only relevant lines are compared (excluding comments
|
|
// etc.).
|
|
func fuzzyEqualIR(s1, s2 string) bool {
|
|
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
|
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
|
if len(lines1) != len(lines2) {
|
|
return false
|
|
}
|
|
for i, line1 := range lines1 {
|
|
line2 := lines2[i]
|
|
if line1 != line2 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
|
// are not relevant in comparing IR. For example, empty lines and comments are
|
|
// stripped out.
|
|
func filterIrrelevantIRLines(lines []string) []string {
|
|
var out []string
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line) // drop '\r' on Windows
|
|
if line == "" || line[0] == ';' {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "source_filename = ") {
|
|
continue
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
return out
|
|
}
|