Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
466 changes: 466 additions & 0 deletions .github/workflows/nightly.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</p>

<p>
<a href="https://github.com/wavefnd/Wave/actions/workflows/rust.yml"><img src="https://img.shields.io/github/actions/workflow/status/wavefnd/Wave/rust.yml?branch=master&style=flat-square&label=build&labelColor=17132B&color=6654F1" alt="Build status"></a>
<a href="https://github.com/wavefnd/Wave/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/wavefnd/Wave/ci.yml?branch=master&style=flat-square&label=build&labelColor=17132B&color=6654F1" alt="Build status"></a>
<a href="https://github.com/wavefnd/Wave/releases"><img src="https://img.shields.io/github/v/release/wavefnd/Wave?include_prereleases&style=flat-square&label=release&labelColor=17132B&color=6654F1" alt="Latest release"></a>
<a href="https://opencollective.com/wave-lang/contribute"><img src="https://img.shields.io/badge/sponsor-Wave-6654F1?style=flat-square&labelColor=17132B&logo=opencollective&logoColor=white" alt="Sponsor Wave on OpenCollective"></a>
</p>
Expand Down
17 changes: 6 additions & 11 deletions examples/std/net_unix.wave
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import("std::net::error")::{NET_ERROR_UNSUPPORTED, NetError, NetIoResult, NetResult};
import("std::net::error")::{NetError, NetIoResult, NetResult};
import("std::net::unix")::{
UnixListener, UnixStream,
unix_listen, unix_connect, unix_accept,
Expand All @@ -7,20 +7,15 @@ import("std::net::unix")::{
};

#[target(os="linux")]
fun main() -> i32 { return run_unix_stream(); }
fun main() -> i32 { return run_unix_stream("/tmp/wave-std-net-unix.sock"); }
#[target(os="macos")]
fun main() -> i32 { return run_unix_stream(); }
fun main() -> i32 { return run_unix_stream("/tmp/wave-std-net-unix.sock"); }
#[target(os="freebsd")]
fun main() -> i32 { return run_unix_stream(); }
fun main() -> i32 { return run_unix_stream("/tmp/wave-std-net-unix.sock"); }
#[target(os="windows")]
fun main() -> i32 {
var unavailable: NetResult<UnixListener> = unix_listen("wave-unavailable.sock", 1);
if (unavailable.ok || unavailable.error.kind != NET_ERROR_UNSUPPORTED) { return 1; }
return 0;
}
fun main() -> i32 { return run_unix_stream("wave-std-net-unix.sock"); }

fun run_unix_stream() -> i32 {
var path: str = "/tmp/wave-std-net-unix.sock";
fun run_unix_stream(path: str) -> i32 {
unix_remove(path);

var listener: NetResult<UnixListener> = unix_listen(path, 4);
Expand Down
3 changes: 2 additions & 1 deletion std/net/error.wave
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ fun classify_native_error(code: i64) -> i32 {
if (code == -10054) { return NET_ERROR_RESET; }
if (code == -10048) { return NET_ERROR_ADDRESS_IN_USE; }
if (code == -10057) { return NET_ERROR_NOT_CONNECTED; }
if (code == -95 || code == -10045) { return NET_ERROR_UNSUPPORTED; }
if (code == -95 || code == -10043 || code == -10044
|| code == -10045 || code == -10047) { return NET_ERROR_UNSUPPORTED; }
return NET_ERROR_OTHER;
}

Expand Down
12 changes: 10 additions & 2 deletions std/net/unix.wave
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import("std::sys::fs")::{unlink};
import("std::sys::fs")::{unlink};
#[target(os="freebsd")]
import("std::sys::fs")::{unlink};
#[target(os="windows")]
import("std::sys::fs")::{unlink};
import("std::net::error")::{
NetError, NetResult, NetIoResult,
net_error_none, net_error_from_native,
Expand Down Expand Up @@ -54,7 +56,7 @@ fun _unix_socket() -> i64 {
#[target(os="freebsd")]
fun _unix_socket() -> i64 { return socket(AF_LOCAL, SOCK_STREAM, 0); }
#[target(os="windows")]
fun _unix_socket() -> i64 { return -95; }
fun _unix_socket() -> i64 { return socket(AF_LOCAL, SOCK_STREAM, 0); }

fun _unix_address(path: str, address: ptr<NativeLocalAddr>) -> i32 {
return native_local_addr(path, address);
Expand Down Expand Up @@ -202,7 +204,13 @@ pub fun unix_remove(path: str) -> NetError {
return net_error_none();
}
#[target(os="windows")]
pub fun unix_remove(path: str) -> NetError { return net_error_from_native(-95); }
pub fun unix_remove(path: str) -> NetError {
// Removal is explicit, including a path left by successful bind followed
// by failed listen. Never remove an existing path automatically on bind.
var result: i64 = unlink(path);
if (result < 0) { return net_error_from_native(result); }
return net_error_none();
}

pub fun unix_stream_valid(stream: UnixStream) -> bool { return net_fd_valid(stream.fd); }
pub fun unix_listener_valid(listener: UnixListener) -> bool { return net_fd_valid(listener.fd); }
8 changes: 8 additions & 0 deletions std/sys/windows/event.wave
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import("std::sys::windows::socket")::{
POLLIN, POLLOUT, POLLERR, POLLHUP, POLLNVAL, PollFd, poll,
};
import("std::sys::windows::memory")::{sys_alloc, sys_free};
extern(system, "Sleep") fun event_sleep(milliseconds: u32);

pub const EVENT_BACKEND_KIND: i32 = 3;
pub const EVENT_READABLE: i32 = 1;
Expand Down Expand Up @@ -89,6 +90,13 @@ pub fun event_wait(
|| capacity > WINDOWS_EVENT_CAPACITY || timeout_ms < -1) { return -22; }
var descriptors: array<PollFd, 64>;
var count: i32 = deref state.count;
if (count == 0) {
// There is no concurrent registration/wakeup contract for this set.
// Sleep(INFINITE) blocks without spinning; WSAPoll rejects nfds=0.
if (timeout_ms == -1) { event_sleep(4294967295 as u32); }
if (timeout_ms > 0) { event_sleep(timeout_ms as u32); }
return 0;
}
var i: i32 = 0;
while (i < count) {
descriptors[i] = PollFd {
Expand Down
43 changes: 37 additions & 6 deletions std/sys/windows/local_addr.wave
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
// Unix-domain sockets are intentionally unavailable through this Unix API.
// Windows named pipes and AF_UNIX require separate Windows-native contracts.
// Windows 10 1803+ pathname AF_UNIX streams. The OS reports unavailable
// providers through Winsock; abstract addresses are outside this API contract.
// SOCKADDR_UN uses a u16 family followed by 108 UTF-8 bytes, including NUL.

extern(system, "MultiByteToWideChar") fun local_path_to_wide(
page: u32, flags: u32, source: ptr<u8>, bytes: i32, output: ptr<u16>, capacity: i32
) -> i32;

pub const AF_LOCAL: i32 = 1;
pub const NATIVE_LOCAL_PATH_CAPACITY: i32 = 1;
pub const NATIVE_LOCAL_PATH_CAPACITY: i32 = 108;

pub struct NativeLocalAddr {
unavailable: u8;
family: u16;
path: array<u8, 108>;
}

pub fun native_local_addr(path: str, output: ptr<NativeLocalAddr>) -> i32 {
if (path as ptr<u8> == null || output == null) { return -22; }
var length: i32 = 0;
while (path[length] != 0) {
if (length >= NATIVE_LOCAL_PATH_CAPACITY - 1) { return -36; }
length += 1;
}
if (length == 0) { return -22; }
// Validate UTF-8 without allocating or changing the caller's output.
if (local_path_to_wide(65001, 8, path as ptr<u8>, length, null, 0) == 0) {
return -84;
}
var value: NativeLocalAddr;
value.family = AF_LOCAL as u16;
var i: i32 = 0;
while (i < NATIVE_LOCAL_PATH_CAPACITY) {
value.path[i] = 0;
if (i < length) { value.path[i] = path[i]; }
i += 1;
}
deref output = value;
return length + 3;
}

pub fun native_local_addr(path: str, output: ptr<NativeLocalAddr>) -> i32 { return -95; }
pub fun native_local_abstract(name: str, output: ptr<NativeLocalAddr>) -> i32 { return -95; }
pub fun native_local_abstract(name: str, output: ptr<NativeLocalAddr>) -> i32 {
return -95;
}
41 changes: 41 additions & 0 deletions tests/boundary_regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,44 @@ fn read_to_end_rejects_capacity_overflow_and_preserves_normal_growth() {
fs::write(case.root.join("empty.bin"), []).unwrap();
case.run(&source("tests/fixtures/boundaries/read_to_end.wave"));
}
#[test]
#[cfg(windows)]
fn empty_windows_event_set_returns_zero() {
let case = Case::new("empty-windows-event");

let path = case.root.join("empty_event.wave");

fs::write(
&path,
r#"
import("std::sys::event")::{
NativeEvent, event_create, event_wait, event_close
};

fun main() -> i32 {
var handle: i64 = event_create(4);
if (handle < 0) { return 1; }

var events: array<NativeEvent, 4>;
var count: i64 = event_wait(handle, &events[0], 4, 0);

event_close(handle);

if (count != 0) { return 2; }
return 0;
}
"#,
)
.unwrap();

success(
case.command()
.arg("--std-root")
.arg(case.home.join(".wave/lib/wave/std"))
.arg("build")
.arg(&path)
.arg("--run")
.output()
.unwrap(),
);
}
3 changes: 3 additions & 0 deletions tests/fixtures/native_providers/network_errors.wave
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ fun platform() -> i32 {
if (!check(-10057, NET_ERROR_NOT_CONNECTED)) { return 58; }
if (!check(-95, NET_ERROR_UNSUPPORTED)) { return 96; }
if (!check(-10045, NET_ERROR_UNSUPPORTED)) { return 46; }
if (!check(-10043, NET_ERROR_UNSUPPORTED)) { return 47; }
if (!check(-10044, NET_ERROR_UNSUPPORTED)) { return 48; }
if (!check(-10047, NET_ERROR_UNSUPPORTED)) { return 49; }
if (!check(-4, NET_ERROR_OTHER)) { return 201; }
if (!check(-11, NET_ERROR_OTHER)) { return 201; }
if (!check(-35, NET_ERROR_OTHER)) { return 201; }
Expand Down
25 changes: 25 additions & 0 deletions tests/fixtures/native_providers/windows_event.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MPL-2.0
import("std::sys::event")::{NativeEvent, event_create, event_add, event_remove, event_wait, event_close, EVENT_READABLE};
import("std::sys::socket")::{socket, bind, close_socket, AF_INET, SOCK_DGRAM};
struct Address { family: u16; port: u16; ip: array<u8, 4>; padding: array<u8, 8>; }
extern(system, "GetTickCount64") fun tick() -> u64;
fun empty_wait(handle: i64) -> bool {
var output: NativeEvent;
if (event_wait(handle, &output, 1, 0) != 0) { return false; }
var start: u64 = tick();
if (event_wait(handle, &output, 1, 150) != 0) { return false; }
// Allow Windows clock granularity, but reject an immediate return.
return tick() - start >= 120;
}
fun main() -> i32 {
var handle: i64 = event_create(1);
if (handle < 0 || !empty_wait(handle)) { return 1; }
var fd: i64 = socket(AF_INET, SOCK_DGRAM, 0);
var address: Address = Address { family: 2, port: 0, ip: [127, 0, 0, 1], padding: [0,0,0,0,0,0,0,0] };
if (fd < 0 || bind(fd, &address as ptr<i8>, 16) != 0 || event_add(handle, fd, 42, EVENT_READABLE) != 0) { return 2; }
var output: NativeEvent;
if (event_wait(handle, &output, 1, 0) < 0) { return 3; }
if (event_remove(handle, fd) != 0 || !empty_wait(handle)) { return 4; }
if (close_socket(fd) != 0 || event_close(handle) != 0) { return 5; }
return 0;
}
12 changes: 12 additions & 0 deletions tests/fixtures/native_providers/windows_event_infinite.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
import("std::sys::event")::{NativeEvent, event_create, event_wait};
import("std::sys::fs")::{open, close, FS_O_CREAT, FS_O_EXCL, FS_O_WRONLY};
fun main() -> i32 {
var handle: i64 = event_create(1);
if (handle < 0) { return 1; }
var fd: i64 = open("ready", FS_O_CREAT | FS_O_EXCL | FS_O_WRONLY, 0);
if (fd < 0 || close(fd) != 0) { return 2; }
var output: NativeEvent;
event_wait(handle, &output, 1, -1);
return 3;
}
19 changes: 19 additions & 0 deletions tests/fixtures/native_providers/windows_event_mock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MPL-2.0
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
static int sleeps, polls;
static uint32_t milliseconds;
void reset_calls(void) { sleeps = polls = 0; milliseconds = 0; }
bool calls_ok(int s, uint32_t ms, int p) { return sleeps == s && milliseconds == ms && polls == p; }
void Sleep(uint32_t ms) { sleeps++; milliseconds = ms; }
void *sys_alloc(int64_t size) { return calloc(1, (size_t)size); }
int64_t sys_free(void *p, int64_t size) { (void)size; free(p); return 0; }
struct pollfd { int64_t fd; int16_t events, revents; };
int64_t poll(struct pollfd *fds, int64_t count, int32_t timeout) {
polls++;
assert(count == 1 && timeout == 12 && fds[0].fd == 17 && fds[0].events == 256);
fds[0].revents = 256;
return 1;
}
27 changes: 27 additions & 0 deletions tests/fixtures/native_providers/windows_event_mock.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
extern(c) fun reset_calls();
extern(c) fun calls_ok(sleeps: i32, milliseconds: u32, polls: i32) -> bool;
fun verify_empty(handle: i64) -> bool {
var output: NativeEvent;
output.token = 987;
reset_calls();
if (event_wait(handle, &output, 1, 0) != 0 || !calls_ok(0, 0, 0) || output.token != 987) { return false; }
if (event_wait(handle, &output, 1, 123) != 0 || !calls_ok(1, 123, 0)) { return false; }
reset_calls();
if (event_wait(handle, &output, 1, -1) != 0 || !calls_ok(1, 4294967295 as u32, 0)) { return false; }
reset_calls();
if (event_wait(handle, &output, 1, -2) != -22 || event_wait(handle, null, 1, 0) != -22
|| !calls_ok(0, 0, 0)) { return false; }
return true;
}
fun main() -> i32 {
var handle: i64 = event_create(1);
if (handle < 0 || !verify_empty(handle)) { return 1; }
if (event_add(handle, 17, 42, EVENT_READABLE) != 0) { return 2; }
var output: NativeEvent;
reset_calls();
if (event_wait(handle, &output, 1, 12) != 1 || output.token != 42
|| output.fd != 17 || output.flags != EVENT_READABLE || !calls_ok(0, 0, 1)) { return 3; }
if (event_remove(handle, 17) != 0 || !verify_empty(handle) || event_close(handle) != 0) { return 4; }
return 0;
}
28 changes: 28 additions & 0 deletions tests/fixtures/native_providers/windows_local_addr.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MPL-2.0
import("std::sys::windows::local_addr")::{
AF_LOCAL, NativeLocalAddr, native_local_addr, native_local_abstract
};
import("std::mem::layout")::{size_of};
fun main() -> i32 {
var address: NativeLocalAddr;
if (AF_LOCAL != 1 || size_of<NativeLocalAddr>() != 110) { return 1; }
if (native_local_addr("한글.sock", &address) != 14 || address.family != 1
|| address.path[0] != 237 || address.path[11] != 0 || address.path[107] != 0) { return 2; }
var path: array<u8, 109>;
var i: i32 = 0;
while (i < 108) { path[i] = 97; i += 1; }
path[107] = 0;
if (native_local_addr(&path[0] as str, &address) != 110 || address.path[106] != 97
|| address.path[107] != 0) { return 3; }
address.family = 99;
path[107] = 97; path[108] = 0;
if (native_local_addr(&path[0] as str, &address) != -36 || address.family != 99) { return 4; }
var nil: ptr<u8> = null;
if (native_local_addr("", &address) != -22 || address.family != 99
|| native_local_addr(nil as str, &address) != -22
|| native_local_addr("ok", null) != -22) { return 5; }
var invalid: array<u8, 3> = [192, 175, 0];
if (native_local_addr(&invalid[0] as str, &address) != -84 || address.family != 99) { return 6; }
if (native_local_abstract("name", &address) != -95 || address.family != 99) { return 7; }
return 0;
}
10 changes: 10 additions & 0 deletions tests/fixtures/native_providers/windows_local_addr_mock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
#include <stdint.h>
#include <assert.h>
int MultiByteToWideChar(uint32_t page, uint32_t flags, const unsigned char *s,
int bytes, uint16_t *output, int capacity) {
assert(page == 65001 && flags == 8 && bytes > 0 && bytes <= 107);
assert(output == 0 && capacity == 0);
// Boundary mock: native Windows tests validate the actual UTF-8 decoder.
return s[0] == 192 ? 0 : bytes;
}
26 changes: 26 additions & 0 deletions tests/fixtures/native_providers/windows_local_addr_mock.wave
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MPL-2.0

import("std::mem::layout")::{size_of};
fun main() -> i32 {
var address: NativeLocalAddr;
if (AF_LOCAL != 1 || size_of<NativeLocalAddr>() != 110) { return 1; }
if (native_local_addr("한글.sock", &address) != 14 || address.family != 1
|| address.path[0] != 237 || address.path[11] != 0 || address.path[107] != 0) { return 2; }
var path: array<u8, 109>;
var i: i32 = 0;
while (i < 108) { path[i] = 97; i += 1; }
path[107] = 0;
if (native_local_addr(&path[0] as str, &address) != 110 || address.path[106] != 97
|| address.path[107] != 0) { return 3; }
address.family = 99;
path[107] = 97; path[108] = 0;
if (native_local_addr(&path[0] as str, &address) != -36 || address.family != 99) { return 4; }
var nil: ptr<u8> = null;
if (native_local_addr("", &address) != -22 || address.family != 99
|| native_local_addr(nil as str, &address) != -22
|| native_local_addr("ok", null) != -22) { return 5; }
var invalid: array<u8, 3> = [192, 175, 0];
if (native_local_addr(&invalid[0] as str, &address) != -84 || address.family != 99) { return 6; }
if (native_local_abstract("name", &address) != -95 || address.family != 99) { return 7; }
return 0;
}
Loading
Loading