Files
wazero/internal/wasm/text/decoder.go
Crypt Keeper ea91be49eb Improves error handling in instantiation (#346)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-03-08 11:04:37 +08:00

770 lines
29 KiB
Go

package text
import (
"errors"
"fmt"
wasm "github.com/tetratelabs/wazero/internal/wasm"
)
// parserPosition holds the positional state of a parser. Values are also useful as they allow you to do a reference
// search for all related code including parsers of that position.
type parserPosition byte
const (
positionInitial parserPosition = iota
positionFunc
positionType
positionParam
positionResult
positionModule
positionImport
positionImportFunc
positionMemory
positionExport
positionExportFunc
positionExportMemory
positionStart
)
type callbackPosition byte
const (
// callbackPositionUnhandledToken is set on a token besides a paren.
callbackPositionUnhandledToken callbackPosition = iota
// callbackPositionUnhandledField is at the field name (tokenKeyword) which isn't "type", "param" or "result"
callbackPositionUnhandledField
// callbackPositionEndField is at the end (tokenRParen) of the field enclosing the type use.
callbackPositionEndField
)
// moduleParser parses a single wasm.Module from WebAssembly 1.0 (20191205) Text format.
//
// Note: The indexNamespace of wasm.SectionIDMemory and wasm.SectionIDTable allow up-to-one item. For example, you
// cannot define both one import and one module-defined memory, rather one or the other (or none). Even if these rules
// are also enforced in module instantiation, they are also enforced here, to allow relevant source line/col in errors.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A3
type moduleParser struct {
// source is the entire WebAssembly text format source code being parsed.
source []byte
// enabledFeatures ensure parsing errs at the correct line and column number when a feature is disabled.
enabledFeatures wasm.Features
// module holds the fields incrementally parsed from tokens in the source.
module *wasm.Module
// pos is used to give an appropriate errorContext
pos parserPosition
// currentModuleField holds the incremental state of a module-scoped field such as an import.
currentModuleField interface{}
// typeNamespace represents the function type index namespace, which begins with the TypeSection and ends with any
// inlined type uses which had neither a type index, nor matched an existing type. Elements in the TypeSection can
// can declare symbolic IDs, such as "$v_v", which are resolved here (without the '$' prefix)
typeNamespace *indexNamespace
// typeParser parses "param" and "result" fields in the TypeSection.
typeParser *typeParser
// typeParser parses "type", "param" and "result" fields for type uses such as imported or module-defined
// functions.
typeUseParser *typeUseParser
// funcNamespace represents the function index namespace, which begins with any internalwasm.ExternTypeFunc in the
// internalwasm.SectionIDImport followed by the internalwasm.SectionIDFunction.
//
// Non-abbreviated imported and module-defined functions can declare symbolic IDs, such as "$main", which are
// resolved here (without the '$' prefix).
funcNamespace *indexNamespace
// funcParser parses the CodeSection for a given module-defined function.
funcParser *funcParser
// memoryNamespace represents the memory index namespace, which begins with any internalwasm.ExternTypeMemory in
// the internalwasm.SectionIDImport followed by the internalwasm.SectionIDMemory.
//
// Non-abbreviated imported and module-defined memories can declare symbolic IDs, such as "$mem", which are resolved
// here (without the '$' prefix).
memoryNamespace *indexNamespace
// memoryParser parses the MemorySection for a given module-defined memory.
memoryParser *memoryParser
// unresolvedExports holds any exports whose type index wasn't resolvable when parsed.
unresolvedExports map[wasm.Index]*wasm.Export
// field counts can be different from the count in a section when abbreviated imports exist. To give an accurate
// errorContext, we count explicitly.
fieldCountFunc, fieldCountMemory uint32
}
// DecodeModule implements internalwasm.DecodeModule for the WebAssembly 1.0 (20191205) Text Format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#text-format%E2%91%A0
func DecodeModule(source []byte, enabledFeatures wasm.Features) (result *wasm.Module, err error) {
// TODO: when globals are supported, err on mutable globals if disabled
// names are the wasm.Module NameSection
//
// * ModuleName: ex. "test" if (module $test)
// * FunctionNames: nil od no imported or module-defined function had a name
// * LocalNames: nil when no imported or module-defined function had named (param) fields.
names := &wasm.NameSection{}
module := &wasm.Module{NameSection: names}
p := newModuleParser(module, enabledFeatures)
p.source = source
// A valid source must begin with the token '(', but it could be preceded by whitespace or comments. For this
// reason, we cannot enforce source[0] == '(', and instead need to start the lexer to check the first token.
line, col, err := lex(p.ensureLParen, p.source)
if err != nil {
return nil, &FormatError{line, col, p.errorContext(), err}
}
// All identifier contexts are now bound, so resolveTypeUses any uses of symbolic identifiers into concrete indices.
if err = p.resolveTypeUses(module); err != nil {
return nil, err
}
if err = p.resolveTypeIndices(module); err != nil {
return nil, err
}
if err = p.resolveFunctionIndices(module); err != nil {
return nil, err
}
// Don't set the name section unless we parsed a name!
if names.ModuleName == "" && names.FunctionNames == nil && names.LocalNames == nil {
module.NameSection = nil
}
return module, nil
}
func newModuleParser(module *wasm.Module, enabledFeatures wasm.Features) *moduleParser {
p := moduleParser{module: module, enabledFeatures: enabledFeatures,
typeNamespace: newIndexNamespace(module.SectionElementCount),
funcNamespace: newIndexNamespace(module.SectionElementCount),
memoryNamespace: newIndexNamespace(module.SectionElementCount),
}
p.typeParser = newTypeParser(p.typeNamespace, p.onTypeEnd)
p.typeUseParser = newTypeUseParser(module, p.typeNamespace)
p.funcParser = newFuncParser(enabledFeatures, p.typeUseParser, p.funcNamespace, p.endFunc)
p.memoryParser = newMemoryParser(p.memoryNamespace, p.endMemory)
return &p
}
// ensureLParen errors unless a '(' is found as the text format must start with a field.
func (p *moduleParser) ensureLParen(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenLParen {
return nil, fmt.Errorf("expected '(', but parsed %s: %s", tok, tokenBytes)
}
return p.beginSourceField, nil
}
// beginSourceField returns parseModuleName if the field name is "module".
func (p *moduleParser) beginSourceField(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenKeyword {
return nil, expectedField(tok)
}
if string(tokenBytes) != "module" {
return nil, unexpectedFieldName(tokenBytes)
}
p.pos = positionModule
return p.parseModuleName, nil
}
// beginModuleField returns a parser according to the module field name (tokenKeyword), or errs if invalid.
func (p *moduleParser) beginModuleField(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok == tokenKeyword {
switch string(tokenBytes) {
case "type":
p.pos = positionType
return p.typeParser.begin, nil
case "import":
p.pos = positionImport
return p.parseImportModule, nil
case wasm.ExternTypeFuncName:
p.pos = positionFunc
return p.funcParser.begin, nil
case wasm.ExternTypeTableName:
return nil, fmt.Errorf("TODO: %s", tokenBytes)
case wasm.ExternTypeMemoryName:
if p.memoryNamespace.count > 0 {
return nil, moreThanOneInvalidInSection(wasm.SectionIDMemory)
}
p.pos = positionMemory
return p.memoryParser.begin, nil
case wasm.ExternTypeGlobalName:
return nil, fmt.Errorf("TODO: %s", tokenBytes)
case "export":
p.pos = positionExport
return p.parseExportName, nil
case "start":
if p.module.SectionElementCount(wasm.SectionIDStart) > 0 {
return nil, moreThanOneInvalid("start")
}
p.pos = positionStart
return p.parseStart, nil
case "elem":
return nil, errors.New("TODO: elem")
case "data":
return nil, errors.New("TODO: data")
default:
return nil, unexpectedFieldName(tokenBytes)
}
}
return nil, expectedField(tok)
}
// parseModuleName records the wasm.NameSection ModuleName, if present, and resumes with parseModule.
//
// Ex. A module name is present `(module $math)`
// records math --^
//
// Ex. No module name `(module)`
// calls parseModule here --^
func (p *moduleParser) parseModuleName(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
if tok == tokenID { // Ex. $Math
p.module.NameSection.ModuleName = string(stripDollar(tokenBytes))
return p.parseModule, nil
}
return p.parseModule(tok, tokenBytes, line, col)
}
// parseModule returns beginModuleField on the start of a field '(' or parseUnexpectedTrailingCharacters if the module
// is complete ')'.
func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenID:
return nil, fmt.Errorf("redundant ID %s", tokenBytes)
case tokenLParen:
return p.beginModuleField, nil
case tokenRParen: // end of module
p.pos = positionInitial
return p.parseUnexpectedTrailingCharacters, nil // only one module is allowed and nothing else
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// onType adds the current type into the TypeSection and returns parseModule to prepare for the next field.
func (p *moduleParser) onTypeEnd(ft *wasm.FunctionType) tokenParser {
p.module.TypeSection = append(p.module.TypeSection, ft)
p.pos = positionModule
return p.parseModule
}
// parseImportModule returns parseImportName after recording the import module name, or errs if it couldn't be read.
//
// Ex. Imported module name is present `(import "Math" "PI" (func (result f32)))`
// records Math --^ ^
// parseImportName resumes here --+
//
// Ex. Imported module name is absent `(import (func (result f32)))`
// errs here --^
func (p *moduleParser) parseImportModule(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenString: // Ex. "" or "Math"
module := string(tokenBytes[1 : len(tokenBytes)-1]) // unquote
p.currentModuleField = &wasm.Import{Module: module}
return p.parseImportName, nil
case tokenLParen, tokenRParen:
return nil, errors.New("missing module and name")
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// parseImportName returns parseImport after recording the import name, or errs if it couldn't be read.
//
// Ex. Import name is present `(import "Math" "PI" (func (result f32)))`
// starts here --^^ ^
// records PI --+ |
// parseImport resumes here --+
//
// Ex. Imported function name is absent `(import "Math" (func (result f32)))`
// errs here --+
func (p *moduleParser) parseImportName(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenString: // Ex. "" or "PI"
name := string(tokenBytes[1 : len(tokenBytes)-1]) // unquote
(p.currentModuleField.(*wasm.Import)).Name = name
return p.parseImport, nil
case tokenLParen, tokenRParen:
return nil, errors.New("missing name")
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// parseImport returns beginImportDesc to determine the wasm.ExternType and dispatch accordingly.
func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenString: // Ex. (import "Math" "PI" "PI"
return nil, fmt.Errorf("redundant name: %s", tokenBytes[1:len(tokenBytes)-1]) // unquote
case tokenLParen: // start fields, ex. (func
return p.beginImportDesc, nil
case tokenRParen: // end of this import
return nil, errors.New("missing description field") // Ex. missing (func): (import "Math" "Pi")
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// beginImportDesc returns a parser according to the import field name (tokenKeyword), or errs if invalid.
func (p *moduleParser) beginImportDesc(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenKeyword {
return nil, expectedField(tok)
}
switch string(tokenBytes) {
case wasm.ExternTypeFuncName:
if p.module.SectionElementCount(wasm.SectionIDFunction) > 0 {
return nil, importAfterModuleDefined(wasm.SectionIDFunction)
}
p.pos = positionImportFunc
return p.parseImportFuncID, nil
case wasm.ExternTypeTableName, wasm.ExternTypeMemoryName, wasm.ExternTypeGlobalName:
return nil, fmt.Errorf("TODO: %s", tokenBytes)
default:
return nil, unexpectedFieldName(tokenBytes)
}
}
// parseImportFuncID records the ID of the current imported function, if present, and resumes with parseImportFunc.
//
// Ex. A function ID is present `(import "Math" "PI" (func $math.pi (result f32))`
// records math.pi here --^
// parseImportFunc resumes here --^
//
// Ex. No function ID `(import "Math" "PI" (func (result f32))`
// calls parseImportFunc here --^
func (p *moduleParser) parseImportFuncID(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
if tok == tokenID { // Ex. $main
if name, err := p.funcNamespace.setID(tokenBytes); err != nil {
return nil, err
} else {
p.addFunctionName(name)
}
return p.parseImportFunc, nil
}
return p.parseImportFunc(tok, tokenBytes, line, col)
}
// addFunctionName appends the current imported or module-defined function name to the wasm.NameSection iff it is not
// empty.
func (p *moduleParser) addFunctionName(name string) {
if name == "" {
return // there's no value in an empty name
}
na := &wasm.NameAssoc{Index: p.funcNamespace.count, Name: name}
p.module.NameSection.FunctionNames = append(p.module.NameSection.FunctionNames, na)
}
// parseImportFunc passes control to the typeUseParser until any signature is read, then returns onImportFunc.
//
// Ex. `(import "Math" "PI" (func $math.pi (result f32)))`
// starts here --^ ^
// onImportFunc resumes here --+
//
// 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) {
if tok == tokenID { // Ex. (func $main $main)
return nil, fmt.Errorf("redundant ID %s", tokenBytes)
}
return p.typeUseParser.begin(wasm.SectionIDImport, p.onImportFunc, tok, tokenBytes, line, col)
}
// onImportFunc records the type index and local names of the current imported function, and increments
// funcNamespace as it is shared across imported and module-defined functions. Finally, this returns parseImportEnd to
// the current import into the ImportSection.
func (p *moduleParser) onImportFunc(typeIdx wasm.Index, paramNames wasm.NameMap, pos callbackPosition, tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
i := p.currentModuleField.(*wasm.Import)
i.Type = wasm.ExternTypeFunc
i.DescFunc = typeIdx
p.addLocalNames(paramNames)
p.funcNamespace.count++
switch pos {
case callbackPositionUnhandledToken:
return nil, unexpectedToken(tok, tokenBytes)
case callbackPositionUnhandledField:
return nil, unexpectedFieldName(tokenBytes)
case callbackPositionEndField:
p.pos = positionImport
return p.parseImportEnd, nil
}
return p.parseImportFuncEnd, nil
}
// parseImportEnd adds the current import into the ImportSection and returns parseModule to prepare for the next field.
func (p *moduleParser) parseImportFuncEnd(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenRParen {
return nil, unexpectedToken(tok, tokenBytes)
}
p.pos = positionImport
return p.parseImportEnd, nil
}
// addLocalNames appends wasm.NameSection LocalNames for the current function.
func (p *moduleParser) addLocalNames(localNames wasm.NameMap) {
if localNames != nil {
na := &wasm.NameMapAssoc{Index: p.funcNamespace.count, NameMap: localNames}
p.module.NameSection.LocalNames = append(p.module.NameSection.LocalNames, na)
}
}
// parseImportEnd adds the current import into the ImportSection and returns parseModule to prepare for the next field.
func (p *moduleParser) parseImportEnd(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenRParen {
return nil, unexpectedToken(tok, tokenBytes)
}
p.module.ImportSection = append(p.module.ImportSection, p.currentModuleField.(*wasm.Import))
p.currentModuleField = nil
p.pos = positionModule
return p.parseModule, nil
}
// endFunc adds the type index, code and local names for the current function, and increments funcNamespace as it is
// shared across imported and module-defined functions. Finally, this returns parseModule to prepare for the next field.
func (p *moduleParser) endFunc(typeIdx wasm.Index, code *wasm.Code, name string, localNames wasm.NameMap) (tokenParser, error) {
p.addFunctionName(name)
p.module.FunctionSection = append(p.module.FunctionSection, typeIdx)
p.module.CodeSection = append(p.module.CodeSection, code)
p.addLocalNames(localNames)
// Multiple funcs are allowed, so advance in case there's a next.
p.funcNamespace.count++
p.fieldCountFunc++
p.pos = positionModule
return p.parseModule, nil
}
// endMemory adds the limits for the current memory, and increments memoryNamespace as it is shared across imported and
// module-defined memories. Finally, this returns parseModule to prepare for the next field.
func (p *moduleParser) endMemory(min uint32, max *uint32) tokenParser {
p.module.MemorySection = append(p.module.MemorySection, &wasm.MemoryType{Min: min, Max: max})
// Multiple memories are allowed, so advance in case there's a next.
p.memoryNamespace.count++
p.fieldCountMemory++
p.pos = positionModule
return p.parseModule
}
// parseExportName returns parseExport after recording the export name, or errs if it couldn't be read.
//
// Ex. Export name is present `(export "PI" (func 0))`
// starts here --^ ^
// records PI --^ |
// parseExport resumes here --+
//
// Ex. Export name is absent `(export (func 0))`
// errs here --^
func (p *moduleParser) parseExportName(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenString: // Ex. "" or "PI"
name := string(tokenBytes[1 : len(tokenBytes)-1]) // strip quotes
if _, ok := p.module.ExportSection[name]; ok {
return nil, fmt.Errorf("%q already exported", name)
}
p.currentModuleField = &wasm.Export{Name: name}
return p.parseExport, nil
case tokenLParen, tokenRParen:
return nil, errors.New("missing name")
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// parseExport returns beginExportDesc to determine the wasm.ExternType and dispatch accordingly.
func (p *moduleParser) parseExport(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenString: // Ex. (export "PI" "PI"
return nil, fmt.Errorf("redundant name: %s", tokenBytes[1:len(tokenBytes)-1]) // unquote
case tokenLParen: // start fields, ex. (func
return p.beginExportDesc, nil
case tokenRParen: // end of this export
return nil, errors.New("missing description field") // Ex. missing (func): (export "Math" "Pi")
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// beginExportDesc returns a parser according to the export field name (tokenKeyword), or errs if invalid.
func (p *moduleParser) beginExportDesc(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenKeyword {
return nil, expectedField(tok)
}
switch string(tokenBytes) {
case wasm.ExternTypeFuncName:
p.pos = positionExportFunc
return p.parseExportDesc, nil
case wasm.ExternTypeMemoryName:
p.pos = positionExportMemory
return p.parseExportDesc, nil
case wasm.ExternTypeTableName, wasm.ExternTypeGlobalName:
return nil, fmt.Errorf("TODO: %s", tokenBytes)
default:
return nil, unexpectedFieldName(tokenBytes)
}
}
// parseExportDesc records the symbolic or numeric function index of the export target
func (p *moduleParser) parseExportDesc(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
var namespace *indexNamespace
e := p.currentModuleField.(*wasm.Export)
switch p.pos {
case positionExportFunc:
e.Type = wasm.ExternTypeFunc
namespace = p.funcNamespace
case positionExportMemory:
e.Type = wasm.ExternTypeMemory
namespace = p.memoryNamespace
default:
panic(fmt.Errorf("BUG: unhandled parsing state on parseExportDesc: %v", p.pos))
}
typeIdx, resolved, err := namespace.parseIndex(wasm.SectionIDExport, 0, tok, tokenBytes, line, col)
if err != nil {
return nil, err
}
e.Index = typeIdx
// All sections in wasm.Module are numeric indices except exports. Hence, we have to special-case here
if !resolved {
eIdx := p.module.SectionElementCount(wasm.SectionIDExport)
if p.unresolvedExports == nil {
p.unresolvedExports = map[wasm.Index]*wasm.Export{eIdx: e}
} else {
p.unresolvedExports[eIdx] = e
}
}
return p.parseExportDescEnd, nil
}
// parseExportFuncEnd returns parseExportEnd to add the current export.
func (p *moduleParser) parseExportDescEnd(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenUN, tokenID:
return nil, errors.New("redundant index")
case tokenRParen:
p.pos = positionExport
return p.parseExportEnd, nil
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
// parseImportEnd adds the current export into the ExportSection and returns parseModule to prepare for the next field.
func (p *moduleParser) parseExportEnd(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
if tok != tokenRParen {
return nil, unexpectedToken(tok, tokenBytes)
}
e := p.currentModuleField.(*wasm.Export)
p.currentModuleField = nil
if p.module.ExportSection == nil {
p.module.ExportSection = map[string]*wasm.Export{e.Name: e}
} else {
p.module.ExportSection[e.Name] = e
}
p.pos = positionModule
return p.parseModule, nil
}
// parseStart returns parseStartEnd after recording the start function index, or errs if it couldn't be read.
func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
idx, _, err := p.funcNamespace.parseIndex(wasm.SectionIDStart, 0, tok, tokenBytes, line, col)
if err != nil {
return nil, err
}
p.module.StartSection = &idx
return p.parseStartEnd, nil
}
// parseStartEnd returns parseModule to prepare for the next field.
func (p *moduleParser) parseStartEnd(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
switch tok {
case tokenUN, tokenID:
return nil, errors.New("redundant index")
case tokenRParen:
p.pos = positionModule
return p.parseModule, nil
default:
return nil, unexpectedToken(tok, tokenBytes)
}
}
func (p *moduleParser) parseUnexpectedTrailingCharacters(_ tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
return nil, fmt.Errorf("unexpected trailing characters: %s", tokenBytes)
}
// resolveTypeIndices ensures any indices point are numeric or returns a FormatError if they cannot be bound.
func (p *moduleParser) resolveTypeIndices(module *wasm.Module) error {
for _, unresolved := range p.typeNamespace.unresolvedIndices {
target, err := p.typeNamespace.resolve(unresolved)
if err != nil {
return err
}
switch unresolved.section {
case wasm.SectionIDImport:
module.ImportSection[unresolved.idx].DescFunc = target
case wasm.SectionIDFunction:
module.FunctionSection[unresolved.idx] = target
default:
panic(unhandledSection(unresolved.section))
}
}
return nil
}
// resolveFunctionIndices ensures any indices point are numeric or returns a FormatError if they cannot be bound.
func (p *moduleParser) resolveFunctionIndices(module *wasm.Module) error {
for _, unresolved := range p.funcNamespace.unresolvedIndices {
target, err := p.funcNamespace.resolve(unresolved)
if err != nil {
return err
}
switch unresolved.section {
case wasm.SectionIDCode:
if target > 255 {
return errors.New("TODO: unresolved function indexes that don't fit in a byte")
}
module.CodeSection[unresolved.idx].Body[unresolved.bodyOffset] = byte(target)
case wasm.SectionIDExport:
p.unresolvedExports[unresolved.idx].Index = target
case wasm.SectionIDStart:
module.StartSection = &target
default:
panic(unhandledSection(unresolved.section))
}
}
return nil
}
// resolveTypeUses adds any missing inlined types, resolving any type indexes in the FunctionSection or ImportSection.
// This errs if any type index is unresolved, out of range or mismatches an inlined type use signature.
func (p *moduleParser) resolveTypeUses(module *wasm.Module) error {
inlinedToRealIdx := p.addInlinedTypes()
return p.resolveInlinedTypes(module, inlinedToRealIdx)
}
func (p *moduleParser) resolveInlinedTypes(module *wasm.Module, inlinedToRealIdx map[wasm.Index]wasm.Index) error {
// Now look for all the uses of the inlined types and apply the mapping above
for _, i := range p.typeUseParser.inlinedTypeIndices {
switch i.section {
case wasm.SectionIDImport:
if i.typePos == nil {
module.ImportSection[i.idx].DescFunc = inlinedToRealIdx[i.inlinedIdx]
continue
}
typeIdx := module.ImportSection[i.idx].DescFunc
if err := p.requireInlinedMatchesReferencedType(module.TypeSection, typeIdx, i); err != nil {
return err
}
case wasm.SectionIDFunction:
if i.typePos == nil {
module.FunctionSection[i.idx] = inlinedToRealIdx[i.inlinedIdx]
continue
}
typeIdx := module.FunctionSection[i.idx]
if err := p.requireInlinedMatchesReferencedType(module.TypeSection, typeIdx, i); err != nil {
return err
}
default:
panic(unhandledSection(i.section))
}
}
return nil
}
func (p *moduleParser) requireInlinedMatchesReferencedType(typeSection []*wasm.FunctionType, typeIdx wasm.Index, i *inlinedTypeIndex) error {
inlined := p.typeUseParser.inlinedTypes[i.inlinedIdx]
if err := requireInlinedMatchesReferencedType(typeSection, typeIdx, inlined.Params, inlined.Results); err != nil {
var context string
switch i.section {
case wasm.SectionIDImport:
context = fmt.Sprintf("module.import[%d].func", i.idx)
case wasm.SectionIDFunction:
context = fmt.Sprintf("module.func[%d]", i.idx)
default:
panic(unhandledSection(i.section))
}
return &FormatError{Line: i.typePos.line, Col: i.typePos.col, Context: context, cause: err}
}
return nil
}
// addInlinedTypes adds any inlined types missing from the module TypeSection and returns an index mapping the inlined
// index to real index in the TypeSection. This avoids adding or looking up a type twice when it has multiple type uses.
func (p *moduleParser) addInlinedTypes() map[wasm.Index]wasm.Index {
inlinedTypeCount := len(p.typeUseParser.inlinedTypes)
if inlinedTypeCount == 0 {
return nil
}
inlinedToRealIdx := make(map[wasm.Index]wasm.Index, inlinedTypeCount)
INLINED:
for idx, inlined := range p.typeUseParser.inlinedTypes {
inlinedIdx := wasm.Index(idx)
// A type can be defined after its type use. Ex. (module (func (param i32)) (type (func (param i32)))
// This uses an inner loop to avoid creating a large map for an edge case.
for realIdx, t := range p.module.TypeSection {
if t.EqualsSignature(inlined.Params, inlined.Results) {
inlinedToRealIdx[inlinedIdx] = wasm.Index(realIdx)
continue INLINED
}
}
// When we get here, this means the inlined type is not in the TypeSection, yet, so add it.
inlinedToRealIdx[inlinedIdx] = p.typeNamespace.count
p.module.TypeSection = append(p.module.TypeSection, inlined)
p.typeNamespace.count++
}
return inlinedToRealIdx
}
func (p *moduleParser) errorContext() string {
switch p.pos {
case positionInitial:
return ""
case positionModule:
return "module"
case positionType:
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.SectionElementCount(wasm.SectionIDImport)
if p.pos == positionImport {
return fmt.Sprintf("module.import[%d]", idx)
}
return fmt.Sprintf("module.import[%d].%s%s", idx, wasm.ExternTypeFuncName, p.typeUseParser.errorContext())
case positionFunc:
idx := p.fieldCountFunc
return fmt.Sprintf("module.%s[%d]%s", wasm.ExternTypeFuncName, idx, p.typeUseParser.errorContext())
case positionMemory:
return fmt.Sprintf("module.%s[%d]", wasm.ExternTypeMemoryName, p.fieldCountMemory)
case positionExport, positionExportFunc: // TODO: table, memory or global
idx := p.module.SectionElementCount(wasm.SectionIDExport)
if p.pos == positionExport {
return fmt.Sprintf("module.export[%d]", idx)
}
return fmt.Sprintf("module.export[%d].%s", idx, wasm.ExternTypeFuncName)
case positionStart:
return "module.start"
default: // parserPosition is an enum, we expect to have handled all cases above. panic if we didn't
panic(fmt.Errorf("BUG: unhandled parsing state on errorContext: %v", p.pos))
}
}