From f9118230f551e4c505248e4d738e83ac54a841cc Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 10:47:33 -0700 Subject: [PATCH] Step 566: add allocation ownership trace hooks --- editor/CMakeLists.txt | 9 ++ editor/src/AllocationOwnershipTraceHooks.h | 150 +++++++++++++++++++ editor/tests/step566_test.cpp | 160 +++++++++++++++++++++ progress.md | 35 +++++ 4 files changed, 354 insertions(+) create mode 100644 editor/src/AllocationOwnershipTraceHooks.h create mode 100644 editor/tests/step566_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a4fef9f..ea285e6 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3973,4 +3973,13 @@ target_link_libraries(step565_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step566_test tests/step566_test.cpp) +target_include_directories(step566_test PRIVATE src) +target_link_libraries(step566_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/AllocationOwnershipTraceHooks.h b/editor/src/AllocationOwnershipTraceHooks.h new file mode 100644 index 0000000..1d68256 --- /dev/null +++ b/editor/src/AllocationOwnershipTraceHooks.h @@ -0,0 +1,150 @@ +#pragma once +// Step 566: Allocation/Ownership Trace Hooks + +#include "DebugValidationUtil.h" + +#include +#include +#include +#include +#include + +enum class AllocationTraceEventType { + Alloc, + Transfer, + Free +}; + +struct AllocationTraceEvent { + std::string eventId; + std::string sessionId; + std::string allocationId; + AllocationTraceEventType type = AllocationTraceEventType::Alloc; + std::uint64_t timestamp = 0; + std::uint64_t address = 0; + std::uint64_t sizeBytes = 0; + std::string ownerFrom; + std::string ownerTo; +}; + +class AllocationOwnershipTraceHooks { +public: + bool recordAllocation(const std::string& sessionId, + const std::string& allocationId, + std::uint64_t address, + std::uint64_t sizeBytes, + const std::string& owner, + std::uint64_t timestamp, + std::string* error) { + if (!error) return false; + error->clear(); + if (!hasRequiredDebugIds(allocationId, sessionId)) return fail(error, "allocation_or_session_missing"); + if (address == 0) return fail(error, "allocation_address_invalid"); + if (sizeBytes == 0) return fail(error, "allocation_size_invalid"); + if (owner.empty()) return fail(error, "allocation_owner_missing"); + if (activeOwners_.count(allocationId) != 0) return fail(error, "allocation_id_active_duplicate"); + + activeOwners_[allocationId] = owner; + AllocationTraceEvent event; + event.eventId = nextEventId(); + event.sessionId = sessionId; + event.allocationId = allocationId; + event.type = AllocationTraceEventType::Alloc; + event.timestamp = timestamp; + event.address = address; + event.sizeBytes = sizeBytes; + event.ownerTo = owner; + appendEvent(event); + return true; + } + + bool transferOwnership(const std::string& sessionId, + const std::string& allocationId, + const std::string& newOwner, + std::uint64_t timestamp, + std::string* error) { + if (!error) return false; + error->clear(); + if (!hasRequiredDebugIds(allocationId, sessionId)) return fail(error, "allocation_or_session_missing"); + if (newOwner.empty()) return fail(error, "transfer_owner_missing"); + + auto it = activeOwners_.find(allocationId); + if (it == activeOwners_.end()) return fail(error, "allocation_not_active"); + if (it->second == newOwner) return fail(error, "transfer_owner_unchanged"); + + AllocationTraceEvent event; + event.eventId = nextEventId(); + event.sessionId = sessionId; + event.allocationId = allocationId; + event.type = AllocationTraceEventType::Transfer; + event.timestamp = timestamp; + event.ownerFrom = it->second; + event.ownerTo = newOwner; + it->second = newOwner; + appendEvent(event); + return true; + } + + bool recordFree(const std::string& sessionId, + const std::string& allocationId, + std::uint64_t timestamp, + std::string* error) { + if (!error) return false; + error->clear(); + if (!hasRequiredDebugIds(allocationId, sessionId)) return fail(error, "allocation_or_session_missing"); + + auto it = activeOwners_.find(allocationId); + if (it == activeOwners_.end()) return fail(error, "allocation_not_active"); + + AllocationTraceEvent event; + event.eventId = nextEventId(); + event.sessionId = sessionId; + event.allocationId = allocationId; + event.type = AllocationTraceEventType::Free; + event.timestamp = timestamp; + event.ownerFrom = it->second; + activeOwners_.erase(it); + appendEvent(event); + return true; + } + + std::string ownerFor(const std::string& allocationId) const { + auto it = activeOwners_.find(allocationId); + if (it == activeOwners_.end()) return ""; + return it->second; + } + + std::size_t activeAllocationCount() const { + return activeOwners_.size(); + } + + std::vector timelineForSession(const std::string& sessionId) const { + std::vector filtered; + for (const auto& event : events_) { + if (sessionId.empty() || event.sessionId == sessionId) filtered.push_back(event); + } + return filtered; + } + +private: + std::vector events_; + std::map activeOwners_; + std::uint64_t nextId_ = 1; + + static bool fail(std::string* error, const char* code) { + *error = code; + return false; + } + + std::string nextEventId() { + return "alloc-event-" + std::to_string(nextId_++); + } + + void appendEvent(const AllocationTraceEvent& event) { + events_.push_back(event); + std::stable_sort(events_.begin(), events_.end(), [](const AllocationTraceEvent& a, const AllocationTraceEvent& b) { + if (a.timestamp != b.timestamp) return a.timestamp < b.timestamp; + return a.eventId < b.eventId; + }); + } +}; diff --git a/editor/tests/step566_test.cpp b/editor/tests/step566_test.cpp new file mode 100644 index 0000000..1d66f32 --- /dev/null +++ b/editor/tests/step566_test.cpp @@ -0,0 +1,160 @@ +// Step 566: Allocation/Ownership Trace Hooks (12 tests) + +#include "AllocationOwnershipTraceHooks.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +void test_record_allocation_success() { + TEST(record_allocation_success); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 64, "owner-a", 10, &error); + CHECK(ok, "allocation record should succeed"); + CHECK(hooks.activeAllocationCount() == 1, "active count should be one"); + CHECK(hooks.ownerFor("alloc-1") == "owner-a", "owner mismatch"); + PASS(); +} + +void test_record_allocation_rejects_missing_ids() { + TEST(record_allocation_rejects_missing_ids); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.recordAllocation("", "alloc-1", 0x1000, 64, "owner-a", 10, &error); + CHECK(!ok, "allocation record should fail"); + CHECK(error == "allocation_or_session_missing", "wrong error"); + PASS(); +} + +void test_record_allocation_rejects_invalid_address() { + TEST(record_allocation_rejects_invalid_address); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.recordAllocation("sess-1", "alloc-1", 0, 64, "owner-a", 10, &error); + CHECK(!ok, "zero address should fail"); + CHECK(error == "allocation_address_invalid", "wrong error"); + PASS(); +} + +void test_record_allocation_rejects_invalid_size() { + TEST(record_allocation_rejects_invalid_size); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 0, "owner-a", 10, &error); + CHECK(!ok, "zero size should fail"); + CHECK(error == "allocation_size_invalid", "wrong error"); + PASS(); +} + +void test_record_allocation_rejects_active_duplicate() { + TEST(record_allocation_rejects_active_duplicate); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 64, "owner-a", 10, &error), "first allocation failed"); + CHECK(!hooks.recordAllocation("sess-1", "alloc-1", 0x2000, 32, "owner-b", 11, &error), "duplicate should fail"); + CHECK(error == "allocation_id_active_duplicate", "wrong error"); + PASS(); +} + +void test_transfer_ownership_success() { + TEST(transfer_ownership_success); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 64, "owner-a", 10, &error), "allocation failed"); + CHECK(hooks.transferOwnership("sess-1", "alloc-1", "owner-b", 20, &error), "transfer failed"); + CHECK(hooks.ownerFor("alloc-1") == "owner-b", "owner should change"); + PASS(); +} + +void test_transfer_ownership_rejects_unknown_allocation() { + TEST(transfer_ownership_rejects_unknown_allocation); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.transferOwnership("sess-1", "alloc-404", "owner-b", 20, &error); + CHECK(!ok, "transfer should fail"); + CHECK(error == "allocation_not_active", "wrong error"); + PASS(); +} + +void test_transfer_ownership_rejects_unchanged_owner() { + TEST(transfer_ownership_rejects_unchanged_owner); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 64, "owner-a", 10, &error), "allocation failed"); + CHECK(!hooks.transferOwnership("sess-1", "alloc-1", "owner-a", 20, &error), "unchanged transfer should fail"); + CHECK(error == "transfer_owner_unchanged", "wrong error"); + PASS(); +} + +void test_record_free_success() { + TEST(record_free_success); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-1", 0x1000, 64, "owner-a", 10, &error), "allocation failed"); + CHECK(hooks.recordFree("sess-1", "alloc-1", 30, &error), "free should succeed"); + CHECK(hooks.ownerFor("alloc-1").empty(), "owner should be cleared"); + CHECK(hooks.activeAllocationCount() == 0, "active count should be zero"); + PASS(); +} + +void test_record_free_rejects_unknown_allocation() { + TEST(record_free_rejects_unknown_allocation); + AllocationOwnershipTraceHooks hooks; + std::string error; + const bool ok = hooks.recordFree("sess-1", "alloc-x", 30, &error); + CHECK(!ok, "free should fail"); + CHECK(error == "allocation_not_active", "wrong error"); + PASS(); +} + +void test_timeline_orders_by_timestamp() { + TEST(timeline_orders_by_timestamp); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-a", 0x1000, 64, "a", 30, &error), "alloc-a failed"); + CHECK(hooks.recordAllocation("sess-1", "alloc-b", 0x2000, 64, "b", 10, &error), "alloc-b failed"); + CHECK(hooks.transferOwnership("sess-1", "alloc-b", "c", 20, &error), "transfer failed"); + const auto timeline = hooks.timelineForSession("sess-1"); + CHECK(timeline.size() == 3, "timeline count mismatch"); + CHECK(timeline[0].allocationId == "alloc-b", "first event should be alloc-b"); + CHECK(timeline[1].type == AllocationTraceEventType::Transfer, "second should be transfer"); + CHECK(timeline[2].allocationId == "alloc-a", "third should be alloc-a"); + PASS(); +} + +void test_timeline_for_session_filters_events() { + TEST(timeline_for_session_filters_events); + AllocationOwnershipTraceHooks hooks; + std::string error; + CHECK(hooks.recordAllocation("sess-1", "alloc-a", 0x1000, 64, "a", 10, &error), "sess-1 alloc failed"); + CHECK(hooks.recordAllocation("sess-2", "alloc-b", 0x2000, 64, "b", 11, &error), "sess-2 alloc failed"); + const auto timeline = hooks.timelineForSession("sess-2"); + CHECK(timeline.size() == 1, "session filter should return one event"); + CHECK(timeline[0].sessionId == "sess-2", "wrong session"); + PASS(); +} + +int main() { + std::cout << "Step 566: Allocation/Ownership Trace Hooks\n"; + + test_record_allocation_success(); // 1 + test_record_allocation_rejects_missing_ids(); // 2 + test_record_allocation_rejects_invalid_address(); // 3 + test_record_allocation_rejects_invalid_size(); // 4 + test_record_allocation_rejects_active_duplicate(); // 5 + test_transfer_ownership_success(); // 6 + test_transfer_ownership_rejects_unknown_allocation(); // 7 + test_transfer_ownership_rejects_unchanged_owner(); // 8 + test_record_free_success(); // 9 + test_record_free_rejects_unknown_allocation(); // 10 + test_timeline_orders_by_timestamp(); // 11 + test_timeline_for_session_filters_events(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 45781e8..aef1d2c 100644 --- a/progress.md +++ b/progress.md @@ -10224,3 +10224,38 @@ visible-row flattening, and pointer-to-region correlation against memory snapsho - `editor/src/MemoryInspectorUiModel.h` within header-size limit (`194` <= `600`) - `editor/tests/step565_test.cpp` within test-file size guidance (`188` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 566: Allocation/Ownership Trace Hooks +**Status:** PASS (12/12 tests) + +Implements allocation lifecycle and ownership transfer trace hooks with +deterministic timeline ordering, active-allocation tracking, and session filters. + +**Files added:** +- `editor/src/AllocationOwnershipTraceHooks.h` - allocation trace module: + - allocation/transfer/free event schema + - allocation guardrails (ids, address, size, owner, duplicate-active checks) + - ownership transfer and release lifecycle handling + - active-allocation owner lookup/count helpers + - deterministic timeline ordering and per-session filtering +- `editor/tests/step566_test.cpp` - 12 tests covering: + - allocation success/failure validation behavior + - duplicate-active guard behavior + - ownership transfer success and failure behavior + - free lifecycle success/failure behavior + - timeline ordering behavior + - session timeline filtering behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step566_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step566_test step565_test` - PASS +- `./editor/build-native/step566_test` - PASS (12/12) +- `./editor/build-native/step565_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/AllocationOwnershipTraceHooks.h` within header-size limit (`150` <= `600`) +- `editor/tests/step566_test.cpp` within test-file size guidance (`160` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`