Skip to content

feat: Add no-heap static allocation PAL backend for lockstep mode - #268

Open
vtz wants to merge 63 commits into
mainfrom
feature/no-heap-static-alloc
Open

feat: Add no-heap static allocation PAL backend for lockstep mode#268
vtz wants to merge 63 commits into
mainfrom
feature/no-heap-static-alloc

Conversation

@vtz

@vtz vtz commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #174

Adds a complete no-heap static allocation backend for opensomeip, enabling deployment on safety-critical lockstep ECUs (Cortex-R class) where dynamic memory allocation violates ISO 26262 freedom-from-interference requirements.

Completed Migrations

  • Pimpl migration: All 6 public API classes (RpcClient, RpcServer, EventPublisher, EventSubscriber, SdClient, SdServer) use alignas placement-new storage under SOMEIP_STATIC_ALLOC, with placement delete in destructors
  • Container migration: All std::vector, std::unordered_map, std::string, std::function in core code replaced with platform::Vector, platform::UnorderedMap, platform::String<>, platform::Function — ETL-backed under static alloc, STL-backed under dynamic
  • Transport migration: All 6 impl classes store UdpTransport by value (was std::make_shared)
  • Thread migration: std::unique_ptr<platform::Thread>std::optional<platform::Thread> in UdpTransport, TcpTransport
  • TP migration: std::unique_ptr<TpSegmenter/TpReassembler>std::optional<...>, std::unordered_mapplatform::UnorderedMap
  • SD variant migration: std::unique_ptr<SdEntry/SdOption>std::variant<...> for polymorphic SD entries/options
  • SessionManager: std::shared_ptr<Session> → value storage, get_session() returns std::optional<Session>, set_session_state() for mutations
  • RPC sync path: std::make_shared<SyncState> → stack-local, std::make_shared<RpcResponse>std::optional
  • Callback FreeRTOS: std::function<void()>*platform::Function<void()> in FreeRTOS thread PAL
  • Serializer: std::stringplatform::String<> in deserialize/serialize paths

Key Components

  • PAL allocation axis: SOMEIP_USE_STATIC_ALLOC CMake option selects between dynamic/ (STL) and static/ (ETL + slab pools) via include-path shadowing
  • Tiered slab byte-buffer pool: Three-tier O(1) free-stack pool with ByteBuffer handle providing std::vector<uint8_t>-compatible API
  • Static Message pool: Fixed-size pool with in_use tracking, placement new, PAL mutex, returning IntrusivePtr<Message>
  • IntrusivePtr: Generic intrusive reference-counting smart pointer with embedded atomic ref_count_
  • ETL container aliases: Fixed-capacity Vector, String, UnorderedMap, Queue, Function
  • ETL error handler: Custom handler that logs + returns (no abort), with ETL_LOG_ERRORS=1 and ETL_CHECK_PUSH_POP
  • Malloc trap: Link-time heap interception via dlsym(RTLD_NEXT) + __builtin_trap() for verification
  • PayloadView: Lightweight span-like non-owning view for zero-copy payload access
  • Capacity contracts: size() >= max_size() checks before insert on bounded containers
  • Memory budget: static_memory_budget.py computes BSS total (~495KB with current defaults)

Documentation & Safety

  • 20+ Sphinx-needs requirements (REQ_ARCH_008, REQ_PAL_*, REQ_PLATFORM_STATIC_*, REQ_API_PAYLOAD_VIEW)
  • FMEA for static allocation failure modes
  • @implements / @test_case / @tests traceability tags throughout

Build Verification

  • Static build: 21/21 tests pass (including 13 integration tests under malloc trap)
  • Dynamic build: 15/15 tests pass (zero regressions)
  • API construction under trap: All 6 API classes construct/destroy with zero heap allocations

CI/Target Presets

  • static-alloc-linux-tests preset for host testing
  • freertos-cortexm4-renode-static / threadx-cortexm4-renode-static presets
  • CI jobs for Renode static-alloc tests in FreeRTOS and ThreadX workflows

Known Limitations

  • E2E profiles: std::make_unique<BasicE2EProfile> uses heap due to polymorphic extensibility (plugin API)
  • Debug strings: to_string() methods use std::string/std::stringstream — diagnostic-only, not on critical path

Test Plan

  • ctest passes with SOMEIP_USE_STATIC_ALLOC=ON (21/21)
  • ctest passes with default dynamic config (15/15, zero regression)
  • All 6 API classes construct/destroy under malloc trap (0 heap allocations)
  • Message serialize/deserialize round-trip under trap
  • Buffer pool: acquire, release, tier selection, exhaustion, concurrent access
  • Message pool: allocate, release, reuse, exhaustion, double-release safety, concurrent
  • IntrusivePtr: lifecycle, copy/move, reset, nullptr, comparison
  • ETL error handler: registered, overflow invokes handler, no abort
  • Capacity contracts: bounded container overflow returns error
  • PayloadView: basic access, empty, subview, iteration
  • SessionManager: capacity limit, optional return, state mutation

Summary by CodeRabbit

  • New Features
    • Added an optional static-allocation (no-heap) mode using fixed-capacity pools and deterministic memory.
    • Introduced platform abstractions for buffers, strings, vectors, queues, and callbacks (incl. tiered buffer pool) with bounded capacity exhaustion behavior.
    • Added zero-copy payload viewing (PayloadView) and an intrusive reference-counted message pointer for static mode.
  • Documentation
    • Documented static-allocation contracts, safety analysis, and configuration/memory budgeting.
  • Tests
    • Added/expanded tests covering static pools, malloc-trap no-heap verification, containers, serialization, and static integration (including CI build presets).

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds selectable ETL-based static allocation with bounded message and byte-buffer pools, platform container aliases, intrusive message ownership, static PIMPL storage, migrated protocol APIs, no-heap verification, memory-budget tooling, and host/RTOS CI coverage.

Changes

Static allocation backend

