memio: make cursor operations overlap-safe

This commit is contained in:
2026-08-09 18:03:19 +09:00
parent d7d5b593c2
commit cf8244c0ec
2 changed files with 158 additions and 23 deletions

View File

@@ -11,6 +11,7 @@ import bytes;
import errors;
import io;
import memio;
import test;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
@@ -69,6 +70,21 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
@test fn fixedreadoverlap() void = {
let arr: [4]u8;
let _: i32 = putstr("ABCD", arr[0:4], 0);
let st: memio.stream = memio.fixed(arr[0:4]);
let r: (size | io.eof | io.error) = io.read(&st.vt, arr[1:4]);
match (r) {
case let n: size => assert(n: i32 == 3);
case io.eof => abort();
case let e: io.error => abort();
};
let want: [4]u8;
let _: i32 = putstr("AABC", want[0:4], 0);
assert(bytes.equal(arr[0:4], want[0:4]));
};
@test fn fixedwritecases() void = {
let dst: [16]u8;
let st: memio.stream = memio.fixed(dst[0:16]);
@@ -158,6 +174,22 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
@test fn fixedwriteoverlap() void = {
let arr: [4]u8;
let _: i32 = putstr("ABCD", arr[0:4], 0);
let st: memio.stream = memio.fixed(arr[0:4]);
let sk: (io.off | io.error) = io.seek(&st.vt, 1, io.whence.SET);
match (sk) { case let o: io.off => {}; case let e: io.error => abort(); };
let r: (size | io.error) = io.write(&st.vt, arr[0:3]);
match (r) {
case let n: size => assert(n: i32 == 3);
case let e: io.error => abort();
};
let want: [4]u8;
let _: i32 = putstr("AABC", want[0:4], 0);
assert(bytes.equal(arr[0:4], want[0:4]));
};
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
// accumulated `pos` after each step.
@test fn dynamicgrowcases() void = {
@@ -198,6 +230,47 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
@test fn dynamicgrowaliasedwrite() void = {
let seed: []u8;
seed.ptr = nil; seed.len = 0; seed.cap = 0;
append(seed, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H');
let st: memio.stream = memio.dynamicfrom(seed);
let sk: (io.off | io.error) = io.seek(&st.vt, 0, io.whence.END);
match (sk) { case let o: io.off => {}; case let e: io.error => abort(); };
let alias: []u8 = memio.buffer(&st);
let r: (size | io.error) = io.write(&st.vt, alias);
match (r) {
case let n: size => assert(n: i32 == 8);
case let e: io.error => abort();
};
let want: [16]u8;
let _: i32 = putstr("ABCDEFGHABCDEFGH", want[0:16], 0);
assert(alias.ptr != memio.buffer(&st).ptr);
assert(bytes.equal(memio.buffer(&st), want[0:16]));
let c: (void | io.error) = io.close(&st.vt);
match (c) { case void => {}; case let e: io.error => abort(); };
};
@test fn dynamicwriteoverflow() void = {
let st: memio.stream = memio.dynamic();
let one: [1]u8;
one[0] = 1u8;
let first: (size | io.error) = io.write(&st.vt, one[0:1]);
match (first) { case let n: size => {}; case let e: io.error => abort(); };
let huge: []u8;
huge.ptr = nil;
huge.len = 2147483647;
huge.cap = huge.len;
let r: (size | io.error) = io.write(&st.vt, huge);
match (r) {
case let n: size => abort();
case let e: io.error => assert(e is nomem);
};
assert(memio.buffer(&st).len == 1);
let c: (void | io.error) = io.close(&st.vt);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// op=0 writes `ln` bytes from a rolling source; op=1 resets and ignores
// ln. After each row the accumulated len must equal `want`.
@test fn dynamicreset() void = {
@@ -267,6 +340,7 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
case let v: []u8 => {
assert(!(weof[i] != 0));
assert(!(v.len != amt[i]));
assert(v.cap == v.len);
assert(!(v.len > 0 && v[0] != wf[i]));
assert(!(v.len > 0 && v[v.len - 1] != wl[i]));
};
@@ -276,6 +350,13 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
@test fn borrowedreadnegativeaborts() void = {
test.expectabort();
let arr: [1]u8;
let st: memio.stream = memio.fixed(arr[0:1]);
let r: ([]u8 | io.eof) = memio.borrowedread(&st, -1);
};
@test fn stringview() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
@@ -303,6 +384,8 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (r) { case let n: size => {}; case let e: io.error => abort(); };
let v: str = memio.string(&st);
assert(!(v.len != want[i]));
assert(v.cap == v.len);
assert(memio.buffer(&st).cap == v.len);
assert(!(v[0] != wf[i]));
assert(!(v[v.len - 1] != wl[i]));
i += 1;
@@ -448,6 +531,24 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
@test fn seekminleavescursor() void = {
let arr: [8]u8;
let st: memio.stream = memio.fixed(arr[0:8]);
let set: (io.off | io.error) = io.seek(&st.vt, 3, io.whence.SET);
match (set) { case let o: io.off => {}; case let e: io.error => abort(); };
let min: i64 = -9223372036854775807i64 - 1i64;
let r: (io.off | io.error) = io.seek(&st.vt, min, io.whence.CUR);
match (r) {
case let o: io.off => abort();
case let e: io.error => assert(e is errors.invalid);
};
let pos: (io.off | io.error) = io.tell(&st.vt);
match (pos) {
case let o: io.off => assert((o: i64) == 3);
case let e: io.error => abort();
};
};
@test fn emptyseek() void = {
let arr: [1]u8;
arr[0] = 0u8;