github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/wasm/table_test.go (about)

     1  package wasm
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/wazero/api"
     8  	"github.com/bananabytelabs/wazero/internal/leb128"
     9  	"github.com/bananabytelabs/wazero/internal/testing/require"
    10  )
    11  
    12  // Test_ElementInitNullReference_valid ensures it is actually safe to use ElementInitNullReference
    13  // as a null reference, and it won't collide with the actual function Index.
    14  func Test_ElementInitNullReference_valid(t *testing.T) {
    15  	require.True(t, MaximumFunctionIndex < ElementInitNullReference)
    16  }
    17  
    18  func Test_resolveImports_table(t *testing.T) {
    19  	const moduleName = "test"
    20  	const name = "target"
    21  
    22  	t.Run("ok", func(t *testing.T) {
    23  		max := uint32(10)
    24  		tableInst := &TableInstance{Max: &max}
    25  		s := newStore()
    26  		s.nameToModule[moduleName] = &ModuleInstance{
    27  			Tables:     []*TableInstance{tableInst},
    28  			Exports:    map[string]*Export{name: {Type: ExternTypeTable, Index: 0}},
    29  			ModuleName: moduleName,
    30  		}
    31  		m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
    32  		err := m.resolveImports(&Module{
    33  			ImportPerModule: map[string][]*Import{
    34  				moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Max: &max}}},
    35  			},
    36  		})
    37  		require.NoError(t, err)
    38  		require.Equal(t, m.Tables[0], tableInst)
    39  	})
    40  	t.Run("minimum size mismatch", func(t *testing.T) {
    41  		s := newStore()
    42  		importTableType := Table{Min: 2}
    43  		s.nameToModule[moduleName] = &ModuleInstance{
    44  			Tables:     []*TableInstance{{Min: importTableType.Min - 1}},
    45  			Exports:    map[string]*Export{name: {Type: ExternTypeTable}},
    46  			ModuleName: moduleName,
    47  		}
    48  		m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
    49  		err := m.resolveImports(&Module{
    50  			ImportPerModule: map[string][]*Import{
    51  				moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}},
    52  			},
    53  		})
    54  		require.EqualError(t, err, "import table[test.target]: minimum size mismatch: 2 > 1")
    55  	})
    56  	t.Run("maximum size mismatch", func(t *testing.T) {
    57  		max := uint32(10)
    58  		importTableType := Table{Max: &max}
    59  		s := newStore()
    60  		s.nameToModule[moduleName] = &ModuleInstance{
    61  			Tables:     []*TableInstance{{Min: importTableType.Min - 1}},
    62  			Exports:    map[string]*Export{name: {Type: ExternTypeTable}},
    63  			ModuleName: moduleName,
    64  		}
    65  		m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
    66  		err := m.resolveImports(&Module{
    67  			ImportPerModule: map[string][]*Import{
    68  				moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}},
    69  			},
    70  		})
    71  		require.EqualError(t, err, "import table[test.target]: maximum size mismatch: 10, but actual has no max")
    72  	})
    73  	t.Run("type mismatch", func(t *testing.T) {
    74  		s := newStore()
    75  		s.nameToModule[moduleName] = &ModuleInstance{
    76  			Tables:     []*TableInstance{{Type: RefTypeFuncref}},
    77  			Exports:    map[string]*Export{name: {Type: ExternTypeTable}},
    78  			ModuleName: moduleName,
    79  		}
    80  		m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
    81  		err := m.resolveImports(&Module{
    82  			ImportPerModule: map[string][]*Import{
    83  				moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Type: RefTypeExternref}}},
    84  			},
    85  		})
    86  		require.EqualError(t, err, "import table[test.target]: table type mismatch: externref != funcref")
    87  	})
    88  }
    89  
    90  var codeEnd = Code{Body: []byte{OpcodeEnd}}
    91  
    92  func TestModule_validateTable(t *testing.T) {
    93  	const maxTableIndex = 5
    94  	three := uint32(3)
    95  	tests := []struct {
    96  		name  string
    97  		input *Module
    98  	}{
    99  		{
   100  			name:  "empty",
   101  			input: &Module{},
   102  		},
   103  		{
   104  			name:  "min zero",
   105  			input: &Module{TableSection: []Table{{}}},
   106  		},
   107  		{
   108  			name:  "maximum number of tables",
   109  			input: &Module{TableSection: []Table{{}, {}, {}, {}, {}}},
   110  		},
   111  		{
   112  			name:  "min/max",
   113  			input: &Module{TableSection: []Table{{Min: 1, Max: &three}}},
   114  		},
   115  		{ // See: https://github.com/WebAssembly/spec/issues/1427
   116  			name: "constant derived element offset=0 and no index",
   117  			input: &Module{
   118  				TypeSection:     []FunctionType{{}},
   119  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   120  				FunctionSection: []Index{0},
   121  				CodeSection:     []Code{codeEnd},
   122  				ElementSection: []ElementSegment{
   123  					{
   124  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
   125  						Type:       RefTypeFuncref,
   126  					},
   127  				},
   128  			},
   129  		},
   130  		{
   131  			name: "constant derived element offset=0 and one index",
   132  			input: &Module{
   133  				TypeSection:     []FunctionType{{}},
   134  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   135  				FunctionSection: []Index{0},
   136  				CodeSection:     []Code{codeEnd},
   137  				ElementSection: []ElementSegment{
   138  					{
   139  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
   140  						Init:       []Index{0},
   141  						Type:       RefTypeFuncref,
   142  					},
   143  				},
   144  			},
   145  		},
   146  		{
   147  			name: "constant derived element offset - ignores min on imported table",
   148  			input: &Module{
   149  				ImportTableCount: 1,
   150  				TypeSection:      []FunctionType{{}},
   151  				ImportSection:    []Import{{Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}}},
   152  				FunctionSection:  []Index{0},
   153  				CodeSection:      []Code{codeEnd},
   154  				ElementSection: []ElementSegment{
   155  					{
   156  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
   157  						Init:       []Index{0},
   158  						Type:       RefTypeFuncref,
   159  					},
   160  				},
   161  			},
   162  		},
   163  		{
   164  			name: "constant derived element offset=0 and one index - imported table",
   165  			input: &Module{
   166  				TypeSection:     []FunctionType{{}},
   167  				ImportSection:   []Import{{Type: ExternTypeTable, DescTable: Table{Min: 1, Type: RefTypeFuncref}}},
   168  				FunctionSection: []Index{0},
   169  				CodeSection:     []Code{codeEnd},
   170  				ElementSection: []ElementSegment{
   171  					{
   172  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
   173  						Init:       []Index{0},
   174  						Type:       RefTypeFuncref,
   175  					},
   176  				},
   177  			},
   178  		},
   179  		{
   180  			name: "constant derived element offset and two indices",
   181  			input: &Module{
   182  				TypeSection:     []FunctionType{{}},
   183  				TableSection:    []Table{{Min: 3, Type: RefTypeFuncref}},
   184  				FunctionSection: []Index{0, 0, 0, 0},
   185  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   186  				ElementSection: []ElementSegment{
   187  					{
   188  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
   189  						Init:       []Index{0, 2},
   190  						Type:       RefTypeFuncref,
   191  					},
   192  				},
   193  			},
   194  		},
   195  		{ // See: https://github.com/WebAssembly/spec/issues/1427
   196  			name: "imported global derived element offset and no index",
   197  			input: &Module{
   198  				TypeSection: []FunctionType{{}},
   199  				ImportSection: []Import{
   200  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   201  				},
   202  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   203  				FunctionSection: []Index{0},
   204  				CodeSection:     []Code{codeEnd},
   205  				ElementSection: []ElementSegment{
   206  					{
   207  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   208  						Type:       RefTypeFuncref,
   209  					},
   210  				},
   211  			},
   212  		},
   213  		{
   214  			name: "imported global derived element offset and one index",
   215  			input: &Module{
   216  				TypeSection: []FunctionType{{}},
   217  				ImportSection: []Import{
   218  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   219  				},
   220  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   221  				FunctionSection: []Index{0},
   222  				CodeSection:     []Code{codeEnd},
   223  				ElementSection: []ElementSegment{
   224  					{
   225  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   226  						Init:       []Index{0},
   227  						Type:       RefTypeFuncref,
   228  					},
   229  				},
   230  			},
   231  		},
   232  		{
   233  			name: "imported global derived element offset and one index - imported table",
   234  			input: &Module{
   235  				TypeSection: []FunctionType{{}},
   236  				ImportSection: []Import{
   237  					{Type: ExternTypeTable, DescTable: Table{Min: 1, Type: RefTypeFuncref}},
   238  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   239  				},
   240  				FunctionSection: []Index{0},
   241  				CodeSection:     []Code{codeEnd},
   242  				ElementSection: []ElementSegment{
   243  					{
   244  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   245  						Init:       []Index{0},
   246  						Type:       RefTypeFuncref,
   247  					},
   248  				},
   249  			},
   250  		},
   251  		{
   252  			name: "imported global derived element offset - ignores min on imported table",
   253  			input: &Module{
   254  				TypeSection: []FunctionType{{}},
   255  				ImportSection: []Import{
   256  					{Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}},
   257  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   258  				},
   259  				FunctionSection: []Index{0},
   260  				CodeSection:     []Code{codeEnd},
   261  				ElementSection: []ElementSegment{
   262  					{
   263  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   264  						Init:       []Index{0},
   265  						Type:       RefTypeFuncref,
   266  					},
   267  				},
   268  			},
   269  		},
   270  		{
   271  			name: "imported global derived element offset - two indices",
   272  			input: &Module{
   273  				TypeSection: []FunctionType{{}},
   274  				ImportSection: []Import{
   275  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}},
   276  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   277  				},
   278  				TableSection:    []Table{{Min: 3, Type: RefTypeFuncref}},
   279  				FunctionSection: []Index{0, 0, 0, 0},
   280  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   281  				ElementSection: []ElementSegment{
   282  					{
   283  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}},
   284  						Init:       []Index{0, 2},
   285  						Type:       RefTypeFuncref,
   286  					},
   287  				},
   288  			},
   289  		},
   290  		{
   291  			name: "mixed elementSegments - const before imported global",
   292  			input: &Module{
   293  				TypeSection: []FunctionType{{}},
   294  				ImportSection: []Import{
   295  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}},
   296  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   297  				},
   298  				TableSection:    []Table{{Min: 3, Type: RefTypeFuncref}},
   299  				FunctionSection: []Index{0, 0, 0, 0},
   300  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   301  				ElementSection: []ElementSegment{
   302  					{
   303  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
   304  						Init:       []Index{0, 2},
   305  						Type:       RefTypeFuncref,
   306  					},
   307  					{
   308  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}},
   309  						Init:       []Index{1, 2},
   310  						Type:       RefTypeFuncref,
   311  					},
   312  				},
   313  			},
   314  		},
   315  	}
   316  
   317  	for _, tt := range tests {
   318  		tc := tt
   319  
   320  		t.Run(tc.name, func(t *testing.T) {
   321  			_, _, _, tables, err := tc.input.AllDeclarations()
   322  			require.NoError(t, err)
   323  
   324  			err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex)
   325  			require.NoError(t, err)
   326  
   327  			err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex)
   328  			require.NoError(t, err)
   329  		})
   330  	}
   331  }
   332  
   333  func TestModule_validateTable_Errors(t *testing.T) {
   334  	const maxTableIndex = 5
   335  	tests := []struct {
   336  		name        string
   337  		input       *Module
   338  		expectedErr string
   339  	}{
   340  		{
   341  			name: "too many tables",
   342  			input: &Module{
   343  				TableSection: []Table{{}, {}, {}, {}, {}, {}},
   344  			},
   345  			expectedErr: "too many tables in a module: 6 given with limit 5",
   346  		},
   347  		{
   348  			name: "type mismatch: unknown ref type",
   349  			input: &Module{
   350  				TableSection: []Table{{Type: RefTypeFuncref}},
   351  				ElementSection: []ElementSegment{
   352  					{
   353  						OffsetExpr: ConstantExpression{
   354  							Opcode: OpcodeI32Const,
   355  							Data:   leb128.EncodeUint64(math.MaxUint64),
   356  						},
   357  						Type: 0xff,
   358  					},
   359  				},
   360  			},
   361  			expectedErr: "element type mismatch: table has funcref but element has unknown(0xff)",
   362  		},
   363  		{
   364  			name: "type mismatch: funcref elem on extern table",
   365  			input: &Module{
   366  				TableSection: []Table{{Type: RefTypeExternref}},
   367  				ElementSection: []ElementSegment{
   368  					{
   369  						OffsetExpr: ConstantExpression{
   370  							Opcode: OpcodeI32Const,
   371  							Data:   leb128.EncodeUint64(math.MaxUint64),
   372  						},
   373  						Type: RefTypeFuncref,
   374  					},
   375  				},
   376  			},
   377  			expectedErr: "element type mismatch: table has externref but element has funcref",
   378  		},
   379  		{
   380  			name: "type mismatch: extern elem on funcref table",
   381  			input: &Module{
   382  				TableSection: []Table{{Type: RefTypeFuncref}},
   383  				ElementSection: []ElementSegment{
   384  					{
   385  						OffsetExpr: ConstantExpression{
   386  							Opcode: OpcodeI32Const,
   387  							Data:   leb128.EncodeUint64(math.MaxUint64),
   388  						},
   389  						Type: RefTypeExternref,
   390  					},
   391  				},
   392  			},
   393  			expectedErr: "element type mismatch: table has funcref but element has externref",
   394  		},
   395  		{
   396  			name: "non-nil externref",
   397  			input: &Module{
   398  				TableSection: []Table{{Type: RefTypeFuncref}},
   399  				ElementSection: []ElementSegment{
   400  					{
   401  						OffsetExpr: ConstantExpression{
   402  							Opcode: OpcodeI32Const,
   403  							Data:   leb128.EncodeUint64(math.MaxUint64),
   404  						},
   405  						Type: RefTypeExternref,
   406  						Init: []Index{0},
   407  					},
   408  				},
   409  			},
   410  			expectedErr: "element[0].init[0] must be ref.null but was 0",
   411  		},
   412  		{
   413  			name: "constant derived element offset - decode error",
   414  			input: &Module{
   415  				TypeSection:     []FunctionType{{}},
   416  				TableSection:    []Table{{Type: RefTypeFuncref}},
   417  				FunctionSection: []Index{0},
   418  				CodeSection:     []Code{codeEnd},
   419  				ElementSection: []ElementSegment{
   420  					{
   421  						OffsetExpr: ConstantExpression{
   422  							Opcode: OpcodeI32Const,
   423  							Data:   leb128.EncodeUint64(math.MaxUint64),
   424  						},
   425  						Init: []Index{0},
   426  						Type: RefTypeFuncref,
   427  					},
   428  				},
   429  			},
   430  			expectedErr: "element[0] couldn't read i32.const parameter: overflows a 32-bit integer",
   431  		},
   432  		{
   433  			name: "constant derived element offset - wrong ValType",
   434  			input: &Module{
   435  				TypeSection:     []FunctionType{{}},
   436  				TableSection:    []Table{{Type: RefTypeFuncref}},
   437  				FunctionSection: []Index{0},
   438  				CodeSection:     []Code{codeEnd},
   439  				ElementSection: []ElementSegment{
   440  					{
   441  						OffsetExpr: ConstantExpression{Opcode: OpcodeI64Const, Data: const0}, Init: []Index{0},
   442  						Type: RefTypeFuncref,
   443  					},
   444  				},
   445  			},
   446  			expectedErr: "element[0] has an invalid const expression: i64.const",
   447  		},
   448  		{
   449  			name: "constant derived element offset - missing table",
   450  			input: &Module{
   451  				TypeSection:     []FunctionType{{}},
   452  				FunctionSection: []Index{0},
   453  				CodeSection:     []Code{codeEnd},
   454  				ElementSection: []ElementSegment{
   455  					{
   456  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []Index{0},
   457  						Type: RefTypeFuncref,
   458  					},
   459  				},
   460  			},
   461  			expectedErr: "unknown table 0 as active element target",
   462  		},
   463  		{
   464  			name: "constant derived element offset exceeds table min",
   465  			input: &Module{
   466  				TypeSection:     []FunctionType{{}},
   467  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   468  				FunctionSection: []Index{0},
   469  				CodeSection:     []Code{codeEnd},
   470  				ElementSection: []ElementSegment{
   471  					{
   472  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []Index{0},
   473  						Type: RefTypeFuncref,
   474  					},
   475  				},
   476  			},
   477  			expectedErr: "element[0].init exceeds min table size",
   478  		},
   479  		{
   480  			name: "constant derived element offset puts init beyond table min",
   481  			input: &Module{
   482  				TypeSection:     []FunctionType{{}},
   483  				TableSection:    []Table{{Min: 2, Type: RefTypeFuncref}},
   484  				FunctionSection: []Index{0},
   485  				CodeSection:     []Code{codeEnd},
   486  				ElementSection: []ElementSegment{
   487  					{
   488  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0},
   489  						Type: RefTypeFuncref,
   490  					},
   491  					{
   492  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 0},
   493  						Type: RefTypeFuncref,
   494  					},
   495  				},
   496  			},
   497  			expectedErr: "element[1].init exceeds min table size",
   498  		},
   499  		{ // See: https://github.com/WebAssembly/spec/issues/1427
   500  			name: "constant derived element offset beyond table min - no init elements",
   501  			input: &Module{
   502  				TypeSection:     []FunctionType{{}},
   503  				TableSection:    []Table{{Min: 1, Type: RefTypeFuncref}},
   504  				FunctionSection: []Index{0},
   505  				CodeSection:     []Code{codeEnd},
   506  				ElementSection: []ElementSegment{
   507  					{
   508  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
   509  						Type:       RefTypeFuncref,
   510  					},
   511  				},
   512  			},
   513  			expectedErr: "element[0].init exceeds min table size",
   514  		},
   515  		{
   516  			name: "constant derived element offset - funcidx out of range",
   517  			input: &Module{
   518  				TypeSection:     []FunctionType{{}},
   519  				TableSection:    []Table{{Min: 1}},
   520  				FunctionSection: []Index{0},
   521  				CodeSection:     []Code{codeEnd},
   522  				ElementSection: []ElementSegment{
   523  					{
   524  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 1},
   525  						Type: RefTypeFuncref,
   526  					},
   527  				},
   528  			},
   529  			expectedErr: "element[0].init[1] funcidx 1 out of range",
   530  		},
   531  		{
   532  			name: "constant derived element offset - global out of range",
   533  			input: &Module{
   534  				ImportGlobalCount: 50,
   535  				TypeSection:       []FunctionType{{}},
   536  				TableSection:      []Table{{Min: 1}},
   537  				FunctionSection:   []Index{0},
   538  				CodeSection:       []Code{codeEnd},
   539  				ElementSection: []ElementSegment{
   540  					{
   541  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{
   542  							ElementInitImportedGlobalFunctionReference | 1,
   543  							ElementInitImportedGlobalFunctionReference | 100,
   544  						},
   545  						Type: RefTypeFuncref,
   546  					},
   547  				},
   548  			},
   549  			expectedErr: "element[0].init[1] globalidx 100 out of range",
   550  		},
   551  		{
   552  			name: "imported global derived element offset - missing table",
   553  			input: &Module{
   554  				TypeSection: []FunctionType{{}},
   555  				ImportSection: []Import{
   556  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   557  				},
   558  				FunctionSection: []Index{0},
   559  				CodeSection:     []Code{codeEnd},
   560  				ElementSection: []ElementSegment{
   561  					{
   562  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0},
   563  						Type: RefTypeFuncref,
   564  					},
   565  				},
   566  			},
   567  			expectedErr: "unknown table 0 as active element target",
   568  		},
   569  		{
   570  			name: "imported global derived element offset - funcidx out of range",
   571  			input: &Module{
   572  				TypeSection: []FunctionType{{}},
   573  				ImportSection: []Import{
   574  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   575  				},
   576  				TableSection:    []Table{{Min: 1}},
   577  				FunctionSection: []Index{0},
   578  				CodeSection:     []Code{codeEnd},
   579  				ElementSection: []ElementSegment{
   580  					{
   581  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0, 1},
   582  						Type: RefTypeFuncref,
   583  					},
   584  				},
   585  			},
   586  			expectedErr: "element[0].init[1] funcidx 1 out of range",
   587  		},
   588  		{
   589  			name: "imported global derived element offset - wrong ValType",
   590  			input: &Module{
   591  				TypeSection: []FunctionType{{}},
   592  				ImportSection: []Import{
   593  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}},
   594  				},
   595  				TableSection:    []Table{{Type: RefTypeFuncref}},
   596  				FunctionSection: []Index{0},
   597  				CodeSection:     []Code{codeEnd},
   598  				ElementSection: []ElementSegment{
   599  					{
   600  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0},
   601  						Type: RefTypeFuncref,
   602  					},
   603  				},
   604  			},
   605  			expectedErr: "element[0] (global.get 0): import[0].global.ValType != i32",
   606  		},
   607  		{
   608  			name: "imported global derived element offset - decode error",
   609  			input: &Module{
   610  				TypeSection: []FunctionType{{}},
   611  				ImportSection: []Import{
   612  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   613  				},
   614  				TableSection:    []Table{{Type: RefTypeFuncref}},
   615  				FunctionSection: []Index{0},
   616  				CodeSection:     []Code{codeEnd},
   617  				ElementSection: []ElementSegment{
   618  					{
   619  						OffsetExpr: ConstantExpression{
   620  							Opcode: OpcodeGlobalGet,
   621  							Data:   leb128.EncodeUint64(math.MaxUint64),
   622  						},
   623  						Init: []Index{0},
   624  						Type: RefTypeFuncref,
   625  					},
   626  				},
   627  			},
   628  			expectedErr: "element[0] couldn't read global.get parameter: overflows a 32-bit integer",
   629  		},
   630  		{
   631  			name: "imported global derived element offset - no imports",
   632  			input: &Module{
   633  				TypeSection:     []FunctionType{{}},
   634  				TableSection:    []Table{{Type: RefTypeFuncref}},
   635  				FunctionSection: []Index{0},
   636  				GlobalSection:   []Global{{Type: GlobalType{ValType: ValueTypeI32}}}, // ignored as not imported
   637  				CodeSection:     []Code{codeEnd},
   638  				ElementSection: []ElementSegment{
   639  					{
   640  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0},
   641  						Type: RefTypeFuncref,
   642  					},
   643  				},
   644  			},
   645  			expectedErr: "element[0] (global.get 0): out of range of imported globals",
   646  		},
   647  		{
   648  			name: "imported global derived element offset - no imports are globals",
   649  			input: &Module{
   650  				TypeSection: []FunctionType{{}},
   651  				ImportSection: []Import{
   652  					{Type: ExternTypeFunc, DescFunc: 0},
   653  				},
   654  				TableSection:    []Table{{Type: RefTypeFuncref}},
   655  				FunctionSection: []Index{0},
   656  				GlobalSection:   []Global{{Type: GlobalType{ValType: ValueTypeI32}}}, // ignored as not imported
   657  				CodeSection:     []Code{codeEnd},
   658  				ElementSection: []ElementSegment{
   659  					{
   660  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0},
   661  						Type: RefTypeFuncref,
   662  					},
   663  				},
   664  			},
   665  			expectedErr: "element[0] (global.get 0): out of range of imported globals",
   666  		},
   667  	}
   668  
   669  	for _, tt := range tests {
   670  		tc := tt
   671  
   672  		t.Run(tc.name, func(t *testing.T) {
   673  			_, _, _, tables, err := tc.input.AllDeclarations()
   674  			require.NoError(t, err)
   675  			err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex)
   676  			require.EqualError(t, err, tc.expectedErr)
   677  		})
   678  	}
   679  }
   680  
   681  var (
   682  	const0 = leb128.EncodeInt32(0)
   683  	const1 = leb128.EncodeInt32(1)
   684  )
   685  
   686  func TestModule_buildTables(t *testing.T) {
   687  	three := uint32(3)
   688  	tests := []struct {
   689  		name            string
   690  		module          *Module
   691  		importedTables  []*TableInstance
   692  		importedGlobals []*GlobalInstance
   693  		expectedTables  []*TableInstance
   694  	}{
   695  		{
   696  			name: "empty",
   697  			module: &Module{
   698  				ElementSection: []ElementSegment{},
   699  			},
   700  		},
   701  		{
   702  			name: "min zero",
   703  			module: &Module{
   704  				TableSection:   []Table{{Type: RefTypeFuncref}},
   705  				ElementSection: []ElementSegment{},
   706  			},
   707  			expectedTables: []*TableInstance{{References: make([]Reference, 0), Min: 0, Type: RefTypeFuncref}},
   708  		},
   709  		{
   710  			name: "min/max",
   711  			module: &Module{
   712  				TableSection:   []Table{{Min: 1, Max: &three}},
   713  				ElementSection: []ElementSegment{},
   714  			},
   715  			expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1, Max: &three}},
   716  		},
   717  		{ // See: https://github.com/WebAssembly/spec/issues/1427
   718  			name: "constant derived element offset=0 and no index",
   719  			module: &Module{
   720  				TypeSection:     []FunctionType{{}},
   721  				TableSection:    []Table{{Min: 1}},
   722  				FunctionSection: []Index{0},
   723  				CodeSection:     []Code{codeEnd},
   724  				ElementSection:  []ElementSegment{},
   725  			},
   726  			expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}},
   727  		},
   728  		{
   729  			name: "null extern refs",
   730  			module: &Module{
   731  				TableSection: []Table{{Min: 10, Type: RefTypeExternref}},
   732  				ElementSection: []ElementSegment{
   733  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{ElementInitNullReference, ElementInitNullReference, ElementInitNullReference}}, // three null refs.
   734  				},
   735  			},
   736  			expectedTables: []*TableInstance{{References: make([]Reference, 10), Min: 10, Type: RefTypeExternref}},
   737  		},
   738  		{
   739  			name: "constant derived element offset=0 and one index",
   740  			module: &Module{
   741  				TypeSection:     []FunctionType{{}},
   742  				TableSection:    []Table{{Min: 1}},
   743  				FunctionSection: []Index{0},
   744  				CodeSection:     []Code{codeEnd},
   745  				ElementSection: []ElementSegment{
   746  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   747  				},
   748  			},
   749  			expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}},
   750  		},
   751  		{
   752  			name: "constant derived element offset - imported table",
   753  			module: &Module{
   754  				TypeSection:     []FunctionType{{}},
   755  				FunctionSection: []Index{0},
   756  				CodeSection:     []Code{codeEnd},
   757  				ElementSection: []ElementSegment{
   758  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   759  				},
   760  			},
   761  			importedTables: []*TableInstance{{Min: 2}},
   762  			expectedTables: []*TableInstance{{Min: 2}},
   763  		},
   764  		{
   765  			name: "constant derived element offset=0 and one index - imported table",
   766  			module: &Module{
   767  				TypeSection:     []FunctionType{{}},
   768  				ImportSection:   []Import{{Type: ExternTypeTable, DescTable: Table{Min: 1}}},
   769  				FunctionSection: []Index{0},
   770  				CodeSection:     []Code{codeEnd},
   771  				ElementSection: []ElementSegment{
   772  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   773  				},
   774  			},
   775  			importedTables: []*TableInstance{{Min: 1}},
   776  			expectedTables: []*TableInstance{{Min: 1}},
   777  		},
   778  		{
   779  			name: "constant derived element offset and two indices",
   780  			module: &Module{
   781  				TypeSection:     []FunctionType{{}},
   782  				TableSection:    []Table{{Min: 3}},
   783  				FunctionSection: []Index{0, 0, 0, 0},
   784  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   785  				ElementSection: []ElementSegment{
   786  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}}, Init: []Index{0, 2}},
   787  				},
   788  			},
   789  			expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}},
   790  		},
   791  		{ // See: https://github.com/WebAssembly/spec/issues/1427
   792  			name: "imported global derived element offset and no index",
   793  			module: &Module{
   794  				TypeSection: []FunctionType{{}},
   795  				ImportSection: []Import{
   796  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   797  				},
   798  				TableSection:    []Table{{Min: 1}},
   799  				FunctionSection: []Index{0},
   800  				CodeSection:     []Code{codeEnd},
   801  				ElementSection:  []ElementSegment{},
   802  			},
   803  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}},
   804  			expectedTables:  []*TableInstance{{References: make([]Reference, 1), Min: 1}},
   805  		},
   806  		{
   807  			name: "imported global derived element offset and one index",
   808  			module: &Module{
   809  				TypeSection: []FunctionType{{}},
   810  				ImportSection: []Import{
   811  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   812  				},
   813  				TableSection:    []Table{{Min: 2}},
   814  				FunctionSection: []Index{0},
   815  				CodeSection:     []Code{codeEnd},
   816  				ElementSection: []ElementSegment{
   817  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   818  				},
   819  			},
   820  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}},
   821  			expectedTables:  []*TableInstance{{References: make([]Reference, 2), Min: 2}},
   822  		},
   823  		{
   824  			name: "imported global derived element offset and one index - imported table",
   825  			module: &Module{
   826  				TypeSection: []FunctionType{{}},
   827  				ImportSection: []Import{
   828  					{Type: ExternTypeTable, DescTable: Table{Min: 1}},
   829  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   830  				},
   831  				FunctionSection: []Index{0},
   832  				CodeSection:     []Code{codeEnd},
   833  				ElementSection: []ElementSegment{
   834  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   835  				},
   836  			},
   837  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}},
   838  			importedTables:  []*TableInstance{{References: make([]Reference, 2), Min: 2}},
   839  			expectedTables:  []*TableInstance{{Min: 2, References: []Reference{0, 0}}},
   840  		},
   841  		{
   842  			name: "imported global derived element offset - ignores min on imported table",
   843  			module: &Module{
   844  				TypeSection: []FunctionType{{}},
   845  				ImportSection: []Import{
   846  					{Type: ExternTypeTable, DescTable: Table{}},
   847  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   848  				},
   849  				FunctionSection: []Index{0},
   850  				CodeSection:     []Code{codeEnd},
   851  				ElementSection: []ElementSegment{
   852  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}},
   853  				},
   854  			},
   855  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}},
   856  			importedTables:  []*TableInstance{{References: make([]Reference, 2), Min: 2}},
   857  			expectedTables:  []*TableInstance{{Min: 2, References: []Reference{0, 0}}},
   858  		},
   859  		{
   860  			name: "imported global derived element offset - two indices",
   861  			module: &Module{
   862  				TypeSection: []FunctionType{{}},
   863  				ImportSection: []Import{
   864  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}},
   865  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   866  				},
   867  				TableSection:    []Table{{Min: 3}, {Min: 100}},
   868  				FunctionSection: []Index{0, 0, 0, 0},
   869  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   870  				ElementSection: []ElementSegment{
   871  					{
   872  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   873  						Init:       []Index{ElementInitNullReference, 2},
   874  						TableIndex: 1,
   875  					},
   876  					{
   877  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}},
   878  						Init:       []Index{0, 2},
   879  						TableIndex: 0,
   880  					},
   881  				},
   882  			},
   883  			importedGlobals: []*GlobalInstance{
   884  				{Type: GlobalType{ValType: ValueTypeI64}, Val: 3},
   885  				{Type: GlobalType{ValType: ValueTypeI32}, Val: 1},
   886  			},
   887  			expectedTables: []*TableInstance{
   888  				{References: make([]Reference, 3), Min: 3},
   889  				{References: make([]Reference, 100), Min: 100},
   890  			},
   891  		},
   892  		{
   893  			name: "mixed elementSegments - const before imported global",
   894  			module: &Module{
   895  				TypeSection: []FunctionType{{}},
   896  				ImportSection: []Import{
   897  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}},
   898  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   899  				},
   900  				TableSection:    []Table{{Min: 3}},
   901  				FunctionSection: []Index{0, 0, 0, 0},
   902  				CodeSection:     []Code{codeEnd, codeEnd, codeEnd, codeEnd},
   903  				ElementSection: []ElementSegment{
   904  					{OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}}, Init: []Index{0, 2}},
   905  					{OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, Init: []Index{1, 2}},
   906  				},
   907  			},
   908  			importedGlobals: []*GlobalInstance{
   909  				{Type: GlobalType{ValType: ValueTypeI64}, Val: 3},
   910  				{Type: GlobalType{ValType: ValueTypeI32}, Val: 1},
   911  			},
   912  			expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}},
   913  		},
   914  	}
   915  
   916  	for _, tt := range tests {
   917  		tc := tt
   918  
   919  		t.Run(tc.name, func(t *testing.T) {
   920  			m := &ModuleInstance{
   921  				Tables:  append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...),
   922  				Globals: tc.importedGlobals,
   923  			}
   924  			err := m.buildTables(tc.module, false)
   925  			require.NoError(t, err)
   926  
   927  			require.Equal(t, tc.expectedTables, m.Tables)
   928  		})
   929  	}
   930  }
   931  
   932  // TestModule_buildTable_Errors covers the only late error conditions possible.
   933  func TestModule_buildTable_Errors(t *testing.T) {
   934  	tests := []struct {
   935  		name            string
   936  		module          *Module
   937  		importedTables  []*TableInstance
   938  		importedGlobals []*GlobalInstance
   939  		expectedErr     string
   940  	}{
   941  		{
   942  			name: "constant derived element offset exceeds table min - imported table",
   943  			module: &Module{
   944  				TypeSection:     []FunctionType{{}},
   945  				ImportSection:   []Import{{Type: ExternTypeTable, DescTable: Table{}}},
   946  				FunctionSection: []Index{0},
   947  				CodeSection:     []Code{codeEnd},
   948  				ElementSection: []ElementSegment{
   949  					{
   950  						OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{2}},
   951  						Init:       []Index{0},
   952  					},
   953  				},
   954  			},
   955  			importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}},
   956  			expectedErr:    "element[0].init exceeds min table size",
   957  		},
   958  		{
   959  			name: "imported global derived element offset exceeds table min",
   960  			module: &Module{
   961  				TypeSection: []FunctionType{{}},
   962  				ImportSection: []Import{
   963  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   964  				},
   965  				TableSection:    []Table{{Min: 2}},
   966  				FunctionSection: []Index{0},
   967  				CodeSection:     []Code{codeEnd},
   968  				ElementSection: []ElementSegment{
   969  					{
   970  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   971  						Init:       []Index{0},
   972  					},
   973  				},
   974  			},
   975  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 2}},
   976  			expectedErr:     "element[0].init exceeds min table size",
   977  		},
   978  		{
   979  			name: "imported global derived element offset exceeds table min imported table",
   980  			module: &Module{
   981  				TypeSection: []FunctionType{{}},
   982  				ImportSection: []Import{
   983  					{Type: ExternTypeTable, DescTable: Table{}},
   984  					{Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}},
   985  				},
   986  				TableSection:    []Table{{Min: 2}},
   987  				FunctionSection: []Index{0},
   988  				CodeSection:     []Code{codeEnd},
   989  				ElementSection: []ElementSegment{
   990  					{
   991  						OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}},
   992  						Init:       []Index{0},
   993  					},
   994  				},
   995  			},
   996  			importedTables:  []*TableInstance{{References: make([]Reference, 2), Min: 2}},
   997  			importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 2}},
   998  			expectedErr:     "element[0].init exceeds min table size",
   999  		},
  1000  	}
  1001  
  1002  	for _, tt := range tests {
  1003  		tc := tt
  1004  
  1005  		t.Run(tc.name, func(t *testing.T) {
  1006  			m := &ModuleInstance{
  1007  				Tables:  append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...),
  1008  				Globals: tc.importedGlobals,
  1009  			}
  1010  			err := m.buildTables(tc.module, false)
  1011  			require.EqualError(t, err, tc.expectedErr)
  1012  		})
  1013  	}
  1014  }
  1015  
  1016  func TestTableInstance_Grow(t *testing.T) {
  1017  	expOnErr := uint32(0xffff_ffff) // -1 as signed i32.
  1018  	max10 := uint32(10)
  1019  	tests := []struct {
  1020  		name       string
  1021  		currentLen int
  1022  		max        *uint32
  1023  		delta, exp uint32
  1024  	}{
  1025  		{
  1026  			name:       "growing ousside 32-bit range",
  1027  			currentLen: 0x10,
  1028  			delta:      0xffff_fff0,
  1029  			exp:        expOnErr,
  1030  		},
  1031  		{
  1032  			name:       "growing zero",
  1033  			currentLen: 0,
  1034  			delta:      0,
  1035  			exp:        0,
  1036  		},
  1037  		{
  1038  			name:       "growing zero on non zero table",
  1039  			currentLen: 5,
  1040  			delta:      0,
  1041  			exp:        5,
  1042  		},
  1043  		{
  1044  			name:       "grow zero on max",
  1045  			currentLen: 10,
  1046  			delta:      0,
  1047  			max:        &max10,
  1048  			exp:        10,
  1049  		},
  1050  		{
  1051  			name:       "grow out of range beyond max",
  1052  			currentLen: 10,
  1053  			delta:      1,
  1054  			max:        &max10,
  1055  			exp:        expOnErr,
  1056  		},
  1057  		{
  1058  			name:       "grow out of range beyond max part2",
  1059  			currentLen: 10,
  1060  			delta:      100,
  1061  			max:        &max10,
  1062  			exp:        expOnErr,
  1063  		},
  1064  	}
  1065  
  1066  	for _, tt := range tests {
  1067  		tc := tt
  1068  		t.Run(tc.name, func(t *testing.T) {
  1069  			table := &TableInstance{References: make([]uintptr, tc.currentLen), Max: tc.max}
  1070  			actual := table.Grow(tc.delta, 0)
  1071  			require.Equal(t, tc.exp, actual)
  1072  		})
  1073  	}
  1074  }
  1075  
  1076  func Test_unwrapElementInitGlobalReference(t *testing.T) {
  1077  	actual, ok := unwrapElementInitGlobalReference(12345 | ElementInitImportedGlobalFunctionReference)
  1078  	require.True(t, ok)
  1079  	require.Equal(t, actual, uint32(12345))
  1080  
  1081  	actual, ok = unwrapElementInitGlobalReference(12345)
  1082  	require.False(t, ok)
  1083  	require.Equal(t, actual, uint32(12345))
  1084  }
  1085  
  1086  // Test_ElementInitSpecials ensures these special consts are larger than MaximumFunctionIndex so that
  1087  // they won't collide with the actual index.
  1088  func Test_ElementInitSpecials(t *testing.T) {
  1089  	require.True(t, ElementInitNullReference > MaximumFunctionIndex)
  1090  	require.True(t, ElementInitImportedGlobalFunctionReference > MaximumFunctionIndex)
  1091  }