Layer / File(s) Summary
Build, configuration, and verification
CMakeLists.txt, src/CMakeLists.txt, CMakePresets.json, .github/workflows/*, docs/*, scripts/*, tests/CMakeLists.txt, zephyr/*
Adds SOMEIP_USE_STATIC_ALLOC, ETL integration, static capacity configuration, presets, CI jobs, requirements, safety analysis, clang-tidy discovery, and memory-budget reporting.
Platform contracts and static storage
include/platform/*, src/platform/static/*, include/someip/payload_view.h, tests/fakes/*
Adds dynamic/static buffer and container backends, intrusive pointers, tiered byte-buffer and message pools, ETL error handling, malloc trapping, payload views, and static thread callable storage.
Public API migration
include/e2e/*, include/events/*, include/rpc/*, include/sd/*, include/serialization/*, include/tp/*, include/transport/*, include/someip/*, include/core/*
Replaces STL payloads, strings, containers, callbacks, shared pointers, SD polymorphic ownership, session pointers, and heap-backed PIMPL storage with platform abstractions and bounded/value-based representations.
Runtime migration
src/core/*, src/e2e/*, src/events/*, src/rpc/*, src/sd/*, src/serialization/*, src/tp/*, src/transport/*
Updates serialization, SD variant processing, session management, event/RPC/SD storage, transport buffers, thread ownership, capacity checks, and static PIMPL construction.
Static and regression tests
tests/test_*, tests/shared/*, tests/static_pool_init.h
Adds pool, container, intrusive-pointer, ETL, PAL, malloc-trap, integration, and capacity tests while migrating E2E, message, SD, serialization, TP, and transport suites.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CMake
  participant Build
  participant StaticBackend
  participant Tests
  CMake->>Build: configure SOMEIP_USE_STATIC_ALLOC
  Build->>StaticBackend: select ETL, pools, and platform headers
  StaticBackend->>Tests: expose static targets and malloc trap
  Tests->>StaticBackend: initialize pools and exercise bounded APIs
Loading
sequenceDiagram
  participant MessagePtr
  participant Message
  participant StaticMemory
  participant BufferPool
  MessagePtr->>Message: add or release intrusive reference
  Message->>StaticMemory: return object when refcount reaches zero
  StaticMemory->>BufferPool: initialize and manage byte-buffer pools
Loading

Possibly related PRs

  • vtz/opensomeip#267: Modifies EventPublisher subscription APIs touched by this migration.
  • vtz/opensomeip#225: Overlaps with the E2E CRC API migrated to platform::ByteBuffer.
  • vtz/opensomeip#107: Shares the Renode preset and workflow areas extended with static-allocation variants.

Poem

A rabbit bounded every heap,
Through ETL pools the bytes now leap.
Buffers tier and messages share,
No-heap tests guard everywhere. 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #174 was investigation-only, but this PR implements a broad static backend instead of just analysis and follow-up tickets. Split the implementation into a separate issue, or update the linked issue scope; #174 itself only asks for investigation and follow-up tickets.
Out of Scope Changes check ⚠️ Warning Relative to the linked investigation-only issue, the implementation, CI, docs, and test changes are out of scope. Limit this PR to the investigation deliverables, or retarget it to an implementation ticket that explicitly authorizes the backend and test work.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a static no-heap allocation backend.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/no-heap-static-alloc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 892f880cfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/platform/static/memory.cpp
Comment thread src/platform/static/buffer_pool.cpp Outdated
@vtz
vtz force-pushed the feature/no-heap-static-alloc branch 2 times, most recently from 2b9db83 to d731466 Compare June 11, 2026 17:08
…p mode

Add a complete static allocation backend for opensomeip enabling deployment
on safety-critical lockstep ECUs where dynamic memory violates ISO 26262
freedom-from-interference requirements.

Core components:
- SOMEIP_USE_STATIC_ALLOC CMake option with ETL FetchContent
- Three-tier slab byte-buffer pool (small/medium/large) with free-stacks
- ByteBuffer class with std::vector<uint8_t>-compatible API
- Static Message pool with placement new and PAL mutex
- IntrusivePtr<T> with embedded atomic ref_count_ in Message
- ETL container aliases (Vector, String, UnorderedMap, Queue, Function)
- Malloc trap for link-time no-heap verification

Documentation and safety:
- 20 new Sphinx-needs requirements (REQ_ARCH_008, REQ_PAL_*, REQ_PLATFORM_STATIC_*)
- FMEA for static allocation failure modes
- static_memory_budget.py footprint calculator

Testing:
- 30+ unit tests covering pools, ByteBuffer, IntrusivePtr, concurrency
- All 16 tests pass (static), all 15 tests pass (dynamic)

Co-authored-by: Cursor <cursoragent@cursor.com>
@vtz
vtz force-pushed the feature/no-heap-static-alloc branch from d731466 to 2a04600 Compare June 11, 2026 21:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/requirements/implementation/platform.rst (1)

483-485: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the std::shared_ptr hard-coding.

REQ_PAL_MEM_ALLOC should stay backend-agnostic. It currently conflicts with REQ_PAL_INTRUSIVE_PTR and the static message-pool requirement, which use intrusive ref-counting instead of a shared_ptr control block.

🔧 Proposed fix
-   ``MessagePtr`` (``std::shared_ptr<Message>``) owning a fully constructed, usable
-   ``Message`` object.
+   ``MessagePtr`` owning a fully constructed, usable ``Message`` object.

Also applies to: 966-977

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/requirements/implementation/platform.rst` around lines 483 - 485,
REQ_PAL_MEM_ALLOC currently hard-codes std::shared_ptr in the spec: change the
wording around someip::platform::allocate_message() and MessagePtr so they do
not mandate std::shared_ptr; instead describe MessagePtr as an abstract/opaque
smart-pointer type (or "pointer-like handle") owning a fully constructed Message
and allow backend choice (e.g., intrusive ref-counting or static pool-backed
handles) so it remains compatible with REQ_PAL_INTRUSIVE_PTR and the static
message-pool requirement; update all occurrences (including the section
referencing lines ~966-977) to remove "std::shared_ptr<Message>" and replace
with this backend-agnostic description, while keeping the contract that callers
receive an owning MessagePtr with a usable Message instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/requirements/implementation/architecture.rst`:
- Around line 202-203: Update the acceptance text that currently instructs
verifying no "malloc"/"new" calls by expanding it to cover deallocations too:
change the verification sentence that references SOMEIP_USE_STATIC_ALLOC and
REQ_PAL_NOOP_HEAP_VERIFY so it requires verifying no "malloc"/"new" or
"free"/"delete" calls during protocol operation and that the heap-interception
check covers both allocation and deallocation failures; keep the existing
guidance to inspect container and pool types for compile-time capacity bounds
and mention the combined allocation/deallocation surface to match the no-heap
trap behavior.

In `@docs/requirements/implementation/platform.rst`:
- Around line 734-817: The requirement names
(StaticVector/StaticString/StaticUnorderedMap/StaticQueue/StaticFunction) do not
match the actual exported API (Vector/String/... in
include/platform/static/containers_impl.h); update the implementation to expose
the public symbols named in the docs (either rename the types in
containers_impl.h to StaticVector, StaticString, StaticUnorderedMap,
StaticQueue, StaticFunction or add clear type aliases with those Static* names
that forward to the existing Vector/String/... types), and update the
corresponding backend requirement text to reference the same Static* symbols so
doc and code agree.

In `@docs/safety/FMEA_STATIC_ALLOCATION.md`:
- Around line 126-170: Residual-risk claims rely on ETL being built with
error-logging enabled and exceptions disabled but the build lacks those defines;
update the build config (CMakeLists.txt) to add the compile definitions
ETL_LOG_ERRORS=1 and ETL_THROW_EXCEPTIONS=0 (use target_compile_definitions or
add_compile_definitions for the relevant targets) and ensure the custom ETL
error handler object/library is linked into safety builds so the handler
registration code is included; after changing, run a safety-build to verify the
ETL handler symbol is present and that overflow paths exercise the custom
handler.
- Line 124: The FMEA claims a saturation check but intrusive_ptr_add_ref in
src/someip/message.cpp (under SOMEIP_STATIC_ALLOC) unconditionally does
p->ref_count_.fetch_add(1...), so fix intrusive_ptr_add_ref to perform an atomic
read/compare-and-swap loop that ensures ref_count_ < UINT16_MAX before
incrementing (or else log an error and refuse to increment/return a
failure/no-op copy), keep a debug-build saturation assertion, and update related
unit test TC_INTRUSIVE_PTR_LIFETIME and any copy sites (e.g., MessagePtr copy
logic) to handle the refused-increment path; alternatively, if you decide not to
implement the guard, update the FMEA entry to match current behavior.
- Around line 78-92: The build is missing the ETL safety compile definitions
described in the FMEA; add target_compile_definitions for ETL_LOG_ERRORS=1 and
ETL_THROW_EXCEPTIONS=0 to the actual CMake target (opensomeip) in
src/CMakeLists.txt so ETL uses non-terminating error propagation, and update the
documentation snippet in docs/safety/FMEA_STATIC_ALLOCATION.md to reference the
real target name opensomeip instead of someip; ensure the compile definitions
are applied PRIVATE to the opensomeip target and include a short comment linking
to the FMEA section for traceability.

In `@include/platform/static/buffer_pool_impl.h`:
- Around line 130-184: The code currently calls ensure_capacity(...) which may
fail to grow the buffer but leaves slot_ non-null, so resize(size_t),
resize(size_t,uint8_t), push_back(uint8_t) and insert(...) can still write past
the slot_'s capacity; after each ensure_capacity(new_size) (or
ensure_capacity(cur + 1) / ensure_capacity(min_capacity) where used) add a
post-check that the reserved capacity is sufficient (e.g., if (!slot_ ||
capacity() < new_size) return; or compare slot_->capacity) and return early to
avoid writing beyond slot_->capacity; apply the same guard to the other affected
sites (the overload resize(size_t,uint8_t), push_back, and insert) and ensure
slot_->size is only updated when the capacity check passes.
- Around line 190-193: end() and the const end() perform pointer arithmetic on
data() which may be nullptr for empty/default-constructed ByteBuffer; change
them to guard against nullptr by returning data() when data() == nullptr instead
of data() + size(). Update iterator end() and const_iterator end() (and keep
begin()/begin() const as-is) to compute: return data() ? data() + size() :
data(); so no nullptr + 0 occurs.

In `@include/platform/static/static_config.h`:
- Around line 35-80: Add compile-time range checks for all macro overrides
(SOMEIP_MESSAGE_POOL_SIZE, SOMEIP_MAX_SESSIONS, SOMEIP_MAX_SD_ENTRIES,
SOMEIP_MAX_MULTICAST_GROUPS, SOMEIP_MAX_CONCURRENT_TP, SOMEIP_MAX_RECEIVE_QUEUE,
SOMEIP_BYTE_POOL_SMALL_COUNT, SOMEIP_BYTE_POOL_MEDIUM_COUNT,
SOMEIP_BYTE_POOL_LARGE_COUNT, SOMEIP_BYTE_POOL_SMALL_SIZE,
SOMEIP_BYTE_POOL_MEDIUM_SIZE, SOMEIP_BYTE_POOL_LARGE_SIZE) so invalid zero or
>UINT16_MAX values cannot silently wrap; implement either preprocessor guards
(`#if` ... `#error`) or constexpr/static_assert immediately after each `#define` to
enforce 1 <= count <= 65535 for pool counts and 1 <= size <= 65535 (or
appropriate max) for byte sizes, matching the uint16_t indices used in
src/platform/static/buffer_pool.cpp and src/platform/static/memory.cpp. Ensure
messages identify the offending macro name in the `#error/static_assert` text so
integrators can fix their -D overrides.

In `@scripts/run_clang_tidy.sh`:
- Around line 67-77: Replace the fragile inline Python invocation with a small
robust script that reads BUILD_DIR and SOURCE_DIR from os.environ (instead of
shell-interpolating paths), opens compile_commands.json using a with open(...,
encoding='utf-8') context manager, wraps json.load in a try/except catching
json.JSONDecodeError and exits non‑zero on failure, and then performs the same
realpath/filter logic for .cpp files; reference the embedded Python block in
scripts/run_clang_tidy.sh and the BUILD_DIR/SOURCE_DIR variables when locating
compile_commands.json and the src directory.
- Around line 64-80: The current selection reads every entry's e['file'] from
compile_commands.json and can pick commands from other CMake configs; change the
filter to only include entries whose e['directory'] (or directory field) belongs
to the active build directory ($BUILD_DIR) and whose realpath file starts with
the project src ($SOURCE_DIR/src) and ends with .cpp, and deduplicate the list
(e.g., via set or sort -u) before assigning SOURCE_FILES; update the python
snippet that populates SOURCE_FILES (references: BUILD_DIR, SOURCE_DIR,
SOURCE_FILES, e['file'], e['directory'], compile_commands.json) to perform this
directory check and de-duplication so clang-tidy only analyzes files from the
active CMake configuration.

In `@scripts/static_memory_budget.py`:
- Around line 25-31: Add an explicit Python minimum-version note so the script
won’t be misused on older runtimes: update the top of the module to include a
module docstring stating "Requires Python 3.10+ (repo uses Python 3.12+)" and
also include the same note in the argparse description/help so command-line
users see it; locate the module header and the argparse setup (the function or
main entry that accepts argv: list[str] | None) and add the short clear version
requirement there.

In `@src/CMakeLists.txt`:
- Around line 51-56: The comment about allocation being "listed before
threading" is misplaced next to the SOMEIP_ALLOC_IMPL_DIR variable and should be
moved to where include ordering is actually set; relocate or duplicate the
comment near the target_include_directories call (the block that adds
${SOMEIP_ALLOC_IMPL_DIR} and threading include dirs) so it clearly documents why
${SOMEIP_ALLOC_IMPL_DIR} must appear before the threading include dirs; keep
references to SOMEIP_USE_STATIC_ALLOC and SOMEIP_ALLOC_IMPL_DIR so future
readers can find the related conditional and include-path logic.

In `@src/platform/static/malloc_trap.cpp`:
- Around line 37-63: The current trap implements malloc/free/calloc/realloc but
misses C++ heap operators so non-placement new bypasses it; add matching global
operator new, operator new[](std::size_t) and their corresponding operator
delete/operator delete[](void*) (and the nothrow/new(size, std::nothrow_t)
overloads if used) to abort with an explanatory fprintf like the existing
functions, referencing the same behavior as malloc/free handlers (e.g.,
implement operator new(std::size_t) and operator new[](std::size_t) to fprintf
"MALLOC TRAP: operator new(%zu) detected" and call std::abort(), and implement
operator delete/operator delete[] to detect frees and abort similarly) so all
non-placement C++ allocations are trapped.

In `@tests/CMakeLists.txt`:
- Around line 110-117: The test target test_static_alloc is not linked against
the someip_malloc_trap OBJECT library so heap-interception won't be enforced;
update the CMake target for test_static_alloc (target name "test_static_alloc")
to add the someip_malloc_trap object library via target_link_libraries (e.g.,
link someip_malloc_trap as a PRIVATE dependency) so the malloc trap defined as
someip_malloc_trap in src/CMakeLists.txt is included at link time and enables
no-heap verification.
- Line 114: Remove the redundant PRIVATE definition of SOMEIP_STATIC_ALLOC from
the test target: delete the line calling
target_compile_definitions(test_static_alloc PRIVATE SOMEIP_STATIC_ALLOC) since
SOMEIP_STATIC_ALLOC is already defined PUBLIC on the opensomeip target and will
propagate to test_static_alloc through linking; ensure opensomeip remains a
PUBLIC-linked target for test_static_alloc so the definition still applies.

In `@tests/test_static_alloc.cpp`:
- Around line 385-411: The test BufferPoolThreadSafety currently uses a weak
smoke assertion (EXPECT_GT(success_count.load(), 0)); replace it with a stronger
correctness check that validates all attempted acquires succeeded by asserting
EXPECT_EQ(success_count.load(), kThreads * kOpsPerThread) (referencing kThreads,
kOpsPerThread, success_count, and the test name) so the test fails if any
acquisition/release under contention is lost; after changing the assertion,
consider enabling ThreadSanitizer in CI for additional race detection.

---

Outside diff comments:
In `@docs/requirements/implementation/platform.rst`:
- Around line 483-485: REQ_PAL_MEM_ALLOC currently hard-codes std::shared_ptr in
the spec: change the wording around someip::platform::allocate_message() and
MessagePtr so they do not mandate std::shared_ptr; instead describe MessagePtr
as an abstract/opaque smart-pointer type (or "pointer-like handle") owning a
fully constructed Message and allow backend choice (e.g., intrusive ref-counting
or static pool-backed handles) so it remains compatible with
REQ_PAL_INTRUSIVE_PTR and the static message-pool requirement; update all
occurrences (including the section referencing lines ~966-977) to remove
"std::shared_ptr<Message>" and replace with this backend-agnostic description,
while keeping the contract that callers receive an owning MessagePtr with a
usable Message instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7022fc2e-e97d-4c07-b656-c9fe3e100de8

📥 Commits

Reviewing files that changed from the base of the PR and between 43f0d16 and 2a04600.

📒 Files selected for processing (24)
  • CMakeLists.txt
  • docs/requirements/implementation/architecture.rst
  • docs/requirements/implementation/platform.rst
  • docs/safety/FMEA_STATIC_ALLOCATION.md
  • include/platform/buffer_pool.h
  • include/platform/containers.h
  • include/platform/dynamic/buffer_pool_impl.h
  • include/platform/dynamic/containers_impl.h
  • include/platform/intrusive_ptr.h
  • include/platform/static/buffer_pool_impl.h
  • include/platform/static/containers_impl.h
  • include/platform/static/memory_impl.h
  • include/platform/static/static_config.h
  • scripts/run_clang_tidy.sh
  • scripts/static_memory_budget.py
  • src/CMakeLists.txt
  • src/platform/static/buffer_pool.cpp
  • src/platform/static/malloc_trap.cpp
  • src/platform/static/memory.cpp
  • src/someip/message.cpp
  • tests/CMakeLists.txt
  • tests/fakes/buffer_pool_impl.h
  • tests/fakes/containers_impl.h
  • tests/test_static_alloc.cpp

Comment thread docs/requirements/implementation/architecture.rst Outdated
Comment thread docs/requirements/implementation/platform.rst Outdated
Comment thread docs/safety/FMEA_STATIC_ALLOCATION.md
Comment thread docs/safety/FMEA_STATIC_ALLOCATION.md Outdated
Comment thread docs/safety/FMEA_STATIC_ALLOCATION.md
Comment thread src/CMakeLists.txt
Comment thread src/platform/static/malloc_trap.cpp Outdated
Comment thread tests/CMakeLists.txt
Comment thread tests/CMakeLists.txt Outdated
Comment thread tests/test_static_alloc.cpp Outdated
vtz and others added 2 commits June 12, 2026 17:36
Move MessagePtr typedef to PAL dispatch (message_ptr.h -> message_ptr_impl.h)
resolved by include-path shadowing, matching the existing PAL pattern.

- ref_count_ and intrusive_ptr hooks are now unconditional (2 bytes per
  Message; negligible overhead, identical class layout across backends)
- Dynamic backends gain a trivial release_message() so the unconditional
  intrusive_ptr_release() compiles and links (dead code in dynamic path)
- SOMEIP_STATIC_ALLOC compile definition removed entirely -- no source
  code references it anymore; backend selection is purely via -I paths

Co-authored-by: Cursor <cursoragent@cursor.com>
Blocker:
- ByteBuffer: check slot_->capacity >= needed after ensure_capacity()
  to prevent out-of-bounds writes when pool upgrade fails

High:
- ByteBuffer: return nullptr from end() when slot_ is null to avoid
  undefined pointer arithmetic (nullptr + 0)
- malloc_trap: add operator new/new[]/delete/delete[] traps alongside
  C malloc/free/calloc/realloc for complete no-heap verification
- Link someip_malloc_trap OBJECT library into test_static_alloc so
  REQ_PAL_NOOP_HEAP_VERIFY is enforced at test runtime

Medium:
- static_config.h: add static_assert guards for pool sizes (uint16_t
  range) and tier size ordering
- platform.rst: rename StaticVector/StaticString/StaticUnorderedMap/
  StaticQueue/StaticFunction to Vector/String/UnorderedMap/Queue/
  Function matching actual containers_impl.h; fix Code Location
  references to point to containers_impl.h instead of non-existent
  container_*.h files; update test file references

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/someip/message.cpp (1)

553-557: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Missing overflow protection for reference counter.

The intrusive reference counter uses uint16_t (max 65,535) with no overflow check. If ref_count_ reaches its maximum and intrusive_ptr_add_ref is called again, it will wrap to 0, causing premature deletion when the first IntrusivePtr is destroyed.

For ISO 26262 safety-critical deployments, bounded reference counts should either use a wider type or detect overflow and fail safely.

🛡️ Proposed overflow guard
 void intrusive_ptr_add_ref(const Message* p) {
     if (p) {
-        p->ref_count_.fetch_add(1, std::memory_order_relaxed);
+        auto old_count = p->ref_count_.fetch_add(1, std::memory_order_relaxed);
+        // Safety check: detect overflow before it causes wrap-around
+        if (old_count >= std::numeric_limits<uint16_t>::max()) {
+            // Overflow: either abort or use a wider type (uint32_t)
+            std::abort();  // Or log critical error and halt
+        }
     }
 }

Alternatively, consider changing ref_count_ to std::atomic<uint32_t> in include/someip/message.h if the 2-byte footprint saving is not critical.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/someip/message.cpp` around lines 553 - 557, intrusive_ptr_add_ref
currently increments Message::ref_count_ (a uint16_t atomic) without overflow
protection; either widen the atomic to std::atomic<uint32_t> in the Message
class (preferred) or add an overflow check in intrusive_ptr_add_ref: load the
current value, if it equals std::numeric_limits<uint16_t>::max() fail safely
(log/abort/throw) and otherwise increment using compare-exchange or fetch_add
with a check to avoid wraparound; update any code using ref_count_ accordingly
and preserve the intended memory ordering semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@include/platform/dynamic/message_ptr_impl.h`:
- Around line 10-15: This header declares MessagePtr and MessageConstPtr using
std::shared_ptr (symbols: MessagePtr, MessageConstPtr, namespace someip) but
lacks the required <memory> include; add a direct include of <memory> at the top
of include/platform/dynamic/message_ptr_impl.h so the header no longer relies on
transitive includes for std::shared_ptr and remains robust to include-order
changes.

In `@include/platform/static/message_ptr_impl.h`:
- Around line 10-15: Add a direct include for the intrusive pointer header so
the typedefs compile without relying on transitive includes: include
"platform/intrusive_ptr.h" at the top of message_ptr_impl.h before using
platform::IntrusivePtr; update the file that defines MessagePtr and
MessageConstPtr (the typedefs using platform::IntrusivePtr<Message> and
platform::IntrusivePtr<const Message>) to reference the header explicitly.

In `@tests/fakes/memory_impl.h`:
- Around line 33-35: The current release_message(Message* msg) deletes the raw
pointer while allocate_message() returns std::shared_ptr<Message>, risking
double-free if callers pass ptr.get(); fix by making the ownership contract
explicit: add a clear comment above release_message stating "must never be
called with pointers owned by allocate_message()/std::shared_ptr" and provide a
safe overload release_message(std::shared_ptr<Message> msg) that is a no-op (or
simply consumes the shared_ptr) so accidental calls with shared_ptr objects are
safe; optionally mark the raw-pointer release_message as deprecated in the
comment to discourage use and keep delete behavior only for truly raw-allocated
Message instances.

---

Outside diff comments:
In `@src/someip/message.cpp`:
- Around line 553-557: intrusive_ptr_add_ref currently increments
Message::ref_count_ (a uint16_t atomic) without overflow protection; either
widen the atomic to std::atomic<uint32_t> in the Message class (preferred) or
add an overflow check in intrusive_ptr_add_ref: load the current value, if it
equals std::numeric_limits<uint16_t>::max() fail safely (log/abort/throw) and
otherwise increment using compare-exchange or fetch_add with a check to avoid
wraparound; update any code using ref_count_ accordingly and preserve the
intended memory ordering semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aaeb92b7-dfc8-4aee-a02f-4686b83d3863

📥 Commits

Reviewing files that changed from the base of the PR and between 2a04600 and a33acfd.

📒 Files selected for processing (14)
  • include/platform/dynamic/message_ptr_impl.h
  • include/platform/message_ptr.h
  • include/platform/posix/memory_impl.h
  • include/platform/static/message_ptr_impl.h
  • include/platform/win32/memory_impl.h
  • include/someip/message.h
  • src/CMakeLists.txt
  • src/someip/message.cpp
  • tests/CMakeLists.txt
  • tests/fakes/memory_impl.h
  • tests/fakes/message_ptr_impl.h
  • tests/test_pal_freertos_mock.cpp
  • tests/test_pal_threadx_mock.cpp
  • tests/test_pal_zephyr_mock.cpp
💤 Files with no reviewable changes (1)
  • src/CMakeLists.txt

Comment thread include/platform/dynamic/message_ptr_impl.h
Comment thread include/platform/static/message_ptr_impl.h
Comment thread tests/fakes/memory_impl.h
vtz and others added 3 commits June 12, 2026 17:53
- intrusive_ptr.h: suppress hicpp-explicit-conversions for nullptr_t ctor
- posix/memory_impl.h: suppress cppcoreguidelines-owning-memory on delete
- message.cpp: add <atomic> include, use explicit nullptr comparisons,
  suppress const_cast and include-cleaner in intrusive_ptr hooks

Co-authored-by: Cursor <cursoragent@cursor.com>
The dispatch header message_ptr.h includes message_ptr_impl.h which
must be findable from the backend's include directory. For the host
CMake build, include/platform/dynamic/ provides it. But standalone
build systems (Zephyr west) only add the backend directory to -I,
so each backend needs its own message_ptr_impl.h.

Fixes Zephyr S32K388 Renode build failure:
  fatal error: message_ptr_impl.h: No such file or directory

Co-authored-by: Cursor <cursoragent@cursor.com>
- CMake: set ETL_LOG_ERRORS=1, ETL_THROW_EXCEPTIONS=0, ETL_NO_STL=1
  when SOMEIP_USE_STATIC_ALLOC=ON — enforces FMEA-prescribed ETL error
  handling (no abort/exception on container overflow)

- Add test_pal_static_alloc_mock.cpp: PAL conformance tests exercising
  the real static message pool + buffer pool with POSIX threading,
  verifying the static backend satisfies the same contracts as dynamic

- Add debug-mode assert in intrusive_ptr_add_ref to detect ref_count_
  saturation (uint16_t overflow) — fires only in debug builds, prevents
  silent wrap-around in development

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/requirements/implementation/platform.rst (2)

840-846: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the expected error-handling behavior for capacity exhaustion (line 845).

The parenthetical comment "(ETL container overflow behavior governed by the ETL error handler)" appears incomplete. The requirement text (lines 833–842) states that insertion operations "shall fail gracefully…without crashing, corrupting…or allocating from the heap," but line 845 defers error handling to ETL's handler without explaining what that entails.

For safety-critical contexts, readers need clarity: Does "gracefully" mean ETL logs an error, throws an exception (if ETL_THROW_EXCEPTIONS=1), or returns a status code? If ETL behavior is externally defined, the requirement should explicitly state the expected outcome (e.g., "insertion returns false on overflow" or "ETL error handler is configured via ETL_LOG_ERRORS=1 and ETL_THROW_EXCEPTIONS=0").

📝 Suggested revision

Replace lines 841–845 with explicit expected behavior:

   **Error Handling**: Return ``false`` or an error indicator; container
-  internals remain consistent.
+  internals remain consistent. When ``SOMEIP_USE_STATIC_ALLOC`` is enabled,
+  ETL containers are configured with ``ETL_THROW_EXCEPTIONS=0``,
+  ``ETL_LOG_ERRORS=1``, and ``ETL_NO_STL=1``; insertion failures are
+  signaled via return codes rather than exceptions.

-  **Code Location**: ``include/platform/static/containers_impl.h``
+  **Code Location**: ``include/platform/static/containers_impl.h``,
+  ``CMakeLists.txt`` (ETL build flags)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/requirements/implementation/platform.rst` around lines 840 - 846,
Clarify that on capacity exhaustion insertion operations (functions in
include/platform/static/containers_impl.h) must return false or an error status
without allocating from the heap, crashing, or corrupting state; explicitly
state that ETL is used only for diagnostics and must be configured so
ETL_THROW_EXCEPTIONS=0 and ETL_LOG_ERRORS=1 (so the container returns false on
overflow and ETL logs the event) or otherwise document the alternative
configuration and outcome; update the parenthetical to read that the ETL error
handler is used only to log diagnostics and must be configured not to throw so
container methods (e.g., push/insert/emplace in the static containers)
deterministically return false on overflow.

981-982: 📐 Maintainability & Code Quality | 🟡 Minor

Fix docs “Code Location” for Message intrusive ref-count
docs/requirements/implementation/platform.rst lines 981-982 cite include/common/message.h for the embedded intrusive ref-count, but include/common/message.h does not exist in the repo; Message’s intrusive ref_count_{0} is in include/someip/message.h (with intrusive_ptr_add_ref / intrusive_ptr_release). Update the cited path to include/someip/message.h.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/requirements/implementation/platform.rst` around lines 981 - 982, The
docs "Code Location" entry currently points to a non-existent header; update the
Code Location to cite the header that actually defines Message and its intrusive
ref-count (the header that contains class Message, ref_count_{0}, and the
intrusive_ptr_add_ref / intrusive_ptr_release implementations) — replace the
incorrect include/common/message.h with include/someip/message.h and ensure the
text mentions Message, ref_count_{0}, intrusive_ptr_add_ref, and
intrusive_ptr_release so readers can find the implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/requirements/implementation/platform.rst`:
- Line 740: platform.rst currently references non-existent test files and
TC_CONTAINER_* IDs (e.g., TC_CONTAINER_VECTOR_PUSH_BACK) which are not present;
update the requirement-to-test traceability by editing
docs/requirements/implementation/platform.rst to either (A) replace references
to tests/test_buffer_pool.cpp, tests/test_no_heap.cpp,
tests/test_pimpl_static.cpp, and tests/test_static_memory.cpp with the
consolidated tests/test_static_alloc.cpp and the actual TC IDs found there
(e.g., TC_BUFPOOL_* , TC_INTRUSIVE_PTR_LIFETIME, TC_BYTEBUFFER_API,
TC_PIMPL_NO_HEAP), and adjust the TC mapping entries so
TC_CONTAINER_VECTOR_PUSH_BACK and other TC_CONTAINER_* entries point to the
correct test or are removed, or (B) restore the missing test sources and their
TC_CONTAINER_* mappings if you intend to keep those IDs; ensure the platform.rst
mapping lines reference the exact test file name tests/test_static_alloc.cpp and
the exact TC identifiers present in that file to restore correct traceability.

---

Outside diff comments:
In `@docs/requirements/implementation/platform.rst`:
- Around line 840-846: Clarify that on capacity exhaustion insertion operations
(functions in include/platform/static/containers_impl.h) must return false or an
error status without allocating from the heap, crashing, or corrupting state;
explicitly state that ETL is used only for diagnostics and must be configured so
ETL_THROW_EXCEPTIONS=0 and ETL_LOG_ERRORS=1 (so the container returns false on
overflow and ETL logs the event) or otherwise document the alternative
configuration and outcome; update the parenthetical to read that the ETL error
handler is used only to log diagnostics and must be configured not to throw so
container methods (e.g., push/insert/emplace in the static containers)
deterministically return false on overflow.
- Around line 981-982: The docs "Code Location" entry currently points to a
non-existent header; update the Code Location to cite the header that actually
defines Message and its intrusive ref-count (the header that contains class
Message, ref_count_{0}, and the intrusive_ptr_add_ref / intrusive_ptr_release
implementations) — replace the incorrect include/common/message.h with
include/someip/message.h and ensure the text mentions Message, ref_count_{0},
intrusive_ptr_add_ref, and intrusive_ptr_release so readers can find the
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5595ffbc-2a5e-4197-b90c-920c20f33308

📥 Commits

Reviewing files that changed from the base of the PR and between a33acfd and 2a32a98.

📒 Files selected for processing (15)
  • docs/requirements/implementation/platform.rst
  • include/platform/freertos/message_ptr_impl.h
  • include/platform/intrusive_ptr.h
  • include/platform/posix/memory_impl.h
  • include/platform/posix/message_ptr_impl.h
  • include/platform/static/buffer_pool_impl.h
  • include/platform/static/static_config.h
  • include/platform/threadx/message_ptr_impl.h
  • include/platform/win32/message_ptr_impl.h
  • include/platform/zephyr/message_ptr_impl.h
  • src/CMakeLists.txt
  • src/platform/static/malloc_trap.cpp
  • src/someip/message.cpp
  • tests/CMakeLists.txt
  • tests/test_pal_static_alloc_mock.cpp

Comment thread docs/requirements/implementation/platform.rst
vtz and others added 2 commits June 12, 2026 19:13
- Add TC_CONTAINER_VECTOR_PUSH_BACK, TC_CONTAINER_STRING_APPEND,
  TC_CONTAINER_MAP_INSERT_LOOKUP, TC_CONTAINER_QUEUE_FIFO,
  TC_CONTAINER_FUNCTION_INVOKE, TC_CONTAINER_CAPACITY_EXHAUST tests
  to test_static_alloc.cpp — completes requirement traceability for
  REQ_PAL_CONTAINER_* in platform.rst

- Fix doc references: update Code Location for intrusive ptr to
  include/someip/message.h, update no-heap test file ref to
  test_static_alloc.cpp, clarify ETL error handling defines

- Remove ETL_NO_STL=1 (conflicts with host STL on non-bare-metal
  targets; only ETL_LOG_ERRORS and ETL_THROW_EXCEPTIONS needed)

Co-authored-by: Cursor <cursoragent@cursor.com>
- architecture.rst: broaden heap-verification scope to include free/delete
- FMEA: fix CMake target name (opensomeip, PUBLIC), update refcount
  saturation entry to match debug-assert implementation, update residual
  risk assessment

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_static_alloc.cpp`:
- Around line 533-547: Extend the ContainerTest::CapacityExhaust test to verify
overflow behavior when capacity is exhausted: after filling Vector<int,4> v and
Queue<int,2> q to capacity (using Vector::push_back and Queue::push), attempt
one more insertion for each and assert the operation fails gracefully (e.g.,
returns a false/error indicator or does not change state) by checking the return
value if push/push_back return status or by asserting size() remains at capacity
and container contents are unchanged; update the test to use Vector, Queue,
push_back, push, full, and size to locate the relevant checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 39955ab6-d74f-482d-ac92-8fdd0c7a1d83

📥 Commits

Reviewing files that changed from the base of the PR and between 2a32a98 and 69bbeda.

📒 Files selected for processing (5)
  • docs/requirements/implementation/architecture.rst
  • docs/requirements/implementation/platform.rst
  • docs/safety/FMEA_STATIC_ALLOCATION.md
  • src/CMakeLists.txt
  • tests/test_static_alloc.cpp
💤 Files with no reviewable changes (1)
  • src/CMakeLists.txt

Comment thread tests/test_static_alloc.cpp Outdated
vtz and others added 2 commits June 12, 2026 23:44
Three issues addressed from follow-up review:

1. CI/preset gap: static-alloc backend was never built or tested in CI.
   - Add `static-alloc-linux-tests` preset to CMakePresets.json
   - Add `build-static` job to host.yml (build + ctest with
     SOMEIP_USE_STATIC_ALLOC=ON)
   - Register preset in preset-validation.yml

2. Traceability: platform.rst referenced non-existent TC_NO_HEAP_PROTOCOL_RUN.
   - Rename to TC_NO_HEAP_VERIFY and update description to reflect
     that test_static_alloc links malloc_trap which aborts on any heap
     call, so all passing tests implicitly verify no-heap compliance
   - Add TC_NO_HEAP_VERIFY and REQ_PAL_NOOP_HEAP_VERIFY to test header

3. Double-release: release_buffer had no guard against returning the
   same slot index to the free stack twice.
   - Add per-slot `in_use` tracking arrays in buffer_pool.cpp
   - Set true on acquire, false on release; reject release if !in_use
   - Add DoubleReleaseIsSafe test proving two consumers never get the
     same slot after a double-release attempt

Co-authored-by: Cursor <cursoragent@cursor.com>
GCC with -Werror rejects operator new returning nullptr unless declared
noexcept/throw(). Since these trap functions always call abort(), the
noexcept specification is correct and silences the diagnostic.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vtz
vtz force-pushed the feature/no-heap-static-alloc branch from 21f0fc2 to 1dc6696 Compare June 13, 2026 03:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/platform/static/buffer_pool.cpp (1)

135-144: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

in_use still allows stale double-release after the slot is reissued.

This check only answers whether the slot is leased now; it does not prove the caller owns the current lease. If a slot is released, reacquired, and then an old BufferSlot* is released again, Line 139 still passes, the live slot is pushed back onto the free stack, and a later acquire_buffer() can hand the same backing storage to two owners at once. This needs an ownership-carrying handle/token (for example, a generation-bound opaque handle) instead of a per-slot boolean. Please also extend BufferPoolTest.DoubleReleaseIsSafe to reacquire the slot before the second release so this path regresses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/static/buffer_pool.cpp` around lines 135 - 144, The release path
allows stale BufferSlot* double-release because in_use_ptrs[t][i] is a boolean
that doesn't prove the caller still owns the lease; replace it with an ownership
token (e.g., add a per-slot generation counter and return an opaque handle /
generation-bound token from acquire_buffer() or embed a generation field in
BufferSlot) and require the release function to verify the token/generation
matches the current slot generation before accepting it; on successful release
increment the slot generation and mark it free (update in_use_ptrs => ownership
state), update free_stack_ptrs/stack_top logic to only run after token
verification, and extend BufferPoolTest.DoubleReleaseIsSafe to reacquire the
slot before attempting the second release to assert the regression is covered.
docs/requirements/implementation/platform.rst (1)

956-956: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the actual static-config knob names.

include/platform/static/static_config.h exposes SOMEIP_MESSAGE_POOL_SIZE and SOMEIP_BYTE_POOL_*; the SOMEIP_STATIC_MESSAGE_POOL_SIZE / SOMEIP_STATIC_BUFPOOL_TIER_* names here do not exist, so this points users at the wrong CMake inputs.

Suggested fix
-   Configure ``SOMEIP_STATIC_MESSAGE_POOL_SIZE``, ``SOMEIP_STATIC_BUFPOOL_TIER_*`` via CMake
+   Configure ``SOMEIP_MESSAGE_POOL_SIZE`` and ``SOMEIP_BYTE_POOL_*`` via CMake
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/requirements/implementation/platform.rst` at line 956, The documentation
references non-existent CMake knobs `SOMEIP_STATIC_MESSAGE_POOL_SIZE` and
`SOMEIP_STATIC_BUFPOOL_TIER_*`; update the text to use the actual static-config
symbol names exported in include/platform/static/static_config.h — namely
`SOMEIP_MESSAGE_POOL_SIZE` and `SOMEIP_BYTE_POOL_*` (or the exact tiered
`SOMEIP_BYTE_POOL_TIER_*` names used in the header) and state that these are
configured via CMake so the generated static_config.h reflects those values and
pool sizes at runtime without heap allocation.
♻️ Duplicate comments (1)
docs/requirements/implementation/platform.rst (1)

859-859: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the remaining traceability refs to the consolidated static suite.

These requirements still point at the pre-consolidation split-test layout. Please retarget the buffer-pool, no-heap, static-memory, and pimpl verifications to tests/test_static_alloc.cpp and the TC IDs that actually live there; the no-heap block should also update its Code Location to src/platform/static/malloc_trap.cpp. Same cleanup as the earlier traceability note.

Also applies to: 878-878, 896-896, 914-914, 933-933, 1004-1004, 1033-1033, 1056-1056, 1078-1078, 1096-1096

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/requirements/implementation/platform.rst` at line 859, Update the
traceability references that still point to the old split-test layout by
retargeting the buffer-pool, no-heap, static-memory, and pimpl verification
entries to the consolidated static test suite: change their test verification
lines to reference tests/test_static_alloc.cpp and the corresponding TC IDs that
now live in that file (instead of the old per-component IDs), and update the
no-heap Code Location reference to src/platform/static/malloc_trap.cpp; apply
the same replacement pattern for the other occurrences listed (lines noted in
the comment) so all refs consistently point to tests/test_static_alloc.cpp and
the correct TC identifiers and code location.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/host.yml:
- Line 179: The checkout step currently uses actions/checkout@v4 without
disabling credential persistence; update the checkout invocation for the host
workflow (the actions/checkout@v4 usage) to include persist-credentials: false
so it matches the coverage job and minimizes stored credentials—locate the
actions/checkout@v4 step and add the persist-credentials: false key to its
configuration.

In `@src/platform/static/malloc_trap.cpp`:
- Around line 71-75: Add the same unreachable return pattern used in the C
overrides to the C++ allocation overrides: after the std::abort() call in both
operator new(std::size_t) noexcept and operator new[](std::size_t) noexcept, add
a "return nullptr; // unreachable" to suppress potential compiler warnings and
keep consistency with malloc/calloc/realloc overrides.
- Around line 71-81: Add the nothrow allocation overloads and the <new> include
so nothrow allocations cannot bypass the trap: include <new> at the top, and
implement operator new(std::size_t, const std::nothrow_t&) noexcept and operator
new[](std::size_t, const std::nothrow_t&) noexcept matching the existing trap
behavior (print the MALLOC TRAP message including the size with fprintf and
abort), using the same message style as operator new and operator new[] so all
forms of new are covered.

---

Outside diff comments:
In `@docs/requirements/implementation/platform.rst`:
- Line 956: The documentation references non-existent CMake knobs
`SOMEIP_STATIC_MESSAGE_POOL_SIZE` and `SOMEIP_STATIC_BUFPOOL_TIER_*`; update the
text to use the actual static-config symbol names exported in
include/platform/static/static_config.h — namely `SOMEIP_MESSAGE_POOL_SIZE` and
`SOMEIP_BYTE_POOL_*` (or the exact tiered `SOMEIP_BYTE_POOL_TIER_*` names used
in the header) and state that these are configured via CMake so the generated
static_config.h reflects those values and pool sizes at runtime without heap
allocation.

In `@src/platform/static/buffer_pool.cpp`:
- Around line 135-144: The release path allows stale BufferSlot* double-release
because in_use_ptrs[t][i] is a boolean that doesn't prove the caller still owns
the lease; replace it with an ownership token (e.g., add a per-slot generation
counter and return an opaque handle / generation-bound token from
acquire_buffer() or embed a generation field in BufferSlot) and require the
release function to verify the token/generation matches the current slot
generation before accepting it; on successful release increment the slot
generation and mark it free (update in_use_ptrs => ownership state), update
free_stack_ptrs/stack_top logic to only run after token verification, and extend
BufferPoolTest.DoubleReleaseIsSafe to reacquire the slot before attempting the
second release to assert the regression is covered.

---

Duplicate comments:
In `@docs/requirements/implementation/platform.rst`:
- Line 859: Update the traceability references that still point to the old
split-test layout by retargeting the buffer-pool, no-heap, static-memory, and
pimpl verification entries to the consolidated static test suite: change their
test verification lines to reference tests/test_static_alloc.cpp and the
corresponding TC IDs that now live in that file (instead of the old
per-component IDs), and update the no-heap Code Location reference to
src/platform/static/malloc_trap.cpp; apply the same replacement pattern for the
other occurrences listed (lines noted in the comment) so all refs consistently
point to tests/test_static_alloc.cpp and the correct TC identifiers and code
location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61c9ea59-ee13-4796-855f-8652ef237dce

📥 Commits

Reviewing files that changed from the base of the PR and between 69bbeda and 1dc6696.

📒 Files selected for processing (7)
  • .github/workflows/host.yml
  • .github/workflows/preset-validation.yml
  • CMakePresets.json
  • docs/requirements/implementation/platform.rst
  • src/platform/static/buffer_pool.cpp
  • src/platform/static/malloc_trap.cpp
  • tests/test_static_alloc.cpp

Comment thread src/platform/static/malloc_trap.cpp Outdated
Comment thread src/platform/static/malloc_trap.cpp Outdated
vtz and others added 9 commits June 13, 2026 00:00
The unconditional malloc_trap aborted during GTest initialization
(73728-byte allocation from GTest's own infrastructure). Redesign to
arm/disarm model:

- malloc_trap_arm() / malloc_trap_disarm(): test code enables the trap
  only around protocol operations, allowing GTest and std::thread to
  use the heap normally.
- When disarmed, calls pass through to __libc_malloc/free.
- When armed, any heap call (malloc/free/new/delete) aborts.
- New TC_NO_HEAP_VERIFY test: arms trap, exercises message pool +
  buffer pool + intrusive ptr lifecycle, then disarms.
- Add malloc_trap.h header for test API declarations.
- Filter DestructorWithoutJoinSafe from test_pal_static_alloc_mock
  (POSIX Thread correctly terminates per REQ_PAL_THREAD_DTOR_E01,
  which is incompatible with this conformance test expectation).

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace direct STL container usage throughout the protocol stack with
platform-abstracted type aliases (platform::ByteBuffer, platform::Vector,
platform::String, platform::UnorderedMap, platform::Queue,
platform::Function). Under the dynamic backend these resolve to their
STL counterparts (zero overhead). Under the static backend they map to
ETL fixed-capacity types enabling no-heap operation.

Key changes across 46 files:
- message.h/cpp: payload_ and serialize/deserialize use ByteBuffer
- serializer.h/cpp: buffer_, array serialization use platform types
- Transport (endpoint, tcp, udp): String for addresses, ByteBuffer
  for wire data, Queue for message queues
- SD (types, message, client, server): all entry/option serialization,
  callbacks, and config strings migrated
- TP (types, reassembler, segmenter, manager): segment payloads,
  reassembly buffers, and callbacks migrated
- Events/RPC (types, publisher, subscriber, client, server): event
  data, filter data, request/response payloads, callbacks migrated
- E2E (config, profile, registry, standard_profile): profile names,
  counter/freshness maps, CRC data buffers migrated
- Session manager: sessions map migrated to UnorderedMap

Debug-only functions (to_string()) intentionally retain std::string.
Smart pointers (shared_ptr, unique_ptr) and std::chrono stay as-is.
No #ifdef introduced — PAL include-path shadowing handles dispatch.

Closes #174

Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix static-alloc build: use std::hash<string_view> instead of
  std::hash<platform::String<>> (no std::hash specialization for
  etl::string); replace std::stoi with std::atoi for ETL compat.
- Fix clang-tidy misc-include-cleaner: add IWYU pragma: export to
  PAL dispatch headers so clang-tidy recognizes them as providers.
- Fix clang-tidy readability-implicit-bool-conversion: use explicit
  nullptr comparison in message.h and message.cpp.
- Remove redundant platform includes from .cpp files (already
  provided through their corresponding .h headers).
- Fix Zephyr build: add platform/dynamic include path for
  buffer_pool_impl.h and containers_impl.h resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace brace-initializer-list ByteBuffer construction from iterator
pair with the deserialize(uint8_t*, size_t) overload, which works with
both std::vector and ETL static backends.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Re-add platform dispatch header includes where clang-tidy's
  misc-include-cleaner flags "no header providing X". The IWYU
  export pragma prevents the "not used directly" false positive.
- Replace atoi with strtol (cert-err34-c).
- Fix UDP transport: use deserialize(ptr, size) overload instead of
  brace-init-list ByteBuffer construction from iterator pair.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add ByteBuffer(const uint8_t* first, const uint8_t* last) constructor
  for iterator-pair construction used in TCP transport.
- Add erase(const_iterator, const_iterator) for buffer management in
  TCP transport's message framing and resync logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
Config string data (char*) must be reinterpret_cast to uint8_t* when
inserting into ByteBuffer, matching the pattern already used in
serializer.cpp.

Co-authored-by: Cursor <cursoragent@cursor.com>
etl::string::assign() requires char* iterators, not uint8_t*. Use
reinterpret_cast when extracting string data from ByteBuffer.

Co-authored-by: Cursor <cursoragent@cursor.com>
vtz and others added 2 commits July 2, 2026 10:47
…ialized)

GCC 13 emits false-positive -Wmaybe-uninitialized warnings when
std::variant move-constructs classes with trivial members through
deep inlining of variant internals. Add explicit move constructors
that member-wise initialize all fields for ServiceEntry,
EventGroupEntry, IPv4EndpointOption, and IPv4MulticastOption.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Revert explicit move ctors on SD variant types (caused
  bugprone-use-after-move and special-member-functions warnings)
- Add #pragma GCC diagnostic to sd_message.cpp to suppress the
  false-positive -Wmaybe-uninitialized from std::variant internals
- Wrap #include <new> in #ifdef SOMEIP_STATIC_ALLOC in 6 pimpl source
  files to fix misc-include-cleaner warnings in the dynamic build

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (19)
src/core/session_manager.cpp (1)

38-45: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle fixed-capacity session exhaustion before insertion.

sessions_ is now platform::UnorderedMap<uint16_t, Session, 256>, but create_session() still inserts unconditionally. In static builds, the 257th live session can overflow/assert/trap instead of returning a controlled failure. Since session ID 0 is already reserved, use it as a failure sentinel or change the API to return a status.

Proposed fix
 uint16_t SessionManager::create_session(uint16_t client_id) {
     platform::ScopedLock const lock(sessions_mutex_);

+    if (sessions_.size() >= sessions_.max_size()) {
+        return 0;
+    }
+
     uint16_t const session_id = get_next_session_id();

     sessions_[session_id] = Session(session_id, client_id);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/session_manager.cpp` around lines 38 - 45,
SessionManager::create_session currently inserts into the fixed-capacity
sessions_ map unconditionally, which can overflow once the 256-entry limit is
reached. Update create_session (and any callers relying on its return value) to
check for capacity exhaustion before calling sessions_[session_id] =
Session(...), and return the reserved session_id 0 as a failure sentinel or
otherwise surface a controlled failure instead of trapping. Use the
SessionManager::create_session, get_next_session_id, and sessions_ symbols to
locate the fix.
src/platform/static/malloc_trap.cpp (1)

43-47: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the trap arm state atomic.

Allocation hooks can run on any thread while tests arm/disarm the trap; reading and writing g_trap_armed as a plain bool is a data race. Use std::atomic_bool with relaxed load/store.

Proposed fix
-static bool g_trap_armed = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+static std::atomic_bool g_trap_armed{false};  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

-void malloc_trap_arm() { g_trap_armed = true; }
-void malloc_trap_disarm() { g_trap_armed = false; }
-bool malloc_trap_is_armed() { return g_trap_armed; }
+void malloc_trap_arm() { g_trap_armed.store(true, std::memory_order_relaxed); }
+void malloc_trap_disarm() { g_trap_armed.store(false, std::memory_order_relaxed); }
+bool malloc_trap_is_armed() { return g_trap_armed.load(std::memory_order_relaxed); }

Also applies to: 70-83, 87-101, 111-181

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/static/malloc_trap.cpp` around lines 43 - 47, Make the trap arm
state thread-safe by changing g_trap_armed in malloc_trap.cpp from a plain bool
to std::atomic_bool, since malloc hooks can call malloc_trap_is_armed()
concurrently with malloc_trap_arm() and malloc_trap_disarm(). Update the three
helpers to use relaxed store/load on g_trap_armed, and keep the existing
behavior and naming in malloc_trap_arm, malloc_trap_disarm, and
malloc_trap_is_armed unchanged.
src/sd/sd_server.cpp (3)

159-168: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject empty unicast endpoints before storing the service.

offer_service() skips validation when unicast_endpoint is empty, stores the service, returns true, and then send_service_offer() silently returns because parsing fails. This reports a service as offered even though no offer can be sent.

Proposed fix
-        if (!unicast_endpoint.empty()) {
-            platform::String<> tmp_ip;
-            uint16_t tmp_port = 0;
-            if (!parse_endpoint_string(unicast_endpoint, tmp_ip, tmp_port)) {
-                return false;
-            }
+        platform::String<> tmp_ip;
+        uint16_t tmp_port = 0;
+        if (!parse_endpoint_string(unicast_endpoint, tmp_ip, tmp_port)) {
+            return false;
         }

Also applies to: 202-205

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_server.cpp` around lines 159 - 168, offer_service() currently
accepts an empty unicast_endpoint and still stores the service, which makes the
call succeed even though send_service_offer() cannot actually send anything.
Update offer_service() to reject empty unicast endpoints up front, using the
existing validation flow around parse_endpoint_string(), and return false before
storing the service or proceeding with the offer. Make the same check in the
corresponding service-offer path referenced by the later block so both entry
points consistently fail fast on empty unicast_endpoint values.

71-77: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Remove heap allocation from the static SD server path.

create_sd_transport() still constructs the UDP transport with std::make_shared, so SdServer construction in static-allocation mode is not heap-free. Use inline storage or a pool-backed transport factory under SOMEIP_STATIC_ALLOC.

Also applies to: 793-793

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_server.cpp` around lines 71 - 77, Remove the heap allocation from
the static SD server path: create_sd_transport() currently uses std::make_shared
to build transport::UdpTransport, so SdServer initialization is not heap-free
under SOMEIP_STATIC_ALLOC. Update create_sd_transport() and the SdServer
construction path to use inline storage or a pool-backed transport factory
instead of shared_ptr allocation, while keeping the transport::UdpTransport and
UdpTransportConfig behavior the same.

183-202: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound insertions by actual static container capacity.

offer_service() only checks config_.max_services, and next_unicast_session_id() inserts into a 16-entry map with operator[]. If configuration or peer count exceeds the static container capacity, static builds can overflow/assert instead of failing predictably. Check max_size()/fullness before push_back and before adding a new peer key.

Also applies to: 795-803, 811-814

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_server.cpp` around lines 183 - 202, `offer_service()` and
`next_unicast_session_id()` currently rely only on config limits, but they can
still overfill their fixed-size containers in static builds. Add explicit
capacity/fullness checks against the actual container limits before
`offered_services_.push_back()` and before inserting a new peer key with
`operator[]`, and fail/return predictably when the container is full. Use the
existing `offer_service()` flow and `next_unicast_session_id()` peer-map
insertion path to keep the behavior bounded by static capacity rather than
configuration alone.
src/transport/tcp_transport.cpp (3)

89-92: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not report success for an empty serialized frame.

A SOME/IP message should serialize to at least the header. If static ByteBuffer allocation fails and returns empty, send_data returns SUCCESS without sending bytes.

Suggested fix
     // Serialize message
     const platform::ByteBuffer data = message.serialize();
+    if (data.empty()) {
+        return Result::BUFFER_OVERFLOW;
+    }

     // Send data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/tcp_transport.cpp` around lines 89 - 92, The TCP send path in
send_data() is treating an empty serialized frame as a successful send, which
can happen when message.serialize() returns an empty platform::ByteBuffer after
allocation failure. Update the send logic in tcp_transport.cpp to detect an
empty ByteBuffer before calling send_data(connection_.socket_fd, data) and
return an error instead of SUCCESS, so a SOME/IP frame must at least contain the
header.

560-565: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard against unsigned underflow in receive-buffer accounting.

When data.size() is already greater than config_.max_receive_buffer, config_.max_receive_buffer - data.size() wraps and allows another 4096-byte read.

Suggested fix
 Result TcpTransport::receive_data(someip_socket_t socket_fd, platform::ByteBuffer& data) {
     // Respect maximum receive buffer size from config
-    const size_t max_chunk_size = std::min(static_cast<size_t>(4096), config_.max_receive_buffer - data.size());
-    if (max_chunk_size == 0) {
+    if (data.size() >= config_.max_receive_buffer) {
         return Result::BUFFER_OVERFLOW;  // Already at buffer limit
     }
+    const size_t max_chunk_size =
+        std::min(static_cast<size_t>(4096), config_.max_receive_buffer - data.size());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/tcp_transport.cpp` around lines 560 - 565, The receive-buffer
limit check in TcpTransport::receive_data can underflow because
config_.max_receive_buffer is subtracted from data.size() before verifying the
current buffer is already over the limit. Update the max_chunk_size calculation
to first detect when data.size() is greater than or equal to
config_.max_receive_buffer and immediately return BUFFER_OVERFLOW, then compute
the remaining space using a safe difference only when capacity is available. Use
the receive_data method and the existing config_ / data.size() accounting to
keep the fix localized.

649-656: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not erase the TCP frame before extraction and allocation succeed.

message_data construction or allocate_message() can fail in static mode; the current code erases the frame first, losing a valid message on pool exhaustion.

Suggested fix
     // Extract message data
     const auto msg_end = buffer.begin() + static_cast<std::ptrdiff_t>(total_message_size);
     const platform::ByteBuffer message_data(buffer.begin(), msg_end);
-    buffer.erase(buffer.begin(), msg_end);
+    if (message_data.size() != total_message_size) {
+        return false;
+    }

     // Parse message
-    message = platform::allocate_message();
-    return message && message->deserialize(message_data);
+    auto parsed = platform::allocate_message();
+    if (!parsed) {
+        return false;
+    }
+    if (!parsed->deserialize(message_data)) {
+        buffer.erase(buffer.begin(), msg_end);
+        return false;
+    }
+    buffer.erase(buffer.begin(), msg_end);
+    message = std::move(parsed);
+    return true;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/tcp_transport.cpp` around lines 649 - 656, The TCP receive path
in tcp_transport.cpp erases the frame before message extraction/allocation
succeeds, so a valid frame can be lost if ByteBuffer construction or
platform::allocate_message() fails. Update the receive flow around the message
parsing block to first build message_data and allocate/deserialize the message,
and only call buffer.erase(...) after both operations succeed in the parsing
logic that uses total_message_size, message_data, and
platform::allocate_message().
src/rpc/rpc_client.cpp (1)

122-132: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Avoid std::make_shared in synchronous calls under static allocation.

call_method_sync heap-allocates both SyncState and RpcResponse; in a static build, every sync RPC can fail the malloc trap. Replace this with a bounded/intrusive wait state or a pending-call-owned response slot. This relies on the PR objective that static builds perform no heap allocation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_client.cpp` around lines 122 - 132, call_method_sync currently
heap-allocates SyncState and RpcResponse via std::make_shared, which violates
the static-build no-heap requirement. Update the sync path in rpc_client.cpp to
use a bounded allocation-free wait state or a pending-call-owned response slot
instead of shared_ptr/make_shared, and keep the async callback and waiting logic
in call_method_sync compatible with that storage.
src/sd/sd_client.cpp (1)

45-51: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Remove the heap-backed SD transport from the static path.

create_sd_transport still returns std::make_shared<transport::UdpTransport>, so SdClient construction can allocate even with SOMEIP_STATIC_ALLOC. Use in-place/static transport storage for the static backend. This relies on the PR objective that static builds perform no heap allocation.

Also applies to: 620-621

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_client.cpp` around lines 45 - 51, The static SD path still
heap-allocates because create_sd_transport returns
std::make_shared<transport::UdpTransport>, which breaks the no-heap goal for
SOMEIP_STATIC_ALLOC. Update the SD transport creation and the SdClient
construction path to use in-place/static transport storage instead of shared_ptr
allocation, keeping the transport owned by SdClient or a fixed storage wrapper.
Make sure the change is applied consistently in create_sd_transport and the
SdClient initialization code around the referenced call sites so the static
backend never allocates on the heap.
src/platform/static/memory.cpp (1)

68-87: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add in-use tracking before returning message slots.

release_message validates the pointer but not whether the slot is currently allocated, so a duplicate release can destroy the same Message twice and duplicate the slot in free_stack.

Suggested fix
 static uint16_t free_stack[SOMEIP_MESSAGE_POOL_SIZE];
 static uint16_t stack_top{0};
+static bool in_use[SOMEIP_MESSAGE_POOL_SIZE]{};

 void init_message_pool() {
     ScopedLock lk(pool_mutex);
     for (uint16_t i = 0; i < SOMEIP_MESSAGE_POOL_SIZE; ++i) {
         free_stack[i] = i;
+        in_use[i] = false;
     }
     stack_top = SOMEIP_MESSAGE_POOL_SIZE;
 }

 MessagePtr allocate_message() {
     ScopedLock lk(pool_mutex);
@@
     --stack_top;
     uint16_t idx = free_stack[stack_top];
+    in_use[idx] = true;

     auto* msg = new (&message_slab[idx][0]) Message();
     return MessagePtr(msg, true);
 }

 void release_message(Message* msg) {
@@
     size_t offset = static_cast<size_t>(raw - base);
     if (offset % sizeof(Message) != 0) { return; }
     auto idx = static_cast<uint16_t>(offset / sizeof(Message));

+    ScopedLock lk(pool_mutex);
+    if (!in_use[idx]) { return; }
+    in_use[idx] = false;
+
     msg->~Message();
-
-    ScopedLock lk(pool_mutex);
     if (stack_top < SOMEIP_MESSAGE_POOL_SIZE) {
         free_stack[stack_top] = idx;
         ++stack_top;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/static/memory.cpp` around lines 68 - 87, The release_message
path only checks that the pointer falls within message_slab and aligns to a
slot, but it does not verify the slot is currently allocated, so duplicate
releases can double-destroy the same Message and push the same index into
free_stack twice. Add an in-use/allocated tracking check inside release_message
before calling the Message destructor and before updating stack_top, using the
existing slot index derived from the Message* to reject or ignore already-freed
slots. Ensure the allocation bookkeeping is updated consistently wherever
Message slots are handed out and returned so the free_stack remains unique.
src/sd/sd_message.cpp (3)

86-88: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Use overflow-safe remaining-byte checks before indexing.

Several SD parse paths validate with offset + needed > size; on 32-bit targets or public offset-based calls this can wrap and allow out-of-bounds reads. Use offset > size || needed > size - offset consistently.

Proposed helper
 namespace someip::sd {
+
+namespace {
+bool has_remaining(size_t offset, size_t needed, size_t size) {
+    return offset <= size && needed <= size - offset;
+}
+}  // namespace
@@
-    if (offset + 16 > data.size()) {
+    if (!has_remaining(offset, 16, data.size())) {
         return false;
     }
@@
-    if (offset + 12 > data.size()) {
+    if (!has_remaining(offset, 12, data.size())) {
         return false;
     }
@@
-    if (offset + entries_length > data.size()) {
+    if (!has_remaining(offset, entries_length, data.size())) {
         return false;
     }

Also applies to: 133-134, 185-186, 220-222, 279-280, 375-376, 442-443, 551-552, 591-592, 630-633

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_message.cpp` around lines 86 - 88, The SD parsing guards in
SdEntry::deserialize and the other offset-based decode paths still use offset +
needed > data.size(), which can overflow before the bounds check. Update these
checks to use overflow-safe remaining-byte validation everywhere in the SD
message deserializers, following the pattern offset > data.size() || needed >
data.size() - offset, and apply the same fix consistently across the referenced
parse helpers in this file.

531-639: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear entries and options before deserializing.

deserialize() appends parsed entries/options into existing storage. Reusing an SdMessage for a second packet leaves stale entries/options from the previous packet mixed with the new one.

Proposed fix
 bool SdMessage::deserialize(const platform::ByteBuffer& data) {
     if (data.size() < 12) {
         return false;
     }
 
+    entries_.clear();
+    options_.clear();
+
     size_t offset = 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_message.cpp` around lines 531 - 639, SdMessage::deserialize
currently appends into entries_ and options_ without resetting them, so reusing
the same SdMessage instance can retain stale data from a previous packet. Clear
both containers at the start of SdMessage::deserialize before parsing flags_,
entries, and options, then proceed with the existing length checks and parsing
logic so each call produces only the current message contents.

237-263: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Validate option buffer size before fixed-offset writes.

SdOption::serialize() is pool-backed; if it returns fewer than the 4 base bytes, the derived serializers still write data[0]/data[1]. Return {} before indexing when the base buffer is short, and validate the final expected size before patching the length.

Proposed fix pattern
 platform::ByteBuffer IPv4EndpointOption::serialize() const {
     platform::ByteBuffer data = SdOption::serialize();
+    if (data.size() != 4) {
+        return {};
+    }
@@
     data.push_back(static_cast<uint8_t>(port_ & 0xFFU));
+    if (data.size() != 12) {
+        return {};
+    }
@@
 platform::ByteBuffer ConfigurationOption::serialize() const {
     platform::ByteBuffer data = SdOption::serialize();
+    if (data.size() != 4) {
+        return {};
+    }
@@
     data.insert(data.end(), str_begin, str_begin + config_string_.size());
+    if (data.size() != 4 + config_string_.size()) {
+        return {};
+    }

Also applies to: 335-359, 413-424

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_message.cpp` around lines 237 - 263, In
IPv4EndpointOption::serialize, the code assumes SdOption::serialize() always
returns at least the 4-byte option header, but it may be shorter because the
buffer is pool-backed; add an early guard to return an empty buffer before any
fixed-offset writes to data[0] and data[1] when the base buffer is too small.
Also verify the final serialized size matches the expected option layout before
patching the length field, and apply the same size checks in the related
serialize implementations referenced by the comment so all fixed-offset writes
are safe.
src/e2e/e2e_profiles/standard_profile.cpp (1)

70-95: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check CRC input size before calculating.

platform::ByteBuffer assembly can fail or short-write under static pool pressure; computing CRC over the resulting partial buffer silently weakens E2E validation. Verify crc_data.size() == 16 + payload.size() after the final insert and return Result::RESOURCE_EXHAUSTED on mismatch.

Proposed fix
          platform::ByteBuffer crc_data;
-         crc_data.reserve(16 + msg.get_payload().size());
+         const auto& payload = msg.get_payload();
+         const size_t expected_crc_size = 16 + payload.size();
+         crc_data.reserve(expected_crc_size);
@@
-         const auto& payload = msg.get_payload();
          crc_data.insert(crc_data.end(), payload.begin(), payload.end());
+         if (crc_data.size() != expected_crc_size) {
+             return Result::RESOURCE_EXHAUSTED;
+         }
@@
             platform::ByteBuffer crc_data;
-            crc_data.reserve(16 + msg.get_payload().size());
+            const auto& payload = msg.get_payload();
+            const size_t expected_crc_size = 16 + payload.size();
+            crc_data.reserve(expected_crc_size);
@@
-            // Include payload
-            const auto& payload = msg.get_payload();
             crc_data.insert(crc_data.end(), payload.begin(), payload.end());
+            if (crc_data.size() != expected_crc_size) {
+                return Result::RESOURCE_EXHAUSTED;
+            }

Also applies to: 169-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/e2e/e2e_profiles/standard_profile.cpp` around lines 70 - 95, The CRC
calculation path in the e2e profile buffer assembly can proceed even if
platform::ByteBuffer insertion was incomplete, which would validate a partial
message. In the code that builds crc_data before calling e2ecrc::calculate_crc,
verify that crc_data.size() matches the expected 16 + payload.size() after all
inserts; if it does not, return Result::RESOURCE_EXHAUSTED instead of
continuing. Apply the same guard in the corresponding CRC assembly logic used by
the standard_profile implementation.
src/tp/tp_reassembler.cpp (1)

157-174: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Unchecked insert() result risks returning/dereferencing an invalid buffer pointer.

reassembly_buffers_.insert(...) result's .second (success flag) is never checked before using result.first and returning &it->second. If the underlying container has fixed capacity (as platform::UnorderedMap does under the static-allocation backend), a full container makes insert() fail, and the returned iterator may be invalid/end(). The caller (process_segment) treats only nullptr as failure and otherwise immediately dereferences the returned pointer, so a full reassembly-buffer pool here can lead to an invalid-pointer dereference rather than a clean TpResult/false failure path.

🐛 Proposed fix
     if (it == reassembly_buffers_.end()) {
         if (segment.header.message_type == TpMessageType::FIRST_SEGMENT ||
             segment.header.message_type == TpMessageType::SINGLE_MESSAGE) {
 
             auto result = reassembly_buffers_.insert(
                 std::make_pair(segment.header.sequence_number,
                                TpReassemblyBuffer(segment.header.sequence_number, segment.header.message_length)));
+            if (!result.second) {
+                return nullptr;  // pool exhausted
+            }
             it = result.first;
         } else {
             return nullptr;
         }
     }
 
     return &it->second;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tp/tp_reassembler.cpp` around lines 157 - 174, The
find_or_create_buffer() path does not check whether reassembly_buffers_.insert()
actually succeeded before using result.first and returning &it->second. Update
TpReassembler::find_or_create_buffer to inspect the insert success flag and
return nullptr when the buffer pool is full or insertion fails, so
process_segment continues to follow its existing failure path instead of
dereferencing an invalid iterator/pointer.
src/rpc/rpc_server.cpp (2)

95-104: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing capacity check before inserting into fixed-size method_handlers_.

method_handlers_ is now a platform::UnorderedMap<MethodId, MethodHandler, 32> (fixed capacity), but register_method still blindly does method_handlers_[method_id] = std::move(handler); with no bound check. Once 32 unique methods are registered, ETL's fixed-capacity map either asserts (map_full) or exhibits undefined behavior if asserts/exceptions are disabled — a real regression versus the previous unbounded std::unordered_map. The PR already applies this exact defensive pattern elsewhere (e.g. tp_segmenter.cpp's segments.size() >= segments.max_size() checks); this call site is missing the analogous guard.

🛡️ Proposed fix
 bool register_method(MethodId method_id, MethodHandler handler) {
     platform::ScopedLock const lock(methods_mutex_);
 
     // Check if already registered
     const bool already_exists = method_handlers_.count(method_id) > 0;
-    if (!already_exists) {
+    if (!already_exists && method_handlers_.size() < method_handlers_.max_size()) {
         method_handlers_[method_id] = std::move(handler);
+        return true;
+    }
+    if (!already_exists) {
+        return false; // capacity exceeded
     }
-    return !already_exists;
+    return false;
 }

Also applies to: 227-229

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_server.cpp` around lines 95 - 104, `register_method` now writes
into the fixed-capacity `method_handlers_` map without checking space, so add a
guard before inserting in `register_method` and return failure when
`method_handlers_.size()` has reached `method_handlers_.max_size()`. Keep the
existing duplicate-method check, and make sure the new capacity check happens
before `method_handlers_[method_id] = std::move(handler)` so registration of the
33rd unique method is safely rejected instead of triggering ETL `map_full`
behavior. Also update the other `register_method` call site in the same class to
use the same bounded insert pattern.

49-56: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

RpcServer still heap-allocates in static builds src/rpc/rpc_server.cpp:49-56 uses std::make_shared, so SOMEIP_STATIC_ALLOC is not heap-free on the RPC server path. The static-alloc trap tests cover messages/buffers/containers, not RpcServer, so this gap still isn’t exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_server.cpp` around lines 49 - 56, The RpcServerImpl constructor
still uses std::make_shared for transport_, which keeps the static-build RPC
path heap-allocating; update the transport initialization in RpcServerImpl so it
uses the existing static/allocation-free transport approach instead of
shared_ptr-based construction. Locate the fix in RpcServerImpl and ensure
transport::UdpTransport is created and owned without heap allocation while
preserving the set_listener(this) behavior.
src/transport/udp_transport.cpp (1)

368-390: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Missing null check on platform::allocate_message() result before deserialize.

Line 370 correctly guards against receive-buffer pool exhaustion (buffer.data() == nullptr), but platform::allocate_message() at line 378 — which draws from the fixed-size static message pool per the PR's design — is used unconditionally at line 379 (message->deserialize(...)) with no null check. Under message-pool exhaustion (a realistic scenario during sustained high load, which is precisely the condition a no-heap static backend must survive gracefully), this is a null-pointer dereference/crash.

🛡️ Proposed fix
             MessagePtr const message = platform::allocate_message();
-            if (message->deserialize(buffer.data(), bytes_received)) {
+            if (message && message->deserialize(buffer.data(), bytes_received)) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transport/udp_transport.cpp` around lines 368 - 390,
UdpTransport::receive_loop currently assumes platform::allocate_message() always
succeeds, but it can return null when the fixed message pool is exhausted. Add a
null check immediately after allocating the message and before calling
deserialize(), and if allocation fails, skip processing the packet gracefully
(optionally continue the loop without queueing or notifying the listener). Keep
the fix localized to receive_loop and use the existing receive_queue_ /
listener_ flow only when a valid MessagePtr is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@include/core/session_manager.h`:
- Line 92: The current get_session() accessor exposes a raw pointer to an
element in sessions_ after the mutex is released, which can become dangling
during remove_session(), cleanup_expired_sessions(), or other mutations. Update
SessionManager to avoid returning an unlocked pointer by replacing get_session()
with a lock-scoped accessor/callback, a copied Session snapshot, or another
ownership-safe handle, and make sure any internal use of sessions_ follows the
same safe access pattern.

In `@include/platform/static/static_config.h`:
- Around line 105-107: The default for SOMEIP_PIMPL_EVENTPUB_SIZE in
static_config.h is far larger than the apparent EventPublisherImpl layout, so
either reduce the macro to a tighter size that matches the actual object
footprint or add a clear comment documenting why a 1 MiB inline buffer is
required. Update the definition near SOMEIP_PIMPL_EVENTPUB_SIZE and, if needed,
reference EventPublisherImpl to justify the chosen bound.

In `@include/rpc/rpc_server.h`:
- Around line 137-145: The static PIMPL storage in RpcServerImpl uses
alignas(alignof(std::max_align_t)) and only a size check exists, so add a
compile-time alignment assertion alongside the existing size static_assert in
rpc_server.cpp. Update the checks around RpcServerImpl and
SOMEIP_PIMPL_RPCSERVER_SIZE to verify that the buffer alignment is sufficient
for RpcServerImpl as well, so the placement-new path in RpcServer::impl()
remains safe if the implementation’s alignment requirements change.

In `@src/events/event_publisher.cpp`:
- Around line 451-458: The fixed-capacity publisher containers can still
overflow because registration/subscription paths use operator[] and push_back
without checking whether registered_events_, subscriptions_, or
last_publish_times_ are already full. Update the insertion logic in
EventPublisher to check capacity before adding entries, and reject new
registrations/subscriptions cleanly when the 32-entry limit is reached. Use the
existing EventPublisher methods that mutate registered_events_, subscriptions_,
and last_publish_times_ to centralize the guard and avoid relying on ETL
overflow behavior.
- Around line 52-54: The static publisher path in EventPublisherImpl still uses
std::make_shared for transport::UdpTransport, which can allocate on the heap
even in SOMEIP_STATIC_ALLOC mode. Replace this with a heap-free construction
path by using conditional inline storage or a pool-backed transport factory when
initializing transport_ in the EventPublisherImpl constructor, and make sure the
same fix is applied wherever this transport initialization is duplicated.

In `@src/events/event_subscriber.cpp`:
- Around line 71-73: The static subscriber path in EventSubscriberImpl still
allocates on the heap because the constructor uses
std::make_shared<transport::UdpTransport>. Replace that ownership in
EventSubscriberImpl with static storage or a pool-backed transport wrapper so
transport_ is initialized without heap use, and keep the fix consistent anywhere
the same construction pattern appears in the subscriber path.

In `@src/rpc/rpc_client.cpp`:
- Around line 54-55: The static allocation path in RpcClient still allocates by
creating transport_ with std::make_shared<transport::UdpTransport>, which can
hit the malloc trap during construction. Update RpcClient’s constructor and
transport handling to use in-place storage or a bounded PAL-owned transport
holder instead of a heap-backed shared_ptr, so the SOMEIP_STATIC_ALLOC path
performs no dynamic allocation. Also apply the same change to the other
constructor/site referenced by RpcClient’s transport initialization logic.
- Line 294: The pending call tracking in RpcClient::pending_calls_ can hit the
fixed ETL capacity limit in the static backend, so update the async call path to
guard capacity before using pending_calls_[handle] = ... and return 0 when the
map is full instead of allowing the insertion to fail. Make the check in the
RpcClient code that manages PendingCall entries so the 33rd concurrent async
call is handled gracefully before any insertion attempt.

In `@src/sd/sd_client.cpp`:
- Around line 623-638: The static backend’s service-tracking maps are capped
below the configured limit, so update the fixed capacities in SDClient to match
SdConfig::max_services instead of hardcoding 32. Adjust the declarations for
service_subscriptions_, cached_services_, and eventgroup_subscriptions_ so they
can hold the full configured number of services, and verify any related
insertion paths in SDClient still behave correctly without explicit overflow
handling.

In `@src/sd/sd_message.cpp`:
- Around line 458-463: `SdMessage::add_entry` and `SdMessage::add_option` append
directly to bounded storage without checking capacity, so oversized SD messages
can hit a vector assertion instead of failing cleanly. Add a
`full()`/`max_size()` guard before every append in the SD parsing paths that
populate `entries_` and `options_`, and make the helper methods return failure
or otherwise prevent insertion when storage is already at capacity. Keep the
callers in the SD message handling flow consistent with this check so
wire-controlled entries/options are rejected safely.

In `@src/serialization/serializer.cpp`:
- Around line 399-418: Reject oversized ETL strings in
Deserializer::deserialize_string by adding an explicit capacity/truncation check
before constructing platform::String<>. If the wire length exceeds the string’s
limit, return Result::MALFORMED_MESSAGE instead of allowing a truncated value to
be created. Keep the fix localized to deserialize_string and use the existing
length/position checks as the place to gate malformed payloads.

In `@tests/test_buffer_pool.cpp`:
- Around line 121-138: The tests in BufferPoolTest leave process-global pool
slots allocated if ASSERT_NE fails inside the acquire loops, because the
trailing release_buffer cleanup is skipped; update ExhaustTier0, ExhaustTier1,
CrossTierFallback, and PoolStateAfterFullCycle to use a local RAII cleanup
helper or equivalent scoped guard around acquired BufferSlot pointers so
release_buffer always runs even on fatal assertion failures. Keep the fix
localized to the test file and preserve the existing
acquire_buffer/release_buffer flow.
- Around line 232-263: The ConcurrentAcquireRelease test in BufferPoolTest uses
a too-weak final check; replace the loose success_count > 0 assertion with a
stronger expectation that all worker acquisitions/releases succeed. Update the
worker loop and final EXPECT in ConcurrentAcquireRelease, using the
acquire_buffer, release_buffer, and success_count symbols, so the test verifies
the expected 4 x 50 concurrent operations rather than only confirming partial
success.

In `@tests/test_pal_static_alloc_mock.cpp`:
- Around line 32-44: The test file is duplicating the global GoogleTest
environment setup already provided by StaticPoolEnvironment in
static_pool_init.h. Replace the local StaticAllocPalEnv and
AddGlobalTestEnvironment usage with the shared header by including
static_pool_init.h and reusing its environment initialization so the allocator
setup logic stays in one place.

In `@tests/test_platform_containers.cpp`:
- Around line 314-333: The CapacityExhaust test currently only verifies that
Vector and Queue reach full capacity, not that they behave correctly when
overfilled. Update ContainerTest::CapacityExhaust to exercise one additional
push_back/push after full for Vector<int, 4> and Queue<int, 2>, then assert the
expected overflow behavior from the container implementation (such as unchanged
size/full state, rejection, or other defined handling) using the same Vector and
Queue symbols. Keep the existing capacity checks, but add the exhaustion attempt
so REQ_PAL_CONTAINER_CAPACITY_EXHAUST actually covers overflow behavior.

In `@tests/test_static_alloc_integration.cpp`:
- Around line 60-81: The malloc trap setup in
MessageSerializeDeserializeRoundTrip, SerializerWithStaticBuffer, and
FullStackUnderTrap is manually paired, so any ASSERT_* early return can leave
the trap armed for later tests. Replace the direct
malloc_trap_arm()/malloc_trap_disarm() usage in these test bodies with an RAII
guard so cleanup happens automatically even when ASSERT_FALSE or ASSERT_TRUE
fails. Use the existing test names and the trap helper calls as the places to
apply the guard pattern consistently.

In `@tests/test_static_message_pool.cpp`:
- Around line 28-33: The test setup duplicates the global environment pattern by
defining a local StaticAllocEnv and calling init_static_allocator() again
instead of reusing the shared StaticPoolEnvironment. Update the test to use
tests/static_pool_init.h::StaticPoolEnvironment (as in
test_static_alloc_integration.cpp) and remove the local environment class and
AddGlobalTestEnvironment registration so allocator initialization stays
centralized and not duplicated.

---

Outside diff comments:
In `@src/core/session_manager.cpp`:
- Around line 38-45: SessionManager::create_session currently inserts into the
fixed-capacity sessions_ map unconditionally, which can overflow once the
256-entry limit is reached. Update create_session (and any callers relying on
its return value) to check for capacity exhaustion before calling
sessions_[session_id] = Session(...), and return the reserved session_id 0 as a
failure sentinel or otherwise surface a controlled failure instead of trapping.
Use the SessionManager::create_session, get_next_session_id, and sessions_
symbols to locate the fix.

In `@src/e2e/e2e_profiles/standard_profile.cpp`:
- Around line 70-95: The CRC calculation path in the e2e profile buffer assembly
can proceed even if platform::ByteBuffer insertion was incomplete, which would
validate a partial message. In the code that builds crc_data before calling
e2ecrc::calculate_crc, verify that crc_data.size() matches the expected 16 +
payload.size() after all inserts; if it does not, return
Result::RESOURCE_EXHAUSTED instead of continuing. Apply the same guard in the
corresponding CRC assembly logic used by the standard_profile implementation.

In `@src/platform/static/malloc_trap.cpp`:
- Around line 43-47: Make the trap arm state thread-safe by changing
g_trap_armed in malloc_trap.cpp from a plain bool to std::atomic_bool, since
malloc hooks can call malloc_trap_is_armed() concurrently with malloc_trap_arm()
and malloc_trap_disarm(). Update the three helpers to use relaxed store/load on
g_trap_armed, and keep the existing behavior and naming in malloc_trap_arm,
malloc_trap_disarm, and malloc_trap_is_armed unchanged.

In `@src/platform/static/memory.cpp`:
- Around line 68-87: The release_message path only checks that the pointer falls
within message_slab and aligns to a slot, but it does not verify the slot is
currently allocated, so duplicate releases can double-destroy the same Message
and push the same index into free_stack twice. Add an in-use/allocated tracking
check inside release_message before calling the Message destructor and before
updating stack_top, using the existing slot index derived from the Message* to
reject or ignore already-freed slots. Ensure the allocation bookkeeping is
updated consistently wherever Message slots are handed out and returned so the
free_stack remains unique.

In `@src/rpc/rpc_client.cpp`:
- Around line 122-132: call_method_sync currently heap-allocates SyncState and
RpcResponse via std::make_shared, which violates the static-build no-heap
requirement. Update the sync path in rpc_client.cpp to use a bounded
allocation-free wait state or a pending-call-owned response slot instead of
shared_ptr/make_shared, and keep the async callback and waiting logic in
call_method_sync compatible with that storage.

In `@src/rpc/rpc_server.cpp`:
- Around line 95-104: `register_method` now writes into the fixed-capacity
`method_handlers_` map without checking space, so add a guard before inserting
in `register_method` and return failure when `method_handlers_.size()` has
reached `method_handlers_.max_size()`. Keep the existing duplicate-method check,
and make sure the new capacity check happens before `method_handlers_[method_id]
= std::move(handler)` so registration of the 33rd unique method is safely
rejected instead of triggering ETL `map_full` behavior. Also update the other
`register_method` call site in the same class to use the same bounded insert
pattern.
- Around line 49-56: The RpcServerImpl constructor still uses std::make_shared
for transport_, which keeps the static-build RPC path heap-allocating; update
the transport initialization in RpcServerImpl so it uses the existing
static/allocation-free transport approach instead of shared_ptr-based
construction. Locate the fix in RpcServerImpl and ensure transport::UdpTransport
is created and owned without heap allocation while preserving the
set_listener(this) behavior.

In `@src/sd/sd_client.cpp`:
- Around line 45-51: The static SD path still heap-allocates because
create_sd_transport returns std::make_shared<transport::UdpTransport>, which
breaks the no-heap goal for SOMEIP_STATIC_ALLOC. Update the SD transport
creation and the SdClient construction path to use in-place/static transport
storage instead of shared_ptr allocation, keeping the transport owned by
SdClient or a fixed storage wrapper. Make sure the change is applied
consistently in create_sd_transport and the SdClient initialization code around
the referenced call sites so the static backend never allocates on the heap.

In `@src/sd/sd_message.cpp`:
- Around line 86-88: The SD parsing guards in SdEntry::deserialize and the other
offset-based decode paths still use offset + needed > data.size(), which can
overflow before the bounds check. Update these checks to use overflow-safe
remaining-byte validation everywhere in the SD message deserializers, following
the pattern offset > data.size() || needed > data.size() - offset, and apply the
same fix consistently across the referenced parse helpers in this file.
- Around line 531-639: SdMessage::deserialize currently appends into entries_
and options_ without resetting them, so reusing the same SdMessage instance can
retain stale data from a previous packet. Clear both containers at the start of
SdMessage::deserialize before parsing flags_, entries, and options, then proceed
with the existing length checks and parsing logic so each call produces only the
current message contents.
- Around line 237-263: In IPv4EndpointOption::serialize, the code assumes
SdOption::serialize() always returns at least the 4-byte option header, but it
may be shorter because the buffer is pool-backed; add an early guard to return
an empty buffer before any fixed-offset writes to data[0] and data[1] when the
base buffer is too small. Also verify the final serialized size matches the
expected option layout before patching the length field, and apply the same size
checks in the related serialize implementations referenced by the comment so all
fixed-offset writes are safe.

In `@src/sd/sd_server.cpp`:
- Around line 159-168: offer_service() currently accepts an empty
unicast_endpoint and still stores the service, which makes the call succeed even
though send_service_offer() cannot actually send anything. Update
offer_service() to reject empty unicast endpoints up front, using the existing
validation flow around parse_endpoint_string(), and return false before storing
the service or proceeding with the offer. Make the same check in the
corresponding service-offer path referenced by the later block so both entry
points consistently fail fast on empty unicast_endpoint values.
- Around line 71-77: Remove the heap allocation from the static SD server path:
create_sd_transport() currently uses std::make_shared to build
transport::UdpTransport, so SdServer initialization is not heap-free under
SOMEIP_STATIC_ALLOC. Update create_sd_transport() and the SdServer construction
path to use inline storage or a pool-backed transport factory instead of
shared_ptr allocation, while keeping the transport::UdpTransport and
UdpTransportConfig behavior the same.
- Around line 183-202: `offer_service()` and `next_unicast_session_id()`
currently rely only on config limits, but they can still overfill their
fixed-size containers in static builds. Add explicit capacity/fullness checks
against the actual container limits before `offered_services_.push_back()` and
before inserting a new peer key with `operator[]`, and fail/return predictably
when the container is full. Use the existing `offer_service()` flow and
`next_unicast_session_id()` peer-map insertion path to keep the behavior bounded
by static capacity rather than configuration alone.

In `@src/tp/tp_reassembler.cpp`:
- Around line 157-174: The find_or_create_buffer() path does not check whether
reassembly_buffers_.insert() actually succeeded before using result.first and
returning &it->second. Update TpReassembler::find_or_create_buffer to inspect
the insert success flag and return nullptr when the buffer pool is full or
insertion fails, so process_segment continues to follow its existing failure
path instead of dereferencing an invalid iterator/pointer.

In `@src/transport/tcp_transport.cpp`:
- Around line 89-92: The TCP send path in send_data() is treating an empty
serialized frame as a successful send, which can happen when message.serialize()
returns an empty platform::ByteBuffer after allocation failure. Update the send
logic in tcp_transport.cpp to detect an empty ByteBuffer before calling
send_data(connection_.socket_fd, data) and return an error instead of SUCCESS,
so a SOME/IP frame must at least contain the header.
- Around line 560-565: The receive-buffer limit check in
TcpTransport::receive_data can underflow because config_.max_receive_buffer is
subtracted from data.size() before verifying the current buffer is already over
the limit. Update the max_chunk_size calculation to first detect when
data.size() is greater than or equal to config_.max_receive_buffer and
immediately return BUFFER_OVERFLOW, then compute the remaining space using a
safe difference only when capacity is available. Use the receive_data method and
the existing config_ / data.size() accounting to keep the fix localized.
- Around line 649-656: The TCP receive path in tcp_transport.cpp erases the
frame before message extraction/allocation succeeds, so a valid frame can be
lost if ByteBuffer construction or platform::allocate_message() fails. Update
the receive flow around the message parsing block to first build message_data
and allocate/deserialize the message, and only call buffer.erase(...) after both
operations succeed in the parsing logic that uses total_message_size,
message_data, and platform::allocate_message().

In `@src/transport/udp_transport.cpp`:
- Around line 368-390: UdpTransport::receive_loop currently assumes
platform::allocate_message() always succeeds, but it can return null when the
fixed message pool is exhausted. Add a null check immediately after allocating
the message and before calling deserialize(), and if allocation fails, skip
processing the packet gracefully (optionally continue the loop without queueing
or notifying the listener). Keep the fix localized to receive_loop and use the
existing receive_queue_ / listener_ flow only when a valid MessagePtr is
available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7745b22-af62-4a85-bd19-9ed6601672c6

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6a67e and 5ddced4.

📒 Files selected for processing (66)
  • .github/workflows/freertos.yml
  • .github/workflows/host.yml
  • docs/requirements/implementation/platform.rst
  • include/core/session_manager.h
  • include/events/event_publisher.h
  • include/events/event_subscriber.h
  • include/events/event_types.h
  • include/platform/freertos/thread_impl.h
  • include/platform/static/containers_impl.h
  • include/platform/static/memory_impl.h
  • include/platform/static/message_ptr_impl.h
  • include/platform/static/static_config.h
  • include/platform/threadx/thread_impl.h
  • include/platform/zephyr/thread_impl.h
  • include/rpc/rpc_client.h
  • include/rpc/rpc_server.h
  • include/sd/sd_client.h
  • include/sd/sd_message.h
  • include/sd/sd_server.h
  • include/serialization/serializer.h
  • include/tp/tp_manager.h
  • include/tp/tp_reassembler.h
  • include/transport/tcp_transport.h
  • include/transport/udp_transport.h
  • src/CMakeLists.txt
  • src/core/session_manager.cpp
  • src/e2e/e2e_crc.cpp
  • src/e2e/e2e_header.cpp
  • src/e2e/e2e_profiles/standard_profile.cpp
  • src/events/event_publisher.cpp
  • src/events/event_subscriber.cpp
  • src/platform/static/buffer_pool.cpp
  • src/platform/static/malloc_trap.cpp
  • src/platform/static/memory.cpp
  • src/rpc/rpc_client.cpp
  • src/rpc/rpc_server.cpp
  • src/sd/sd_client.cpp
  • src/sd/sd_message.cpp
  • src/sd/sd_server.cpp
  • src/serialization/serializer.cpp
  • src/tp/tp_manager.cpp
  • src/tp/tp_reassembler.cpp
  • src/tp/tp_segmenter.cpp
  • src/transport/tcp_transport.cpp
  • src/transport/udp_transport.cpp
  • tests/CMakeLists.txt
  • tests/static_pool_init.h
  • tests/test_buffer_pool.cpp
  • tests/test_e2e.cpp
  • tests/test_endpoint.cpp
  • tests/test_events.cpp
  • tests/test_message.cpp
  • tests/test_pal_static_alloc_mock.cpp
  • tests/test_platform_containers.cpp
  • tests/test_platform_threading.cpp
  • tests/test_rpc.cpp
  • tests/test_sd.cpp
  • tests/test_serialization.cpp
  • tests/test_session_manager.cpp
  • tests/test_static_alloc_integration.cpp
  • tests/test_static_message_pool.cpp
  • tests/test_tcp_transport.cpp
  • tests/test_tp.cpp
  • tests/test_udp_transport.cpp
  • zephyr/CMakeLists.txt
  • zephyr/Kconfig

Comment thread include/core/session_manager.h Outdated
Comment thread include/platform/static/static_config.h
Comment thread include/rpc/rpc_server.h
Comment on lines +137 to +145
#ifdef SOMEIP_STATIC_ALLOC
alignas(alignof(std::max_align_t)) char impl_storage_[SOMEIP_PIMPL_RPCSERVER_SIZE];
RpcServerImpl* impl() noexcept { return reinterpret_cast<RpcServerImpl*>(impl_storage_); }
const RpcServerImpl* impl() const noexcept { return reinterpret_cast<const RpcServerImpl*>(impl_storage_); }
#else
std::unique_ptr<RpcServerImpl> impl_;
RpcServerImpl* impl() noexcept { return impl_.get(); }
const RpcServerImpl* impl() const noexcept { return impl_.get(); }
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting alignment, not just size, for the static PIMPL storage.

impl_storage_ is aligned to alignof(std::max_align_t), but only sizeof(RpcServerImpl) <= SOMEIP_PIMPL_RPCSERVER_SIZE is statically asserted (in rpc_server.cpp). If RpcServerImpl ever gains a member requiring stricter alignment than max_align_t, placement-new into this buffer becomes UB with no compile-time signal.

🛡️ Suggested addition (in rpc_server.cpp alongside the existing static_assert)
 `#ifdef` SOMEIP_STATIC_ALLOC
 static_assert(sizeof(RpcServerImpl) <= SOMEIP_PIMPL_RPCSERVER_SIZE,
               "RpcServerImpl exceeds pimpl storage size; increase SOMEIP_PIMPL_RPCSERVER_SIZE");
+static_assert(alignof(RpcServerImpl) <= alignof(std::max_align_t),
+              "RpcServerImpl alignment exceeds impl_storage_ alignment");
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/rpc/rpc_server.h` around lines 137 - 145, The static PIMPL storage in
RpcServerImpl uses alignas(alignof(std::max_align_t)) and only a size check
exists, so add a compile-time alignment assertion alongside the existing size
static_assert in rpc_server.cpp. Update the checks around RpcServerImpl and
SOMEIP_PIMPL_RPCSERVER_SIZE to verify that the buffer alignment is sufficient
for RpcServerImpl as well, so the placement-new path in RpcServer::impl()
remains safe if the implementation’s alignment requirements change.

Comment thread src/events/event_publisher.cpp Outdated
Comment thread src/events/event_publisher.cpp Outdated
Comment on lines +232 to +263
/**
* @test_case TC_BUFPOOL_CONCURRENT
* @tests REQ_PAL_BUFPOOL_ACQUIRE
* @tests REQ_PAL_BUFPOOL_RELEASE
*/
TEST_F(BufferPoolTest, ConcurrentAcquireRelease) {
constexpr int kThreads = 4;
constexpr int kOpsPerThread = 50;
std::atomic<int> success_count{0};

auto worker = [&]() {
for (int i = 0; i < kOpsPerThread; ++i) {
BufferSlot* s = acquire_buffer(64);
if (s) {
s->data[0] = 0x42;
release_buffer(s);
success_count.fetch_add(1, std::memory_order_relaxed);
}
}
};

std::vector<std::thread> threads;
threads.reserve(kThreads);
for (int i = 0; i < kThreads; ++i) {
threads.emplace_back(worker);
}
for (auto& t : threads) {
t.join();
}

EXPECT_GT(success_count.load(), 0);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Weak assertion undermines the concurrency test.

EXPECT_GT(success_count.load(), 0) passes even if the vast majority of the 200 concurrent acquire/release operations fail — it only proves the pool isn't completely broken, not that concurrent access is correct. Since each worker acquires and immediately releases (never holding more than one buffer at a time across 4 threads), all 200 operations should reasonably succeed if locking is correct.

💚 Stronger assertion
-    EXPECT_GT(success_count.load(), 0);
+    EXPECT_EQ(success_count.load(), kThreads * kOpsPerThread);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @test_case TC_BUFPOOL_CONCURRENT
* @tests REQ_PAL_BUFPOOL_ACQUIRE
* @tests REQ_PAL_BUFPOOL_RELEASE
*/
TEST_F(BufferPoolTest, ConcurrentAcquireRelease) {
constexpr int kThreads = 4;
constexpr int kOpsPerThread = 50;
std::atomic<int> success_count{0};
auto worker = [&]() {
for (int i = 0; i < kOpsPerThread; ++i) {
BufferSlot* s = acquire_buffer(64);
if (s) {
s->data[0] = 0x42;
release_buffer(s);
success_count.fetch_add(1, std::memory_order_relaxed);
}
}
};
std::vector<std::thread> threads;
threads.reserve(kThreads);
for (int i = 0; i < kThreads; ++i) {
threads.emplace_back(worker);
}
for (auto& t : threads) {
t.join();
}
EXPECT_GT(success_count.load(), 0);
}
/**
* `@test_case` TC_BUFPOOL_CONCURRENT
* `@tests` REQ_PAL_BUFPOOL_ACQUIRE
* `@tests` REQ_PAL_BUFPOOL_RELEASE
*/
TEST_F(BufferPoolTest, ConcurrentAcquireRelease) {
constexpr int kThreads = 4;
constexpr int kOpsPerThread = 50;
std::atomic<int> success_count{0};
auto worker = [&]() {
for (int i = 0; i < kOpsPerThread; ++i) {
BufferSlot* s = acquire_buffer(64);
if (s) {
s->data[0] = 0x42;
release_buffer(s);
success_count.fetch_add(1, std::memory_order_relaxed);
}
}
};
std::vector<std::thread> threads;
threads.reserve(kThreads);
for (int i = 0; i < kThreads; ++i) {
threads.emplace_back(worker);
}
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(success_count.load(), kThreads * kOpsPerThread);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_buffer_pool.cpp` around lines 232 - 263, The
ConcurrentAcquireRelease test in BufferPoolTest uses a too-weak final check;
replace the loose success_count > 0 assertion with a stronger expectation that
all worker acquisitions/releases succeed. Update the worker loop and final
EXPECT in ConcurrentAcquireRelease, using the acquire_buffer, release_buffer,
and success_count symbols, so the test verifies the expected 4 x 50 concurrent
operations rather than only confirming partial success.

Comment on lines +32 to +44
#include <gtest/gtest.h>
#include "platform/memory.h"

namespace {
class StaticAllocPalEnv : public ::testing::Environment {
public:
void SetUp() override {
someip::platform::init_static_allocator();
}
};
[[maybe_unused]] auto* const g_env =
::testing::AddGlobalTestEnvironment(new StaticAllocPalEnv());
} // namespace

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate environment registration — reuse static_pool_init.h.

This reimplements the same Environment/SetUp pattern already defined in tests/static_pool_init.h (StaticPoolEnvironment). Prefer including the shared header instead of duplicating the logic.

♻️ Proposed refactor to reuse the shared environment
-#include <gtest/gtest.h>
-#include "platform/memory.h"
-
-namespace {
-class StaticAllocPalEnv : public ::testing::Environment {
-public:
-    void SetUp() override {
-        someip::platform::init_static_allocator();
-    }
-};
-[[maybe_unused]] auto* const g_env =
-    ::testing::AddGlobalTestEnvironment(new StaticAllocPalEnv());
-}  // namespace
+#include <gtest/gtest.h>
+#include "static_pool_init.h"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <gtest/gtest.h>
#include "platform/memory.h"
namespace {
class StaticAllocPalEnv : public ::testing::Environment {
public:
void SetUp() override {
someip::platform::init_static_allocator();
}
};
[[maybe_unused]] auto* const g_env =
::testing::AddGlobalTestEnvironment(new StaticAllocPalEnv());
} // namespace
`#include` <gtest/gtest.h>
`#include` "static_pool_init.h"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_pal_static_alloc_mock.cpp` around lines 32 - 44, The test file is
duplicating the global GoogleTest environment setup already provided by
StaticPoolEnvironment in static_pool_init.h. Replace the local StaticAllocPalEnv
and AddGlobalTestEnvironment usage with the shared header by including
static_pool_init.h and reusing its environment initialization so the allocator
setup logic stays in one place.

Comment on lines +314 to +333
/**
* @test_case TC_CONTAINER_CAPACITY_EXHAUST
* @tests REQ_PAL_CONTAINER_CAPACITY_EXHAUST
*/
TEST(ContainerTest, CapacityExhaust) {
Vector<int, 4> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
EXPECT_EQ(v.size(), 4U);
EXPECT_TRUE(v.full());

Queue<int, 2> q;
q.push(1);
q.push(2);
EXPECT_EQ(q.size(), 2U);
EXPECT_TRUE(q.full());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

CapacityExhaust doesn't test exhaustion, only reaching full capacity.

The test (tagged REQ_PAL_CONTAINER_CAPACITY_EXHAUST) fills Vector/Queue to exactly their capacity and checks full(), but never attempts to push one more element to verify the container's actual overflow behavior (safe no-op, assertion, truncation, etc.). For a safety-critical no-heap backend, verifying that an over-capacity push_back/push doesn't corrupt memory or crash uncontrolled seems like the point of this requirement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_platform_containers.cpp` around lines 314 - 333, The
CapacityExhaust test currently only verifies that Vector and Queue reach full
capacity, not that they behave correctly when overfilled. Update
ContainerTest::CapacityExhaust to exercise one additional push_back/push after
full for Vector<int, 4> and Queue<int, 2>, then assert the expected overflow
behavior from the container implementation (such as unchanged size/full state,
rejection, or other defined handling) using the same Vector and Queue symbols.
Keep the existing capacity checks, but add the exhaustion attempt so
REQ_PAL_CONTAINER_CAPACITY_EXHAUST actually covers overflow behavior.

Comment thread tests/test_static_alloc_integration.cpp
Comment on lines +28 to +33
class StaticAllocEnv : public ::testing::Environment {
public:
void SetUp() override { init_static_allocator(); }
};
[[maybe_unused]] auto* const g_env =
::testing::AddGlobalTestEnvironment(new StaticAllocEnv());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Same duplicate-environment pattern as test_static_alloc_integration.cpp.

This file also redefines a local StaticAllocEnv instead of including the shared tests/static_pool_init.h::StaticPoolEnvironment. See the corresponding comment in test_static_alloc_integration.cpp — consolidating avoids drift and potential double-invocation of init_static_allocator() if both translation units are ever linked into the same binary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_static_message_pool.cpp` around lines 28 - 33, The test setup
duplicates the global environment pattern by defining a local StaticAllocEnv and
calling init_static_allocator() again instead of reusing the shared
StaticPoolEnvironment. Update the test to use
tests/static_pool_init.h::StaticPoolEnvironment (as in
test_static_alloc_integration.cpp) and remove the local environment class and
AddGlobalTestEnvironment registration so allocator initialization stays
centralized and not duplicated.

vtz and others added 4 commits July 2, 2026 11:10
Add explicit move constructors to ServiceEntry, EventGroupEntry,
IPv4EndpointOption, and IPv4MulticastOption that copy-construct the
base class (all trivial members, identical codegen) instead of
moving it — avoids both GCC -Wmaybe-uninitialized false positives
on std::variant internals and clang-tidy bugprone-use-after-move.

Add defaulted destructors to satisfy cppcoreguidelines Rule of Five.

Remove #pragma GCC diagnostic suppression from sd_message.cpp since
the root cause is now properly fixed in the header.

Revert #ifdef around #include <new> in pimpl sources — use NOLINTNEXTLINE
for misc-include-cleaner instead, keeping includes unconditional.

Co-authored-by: Cursor <cursoragent@cursor.com>
SD variant types:
- Use std::move(o) for base init with NOLINTNEXTLINE for the safe
  trivial-member access (bugprone-use-after-move false positive)
- Add defaulted destructors for Rule of Five compliance
- Switch push_back to emplace_back (modernize-use-emplace)

GCC -Wmaybe-uninitialized:
- Add per-file -Wno-maybe-uninitialized for sd_message.cpp via CMake
  when compiling with GCC under SOMEIP_USE_STATIC_ALLOC

Stale includes:
- Remove unused <string>, <unordered_map> from sd_server.cpp
- Remove unused <memory> from tcp_transport.cpp, udp_transport.cpp
- Add NOLINTNEXTLINE for <new> in pimpl sources (used under static alloc)

Optional access:
- Add has_value() guards for segmenter_/reassembler_ in tp_manager.cpp
  to satisfy bugprone-unchecked-optional-access

Co-authored-by: Cursor <cursoragent@cursor.com>
NOLINTNEXTLINE only suppresses the immediately following line, not the
member initializer lines that follow. Switch to NOLINTBEGIN/NOLINTEND
blocks to cover the entire move constructor body.

Co-authored-by: Cursor <cursoragent@cursor.com>
The hand-written move constructors were added to work around a GCC 13
false-positive (-Wmaybe-uninitialized through std::variant inlining,
GCC bug 109561) but never actually fixed it — the CMake per-file
flag was doing the real work. Meanwhile, the hand-written move ctors
created clang-tidy bugprone-use-after-move false positives that
required NOLINT blocks.

Fix: declare all five special member functions as = default (Rule of
Five) for ServiceEntry, EventGroupEntry, IPv4EndpointOption, and
IPv4MulticastOption. Extend the CMake GCC suppression to cover
sd_client.cpp and sd_server.cpp as well.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vtz
vtz force-pushed the feature/no-heap-static-alloc branch from 53c9d64 to 85b7f6e Compare July 24, 2026 00:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/events/event_subscriber.cpp (1)

220-234: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove field callbacks when sending the request fails.

A callback is stored before send_message; on failure, the entry remains and may be invoked by a later unrelated field response. Erase key before returning false.

Proposed fix
-        return transport_->send_message(field_msg, service_endpoint) == Result::SUCCESS;
+        const bool success =
+            transport_->send_message(field_msg, service_endpoint) == Result::SUCCESS;
+        if (!success) {
+            field_requests_.erase(key);
+        }
+        return success;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/events/event_subscriber.cpp` around lines 220 - 234, Update the request
flow around field_requests_ and transport_->send_message so a failed send
removes the callback stored under key before returning false. Preserve the
existing success behavior and return true only when send_message returns
Result::SUCCESS.
src/rpc/rpc_server.cpp (2)

50-53: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Remove heap allocation from the static server path.

std::make_shared<transport::UdpTransport> executes even when SOMEIP_STATIC_ALLOC is enabled, so placement-new of RpcServerImpl does not make construction heap-free and can trip the malloc trap. Use in-place storage or a PAL-owned bounded transport holder for the static backend.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_server.cpp` around lines 50 - 53, Update RpcServerImpl
construction so the SOMEIP_STATIC_ALLOC path does not call std::make_shared or
otherwise allocate from the heap. Replace transport_ initialization with
in-place storage or the existing PAL-owned bounded transport holder, while
preserving the current UdpTransport endpoint configuration and dynamic-path
behavior.

96-103: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle full static UnorderedMap insertions before using operator[].

The static backend uses etl::unordered_map, whose operator[] can throw on a full fixed-size map, so these inserts should be protected by a backend-compatible full-capacity/fallback path. This applies to:

  • src/rpc/rpc_server.cpp#L96-L103: method_handlers_[method_id]
  • src/events/event_subscriber.cpp#L137-L140: subscriptions_[key]
  • src/events/event_subscriber.cpp#L220-L222: field_requests_[key]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_server.cpp` around lines 96 - 103, Guard insertions into the
fixed-capacity maps before using operator[]: update register_method’s
method_handlers_ insertion, event subscriber subscription insertion at
src/events/event_subscriber.cpp lines 137-140, and field_requests_ insertion at
lines 220-222 to use the backend-compatible capacity check or fallback behavior
when the static map is full.
src/sd/sd_client.cpp (1)

164-168: 🩺 Stability & Availability | 🟠 Major

Handle bounded SD client-container exhaustion.

The static migration changes these members to bounded platform containers, but insertion still uses operator[] and push_back without checking capacity. A workload can exceed the fixed static limits before max_services or other runtime limits are reached, causing an assertion/abort. Align capacities with configured limits and propagate insertion failure for pending finds, subscriptions, available services, and cached services.

Also applies to: 246-246, 540-547, 624-639

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_client.cpp` around lines 164 - 168, Update the SD client insertion
paths for pending finds, subscriptions, available services, and cached services
to use capacity-aware insertion rather than unchecked operator[] or push_back.
Align each bounded platform container’s capacity with its configured limit,
detect insertion failure, and propagate an appropriate failure through the
existing request or update flow instead of allowing an assertion/abort; preserve
normal behavior when capacity is available.
src/sd/sd_message.cpp (1)

43-45: 🩺 Stability & Availability | 🟠 Major

Validate pooled-buffer size before fixed-offset writes.

Derived serializers write data[4]/data[15] or data[0]/data[1] without verifying that the base serialization succeeded. In static mode, an allocation failure can produce a short buffer and trigger an assertion or out-of-bounds access before SdMessage::serialize() sees the empty result. Check for exactly 16 bytes for entries and 4 bytes for options immediately after each base call.

Also applies to: 104-108, 157-161, 204-205, 237-263, 335-359, 413-424

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_message.cpp` around lines 43 - 45, Add an immediate size check
after every base serialization call in the affected SdEntry-derived serializers
and option serializers. Require exactly 16 bytes for entries and 4 bytes for
options before any fixed-offset writes, returning the existing failure
representation when the size is incorrect. Apply this consistently across
SdEntry::serialize and the additional serializers identified by the review.
src/sd/sd_server.cpp (1)

182-201: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound SD inserts by static container capacity.

config_.max_services can be 100 while offered_services_ is an etl::vector<…, 32> in static mode, so offered_services_.erase(begin()) followed by push_back(std::move(offered)) can still overflow when more than 32 distinct services are offered. Align the service/event-group/session buckets with configuration limits, or reject/evict explicitly based on actual static capacity instead of config_.max_services.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_server.cpp` around lines 182 - 201, Update the service insertion
logic around offered_services_ and its capacity configuration so static ETL
storage cannot exceed its actual compile-time capacity. Enforce the effective
limit as the lower of config_.max_services and the container’s capacity, and
evict or reject before push_back accordingly; apply the same capacity-aware
handling to related event-group and session buckets if they use fixed-size
containers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/CMakeLists.txt`:
- Around line 113-122: Restrict the warning suppression in the GNU compiler
condition around the affected SD source files to the GCC versions impacted by
the documented false positive, using CMAKE_CXX_COMPILER_VERSION. Preserve the
existing source-file list and compile option, and avoid applying
-Wno-maybe-uninitialized to newer GCC releases unless an explicit justification
is added.

In `@src/sd/sd_message.cpp`:
- Around line 458-463: The bounded SD storage insertion path must report
capacity failures end to end. Update src/sd/sd_message.cpp:458-463 in
SdMessage::add_entry and SdMessage::add_option to check capacity before
insertion and return failure; update include/sd/sd_message.h:244-248 to expose
the insertion status; and update src/sd/sd_message.cpp:569-575 and :612-624 in
deserialize() to reject and propagate entry and option overflow respectively.

In `@src/tp/tp_manager.cpp`:
- Around line 79-84: Update the segment storage in the transfer flow around
segmenter_->segment_message so platform::Vector<TpSegment> can hold the maximum
segment count for a configured max_message_size, accounting for max_segment_size
and protocol overhead. Ensure valid worst-case messages cannot overflow the
bounded vector, either by sizing it for that limit or by making segmentation
fail before inserting beyond capacity.

---

Outside diff comments:
In `@src/events/event_subscriber.cpp`:
- Around line 220-234: Update the request flow around field_requests_ and
transport_->send_message so a failed send removes the callback stored under key
before returning false. Preserve the existing success behavior and return true
only when send_message returns Result::SUCCESS.

In `@src/rpc/rpc_server.cpp`:
- Around line 50-53: Update RpcServerImpl construction so the
SOMEIP_STATIC_ALLOC path does not call std::make_shared or otherwise allocate
from the heap. Replace transport_ initialization with in-place storage or the
existing PAL-owned bounded transport holder, while preserving the current
UdpTransport endpoint configuration and dynamic-path behavior.
- Around line 96-103: Guard insertions into the fixed-capacity maps before using
operator[]: update register_method’s method_handlers_ insertion, event
subscriber subscription insertion at src/events/event_subscriber.cpp lines
137-140, and field_requests_ insertion at lines 220-222 to use the
backend-compatible capacity check or fallback behavior when the static map is
full.

In `@src/sd/sd_client.cpp`:
- Around line 164-168: Update the SD client insertion paths for pending finds,
subscriptions, available services, and cached services to use capacity-aware
insertion rather than unchecked operator[] or push_back. Align each bounded
platform container’s capacity with its configured limit, detect insertion
failure, and propagate an appropriate failure through the existing request or
update flow instead of allowing an assertion/abort; preserve normal behavior
when capacity is available.

In `@src/sd/sd_message.cpp`:
- Around line 43-45: Add an immediate size check after every base serialization
call in the affected SdEntry-derived serializers and option serializers. Require
exactly 16 bytes for entries and 4 bytes for options before any fixed-offset
writes, returning the existing failure representation when the size is
incorrect. Apply this consistently across SdEntry::serialize and the additional
serializers identified by the review.

In `@src/sd/sd_server.cpp`:
- Around line 182-201: Update the service insertion logic around
offered_services_ and its capacity configuration so static ETL storage cannot
exceed its actual compile-time capacity. Enforce the effective limit as the
lower of config_.max_services and the container’s capacity, and evict or reject
before push_back accordingly; apply the same capacity-aware handling to related
event-group and session buckets if they use fixed-size containers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 97cd78dc-a7ff-4064-ae89-c17ab80fb114

📥 Commits

Reviewing files that changed from the base of the PR and between 5ddced4 and 53c9d64.

📒 Files selected for processing (12)
  • include/sd/sd_message.h
  • src/CMakeLists.txt
  • src/events/event_publisher.cpp
  • src/events/event_subscriber.cpp
  • src/rpc/rpc_client.cpp
  • src/rpc/rpc_server.cpp
  • src/sd/sd_client.cpp
  • src/sd/sd_message.cpp
  • src/sd/sd_server.cpp
  • src/tp/tp_manager.cpp
  • src/transport/tcp_transport.cpp
  • src/transport/udp_transport.cpp
💤 Files with no reviewable changes (2)
  • src/transport/tcp_transport.cpp
  • src/transport/udp_transport.cpp

Comment thread src/CMakeLists.txt
Comment on lines +113 to +122
# GCC 13 false-positive (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109561):
# std::variant move-constructor inlining triggers -Wmaybe-uninitialized on
# SD entry/option members despite {0} default member initializers.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_message.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_server.cpp
PROPERTIES COMPILE_OPTIONS "-Wno-maybe-uninitialized")
endif()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- src/CMakeLists.txt relevant lines ---\n'
sed -n '100,130p' src/CMakeLists.txt || true

printf '\n--- search for compiler/version conditions in CMake files ---\n'
rg -n "CMAKE_CXX_COMPILER_(ID|VERSION)|GCC 13|maybe-uninitialized|109561" -S . || true

printf '\n--- git diff stat/name status ---\n'
git diff --stat || true
git diff --name-status || true

Repository: vtz/opensomeip

Length of output: 243


🌐 Web query:

GCC Bug 109561 std::variant move constructor maybe-uninitialized GCC 13

💡 Result:

GCC Bug 109561 refers to a known, long-standing issue in the GNU Compiler Collection where the optimizer generates false-positive -Wmaybe-uninitialized warnings when dealing with std::variant or similar union-based storage structures, particularly when using GCC 13 and other recent versions [1][2][3]. These warnings typically occur because the compiler's inter-procedural analysis and inlining optimizations fail to accurately track the initialization state of members within complex, nested union structures (often used to implement variant storage) [1][3]. Even when code is logically correct and correctly initializes the active member of a union, the optimizer may conclude that a member is "potentially used" while uninitialized [1][3]. Key characteristics of this issue include: 1. False Positives: The warnings are diagnostic artifacts of the compiler's optimization pass (often occurring at -O2 or -O3) rather than actual bugs in the user's code [1][2][3]. 2. Context: This frequently appears when using std::variant with complex types (like std::string or other containers) or when using library implementations like Boost.Variant2 or Boost.Optional, where deeply nested inlining makes the initialization path opaque to the compiler [1][3]. 3. Mitigation: Because the compiler cannot always see the initialization proof through the inlining, the standard community recommendation is to suppress these false positives locally using pragmas or compiler flags [1][2][3]. Common approaches include: - Using -Wno-error=maybe-uninitialized to prevent the warnings from breaking builds [1]. - Applying diagnostic pragmas around the specific code blocks triggering the warning to suppress it without disabling it globally [2][3]. While the GCC maintainers acknowledge the difficulty of eliminating these false positives without degrading optimization performance, the issue remains a recurring point of friction for C++ developers using modern GCC versions [1][2][3].

Citations:


🏁 Script executed:

#!/bin/sh
set -euo pipefail

printf '%s\n' '--- src/CMakeLists.txt relevant lines ---'
sed -n '100,130p' src/CMakeLists.txt || true

printf '%s\n' ''
printf '%s\n' '--- search for compiler/version conditions in CMake files ---'
rg -n "CMAKE_CXX_COMPILER_(ID|VERSION)|GCC 13|maybe-uninitialized|109561" -S . || true

printf '%s\n' ''
printf '%s\n' '--- git diff stat/name status ---'
git diff --stat || true
git diff --name-status || true

Repository: vtz/opensomeip

Length of output: 2353


🌐 Web query:

"CMAKE_CXX_COMPILER_VERSION" "GCC 13" "-Wno-maybe-uninitialized"

💡 Result:

To conditionally disable the -Wmaybe-uninitialized compiler warning specifically for GCC 13 in CMake, you should use the CMAKE_CXX_COMPILER_ID and CMAKE_CXX_COMPILER_VERSION variables within an if-block in your CMakeLists.txt [1][2]. The most common approach is to check if the compiler is GNU and if the version is within the 13.x range [1][3]. You can then apply the flag using target_compile_options (recommended for target-specific settings) or add_compile_options (for directory-wide settings) [4][5]. Example implementation: if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14.0) target_compile_options(your_target_name PRIVATE -Wno-maybe-uninitialized) endif Key technical details: - CMAKE_CXX_COMPILER_VERSION: This variable stores the compiler version in major[.minor[.patch[.tweak]]] format [6]. - VERSION_GREATER_EQUAL / VERSION_LESS: CMake provides these operators for robust version comparison, which are preferred over manual string parsing [7]. - Target Scope: Using target_compile_options with the PRIVATE or INTERFACE keyword is generally preferred over directory-wide commands to avoid polluting other targets in your build [5]. For more complex requirements, you can also use generator expressions, which allow these checks to be evaluated at build-time rather than configure-time [4][5]: target_compile_options(your_target_name PRIVATE $&lt;$&lt;AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,13.0>>:-Wno-maybe-uninitialized>)

