From c5a90e7341c2ab20f0724f1ad7ed61e662939d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunics=20Bal=C3=A1zs?= Date: Wed, 14 Oct 2026 09:32:14 +0200 Subject: [PATCH] Read device element children by index, not through the broken DataSpan DeviceElementObject::get_child_object_ids() returns a DataSpan, and AgIsoStack's DataSpan scales end() and operator[] by sizeof(T) on top of the pointer arithmetic. Iterating it reads twice as many IDs as the element has children; the second half comes from past the end of the list. Whatever is in that memory can match an object ID. All four TC loops that map DDIs to elements iterated it. On a Mueller sprayer (11 sections), DDI 160 Section Control State was subscribed on elements 2, 17 and 18 although only element 2 lists it, and the TC wrote "automatic" to element 18. The sprayer stayed in manual on element 2 and ignored every setpoint. Whether it happens depends on heap contents, so the same implement mapped correctly with another build. Copy the child IDs through get_number_child_objects()/get_child_object_id(i), which AgIsoStack's own code uses, in one helper used at all four sites. Co-Authored-By: Claude Opus 5.5 --- src/task_controller.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/task_controller.cpp b/src/task_controller.cpp index 614bb8a..4e92566 100644 --- a/src/task_controller.cpp +++ b/src/task_controller.cpp @@ -20,6 +20,7 @@ #include #include #include +#include // Sanitize a string for use as a filename by replacing invalid characters with underscores static std::string sanitize_filename(const std::string &input) @@ -37,6 +38,22 @@ static std::string sanitize_filename(const std::string &input) return result; } +// Returns the IDs of a device element's child objects. +// Do not iterate DeviceElementObject::get_child_object_ids() instead: it returns a DataSpan, +// and DataSpan scales end() and operator[] by sizeof(T) on top of the pointer arithmetic, so iterating it reads +// twice as many IDs as the element has children, the second half from past the end of the list. Whatever is in +// that memory can match an object ID and map a DDI to the wrong element. +static std::vector get_child_object_ids(isobus::task_controller_object::DeviceElementObject &element) +{ + std::vector childIds; + childIds.reserve(element.get_number_child_objects()); + for (std::uint16_t i = 0; i < element.get_number_child_objects(); i++) + { + childIds.push_back(element.get_child_object_id(i)); + } + return childIds; +} + // Finds the number of the first device element that lists the process data object as a child. static bool find_owning_element_number(isobus::DeviceDescriptorObjectPool &pool, const isobus::task_controller_object::DeviceProcessDataObject &processData, @@ -56,7 +73,7 @@ static bool find_owning_element_number(isobus::DeviceDescriptorObjectPool &pool, continue; } - for (std::uint16_t childId : elementObject->get_child_object_ids()) + for (std::uint16_t childId : get_child_object_ids(*elementObject)) { if (childId == processData.get_object_id()) { @@ -326,7 +343,7 @@ bool ClientState::is_element_or_parent_off(std::uint16_t elementNumber) const continue; } - for (std::uint16_t childId : elementObject->get_child_object_ids()) + for (std::uint16_t childId : get_child_object_ids(*elementObject)) { for (std::uint32_t j = 0; j < nonConstPool.size(); j++) { @@ -834,7 +851,7 @@ void MyTCServer::request_measurement_commands() if (parentObject && parentObject->get_object_type() == isobus::task_controller_object::ObjectTypes::DeviceElement) { auto elementObject = std::dynamic_pointer_cast(parentObject); - for (std::uint16_t elementObjectChild : elementObject->get_child_object_ids()) + for (std::uint16_t elementObjectChild : get_child_object_ids(*elementObject)) { if (elementObjectChild == processDataObject->get_object_id()) { @@ -881,7 +898,7 @@ void MyTCServer::request_measurement_commands() if (parentObject && parentObject->get_object_type() == isobus::task_controller_object::ObjectTypes::DeviceElement) { auto elementObject = std::dynamic_pointer_cast(parentObject); - for (std::uint16_t elementObjectChild : elementObject->get_child_object_ids()) + for (std::uint16_t elementObjectChild : get_child_object_ids(*elementObject)) { if (elementObjectChild == processDataObject->get_object_id()) {