Tracking allocator#24
Conversation
📝 WalkthroughWalkthroughAdds debug-only std::source_location to allocator APIs and implementations, introduces a tracking allocator module that records per-allocation metadata and analytics, updates module exports, and adjusts tests to use the allocator wrapper and validate tracking behavior. ChangesAllocator Source-Location Tracking and Analytics
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@engine/native/core/memory/fixedAllocator.test.cpp`:
- Around line 15-16: The test calls alloc.alloc(&block, 512, 16) but never
verifies it succeeded before inspecting block.data; change the test to assert
the allocation returned success (e.g., REQUIRE on the boolean/result of
alloc.alloc) before the alignment check, so verify alloc.alloc(...) is true and
only then perform REQUIRE((((uintptr_t)block.data) & 15) == 0); reference
alloc.alloc and block in the fixedAllocator.test.cpp test.
In `@engine/native/core/memory/trackingAllocator.cpp`:
- Around line 88-89: The code currently clears allocData->nodes before calling
allocData->base.vtbl->freeAll(allocData->base); change this so you call freeAll
first, capture its return/result, and only set allocData->nodes = nullptr (and
any other tracking-state resets) if freeAll succeeded; if freeAll fails, leave
allocData->nodes unchanged and return the error result from
allocData->base.vtbl->freeAll(allocData->base) so tracking still reflects live
blocks. Ensure you reference allocData->base.vtbl->freeAll and allocData->nodes
when making the change.
- Around line 69-82: The unlink logic in the free path dereferences prev/next
blindly and always adjusts block.size even when no matching Node was found;
update the code in trackingAllocator.cpp (the Node list manipulation around
node, prev, next) to: only perform memset(node) and add sizeof(Node) to
block.size when a matching node is found; when unlinking, check for nullptr
before dereferencing prev or next and update head/tail pointers appropriately to
handle removing the head, tail, or the sole node; if node is not found, do not
modify block.size or attempt unlinking—simply call
allocData->base.vtbl->free(allocData->base, block) (optionally log or assert the
unexpected free) to avoid crashes and allocator corruption.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 17559e75-eb06-4c1a-8335-b6bf804b8709
📒 Files selected for processing (14)
engine/native/core/memory/allocator.cppengine/native/core/memory/allocator.cppmengine/native/core/memory/bumpAllocator.cppengine/native/core/memory/bumpAllocator.cppmengine/native/core/memory/bumpAllocator.test.cppengine/native/core/memory/fixedAllocator.cppengine/native/core/memory/fixedAllocator.cppmengine/native/core/memory/fixedAllocator.test.cppengine/native/core/memory/pageAllocator.cppengine/native/core/memory/pageAllocator.cppmengine/native/core/memory/root.cppmengine/native/core/memory/trackingAllocator.cppengine/native/core/memory/trackingAllocator.cppmengine/native/core/memory/trackingAllocator.test.cpp
| alloc.alloc(&block, 512, 16); | ||
| REQUIRE((((uintptr_t)block.data) & 15) == 0); |
There was a problem hiding this comment.
Assert allocation success before checking alignment.
Line 15 ignores the alloc.alloc(...) result. If allocation fails, Line 16 may assert on an invalid block.
Proposed fix
- alloc.alloc(&block, 512, 16);
+ Error err = alloc.alloc(&block, 512, 16);
+ REQUIRE(err == Error::Okay);
REQUIRE((((uintptr_t)block.data) & 15) == 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 `@engine/native/core/memory/fixedAllocator.test.cpp` around lines 15 - 16, The
test calls alloc.alloc(&block, 512, 16) but never verifies it succeeded before
inspecting block.data; change the test to assert the allocation returned success
(e.g., REQUIRE on the boolean/result of alloc.alloc) before the alignment check,
so verify alloc.alloc(...) is true and only then perform
REQUIRE((((uintptr_t)block.data) & 15) == 0); reference alloc.alloc and block in
the fixedAllocator.test.cpp test.
OldDev78
left a comment
There was a problem hiding this comment.
lgtm.
I still wonder about our mechanism for debug, but this will work, of course.
Approved, pending the @CodeRabbit comments.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
engine/native/core/memory/trackingAllocator.cpp (1)
132-132: 💤 Low valueUse logical
&&instead of bitwise&for the loop condition.
(node != nullptr) & (ret < detailsCount)works only because both operands are side-effect-free booleans, but it disables short-circuit evaluation and is easy to misread as a typo. Prefer&&for boolean control flow.- while ((node != nullptr) & (ret < detailsCount)) + while ((node != nullptr) && (ret < detailsCount))🤖 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 `@engine/native/core/memory/trackingAllocator.cpp` at line 132, Replace the bitwise AND in the loop condition with a logical AND to enable short-circuiting: change the expression using node, ret and detailsCount from "(node != nullptr) & (ret < detailsCount)" to use "&&" so the condition reads "(node != nullptr) && (ret < detailsCount)"; ensure this occurs in the loop that iterates over node while checking ret against detailsCount so behavior and readability are correct.
🤖 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.
Nitpick comments:
In `@engine/native/core/memory/trackingAllocator.cpp`:
- Line 132: Replace the bitwise AND in the loop condition with a logical AND to
enable short-circuiting: change the expression using node, ret and detailsCount
from "(node != nullptr) & (ret < detailsCount)" to use "&&" so the condition
reads "(node != nullptr) && (ret < detailsCount)"; ensure this occurs in the
loop that iterates over node while checking ret against detailsCount so behavior
and readability are correct.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6b59275e-3053-43cf-92c0-fe436ddca4c9
📒 Files selected for processing (1)
engine/native/core/memory/trackingAllocator.cpp
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
engine/native/core/math/vector3.cppm (1)
68-87:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject invalid
constexprindices instead of defaulting toz.In the
if constevalpath, thedefaultarm falls through tocase 2, soconstexpraccess likev[99]returnszinstead of surfacing the bug. The swizzle overloads already reject invalid constexpr indices, so single-element access should match that behavior.Suggested fix
[[nodiscard]] constexpr f32& Vector3::operator[](const i32 i) noexcept { if consteval { switch (i) { case 0: return x; case 1: return y; - default: - case 2: return z; + case 2: return z; + default: throw "Index out of range"; } } else { return (&x)[i]; } } [[nodiscard]] constexpr const f32& Vector3::operator[](const i32 i) const noexcept { if consteval { switch (i) { case 0: return x; case 1: return y; - default: - case 2: return z; + case 2: return z; + default: throw "Index out of range"; } } else { return (&x)[i]; } }🤖 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 `@engine/native/core/math/vector3.cppm` around lines 68 - 87, In Vector3::operator[](const i32) and its const overload, the if consteval switch currently falls through the default to case 2 (returning z) for out-of-range indices; change the default branch to reject invalid constexpr indices by causing a hard error (e.g. throw std::out_of_range("Vector3 index out of range") or call an unreachable helper) instead of returning z so constexpr access like v[99] fails at compile time; update both Vector3::operator[](const i32) and Vector3::operator[](const i32) const accordingly to match swizzle behavior.engine/native/core/math/vector4.cppm (1)
75-97:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject invalid
constexprindices instead of defaulting tow.The
if constevalbranch currently routes any out-of-range index tow, so bad constexpr indexing is silently accepted. That masks bugs and diverges from the swizzle overloads, which already fail invalid constexpr indices.Suggested fix
[[nodiscard]] constexpr f32& Vector4::operator[](const i32 i) noexcept { if consteval { switch (i) { case 0: return x; case 1: return y; case 2: return z; - default: - case 3: return w; + case 3: return w; + default: throw "Index out of range"; } } else { return (&x)[i]; } } [[nodiscard]] constexpr const f32& Vector4::operator[](const i32 i) const noexcept { if consteval { switch (i) { case 0: return x; case 1: return y; case 2: return z; - default: - case 3: return w; + case 3: return w; + default: throw "Index out of range"; } } else { return (&x)[i]; } }🤖 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 `@engine/native/core/math/vector4.cppm` around lines 75 - 97, The consteval branches of Vector4::operator[](i32) and its const overload currently map any out-of-range index to w, silently accepting bad constexpr indexing; change the default case in both consteval switch statements to reject invalid indices (e.g., throw std::out_of_range("Vector4 index out of range") or call std::unreachable()/std::terminate) instead of returning w so invalid constexpr indexing becomes a compile-time error and matches the swizzle behavior.engine/native/core/math/vector2.cppm (1)
56-69:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject invalid
constexprindices in single-element access.The
if constevalbranch currently maps every non-zero index toy, soconstexprcalls likev[99]succeed with the wrong component instead of failing like the swizzle overloads do.Suggested fix
[[nodiscard]] constexpr f32& Vector2::operator[](const i32 i) noexcept { if consteval { - return i ? y : x; + switch (i) { + case 0: return x; + case 1: return y; + default: throw "Index out of range"; + } } else { return (&x)[i]; } } [[nodiscard]] constexpr const f32& Vector2::operator[](const i32 i) const noexcept { if consteval { - return i ? y : x; + switch (i) { + case 0: return x; + case 1: return y; + default: throw "Index out of range"; + } } else { return (&x)[i]; } }🤖 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 `@engine/native/core/math/vector2.cppm` around lines 56 - 69, In both Vector2::operator[](const i32) and the const overload, change the if consteval branch to reject invalid indices at compile time by asserting the index is 0 or 1 before returning the component; e.g. inside the if consteval block add a compile-time check like static_assert(i == 0 || i == 1, "Vector2 index out of range") (or equivalent consteval-safe check) and then return the requested component (i ? y : x), so constexpr calls such as v[99] fail instead of silently returning y.engine/native/core/memory/allocator.cppm (1)
72-89:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject invalid alignments before dispatch.
alignis forwarded unchecked here, but bothbump::allocandfixed::allocimmediately doalign - 1before their debug-onlyassert(std::popcount(align) == 1). In release builds,align == 0underflows, and non-power-of-two values can produce bad padding/alignment instead of a clean error. Please enforce the precondition here or in every exported allocator entrypoint.🤖 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 `@engine/native/core/memory/allocator.cppm` around lines 72 - 89, Allocator::alloc currently forwards align unchecked to vtbl->alloc which lets align==0 underflow or non-power-of-two values produce bad behavior; validate align at the entrypoint (Allocator::alloc) by rejecting align==0 and non-power-of-two aligns (e.g. check (align & (align - 1)) == 0) and return an appropriate Error before calling vtbl->alloc, or add the same validation to every exported allocator entrypoint (including bump::alloc and fixed::alloc) to guarantee the precondition is enforced in release builds.engine/native/core/memory/trackingAllocator.cpp (1)
130-130:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse logical
&&instead of bitwise&in the loop condition.
(node != nullptr) & (ret < detailsCount)is non-short-circuiting. It happens to work here because the right-hand check has no side effects, but it’s misleading to readers and is a common static-analysis warning. Switch to&&for idiomatic short-circuit semantics.Proposed fix
- while ((node != nullptr) & (ret < detailsCount)) + while ((node != nullptr) && (ret < detailsCount))🤖 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 `@engine/native/core/memory/trackingAllocator.cpp` at line 130, The while-loop condition in trackingAllocator.cpp uses the bitwise operator (&) between (node != nullptr) and (ret < detailsCount); change this to the logical AND operator (&&) so the loop reads (node != nullptr) && (ret < detailsCount) to get idiomatic short-circuiting behavior—locate the loop that references variables node, ret, and detailsCount and replace & with &&.
♻️ Duplicate comments (2)
engine/native/core/memory/fixedAllocator.test.cpp (1)
16-17:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAssert allocation success before asserting alignment.
alloc.alloc(...)return value is still discarded; if allocation fails,block.datais uninitialized and L17 may assert on garbage.Proposed fix
- alloc.alloc(&block, 512, 16); + Error err = alloc.alloc(&block, 512, 16); + REQUIRE(err == Error::Okay); REQUIRE((((uintptr_t)block.data) & 15) == 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 `@engine/native/core/memory/fixedAllocator.test.cpp` around lines 16 - 17, Test currently discards the result of alloc.alloc(&block, 512, 16) and immediately asserts alignment on block.data; first check that the allocation succeeded by capturing the return value (or calling the allocator's success predicate) and REQUIRE that alloc.alloc(...) returned true before asserting (((uintptr_t)block.data) & 15) == 0, so that alignment is only tested on a valid initialized block (referencing alloc.alloc and block.data in fixedAllocator.test.cpp).engine/native/core/memory/trackingAllocator.cpp (1)
72-87:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
freestill corrupts allocator state in the head/untracked cases.The previous review flagged this and only the
next == nullptrbranch was addressed. Three problems remain:
- L72:
block.size += sizeof(Node);is applied unconditionally and then forwarded tobase.vtbl->freebefore we confirm the block was actually tracked. If a caller frees an untracked slice (e.g., obtained from a different layer), the wrong size is propagated to the base allocator.- L81:
prev->next = next;dereferencesprevwithout a null check. When freeing the head node (node == allocData->nodes),previsnullptrand this is a NPE.- L77-87: when unlinking the head,
allocData->nodesis never updated tonext, so the head pointer ends up dangling at a memset-zeroed node.Proposed fix
- while (node && (node->details.data.data != block.data)) - { - node = node->next; - } - block.size += sizeof(Node); - err = allocData->base.vtbl->free(allocData->base, block); - switch (err) - { - case Error::Okay: - if (node) - { - prev = node->prev; - next = node->next; - prev->next = next; - if (next) - { - next->prev = prev; - } - memset(node, 0, sizeof(Node)); - } - [[fallthrough]]; - default: - return err; - } + while (node && (node->details.data.data != block.data)) + { + node = node->next; + } + if (!node) + { + // Untracked block — forward without adjusting size. + return allocData->base.vtbl->free(allocData->base, block); + } + block.size += sizeof(Node); + err = allocData->base.vtbl->free(allocData->base, block); + if (err != Error::Okay) { return err; } + prev = node->prev; + next = node->next; + if (prev) { prev->next = next; } + else { allocData->nodes = next; } + if (next) { next->prev = prev; } + memset(node, 0, sizeof(Node)); + return err;🤖 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 `@engine/native/core/memory/trackingAllocator.cpp` around lines 72 - 87, The free path in trackingAllocator.cpp must only add sizeof(Node) to block.size and call allocData->base.vtbl->free when the block is confirmed tracked: move the block.size += sizeof(Node) and the base.vtbl->free call into the branch after you verify node is non-null (i.e., a tracked node), and leave the original block unmodified for untracked frees; also protect the unlink logic by checking prev for nullptr before doing prev->next = next, and when node == allocData->nodes update allocData->nodes = next to advance the head (and then perform memset(node,0,sizeof(Node))). Reference: the local variables block, node, prev, next and methods allocData->base.vtbl->free and allocData->nodes in trackingAllocator.cpp.
🧹 Nitpick comments (4)
engine/native/core/math/constants.cppm (1)
14-37: 💤 Low valueMigration to
f32is partial within this file.
CMP_EPSILON/CMP_NORMALIZE_TOLERANCE*/CMP_POINT_IN_PLANE_EPSILONwere switched tof32, but the other floating-point constants (SQRT2,SQRT3,PI,TAU,E,INF,NaN,DB_CONVERSION_GAIN,GAIN_CONVERSION_DB,UINT32_MAX_F,DECIMAL_LIMIT_F) still usefloat, andMAX_RECURSIONSstill usesint. Sincef32 == floatthis is purely stylistic, but the half-converted state will be confusing to readers (and the rest of the PR's math layer commits to draco aliases).Consider migrating the remaining constants in one pass:
- constexpr int MAX_RECURSIONS = 100; + constexpr i32 MAX_RECURSIONS = 100; ... - constexpr float SQRT2 = std::numbers::sqrt2_v<float>; + constexpr f32 SQRT2 = std::numbers::sqrt2_v<f32>; ... - constexpr float UINT32_MAX_F = 1.f / std::numeric_limits<u32>::max(); - constexpr float DECIMAL_LIMIT_F = 8388608.0f; + constexpr f32 UINT32_MAX_F = 1.f / std::numeric_limits<u32>::max(); + constexpr f32 DECIMAL_LIMIT_F = 8388608.0f;🤖 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 `@engine/native/core/math/constants.cppm` around lines 14 - 37, The file has a partial migration to the draco alias f32; update the remaining floating constants to use f32 (replace declarations for SQRT2, SQRT3, SQRT12, SQRT13, LN2, LN10, PI, PI2, TAU, E, INF, NaN, DB_CONVERSION_GAIN, GAIN_CONVERSION_DB, UINT32_MAX_F, DECIMAL_LIMIT_F) so all floats consistently use f32, and also make MAX_RECURSIONS use the project's integer alias (replace plain int with the aliased integer type used elsewhere) to complete the migration in one pass and avoid mixed-style declarations.engine/native/core/definitions/stdtypes.cppm (2)
9-28: 💤 Low valueOptional: use
std::-qualified names from<cstdint>/<cstddef>.Strictly,
<cstdint>and<cstddef>only guaranteestd::int8_t,std::uintptr_t,std::ptrdiff_t, etc.; the unqualified global names are an implementation extension (which GCC and Clang both provide, so this is not a correctness issue under the enforced toolchains). Using thestd::forms makes the module's source self-documenting and immune to future header hygiene changes.-using i8 = int8_t; -using i16 = int16_t; -using i32 = int32_t; -using i64 = int64_t; +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; ... -using uintptr = uintptr_t; -using ptrdiff = ptrdiff_t; +using uintptr = std::uintptr_t; +using ptrdiff = std::ptrdiff_t;🤖 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 `@engine/native/core/definitions/stdtypes.cppm` around lines 9 - 28, The type aliases currently use unqualified implementation-defined names (e.g. i8, i16, i32, i64, u8, u16, u32, u64, uintptr, ptrdiff, isize, usize); update them to use the std::-qualified typedefs from <cstdint> and <cstddef> (e.g. std::int8_t, std::uint32_t, std::uintptr_t, std::ptrdiff_t, std::size_t, etc.) and ensure the module includes the proper headers so aliases like i8, u32, uintptr, ptrdiff, isize, and usize refer to the std:: types instead of relying on global names.
23-24: ⚡ Quick win
isizeis hardcoded to 64-bit and decouples fromusize.
usizeisstd::size_t(platform-dependent), butisize = int64_tis fixed-width. On a hypothetical 32-bit target this givessizeof(isize) != sizeof(usize)andsizeof(isize) != sizeof(rawptr), defeating the typical "signed counterpart ofsize_t/pointer" semantics callers will assume (e.g. for pointer arithmetic, indexing, andptrdiff-like math).If 64-bit-only is an intentional project constraint, consider an explicit
static_assert(sizeof(void*) == 8). Otherwise, prefer a definition that tracks the platform:♻️ Suggested alternative
-using isize = int64_t; -using usize = std::size_t; +using usize = std::size_t; +using isize = std::make_signed_t<std::size_t>;🤖 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 `@engine/native/core/definitions/stdtypes.cppm` around lines 23 - 24, The typedef for isize is fixed to int64_t and can differ from usize/rawptr on non-64-bit targets; change isize so it tracks the platform's unsigned size/pointer type (e.g., use the signed counterpart of std::size_t via std::make_signed_t<std::size_t> or use std::ptrdiff_t as the signed pointer/size type) and replace the current using isize = int64_t; declaration; if the project truly requires 64-bit only, add an explicit static_assert checking sizeof(void*) == 8 and document the constraint instead of leaving isize hardcoded.engine/native/core/memory/trackingAllocator.test.cpp (1)
30-41: ⚡ Quick winAdd coverage for freeing the head and tail nodes of the tracking list.
Currently the test only frees the middle node (
bSlice), so theprev == nullptr(head) andnext == nullptr(tail) branches intracking::freeare never exercised. Adding frees forcSlice(head, since allocations are inserted at the head) andaSlice(tail) would catch the remaining unlink/head-pointer bugs flagged intrackingAllocator.cpp.Suggested additional assertions
alloc.free(bSlice); REQUIRE(tracking::getActiveAllocations(trackingAlloc, 2, details) == 2); REQUIRE(details[1].data.data == aSlice.data); REQUIRE(details[0].data.data == cSlice.data); + // Exercise tail removal (aSlice is currently the tail). + alloc.free(aSlice); + REQUIRE(tracking::getActiveAllocations(trackingAlloc, 1, details) == 1); + REQUIRE(details[0].data.data == cSlice.data); + // Exercise head removal (cSlice is the sole/head node). + alloc.free(cSlice); + REQUIRE(tracking::getActiveAllocations(trackingAlloc, 0, nullptr) == 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 `@engine/native/core/memory/trackingAllocator.test.cpp` around lines 30 - 41, The test currently only frees the middle allocation (bSlice) and never exercises the head/tail unlink branches in tracking::free; update the test to also free cSlice (the head, since inserts are at head) and then assert tracking::getActiveAllocations(trackingAlloc, N, details) and details order/counts and analytics reflect that removal, then free aSlice (the tail) and assert activeAllocationsCount becomes 0 (or that subsequent tracking::getActiveAllocations returns 0 and analytics.activeAllocationsCount == 0) before/after alloc.freeAll(); reference variables/functions: aSlice, bSlice, cSlice, alloc.free, tracking::getActiveAllocations, tracking::getAnalytics, analytics, trackingAlloc.
🤖 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.
Outside diff comments:
In `@engine/native/core/math/vector2.cppm`:
- Around line 56-69: In both Vector2::operator[](const i32) and the const
overload, change the if consteval branch to reject invalid indices at compile
time by asserting the index is 0 or 1 before returning the component; e.g.
inside the if consteval block add a compile-time check like static_assert(i == 0
|| i == 1, "Vector2 index out of range") (or equivalent consteval-safe check)
and then return the requested component (i ? y : x), so constexpr calls such as
v[99] fail instead of silently returning y.
In `@engine/native/core/math/vector3.cppm`:
- Around line 68-87: In Vector3::operator[](const i32) and its const overload,
the if consteval switch currently falls through the default to case 2 (returning
z) for out-of-range indices; change the default branch to reject invalid
constexpr indices by causing a hard error (e.g. throw std::out_of_range("Vector3
index out of range") or call an unreachable helper) instead of returning z so
constexpr access like v[99] fails at compile time; update both
Vector3::operator[](const i32) and Vector3::operator[](const i32) const
accordingly to match swizzle behavior.
In `@engine/native/core/math/vector4.cppm`:
- Around line 75-97: The consteval branches of Vector4::operator[](i32) and its
const overload currently map any out-of-range index to w, silently accepting bad
constexpr indexing; change the default case in both consteval switch statements
to reject invalid indices (e.g., throw std::out_of_range("Vector4 index out of
range") or call std::unreachable()/std::terminate) instead of returning w so
invalid constexpr indexing becomes a compile-time error and matches the swizzle
behavior.
In `@engine/native/core/memory/allocator.cppm`:
- Around line 72-89: Allocator::alloc currently forwards align unchecked to
vtbl->alloc which lets align==0 underflow or non-power-of-two values produce bad
behavior; validate align at the entrypoint (Allocator::alloc) by rejecting
align==0 and non-power-of-two aligns (e.g. check (align & (align - 1)) == 0) and
return an appropriate Error before calling vtbl->alloc, or add the same
validation to every exported allocator entrypoint (including bump::alloc and
fixed::alloc) to guarantee the precondition is enforced in release builds.
In `@engine/native/core/memory/trackingAllocator.cpp`:
- Line 130: The while-loop condition in trackingAllocator.cpp uses the bitwise
operator (&) between (node != nullptr) and (ret < detailsCount); change this to
the logical AND operator (&&) so the loop reads (node != nullptr) && (ret <
detailsCount) to get idiomatic short-circuiting behavior—locate the loop that
references variables node, ret, and detailsCount and replace & with &&.
---
Duplicate comments:
In `@engine/native/core/memory/fixedAllocator.test.cpp`:
- Around line 16-17: Test currently discards the result of alloc.alloc(&block,
512, 16) and immediately asserts alignment on block.data; first check that the
allocation succeeded by capturing the return value (or calling the allocator's
success predicate) and REQUIRE that alloc.alloc(...) returned true before
asserting (((uintptr_t)block.data) & 15) == 0, so that alignment is only tested
on a valid initialized block (referencing alloc.alloc and block.data in
fixedAllocator.test.cpp).
In `@engine/native/core/memory/trackingAllocator.cpp`:
- Around line 72-87: The free path in trackingAllocator.cpp must only add
sizeof(Node) to block.size and call allocData->base.vtbl->free when the block is
confirmed tracked: move the block.size += sizeof(Node) and the base.vtbl->free
call into the branch after you verify node is non-null (i.e., a tracked node),
and leave the original block unmodified for untracked frees; also protect the
unlink logic by checking prev for nullptr before doing prev->next = next, and
when node == allocData->nodes update allocData->nodes = next to advance the head
(and then perform memset(node,0,sizeof(Node))). Reference: the local variables
block, node, prev, next and methods allocData->base.vtbl->free and
allocData->nodes in trackingAllocator.cpp.
---
Nitpick comments:
In `@engine/native/core/definitions/stdtypes.cppm`:
- Around line 9-28: The type aliases currently use unqualified
implementation-defined names (e.g. i8, i16, i32, i64, u8, u16, u32, u64,
uintptr, ptrdiff, isize, usize); update them to use the std::-qualified typedefs
from <cstdint> and <cstddef> (e.g. std::int8_t, std::uint32_t, std::uintptr_t,
std::ptrdiff_t, std::size_t, etc.) and ensure the module includes the proper
headers so aliases like i8, u32, uintptr, ptrdiff, isize, and usize refer to the
std:: types instead of relying on global names.
- Around line 23-24: The typedef for isize is fixed to int64_t and can differ
from usize/rawptr on non-64-bit targets; change isize so it tracks the
platform's unsigned size/pointer type (e.g., use the signed counterpart of
std::size_t via std::make_signed_t<std::size_t> or use std::ptrdiff_t as the
signed pointer/size type) and replace the current using isize = int64_t;
declaration; if the project truly requires 64-bit only, add an explicit
static_assert checking sizeof(void*) == 8 and document the constraint instead of
leaving isize hardcoded.
In `@engine/native/core/math/constants.cppm`:
- Around line 14-37: The file has a partial migration to the draco alias f32;
update the remaining floating constants to use f32 (replace declarations for
SQRT2, SQRT3, SQRT12, SQRT13, LN2, LN10, PI, PI2, TAU, E, INF, NaN,
DB_CONVERSION_GAIN, GAIN_CONVERSION_DB, UINT32_MAX_F, DECIMAL_LIMIT_F) so all
floats consistently use f32, and also make MAX_RECURSIONS use the project's
integer alias (replace plain int with the aliased integer type used elsewhere)
to complete the migration in one pass and avoid mixed-style declarations.
In `@engine/native/core/memory/trackingAllocator.test.cpp`:
- Around line 30-41: The test currently only frees the middle allocation
(bSlice) and never exercises the head/tail unlink branches in tracking::free;
update the test to also free cSlice (the head, since inserts are at head) and
then assert tracking::getActiveAllocations(trackingAlloc, N, details) and
details order/counts and analytics reflect that removal, then free aSlice (the
tail) and assert activeAllocationsCount becomes 0 (or that subsequent
tracking::getActiveAllocations returns 0 and analytics.activeAllocationsCount ==
0) before/after alloc.freeAll(); reference variables/functions: aSlice, bSlice,
cSlice, alloc.free, tracking::getActiveAllocations, tracking::getAnalytics,
analytics, trackingAlloc.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c9069cf2-a158-4379-a989-a2d13dbfc3a7
📒 Files selected for processing (26)
engine/native/core/CMakeLists.txtengine/native/core/definitions/definitions.cppmengine/native/core/definitions/stdtypes.cppmengine/native/core/definitions/version.cppmengine/native/core/math/constants.cppmengine/native/core/math/functions.cppmengine/native/core/math/math.cppmengine/native/core/math/math.test.cppengine/native/core/math/types_common.cppmengine/native/core/math/vector2.cppmengine/native/core/math/vector3.cppmengine/native/core/math/vector4.cppmengine/native/core/memory/allocator.cppengine/native/core/memory/allocator.cppmengine/native/core/memory/bumpAllocator.cppengine/native/core/memory/bumpAllocator.cppmengine/native/core/memory/bumpAllocator.test.cppengine/native/core/memory/fixedAllocator.cppengine/native/core/memory/fixedAllocator.cppmengine/native/core/memory/fixedAllocator.test.cppengine/native/core/memory/pageAllocator.cppengine/native/core/memory/pageAllocator.cppmengine/native/core/memory/slice.cppmengine/native/core/memory/trackingAllocator.cppengine/native/core/memory/trackingAllocator.cppmengine/native/core/memory/trackingAllocator.test.cpp
New Features
Summary by CodeRabbit
New Features
Tests