Citations:


Limit this warning suppression to affected GCC versions.

The comment identifies a GCC false positive, but the condition suppresses -Wmaybe-uninitialized for every GNU compiler release. Gate it with CMAKE_CXX_COMPILER_VERSION, or document why the suppression must remain unconditional; otherwise newer compilers may hide genuine diagnostics.

Suggested direction
-    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
+       CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13 AND
+       CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# GCC 13 false-positive (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109561):
# std::variant move-constructor inlining triggers -Wmaybe-uninitialized on
# SD entry/option members despite {0} default member initializers.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_message.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_server.cpp
PROPERTIES COMPILE_OPTIONS "-Wno-maybe-uninitialized")
endif()
# GCC 13 false-positive (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109561):
# std::variant move-constructor inlining triggers -Wmaybe-uninitialized on
# SD entry/option members despite {0} default member initializers.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13 AND
CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_message.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sd/sd_server.cpp
PROPERTIES COMPILE_OPTIONS "-Wno-maybe-uninitialized")
endif()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CMakeLists.txt` around lines 113 - 122, Restrict the warning suppression
in the GNU compiler condition around the affected SD source files to the GCC
versions impacted by the documented false positive, using
CMAKE_CXX_COMPILER_VERSION. Preserve the existing source-file list and compile
option, and avoid applying -Wno-maybe-uninitialized to newer GCC releases unless
an explicit justification is added.

Source: MCP tools

Comment thread src/sd/sd_message.cpp Outdated
Comment thread src/tp/tp_manager.cpp Outdated
vtz and others added 4 commits July 24, 2026 11:29
Pool lifecycle:
- Add in_use[] tracking to message pool (mirrors buffer_pool.cpp)
- release_message() now checks in_use before returning slot to free stack,
  preventing double-release from corrupting the pool
- Add TC_MSGPOOL_DOUBLE_RELEASE, TC_MSGPOOL_NULL_RELEASE,
  TC_MSGPOOL_FOREIGN_PTR_RELEASE tests

ETL safety handler:
- Register custom ETL error handler via init_static_allocator() that
  increments an atomic counter on assertion failure (no abort)
- Fix ETL configuration: remove ETL_THROW_EXCEPTIONS=0 (which
  paradoxically ENABLED exceptions), add ETL_CHECK_PUSH_POP for
  bounded container overflow detection
- Add etl_error_handler.h/.cpp with get/reset_etl_error_count() API
- Add TC_ETL_HANDLER_* tests verifying overflow invokes handler
  without terminating

Key finding: ETL unordered_map does NOT degrade gracefully on overflow
(segfaults internally), confirming that callers MUST use full() checks
before insert — the error handler is a safety net, not a substitute.

Co-authored-by: Cursor <cursoragent@cursor.com>
Without pre-insert capacity checks, ETL unordered_map segfaults on
overflow. Add size() >= max_size() guards before insert/operator[]
calls in RPC, events, SD, and TP code paths. The checks are safe
for both dynamic (always passes) and static (enforces bounds) builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
SessionManager safety improvements:
- get_session() now returns std::optional<Session> (copy) instead of
  raw Session* pointer, eliminating dangling pointer risk when the
  lock is released before the caller uses the result
- Add set_session_state() for thread-safe state mutation
- Add size() >= max_size() guard in create_session(), returning 0
  when the bounded map is full (prevents ETL segfault on overflow)
- Change cleanup_expired_sessions() parameter from seconds to
  steady_clock::duration for more precise testing
- Update all tests to use the new API
- Add TC_SM_CAPACITY_001 (static alloc only) verifying capacity limit

Co-authored-by: Cursor <cursoragent@cursor.com>
Shrink nested ETL container capacities in EventPublisherImpl:
- subscriptions_ map: 32→16 event groups
- Vector<ClientInfo> per group: 32→8 subscribers per event group
- ClientInfo::filters: 32→4 filters per client
- registered_events_ map: 32→16 events
- last_publish_times_ map: 32→16 events

SOMEIP_PIMPL_EVENTPUB_SIZE reduced from 1MiB (1048576) to 64KiB
(65536) with actual sizeof ≈ 33KiB. All capacities remain
overridable via -D compiler flags for deployment-specific tuning.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/platform/static/memory.cpp (1)

76-84: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use defined pointer arithmetic when locating the release slot.

The first relational check can pass for a pointer in a later message_slab[idx] row, but raw - base subtracts base pointers from different inner arrays, which is not defined. Locate the slot with equality checks/validated index arithmetic instead, so every release stays inside defined behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/static/memory.cpp` around lines 76 - 84, Update the release-slot
validation around message_slab so it does not subtract pointers from different
inner arrays. Locate the matching row using equality checks or validated index
arithmetic, then derive the slot index only after confirming raw points to a
valid Message within that row; preserve the early return for invalid pointers
and retain the existing idx calculation outcome.
include/sd/sd_message.h (2)

