Stream: git-wasmtime

Topic: wasmtime / issue #13023 Futures/streams don't fully valid...


view this post on Zulip Wasmtime GitHub notifications bot (Apr 09 2026 at 20:30):

alexcrichton opened issue #13023:

The following tests all fail in Wasmtime with debug assertions enabled, and they shouldn't. These should probably return a first-class trap of some kind or have some more validation earlier on. Note that these test cases are all generated and likely want edits before committing.

<details>

<summary>test 1</summary>

;;! component_model_async = true
;;! multi_memory = true

(component
  (core module $libc
     (memory (export "m") 1)
  )
  (core instance $libc (instantiate $libc))

  (type $s (stream))
  (core func $stream.new (canon stream.new $s))
  (core func $stream.read (canon stream.read $s async (memory $libc "m")))
  (core func $stream.write (canon stream.write $s async (memory $libc "m")))

  (core module $m
    (import "" "m" (memory 1))
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run")
      (local $tmp i64)
      (local $r i32)
      (local $w i32)
      (local.set $tmp (call $stream.new))
      (local.set $r (i32.wrap_i64 (local.get $tmp)))
      (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32))))

      ;; reader requests a large number of zero-sized items
      (call $stream.read (local.get $r) (i32.const 0) (i32.const 0x20000000))
      i32.const -1 ;; BLOCKED
      i32.ne
      if unreachable end

      ;; writer writes the same large number - triggers encode overflow
      (call $stream.write (local.get $w) (i32.const 0) (i32.const 0x20000000))
      drop
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "m" (memory $libc "m"))
      (export "stream.new" (func $stream.new))
      (export "stream.read" (func $stream.read))
      (export "stream.write" (func $stream.write))
    ))
  ))

  (func (export "run") (canon lift (core func $i "run")))
)

(assert_return (invoke "run"))

</details>

<details>

<summary>test 2</summary>

;;! component_model_async = true
;;! reference_types = true
;;! multi_memory = true
;;! gc_types = true

;; Vulnerability: ReturnCode::encode overflow via event delivery path.
;;
;; This demonstrates the same root cause as vuln1 but through the waitable_set_wait
;; event delivery code path. A zero-payload stream read with count >= 2^28 causes
;; the event's ReturnCode to overflow when encoded in Event::parts() during
;; waitable_set_wait, crashing the host.
;;
;; In this test:
;; - Component $C exports an async function that reads from a zero-payload stream
;;   with count = 0x10000000 (exactly 2^28), then waits for the result event
;; - Component $D calls $C and writes 0x10000000 items to the stream
;; - When $C receives the event through waitable_set_wait, Event::parts() calls
;;   ReturnCode::encode() with n=0x10000000, triggering the debug_assert panic
;;
;; In debug builds: host process crashes with "assertion failed: *n < (1 << 28)"
;; In release builds: the count is silently truncated, corrupting the event payload

