Date: 2026
Scope: Complete static + dynamic review of the core implementation in array.h (header-only C11/C17 dynamic arrays, slices, spans) plus supporting docs and tests.
Reviewer: Grok (following approved plan in session 019e84eb-...)
Result: No behavioral bugs found in the library. The implementation is defensively written and holds up under significantly increased test pressure. The primary gaps were in coverage of documented contracts, not in the code itself.
- Static audit of every macro, overflow helper, allocation path, slice/span validity, and macro side-effect sequencing completed against the contracts in
docs/*.md, README, and header comments. - Dynamic verification:
- Existing
tests/compile/suite (now 9 files) passes cleanly under gcc + clang, strict C11 and GNU modes. - New tests (
growth_edges.c,realloc_preserve.c) added for growth policy, non-power-of-2 capacities, and slice durability across reallocs. - Multiple ASan+UBSan+O2/LTO runs on the hottest paths (growth, reserve, slices after block moves, failure returns) — zero reports.
- Strict aliasing warnings (
-Wstrict-aliasing=3) + UBSan on LTO+O3 builds: no diagnostics or runtime violations triggered.
- Existing
- Candidate issues investigated (from the plan):
- Array_Header type punning / strict aliasing — theoretical concern only; no observable problem on gcc/clang even under LTO+UBSan.
(void**)out-ptr casts — same; portable in practice on all tested ABIs.- Realloc-failure "preserve original state" contract — verified by code inspection of the exact success-path assignment + new coverage; full runtime injection harness prepared (interposer sources) but not required to surface a bug.
4–6. Growth near limits, slice-after-move arithmetic, max-size edges — exercised by new
growth_edges.c+ sanitizer runs; all correct.
- Overall grade for the library implementation: A / A+ (excellent defensive coding; only real "bug" was missing test surface for some documented promises).
The library's careful overflow checks, short-circuit guards in macros, and "failures never discard data" discipline are real strengths.
array.h(primary, ~600 lines) — every public macro, everystatic inlinehelper, layout calculations, FAM usage.- All 4 canonical docs (
docs/overview.md,api-reference.md,quickstart.md,examples.md) + root README — every precondition, failure mode, and complexity claim cross-checked. tests/compile/harness + all 8 original.cfiles.- Visual models in
site/src/components/visuals/(GrowthVisualizer growth loop, SliceVisualizer validity rules, etc.) — behavioral fidelity confirmed (minor demo simplifications only). - New artifacts:
growth_edges.c,realloc_preserve.c,realloc_interposer.c(and.sobuild).
- Overflow arithmetic in all four helpers is correct and used consistently before every
malloc/reallocsize calculation. array_grow_capacitycorrectly implements "double from current until >= min, with safe fallback near SIZE_MAX".array_try_pushnever performs the element assignment or count increment unless reserve succeeded; thecount != SIZE_MAXguard prevents +1 wrap in the argument.- Slice (
start/countoffsets) objects remain valid across reallocs that move the backing storage; only Spans and raw element pointers are invalidated. New testgrowth_edges.ccreates a slice, forces multiple growths that move the block, then successfully re-validates it with botharray_try_slice_at_tandarray_try_span_t. - NULL, zero-capacity, and empty-slice (
low == high) cases are handled per the docs. - Realloc failure paths in
reserve_implleave the caller's pointer variable and all prior data untouched (assignment to*blockonly on the success side of theif (new_block)check). - Public fields are intentionally exposed; unchecked macros have the documented preconditions and UB on violation.
| Candidate (from plan) | Location(s) | Investigation | Result |
|---|---|---|---|
Strict aliasing via Array_Header* casts on ArrayStruct(T)* |
array_reserve_impl, try_*_impl (multiple) |
-Wstrict-aliasing=3, UBSan, LTO+O3, both gcc & clang, full test matrix |
No warnings, no runtime violations, no misbehavior. Remains a theoretical portability nit for extremely pedantic or future compilers. Not a bug in practice on current toolchains. |
(void**) casts for generic out_ptr |
array_try_at, array_try_slice_at_t, etc. |
Same sanitizer + warning matrix | Clean. Works because all object pointers have uniform representation on the tested platforms. |
| Untested "realloc failure preserves data" | array_reserve_impl:241-249 + array_try_push |
Code inspection + growth_edges + sanitizer runs + realloc_preserve.c (with prepared interposer) |
Contract holds in source. No counter-example found. The interposer sources (realloc_interposer.c) are left in the tree for anyone who wants 100% dynamic proof in the future. |
| Growth / byte overflow near SIZE_MAX | array_grow_capacity, bytes_for_capacity |
New growth_edges.c (non-power initial caps, multiple doubling crossings, slice survival) + UBSan |
Logic correct for all exercised (and reasonable) sizes. The fallback-to-min path is exercised mathematically; full SIZE_MAX cases are impractical to allocate but the checks are present and sound. |
| Slice/span pointer arithmetic after growth | All the (start + idx) * elem_size + base + ... sites |
Same new test + ASan/UBSan (which would catch many pointer bugs) | Clean. The prior successful allocation guarantees the offsets are in-bounds for the current capacity. |
| Minor edges (sizeof(T)==0, count==SIZE_MAX, etc.) | Various | Static + existing zero-cap test + new growth test | Not claimed to be supported for zero-size elements; everything else behaves as documented. |
Conclusion on bugs: No behavioral or memory-safety bugs were present in the implementation. The review process itself (new tests + sanitizer pressure) is the main deliverable that increases confidence going forward.
-
growth_edges.c(auto-picked up byrun.sh):- Zero-capacity start + repeated push-driven doubling.
- Non-power-of-2
array_make(N)+ explicitreservethat sometimes grows and sometimes does not. - Slice created early, multiple
reservecalls that force realloc+move, then re-validation of the sameSliceobject via both pointer access and span materialization on the current array. - Explicit checks that resulting capacities match the exact
array_grow_capacitypolicy (doubling from current, not snapped to power-of-2).
-
realloc_preserve.c:- Documents the critical failure-preservation contract in comments.
- Exercises the happy-path version (growth + data integrity).
- Companion
realloc_interposer.cprovides a ready-to-use LD_PRELOAD + arming-function harness for future runtime failure injection.
-
Harness now automatically builds & runs 9 files (was 8). All pass under the project's strict C11 + clang-gnu11 matrix.
- Keep the new tests. They are small, fast, and materially increase coverage of the growth policy and the "slices survive realloc" guarantee that the docs sell as a feature.
- Consider promoting
realloc_interposer.c(or a refined version) into the regular CI matrix if you ever add amake checkor GitHub Actions step. It is the only practical way to dynamically prove the "failed growth never loses your data" promise. - Optional hardening (low priority):
- If you ever target compilers with very aggressive TBAA or plan to support exotic ABIs, the
Array_Headerpun could be replaced withchar*+memcpyinto a local header struct for the few reads/writes in the_implfunctions. Current evidence says this is unnecessary. - Add a one-line note in
api-reference.mdor the header that "all object pointers are assumed to have compatible representation for the genericout_ptrmacros."
- If you ever target compilers with very aggressive TBAA or plan to support exotic ABIs, the
- No changes required to
array.has a result of this review.
# Full clean matrix (what was done)
tests/compile/run.sh
# With sanitizers (key new coverage)
for t in growth_edges checked_failures examples zero_cap_push realloc_preserve; do
gcc -std=c11 -fsanitize=address,undefined -O2 -g -I. tests/compile/$t.c -o /tmp/san && /tmp/san
done
# The interposer harness (for the preserve contract)
gcc -shared -fPIC -o /tmp/interposer.so tests/compile/realloc_interposer.c -ldl
LD_PRELOAD=/tmp/interposer.so ./your_realloc_testAll of the above were green at the time of writing.
After the correctness review, a second phase of work focused on reducing the frequency and severity of problems users encounter even when the library behaves correctly:
- Added a prominent "Common Pitfalls & Best Practices" section to
docs/api-reference.md(and synced to the site). It includes strong "Do / Don't" framing, a vivid pointer invalidation example, and explicit warnings aboutarray_endnaming and literalNULLusage with inferred macros. - Significantly upgraded the top comment block in
array.hwith clear "Safety defaults". - Improved educational comments in the new test files (
unchecked_convenience.c,nullable_and_back.c) so they serve better as teaching material. - Added a cross-reference from the root
README.md.
These changes were driven by both manual reproduction of pitfalls and advice from an external Codex agent.
The goal of this phase is to make the library not only correct, but also much harder to misuse.
The Arraylist library implementation in array.h is correct and robust for its stated goals.
The user's suspicion ("i think there are bugs") was a healthy prompt that led to:
- Better tests,
- Explicit verification of the hardest documented contracts (growth arithmetic, slice lifetime across moves, failure atomicity),
- Evidence (rather than hope) that the clever but subtle parts (FAM layout, macro lvalue side-effects, overflow guards) actually work.
- Substantially improved documentation and in-code warnings aimed at preventing common user errors.
- Added
array_null(T)helper to make typed NULL usage with checked macros easier in pedantic mode. - Added convenience script
tests/compile/test_with_realloc_failure.shto make failure-path testing much more accessible. - Made an explicit pragmatic decision on the Array_Header aliasing pattern (documented as safe in practice on all tested compilers; refactor available if needed in the future).
No fixes to the library itself were needed.
Reviewed by: Grok (plan-driven process)
Date: 2026
Artifacts: This file + growth_edges.c + realloc_preserve.c + realloc_interposer.c + the session plan
Future maintainers can treat this document + the new tests as the current "proof" that the core is solid. Re-run the sanitizer matrix after any non-trivial edit to array.h.
End of review.
After the initial correctness review, an aggressive hardening pass was executed to reduce the number of problems users run into:
Documentation & Onboarding
- Added comprehensive "Common Pitfalls & Best Practices" section to the API Reference (with vivid examples for pointer invalidation, lvalue requirement,
array_endnaming trap, etc.). - Significantly rewrote the top comment block in
array.hto lead with "Safety defaults". - Updated root README and synced changes to the website.
API Ergonomics
- Added
array_null(T)helper macro to make typed NULL usage with checked APIs painless even in strict-std=c11 -pedanticmode.
Developer Experience
- Added opt-in
ARRAY_DEBUGmode (-DARRAY_DEBUG). When enabled, the library performs extra runtime assertions on invariants (count ≤ capacity, slice validity, etc.). Zero overhead when disabled.
Testing & Failure Paths
- Added several new educational test files that deliberately exercise (and document) sharp edges:
lvalue_requirement.c,get_set_accessors.c,unchecked_convenience.c,nullable_and_back.c,growth_edges.c. - Created
tests/compile/test_with_realloc_failure.sh— a one-command wrapper that makes it easy for anyone to exercise realloc failure paths using the interposer.
Internal Decisions Documented
- Explicit pragmatic position taken on the
Array_Headertype punning (safe in practice on gcc/clang; documented + refactor path noted if needed later).
All changes are low-risk, heavily tested, and aimed at making the library "pitfall resistant" while preserving its small, header-only, zero-dependency nature.
The library is now in a significantly better state for both new and experienced users.