Skip to content

Muse skeleton get remaining part#48

Open
saksham-chawla wants to merge 6 commits intocodex/replay-20260319023913-pr-207-basefrom
codex/replay-20260319023913-pr-207-head
Open

Muse skeleton get remaining part#48
saksham-chawla wants to merge 6 commits intocodex/replay-20260319023913-pr-207-basefrom
codex/replay-20260319023913-pr-207-head

Conversation

@saksham-chawla
Copy link
Collaborator

@saksham-chawla saksham-chawla commented Mar 18, 2026

  • introduces EnableGet flag on SkeletonField Side.
  • implements Get function functionality.

Please note that this PR is rebased on
eclipse-score#204
https://cc-github.bmwgroup.net/oss/eclipse-score-communication/pull/175/
These PRs needs to be merged before this PR.


Muse skeleton: Implement Get functionality for SkeletonField

This pull request introduces the EnableGet template flag to the SkeletonField class, enabling the implementation of 'Get' function functionality. It updates the SkeletonField constructors to support various combinations of EnableSet and EnableGet flags using SFINAE and tag dispatching. A RegisterGetHandler method is added to SkeletonField to automatically register a handler that retrieves the latest field sample. Correspondingly, a GetLatestSample method is added to SkeletonEventBinding and SkeletonEvent, and the EventDataControlCompositeImpl is extended with FindLatestReadableSlotIndex and TryIncreaseReferenceCount to support the GetLatestSlot functionality. A std::mutex is conditionally included in SkeletonField to serialize concurrent 'Get' requests.

Architecture Diagram:

flowchart TD
    subgraph API_Layer
        A["SkeletonField (+752 lines)"]
        B["SkeletonEvent (+8 lines)"]
    end

    subgraph Service_Layer
        C["SkeletonEventBinding (+2 lines)"]
        D["SkeletonMethod (+1 line)"]
    end

    subgraph Binding_Layer
        E["EventDataControlCompositeImpl (+52 lines)"]
        F["EventDataControl"]
    end

    subgraph Core_Components
        G["SkeletonBase"]
        H["SkeletonTrait"]
    end

    A -- uses --> B: "dispatches to"
    A -- creates --> D: "for Get/Set"
    B -- implements --> C
    C -- uses --> E: "for latest sample"
    E -- manages --> F: "controls slots"
    G -- contains --> A: "registers fields"
    H -- defines --> A: "template alias"
    A -- locks --> A_mutex["std::mutex (GetHandlerMutexType)"]
Loading

This summary was automatically generated by @propel-code-bot

muhseth and others added 6 commits March 17, 2026 13:04
- Adds GetLatestSlot function to EventDataControlComposite

Issue: SWP-249517
- Adds GetLatestSampleFunction to SkeletonEvent
Issue: SWP-249517
Implement RegisterSetHandler() method to allow users to register
callbacks for handling remote Set requests on skeleton fields.
The Set handler wraps user-provided validation/modification logic
and automatically broadcasts accepted values to all field
subscribers via Update().

Configuration support is stubbed with TODO - requires extension of
LolaFieldInstanceDeployment to include has_setter flag.

Issue: SWP-249527
Replace runtime is_setter_ guard with EnableSet/EnableNotifier
template bool parameters on SkeletonField.

Issue: SWP-249527
- SkeletonField now includes a new template parameter, EnableGet.
- When EnableGet is true, a skeleton Get method attribute is added to SkeletonField.
- Constructors were updated to support all relevant EnableSet and EnableGet combinations.
Issue: SWP-249517
- Automatically register a Get handler for fields with EnableGet.
- Serve Get requests by reading the latest field value and returning it through the Get method.
- Update the required slot calculation.
- Add a test covering this behavior.
Issue: SWP-249517
@saksham-chawla saksham-chawla marked this pull request as ready for review March 18, 2026 20:00
Copy link

@propel-code-bot propel-code-bot bot left a comment

Choose a reason for hiding this comment

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

Review found a critical move-related handler bug plus several logic/reliability issues needing fixes.

Status: Action Required | Risk: High | Readiness: 3/10 (Needs Significant Work)