237-250: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Preserve pointer lifetime across platform::Vector mutations.

SdMessage stores variants in platform::Vector<SdEntryStorage> / platform::Vector<SdOptionStorage>, and add_entry() / add_option() use emplace_back(). With the dynamic backend, this is std::vector, so an earlier pointer returned by get_entry_ptr() / get_option_ptr() can be invalidated by a later insertion. Avoid returning raw pointers from value-backed storage, or add an explicit contract proving returned pointers never outlive any SdMessage mutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/sd/sd_message.h` around lines 237 - 250, Update get_entry_ptr and
get_option_ptr and their SdMessage callers so pointers into SdEntryStorage and
SdOptionStorage are not exposed across add_entry() or add_option() mutations;
prefer returning stable value-based access or otherwise enforce a lifetime
contract that prevents callers from retaining pointers through platform::Vector
reallocation. Preserve const and mutable access semantics while ensuring later
insertions cannot invalidate previously returned references or pointers.

35-37: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not expose cross-derived base assignment with copied type tags.

A ServiceEntry can be assigned from an EventGroupEntry through SdEntry&, and an IPv4EndpointOption from an IPv4MulticastOption through SdOption&. Default assignment copies type_ while leaving the derived payload unchanged, so serialization can emit an SdEntry::serialize() tag that does not match the dynamic entry type. Make the base assignments inaccessible to callers, or implement them so the dynamic-type tag is not copied.

Also applies to lines 142-144.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/sd/sd_message.h` around lines 35 - 37, Restrict or revise the copy
and move assignment operators in the base classes SdEntry and SdOption so
callers cannot perform cross-derived assignment through a base reference while
copying type_. Preserve derived payload and type-tag consistency during
assignment, including the declarations around the referenced SdEntry and
SdOption assignment operators.
tests/test_static_message_pool.cpp (1)

