Пример #1
0
func misc(t *testing.T, filename, expectOutput string) {
	const (
		maxTextSize   = 65536
		maxRODataSize = 4096
		stackSize     = 4096

		dumpText = false
	)

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatal(err)
	}

	wasmReadCloser := wast2wasm(data, false)
	defer wasmReadCloser.Close()
	wasm := bufio.NewReader(wasmReadCloser)

	p, err := runner.NewProgram(maxTextSize, maxRODataSize)
	if err != nil {
		t.Fatal(err)
	}
	defer p.Close()

	var m Module
	m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil)
	p.Seal()
	p.SetData(m.Data())
	p.SetFunctionMap(m.FunctionMap())
	p.SetCallMap(m.CallMap())
	minMemorySize, maxMemorySize := m.MemoryLimits()

	if dumpText && testing.Verbose() {
		dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil)
	}

	var printBuf bytes.Buffer

	r, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize)
	if err != nil {
		t.Fatal(err)
	}
	defer r.Close()

	_, err = r.Run(0, m.Signatures(), &printBuf)
	if err != nil {
		t.Fatal(err)
	}

	output := string(printBuf.Bytes())
	t.Logf("print output:\n%s", output)
	if output != expectOutput {
		t.Fail()
	}
}
Пример #2
0
func fuzz(t *testing.T, filename string) {
	const (
		maxTextSize   = 65536
		maxRODataSize = 4096
		stackSize     = 4096
	)

	p, err := runner.NewProgram(maxTextSize, maxRODataSize)
	if err != nil {
		t.Fatal(err)
	}
	defer p.Close()

	f, err := os.Open(filename)
	if err != nil {
		t.Errorf("%s: %v", filename, err)
		return
	}
	defer f.Close()

	var m Module
	var ok bool

	defer func() {
		if !ok {
			t.Logf("%s: panic", filename)
		}
	}()

	err = m.Load(bufio.NewReader(f), runner.Env, p.Text, p.ROData, p.RODataAddr(), nil)
	if err == nil {
		t.Logf("%s: no error", filename)
	} else {
		t.Logf("%s: %v", filename, err)
	}

	ok = true
}
Пример #3
0
func testModule(t *testing.T, data []byte, filename string, quiet bool) []byte {
	const (
		maxTextSize   = 0x100000
		maxRODataSize = 0x100000
		maxMemorySize = 0x100000
		stackSize     = 4096 // limit stacktrace length

		timeout     = time.Second * 3
		dumpExps    = false
		dumpText    = false
		dumpROData  = false
		dumpGlobals = false
		dumpMemory  = false
	)

	module, data := sexp.ParsePanic(data)
	if module == nil {
		return nil
	}

	if name := module[0].(string); name != "module" {
		t.Logf("%s not supported", name)
		return data
	}

	module = append([]interface{}{
		module[0],
		[]interface{}{
			"import",
			sexp.Quote("wag"),
			sexp.Quote("get_arg"),
			[]interface{}{
				"func",
				"$get_arg",
				[]interface{}{"result", "i64"},
			},
		},
		[]interface{}{
			"import",
			sexp.Quote("wag"),
			sexp.Quote("set_result"),
			[]interface{}{
				"func",
				"$set_result",
				[]interface{}{"param", "i32"},
			},
		},
	}, module[1:]...)

	var realStartName string
	exports := make(map[string]string)

	for i := 1; i < len(module); {
		item, ok := module[i].([]interface{})
		if !ok {
			i++
			continue
		}

		var itemName string
		switch x := item[0].(type) {
		case string:
			itemName = x

		case sexp.Quoted:
			itemName = x.String()
		}

		switch itemName {
		case "start":
			realStartName = item[1].(string)
			module = append(module[:i], module[i+1:]...)

		case "export":
			exports[item[1].(string)] = item[2].(string)
			module = append(module[:i], module[i+1:]...)

		case "func":
			if len(item) > 1 {
				if expo, ok := item[1].([]interface{}); ok && expo[0].(string) == "export" {
					item[1] = "$" + expo[1].(sexp.Quoted).String()
				}

				if s, ok := item[1].(string); ok && len(item) > 2 {
					if expo, ok := item[2].([]interface{}); ok && expo[0].(string) == "export" {
						exports[expo[1].(sexp.Quoted).String()] = s[1:]
					}
				}
			}
			i++

		default:
			i++
		}
	}

	testTypes := make(map[int]string)

	testFunc := []interface{}{
		"func",
		"$test",
		[]interface{}{
			"call",
			"$set_result",
			[]interface{}{"i32.const", "0xbadc0de"},
		},
	}

	if realStartName != "" {
		testFunc = append(testFunc, []interface{}{
			"if",
			[]interface{}{
				"i64.eq",
				[]interface{}{"call", "$get_arg"},
				[]interface{}{"i64.const", "0"},
			},
			[]interface{}{
				"block",
				[]interface{}{"call", realStartName},
				[]interface{}{
					"set_global",
					"$test_result",
					[]interface{}{"i64.const", "777"},
				},
				[]interface{}{"return"},
			},
		})
	}

	var idCount int

	for {
		id := idCount

		assert, tail := sexp.ParsePanic(data)
		if assert == nil {
			data = tail
			break
		}

		testType := assert[0].(string)
		if testType == "module" {
			break
		}

		idCount++
		data = tail

		var argCount int
		var exprType string

		for _, x := range assert[1:] {
			if expr, ok := x.([]interface{}); ok {
				argCount++

				exprName := expr[0].(string)
				if strings.Contains(exprName, ".") {
					exprType = strings.SplitN(exprName, ".", 2)[0]
					break
				}
			}
		}

		if argCount > 1 && exprType == "" {
			t.Fatalf("can't figure out type of %s", sexp.Stringify(assert, true))
		}

		invoke2call(exports, assert[1:])

		var test []interface{}

		switch testType {
		case "assert_return":
			if argCount > 1 {
				var check interface{}

				switch exprType {
				case "f32", "f64":
					bitsType := strings.Replace(exprType, "f", "i", 1)

					check = []interface{}{
						bitsType + ".eq",
						[]interface{}{
							bitsType + ".reinterpret/" + exprType,
							assert[1],
						},
						[]interface{}{
							bitsType + ".reinterpret/" + exprType,
							assert[2],
						},
					}

				default:
					check = []interface{}{exprType + ".eq", assert[1], assert[2]}
				}

				test = []interface{}{
					"block",
					[]interface{}{
						"call",
						"$set_result",
						check,
					},
					[]interface{}{"return"},
				}
			} else {
				test = append([]interface{}{"block"}, assert[1:]...)
				test = append(test, []interface{}{
					"block",
					[]interface{}{
						"call",
						"$set_result",
						[]interface{}{"i32.const", "1"},
					},
					[]interface{}{"return"},
				})
			}

		case "assert_trap":
			test = []interface{}{
				"block",
				assert[1],
				[]interface{}{"return"},
			}

		case "invoke":
			n := assert[1].(sexp.Quoted).String()
			name, found := exports[n]
			if !found {
				name = n
			}

			test = []interface{}{
				"block",
				append([]interface{}{"call", "$" + name}, assert[2:]...),
				[]interface{}{
					"call",
					"$set_result",
					[]interface{}{"i32.const", "-1"},
				},
				[]interface{}{"return"},
			}

		default:
			testType = ""
		}

		testTypes[id] = testType

		if test != nil {
			testFunc = append(testFunc, []interface{}{
				"if",
				[]interface{}{
					"i64.eq",
					[]interface{}{"call", "$get_arg"},
					[]interface{}{"i64.const", strconv.Itoa(id)},
				},
				test,
			})
		}
	}

	module = append(module, testFunc)
	module = append(module, []interface{}{"start", "$test"})

	if dumpExps {
		fmt.Println(sexp.Stringify(module, true))
	}

	{
		wasmReadCloser := wast2wasm(sexp.Unparse(module), quiet)
		defer wasmReadCloser.Close()
		wasm := bufio.NewReader(wasmReadCloser)

		var timedout bool

		p, err := runner.NewProgram(maxTextSize, maxRODataSize)
		if err != nil {
			t.Fatal(err)
		}
		defer func() {
			if !timedout {
				p.Close()
			}
		}()

		var nameSection sections.NameSection

		m := Module{
			UnknownSectionLoader: sections.UnknownLoaders{
				"name": nameSection.Load,
			}.Load,
		}

		m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil)
		p.Seal()
		p.SetData(m.Data())
		p.SetFunctionMap(m.FunctionMap())
		p.SetCallMap(m.CallMap())
		minMemorySize, maxMemorySize := m.MemoryLimits()

		if dumpText && testing.Verbose() {
			dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), &nameSection)
		}

		if dumpROData {
			buf := m.ROData()
			for i := 0; len(buf) > 0; i++ {
				if len(buf) > 4 {
					t.Logf("read-only data #%d*8: 0x%08x 0x%08x", i, binary.LittleEndian.Uint32(buf[:4]), binary.LittleEndian.Uint32(buf[4:8]))
					buf = buf[8:]
				} else {
					t.Logf("read-only data #%d*8: 0x%08x", i, binary.LittleEndian.Uint32(buf[:4]))
					buf = buf[4:]
				}
			}
		}

		if dumpGlobals {
			data, memoryOffset := m.Data()
			buf := data[:memoryOffset]

			if len(buf) == 0 {
				t.Log("no globals")
			}

			for i := 0; len(buf) > 0; i++ {
				t.Logf("global #%d: 0x%016x", i, binary.LittleEndian.Uint64(buf))
				buf = buf[8:]
			}
		}

		if dumpMemory {
			data, memoryOffset := m.Data()
			t.Logf("memory: %#v", data[memoryOffset:])
		}

		memGrowSize := maxMemorySize
		if maxMemorySize > 0 && memGrowSize > maxMemorySize {
			memGrowSize = maxMemorySize
		}

		r, err := p.NewRunner(minMemorySize, memGrowSize, stackSize)
		if err != nil {
			t.Fatal(err)
		}
		defer func() {
			if !timedout {
				r.Close()
			}
		}()

		if realStartName != "" {
			var printBuf bytes.Buffer
			result, err := r.Run(0, m.Signatures(), &printBuf)
			if printBuf.Len() > 0 {
				t.Logf("run: module %s: print:\n%s", filename, string(printBuf.Bytes()))
			}
			if err != nil {
				t.Fatal(err)
			}
			if result != 777 {
				t.Fatalf("0x%x", result)
			}
			t.Logf("run: module %s: start", filename)
		}

		for id := 0; id < idCount; id++ {
			testType := testTypes[id]
			if testType == "" {
				t.Logf("run: module %s: test #%d: not supported", filename, id)
				continue
			}

			var printBuf bytes.Buffer
			var result int32
			var panicked interface{}
			done := make(chan struct{})

			go func() {
				defer close(done)
				defer func() {
					panicked = recover()
				}()
				result, err = r.Run(int64(id), m.Signatures(), &printBuf)
			}()

			timer := time.NewTimer(timeout)

			select {
			case <-done:
				timer.Stop()

			case <-timer.C:
				timedout = true
				t.Fatalf("run: module %s: test #%d: timeout", filename, id)
			}

			if printBuf.Len() > 0 {
				t.Logf("run: module %s: test #%d: print output:\n%s", filename, id, string(printBuf.Bytes()))
			}

			if panicked != nil {
				t.Fatalf("run: module %s: test #%d: panic: %v", filename, id, panicked)
			}

			var stackBuf bytes.Buffer
			if err := r.WriteStacktraceTo(&stackBuf, m.FunctionSignatures(), &nameSection); err == nil {
				if stackBuf.Len() > 0 {
					t.Logf("run: module %s: test #%d: stacktrace:\n%s", filename, id, string(stackBuf.Bytes()))
				}
			} else {
				t.Errorf("run: module %s: test #%d: stacktrace error: %v", filename, id, err)
			}

			if err != nil {
				if _, ok := err.(traps.Id); ok {
					if testType == "assert_trap" {
						t.Logf("run: module %s: test #%d: pass", filename, id)
					} else {
						t.Errorf("run: module %s: test #%d: FAIL due to unexpected trap", filename, id)
					}
				} else {
					t.Fatal(err)
				}
			} else {
				if testType == "assert_return" {
					switch result {
					case 1:
						t.Logf("run: module %s: test #%d: pass", filename, id)

					case 0:
						t.Errorf("run: module %s: test #%d: FAIL", filename, id)

					default:
						t.Fatalf("run: module %s: test #%d: bad result: 0x%x", filename, id, result)
					}
				} else if testType == "invoke" {
					switch result {
					case -1:
						t.Logf("run: module %s: test #%d: invoke", filename, id)

					default:
						t.Fatalf("run: module %s: test #%d: bad result: 0x%x", filename, id, result)
					}
				} else {
					t.Errorf("run: module %s: test #%d: FAIL due to unexpected return (result: 0x%x)", filename, id, result)
				}
			}
		}
	}

	return data
}
Пример #4
0
func TestExec(t *testing.T) {
	const (
		maxTextSize   = 65536
		maxRODataSize = 4096
		stackSize     = 4096

		dumpText = false
	)

	data, err := ioutil.ReadFile("testdata/exec.wast")
	if err != nil {
		t.Fatal(err)
	}

	wasmReadCloser := wast2wasm(data, false)
	defer wasmReadCloser.Close()
	wasm := bufio.NewReader(wasmReadCloser)

	var m Module
	m.loadPreliminarySections(wasm, runner.Env)

	var codeBuf bytes.Buffer

	if ok, err := sections.CopyCodeSection(&codeBuf, wasm); err != nil {
		t.Fatal(err)
	} else if !ok {
		t.Fatal(ok)
	}

	// skip name section
	if err := sections.DiscardUnknownSections(wasm); err != nil {
		t.Fatal(err)
	}

	minMemorySize, maxMemorySize := m.MemoryLimits()

	p, err := runner.NewProgram(maxTextSize, maxRODataSize)
	if err != nil {
		t.Fatal(err)
	}
	defer p.Close()

	r, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize)
	if err != nil {
		t.Fatal(err)
	}
	defer r.Close()

	var printBuf bytes.Buffer
	e, trigger := r.NewExecutor(m.Signatures(), &printBuf)

	m.loadDataSection(wasm)
	p.SetData(m.Data())
	m.loadCodeSection(&codeBuf, p.Text, p.ROData, p.RODataAddr(), trigger)
	p.Seal()
	p.SetFunctionMap(m.FunctionMap())
	p.SetCallMap(m.CallMap())
	if _, err := e.Wait(); err != nil {
		t.Fatal(err)
	}

	if printBuf.Len() > 0 {
		t.Logf("print output:\n%s", string(printBuf.Bytes()))
	}

	if dumpText && testing.Verbose() {
		dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil)
	}
}
Пример #5
0
func TestSnapshot(t *testing.T) {
	const (
		maxTextSize   = 65536
		maxRODataSize = 4096
		stackSize     = 4096

		dumpText = false
	)

	data, err := ioutil.ReadFile("testdata/snapshot.wast")
	if err != nil {
		t.Fatal(err)
	}

	wasmReadCloser := wast2wasm(data, false)
	defer wasmReadCloser.Close()
	wasm := bufio.NewReader(wasmReadCloser)

	p, err := runner.NewProgram(maxTextSize, maxRODataSize)
	if err != nil {
		t.Fatal(err)
	}
	defer p.Close()

	var m Module
	m.load(wasm, runner.Env, p.Text, p.ROData, p.RODataAddr(), nil)
	p.Seal()
	p.SetData(m.Data())
	p.SetFunctionMap(m.FunctionMap())
	p.SetCallMap(m.CallMap())
	minMemorySize, maxMemorySize := m.MemoryLimits()

	if dumpText && testing.Verbose() {
		dewag.PrintTo(os.Stdout, m.Text(), m.FunctionMap(), nil)
	}

	var printBuf bytes.Buffer

	r1, err := p.NewRunner(minMemorySize, maxMemorySize, stackSize)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r1.Run(0, m.Signatures(), &printBuf)
	r1.Close()

	if printBuf.Len() > 0 {
		t.Logf("print output:\n%s", string(printBuf.Bytes()))
	}

	if len(r1.Snapshots) != 1 {
		t.Fatal(r1.Snapshots)
	}
	s := r1.Snapshots[0]

	t.Log("resuming")

	printBuf.Reset()

	r2, err := s.NewRunner(maxMemorySize, stackSize)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r2.Run(0, m.Signatures(), &printBuf)
	r2.Close()

	if printBuf.Len() > 0 {
		t.Logf("print output:\n%s", string(printBuf.Bytes()))
	}

	if len(r2.Snapshots) != 0 {
		t.Fatal(r2.Snapshots)
	}
}