Skip to content

Commit 4a7f881

Browse files
committed
feat: add versioning, state inspection, and expand fuzzer and tests
- Add Library Versioning: Introduced `ESTACK_VERSION` preprocessor macros for compile-time version comparisons. - Add State Inspection API: Implemented `estack_capacity`, `estack_count`, and `estack_free_space` as public APIs for querying stack state. - Improve `estack_destroy`: Added unconditional header invalidation to catch post-destroy API usage on static stacks. Added targeted memory poisoning of only active regions under `#ifdef ESTACK_POISONING`. - Expand Test Suite: Added exact-fit boundary and destroy poisoning validations. - Expand Fuzzer: Added support for static stack configurations, address jitter, multiple metadata cell sizes, and marker rollback validation.
1 parent 7fc0f7b commit 4a7f881

5 files changed

Lines changed: 339 additions & 47 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- 'easy_stack.h'
88
- 'Makefile'
99
- 'tests/**'
10+
- 'fuzzers/**'
1011
- '.github/workflows/**'
1112
pull_request:
1213
branches: [ "main", "master" ]
@@ -303,4 +304,30 @@ jobs:
303304
avr-size avr_test.elf
304305
305306
# Run the test binary inside the simavr emulator
306-
simavr -m atmega328p avr_test.elf
307+
simavr -m atmega328p avr_test.elf
308+
309+
# ==========================================
310+
# 7. FUZZING SMOKE TEST (libFuzzer)
311+
# ==========================================
312+
fuzz-smoke:
313+
name: fuzz-smoke
314+
runs-on: ubuntu-latest
315+
steps:
316+
- uses: actions/checkout@v6
317+
318+
- name: install-clang
319+
run: sudo apt-get update && sudo apt-get install -y clang
320+
321+
- name: run-fuzzer
322+
run: make fuzz_stack FUZZ_TIME=90
323+
324+
- name: upload-crash-artifacts
325+
if: failure()
326+
uses: actions/upload-artifact@v4
327+
with:
328+
name: fuzz-crashes
329+
path: |
330+
crash-*
331+
leak-*
332+
oom-*
333+
if-no-files-found: ignore

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<table>
22
<tr>
33
<td width="150" valign="middle">
4-
<img src=".github/assets/logo.jpg" width="150" alt="easy_stack logo" />
4+
<img src="https://raw.githubusercontent.com/EasyMem/easy_stack/refs/heads/main/.github/assets/logo.jpg" width="150" alt="easy_stack logo" />
55
</td>
66
<td valign="middle">
77
<div id="user-content-toc">
@@ -42,7 +42,7 @@
4242
* **Inverted Bi-Directional Layout:** Metadata offsets grow forward from the header (lowest addresses), while aligned user payloads grow backward from the end of the buffer (highest addresses). They meet in the middle. This physical segregation isolates metadata from payload alignment gaps, resulting in **zero alignment padding waste** in the control zone.
4343
* **Dynamic Metadata Bit-Width Scaling:** Unlike traditional stack allocators that prefix each allocation with a fixed-size inline header (typically 16 bytes on 64-bit platforms), `easy_stack` dynamically scales metadata cells to 1, 2, 4, or 8 bytes based on overall buffer capacity. For standard frame workloads (< 64 KB), offset cells take only 2 bytes—unlocking up to an **8x reduction in metadata overhead**.
4444
* **L1 Cache Line "Free Lunch" Optimization:** The compact `EStack` header requires only 2 machine words (16 bytes on 64-bit systems). By default, on modern desktop and application processors, the library automatically aligns the header boundary to 64-byte or 32-byte cache lines. This guarantees that fetching the stack header into cache automatically and instantly prefetches the **first 24 active metadata offsets** for **free**, bypassing main memory latency entirely.
45-
* **XOR-Hardened Stack Markers:** Rollback states (markers) are encrypted by XORing the current allocation index and signature with the stack's base memory address and `ESTACK_MAGIC`. This completely prevents cross-allocator marker pollution (e.g., passing Stack A's marker to Stack B) and detects forged marker rollbacks with **zero mathematical overhead** (1-cycle XOR instructions).
45+
* **XOR-Hardened Stack Markers:** Rollback states (markers) are masked by XORing the current allocation index and signature with the stack's base memory address and `ESTACK_MAGIC`. This catches cross-allocator marker pollution (e.g., passing Stack A's marker to Stack B) and accidental marker corruption with **zero runtime overhead** (1-cycle XOR instructions).
4646
* **Zero-Multiplication Boundary Checks:** Completely eliminates expensive CPU multiplication (`imul`) instructions from the critical allocation path. Since the metadata array cell widths are scaled strictly to powers of two ($1, 2, 4, \text{or } 8$ bytes), calculating the current metadata offset boundary is resolved using an ultra-fast bitwise shift left (`<< meta_type`). This reduces the boundary check to a single addition and shift, executing in just 1-2 CPU cycles.
4747
* **Arbitrary Power-of-Two Alignment:** Supports customized alignment boundaries (powers of two) for individual allocations, up to the stack's total capacity. Essential for SIMD vectors, cache-line aligned arrays, and hardware DMA buffers.
4848
* **Compiler Agnostic & Optimization Resilient:** Verified to work flawlessly across all compiler optimization levels (`-O1` through `-O3`, `-Os`, `-Oz`, `/O1`, `/O2`, `/Ox`). Built with strict adherence to **Strict Aliasing** rules, ensuring that aggressive compiler optimizations never break internal layout mechanics.
@@ -142,7 +142,7 @@ Rather than benchmarking only a single shallow depth, the suite tests scaling ac
142142
With a 64 KB capacity, `easy_stack` configures itself to use ultra-compact **Type 1 (16-bit)** metadata offsets.
143143

