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

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	"wa-lang.org/wazero/api"
    10  	"wa-lang.org/wazero/internal/leb128"
    11  	"wa-lang.org/wazero/internal/wasm"
    12  )
    13  
    14  // DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format
    15  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
    16  func DecodeModule(
    17  	binary []byte,
    18  	enabledFeatures api.CoreFeatures,
    19  	memoryLimitPages uint32,
    20  	memoryCapacityFromMax bool,
    21  ) (*wasm.Module, error) {
    22  	r := bytes.NewReader(binary)
    23  
    24  	// Magic number.
    25  	buf := make([]byte, 4)
    26  	if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) {
    27  		return nil, ErrInvalidMagicNumber
    28  	}
    29  
    30  	// Version.
    31  	if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) {
    32  		return nil, ErrInvalidVersion
    33  	}
    34  
    35  	memorySizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax)
    36  
    37  	m := &wasm.Module{}
    38  	for {
    39  		// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
    40  		// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA
    41  		sectionID, err := r.ReadByte()
    42  		if err == io.EOF {
    43  			break
    44  		} else if err != nil {
    45  			return nil, fmt.Errorf("read section id: %w", err)
    46  		}
    47  
    48  		sectionSize, _, err := leb128.DecodeUint32(r)
    49  		if err != nil {
    50  			return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
    51  		}
    52  
    53  		sectionContentStart := r.Len()
    54  		switch sectionID {
    55  		case wasm.SectionIDCustom:
    56  			// First, validate the section and determine if the section for this name has already been set
    57  			name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
    58  			if decodeErr != nil {
    59  				err = decodeErr
    60  				break
    61  			} else if sectionSize < nameSize {
    62  				err = fmt.Errorf("malformed custom section %s", name)
    63  				break
    64  			} else if name == "name" && m.NameSection != nil {
    65  				err = fmt.Errorf("redundant custom section %s", name)
    66  				break
    67  			}
    68  
    69  			// Now, either decode the NameSection or skip an unsupported one
    70  			limit := sectionSize - nameSize
    71  			if name == "name" {
    72  				m.NameSection, err = decodeNameSection(r, uint64(limit))
    73  			} else {
    74  				// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
    75  				if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
    76  					return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
    77  				}
    78  			}
    79  
    80  		case wasm.SectionIDType:
    81  			m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
    82  		case wasm.SectionIDImport:
    83  			if m.ImportSection, err = decodeImportSection(r, memorySizer, memoryLimitPages, enabledFeatures); err != nil {
    84  				return nil, err // avoid re-wrapping the error.
    85  			}
    86  		case wasm.SectionIDFunction:
    87  			m.FunctionSection, err = decodeFunctionSection(r)
    88  		case wasm.SectionIDTable:
    89  			m.TableSection, err = decodeTableSection(r, enabledFeatures)
    90  		case wasm.SectionIDMemory:
    91  			m.MemorySection, err = decodeMemorySection(r, memorySizer, memoryLimitPages)
    92  		case wasm.SectionIDGlobal:
    93  			if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil {
    94  				return nil, err // avoid re-wrapping the error.
    95  			}
    96  		case wasm.SectionIDExport:
    97  			m.ExportSection, err = decodeExportSection(r)
    98  		case wasm.SectionIDStart:
    99  			if m.StartSection != nil {
   100  				return nil, errors.New("multiple start sections are invalid")
   101  			}
   102  			m.StartSection, err = decodeStartSection(r)
   103  		case wasm.SectionIDElement:
   104  			m.ElementSection, err = decodeElementSection(r, enabledFeatures)
   105  		case wasm.SectionIDCode:
   106  			m.CodeSection, err = decodeCodeSection(r)
   107  		case wasm.SectionIDData:
   108  			m.DataSection, err = decodeDataSection(r, enabledFeatures)
   109  		case wasm.SectionIDDataCount:
   110  			if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
   111  				return nil, fmt.Errorf("data count section not supported as %v", err)
   112  			}
   113  			m.DataCountSection, err = decodeDataCountSection(r)
   114  		default:
   115  			err = ErrInvalidSectionID
   116  		}
   117  
   118  		readBytes := sectionContentStart - r.Len()
   119  		if err == nil && int(sectionSize) != readBytes {
   120  			err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes)
   121  		}
   122  
   123  		if err != nil {
   124  			return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
   125  		}
   126  	}
   127  
   128  	functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
   129  	if functionCount != codeCount {
   130  		return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
   131  	}
   132  	return m, nil
   133  }
   134  
   135  // memorySizer derives min, capacity and max pages from decoded wasm.
   136  type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32)
   137  
   138  // newMemorySizer sets capacity to minPages unless max is defined and
   139  // memoryCapacityFromMax is true.
   140  func newMemorySizer(memoryLimitPages uint32, memoryCapacityFromMax bool) memorySizer {
   141  	return func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
   142  		if maxPages != nil {
   143  			if memoryCapacityFromMax {
   144  				return minPages, *maxPages, *maxPages
   145  			}
   146  			return minPages, minPages, *maxPages
   147  		}
   148  		return minPages, minPages, memoryLimitPages
   149  	}
   150  }