a73x

c707efd5

test: harden wasm client state resets

a73x   2026-08-18 12:27

Commit message
test: harden wasm client state resets

src/wasm_core.zig
Old New
@@ -254,13 +254,13 @@ export fn mux_apply_frame(msg_type: u32, len: u32) i32 {
254 /// the host stages the next frame; getters never copy or allocate them. 254 /// the host stages the next frame; getters never copy or allocate them.
255 export fn mux_client_frame(msg_type: u32, len: u32) i32 { 255 export fn mux_client_frame(msg_type: u32, len: u32) i32 {
256 const c = core orelse return -1; 256 const c = core orelse return -1;
257 c.clipboard = .{ .target = 0, .base64 = &.{} };
257 if (len > input_buf.len) return -2; 258 if (len > input_buf.len) return -2;
258 if (msg_type > 0xff) return @intFromEnum(ClientAction.ignored); 259 if (msg_type > 0xff) return @intFromEnum(ClientAction.ignored);
259 260
260 // MsgType is deliberately non-exhaustive, so every u8 is a valid enum 261 // MsgType is deliberately non-exhaustive, so every u8 is a valid enum
261 // value. Unknown wire bytes reach ClientCore and are ignored safely. 262 // value. Unknown wire bytes reach ClientCore and are ignored safely.
262 const t: proto.MsgType = @enumFromInt(@as(u8, @intCast(msg_type))); 263 const t: proto.MsgType = @enumFromInt(@as(u8, @intCast(msg_type)));
263 c.clipboard = .{ .target = 0, .base64 = &.{} };
264 return switch (c.client.receive(t, input_buf[0..len])) { 264 return switch (c.client.receive(t, input_buf[0..len])) {
265 .ignored => @intFromEnum(ClientAction.ignored), 265 .ignored => @intFromEnum(ClientAction.ignored),
266 .state => |state| switch (state) { 266 .state => |state| switch (state) {
web/verify.js
Old New
@@ -104,8 +104,12 @@ async function main() {
104 104
105 // --- shared client semantics ABI --- 105 // --- shared client semantics ABI ---
106 // Pasting is raw until the daemon explicitly samples mode 2004 as on. 106 // Pasting is raw until the daemon explicitly samples mode 2004 as on.
107 stage(Buffer.from('seed'));
108 check('seed output before disabled begin', e.mux_text_encode(4), 4);
107 check('paste begin disabled', e.mux_paste_begin(), 0); 109 check('paste begin disabled', e.mux_paste_begin(), 0);
108 check('paste begin disabled clears output', e.mux_output_len(), 0); 110 check('paste begin disabled clears output', e.mux_output_len(), 0);
111 stage(Buffer.from('again'));
112 check('seed output before disabled end', e.mux_text_encode(5), 5);
109 check('paste end disabled', e.mux_paste_end(), 0); 113 check('paste end disabled', e.mux_paste_end(), 0);
110 check('paste end disabled clears output', e.mux_output_len(), 0); 114 check('paste end disabled clears output', e.mux_output_len(), 0);
111 115
@@ -134,6 +138,15 @@ async function main() {
134 check('ignored event clears clipboard getter', e.mux_clipboard_len(), 0); 138 check('ignored event clears clipboard getter', e.mux_clipboard_len(), 0);
135 check('client unknown u8 type ignored', e.mux_client_frame(0x40, 0), clientAction.ignored); 139 check('client unknown u8 type ignored', e.mux_client_frame(0x40, 0), clientAction.ignored);
136 140
141 check('clipboard before oversize guard', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
142 check('client oversize length', e.mux_client_frame(0x8f, e.mux_input_cap() + 1), -2);
143 check('oversize clears clipboard len', e.mux_clipboard_len(), 0);
144 check('oversize clears clipboard target', e.mux_clipboard_target(), 0);
145 check('clipboard before wide-type guard', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
146 check('client wide type ignored', e.mux_client_frame(0x100, 0), clientAction.ignored);
147 check('wide type clears clipboard len', e.mux_clipboard_len(), 0);
148 check('wide type clears clipboard target', e.mux_clipboard_target(), 0);
149
137 // --- attach payload before any state: quotes (0,0); wall spelling 1x1 --- 150 // --- attach payload before any state: quotes (0,0); wall spelling 1x1 ---
138 check('attach len', e.mux_attach_payload(1, 1, 0), 20); 151 check('attach len', e.mux_attach_payload(1, 1, 0), 20);
139 let att = outBytes(); 152 let att = outBytes();
@@ -304,10 +317,15 @@ async function main() {
304 check('instantiate(bytes) is the OTHER shape', fromBytes.instance instanceof WebAssembly.Instance, true); 317 check('instantiate(bytes) is the OTHER shape', fromBytes.instance instanceof WebAssembly.Instance, true);
305 318
306 // --- deinit / re-init --- 319 // --- deinit / re-init ---
320 check('clipboard before lifecycle reset', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
321 check('clipboard populated before lifecycle reset', e.mux_clipboard_len(), 4);
322 check('modes populated before lifecycle reset', e.mux_bracketed_paste(), 1);
307 e.mux_deinit(); 323 e.mux_deinit();
308 check('apply after deinit', e.mux_apply_frame(0x81, 0), -1); 324 check('apply after deinit', e.mux_apply_frame(0x81, 0), -1);
309 check('client frame after deinit', e.mux_client_frame(0x8d, 0), -1); 325 check('client frame after deinit', e.mux_client_frame(0x8d, 0), -1);
310 check('re-init', e.mux_init(80, 24), 0); 326 check('re-init', e.mux_init(80, 24), 0);
327 check('re-init resets bracketed paste', e.mux_bracketed_paste(), 0);
328 check('re-init resets clipboard', e.mux_clipboard_len(), 0);
311 e.mux_deinit(); 329 e.mux_deinit();
312 330
313 console.log(`verify: ${passed} passed, ${failed} failed`); 331 console.log(`verify: ${passed} passed, ${failed} failed`);