github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/wasm/binary/decoder_test.go (about) 1 package binary 2 3 import ( 4 "testing" 5 6 "github.com/bananabytelabs/wazero/api" 7 "github.com/bananabytelabs/wazero/internal/testing/binaryencoding" 8 "github.com/bananabytelabs/wazero/internal/testing/dwarftestdata" 9 "github.com/bananabytelabs/wazero/internal/testing/require" 10 "github.com/bananabytelabs/wazero/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 }