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

@@ -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;
};