...

Source file src/github.com/tetratelabs/wazero/internal/wasm/store_test.go

Documentation: github.com/tetratelabs/wazero/internal/wasm

     1  package wasm
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/tetratelabs/wazero/api"
    12  	"github.com/tetratelabs/wazero/experimental"
    13  	"github.com/tetratelabs/wazero/internal/internalapi"
    14  	"github.com/tetratelabs/wazero/internal/leb128"
    15  	"github.com/tetratelabs/wazero/internal/sys"
    16  	"github.com/tetratelabs/wazero/internal/testing/hammer"
    17  	"github.com/tetratelabs/wazero/internal/testing/require"
    18  	"github.com/tetratelabs/wazero/internal/u64"
    19  )
    20  
    21  func TestModuleInstance_Memory(t *testing.T) {
    22  	tests := []struct {
    23  		name        string
    24  		input       *Module
    25  		expected    bool
    26  		expectedLen uint32
    27  	}{
    28  		{
    29  			name:  "no memory",
    30  			input: &Module{},
    31  		},
    32  		{
    33  			name: "memory not exported, one page",
    34  			input: &Module{
    35  				MemorySection:           &Memory{Min: 1, Cap: 1},
    36  				MemoryDefinitionSection: []MemoryDefinition{{}},
    37  			},
    38  		},
    39  		{
    40  			name: "memory exported, different name",
    41  			input: &Module{
    42  				MemorySection:           &Memory{Min: 1, Cap: 1},
    43  				MemoryDefinitionSection: []MemoryDefinition{{}},
    44  				ExportSection:           []Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}},
    45  			},
    46  		},
    47  		{
    48  			name: "memory exported, but zero length",
    49  			input: &Module{
    50  				MemorySection:           &Memory{},
    51  				MemoryDefinitionSection: []MemoryDefinition{{}},
    52  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    53  			},
    54  			expected: true,
    55  		},
    56  		{
    57  			name: "memory exported, one page",
    58  			input: &Module{
    59  				MemorySection:           &Memory{Min: 1, Cap: 1},
    60  				MemoryDefinitionSection: []MemoryDefinition{{}},
    61  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    62  			},
    63  			expected:    true,
    64  			expectedLen: 65536,
    65  		},
    66  		{
    67  			name: "memory exported, two pages",
    68  			input: &Module{
    69  				MemorySection:           &Memory{Min: 2, Cap: 2},
    70  				MemoryDefinitionSection: []MemoryDefinition{{}},
    71  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    72  			},
    73  			expected:    true,
    74  			expectedLen: 65536 * 2,
    75  		},
    76  	}
    77  
    78  	for _, tt := range tests {
    79  		tc := tt
    80  
    81  		t.Run(tc.name, func(t *testing.T) {
    82  			s := newStore()
    83  
    84  			instance, err := s.Instantiate(testCtx, tc.input, "test", nil, nil)
    85  			require.NoError(t, err)
    86  
    87  			mem := instance.ExportedMemory("memory")
    88  			if tc.expected {
    89  				require.Equal(t, tc.expectedLen, mem.Size())
    90  			} else {
    91  				require.Nil(t, mem)
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func TestStore_Instantiate(t *testing.T) {
    98  	s := newStore()
    99  	m, err := NewHostModule(
   100  		"foo",
   101  		[]string{"fn"},
   102  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   103  		api.CoreFeaturesV1,
   104  	)
   105  	require.NoError(t, err)
   106  
   107  	sysCtx := sys.DefaultContext(nil)
   108  	mod, err := s.Instantiate(testCtx, m, "bar", sysCtx, []FunctionTypeID{0})
   109  	require.NoError(t, err)
   110  	defer mod.Close(testCtx)
   111  
   112  	t.Run("ModuleInstance defaults", func(t *testing.T) {
   113  		require.Equal(t, s.nameToModule["bar"], mod)
   114  		require.Equal(t, s.nameToModule["bar"].MemoryInstance, mod.MemoryInstance)
   115  		require.Equal(t, s, mod.s)
   116  		require.Equal(t, sysCtx, mod.Sys)
   117  	})
   118  }
   119  
   120  func TestStore_CloseWithExitCode(t *testing.T) {
   121  	const importedModuleName = "imported"
   122  	const importingModuleName = "test"
   123  
   124  	tests := []struct {
   125  		name       string
   126  		testClosed bool
   127  	}{
   128  		{
   129  			name:       "nothing closed",
   130  			testClosed: false,
   131  		},
   132  		{
   133  			name:       "partially closed",
   134  			testClosed: true,
   135  		},
   136  	}
   137  
   138  	for _, tt := range tests {
   139  		tc := tt
   140  		t.Run(tc.name, func(t *testing.T) {
   141  			s := newStore()
   142  
   143  			_, err := s.Instantiate(testCtx, &Module{
   144  				TypeSection:               []FunctionType{v_v},
   145  				FunctionSection:           []uint32{0},
   146  				CodeSection:               []Code{{Body: []byte{OpcodeEnd}}},
   147  				Exports:                   map[string]*Export{"fn": {Type: ExternTypeFunc, Name: "fn"}},
   148  				FunctionDefinitionSection: []FunctionDefinition{{Functype: &v_v}},
   149  			}, importedModuleName, nil, []FunctionTypeID{0})
   150  			require.NoError(t, err)
   151  
   152  			m2, err := s.Instantiate(testCtx, &Module{
   153  				ImportFunctionCount:     1,
   154  				TypeSection:             []FunctionType{v_v},
   155  				ImportSection:           []Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
   156  				MemorySection:           &Memory{Min: 1, Cap: 1},
   157  				MemoryDefinitionSection: []MemoryDefinition{{}},
   158  				GlobalSection:           []Global{{Type: GlobalType{}, Init: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}},
   159  				TableSection:            []Table{{Min: 10}},
   160  			}, importingModuleName, nil, []FunctionTypeID{0})
   161  			require.NoError(t, err)
   162  
   163  			if tc.testClosed {
   164  				err = m2.CloseWithExitCode(testCtx, 2)
   165  				require.NoError(t, err)
   166  			}
   167  
   168  			err = s.CloseWithExitCode(testCtx, 2)
   169  			require.NoError(t, err)
   170  
   171  			// If Store.CloseWithExitCode was dispatched properly, modules should be empty
   172  			require.Nil(t, s.moduleList)
   173  
   174  			// Store state zeroed
   175  			require.Zero(t, len(s.typeIDs))
   176  		})
   177  	}
   178  }
   179  
   180  func TestStore_hammer(t *testing.T) {
   181  	const importedModuleName = "imported"
   182  
   183  	m, err := NewHostModule(
   184  		importedModuleName,
   185  		[]string{"fn"},
   186  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   187  		api.CoreFeaturesV1,
   188  	)
   189  	require.NoError(t, err)
   190  
   191  	s := newStore()
   192  	imported, err := s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   193  	require.NoError(t, err)
   194  
   195  	_, ok := s.nameToModule[imported.Name()]
   196  	require.True(t, ok)
   197  
   198  	importingModule := &Module{
   199  		ImportFunctionCount:     1,
   200  		TypeSection:             []FunctionType{v_v},
   201  		FunctionSection:         []uint32{0},
   202  		CodeSection:             []Code{{Body: []byte{OpcodeEnd}}},
   203  		MemorySection:           &Memory{Min: 1, Cap: 1},
   204  		MemoryDefinitionSection: []MemoryDefinition{{}},
   205  		GlobalSection: []Global{{
   206  			Type: GlobalType{ValType: ValueTypeI32},
   207  			Init: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
   208  		}},
   209  		TableSection: []Table{{Min: 10}},
   210  		ImportSection: []Import{
   211  			{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   212  		},
   213  	}
   214  
   215  	// Concurrent instantiate, close should test if locks work on the ns. If they don't, we should see leaked modules
   216  	// after all of these complete, or an error raised.
   217  	P := 8               // max count of goroutines
   218  	N := 1000            // work per goroutine
   219  	if testing.Short() { // Adjust down if `-test.short`
   220  		P = 4
   221  		N = 100
   222  	}
   223  	hammer.NewHammer(t, P, N).Run(func(name string) {
   224  		mod, instantiateErr := s.Instantiate(testCtx, importingModule, name, sys.DefaultContext(nil), []FunctionTypeID{0})
   225  		require.NoError(t, instantiateErr)
   226  		require.NoError(t, mod.Close(testCtx))
   227  	}, nil)
   228  	if t.Failed() {
   229  		return // At least one test failed, so return now.
   230  	}
   231  
   232  	// Close the imported module.
   233  	require.NoError(t, imported.Close(testCtx))
   234  
   235  	// All instances are freed.
   236  	require.Nil(t, s.moduleList)
   237  }
   238  
   239  func TestStore_hammer_close(t *testing.T) {
   240  	const importedModuleName = "imported"
   241  
   242  	m, err := NewHostModule(
   243  		importedModuleName,
   244  		[]string{"fn"},
   245  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   246  		api.CoreFeaturesV1,
   247  	)
   248  	require.NoError(t, err)
   249  
   250  	s := newStore()
   251  	imported, err := s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   252  	require.NoError(t, err)
   253  
   254  	_, ok := s.nameToModule[imported.Name()]
   255  	require.True(t, ok)
   256  
   257  	importingModule := &Module{
   258  		ImportFunctionCount:     1,
   259  		TypeSection:             []FunctionType{v_v},
   260  		FunctionSection:         []uint32{0},
   261  		CodeSection:             []Code{{Body: []byte{OpcodeEnd}}},
   262  		MemorySection:           &Memory{Min: 1, Cap: 1},
   263  		MemoryDefinitionSection: []MemoryDefinition{{}},
   264  		GlobalSection: []Global{{
   265  			Type: GlobalType{ValType: ValueTypeI32},
   266  			Init: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
   267  		}},
   268  		TableSection: []Table{{Min: 10}},
   269  		ImportSection: []Import{
   270  			{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   271  		},
   272  	}
   273  
   274  	const instCount = 10000
   275  	instances := make([]api.Module, instCount)
   276  	for i := 0; i < instCount; i++ {
   277  		mod, instantiateErr := s.Instantiate(testCtx, importingModule, strconv.Itoa(i), sys.DefaultContext(nil), []FunctionTypeID{0})
   278  		require.NoError(t, instantiateErr)
   279  		instances[i] = mod
   280  	}
   281  
   282  	hammer.NewHammer(t, 100, 2).Run(func(name string) {
   283  		for i := 0; i < instCount; i++ {
   284  			if i == instCount/2 {
   285  				// Close store concurrently as well.
   286  				err := s.CloseWithExitCode(testCtx, 0)
   287  				require.NoError(t, err)
   288  			}
   289  			err := instances[i].CloseWithExitCode(testCtx, 0)
   290  			require.NoError(t, err)
   291  		}
   292  		require.NoError(t, err)
   293  	}, nil)
   294  	if t.Failed() {
   295  		return // At least one test failed, so return now.
   296  	}
   297  
   298  	// All instances are freed.
   299  	require.Nil(t, s.moduleList)
   300  }
   301  
   302  func TestStore_Instantiate_Errors(t *testing.T) {
   303  	const importedModuleName = "imported"
   304  	const importingModuleName = "test"
   305  
   306  	m, err := NewHostModule(
   307  		importedModuleName,
   308  		[]string{"fn"},
   309  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   310  		api.CoreFeaturesV1,
   311  	)
   312  	require.NoError(t, err)
   313  
   314  	t.Run("Fails if module name already in use", func(t *testing.T) {
   315  		s := newStore()
   316  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   317  		require.NoError(t, err)
   318  
   319  		// Trying to register it again should fail
   320  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   321  		require.EqualError(t, err, "module[imported] has already been instantiated")
   322  	})
   323  
   324  	t.Run("fail resolve import", func(t *testing.T) {
   325  		s := newStore()
   326  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   327  		require.NoError(t, err)
   328  
   329  		hm := s.nameToModule[importedModuleName]
   330  		require.NotNil(t, hm)
   331  
   332  		_, err = s.Instantiate(testCtx, &Module{
   333  			TypeSection: []FunctionType{v_v},
   334  			ImportSection: []Import{
   335  				// The first import resolve succeeds -> increment hm.dependentCount.
   336  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   337  				// But the second one tries to import uninitialized-module ->
   338  				{Type: ExternTypeFunc, Module: "non-exist", Name: "fn", DescFunc: 0},
   339  			},
   340  			ImportPerModule: map[string][]*Import{
   341  				importedModuleName: {{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
   342  				"non-exist":        {{Name: "fn", DescFunc: 0}},
   343  			},
   344  		}, importingModuleName, nil, nil)
   345  		require.EqualError(t, err, "module[non-exist] not instantiated")
   346  	})
   347  
   348  	t.Run("creating engine failed", func(t *testing.T) {
   349  		s := newStore()
   350  
   351  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   352  		require.NoError(t, err)
   353  
   354  		hm := s.nameToModule[importedModuleName]
   355  		require.NotNil(t, hm)
   356  
   357  		engine := s.Engine.(*mockEngine)
   358  		engine.shouldCompileFail = true
   359  
   360  		importingModule := &Module{
   361  			ImportFunctionCount: 1,
   362  			TypeSection:         []FunctionType{v_v},
   363  			FunctionSection:     []uint32{0, 0},
   364  			CodeSection: []Code{
   365  				{Body: []byte{OpcodeEnd}},
   366  				{Body: []byte{OpcodeEnd}},
   367  			},
   368  			ImportSection: []Import{
   369  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   370  			},
   371  		}
   372  
   373  		_, err = s.Instantiate(testCtx, importingModule, importingModuleName, nil, []FunctionTypeID{0})
   374  		require.EqualError(t, err, "some engine creation error")
   375  	})
   376  
   377  	t.Run("start func failed", func(t *testing.T) {
   378  		s := newStore()
   379  		engine := s.Engine.(*mockEngine)
   380  		engine.callFailIndex = 1
   381  
   382  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   383  		require.NoError(t, err)
   384  
   385  		hm := s.nameToModule[importedModuleName]
   386  		require.NotNil(t, hm)
   387  
   388  		startFuncIndex := uint32(1)
   389  		importingModule := &Module{
   390  			ImportFunctionCount: 1,
   391  			TypeSection:         []FunctionType{v_v},
   392  			FunctionSection:     []uint32{0},
   393  			CodeSection:         []Code{{Body: []byte{OpcodeEnd}}},
   394  			StartSection:        &startFuncIndex,
   395  			ImportSection: []Import{
   396  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   397  			},
   398  		}
   399  
   400  		_, err = s.Instantiate(testCtx, importingModule, importingModuleName, nil, []FunctionTypeID{0})
   401  		require.EqualError(t, err, "start function[1] failed: call failed")
   402  	})
   403  }
   404  
   405  type mockEngine struct {
   406  	shouldCompileFail bool
   407  	callFailIndex     int
   408  }
   409  
   410  type mockModuleEngine struct {
   411  	name                 string
   412  	callFailIndex        int
   413  	functionRefs         map[Index]Reference
   414  	resolveImportsCalled map[Index]Index
   415  	importedMemModEngine ModuleEngine
   416  	lookupEntries        map[Index]mockModuleEngineLookupEntry
   417  }
   418  
   419  type mockModuleEngineLookupEntry struct {
   420  	m     *ModuleInstance
   421  	index Index
   422  }
   423  
   424  type mockCallEngine struct {
   425  	internalapi.WazeroOnlyType
   426  	index         Index
   427  	callFailIndex int
   428  }
   429  
   430  func newStore() *Store {
   431  	return NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1})
   432  }
   433  
   434  // CompileModule implements the same method as documented on wasm.Engine.
   435  func (e *mockEngine) Close() error {
   436  	return nil
   437  }
   438  
   439  // CompileModule implements the same method as documented on wasm.Engine.
   440  func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.FunctionListener, bool) error {
   441  	return nil
   442  }
   443  
   444  // LookupFunction implements the same method as documented on wasm.Engine.
   445  func (e *mockModuleEngine) LookupFunction(_ *TableInstance, _ FunctionTypeID, offset Index) (*ModuleInstance, Index) {
   446  	if entry, ok := e.lookupEntries[offset]; ok {
   447  		return entry.m, entry.index
   448  	}
   449  	return nil, 0
   450  }
   451  
   452  // CompiledModuleCount implements the same method as documented on wasm.Engine.
   453  func (e *mockEngine) CompiledModuleCount() uint32 { return 0 }
   454  
   455  // DeleteCompiledModule implements the same method as documented on wasm.Engine.
   456  func (e *mockEngine) DeleteCompiledModule(*Module) {}
   457  
   458  // NewModuleEngine implements the same method as documented on wasm.Engine.
   459  func (e *mockEngine) NewModuleEngine(_ *Module, _ *ModuleInstance) (ModuleEngine, error) {
   460  	if e.shouldCompileFail {
   461  		return nil, fmt.Errorf("some engine creation error")
   462  	}
   463  	return &mockModuleEngine{callFailIndex: e.callFailIndex, resolveImportsCalled: map[Index]Index{}}, nil
   464  }
   465  
   466  // GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
   467  func (e *mockModuleEngine) GetGlobalValue(idx Index) (lo, hi uint64) { panic("BUG") }
   468  
   469  // OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
   470  func (e *mockModuleEngine) OwnsGlobals() bool { return false }
   471  
   472  // DoneInstantiation implements the same method as documented on wasm.ModuleEngine.
   473  func (e *mockModuleEngine) DoneInstantiation() {}
   474  
   475  // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
   476  func (e *mockModuleEngine) FunctionInstanceReference(i Index) Reference {
   477  	return e.functionRefs[i]
   478  }
   479  
   480  // ResolveImportedFunction implements the same method as documented on wasm.ModuleEngine.
   481  func (e *mockModuleEngine) ResolveImportedFunction(index, importedIndex Index, _ ModuleEngine) {
   482  	e.resolveImportsCalled[index] = importedIndex
   483  }
   484  
   485  // ResolveImportedMemory implements the same method as documented on wasm.ModuleEngine.
   486  func (e *mockModuleEngine) ResolveImportedMemory(imp ModuleEngine) {
   487  	e.importedMemModEngine = imp
   488  }
   489  
   490  // NewFunction implements the same method as documented on wasm.ModuleEngine.
   491  func (e *mockModuleEngine) NewFunction(index Index) api.Function {
   492  	return &mockCallEngine{index: index, callFailIndex: e.callFailIndex}
   493  }
   494  
   495  // InitializeFuncrefGlobals implements the same method as documented on wasm.ModuleEngine.
   496  func (e *mockModuleEngine) InitializeFuncrefGlobals(globals []*GlobalInstance) {}
   497  
   498  // Name implements the same method as documented on wasm.ModuleEngine.
   499  func (e *mockModuleEngine) Name() string {
   500  	return e.name
   501  }
   502  
   503  // Close implements the same method as documented on wasm.ModuleEngine.
   504  func (e *mockModuleEngine) Close(context.Context) {
   505  }
   506  
   507  // Call implements the same method as documented on api.Function.
   508  func (ce *mockCallEngine) Definition() api.FunctionDefinition { return nil }
   509  
   510  // Call implements the same method as documented on api.Function.
   511  func (ce *mockCallEngine) Call(ctx context.Context, _ ...uint64) (results []uint64, err error) {
   512  	return nil, ce.CallWithStack(ctx, nil)
   513  }
   514  
   515  // CallWithStack implements the same method as documented on api.Function.
   516  func (ce *mockCallEngine) CallWithStack(_ context.Context, _ []uint64) error {
   517  	if ce.callFailIndex >= 0 && ce.index == Index(ce.callFailIndex) {
   518  		return errors.New("call failed")
   519  	}
   520  	return nil
   521  }
   522  
   523  func TestStore_getFunctionTypeID(t *testing.T) {
   524  	t.Run("too many functions", func(t *testing.T) {
   525  		s := newStore()
   526  		const max = 10
   527  		s.functionMaxTypes = max
   528  		s.typeIDs = make(map[string]FunctionTypeID)
   529  		for i := 0; i < max; i++ {
   530  			s.typeIDs[strconv.Itoa(i)] = 0
   531  		}
   532  		_, err := s.GetFunctionTypeID(&FunctionType{})
   533  		require.Error(t, err)
   534  	})
   535  	t.Run("ok", func(t *testing.T) {
   536  		tests := []FunctionType{
   537  			{Params: []ValueType{}},
   538  			{Params: []ValueType{ValueTypeF32}},
   539  			{Results: []ValueType{ValueTypeF64}},
   540  			{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}},
   541  		}
   542  
   543  		for _, tt := range tests {
   544  			tc := tt
   545  			t.Run(tc.String(), func(t *testing.T) {
   546  				s := newStore()
   547  				actual, err := s.GetFunctionTypeID(&tc)
   548  				require.NoError(t, err)
   549  
   550  				expectedTypeID, ok := s.typeIDs[tc.String()]
   551  				require.True(t, ok)
   552  				require.Equal(t, expectedTypeID, actual)
   553  			})
   554  		}
   555  	})
   556  }
   557  
   558  func TestGlobalInstance_initialize(t *testing.T) {
   559  	t.Run("basic type const expr", func(t *testing.T) {
   560  		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
   561  			t.Run(ValueTypeName(vt), func(t *testing.T) {
   562  				g := &GlobalInstance{Type: GlobalType{ValType: vt}}
   563  				expr := &ConstantExpression{}
   564  				switch vt {
   565  				case ValueTypeI32:
   566  					expr.Data = []byte{1}
   567  					expr.Opcode = OpcodeI32Const
   568  				case ValueTypeI64:
   569  					expr.Data = []byte{2}
   570  					expr.Opcode = OpcodeI64Const
   571  				case ValueTypeF32:
   572  					expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
   573  					expr.Opcode = OpcodeF32Const
   574  				case ValueTypeF64:
   575  					expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
   576  					expr.Opcode = OpcodeF64Const
   577  				}
   578  
   579  				g.initialize(nil, expr, nil)
   580  
   581  				switch vt {
   582  				case ValueTypeI32:
   583  					require.Equal(t, int32(1), int32(g.Val))
   584  				case ValueTypeI64:
   585  					require.Equal(t, int64(2), int64(g.Val))
   586  				case ValueTypeF32:
   587  					require.Equal(t, float32(math.MaxFloat32), math.Float32frombits(uint32(g.Val)))
   588  				case ValueTypeF64:
   589  					require.Equal(t, math.MaxFloat64, math.Float64frombits(g.Val))
   590  				}
   591  			})
   592  		}
   593  	})
   594  	t.Run("ref.null", func(t *testing.T) {
   595  		tests := []struct {
   596  			name string
   597  			expr *ConstantExpression
   598  		}{
   599  			{
   600  				name: "ref.null (externref)",
   601  				expr: &ConstantExpression{
   602  					Opcode: OpcodeRefNull,
   603  					Data:   []byte{RefTypeExternref},
   604  				},
   605  			},
   606  			{
   607  				name: "ref.null (funcref)",
   608  				expr: &ConstantExpression{
   609  					Opcode: OpcodeRefNull,
   610  					Data:   []byte{RefTypeFuncref},
   611  				},
   612  			},
   613  		}
   614  
   615  		for _, tt := range tests {
   616  			tc := tt
   617  			t.Run(tc.name, func(t *testing.T) {
   618  				g := GlobalInstance{}
   619  				g.Type.ValType = tc.expr.Data[0]
   620  				g.initialize(nil, tc.expr, nil)
   621  				require.Equal(t, uint64(0), g.Val)
   622  			})
   623  		}
   624  	})
   625  	t.Run("ref.func", func(t *testing.T) {
   626  		g := GlobalInstance{Type: GlobalType{ValType: RefTypeFuncref}}
   627  		g.initialize(nil,
   628  			&ConstantExpression{Opcode: OpcodeRefFunc, Data: []byte{1}},
   629  			func(funcIndex Index) Reference {
   630  				require.Equal(t, Index(1), funcIndex)
   631  				return 0xdeadbeaf
   632  			},
   633  		)
   634  		require.Equal(t, uint64(0xdeadbeaf), g.Val)
   635  	})
   636  	t.Run("global expr", func(t *testing.T) {
   637  		tests := []struct {
   638  			valueType  ValueType
   639  			val, valHi uint64
   640  		}{
   641  			{valueType: ValueTypeI32, val: 10},
   642  			{valueType: ValueTypeI64, val: 20},
   643  			{valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))},
   644  			{valueType: ValueTypeF64, val: math.Float64bits(1.12312311)},
   645  			{valueType: ValueTypeV128, val: 0x1, valHi: 0x2},
   646  			{valueType: ValueTypeExternref, val: 0x12345},
   647  			{valueType: ValueTypeFuncref, val: 0x54321},
   648  		}
   649  
   650  		for _, tt := range tests {
   651  			tc := tt
   652  			t.Run(ValueTypeName(tc.valueType), func(t *testing.T) {
   653  				// The index specified in Data equals zero.
   654  				expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
   655  				globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: GlobalType{ValType: tc.valueType}}}
   656  
   657  				g := &GlobalInstance{Type: GlobalType{ValType: tc.valueType}}
   658  				g.initialize(globals, expr, nil)
   659  
   660  				switch tc.valueType {
   661  				case ValueTypeI32:
   662  					require.Equal(t, int32(tc.val), int32(g.Val))
   663  				case ValueTypeI64:
   664  					require.Equal(t, int64(tc.val), int64(g.Val))
   665  				case ValueTypeF32:
   666  					require.Equal(t, tc.val, g.Val)
   667  				case ValueTypeF64:
   668  					require.Equal(t, tc.val, g.Val)
   669  				case ValueTypeV128:
   670  					require.Equal(t, uint64(0x1), g.Val)
   671  					require.Equal(t, uint64(0x2), g.ValHi)
   672  				case ValueTypeFuncref, ValueTypeExternref:
   673  					require.Equal(t, tc.val, g.Val)
   674  				}
   675  			})
   676  		}
   677  	})
   678  
   679  	t.Run("vector", func(t *testing.T) {
   680  		expr := &ConstantExpression{Data: []byte{
   681  			1, 0, 0, 0, 0, 0, 0, 0,
   682  			2, 0, 0, 0, 0, 0, 0, 0,
   683  		}, Opcode: OpcodeVecV128Const}
   684  		g := GlobalInstance{Type: GlobalType{ValType: ValueTypeV128}}
   685  		g.initialize(nil, expr, nil)
   686  		require.Equal(t, uint64(0x1), g.Val)
   687  		require.Equal(t, uint64(0x2), g.ValHi)
   688  	})
   689  }
   690  
   691  func Test_resolveImports(t *testing.T) {
   692  	const moduleName = "test"
   693  	const name = "target"
   694  
   695  	t.Run("module not instantiated", func(t *testing.T) {
   696  		m := &ModuleInstance{s: newStore()}
   697  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{"unknown": {{}}}})
   698  		require.EqualError(t, err, "module[unknown] not instantiated")
   699  	})
   700  	t.Run("export instance not found", func(t *testing.T) {
   701  		m := &ModuleInstance{s: newStore()}
   702  		m.s.nameToModule[moduleName] = &ModuleInstance{Exports: map[string]*Export{}, ModuleName: moduleName}
   703  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{moduleName: {{Name: "unknown"}}}})
   704  		require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
   705  	})
   706  	t.Run("func", func(t *testing.T) {
   707  		t.Run("ok", func(t *testing.T) {
   708  			s := newStore()
   709  			s.nameToModule[moduleName] = &ModuleInstance{
   710  				Exports: map[string]*Export{
   711  					name: {Type: ExternTypeFunc, Index: 2},
   712  					"":   {Type: ExternTypeFunc, Index: 4},
   713  				},
   714  				ModuleName: moduleName,
   715  				Source: &Module{
   716  					FunctionSection: []Index{0, 0, 1, 0, 0},
   717  					TypeSection: []FunctionType{
   718  						{Params: []ValueType{ExternTypeFunc}},
   719  						{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   720  					},
   721  				},
   722  			}
   723  
   724  			module := &Module{
   725  				TypeSection: []FunctionType{
   726  					{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   727  					{Params: []ValueType{ExternTypeFunc}},
   728  				},
   729  				ImportFunctionCount: 2,
   730  				ImportPerModule: map[string][]*Import{
   731  					moduleName: {
   732  						{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0, IndexPerType: 0},
   733  						{Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1, IndexPerType: 1},
   734  					},
   735  				},
   736  			}
   737  
   738  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   739  			err := m.resolveImports(module)
   740  			require.NoError(t, err)
   741  
   742  			me := m.Engine.(*mockModuleEngine)
   743  			require.Equal(t, me.resolveImportsCalled[0], Index(2))
   744  			require.Equal(t, me.resolveImportsCalled[1], Index(4))
   745  		})
   746  		t.Run("signature mismatch", func(t *testing.T) {
   747  			s := newStore()
   748  			s.nameToModule[moduleName] = &ModuleInstance{
   749  				Exports: map[string]*Export{
   750  					name: {Type: ExternTypeFunc, Index: 0},
   751  				},
   752  				ModuleName: moduleName,
   753  				TypeIDs:    []FunctionTypeID{123435},
   754  				Source: &Module{
   755  					FunctionSection: []Index{0},
   756  					TypeSection: []FunctionType{
   757  						{Params: []ValueType{}},
   758  					},
   759  				},
   760  			}
   761  			module := &Module{
   762  				TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},
   763  				ImportPerModule: map[string][]*Import{
   764  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
   765  				},
   766  			}
   767  
   768  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   769  			err := m.resolveImports(module)
   770  			require.EqualError(t, err, "import func[test.target]: signature mismatch: v_f32 != v_v")
   771  		})
   772  	})
   773  	t.Run("global", func(t *testing.T) {
   774  		t.Run("ok", func(t *testing.T) {
   775  			s := newStore()
   776  			g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}}
   777  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   778  			s.nameToModule[moduleName] = &ModuleInstance{
   779  				Globals: []*GlobalInstance{g},
   780  				Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName,
   781  			}
   782  			err := m.resolveImports(
   783  				&Module{
   784  					ImportPerModule: map[string][]*Import{moduleName: {{Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}},
   785  				},
   786  			)
   787  			require.NoError(t, err)
   788  			require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals)
   789  		})
   790  		t.Run("mutability mismatch", func(t *testing.T) {
   791  			s := newStore()
   792  			s.nameToModule[moduleName] = &ModuleInstance{
   793  				Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}},
   794  				Exports: map[string]*Export{name: {
   795  					Type:  ExternTypeGlobal,
   796  					Index: 0,
   797  				}},
   798  				ModuleName: moduleName,
   799  			}
   800  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   801  			err := m.resolveImports(&Module{
   802  				ImportPerModule: map[string][]*Import{moduleName: {
   803  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}},
   804  				}},
   805  			})
   806  			require.EqualError(t, err, "import global[test.target]: mutability mismatch: true != false")
   807  		})
   808  		t.Run("type mismatch", func(t *testing.T) {
   809  			s := newStore()
   810  			s.nameToModule[moduleName] = &ModuleInstance{
   811  				Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}},
   812  				Exports: map[string]*Export{name: {
   813  					Type:  ExternTypeGlobal,
   814  					Index: 0,
   815  				}},
   816  				ModuleName: moduleName,
   817  			}
   818  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   819  			err := m.resolveImports(&Module{
   820  				ImportPerModule: map[string][]*Import{moduleName: {
   821  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}},
   822  				}},
   823  			})
   824  			require.EqualError(t, err, "import global[test.target]: value type mismatch: f64 != i32")
   825  		})
   826  	})
   827  	t.Run("memory", func(t *testing.T) {
   828  		t.Run("ok", func(t *testing.T) {
   829  			max := uint32(10)
   830  			memoryInst := &MemoryInstance{Max: max}
   831  			s := newStore()
   832  			importedME := &mockModuleEngine{}
   833  			s.nameToModule[moduleName] = &ModuleInstance{
   834  				MemoryInstance: memoryInst,
   835  				Exports: map[string]*Export{name: {
   836  					Type: ExternTypeMemory,
   837  				}},
   838  				ModuleName: moduleName,
   839  				Engine:     importedME,
   840  			}
   841  			m := &ModuleInstance{s: s, Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}}
   842  			err := m.resolveImports(&Module{
   843  				ImportPerModule: map[string][]*Import{
   844  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}},
   845  				},
   846  			})
   847  			require.NoError(t, err)
   848  			require.Equal(t, m.MemoryInstance, memoryInst)
   849  			require.Equal(t, importedME, m.Engine.(*mockModuleEngine).importedMemModEngine)
   850  		})
   851  		t.Run("minimum size mismatch", func(t *testing.T) {
   852  			importMemoryType := &Memory{Min: 2, Cap: 2}
   853  			s := newStore()
   854  			s.nameToModule[moduleName] = &ModuleInstance{
   855  				MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
   856  				Exports: map[string]*Export{name: {
   857  					Type: ExternTypeMemory,
   858  				}},
   859  				ModuleName: moduleName,
   860  			}
   861  			m := &ModuleInstance{s: s}
   862  			err := m.resolveImports(&Module{
   863  				ImportPerModule: map[string][]*Import{
   864  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}},
   865  				},
   866  			})
   867  			require.EqualError(t, err, "import memory[test.target]: minimum size mismatch: 2 > 1")
   868  		})
   869  		t.Run("maximum size mismatch", func(t *testing.T) {
   870  			s := newStore()
   871  			s.nameToModule[moduleName] = &ModuleInstance{
   872  				MemoryInstance: &MemoryInstance{Max: MemoryLimitPages},
   873  				Exports: map[string]*Export{name: {
   874  					Type: ExternTypeMemory,
   875  				}},
   876  				ModuleName: moduleName,
   877  			}
   878  
   879  			max := uint32(10)
   880  			importMemoryType := &Memory{Max: max}
   881  			m := &ModuleInstance{s: s}
   882  			err := m.resolveImports(&Module{
   883  				ImportPerModule: map[string][]*Import{moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}},
   884  			})
   885  			require.EqualError(t, err, "import memory[test.target]: maximum size mismatch: 10 < 65536")
   886  		})
   887  	})
   888  }
   889  
   890  func TestModuleInstance_validateData(t *testing.T) {
   891  	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   892  	tests := []struct {
   893  		name   string
   894  		data   []DataSegment
   895  		expErr string
   896  	}{
   897  		{
   898  			name: "ok",
   899  			data: []DataSegment{
   900  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}},
   901  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}},
   902  			},
   903  		},
   904  		{
   905  			name: "out of bounds - single one byte",
   906  			data: []DataSegment{
   907  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}},
   908  			},
   909  			expErr: "data[0]: out of bounds memory access",
   910  		},
   911  		{
   912  			name: "out of bounds - multi bytes",
   913  			data: []DataSegment{
   914  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(0)}, Init: []byte{0}},
   915  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}},
   916  			},
   917  			expErr: "data[1]: out of bounds memory access",
   918  		},
   919  	}
   920  
   921  	for _, tt := range tests {
   922  		tc := tt
   923  		t.Run(tc.name, func(t *testing.T) {
   924  			err := m.validateData(tc.data)
   925  			if tc.expErr != "" {
   926  				require.EqualError(t, err, tc.expErr)
   927  			} else {
   928  				require.NoError(t, err)
   929  			}
   930  		})
   931  	}
   932  }
   933  
   934  func TestModuleInstance_applyData(t *testing.T) {
   935  	t.Run("ok", func(t *testing.T) {
   936  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 10)}}
   937  		err := m.applyData([]DataSegment{
   938  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}},
   939  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}},
   940  		})
   941  		require.NoError(t, err)
   942  		require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.MemoryInstance.Buffer)
   943  		require.Equal(t, [][]byte{{0xa, 0xf}, {0x1, 0x5}}, m.DataInstances)
   944  	})
   945  	t.Run("error", func(t *testing.T) {
   946  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   947  		err := m.applyData([]DataSegment{
   948  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{}},
   949  		})
   950  		require.EqualError(t, err, "data[0]: out of bounds memory access")
   951  	})
   952  }
   953  
   954  func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool {
   955  	for _, f := range globals {
   956  		if f == want {
   957  			return true
   958  		}
   959  	}
   960  	return false
   961  }
   962  
   963  func TestModuleInstance_applyElements(t *testing.T) {
   964  	leb128_100 := leb128.EncodeInt32(100)
   965  
   966  	t.Run("extenref", func(t *testing.T) {
   967  		m := &ModuleInstance{}
   968  		m.Tables = []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}}
   969  		for i := range m.Tables[0].References {
   970  			m.Tables[0].References[i] = 0xffff // non-null ref.
   971  		}
   972  
   973  		// This shouldn't panic.
   974  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}}})
   975  		m.applyElements([]ElementSegment{
   976  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: make([]Index, 3)},
   977  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: make([]Index, 5)}, // Iteration stops at this point, so the offset:5 below shouldn't be applied.
   978  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   979  		})
   980  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff},
   981  			m.Tables[0].References)
   982  		m.applyElements([]ElementSegment{
   983  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   984  		})
   985  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, m.Tables[0].References)
   986  	})
   987  	t.Run("funcref", func(t *testing.T) {
   988  		e := &mockEngine{}
   989  		me, err := e.NewModuleEngine(nil, nil)
   990  		me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa}
   991  		require.NoError(t, err)
   992  		m := &ModuleInstance{Engine: me, Globals: []*GlobalInstance{{}, {Val: 0xabcde}}}
   993  
   994  		m.Tables = []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}}
   995  		for i := range m.Tables[0].References {
   996  			m.Tables[0].References[i] = 0xffff // non-null ref.
   997  		}
   998  
   999  		// This shouldn't panic.
  1000  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: []Index{1, 2, 3}}})
  1001  		m.applyElements([]ElementSegment{
  1002  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0, 1, 2}},
  1003  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{9}}, Init: []Index{1 | ElementInitImportedGlobalFunctionReference}},
  1004  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: make([]Index, 5)}, // Iteration stops at this point, so the offset:5 below shouldn't be applied.
  1005  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
  1006  		})
  1007  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xabcde},
  1008  			m.Tables[0].References)
  1009  		m.applyElements([]ElementSegment{
  1010  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{0, ElementInitNullReference, 2}},
  1011  		})
  1012  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xabcde},
  1013  			m.Tables[0].References)
  1014  	})
  1015  }
  1016  

View as plain text