Issues Identified & Suggestions
  • Move/handler capture causes UAF; re-register after move: score/mw/com/impl/skeleton_field.h
  • Testing ctor SFINAE mismatch blocks EnableSet/Get tests: score/mw/com/impl/skeleton_field.h
  • RegisterGetHandler failure only logged; propagate or terminate: score/mw/com/impl/skeleton_field.h
  • Ignore QM disconnect in GetLatestSlot to return ASIL-B: score/mw/com/impl/bindings/lola/event_data_control_composite.cpp
Review Details

📁 15 files reviewed | 💬 4 comments

👍 / 👎 individual comments to help improve reviews for you

Comment on lines +424 to +434
template <typename SampleDataType, bool EnableSet, bool EnableNotifier, bool EnableGet>
SkeletonField<SampleDataType, EnableSet, EnableNotifier, EnableGet>::SkeletonField(SkeletonField&& other) noexcept
: SkeletonFieldBase(static_cast<SkeletonFieldBase&&>(other)),
// known llvm bug (https://github.com/llvm/llvm-project/issues/63202)
// This usage is safe because the previous line only moves the base class portion via static_cast.
// The derived class member 'initial_field_value_' remains untouched by the base class move constructor,
// so it's still valid to access it here for moving into our own member.
// coverity[autosar_cpp14_a12_8_3_violation] This is a false-positive.
initial_field_value_{std::move(other.initial_field_value_)},
skeleton_field_mock_{other.skeleton_field_mock_}
skeleton_field_mock_{other.skeleton_field_mock_},
set_method_{std::move(other.set_method_)},

Choose a reason for hiding this comment

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

Critical

[Reliability] After move-constructing or move-assigning a SkeletonField with EnableGet=true, the lambda registered via RegisterGetHandler() still captures the this pointer of the moved-from object. When a Get request is subsequently processed on the moved-to object, the handler (now owned by the moved-to get_method_) dereferences the stale this pointer, accessing get_handler_mutex_ and calling GetTypedEvent() on the moved-from (potentially destroyed) object. This is a use-after-free / dangling pointer bug.

The same issue applies to RegisterSetHandler — the wrapped_callback lambda captures this, so after move, this->Update(result.value()) targets the wrong object.

Trigger: Move-construct or move-assign a SkeletonField<T, ES, EN, true> (or one where RegisterSetHandler was called), then invoke the get/set method on the new object.

Impact: Undefined behavior — reads/writes to freed memory, potential crash or data corruption.

The move constructor and move assignment operator need to re-register the get handler (and potentially the set handler) so the lambda captures the correct this pointer of the new object.

Context for Agents
[**Bug**] After move-constructing or move-assigning a `SkeletonField` with `EnableGet=true`, the lambda registered via `RegisterGetHandler()` still captures the `this` pointer of the **moved-from** object. When a Get request is subsequently processed on the moved-to object, the handler (now owned by the moved-to `get_method_`) dereferences the stale `this` pointer, accessing `get_handler_mutex_` and calling `GetTypedEvent()` on the moved-from (potentially destroyed) object. This is a use-after-free / dangling pointer bug.

The same issue applies to `RegisterSetHandler` — the `wrapped_callback` lambda captures `this`, so after move, `this->Update(result.value())` targets the wrong object.

**Trigger:** Move-construct or move-assign a `SkeletonField<T, ES, EN, true>` (or one where `RegisterSetHandler` was called), then invoke the get/set method on the new object.

**Impact:** Undefined behavior — reads/writes to freed memory, potential crash or data corruption.

The move constructor and move assignment operator need to re-register the get handler (and potentially the set handler) so the lambda captures the correct `this` pointer of the new object.

File: score/mw/com/impl/skeleton_field.h
Line: 434
Chain of Thought
  • Validation Decision: accepted
  • Confidence: 0.95
  • Likelihood Score: 4/5
  • Likelihood Reason: If anyone move-constructs or move-assigns a SkeletonField with EnableGet=true (or after calling RegisterSetHandler), and then processes a get/set request on the moved-to object, the handler will dereference a stale this pointer. The move constructor is explicitly defined and used (UpdateField is called), making moves a supported operation. The bug will manifest whenever move semantics are actually exercised on an instance with registered handlers.
  • Suggested Action: keep
  • Tool Calls Used: 5

