Skip to content

Releases: rubidus-api/proven_c_lib

proven_c_lib-v0.6.0

Choose a tag to compare

@rubidus-api rubidus-api released this 01 Oct 10:21

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_open with PROVEN_FS_CREATE_NEW over an existing name returned PROVEN_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_LAST moves to it; alias XCV_ERR_EXISTS. Code that
    compared that case against PROVEN_ERR_IO must compare against PROVEN_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, and false when there is none
    (RFC-0009 S-002). proven_random_u64 returns 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. Alias xcv_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_all reads 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 returns PROVEN_ERR_OUT_OF_BOUNDS and 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, and proven_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 step PROVEN_ERR_INVALID_STATE instead of skipping
    or repeating entries. Aliases xcv_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_remove and _extend
    (RFC-0009 X-002) - the operations callers wrote by hand on top
    of get_mut and len. Each changes nothing when it fails; insert and extend accept
    elements taken from the array itself (a source that only partly overlaps it is refused), and
    extend reallocates at most once. Aliases xcv_array_*.
  • Jobs: proven_job_submit_ex says why a submit was refused, and job groups can be waited on
    (RFC-0009 X-003). proven_job_submit returns false both for a full queue and for a closed
    system, which call for opposite responses; _ex returns PROVEN_ERR_AGAIN or
    PROVEN_ERR_INVALID_STATE. proven_job_group_t counts submitted jobs
    (proven_job_group_init, _submit, _pending); proven_job_group_wait returns when all have
    run, running queued jobs itself while it waits, with what they wrote visible afterwards.
    Aliases xcv_job_submit_ex, xcv_job_group_*.

Changed

  • SHA-256 is about 25% faster (RFC-0009 P-107): proven_sha256_update compresses 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-going runs 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 N runs 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 asan takes 8.9 s instead of 16.9, a cached ./nob build 7.1 s
    instead of 10.1. The default stays one test at a time.

  • A clean ./nob build takes 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 / *_impl functions 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_read with 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 out cap of them: 261 ns per unit at cap 2. It now examines cap + 1 units: 12 ns per
    unit at cap 2, 2.3 at 64 (from 9.6). The text delivered and the error that ends it are the
    same for every cap.

  • proven_reader_read_line searches 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 platform memchr: 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_t gains a field, scanned, set by proven_reader_buffered.

  • Listing a directory on POSIX costs one metadata call per entry, or none (RFC-0009 P-102).
    proven_fs_dir_next (and so proven_fs_list and the walk) asked for every entry's type twice,
    following and not following symlinks. readdir's d_type already 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 leaves d_type unknown 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=1 keeps 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_trusted keeps 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_hash values 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 now PROVEN_ERR_INVALID_ENCODING. Before, POSIX returned the raw
    bytes as PROVEN_OK and Windows silently substituted U+FFFD, which in a listing named a
    different file, or none. proven_fs_dir_next and proven_fs_walk_next report such an entry
    once (empty name, other fields filled in) and go on; proven_fs_list refuses 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 in nob.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 in nob.c.

