This document records intentional deviations from MISRA C:2012 rules in the
engine-control test-rig simulator. The project enforces compliance via
make analyze-misra which runs cppcheck with the MISRA C:2012 addon
and --error-exitcode=1. Suppressed rules are listed in the Makefile; any
violation of a non-suppressed rule fails the build.
Edition note (2026-02): MISRA C:2025 support in common open-source static analysis toolchains is not assumed here. This project targets MISRA C:2012 and can migrate when the chosen toolchain supports it.
The following rules had violations that were eliminated by code changes - no deviations required:
| Rule | Category | Violations Fixed | Fix Applied |
|---|---|---|---|
| 11.4 | Advisory | 167 | Replaced (type *)0 with NULL; _Static_assert uses compound literals |
| 11.8 | Required | 2 | Removed unnecessary (char *) cast on strstr() return |
| 12.1 | Advisory | 7 | Added explicit parentheses around mixed-precedence expressions |
| 13.4 | Advisory | 2 | Hoisted while ((x = f()) != ...) assignments before the loop |
| 14.2 | Required | 2 | Replaced index += 1 in for-body with skip_next flag |
| 15.7 | Required | 1 | Added terminal else clause |
| ID | Rule | Category | Suppressed Count | Scope |
|---|---|---|---|---|
| DEV-001 | 15.5 | Advisory | 383 | Project-wide - early-return guard pattern |
| DEV-002 | 21.6 | Required | 81 | src/ - stdio.h functions |
| DEV-003 | 8.7 | Advisory | 20 | src/ - external-linkage functions testable from separate TU |
| DEV-004 | 10.4 | Required | 11 | include/ - _Static_assert operand type mismatch |
| DEV-005 | 18.4 | Advisory | 10 | src/ - pointer arithmetic on arrays/buffers |
| DEV-006 | 11.5 | Advisory | 10 | src/ - conversion from void * to object pointer |
| DEV-007 | 4.1 | Required | 8 | src/reporting/ - ANSI colour escape sequences |
| DEV-008 | 10.8 | Required | 3 | src/platform/hal.c - composite expression widening cast |
| DEV-009 | 8.9 | Advisory | 2 | src/domain/ - file-scope static data |
| DEV-010 | 2.5 | Advisory | 2 | include/ - unused macro definitions |
| DEV-011 | 2.3 | Advisory | 2 | include/ - unused type declarations |
| DEV-012 | 2.4 | Advisory | 1 | include/ - unused tag declaration |
| DEV-013 | 21.8 | Required | 1 | src/reporting/logger.c - getenv("CI") |
| DEV-014 | 1.2 | Advisory | - | include/hal.h - __attribute__((packed)) |
Category: Advisory Suppressed count: 383 Locations: All source files
Rationale: The project uses a guard-clause / early-return coding style where each public function begins with NULL-pointer checks and argument validation that return immediately on failure. This pattern is widely adopted in safety-critical C (e.g., AUTOSAR, NASA/JPL) because it keeps the happy path at the lowest indentation level and makes error handling explicit at the call site. Converting 383 early returns into a single-exit structure would require deeply nested if/else or goto-cleanup patterns that are harder to review.
Mitigation: Every early-return path is unit-tested (255 tests, 100% line coverage on unit-tested modules). Functions that return error codes are documented with
@return Doxygen annotations.
Category: Required
Suppressed count: 81
Locations: src/scenario/script_parser.c, src/app/config.c,
src/reporting/output.c, src/reporting/logger.c,
src/scenario/scenario_report.c
Rationale: The simulator is a host-side test harness, not a deployed
embedded controller. It must read scenario scripts from files (fopen/fgets),
parse calibration JSON (sscanf, strtof, strtoul), write diagnostic logs
(fprintf), and emit JSON contract reports (snprintf, fwrite). There is no
alternative I/O mechanism on the host platform.
Mitigation: All format strings are compile-time constants. Buffer sizes are
bounded by sizeof or _Static_assert. Parsed values pass through range
validation. File handles are closed deterministically.
Category: Advisory
Suppressed count: 20
Locations: Various src/ files
Rationale: Functions exposed in public headers (engine.h, control.h,
etc.) are intentionally extern to support unit testing from a separate
translation unit (tests/). Making them static would break the test harness.
Mitigation: The analyze-layering target validates include-graph
dependencies. Only the declared public API crosses module boundaries.
Category: Required
Suppressed count: 11
Locations: include/engine.h, include/hal.h (_Static_assert expressions)
Rationale: _Static_assert comparisons like sizeof(...) == 4U involve
size_t compared with an unsigned literal. Both operands are unsigned; the
mismatch is a cppcheck false positive. The assertion generates no runtime code.
Mitigation: These are compile-time-only checks. Operands are always unsigned constants.
Category: Advisory
Suppressed count: 10
Locations: src/app/config.c, src/scenario/script_parser.c,
src/platform/hal.c
Rationale: Pointer arithmetic is used for bounded buffer traversal during
JSON key parsing (cursor + 1, end_quote + 1) and CAN frame byte
encoding/decoding. These operations always work within a known-size buffer whose
bounds are checked before access.
Mitigation: All pointer arithmetic is bounded by prior length checks.
AddressSanitizer (make analyze-sanitizers) validates memory access at runtime.
Category: Advisory
Suppressed count: 10
Locations: src/scenario/scenario_profiles.c, src/app/test_runner.c
Rationale: The test framework passes scenario-specific context through a
void * parameter in the generic TestCase function signature. Each scenario
function casts this back to the expected concrete type. This is the standard C
pattern for type-erased callbacks.
Mitigation: NULL-pointer checks precede every cast. The type relationship is enforced by the scenario registration code that passes the context.
Category: Required
Suppressed count: 8
Locations: src/reporting/logger.c
Rationale: ANSI terminal colour codes use hex escape sequences (e.g.,
\x1b[31m). These are string constants consumed only by terminal emulators and
are gated behind the --color CLI flag.
Mitigation: Colour output is disabled by default and in CI. The escape sequences are compile-time string literals.
Category: Required
Suppressed count: 3
Locations: src/platform/hal.c
Rationale: CAN frame encoding casts float arithmetic results to
uint16_t for wire format. The cast is explicit and intentional, with
intermediate values bounded by physical constraints (RPM < 65535, temperature
offset < 6553.5).
Mitigation: validate_sensor_frame() rejects out-of-range inputs before
encoding. Round-trip decode tests verify correctness.
Category: Advisory
Suppressed count: 2
Locations: src/domain/control.c, src/domain/engine.c
Rationale: Module-level calibration/config data (g_calibration,
g_physics_config) is intentionally file-scoped static to survive across
function calls within the module.
Mitigation: Both objects are static and initialised to safe defaults.
Getter/setter functions enforce validation.
Category: Advisory
Suppressed count: 2
Locations: include/config.h, include/hal.h
Rationale: Configuration constants and guard macros are defined for future use or compile-time documentation.
Mitigation: Reviewed periodically.
Category: Advisory
Suppressed count: 2
Locations: include/hal.h
Rationale: Type aliases (HAL_BusFrame, HAL_Frame) are provided for API
clarity even if only one is currently used downstream.
Mitigation: Reviewed periodically.
Category: Advisory
Suppressed count: 1
Location: include/hal.h
Rationale: The BusFrame struct tag is retained for debugger visibility
even though most code uses the typedef.
Mitigation: Reviewed periodically.
Category: Required
Suppressed count: 1
Location: src/reporting/logger.c
Rationale: getenv("CI") is a read-only query to detect CI environments
where DEBUG logging would generate excessive output. The return value is only
compared against NULL - never dereferenced, stored, or written through.
Mitigation: Affects only log verbosity. No safety-critical behaviour depends on environment variables. Called once during initialisation.
Category: Advisory
Suppressed count: Not detected by cppcheck (documented proactively)
Location: include/hal.h
Rationale: __attribute__((packed)) is essential for the BusFrame to
match the 13-byte CAN-like wire format. The HAL_PACKED macro provides a
conditional compilation path for compilers that do not support the attribute.
Mitigation: _Static_assert(sizeof(BusFrame) == 13U, ...) detects ABI
violations at compile time.
make analyze-misra # cppcheck --error-exitcode=1 with MISRA addon
- Non-deviated rules: Any new violation fails the build (exit code 1).
- Deviated rules: Suppressed in the Makefile via
--suppress=misra-c2012-X.Y. - Adding a deviation: Requires a new entry in this document with rationale,
mitigation, and a corresponding
--suppressline in the Makefile. - CI integration: The
analyze-misratarget should be included in the CI pipeline alongsideanalyze-cppcheckandanalyze-clang-tidy.