Step 566: add allocation ownership trace hooks
This commit is contained in:
@@ -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)
|
||||
|
||||
150
editor/src/AllocationOwnershipTraceHooks.h
Normal file
150
editor/src/AllocationOwnershipTraceHooks.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
// Step 566: Allocation/Ownership Trace Hooks
|
||||
|
||||
#include "DebugValidationUtil.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<AllocationTraceEvent> timelineForSession(const std::string& sessionId) const {
|
||||
std::vector<AllocationTraceEvent> filtered;
|
||||
for (const auto& event : events_) {
|
||||
if (sessionId.empty() || event.sessionId == sessionId) filtered.push_back(event);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<AllocationTraceEvent> events_;
|
||||
std::map<std::string, std::string> 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;
|
||||
});
|
||||
}
|
||||
};
|
||||
160
editor/tests/step566_test.cpp
Normal file
160
editor/tests/step566_test.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// Step 566: Allocation/Ownership Trace Hooks (12 tests)
|
||||
|
||||
#include "AllocationOwnershipTraceHooks.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
35
progress.md
35
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`
|
||||
|
||||
Reference in New Issue
Block a user