github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/wasm/binary/decoder_test.go (about)

     1  package binary
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/wasilibs/wazerox/api"
     7  	"github.com/wasilibs/wazerox/internal/testing/binaryencoding"
     8  	"github.com/wasilibs/wazerox/internal/testing/dwarftestdata"
     9  	"github.com/wasilibs/wazerox/internal/testing/require"
    10  	"github.com/wasilibs/wazerox/internal/wasm"
    11  )
    12  
    13  // TestDecodeModule relies on unit tests for Module.Encode, specifically that the encoding is both known and correct.
    14  // This avoids having to copy/paste or share variables to assert against byte arrays.
    15  func TestDecodeModule(t *testing.T) {
    16  	i32, f32 := wasm.ValueTypeI32, wasm.ValueTypeF32
    17  	zero := uint32(0)
    18  
    19  	tests := []struct {
    20  		name  string
    21  		input *wasm.Module // round trip test!
    22  	}{
    23  		{
    24  			name:  "empty",
    25  			input: &wasm.Module{},
    26  		},
    27  		{
    28  			name:  "only name section",
    29  			input: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}},
    30  		},
    31  		{
    32  			name: "type section",
    33  			input: &wasm.Module{
    34  				TypeSection: []wasm.FunctionType{
    35  					{},
    36  					{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
    37  					{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
    38  				},
    39  			},
    40  		},
    41  		{
    42  			name: "type and import section",
    43  			input: &wasm.Module{
    44  				ImportFunctionCount: 2,
    45  				ImportTableCount:    1,
    46  				ImportMemoryCount:   1,
    47  				ImportGlobalCount:   3,
    48  				TypeSection: []wasm.FunctionType{
    49  					{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
    50  					{Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}},
    51  				},
    52  				ImportSection: []wasm.Import{
    53  					{
    54  						Module: "Math", Name: "Mul",
    55  						Type:         wasm.ExternTypeFunc,
    56  						DescFunc:     1,
    57  						IndexPerType: 0,
    58  					},
    59  					{
    60  						Module: "foo", Name: "bar",
    61  						Type:         wasm.ExternTypeTable,
    62  						DescTable:    wasm.Table{Type: wasm.ValueTypeFuncref},
    63  						IndexPerType: 0,
    64  					},
    65  					{
    66  						Module: "Math", Name: "Add",
    67  						Type:         wasm.ExternTypeFunc,
    68  						DescFunc:     0,
    69  						IndexPerType: 1,
    70  					},
    71  					{
    72  						Module: "bar", Name: "mem",
    73  						Type:         wasm.ExternTypeMemory,
    74  						DescMem:      &wasm.Memory{IsMaxEncoded: true},
    75  						IndexPerType: 0,
    76  					},
    77  					{
    78  						Module: "foo", Name: "bar2",
    79  						Type:         wasm.ExternTypeGlobal,
    80  						DescGlobal:   wasm.GlobalType{ValType: wasm.ValueTypeI32},
    81  						IndexPerType: 0,
    82  					},
    83  					{
    84  						Module: "foo", Name: "bar3",
    85  						Type:         wasm.ExternTypeGlobal,
    86  						DescGlobal:   wasm.GlobalType{ValType: wasm.ValueTypeI32},
    87  						IndexPerType: 1,
    88  					},
    89  					{
    90  						Module: "foo", Name: "bar4",
    91  						Type:         wasm.ExternTypeGlobal,
    92  						DescGlobal:   wasm.GlobalType{ValType: wasm.ValueTypeI32},
    93  						IndexPerType: 2,
    94  					},
    95  				},
    96  			},
    97  		},
    98  		{
    99  			name: "table and memory section",
   100  			input: &wasm.Module{
   101  				TableSection:  []wasm.Table{{Min: 3, Type: wasm.RefTypeFuncref}},
   102  				MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 1, IsMaxEncoded: true},
   103  			},
   104  		},
   105  		{
   106  			name: "type function and start section",
   107  			input: &wasm.Module{
   108  				ImportFunctionCount: 1,
   109  				TypeSection:         []wasm.FunctionType{{}},
   110  				ImportSection: []wasm.Import{{
   111  					Module: "", Name: "hello",
   112  					Type:     wasm.ExternTypeFunc,
   113  					DescFunc: 0,
   114  				}},
   115  				StartSection: &zero,
   116  			},
   117  		},
   118  	}
   119  
   120  	for _, tt := range tests {
   121  		tc := tt
   122  
   123  		t.Run(tc.name, func(t *testing.T) {
   124  			m, e := DecodeModule(binaryencoding.EncodeModule(tc.input), api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false, false)
   125  			require.NoError(t, e)
   126  			// Set the FunctionType keys on the input.
   127  			for i := range tc.input.TypeSection {
   128  				tp := &(tc.input.TypeSection)[i]
   129  				_ = tp.String()
   130  			}
   131  			if len(tc.input.ImportSection) > 0 {
   132  				expImportPerModule := make(map[string][]*wasm.Import)
   133  				for i := range m.ImportSection {
   134  					imp := &m.ImportSection[i]
   135  					expImportPerModule[imp.Module] = append(expImportPerModule[imp.Module], imp)
   136  				}
   137  				tc.input.ImportPerModule = expImportPerModule
   138  			}
   139  			require.Equal(t, tc.input, m)
   140  		})
   141  	}
   142  
   143  	t.Run("skips custom section", func(t *testing.T) {
   144  		input := append(append(Magic, version...),
   145  			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
   146  			0x04, 'm', 'e', 'm', 'e',
   147  			1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
   148  		m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false, false)
   149  		require.NoError(t, e)
   150  		require.Equal(t, &wasm.Module{}, m)
   151  	})
   152  
   153  	t.Run("reads custom sections", func(t *testing.T) {
   154  		input := append(append(Magic, version...),
   155  			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
   156  			0x04, 'm', 'e', 'm', 'e',
   157  			1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
   158  		m, e := DecodeModule(input, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false, true)
   159  		require.NoError(t, e)
   160  		require.Equal(t, &wasm.Module{
   161  			CustomSections: []*wasm.CustomSection{
   162  				{
   163  					Name: "meme",
   164  					Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
   165  				},
   166  			},
   167  		}, m)
   168  	})
   169  
   170  	t.Run("skips custom section, but not name", func(t *testing.T) {
   171  		input := append(append(Magic, version...),
   172  			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
   173  			0x04, 'm', 'e', 'm', 'e',
   174  			1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
   175  			wasm.SectionIDCustom, 0x0e, // 14 bytes in this section
   176  			0x04, 'n', 'a', 'm', 'e',
   177  			subsectionIDModuleName, 0x07, // 7 bytes in this subsection
   178  			0x06, // the Module name simple is 6 bytes long
   179  			's', 'i', 'm', 'p', 'l', 'e')
   180  		m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false, false)
   181  		require.NoError(t, e)
   182  		require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m)
   183  	})
   184  
   185  	t.Run("read custom sections and name separately", func(t *testing.T) {
   186  		input := append(append(Magic, version...),
   187  			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
   188  			0x04, 'm', 'e', 'm', 'e',
   189  			1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
   190  			wasm.SectionIDCustom, 0x0e, // 14 bytes in this section
   191  			0x04, 'n', 'a', 'm', 'e',
   192  			subsectionIDModuleName, 0x07, // 7 bytes in this subsection
   193  			0x06, // the Module name simple is 6 bytes long
   194  			's', 'i', 'm', 'p', 'l', 'e')
   195  		m, e := DecodeModule(input, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false, true)
   196  		require.NoError(t, e)
   197  		require.Equal(t, &wasm.Module{
   198  			NameSection: &wasm.NameSection{ModuleName: "simple"},
   199  			CustomSections: []*wasm.CustomSection{
   200  				{
   201  					Name: "meme",
   202  					Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
   203  				},
   204  			},
   205  		}, m)
   206  	})
   207  
   208  	t.Run("DWARF enabled", func(t *testing.T) {
   209  		m, err := DecodeModule(dwarftestdata.ZigWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, true, true)
   210  		require.NoError(t, err)
   211  		require.NotNil(t, m.DWARFLines)
   212  	})
   213  
   214  	t.Run("DWARF disabled", func(t *testing.T) {
   215  		m, err := DecodeModule(dwarftestdata.ZigWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false, true)
   216  		require.NoError(t, err)
   217  		require.Nil(t, m.DWARFLines)
   218  	})
   219  
   220  	t.Run("data count section disabled", func(t *testing.T) {
   221  		input := append(append(Magic, version...),
   222  			wasm.SectionIDDataCount, 1, 0)
   223  		_, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false, false)
   224  		require.EqualError(t, e, `data count section not supported as feature "bulk-memory-operations" is disabled`)
   225  	})
   226  }
   227  
   228  func TestDecodeModule_Errors(t *testing.T) {
   229  	tests := []struct {
   230  		name        string
   231  		input       []byte
   232  		expectedErr string
   233  	}{
   234  		{
   235  			name:        "wrong magic",
   236  			input:       []byte("wasm\x01\x00\x00\x00"),
   237  			expectedErr: "invalid magic number",
   238  		},
   239  		{
   240  			name:        "wrong version",
   241  			input:       []byte("\x00asm\x01\x00\x00\x01"),
   242  			expectedErr: "invalid version header",
   243  		},
   244  		{
   245  			name: "multiple start sections",
   246  			input: append(append(Magic, version...),
   247  				wasm.SectionIDType, 4, 1, 0x60, 0, 0,
   248  				wasm.SectionIDFunction, 2, 1, 0,
   249  				wasm.SectionIDCode, 4, 1,
   250  				2, 0, wasm.OpcodeEnd,
   251  				wasm.SectionIDStart, 1, 0,
   252  				wasm.SectionIDStart, 1, 0,
   253  			),
   254  			expectedErr: `multiple start sections are invalid`,
   255  		},
   256  		{
   257  			name: "redundant name section",
   258  			input: append(append(Magic, version...),
   259  				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
   260  				0x04, 'n', 'a', 'm', 'e',
   261  				subsectionIDModuleName, 0x02, 0x01, 'x',
   262  				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
   263  				0x04, 'n', 'a', 'm', 'e',
   264  				subsectionIDModuleName, 0x02, 0x01, 'x'),
   265  			expectedErr: "section custom: redundant custom section name",
   266  		},
   267  	}
   268  
   269  	for _, tt := range tests {
   270  		tc := tt
   271  
   272  		t.Run(tc.name, func(t *testing.T) {
   273  			_, e := DecodeModule(tc.input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false, false)
   274  			require.EqualError(t, e, tc.expectedErr)
   275  		})
   276  	}
   277  }