144144
<p align="center">
145-
<img src=".github/assets/throughput_64kb_chart.png" width="750" alt="Throughput Scaling vs Stack Allocation Depth at 64KB" />
145+
<img src="https://raw.githubusercontent.com/EasyMem/easy_stack/refs/heads/main/.github/assets/throughput_64kb_chart.png" width="750" alt="Throughput Scaling vs Stack Allocation Depth at 64KB" />
146146
</p>
147147

148148
At shallow depths (15 objects), the entire active allocator footprint (header + 30 bytes of metadata) fits completely into a **single 64-byte L1 cache line**. This yields a hardware-limit speed of **939 Million ops/sec** (~1.06 ns per allocation/free cycle) in Contract mode, outperforming the C++ template LIFO allocator by **371%**.
@@ -166,7 +166,7 @@ make bench DEPTH=100
166166
This ensures that at depth 100, the metadata array alone spans multiple cache lines, removing any artificial L1 prefetching advantages. Here is how the allocator scales under the heavier 32-bit math path:
167167

168168
<p align="center">
169-
<img src=".github/assets/throughput_1mb_chart.png" width="750" alt="Throughput Scaling vs Stack Allocation Depth at 1MB" />
169+
<img src="https://raw.githubusercontent.com/EasyMem/easy_stack/refs/heads/main/.github/assets/throughput_1mb_chart.png" width="750" alt="Throughput Scaling vs Stack Allocation Depth at 1MB" />
170170
</p>
171171

172172
#### Key Takeaways:
@@ -192,7 +192,7 @@ Traditional stack allocators prefix each block with a fixed 16-byte inline heade
192192
Below is a comparison of usable payload space in a **10 KB buffer** across different allocation sizes:
193193

194194
<p align="center">
195-
<img src=".github/assets/memory_efficiency_chart.png" width="700" alt="Memory Efficiency" />
195+
<img src="https://raw.githubusercontent.com/EasyMem/easy_stack/refs/heads/main/.github/assets/memory_efficiency_chart.png" width="700" alt="Memory Efficiency" />
196196
</p>
197197

198198
* **For 8-byte allocations:** `easy_stack` lets you fit **2.40x more objects** into the same memory buffer (80% usable memory vs. 33%).
@@ -295,7 +295,7 @@ EStack *stack = estack_create(2048);
295295

296296
void *p1 = estack_alloc(stack, 64);
297297

298-
// Take a secure, XOR-hardened snapshot of the current stack state
298+
// Take an XOR-hardened snapshot of the current stack state
299299
EStackMarker marker = estack_get_marker(stack);
300300

301301
// Make temporary allocations
@@ -377,7 +377,7 @@ Helps detect use-after-free and uninitialized memory usage.
377377
| `ESTACK_NO_ALIGN_HEADER` | *None* | Completely disables context header alignment (forces 1-byte boundary). Automatically enabled on 8/16-bit systems to eliminate padding waste. |
378378
| `ESTACK_DEFAULT_HEADER_ALIGNMENT` | *Auto* | Override the optimal context header alignment boundary (defaults to 64-byte for 64-bit, 32-byte for 32-bit platforms to prevent L1 cache line splits). |
379379
| `ESTACK_NO_BRANCH_HINTS` | *None* | Completely disables compiler branch prediction hints (`ESTACK_LIKELY` and `ESTACK_UNLIKELY`). |
380-
| `ESTACK_MAGIC` | `0xDEADBEEF..` | Magic number used for Stack Marker cryptographic XOR-encryption. |
380+
| `ESTACK_MAGIC` | `0xDEADBEEF..` | Magic number used for Stack Marker XOR integrity masking. |
381381

382382
---
383383

0 commit comments

Comments
 (0)