53-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the pool assertions.

IndependentAllocations never checks that live allocations have distinct addresses, IntrusivePtrReturnsToPool does not prove the released slot was returned, and ConcurrentAllocRelease passes if only one allocation succeeds. A faulty allocator or release path could therefore pass these tests. Assert uniqueness, actual reuse/replenishment, and the expected concurrent success/failure count.

Also applies to: 144-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_static_message_pool.cpp` around lines 53 - 74, Strengthen the
tests around allocate_message: in IndependentAllocations, record each live
allocation’s address and assert all five are distinct; in
IntrusivePtrReturnsToPool, verify the allocation after reset reuses the released
slot or otherwise confirms pool replenishment; and in ConcurrentAllocRelease,
assert the expected number of successful and failed allocations rather than only
checking that one succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_etl_error_handler.cpp`:
- Around line 44-46: Update EtlErrorHandlerTest.HandlerIsRegistered so it
triggers an ETL error and verifies the registered handler responds, rather than
only asserting get_etl_error_count() is zero. Reuse the existing ETL
error-triggering mechanism and expected count assertion from the overflow tests;
otherwise rename or remove the test if registration is already covered there.

---

Outside diff comments:
In `@include/sd/sd_message.h`:
- Around line 237-250: Update get_entry_ptr and get_option_ptr and their
SdMessage callers so pointers into SdEntryStorage and SdOptionStorage are not
exposed across add_entry() or add_option() mutations; prefer returning stable
value-based access or otherwise enforce a lifetime contract that prevents
callers from retaining pointers through platform::Vector reallocation. Preserve
const and mutable access semantics while ensuring later insertions cannot
invalidate previously returned references or pointers.
- Around line 35-37: Restrict or revise the copy and move assignment operators
in the base classes SdEntry and SdOption so callers cannot perform cross-derived
assignment through a base reference while copying type_. Preserve derived
payload and type-tag consistency during assignment, including the declarations
around the referenced SdEntry and SdOption assignment operators.

