memio: make cursor operations overlap-safe
This commit is contained in:
@@ -54,6 +54,7 @@ import errors;
|
||||
import io;
|
||||
import os;
|
||||
import rt;
|
||||
import types;
|
||||
|
||||
// stream — Hare's memio::stream (ref/hare/memio/stream.ha:18). `vt` at
|
||||
// offset 0 for the intrusive stream→io.stream cast (`&s.vt`) and the
|
||||
@@ -122,6 +123,26 @@ export fn dynamicfrom(buf: []u8) stream = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// memiomove copies n bytes correctly for disjoint or overlapping regions.
|
||||
// Copy backwards whenever the destination starts above the source; that is
|
||||
// the only overlap direction a forward loop can corrupt.
|
||||
fn memiomove(dst: *u8, src: *u8, n: i32) void = {
|
||||
if (n <= 0 || dst == src) { return; };
|
||||
let i: i32 = 0;
|
||||
if ((dst: uintptr) > (src: uintptr)) {
|
||||
i = n;
|
||||
for (i > 0) {
|
||||
i -= 1;
|
||||
dst[i] = src[i];
|
||||
};
|
||||
} else {
|
||||
for (i < n) {
|
||||
dst[i] = src[i];
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// readfn — recover the stream from the io.stream's `*vtable` via the
|
||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
||||
@@ -135,11 +156,7 @@ fn readfn(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||
let avail: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = m.ptr[m.pos + i];
|
||||
i += 1;
|
||||
};
|
||||
memiomove(buf.ptr, m.ptr + (m.pos: u64), n);
|
||||
m.pos += n;
|
||||
return n: size;
|
||||
};
|
||||
@@ -166,7 +183,7 @@ fn seekfn(s: io.stream, off: io.off, w: io.whence) (io.off | io.error) = {
|
||||
case io.whence.END: start = m.len: i64;
|
||||
};
|
||||
if (n < 0) {
|
||||
if (start < -n) {
|
||||
if (n < -start) {
|
||||
let v: errors.invalid;
|
||||
let e: io.error = v;
|
||||
return e;
|
||||
@@ -193,24 +210,35 @@ fn fixedwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
memiomove(m.ptr + (m.pos: u64), buf.ptr, n);
|
||||
m.pos += n;
|
||||
return n: size;
|
||||
};
|
||||
|
||||
fn dynamicwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
||||
let m: *stream = s: *stream;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { dynamicgrow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
if (buf.len > types.I32_MAX - m.pos) {
|
||||
let nm: nomem;
|
||||
let e: io.error = nm;
|
||||
return e;
|
||||
};
|
||||
let need: i32 = m.pos + buf.len;
|
||||
let aliased: bool = false;
|
||||
let srcoff: i32 = 0;
|
||||
if (m.cap > 0 && (buf.ptr: uintptr) >= (m.ptr: uintptr)) {
|
||||
let delta: uintptr = (buf.ptr: uintptr) - (m.ptr: uintptr);
|
||||
if (delta < (m.cap: uintptr)) {
|
||||
assert(buf.len <= m.cap - (delta: i32),
|
||||
"memio.dynamicwrite: aliased source exceeds buffer");
|
||||
aliased = true;
|
||||
srcoff = delta: i32;
|
||||
};
|
||||
};
|
||||
if (need > m.cap) {
|
||||
dynamicgrow(m, need);
|
||||
if (aliased) { buf.ptr = m.ptr + (srcoff: u64); };
|
||||
};
|
||||
memiomove(m.ptr + (m.pos: u64), buf.ptr, buf.len);
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len: size;
|
||||
@@ -233,13 +261,15 @@ fn dynamicclose(s: io.stream) (void | io.error) = {
|
||||
fn dynamicgrow(d: *stream, need: i32) void = {
|
||||
let newcap: i32 = d.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < d.len) {
|
||||
nbuf[i] = d.ptr[i];
|
||||
i += 1;
|
||||
for (newcap < need) {
|
||||
if (newcap > types.I32_MAX / 2) {
|
||||
newcap = need;
|
||||
break;
|
||||
};
|
||||
newcap *= 2;
|
||||
};
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
memiomove(nbuf, d.ptr, d.len);
|
||||
if (d.cap > 0) { os.free(d.ptr: *void, d.cap: u64); };
|
||||
d.ptr = nbuf;
|
||||
d.cap = newcap;
|
||||
@@ -256,6 +286,7 @@ export fn string(s: *stream) str = {
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
r.cap = r.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -266,6 +297,7 @@ export fn buffer(s: *stream) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.pos;
|
||||
r.cap = r.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -285,6 +317,7 @@ export fn reset(s: *stream) void = {
|
||||
// Mirrors ref/hare/memio/stream.ha:94 borrowedread(st: *stream, amt).
|
||||
// `amt: i32` (not Hare's `size`) per the i32-index convention.
|
||||
export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
assert(amt >= 0, "memio.borrowedread: amount must not be negative");
|
||||
if (s.len - s.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
@@ -292,6 +325,7 @@ export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
let r: []u8;
|
||||
r.ptr = s.ptr + (s.pos: u64);
|
||||
r.len = amt;
|
||||
r.cap = r.len;
|
||||
s.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user