Releases: rubidus-api/proven_c_lib
Release list
proven_c_lib-v0.6.0
A MINOR release: nothing public removed. It carries the whole-library review (RFC-0009): defects
fixed, four security changes, faster CRC-32, SHA-256, directory listing and stream readers, map
iteration, in-place array editing, job groups, and a build that links its tests in parallel.
Behaviour changes, each where the old behaviour was wrong or unsafe: an exclusive create over an
existing name is PROVEN_ERR_EXISTS instead of PROVEN_ERR_IO; environment values and directory
entry names that are not valid text are PROVEN_ERR_INVALID_ENCODING on POSIX too (they were
returned as raw bytes; Windows substituted U+FFFD); proven_map_hash values for default
integer-key maps change and now differ per process; and proven_reader_buffered_t gains a field.
Added
PROVEN_ERR_EXISTS: an exclusive create found the name already there (RFC-0009 X-009).
proven_fs_openwithPROVEN_FS_CREATE_NEWover an existing name returnedPROVEN_ERR_IO,
the same code as every unclassified failure, so a caller could not tell "choose another name"
from "the disk failed". It now returns the new code, on POSIX (EEXIST) and Windows
(ERROR_FILE_EXISTS).PROVEN_ERR_LASTmoves to it; aliasXCV_ERR_EXISTS. Code that
compared that case againstPROVEN_ERR_IOmust compare againstPROVEN_ERR_EXISTS.proven_fs_is_staging_name(name): true for a name with the shape of the temp file an
atomic or durable write leaves behind when killed mid-write (today's and the old one), for a
cleanup job. The library itself never removes one it did not just create (RFC-0009 D-001).proven_random_u64_checked(&out): one strong word, andfalsewhen there is none
(RFC-0009 S-002).proven_random_u64returns 0 when the entropy source fails - on a bare-metal
target with no source that is every call - and its documentation offered it for tokens. A
token of 0 is a predictable secret nothing reports. The checked form is for secrets; the
unchecked one stays, now documented as not for them. Aliasxcv_random_u64_checked.proven_fs_read_all_bounded(alloc, path, max_bytes): a whole-file read with a ceiling
(RFC-0009 S-003).proven_fs_read_allreads until the source ends, so a path naming
/dev/zero, an endless FIFO, or a far larger file than expected grows the buffer until the
allocator refuses. The bounded form returnsPROVEN_ERR_OUT_OF_BOUNDSand no buffer past
max_bytes: before allocating when the reported size says so, and as the extra byte arrives
when the size says nothing. The unbounded functions now say they are for trusted paths. Alias
xcv_fs_read_all_bounded.- A map can be walked:
proven_map_iter_init/proven_map_iter_next, andproven_map_len
(RFC-0009 X-001). Before, a program could not list what it stored, serialise a map, or release
what its values own without keeping a second list of keys. The walk is in bucket order; removing
entries (including the current one) and updating values are allowed during it; a new key or a
reserve that rehashes the map makes the next stepPROVEN_ERR_INVALID_STATEinstead of skipping
or repeating entries. Aliasesxcv_map_iter_t,xcv_map_iter_init,xcv_map_iter_next,
xcv_map_len. - Arrays can be edited in place:
proven_array_clear,_truncate,_insert,_remove_at,
_swap_removeand_extend(RFC-0009 X-002) - the operations callers wrote by hand on top
ofget_mutandlen. Each changes nothing when it fails;insertandextendaccept
elements taken from the array itself (a source that only partly overlaps it is refused), and
extendreallocates at most once. Aliasesxcv_array_*. - Jobs:
proven_job_submit_exsays why a submit was refused, and job groups can be waited on
(RFC-0009 X-003).proven_job_submitreturnsfalseboth for a full queue and for a closed
system, which call for opposite responses;_exreturnsPROVEN_ERR_AGAINor
PROVEN_ERR_INVALID_STATE.proven_job_group_tcounts submitted jobs
(proven_job_group_init,_submit,_pending);proven_job_group_waitreturns when all have
run, running queued jobs itself while it waits, with what they wrote visible afterwards.
Aliasesxcv_job_submit_ex,xcv_job_group_*.
Changed
-
SHA-256 is about 25% faster (RFC-0009 P-107):
proven_sha256_updatecompresses whole
64-byte blocks straight from the input instead of copying every byte into its block buffer
first. Measured on 4 MiB: 4.7 ns per byte, from 6.2. Digests are unchanged. -
./nob build -keep-goingruns every test and lists the failures (RFC-0009 X-006, first part).
A run stopped at the first failing test, so one failure hid every other. With the flag, each
test still runs once and the run ends with a summary and exit status 1. -
./nob <mode> -jobs Nruns tests in parallel on POSIX (RFC-0009 X-006). Tests created
fixtures under fixed names in the working directory, so they could only run one at a time. With
the flag each runs in a directory of its own that links to the repository's top level, its output
is printed whole in registry order, and stress and benchmark tests still run alone. On the
16-CPU host a cached./nob asantakes 8.9 s instead of 16.9, a cached./nob build7.1 s
instead of 10.1. The default stays one test at a time. -
A clean
./nob buildtakes 14 s instead of 36 on a 16-CPU host (RFC-0009 P-108). The test
executables were compiled and linked one at a time - 20 of the 31 seconds a clean build spent
outside the tests themselves. They are now linked in parallel, up to one per CPU, and then run
one at a time in registry order as before, with a link failure still reported under the test's
name. Running the tests in parallel needs per-test scratch files first and is not done. -
The seven
*_internal/*_implfunctions in public headers are marked MACRO SUPPORT and are
not a stable interface (RFC-0009 X-004):proven_u8str_fmt_internal,proven_scan_fmt_internal,
proven_scan_fmt_internal_view,proven_fmt_to_writer_impl,proven_sysio_scanner_scan_impl,
proven_sysio_print_impl,proven_sysio_scan_chunk_impl. Their signatures may change in a MINOR
release; call the macros. A source-contract test keeps the list closed. -
proven_u16_reader_readwith a small destination no longer revalidates everything it holds
(RFC-0009 P-103). Each call decoded and validated the whole staged area - up to 512 units - to
hand outcapof them: 261 ns per unit atcap2. It now examinescap + 1units: 12 ns per
unit atcap2, 2.3 at 64 (from 9.6). The text delivered and the error that ends it are the
same for everycap. -
proven_reader_read_linesearches each byte once (RFC-0009 P-104). It searched from the
start of the pending line, one byte at a time, after every refill, so a long line arriving in
small pieces cost time quadratic in its length: 4.8 us per byte for a 16,000-byte line read a
byte at a time. It now remembers how far it has searched and uses the platformmemchr: 21-25 ns
per byte for that case, and 0.27 ns per byte from 1.33 for 80-byte lines read from a file.
proven_reader_buffered_tgains a field,scanned, set byproven_reader_buffered. -
Listing a directory on POSIX costs one metadata call per entry, or none (RFC-0009 P-102).
proven_fs_dir_next(and soproven_fs_listand the walk) asked for every entry's type twice,
following and not following symlinks.readdir'sd_typealready says what most entries are:
a directory or a special file now needs no call, a regular file one (for its size), and only a
symlink or a filesystem that leavesd_typeunknown takes both. Measured over 50,000 files:
3.5 us per entry, from 5.9. What is reported is unchanged. -
CRC-32 is about 6x faster on long inputs (RFC-0009 P-101): slicing-by-8, eight bytes per
step with independent table lookups. Measured on 1 MiB: 0.36 ns per byte, from 2.30. Output is
unchanged (checked against the single-table path at every length and offset, and against
zlib). It costs 7 KiB more read-only data; a build with-DPROVEN_CRC32_SMALL=1keeps the
single 1 KiB table. -
Integer map keys are hashed with the per-process secret in a default map (RFC-0009 S-001).
They used a public bit-mix finaliser whatever the map was created with, and its inverse is
public, so anyone choosing the keys - user ids, record numbers from a request - could put any
number of them in one bucket and make the map quadratic. A default (proven_map_create)
integer-key map now uses SipHash-1-3 of the key under the same secret as string keys;
proven_map_create_trustedkeeps the finaliser. Measured cost on a default map: about 7 ns
more per lookup on a 1K-key table, 15-20 ns per insert.proven_map_hashvalues for default
integer-key maps change (and differ per process); trusted maps are unchanged. -
Text from the platform is strict, like all other text (RFC-0009 D-003). An environment value
or a directory entry name that is not valid text - bytes that are not UTF-8 on POSIX, a lone
surrogate on Windows - is nowPROVEN_ERR_INVALID_ENCODING. Before, POSIX returned the raw
bytes asPROVEN_OKand Windows silently substituted U+FFFD, which in a listing named a
different file, or none.proven_fs_dir_nextandproven_fs_walk_nextreport such an entry
once (emptyname, other fields filled in) and go on;proven_fs_listrefuses the whole
listing rather than leave a file out. -
The library's source list is one manifest,
build_sources.inc, with a freestanding attribute
per source (RFC-0009 X-005). It was a literal array innob.c, and the hosted-only sources
were listed twice more - for the native freestanding build and for the cross matrix - with
nothing checking the copies agreed. Both now read the manifest; a source-contract test refuses a
hand-kept list innob.c.
Fixed
- **`./nob...
proven_c_lib-v0.5.0
A MINOR release: nothing public removed, no behaviour changed. Three additions requested by
Pnakotic (B-044): one-character UTF-8 decoding, hexadecimal scanning, and a stated ceiling on the
error-code space. The repository now carries only the library, its tests, the manual and the
public documents; design records, benchmark archives and process notes are kept outside it, and
the public history was rewritten to match.
Added
- One-character UTF-8 decoding:
proven_utf8_decode_next(s, pos). For code that walks text a
character at a time (requested by Pnakotic, which carried a stand-in). Same strict rules as the
rest ofutf.h; the returnedlenis always the step to take - the character, the maximal
subpart of malformed input (where Unicode says to resynchronise), or the bytes left when the
text ends mid-character (NEED_MORE). Nothing is substituted. Freestanding-available. - Hexadecimal scanning:
proven_scan_u64_hex,proven_scan_i64_hex, and{:x}/{:X}in a
scan format for any integer argument, mirroring the formatter's spec (requested by Pnakotic for
0041..005A-style ranges).0xis taken only before a digit, asstrtoultakes it; overflow,
cursor restore and the stream signal behave as for decimal, including a0xthat arrives before
its digits. Before, every format placeholder had to be{}. - The error-code space is a promise:
PROVEN_ERR_RESERVED_END(0x1000) andPROVEN_ERR_LAST.
proven will never define a code at or above 0x1000, so a program can carryproven_err_tvalues
unchanged in a wider type and number its own codes from there (requested by Pnakotic).
PROVEN_ERR_LASTnames today's highest code; a build-time assertion holds the ceiling and a
source contract keeps the macro on the enum's real last value. AliasesXCV_ERR_LAST,
XCV_ERR_RESERVED_END, and the missingXCV_ERR_INVALID_FORMAT.
Changed
- The repository history was rewritten to drop the design records, benchmark archives, process
notes and private verification scripts that earlier commits carried underdocs/and
scripts/. Every commit hash changed, and every tag was moved to its rewritten commit with the
same library sources; a checkout that pinned an old hash should re-pin by tag. Public documents
cite the moved records by label only (RFC-0007,B-043).
Fixed
- Chapter 1 listed
proven_err_twithoutPROVEN_ERR_NEED_MORE, in both the enum listing and
the meaning table (English and Korean). Both now carry it.
proven_c_lib-v0.4.0
A MINOR release: nothing public removed. The whole test suite now runs natively on Windows
(./nob build -no-run, a mingw-w64 build for x86-64 and i686), and its first run found six
Windows defects, all fixed - an empty directory could not be removed, pread/pwrite moved the file
position, positioned calls on a pipe were not refused, and the end of a pipe read as an I/O error
(B-035). Float parsing is faster than glibc at every length measured (B-043), and 32-bit targets
use the Eisel-Lemire fast path (B-042). MSVC and clang-cl are stated as not supported. Behaviour
changes only where it was wrong: proven_fs_remove on an empty directory on Windows, and the
Windows pipe and position cases above.
Added
- The full test suite runs on Windows (B-035).
./nob build -no-runbuilds every test
executable and runs none; the build driver asks the compiler for its target, so a mingw-w64
cross compiler produces static.exetests (with-lbcrypt, without-ldl).
win11kd-full-suite.shandwin11kd-run-suite.ps1build both word sizes and
run them natively, reporting PASS / FAIL / SKIP / TIMEOUT per test. First run on the Windows 11
test VM found the six defects below. Three tests that skipped on Windows now run there
(test_unit_sysio_streams,test_regression_scanner_short_read,
test_regression_scanner_float_split). x86-64 and i686 each pass 214 with 0 failures; 7
fixtures whose subject is POSIX skip.
Changed
- Float parsing is faster than glibc at every length measured (B-043). Three changes, each
exact: a direct Eisel-Lemire layer (Lemire 2021) proves most results from a 64x128-bit product
with a truncated power of five, where the staged layer validated every candidate with big-
integer arithmetic; a significand past 19 digits is bounded by its first 19 digits and the same
plus one, instead of going to the exact path; and one pass reads the digits, where a digit-by-
digit builder kept pending-zero bookkeeping.test_bench_float_host, x86-64, against glibc
strtod:%.6g83 -> 81 ns (0.58x), ~16 digits 186 -> 145 ns (0.72x),%.17g224 -> 152 ns
(0.75x, was 1.13x), 25 digits ~640 -> 197 ns (0.86x, a new corpus). Results are unchanged:
every row is checked againststrtodin the same run, and 60 million generated inputs - random
significands and exponents,%.15g-%.40gof random doubles, integers above 2^53, subnormal
and overflow edges - matchedstrtodbit for bit, with and without 128-bit integers. The
cached powers come fromscripts/generate_float_decimal_tables.py, which checks its exponent
estimate against exact integer arithmetic. - The Eisel-Lemire float-parsing fast path is used on 32-bit targets too (B-042). It was
compiled only where the compiler hasunsigned __int128, although the code needs none - it
goes through the portable 64x64 multiply. Without it a 32-bit target sent every decimal past
the Clinger path to the exact big-integer path. Measured with 128-bit integers disabled on
x86-64 (test_bench_float_host, release): 16-digit parsing 287 -> 183 ns, 17-digit 473 -> 222 ns;
the results are checked against the hoststrtodin the same run, and the Windows i686 run
passes the differential corpora and expects the fast path intest_unit_float_parse_api. - MSVC and clang-cl are not supported (owner decision; possibly later). Their C23 support is
incomplete. The manual used to say recent MSVC worked, which was never verified; it now names
what is: GCC 13+, Clang 16+, and on Windows mingw-w64 GCC (x86-64 and i686).
Fixed
- Windows:
proven_fs_removedeletes an empty directory, as POSIXremove()does. It called
DeleteFileWonly, which refuses a directory, and reported PERMISSION. - Windows:
proven_fs_preadandproven_fs_pwriteno longer move the file position. A
positionedReadFile/WriteFileon a synchronous handle leaves the pointer after the bytes;
the position is now restored. - Windows: positioned calls on a pipe are UNSUPPORTED.
proven_fs_seek,preadandpwrite
usedSetFilePointerEx/ an OVERLAPPED offset on a pipe, which Windows does not support, and
proven_sysio_scan_chunktherefore accepted a pipe it must refuse. The handle type is now
checked first (FILE_TYPE_DISK), matching POSIXESPIPE. - Windows: the end of a pipe is EOF.
ReadFilereports a closed writer asERROR_BROKEN_PIPE,
which came back asPROVEN_ERR_IO- soproducer | programended in an error. Both read paths
now map it toPROVEN_ERR_EOF, asread(2)returning 0 does. - Tests and examples that assumed POSIX or a 64-bit target. Permission checks compare with the
mode read back afterchmod(Windows keeps only the owner-write bit); the durable-write example
acceptssync_dir's documented UNSUPPORTED on Windows;test_unit_u128_muland
test_unit_float_bigint_divmodhave references without__int128; a float comparison
uses a storeddouble, not a literal that x87 evaluates in long double; a fixture no longer
needs abuild/directory. scripts/release.shbuilds a release's PDFs with one date. The site is built at the release
commit and again by--publishat the site commit; both now take the date of the commit that
last set the version, so the uploaded PDFs equal the committed ones without a re-publish.
proven_c_lib-v0.3.0
A MINOR release: nothing public removed, one build profile added, and one profile's behaviour
changed on purpose. ./nob release now defines NDEBUG, so pool and map misuse checks are out
of release builds (pool teardown was quadratic with them), and the new ./nob hardened keeps them
in an optimised build (B-036). find_last makes the forward search's choices, and its quadratic
tail is gone (B-024). The freestanding runtime contract is stated and link-proven (B-034). ./nob cross reports every target and cannot skip the mandatory ones (B-035). Benchmarks share one row
format and back the published numbers (B-037, B-038). The build rebuilds exactly what a header
change touched, and ./nob clean removes the selected build root safely (B-036). English public
text is ASCII, and a gate keeps it that way.
Added
- One benchmark row format, and the benchmarks behind the published numbers (B-037, B-038).
tests/proven_bench.h: warmup, five samples on a monotonic clock, median, spread, raw samples,
checksum, compiler and profile on one line; every registered benchmark uses it. New
tests/test_bench_float_host.c(the float-vs-glibc comparison, now checked in, with accuracy
asserted in the same run) andtests/test_bench_job.c(idle CPU for 1-32 workers, idle / burst
/ saturated wake latency, throughput). Raw results and a claim-to-row map in
the maintainers' benchmark records, with the job system's latency budget.
Changed
- The build rebuilds exactly what a change touched (B-036). Objects and test executables are
built with compiler dependency files (-MMD); the cache key is the exact compile or link command
plus the contents of every file the dependency file names. Editinginclude/proven/job.hnow
recompilesjob.calone (1 of 36 objects; the tests relink because they link every object), and
editingmanual/examples/example.hrelinks the 92 examples that include it and nothing else -
before, any header edit rebuilt everything. A missing dependency file means a rebuild, never a
stale cache hit. The test cache hash is now taken from the exact link command (it used to be a
hand-kept copy that differed from it), and-ldlis linked only into the test that interposes
libc withdlsym(RTLD_NEXT, ...)instead of every POSIX test (B-035). The first build after
updating rebuilds everything once. ./nob cleanremoves the selected build root, safely (B-036). It honours-build-rootand
PROVEN_BUILD_ROOTinstead of always runningrm -rf build/rmdir /s /q buildthrough the
shell, deletes without following symlinks or junctions, refuses.,/, any..component
and unusual characters, and removes a root other than the defaultbuildonly when it carries
the.proven-build-rootmarker nob now writes into every build root it creates. Compatibility
note: a custom build root created by an oldernobhas no marker, socleanrefuses it once
with a hint; remove it by hand or build into it again first. As for building, a root with a
drive letter or backslashes (C:\...) is refused; on Windows use a relative root. The Windows
deletion path is compiled with mingw-w64 (x86-64, i686) but not yet run on Windows.- The job system's wake-latency budget states its condition (B-041). The Windows idle-wake p99
of ~8.9 ms was traced withb041-wake-probe.c: a bare OS semaphore shows the same tail
whenever CPUs are scarce (the 2-vCPU VM; Linux pinned to 2 CPUs), and neither shows it on a
quiet 16-CPU host. The job system adds ~1 us at the median. No library change; the budget in
the job budget in the maintainers' benchmark records now says it assumes free CPUs, andtest_bench_jobprints the
host's logical CPU count. ./nob releasedefinesNDEBUG; new./nob hardenedkeeps the checks (B-036).
Compatibility note: a release build no longer traps a pool double free or a foreign pointer, or
the map's key-overlap misuse - those checks are for debug and hardened builds. They made pool
teardown quadratic: 20,000 frees took 59.6 ms with the check, 0.05 ms without
(b036-pool-teardown-benchmark.c). Build withhardened(-O2 -DNDEBUG -DPROVEN_HARDENED=1) to keep them in an optimised build, and usealloc_check.hin tests.
Every build now logs its safety profile.- Published float speed claims follow the checked-in benchmark. Re-measured: parsing is
faster than glibc on short numbers, level at ~16 digits, ~1.1x slower at 17; shortest formatting
~3.6x faster than%.17g;%f/%efaster at every magnitude measured - the June claim that
they were 3-5x slower at extreme magnitudes did not reproduce. README (both), the float doc and
primitives-benchmark.mdupdated. - English public text is ASCII, and stays so (B-036). README.md, TEST.md, CHANGELOG.md, the
English manual and examples, and all C sources and build files were normalised (em dashes,
section signs, arrows and the like; 48 files), with the Markdown anchors of changed headings
updated.scripts/ascii_policy.py check, run byproject-check, fails on any new non-ASCII byte
in that scope; Hangul is exempt, and the Korean mirrors are out of scope. ./nob crossreports every target and cannot skip the ones a release needs (B-035). Each
target ends PASS, FAIL or SKIP with a reason, a failure no longer stops the other targets, and a
summary is printed. Skippingnative-gcc-hosted,native-clang-hosted,windows-x86_64-winapi
orwindows-i686-winapifails the run; before, a run that skipped nine of eleven targets
finished green.- The freestanding runtime contract is explicit and linked (B-034). A freestanding build needs
memcpy,memmove,memset,memcmpand the compiler support library, nothing else - stated
in the freestanding guide and proven by a new./nob crossstage that links every freestanding
object with a program supplying only those four (-nostdlib -nostartfiles -static -lgcc) for
Cortex-M4 and RISC-V.float_format.cno longer callsstrlen, which was the one dependency
outside that set. proven_u8str_view_find_lastmakes the forward search's choices (B-024). The same entropy
sample; an anchored backward scan over a new portableproven_sys_mem_rchron ordinary input;
backward Shift-Or (<= 64 bytes) or a new reverse Two-Way (> 64) on low-entropy input. Measured
withb024-find-last-benchmark.c: about 5x faster on ordinary text for 2-64 byte needles
(0.30 -> 0.055 ns/byte), and the long-needle quadratic tail is gone (a 256-byte needle on a dense
run: 1,994 -> 0.002 ns/byte). Long needles on ordinary text are slower (0.022 -> 0.055), the price
of a portable backward scan. Results unchanged: the oracle agrees on 120,000 cases.
Fixed
tests/test_regression_fs_perms_and_typesbuilds with clang. It passed_Atomic int
objects to the GCC__atomic_*_nbuiltins, which clang rejects; it now uses<stdatomic.h>
atomic_load_explicit/atomic_store_explicit. The full hosted suite passes under clang.
proven_c_lib-v0.2.0
A MINOR release: new public API, nothing removed. UTF-16 text gets a way in and out and UTF-8
shows correctly on a Windows console (B-039); the view vocabulary - split, trim, affixes,
find_last, contains, ordering (RFC-0005, B-018 to B-022); an allocator wrapper that catches the
wrong allocator at the call (B-040); Windows symlinks and the 4 GiB entropy boundary measured and
fixed (B-033); reproducible manual PDFs. Existing behaviour changes only where it was wrong: on a
Windows console, for Windows symlinks, and in proven_time_u16_fmt with non-ASCII locales.
Added
-
utf.h: strict UTF-8 <-> UTF-16 transcoding. Measuring (proven_utf8_to_utf16_size,
proven_utf16_to_utf8_size), fixed-capacity all-or-nothing (proven_utf8_to_utf16,
proven_utf16_to_utf8), partial for text read in pieces (proven_utf8_to_utf16_partial,
proven_utf16_to_utf8_partial, reportingproven_utf_step_t), and growable all-or-nothing
(proven_utf8_append_to_u16str,proven_utf16_append_to_u8str). Malformed input is always
PROVEN_ERR_INVALID_ENCODING- overlongs, encoded surrogates, values above U+10FFFF, stray
continuations, unpaired surrogates - and nothing is repaired. Input cut mid-character is
PROVEN_ERR_NEED_MOREin the partial forms, malformed in the whole forms. -
u16 text through the formatter.
proven_arg_u16, andPROVEN_ARGon a
proven_u16str_view_t, render UTF-8 into every formatter sink:proven_println,
proven_eprintln,proven_fprintlninto any writer,proven_u8str_append_fmt*. Width counts
UTF-8 bytes as for a u8 view; an unpaired surrogate fails the format. -
u16 text through writers and readers (
stream.h).proven_writer_write_u16writes UTF-8,
UTF-16LE or UTF-16BE (proven_text_encoding_t), validated before anything is written;
proven_writer_write_bomwrites a byte order mark only on request.proven_u16_reader_t
(proven_u16_reader_init,_read_line,_read) decodes any of the three encodings from any
reader into a caller-owned buffer of code units, withPROVEN_TEXT_AUTOchoosing by BOM,
carrying a character split across reads, and keeping the byte line reader's newline, full-buffer
and last-line rules. -
sysio u16 line input:
proven_sysio_u16_lines_open,proven_sysio_stdin_u16_lines,
proven_sysio_read_u16_line, withproven_sysio_u16_lines_t.proven_result_u16str_view_tin
u16str.h. -
PAL:
proven_sys_io_is_console,proven_sys_io_console_write_u16,
proven_sys_io_console_read_u16(Windows; POSIX answers "not a console" / unsupported). -
Manual: chapter 3 "Converting between UTF-8 and UTF-16", chapter 5 "UTF-16 text in and out,
and the Windows console", both editions, with runnable examplesex_03_utfandex_05_u16_io. -
b039-console-check.candbuild-b039-check.sh: a native Windows check that
makes its own console in code page 949 and verifies output by reading the screen buffer back
and input by injecting key events. -
alloc_check.h: an allocator that knows its own blocks (B-040).proven_alloc_check_wrap
puts a checker in front of any allocator and records the blocks it hands out in caller-supplied
memory; a foreign free, a double free, a realloc of a foreign block or with the wrong old size or
alignment, and an allocation past the record are refused withproven_panicat the call, and the
refused pointer never reaches the inner allocator.proven_alloc_checkedwraps only where
PROVEN_ALLOC_CHECKis defined (before the first proven header, e.g.-D) and is otherwise the
identity - it is a testing and debugging tool, and the lookup is linear.proven_alloc_check_owns,
proven_alloc_check_live(a leak check). Teststest_unit_alloc_check,test_unit_alloc_check_on;
manual chapter 2 section 7 withex_02_alloc_check, both editions. -
The view vocabulary (RFC-0005; B-018 to B-022). In
u8str.h, all pure and non-allocating,
ill-formed views treated as empty, every empty result{NULL, 0}:
proven_u8str_view_split/_split_nextwithproven_u8str_view_split_t(n separators yield
n + 1 fields; an empty separator yields the input once; the iterator is copyable);
proven_u8str_view_trim,_trim_start,_trim_end(exactly six ASCII whitespace bytes);
proven_u8str_view_remove_prefix/_remove_suffix(unchanged when absent);
proven_u8str_view_find_last(last start position, overlaps counted; size for an empty needle;
byte scan / backward Shift-Or / repeated forward search by needle length) and
proven_u8str_view_contains;proven_u8str_view_cmp/_cmp_ptr(bytewise unsigned, prefix
first, sign only);proven_u8str_view_is_well_formed. Teststest_unit_u8str_view_cmp,
test_unit_u8str_view_ops,test_unit_u8str_split,test_regression_split_empty_sep,
test_differential_find_last_oracle(60,000 cases; planted defects caught). Manual chapter 3
section 1 in both editions withex_03_view_ops. The RFC-0004 benchmark now measures the
shipped iterator: 18.1 ns/field against 16.5 for a correct hand-rolled loop (median of three).
Changed
-
On a Windows console, sysio writes and reads UTF-16.
proven_print/proven_eprint, the
stdout/stderr writers, the stdin reader,proven_sysio_*_buffered, the u8 and u16 line
readers andproven_sysio_scanner_tdetect a console once (GetConsoleMode) and use
WriteConsoleW/ReadConsoleW, converting at the edge. Before, UTF-8 was handed to the
console withWriteFileand shown in the console's code page - mojibake under 949 unless
chcp 65001had been run - and console input came back in that code page. The console's code
page is not changed. Malformed UTF-8 sent to a console is refused after the valid part; a
character split across buffered flushes is carried in the state struct; Ctrl+Z at the start of
a console line is end of input. Files, pipes and redirected streams stay byte-exact; POSIX is
unchanged.proven_writer_from_fileon a console handle stays byte-exact, as documented. -
The allocator pairing of owned strings is now stated as a warning (B-023, owner decision
2026-09-28):u8str.h/u16str.hand manual chapter 3 say that the string does not remember its
allocator and nothing checks it, with a counter-example. No field was added; an allocator-side
ownership check is proposed as B-040. -
proven_sysio_std_tgainsconsoleandcarry(proven_sysio_carry_t);
proven_sysio_scanner_tgains the same two fields. Layout change for code that declares them. -
The manual PDFs are reproducible.
scripts/build-site.shsetsSOURCE_DATE_EPOCHfrom the
commit being built (a caller's own value wins), so the same commit gives byte-identical PDFs -
two full builds matched by SHA-256 for both editions; before, they matched only in size.
scripts/release.shnow compares an existing release asset with the built one by SHA-256
(GitHub's asset digest, or the downloaded asset), not by size.
Fixed
- Code review of the unreleased work (2026-09-28), ten findings:
- A buffered writer's automatic drain flushed the inner writer, and on a Windows console that
flush ended the text: valid UTF-8 split at a buffer boundary came back INVALID_ENCODING with
bytes lost. Drains no longer flush the inner writer, and the console writer's flush keeps an
open character for the next write. Reproduced on the Win11 VM before (FAIL) and fixed after
(win64/win32 18/18). - A non-NULL empty view passed through
remove_prefix,remove_suffixandsplitas{p, 0};
every empty result is now{NULL, 0}as documented. - The Windows symlink kind is decided from a path normalised before any
\\?\prefix.
Defensive: the long-path case the review predicted did not fail on Windows 11 before the fix. proven_u16_reader_tstages 1 KiB with a cursor: about one source read per KiB instead of
one per 64 bytes.utf.h's append functions grow once and convert in place (no chunk copy, no rollback).- One overlap rule (
proven_range_overlaps) for u16 input inutf.hand the formatter; the
formatter used to accept a view running into its output from below. - One padding rule (
spec_padding) for plain, custom and UTF-16 fields. build-b033-check.shprints its report and cleans up when the check fails;release.sh
never deletes an asset because a digest download failed.- New comments are ASCII.
- A buffered writer's automatic drain flushed the inner writer, and on a Windows console that
- Windows symlinks (B-033).
proven_fs_symlinkcreated a link to a directory as a file
link, which cannot be listed or entered, and made every relative target absolute against the
current directory (sub/rel -> tpointed at./t), because the target went through the
helper that callsGetFullPathNameW. The target is now stored as written (with/as\),
the directory flag follows the target as the link resolves it, older Windows without
ALLOW_UNPRIVILEGED_CREATEis retried, and failures arePROVEN_ERR_PERMISSIONor
PROVEN_ERR_NOT_FOUNDwhere they can be told apart (POSIX too), not alwaysPROVEN_ERR_IO.
Measured on the Win11 VM before (7 of 12 failed) and after (win64 12/12, win32 11/11) with
b033-windows-check.c, which also filled a 4 GiB + 4 KiB entropy request across the
32-bit count boundary. proven_time_u16_fmtwidened each UTF-8 byte into a code unit. A caller-supplied locale
with non-ASCII names produced three meaningless units per Hangul syllable; it now transcodes.
Reproduced red first intests/test_unit_time_fmt_u16_parity.
Verification
- New tests:
test_unit_utf(every scalar value; UTF-8 validity against an independent
formulation over every 1-3 byte input; planted defects caught),test_unit_stream_u16,
test_unit_sysio_console(a fake console at every split offset and read size). Debug build:
209 executables. Windows 11 VM, 2026-09-27:b039-check-win64.exeand-win32.exe17/17
each - console output under code p...
proven_c_lib-v0.1.1
A PATCH release: the tutorial gains three lessons, and the hand build the manual prints works
on GCC 14 with glibc. No public API changes.
Added
- Tutorial lessons 7-9 (
manual/manual-t-tutorial.mdand the Korean edition): memory freed
all at once (proven_arena_resetin a per-round loop, andPROVEN_ERR_NOMEMwhen the arena
is too small), a container that keeps its allocator (PROVEN_ARRAY_*growing past its
starting capacity), and failures from outside the program (a whole-file write and read, then
PROVEN_ERR_NOT_FOUNDfor the removed file). Lesson 5 already pointed at lesson 7 for the
arena reset; it now exists. Each lesson is a runnable program in both example trees
(tut_07_arena,tut_08_containers,tut_09_files), so the full run is 84 manual examples
and 202 executables.
Fixed
- The hand build the manual prints now compiles on GCC 14 with glibc. The tutorial and
Chapter 0 givecc -std=c23 -Iinclude your_program.c src/proven/*.c platform/*.cwith no
-Dflags. Under a strict-std, glibc hides the POSIX declarations the PAL uses
(pread,pwrite,ftruncate,clock_gettime,nanosleep,O_CLOEXEC), and GCC 14
treats an implicit declaration as an error, soproven_sys_io.c,proven_sys_random.c
andproven_sys_time.cfailed. Nothing here noticed becausenob.cpasses
-D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809Litself. Every PAL source now requests them
before its first include, asproven_sys_fs.calready did;-Dflags given on the
command line still win.tests/test_portability_source_contractschecks all eight.
proven_c_lib-v0.1.0
The RFC-0008 release: six security and boundary defects fixed, a rule for protected
destinations, errors a caller can act on, and native Windows verified on 64 and 32 bit.
MINOR, not PATCH, because behaviour changes: an atomic write, copy or rename over a
read-only destination is now refused, and several failures that were PROVEN_ERR_IO now
name themselves. v0.0.1 was set on 2026-09-04 and tagged only now, at its own commit; it
was never published as a GitHub release.
Changed
- Windows: an atomic write replaces a file someone is reading, as on POSIX (RFC-0008
Decision 2, option (b), the owner's choice). The rename now tries the POSIX-semantics
rename first (SetFileInformationByHandlewithFileRenameInfoEx,
REPLACE_IF_EXISTS | POSIX_SEMANTICS, Windows 10 1809+): a reader that allowed delete
sharing - whichproven_fs_opendoes - no longer blocks the write, and its open handle
keeps the old bytes. Where Windows or the volume answers "unsupported" (older Windows,
FAT/exFAT, many network shares) it falls back toMoveFileExW, and there the write is
refused withPROVEN_ERR_BUSYas before. A reader that did NOT allow delete sharing
blocks it on every Windows: BUSY. Verified on the Windows 11 VM, x86-64 and i686, plus a
test build that forces the fallback: 41 checks each, none failed. On FAT32 and exFAT
disks attached to the VM the POSIX rename answersERROR_INVALID_PARAMETER, the
fallback runs, and all three builds pass there too (nine runs in all). The same holds on
a Windows SMB network drive (a share on the VM mapped back to it): INVALID_PARAMETER,
fallback, 41 checks each for all three builds. Pre-1809 Windows and a Samba server were
not available to run on.
Fixed
- Windows: a replacement blocked by a file in use now says BUSY, not PERMISSION. Found by
the first run on the Windows 11 test VM (2026-09-11):MoveFileExWanswers
ERROR_ACCESS_DENIEDboth for a read-only destination and for one another process holds
open - with any sharing mode, delete sharing included - and never a sharing violation. The
platform layer mapped that straight to "denied", so an atomic write over a file someone was
merely reading told the caller the file was protected. The Windows rename now asks the file
after a refusal (read-only attribute, then an open for DELETE) and answers
PROVEN_ERR_BUSYfor the in-use case. The branch that expected a sharing violation from
MoveFileExWnever fired; it is kept but documented as such. - Windows: a failed path conversion in rename no longer reports success. The allocation
failure path returnedfalse, which is 0, which isPROVEN_SYS_FS_RENAME_OK. - Verified on the VM, x86-64 and i686, gcc 16.2 mingw static builds: 38 checks each, none
failed. 32-bit Windows is now confirmed rather than explained. - The job-system deadlock fix now has a Windows run.
tests/test_regression_job_permit_starvation
was proven on the POSIX semaphore path only; built statically for x86-64 and i686 and run on
the Windows 11 test VM (theCreateSemaphoreWpath), it passes on both.
Security
-
A staging file is now created private, not narrowed afterwards (RFC-0008 H-002).
Replacing a 0600 file withproven_fs_write_file_atomicorproven_fs_write_file_durable
wrote the new contents into a.pvtmpNNsibling that was created with0666 & ~umask
and narrowed a moment later. Achmoddoes not reach a descriptor another local user
opened during that moment: that descriptor stays open, stays readable, and then reads the
private payload. The staging file is now created owner-only by the creating call itself,
and the target's mode is applied through the open handle rather than by re-resolving the
staging name. A new destination ofproven_fs_copyis created the same way. The process
umask is not touched - it is shared mutable state. -
A failed metadata lookup no longer reads as "no such file" (RFC-0008 H-002).
internal_write_file_atomictreated anystatfailure as a missing target and carried
on with default permissions. It now stops before creating anything unless the target is
genuinely absent. The platform layer gainedproven_sys_fs_stat_checked, which
distinguishes the two; the publicproven_fs_statis unchanged and still answers
PROVEN_ERR_IOfor both. -
Encoding sizes are computed with checked arithmetic (RFC-0008 H-001).
proven_hex_encoded_size,proven_base64_encoded_sizeandproven_base64_decoded_size
multiplied and added inproven_size_tand wrapped at the top of the range - all three
answered 0 for the inputs where they should have said "that does not fit", and 0 passes
every capacity check there is. The encoders repeated the same arithmetic internally, so
fixing the helpers alone would not have protected them: a wrappedneedpassed
need > out_capand the loop then wrote past the caller's buffer. -
A failed atomic write no longer leaves its staging file behind on Windows (RFC-0008
H-005 follow-up, found by the first native run on 2026-09-10). The staging file carries
the target's mode; when that target is read-only, the mode is the READONLY attribute, and
Windows will not delete a read-only file - so the cleanup after a refused replacement
failed silently and the debris stayed. Owner-write is now held back until the payload is
written (it is not a read permission, so nothing about confidentiality changes), the exact
target mode goes on before the rename that publishes the file, and the cleanup path
restores write permission before removing. Found by running the code, not by reading it, and
confirmed by a second native run on the same machine: 31 checks, none failed. -
Windows: an atomic write can replace a file that already exists (RFC-0008 H-005,
first recorded as RFC-0007 C-001).proven_sys_fs_renameusedMoveFileW, which fails
outright when the destination exists - and both whole-file atomic writes rename a staging
file over their target, so on Windows the first write to a name succeeded and every write
after it failed. NowMoveFileExWwithMOVEFILE_REPLACE_EXISTING, without
MOVEFILE_COPY_ALLOWED(a cross-volume copy-and-delete is not atomic) and without
deleting the destination first (that opens an interval in which the name does not exist).
Implemented and cross-compiled for both Windows targets; not run natively, so the
behaviour against a read-only destination, ACLs, sharing modes and symlinks has no result. -
Windows: every requested entropy byte is actually requested (RFC-0008 H-006, first
recorded as RFC-0007 V-003).proven_sys_random_bytescast itssize_tlength once to
theULONGthatBCryptGenRandomtakes. On 64-bit Windows a length aboveULONG_MAX
narrowed silently - a request for exactly 2^32 bytes asked the OS for zero - and the
success of that short request was returned as success for the whole buffer, so a caller
read bytes nothing had written as fresh entropy. The request is now made in chunks the
backend accepts, the pointer advances only after the OS reports success, and a failed
chunk fails the whole call; there is no fallback to a PRNG. Failure may leave a filled
prefix, which the boolean API cannot report, so a caller must discard the whole buffer.
Implemented and cross-compiled; not run natively. -
A protected destination is refused, by every whole-file replacement (RFC-0008
follow-up; the owner's decision, 2026-09-10).proven_fs_write_file,
proven_fs_write_file_atomic,proven_fs_write_file_durableandproven_fs_copynow
returnPROVEN_ERR_PERMISSIONwhen the destination's owner-write bit is clear, and leave
the file exactly as it was. That bit is where both platforms record "do not write this
file": mode0200on POSIX, the READONLY attribute on Windows.It had been three answers to one question on ONE platform, measured:
write_filerefused
(it opens the destination for writing),write_file_atomicsucceeded (renameasks the
DIRECTORY for permission, so the file's mode was never consulted), andcopysucceeded
and left a 0444 file as 0664 - a protection the caller had set, gone, with nothing
saying so. The Windows/POSIX divergence RFC-0008 recorded was the fourth face of the same
unresolved question, not a portability wart.This is a behaviour change. Code that replaced a read-only file through
write_file_atomic,write_file_durableorcopynow getsPROVEN_ERR_PERMISSION; the
caller lifts the mark first, which is one line and visible. In particular the backup loop
intests/test_regression_fs_perms_and_types- copying a read-only source onto the same
destination twice - now fails on the second run. That behaviour was deliberately added
once, and is deliberately reversed here: the failure it produced then was
PROVEN_ERR_IO, which a caller cannot act on, and the cure was stripping the
destination's protection without saying so. It is not a security boundary: the mode is
read before the work and acted on after it, and anyone who canchmodcan lift the mark.
Changed
proven_fs_renameobeys the protected-destination rule, andproven_fs_removedoes
not. An audit of every public door against a0444file found two that did not follow
the rule the rest do.proven_fs_renamereplaced the protected file outright - contents
and mode both - and it is what the atomic write is built on, so a caller refused by
proven_fs_write_file_atomicgot the result fromproven_fs_renameinstead. It refuses
now.proven_fs_removestill deletes: a name is removed from a directory, and POSIX has
never let the file's own mode have a say in that; refusing there would break ordinary
cleanup of read-only files for a rule about writing. Windows does refuse it, and that
difference is now reported asPROVEN_ERR_PERMISSIONins...
proven_c_lib-v26.09.04a
Changed
- The manual's front page is the contents and the copyright, nothing else.
Each edition's index used to be the spine — intent, build model, global
contracts, the ownership matrix, behaviour classes, header map, platform
support — with the table of contents underneath. Now, like the book, the
index is the full contents (every chapter and every section) followed by the
copyright, and the ↑ button in every chapter lands there. The spine moved
into Chapter 0 as §6–§15, after the plain-language sections that always
referred to it as "the formal versions"; appendices B, C and D sit side by
side. Chapter 0's glossary, libc map and closing section renumber to 13, 15
and 16, and every link to the old anchors follows. Chapter 0 joins the
code-block gate innob.cso the five blocks that moved stay checked. docs/index.html, the landing page above both editions, carries the full
contents of both — every chapter and every section, linked into the
edition — instead of two bare language links.scripts/site_root.pywrites
it from the contents each edition's build leaves behind; the link checker
follows its links and the web-font subset includes its characters.
proven_c_lib-v26.09.03a
Added
- A tutorial track for readers who have just finished one C book.
manual/manual-t-tutorial.mdandmanual-ko/manual-t-tutorial-ko.mdteach the
library in six short programs, each introducing exactly one idea — printing,
views, errors, results, allocators — and ending with the Chapter 0 greeting
program read line by line. Chapter 0 shows that program on its first page and
it carries five new ideas at once; the tutorial hands them over one at a time.
All six are real programs the build compiles and runs. scripts/check-example-parity.py— the two example trees may differ in
their comments and in nothing else. It strips comments and string bodies and
compares what is left, andscripts/project-check.shruns it.
Changed
- The manual's examples are now two trees, one per language.
manual/examples/en/is quoted by the English chapters andmanual/examples/ko/
by the Korean ones, so a reader of the Korean manual is not made to read English
comments to follow the code the Korean prose is explaining. All 39 programs are
translated: roughly 1,400 lines of comments, with the code identical in both
trees —scripts/check-example-parity.pyproves that mechanically, and
./nob buildcompiles and runs both trees (78 example executables where there
were 33). - The web edition carries its table of contents in one place. The per-chapter
left sidebar is gone; the index page now lists every chapter and every section
within it. Jumping about inside a chapter is what the panel at the top is for. tests/test_docs_manual_examples.creads the chapter list from the manual
directories instead of a hand-written array. Six examples had become invisible to
it because a chapter added later was never added to that array.- The same gate's quoted-example cap was 64 and silently dropped everything past it;
it is now 512 and overflowing fails the test. A cap that truncates in silence turns
a gate into decoration.
Fixed
.gitattributesmarks*.pdfand*.zipbinary. Regenerating the site made
git diff --check— and thereforescripts/project-check.sh— fail on the
published PDFs.
proven_c_lib-v26.09.02b
Fixed
-
The job system deadlocked under load, and
proven_job_system_destroy
never returned. A permit on the workers' semaphore meant "take exactly one
job", which is sound only if a woken worker can always find the job its permit
announced — and it cannot. The queue hands out its slots in order, so a
producer that has claimed slot n and not yet published it hides slot n+1
from every consumer. The worker woken for n+1 reads an empty queue, spends
the permit, and parks; slot n is published a moment later with no permit
left to announce it.Lose enough of those and the queue stops draining. Because it never empties,
no worker reaches its exit test either, socloseanddestroywait on
threads that will never finish. Measured under parallel load, the stress
harness hung in 18 runs out of 40.A permit now means "there may be work", and a woken worker drains the
queue instead of taking one job from it, so a spent permit cannot strand the
jobs behind it. A departing worker also posts one permit before it leaves, so
shutdown needs one permit to reach every worker rather than exactly one each.
The drain is what fixes the deadlock: with the baton alone the harness still
hung in 18 runs out of 80; with the drain, 400 runs out of 400 passed.
Added
tests/test_regression_job_permit_starvation— six rounds of 24
producers against a four-slot queue, each closing and destroying the system.
A watchdog turns a hang into a reported failure naming the round, because a
deadlock has no wrong answer to assert on: the process simply stops. Verified
to fail against the pre-fix source, five deadlocks in five runs, where the
existing stress harness needed heavy background load to hang at all.