In `@src/platform/static/memory.cpp`:
- Around line 76-84: Update the release-slot validation around message_slab so
it does not subtract pointers from different inner arrays. Locate the matching
row using equality checks or validated index arithmetic, then derive the slot
index only after confirming raw points to a valid Message within that row;
preserve the early return for invalid pointers and retain the existing idx
calculation outcome.

In `@tests/test_static_message_pool.cpp`:
- Around line 53-74: Strengthen the tests around allocate_message: in
IndependentAllocations, record each live allocation’s address and assert all
five are distinct; in IntrusivePtrReturnsToPool, verify the allocation after
reset reuses the released slot or otherwise confirms pool replenishment; and in
ConcurrentAllocRelease, assert the expected number of successful and failed
allocations rather than only checking that one succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88882ae7-5e25-41d9-bea2-3e78ec4099e6

📥 Commits

Reviewing files that changed from the base of the PR and between 53c9d64 and 47430ac.

📒 Files selected for processing (8)
  • include/platform/static/etl_error_handler.h
  • include/sd/sd_message.h
  • src/CMakeLists.txt
  • src/platform/static/etl_error_handler.cpp
  • src/platform/static/memory.cpp
  • tests/CMakeLists.txt
  • tests/test_etl_error_handler.cpp
  • tests/test_static_message_pool.cpp

