Files
libakstdlib/include/akstdlib.h
Andrew Kesterson 95e5002512
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 2m49s
libakstdlib CI Build / coverage (push) Successful in 2m37s
libakstdlib CI Build / mutation_test (push) Successful in 10m13s
Version the library at 0.1.0
project() now carries VERSION 0.1.0, and is the single place a version number
is spelled. It flows into generated version macros, the shared library's
VERSION/SOVERSION, the Version: field in akstdlib.pc, and a new
akstdlibConfigVersion.cmake. Before this @PROJECT_VERSION@ expanded to
nothing, so akstdlib.pc shipped an empty Version: and libakstdlib.so carried
no soname at all.

0.x on purpose: TODO.md section 2.1 still records four confirmed defects whose
fixes change documented behaviour, so the API is not being promised yet. While
the major version is 0 the soname carries MAJOR.MINOR -- 0.1 and 0.2 are
different ABIs -- and becomes MAJOR alone at 1.0. The if() in CMakeLists.txt
and the #if in tests/test_version.c encode that rule and are tested against
each other.

include/akstdlib_version.h.in is configured into the build tree as
akstdlib_version.h and installed beside akstdlib.h. It defines
AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME.
AKSL_VERSION_NUMBER is computed rather than written as a literal, because a
literal 000100 is octal in C and would make 0.1.0 compare as 64;
test_version.c asserts it against the runtime components, so a rewrite to a
literal fails.

Those macros record what a caller was compiled against. aksl_version(),
aksl_version_string() and aksl_version_soname() report what actually loaded,
and AKSL_VERSION_CHECK() compares the two, raising AKERR_VALUE naming both.
It is a macro so that it expands at the caller's site and captures the
caller's numbers; the function compares them against the ones baked into the
library. Compatibility is "same soname", so patch is ignored -- a caller built
against 0.1.0 keeps working against 0.1.7.

Normally the soname catches a mismatch at load time and the check never fires.
It earns its keep when the soname is bypassed: a 0.2.0 build dropped in under
the 0.1 filename loads happily, and only the check notices.

write_basic_package_version_file() uses SameMinorVersion to mirror the soname,
falling back to ExactVersion below CMake 3.11 where that mode does not exist.
The fallback is stricter than the soname rule -- it pins the patch level too --
but never laxer, and wrongly refusing a good pairing beats wrongly accepting a
bad one.

Coverage of src/stdlib.c rose to 99.1% of lines (217/219), 45.1% of branches
and 25/25 functions. That puts branch coverage back over the old 45 gate, but
the gate stays at 40: 0.1 points of headroom is not a ratchet.

ctest 14/14, ASan+UBSan 14/14, coverage 16/16 at 90/40. Also verified out of
tree: SONAME libakstdlib.so.0.1 recorded in consumers, pkg-config
--modversion reporting 0.1.0, find_package(akstdlib 0.1) accepted with 0.2 and
1.0 refused, a patch-bumped 0.1.1 loading and passing the check, a 0.2.0
dropped in under the 0.1 filename caught by it, and an embedded
add_subdirectory build keeping its own version rather than the parent's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:40:49 -04:00

131 lines
6.5 KiB
C