Reasoning

The comment correctly identifies a use-after-free / dangling-pointer bug introduced by this PR. Here's the evidence:

  1. RegisterGetHandler() (line 201) creates a lambda [this]() -&gt; Result&lt;FieldType&gt; { std::lock_guard&lt;std::mutex&gt; lock{get_handler_mutex_}; return SkeletonEventView&lt;FieldType&gt;{*GetTypedEvent()}.GetLatestSample(); } which captures this (the SkeletonField pointer) at registration time. This handler is stored inside get_method_'s binding.

  2. Move constructor (lines 424-440) moves get_method_ via std::move(other.get_method_). Since get_method_ is a unique_ptr&lt;SkeletonMethod&lt;...&gt;&gt;, this just transfers pointer ownership — the underlying SkeletonMethod object on the heap is unchanged, and its stored handler lambda still captures the old this (pointing to the moved-from SkeletonField). There is NO call to RegisterGetHandler() after the move.

  3. Move assignment (lines 442-460) has the same issue — get_method_ is move-assigned but the handler isn't re-registered.

  4. RegisterSetHandler (line 158) similarly captures this in a lambda (this-&gt;Update(result.value())), which will also dangle after move.

  5. SkeletonMethod::RegisterHandler (skeleton_method.h line 126-171) stores the callback permanently inside the binding. There's no mechanism to rebind the captured this pointer.

This is entirely new code introduced by this PR. All of get_method_, set_method_, RegisterGetHandler(), and RegisterSetHandler() are newly added. The fix would be to re-register handlers (with the new this) in the move constructor and move assignment operator.

The comment is accurate, specific, and actionable — it identifies the exact bug, the trigger condition, and the needed fix.

Evidence Summary

1. File Lookup

  • Filename: skeleton_method.h
Found 3 file(s) matching 'skeleton_method.h':
- score/mw/com/impl/bindings/lola/skeleton_method.h
- score/mw/com/impl/bindings/mock_binding/skeleton_method.h
- score/mw/com/impl/methods/skeleton_method.h

2. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 424-460
424: template <typename SampleDataType, bool EnableSet, bool EnableNotifier, bool EnableGet>
425: SkeletonField<SampleDataType, EnableSet, EnableNotifier, EnableGet>::SkeletonField(SkeletonField&& other) noexcept
426:     : SkeletonFieldBase(static_cast<SkeletonFieldBase&&>(other)),
427:       // known llvm bug (https://github.com/llvm/llvm-project/issues/63202)
428:       // This usage is safe because the previous line only moves the base class portion via static_cast.
429:       // The derived class member 'initial_field_value_' remains untouched by the base class move constructor,
430:       // so it's still valid to access it here for moving into our own member.
431:       // coverity[autosar_cpp14_a12_8_3_violation] This is a false-positive.
432:       initial_field_value_{std::move(other.initial_field_value_)},
433:       skeleton_field_mock_{other.skeleton_field_mock_},
...

3. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 195-215
195:     // Get handler registration
196:     // SFINAE-gated: only exists when EnableGet == true
197:     template <bool EG = EnableGet, typename std::enable_if<EG, int>::type = 0>
198:     void RegisterGetHandler()
199:     {
200:         // get_method_ is guaranteed to be unique_ptr here since RegisterGetHandler is SFINAE-gated on EnableGet=true.
201:         const auto result = get_method_.get()->RegisterHandler([this]() -> Result<FieldType> {
202:             // need to serialize access to Get. In case of concurrent Get calls,
203:             // we want to ensure that they are processed sequentially.
204:             std::lock_guard<std::mutex> lock{get_handler_mutex_};
...

4. File Snapshot (HEAD)

  • File: score/mw/com/impl/methods/skeleton_method.h
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

5. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 152-180
152:     // Set handler registration
153:     // SFINAE-gated: only exists when EnableSet == true
154:     template <typename Callable, bool ES = EnableSet, typename std::enable_if<ES, int>::type = 0>
155:     ResultBlank RegisterSetHandler(Callable&& callback)
156:     {
157:         // Wrap user callback to also call Update() after user validation
158:         auto wrapped_callback = [this, user_callback = std::move(callback)](FieldType new_value) -> Result<FieldType> {
159:             // 1. Call user's validation/modification handler
160:             auto result = user_callback(new_value);
161: 
...

Fix in Cursor

Comment on lines +357 to +367
EventDataControl* control = (asil_b_control_ != nullptr) ? asil_b_control_ : asil_qm_control_;

for (std::size_t retry_counter{0U}; retry_counter < MAX_MULTI_ALLOCATE_RETRY_COUNT; ++retry_counter)
{
const auto latest_slot_index = FindLatestReadableSlotIndex(*control);

if (!latest_slot_index.has_value())
{
return {};
}

Choose a reason for hiding this comment

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

Important

[Logic] When ignore_qm_control_ is set (QM consumers misbehaved and we stopped updating the QM control), GetLatestSlot() still tries to grab a QM slot and only returns a slot if the QM reference count can be incremented. After the disconnect, QM slots stay invalid forever, so TryIncreaseReferenceCount(slot_qm) always fails and the method returns {} even though the ASIL‑B control still contains the latest sample. This means SkeletonEvent::GetLatestSample() will never succeed once QM has been disconnected.

We need to skip the QM branch when ignore_qm_control_ is true and just operate on the ASIL‑B slot, similar to the existing QM‑only code path:

@@
-        if (asil_b_control_ != nullptr)
-        {
-            auto& slot_qm = asil_qm_control_->state_slots_[slot_index];
-            auto& slot_asil_b = asil_b_control_->state_slots_[slot_index];
-
-            if (!TryIncreaseReferenceCount(slot_qm))
-            {
-                continue;
-            }
-
-            if (!TryIncreaseReferenceCount(slot_asil_b))
-            {
-                score::cpp::ignore = slot_qm.fetch_sub(1U, std::memory_order_acq_rel);
-                continue;
-            }
-
-            return {slot_index, slot_qm, slot_asil_b};
-        }
+        if (asil_b_control_ != nullptr)
+        {
+            auto& slot_asil_b = asil_b_control_->state_slots_[slot_index];
+
+            if (!ignore_qm_control_)
+            {
+                auto& slot_qm = asil_qm_control_->state_slots_[slot_index];
+                if (!TryIncreaseReferenceCount(slot_qm))
+                {
+                    continue;
+                }
+
+                if (!TryIncreaseReferenceCount(slot_asil_b))
+                {
+                    score::cpp::ignore = slot_qm.fetch_sub(1U, std::memory_order_acq_rel);
+                    continue;
+                }
+
+                return {slot_index, slot_qm, slot_asil_b};
+            }
+
+            if (!TryIncreaseReferenceCount(slot_asil_b))
+            {
+                continue;
+            }
+
+            return {slot_index, slot_asil_b, ControlSlotCompositeIndicator::CompositeSlotTagType::ASIL_B};
+        }

This keeps ASIL‑B GETs working after a QM disconnect.

Context for Agents
When `ignore_qm_control_` is set (QM consumers misbehaved and we stopped updating the QM control), `GetLatestSlot()` still tries to grab a QM slot and only returns a slot if the QM reference count can be incremented. After the disconnect, QM slots stay invalid forever, so `TryIncreaseReferenceCount(slot_qm)` always fails and the method returns `{}` even though the ASIL‑B control still contains the latest sample. This means `SkeletonEvent::GetLatestSample()` will never succeed once QM has been disconnected.

We need to skip the QM branch when `ignore_qm_control_` is true and just operate on the ASIL‑B slot, similar to the existing QM‑only code path:

```diff
@@
-        if (asil_b_control_ != nullptr)
-        {
-            auto& slot_qm = asil_qm_control_->state_slots_[slot_index];
-            auto& slot_asil_b = asil_b_control_->state_slots_[slot_index];
-
-            if (!TryIncreaseReferenceCount(slot_qm))
-            {
-                continue;
-            }
-
-            if (!TryIncreaseReferenceCount(slot_asil_b))
-            {
-                score::cpp::ignore = slot_qm.fetch_sub(1U, std::memory_order_acq_rel);
-                continue;
-            }
-
-            return {slot_index, slot_qm, slot_asil_b};
-        }
+        if (asil_b_control_ != nullptr)
+        {
+            auto& slot_asil_b = asil_b_control_->state_slots_[slot_index];
+
+            if (!ignore_qm_control_)
+            {
+                auto& slot_qm = asil_qm_control_->state_slots_[slot_index];
+                if (!TryIncreaseReferenceCount(slot_qm))
+                {
+                    continue;
+                }
+
+                if (!TryIncreaseReferenceCount(slot_asil_b))
+                {
+                    score::cpp::ignore = slot_qm.fetch_sub(1U, std::memory_order_acq_rel);
+                    continue;
+                }
+
+                return {slot_index, slot_qm, slot_asil_b};
+            }
+
+            if (!TryIncreaseReferenceCount(slot_asil_b))
+            {
+                continue;
+            }
+
+            return {slot_index, slot_asil_b, ControlSlotCompositeIndicator::CompositeSlotTagType::ASIL_B};
+        }
```

This keeps ASIL‑B GETs working after a QM disconnect.

File: score/mw/com/impl/bindings/lola/event_data_control_composite.cpp
Line: 367
Chain of Thought
  • Validation Decision: accepted
  • Confidence: 0.00
  • Likelihood Score: 0/5
  • Suggested Action: keep
  • Tool Calls Used: 9

Reasoning

Failed to parse validation response

Evidence Summary

1. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/event_data_control_composite.h
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

2. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/event_data_control_composite.cpp
  • Lines: 354-399
354: template <template <class> class AtomicIndirectorType>
355: ControlSlotCompositeIndicator EventDataControlCompositeImpl<AtomicIndirectorType>::GetLatestSlot() const noexcept
356: {
357:     EventDataControl* control = (asil_b_control_ != nullptr) ? asil_b_control_ : asil_qm_control_;
358: 
359:     for (std::size_t retry_counter{0U}; retry_counter < MAX_MULTI_ALLOCATE_RETRY_COUNT; ++retry_counter)
360:     {
361:         const auto latest_slot_index = FindLatestReadableSlotIndex(*control);
362: 
363:         if (!latest_slot_index.has_value())
...

3. File Snapshot (BASE)

  • File: score/mw/com/impl/bindings/lola/event_data_control_composite.cpp
  • Lines: 1-100
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

4. File Lookup

  • Filename: control_slot_composite_indicator.h
Found 1 file(s) matching 'control_slot_composite_indicator.h':
- score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h

5. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/event_data_control_composite.cpp
  • Lines: 89-350
89: template <template <class> class AtomicIndirectorType>
90: // Suppress "AUTOSAR C++14 A15-5-3" rule findings. This rule states: "The std::terminate() function shall not be called
91: // implicitly". This is a false positive, all results which are accessed with '.value()' that could implicitly call
92: // 'std::terminate()' (in case it doesn't have value) has a check in advance using '.has_value()', so no way for
93: // throwing std::bad_optional_access which leds to std::terminate(). This suppression should be removed after fixing
94: // [Ticket-173043](broken_link_j/Ticket-173043)
95: // coverity[autosar_cpp14_a15_5_3_violation : FALSE]
96: auto EventDataControlCompositeImpl<AtomicIndirectorType>::GetNextFreeMultiSlot() const noexcept
97:     -> ControlSlotCompositeIndicator
98: {
...

6. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

7. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/skeleton_event.h
  • Lines: 1-150
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

8. File Snapshot (HEAD)

  • File: score/mw/com/impl/bindings/lola/skeleton_event.h
  • Lines: 150-398
150:                                          impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data) noexcept
151:     : SkeletonEventBinding<SampleType>{},
152:       parent_{parent},
153:       event_fqn_{event_fqn},
154:       event_name_{event_name},
155:       event_properties_{properties},
156:       event_data_storage_{nullptr},
157:       event_data_control_composite_{std::nullopt},
158:       current_timestamp_{1U},
159:       qm_disconnect_{false},
...

Fix in Cursor

Comment on lines +344 to +352
SkeletonField<SampleDataType, EnableSet, EnableNotifier, EnableGet>::SkeletonField(
SkeletonBase& skeleton_base,
const std::string_view field_name,
std::unique_ptr<SkeletonEventBinding<FieldType>> binding)
: SkeletonField{skeleton_base,
std::make_unique<SkeletonEvent<FieldType>>(skeleton_base, field_name, std::move(binding)),
field_name}
{
}

Choose a reason for hiding this comment

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

Important

[Logic] The testing constructor (3-arg ctor taking SkeletonBase&, string_view, unique_ptr<SkeletonEventBinding>) always delegates to a private constructor with signature (SkeletonBase&, unique_ptr<SkeletonEvent>, string_view). Only the !ES && !EG private delegating constructor matches this signature. When EnableSet=true or EnableGet=true, the SFINAE condition std::enable_if_t<!ES && !EG> on that constructor eliminates it, and no other private constructor matches (they require additional set_method/get_method arguments). This results in a compilation error when attempting to instantiate the testing constructor for any SkeletonField with EnableSet=true or EnableGet=true.

This means it is impossible to write tests using mock bindings for any SkeletonField that has Get or Set enabled via this constructor.

Context for Agents
[**Bug**] The testing constructor (3-arg ctor taking `SkeletonBase&`, `string_view`, `unique_ptr<SkeletonEventBinding>`) always delegates to a private constructor with signature `(SkeletonBase&, unique_ptr<SkeletonEvent>, string_view)`. Only the `!ES && !EG` private delegating constructor matches this signature. When `EnableSet=true` or `EnableGet=true`, the SFINAE condition `std::enable_if_t<!ES && !EG>` on that constructor eliminates it, and no other private constructor matches (they require additional `set_method`/`get_method` arguments). This results in a **compilation error** when attempting to instantiate the testing constructor for any `SkeletonField` with `EnableSet=true` or `EnableGet=true`.

This means it is impossible to write tests using mock bindings for any `SkeletonField` that has Get or Set enabled via this constructor.

File: score/mw/com/impl/skeleton_field.h
Line: 352
Chain of Thought
  • Validation Decision: accepted
  • Confidence: 0.00
  • Likelihood Score: 0/5
  • Suggested Action: keep
  • Tool Calls Used: 10

Reasoning

Failed to parse validation response

Evidence Summary

1. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 342-355
342: /// \brief Testing ctor: binding is provided directly (used with mock bindings in tests).
343: template <typename SampleDataType, bool EnableSet, bool EnableNotifier, bool EnableGet>
344: SkeletonField<SampleDataType, EnableSet, EnableNotifier, EnableGet>::SkeletonField(
345:     SkeletonBase& skeleton_base,
346:     const std::string_view field_name,
347:     std::unique_ptr<SkeletonEventBinding<FieldType>> binding)
348:     : SkeletonField{skeleton_base,
349:                     std::make_unique<SkeletonEvent<FieldType>>(skeleton_base, field_name, std::move(binding)),
350:                     field_name}
351: {
...

2. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 99-145
99: 
100:     /// Constructor that allows to set the binding directly.
101:     ///
102:     /// This is used only used for testing.
103:     SkeletonField(SkeletonBase& skeleton_base,
104:                   const std::string_view field_name,
105:                   std::unique_ptr<SkeletonEventBinding<FieldType>> binding);
106: 
107:     ~SkeletonField() override = default;
108: 
...

3. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 233-260
233:     /// \brief Private delegating constructor: EnableSet=true, EnableGet=true.
234:     template <bool ES = EnableSet, bool EG = EnableGet, typename = std::enable_if_t<ES && EG>>
235:     SkeletonField(SkeletonBase& parent,
236:                   std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
237:                   std::unique_ptr<SkeletonMethod<Result<FieldType>(FieldType)>> set_method,
238:                   std::unique_ptr<SkeletonMethod<Result<FieldType>()>> get_method,
239:                   const std::string_view field_name);
240: 
241:     /// \brief Private delegating constructor: EnableSet=false, EnableGet=true.
242:     template <bool ES = EnableSet, bool EG = EnableGet, typename = std::enable_if_t<!ES && EG>>
...

4. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Lines: 1-80
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

5. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Lines: 80-200
80:     RecordProperty("Description", "Checks copy constructors for SkeletonField");
81:     RecordProperty("TestType", "Requirements-based test");
82:     RecordProperty("Priority", "1");
83:     RecordProperty("DerivationTechnique", "Analysis of requirements");
84: 
85:     static_assert(!std::is_copy_constructible<SkeletonField<TestSampleType>>::value, "Is wrongly copyable");
86:     static_assert(!std::is_copy_assignable<SkeletonField<TestSampleType>>::value, "Is wrongly copyable");
87: }
88: 
89: TEST(SkeletonFieldTest, IsMoveable)
...

6. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Lines: 300-500
300:     // then it does not return an error
301:     ASSERT_TRUE(update_result.has_value());
302: 
303:     // and send is not called on the binding
304:     EXPECT_FALSE(is_send_called_on_binding);
305: 
306:     // and when PrepareOffer() is called on the field
307:     const auto prepare_offer_result = unit.my_dummy_field_.PrepareOffer();
308: 
309:     //  Then the result will contain an error that the binding failed
...

7. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Lines: 200-300
200: 
201: // When Ticket-104261 is implemented, the Update call does not have to be deferred until OfferService is called. This
202: // test can be reworked to remove the call to PrepareOffer() and simply test Update() before PrepareOffer() is called.
203: TEST(SkeletonFieldCopyUpdateTest, CallingUpdateBeforeOfferServiceDefersCallToOfferService)
204: {
205:     RecordProperty("Verifies", "SCR-17434775, SCR-17563743, SCR-21553554");
206:     RecordProperty("Description", "Checks that calling Update before offer service defers the call to OfferService().");
207:     RecordProperty("TestType", "Requirements-based test");
208:     RecordProperty("Priority", "1");
209:     RecordProperty("DerivationTechnique", "Analysis of requirements");
...

8. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 100-260
100:     /// Constructor that allows to set the binding directly.
101:     ///
102:     /// This is used only used for testing.
103:     SkeletonField(SkeletonBase& skeleton_base,
104:                   const std::string_view field_name,
105:                   std::unique_ptr<SkeletonEventBinding<FieldType>> binding);
106: 
107:     ~SkeletonField() override = default;
108: 
109:     SkeletonField(const SkeletonField&) = delete;
...

Fix in Cursor

Comment on lines +207 to +211
if (!result.has_value())
{
score::mw::log::LogError("lola")
<< "RegisterGetHandler: failed to register get handler: " << result.error();
}

Choose a reason for hiding this comment

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

Important

[Reliability] RegisterGetHandler failure is silently logged but not propagated, and construction continues. If handler registration fails, the SkeletonField is constructed in a broken state where Get requests will never be served. The error should be made more visible — consider calling std::terminate(), storing the failure for later checking, or propagating the error from the constructor rather than only logging.

Suggested change
if (!result.has_value())
{
score::mw::log::LogError("lola")
<< "RegisterGetHandler: failed to register get handler: " << result.error();
}
if (!result.has_value())
{
score::mw::log::LogFatal("lola")
<< "RegisterGetHandler: failed to register get handler: " << result.error();
std::terminate();
}
Context for Agents
`RegisterGetHandler` failure is silently logged but not propagated, and construction continues. If handler registration fails, the `SkeletonField` is constructed in a broken state where Get requests will never be served. The error should be made more visible — consider calling `std::terminate()`, storing the failure for later checking, or propagating the error from the constructor rather than only logging.

```suggestion
        if (!result.has_value())
        {
            score::mw::log::LogFatal("lola")
                << "RegisterGetHandler: failed to register get handler: " << result.error();
            std::terminate();
        }
```

File: score/mw/com/impl/skeleton_field.h
Line: 211
Chain of Thought
  • Validation Decision: accepted
  • Confidence: 0.82
  • Likelihood Score: 3/5
  • Likelihood Reason: The RegisterHandler binding call returns ResultBlank, explicitly allowing failure. While it may rarely fail in practice (e.g., double registration), the code silently proceeds leaving the SkeletonField in a state where Get requests are never served, which could cause hard-to-diagnose issues in production.
  • Suggested Action: modify
  • Tool Calls Used: 15

Reasoning

The comment identifies a genuine issue newly introduced by this PR:

  1. New code: The RegisterGetHandler() method (lines 195-212) and its invocation from constructors (lines 371, 389) are entirely new in this PR.

  2. Silent failure: When get_method_-&gt;RegisterHandler(...) fails, the code only logs via LogError and continues. The method returns void, so the error is not propagated to the caller.

  3. Broken state: If handler registration fails, the SkeletonField is constructed without a get handler. This means proxy Get requests will never be served, but the object appears valid. This is a genuine design concern.

  4. Actionable suggestion: The comment provides a concrete code replacement using LogFatal + std::terminate().

  5. Minor inaccuracy: The comment mentions "noexcept-adjacent (move ctor is noexcept)" which is slightly misleading - the regular constructors that call RegisterGetHandler are NOT noexcept, so they could technically throw. However, the core point about the error being silently swallowed remains valid regardless.

The std::terminate() suggestion is opinionated (another valid approach might be to propagate the error), but the core issue - that a critical handler registration failure is silently logged and ignored, leaving the object in a non-functional state - is accurate, actionable, and introduced by this PR.

Evidence Summary

1. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 195-215
195:     // Get handler registration
196:     // SFINAE-gated: only exists when EnableGet == true
197:     template <bool EG = EnableGet, typename std::enable_if<EG, int>::type = 0>
198:     void RegisterGetHandler()
199:     {
200:         // get_method_ is guaranteed to be unique_ptr here since RegisterGetHandler is SFINAE-gated on EnableGet=true.
201:         const auto result = get_method_.get()->RegisterHandler([this]() -> Result<FieldType> {
202:             // need to serialize access to Get. In case of concurrent Get calls,
203:             // we want to ensure that they are processed sequentially.
204:             std::lock_guard<std::mutex> lock{get_handler_mutex_};
...

2. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field.h
  • Lines: 354-395
354: /// \brief Private delegating ctor - EnableSet=true, EnableGet=true.
355: template <typename SampleDataType, bool EnableSet, bool EnableNotifier, bool EnableGet>
356: template <bool ES, bool EG, typename>
357: SkeletonField<SampleDataType, EnableSet, EnableNotifier, EnableGet>::SkeletonField(
358:     SkeletonBase& parent,
359:     std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
360:     std::unique_ptr<SkeletonMethod<Result<FieldType>(FieldType)>> set_method,
361:     std::unique_ptr<SkeletonMethod<Result<FieldType>()>> get_method,
362:     const std::string_view field_name)
363:     : SkeletonFieldBase{parent, field_name, std::move(skeleton_event_dispatch)},
...

3. File Lookup

  • Filename: skeleton_method.h
Found 3 file(s) matching 'skeleton_method.h':
- score/mw/com/impl/bindings/lola/skeleton_method.h
- score/mw/com/impl/bindings/mock_binding/skeleton_method.h
- score/mw/com/impl/methods/skeleton_method.h

4. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Lines: 1-100
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

5. File Snapshot (HEAD)

  • File: score/mw/com/impl/methods/skeleton_method.h
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

6. File Lookup

  • Filename: skeleton_method_binding.h
Found 1 file(s) matching 'skeleton_method_binding.h':
- score/mw/com/impl/methods/skeleton_method_binding.h

7. File Snapshot (HEAD)

  • File: score/mw/com/impl/skeleton_field_test.cpp
  • Line: 900
900:     // When the initial value is set via an Update call
901:     const auto update_result_2 = unit_2.my_dummy_field_.Update(initial_value_2);
902: 
903:     // which does not return an error
904:     EXPECT_TRUE(update_result_2.has_value());
905: 
906:     unit_2 = std::move(unit);
907: 
908:     // and PrepareOffer() is called on the field in the new skeleton
909:     const auto prepare_offer_result = unit_2.my_dummy_field_.PrepareOffer();
...

8. File Snapshot (HEAD)

  • File: score/mw/com/impl/methods/skeleton_method_binding.h
1: /********************************************************************************
2:  * Copyright (c) 2025 Contributors to the Eclipse Foundation
3:  *
4:  * See the NOTICE file(s) distributed with this work for additional
5:  * information regarding copyright ownership.
6:  *
7:  * This program and the accompanying materials are made available under the
8:  * terms of the Apache License Version 2.0 which is available at
9:  * https://www.apache.org/licenses/LICENSE-2.0
10:  *
...

Fix in Cursor

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.

3 participants