Comment on lines +44 to +46
TEST_F(EtlErrorHandlerTest, HandlerIsRegistered) {
EXPECT_EQ(get_etl_error_count(), 0u);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make HandlerIsRegistered test registration rather than reset.

get_etl_error_count() == 0 is true even when no callback is installed; this only verifies reset_etl_error_count(). Trigger an ETL error in this test or rename/remove it, since the overflow tests are the ones that currently validate registration behavior.

🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 44-44: syntax error

(syntaxError)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_etl_error_handler.cpp` around lines 44 - 46, Update
EtlErrorHandlerTest.HandlerIsRegistered so it triggers an ETL error and verifies
the registered handler responds, rather than only asserting
get_etl_error_count() is zero. Reuse the existing ETL error-triggering mechanism
and expected count assertion from the overflow tests; otherwise rename or remove
the test if registration is already covered there.

vtz and others added 8 commits July 24, 2026 11:44
Budget script:
- Update DEFAULT_DEFINES to match actual static_config.h values
  (pimpl sizes were 512 → now 4096-65536)
- Fix parse error on include guard defines and comment-only values
- BSS total with default config: ~495 KB

RPC client sync path:
- Replace std::make_shared<SyncState> with stack-local SyncState
  (lifetime is bounded by the polling loop)
- Replace std::make_shared<RpcResponse> with std::optional<RpcResponse>
- Eliminates two heap allocations per synchronous RPC call

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce PayloadView — a lightweight non-owning view over a
contiguous byte range, providing std::span-like semantics without
requiring C++20. Works identically with both dynamic (std::vector)
and static (slab-backed ByteBuffer) payloads.

- PayloadView(data, size) and PayloadView(container) constructors
- operator[], begin/end iterators, subview() for sub-ranges
- Message::payload_view() accessor for zero-copy payload reading
- Tests: TC_PAYLOADVIEW_001..004 covering basic access, empty
  payload, subview, and range-based iteration

Co-authored-by: Cursor <cursoragent@cursor.com>
The transport member is never shared — it's created and exclusively
owned by each pimpl Impl class. Replace std::make_shared with direct
value construction, eliminating the last major heap allocation in
the public API classes under static allocation mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extend test_static_alloc_integration.cpp with heap-zero-proof tests
for all 6 public API classes: RpcClient, RpcServer, EventPublisher,
EventSubscriber, SdClient, SdServer. Each test constructs and
destroys the object under the malloc trap to verify that pimpl
placement new/delete performs no heap allocation.

Also adds PayloadView integration test under the trap.

New test cases:
- TC_STATIC_INT_RPCCLIENT_CTOR
- TC_STATIC_INT_RPCSERVER_CTOR
- TC_STATIC_INT_EVENTPUB_CTOR
- TC_STATIC_INT_EVENTSUB_CTOR
- TC_STATIC_INT_SDCLIENT_CTOR
- TC_STATIC_INT_SDSERVER_CTOR
- TC_STATIC_INT_PAYLOADVIEW

Co-authored-by: Cursor <cursoragent@cursor.com>
Add CMake presets:
- freertos-cortexm4-renode-static: FreeRTOS Cortex-M4 Renode with
  static allocation backend
- threadx-cortexm4-renode-static: ThreadX Cortex-M4 Renode with
  static allocation backend

Add CI jobs:
- FreeRTOS Renode static-alloc test job in freertos.yml
- ThreadX Renode static-alloc test job in threadx.yml
- static_alloc path filter in ci.yml for targeted triggering

Co-authored-by: Cursor <cursoragent@cursor.com>
- malloc_trap.h: add missing @implements REQ_PAL_NOOP_HEAP_VERIFY
- etl_error_handler.h/.cpp: fix REQ_PLATFORM_STATIC_ETL_HANDLER →
  REQ_PAL_ETL_ERROR_HANDLER to match requirement definitions
- test_etl_error_handler.cpp: fix @tests tags to use correct REQ ID

Co-authored-by: Cursor <cursoragent@cursor.com>
Updated by running extract_code_requirements.py,
generate_traceability_matrix.py, and related validation scripts.

Key metrics:
- Total requirements: 649 → 669 (+20 new static-alloc REQs)
- Fully traced: 585 → 594 (88.8%)
- Code refs: 587 → 596
- Test coverage: 647 → 662

Co-authored-by: Cursor <cursoragent@cursor.com>
malloc_trap.cpp requires <dlfcn.h> (dlsym/RTLD_NEXT) which is
unavailable on bare-metal ARM targets. Guard the someip_malloc_trap
object library and its dependent integration test behind
NOT CMAKE_CROSSCOMPILING so cross-compile presets
(freertos-cortexm4-renode-static, threadx-cortexm4-renode-static)
can build without errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/events/event_subscriber.cpp (1)

222-240: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the field callback when sending fails.

On send failure, this returns false but retains field_requests_[key]. A later response can invoke a callback for a failed request, and repeated failures exhaust the fixed-capacity map. Erase the entry on failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/events/event_subscriber.cpp` around lines 222 - 240, Update the field
request flow around field_requests_ and transport_.send_message so a failed send
removes the newly stored callback for key before returning false. Preserve the
existing successful-send behavior and avoid erasing an existing callback when
replacing or reusing the same key.
src/rpc/rpc_client.cpp (2)

169-170: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Release sessions and reject failed allocation.

Each call allocates a SessionManager entry, but no completion, cancellation, send-failure, or shutdown path removes it. After 256 completed calls, create_session() returns 0 and requests are sent with the reserved session ID. Check for zero and remove the session whenever its pending call is removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_client.cpp` around lines 169 - 170, Update the call/session
lifecycle around SessionManager::create_session: reject session_id 0 before
sending the request, and release the allocated session whenever the pending call
is removed, including completion, cancellation, send failure, and shutdown
paths. Ensure cleanup uses the same session ID and occurs on every exit path.

122-155: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Eliminate the synchronous callback use-after-return race.

If the receive path removes a pending call but has not invoked its callback, cancel_call() returns false and this method can return while the callback still captures state by reference. The transport thread then dereferences destroyed stack storage. Use a completion protocol that waits for any in-flight callback before returning, or avoid stack-captured async state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_client.cpp` around lines 122 - 155, Update the synchronous wait
flow around SyncState and the call_method_async callback so every return path,
including timeout after cancel_call(handle) returns false, waits for any
in-flight callback before destroying state. Add an explicit callback-completion
protocol or replace the stack-captured state with lifetime-managed storage,
while preserving the existing response and timeout results.
include/someip/message.h (1)

102-105: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject null source bytes for non-empty payloads.

set_payload(nullptr, N) resizes payload_ to N, skips memcpy, and later serialize() copies that uninitialized/reused content back into the message. Enforce data != nullptr || size == 0 and return/clear instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/someip/message.h` around lines 102 - 105, Update set_payload to
reject a null data pointer whenever size is nonzero, before resizing or copying;
return or clear the payload according to the existing API convention. Preserve
valid zero-length calls with data == nullptr and normal copying for non-null
data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 113-121: Connect the changes.static_alloc output to a CI consumer
so static-allocation changes cannot bypass validation. Update the relevant
static/host validation job condition to run when static_alloc is true, or add a
dedicated consumer job if none exists; do not rely on host.yml unless it
explicitly consumes this output.

In @.github/workflows/freertos.yml:
- Around line 341-403: Pin every actions/checkout, actions/cache, and
actions/upload-artifact reference in the static Renode jobs to reviewed
immutable full commit SHAs, replacing the mutable version tags while preserving
each action’s existing configuration. Apply this change in
.github/workflows/freertos.yml lines 341-403 and .github/workflows/threadx.yml
lines 340-402.

In `@include/someip/payload_view.h`:
- Around line 32-34: Update the templated PayloadView constructor to reject
rvalue or temporary containers while preserving construction from lvalue
containers. Use an appropriate deleted rvalue overload or lvalue-only constraint
on the constructor, ensuring data_ never references a container that has already
been destroyed.

In `@src/CMakeLists.txt`:
- Around line 125-133: Update the someip_malloc_trap creation guard to require
loader support in addition to not being cross-compiling: check UNIX, a non-empty
CMAKE_DL_LIBS, and availability of dlfcn.h before add_library. Keep the existing
target_compile_definitions unchanged and avoid creating the object library when
any requirement is unavailable.

In `@src/events/event_publisher.cpp`:
- Around line 235-238: Update the filter-copying logic in the event publisher to
avoid silently dropping entries when client_info.filters reaches max_size().
Reject subscriptions whose filters exceed the backend capacity, or otherwise
expose truncation explicitly, and ensure the operation does not report success
while excess filters are ignored.

In `@src/sd/sd_client.cpp`:
- Around line 154-166: Reorder both request flows so local tracking is reserved
before network transmission: in src/sd/sd_client.cpp#L154-L166, update
find_service() to insert the pending find before send_message() and erase it on
send failure; in src/sd/sd_client.cpp#L238-L255, update the eventgroup
subscription flow to store its tracking entry before send_message() and roll it
back when transmission fails.

In `@src/sd/sd_server.cpp`:
- Around line 805-812: Update next_unicast_session_id to return an
optional/result that represents allocation failure instead of using 0. In each
caller that creates or sends a request with this session ID, detect the
unavailable result and abort the affected response path before constructing or
transmitting the request; preserve normal behavior for valid non-zero IDs.

In `@TEST_TRACEABILITY_MATRIX.md`:
- Around line 227-251: Use one authoritative test-suite inventory across all
traceability documentation. In TEST_TRACEABILITY_MATRIX.md lines 22-45,
distinguish inventory-only files from suites executed in the current
environment; regenerate TEST_TRACEABILITY_MATRIX.md lines 227-251 from the
complete validated inventory; and align TRACEABILITY_SUMMARY.md lines 74-93 with
those validated execution results so every documented suite and count is
consistent.

In `@tests/CMakeLists.txt`:
- Around line 134-141: Add ${CMAKE_DL_LIBS} to target_link_libraries for
test_static_alloc_integration alongside opensomeip and gtest_main, ensuring
dlsym dependencies from someip_malloc_trap are explicitly linked.

In `@TRACEABILITY_MATRIX.md`:
- Around line 323-325: Update the traceability metrics and recommendations
together so the stale REQ_PLATFORM_ARCH_001 spec-link recommendation is removed
or reflects the current state. Ensure the reported spec-linked requirement count
and recommended actions are consistent after regeneration.
- Around line 312-314: Regenerate the serialization gap and remediation data as
one consistent update: in TRACEABILITY_MATRIX.md:312-314 publish the complete
validated missing-requirement set and in TRACEABILITY_MATRIX.md:329-330 list
every remaining serialization requirement; update
TRACEABILITY_SUMMARY.md:111-114 and TEST_TRACEABILITY_MATRIX.md:267-269 so their
recommended actions and counts cover that same full set rather than only
REQ_SER_090–REQ_SER_107.

In `@TRACEABILITY_SUMMARY.md`:
- Around line 60-63: Correct the module-table metric in TRACEABILITY_SUMMARY.md:
either rename the section to indicate it reports full RST definitions,
consistent with Line 66, or replace the row values with actual per-module counts
for the 594 fully traced requirements. Ensure the displayed totals and label
describe the same metric.

---

Outside diff comments:
In `@include/someip/message.h`:
- Around line 102-105: Update set_payload to reject a null data pointer whenever
size is nonzero, before resizing or copying; return or clear the payload
according to the existing API convention. Preserve valid zero-length calls with
data == nullptr and normal copying for non-null data.

In `@src/events/event_subscriber.cpp`:
- Around line 222-240: Update the field request flow around field_requests_ and
transport_.send_message so a failed send removes the newly stored callback for
key before returning false. Preserve the existing successful-send behavior and
avoid erasing an existing callback when replacing or reusing the same key.

In `@src/rpc/rpc_client.cpp`:
- Around line 169-170: Update the call/session lifecycle around
SessionManager::create_session: reject session_id 0 before sending the request,
and release the allocated session whenever the pending call is removed,
including completion, cancellation, send failure, and shutdown paths. Ensure
cleanup uses the same session ID and occurs on every exit path.
- Around line 122-155: Update the synchronous wait flow around SyncState and the
call_method_async callback so every return path, including timeout after
cancel_call(handle) returns false, waits for any in-flight callback before
destroying state. Add an explicit callback-completion protocol or replace the
stack-captured state with lifetime-managed storage, while preserving the
existing response and timeout results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 53c2dd2c-679e-4ec6-9dba-ef2884d24af2

📥 Commits

Reviewing files that changed from the base of the PR and between 47430ac and 0832ed5.

📒 Files selected for processing (29)
  • .github/workflows/ci.yml
  • .github/workflows/freertos.yml
  • .github/workflows/threadx.yml
  • CMakePresets.json
  • TEST_TRACEABILITY_MATRIX.md
  • TRACEABILITY_MATRIX.md
  • TRACEABILITY_SUMMARY.md
  • include/core/session_manager.h
  • include/platform/static/etl_error_handler.h
  • include/platform/static/malloc_trap.h
  • include/platform/static/static_config.h
  • include/someip/message.h
  • include/someip/payload_view.h
  • scripts/static_memory_budget.py
  • src/CMakeLists.txt
  • src/core/session_manager.cpp
  • src/events/event_publisher.cpp
  • src/events/event_subscriber.cpp
  • src/platform/static/etl_error_handler.cpp
  • src/rpc/rpc_client.cpp
  • src/rpc/rpc_server.cpp
  • src/sd/sd_client.cpp
  • src/sd/sd_server.cpp
  • src/tp/tp_manager.cpp
  • tests/CMakeLists.txt
  • tests/test_etl_error_handler.cpp
  • tests/test_message.cpp
  • tests/test_session_manager.cpp
  • tests/test_static_alloc_integration.cpp

Comment thread .github/workflows/ci.yml
Comment on lines +113 to +121
static_alloc:
- 'src/platform/static/**'
- 'include/platform/static/**'
- 'tests/test_static_alloc*.cpp'
- 'tests/test_buffer_pool.cpp'
- 'tests/test_static_message_pool.cpp'
- 'tests/test_platform_containers.cpp'
- 'tests/test_etl_error_handler.cpp'
- '.github/workflows/host.yml'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C4 'needs\.changes\.outputs\.static_alloc|static_alloc:' .github/workflows

Repository: vtz/opensomeip

Length of output: 1515


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow file size =="
wc -l .github/workflows/ci.yml

echo "== jobs outline/key regions =="
rg -n '^\w+:|if:|needs:|outputs:|runs-on:|static_alloc|host:|changes:|fail-safe|checkout|run:' .github/workflows/ci.yml

Repository: vtz/opensomeip

Length of output: 1399


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 250-330 =="
sed -n '250,330p' .github/workflows/ci.yml

echo "== fail-safe definitions =="
rg -n -C3 "fail-safe|fail_safe|fail|if:|STATIC_ALLOC|static_alloc|changes.outputs" .github/workflows/ci.yml

Repository: vtz/opensomeip

Length of output: 6872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re
text = Path(".github/workflows/ci.yml").read_text()
lines = text.splitlines()

# Extract job ids by matching top-level lines and their indentation block via simple parser.
jobs = {}
current = None
indent = None
for i,line in enumerate(lines, start=1):
    if not line.startswith(" "):
        continue
    m = re.match(r"^  ([A-Za-z_][A-Za-z0-9_-]*):\s*$", line)
    if m:
        current = m.group(1)
        indent = 2
        jobs[current] = {"name": current, "if": None, "uses": None, "needs": None, "static_alloc_consumer": False, "line": i}
    elif current and current != "changes":
        stripped = line.lstrip()
        prefix = indent * 4
        if len(line) - len(stripped) == prefix:
            if re.match(r"^if:\s*===", stripped):
                # multi-line if continuation
                jobs[current]["if"] = "\n" + "\n".join(stripped for stripped in stripped.splitlines())
                continue
            m2 = re.match(r"^if:\s*$", stripped)
            if m2:
                jobs[current]["if"] = ""
                continue
            jobs[current].setdefault("if", "")
            jobs[current]["if"] += stripped + "\n"
            if "needs.changes.outputs.static_alloc" in stripped:
                jobs[current]["static_alloc_consumer"] = True
        elif stripped.startswith("if:"):
            jobs[current]["if"] = stripped[4:].strip()
            if "needs.changes.outputs.static_alloc" in stripped:
                jobs[current]["static_alloc_consumer"] = True
        elif stripped.startswith("uses:"):
            jobs[current]["uses"] = stripped[5:].strip()
        elif stripped.startswith("needs:"):
            jobs[current]["needs"] = stripped[5:].strip()

print("jobs:", list(jobs))
print("static_alloc_consumer jobs:", [j for j,v in jobs.items() if v.get("static_alloc_consumer")])
print("all non-changes jobs gated only by static_alloc output? no jobs gated solely by static_alloc:", all(not v.get("if") or "needs.changes.outputs.static_alloc" not in v.get("if") else True for _,v in jobs.items()))
PY

echo "== host workflow references to static_alloc =="
rg -n "static_alloc|STATIC_ALLOC|static_alloc" .github/workflows/host.yml src include tests 2>/dev/null || true

Repository: vtz/opensomeip

Length of output: 619


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re
text = Path(".github/workflows/ci.yml").read_text()
lines = text.splitlines()

jobs = {}
current = None
indent = None
for i,line in enumerate(lines, start=1):
    if not line.startswith(" "):
        continue
    m = re.match(r"^  ([A-Za-z_][A-Za-z0-9_-]*):\s*$", line)
    if m:
        current = m.group(1)
        indent = 2
        jobs[current] = {
            "name": current,
            "if": None,
            "uses": None,
            "needs": None,
            "static_alloc_consumer": False,
            "line": i,
        }
    elif current and current != "changes":
        stripped = line.lstrip()
        prefix = indent * 4
        if len(line) - len(stripped) == prefix:
            m2 = re.match(r"^if:.*$", stripped)
            jobs[current].setdefault("if", "")
            if m2:
                jobs[current]["if"] += stripped + "\n"
                if "needs.changes.outputs.static_alloc" in stripped:
                    jobs[current]["static_alloc_consumer"] = True
            elif stripped.startswith("uses:"):
                jobs[current]["uses"] = stripped[5:].strip()
            elif stripped.startswith("needs:"):
                jobs[current]["needs"] = stripped[5:].strip()

print("jobs:", list(jobs))
print("static_alloc consumers:", [j for j,v in jobs.items() if v.get("static_alloc_consumer")])
print("static_alloc only conditions:", [(j, v["if"]) for j in jobs if "needs.changes.outputs.static_alloc" in (v.get("if") or "")])
PY

echo "== host/static alloc references =="
rg -n "static_alloc|STATIC_ALLOC" .github/workflows/host.yml src include tests 2>/dev/null || true

echo "== ci.yml change detection definition =="
sed -n '20,130p' .github/workflows/ci.yml

Repository: vtz/opensomeip

Length of output: 459


Gate a CI job with static_alloc.

static_alloc is emitted from changes, but no job after that consumes it, and .github/workflows/host.yml does not use STATIC_ALLOC. Changes limited to src/platform/static/**, include/platform/static/**, or the listed static allocation tests can pass the fail-safe without running static/host validation. Add a consumer job or include this output in the relevant job condition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 113 - 121, Connect the
changes.static_alloc output to a CI consumer so static-allocation changes cannot
bypass validation. Update the relevant static/host validation job condition to
run when static_alloc is true, or add a dedicated consumer job if none exists;
do not rely on host.yml unless it explicitly consumes this output.

Comment on lines +341 to +403
- uses: actions/checkout@v4

- name: Install ccache
run: sudo apt-get update -qq && sudo apt-get install -y ccache

- name: Cache ccache
uses: actions/cache@v4
with:
path: ~/.cache/ccache
key: ccache-${{ runner.os }}-freertos-renode-cortexm4-static-${{ github.sha }}
restore-keys: |
ccache-${{ runner.os }}-freertos-renode-cortexm4-static-

- name: Cache ARM toolchain apt packages
uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-arm-toolchain-${{ runner.os }}

- name: Install ARM toolchain
run: |
sudo apt-get update
sudo apt-get install -y gcc-arm-none-eabi libnewlib-arm-none-eabi

- name: Cache CMake FetchContent
uses: actions/cache@v4
with:
path: build/freertos-cortexm4-renode-static/_deps
key: fetchcontent-freertos-renode-static-${{ hashFiles('CMakeLists.txt') }}
restore-keys: |
fetchcontent-freertos-renode-static-

- name: Cache Renode
id: renode-cache
uses: actions/cache@v4
with:
path: /opt/renode
key: renode-${{ env.RENODE_VERSION }}-linux

- name: Install Renode
if: steps.renode-cache.outputs.cache-hit != 'true'
run: |
wget -q "https://github.com/renode/renode/releases/download/v${RENODE_VERSION}/renode-${RENODE_VERSION}.linux-portable.tar.gz"
sudo mkdir -p /opt/renode
sudo tar xzf "renode-${RENODE_VERSION}.linux-portable.tar.gz" -C /opt/renode --strip-components=1

- name: Add Renode to PATH
run: sudo ln -sf /opt/renode/renode /usr/local/bin/renode

- name: Build & run FreeRTOS static-alloc tests on Renode
run: |
cmake --preset freertos-cortexm4-renode-static -S "$GITHUB_WORKSPACE" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
cmake --build "$GITHUB_WORKSPACE/build/freertos-cortexm4-renode-static" -j"$(nproc)"
./scripts/run_freertos_renode_test.sh \
--skip-build \
--build-dir build/freertos-cortexm4-renode-static \
--timeout 60 \
--junit-output /tmp/freertos_renode_static_junit.xml

- name: Upload Renode test results
uses: actions/upload-artifact@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -nP '^\s*uses:\s+actions/(checkout|cache|upload-artifact)@(?![0-9a-f]{40}\b)' \
  .github/workflows/freertos.yml .github/workflows/threadx.yml
# Expected: no output for the newly added static Renode jobs.

Repository: vtz/opensomeip

Length of output: 3386


Pin the static CI actions to immutable commit hashes.

The new FreeRTOS and ThreadX static Renode jobs still use mutable GitHub Action tags for actions/checkout, actions/cache, and actions/upload-artifact; pin these occurrences to reviewed full SHAs to avoid code changes through tag rewrites.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 341-341: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 341-341: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 347-347: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 355-355: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 366-366: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 375-375: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 403-403: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

📍 Affects 2 files
  • .github/workflows/freertos.yml#L341-L403 (this comment)
  • .github/workflows/threadx.yml#L340-L402
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/freertos.yml around lines 341 - 403, Pin every
actions/checkout, actions/cache, and actions/upload-artifact reference in the
static Renode jobs to reviewed immutable full commit SHAs, replacing the mutable
version tags while preserving each action’s existing configuration. Apply this
change in .github/workflows/freertos.yml lines 341-403 and
.github/workflows/threadx.yml lines 340-402.

Source: Linters/SAST tools

Comment on lines +32 to +34
template <typename Container>
explicit PayloadView(const Container& c) noexcept
: data_(c.data()), size_(c.size()) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg 'include/someip/payload_view\.h|ByteBuffer|payload_view' || true

echo
echo "payload_view.h outline:"
if [ -f include/someip/payload_view.h ]; then
  ast-grep outline include/someip/payload_view.h || true
  echo
  echo "payload_view.h contents:"
  cat -n include/someip/payload_view.h
fi

echo
echo "ByteBuffer definitions/usages:"
rg -n "struct ByteBuffer|class ByteBuffer|class ByteBuffer|using ByteBuffer|namespace ByteBuffer|ByteBuffer" -g '*.h' -g '*.hpp' -g '*.cc' -g '*.cpp' . | head -200

Repository: vtz/opensomeip

Length of output: 20412


🌐 Web query:

C++ constexpr const T& parameter rvalue argument object lifetime

💡 Result:

In C++, binding a temporary object (rvalue) to a const T& function parameter does not extend the temporary's lifetime beyond the full-expression containing the function call [1][2][3]. While a const reference in a local scope (e.g., const T& ref = T;) extends the lifetime of the temporary to the scope of that reference [4][5], this rule specifically excludes function parameters [1][2][3]. When a temporary is bound to a reference parameter, the temporary persists only until the completion of the full-expression that contains the function call [1][2][3]. Consequently, if a function returns a reference to one of its arguments that was bound to a temporary, the returned reference becomes dangling immediately after the function returns [6][2]. This behavior remains the same regardless of whether the function is declared constexpr [7]. During constant evaluation, the C++ abstract machine manages the lifetime of temporaries created within that evaluation [7]. If a constexpr function is evaluated at compile-time, the lifetime of temporaries bound to its reference parameters will align with the duration of that specific constant-evaluation [7]. However, this does not grant the reference a persistent lifetime outside of that evaluation context [7]. Because constexpr functions must be valid for both compile-time and runtime execution, the compiler enforces the same lifetime rules for reference parameters in both contexts [7]. Therefore, code that relies on lifetime extension via a reference parameter will lead to undefined behavior at runtime, and such expressions are generally not usable in a constant expression if they result in dangling references [6][2][7]. Top results: [1], [2], [7], [3]

Citations:


Prevent construction from temporary containers.

PayloadView(platform::ByteBuffer{...}) compiles, but the temporary argument ends at the end of the expression, so the stored data_ immediately points outside a valid container. Delete or overload for rvalue containers, or constrain construction to lvalue containers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/someip/payload_view.h` around lines 32 - 34, Update the templated
PayloadView constructor to reject rvalue or temporary containers while
preserving construction from lvalue containers. Use an appropriate deleted
rvalue overload or lvalue-only constraint on the constructor, ensuring data_
never references a container that has already been destroyed.

Comment thread src/CMakeLists.txt
Comment on lines +125 to +133
# Malloc trap object library (linked into verification tests, not into main lib).
# Only built on hosted platforms — requires <dlfcn.h> (unavailable on bare-metal).
if(NOT CMAKE_CROSSCOMPILING)
add_library(someip_malloc_trap OBJECT
${CMAKE_CURRENT_SOURCE_DIR}/platform/static/malloc_trap.cpp)
target_compile_definitions(someip_malloc_trap PRIVATE SOMEIP_MALLOC_TRAP)
endif()
endif()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C4 'dlfcn|CMAKE_CROSSCOMPILING|CMAKE_DL_LIBS|WIN32|UNIX' src tests CMakeLists.txt

Repository: vtz/opensomeip

Length of output: 13538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the malloc trap implementation and the CMake section around the guard.
wc -l src/platform/static/malloc_trap.cpp src/CMakeLists.txt tests/CMakeLists.txt
printf '\n--- src/platform/static/malloc_trap.cpp first 80 lines ---\n'
sed -n '1,90p' src/platform/static/malloc_trap.cpp
printf '\n--- src/CMakeLists.txt malloc trap section ---\n'
sed -n '118,136p' src/CMakeLists.txt
printf '\n--- tests/CMakeLists.txt malloc trap tests references ---\n'
sed -n '130,139p' tests/CMakeLists.txt

# Deterministically identify malloc_trap inclusion condition from src/CMakeLists.txt and report how non-Unix native targets would match it.
python3 - <<'PY'
from pathlib import Path
text = Path('src/CMakeLists.txt').read_text()
needle = '\tif(NOT CMAKE_CROSSCOMPILING)\n'
idx = text.index('{}{}{}\n'.format(needle, 'add_library(someip_malloc_trap OBJECT', f'\t\t${{CMAKE_CURRENT_SOURCE_DIR}}/platform/static/malloc_trap.cpp)'))
start = text.rfind('\n', 0, idx - len(needle)) + 1
block = text[idx:idx + 200]
print('has_malloc_trap_block=', 'add_library(someip_malloc_trap OBJECT' in block)
print('current_block=\n' + '\n'.join(block.splitlines()[:12]))
print('matches_windows_host_scenario=', 'src/platform/static/malloc_trap.cpp' in block and 'if(NOT CMAKE_CROSSCOMPILING)' in block)
PY

# Inspect platform definitions in tests to see whether malloc_trap tests are skipped on WIN32 even if target existed.
rg -n -C3 'TARGET someip_malloc_trap|test_static_alloc_integration|WIN32' tests/CMakeLists.txt src tests/platform include platform || true

Repository: vtz/opensomeip

Length of output: 5214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- malloc_trap.cpp header guards ---'
sed -n '1,48p' src/platform/static/malloc_trap.cpp

printf '%s\n' '--- tests malloc trap inclusion context ---'
sed -n '130,140p' tests/CMakeLists.txt

printf '%s\n' '--- CMake configuration for tests ---'
sed -n '56,140p' tests/CMakeLists.txt

Repository: vtz/opensomeip

Length of output: 6456


Gate the malloc trap on loader support, not only cross-compilation.

malloc_trap.cpp uses <dlfcn.h>/dlsym, but the object library is created whenever CMAKE_CROSSCOMPILING is false. A native Windows build can therefore attempt to compile the trap target without that header. Add UNIX/CMAKE_DL_LIBS/dlfcn.h checks before creating someip_malloc_trap.

Suggested direction
-if(NOT CMAKE_CROSSCOMPILING)
+if(UNIX AND NOT CMAKE_CROSSCOMPILING)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Malloc trap object library (linked into verification tests, not into main lib).
# Only built on hosted platforms — requires <dlfcn.h> (unavailable on bare-metal).
if(NOT CMAKE_CROSSCOMPILING)
add_library(someip_malloc_trap OBJECT
${CMAKE_CURRENT_SOURCE_DIR}/platform/static/malloc_trap.cpp)
target_compile_definitions(someip_malloc_trap PRIVATE SOMEIP_MALLOC_TRAP)
endif()
endif()
# Malloc trap object library (linked into verification tests, not into main lib).
# Only built on hosted platforms — requires <dlfcn.h> (unavailable on bare-metal).
if(UNIX AND NOT CMAKE_CROSSCOMPILING)
add_library(someip_malloc_trap OBJECT
${CMAKE_CURRENT_SOURCE_DIR}/platform/static/malloc_trap.cpp)
target_compile_definitions(someip_malloc_trap PRIVATE SOMEIP_MALLOC_TRAP)
endif()
endif()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CMakeLists.txt` around lines 125 - 133, Update the someip_malloc_trap
creation guard to require loader support in addition to not being
cross-compiling: check UNIX, a non-empty CMAKE_DL_LIBS, and availability of
dlfcn.h before add_library. Keep the existing target_compile_definitions
unchanged and avoid creating the object library when any requirement is
unavailable.

Comment on lines +235 to +238
for (const auto& f : filters) {
if (client_info.filters.size() >= client_info.filters.max_size()) { break; }
client_info.filters.push_back(f);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not silently discard subscription filters.

A caller can pass more than four filters, receive true, and have the excess filters ignored only in the bounded backend. Reject oversized input or expose truncation explicitly so filtering behavior remains deterministic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/events/event_publisher.cpp` around lines 235 - 238, Update the
filter-copying logic in the event publisher to avoid silently dropping entries
when client_info.filters reaches max_size(). Reject subscriptions whose filters
exceed the backend capacity, or otherwise expose truncation explicitly, and
ensure the operation does not report success while excess filters are ignored.

Comment on lines 227 to +251
### Test Execution Results (Current Environment)

| Test Suite | Tests | Passing | Notes |
|------------|-------|---------|-------|
| Message Tests | 23 | 23 | |
| Serialization Tests | 49 | 49 | |
| SD Tests | 52 | 52 | |
| TP Tests | 23 | 23 | |
| TCP Transport Tests | 16 | 16 | |
| UDP Transport Tests | 27 | 27 | |
| Platform Threading | 21 | 21 | |
| E2E Tests | 11 | 11 | |
| RPC Tests | 8 | 8 | |
| Events Tests | 14 | 14 | |
| PAL FreeRTOS Mock | 22 | 22 | |
| PAL ThreadX Mock | 22 | 22 | |
| PAL Zephyr Mock | 22 | 22 | |
| SD Tests | 57 | 57 | |
| Serialization Tests | 37 | 37 | |
| E2E Tests | 31 | 31 | |
| Message Tests | 27 | 27 | |
| TP Tests | 25 | 25 | |
| Platform Containers | 21 | 21 | |
| Session Manager Tests | 19 | 19 | |
| TCP Transport Tests | 17 | 17 | |
| Static Message Pool | 16 | 16 | |
| Platform Threading | 15 | 15 | |
| Buffer Pool Tests | 14 | 14 | |
| Static Alloc Integration | 14 | 14 | |
| Events Tests | 10 | 10 | |
| UDP Transport Tests | 6 | 6 | |
| ETL Error Handler | 4 | 4 | |
| System Tests | 4 | 4 | |
| PAL FreeRTOS Mock | 1 | 1 | |
| PAL ThreadX Mock | 1 | 1 | |
| PAL Zephyr Mock | 1 | 1 | |
| PAL Static Alloc Mock | 1 | 1 | |
| RPC Tests | 1 | 1 | |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use one authoritative test-suite inventory for all traceability tables.

The documented file inventory and execution summaries list different suites and can misrepresent what was actually run.

  • TEST_TRACEABILITY_MATRIX.md#L227-L251: regenerate execution results from the complete suite inventory.
  • TRACEABILITY_SUMMARY.md#L74-L93: align the summary suite list with the validated execution results.
  • TEST_TRACEABILITY_MATRIX.md#L22-L45: distinguish inventory-only files from suites executed in the current environment.
📍 Affects 2 files
  • TEST_TRACEABILITY_MATRIX.md#L227-L251 (this comment)
  • TRACEABILITY_SUMMARY.md#L74-L93
  • TEST_TRACEABILITY_MATRIX.md#L22-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TEST_TRACEABILITY_MATRIX.md` around lines 227 - 251, Use one authoritative
test-suite inventory across all traceability documentation. In
TEST_TRACEABILITY_MATRIX.md lines 22-45, distinguish inventory-only files from
suites executed in the current environment; regenerate
TEST_TRACEABILITY_MATRIX.md lines 227-251 from the complete validated inventory;
and align TRACEABILITY_SUMMARY.md lines 74-93 with those validated execution
results so every documented suite and count is consistent.

Comment thread tests/CMakeLists.txt
Comment on lines +134 to +141
# Malloc trap integration tests — host-only (requires dlsym/dlfcn.h)
if(TARGET someip_malloc_trap)
add_executable(test_static_alloc_integration test_static_alloc_integration.cpp
$<TARGET_OBJECTS:someip_malloc_trap>)
target_link_libraries(test_static_alloc_integration opensomeip gtest_main)
add_test(NAME StaticAllocIntegrationTest COMMAND test_static_alloc_integration)
set_tests_properties(StaticAllocIntegrationTest PROPERTIES TIMEOUT 30)
endif()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate relevant files =="
git ls-files | grep -E '(^|/)(CMakeLists\.txt|malloc_trap|test_static_alloc_integration)' | sed -n '1,120p'

echo
echo "== Inspect tests/CMakeLists.txt around target =="
if [ -f tests/CMakeLists.txt ]; then
  wc -l tests/CMakeLists.txt
  sed -n '120,150p' tests/CMakeLists.txt | cat -n
fi

echo
echo "== Search for someip_malloc_trap and malloc_trap usages =="
rg -n "someip_malloc_trap|malloc_trap|dlsym|dlfcn|CMAKE_DL_LIBS|FindDL|FindThreads" -S . | sed -n '1,200p'

Repository: vtz/opensomeip

Length of output: 7523


Link the platform dynamic-loader library.

someip_malloc_trap calls dlsym, but test_static_alloc_integration does not link ${CMAKE_DL_LIBS} and object files do not propagate the dynamic loader dependency. Add ${CMAKE_DL_LIBS} to this executable so the link works on platforms where loader symbols are in a separate library.

Suggested fix
-            target_link_libraries(test_static_alloc_integration opensomeip gtest_main)
+            target_link_libraries(test_static_alloc_integration PRIVATE
+                opensomeip gtest_main ${CMAKE_DL_LIBS})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Malloc trap integration tests — host-only (requires dlsym/dlfcn.h)
if(TARGET someip_malloc_trap)
add_executable(test_static_alloc_integration test_static_alloc_integration.cpp
$<TARGET_OBJECTS:someip_malloc_trap>)
target_link_libraries(test_static_alloc_integration opensomeip gtest_main)
add_test(NAME StaticAllocIntegrationTest COMMAND test_static_alloc_integration)
set_tests_properties(StaticAllocIntegrationTest PROPERTIES TIMEOUT 30)
endif()
# Malloc trap integration tests — host-only (requires dlsym/dlfcn.h)
if(TARGET someip_malloc_trap)
add_executable(test_static_alloc_integration test_static_alloc_integration.cpp
$<TARGET_OBJECTS:someip_malloc_trap>)
target_link_libraries(test_static_alloc_integration PRIVATE
opensomeip gtest_main ${CMAKE_DL_LIBS})
add_test(NAME StaticAllocIntegrationTest COMMAND test_static_alloc_integration)
set_tests_properties(StaticAllocIntegrationTest PROPERTIES TIMEOUT 30)
endif()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/CMakeLists.txt` around lines 134 - 141, Add ${CMAKE_DL_LIBS} to
target_link_libraries for test_static_alloc_integration alongside opensomeip and
gtest_main, ensuring dlsym dependencies from someip_malloc_trap are explicitly
linked.

Comment thread TRACEABILITY_MATRIX.md
Comment on lines +312 to +314
- 73 requirements have no `@implements` annotation in code (some may be implemented but unannotated)
- 7 requirements have no `@tests` annotation
- Serialization module has the most unimplemented requirements (23 missing)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Regenerate the serialization gap and remediation data together.

The documents report 23 missing serialization requirements but recommend only the 18-ID range REQ_SER_090–REQ_SER_107.

  • TRACEABILITY_MATRIX.md#L312-L314: publish the complete missing-requirement set.
  • TRACEABILITY_SUMMARY.md#L111-L114: align the short-term action with all missing requirements.
  • TRACEABILITY_MATRIX.md#L329-L330: list every remaining serialization requirement.
  • TEST_TRACEABILITY_MATRIX.md#L267-L269: keep the recommended improvements consistent with the validated count.
📍 Affects 3 files
  • TRACEABILITY_MATRIX.md#L312-L314 (this comment)
  • TRACEABILITY_SUMMARY.md#L111-L114
  • TRACEABILITY_MATRIX.md#L329-L330
  • TEST_TRACEABILITY_MATRIX.md#L267-L269
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TRACEABILITY_MATRIX.md` around lines 312 - 314, Regenerate the serialization
gap and remediation data as one consistent update: in
TRACEABILITY_MATRIX.md:312-314 publish the complete validated
missing-requirement set and in TRACEABILITY_MATRIX.md:329-330 list every
remaining serialization requirement; update TRACEABILITY_SUMMARY.md:111-114 and
TEST_TRACEABILITY_MATRIX.md:267-269 so their recommended actions and counts
cover that same full set rather than only REQ_SER_090–REQ_SER_107.

Comment thread TRACEABILITY_MATRIX.md
Comment on lines +323 to +325
1. Add `@implements` / `@satisfies` annotations to 73 unannotated requirements
2. Add `@tests` annotations for the 7 remaining requirements without test coverage
3. Add `:satisfies:` field to REQ_PLATFORM_ARCH_001 (only spec-link gap)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Remove or update the stale spec-link recommendation.

This section reports 669/669 spec-linked requirements, while the recommendation still says REQ_PLATFORM_ARCH_001 lacks :satisfies:. Regenerate the metrics and recommendations together so the reported gap and action agree.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TRACEABILITY_MATRIX.md` around lines 323 - 325, Update the traceability
metrics and recommendations together so the stale REQ_PLATFORM_ARCH_001
spec-link recommendation is removed or reflects the current state. Ensure the
reported spec-linked requirement count and recommended actions are consistent
after regeneration.

Comment thread TRACEABILITY_SUMMARY.md
Comment on lines +60 to +63
| Platform (REQ_PLATFORM_*, REQ_PAL_*) | 73 |
| Transport (REQ_TRANSPORT_*) | 42 |
| PAL Abstractions (REQ_PAL_*) | 35 |
| Platform Backends (REQ_PLATFORM_*) | 17 |
| Compatibility (REQ_COMPAT_*) | 17 |
| Architecture (REQ_ARCH_*) | 7 |
| Architecture (REQ_ARCH_*) | 8 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the module-table metric label.

These rows sum to 673 and Line 66 says they represent full RST definitions, not the 594 fully traced requirements. Rename the section or publish actual per-module fully traced counts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TRACEABILITY_SUMMARY.md` around lines 60 - 63, Correct the module-table
metric in TRACEABILITY_SUMMARY.md: either rename the section to indicate it
reports full RST definitions, consistent with Line 66, or replace the row values
with actual per-module counts for the 594 fully traced requirements. Ensure the
displayed totals and label describe the same metric.

vtz and others added 5 commits July 24, 2026 13:04
- session_manager.cpp: add missing #include <optional>
- rpc_client.cpp: guard optional access with has_value() check
- thread_impl.h: replace ctx_ = {} with ctx_ = nullptr to fix
  ambiguous operator= with etl::inplace_function; add specialized
  store_callable overload for member-function-pointer + object
  pattern to keep lambda capture within inplace_function capacity

Co-authored-by: Cursor <cursoragent@cursor.com>
The general store_callable wrapped all callables in a
lambda+tuple, exceeding etl::inplace_function's 32-byte capture
limit on 32-bit ARM. Add a zero-arg overload that assigns the
callable directly to ctx_, avoiding the tuple wrapper for the
common Thread([this]() { ... }) pattern used in sd_client,
sd_server, and event_publisher.

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve all remaining Renode static-alloc CI failures and strengthen
the no-heap backend for both FreeRTOS and ThreadX bare-metal targets.

Renode / cross-compile fixes:
- Migrate tests/shared/*.inc from std::vector/std::string to
  platform::ByteBuffer / platform::String for ETL compatibility
- Add char8_t polyfill for GCC <11 in C++17 mode (ETL string_view.h)
- Add __sync_synchronize stub in BSP startup for bare-metal libstdc++
- Use placement-new for pool mutexes to avoid static-init-order fiasco
  with FreeRTOS xSemaphoreCreateMutex
- Remove ${PROJECT_SOURCE_DIR} from Renode test include paths to avoid
  <version> header collision on case-insensitive filesystems
- Propagate pool-sizing cache variables as target_compile_definitions
  instead of CMAKE_CXX_FLAGS (prevents wiping ARM toolchain flags)
- Make FreeRTOS configTOTAL_HEAP_SIZE overridable via
  SOMEIP_FREERTOS_HEAP_SIZE
- Add SOMEIP_MAX_TP_REASSEMBLY_SIZE to control per-byte reception
  bitvector capacity in TpReassemblyBuffer (ETL vector<bool> is not
  bit-packed — 16384 bytes per entry caused 66 KB stack overflow)
- Tune Renode preset pool sizes, TP segments, map capacity, heap,
  and task stack sizes for STM32F407 256 KB SRAM budget

Host / API hardening:
- MallocTrapGuard RAII in test_static_alloc_integration.cpp
- Assert-on-uninitialized in allocate_message / acquire_buffer (debug)
- Document init_static_allocator() precondition in memory_impl.h
- TpSegmentVector type alias sized by SOMEIP_MAX_TP_SEGMENTS
- SD add_entry / add_option return bool; deserialize checks capacity
- ThreadX thread_impl.h: ctx_=nullptr instead of ctx_={}, add
  store_callable overloads to stay within inplace_function capture size
- Keep malloc_trap host-only (NOT CMAKE_CROSSCOMPILING)

Validated: host static ctest (21/21), host dynamic ctest (15/15),
FreeRTOS Renode static (133/133), ThreadX Renode static (121/121).

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add --build-dir argument to run_freertos_renode_test.sh and
  run_threadx_renode_test.sh so CI can pass the static-alloc build
  directory (the workflow already passes --build-dir but the scripts
  lacked the case branch, causing "Unknown argument" failures).
- Wrap the char8_t polyfill in containers_impl.h with
  #pragma GCC diagnostic ignored "-Wc++20-compat" so host builds
  with -Werror don't reject the typedef.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Rename kMaxTpReassemblySize/kMaxTpSegments to UPPER_CASE per
  project readability-identifier-naming convention
- Add NOLINT for macro-usage on the fallback #define (must be a
  preprocessor macro since it comes from -D flags)
- Remove unused containers.h include from tp_segmenter.cpp

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/rpc/rpc_client.cpp (2)

122-159: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Prevent the synchronous-response callback from outliving SyncState.

A response can erase and copy the callback just before Line 145, causing cancel_call() to return false; call_method_sync() then returns and destroys state before on_message_received() invokes the copied callback. The callback dereferences the destroyed stack object at Lines 130-133.

Use client-owned/bounded response state or add a completion handoff that guarantees no callback can begin after this frame is destroyed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_client.cpp` around lines 122 - 159, Update call_method_sync and
its SyncState/callback lifecycle so the asynchronous response callback cannot
access destroyed stack state after timeout or cancellation. Use client-owned
bounded response state or an explicit completion handoff that waits for any
copied callback to finish before returning, while preserving normal response and
timeout results.

173-195: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Abort when bounded session allocation fails.

SessionManager::create_session() returns 0 when its session container is full, but this path still records and sends an RPC using session ID 0. Concurrent calls can then share an untracked session ID and be matched incorrectly.

Proposed fix
         const uint16_t session_id = session_manager_.create_session(client_id_);
+        if (session_id == 0) {
+            return 0;
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/rpc_client.cpp` around lines 173 - 195, Check the result of
SessionManager::create_session() in the RPC call flow before constructing the
request or PendingCall; if it returns 0, abort immediately with the existing
failure result (0) and do not record or send the call. Preserve normal
processing for valid session IDs.
src/sd/sd_message.cpp (1)

609-647: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Bound each option to the declared options array.

Known-option parsing validates against data.size(), not options_end. A malformed length can therefore make a known option consume trailing bytes outside the declared options array and still deserialize successfully. Validate the complete wire size (3 + option_len) against options_end before dispatching.

Proposed fix
     while (offset < options_end) {
-        if (offset + 4 > data.size()) {
+        if (offset + 4 > options_end) {
             return false;
         }
+
+        const uint16_t option_len = static_cast<uint16_t>(
+            (static_cast<uint32_t>(data[offset]) << 8U) |
+            static_cast<uint32_t>(data[offset + 1]));
+        if (offset + 3U + option_len > options_end) {
+            return false;
+        }
 
         // Options start with length(2) + type(1) + reserved(1).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sd/sd_message.cpp` around lines 609 - 647, In the option-parsing loop,
validate each option’s complete wire size (3-byte header plus the declared
option length) does not exceed options_end before dispatching to any known
option deserializer. Use the length field at the current offset and preserve the
existing data.size() bounds check, rejecting malformed options that extend
beyond the declared options array.
src/platform/static/buffer_pool.cpp (1)

104-124: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Pool mutex is dereferenced before/without checking initialization in four places across two files. pool_mutex_ptr stays nullptr until init_buffer_pool()/init_static_allocator() runs, and each of these functions constructs a ScopedLock from *pool_mutex_ptr either before the precondition assert or with no guard at all — turning a documented, diagnosable precondition violation into an undiagnosed null-pointer crash.

  • src/platform/static/buffer_pool.cpp#L104-L124: move assert(pool_initialized && ...) above ScopedLock lk(*pool_mutex_ptr); in acquire_buffer().
  • src/platform/static/buffer_pool.cpp#L126-L142: add the same pool_initialized assert before ScopedLock lk(*pool_mutex_ptr); in release_buffer(), or document why it's safe to omit.
  • src/platform/static/memory.cpp#L67-L81: move assert(pool_initialized && ...) above ScopedLock lk(*pool_mutex_ptr); in allocate_message().
  • src/platform/static/memory.cpp#L83-L106: add the same guard before ScopedLock lk(*pool_mutex_ptr); in release_message(), or document why it's safe to omit.
🛡️ Proposed fix for acquire_buffer / allocate_message
 BufferSlot* acquire_buffer(size_t requested_size) {
     if (requested_size == 0) {
         requested_size = 1;
     }
-    ScopedLock lk(*pool_mutex_ptr);
     assert(pool_initialized &&
            "init_static_allocator() must be called before acquire_buffer()");
+    ScopedLock lk(*pool_mutex_ptr);
 MessagePtr allocate_message() {
-    ScopedLock lk(*pool_mutex_ptr);
     assert(pool_initialized &&
            "init_static_allocator() must be called before allocate_message()");
+    ScopedLock lk(*pool_mutex_ptr);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platform/static/buffer_pool.cpp` around lines 104 - 124, Move the
pool_initialized assertion before lock construction in acquire_buffer and
allocate_message, and add the same precondition guard before locking in
release_buffer and release_message. Apply these changes at
src/platform/static/buffer_pool.cpp lines 104-124 and 126-142, and
src/platform/static/memory.cpp lines 67-81 and 83-106; all four functions must
validate initialization before dereferencing pool_mutex_ptr.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@include/platform/static/static_config.h`:
- Around line 105-115: Add compile-time range validation for
SOMEIP_MAX_TP_SEGMENTS and SOMEIP_MAX_TP_REASSEMBLY_SIZE, matching the valid
capacities required by the static backend’s platform::Vector usage. Since
SOMEIP_MAX_SD_OPTIONS has no static vector consumer, remove its
compiler-override documentation or add an appropriate range check only if it is
used for a capacity.

In `@tests/freertos/test_freertos_core.cpp`:
- Around line 184-208: Update the test around the
allocate_message/serialize/deserialize round trip to instrument FreeRTOS
allocation calls using a test-local counter or trap. Assert that no
pvPortMalloc/free allocation calls occur during the message operations, while
retaining the existing heap-growth assertion as a secondary check; integrate the
instrumentation with the test setup and cleanup so it does not affect other
tests.

In `@tests/test_sd.cpp`:
- Around line 1928-1945: Update SdTest.AddEntryReturnsBool to populate the
message’s entry and option containers to their configured capacities, asserting
each insertion succeeds, then attempt one additional insertion for each
container and assert add_entry and add_option return false. Preserve the
existing test setup and use the container capacity definitions or established
test symbols rather than hardcoding an unrelated limit.

In `@tests/threadx/test_threadx_core.cpp`:
- Around line 174-184: Extend the static roundtrip assertions after
decoded.get_payload() in tests/threadx/test_threadx_core.cpp lines 174-184 and
tests/freertos/test_freertos_core.cpp lines 193-202 to compare every decoded
payload byte with payload, not just the size; preserve the existing size checks
and ensure both tests validate identical contents.

---

Outside diff comments:
In `@src/platform/static/buffer_pool.cpp`:
- Around line 104-124: Move the pool_initialized assertion before lock
construction in acquire_buffer and allocate_message, and add the same
precondition guard before locking in release_buffer and release_message. Apply
these changes at src/platform/static/buffer_pool.cpp lines 104-124 and 126-142,
and src/platform/static/memory.cpp lines 67-81 and 83-106; all four functions
must validate initialization before dereferencing pool_mutex_ptr.

In `@src/rpc/rpc_client.cpp`:
- Around line 122-159: Update call_method_sync and its SyncState/callback
lifecycle so the asynchronous response callback cannot access destroyed stack
state after timeout or cancellation. Use client-owned bounded response state or
an explicit completion handoff that waits for any copied callback to finish
before returning, while preserving normal response and timeout results.
- Around line 173-195: Check the result of SessionManager::create_session() in
the RPC call flow before constructing the request or PendingCall; if it returns
0, abort immediately with the existing failure result (0) and do not record or
send the call. Preserve normal processing for valid session IDs.

In `@src/sd/sd_message.cpp`:
- Around line 609-647: In the option-parsing loop, validate each option’s
complete wire size (3-byte header plus the declared option length) does not
exceed options_end before dispatching to any known option deserializer. Use the
length field at the current offset and preserve the existing data.size() bounds
check, rejecting malformed options that extend beyond the declared options
array.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e7fae9e7-a561-4f96-99ba-6424dc124d48

📥 Commits

Reviewing files that changed from the base of the PR and between 0832ed5 and dd02739.

📒 Files selected for processing (31)
  • CMakeLists.txt
  • CMakePresets.json
  • bsp/stm32f407_renode/FreeRTOSConfig.h
  • bsp/stm32f407_renode/startup.c
  • include/platform/freertos/thread_impl.h
  • include/platform/static/containers_impl.h
  • include/platform/static/memory_impl.h
  • include/platform/static/static_config.h
  • include/platform/threadx/thread_impl.h
  • include/sd/sd_message.h
  • include/tp/tp_segmenter.h
  • include/tp/tp_types.h
  • src/CMakeLists.txt
  • src/core/session_manager.cpp
  • src/platform/static/buffer_pool.cpp
  • src/platform/static/memory.cpp
  • src/rpc/rpc_client.cpp
  • src/sd/sd_message.cpp
  • src/tp/tp_manager.cpp
  • src/tp/tp_segmenter.cpp
  • tests/freertos/CMakeLists.txt
  • tests/freertos/test_freertos_core.cpp
  • tests/shared/test_e2e_common.inc
  • tests/shared/test_message_common.inc
  • tests/shared/test_serializer_common.inc
  • tests/shared/test_tp_common.inc
  • tests/test_sd.cpp
  • tests/test_static_alloc_integration.cpp
  • tests/test_tp.cpp
  • tests/threadx/CMakeLists.txt
  • tests/threadx/test_threadx_core.cpp
💤 Files with no reviewable changes (2)
  • tests/freertos/CMakeLists.txt
  • tests/threadx/CMakeLists.txt

Comment on lines +105 to +115
#ifndef SOMEIP_MAX_TP_SEGMENTS
#define SOMEIP_MAX_TP_SEGMENTS 64
#endif

#ifndef SOMEIP_MAX_TP_REASSEMBLY_SIZE
#define SOMEIP_MAX_TP_REASSEMBLY_SIZE 2048
#endif

#ifndef SOMEIP_MAX_SD_OPTIONS
#define SOMEIP_MAX_SD_OPTIONS 32
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how the new TP/SD capacity macros are used downstream (array sizes, index types).
rg -nP 'SOMEIP_MAX_TP_SEGMENTS|SOMEIP_MAX_TP_REASSEMBLY_SIZE|SOMEIP_MAX_SD_OPTIONS' include/tp include/sd -A3 -B3

Repository: vtz/opensomeip

Length of output: 1385


🏁 Script executed:

#!/bin/bash
# Inspect the definitions/usages around TP/SD capacity symbols in the relevant headers.
set -eo pipefail

printf '--- files ---\n'
git ls-files include/tp include/sd include/platform/static/static_config.h | sed 'N;s/\n/\t/'

printf '\n--- static_config.h relevant lines ---\n'
sed -n '1,180p' include/platform/static/static_config.h

printf '\n--- tp_types.h relevant lines ---\n'
sed -n '1,220p' include/tp/tp_types.h

printf '\n--- sd types candidate usages ---\n'
rg -n 'SOMEIP_MAX_SD_ENTRIES|SOMEIP_MAX_SD_OPTIONS|SD_OPTIONS|sd_options|sd_options' include/sd include platform -A4 -B4 || true

Repository: vtz/opensomeip

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Inspect the definitions/usages around TP/SD capacity symbols in the relevant headers.
set -eo pipefail

printf '%s\n' '--- static_config.h relevant lines ---'
sed -n '1,180p' include/platform/static/static_config.h

printf '%s\n' '--- tp_types.h relevant lines ---'
sed -n '1,220p' include/tp/tp_types.h

printf '%s\n' '--- sd types candidate usages ---'
rg -n 'SOMEIP_MAX_SD_ENTRIES|SOMEIP_MAX_SD_OPTIONS|SD_OPTIONS|sd_options|sd_options|max_options|max_options' include/sd include platform -A4 -B4 || true

Repository: vtz/opensomeip

Length of output: 12416


🏁 Script executed:

#!/bin/bash
# Search the full repository for platform::Vector usage and SD options usage to determine allocation/index widths.
set -eo pipefail

printf '%s\n' '--- files ---'
git ls-files | rg '(^include/platform/|^include/tp/|^include/sd/)' || true

printf '%s\n' '--- platform::Vector definitions/usages ---'
rg -n 'template<.*class.*capacity|class Vector|struct Vector|platform::Vector|Vector<.*Platform|capacity_|capacity' include platform -A5 -B5 || true

printf '%s\n' '--- SOMEIP_MAX_SD_OPTIONS direct usages ---'
rg -n 'SOMEIP_MAX_SD_OPTIONS' . -A4 -B4 || true

printf '%s\n' '--- SD options related names ---'
rg -n 'SD_OPTION|sd_option|sd_options|Option.*Capacity|option.*Capacity|max.*option' include include/sd include/tp platform 2>/dev/null -A3 -B3 || true

Repository: vtz/opensomeip

Length of output: 34750


🏁 Script executed:

#!/bin/bash
# Inspect platform Vector definitions and static containers implementation to determine allocation/index widths.
set -eo pipefail

printf '%s\n' '--- include/platform/containers.h ---'
sed -n '1,260p' include/platform/containers.h

printf '%s\n' '--- include/platform/static/containers_impl.h ---'
sed -n '1,220p' include/platform/static/containers_impl.h

printf '%s\n' '--- include/platform/dynamic/containers_impl.h ---'
sed -n '1,180p' include/platform/dynamic/containers_impl.h

printf '%s\n' '--- include/sd/sd_message relevant definitions ---'
sed -n '1,280p' include/sd/sd_message.h

printf '%s\n' '--- include/platform/static/static_config.h static_assert region ---'
sed -n '120,155p' include/platform/static/static_config.h

Repository: vtz/opensomeip

Length of output: 15929


🌐 Web query:

ETL vector capacity template parameter type and max_size implementation

💡 Result:

In the Embedded Template Library (ETL), the etl::vector class template is designed for fixed-capacity, stack-allocated storage, avoiding heap usage by defining capacity at compile time [1][2]. Template Parameters The primary etl::vector template uses two parameters [3]: - T: The type of elements stored in the vector [3]. - MAX_SIZE_: A size_t value representing the maximum number of elements that can be stored [3]. The class enforces this capacity using a static assertion: ETL_STATIC_ASSERT((MAX_SIZE_ > 0U), "Zero capacity etl::vector is not valid"); [3]. The value is stored as a public member constant: static const size_t MAX_SIZE = MAX_SIZE_; [3]. max_size and capacity Implementation The etl::vector class inherits from etl::ivector, which in turn relies on etl::vector_base to manage size-related information [3][4][2]. - max_size: This method returns the maximum number of elements the vector can hold, which is the compile-time value MAX_SIZE [1][4][5]. - capacity: In the context of etl::vector, this method also returns the maximum possible size of the container (the fixed capacity) [1][4][6]. Because the storage is fixed at compile time, capacity() and max_size() return the same value [1][4]. These implementations are designed to be efficient, avoiding heap allocation and virtual function overhead [2]. An additional available() method is available to return the number of elements that can be added before the vector reaches its capacity [1][4][5].

Citations:


Add range checks around the new TP/SD capacity macros.

SOMEIP_MAX_TP_SEGMENTS and SOMEIP_MAX_TP_REASSEMBLY_SIZE control platform::Vector capacities in the static backend, while SOMEIP_MAX_SD_OPTIONS is exposed as a compiler-overridable default without any use target. Add compile-time checks for these values, or remove the override documentation for SOMEIP_MAX_SD_OPTIONS if no static vector depends on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/platform/static/static_config.h` around lines 105 - 115, Add
compile-time range validation for SOMEIP_MAX_TP_SEGMENTS and
SOMEIP_MAX_TP_REASSEMBLY_SIZE, matching the valid capacities required by the
static backend’s platform::Vector usage. Since SOMEIP_MAX_SD_OPTIONS has no
static vector consumer, remove its compiler-override documentation or add an
appropriate range check only if it is used for a capacity.

Comment on lines +184 to +208
size_t heap_before = xPortGetFreeHeapSize();

{
auto msg = someip::platform::allocate_message();
CHECK(msg != nullptr, "static_pool_alloc");
msg->set_service_id(0x1234);
msg->set_method_id(0x5678);
msg->set_client_id(0x0001);
msg->set_session_id(0x0001);
const uint8_t payload[] = {0xCA, 0xFE, 0xBA, 0xBE};
msg->set_payload(payload, sizeof(payload));

auto wire = msg->serialize();
CHECK(!wire.empty(), "static_serialize");

someip::Message decoded;
bool ok = decoded.deserialize(wire.data(), wire.size());
CHECK(ok, "static_deserialize");
CHECK(decoded.get_payload().size() == sizeof(payload), "static_roundtrip_size");
}

size_t heap_after = xPortGetFreeHeapSize();
auto delta = static_cast<ssize_t>(heap_after) - static_cast<ssize_t>(heap_before);
printf(" Heap delta across message ops: %zd bytes\n", delta);
CHECK(delta == 0, "zero_heap_growth_under_static_alloc");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Detect transient FreeRTOS heap allocations.

A pvPortMalloc/free during serialization restores xPortGetFreeHeapSize() before line 205, so this test can pass despite heap use. Instrument FreeRTOS allocation calls with a test-local counter/trap and assert no calls occur.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/freertos/test_freertos_core.cpp` around lines 184 - 208, Update the
test around the allocate_message/serialize/deserialize round trip to instrument
FreeRTOS allocation calls using a test-local counter or trap. Assert that no
pvPortMalloc/free allocation calls occur during the message operations, while
retaining the existing heap-growth assertion as a secondary check; integrate the
instrumentation with the test setup and cleanup so it does not affect other
tests.

Comment thread tests/test_sd.cpp
Comment on lines +174 to +184
const uint8_t payload[] = {0xCA, 0xFE, 0xBA, 0xBE};
msg->set_payload(payload, sizeof(payload));

auto wire = msg->serialize();
CHECK(!wire.empty(), "static_serialize");

someip::Message decoded;
bool ok = decoded.deserialize(wire.data(), wire.size());
CHECK(ok, "static_deserialize");
CHECK(decoded.get_payload().size() == sizeof(payload), "static_roundtrip_size");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify static roundtrip payload bytes, not only length.

Both tests pass if serialization corrupts payload contents without changing its size.

  • tests/threadx/test_threadx_core.cpp#L174-L184: compare decoded payload bytes with payload.
  • tests/freertos/test_freertos_core.cpp#L193-L202: compare decoded payload bytes with payload.
📍 Affects 2 files
  • tests/threadx/test_threadx_core.cpp#L174-L184 (this comment)
  • tests/freertos/test_freertos_core.cpp#L193-L202
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/threadx/test_threadx_core.cpp` around lines 174 - 184, Extend the
static roundtrip assertions after decoded.get_payload() in
tests/threadx/test_threadx_core.cpp lines 174-184 and
tests/freertos/test_freertos_core.cpp lines 193-202 to compare every decoded
payload byte with payload, not just the size; preserve the existing size checks
and ensure both tests validate identical contents.

Guard both functions with an early return if already initialized,
preventing double placement-new of the pool mutex when called from
multiple test environments or re-entered accidentally.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate reducing heap/STL usage in favor of ETL-compatible patterns

1 participant