MISRA C:2012 deviations¶
SolidSyslog is MISRA-informed, not certified-compliant. The project
adopts a curated subset of MISRA C:2012 rules per rigour level (see
docs/NAMING.md for the levels). This document records every
deliberate deviation from a rule the project otherwise enforces, and every
suppression that is not a deviation at all.
Those are two different things and each entry says which it is. A deviation is a departure: the code does something the guideline forbids, and the entry justifies it. A tool limitation is not: the code complies, cppcheck-misra reports a finding anyway, and the entry explains why the report is wrong. Both are recorded here because a suppression exists either way, and the authorisation for a suppression belongs in one place. Counting the entries is therefore not a count of departures; each entry states which kind it is. Where the distinction does not matter, the words deviation and entry are used interchangeably below.
This register is published as evidence of process, not as a compliance submission. It exists so that an integrator building SolidSyslog into a MISRA-constrained product can see where the code knowingly departs from a guideline, and judge each departure against their own risk posture. It makes no conformance claim on your behalf, and it is an input to your compliance documentation rather than a substitute for it.
MISRA and MISRA C are registered trade marks of The MISRA Consortium Limited, used here for identification only. This project is neither endorsed by nor affiliated with MISRA.
Each deviation is paired with a matching entry in misra_suppressions.txt
(the cppcheck-misra input). The files are complementary:
| File | Audience | Purpose |
|---|---|---|
misra_suppressions.txt |
cppcheck-misra | Machine-readable suppressions per rule / file / line |
docs/misra-deviations.md |
Reviewers, auditors, integrators | Why each deviation exists, with rationale, scope, approval |
misra_suppressions.txt is the authoritative instance-level trace: its entries
are line-specific, so every individual site a deviation authorises appears there
by rule, file and line, and each block back-references the deviation in this
document that authorises it. A finding on a line not listed is not covered by
any deviation and surfaces in CI. The suppressions file was populated
under S10.06
after the rule subset was curated; before then it carried only a
header comment.
Each entry follows a fixed shape: the guideline and its category, the construct at issue, the scope the entry covers, the engineering rationale, the residual risk and how it is mitigated, and a named approval with its dates. The register follows the deviation record structure of MISRA Compliance:2020 (Section 4, example record at Appendix B), adopted voluntarily; it is mandatory only from MISRA C:2023 onward.
Every deviation here is raised and approved by the same person. SolidSyslog is developed by a one-person consultancy, so the project owner holds the designated technical authority for these decisions; there is no second reviewer and the record does not pretend otherwise. An integrator running their own compliance process should re-review each deviation against their own risk posture rather than inheriting this approval.
Guideline text is not reproduced here¶
Each entry identifies its guideline by number and category and then describes the construct in SolidSyslog that the finding lands on - not what the guideline says. MISRA C:2012 is copyrighted and not redistributable, so its rule text, amplification and examples are omitted deliberately rather than by oversight. Read them in your own licensed copy, available from misra.org.uk; Appendix A lists every guideline with its category.
Nothing is lost by this. A deviation record exists to show that the project understood the guideline and reasoned about the risk of departing from it, and a precise description of our own code demonstrates that better than a restated headline an assessor already has in front of them.
Language edition for clause references¶
Unless otherwise stated, all clause references are to ISO/IEC 9899:1999 (C99).
C99 is the conformance baseline - the edition the source is written against and
claims to be valid under. It is not the same thing as the build configuration:
the default build selects C11 (CMAKE_C_STANDARD is 11 unless overridden), and
the build-linux-c99 lane verifies the baseline on every pull request. The claim
is about Core/: the lane builds Core alone at strict -std=c99, and the
platform packs are a best-effort drift check rather than a guarantee - see
builds.md. Where a deviation concerns a code path
that only a later edition compiles, that entry names the applicable edition and
cites both.
Paragraph numbering differs between editions - section 6.7.2.1 renumbered when C11 added anonymous structure and union members - so a C11 paragraph number read against a C99 copy lands on the wrong sentence. That is why the edition is fixed here rather than left to the reader.
D.001 - Rule 5.1 external identifier uniqueness relaxed to 63 characters¶
Guideline¶
MISRA C:2012 Rule 5.1 - Required. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
SolidSyslog requires external identifiers to be distinct within their first 63 characters rather than their first 31.
Thirty-one is the floor the C language sets: C99 section 5.2.4.1 guarantees only 31 significant characters in an external identifier, so a conforming compiler is permitted to treat two identifiers that agree that far as the same identifier. The project asserts a longer guarantee than the language requires, and states the number rather than leaving it implicit.
Every toolchain this project builds and tests on resolves external identifiers well past 63 characters, and the table under Rationale below records what each of them documents. Yours may not be among them: if you build SolidSyslog with a different compiler or linker, confirm its significant-character limit before relying on this deviation.
Scope¶
- Strict level -
Core/Interface/,Core/Source/,Platform/*/Interface/ - Pragmatic level -
Platform/*/Source/
The deviation does not apply to the Consistency-only or Out-of-scope levels (rule 5.1 is not enforced there at all).
Rationale¶
The C99 31-character limit is a legacy linker artifact from the late 1980s. No toolchain SolidSyslog targets imposes anything close to it:
| Toolchain | External identifier behaviour |
|---|---|
GCC (incl. arm-none-eabi-gcc) |
No compiler-imposed limit; identifier length is delegated to the target's linker, and all characters are significant on every linker SolidSyslog targets (ld, gold, lld, link.exe). See GCC manual, "Implementation-defined behavior". |
| Clang / LLVM (incl. Arm Compiler 6 / armclang) | Same rule as GCC for external identifiers - no compiler-imposed limit. |
| MSVC | Documented maximum identifier length 2,047 characters (Microsoft Learn - C Identifiers). CI builds with the windows-latest toolchain; older MSVC releases are not tested. |
| IAR Embedded Workbench, Keil ARMCC 6 | Not built in CI. Identifier limits are documented per compiler SKU; confirm against your SKU's reference at port time. |
The external-linkage naming scheme in docs/NAMING.md (form
SolidSyslogClass_Function) routinely produces identifiers in the
30-40 character range - SolidSyslogPlusTcpResolver_Create is
40, SolidSyslogPlusTcpTcpStream_Destroy is 36 - and a few public
storage-size enums sit just below 40 (e.g.
SOLIDSYSLOG_HMAC_SHA256_POLICY_POOL_SIZE, 40). Strict 31-character
distinctness would either collapse identifier pairs that read
identically up to a trailing word (SolidSyslogPlusTcpResolver_Create
vs SolidSyslogPlusTcpResolver_Destroy) into a single name, or force
unidiomatic abbreviation throughout the public
API. Neither outcome serves clarity, and neither preserves the
distinguishability the guideline exists to protect. Sixty-three characters
preserves both.
63 was chosen rather than "unlimited" so the project still names a concrete number that every targeted toolchain comfortably exceeds. It also matches C99's separate 63-character minimum for internal identifiers (section 5.2.4.1) - a single number applies project-wide.
Risk and mitigation¶
- Portability - Constrained to toolchains that support ≥ 63 significant characters in external identifiers. The table above states which of those are proven by CI and which rest on documentation; adding a target requires verifying the constraint on it.
- Tooling - cppcheck-misra applies its default 31-character window for rule 5.1. The deviation only matters when a real collision would resolve at 63 characters but not at 31 - at which point the project would suppress that specific finding with a rationale tying back to this section. Currently no rule 5.1 collisions occur, so no cppcheck-misra configuration change is required; the enforcement window is strictly stricter than the deviation allows, which is the safe direction. (Decision recorded under S10.06.)
- Review - The naming scheme itself (see
docs/NAMING.md, External linkage) builds in aSolidSyslogprefix and aClass_Functionshape that makes accidental 63-character collisions extremely unlikely. The static-analysis gate exists to catch any that slip in.
Approval¶
Raised and approved 2026-05-14 by the project owner, David Cozens. Recorded as the founding entry in this document under S10.01.
D.002 - Rules 11.2 / 11.3 / 11.5: vtable downcasts + Formatter¶
Guidelines¶
- MISRA C:2012 Rule 11.2 - Required.
- MISRA C:2012 Rule 11.3 - Required.
- MISRA C:2012 Rule 11.5 - Advisory.
Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
SolidSyslog accepts two structural pointer conversions that are
identified in code as SelfFromBase (vtable) or (struct X*) storage
(Formatter). Both are reviewed once here, not per call site.
(a) Vtable / opaque-handle downcasts - every pool-allocated class¶
Every implementation class that participates in a vtable interface
(SolidSyslogBuffer, SolidSyslogSender, SolidSyslogStream,
SolidSyslogDatagram, SolidSyslogStore, SolidSyslogMutex,
SolidSyslogFile, SolidSyslogBlockDevice, SolidSyslogAtomicCounter,
SolidSyslogResolver, SolidSyslogStructuredData,
SolidSyslogSecurityPolicy) carries a static inline ... *SelfFromBase(...)
helper that downcasts the public base pointer back to the concrete
implementation struct so vtable methods can reach their own state:
static inline struct SolidSyslogCircularBuffer*
CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base)
{
return (struct SolidSyslogCircularBuffer*) base;
}
The same structural cast applies to SolidSyslogAddress - a pool-
allocated handle whose struct SolidSyslogAddress is an incomplete
public type, fully defined per platform as
struct SolidSyslog{Posix,Winsock,FreeRtos}Address. Each platform's
*AddressPrivate.h carries downcast accessors
(SolidSyslog<Plat>Address_AsSockaddrIn /
SolidSyslog<Plat>Address_AsConstSockaddrIn /
SolidSyslog<Plat>Address_AsFreertosSockaddr /
SolidSyslog<Plat>Address_AsConstFreertosSockaddr) plus a
HandleFromIndex(size_t) helper in *AddressStatic.c that converts a
pool slot index back to the public handle type. Rule 11.3 fires on
every such cast, and rule 11.2 fires alongside it here but not on the
other vtable classes: struct SolidSyslogAddress is deliberately an
incomplete type in the public header, so these conversions involve a
pointer to an incomplete type as well as to a different object type.
This is the standard OO-in-C "interface pointer back to derived implementation" cast.
(b) SolidSyslogFormatter - variable-size stack builder¶
SolidSyslogFormatter is a transient stack-built builder whose backing
storage is sized at the call site via the
SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(n) macro. Variable-size means it
cannot fit the fixed-pool pattern used elsewhere in the library - its
lifecycle is fundamentally per-call, not per-class. Rules 11.2 / 11.3
fire on the cast between SolidSyslogFormatterStorage* and struct
SolidSyslogFormatter*.
(c) Third-party callback void* arg - SelfFromArg and byte-buffer reinterprets¶
Several wrapped libraries expose callback-style APIs where we register a
function pointer plus an opaque void* context that the library passes
back to us when it invokes the callback. mbedTLS's mbedtls_ssl_set_bio
hands us back void* ctx in BioSend / BioRecv; lwIP Raw's
tcp_arg(pcb, self) hands us back void* arg in every tcp_recv /
tcp_err / tcp_connected / tcp_sent callback we registered. The
implementation has to cast that void* back to the concrete
implementation struct to do any work - Rule 11.5 (advisory) fires on
every such cast.
This is structurally the same OO-in-C downcast as (a) - the library API
is the "base" type (void*), our struct is the "derived" type - just
happening at the callback boundary rather than the vtable-method
boundary. Each affected wrapper concentrates the cast in a single
SelfFromArg-style helper so the suppression has one site per class,
not one per callback (see LwipRawTcpStream_SelfFromArg for the
canonical shape).
A second 11.5 sub-case the library leans on is the
void* ↔ const uint8_t* cast in SolidSyslogUdpSender when trimming
codepoint-boundary bytes from the caller's const void* buffer, a
byte-buffer reinterpretation that crosses the same advisory rule. The
third-party API contract (the public Send / SendTo interface) is
void* for opacity; the byte-level work needs a concrete unit type.
Scope¶
- Strict level -
Core/Source/: theSelfFromBasehelpers on every vtable class, and the Formatter storage cast of sub-case (b). - Pragmatic level -
Platform/*/Source/: the sameSelfFromBaseshape in each adapter, the per-platform Address downcasts, and the callbackvoid*casts of sub-case (c). Every pack that declares a vtable class is in scope.
The deviation does not extend to Tests/ or Bdd/, where these rules are not
enforced. misra_suppressions.txt is the count: every authorised site is a line
in this deviation's block, so read it there rather than from a figure here that
a new vtable class would silently falsify.
A new class added to any vtable role inherits this shape, and its suppressions
belong in this block; adding them is a review step, not an automatic
consequence, because the reviewer has to confirm the new site really is the
SelfFromBase pattern and not a different conversion wearing the same name.
Rationale¶
Every class with a Create/Destroy lifecycle uses the static pool allocator rather than caller-supplied storage, so the only structural pointer conversions are the vtable / opaque-handle downcast (required by the OO-in-C interface decoupling) and the one non-pool exception above (Formatter as a per-call builder). Both would otherwise require either dynamic allocation (not available on bare-metal / FreeRTOS-static- allocation / DO-178C-style targets - the library is callable from boot before any heap exists) or leaking the implementation struct through the public API (breaks ABI stability and the embedded-friendly opaque-type design).
Risk and mitigation¶
- Type safety - For (b) Formatter, a
_Static_assertimmediately below the impl definition pins the relationship between the public storage type and the private impl struct at build time. An integrator who allocates undersized storage is caught at compile time. For (a) vtable / opaque-handle downcasts, type safety is enforced by the contract that vtable methods are only called via the vtable installed in their ownSelfFromBase-aware implementation; the per-platform Address downcast is similarly locked down because the pool slot is statically-typedstruct SolidSyslog<Plat>Address, so the cast back from the opaquestruct SolidSyslogAddress*cannot lie. For (c) callbackvoid*args, the pointer that goes out via the registration call (e.g.tcp_arg(pcb, self),mbedtls_ssl_set_bio(..., self, ...)) is the same pointer that comes back - the library is a pass-through; the cast can only succeed against the type the wrapper passed in. - Validity of the conversion, sub-cases (a) and (c) - These do not
rest on an alignment argument at all. The public base struct is the
first member of the concrete struct, and section 6.7.2.1 ¶13 guarantees that
a pointer to a structure object, suitably converted, points to its
initial member and back again. The address is the same address by
definition, so no alignment question arises. Sub-case (c) is the same
guarantee reached through the library's own
void*round trip: the pointer that comes back is the one that went out. - Alignment, sub-case (b) only - The Formatter is the case where
alignment is the load-bearing argument, because its storage is a
caller-declared array rather than a struct whose first member is the
base. Storage is declared as
intptr_t storage[N](or a struct of the same shape), giving alignment at least as strict as any pointer or scalar the impl contains. - Static analysis - These rules are advisory (11.5) or required
(11.2, 11.3). All current findings are suppressed via
misra_suppressions.txtreferencing this section. The pattern is reviewed once here, not per call site.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06; scope narrowed under S11.11 once every Create-lifecycle class moved off caller-supplied storage onto the pool allocator; further narrowed under S24.07 once Address itself moved onto per-platform pool classes - the casts are now the same OO-in-C downcast that authorised (a), not a separate caller-supplied-storage exception.
D.003 - Rule 5.7: repeating struct tags (no-typedef-struct convention)¶
Guideline¶
MISRA C:2012 Rule 5.7 - Required. Rule text: not reproduced (see above). Classification: Tool limitation - the code complies; cppcheck-misra reports a finding regardless.
cppcheck-misra interprets Rule 5.7 strictly - every repeated struct X
declaration counts as a non-unique tag, including forward declarations
in headers and the matching definition in source.
Construct¶
SolidSyslog uses struct SolidSyslogX directly throughout the public
API and source rather than typedef'ing it (see docs/NAMING.md,
"No struct typedefs"). Each public class therefore necessarily
repeats its tag at every forward-declaration and definition site.
Scope¶
- Strict level - every public
struct SolidSyslogXdeclared as an incomplete type in a header (SolidSyslogBuffer.h,SolidSyslogStore.h,SolidSyslogFile.h, etc.) and re-declared with full body in the matching source file. - Pragmatic level - same pattern across all
Platform/*/Source/classes.
Rationale¶
The no-typedef-struct convention serves two goals that survive unchanged from C89 onwards:
- Discoverability at the call site. A reader of
SolidSyslog_Create(struct SolidSyslogConfig*)sees immediately thatSolidSyslogConfigis a struct, not a typedef'd enum, integer, or function pointer. Tag-prefixed names act as a one-character type marker. - Forward-declaration freedom. A header that needs to mention
struct SolidSyslogX*does not have to include the header that defines the typedef - it just forward-declares the struct. The alternative (typedef pulls in the body) creates header dependency cycles in the vtable-rich Core.
Both goals depend on the tag being identical in the forward declaration and in the definition. The repetition is the convention, not a defect.
Risk and mitigation¶
- Genuine name collisions. Distinct from this deviation: a real
collision (two different
struct Xdefinitions with the same tag) is a code defect. Rule 5.7 surfaces those too, and the deviation scope is therefore limited to "repetition of the same tag across forward declaration and definition." Per-site review catches genuine collisions; the project'sSolidSyslog-prefix convention makes them statistically unlikely.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
VxWorks 6.4 sites (sub-case (c)) added 2026-09-30, approved by the project owner, David Cozens. Recorded under S41.03.
D.004 - Rule 18.4: pointer arithmetic on record buffers (retired)¶
Retired in S10.19. This deviation authorised uint8_t* pointer arithmetic in
Core/Source/SolidSyslogRecordStore.c, where four field-offset helpers walked the
[magic][length][message] record layout by adding an offset to a base address.
S10.19 rewrote them to take the address of an indexed element (&base[OFFSET])
instead. That is the same address by definition, but rule 18.4 fires on the +,
-, += and -= operators specifically rather than on subscripting, so the
finding no longer arises and the deviation had nothing left to authorise.
cppcheck-misra reported no 18.4 finding in SolidSyslogRecordStore.c, and the
suppression was removed from misra_suppressions.txt at the same time.
The entry is kept, rather than the number reused, so the register has no gaps and a reader of an older revision can still resolve D.004. Raised 2026-05-14 and approved 2026-05-15 by the project owner, David Cozens, alongside the other founding entries; retired 2026-05-23 under #436.
D.005 - Rule 18.7: flexible array members¶
Guideline¶
MISRA C:2012 Rule 18.7 - Required. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
struct SolidSyslogFormatter ends with a flexible array member that
holds the caller-supplied backing storage:
Scope¶
One class only:
Core/Source/SolidSyslogFormatter.c
SolidSyslogCircularBuffer does not use this shape - its instance
struct holds an external ring pointer rather than a trailing FAM.
Rationale¶
The Formatter implements the variable-size variant of the
caller-supplied-storage pattern (D.002). The calling translation unit
declares a storage buffer of arbitrary size (with a minimum enforced by
_Static_assert), and the class lives inside that storage -
bookkeeping fields at the start, payload bytes filling the rest.
The flexible array member is C99's standard mechanism for exactly this shape (section 6.7.2.1 ¶16). The alternatives all regress:
| Alternative | Why rejected |
|---|---|
| Pointer to separately-allocated payload | Re-introduces dynamic allocation or a second storage parameter. |
Fixed-size payload (char buffer[MAX]) |
Forces every integrator to pay for the worst-case footprint. |
Trailing char buffer[1] "struct hack" |
Pre-C99 idiom, technically UB; flexible array members exist precisely because the hack was unsafe. |
Risk and mitigation¶
- Compiler support. GCC, Clang and the ARM cross-compilers accept
the construct as the C99 feature it is, and CI compiles it on every
push. MSVC compiles it too, but reports C4200 - it treats a trailing
unsized array as a nonstandard extension - so the build carries
/wd4200for this construct specifically (seeCMakeLists.txt); without it,/WXwould fail the Windows lane. IAR and Keil ARMCC 6 are not built in CI, so support there rests on their documentation and is confirmed at port time rather than per push. - Allocation surprise. The
_Static_assertaccompanying each flexible-array struct pins the storage-type-to-impl-type relationship at build time; an undersized storage allocation is a compile error.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
D.006 - Rule 11.8: const qualification under field access of const struct*¶
Guideline¶
MISRA C:2012 Rule 11.8 - Required. Rule text: not reproduced (see above). Classification: both kinds, which is why they share an entry - category 1 below is a tool limitation, category 2 is a genuine deviation.
Construct¶
Site categories that trigger this rule:
- Field-access "false positive" - reading a non-const
pointer field through a
const struct*parameter:
void SolidSyslog_Create(const struct SolidSyslogConfig* config)
{
InstallBuffer(config->buffer); /* config->buffer has type
struct SolidSyslogBuffer*, not
const-qualified */
…
}
section 6.5.2.3 ¶4 governs this, and it has to be read whole. The result of
-> is the named member's type; and where the left operand is a
pointer to a qualified type, the result carries the so-qualified
version of that type. That second clause looks at first like it works
against us. It does not. The member's type here is
struct SolidSyslogBuffer *, so the so-qualified version is
struct SolidSyslogBuffer * const - the qualification attaches to the
pointer, not to the object the pointer designates. Passing it by value
to InstallBuffer copies the pointer, and a top-level qualifier on a
copied value is discarded.
So no qualification is removed from the pointed-to type, and no cast
is performed. cppcheck-misra flags the access anyway, tracking the
outer const on *config rather than the type of the member
expression. The project assesses this as a tool limitation rather
than a departure from the guideline, and records it here so the
assessment is visible rather than silent.
The same pattern recurs in
SolidSyslogMessageFormatter_Format(const struct
SolidSyslogMessageFormatterContext* context) (context->Clock,
GetHostname, GetAppName, GetProcessId, Sd), which reads its
read-only context exactly as <Class>_Create reads its config. The const
is deliberate (the formatter must not mutate the context); keeping it
and accepting the false positive is preferred over weakening the
signature to silence the tool.
- Platform-API const-strip -
(a) Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:
Winsock's select() declares its timeout parameter as
struct timeval* (non-const) where POSIX select() declares it as
const struct timeval*. The seam keeps the SolidSyslog side
const-correct and forces the const-strip down to the platform-API
boundary. The code carries a comment explaining the cast.
(b) Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c:
/* SolidSyslogDatagram_SendTo's buffer is const void*; lwIP's
* pbuf->payload is plain void*. Assignment forces the const-strip. */
p->payload = (void*) buffer;
SolidSyslogDatagram_SendTo takes the caller's buffer as
const void* - the contract is read-only inside the library.
the payload field of lwIP's struct pbuf is declared void* (no const
variant in the lwIP headers); udp_sendto only reads the
payload - that is the PBUF_REF zero-copy contract - but the field
type does not encode that. Assigning
our const void* parameter to lwIP's void* field strips the
qualifier at the platform-API boundary, same shape as the
Winsock select() site above. Alternatives considered and
rejected: changing the public SendTo signature to void*
(breaks const-correctness for every other backend); copying
the buffer to PBUF_RAM (defeats the zero-copy point of PBUF_REF
and doubles per-send pool pressure).
(c) Platform/VxWorks64/Source/SolidSyslogVxWorks64Datagram.c,
SolidSyslogVxWorks64Resolver.c, SolidSyslogVxWorks64TcpStream.c and
SolidSyslogVxWorks64File.c:
int sent = sendto(self->Fd, (char*) buffer, (int) size, 0, (struct sockaddr*) sin, (int) sizeof(*sin));
uint32_t found = (uint32_t) inet_addr((char*) host);
found = (uint32_t) hostGetByName((char*) host);
STATUS status = connectWithTimeout(self->Fd, (struct sockaddr*) sin, (int) sizeof(*sin), &timeout);
int written = write(self->Fd, (char*) buf, count);
The public VxWorks 6.x API reference declares the buffer and address of
sendto, the string of inet_addr and hostGetByName, the address of
connectWithTimeout, and the buffer of the kernel write, without const,
though each only reads them. The Datagram, Resolver, Stream and File
contracts pass these as const, so the
qualifier is stripped at the call. Alternatives considered and rejected:
copying the payload to a non-const buffer (a copy per send, defeating
zero-copy), copying the host into a local non-const buffer (removes the two
resolver sites at the cost of 256 bytes of stack per resolve), and copying
the address into a local non-const sockaddr_in (removes the
connectWithTimeout site at the cost of 16 bytes per connect). Neither copy
reaches sendto, so the deviation is needed regardless; copying the record
into a non-const buffer before write is rejected for the same per-write
cost. The 6.4 headers were checked on 2026-10-03, under S41.03, for the
first three, on 2026-10-05, under S41.11, for connectWithTimeout, and on
2026-10-05, under S41.12, for the kernel write: none takes const, so
the sub-case stands as written.
Scope¶
- Strict level - the field-access reads in
Core/Source/: theSolidSyslog_Install*functions readingconfig->pointer fields inSolidSyslog.c,SolidSyslogMessageFormatter_Formatreadingcontext->Clock,GetHostname,GetAppName,GetProcessIdandSdinSolidSyslogMessageFormatter.c, andBlockStore_ResolveSecurityPolicyacceptingconfig->SecurityPolicyinSolidSyslogBlockStoreStatic.c. - Pragmatic level - the
select()timeout cast inPlatform/Windows/Source/SolidSyslogWinsockTcpStream.c, the lwIPpbuf->payloadfield cast inPlatform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c, and thesendto,inet_addr,hostGetByName,connectWithTimeoutandwritecasts inPlatform/VxWorks64/Source/.
Rationale¶
The field-access sites are not genuine const violations under the C
standard; reorganising the code to avoid the cppcheck-misra
false-positive would either drop the outer const qualifier on
*config / *blockSequence / *config (the wrong direction) or
introduce a no-op const_cast-style explicit cast that the tool would
still flag. Recording the finding here, with the reasoning, is the honest
alternative to bending the code around a tool.
The platform-API sites are the standard case of a const-correct interior
forced to strip qualification at a fixed third-party API boundary. Both
upstream declarations (Microsoft's select() timeout,
lwIP's struct pbuf payload field, and the VxWorks socket and host-library
prototypes) are fixed by their vendors; the
SolidSyslog seam
keeps the const-correctness contract on the caller's side of the
boundary.
Risk and mitigation¶
- Genuine const-strip drift. A new const-strip elsewhere in the codebase would surface as a fresh 11.8 finding, not be silently absorbed by the existing suppressions - the suppressions are line-specific.
- Platform-API sites. The Winsock, lwIP and VxWorks casts are documented at the call site and listed individually here; any new const-strip at a platform boundary surfaces as a fresh 11.8 finding rather than being absorbed by glob.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
VxWorks64 TCP stream connectWithTimeout site added 2026-10-05, approved by the
project owner, David Cozens. Recorded under
S41.11.
VxWorks64 file write site added 2026-10-05, approved by the project owner,
David Cozens. Recorded under
S41.12.
D.007 - Rule 21.10: transitive <wchar.h> via <time.h>¶
Guideline¶
MISRA C:2012 Rule 21.10 - Required. Rule text: not reproduced (see above). Classification: Tool limitation - the code complies; cppcheck-misra reports a finding regardless.
cppcheck-misra also raises this rule for <wchar.h> inclusion, which is what
brings the construct below into scope.
Construct¶
Three POSIX platform sources include <time.h> for struct timespec
and clock_gettime/nanosleep:
Platform/Posix/Source/SolidSyslogPosixClock.cPlatform/Posix/Source/SolidSyslogPosixSleep.cPlatform/Posix/Source/SolidSyslogPosixSysUpTime.c
On glibc, <time.h> transitively includes <wchar.h> (via
bits/types/struct_tm.h and the __wchar_t family in bits/types.h).
cppcheck-misra reports the transitive inclusion as a direct 21.10
violation in each of them.
Platform/VxWorks64/Source/SolidSyslogVxWorks64Clock.c includes <time.h>
for struct timespec, clock_gettime and gmtime_r. The cppcheck-misra run
has no VxWorks headers on its path, so it resolves <time.h> to the host's
glibc header and reports the same transitive inclusion.
Scope¶
Platform/Posix/Source/ and Platform/VxWorks64/Source/ - the files listed
above. The deviation does not apply to Windows or FreeRTOS sources, which use
their own platform clocks and do not include <time.h>.
Rationale¶
The POSIX time and sleep wrappers exist precisely to provide
SolidSyslog's clock and sleep abstractions on POSIX targets. They
must include <time.h> to use clock_gettime / nanosleep /
struct timespec. None of them uses any function or type
from <wchar.h>; the transitive inclusion is glibc-specific and
unavoidable on this platform.
The VxWorks 6.4 clock is in the same position: it must include <time.h> for
the POSIX clock calls, and uses nothing from <wchar.h>. The finding is made
against the host's header, not VxWorks', so whether the VxWorks header pulls in
<wchar.h> is not something the analysis can show either way.
Risk and mitigation¶
- Direct
<wchar.h>use. A future direct#include <wchar.h>in any of these files would not be absorbed by the per-file suppression - only line-1<time.h>is suppressed. - Non-glibc POSIX targets. musl, Bionic and BSDs do not pull
<wchar.h>from<time.h>; the suppression is harmless on those targets (it suppresses a finding that does not occur). - The VxWorks 6.4 clock. The finding is made against the host's
<time.h>, not the one the target compiles against. If the VxWorks header does not pull in<wchar.h>, the suppression covers a finding that never occurs on the target, which is harmless. As for the POSIX files, only the<time.h>include line is suppressed, so a direct#include <wchar.h>would still be reported.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
The VxWorks 6.4 clock added 2026-10-04, approved by the project owner, David Cozens. Recorded under S41.10.
D.008 - Rule 21.6: <stdio.h> for SEEK_SET / SEEK_END only¶
Guideline¶
MISRA C:2012 Rule 21.6 - Required. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
Platform/Windows/Source/SolidSyslogWindowsFile.c includes <stdio.h>
solely to obtain the SEEK_SET and SEEK_END constants used by
_lseeki64 (declared in <io.h>). No <stdio.h> function or type
(FILE, fopen, printf, …) is referenced.
Scope¶
Platform/Windows/Source/SolidSyslogWindowsFile.c only. One line -
the #include <stdio.h> directive.
Rationale¶
On MSVC, the _lseeki64 function takes a "whence" parameter whose
constants (SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2) are
defined exclusively in <stdio.h>. <io.h> declares _lseeki64
itself but does not define the constants; <sys/stat.h> does not
define them either. The three options considered:
| Option | Trade-off |
|---|---|
#define SEEK_SET 0 ourselves |
Hard-codes the MSVC ABI; fragile if the toolchain ever changes the values. |
Inclusion of <stdio.h> |
Pulls in the entire stdio API surface, but we use only two integer constants. |
Pass numeric literals (0, 2) |
Loses readability at the call site. |
Inclusion of <stdio.h> is the lowest-risk option; the project's
banned-API policy already forbids printf/scanf/etc. and the
clang-tidy bugprone-unsafe-functions family catches accidental
use. The deviation is narrow and visible.
Risk and mitigation¶
- Accidental stdio use. A grep over
WindowsFile.cforprintf|scanf|fopen|FILEproves the negative; CI's clang-tidy step catches future accidental use of banned stdio APIs across the whole tree. - Cross-platform consistency. POSIX
SolidSyslogPosixFile.cuses<unistd.h>for the same constants and does not need this deviation.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
D.009 - Rules 2.4 / 5.7: anonymous enum used as named-constant container¶
Guidelines¶
- MISRA C:2012 Rule 2.4 - Advisory.
- MISRA C:2012 Rule 5.7 - Required.
Rule text: not reproduced (see above). Classification: Tool limitation - the code complies; cppcheck-misra reports a finding regardless.
cppcheck-misra interprets an anonymous enum { ... }; declaration
(no enum tag, no typedef) two ways:
- under 2.4 it reports the enum tag as unused - the enumerators are used as named constants but the enum type itself is never referenced;
- under 5.7 it reports the tag as non-unique - every anonymous
enumshares the same empty tag identifier, so the second and subsequent ones collide.
Both findings originate from the same syntactic shape - the
anonymous-enum named-constant idiom - and are covered by a single
deviation here.
Construct¶
SolidSyslog uses the anonymous-enum idiom across the codebase as
a portable mechanism for declaring named integer constants in
header and source scope:
enum
{
SOLIDSYSLOG_UDP_DEFAULT_PORT = 514, /* RFC 5426 */
SOLIDSYSLOG_TCP_DEFAULT_PORT = 601, /* RFC 6587 section 3.2 */
};
The idiom recurs throughout Core/ and
Platform/. Adding inline-suppress comments at every
site would add visual noise next to a project-wide intentional
idiom - listing them in misra_suppressions.txt under this
deviation keeps the source clean.
Suppression-file layout. Which deviation authorises a rule 5.7 finding is
decided by the identifier it lands on: a repeated struct tag is D.003, an
anonymous enum is D.009. That is a standing convention,
not a transitional state - both kinds of finding exist permanently in this
codebase, so both blocks permanently carry rule 5.7 lines. Each block in
misra_suppressions.txt is headed by the deviation that authorises its entries,
so the mapping is explicit per line rather than inferred.
Scope¶
- Strict level - every anonymous-
enumconstants block inCore/Interface/andCore/Source/. - Pragmatic level - every anonymous-
enumconstants block inPlatform/*/Source/.
Rationale¶
enum { CONST = N }; is the project's preferred way to introduce
named integer constants for two reasons:
- Type-safety vs.
#define. Enum constants are first-class integers with proper compile-time evaluation;#defineconstants are token substitutions and the project's clang-tidycppcoreguidelines-macro-usagerule discourages them outside the very small surface of true preprocessor macros (e.g.SOLIDSYSLOG_STATIC_ASSERT,SOLIDSYSLOG_X_STORAGE_SIZE). - Local scoping. A block-scope
enum { ... };introduces constants visible only inside the function or file, with no global namespace pollution.
The anonymous form (no tag) is correct because the enum type is never needed, only the enumerator values. Adding a tag solely to satisfy rule 2.4 would create an unused identifier that would itself need a suppression, and the tag-named enum would not be substitutable for any other type.
Risk and mitigation¶
- Constant collision. Two enums declaring the same enumerator name collide at compile time (a duplicate-identifier error from the compiler). This deviation does not relax that compiler check.
- Missing values. A typo in a constant name is caught at compile time. The enum has no runtime cost.
- Future tag-need. If a particular constant set ever needs the enum type (e.g. to type a function parameter), the anonymous form is upgraded to a named-tag form on a per-case basis; the deviation does not preclude this.
Approval¶
Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under S10.06.
D.010 - Rule 20.10: # stringification in the SOLIDSYSLOG_STATIC_ASSERT polyfill¶
Guideline¶
MISRA C:2012 Rule 20.10 - Advisory. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
Core/Source/SolidSyslogMacros.h defines SOLIDSYSLOG_STATIC_ASSERT. The
native C++/C11 expansions need a string-literal message, so the macro
stringifies its msg argument via the standard two-step #-operator idiom:
#define SOLIDSYSLOG_STATIC_ASSERT_STRING_INNER(s) #s /* <- # */
#define SOLIDSYSLOG_STATIC_ASSERT_STRING(s) SOLIDSYSLOG_STATIC_ASSERT_STRING_INNER(s)
#if defined(__cplusplus)
#define SOLIDSYSLOG_STATIC_ASSERT(cond, msg) static_assert((cond), SOLIDSYSLOG_STATIC_ASSERT_STRING(msg))
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
#define SOLIDSYSLOG_STATIC_ASSERT(cond, msg) _Static_assert((cond), SOLIDSYSLOG_STATIC_ASSERT_STRING(msg))
#else
#define SOLIDSYSLOG_STATIC_ASSERT(cond, msg) extern char SolidSyslogStaticAssertViolated[(cond) ? 1 : -1]
#endif
The #s operator is the deviation. The C99 fallback (extern char array) uses
no preprocessor operators - a fixed name suffices because identical extern
declarations in one translation unit are compatible, so no __LINE__ pasting
(and no ##) is required.
Scope¶
Core/Source/SolidSyslogMacros.h only. One line - the
SOLIDSYSLOG_STATIC_ASSERT_STRING_INNER definition.
Rationale¶
SOLIDSYSLOG_STATIC_ASSERT selects one of three expansions on
__cplusplus / __STDC_VERSION__, and all three are compiled: C++
static_assert in the CppUTest harnesses, C11 _Static_assert in the default
build (CMAKE_C_STANDARD is 11 unless overridden), and the C99
negative-array-size fallback in the build-linux-c99 lane, which compiles
Core/ at strict -std=c99 on every pull request. The deviation is confined to the two
string-literal forms; only they need the message stringified, and the C99
fallback uses no preprocessor operator at all.
C++ static_assert and C11 _Static_assert are the standard compile-time
assertion primitives for their editions. Their message argument is a string
literal, and there is no way to convert an arbitrary identifier-shaped message
into one without #. The alternatives all regress:
| Alternative | Why rejected |
|---|---|
| Hard-coded literal message in the macro | Loses per-site context - every assertion would report the same generic string. |
| Force every caller to pass a string literal | Spreads strings across the call sites and gives up the per-site identifier form some files already use. |
| Drop the message argument entirely | Loses readability at the assertion site. |
The advisory rule's intent is to discourage opaque token games. The two-step stringify idiom here is the standard, documented C preprocessor pattern and has been since C89; it is neither opaque nor novel.
Risk and mitigation¶
- Single-site exposure. The deviation is the macro definition
itself, line-specific. Any future
#/##use elsewhere would surface as a fresh 20.10 finding, not absorbed by this suppression. - Elimination path. If the project ever drops the
msgparameter (or moves entirely to inline_Static_assert((cond), "literal")at call sites), the deviation can be retired.
Approval¶
Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recorded under S10.10.
D.011 - Rule 2.5: public API macros consumed outside the cppcheck-misra scope¶
Guideline¶
MISRA C:2012 Rule 2.5 - Advisory. Rule text: not reproduced (see above). Classification: Tool limitation - the code complies; cppcheck-misra reports a finding regardless.
Construct¶
Core/Interface/SolidSyslogCircularBuffer.h declares one function-like
macro - SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES - that integrator code
uses to size caller-supplied ring memory. cppcheck-misra runs only over
the Strict level (Core/Source/) and Pragmatic level (Platform/*/Source/);
the actual consumers live under Tests/ (Consistency-only level) and
Bdd/Targets/ (Out of scope) and are therefore invisible to the
checker.
Scope¶
Core/Interface/SolidSyslogCircularBuffer.h - one macro definition.
This entry authorises that one macro and no other. Per the rigour levels, MISRA
enforcement does not cross into Tests/ or Bdd/, so a future sweep may
surface the same shape on another public API macro. That does not extend this
deviation automatically: each new instance is reviewed on its merits and either
amends this entry with the file named, or is raised as its own. Until then the
rule still catches genuinely unused macros inside the scanned scope.
Rationale¶
The macro is used by integrators in Tests/ and Bdd/Targets/.
Verified by grep over the tree:
Tests/SolidSyslogCircularBufferTest.cpp - RING_BYTES
Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp - RING_BYTES
Bdd/Targets/Windows/BddTargetWindows.c - RING_BYTES
Bdd/Targets/FreeRtos/main.c - RING_BYTES
The macro is part of the public API; integrators use it to size
the caller-supplied ring buffer in bytes, derived from a maximum
message count (e.g. uint8_t ring[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(4)]
allocates enough bytes for four full-size messages plus their
length headers).
The alternatives all regress:
| Alternative | Why rejected |
|---|---|
Inline cppcheck-suppress misra-c2012-2.5 at the macro |
Project preference. Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. |
Widen the cppcheck-misra scan to include Tests/ |
Tests are the Consistency-only level; running MISRA there is out of scope by design. |
Move the macro into Core/Source/ |
Public API by definition lives under Core/Interface/. Moving it would break the audience-segregated header layout. |
Risk and mitigation¶
- Genuinely unused public macros. A future public-API macro that is truly unused (no integrator consumer either) would still be a defect; this deviation is line-specific, so a new unused macro surfaces as a fresh 2.5 finding rather than being silently absorbed.
- Elimination path. If the cppcheck-misra scan is ever widened to
include
Tests/andBdd/Targets/(unlikely under the current rigour levels), the suppressions become unnecessary and can be removed.
Approval¶
Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recorded under S10.10.
D.012 - Rule 8.9: file-scope static const referenced from a file-scope enum + one function¶
Guideline¶
MISRA C:2012 Rule 8.9 - Advisory. Rule text: not reproduced (see above). Classification: Tool limitation - the code complies; cppcheck-misra reports a finding regardless.
Construct¶
Core/Source/SolidSyslogFileBlockDevice.c:24 declares
static const char FILE_EXTENSION[] = ".log". The constant is the
single source of truth for the on-disk filename extension and is
referenced from two places in the translation unit:
- The file-scope enum at line 29 -
sizeof(FILE_EXTENSION) - 1Ucontributes toFILENAME_SUFFIX, which in turn computesMAX_PREFIX_LENGTH(an integer constant expression consumed by the formatter at the call site). FileBlockDevice_FormatBlockFilenameat line 218 - both the bytes pointer and the runtime length are derived from the same constant.
cppcheck-misra's 8.9 tracker counts only function-scope references.
The file-scope enum reference at line 29 is invisible to it, so it
sees a single function reference (line 218) and reports the constant
as having "block-scope-only" usage - even though moving it into the
function would break the enum's compile-time sizeof() evaluation.
Scope¶
Core/Source/SolidSyslogFileBlockDevice.c:24 - one declaration.
This entry authorises that one declaration and no other. A future sweep may
surface the same shape elsewhere - a file-scope static const whose identifier
is read by a file-scope enum initialiser and exactly one function. Each such
instance is reviewed on its merits and either amends this entry with the file
named, or is raised as its own; it is not covered by this record until that
happens. The rule still catches genuinely single-function-scoped objects.
Rationale¶
The constant cannot move to block scope without regressing the code. Three alternatives were considered and rejected:
- Inlining the literal at the enum site and at the call site would
introduce a second copy of
".log", violating DRY for what is effectively a single on-disk format invariant. - Promoting the constant's dependents from enum entries to file-scope
static const size_t(verified experimentally during S10.18) does not satisfy the rule - the tracker treats file-scope references uniformly, so a newstatic const size_t FILENAME_SUFFIXtrips a second 8.9 finding for the same reason. The fix path amplifies the problem rather than resolving it. - Promoting to
#define FILE_EXTENSION ".log"would side-step rule 8.9 (macros are not objects) at the cost of introducing a string macro where the codebase otherwise usesstatic const. Rejected to keep the file-scope-const idiom consistent across the tree.
Summary:
| Alternative | Why rejected |
|---|---|
Inline cppcheck-suppress misra-c2012-8.9 at the declaration |
Project preference. Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. |
Inline the ".log" literal at both use sites |
DRY violation for a single-source-of-truth on-disk constant. |
Promote the dependent enum entries to static const size_t |
Verified to not satisfy 8.9; instead surfaces a second false positive on the new constant. |
Promote to #define FILE_EXTENSION ".log" |
Introduces a string macro inconsistent with the file-scope-const pattern used elsewhere in storage code. |
Risk and mitigation¶
- Genuinely single-function-scoped constants. A new file-scope
static constwhose identifier really appears in only one function would still be a defect; this deviation is line-specific, so a fresh 8.9 finding elsewhere still surfaces in CI. - Elimination path. If a future cppcheck-misra version teaches its 8.9 tracker to count file-scope-initialiser references, the suppression becomes unnecessary and can be removed.
Approval¶
Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded under S10.18.
D.013 - Rule 11.5: void* ↔ a byte pointer at third-party byte-buffer API boundaries¶
Guideline¶
MISRA C:2012 Rule 11.5 - Advisory. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
SolidSyslogStream_Send takes const void* and SolidSyslogStream_Read
takes void* - the project-wide byte-buffer contract used by every
Stream implementation, and SolidSyslogDatagram_SendTo takes const void*
likewise. Some third-party C libraries type their byte buffers as a character
pointer rather than void*: mbedTLS uses const unsigned char* /
unsigned char*, and the Winsock socket calls use const char* / char*
where their POSIX counterparts use void*, as do the VxWorks 6.x sendto,
which the public API reference declares taking char*, and the VxWorks 6.4
kernel read and write, both declared taking char*. The implementation
cast bridging the two is unavoidable at the API boundary:
int rc = mbedtls_ssl_write(&self->SslContext, (const unsigned char*) buffer, size);
int sent = WinsockTcpStream_send(self->Fd, (const char*) buffer, (int) size, 0);
Rule 11.5 fires on each such adapter cast.
Scope¶
Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c-MbedTlsStream_SendandMbedTlsStream_Read,unsigned char*.Platform/Windows/Source/SolidSyslogWinsockTcpStream.c-WinsockTcpStream_SendandWinsockTcpStream_Read,char*.Platform/Windows/Source/SolidSyslogWinsockDatagram.c-WinsockDatagram_SendTo,char*.Platform/VxWorks64/Source/SolidSyslogVxWorks64Datagram.c-VxWorks64Datagram_SendTo,char*.Platform/VxWorks64/Source/SolidSyslogVxWorks64TcpStream.c-VxWorks64TcpStream_SendandVxWorks64TcpStream_Read,char*.Platform/VxWorks64/Source/SolidSyslogVxWorks64File.c-VxWorks64File_ReadandVxWorks64File_Write,char*.
A future Stream, Datagram, hash or MAC implementation wrapping a byte-typed
third-party C API will meet the same boundary, but is not covered by this
record until reviewed and added to it - or given its own entry. The OpenSSL
adapter (Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c) does not fall
under this deviation - SSL_write / SSL_read take void* and so no
cast is needed.
Rationale¶
The alternatives all regress:
| Alternative | Why rejected |
|---|---|
Refactor SolidSyslogStream_Send to const unsigned char* and SolidSyslogStream_Read to unsigned char* |
Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (SolidSyslogStreamSender). The void* byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX send/recv, OpenSSL SSL_write/SSL_read, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. |
Copy through an unsigned char scratch buffer per call |
Runtime cost on the hot send/receive path; adds a fixed-size scratch or a stack-allocated VLA in a critical-path function. Defeats the zero-copy intent of the Stream contract. |
Inline cppcheck-suppress misra-c2012-11.5 at each site |
Project preference. Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. |
The cast is well-defined: a character type may alias any object type
(section 6.5 ¶7), so reinterpreting a void* byte buffer as char* or
unsigned char* and back is a no-op at the abstract-machine level.
Risk and mitigation¶
- Alignment - Both representations are byte-addressed; no alignment promotion occurs. The cast targets a character pointer, which has the weakest alignment requirement of any object pointer.
- Type safety - Both directions carry a contiguous byte sequence and
neither is interpreted as anything else: on the send path it is the
formatted record the caller hands over, and on the read path it is the
caller's destination, written into and never read as a wider type.
Treating either as
char*orunsigned char*at the third-party API boundary is the same bytes under a different pointer type. - Elimination path - The deviation retires per API, not as a whole, and
each direction keeps its qualification.
SolidSyslogStream_Sendtyped toconst unsigned char*andSolidSyslogStream_Readtounsigned char*would retire the Stream sites;SolidSyslogDatagram_SendTotyped toconst unsigned char*would retireWinsockDatagram_SendToandVxWorks64Datagram_SendTo. Either would only retire the sites whose third-party spelling it matched. Tracked as a possible E10-successor refactor, not scheduled.
Approval¶
Raised and approved 2026-05-23 by the project owner, David Cozens. Recorded under S10.20.
VxWorks64 datagram site added 2026-09-30, approved by the project owner, David Cozens. Recorded under S41.03.
VxWorks64 TCP stream sites added 2026-10-05, approved by the project owner, David Cozens. Recorded under S41.11.
VxWorks64 file sites added 2026-10-05, approved by the project owner, David Cozens. Recorded under S41.12.
D.014 - Rule 8.7: public-API SolidSyslogErrorSource objects (retired)¶
Retired in S12.26. This deviation covered the crypto-policy
SolidSyslogErrorSource objects, which rule 8.7 flagged because the
<Class>_Report wrapper confined every emission to the source's own *Messages.c,
a single translation unit. S12.26 decoupled error text from the library
(deleting the *Messages.c message tables) and unwound the <Class>_Report wrapper,
so each source is now defined in its class's vtable TU and reached from the
<Class>_Report inline in its *Private.h, which both that TU and its
*Static.c lifecycle code include - so the object is used from more than one
translation unit, which is the resolution the entry's risk analysis anticipated
before it was collapsed to this note; see the revision prior to retirement for
that text.
cppcheck-misra reported no 8.7 finding for any error source, and the suppression
lines were removed.
Raised and approved 2026-05-31 by the project owner, David Cozens, under S17.02; retired 2026-06-03 under S12.26. The story numbers run backwards because they are numbered by epic rather than chronologically - E12 was elaborated after E17 - so read the dates, not the labels, for the order of events.
D.015 - Rule 8.9: rollover state at file scope so a test can clear it¶
Guideline¶
MISRA C:2012 Rule 8.9 - Advisory. Rule text: not reproduced (see above). Classification: Deviation - the code departs from the guideline.
Construct¶
Platform/CmsisRtos/Source/SolidSyslogCmsisRtosSysUpTime.c:18 and :19
declare static uint32_t CmsisRtosSysUpTime_LastTicks and
static uint32_t CmsisRtosSysUpTime_Rollovers. Both are read and written by
CmsisRtosSysUpTime_Extend and by nothing else in the translation unit, which
is what the rule asks be moved to block scope.
The pair carries the phase of osKernelGetTickCount, which is 32 bits on every
CMSIS-RTOS2 implementation and cannot say how often it has wrapped. The adapter
is a bare SolidSyslogSysUpTimeFunction, so there is no handle and no Create to
hang the state off; it has to live in the translation unit.
Unlike D.012, the finding is accurate. The objects really are referenced from one function, and cppcheck-misra is reporting what is there.
Scope¶
Platform/CmsisRtos/Source/SolidSyslogCmsisRtosSysUpTime.c:18 and :19 - two
declarations.
This entry authorises those two and no other. A future file-scope static
whose identifier appears in a single function is reviewed on its merits and
either amends this entry with the file named, or is raised as its own; it is not
covered until that happens.
Rationale¶
Testability. Block scope would put the wrap count beyond the reach of anything
outside CmsisRtosSysUpTime_Extend, and the counting has to be tested: a wrap
that is missed, double-counted, or counted when the counter has not moved each
produces a sysUpTime wrong by about 497 days.
Tests/CmsisRtos/SolidSyslogCmsisRtosSysUpTimeTestHelper.c includes the adapter
into the test translation unit and adds TestCmsisRtosSysUpTime_Reset, so every
test starts from a known phase and asserts absolutes rather than differences
between calls. A static inside the function is unreachable even from a
translation unit that has included the file, so the helper cannot work without
the wider scope. Nothing about storage duration changes: both spellings give the
objects static storage for the life of the program, so only scope is deviated.
| Alternative | Why rejected |
|---|---|
Inline cppcheck-suppress misra-c2012-8.9 at the declarations |
Project preference. Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. |
Declare the state inside CmsisRtosSysUpTime_Extend |
The wrap count becomes unreachable, so the tests fall back to asserting differences between consecutive calls in a fixed order, and each case inherits the phase the one before it left. |
| Expose a reset from the adapter itself | Puts a function in the shipped library that exists only for the tests, and offers integrators a way to corrupt the phase. |
| Give the state a second production reference to satisfy the tracker | Contrives a caller to quiet a tool. The finding is accurate; dressing it up is worse than recording it. |
Risk and mitigation¶
- Wider scope than the code needs. Any function later added to this
translation unit could reach the phase directly rather than through
CmsisRtosSysUpTime_Extend. The file holds one public function and two helpers, and the scaling helper is pure by design and documented as such. - Genuinely single-function-scoped objects elsewhere. This deviation is line-specific, so a fresh 8.9 finding in another file still fails CI.
- Elimination path. None expected: the constraint is the callback shape,
which is public API. Were
sysUpTimeever to gain a handle, the state would move into it and both the suppression and this entry would go.
Approval¶
Raised and approved 2026-09-20 by the project owner, David Cozens. Recorded under S40.03.