(component
  (component $C
    (core module $Memory (memory (export "mem") 1))
    (core instance $memory (instantiate $Memory))
    (core module $CM
      (import "" "mem" (memory 1))
      (import "" "task.return" (func $task.return))
      (import "" "waitable.join" (func $waitable.join (param i32 i32)))
      (import "" "waitable-set.new" (func $waitable-set.new (result i32)))
      (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
      (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
      (import "" "stream.drop-readable" (func $stream.drop-readable (param i32)))

      (global $ws (mut i32) (i32.const 0))
      (global $insr (mut i32) (i32.const 0))

      (func $start (global.set $ws (call $waitable-set.new)))
      (start $start)

      (func $transform (export "transform") (param $readable i32) (result i32)
        (local $ret i32)
        (global.set $insr (local.get $readable))

        ;; Read 0x10000000 (2^28) items from a zero-payload stream — should BLOCK
        (local.set $ret (call $stream.read (global.get $insr) (i32.const 0) (i32.const 0x10000000)))
        (if (i32.ne (local.get $ret) (i32.const -1)) (then unreachable))

        ;; Return nothing, then wait for event via callback
        (call $task.return)
        (call $waitable.join (global.get $insr) (global.get $ws))
        (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))
      )

      (func $transform_cb (export "transform_cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
        ;; If we get here, the event was delivered without crashing.
        ;; In debug builds, the host crashes before reaching this point.
        ;; $event_code should be 2 (STREAM_READ)
        ;; $payload should contain the encoded ReturnCode, but with 2^28 count
        ;; it overflows: (0x10000000 << 4) | 0 = 0 (truncated)
        (call $stream.drop-readable (global.get $insr))
        (i32.const 0 (; EXIT ;))
      )
    )

    (type $ST (stream))
    (canon task.return (memory $memory "mem") (core func $task.return))
    (canon waitable.join (core func $waitable.join))
    (canon waitable-set.new (core func $waitable-set.new))
    (canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
    (canon stream.read $ST async (memory $memory "mem") (core func $stream.read))
    (canon stream.drop-readable $ST (core func $stream.drop-readable))

    (core instance $cm (instantiate $CM (with "" (instance
      (export "mem" (memory $memory "mem"))
      (export "task.return" (func $task.return))
      (export "waitable.join" (func $waitable.join))
      (export "waitable-set.new" (func $waitable-set.new))
      (export "waitable-set.wait" (func $waitable-set.wait))
      (export "stream.read" (func $stream.read))
      (export "stream.drop-readable" (func $stream.drop-readable))
    ))))

    (func (export "transform") (param "in" (stream)) (canon lift
      (core func $cm "transform")
      async (memory $memory "mem") (callback (func $cm "transform_cb"))
    ))
  )

  (component $D
    (import "transform" (func $transform (param "in" (stream))))

    (core module $Memory (memory (export "mem") 1))
    (core instance $memory (instantiate $Memory))
    (core module $DM
      (import "" "mem" (memory 1))
      (import "" "stream.new" (func $stream.new (result i64)))
      (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))
      (import "" "stream.drop-writable" (func $stream.drop-writable (param i32)))
      (import "" "transform" (func $transform (param i32) (result i32)))

      (func $run (export "run")
        (local $ret i32) (local $ret64 i64)
        (local $sr i32) (local $sw i32)

        ;; Create a zero-payload stream
        (local.set $ret64 (call $stream.new))
        (local.set $sr (i32.wrap_i64 (local.get $ret64)))
        (local.set $sw (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32))))

        ;; Call transform, passing the readable end
        ;; transform returns RETURNED status immediately
        (local.set $ret (call $transform (local.get $sr)))

        ;; Write 0x10000000 items — this rendezvous with the reader
        ;; This causes the reader's event to contain count = 0x10000000
        ;; When waitable_set_wait delivers the event, Event::parts() will
        ;; call ReturnCode::encode() and hit the overflow
        (local.set $ret (call $stream.write (local.get $sw) (i32.const 0) (i32.const 0x10000000)))

        ;; Clean up
        (call $stream.drop-writable (local.get $sw))
      )
    )

    (type $ST (stream))
    (canon stream.new $ST (core func $stream.new))
    (canon stream.write $ST async (memory $memory "mem") (core func $stream.write))
    (canon stream.drop-writable $ST (core func $stream.drop-writable))
    (canon lower (func $transform) async (memory $memory "mem") (core func $transform'))

    (core instance $dm (instantiate $DM (with "" (instance
      (export "mem" (memory $memory "mem"))
      (export "stream.new" (func $stream.new))
      (export "stream.write" (func $stream.write))
      (export "stream.drop-writable" (func $stream.drop-writable))
      (export "transform" (func $transform'))
    ))))

    (func (export "run") (canon lift (core func $dm "run")))
  )

  (instance $c (instantiate $C))
  (instance $d (instantiate $D (with "transform" (func $c "transform"))))
  (func (export "run") (alias export $d "run"))
)
(assert_return (invoke "run"))

</details>

<details>

<summary>test 3</summary>

;;! component_model_async = true

;; Vulnerability: ReturnCode::encode overflow via stream.cancel-write
;;
;; Attack: Create a zero-payload intra-component stream. Start a large write
;; that blocks. Read small chunks to accumulate the writer's completion event
;; past 2^28 items. Then cancel the write — the cancel path takes the
;; accumulated Completed(n) event and converts it to Cancelled(n).
;; When n >= 2^28, encode() triggers debug_assert panic, crashing the host.
;;
;; The distinct trigger path is: guest_cancel_write → cancel_write → encode()
;; at futures_and_streams.rs line 4273.

(component definition $C
  (core module $libc (memory (export "mem") 1))
  (core instance $libc (instantiate $libc))

  (core module $m
    (import "" "mem" (memory 1))
    (import "" "stream.new" (func $stream_new (result i64)))
    (import "" "stream.read" (func $stream_read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream_write (param i32 i32 i32) (result i32)))
    (import "" "stream.cancel-write" (func $stream_cancel_write (param i32) (result i32)))
    (import "" "stream.drop-readable" (func $stream_drop_readable (param i32)))
    (import "
[message truncated]

view this post on Zulip Wasmtime GitHub notifications bot (Apr 09 2026 at 20:30):

alexcrichton added the wasm-proposal:component-model-async label to Issue #13023.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 13 2026 at 21:28):

alexcrichton commented on issue #13023:

Another test (probably a duplicate)

<details>

;;! component_model_async = true
;;! reference_types = true

;; Bug: ReturnCode::encode() uses debug_assert! for the count < 2^28 bound.
;; The Component Model spec's pack_copy_result requires count < (1 << 28),
;; but wasmtime only checks this in debug builds. In release builds, the
;; unchecked (count << 4) | tag silently overflows u32, producing wrong
;; return codes.
;;
;; This test uses a payloadless stream (items have size 0, no memory is
;; copied) so that large counts can be tested without large allocations.
;;
;; To reproduce the silent corruption (MUST use --release):
;;   cargo test --release -p wasmtime-cli --test wast -- stream-return-code-overflow
;;
;; In debug mode, this test hits the debug_assert panic (host panic, not
;; a clean guest trap -- which is also a bug; see report.md).
;;
;; In release mode, the assertions below PASS, meaning the test shows
;; that stream.write with count=2^28 silently returns a wrong value
;; (0x00000000 = "COMPLETED with 0 items") rather than trapping.

;; Baseline: count = 2^28 - 1 encodes correctly.
(component definition $baseline
  (core module $Memory (memory (export "mem") 1))
  (core instance $memory (instantiate $Memory))
  (core module $M
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run") (result i32)
      (local $s i64) (local $rx i32) (local $tx i32) (local $ret i32)

      ;; Create a payloadless stream
      (local.set $s (call $stream.new))
      (local.set $rx (i32.wrap_i64 (local.get $s)))
      (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $s) (i64.const 32))))

      ;; Reader reads with count = 0x0FFFFFFF (2^28 - 1, max valid count)
      (local.set $ret (call $stream.read
        (local.get $rx) (i32.const 0) (i32.const 0x0FFFFFFF)))
      (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret))
        (then unreachable))

      ;; Writer writes with count = 0x0FFFFFFF
      ;; Expected: (0x0FFFFFFF << 4) | 0 = 0xFFFFFFF0 (COMPLETED with 2^28-1)
      (local.set $ret (call $stream.write
        (local.get $tx) (i32.const 0) (i32.const 0x0FFFFFFF)))
      (if (i32.ne (i32.const 0xFFFFFFF0) (local.get $ret))
        (then unreachable))

      (i32.const 42)
    )
  )
  (type $ST (stream))
  (canon stream.new $ST (core func $stream.new))
  (canon stream.read $ST async (core func $stream.read))
  (canon stream.write $ST async (core func $stream.write))
  (core instance $m (instantiate $M (with "" (instance
    (export "stream.new" (func $stream.new))
    (export "stream.read" (func $stream.read))
    (export "stream.write" (func $stream.write))
  ))))
  (func (export "run") (result u32) (canon lift (core func $m "run")))
)
(component instance $baseline $baseline)
(assert_return (invoke "run") (u32.const 42))

;; Bug demonstration: count = 2^28 causes encode() to silently overflow.
;; In release mode, stream.write returns 0x00000000 (COMPLETED with 0)
;; instead of trapping. The guest sees 0 items transferred when 2^28
;; items were actually transferred via the payloadless stream.
(component definition $overflow
  (core module $Memory (memory (export "mem") 1))
  (core instance $memory (instantiate $Memory))
  (core module $M
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run") (result i32)
      (local $s i64) (local $rx i32) (local $tx i32) (local $ret i32)

      ;; Create a payloadless stream
      (local.set $s (call $stream.new))
      (local.set $rx (i32.wrap_i64 (local.get $s)))
      (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $s) (i64.const 32))))

      ;; Reader reads with count = 0x10000000 (2^28)
      (local.set $ret (call $stream.read
        (local.get $rx) (i32.const 0) (i32.const 0x10000000)))
      (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret))
        (then unreachable))

      ;; Writer writes with count = 0x10000000 (2^28)
      ;; BUG: Returns 0 in release mode.
      ;;   (0x10000000 << 4) | 0 = 0x100000000 overflows u32 to 0.
      ;; EXPECTED: Should trap (count unrepresentable in 28-bit field).
      (local.set $ret (call $stream.write
        (local.get $tx) (i32.const 0) (i32.const 0x10000000)))

      ;; In release mode, $ret == 0 (COMPLETED | (0 << 4)), proving the bug.
      ;; Return $ret so the test harness can observe the wrong value.
      (local.get $ret)
    )
  )
  (type $ST (stream))
  (canon stream.new $ST (core func $stream.new))
  (canon stream.read $ST async (core func $stream.read))
  (canon stream.write $ST async (core func $stream.write))
  (core instance $m (instantiate $M (with "" (instance
    (export "stream.new" (func $stream.new))
    (export "stream.read" (func $stream.read))
    (export "stream.write" (func $stream.write))
  ))))
  (func (export "run") (result u32) (canon lift (core func $m "run")))
)
(component instance $overflow $overflow)
;; BUG: In release mode this returns 0 (wrong!) instead of trapping.
;; The correct behavior would be to trap since count >= 2^28 cannot be
;; encoded in the pack_copy_result format.
;; In debug mode, the debug_assert at encode() line 77 fires as a host
;; panic before this assertion is reached.
(assert_return (invoke "run") (u32.const 0))

