wa-lang.org/wazero@v1.0.2/internal/wasm/binary/names_test.go (about)

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"wa-lang.org/wazero/internal/testing/require"
     8  	"wa-lang.org/wazero/internal/wasm"
     9  )
    10  
    11  func TestEncodeNameSectionData(t *testing.T) {
    12  	tests := []struct {
    13  		name     string
    14  		input    *wasm.NameSection
    15  		expected []byte
    16  	}{
    17  		{
    18  			name:  "empty",
    19  			input: &wasm.NameSection{},
    20  		},
    21  		{
    22  			name: "only module",
    23  			// e.g. (module $simple )
    24  			input: &wasm.NameSection{ModuleName: "simple"},
    25  			expected: []byte{
    26  				subsectionIDModuleName, 0x07, // 7 bytes
    27  				0x06, // the Module name simple is 6 bytes long
    28  				's', 'i', 'm', 'p', 'l', 'e',
    29  			},
    30  		},
    31  		{
    32  			name: "module and function name",
    33  			//	(module $simple
    34  			//		(import "" "Hello" (func $hello))
    35  			//		(start $hello)
    36  			//	)
    37  			input: &wasm.NameSection{
    38  				ModuleName:    "simple",
    39  				FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "hello"}},
    40  			},
    41  			expected: []byte{
    42  				subsectionIDModuleName, 0x07, // 7 bytes
    43  				0x06, // the Module name simple is 6 bytes long
    44  				's', 'i', 'm', 'p', 'l', 'e',
    45  				subsectionIDFunctionNames, 0x08, // 8 bytes
    46  				0x01, // one function name
    47  				0x00, // the function index is zero
    48  				0x05, // the function name hello is 5 bytes long
    49  				'h', 'e', 'l', 'l', 'o',
    50  			},
    51  		},
    52  		{
    53  			name: "two function names", // e.g. TinyGo which at one point didn't set a module name
    54  			//	(module
    55  			//		(import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi.args_sizes_get (param i32, i32) (result i32)))
    56  			//		(import "wasi_snapshot_preview1" "fd_write" (func $wasi.fd_write (param i32, i32, i32, i32) (result i32)))
    57  			//	)
    58  			input: &wasm.NameSection{
    59  				FunctionNames: wasm.NameMap{
    60  					{Index: wasm.Index(0), Name: "wasi.args_sizes_get"},
    61  					{Index: wasm.Index(1), Name: "wasi.fd_write"},
    62  				},
    63  			},
    64  			expected: []byte{
    65  				subsectionIDFunctionNames, 0x25, // 37 bytes
    66  				0x02, // two function names
    67  				0x00, // the function index is zero
    68  				0x13, // the function name wasi.args_sizes_get is 19 bytes long
    69  				'w', 'a', 's', 'i', '.', 'a', 'r', 'g', 's', '_', 's', 'i', 'z', 'e', 's', '_', 'g', 'e', 't',
    70  				0x01, // the function index is one
    71  				0x0d, // the function name wasi.fd_write is 13 bytes long
    72  				'w', 'a', 's', 'i', '.', 'f', 'd', '_', 'w', 'r', 'i', 't', 'e',
    73  			},
    74  		},
    75  		{
    76  			name: "function with local names",
    77  			//	(module
    78  			//		(import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
    79  			//		(import "Math" "Add" (func $add (param $l f32) (param $r f32) (result f32)))
    80  			//	)
    81  			input: &wasm.NameSection{
    82  				FunctionNames: wasm.NameMap{
    83  					{Index: wasm.Index(0), Name: "mul"},
    84  					{Index: wasm.Index(1), Name: "add"},
    85  				},
    86  				LocalNames: wasm.IndirectNameMap{
    87  					{Index: wasm.Index(0), NameMap: wasm.NameMap{
    88  						{Index: wasm.Index(0), Name: "x"},
    89  						{Index: wasm.Index(1), Name: "y"},
    90  					}},
    91  					{Index: wasm.Index(1), NameMap: wasm.NameMap{
    92  						{Index: wasm.Index(0), Name: "l"},
    93  						{Index: wasm.Index(1), Name: "r"},
    94  					}},
    95  				},
    96  			},
    97  			expected: []byte{
    98  				subsectionIDFunctionNames, 0x0b, // 7 bytes
    99  				0x02,                      // two function names
   100  				0x00, 0x03, 'm', 'u', 'l', // index 0, size of "mul", "mul"
   101  				0x01, 0x03, 'a', 'd', 'd', // index 1, size of "add", "add"
   102  				subsectionIDLocalNames, 0x11, // 17 bytes
   103  				0x02,       // two functions
   104  				0x00, 0x02, // index 0 has 2 locals
   105  				0x00, 0x01, 'x', // index 0, size of "x", "x"
   106  				0x01, 0x01, 'y', // index 1, size of "y", "y"
   107  				0x01, 0x02, // index 1 has 2 locals
   108  				0x00, 0x01, 'l', // index 0, size of "l", "l"
   109  				0x01, 0x01, 'r', // index 1, size of "r", "r"
   110  			},
   111  		},
   112  	}
   113  
   114  	for _, tt := range tests {
   115  		tc := tt
   116  
   117  		t.Run(tc.name, func(t *testing.T) {
   118  			bytes := encodeNameSectionData(tc.input)
   119  			require.Equal(t, tc.expected, bytes)
   120  		})
   121  	}
   122  }
   123  
   124  func TestEncodeNameSubsection(t *testing.T) {
   125  	subsectionID := uint8(1)
   126  	name := []byte("simple")
   127  	require.Equal(t, []byte{
   128  		subsectionID,
   129  		byte(1 + 6), // 1 is the size of 6 in LEB128 encoding
   130  		6, 's', 'i', 'm', 'p', 'l', 'e',
   131  	}, encodeNameSubsection(subsectionID, encodeSizePrefixed(name)))
   132  }
   133  
   134  func TestEncodeNameAssoc(t *testing.T) {
   135  	na := &wasm.NameAssoc{Index: 1, Name: "hello"}
   136  	require.Equal(t, []byte{byte(na.Index), 5, 'h', 'e', 'l', 'l', 'o'}, encodeNameAssoc(na))
   137  }
   138  
   139  func TestEncodeNameMap(t *testing.T) {
   140  	na := &wasm.NameAssoc{Index: 1, Name: "hello"}
   141  	m := wasm.NameMap{na}
   142  	require.Equal(t, []byte{byte(1), byte(na.Index), 5, 'h', 'e', 'l', 'l', 'o'}, encodeNameMap(m))
   143  }
   144  
   145  func TestEncodeSizePrefixed(t *testing.T) {
   146  	// We expect size in bytes (LEB128 encoded) then the bytes
   147  	require.Equal(t, []byte{5, 'h', 'e', 'l', 'l', 'o'}, encodeSizePrefixed([]byte("hello")))
   148  }
   149  
   150  // TestDecodeNameSection relies on unit tests for NameSection.EncodeData, specifically that the encoding is
   151  // both known and correct. This avoids having to copy/paste or share variables to assert against byte arrays.
   152  func TestDecodeNameSection(t *testing.T) {
   153  	tests := []struct {
   154  		name  string
   155  		input *wasm.NameSection // round trip test!
   156  	}{
   157  		{
   158  			name:  "empty",
   159  			input: &wasm.NameSection{},
   160  		},
   161  		{
   162  			name:  "only module",
   163  			input: &wasm.NameSection{ModuleName: "simple"},
   164  		},
   165  		{
   166  			name: "module and function name",
   167  			input: &wasm.NameSection{
   168  				ModuleName:    "simple",
   169  				FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "wasi.hello"}},
   170  			},
   171  		},
   172  		{
   173  			name: "two function names",
   174  			input: &wasm.NameSection{
   175  				FunctionNames: wasm.NameMap{
   176  					{Index: wasm.Index(0), Name: "wasi.args_sizes_get"},
   177  					{Index: wasm.Index(1), Name: "wasi.fd_write"},
   178  				},
   179  			},
   180  		},
   181  		{
   182  			name: "function with local names",
   183  			input: &wasm.NameSection{
   184  				FunctionNames: wasm.NameMap{
   185  					{Index: wasm.Index(0), Name: "mul"},
   186  					{Index: wasm.Index(1), Name: "add"},
   187  				},
   188  				LocalNames: wasm.IndirectNameMap{
   189  					{Index: wasm.Index(0), NameMap: wasm.NameMap{
   190  						{Index: wasm.Index(0), Name: "x"},
   191  						{Index: wasm.Index(1), Name: "y"},
   192  					}},
   193  					{Index: wasm.Index(1), NameMap: wasm.NameMap{
   194  						{Index: wasm.Index(0), Name: "l"},
   195  						{Index: wasm.Index(1), Name: "r"},
   196  					}},
   197  				},
   198  			},
   199  		},
   200  	}
   201  
   202  	for _, tt := range tests {
   203  		tc := tt
   204  
   205  		t.Run(tc.name, func(t *testing.T) {
   206  			data := encodeNameSectionData(tc.input)
   207  			ns, err := decodeNameSection(bytes.NewReader(data), uint64(len(data)))
   208  			require.NoError(t, err)
   209  			require.Equal(t, tc.input, ns)
   210  		})
   211  	}
   212  }
   213  
   214  func TestDecodeNameSection_Errors(t *testing.T) {
   215  	// currently, we ignore the size of known subsections
   216  	ignoredSubsectionSize := byte(50)
   217  	tests := []struct {
   218  		name        string
   219  		input       []byte
   220  		expectedErr string
   221  	}{
   222  		{
   223  			name:        "EOF after module name subsection ID",
   224  			input:       []byte{subsectionIDModuleName},
   225  			expectedErr: "failed to read the size of subsection[0]: EOF",
   226  		},
   227  		{
   228  			name:        "EOF after function names subsection ID",
   229  			input:       []byte{subsectionIDFunctionNames},
   230  			expectedErr: "failed to read the size of subsection[1]: EOF",
   231  		},
   232  		{
   233  			name:        "EOF after local names subsection ID",
   234  			input:       []byte{subsectionIDLocalNames},
   235  			expectedErr: "failed to read the size of subsection[2]: EOF",
   236  		},
   237  		{
   238  			name:        "EOF after unknown subsection ID",
   239  			input:       []byte{4},
   240  			expectedErr: "failed to read the size of subsection[4]: EOF",
   241  		},
   242  		{
   243  			name:        "EOF after module name subsection size",
   244  			input:       []byte{subsectionIDModuleName, ignoredSubsectionSize},
   245  			expectedErr: "failed to read module name size: EOF",
   246  		},
   247  		{
   248  			name:        "EOF after function names subsection size",
   249  			input:       []byte{subsectionIDFunctionNames, ignoredSubsectionSize},
   250  			expectedErr: "failed to read the function count of subsection[1]: EOF",
   251  		},
   252  		{
   253  			name:        "EOF after local names subsection size",
   254  			input:       []byte{subsectionIDLocalNames, ignoredSubsectionSize},
   255  			expectedErr: "failed to read the function count of subsection[2]: EOF",
   256  		},
   257  		{
   258  			name:        "EOF skipping unknown subsection size",
   259  			input:       []byte{4, 100},
   260  			expectedErr: "failed to skip subsection[4]: EOF",
   261  		},
   262  		{
   263  			name:        "EOF after module name size",
   264  			input:       []byte{subsectionIDModuleName, ignoredSubsectionSize, 5},
   265  			expectedErr: "failed to read module name: EOF",
   266  		},
   267  		{
   268  			name:        "EOF after function name count",
   269  			input:       []byte{subsectionIDFunctionNames, ignoredSubsectionSize, 2},
   270  			expectedErr: "failed to read a function index in subsection[1]: EOF",
   271  		},
   272  		{
   273  			name:        "EOF after local names function count",
   274  			input:       []byte{subsectionIDLocalNames, ignoredSubsectionSize, 2},
   275  			expectedErr: "failed to read a function index in subsection[2]: EOF",
   276  		},
   277  		{
   278  			name:        "EOF after function name index",
   279  			input:       []byte{subsectionIDFunctionNames, ignoredSubsectionSize, 2, 0},
   280  			expectedErr: "failed to read function[0] name size: EOF",
   281  		},
   282  		{
   283  			name:        "EOF after local names function index",
   284  			input:       []byte{subsectionIDLocalNames, ignoredSubsectionSize, 2, 0},
   285  			expectedErr: "failed to read the local count for function[0]: EOF",
   286  		},
   287  		{
   288  			name:        "EOF after function name size",
   289  			input:       []byte{subsectionIDFunctionNames, ignoredSubsectionSize, 2, 0, 5},
   290  			expectedErr: "failed to read function[0] name: EOF",
   291  		},
   292  		{
   293  			name:        "EOF after local names count for a function index",
   294  			input:       []byte{subsectionIDLocalNames, ignoredSubsectionSize, 2, 0, 2},
   295  			expectedErr: "failed to read a local index of function[0]: EOF",
   296  		},
   297  		{
   298  			name:        "EOF after local name size",
   299  			input:       []byte{subsectionIDLocalNames, ignoredSubsectionSize, 2, 0, 2, 1},
   300  			expectedErr: "failed to read function[0] local[1] name size: EOF",
   301  		},
   302  	}
   303  
   304  	for _, tt := range tests {
   305  		tc := tt
   306  
   307  		t.Run(tc.name, func(t *testing.T) {
   308  			_, err := decodeNameSection(bytes.NewReader(tc.input), uint64(len(tc.input)))
   309  			require.EqualError(t, err, tc.expectedErr)
   310  		})
   311  	}
   312  }