#ifndef _AKSTDLIB_H_
#define _AKSTDLIB_H_
#include <akerror.h>
/*
* libakerror 1.0.0 is the floor. That release moved the status-name table into a
* private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
* registry entry points raise akerr_ErrorContext * instead of returning int, and
* the library gained an soname -- so a translation unit that pairs this header
* with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
*
* libakerror publishes no version macro, so this feature-tests on
* AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a
* version number that does not exist. Without the guard a stale installed header
* fails much further in, as a pile of unrelated errors inside src/stdlib.c.
*
* See deps/libakerror/UPGRADING.md.
*/
#ifndef AKERR_FIRST_CONSUMER_STATUS
#error "libakstdlib requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
#endif
/*
* AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME.
* Generated by CMake from include/akstdlib_version.h.in, which is why there is
* no such file in the source tree -- it is configured into the build tree and
* installed alongside this header.
*/
#include <akstdlib_version.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
typedef struct aksl_ListNode {
void *data;
struct aksl_ListNode *next;
struct aksl_ListNode *prev;
} aksl_ListNode;
typedef struct aksl_TreeNode {
struct aksl_TreeNode *parent;
struct aksl_TreeNode *left;
struct aksl_TreeNode *right;
void *leaf;
} aksl_TreeNode;
#define AKSL_TREE_SEARCH_BFS 0 /** Breadth-first search mode for tree nodes. Currently unsupported. */
#define AKSL_TREE_SEARCH_BFS_RIGHT 1 /** Right-hand breadth-first search mode for tree nodes. Currentl unsupported. */
#define AKSL_TREE_SEARCH_DFS 2 /** Alias for AKSL_TREE_SEARCH_DFS_PREORDER */
#define AKSL_TREE_SEARCH_DFS_PREORDER 2 /** Depth first pre-order (root, left, right) search mode for tree nodes */
#define AKSL_TREE_SEARCH_DFS_INORDER 3 /** Depth first in-order (left, root, right) search mode for tree nodes. Currently unsupported. */
#define AKSL_TREE_SEARCH_DFS_POSTORDER 4 /** Depth first post-order (left, right, root) search mode for tree nodes. Currently unsupported. */
#define AKSL_TREE_SEARCH_VISIT 5 /** Used when iterating through a tree structure as a control flag: don't traverse the children, just visit the node */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_ListNodeIterator)(aksl_ListNode *node, void *data);
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeNodeIterator)(aksl_TreeNode *node, void *data);
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_AllocFunc)(size_t size, void **dest);
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_FreeFunc)(void *ptr);
/*
* Version of the shared library actually loaded, as opposed to the
* AKSL_VERSION_* macros above, which record what the caller was *compiled*
* against. The two differ exactly when a stale libakstdlib.so is on the loader
* path, which is the failure these entry points exist to name.
*
* All three pointers are required; this library treats a NULL out-param as a
* caller error rather than as "don't care" (so does aksl_free).
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_version(int *major, int *minor, int *patch);
/*
* The loaded library's version as "MAJOR.MINOR.PATCH", and its soname. Neither
* can fail -- they return pointers to string literals in the library's own
* .rodata -- so they return the string directly rather than an error context,
* the same exception akerr_name_for_status() makes for the same reason.
*/
const char *aksl_version_string(void);
const char *aksl_version_soname(void);
/*
* Compare the caller's compiled-in version against the loaded library's.
* Compatibility is defined as "same soname", so pre-1.0 both major and minor
* must match and patch is ignored; from 1.0 only major will matter. Raises
* AKERR_VALUE naming both versions on a mismatch.
*
* Call it through AKSL_VERSION_CHECK() below rather than directly. The macro
* expands at *your* call site, so it captures the AKSL_VERSION_* the caller was
* built with; the function compares them against the values baked into the
* library. Passing the numbers by hand defeats the entire mechanism.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_version_check(int major, int minor, int patch);
#define AKSL_VERSION_CHECK() \
aksl_version_check(AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH)
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(char *pathname, char *mode, FILE **fp);
akerr_ErrorContext AKERR_NOIGNORE *aksl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
akerr_ErrorContext AKERR_NOIGNORE *aksl_fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp);
akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream);
akerr_ErrorContext AKERR_NOIGNORE *aksl_malloc(size_t size, void **dst);
akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n);
akerr_ErrorContext AKERR_NOIGNORE *aksl_memcpy(void *d, void *s, size_t n);
akerr_ErrorContext AKERR_NOIGNORE *aksl_free(void *ptr);
akerr_ErrorContext AKERR_NOIGNORE *aksl_printf(int *count, const char *restrict format, ...);
akerr_ErrorContext AKERR_NOIGNORE *aksl_fprintf(int *count, FILE *restrict stream, const char *restrict format, ...);
akerr_ErrorContext AKERR_NOIGNORE *aksl_sprintf(int *count, char *restrict str, const char *restrict format, ...);
akerr_ErrorContext AKERR_NOIGNORE *aksl_atoi(const char *nptr, int *dest);
akerr_ErrorContext AKERR_NOIGNORE *aksl_atol(const char *nptr, long *dest);
akerr_ErrorContext AKERR_NOIGNORE *aksl_atoll(const char *nptr, long long *dest);
akerr_ErrorContext AKERR_NOIGNORE *aksl_atof(const char *nptr, double *dest);
akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath(const char *restrict path, char *restrict resolved_path);
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(char *str, size_t len, uint32_t *hashval);
// Linked list functions
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_ListNode *obj);
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode *node);
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_ListNodeIterator iter, void *data);
// Tree functions
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_iterate(aksl_TreeNode *root, aksl_TreeNodeIterator iter, aksl_AllocFunc lalloc, aksl_FreeFunc lfree, uint8_t searchmode, void *data, aksl_ListNode *queue);
#endif