</details>

view this post on Zulip Wasmtime GitHub notifications bot (Apr 14 2026 at 19:51):

alexcrichton closed issue #13023:

The following tests all fail in Wasmtime with debug assertions enabled, and they shouldn't. These should probably return a first-class trap of some kind or have some more validation earlier on. Note that these test cases are all generated and likely want edits before committing.

<details>

<summary>test 1</summary>

;;! component_model_async = true
;;! multi_memory = true

(component
  (core module $libc
     (memory (export "m") 1)
  )
  (core instance $libc (instantiate $libc))

  (type $s (stream))
  (core func $stream.new (canon stream.new $s))
  (core func $stream.read (canon stream.read $s async (memory $libc "m")))
  (core func $stream.write (canon stream.write $s async (memory $libc "m")))

  (core module $m
    (import "" "m" (memory 1))
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run")
      (local $tmp i64)
      (local $r i32)
      (local $w i32)
      (local.set $tmp (call $stream.new))
      (local.set $r (i32.wrap_i64 (local.get $tmp)))
      (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32))))

      ;; reader requests a large number of zero-sized items
      (call $stream.read (local.get $r) (i32.const 0) (i32.const 0x20000000))
      i32.const -1 ;; BLOCKED
      i32.ne
      if unreachable end

      ;; writer writes the same large number - triggers encode overflow
      (call $stream.write (local.get $w) (i32.const 0) (i32.const 0x20000000))
      drop
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "m" (memory $libc "m"))
      (export "stream.new" (func $stream.new))
      (export "stream.read" (func $stream.read))
      (export "stream.write" (func $stream.write))
    ))
  ))

  (func (export "run") (canon lift (core func $i "run")))
)

