Skip to content

Reject async canonical lowers targeting synchronous aliased functions #2

Description

@JairusSW

Problem

validateAsyncCanonOptsAgreeWithTypes validates canon lower ... async only when the function index resolves directly to a top-level function import. When the lower targets a ComponentFuncFromAlias, validation silently skips it:

fe := comp.ComponentFuncSpace[cn.FuncIdx]
if fe.Kind != binary.ComponentFuncFromImport || int(fe.Import) >= len(comp.Imports) {
    continue
}

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:

// 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))

Impact

A component can attach the async canonical 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:

  • follow the alias to the referenced component instance export;
  • recover that export's declared FuncDesc in its defining type space;
  • reject async when FuncDesc.Async is false.

When an async lower'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 same async canonical option requires an async function type error used for direct imports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions