Repository: tetratelabs/wazero
Pull request #234 (2022-02-14 01:25:06)

Removes CustomSection except NameSection
Per #232, we will be unexporting fields only used during instantiate. Any
additional custom sections are unusable otherwise, so this skips them.
In doing so, this eliminates a length check bug, some TODO and some
logic that enforced strict name uniqueness even though that only rule
only applied to the name section.

Note: this doesn't restrict future use of custom sections, ex for DWARF,
just we don't buffer sections we don't use anymore.

See https://www.w3.org/TR/wasm-core-1/#custom-section%E2%91%A0


codefromthecrypt: Thanks for the peek fellas

Patch:
diff --git a/wasm/binary/decoder.go b/wasm/binary/decoder.go
index 0ae26ec4..d1af58a1 100644
--- a/wasm/binary/decoder.go
+++ b/wasm/binary/decoder.go
@@ -29,8 +29,8 @@ func DecodeModule(binary []byte) (*wasm.Module, error) {
 	for {
 		// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
 		// See https://www.w3.org/TR/wasm-core-1/#modules%E2%91%A0%E2%93%AA
-		sectionID := make([]byte, 1)
-		if _, err := io.ReadFull(r, sectionID); err == io.EOF {
+		sectionID, err := r.ReadByte()
+		if err == io.EOF {
 			break
 		} else if err != nil {
 			return nil, fmt.Errorf("read section id: %w", err)
@@ -38,11 +38,11 @@ func DecodeModule(binary []byte) (*wasm.Module, error) {
 
 		sectionSize, _, err := leb128.DecodeUint32(r)
 		if err != nil {
-			return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID[0]), err)
+			return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
 		}
 
 		sectionContentStart := r.Len()
-		switch sectionID[0] {
+		switch sectionID {
 		case wasm.SectionIDCustom:
 			// First, validate the section and determine if the section for this name has already been set
 			name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
@@ -55,25 +55,19 @@ func DecodeModule(binary []byte) (*wasm.Module, error) {
 			} else if name == "name" && m.NameSection != nil {
 				err = fmt.Errorf("redundant custom section %s", name)
 				break
-			} else if _, ok := m.CustomSections[name]; ok {
-				err = fmt.Errorf("redundant custom section %s", name)
-				break
 			}
 
-			// Now, either decode the NameSection or store an unsupported one
-			// TODO: Do we care to store something we don't use? We could also skip it!
-			data, dataErr := readCustomSectionData(r, sectionSize-nameSize)
-			if dataErr != nil {
-				err = dataErr
-			} else if name == "name" {
-				m.NameSection, err = decodeNameSection(data)
+			// Now, either decode the NameSection or skip an unsupported one
+			limit := sectionSize - nameSize
+			if name == "name" {
+				m.NameSection, err = decodeNameSection(r, uint64(limit))
 			} else {
-				if m.CustomSections == nil {
-					m.CustomSections = map[string][]byte{name: data}
-				} else {
-					m.CustomSections[name] = data
+				// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
+				if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
+					return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
 				}
 			}
+
 		case wasm.SectionIDType:
 			m.TypeSection, err = decodeTypeSection(r)
 		case wasm.SectionIDImport:
@@ -106,11 +100,11 @@ func DecodeModule(binary []byte) (*wasm.Module, error) {
 		}
 
 		if err != nil {
-			return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID[0]), err)
+			return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
 		}
 	}
 
-	functionCount, codeCount := m.SectionSize(wasm.SectionIDFunction), m.SectionSize(wasm.SectionIDCode)
+	functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
 	if functionCount != codeCount {
 		return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
 	}
diff --git a/wasm/binary/encoder.go b/wasm/binary/encoder.go
index 54417a35..3db533cb 100644
--- a/wasm/binary/encoder.go
+++ b/wasm/binary/encoder.go
@@ -11,44 +11,40 @@ var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'}
 // See https://www.w3.org/TR/wasm-core-1/#binary-format%E2%91%A0
 func EncodeModule(m *wasm.Module) (bytes []byte) {
 	bytes = append(magic, version...)
-	if m.SectionSize(wasm.SectionIDType) > 0 {
+	if m.SectionElementCount(wasm.SectionIDType) > 0 {
 		bytes = append(bytes, encodeTypeSection(m.TypeSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDImport) > 0 {
+	if m.SectionElementCount(wasm.SectionIDImport) > 0 {
 		bytes = append(bytes, encodeImportSection(m.ImportSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDFunction) > 0 {
+	if m.SectionElementCount(wasm.SectionIDFunction) > 0 {
 		bytes = append(bytes, encodeFunctionSection(m.FunctionSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDTable) > 0 {
+	if m.SectionElementCount(wasm.SectionIDTable) > 0 {
 		panic("TODO: TableSection")
 	}
-	if m.SectionSize(wasm.SectionIDMemory) > 0 {
+	if m.SectionElementCount(wasm.SectionIDMemory) > 0 {
 		bytes = append(bytes, encodeMemorySection(m.MemorySection)...)
 	}
-	if m.SectionSize(wasm.SectionIDGlobal) > 0 {
+	if m.SectionElementCount(wasm.SectionIDGlobal) > 0 {
 		panic("TODO: GlobalSection")
 	}
-	if m.SectionSize(wasm.SectionIDExport) > 0 {
+	if m.SectionElementCount(wasm.SectionIDExport) > 0 {
 		bytes = append(bytes, encodeExportSection(m.ExportSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDStart) > 0 {
+	if m.SectionElementCount(wasm.SectionIDStart) > 0 {
 		bytes = append(bytes, encodeStartSection(*m.StartSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDElement) > 0 {
+	if m.SectionElementCount(wasm.SectionIDElement) > 0 {
 		panic("TODO: ElementSection")
 	}
-	if m.SectionSize(wasm.SectionIDCode) > 0 {
+	if m.SectionElementCount(wasm.SectionIDCode) > 0 {
 		bytes = append(bytes, encodeCodeSection(m.CodeSection)...)
 	}
-	if m.SectionSize(wasm.SectionIDData) > 0 {
+	if m.SectionElementCount(wasm.SectionIDData) > 0 {
 		panic("TODO: DataSection")
 	}
-	if m.SectionSize(wasm.SectionIDCustom) > 0 {
-		for name, data := range m.CustomSections {
-			bytes = append(bytes, encodeCustomSection(name, data)...)
-		}
-
+	if m.SectionElementCount(wasm.SectionIDCustom) > 0 {
 		// >> The name section should appear only once in a module, and only after the data section.
 		// See https://www.w3.org/TR/wasm-core-1/#binary-namesec
 		if m.NameSection != nil {
diff --git a/wasm/binary/names.go b/wasm/binary/names.go
index b9590522..dc64cdfd 100644
--- a/wasm/binary/names.go
+++ b/wasm/binary/names.go
@@ -27,17 +27,17 @@ const (
 // * LocalNames decode from subsection 2
 //
 // See https://www.w3.org/TR/wasm-core-1/#binary-namesec
-func decodeNameSection(data []byte) (result *wasm.NameSection, err error) {
+func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) {
 	// TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based
 	// leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte.
-	r := bytes.NewReader(data)
 	result = &wasm.NameSection{}
 
 	// subsectionID is decoded if known, and skipped if not
 	var subsectionID uint8
 	// subsectionSize is the length to skip when the subsectionID is unknown
 	var subsectionSize uint32
-	for {
+	var bytesRead uint64
+	for limit > 0 {
 		if subsectionID, err = r.ReadByte(); err != nil {
 			if err == io.EOF {
 				return result, nil
@@ -45,11 +45,12 @@ func decodeNameSection(data []byte) (result *wasm.NameSection, err error) {
 			// TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer
 			return nil, fmt.Errorf("failed to read a subsection ID: %w", err)
 		}
+		limit--
 
-		// TODO: unused except when skipping. This means we can pass on a corrupt length of a known subsection
-		if subsectionSize, _, err = leb128.DecodeUint32(r); err != nil {
+		if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil {
 			return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err)
 		}
+		limit -= bytesRead
 
 		switch subsectionID {
 		case subsectionIDModuleName:
@@ -66,11 +67,13 @@ func decodeNameSection(data []byte) (result *wasm.NameSection, err error) {
 			}
 		default: // Skip other subsections.
 			// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
-			if _, err := io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
+			if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
 				return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err)
 			}
 		}
+		limit -= uint64(subsectionSize)
 	}
+	return
 }
 
 func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) {
diff --git a/wasm/binary/section.go b/wasm/binary/section.go
index e117a6b6..7c587f97 100644
--- a/wasm/binary/section.go
+++ b/wasm/binary/section.go
@@ -9,14 +9,6 @@ import (
 	"github.com/tetratelabs/wazero/wasm/internal/leb128"
 )
 
-func readCustomSectionData(r *bytes.Reader, dataSize uint32) ([]byte, error) {
-	data := make([]byte, dataSize)
-	if _, err := io.ReadFull(r, data); err != nil {
-		return nil, fmt.Errorf("cannot read custom section data: %w", err)
-	}
-	return data, nil
-}
-
 func decodeTypeSection(r io.Reader) ([]*wasm.FunctionType, error) {
 	vs, _, err := leb128.DecodeUint32(r)
 	if err != nil {
@@ -218,14 +210,6 @@ func decodeDataSection(r *bytes.Reader) ([]*wasm.DataSegment, error) {
 	return result, nil
 }
 
-// encodeCustomSection encodes the opaque bytes for the given name as a SectionIDCustom
-// See https://www.w3.org/TR/wasm-core-1/#binary-customsec
-func encodeCustomSection(name string, data []byte) []byte {
-	// The contents of a custom section is the non-empty name followed by potentially empty opaque data
-	contents := append(encodeSizePrefixed([]byte(name)), data...)
-	return encodeSection(wasm.SectionIDCustom, contents)
-}
-
 // encodeSection encodes the sectionID, the size of its contents in bytes, followed by the contents.
 // See https://www.w3.org/TR/wasm-core-1/#sections%E2%91%A0
 func encodeSection(sectionID wasm.SectionID, contents []byte) []byte {
diff --git a/wasm/module.go b/wasm/module.go
index 33e5953c..2965fe93 100644
--- a/wasm/module.go
+++ b/wasm/module.go
@@ -92,6 +92,7 @@ type Module struct {
 	// (Store.Instantiate).
 	//
 	// Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name.
+	//
 	// Note: In the Binary Format, this is SectionIDImport.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#import-section%E2%91%A0
@@ -105,6 +106,7 @@ type Module struct {
 	//
 	// Note: FunctionSection is index correlated with the CodeSection. If given the same position, ex. 2, a function
 	// type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2].
+	//
 	// Note: In the Binary Format, this is SectionIDFunction.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#function-section%E2%91%A0
@@ -151,6 +153,8 @@ type Module struct {
 
 	// ExportSection contains each export defined in this module.
 	//
+	// Note: In the Binary Format, this is SectionIDExport.
+	//
 	// See https://www.w3.org/TR/wasm-core-1/#exports%E2%91%A0
 	ExportSection map[string]*Export
 
@@ -179,20 +183,12 @@ type Module struct {
 
 	// NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format.
 	//
-	// Note: This is the only SectionIDCustom defined in the WebAssembly 1.0 (MVP) Binary Format. Others are in
-	// CustomSections
+	// Note: This is the only SectionIDCustom defined in the WebAssembly 1.0 (MVP) Binary Format.
+	// Others are skipped as they are not used in wazero.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#name-section%E2%91%A0
-	NameSection *NameSection
-
-	// CustomSections is set when at least one non-standard, or otherwise unsupported custom section was found in the
-	// binary format.
-	//
-	// Note: This never contains a "name" because that is standard and parsed into the NameSection.
-	// Note: In the Binary Format, this is SectionIDCode.
-	//
 	// See https://www.w3.org/TR/wasm-core-1/#custom-section%E2%91%A0
-	CustomSections map[string][]byte
+	NameSection *NameSection
 }
 
 // Index is the offset in an index namespace, not necessarily an absolute position in a Module section. This is because
@@ -467,20 +463,19 @@ func (m *Module) allDeclarations() (functions []Index, globals []*GlobalType, me
 	return
 }
 
-// SectionSize returns the count of items for a given section ID
+// SectionElementCount returns the count of elements in a given section ID
 //
-// For example, given...
-// * SectionIDType this returns the count of FunctionType
-// * SectionIDCustom this returns the count of unique section names
-// * SectionIDExport this returns the count of unique export names
-func (m *Module) SectionSize(sectionID SectionID) uint32 {
+// For example...
+// * SectionIDType returns the count of FunctionType
+// * SectionIDCustom returns one if the NameSection is present
+// * SectionIDExport returns the count of unique export names
+func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
 	switch sectionID {
 	case SectionIDCustom:
-		count := uint32(len(m.CustomSections))
 		if m.NameSection != nil {
-			return count + 1
+			return 1
 		}
-		return count
+		return 0
 	case SectionIDType:
 		return uint32(len(m.TypeSection))
 	case SectionIDImport:
diff --git a/wasm/store.go b/wasm/store.go
index 0732111f..3771303c 100644
--- a/wasm/store.go
+++ b/wasm/store.go
@@ -288,7 +288,7 @@ func (s *Store) Instantiate(module *Module, name string) error {
 
 	for i, f := range instance.Functions {
 		if err := s.engine.Compile(f); err != nil {
-			idx := module.SectionSize(SectionIDFunction) - 1
+			idx := module.SectionElementCount(SectionIDFunction) - 1
 			return fmt.Errorf("compilation failed at index %d/%d: %v", i, idx, err)
 		}
 	}
@@ -598,9 +598,9 @@ func (s *Store) buildFunctionInstances(module *Module, target *ModuleInstance) (
 	n, nLen := 0, len(functionNames)
 
 	for codeIndex, typeIndex := range module.FunctionSection {
-		if typeIndex >= module.SectionSize(SectionIDType) {
+		if typeIndex >= module.SectionElementCount(SectionIDType) {
 			return rollbackFuncs, fmt.Errorf("function type index out of range")
-		} else if uint32(codeIndex) >= module.SectionSize(SectionIDCode) {
+		} else if uint32(codeIndex) >= module.SectionElementCount(SectionIDCode) {
 			return rollbackFuncs, fmt.Errorf("code index out of range")
 		}
 
@@ -631,7 +631,7 @@ func (s *Store) buildFunctionInstances(module *Module, target *ModuleInstance) (
 		}
 
 		if err := validateFunctionInstance(f, funcs, globals, mems, tables, module.TypeSection, maximumValuesOnStack); err != nil {
-			idx := module.SectionSize(SectionIDFunction) - 1
+			idx := module.SectionElementCount(SectionIDFunction) - 1
 			return rollbackFuncs, fmt.Errorf("invalid function '%s' (%d/%d): %v", f.Name, codeIndex, idx, err)
 		}
 
@@ -684,7 +684,7 @@ func (s *Store) buildMemoryInstances(module *Module, target *ModuleInstance) (ro
 
 		size := uint64(offset) + uint64(len(d.Init))
 		maxPage := MemoryMaxPages
-		if d.MemoryIndex < module.SectionSize(SectionIDMemory) && module.MemorySection[d.MemoryIndex].Max != nil {
+		if d.MemoryIndex < module.SectionElementCount(SectionIDMemory) && module.MemorySection[d.MemoryIndex].Max != nil {
 			maxPage = *module.MemorySection[d.MemoryIndex].Max
 		}
 		if size > memoryPagesToBytesNum(maxPage) {
@@ -737,7 +737,7 @@ func (s *Store) buildTableInstances(module *Module, target *ModuleInstance) (rol
 		size := offset + len(elem.Init)
 
 		max := uint32(math.MaxUint32)
-		if elem.TableIndex < module.SectionSize(SectionIDTable) && module.TableSection[elem.TableIndex].Limit.Max != nil {
+		if elem.TableIndex < module.SectionElementCount(SectionIDTable) && module.TableSection[elem.TableIndex].Limit.Max != nil {
 			max = *module.TableSection[elem.TableIndex].Limit.Max
 		}
 
@@ -775,7 +775,7 @@ func (s *Store) buildTableInstances(module *Module, target *ModuleInstance) (rol
 }
 
 func (s *Store) buildExportInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	target.Exports = make(map[string]*ExportInstance, module.SectionSize(SectionIDExport))
+	target.Exports = make(map[string]*ExportInstance, module.SectionElementCount(SectionIDExport))
 	for name, exp := range module.ExportSection {
 		index := exp.Index
 		var ei *ExportInstance
diff --git a/wasm/text/decoder.go b/wasm/text/decoder.go
index 7ee66b5e..1d5f89d7 100644
--- a/wasm/text/decoder.go
+++ b/wasm/text/decoder.go
@@ -175,7 +175,7 @@ func (p *moduleParser) beginModuleField(tok tokenType, tokenBytes []byte, _, _ u
 			return p.parseImportModule, nil
 		case "func":
 			p.pos = positionFunc
-			p.funcParser.currentIdx = p.module.SectionSize(wasm.SectionIDFunction)
+			p.funcParser.currentIdx = p.module.SectionElementCount(wasm.SectionIDFunction)
 			return p.funcParser.begin, nil
 		case "table":
 			return nil, errors.New("TODO: table")
@@ -191,7 +191,7 @@ func (p *moduleParser) beginModuleField(tok tokenType, tokenBytes []byte, _, _ u
 			p.pos = positionExport
 			return p.parseExportName, nil
 		case "start":
-			if p.module.SectionSize(wasm.SectionIDStart) > 0 {
+			if p.module.SectionElementCount(wasm.SectionIDStart) > 0 {
 				return nil, moreThanOneInvalid("start")
 			}
 			p.pos = positionStart
@@ -310,7 +310,7 @@ func (p *moduleParser) beginImportDesc(tok tokenType, tokenBytes []byte, _, _ ui
 
 	switch string(tokenBytes) {
 	case "func":
-		if p.module.SectionSize(wasm.SectionIDFunction) > 0 {
+		if p.module.SectionElementCount(wasm.SectionIDFunction) > 0 {
 			return nil, importAfterModuleDefined(wasm.SectionIDFunction)
 		}
 		p.pos = positionImportFunc
@@ -361,7 +361,7 @@ func (p *moduleParser) addFunctionName(name string) {
 // Ex. If there is no signature `(import "" "main" (func))`
 //                     calls onImportFunc here ---^
 func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
-	idx := p.module.SectionSize(wasm.SectionIDImport)
+	idx := p.module.SectionElementCount(wasm.SectionIDImport)
 	if tok == tokenID { // Ex. (func $main $main)
 		return nil, fmt.Errorf("redundant ID %s", tokenBytes)
 	}
@@ -520,7 +520,7 @@ func (p *moduleParser) parseExportDesc(tok tokenType, tokenBytes []byte, line, c
 	default:
 		panic(fmt.Errorf("BUG: unhandled parsing state on parseExportDesc: %v", p.pos))
 	}
-	eIdx := p.module.SectionSize(wasm.SectionIDExport)
+	eIdx := p.module.SectionElementCount(wasm.SectionIDExport)
 	typeIdx, resolved, err := namespace.parseIndex(wasm.SectionIDExport, eIdx, 0, tok, tokenBytes, line, col)
 	if err != nil {
 		return nil, err
@@ -730,22 +730,22 @@ func (p *moduleParser) errorContext() string {
 	case positionModule:
 		return "module"
 	case positionType:
-		idx := p.module.SectionSize(wasm.SectionIDType)
+		idx := p.module.SectionElementCount(wasm.SectionIDType)
 		return fmt.Sprintf("module.type[%d]%s", idx, p.typeParser.errorContext())
 	case positionImport, positionImportFunc: // TODO: table, memory or global
-		idx := p.module.SectionSize(wasm.SectionIDImport)
+		idx := p.module.SectionElementCount(wasm.SectionIDImport)
 		if p.pos == positionImport {
 			return fmt.Sprintf("module.import[%d]", idx)
 		}
 		return fmt.Sprintf("module.import[%d].func%s", idx, p.typeUseParser.errorContext())
 	case positionFunc:
-		idx := p.module.SectionSize(wasm.SectionIDFunction)
+		idx := p.module.SectionElementCount(wasm.SectionIDFunction)
 		return fmt.Sprintf("module.func[%d]%s", idx, p.typeUseParser.errorContext())
 	case positionMemory:
-		idx := p.module.SectionSize(wasm.SectionIDMemory)
+		idx := p.module.SectionElementCount(wasm.SectionIDMemory)
 		return fmt.Sprintf("module.memory[%d]", idx)
 	case positionExport, positionExportFunc: // TODO: table, memory or global
-		idx := p.module.SectionSize(wasm.SectionIDExport)
+		idx := p.module.SectionElementCount(wasm.SectionIDExport)
 		if p.pos == positionExport {
 			return fmt.Sprintf("module.export[%d]", idx)
 		}


Test patch:
diff --git a/wasm/binary/decoder_test.go b/wasm/binary/decoder_test.go
index 813d801c..9ab47ce8 100644
--- a/wasm/binary/decoder_test.go
+++ b/wasm/binary/decoder_test.go
@@ -26,21 +26,6 @@ func TestDecodeModule(t *testing.T) {
 			name:  "only name section",
 			input: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}},
 		},
-		{
-			name: "only custom section",
-			input: &wasm.Module{CustomSections: map[string][]byte{
-				"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-			}},
-		},
-		{
-			name: "name section and a custom section",
-			input: &wasm.Module{
-				NameSection: &wasm.NameSection{ModuleName: "simple"},
-				CustomSections: map[string][]byte{
-					"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-				},
-			},
-		},
 		{
 			name: "type section",
 			input: &wasm.Module{
@@ -107,6 +92,29 @@ func TestDecodeModule(t *testing.T) {
 			require.Equal(t, tc.input, m)
 		})
 	}
+	t.Run("skips custom section", func(t *testing.T) {
+		input := append(append(magic, version...),
+			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
+			0x04, 'm', 'e', 'm', 'e',
+			1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
+		m, e := DecodeModule(input)
+		require.NoError(t, e)
+		require.Equal(t, &wasm.Module{}, m)
+	})
+	t.Run("skips custom section, but not name", func(t *testing.T) {
+		input := append(append(magic, version...),
+			wasm.SectionIDCustom, 0xf, // 15 bytes in this section
+			0x04, 'm', 'e', 'm', 'e',
+			1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
+			wasm.SectionIDCustom, 0x0e, // 14 bytes in this section
+			0x04, 'n', 'a', 'm', 'e',
+			subsectionIDModuleName, 0x07, // 7 bytes in this subsection
+			0x06, // the Module name simple is 6 bytes long
+			's', 'i', 'm', 'p', 'l', 'e')
+		m, e := DecodeModule(input)
+		require.NoError(t, e)
+		require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m)
+	})
 }
 
 func TestDecodeModule_Errors(t *testing.T) {
@@ -125,26 +133,15 @@ func TestDecodeModule_Errors(t *testing.T) {
 			input:       []byte("\x00asm\x01\x00\x00\x01"),
 			expectedErr: "invalid version header",
 		},
-		{
-			name: "redundant custom section",
-			input: append(append(magic, version...),
-				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
-				0x04, 'm', 'e', 'm', 'e',
-				subsectionIDModuleName, 0x03, 0x01, 'x',
-				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
-				0x04, 'm', 'e', 'm', 'e',
-				subsectionIDModuleName, 0x03, 0x01, 'y'),
-			expectedErr: "section custom: redundant custom section meme",
-		},
 		{
 			name: "redundant name section",
 			input: append(append(magic, version...),
 				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
 				0x04, 'n', 'a', 'm', 'e',
-				subsectionIDModuleName, 0x03, 0x01, 'x',
+				subsectionIDModuleName, 0x02, 0x01, 'x',
 				wasm.SectionIDCustom, 0x09, // 9 bytes in this section
 				0x04, 'n', 'a', 'm', 'e',
-				subsectionIDModuleName, 0x03, 0x01, 'x'),
+				subsectionIDModuleName, 0x02, 0x01, 'x'),
 			expectedErr: "section custom: redundant custom section name",
 		},
 	}
diff --git a/wasm/binary/encoder_test.go b/wasm/binary/encoder_test.go
index 903466b9..6dc938b5 100644
--- a/wasm/binary/encoder_test.go
+++ b/wasm/binary/encoder_test.go
@@ -32,34 +32,6 @@ func TestModule_Encode(t *testing.T) {
 				0x06, // the Module name simple is 6 bytes long
 				's', 'i', 'm', 'p', 'l', 'e'),
 		},
-		{
-			name: "only custom section",
-			input: &wasm.Module{CustomSections: map[string][]byte{
-				"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-			}},
-			expected: append(append(magic, version...),
-				wasm.SectionIDCustom, 0xf, // 15 bytes in this section
-				0x04, 'm', 'e', 'm', 'e',
-				1, 2, 3, 4, 5, 6, 7, 8, 9, 0),
-		},
-		{
-			name: "name section and a custom section", // name should encode last
-			input: &wasm.Module{
-				NameSection: &wasm.NameSection{ModuleName: "simple"},
-				CustomSections: map[string][]byte{
-					"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-				},
-			},
-			expected: append(append(magic, version...),
-				wasm.SectionIDCustom, 0xf, // 15 bytes in this section
-				0x04, 'm', 'e', 'm', 'e',
-				1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
-				wasm.SectionIDCustom, 0x0e, // 14 bytes in this section
-				0x04, 'n', 'a', 'm', 'e',
-				subsectionIDModuleName, 0x07, // 7 bytes in this subsection
-				0x06, // the Module name simple is 6 bytes long
-				's', 'i', 'm', 'p', 'l', 'e'),
-		},
 		{
 			name: "type section",
 			input: &wasm.Module{
diff --git a/wasm/binary/names_test.go b/wasm/binary/names_test.go
index d148b7cb..0cf34505 100644
--- a/wasm/binary/names_test.go
+++ b/wasm/binary/names_test.go
@@ -1,6 +1,7 @@
 package binary
 
 import (
+	"bytes"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -201,7 +202,8 @@ func TestDecodeNameSection(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			ns, err := decodeNameSection(encodeNameSectionData(tc.input))
+			data := encodeNameSectionData(tc.input)
+			ns, err := decodeNameSection(bytes.NewReader(data), uint64(len(data)))
 			require.NoError(t, err)
 			require.Equal(t, tc.input, ns)
 		})
@@ -302,7 +304,7 @@ func TestDecodeNameSection_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			_, err := decodeNameSection(tc.input)
+			_, err := decodeNameSection(bytes.NewReader(tc.input), uint64(len(tc.input)))
 			require.EqualError(t, err, tc.expectedErr)
 		})
 	}
diff --git a/wasm/module_test.go b/wasm/module_test.go
index ad558122..13a463ad 100644
--- a/wasm/module_test.go
+++ b/wasm/module_test.go
@@ -224,23 +224,6 @@ func TestModule_SectionSize(t *testing.T) {
 			input:    &Module{NameSection: &NameSection{ModuleName: "simple"}},
 			expected: map[string]uint32{"custom": 1},
 		},
-		{
-			name: "only custom section",
-			input: &Module{CustomSections: map[string][]byte{
-				"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-			}},
-			expected: map[string]uint32{"custom": 1},
-		},
-		{
-			name: "name section and a custom section",
-			input: &Module{
-				NameSection: &NameSection{ModuleName: "simple"},
-				CustomSections: map[string][]byte{
-					"meme": {1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
-				},
-			},
-			expected: map[string]uint32{"custom": 2},
-		},
 		{
 			name: "type section",
 			input: &Module{
@@ -312,7 +295,7 @@ func TestModule_SectionSize(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			actual := map[string]uint32{}
 			for i := SectionID(0); i <= SectionIDData; i++ {
-				if size := tc.input.SectionSize(i); size > 0 {
+				if size := tc.input.SectionElementCount(i); size > 0 {
 					actual[SectionIDName(i)] = size
 				}
 			}