(assert_return (invoke "run"))

</details>

<details>

<summary>test 2</summary>

;;! component_model_async = true
;;! reference_types = true
;;! multi_memory = true
;;! gc_types = true

;; Vulnerability: ReturnCode::encode overflow via event delivery path.
;;
;; This demonstrates the same root cause as vuln1 but through the waitable_set_wait
;; event delivery code path. A zero-payload stream read with count >= 2^28 causes
;; the event's ReturnCode to overflow when encoded in Event::parts() during
;; waitable_set_wait, crashing the host.
;;
;; In this test:
;; - Component $C exports an async function that reads from a zero-payload stream
;;   with count = 0x10000000 (exactly 2^28), then waits for the result event
;; - Component $D calls $C and writes 0x10000000 items to the stream
;; - When $C receives the event through waitable_set_wait, Event::parts() calls
;;   ReturnCode::encode() with n=0x10000000, triggering the debug_assert panic
;;
;; In debug builds: host process crashes with "assertion failed: *n < (1 << 28)"
;; In release builds: the count is silently truncated, corrupting the event payload

(component
  (component $C
    (core module $Memory (memory (export "mem") 1))
    (core instance $memory (instantiate $Memory))
    (core module $CM
      (import "" "mem" (memory 1))
      (import "" "task.return" (func $task.return))
      (import "" "waitable.join" (func $waitable.join (param i32 i32)))
      (import "" "waitable-set.new" (func $waitable-set.new (result i32)))
      (import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
      (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
      (import "" "stream.drop-readable" (func $stream.drop-readable (param i32)))

      (global $ws (mut i32) (i32.const 0))
      (global $insr (mut i32) (i32.const 0))

      (func $start (global.set $ws (call $waitable-set.new)))
      (start $start)

      (func $transform (export "transform") (param $readable i32) (result i32)
        (local $ret i32)
        (global.set $insr (local.get $readable))

        ;; Read 0x10000000 (2^28) items from a zero-payload stream — should BLOCK
        (local.set $ret (call $stream.read (global.get $insr) (i32.const 0) (i32.const 0x10000000)))
        (if (i32.ne (local.get $ret) (i32.const -1)) (then unreachable))

        ;; Return nothing, then wait for event via callback
        (call $task.return)
        (call $waitable.join (global.get $insr) (global.get $ws))
        (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))
      )

      (func $transform_cb (export "transform_cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
        ;; If we get here, the event was delivered without crashing.
        ;; In debug builds, the host crashes before reaching this point.
        ;; $event_code should be 2 (STREAM_READ)
        ;; $payload should contain the encoded ReturnCode, but with 2^28 count
        ;; it overflows: (0x10000000 << 4) | 0 = 0 (truncated)
        (call $stream.drop-readable (global.get $insr))
        (i32.const 0 (; EXIT ;))
      )
    )

    (type $ST (stream))
    (canon task.return (memory $memory "mem") (core func $task.return))
    (canon waitable.join (core func $waitable.join))
    (canon waitable-set.new (core func $waitable-set.new))
    (canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
    (canon stream.read $ST async (memory $memory "mem") (core func $stream.read))
    (canon stream.drop-readable $ST (core func $stream.drop-readable))

    (core instance $cm (instantiate $CM (with "" (instance
      (export "mem" (memory $memory "mem"))
      (export "task.return" (func $task.return))
      (export "waitable.join" (func $waitable.join))
      (export "waitable-set.new" (func $waitable-set.new))
      (export "waitable-set.wait" (func $waitable-set.wait))
      (export "stream.read" (func $stream.read))
      (export "stream.drop-readable" (func $stream.drop-readable))
    ))))

    (func (export "transform") (param "in" (stream)) (canon lift
      (core func $cm "transform")
      async (memory $memory "mem") (callback (func $cm "transform_cb"))
    ))
  )

  (component $D
    (import "transform" (func $transform (param "in" (stream))))

    (core module $Memory (memory (export "mem") 1))
    (core instance $memory (instantiate $Memory))
    (core module $DM
      (import "" "mem" (memory 1))
      (import "" "stream.new" (func $stream.new (result i64)))
      (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))
      (import "" "stream.drop-writable" (func $stream.drop-writable (param i32)))
      (import "" "transform" (func $transform (param i32) (result i32)))

      (func $run (export "run")
        (local $ret i32) (local $ret64 i64)
        (local $sr i32) (local $sw i32)

        ;; Create a zero-payload stream
        (local.set $ret64 (call $stream.new))
        (local.set $sr (i32.wrap_i64 (local.get $ret64)))
        (local.set $sw (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32))))

        ;; Call transform, passing the readable end
        ;; transform returns RETURNED status immediately
        (local.set $ret (call $transform (local.get $sr)))

        ;; Write 0x10000000 items — this rendezvous with the reader
        ;; This causes the reader's event to contain count = 0x10000000
        ;; When waitable_set_wait delivers the event, Event::parts() will
        ;; call ReturnCode::encode() and hit the overflow
        (local.set $ret (call $stream.write (local.get $sw) (i32.const 0) (i32.const 0x10000000)))

        ;; Clean up
        (call $stream.drop-writable (local.get $sw))
      )
    )

    (type $ST (stream))
    (canon stream.new $ST (core func $stream.new))
    (canon stream.write $ST async (memory $memory "mem") (core func $stream.write))
    (canon stream.drop-writable $ST (core func $stream.drop-writable))
    (canon lower (func $transform) async (memory $memory "mem") (core func $transform'))

    (core instance $dm (instantiate $DM (with "" (instance
      (export "mem" (memory $memory "mem"))
      (export "stream.new" (func $stream.new))
      (export "stream.write" (func $stream.write))
      (export "stream.drop-writable" (func $stream.drop-writable))
      (export "transform" (func $transform'))
    ))))

    (func (export "run") (canon lift (core func $dm "run")))
  )

  (instance $c (instantiate $C))
  (instance $d (instantiate $D (with "transform" (func $c "transform"))))
  (func (export "run") (alias export $d "run"))
)
(assert_return (invoke "run"))

</details>

<details>

<summary>test 3</summary>

;;! component_model_async = true

;; Vulnerability: ReturnCode::encode overflow via stream.cancel-write
;;
;; Attack: Create a zero-payload intra-component stream. Start a large write
;; that blocks. Read small chunks to accumulate the writer's completion event
;; past 2^28 items. Then cancel the write — the cancel path takes the
;; accumulated Completed(n) event and converts it to Cancelled(n).
;; When n >= 2^28, encode() triggers debug_assert panic, crashing the host.
;;
;; The distinct trigger path is: guest_cancel_write → cancel_write → encode()
;; at futures_and_streams.rs line 4273.

(component definition $C
  (core module $libc (memory (export "mem") 1))
  (core instance $libc (instantiate $libc))

  (core module $m
    (import "" "mem" (memory 1))
    (import "" "stream.new" (func $stream_new (result i64)))
    (import "" "stream.read" (func $stream_read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream_write (param i32 i32 i32) (result i32)))
    (import "" "stream.cancel-write" (func $stream_cancel_write (param i32) (result i32)))
    (import "" "stream.drop-readable" (func $stream_drop_readable (param i32)))
    (import "
[message truncated]


Last updated: May 03 2026 at 21:15 UTC