7.2 KiB
Upgrading
0.1.0 → 0.2.0
This is an ABI break and a source break. Pre-1.0 the soname is MAJOR.MINOR, so
libakstdlib.so.0.1 and libakstdlib.so.0.2 are different libraries as far as
the loader is concerned and a consumer built against 0.1 will not silently pick
this up. Rebuild against the new header.
AKSL_VERSION_CHECK() catches the pairing at runtime if a stale .so ever does
end up on the path.
Everything below was recorded in TODO.md before the move to the tracker — the confirmed
defects, all of which were reproduced against the 0.1.0 library before being
fixed.
Behaviour changes with the same signature
aksl_atoi, aksl_atol, aksl_atoll, aksl_atof report bad conversions.
This is the one to look at first, because the compiler will not tell you about
it. They used to return success for anything: aksl_atoi("not a number", &n)
gave you 0, and aksl_atoi("99999999999999999999", &n) gave you a wrapped
value. Now:
| input | before | after |
|---|---|---|
"42" |
success, 42 | success, 42 |
"not a number" |
success, 0 | AKERR_VALUE |
"" or " " |
success, 0 | AKERR_VALUE |
"12abc" |
success, 12 | AKERR_VALUE |
"0x10" |
success, 0 | AKERR_VALUE (base 10 stops at the x) |
"99999999999999999999" |
success, garbage | ERANGE |
*dest is 0 on every failure path. If you were relying on the old silence —
treating an unparseable string as zero on purpose — that is now an error you
have to handle or deliberately ignore.
For "0x10" and friends, use aksl_strtol(nptr, NULL, 0, &dest), which honours
the 0x and 0 prefixes. The whole aksl_strto* family is new and is what the
ato* forms are built on.
Errors no longer carry status 0. Any wrapper that reported errno could
previously raise an error whose status was whatever errno happened to hold,
including 0 — which every DETECT and CATCH downstream reads as success
while the context still holds a pool slot. errno is now cleared before each
wrapped call and read back through a fallback. If you were matching on a
specific status, check it is still the one you get; several calls that used to
report a flat AKERR_IO now report the real errno (a read from a write-only
stream is EBADF, not AKERR_IO).
aksl_malloc(0, &p) is AKERR_VALUE. It used to depend on whether the
platform's malloc(0) returned NULL.
aksl_memcpy refuses overlapping ranges with AKERR_VALUE. Use
aksl_memmove.
aksl_list_append refuses a node already in the list with AKERR_VALUE.
It used to relink it and orphan everything between its old position and the tail.
aksl_list_append and aksl_list_iterate are correct now. If you worked
around either defect — appending one node at a time and fixing up the links by
hand, or starting your own iteration from the head because the callback never
saw it — that workaround is now wrong. append truncated any list of two or
more nodes; iterate skipped everything before the list midpoint.
AKERR_ITERATOR_BREAK stops a tree traversal. It previously did not: the
walk ran to completion regardless. Code that raised it and relied on the
traversal continuing anyway (unlikely, but it was the behaviour) will now stop.
Tree traversal is bounded. A tree deeper than AKSL_TREE_MAX_DEPTH (256) is
AKERR_OUTOFBOUNDS, and a depth-first walk over a tree whose child points back
at an ancestor is AKERR_CIRCULAR_REFERENCE. Both used to run until the process
died.
An unrecognised searchmode is AKERR_VALUE. It used to return success
having visited nothing.
Signature changes
aksl_sprintf is gone. It wrapped vsprintf, which cannot be bounded.
/* before */
aksl_sprintf(&count, buf, "%s=%d", key, value);
/* after */
aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", key, value);
Truncation is AKERR_OUTOFBOUNDS rather than a short success, and *count receives the required length.
aksl_realpath takes the destination's length.
/* before */
char resolved[PATH_MAX];
aksl_realpath(path, resolved);
/* after */
char resolved[PATH_MAX];
aksl_realpath(path, resolved, sizeof(resolved));
buflen must be at least PATH_MAX; realpath(3) cannot be bounded below it,
so a shorter buffer is AKERR_OUTOFBOUNDS rather than an overflow. If you have
no PATH_MAX-sized buffer to hand, aksl_realpath_alloc(path, &dest) allocates
one and hands it over — release it with aksl_free.
aksl_fread and aksl_fwrite take a transferred-count out-param.
/* before */
aksl_fread(buf, 1, sizeof(buf), fp);
/* after */
size_t got = 0;
aksl_fread(buf, 1, sizeof(buf), fp, &got);
It is required, not optional, because it is the only way a caller who gets
AKERR_EOF can find out how much data arrived. It is written on every path
including the failure ones. A short transfer with neither EOF nor a stream error
set is now AKERR_IO rather than success.
aksl_list_pop takes the head by reference.
/* before */
aksl_list_pop(node);
/* after */
aksl_list_pop(&head, node); /* head is updated when node was the head */
Popping the head used to leave the caller's own head pointer aimed at a now-detached node with no way to learn the new one.
aksl_tree_iterate lost its queue parameter.
/* before */
aksl_tree_iterate(root, iter, NULL, NULL, mode, data, NULL);
/* after */
aksl_tree_iterate(root, iter, NULL, NULL, mode, data);
The doc comment told callers to pass NULL, which is a sign the queue belonged
in an internal helper. lalloc and lfree stay, and they are actually used now
— the breadth-first modes are implemented, where before they raised
AKERR_NOT_IMPLEMENTED.
aksl_strhash_djb2 takes const char * and reads bytes as unsigned. Callers
no longer have to cast away constness. The hash value changes for any input
containing a byte ≥ 0x80: it now matches canonical djb2 instead of depending on
whether plain char is signed on the target. 7-bit ASCII keys are unaffected. If
you have persisted djb2 values across a restart, they will not match.
aksl_fopen takes const char * for both pathname and mode, and checks
them — fopen(NULL, ...) is undefined behaviour and used to go straight through.
aksl_memcpy takes const void * for its source.
Additions
Nothing here breaks anything; see README.md for the full surface.
aksl_calloc,aksl_realloc,aksl_freep,aksl_memmove,aksl_memcmp,aksl_memchraksl_snprintf,aksl_vprintf,aksl_vfprintf,aksl_vsnprintfaksl_strtol,aksl_strtoll,aksl_strtoul,aksl_strtoull,aksl_strtod,aksl_strtof,aksl_strtold- the whole of
src/string.candsrc/stream.c aksl_list_prepend/insert_after/insert_before/length/find/reverse/concat/free_all/iterate_reverse, and theaksl_Listcontainer so append is O(1)aksl_tree_insert/find/remove/height/count/free_allaksl_hashmap_*,aksl_strbuf_*,aksl_strhash_fnv1aaksl_list_node_initandaksl_tree_node_init, so a caller no longer has to remember tomemseta node before its first use- the header is
extern "C"-guarded and pulls in four standard headers rather than six