binary: adds custom section decoder (#877)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -89,7 +89,7 @@ func newExample() *wasm.Module {
|
|||||||
|
|
||||||
func TestExampleUpToDate(t *testing.T) {
|
func TestExampleUpToDate(t *testing.T) {
|
||||||
t.Run("binary.DecodeModule", func(t *testing.T) {
|
t.Run("binary.DecodeModule", func(t *testing.T) {
|
||||||
m, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false)
|
m, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, example, m)
|
require.Equal(t, example, m)
|
||||||
})
|
})
|
||||||
@@ -116,7 +116,7 @@ func BenchmarkCodec(b *testing.B) {
|
|||||||
b.Run("binary.DecodeModule", func(b *testing.B) {
|
b.Run("binary.DecodeModule", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if _, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false); err != nil {
|
if _, err := binary.DecodeModule(exampleWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ var spectestWasm []byte
|
|||||||
// See https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/imports.wast
|
// See https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/imports.wast
|
||||||
// See https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/script/js.ml#L13-L25
|
// See https://github.com/WebAssembly/spec/blob/wg-1.0/interpreter/script/js.ml#L13-L25
|
||||||
func addSpectestModule(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, enabledFeatures api.CoreFeatures) {
|
func addSpectestModule(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, enabledFeatures api.CoreFeatures) {
|
||||||
mod, err := binaryformat.DecodeModule(spectestWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false)
|
mod, err := binaryformat.DecodeModule(spectestWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// (global (export "global_i32") i32 (i32.const 666))
|
// (global (export "global_i32") i32 (i32.const 666))
|
||||||
@@ -420,7 +420,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(
|
|||||||
case "module":
|
case "module":
|
||||||
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
|
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
|
||||||
require.NoError(t, err, msg)
|
require.NoError(t, err, msg)
|
||||||
mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemoryLimitPages, false)
|
mod, err := binaryformat.DecodeModule(buf, enabledFeatures, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, err, msg)
|
require.NoError(t, err, msg)
|
||||||
require.NoError(t, mod.Validate(enabledFeatures))
|
require.NoError(t, mod.Validate(enabledFeatures))
|
||||||
mod.AssignModuleID(buf)
|
mod.AssignModuleID(buf)
|
||||||
@@ -561,7 +561,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(
|
|||||||
//
|
//
|
||||||
// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
|
// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
|
||||||
// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
|
// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
|
||||||
mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false)
|
mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, err, msg)
|
require.NoError(t, err, msg)
|
||||||
|
|
||||||
err = mod.Validate(s.EnabledFeatures)
|
err = mod.Validate(s.EnabledFeatures)
|
||||||
@@ -590,7 +590,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, buf []byte, msg string) {
|
func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store, ns *wasm.Namespace, buf []byte, msg string) {
|
||||||
mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false)
|
mod, err := binaryformat.DecodeModule(buf, s.EnabledFeatures, wasm.MemoryLimitPages, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
22
internal/wasm/binary/custom.go
Normal file
22
internal/wasm/binary/custom.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package binary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/internal/wasm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom.
|
||||||
|
//
|
||||||
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||||
|
func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) {
|
||||||
|
buf := make([]byte, limit)
|
||||||
|
_, err = r.Read(buf)
|
||||||
|
|
||||||
|
result = &wasm.CustomSection{
|
||||||
|
Name: name,
|
||||||
|
Data: buf,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ func DecodeModule(
|
|||||||
binary []byte,
|
binary []byte,
|
||||||
enabledFeatures api.CoreFeatures,
|
enabledFeatures api.CoreFeatures,
|
||||||
memoryLimitPages uint32,
|
memoryLimitPages uint32,
|
||||||
memoryCapacityFromMax bool,
|
memoryCapacityFromMax,
|
||||||
|
storeCustomSections bool,
|
||||||
) (*wasm.Module, error) {
|
) (*wasm.Module, error) {
|
||||||
r := bytes.NewReader(binary)
|
r := bytes.NewReader(binary)
|
||||||
|
|
||||||
@@ -66,17 +67,22 @@ func DecodeModule(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, either decode the NameSection or skip an unsupported one
|
// Now, either decode the NameSection or CustomSection
|
||||||
limit := sectionSize - nameSize
|
limit := sectionSize - nameSize
|
||||||
if name == "name" {
|
if name == "name" {
|
||||||
m.NameSection, err = decodeNameSection(r, uint64(limit))
|
m.NameSection, err = decodeNameSection(r, uint64(limit))
|
||||||
|
} else if storeCustomSections {
|
||||||
|
custom, err := decodeCustomSection(r, name, uint64(limit))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err)
|
||||||
|
}
|
||||||
|
m.CustomSections = append(m.CustomSections, custom)
|
||||||
} else {
|
} else {
|
||||||
// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
|
// 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 {
|
if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
|
return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case wasm.SectionIDType:
|
case wasm.SectionIDType:
|
||||||
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
|
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
|
||||||
case wasm.SectionIDImport:
|
case wasm.SectionIDImport:
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func TestDecodeModule(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
m, e := DecodeModule(EncodeModule(tc.input), api.CoreFeaturesV1, wasm.MemoryLimitPages, false)
|
m, e := DecodeModule(EncodeModule(tc.input), api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, e)
|
require.NoError(t, e)
|
||||||
require.Equal(t, tc.input, m)
|
require.Equal(t, tc.input, m)
|
||||||
})
|
})
|
||||||
@@ -92,11 +92,28 @@ func TestDecodeModule(t *testing.T) {
|
|||||||
wasm.SectionIDCustom, 0xf, // 15 bytes in this section
|
wasm.SectionIDCustom, 0xf, // 15 bytes in this section
|
||||||
0x04, 'm', 'e', 'm', 'e',
|
0x04, 'm', 'e', 'm', 'e',
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
|
||||||
m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false)
|
m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, e)
|
require.NoError(t, e)
|
||||||
require.Equal(t, &wasm.Module{}, m)
|
require.Equal(t, &wasm.Module{}, m)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("reads custom sections", 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, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, true)
|
||||||
|
require.NoError(t, e)
|
||||||
|
require.Equal(t, &wasm.Module{
|
||||||
|
CustomSections: []*wasm.CustomSection{
|
||||||
|
{
|
||||||
|
Name: "meme",
|
||||||
|
Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, m)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("skips custom section, but not name", func(t *testing.T) {
|
t.Run("skips custom section, but not name", func(t *testing.T) {
|
||||||
input := append(append(Magic, version...),
|
input := append(append(Magic, version...),
|
||||||
wasm.SectionIDCustom, 0xf, // 15 bytes in this section
|
wasm.SectionIDCustom, 0xf, // 15 bytes in this section
|
||||||
@@ -107,14 +124,38 @@ func TestDecodeModule(t *testing.T) {
|
|||||||
subsectionIDModuleName, 0x07, // 7 bytes in this subsection
|
subsectionIDModuleName, 0x07, // 7 bytes in this subsection
|
||||||
0x06, // the Module name simple is 6 bytes long
|
0x06, // the Module name simple is 6 bytes long
|
||||||
's', 'i', 'm', 'p', 'l', 'e')
|
's', 'i', 'm', 'p', 'l', 'e')
|
||||||
m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false)
|
m, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false)
|
||||||
require.NoError(t, e)
|
require.NoError(t, e)
|
||||||
require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m)
|
require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("read custom sections and name separately", 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, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, true)
|
||||||
|
require.NoError(t, e)
|
||||||
|
require.Equal(t, &wasm.Module{
|
||||||
|
NameSection: &wasm.NameSection{ModuleName: "simple"},
|
||||||
|
CustomSections: []*wasm.CustomSection{
|
||||||
|
{
|
||||||
|
Name: "meme",
|
||||||
|
Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, m)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("data count section disabled", func(t *testing.T) {
|
t.Run("data count section disabled", func(t *testing.T) {
|
||||||
input := append(append(Magic, version...),
|
input := append(append(Magic, version...),
|
||||||
wasm.SectionIDDataCount, 1, 0)
|
wasm.SectionIDDataCount, 1, 0)
|
||||||
_, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false)
|
_, e := DecodeModule(input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false)
|
||||||
require.EqualError(t, e, `data count section not supported as feature "bulk-memory-operations" is disabled`)
|
require.EqualError(t, e, `data count section not supported as feature "bulk-memory-operations" is disabled`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -164,7 +205,7 @@ func TestDecodeModule_Errors(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
_, e := DecodeModule(tc.input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false)
|
_, e := DecodeModule(tc.input, api.CoreFeaturesV1, wasm.MemoryLimitPages, false, false)
|
||||||
require.EqualError(t, e, tc.expectedErr)
|
require.EqualError(t, e, tc.expectedErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,16 +41,17 @@ func (m *Module) importCount(et ExternType) (res uint32) {
|
|||||||
//
|
//
|
||||||
// For example...
|
// For example...
|
||||||
// * SectionIDType returns the count of FunctionType
|
// * SectionIDType returns the count of FunctionType
|
||||||
// * SectionIDCustom returns one if the NameSection is present
|
// * SectionIDCustom returns the count of CustomSections plus one if NameSection is present
|
||||||
// * SectionIDHostFunction returns the count of HostFunctionSection
|
// * SectionIDHostFunction returns the count of HostFunctionSection
|
||||||
// * SectionIDExport returns the count of unique export names
|
// * SectionIDExport returns the count of unique export names
|
||||||
func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
|
func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
|
||||||
switch sectionID {
|
switch sectionID {
|
||||||
case SectionIDCustom:
|
case SectionIDCustom:
|
||||||
|
numCustomSections := uint32(len(m.CustomSections))
|
||||||
if m.NameSection != nil {
|
if m.NameSection != nil {
|
||||||
return 1
|
numCustomSections++
|
||||||
}
|
}
|
||||||
return 0
|
return numCustomSections
|
||||||
case SectionIDType:
|
case SectionIDType:
|
||||||
return uint32(len(m.TypeSection))
|
return uint32(len(m.TypeSection))
|
||||||
case SectionIDImport:
|
case SectionIDImport:
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ type Module struct {
|
|||||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||||
NameSection *NameSection
|
NameSection *NameSection
|
||||||
|
|
||||||
|
// CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format.
|
||||||
|
//
|
||||||
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||||
|
CustomSections []*CustomSection
|
||||||
|
|
||||||
// validatedActiveElementSegments are built on Validate when
|
// validatedActiveElementSegments are built on Validate when
|
||||||
// SectionIDElement is non-empty and all inputs are valid.
|
// SectionIDElement is non-empty and all inputs are valid.
|
||||||
//
|
//
|
||||||
@@ -877,6 +882,12 @@ type NameSection struct {
|
|||||||
LocalNames IndirectNameMap
|
LocalNames IndirectNameMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomSection contains the name and raw data of a custom section.
|
||||||
|
type CustomSection struct {
|
||||||
|
Name string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
// NameMap associates an index with any associated names.
|
// NameMap associates an index with any associated names.
|
||||||
//
|
//
|
||||||
// Note: Often the index namespace bridges multiple sections. For example, the function index namespace starts with any
|
// Note: Often the index namespace bridges multiple sections. For example, the function index namespace starts with any
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledMod
|
|||||||
return nil, errors.New("invalid binary")
|
return nil, errors.New("invalid binary")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, r.memoryLimitPages, r.memoryCapacityFromMax)
|
internal, err := binaryformat.DecodeModule(binary, r.enabledFeatures, r.memoryLimitPages, r.memoryCapacityFromMax, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err = internal.Validate(r.enabledFeatures); err != nil {
|
} else if err = internal.Validate(r.enabledFeatures); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user