Add assert_invalid tests and harden against malicious binary. (#30)

This commit is contained in:
Takeshi Yoneda
2021-10-15 10:00:47 +09:00
committed by GitHub
parent 2e34bad267
commit 69dbb2f8fb
15 changed files with 1051 additions and 524 deletions

View File

@@ -47,11 +47,4 @@ jobs:
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
- name: Install TinyGo
run: |
wget https://github.com/tinygo-org/tinygo/releases/download/v0.20.0/tinygo_0.20.0_amd64.deb
sudo dpkg -i tinygo_0.20.0_amd64.deb
- run: make build.examples
- run: make test

View File

@@ -1,6 +1,6 @@
# Gasm
A minimal implementation of Wasm Virtual machine purely written in Go. The VM passes all the [Wasm Spec test suites](https://github.com/WebAssembly/spec/tree/master/test/core) and is fully compatible Wasm v1.0 Specification.
A minimal implementation of Wasm Virtual Machine purely written in Go. The VM passes all the [Wasm Spec test suites](https://github.com/WebAssembly/spec/tree/wg-1.0/test/core) and is fully compatible with the WebAssembly v1.0 Specification.
The VM can be embedded in your Go program without any dependency like cgo, and enables Gophers to write Wasm host environments easily.

View File

@@ -15,52 +15,58 @@ type ConstantExpression struct {
Data []byte
}
func (s *Store) executeConstExpression(target *ModuleInstance, expr *ConstantExpression) (v interface{}, err error) {
func (s *Store) executeConstExpression(target *ModuleInstance, expr *ConstantExpression) (v interface{}, valueType ValueType, err error) {
r := bytes.NewBuffer(expr.Data)
switch expr.OptCode {
case OptCodeI32Const:
v, _, err = leb128.DecodeInt32(r)
if err != nil {
return nil, fmt.Errorf("read uint32: %w", err)
return nil, 0, fmt.Errorf("read uint32: %w", err)
}
return v, ValueTypeI32, nil
case OptCodeI64Const:
v, _, err = leb128.DecodeInt32(r)
if err != nil {
return nil, fmt.Errorf("read uint64: %w", err)
return nil, 0, fmt.Errorf("read uint64: %w", err)
}
return v, ValueTypeI64, nil
case OptCodeF32Const:
v, err = readFloat32(r)
if err != nil {
return nil, fmt.Errorf("read f34: %w", err)
return nil, 0, fmt.Errorf("read f34: %w", err)
}
return v, ValueTypeF32, nil
case OptCodeF64Const:
v, err = readFloat64(r)
if err != nil {
return nil, fmt.Errorf("read f64: %w", err)
return nil, 0, fmt.Errorf("read f64: %w", err)
}
return v, ValueTypeF64, nil
case OptCodeGlobalGet:
id, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read index of global: %w", err)
return nil, 0, fmt.Errorf("read index of global: %w", err)
}
if uint32(len(target.GlobalsAddrs)) <= id {
return nil, fmt.Errorf("global index out of range")
return nil, 0, fmt.Errorf("global index out of range")
}
g := s.Globals[target.GlobalsAddrs[id]]
switch g.Type.ValType {
case ValueTypeI32:
v = int32(g.Val)
return v, ValueTypeI32, nil
case ValueTypeI64:
v = int64(g.Val)
return v, ValueTypeI64, nil
case ValueTypeF32:
v = math.Float32frombits(uint32(g.Val))
return v, ValueTypeF32, nil
case ValueTypeF64:
v = math.Float64frombits(uint64(g.Val))
return v, ValueTypeF64, nil
}
default:
return nil, fmt.Errorf("invalid opt code: %#x", expr.OptCode)
}
return v, nil
return nil, 0, fmt.Errorf("invalid opt code: %#x", expr.OptCode)
}
func readConstantExpression(r io.Reader) (*ConstantExpression, error) {

View File

@@ -56,12 +56,13 @@ const (
OptCodeMemorySize OptCode = 0x3f
OptCodeMemoryGrow OptCode = 0x40
// numeric instruction
// const instructions.
OptCodeI32Const OptCode = 0x41
OptCodeI64Const OptCode = 0x42
OptCodeF32Const OptCode = 0x43
OptCodeF64Const OptCode = 0x44
// numeric instructions.
OptCodeI32eqz OptCode = 0x45
OptCodeI32eq OptCode = 0x46
OptCodeI32ne OptCode = 0x47

View File

@@ -70,7 +70,7 @@ func (m *Module) readSections(r *Reader) error {
err = errors.New("invalid section id")
}
if sectionContentStart+int(ss) != r.read {
if err == nil && sectionContentStart+int(ss) != r.read {
err = fmt.Errorf("invalid section length: expected to be %d but got %d", ss, r.read-sectionContentStart)
}
@@ -149,7 +149,7 @@ func (m *Module) readSectionFunctions(r *Reader) error {
for i := range m.FunctionSection {
m.FunctionSection[i], _, err = leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("get typeidx: %v", err)
return fmt.Errorf("get type index: %v", err)
}
}
return nil
@@ -215,7 +215,9 @@ func (m *Module) readSectionExports(r *Reader) error {
if err != nil {
return fmt.Errorf("read export: %v", err)
}
if _, ok := m.ExportSection[expDesc.Name]; ok {
return fmt.Errorf("duplicate export name: %s", expDesc.Name)
}
m.ExportSection[expDesc.Name] = expDesc
}
return nil
@@ -257,7 +259,7 @@ func (m *Module) readSectionCodes(r *Reader) error {
for i := range m.CodeSection {
m.CodeSection[i], err = readCodeSegment(r)
if err != nil {
return fmt.Errorf("read code segment: %v", err)
return fmt.Errorf("read %d-th code segment: %v", i, err)
}
}
return nil

View File

@@ -3,12 +3,18 @@ package wasm
import (
"fmt"
"io"
"io/ioutil"
"math"
"github.com/mathetake/gasm/wasm/leb128"
)
const (
ImportKindFunction = 0x00
ImportKindTable = 0x01
ImportKindMemory = 0x02
ImportKindGlobal = 0x03
)
type ImportDesc struct {
Kind byte
@@ -25,41 +31,40 @@ func readImportDesc(r io.Reader) (*ImportDesc, error) {
}
switch b[0] {
case 0x00:
case ImportKindFunction:
tID, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read typeindex: %v", err)
}
return &ImportDesc{
Kind: 0x00,
Kind: ImportKindFunction,
TypeIndexPtr: &tID,
}, nil
case 0x01:
case ImportKindTable:
tt, err := readTableType(r)
if err != nil {
return nil, fmt.Errorf("read table type: %v", err)
}
return &ImportDesc{
Kind: 0x01,
Kind: ImportKindTable,
TableTypePtr: tt,
}, nil
case 0x02:
case ImportKindMemory:
mt, err := readMemoryType(r)
if err != nil {
return nil, fmt.Errorf("read table type: %v", err)
}
return &ImportDesc{
Kind: 0x02,
Kind: ImportKindMemory,
MemTypePtr: mt,
}, nil
case 0x03:
case ImportKindGlobal:
gt, err := readGlobalType(r)
if err != nil {
return nil, fmt.Errorf("read global type: %v", err)
}
return &ImportDesc{
Kind: 0x03,
Kind: ImportKindGlobal,
GlobalTypePtr: gt,
}, nil
default:
@@ -206,8 +211,9 @@ func readElementSegment(r io.Reader) (*ElementSegment, error) {
}
type CodeSegment struct {
NumLocals uint32
Body []byte
NumLocals uint32
LocalTypes []ValueType
Body []byte
}
func readCodeSegment(r io.Reader) (*CodeSegment, error) {
@@ -221,29 +227,46 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
// parse locals
ls, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get the size locals: %w", err)
return nil, fmt.Errorf("get the size locals: %v", err)
}
var numLocals uint64
var nums []uint64
var types []ValueType
var sum uint64
b := make([]byte, 1)
for i := uint32(0); i < ls; i++ {
n, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read n of locals: %w", err)
return nil, fmt.Errorf("read n of locals: %v", err)
}
numLocals += uint64(n)
sum += uint64(n)
nums = append(nums, uint64(n))
if _, err := io.ReadFull(r, b); err != nil {
return nil, fmt.Errorf("read type of local")
_, err = io.ReadFull(r, b)
if err != nil {
return nil, fmt.Errorf("read type of local: %v", err)
}
switch vt := ValueType(b[0]); vt {
case ValueTypeI32, ValueTypeF32, ValueTypeI64, ValueTypeF64:
types = append(types, vt)
default:
return nil, fmt.Errorf("invalid local type: 0x%x", vt)
}
}
if numLocals > math.MaxUint32 {
return nil, fmt.Errorf("too many locals: %d", numLocals)
if sum > math.MaxUint32 {
return nil, fmt.Errorf("too many locals: %d", sum)
}
// extract body
body, err := ioutil.ReadAll(r)
var localTypes []ValueType
for i, num := range nums {
t := types[i]
for j := uint64(0); j < num; j++ {
localTypes = append(localTypes, t)
}
}
body, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("read body: %w", err)
}
@@ -253,8 +276,9 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
}
return &CodeSegment{
Body: body[:len(body)-1],
NumLocals: uint32(numLocals),
Body: body,
NumLocals: uint32(sum),
LocalTypes: localTypes,
}, nil
}

View File

@@ -1,231 +0,0 @@
package wasm
import (
"bytes"
"errors"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReadImportDesc(t *testing.T) {
t.Run("ng", func(t *testing.T) {
buf := []byte{0x04}
_, err := readImportDesc(bytes.NewBuffer(buf))
require.True(t, errors.Is(err, ErrInvalidByte))
t.Log(err)
})
for i, c := range []struct {
bytes []byte
exp *ImportDesc
}{
{
bytes: []byte{0x00, 0x0a},
exp: &ImportDesc{
Kind: 0,
TypeIndexPtr: uint32Ptr(10),
},
},
{
bytes: []byte{0x01, 0x70, 0x0, 0x0a},
exp: &ImportDesc{
Kind: 1,
TableTypePtr: &TableType{
ElemType: 0x70,
Limit: &LimitsType{Min: 10},
},
},
},
{
bytes: []byte{0x02, 0x0, 0x0a},
exp: &ImportDesc{
Kind: 2,
MemTypePtr: &MemoryType{Min: 10},
},
},
{
bytes: []byte{0x03, 0x7e, 0x01},
exp: &ImportDesc{
Kind: 3,
GlobalTypePtr: &GlobalType{ValType: ValueTypeI64, Mutable: true},
},
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readImportDesc(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadImportSegment(t *testing.T) {
exp := &ImportSegment{
Module: "abc",
Name: "ABC",
Desc: &ImportDesc{Kind: 0, TypeIndexPtr: uint32Ptr(10)},
}
buf := []byte{byte(len(exp.Module))}
buf = append(buf, exp.Module...)
buf = append(buf, byte(len(exp.Name)))
buf = append(buf, exp.Name...)
buf = append(buf, 0x00, 0x0a)
actual, err := readImportSegment(bytes.NewBuffer(buf))
require.NoError(t, err)
assert.Equal(t, exp, actual)
}
func TestReadGlobalSegment(t *testing.T) {
exp := &GlobalSegment{
Type: &GlobalType{ValType: ValueTypeI64, Mutable: false},
Init: &ConstantExpression{
OptCode: OptCodeI64Const,
Data: []byte{0x01},
},
}
buf := []byte{0x7e, 0x00, 0x42, 0x01, 0x0b}
actual, err := readGlobalSegment(bytes.NewBuffer(buf))
require.NoError(t, err)
assert.Equal(t, exp, actual)
}
func TestReadExportDesc(t *testing.T) {
t.Run("ng", func(t *testing.T) {
buf := []byte{0x04}
_, err := readExportDesc(bytes.NewBuffer(buf))
require.True(t, errors.Is(err, ErrInvalidByte))
t.Log(err)
})
for i, c := range []struct {
bytes []byte
exp *ExportDesc
}{
{
bytes: []byte{0x00, 0x0a},
exp: &ExportDesc{Kind: 0, Index: 10},
},
{
bytes: []byte{0x01, 0x05},
exp: &ExportDesc{Kind: 1, Index: 5},
},
{
bytes: []byte{0x02, 0x01},
exp: &ExportDesc{Kind: 2, Index: 1},
},
{
bytes: []byte{0x03, 0x0b},
exp: &ExportDesc{Kind: 3, Index: 11},
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readExportDesc(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadExportSegment(t *testing.T) {
exp := &ExportSegment{
Name: "ABC",
Desc: &ExportDesc{Kind: 0, Index: 10},
}
buf := []byte{byte(len(exp.Name))}
buf = append(buf, exp.Name...)
buf = append(buf, 0x00, 0x0a)
actual, err := readExportSegment(bytes.NewBuffer(buf))
require.NoError(t, err)
assert.Equal(t, exp, actual)
}
func TestReadElementSegment(t *testing.T) {
for i, c := range []struct {
bytes []byte
exp *ElementSegment
}{
{
bytes: []byte{0xa, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07},
exp: &ElementSegment{
TableIndex: 10,
OffsetExpr: &ConstantExpression{
OptCode: OptCodeI32Const,
Data: []byte{0x01},
},
Init: []uint32{5, 7},
},
},
{
bytes: []byte{0x3, 0x41, 0x04, 0x0b, 0x01, 0x0a},
exp: &ElementSegment{
TableIndex: 3,
OffsetExpr: &ConstantExpression{
OptCode: OptCodeI32Const,
Data: []byte{0x04},
},
Init: []uint32{10},
},
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readElementSegment(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadCodeSegment(t *testing.T) {
buf := []byte{0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x12, 0x3, 0x01, 0x0b}
exp := &CodeSegment{
NumLocals: 0x01,
Body: []byte{0x1, 0x1, 0x12, 0x3, 0x01},
}
actual, err := readCodeSegment(bytes.NewBuffer(buf))
require.NoError(t, err)
assert.Equal(t, exp, actual)
}
func TestDataSegment(t *testing.T) {
for i, c := range []struct {
bytes []byte
exp *DataSegment
}{
{
bytes: []byte{0x0, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07},
exp: &DataSegment{
OffsetExpression: &ConstantExpression{
OptCode: OptCodeI32Const,
Data: []byte{0x01},
},
Init: []byte{5, 7},
},
},
{
bytes: []byte{0x0, 0x41, 0x04, 0x0b, 0x01, 0x0a},
exp: &DataSegment{
OffsetExpression: &ConstantExpression{
OptCode: OptCodeI32Const,
Data: []byte{0x04},
},
Init: []byte{0x0a},
},
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readDataSegment(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}

View File

@@ -202,8 +202,8 @@ func TestSpecification(t *testing.T) {
var base testbase
require.NoError(t, json.Unmarshal(raw, &base))
wastName := filepath.Base(base.SourceFile)
t.Run(wastName, func(t *testing.T) {
vm, err := wasm.NewVM()
require.NoError(t, err)
@@ -317,7 +317,17 @@ func TestSpecification(t *testing.T) {
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_invalid":
// TODO:
if c.ModuleType == "text" {
// We don't support direct loading of wast yet.
t.Skip()
}
buf, err := os.ReadFile(filepath.Join(caseDir, c.Filename))
require.NoError(t, err, msg)
mod, err := wasm.DecodeModule(buf)
if err == nil {
err = vm.InstantiateModule(mod, "")
}
require.Error(t, err, msg)
case "assert_exhaustion":
// TODO:
case "assert_unlinkable":

File diff suppressed because it is too large Load Diff

View File

@@ -39,6 +39,8 @@ func readFunctionType(r io.Reader) (*FunctionType, error) {
s, _, err = leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get the size of output value types: %w", err)
} else if s > 1 {
return nil, fmt.Errorf("multi value results not supported")
}
op, err := readValueTypes(r, s)
@@ -116,7 +118,21 @@ func readTableType(r io.Reader) (*TableType, error) {
type MemoryType = LimitsType
func readMemoryType(r io.Reader) (*MemoryType, error) {
return readLimitsType(r)
ret, err := readLimitsType(r)
if err != nil {
return nil, err
}
if ret.Min > uint32(PageSize) {
return nil, fmt.Errorf("memory min must be at most 65536 pages (4GiB)")
}
if ret.Max != nil {
if *ret.Max < ret.Min {
return nil, fmt.Errorf("memory size minimum must not be greater than maximum")
} else if *ret.Max > uint32(PageSize) {
return nil, fmt.Errorf("memory max must be at most 65536 pages (4GiB)")
}
}
return ret, nil
}
type GlobalType struct {

View File

@@ -1,155 +0,0 @@
package wasm
import (
"bytes"
"errors"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReadFunctionType(t *testing.T) {
t.Run("ng", func(t *testing.T) {
buf := []byte{0x00}
_, err := readFunctionType(bytes.NewBuffer(buf))
assert.True(t, errors.Is(err, ErrInvalidByte))
t.Log(err)
})
for i, c := range []struct {
bytes []byte
exp *FunctionType
}{
{
bytes: []byte{0x60, 0x0, 0x0},
exp: &FunctionType{
InputTypes: []ValueType{},
ReturnTypes: []ValueType{},
},
},
{
bytes: []byte{0x60, 0x2, 0x7f, 0x7e, 0x0},
exp: &FunctionType{
InputTypes: []ValueType{ValueTypeI32, ValueTypeI64},
ReturnTypes: []ValueType{},
},
},
{
bytes: []byte{0x60, 0x1, 0x7e, 0x2, 0x7f, 0x7e},
exp: &FunctionType{
InputTypes: []ValueType{ValueTypeI64},
ReturnTypes: []ValueType{ValueTypeI32, ValueTypeI64},
},
},
{
bytes: []byte{0x60, 0x0, 0x2, 0x7f, 0x7e},
exp: &FunctionType{
InputTypes: []ValueType{},
ReturnTypes: []ValueType{ValueTypeI32, ValueTypeI64},
},
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readFunctionType(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadLimitsType(t *testing.T) {
for i, c := range []struct {
bytes []byte
exp *LimitsType
}{
{bytes: []byte{0x00, 0xa}, exp: &LimitsType{Min: 10}},
{bytes: []byte{0x01, 0xa, 0xa}, exp: &LimitsType{Min: 10, Max: uint32Ptr(10)}},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readLimitsType(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func uint32Ptr(in uint32) *uint32 {
return &in
}
func TestReadTableType(t *testing.T) {
t.Run("ng", func(t *testing.T) {
buf := []byte{0x00}
_, err := readTableType(bytes.NewBuffer(buf))
require.True(t, errors.Is(err, ErrInvalidByte))
t.Log(err)
})
for i, c := range []struct {
bytes []byte
exp *TableType
}{
{
bytes: []byte{0x70, 0x00, 0xa},
exp: &TableType{
ElemType: 0x70,
Limit: &LimitsType{Min: 10},
},
},
{
bytes: []byte{0x70, 0x01, 0x01, 0xa},
exp: &TableType{
ElemType: 0x70,
Limit: &LimitsType{Min: 1, Max: uint32Ptr(10)},
},
},
} {
c := c
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readTableType(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadMemoryType(t *testing.T) {
for i, c := range []struct {
bytes []byte
exp *MemoryType
}{
{bytes: []byte{0x00, 0xa}, exp: &MemoryType{Min: 10}},
{bytes: []byte{0x01, 0xa, 0xa}, exp: &MemoryType{Min: 10, Max: uint32Ptr(10)}},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readMemoryType(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}
func TestReadGlobalType(t *testing.T) {
t.Run("ng", func(t *testing.T) {
buf := []byte{0x7e, 0x3}
_, err := readGlobalType(bytes.NewBuffer(buf))
require.True(t, errors.Is(err, ErrInvalidByte))
t.Log(err)
})
for i, c := range []struct {
bytes []byte
exp *GlobalType
}{
{bytes: []byte{0x7e, 0x00}, exp: &GlobalType{ValType: ValueTypeI64, Mutable: false}},
{bytes: []byte{0x7e, 0x01}, exp: &GlobalType{ValType: ValueTypeI64, Mutable: true}},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
actual, err := readGlobalType(bytes.NewBuffer(c.bytes))
require.NoError(t, err)
assert.Equal(t, c.exp, actual)
})
}
}

View File

@@ -3,6 +3,7 @@ package wasm
import (
"fmt"
"io"
"unicode/utf8"
"github.com/mathetake/gasm/wasm/leb128"
)
@@ -46,6 +47,10 @@ func readNameValue(r io.Reader) (string, error) {
return "", fmt.Errorf("read bytes of name: %v", err)
}
if !utf8.Valid(buf) {
return "", fmt.Errorf("name must be valid as utf8")
}
return string(buf), nil
}

View File

@@ -61,7 +61,7 @@ func (vm *VirtualMachine) ExecExportedFunction(moduleName, funcName string, args
exp, ok := m.Exports[funcName]
if !ok {
return nil, nil, fmt.Errorf("exported func of name '%s' not found in '%s'", funcName, moduleName)
return nil, nil, fmt.Errorf("exported function '%s' not found in '%s'", funcName, moduleName)
}
if exp.Kind != ExportKindFunction {

View File

@@ -15,7 +15,8 @@ type (
}
NativeFunction struct {
Signature *FunctionType
NumLocal uint32
NumLocals uint32
LocalTypes []ValueType
Body []byte
Blocks map[uint64]*NativeFunctionBlock
ModuleInstance *ModuleInstance
@@ -24,6 +25,8 @@ type (
StartAt, ElseAt, EndAt uint64
BlockType *FunctionType
BlockTypeBytes uint64
IsLoop bool // TODO: might not be necessary
IsIf bool // TODO: might not be necessary
}
)
@@ -83,7 +86,7 @@ func (h *HostFunction) Call(vm *VirtualMachine) {
func (n *NativeFunction) Call(vm *VirtualMachine) {
al := len(n.Signature.InputTypes)
locals := make([]uint64, n.NumLocal+uint32(al))
locals := make([]uint64, n.NumLocals+uint32(al))
for i := 0; i < al; i++ {
locals[al-1-i] = vm.OperandStack.Pop()
}

View File

@@ -12,7 +12,6 @@ func setGlobal(vm *VirtualMachine) {
vm.ActiveContext.PC++
index := vm.FetchUint32()
addr := vm.ActiveContext.Function.ModuleInstance.GlobalsAddrs[index]
// TODO: Check mutatability.
vm.Store.Globals[addr].Val = vm.OperandStack.Pop()
vm.ActiveContext.PC++
}