Fixed

  • **`./nob...
Read more

proven_c_lib-v0.5.0

Choose a tag to compare

@rubidus-api rubidus-api released this 01 Oct 07:01

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 of utf.h; the returned len is 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). 0x is taken only before a digit, as strtoul takes it; overflow,
    cursor restore and the stream signal behave as for decimal, including a 0x that arrives before
    its digits. Before, every format placeholder had to be {}.
  • The error-code space is a promise: PROVEN_ERR_RESERVED_END (0x1000) and PROVEN_ERR_LAST.
    proven will never define a code at or above 0x1000, so a program can carry proven_err_t values
    unchanged in a wider type and number its own codes from there (requested by Pnakotic).
    PROVEN_ERR_LAST names 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. Aliases XCV_ERR_LAST,
    XCV_ERR_RESERVED_END, and the missing XCV_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 under docs/ 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_t without PROVEN_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

Choose a tag to compare

@rubidus-api rubidus-api released this 30 Sep 14:34

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-run builds every test
    executable and runs none; the build driver asks the compiler for its target, so a mingw-w64
    cross compiler produces static .exe tests (with -lbcrypt, without -ldl).
    win11kd-full-suite.sh and win11kd-run-suite.ps1 build 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: %.6g 83 -> 81 ns (0.58x), ~16 digits 186 -> 145 ns (0.72x), %.17g 224 -> 152 ns
    (0.75x, was 1.13x), 25 digits ~640 -> 197 ns (0.86x, a new corpus). Results are unchanged:
    every row is checked against strtod in the same run, and 60 million generated inputs - random
    significands and exponents, %.15g-%.40g of random doubles, integers above 2^53, subnormal
    and overflow edges - matched strtod bit for bit, with and without 128-bit integers. The
    cached powers come from scripts/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 has unsigned __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 host strtod in the same run, and the Windows i686 run
    passes the differential corpora and expects the fast path in test_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_remove deletes an empty directory, as POSIX remove() does. It called
    DeleteFileW only, which refuses a directory, and reported PERMISSION.
  • Windows: proven_fs_pread and proven_fs_pwrite no longer move the file position. A
    positioned ReadFile/WriteFile on 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, pread and pwrite
    used SetFilePointerEx / an OVERLAPPED offset on a pipe, which Windows does not support, and
    proven_sysio_scan_chunk therefore accepted a pipe it must refuse. The handle type is now
    checked first (FILE_TYPE_DISK), matching POSIX ESPIPE.
  • Windows: the end of a pipe is EOF. ReadFile reports a closed writer as ERROR_BROKEN_PIPE,
    which came back as PROVEN_ERR_IO - so producer | program ended in an error. Both read paths
    now map it to PROVEN_ERR_EOF, as read(2) returning 0 does.
  • Tests and examples that assumed POSIX or a 64-bit target. Permission checks compare with the
    mode read back after chmod (Windows keeps only the owner-write bit); the durable-write example
    accepts sync_dir's documented UNSUPPORTED on Windows; test_unit_u128_mul and
    test_unit_float_bigint_divmod have references without __int128; a float comparison
    uses a stored double, not a literal that x87 evaluates in long double; a fixture no longer
    needs a build/ directory.
  • scripts/release.sh builds a release's PDFs with one date. The site is built at the release
    commit and again by --publish at 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

Choose a tag to compare

@rubidus-api rubidus-api released this 29 Sep 14:45

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) and tests/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. Editing include/proven/job.h now
    recompiles job.c alone (1 of 36 objects; the tests relink because they link every object), and
    editing manual/examples/example.h relinks 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 -ldl is linked only into the test that interposes
    libc with dlsym(RTLD_NEXT, ...) instead of every POSIX test (B-035). The first build after
    updating rebuilds everything once.
  • ./nob clean removes the selected build root, safely (B-036). It honours -build-root and
    PROVEN_BUILD_ROOT instead of always running rm -rf build / rmdir /s /q build through the
    shell, deletes without following symlinks or junctions, refuses ., /, any .. component
    and unusual characters, and removes a root other than the default build only when it carries
    the .proven-build-root marker nob now writes into every build root it creates. Compatibility
    note: a custom build root created by an older nob has no marker, so clean refuses 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 with b041-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, and test_bench_job prints the
    host's logical CPU count.
  • ./nob release defines NDEBUG; new ./nob hardened keeps 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 with hardened (-O2 -DNDEBUG -DPROVEN_HARDENED=1) to keep them in an optimised build, and use alloc_check.h in 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/%e faster 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.md updated.
  • 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 by project-check, fails on any new non-ASCII byte
    in that scope; Hangul is exempt, and the Korean mirrors are out of scope.
  • ./nob cross reports 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. Skipping native-gcc-hosted, native-clang-hosted, windows-x86_64-winapi
    or windows-i686-winapi fails 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, memcmp and the compiler support library, nothing else - stated
    in the freestanding guide and proven by a new ./nob cross stage that links every freestanding
    object with a program supplying only those four (-nostdlib -nostartfiles -static -lgcc) for
    Cortex-M4 and RISC-V. float_format.c no longer calls strlen, which was the one dependency
    outside that set.
  • proven_u8str_view_find_last makes the forward search's choices (B-024). The same entropy
    sample; an anchored backward scan over a new portable proven_sys_mem_rchr on ordinary input;
    backward Shift-Or (<= 64 bytes) or a new reverse Two-Way (> 64) on low-entropy input. Measured
    with b024-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_types builds with clang. It passed _Atomic int
    objects to the GCC __atomic_*_n builtins, 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

Choose a tag to compare

@rubidus-api rubidus-api released this 28 Sep 03:57

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, reporting proven_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_MORE in the partial forms, malformed in the whole forms.

  • u16 text through the formatter. proven_arg_u16, and PROVEN_ARG on a
    proven_u16str_view_t, render UTF-8 into every formatter sink: proven_println,
    proven_eprintln, proven_fprintln into 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_u16 writes UTF-8,
    UTF-16LE or UTF-16BE (proven_text_encoding_t), validated before anything is written;
    proven_writer_write_bom writes 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, with PROVEN_TEXT_AUTO choosing 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, with proven_sysio_u16_lines_t. proven_result_u16str_view_t in
    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 examples ex_03_utf and ex_05_u16_io.

  • b039-console-check.c and build-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 with proven_panic at the call, and the
    refused pointer never reaches the inner allocator. proven_alloc_checked wraps only where
    PROVEN_ALLOC_CHECK is 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). Tests test_unit_alloc_check, test_unit_alloc_check_on;
    manual chapter 2 section 7 with ex_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_next with proven_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. Tests test_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 with ex_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 and proven_sysio_scanner_t detect a console once (GetConsoleMode) and use
    WriteConsoleW/ReadConsoleW, converting at the edge. Before, UTF-8 was handed to the
    console with WriteFile and shown in the console's code page - mojibake under 949 unless
    chcp 65001 had 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_file on 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.h and 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_t gains console and carry (proven_sysio_carry_t);
    proven_sysio_scanner_t gains the same two fields. Layout change for code that declares them.

  • The manual PDFs are reproducible. scripts/build-site.sh sets SOURCE_DATE_EPOCH from 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.sh now 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_suffix and split as {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_t stages 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 in utf.h and 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.sh prints its report and cleans up when the check fails; release.sh
      never deletes an asset because a digest download failed.
    • New comments are ASCII.
  • Windows symlinks (B-033). proven_fs_symlink created 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 -> t pointed at ./t), because the target went through the
    helper that calls GetFullPathNameW. 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_CREATE is retried, and failures are PROVEN_ERR_PERMISSION or
    PROVEN_ERR_NOT_FOUND where they can be told apart (POSIX too), not always PROVEN_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_fmt widened 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 in tests/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.exe and -win32.exe 17/17
    each - console output under code p...
Read more

proven_c_lib-v0.1.1

Choose a tag to compare

@rubidus-api rubidus-api released this 21 Sep 06:47

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.md and the Korean edition): memory freed
    all at once (proven_arena_reset in a per-round loop, and PROVEN_ERR_NOMEM when 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_FOUND for 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 give cc -std=c23 -Iinclude your_program.c src/proven/*.c platform/*.c with no
    -D flags. 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, so proven_sys_io.c, proven_sys_random.c
    and proven_sys_time.c failed. Nothing here noticed because nob.c passes
    -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L itself. Every PAL source now requests them
    before its first include, as proven_sys_fs.c already did; -D flags given on the
    command line still win. tests/test_portability_source_contracts checks all eight.

proven_c_lib-v0.1.0

Choose a tag to compare

@rubidus-api rubidus-api released this 11 Sep 11:29

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 (SetFileInformationByHandle with FileRenameInfoEx,
    REPLACE_IF_EXISTS | POSIX_SEMANTICS, Windows 10 1809+): a reader that allowed delete
    sharing - which proven_fs_open does - 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 to MoveFileExW, and there the write is
    refused with PROVEN_ERR_BUSY as 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 answers ERROR_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): MoveFileExW answers
    ERROR_ACCESS_DENIED both 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_BUSY for the in-use case. The branch that expected a sharing violation from
    MoveFileExW never fired; it is kept but documented as such.
  • Windows: a failed path conversion in rename no longer reports success. The allocation
    failure path returned false, which is 0, which is PROVEN_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 (the CreateSemaphoreW path), it passes on both.

Security

  • A staging file is now created private, not narrowed afterwards (RFC-0008 H-002).
    Replacing a 0600 file with proven_fs_write_file_atomic or proven_fs_write_file_durable
    wrote the new contents into a .pvtmpNN sibling that was created with 0666 & ~umask
    and narrowed a moment later. A chmod does 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 of proven_fs_copy is 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_atomic treated any stat failure 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 gained proven_sys_fs_stat_checked, which
    distinguishes the two; the public proven_fs_stat is unchanged and still answers
    PROVEN_ERR_IO for both.

  • Encoding sizes are computed with checked arithmetic (RFC-0008 H-001).
    proven_hex_encoded_size, proven_base64_encoded_size and proven_base64_decoded_size
    multiplied and added in proven_size_t and 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 wrapped need passed
    need > out_cap and 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_rename used MoveFileW, 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. Now MoveFileExW with MOVEFILE_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_bytes cast its size_t length once to
    the ULONG that BCryptGenRandom takes. On 64-bit Windows a length above ULONG_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_durable and proven_fs_copy now
    return PROVEN_ERR_PERMISSION when 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": mode 0200 on POSIX, the READONLY attribute on Windows.

    It had been three answers to one question on ONE platform, measured: write_file refused
    (it opens the destination for writing), write_file_atomic succeeded (rename asks the
    DIRECTORY for permission, so the file's mode was never consulted), and copy succeeded
    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_durable or copy now gets PROVEN_ERR_PERMISSION; the
    caller lifts the mark first, which is one line and visible. In particular the backup loop
    in tests/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 can chmod can lift the mark.

Changed

  • proven_fs_rename obeys the protected-destination rule, and proven_fs_remove does
    not.
    An audit of every public door against a 0444 file found two that did not follow
    the rule the rest do. proven_fs_rename replaced 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_atomic got the result from proven_fs_rename instead. It refuses
    now. proven_fs_remove still 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 as PROVEN_ERR_PERMISSION ins...
Read more

proven_c_lib-v26.09.04a

Choose a tag to compare

@rubidus-api rubidus-api released this 03 Sep 22:48

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 in nob.c so 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.py writes
    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

Choose a tag to compare

@rubidus-api rubidus-api released this 03 Sep 02:12

Added

  • A tutorial track for readers who have just finished one C book.
    manual/manual-t-tutorial.md and manual-ko/manual-t-tutorial-ko.md teach 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, and scripts/project-check.sh runs it.

Changed

  • The manual's examples are now two trees, one per language.
    manual/examples/en/ is quoted by the English chapters and manual/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.py proves that mechanically, and
    ./nob build compiles 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.c reads 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

  • .gitattributes marks *.pdf and *.zip binary. Regenerating the site made
    git diff --check — and therefore scripts/project-check.sh — fail on the
    published PDFs.

proven_c_lib-v26.09.02b

Choose a tag to compare

@rubidus-api rubidus-api released this 02 Sep 13:36

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, so close and destroy wait 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.