|
Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
|
Status: Draft Date: 2026-08-02 Applies to: safeAPIFreamwork, include/safeapi/common/sapi_cast.h
CLAUDE.md prohibits implicit type conversions: "No implicit type conversions (explicit casts are required)." An implicit or unchecked C-style narrowing cast (e.g. uint16_t x = some_uint32;) can silently truncate a value - exactly the class of defect MISRA C:2012's essential type model (Rules 10.1-10.8) targets, and unacceptable in SIL 3/4 code where a truncated value (a timeout, an offset, a message length) can turn into a safety-relevant misbehavior.
This ADR defines how every conversion between the framework's integer types is performed: never with a bare C cast at a call site, always through a named, checked sapi_cast_* function.
A checked function is provided for every ordered pair of the project's integer types, not just the conversions that can lose data. This includes lossless widening (e.g. uint8_t -> uint32_t), so that every conversion site in the codebase looks the same and is equally easy to find, review, and grep for - "was this value ever cast without going through `sapi_cast`" is answerable by searching for raw casts, and that search should have no legitimate hits outside sapi_cast.c itself.
Types covered: int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, and size_t (used pervasively by the framework's own APIs for capacity/length/offset parameters - see sapi_buffer_t, sapi_nvm_read/write, sapi_ipc_send/receive).
9 types x 8 other types = 72 functions, named sapi_cast_<from>_to_<to>, e.g. sapi_cast_u32_to_u16, sapi_cast_i16_to_u8, sapi_cast_size_to_u32. Short type tokens: i8/i16/ i32/i64, u8/u16/u32/u64, size.
Every function reduces the problem to a single comparison against the destination type's _MIN/_MAX limit macros, performed in whichever of int64_t/uint64_t matches the source's signedness (both are wide enough to hold any of the 9 covered types without loss, since none exceed 64 bits):
This is one of four small templates (source {signed, unsigned} x destination {signed, unsigned}), instantiated per pair using each destination type's standard <stdint.h>/<stddef.h> limit macros (INT8_MIN/MAX ... UINT64_MAX, SIZE_MAX). Because the comparison is always performed after widening to a single 64-bit type matching the source's signedness, no branch ever compares a signed value against an unsigned value of possibly-different rank, which is what triggers spurious -Wsign-compare/-Wsign-conversion warnings/UB risk with naive casts.
The project targets C99 (CMAKE_C_STANDARD 99, matching qualified SIL 3/4 toolchains), so _Generic (C11) is unavailable. Even where a toolchain does support it, function-like macros are avoided for this kind of check: macro arguments can be evaluated multiple times (a hazard if a future call site passes an expression with side effects), and 72 explicit, individually named, individually documented functions are easier for a reviewer or a static analysis / MISRA deviation review to reason about one at a time than a generic macro whose behavior depends on the calling context.
The 72 function bodies all follow one of the four templates in 2.3 mechanically - only the destination limit macros and type names change per function. To eliminate the transcription errors that come with hand-typing ~70 near-identical functions, the header and source were produced by a small generator script from that template, then compiled and boundary-value tested. The generator itself is not part of the shipped build; only its plain, ordinary-looking C output is. This is a one-time authoring aid, not a build-time code generation step - the framework's build system does not gain a dependency on it.
Superseded by ADR-007. See below for the original path; the current physical layout is include/safeapi/cast/sapi_cast.h + src/cast/sapi_cast.c, target safeapi::cast.
include/safeapi/common/sapi_cast.h + src/common/sapi_cast.c, added to the existing safeapi_common library target (see ADR-002).