From 0f5244b856faeb28e7ecae78f3bb5036f7c7c954 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 22 Oct 2026 19:09:38 -0500 Subject: [PATCH] CABI: check ptr bounds for zero-length reads/writes Resolves #750, #753 --- design/mvp/CanonicalABI.md | 49 ++++---- design/mvp/Explainer.md | 3 +- design/mvp/canonical-abi/definitions.py | 33 +++--- design/mvp/canonical-abi/run_tests.py | 8 +- test/async/same-component-stream-future.wast | 115 +++++++++++++------ test/async/zero-length.wast | 88 +++++++++++++- test/nyi.txt | 1 + 7 files changed, 220 insertions(+), 77 deletions(-) diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index c2c5887a0..544ba0b28 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -1551,7 +1551,7 @@ class GuestBuffer(Buffer): def __init__(self, t, cx, ptr, length): trap_if(length > Buffer.MAX_LENGTH) - if t and length > 0: + if t is not None: trap_if(ptr != align_to(ptr, alignment(t, cx.opts.memory.ptr_type()))) trap_if(ptr + length * elem_size(t, cx.opts.memory.ptr_type()) > len(cx.opts.memory)) self.cx = cx @@ -1584,11 +1584,13 @@ class WritableGuestBuffer(GuestBuffer, WritableBuffer): assert(all(v == () for v in vs)) self.progress += len(vs) ``` -When `t` is `None` (arising from `stream` and `future` with empty element -types), the core-wasm-supplied `ptr` is entirely ignored, while the `length` -and `progress` are still semantically meaningful. Source bindings may represent -this case with a generic stream/future of [unit] type or a distinct type that -conveys events without values. +As with `load_list_from_range` below, the alignment and range checks are +performed even when `length` is `0`. When `t` is `None` (arising from `stream` +and `future` with empty element types), the core-wasm-supplied `ptr` is +entirely ignored, while the `length` and `progress` are still semantically +meaningful. Source bindings may represent this case with a generic +stream/future of [unit] type or a distinct type that conveys events without +values. The `load_list_from_valid_range` and `store_list_into_valid_range` functions that do all the heavy lifting are shared with function parameter/result lifting @@ -1691,30 +1693,33 @@ the code below: assert(self.has_pending_event()) elif self.other.buffer is None: self.buffer = buffer - elif buffer.remain() > 0 and self.other.buffer.remain() > 0: + else: trap_if(self.owner and self.owner is self.other.owner and not none_or_number_type(self.t)) - n = min(buffer.remain(), self.other.buffer.remain()) - if is_read: - buffer.write(self.other.buffer.read(n)) - else: - self.other.buffer.write(buffer.read(n)) - self.notify(buffer.progress) - self.other.notify(self.other.buffer.progress) - if self.other.buffer.remain() == 0: + if buffer.remain() > 0 and self.other.buffer.remain() > 0: + n = min(buffer.remain(), self.other.buffer.remain()) + if is_read: + buffer.write(self.other.buffer.read(n)) + else: + self.other.buffer.write(buffer.read(n)) + self.notify(buffer.progress) + self.other.notify(self.other.buffer.progress) + if self.other.buffer.remain() == 0: + self.other.buffer = None + elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0): + self.other.notify(progress = 0) self.other.buffer = None - elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0): - self.other.notify(progress = 0) - self.other.buffer = None - self.buffer = buffer - else: - self.notify(progress = 0) + self.buffer = buffer + else: + self.notify(progress = 0) ``` As a temporary measure (until [lazy lowering] obviates the problem), there is also a trap when both the `read` and `write` come from the same component instance and there is a non-empty, non-number element type. The reason for this trap is that when lifting and lowering can alias the same memory, the eager interleaving semantics of copying compound values would otherwise be complex to -precisely specify and implement. +precisely specify and implement. The trap is checked whenever both ends have +provided a buffer (cases 3, 4 and 5), even if no elements end up being copied, +so that whether a trap occurs doesn't depend on the buffer lengths. The `End.cancel` method is called by `{stream,future}.cancel-{read,write}` to transition an end from `COPYING` (set by `End.copy` above) to `CANCELLING_COPY`. diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 1e4227627..885d61f7c 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -2006,7 +2006,8 @@ enum copy-result { The `stream.read` and `stream.write` built-ins take the matching [readable or writable end] of a stream as the first parameter and, if `T` is present, a buffer for the `T` values to be read from or written to. If `T` is not present, -the buffer parameter is ignored. +the buffer parameter is ignored. Otherwise, the buffer's alignment and bounds +are checked even when its length is `0`. If the return value is a `stream-result`, then the `progress` field indicates how many `T` elements were read or written from the given buffer before the diff --git a/design/mvp/canonical-abi/definitions.py b/design/mvp/canonical-abi/definitions.py index 38046e5a0..6db48c4e2 100644 --- a/design/mvp/canonical-abi/definitions.py +++ b/design/mvp/canonical-abi/definitions.py @@ -895,7 +895,7 @@ class GuestBuffer(Buffer): def __init__(self, t, cx, ptr, length): trap_if(length > Buffer.MAX_LENGTH) - if t and length > 0: + if t is not None: trap_if(ptr != align_to(ptr, alignment(t, cx.opts.memory.ptr_type()))) trap_if(ptr + length * elem_size(t, cx.opts.memory.ptr_type()) > len(cx.opts.memory)) self.cx = cx @@ -962,23 +962,24 @@ def copy(self, buffer: Buffer, is_read: bool): assert(self.has_pending_event()) elif self.other.buffer is None: self.buffer = buffer - elif buffer.remain() > 0 and self.other.buffer.remain() > 0: + else: trap_if(self.owner and self.owner is self.other.owner and not none_or_number_type(self.t)) - n = min(buffer.remain(), self.other.buffer.remain()) - if is_read: - buffer.write(self.other.buffer.read(n)) - else: - self.other.buffer.write(buffer.read(n)) - self.notify(buffer.progress) - self.other.notify(self.other.buffer.progress) - if self.other.buffer.remain() == 0: + if buffer.remain() > 0 and self.other.buffer.remain() > 0: + n = min(buffer.remain(), self.other.buffer.remain()) + if is_read: + buffer.write(self.other.buffer.read(n)) + else: + self.other.buffer.write(buffer.read(n)) + self.notify(buffer.progress) + self.other.notify(self.other.buffer.progress) + if self.other.buffer.remain() == 0: + self.other.buffer = None + elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0): + self.other.notify(progress = 0) self.other.buffer = None - elif buffer.remain() > 0 or (is_read and self.other.buffer.remain() == 0): - self.other.notify(progress = 0) - self.other.buffer = None - self.buffer = buffer - else: - self.notify(progress = 0) + self.buffer = buffer + else: + self.notify(progress = 0) def cancel(self): assert(self.state == End.State.COPYING) diff --git a/design/mvp/canonical-abi/run_tests.py b/design/mvp/canonical-abi/run_tests.py index d007eba89..04e21bd74 100644 --- a/design/mvp/canonical-abi/run_tests.py +++ b/design/mvp/canonical-abi/run_tests.py @@ -1978,7 +1978,7 @@ def core_func1(args): result,n = unpack_result(mem1[retp+4]) assert(n == 4 and result == CopyResult.COMPLETED) - [ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 12345, 0) + [ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 0, 0) assert(ret == definitions.BLOCKED) fut4.set() @@ -1988,7 +1988,7 @@ def core_func1(args): assert(mem1[retp+0] == wsi) assert(mem1[retp+4] == 0) - [ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 12345, 0) + [ret] = canon_stream_write(StreamType(U8Type()), opts1, wsi, 0, 0) assert(ret == 0) [errctxi] = canon_error_context_new(opts1, 0, 0) @@ -2032,7 +2032,7 @@ def core_func2(args): fut2.set() thread.wait_until(fut3.is_set) - [ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 12345, 0) + [ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 0, 0) assert(ret == 0) mem2[0:8] = bytes(8) @@ -2047,7 +2047,7 @@ def core_func2(args): thread.wait_until(fut4.is_set) - [ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 12345, 0) + [ret] = canon_stream_read(StreamType(U8Type()), opts2, rsi, 0, 0) assert(ret == definitions.BLOCKED) [event] = canon_waitable_set_wait(MemInst(mem2, 'i32'), seti, retp) diff --git a/test/async/same-component-stream-future.wast b/test/async/same-component-stream-future.wast index 8085be4bd..765f1dca3 100644 --- a/test/async/same-component-stream-future.wast +++ b/test/async/same-component-stream-future.wast @@ -16,15 +16,18 @@ (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 "" "future.newb" (func $future.newb (result i64))) - (import "" "future.readb" (func $future.readb (param i32 i32) (result i32))) - (import "" "future.writeb" (func $future.writeb (param i32 i32) (result i32))) - (import "" "stream.newb" (func $stream.newb (result i64))) - (import "" "stream.readb" (func $stream.readb (param i32 i32 i32) (result i32))) - (import "" "stream.writeb" (func $stream.writeb (param i32 i32 i32) (result i32))) + (import "" "future.newu8" (func $future.newu8 (result i64))) + (import "" "future.readu8" (func $future.readu8 (param i32 i32) (result i32))) + (import "" "future.writeu8" (func $future.writeu8 (param i32 i32) (result i32))) + (import "" "stream.newu8" (func $stream.newu8 (result i64))) + (import "" "stream.readu8" (func $stream.readu8 (param i32 i32 i32) (result i32))) + (import "" "stream.writeu8" (func $stream.writeu8 (param i32 i32 i32) (result i32))) (import "" "future.newc" (func $future.newc (result i64))) (import "" "future.readc" (func $future.readc (param i32 i32) (result i32))) (import "" "future.writec" (func $future.writec (param i32 i32) (result i32))) + (import "" "stream.newb" (func $stream.newb (result i64))) + (import "" "stream.readb" (func $stream.readb (param i32 i32 i32) (result i32))) + (import "" "stream.writeb" (func $stream.writeb (param i32 i32 i32) (result i32))) (func (export "test-empty") (result i32) (local $ret i32) (local $ret64 i64) @@ -84,13 +87,13 @@ ;; test stream reader then writer (i64.store (local.get $dstp) (i64.const 0)) (i64.store (local.get $srcp) (i64.const 0x0123456789abcdef)) - (local.set $ret64 (call $stream.newb)) + (local.set $ret64 (call $stream.newu8)) (local.set $rx (i32.wrap_i64 (local.get $ret64))) (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) - (local.set $ret (call $stream.readb (local.get $rx) (local.get $dstp) (i32.const 8))) + (local.set $ret (call $stream.readu8 (local.get $rx) (local.get $dstp) (i32.const 8))) (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) (then unreachable)) - (local.set $ret (call $stream.writeb (local.get $tx) (local.get $srcp) (i32.const 8))) + (local.set $ret (call $stream.writeu8 (local.get $tx) (local.get $srcp) (i32.const 8))) (if (i32.ne (i32.const 0x80 (; COMPLETED=0 | (8<<4) ;)) (local.get $ret)) (then unreachable)) (if (i64.ne (i64.load (local.get $dstp)) (i64.const 0x0123456789abcdef)) @@ -99,13 +102,13 @@ ;; test stream writer than reader (i64.store (local.get $dstp) (i64.const 0)) (i64.store (local.get $srcp) (i64.const 0x0123456789abcdef)) - (local.set $ret64 (call $stream.newb)) + (local.set $ret64 (call $stream.newu8)) (local.set $rx (i32.wrap_i64 (local.get $ret64))) (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) - (local.set $ret (call $stream.writeb (local.get $tx) (local.get $srcp) (i32.const 8))) + (local.set $ret (call $stream.writeu8 (local.get $tx) (local.get $srcp) (i32.const 8))) (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) (then unreachable)) - (local.set $ret (call $stream.readb (local.get $rx) (local.get $dstp) (i32.const 8))) + (local.set $ret (call $stream.readu8 (local.get $rx) (local.get $dstp) (i32.const 8))) (if (i32.ne (i32.const 0x80 (; COMPLETED=0 | (8<<4) ;)) (local.get $ret)) (then unreachable)) (if (i64.ne (i64.load (local.get $dstp)) (i64.const 0x0123456789abcdef)) @@ -125,13 +128,13 @@ (i32.store8 (local.get $srcp) (i32.const 42)) ;; test future reader then writer - (local.set $ret64 (call $future.newb)) + (local.set $ret64 (call $future.newu8)) (local.set $rx (i32.wrap_i64 (local.get $ret64))) (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) - (local.set $ret (call $future.readb (local.get $rx) (local.get $dstp))) + (local.set $ret (call $future.readu8 (local.get $rx) (local.get $dstp))) (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) (then unreachable)) - (local.set $ret (call $future.writeb (local.get $tx) (local.get $srcp))) + (local.set $ret (call $future.writeu8 (local.get $tx) (local.get $srcp))) (if (i32.ne (i32.const 0 (; COMPLETED ;)) (local.get $ret)) (then unreachable)) (if (i32.ne (i32.load8_u (local.get $dstp)) (i32.const 42)) @@ -140,13 +143,13 @@ ;; reset memory and then test future writer than reader (i32.store8 (local.get $dstp) (i32.const 0)) (i32.store8 (local.get $srcp) (i32.const 42)) - (local.set $ret64 (call $future.newb)) + (local.set $ret64 (call $future.newu8)) (local.set $rx (i32.wrap_i64 (local.get $ret64))) (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) - (local.set $ret (call $future.writeb (local.get $tx) (local.get $srcp))) + (local.set $ret (call $future.writeu8 (local.get $tx) (local.get $srcp))) (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) (then unreachable)) - (local.set $ret (call $future.readb (local.get $rx) (local.get $dstp))) + (local.set $ret (call $future.readu8 (local.get $rx) (local.get $dstp))) (if (i32.ne (i32.const 0 (; COMPLETED ;)) (local.get $ret)) (then unreachable)) (if (i32.ne (i32.load8_u (local.get $dstp)) (i32.const 42)) @@ -199,12 +202,36 @@ (call $future.readc (local.get $rx) (i32.const 0)) unreachable ) + + ;; Once both ends have provided a buffer, the trap happens even if one or + ;; both of the buffers are zero-length. + (func (export "test-no-copy-bool") (param $write-first i32) (param $write-len i32) (param $read-len i32) + (local $ret i32) (local $ret64 i64) + (local $rx i32) (local $tx i32) + (local.set $ret64 (call $stream.newb)) + (local.set $rx (i32.wrap_i64 (local.get $ret64))) + (local.set $tx (i32.wrap_i64 (i64.shr_u (local.get $ret64) (i64.const 32)))) + (if (local.get $write-first) + (then + (local.set $ret (call $stream.writeb (local.get $tx) (i32.const 0) (local.get $write-len))) + (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) + (then unreachable)) + (call $stream.readb (local.get $rx) (i32.const 0) (local.get $read-len)) + unreachable) + (else + (local.set $ret (call $stream.readb (local.get $rx) (i32.const 0) (local.get $read-len))) + (if (i32.ne (i32.const -1 (; BLOCKED ;)) (local.get $ret)) + (then unreachable)) + (call $stream.writeb (local.get $tx) (i32.const 0) (local.get $write-len)) + unreachable)) + ) ) (type $FT (future)) (type $ST (stream)) - (type $FTB (future u8)) - (type $STB (stream u8)) + (type $FTU8 (future u8)) + (type $STU8 (stream u8)) (type $FTC (future char)) + (type $STB (stream bool)) (canon waitable.join (core func $waitable.join)) (canon waitable-set.wait (memory (core memory $memory "mem")) (core func $waitable-set.wait)) (canon waitable-set.new (core func $waitable-set.new)) @@ -214,15 +241,18 @@ (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)) - (canon future.new $FTB (core func $future.newb)) - (canon future.read $FTB async (memory (core memory $memory "mem")) (core func $future.readb)) - (canon future.write $FTB async (memory (core memory $memory "mem")) (core func $future.writeb)) - (canon stream.new $STB (core func $stream.newb)) - (canon stream.read $STB async (memory (core memory $memory "mem")) (core func $stream.readb)) - (canon stream.write $STB async (memory (core memory $memory "mem")) (core func $stream.writeb)) + (canon future.new $FTU8 (core func $future.newu8)) + (canon future.read $FTU8 async (memory (core memory $memory "mem")) (core func $future.readu8)) + (canon future.write $FTU8 async (memory (core memory $memory "mem")) (core func $future.writeu8)) + (canon stream.new $STU8 (core func $stream.newu8)) + (canon stream.read $STU8 async (memory (core memory $memory "mem")) (core func $stream.readu8)) + (canon stream.write $STU8 async (memory (core memory $memory "mem")) (core func $stream.writeu8)) (canon future.new $FTC (core func $future.newc)) (canon future.read $FTC async (memory (core memory $memory "mem")) (core func $future.readc)) (canon future.write $FTC async (memory (core memory $memory "mem")) (core func $future.writec)) + (canon stream.new $STB (core func $stream.newb)) + (canon stream.read $STB async (memory (core memory $memory "mem")) (core func $stream.readb)) + (canon stream.write $STB async (memory (core memory $memory "mem")) (core func $stream.writeb)) (core instance $m (instantiate $M (with "" (instance (export "mem" (memory $memory "mem")) (export "waitable.join" (func $waitable.join)) @@ -234,20 +264,25 @@ (export "stream.new" (func $stream.new)) (export "stream.read" (func $stream.read)) (export "stream.write" (func $stream.write)) - (export "future.newb" (func $future.newb)) - (export "future.readb" (func $future.readb)) - (export "future.writeb" (func $future.writeb)) - (export "stream.newb" (func $stream.newb)) - (export "stream.readb" (func $stream.readb)) - (export "stream.writeb" (func $stream.writeb)) + (export "future.newu8" (func $future.newu8)) + (export "future.readu8" (func $future.readu8)) + (export "future.writeu8" (func $future.writeu8)) + (export "stream.newu8" (func $stream.newu8)) + (export "stream.readu8" (func $stream.readu8)) + (export "stream.writeu8" (func $stream.writeu8)) (export "future.newc" (func $future.newc)) (export "future.readc" (func $future.readc)) (export "future.writec" (func $future.writec)) + (export "stream.newb" (func $stream.newb)) + (export "stream.readb" (func $stream.readb)) + (export "stream.writeb" (func $stream.writeb)) )))) (func (export "test-empty") (result u32) (canon lift (core func $m "test-empty"))) (func (export "test-bytes") (result u32) (canon lift (core func $m "test-bytes"))) (func (export "test-no-read-char") (canon lift (core func $m "test-no-read-char"))) (func (export "test-no-write-char") (canon lift (core func $m "test-no-write-char"))) + (func (export "test-no-copy-bool") (param "write-first" bool) (param "write-len" u32) (param "read-len" u32) + (canon lift (core func $m "test-no-copy-bool"))) ) (component instance $i $Tester) (assert_return (invoke "test-empty") (u32.const 42)) @@ -257,3 +292,19 @@ (assert_trap (invoke "test-no-read-char") "cannot read from and write to intra-component future") (component instance $i $Tester) (assert_trap (invoke "test-no-write-char") "cannot read from and write to intra-component future") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const true) (u32.const 0) (u32.const 0)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const true) (u32.const 0) (u32.const 1)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const true) (u32.const 1) (u32.const 0)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const true) (u32.const 1) (u32.const 1)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const false) (u32.const 0) (u32.const 0)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const false) (u32.const 0) (u32.const 1)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const false) (u32.const 1) (u32.const 0)) "cannot read from and write to intra-component future/stream with non-numeric payload") +(component instance $i $Tester) +(assert_trap (invoke "test-no-copy-bool" (bool.const false) (u32.const 1) (u32.const 1)) "cannot read from and write to intra-component future/stream with non-numeric payload") diff --git a/test/async/zero-length.wast b/test/async/zero-length.wast index 0a3a3f8a7..c6a4cc2d6 100644 --- a/test/async/zero-length.wast +++ b/test/async/zero-length.wast @@ -40,7 +40,7 @@ (call $task.return (local.get $outsr)) ;; initiate a zero-length write - (local.set $ret (call $stream.write (global.get $outsw) (i32.const 0xdeadbeef) (i32.const 0))) + (local.set $ret (call $stream.write (global.get $outsw) (i32.const 0) (i32.const 0))) (if (i32.ne (i32.const -1) (local.get $ret)) (then unreachable)) @@ -136,7 +136,7 @@ ;; initiate a zero-length read which will also block (even though there is ;; a pending write, b/c the pending write is 0-length, per spec) - (local.set $ret (call $stream.read (global.get $insr) (i32.const 0xdeadbeef) (i32.const 0))) + (local.set $ret (call $stream.read (global.get $insr) (i32.const 0) (i32.const 0))) (if (i32.ne (i32.const -1) (local.get $ret)) (then unreachable)) @@ -531,3 +531,87 @@ (assert_return (invoke "zero-read-after-cancelled-read") (u32.const 42)) (component instance $i $Tester) (assert_return (invoke "zero-write-after-cancelled-write") (u32.const 42)) + +;; Even when the length is 0, the buffer passed to stream.{read,write} must be +;; aligned and in bounds (though it may point at the very end of memory), +;; unless the element type is empty, in which case the pointer is ignored. +(component definition $BufferChecks + (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))) + (import "" "stream.new-unit" (func $stream.new-unit (result i64))) + (import "" "stream.read-unit" (func $stream.read-unit (param i32 i32 i32) (result i32))) + (import "" "stream.write-unit" (func $stream.write-unit (param i32 i32 i32) (result i32))) + + (func (export "zero-read") (param $ptr i32) (result i32) + (local $rx i32) + (local.set $rx (i32.wrap_i64 (call $stream.new))) + (if (i32.ne (call $stream.read (local.get $rx) (local.get $ptr) (i32.const 0)) + (i32.const -1 (; BLOCKED ;))) + (then unreachable)) + (i32.const 42) + ) + (func (export "zero-write") (param $ptr i32) (result i32) + (local $tx i32) + (local.set $tx (i32.wrap_i64 (i64.shr_u (call $stream.new) (i64.const 32)))) + (if (i32.ne (call $stream.write (local.get $tx) (local.get $ptr) (i32.const 0)) + (i32.const -1 (; BLOCKED ;))) + (then unreachable)) + (i32.const 42) + ) + (func (export "zero-read-unit") (param $ptr i32) (result i32) + (local $rx i32) + (local.set $rx (i32.wrap_i64 (call $stream.new-unit))) + (if (i32.ne (call $stream.read-unit (local.get $rx) (local.get $ptr) (i32.const 0)) + (i32.const -1 (; BLOCKED ;))) + (then unreachable)) + (i32.const 42) + ) + (func (export "zero-write-unit") (param $ptr i32) (result i32) + (local $tx i32) + (local.set $tx (i32.wrap_i64 (i64.shr_u (call $stream.new-unit) (i64.const 32)))) + (if (i32.ne (call $stream.write-unit (local.get $tx) (local.get $ptr) (i32.const 0)) + (i32.const -1 (; BLOCKED ;))) + (then unreachable)) + (i32.const 42) + ) + ) + (type $ST (stream u32)) + (type $STU (stream)) + (canon stream.new $ST (core func $stream.new)) + (canon stream.read $ST async (memory (core memory $memory "mem")) (core func $stream.read)) + (canon stream.write $ST async (memory (core memory $memory "mem")) (core func $stream.write)) + (canon stream.new $STU (core func $stream.new-unit)) + (canon stream.read $STU async (memory (core memory $memory "mem")) (core func $stream.read-unit)) + (canon stream.write $STU async (memory (core memory $memory "mem")) (core func $stream.write-unit)) + (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)) + (export "stream.new-unit" (func $stream.new-unit)) + (export "stream.read-unit" (func $stream.read-unit)) + (export "stream.write-unit" (func $stream.write-unit)) + )))) + (func (export "zero-read") (param "ptr" u32) (result u32) (canon lift (core func $m "zero-read"))) + (func (export "zero-write") (param "ptr" u32) (result u32) (canon lift (core func $m "zero-write"))) + (func (export "zero-read-unit") (param "ptr" u32) (result u32) (canon lift (core func $m "zero-read-unit"))) + (func (export "zero-write-unit") (param "ptr" u32) (result u32) (canon lift (core func $m "zero-write-unit"))) +) +(component instance $i $BufferChecks) +(assert_return (invoke "zero-read" (u32.const 0)) (u32.const 42)) +(assert_return (invoke "zero-write" (u32.const 0)) (u32.const 42)) +(assert_return (invoke "zero-read" (u32.const 0x10000)) (u32.const 42)) +(assert_return (invoke "zero-write" (u32.const 0x10000)) (u32.const 42)) +(assert_return (invoke "zero-read-unit" (u32.const 0xdeadbeef)) (u32.const 42)) +(assert_return (invoke "zero-write-unit" (u32.const 0xdeadbeef)) (u32.const 42)) +(component instance $i $BufferChecks) +(assert_trap (invoke "zero-read" (u32.const 2)) "buffer pointer not aligned") +(component instance $i $BufferChecks) +(assert_trap (invoke "zero-write" (u32.const 2)) "buffer pointer not aligned") +(component instance $i $BufferChecks) +(assert_trap (invoke "zero-read" (u32.const 0x10004)) "buffer pointer out of bounds of memory") +(component instance $i $BufferChecks) +(assert_trap (invoke "zero-write" (u32.const 0x10004)) "buffer pointer out of bounds of memory") diff --git a/test/nyi.txt b/test/nyi.txt index d9493c853..b9fe6dc7a 100644 --- a/test/nyi.txt +++ b/test/nyi.txt @@ -3,3 +3,4 @@ ./values/post-return.wast ./async/big-interleaving-test.wast ./async/forward.wast +./async/zero-length.wast