|
|
|
// validateAsyncCanonOptsAgreeWithTypes checks, for every canon lift (kind |
|
// 0x00) or lower (kind 0x01) in comp, that its `async` CanonOpt (kind 0x06) |
|
// implies the func type it lifts/lowers is itself declared `async func` |
|
// (binary.FuncDesc.Async) -- the spec's validate-no-async-abi-for-sync- |
|
// type.wast requirement, checked here (Instantiate, before either |
|
// instantiation path runs) since it is a pure structural property of the |
|
// component, independent of whether any host import is ever satisfied. |
|
// |
|
// One-directional: only "async option present, type not async" is rejected. |
|
// The reverse -- an async-typed func lifted/lowered with NO async option at |
|
// all (the "stackful" sync-opts shape every stackful conformance suite |
|
// exercises, e.g. an async-typed export whose canon lift has no async/ |
|
// callback option) -- stays legal and is not touched by this check. |
|
// |
|
// A lower's func type is resolvable only when canon.FuncIdx names a |
|
// top-level func IMPORT (ComponentFuncFromImport) -- a func ALIAS |
|
// (ComponentFuncFromAlias, e.g. `canon lower (func $sibling "x") async`) |
|
// targets another component instance's export, whose declared type isn't |
|
// part of THIS component's own TypeSpace/Imports and so can't be resolved |
|
// here; no vendored suite exercises an async-mismatched alias lower, so |
|
// that shape is silently skipped (not rejected, not asserted valid) rather |
|
// than guessed at. |
|
func validateAsyncCanonOptsAgreeWithTypes(comp *binary.Component) error { |
|
for i, cn := range comp.Canons { |
|
if cn.Kind != 0x00 && cn.Kind != 0x01 { |
|
continue |
|
} |
|
isAsync := false |
|
for _, opt := range cn.Opts { |
|
if opt.Kind == 0x06 { |
|
isAsync = true |
|
break |
|
} |
|
} |
|
if !isAsync { |
|
continue |
|
} |
|
|
|
var fd binary.FuncDesc |
|
var resolved bool |
|
switch cn.Kind { |
|
case 0x00: // lift: canon.TypeIdx names the func type directly |
|
if td, err := comp.ResolveType(cn.TypeIdx); err == nil { |
|
fd, resolved = td.(binary.FuncDesc) |
|
} |
|
case 0x01: // lower: canon.FuncIdx names a component-func-space entry |
|
if int(cn.FuncIdx) >= len(comp.ComponentFuncSpace) { |
|
continue |
|
} |
|
fe := comp.ComponentFuncSpace[cn.FuncIdx] |
|
if fe.Kind != binary.ComponentFuncFromImport || int(fe.Import) >= len(comp.Imports) { |
|
continue |
|
} |
|
im := comp.Imports[fe.Import] |
|
if im.ExternType != 0x01 { // func |
|
continue |
|
} |
|
if td, err := comp.ResolveType(im.ExternIndex); err == nil { |
|
fd, resolved = td.(binary.FuncDesc) |
|
} |
|
} |
|
if resolved && !fd.Async { |
|
return fmt.Errorf("component/instance: canon[%d]: the `async` canonical option requires an async function type", i) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// validateCanons checks that every canon in a no-import component is a |
|
// supported "canon lift" whose core func index and type index are in range. |
|
func validateCanons(comp *binary.Component, coreFuncIdx []string) error { |
|
for i, cn := range comp.Canons { |
|
if cn.Kind != 0x00 { |
|
return fmt.Errorf("component/instance: canon[%d] has kind %#x; only canon lift (0x00) is supported for no-import components", i, cn.Kind) |
|
} |
|
if int(cn.CoreFuncIdx) >= len(coreFuncIdx) { |
|
return fmt.Errorf("component/instance: canon[%d] references core func index %d, but the core func index space only has %d entries", i, cn.CoreFuncIdx, len(coreFuncIdx)) |
Problem
validateAsyncCanonOptsAgreeWithTypesvalidatescanon lower ... asynconly when the function index resolves directly to a top-level function import. When the lower targets aComponentFuncFromAlias, validation silently skips it:The code comment explicitly identifies the unsupported shape—such as
canon lower (func $sibling "x") async—and accepts it because the vendored suite does not currently exercise it.Relevant code:
component-model/internal/instance/instance.go
Lines 1380 to 1457 in 8a61139
Impact
A component can attach the
asynccanonical option to an aliased function whose declared component function type is synchronous and pass the pre-instantiation validation. This is a malformed canonical ABI declaration; accepting it can wire async lowering/state-machine behavior against a synchronous type rather than rejecting the component deterministically.Validation should not depend on whether the current conformance corpus happens to include a particular alias route.
Suggested fix
Resolve the function type for every component-function-space source accepted by the graph engine, especially
ComponentFuncFromAlias:FuncDescin its defining type space;asyncwhenFuncDesc.Asyncis false.When an
asynclower's target type cannot be resolved, fail validation rather than silently continuing. Otherwise malformed components are treated as valid merely because type resolution is incomplete.Regression test
Add a component with a sibling/nested instance exporting a synchronous function, alias that function into the parent function index space, and apply
canon lower ... async. Instantiation should fail with the sameasync canonical option requires an async function typeerror used for direct imports.