CHORE: Heavily cleaned up broken and redundant code
This commit is contained in:
@@ -1,325 +1,400 @@
|
|||||||
# libvec bug tracker
|
# libvec bug tracker
|
||||||
|
|
||||||
> **Verified** `char.c` @ **2026-09-28 12:48:04**, 3 independent passes.
|
**Pass 11** — verified **2026-10-09** — `char.c` @ 936 lines · `char.h` @ 108
|
||||||
> `char.h` 108 lines, `utils.h` 31, `utils.c` 12, `CMakeLists.txt` 32, `main.c` 55.
|
|
||||||
> Line numbers are the *only* field that drifts. Re-verify before trusting them.
|
|
||||||
>
|
|
||||||
> **Pass 4** @ **2026-09-28**, `push_back` / `pop_back` (char.c:67-157), ASan.
|
|
||||||
> **Pass 5** @ **2026-09-28**, whole file re-read plus 10 harness runs, ASan.
|
|
||||||
> Line count moved 804 to 800 with the `create` rewrite; re-measured per pass.
|
|
||||||
|
|
||||||
**Open 79** — 11 S1, 27 S2, 11 S3, 29 C
|
| Open | S1 | S2 | S3 | C | DECIDE | Fixed |
|
||||||
**1 DECIDE** · **Fixed 12** (listed at the bottom, no rows) · 79 rows
|
|:----:|:--:|:--:|:--:|:-:|:------:|:-----:|
|
||||||
|
| **49** | 4 | 16 | 7 | 22 | 0 | 58 |
|
||||||
|
|
||||||
**Severity** `S1` memory safety · `S2` wrong result / broken contract ·
|
`S1` memory safety · `S2` wrong result / broken contract · `S3` edge case · `C` cleanup · `DECIDE` open design question
|
||||||
`S3` edge case · `C` cleanup · `DECIDE` open design question
|
|
||||||
|
|
||||||
## How to read this
|
> **Line numbers are the only field that drifts.** Re-verify before trusting them.
|
||||||
|
|
||||||
Each function is a `###` with one table. Rows are sorted by severity (S1 first),
|
|
||||||
so the worst thing in each function is on top. Fixed findings are **not** rows —
|
|
||||||
they are a flat ID list at the bottom of the file.
|
|
||||||
|
|
||||||
Tracking state is a **column**, not a checkbox: a `State` cell carries more than
|
|
||||||
"done or not done" — `DECIDE` has no checkbox equivalent, and it is load-bearing
|
|
||||||
here. And task-list syntax inside a table cell is renderer-dependent, so a
|
|
||||||
`State` column is the form that survives any markdown preview. If your preview
|
|
||||||
*does* render checkboxes in tables and you want click-to-toggle, a leading `Done`
|
|
||||||
column can be added without disturbing anything else.
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
1. **IDs are permanent.** Never reused, never renumbered. An ID leaves the file
|
|
||||||
only when its row is deleted outright, and only under rule 3.
|
|
||||||
2. **Locations drift freely.** The only field updated in place.
|
|
||||||
3. **False findings are deleted, not archived.** If a row turns out to be my own
|
|
||||||
error, the row is removed. A new ID carries the accurate finding if there is
|
|
||||||
one. No `DISPUTED` state, no corrections log — a tracker is not a diary.
|
|
||||||
4. **State** is `OPEN` / `DECIDE`. `FIXED` means the row is gone and its ID is
|
|
||||||
in the bottom list.
|
|
||||||
5. **Fixed findings live in the ID list at the bottom**, nothing else.
|
|
||||||
|
|
||||||
## Triage — the 11 open S1s, in suggested order
|
|
||||||
|
|
||||||
| # | ID | Location | Summary |
|
|
||||||
|----|---|---|---|
|
|
||||||
| 1 | <code><nobr>RS-7</nobr></code> | char.c:175, 200 | `reserve` on a blank vector allocates 1024 but claims 5000. Ordinary `size`, live overflow |
|
|
||||||
| 3 | <code><nobr>PB-8</nobr></code> | char.c:89, 131 | live heap overflow, 2-line repro |
|
|
||||||
| 4 | <code><nobr>PB-14</nobr></code> | char.c:131, 154 | write through the freed buffer after `delete`, 2-line repro |
|
|
||||||
| 5 | <code><nobr>RS-1</nobr></code> | char.c:180-195 | use-after-free in `reserve`'s truncation loop, ASan confirmed |
|
|
||||||
| 6 | <code><nobr>IN-1</nobr></code> | char.c:783 | inverted `iter_end > iter_start` |
|
|
||||||
| 7 | <code><nobr>IN-2</nobr></code> | char.c:783 | dead bounds check sitting behind IN-1 |
|
|
||||||
| 8 | <code><nobr>DL-1</nobr></code> | char.c:293, 297 | `const` delete, double free on second call |
|
|
||||||
| 9 | <code><nobr>DL-2</nobr></code> | char.h:11-17 | no ownership marker, struct copy double-frees |
|
|
||||||
| 8 | <code><nobr>UT-2</nobr></code> | utils.c:3-12 | `error_messages[0]` / `[1]` are `NULL` |
|
|
||||||
| 9 | <code><nobr>PB-3</nobr></code> | char.c:106, 116 | capacity committed before the realloc check |
|
|
||||||
| 10 | <code><nobr>PB-15</nobr></code> | char.c:87, 128-131 | null-buffer path has no `else`, so `arr == NULL && size > 0` writes to NULL. Defensive |
|
|
||||||
|
|
||||||
> **Two of the eleven are the same class.** RS-7 and PB-8 both end in a
|
|
||||||
> write past a small block, and both come from a struct field claiming more
|
|
||||||
> bytes than were allocated: `capacity` set from the request rather than the
|
|
||||||
> block (RS-7, char.c:200), `calloc(1)` with `capacity` untouched (PB-8,
|
|
||||||
> char.c:89). One rule kills both: **never write `_internal_capacity` without
|
|
||||||
> writing the allocation it describes.** Verify with a single postcondition —
|
|
||||||
> after any function that touches capacity, the block size and the field must
|
|
||||||
> agree. CR-8 was a third member of this class; it is fixed as of Pass 5, and
|
|
||||||
> `create` now carries the same postcondition in one place (CR-9, char.c:44).
|
|
||||||
|
|
||||||
> **Fix IN-1 and IN-2 in the same commit.** The raw read in the 3-arg overload
|
|
||||||
> is unreachable *only because* IN-1's inversion empties the loop. Fixing IN-1
|
|
||||||
> alone converts a clean rejection into a live out-of-bounds read, because IN-2's
|
|
||||||
> bound check stays dead behind the short-circuit.
|
|
||||||
|
|
||||||
> **PB-8 and PB-14 are the only two rows in this file with a sanitizer trace
|
|
||||||
> attached.** Both reproduce in three lines from `main`. `delete` should null
|
|
||||||
> the struct's fields (DL-1) and push should size the materialization from
|
|
||||||
> `_internal_capacity` (PB-8); those two fixes close the whole S1 set for this
|
|
||||||
> function pair.
|
|
||||||
|
|
||||||
> **Reproducer harness**: `clang -std=c2x -g -fsanitize=address -Isrc/lib
|
|
||||||
> -Isrc/lib/char t.c src/lib/char/char.c src/lib/utils/utils.c`. `-std=c2x` is
|
|
||||||
> required — `char.c` uses `nullptr`, so the project does not compile as C17.
|
|
||||||
> Reproducers that ask for a multi-exabyte `calloc` additionally need
|
|
||||||
> `ASAN_OPTIONS=allocator_may_return_null=1`, or ASan aborts on the 2^61-byte
|
|
||||||
> `calloc` before the interesting line runs.
|
|
||||||
|
|
||||||
> **Defensive-only rows.** RZ-2, RS-2, PB-15 and PB-18 are not reachable
|
|
||||||
> without an allocation of ~2^61 bytes, or a hand-built struct. Kept as
|
|
||||||
> hardening because the guards are cheap and the invariant is worth stating.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bugs
|
## Contents
|
||||||
|
|
||||||
### `_vec_char_push_back` / `_vec_char_pop_back` — char.c:71, 136
|
- [Triage — the 4 open S1s](#triage)
|
||||||
|
- [Findings](#findings)
|
||||||
| ID | Sev | Location | Finding | State |
|
- [Core storage](#core-storage) — push_back/pop_back · cpy · reserve · shrink_to_fit · swap · delete · clear/print
|
||||||
|-----|---|---|---|---|
|
- [Validation & accessors](#validation--accessors) — __null_check · capacity/max_size/data · at · begin/end · front/back/empty
|
||||||
| `PB-8` | S1 | char.c:89, 131 | `calloc(1)` on the null-buffer path leaves `_internal_capacity` at 1024, so the 2nd `push_back` writes `arr[1]` one byte past a 1-byte block. **ASan: `heap-buffer-overflow` WRITE of size 1 at char.c:131**, block allocated at char.c:89 | OPEN |
|
- [Algorithms](#algorithms) — erase · resize · assign · insert
|
||||||
| <code><nobr>PB-14</nobr></code> | S1 | char.c:75, 131, 154 | neither function checks liveness. `_vec_char_delete` frees without clearing the struct (DL-1), so `delete(&v); push_back(&v,'z')` writes through the freed pointer. **ASan: `heap-use-after-free` WRITE of size 1 at char.c:131**, freed at char.c:297. Same for `pop_back`, which writes `'\0'` at char.c:154 | OPEN |
|
- [Support](#support) — utils · char.h · project
|
||||||
| <code><nobr>PB-15</nobr></code> | S1 | char.c:87, 128-131 | the materialize branch is guarded by `status == ERR_BUF_NULL && _internal_size == 0` and has no `else`. Any state with `arr == NULL && _internal_size > 0` falls through to char.c:126, takes `res = ptr->_internal_arr` (NULL) and writes `arr[size]`. No in-file producer, but nothing checks the invariant either | OPEN |
|
- [Fixed](#fixed)
|
||||||
| <code><nobr>PB-3</nobr></code> | S1 | char.c:106, 116 | `_internal_capacity` assigned before the `!res` check; on OOM the struct overstates its capacity and the next call writes past the real block. Both branches | OPEN |
|
- [Conventions](#conventions)
|
||||||
| <code><nobr>PB-9</nobr></code> | S2 | char.c:85-99| `push_back` materializes a null vector, making it a second path alongside `reserve`; the two disagree on allocation size (1 byte vs 1024) | OPEN |
|
|
||||||
| <code><nobr>PB-16</nobr></code> | S2 | char.c:116, 131 vs 154 | the growth `realloc` leaves the new tail uninitialized and `push_back` never writes a terminator at `_internal_size`, so `_vec_char_data` hands back a non-NUL-terminated buffer — `strlen(data(&v))` runs off the end. **ASan: `heap-buffer-overflow` READ of 2049 bytes past a 2048-byte region** grown at char.c:116. The lone `'\0'` write in either function is `pop_back`'s vacated slot, so termination is accidental | OPEN |
|
|
||||||
| <code><nobr>PB-21</nobr></code> | S2 | char.c:81, 104 | after any `reserve` the vector is permanently full: reserve sets `max_size = capacity`, so char.c:81 fails for good and `push_back` can only return `ERR_MAX_PASSED`. Duplicate view of AS-4, kept so this table stands alone | OPEN |
|
|
||||||
| <code><nobr>PB-17</nobr></code> | S3 | char.c:144-147 vs 87 | `pop_back` refuses to materialize while `push_back` does. A blank vector reports `ERR_BUF_NULL` (21) and logs a spurious "Buffer :- NULL" instead of `ERR_SIZE_ZERO` (22), so a `while (pop_back(&v) == SUCCESS)` drain reports the wrong reason | OPEN |
|
|
||||||
| <code><nobr>PB-18</nobr></code> | S3 | char.c:103, 116, 120-124 | `_internal_capacity == 0` makes `dst_cap` 0, and `realloc(arr, 0)` frees the block. The `!res` check then returns `ERR_ALLOC_FAIL` with `_internal_arr` dangling and capacity already committed as 0; the next push writes to freed memory. Capacity 0 is unreachable from this file today, so defensive | OPEN |
|
|
||||||
| <code><nobr>PB-20</nobr></code> | S3 | char.c:154 | only the vacated slot is zeroed, so bytes past `_internal_size` keep their values and are readable out of band through `data()`. `clear` zeroes the whole range and `erase` zeroes the tail; `pop_back` is the inconsistent one | OPEN |
|
|
||||||
|
|
||||||
> PB-1 and PB-2 were originally described as firing "on the very next call" and
|
|
||||||
> as "the clamp silently does nothing". Both claims were wrong in detail: PB-1
|
|
||||||
> needed `capacity < size+1 <= max_size < 2*capacity`, a band today's `VEC_MAX`
|
|
||||||
> never reaches, and PB-2's cause was `cap` being recomputed from the
|
|
||||||
> already-clobbered `capacity`, not the branch being skipped.
|
|
||||||
|
|
||||||
> **Pass-4 note.** Four pre-existing rows here were my own errors and were
|
|
||||||
> deleted: a "dead else" that is the common path, two `is_valid` double-call
|
|
||||||
> claims in a function that hoists, and an overflow that `VEC_MAX` makes
|
|
||||||
> impossible. Found by diffing the wording against the source, not by testing.
|
|
||||||
> The three new S1/S2 rows *were* tested; the reproducer command is above.
|
|
||||||
|
|
||||||
### `_vec_char_create` — char.c:23
|
|
||||||
|
|
||||||
> **Pass-5 note.** `create`'s growth block was rewritten this pass and the
|
|
||||||
> rewrite is correct. The invariant it now enforces in one place is
|
|
||||||
> `size <= _internal_capacity <= _internal_max_size` on return, held by the
|
|
||||||
> commit-back at char.c:44. Verified under ASan at `size` 0, 1, 1023, 1024,
|
|
||||||
> 1025, 2047, 2048, 2049, 2999, 5000 and `VEC_MAX - 1`: `capacity >= size` on
|
|
||||||
> every call, the `calloc` block matches the reported capacity, no overflow.
|
|
||||||
> One case to know about: `create(NULL, VEC_MAX - 1)` still returns
|
|
||||||
> `capacity=4611686018427387902` over `arr == NULL`, because growth is correct
|
|
||||||
> and the `calloc` then fails. That is CR-3 below, and it is unchanged.
|
|
||||||
> `dst_cap * 2` at char.c:42 cannot wrap, because the loop only doubles while
|
|
||||||
> the product stays under `_internal_max_size`. An intermediate revision of
|
|
||||||
> this fix omitted the commit-back and overflowed every `size > CAPACITY`; it
|
|
||||||
> never reached a build, so no ID was spent on it. Two `create` findings left
|
|
||||||
> the table this pass and their IDs are in the bottom list: **CR-7**, the
|
|
||||||
> second growth branch, which tested the ceiling (`capacity*2 > max_size`)
|
|
||||||
> instead of the loop's own postcondition — a superset that also fired after a
|
|
||||||
> successful grow, overwriting `capacity` with `size` and forcing the next
|
|
||||||
> `push_back` to reallocate for one byte; its `>` test also disagreed with the
|
|
||||||
> loop's `>=` exit on exactly one value, reachable only because `VEC_MAX` is
|
|
||||||
> odd. **CR-8**, the `>=` guard at char.c:32, which left `size == VEC_MAX`
|
|
||||||
> allocating 1024 bytes and copying `VEC_MAX` bytes into it. Both are gone
|
|
||||||
> because the branch they described no longer exists, not because either was
|
|
||||||
> re-filed under a new ID.
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>CR-6</nobr></code> | S2 | char.c:29, 39-45| null vector's `_internal_capacity` is set to `CAPACITY` (1024) while nothing is allocated. Currently **masked** by AC-1: `capacity()` returns `ERR_BUF_NULL` first, so the lie is not observable through the public API. The observable form of this same lie is PB-8, after `push_back` materializes the buffer | OPEN |
|
|
||||||
| <code><nobr>CR-3</nobr></code> | S3 | char.c:48-53 | on `calloc` failure returns a struct with an already-grown `capacity` and `arr == NULL`. Defensive. **Now reachable with an ordinary `size`, not just a huge one**: `create(NULL, 5000)` grows `capacity` to 8192 at char.c:44, then the 8192-byte `calloc` fails and the struct survives claiming 8192 over a null buffer | OPEN |
|
|
||||||
| <code><nobr>CR-4</nobr></code> | DECIDE | char.c:58-65 | no NUL terminator; is a `const char* src, size_t size` pair a byte range or a C string? Affects whether a `from_cstr` helper is needed | DECIDE |
|
|
||||||
|
|
||||||
### `_vec_char_is_valid` — char.c:6
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>IV-1</nobr></code> | S2 | char.c:6 | always returns non-zero, so `if (_vec_char_is_valid(&v))` is true for every input including NULL. Every call site compares codes by luck | OPEN |
|
|
||||||
| <code><nobr>IV-3</nobr></code> | S2 | char.c:6-17| `is_valid` is the only validation primitive in the file and it requires a non-null **buffer**, not just a non-null vector. Every accessor that only needs to validate the vector pointer therefore inherits `ERR_BUF_NULL`. This is the root cause of AC-1, AC-2, SW-1 and ST-2 | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_reserve` — char.c:156
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>RS-7</nobr></code> | S1 | char.c:175, 200 | the `ERR_BUF_NULL` path allocates `ptr->_internal_capacity` bytes, not the requested `capacity`, then line 200 sets `_internal_capacity = capacity` regardless. So `reserve(&v, 5000)` on a blank vector `calloc`s **1024** bytes and the struct claims 5000. **ASan: `heap-buffer-overflow` WRITE at char.c:131, 0 bytes after a 1024-byte region allocated at char.c:175**, reached by 1026 `push_back`s. The `SUCCESS` path at char.c:184 sizes the `realloc` correctly; only the null-buffer path is wrong. Second producer of PB-8, and unlike CR-7 this one needs no huge `size` — 5000 is ordinary | OPEN |
|
|
||||||
| <code><nobr>RS-1</nobr></code> | S1 | char.c:180, 186-193, 195| realloc result held in `res`, but the truncation loop writes `ptr->_internal_arr`, which is still the pre-realloc pointer. Use-after-free when a shrinking `reserve` moves the block. Introduced by the FX-3 fix. **ASan confirmed**: `create("abcdefgh",8)` then `reserve(3)` gives `heap-use-after-free WRITE` at char.c:194 into a block freed at char.c:184 | OPEN |
|
|
||||||
| <code><nobr>RS-2</nobr></code> | S3 | char.c:160-164 | `capacity == 0` checked before the NULL-vector check, so a NULL vector with `capacity == 0` reports `ERR_SIZE_ZERO` instead of `ERR_VECTOR_NULL`. Defensive | OPEN |
|
|
||||||
| <code><nobr>RS-6</nobr></code> | C | char.c:158, 178, 195| clang `-Wsometimes-uninitialized`: `res` used uninitialized at 195 if the `if`/`else if` chain falls through. Not reachable today since `is_valid` returns exactly 3 values, but real fragility | OPEN |
|
|
||||||
| <code><nobr>RS-3</nobr></code> | C | char.c:197 | `max_size = capacity` is correct under the hard-cap semantics but is documented nowhere. **This is also the cause of ST-1 and AS-4** | OPEN |
|
|
||||||
| <code><nobr>RS-4</nobr></code> | C | char.c:188-191 | element-at-a-time loop, should be `memset` | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_shrink_to_fit` — char.c:202
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>ST-1</nobr></code> | S2 | char.c:202, 217 -> 197| calls `reserve(ptr, _internal_size)`, and `reserve` sets `max_size = capacity`. So `max_size` is permanently capped to the current size and **the vector can never grow again** — every later `push_back` trips `size+1 > max_size` and returns `ERR_MAX_PASSED`. Silent brick. No save/restore, unlike `resize` (RZ-3) | OPEN |
|
|
||||||
| <code><nobr>ST-2</nobr></code> | S3 | char.c:204-211| returns `ERR_SIZE_ZERO` on an empty vector; shrinking an empty vector should be a successful no-op. Two unhoisted `is_valid` calls at 204 and 208 | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_swap` — char.c:221
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>SW-1</nobr></code> | S2 | char.c:229-233 | refuses NULL-buffer operands, so a blank vector can never be swapped against a populated one. Blocks the `create(nullptr, 0)` workflow outright | OPEN |
|
|
||||||
| <code><nobr>SW-2</nobr></code> | C | char.c:223 | temp built via `_vec_char_create(nullptr, 0)`; a plain initializer states the intent | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_delete` — char.c:289
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>DL-1</nobr></code> | S1 | char.c:289, 293 | takes `const *` so it cannot reset the vector; a second call is a double free. Only function still taking `const` in a mutable API | OPEN |
|
|
||||||
| <code><nobr>DL-2</nobr></code> | S1 | char.h:11-17 | no ownership marker on the struct, so a by-value copy yields two owners of one buffer and `delete` cannot detect it | OPEN |
|
|
||||||
| <code><nobr>DL-3</nobr></code> | C | char.c:293 | unnecessary `(void*)` cast; `const` on the struct does not propagate to the pointee | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_capacity` / `_vec_char_max_size` / `_vec_char_data` — char.c:261, 275, 252
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>AC-1</nobr></code> | S2 | char.c:254, 267, 281 | all three return `ERR_BUF_NULL` for a null vector, though none of them needs a buffer. `capacity()` should report capacity, not an error | OPEN |
|
|
||||||
| <code><nobr>DA-1</nobr></code> | S2 | char.c:251, 257| `char*` returned from a `const _VecCharInternal*`, laundering the const-correctness the mutable refactor was meant to establish | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_clear` / `_vec_char_print_vec` — char.c:298, 341
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>CL-1</nobr></code> | C | char.c:310-313 | element-at-a-time zeroing loop, should be `memset` | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_at` — char.c:320
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>AT-1</nobr></code> | S2 | char.c:323, 328, 335| returns error codes (23/25, and 21) as a `char`; `assign` at char.c:642 pushes such a value into the vector as data | OPEN |
|
|
||||||
| <code><nobr>AT-2</nobr></code> | C | char.c:332 | raw `fprintf` bypasses `LOGERROR` | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_begin` / `_vec_char_end` — char.c:352, 359
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>BE-1</nobr></code> | S2 | char.c:355, 365, 367, 630, 668 | `FAILURE` (`== 1`) returned as an index, indistinguishable from index 1. Single root cause of the `+ 1` at char.c:630 and char.c:668, of the assign-on-empty failures AS-1, and of the erase-on-empty failure ER-1 | OPEN |
|
|
||||||
| <code><nobr>BE-2</nobr></code> | C | char.c:364 | `(int)` cast on a `size_t` return | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_front` / `_vec_char_back` / `_vec_char_empty` — char.c:371, 379, 387
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>AC-2</nobr></code> | S2 | char.c:392, 393 | inverted: `SUCCESS` (0) when empty, `FAILURE` (1) when not, so `if (empty(v))` is false for an empty vector | OPEN |
|
|
||||||
| <code><nobr>FB-1</nobr></code> | S2 | char.c:373, 374, 381, 382 | returns `FAILURE` (1) as a `char`; `1` is valid data | OPEN |
|
|
||||||
| <code><nobr>FB-2</nobr></code> | C | char.c:373, 381 | validates only `ERR_VECTOR_NULL`, unlike every other accessor | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_erase` (both overloads) — char.c:398, 412
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>ER-1</nobr></code> | S2 | char.c:417, 418 | `iter_start >= iter_end` returns `ERR_OUT_OF_BOUNDS`, so `erase(begin(), end())` on an empty vector always errors. This is what breaks both `assign` overloads. On an empty vector the actual trip is char.c:417 (`iter_end > size`) because `end()` returned 1 and the caller added 1 — see BE-1 | OPEN |
|
|
||||||
| <code><nobr>ER-2</nobr></code> | C | char.c:396, 410, 422-426 | zeroing loop should be `memset`; two stale `// TODO: Fix return value` comments | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_resize` (both overloads) — char.c:432, 518
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>RZ-8</nobr></code> | S2 | char.c:434-444, 520-530 | both overloads reject a null-buffer vector with `ERR_BUF_NULL` before the `_internal_size == 0` branch that would have materialized one, so `resize` cannot be the first call on a blank vector. `push_back` can. **ASan: `create(NULL,0)` then `resize(0,4)` returns 21** and the vector is still empty, where `push_back` four times would have worked. Same shape as PB-17, opposite direction | OPEN |
|
|
||||||
| <code><nobr>RZ-3</nobr></code> | S2 | char.c:456, 471, 478, 497, 540, 557, 569, 579| the `max_size` save/restore is load-bearing (it undoes `reserve`'s cap) but looks removable. Nothing documents that | OPEN |
|
|
||||||
| <code><nobr>RZ-1</nobr></code> | S2 | char.c:442-446, 528-532| `size == _internal_size` returns `ERR_OUT_OF_BOUNDS` for a legitimate no-op; the code is also semantically wrong | OPEN |
|
|
||||||
| <code><nobr>RZ-4</nobr></code> | S3 | char.c:501-504 vs 583-586| 3-arg fills the discarded tail with `val`, 2-arg with `'\0'`. `data()` exposes the difference | OPEN |
|
|
||||||
| <code><nobr>RZ-2</nobr></code> | S3 | char.c:481, 567 | unguarded `tmpcp *= 2` wraps on overflow. Defensive | OPEN |
|
|
||||||
| <code><nobr>RZ-5</nobr></code> | C | char.c:462, 492, 548 | dead post-`reserve` capacity checks; `reserve` always assigns `capacity` to its argument exactly | OPEN |
|
|
||||||
| <code><nobr>RZ-6</nobr></code> | C | char.c:432-515 vs 518-597 | two ~84-line near-identical bodies, with the growth blocks copy-pasted between them | OPEN |
|
|
||||||
| <code><nobr>RZ-7</nobr></code> | C | char.c:444, 467, 501, 508, 530, 553, 583, 590| fill loops should be `memset`; misleading `LOG("ERR :- ...")` on what is now a no-op | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_assign` (both overloads) — char.c:600, 652
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>AS-1</nobr></code> | S2 | char.c:630, 668 | fails on an empty destination, via ER-1. Fatal now because status is propagated; the immutable version discarded it and continued | OPEN |
|
|
||||||
| <code><nobr>AS-2</nobr></code> | S2 | char.c:624-647, 661-687| not atomic: `erase` → `reserve` → `push_back` loop. Reserve failure leaves the destination wiped; a `push_back` failure mid-loop leaves it partially filled | OPEN |
|
|
||||||
| <code><nobr>AS-3</nobr></code> | S2 | char.c:624, 662, 679| no `> _internal_max_size` guard on the assign count | OPEN |
|
|
||||||
| <code><nobr>AS-4</nobr></code> | S2 | char.c:635, 673 | the internal `reserve` caps `max_size` at the assign count with no save/restore, which `resize` does have | OPEN |
|
|
||||||
| <code><nobr>AS-6</nobr></code> | C | char.c:600-649 vs 652-688 | near-duplicate overloads differing only in the value source | OPEN |
|
|
||||||
| <code><nobr>AS-7</nobr></code> | S3 | char.c:642 | no check that `at()` succeeded before pushing its result. Currently correct **only** because the guards at char.c:611-618 and char.c:624 happen to make every index in range and the buffer non-null. Removing or loosening either guard turns this into AT-1's data-corruption case | OPEN |
|
|
||||||
|
|
||||||
### `_vec_char_insert` (all three overloads) — char.c:691, 726, 759
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>IN-1</nobr></code> | S1 | char.c:779 | inverted check: `iter_end > iter_start` should be `iter_end <= iter_start`, so every valid non-empty range is rejected and only an empty range is accepted | OPEN |
|
|
||||||
| <code><nobr>IN-2</nobr></code> | S1 | char.c:779 | that clause short-circuits first, so the `iter_end > iterator->_internal_size` bound never fires | OPEN |
|
|
||||||
| <code><nobr>IN-4</nobr></code> | S2 | char.c:742, 747-754 | no `max_size` pre-check, so an oversized `amount` partially inserts and then fails, leaving the vector half-modified | OPEN |
|
|
||||||
| <code><nobr>IN-6</nobr></code> | S3 | char.c:747 | `iter + amount` can overflow `size_t` | OPEN |
|
|
||||||
| <code><nobr>IN-5</nobr></code> | S3 | char.c:742 | `amount <= 0` on a `size_t` is always just `== 0` | OPEN |
|
|
||||||
| <code><nobr>IN-8</nobr></code> | S2 | char.c:747-754, 793 | the loop copies via the raw `iterator->_internal_arr[i]` rather than a bounds-checked accessor. It is **unreachable today** only because IN-1's inversion makes `iter_end <= iter_start` always true, so the loop never runs. It becomes a live out-of-bounds read the moment IN-1 is fixed, unless IN-2 is fixed in the same change | OPEN |
|
|
||||||
| <code><nobr>IN-9</nobr></code> | C | char.c:747-754 | O(amount²) loop of single inserts where one grow + `memmove` would do | OPEN |
|
|
||||||
|
|
||||||
### `utils.h` / `utils.c`
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>UT-2</nobr></code> | S1 | utils.c:3-12 | indices 0 and 1 are never assigned, so `error_messages[0]` and `[1]` are `NULL`. `LOGERROR(SUCCESS)` prints `%s` of `NULL`, and both are values functions return. Latent only because no caller exists yet | OPEN |
|
|
||||||
| <code><nobr>UT-1</nobr></code> | S2 | utils.h:29 | `SIZE_MAX / sizeof(int)` is what the `char` vector uses, capping it at 1/4 of the usable address space | OPEN |
|
|
||||||
| <code><nobr>UT-3</nobr></code> | S2 | utils.c:3 | `SUCCESS`/`FAILURE` overlap the error-index namespace, so `0` is both "success" and a table slot | OPEN |
|
|
||||||
| <code><nobr>UT-5</nobr></code> | C | utils.h:23 | `DEBUG` prints the literal `DEBUB: `, so char.c:109 — the only `DEBUG` call in the tree, on the clamp path in `push_back` — emits a misspelled prefix | OPEN |
|
|
||||||
| <code><nobr>UT-4</nobr></code> | C | utils.h:7, 17, 20, 22-25, 27, 29 · utils.c:6, 10 | `static const` in a header gives every TU its own copy; `ERR_NULL` is now dead (defined and given a message, never referenced from `char.c`); `ERR_SIZE_ZERO` message mentions `< 0` on a `size_t`; `extern const char*` should be `const char* const[]`; `LOG`/`LOGERROR` are hardwired to stdout and single-arg; `find_len` is UB for pointers; `CAPACITY` is a magic number in a shared header | OPEN |
|
|
||||||
|
|
||||||
### `char.h`
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>CH-9</nobr></code> | S2 | char.h:11-106 (29 identifiers)| every public identifier is reserved by the C standard: `_VecCharInternal` and all 28 `_vec_char_*` functions begin with `_` at file scope, which C11 7.1.3 reserves for the implementation. Undefined behaviour on collision with any libc or third-party name. `clang -Weverything` reports all 28 | OPEN |
|
|
||||||
| <code><nobr>CH-4</nobr></code> | C | char.h:6-8 | `<stdio.h>`/`<stdlib.h>`/`<string.h>` in the header, only needed by the `.c` | OPEN |
|
|
||||||
| <code><nobr>CH-5</nobr></code> | C | char.h | no doc comments on any function, despite the `/**< */` style in utils.h | OPEN |
|
|
||||||
| <code><nobr>CH-7</nobr></code> | C | char.h:22-23 | no `from_cstr` / NUL-termination path for the `char` specialization. See CR-4 | OPEN |
|
|
||||||
| <code><nobr>CH-8</nobr></code> | C | char.h:11-17 | the public struct is named with a `_` prefix and its four fields are named `_internal_*` instead of being `PRIVATE`, so the "internal" contract is expressed only by spelling. Combined with CH-9 this is the whole naming problem | OPEN |
|
|
||||||
| <code><nobr>CH-1</nobr></code> | C | char.h:32 vs char.c:156 | param named `max_size` in the header, `capacity` in the definition | OPEN |
|
|
||||||
| <code><nobr>CH-2</nobr></code> | C | char.h:23-106 | `const` on by-value params throughout (`const size_t size`, `const char val`, `const size_t idx`, …) | OPEN |
|
|
||||||
| <code><nobr>CH-3</nobr></code> | C | char.h | no `size()` accessor, so callers reach into `_internal_size` directly | OPEN |
|
|
||||||
|
|
||||||
### Project
|
|
||||||
|
|
||||||
| ID | Sev | Location | Finding | State |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| <code><nobr>PR-2</nobr></code> | C | char.c:1-3 | mixed brace style (single-line `if` in `erase`/`front`/`back`/`begin`/`empty` vs expanded blocks); `.clang-format` exists and is not run | OPEN |
|
|
||||||
| <code><nobr>PR-3</nobr></code> | C | char.c:204, 208, 224, 225, 229, 230, 254, 255, 263, 267, 277, 281, 291, 300, 304, 322, 326, 343, 344, 354, 361, 373, 381, 389, 390, 400, 401, 415, 416, 434, 438, 520, 524, 603, 607, 611, 615, 654, 658, 693, 697, 763, 767, 771, 775| `is_valid` called 2-3× per function and logging each time, in every function except `push_back`, `pop_back` and `reserve` — those three hoist | OPEN |
|
|
||||||
| <code><nobr>PR-7</nobr></code> | C | CMakeLists.txt:28, main.c:20-54 | the `test_runner` target exists but `main()` is entirely commented out, so nothing in the tree calls a single function in this tracker. This is the only place a regression in the 10 open S1s could be caught, and it is dead | OPEN |
|
|
||||||
| <code><nobr>PR-4</nobr></code> | C | main.c:23-53 | 100% commented out, references removed API `Vec8_t` and `v1.size` | OPEN |
|
|
||||||
| <code><nobr>PR-5</nobr></code> | C | include/libvec.h:4, CMakeLists.txt:12-14 | empty placeholder; `int`/`float`/`string` are empty dirs already listed as comments in CMakeLists.txt | OPEN |
|
|
||||||
|
|
||||||
## ID map
|
|
||||||
|
|
||||||
`FX-1` … `FX-5` are first-pass IDs. FX-1 was a duplicate of PB-6 and was never
|
|
||||||
given a row.
|
|
||||||
|
|
||||||
| Prefix | Scope | | Prefix | Scope |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `PB-` | push_back, pop_back | | `AT-` | at |
|
|
||||||
| `RS-` | reserve | | `FB-` | front, back |
|
|
||||||
| `ST-` | shrink_to_fit | | `BE-` | begin, end |
|
|
||||||
| `CR-` | create | | `SW-` | swap |
|
|
||||||
| `AC-` | accessors | | `CL-` | clear, print_vec |
|
|
||||||
| `DA-` | data | | `IV-` | is_valid |
|
|
||||||
| `DL-` | delete | | `UT-` | utils.h, utils.c |
|
|
||||||
| `ER-` | erase | | `CH-` | char.h |
|
|
||||||
| `RZ-` | resize | | `PR-` | project |
|
|
||||||
| `AS-` | assign | | `FX-` | retired first-pass IDs |
|
|
||||||
| `IN-` | insert | | | |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
`PB-1` `PB-2` `PB-4` `PB-6` `PB-7` `FX-2` `FX-3` `FX-4` `FX-5` `PR-1` `CR-7` `CR-8`
|
<a id="triage"></a>
|
||||||
|
|
||||||
|
## Triage — the 4 open S1s
|
||||||
|
|
||||||
|
In suggested fixing order.
|
||||||
|
|
||||||
|
| # | ID | Location | Summary |
|
||||||
|
|:-:|:---|:---------|:--------|
|
||||||
|
| 1 | `RS-1` | char.c:236, 243-249 | truncation is zeroed through the post-realloc pointer: `memset(&arr[capacity], 0, size - capacity)` (246) writes past the shrunk block → `heap-buffer-overflow`. ASan Pass 11: WRITE at char.c:246 for push 4 + `reserve(&v, 2)` |
|
||||||
|
| 2 | `FB-1` | char.c:423-428, 441-446 | `front`/`back` guard inverted: valid vectors return 0 + `ERR_OUT_OF_BOUNDS`, drained vectors fall through to `*at()` nullptr deref → SEGV at 428. `back` probes index 0 (441) |
|
||||||
|
| 3 | `IN-10` | char.c:865-872 | amount-insert never validates: guard at 866 reads a fresh local (always 0), NULL `ptr` derefs at 872 → SEGV |
|
||||||
|
| 4 | `DL-2` | char.h:11-17 | no ownership marker, by-value struct copy double-frees |
|
||||||
|
|
||||||
|
### Reading notes
|
||||||
|
|
||||||
|
> **Blank-state shapes (PB-9).** Three blank-materialize paths, all in-bounds since Pass 11: `push_back` `malloc(1)` → `capacity=1, size=0`, then WRITE `arr[0]` (140-152). `reserve` `calloc(capacity)` → `capacity=capacity` (221-232). `insert` one-arg `malloc(1)` → `capacity=1, size=1`, WRITE `arr[0]` (818-832). Same input state (blank vector), three allocations, three struct shapes; `insert` and `resize` also disagree with `push_back` on which blank states are accepted.
|
||||||
|
|
||||||
|
> **`max_size` is no longer capped by `reserve`.** The old `ptr->_internal_max_size = capacity;` line is gone entirely — it was commented out at 249 in Pass 10 and deleted in Pass 11. PB-21, ST-1, AS-4 and RS-3 stay retired.
|
||||||
|
|
||||||
|
> **Sanitizer traces.** Rows with ASan output from Pass 6-11: FB-1 (SEGV on 0x0 at char.c:428), IN-10 (SEGV on 0x8 at char.c:872), RS-1 (heap-buffer-overflow WRITE at char.c:246), DL-2 (double-free — struct copy, two deletes). All reproducers are under 25 lines.
|
||||||
|
|
||||||
|
> **Defensive-only rows.** RZ-2 and PR-8 are not reachable without an allocation of ~2^61 bytes, a hand-built struct, or a NULL `err`. Kept as hardening because the guards are cheap and the invariant is worth stating. (CR-11 and RS-2 were in this class and are now fixed.)
|
||||||
|
|
||||||
|
> **Documented-by-contract (Pass 10).** `char.c` gained a DESIGN CONTRACT block (11-32): raw byte container, buffers NEVER NUL-terminated, `err` must be non-NULL, slots past `size` are uninitialized. That resolved PB-16, PB-20, CR-4 and CH-7 — the rows did not lie, the contract now says the behavior is intended.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
<a id="core-storage"></a>
|
||||||
|
|
||||||
|
### Core storage
|
||||||
|
|
||||||
|
<a id="pb"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_push_back` / `_vec_char_pop_back` — char.c:120, 181
|
||||||
|
|
||||||
|
*1 S2*
|
||||||
|
|
||||||
|
> PB-17, PB-14 and PB-15 left this block in Pass 10 and were re-verified in Pass 11 (harness: drained pop → flags=0, size stays 0 — origin POP_BACK is set without any flag, 190-194; `delete; push_back` → err=0, size=1, arr[0]='z'; hand-built `{NULL, 5, …}` push → size 1, no overflow). PB-16/20 retired as documented-by-contract; PB-21 retired with the removed `max_size` line.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `PB-9` | S2 | char.c:140-152 vs 221-232 vs 818-832 | three blank-materialize paths, no shared shape. `push_back`: `malloc(1)`, `capacity=1`, `size=0`, writes `arr[0]`. `reserve`: `calloc(capacity)` and commits `capacity` (correct since Pass 11, RS-7). `insert` one-arg: `malloc(1)`, `capacity=1`, `size=1`, writes `arr[0]` (in-bounds since Pass 11, IN-11). Same input state, three different allocations, three different struct results. `insert` and `resize` also disagree with `push_back` on which blank states are accepted |
|
||||||
|
|
||||||
|
<a id="cpy"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_cpy` — char.c:82
|
||||||
|
|
||||||
|
*none open*
|
||||||
|
|
||||||
|
> CR-4 retired in Pass 10 (the DESIGN CONTRACT answers the byte-range vs C-string question, char.c:11-16). **CR-10 and CR-11 retired in Pass 11**: `malloc` failure now reports `ERR_ALLOC_FAIL` (108-112), not `ERR_BUF_NULL`, and `src_size` is guarded against `_internal_max_size` (101-106).
|
||||||
|
|
||||||
|
<a id="reserve"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_reserve` — char.c:200
|
||||||
|
|
||||||
|
*1 S1*
|
||||||
|
|
||||||
|
> RS-8 retired in Pass 7, RS-3 in Pass 10 (no more `max_size` capping). **Three retired in Pass 11**: RS-7 (the blank path is now `calloc(capacity)` at 223 and commits exactly the requested capacity — harness: `reserve(VEC_INIT, 5)` → cap=5, err=0), RS-4 (truncation is `memset`, 246-247) and RS-2 (guard restructured, 205-219 — a NULL vector now reports `ERR_VEC_NULL` before the `capacity == 0` check). **RS-1 remains open and got worse** — the fix moved the assignment before the truncation and turned a use-after-free into a heap-buffer-overflow.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `RS-1` | S1 | char.c:236, 243-249 | `ptr->_internal_arr = res` (243) runs before the truncation, so the discarded tail is zeroed through the NEW pointer — but `realloc` already shrunk the block to `capacity` bytes, and `memset(&ptr->_internal_arr[capacity], 0, ptr->_internal_size - capacity)` (246-247) writes past it. **ASan Pass 11: `heap-buffer-overflow` WRITE at char.c:246** — push 4, `reserve(&v, 2)` (block is 2 bytes, WRITE lands 0 bytes after). The tail to zero no longer exists after a shrinking realloc: the memset must run on the old buffer before `realloc`, or be dropped — spare slots are uninitialized by contract |
|
||||||
|
|
||||||
|
<a id="shrink"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_shrink_to_fit` — char.c:256
|
||||||
|
|
||||||
|
*none open*
|
||||||
|
|
||||||
|
> ST-1 retired in Pass 10. **ST-2 retired in Pass 11**: the drained no-op landed (267-270) before the forward to `reserve`; harness: push 2, pop 2, shrink → err=0, size=0 (capacity keeps its old value — shrink of an empty vector is a successful no-op). Blank vectors still take the `ERR_BUF_NULL` path at 261-266 — pre-existing, intended. The double `__null_check` at 260-261 is redundant but harmless.
|
||||||
|
|
||||||
|
<a id="swap"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_swap` — char.c:274
|
||||||
|
|
||||||
|
*none open*
|
||||||
|
|
||||||
|
> SW-2 retired in Pass 7. **SW-1 retired in Pass 11**: the guard now checks `ERR_VEC_NULL` only (278-284) and the exchange is two struct assignments (285-287). Harness: `swap(blank, {x,y})` → err=0, operands exchanged (blank side now owns the buffer).
|
||||||
|
|
||||||
|
<a id="delete"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_delete` — char.c:330
|
||||||
|
|
||||||
|
*1 S1*
|
||||||
|
|
||||||
|
> DL-1 retired in Pass 10 (resets `_internal_arr = nullptr`, so double-delete and post-delete `push_back` are safe). **DL-3 retired in Pass 11**: the `(void*)` cast on the `free` argument is gone — `free(ptr->_internal_arr)` (342). The by-value-copy hazard is unchanged (DL-2).
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `DL-2` | S1 | char.h:11-17 | no ownership marker on the struct, so a by-value copy yields two owners of one buffer; each `delete` clears only its own struct's pointer, so the second free hits the first's freed block. **ASan Pass 10: `attempting double-free`** — `w = v; delete(&v); delete(&w)` |
|
||||||
|
|
||||||
|
<a id="clear"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_clear` / `_vec_char_print_vec` — char.c:349, 73
|
||||||
|
|
||||||
|
*none open*
|
||||||
|
|
||||||
|
> CL-1 retired in Pass 9: `clear` uses `memset` (361). CL-2 retired in Pass 7: `print_vec` on a drained vector prints the newline with err untouched; blank vectors take the intended `ERR_BUF_NULL` path.
|
||||||
|
|
||||||
|
<a id="validation--accessors"></a>
|
||||||
|
|
||||||
|
### Validation & accessors
|
||||||
|
|
||||||
|
> IV-3 and IV-4 retired in Pass 7: `__null_check` no longer ORs informational `ERR_SIZE_ZERO` into the validity mask (only `ERR_VEC_NULL` and `ERR_BUF_NULL` remain), and the unused local `res` is gone. That single change is what un-broke accessors, reserve, swap, resize and insert on drained vectors (AC-1, RS-8, SW-1, RZ-9, AS-8, IN-12, CL-2 all retired).
|
||||||
|
|
||||||
|
<a id="accessors"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_capacity` / `_vec_char_max_size` / `_vec_char_data` — char.c:304, 317, 291
|
||||||
|
|
||||||
|
*none open*
|
||||||
|
|
||||||
|
> AC-1 retired in Pass 7 (all three accessors work on a drained vector) and DA-1 retired in Pass 8 (`data()` returns `const char*`, so the const-laundering is gone). Blank vectors still error via `ERR_BUF_NULL` — intended, nothing to return.
|
||||||
|
|
||||||
|
<a id="at"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_at` — char.c:366
|
||||||
|
|
||||||
|
*1 S3 · 1 C*
|
||||||
|
|
||||||
|
> AT-1 retired in Pass 8: `at()` returns `char*` and reports failure as `nullptr`.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `AT-2` | S3 | char.c:376-383 | the OOB path sets `err` (flags + origin AT) and returns `nullptr`, but still prints a raw line to **stdout** via `printf` (378-379), polluting program output and bypassing the error channel. The residual hazard moved to the callers: `front`/`back` dereference the `nullptr` (428, 446) → SEGV (FB-1), and `assign` dereferences at 750 (AS-7) |
|
||||||
|
| `AT-3` | C | char.c:366, 384 | `at()` returns a mutable `char*` into the buffer of a `const _VecCharInternal*` — const-laundering callers can write elements behind the API's back, bypassing `push_back`/`resize` bookkeeping; the pointer also invalidates on any realloc |
|
||||||
|
|
||||||
|
<a id="beginend"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_begin` / `_vec_char_end` — char.c:387, 400
|
||||||
|
|
||||||
|
*1 S2 · 1 C*
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `BE-1` | S2 | char.c:388-410, 728, 778 | `FAILURE` no longer exists, so the failure value changed but the defect did not: `begin` returns raw `status.flags` (bitmask 2/4) as an index (395), and `end`'s `(int)(size - 1)` (410) is **reachable for a drained vector** — `size == 0` wraps to `SIZE_MAX` (harness: end(drained) = 18446744073709551615, flags=0). Flags-as-index and SIZE_MAX-as-end are indistinguishable from real positions; `SIZE_MAX + 1` wraps to 0 at char.c:728, 778, so `assign` into a drained destination erases `[0, 0)` and fails with `ERR_OUT_OF_BOUNDS` (AS-1). Root of ER-1 |
|
||||||
|
| `BE-2` | C | char.c:410 | `(int)` cast on a `size_t` return — now the direct cause of the `SIZE_MAX` wrap above |
|
||||||
|
|
||||||
|
<a id="frontback"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_front` / `_vec_char_back` / `_vec_char_empty` — char.c:413, 431, 449
|
||||||
|
|
||||||
|
*1 S1 · 1 S2*
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `FB-1` | S1 | char.c:423-428, 441-446, 376-383 | the Pass-9 guard meant to kill the drained SEGV is **inverted and probes the wrong index**. `at()` returns `nullptr` on failure, so `if (_vec_char_at(...) != nullptr)` fires for *valid* vectors: **`front`/`back` on a populated vector return 0 with `ERR_OUT_OF_BOUNDS`** (harness Pass 11: front('ab')=0/flags=16, back=0/flags=16 — expected 97/98). For a drained vector `at` returns nullptr, the condition is false, control falls through to the deref — `front` → `*_vec_char_at(ptr, 0, err)` (428), `back` → `*_vec_char_at(ptr, size - 1, err)` (446) — **SEGV** (ASan Pass 11: SEGV on 0x0 at char.c:428). `back` also probes index 0 (441) instead of `size - 1`. Both branches wrong: condition must be `== nullptr`, and `back` must probe the last index |
|
||||||
|
| `AC-2` | S2 | char.c:450-464 | `empty()` still returns 0 in *both* directions: a drained vector takes the `size == 0` branch and `return SUCCESS` (461), a populated one falls through to `return status.flags`, also 0 (463) — so `if (empty(&v))` is never true for a valid vector. **Harness: drained → 0/err 0, populated → 0/err 0** |
|
||||||
|
|
||||||
|
<a id="algorithms"></a>
|
||||||
|
|
||||||
|
### Algorithms
|
||||||
|
|
||||||
|
<a id="erase"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_erase` (both overloads) — char.c:466, 485
|
||||||
|
|
||||||
|
*1 S2 · 1 S3 · 1 C*
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `ER-1` | S2 | char.c:498-503 | `erase` on an empty vector never no-ops: a drained vector passes the `__null_check` guard now, then the range check (498-503: `size < iter_end || iter_end <= iter_start`) fires — for `[0, 1)` because `0 < 1`, and even `[0, 0)` because `0 <= 0`. **Harness Pass 11: erase(begin, end+1) on drained → err=16, origin ERASE.** Past the guard, `begin()`/`end()` still hand back flag bitmasks and `SIZE_MAX` (BE-1) |
|
||||||
|
| `ER-3` | S3 | char.c:477 vs 498-503 | the single-arg overload silently returns on `iter >= size` (477) with `err` untouched (harness: drained erase-1 → flags=0), while the range overload reports `ERR_OUT_OF_BOUNDS` for the same condition. A typo'd index is invisible to an err-driven caller |
|
||||||
|
| `ER-2` | C | char.c:466, 485, 478-481, 507-511 | zeroing loops should be `memset`; two stale `// TODO: Fix return value` comments remain (466, 485) |
|
||||||
|
|
||||||
|
<a id="resize"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_resize` (both overloads) — char.c:516, 613
|
||||||
|
|
||||||
|
*2 S2 · 2 S3 · 3 C*
|
||||||
|
|
||||||
|
> RZ-9 retired in Pass 7 (drained resize grows). RZ-8 retired in Pass 10 — and confirmed in Pass 11 the fix stuck: the blank branch flows through `_vec_char_reserve` (546, 643), which now `calloc`s the requested capacity, so the fill loop stays in bounds (harness: `resize(VEC_INIT, 4)` → err=0, size=4, cap=4).
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `RZ-3` | S2 | char.c:544, 563, 641, 660 | the `max_size` save/restore is now pure belt-and-braces (reserve leaves `max_size` alone since Pass 10) and looks removable. Nothing documents that |
|
||||||
|
| `RZ-1` | S2 | char.c:528-533, 625-630 | `size == _internal_size` returns `ERR_OUT_OF_BOUNDS` for a legitimate no-op; the code is also semantically wrong |
|
||||||
|
| `RZ-4` | S3 | char.c:595-608 vs 686-699 | 3-arg fills the discarded tail with `val`, 2-arg with `'\0'` (harness Pass 11: resize(3) then resize(4,'v') → arr[2]=0 from the 2-arg call, arr[3]='v'). `data()` exposes the difference |
|
||||||
|
| `RZ-2` | S3 | char.c:574, 671 | unguarded `tmpcp *= 2` wraps on overflow. Defensive |
|
||||||
|
| `RZ-5` | C | char.c:553-558, 587-592, 650-655 | dead post-`reserve` capacity checks in the `size == 0` branches of both overloads (553, 650) and in the 2-arg growth branch (587); the 3-arg growth branch has no check at all, relying on `reserve` to be exact |
|
||||||
|
| `RZ-6` | C | char.c:516-611 vs 613-702 | two ~95-line near-identical bodies, with the growth blocks copy-pasted between them |
|
||||||
|
| `RZ-7` | C | char.c:559-562, 597-600, 604-607, 656-659, 688-691, 695-698 | fill loops should be `memset` |
|
||||||
|
|
||||||
|
<a id="assign"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_assign` (both overloads) — char.c:704, 762
|
||||||
|
|
||||||
|
*4 S2 · 1 S3 · 1 C*
|
||||||
|
|
||||||
|
> AS-8 retired in Pass 7 but the fix regressed in Pass 9 (inverted `at()` guard, AS-7) — harness Pass 11 still shows flags=16, dest wiped. AS-4 retired in Pass 10: the internal `reserve` no longer caps `max_size`, so assign can't brick growth anymore.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `AS-1` | S2 | char.c:709-715, 728-729, 778-779 | fails on any empty destination. A **blank** destination trips the `__null_check` guard (709-715) → err=4, origin ASSIGN (harness Pass 11). A **drained** one passes the guard, then `end()` returns `SIZE_MAX` (BE-1) and `end + 1` wraps to 0 (728, 778), so `erase(0, 0)` fails the range check → err=16 (harness: flags=16, size stays 0). Two paths, same refusal |
|
||||||
|
| `AS-2` | S2 | char.c:728-758, 778-802 | not atomic: `erase` → `reserve` → `push_back` loop. Reserve failure leaves the destination wiped; a `push_back` failure mid-loop leaves it partially filled. Still no rollback |
|
||||||
|
| `AS-3` | S2 | char.c:736, 786 | no `> _internal_max_size` guard on the assign count |
|
||||||
|
| `AS-7` | S2 | char.c:743-751 | the Pass-9 `at()` guard in assign1 is **inverted**: `at()` returns `nullptr` on failure, so the `!= nullptr` branch fires for *valid* iterators — assign1 fails with `ERR_OUT_OF_BOUNDS` on the first element of **every non-empty source**, after erase already wiped the destination (harness Pass 11: dest `{x,y}` ← src `{a,b}` → flags=16, size=0). The nullptr-deref it was meant to prevent is still live at 750. Condition must be `== nullptr` |
|
||||||
|
| `AS-9` | S3 | char.c:767-771 | the null guard in the amount overload has **no `return`**, unlike assign1 — control falls through with `err` already flagged. Masked today only because the `erase` at 778 rejects the same state downstream |
|
||||||
|
| `AS-6` | C | char.c:704-760 vs 762-804 | near-duplicate overloads differing only in the value source |
|
||||||
|
|
||||||
|
<a id="insert"></a>
|
||||||
|
|
||||||
|
#### `_vec_char_insert` (all three overloads) — char.c:806, 861, 897
|
||||||
|
|
||||||
|
*1 S1 · 2 S2 · 2 S3 · 2 C*
|
||||||
|
|
||||||
|
> IN-12 retired in Pass 7 (one-arg insert works on drained vectors). **IN-11 retired in Pass 11**: the blank branch no longer writes past its `malloc(1)` — it sets `capacity=1` and `size=1` and writes `arr[0]` (826-831); harness: `insert(VEC_INIT, 0, 'q')` → err=0, size=1, arr[0]='q', ASan clean. IN-10 unchanged and still live.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `IN-10` | S1 | char.c:865-872 | the amount overload never calls `__null_check`: the guard at 866-870 tests a freshly-initialized local `status`, which is always 0, so it can never fire; `ptr` is then dereferenced raw at 872. Every other entry point in the file validates first. **ASan Pass 11: `SEGV` READ of 0x8 at char.c:872** — `_vec_char_insert(nullptr, 0, 2, 'x', &e)` |
|
||||||
|
| `IN-1` | S2 | char.c:911-917 | inverted range check: `iter_end > iter_start` rejects every non-empty range with `ERR_OUT_OF_BOUNDS` and accepts only empty ranges, which then silently do nothing (loop at 924 never runs). Valid insert-range never inserts. **Harness: dest `{a,b}`, src `{Z}`, insert at 1 range [0,1) → err=16, size stays 2; empty range [1,1) → err=0** |
|
||||||
|
| `IN-4` | S2 | char.c:833-838, 884-893 | no `max_size` pre-check in the amount overload: the loop pushes until one-arg insert trips `size+1 > max_size` (833) and then returns, but everything already inserted stays — an oversized `amount` half-inserts and leaves the vector half-modified |
|
||||||
|
| `IN-5` | S3 | char.c:878-882 | `amount <= 0` on a `size_t` is always just `== 0` |
|
||||||
|
| `IN-6` | S3 | char.c:884 | `iter + amount` can overflow `size_t`, wrapping the loop bound and exiting early — a silent partial insert |
|
||||||
|
| `IN-8` | C | char.c:924-934 | the loop reads `iterator->_internal_arr[i]` raw (927) instead of a bounds-checked accessor. **Unreachable today**: IN-1's inversion rejects every non-empty range, so the body never runs. The `iter_end > iterator->_internal_size` clause at 911 does bound the read once IN-1 is fixed, so this stays a style finding |
|
||||||
|
| `IN-9` | C | char.c:884-893 | O(amount²) loop of single inserts where one grow + `memmove` would do |
|
||||||
|
|
||||||
|
<a id="support"></a>
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
<a id="utils"></a>
|
||||||
|
|
||||||
|
#### `utils.h` / `utils.c`
|
||||||
|
|
||||||
|
*3 S2 · 3 C*
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `UT-1` | S2 | utils.h:9 | `SIZE_MAX / sizeof(int)` is what the `char` vector uses as `_internal_max_size`, capping it at 1/4 of the usable address space |
|
||||||
|
| `UT-6` | S2 | utils.c:14-16 | `vec_get_error_desc`'s default case is an expression statement — `"Unknown";` with no `return` — so an out-of-range code falls off the end of a non-void function: UB, and the caller printf's a garbage `char*`. Current call sites only pass constants, so it is one cast away |
|
||||||
|
| `UT-7` | S2 | utils.c:22-35 | `vec_print_errs` prints `err.origin` only on the SUCCESS branch (24), where it is always NULL on an untouched `err_t` — `%s` of NULL, UB — and the failure branch (27-35) never prints origin at all. The root-cause feature surfaces exactly backwards. **Harness: flags=8 + origin set prints only the description; flags=0 + origin prints it** |
|
||||||
|
| `UT-2` | C | utils.h:67-69 | `LOGERROR` expands `error_messages[error_code]`, but the table was deleted from `utils.c` and never declared in the header — any expansion is an undeclared-identifier compile error. The design was also wrong twice over: codes are bit flags (`ERR_OUT_OF_BOUNDS` = 16), not table indices, so it would index past any 10-entry table anyway. Latent — no caller remains |
|
||||||
|
| `UT-5` | C | utils.h:66 | `DEBUG` prints the literal `DEBUB: `, so char.c:162 — the only `DEBUG` call in the tree, on the clamp path in `push_back` — emits a misspelled prefix |
|
||||||
|
| `UT-4` | C | utils.h:7-9, 16-17, 65-71 | `static const` in a header gives every TU its own copy; `ERR_SIZE_ZERO` message mentions `< 0` on a `size_t`; `LOG`/`DEBUG`/`LOGERROR` are hardwired to stdout and single-arg; `find_len` is UB for pointers; `CAPACITY` and `VEC_FAULT_BITS` are now referenced by nothing in the tree |
|
||||||
|
|
||||||
|
<a id="charh"></a>
|
||||||
|
|
||||||
|
#### `char.h`
|
||||||
|
|
||||||
|
*1 S2 · 5 C*
|
||||||
|
|
||||||
|
> CH-7 retired in Pass 10: the DESIGN CONTRACT (char.c:11-16) states the `char` vector is a raw byte container, never a C string, so there is no `from_cstr` gap — that was the answer CR-4 was waiting for.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `CH-9` | S2 | char.h:11-106 · char.c:34, 46, 53 | every public identifier is reserved by the C standard: `_VecCharInternal` and all 27 `_vec_char_*` functions begin with `_` at file scope, which C11 7.1.3 reserves for the implementation, and the `.c` adds three `__`-prefixed helpers (`__null_check` ×2, `__origin_override`), reserved the same way. Undefined behaviour on collision with any libc or third-party name |
|
||||||
|
| `CH-4` | C | char.h:4-9 | `<stdio.h>`/`<stdlib.h>`/`<string.h>`/`<stdint.h>` in the header, only needed by the `.c` |
|
||||||
|
| `CH-5` | C | char.h | no doc comments on any function; the file-level DESIGN CONTRACT (char.c:7-32) documents the `err` requirement and ownership, but no function explains its own contract |
|
||||||
|
| `CH-8` | C | char.h:11-17 | the public struct is named with a `_` prefix and its four fields are named `_internal_*` instead of being `PRIVATE`, so the "internal" contract is expressed only by spelling. Combined with CH-9 this is the whole naming problem |
|
||||||
|
| `CH-2` | C | char.h:22-106 | `const` on by-value params throughout (`const size_t size`, `const char val`, `const size_t idx`, …) |
|
||||||
|
| `CH-3` | C | char.h | no `size()` accessor, so callers reach into `_internal_size` directly |
|
||||||
|
|
||||||
|
<a id="project"></a>
|
||||||
|
|
||||||
|
#### Project
|
||||||
|
|
||||||
|
*5 C*
|
||||||
|
|
||||||
|
> PR-8 demoted S3→C in Pass 10: the DESIGN CONTRACT (char.c:25) now documents the non-NULL `err` requirement, so a NULL `err_t*` is caller UB, not an undocumented hazard. No runtime check was added.
|
||||||
|
|
||||||
|
| ID | Sev | Location | Finding |
|
||||||
|
|:---|:--:|:---------|:--------|
|
||||||
|
| `PR-8` | C | char.c:88, 334 (and every guard) | no function checks `err` for NULL before the first error path derefs it; a NULL `err_t*` segfaults on the first failure. Now documented (char.c:25) — a cheap defensive check would still harden the API |
|
||||||
|
| `PR-2` | C | char.c:42, 49, 76-79, 851-857 | mixed brace style (single-line `if` vs expanded blocks) and a stray blank line between the shift loop and `return` at 857; `.clang-format` exists and is not run |
|
||||||
|
| `PR-7` | C | CMakeLists.txt:6-31, main.c:38-102 | there is no test target at all — CMakeLists builds `libvec` and `libvec_bin` and nothing else — and `main()` exercises zero libvec functions: every vector call is commented out. Nothing in the tree calls a single function in this tracker, so none of the 4 open S1s can regress loudly |
|
||||||
|
| `PR-4` | C | main.c:15, 38-102 | `main` still has no `char.h` include (only a commented `// #include <libvec.h>`, 15), so even uncommenting would not compile; the commented body still references the removed API (`Vec8_t`, `create`, by-value `v1 = push_back(&v1, …)`, `print_vec`, `delete`, `swap`, `v1.size`) and the mismatch with the `err_t` API (`_vec_char_push_back(&v1, 'c', &status)`) |
|
||||||
|
| `PR-5` | C | include/libvec.h:4, CMakeLists.txt:11-14 | empty placeholder; `int`/`float`/`string` are empty dirs already listed as comments in CMakeLists.txt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="fixed"></a>
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>58 fixed IDs — no rows</b></summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
PB-1 PB-2 PB-3 PB-4 PB-6 PB-7 PB-8 PB-14 PB-15 PB-16 PB-17 PB-18 PB-20 PB-21
|
||||||
|
RS-2 RS-3 RS-4 RS-6 RS-7 RS-8
|
||||||
|
IV-1 IV-3 IV-4
|
||||||
|
CH-1 CH-7
|
||||||
|
CR-3 CR-4 CR-6 CR-7 CR-8 CR-10 CR-11
|
||||||
|
SW-1 SW-2
|
||||||
|
FB-2
|
||||||
|
AC-1 DA-1
|
||||||
|
AT-1
|
||||||
|
DL-1 DL-3
|
||||||
|
UT-3
|
||||||
|
PR-1 PR-3
|
||||||
|
CL-1 CL-2
|
||||||
|
RZ-8 RZ-9
|
||||||
|
AS-4 AS-8
|
||||||
|
ST-1 ST-2
|
||||||
|
IN-2 IN-11 IN-12
|
||||||
|
FX-2 FX-3 FX-4 FX-5
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="conventions"></a>
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>How to read this</b></summary>
|
||||||
|
|
||||||
|
Each function is a `####` block with one table; rows are sorted by severity, so the worst finding in each function is on top. Fixed findings are **not** rows — they live in the [Fixed](#fixed) ID list only.
|
||||||
|
|
||||||
|
Severity is the `Sev` column. Every row is `OPEN` (there is no `DECIDE` row since Pass 10), so there is no separate state column to scan.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Rules</b></summary>
|
||||||
|
|
||||||
|
1. **IDs are permanent.** Never reused, never renumbered. An ID leaves the file only when its row is deleted outright, and only under rule 3.
|
||||||
|
2. **Locations drift freely.** They are the only field updated in place.
|
||||||
|
3. **False findings are deleted, not archived.** If a row turns out to be my own error, the row is removed. A new ID carries the accurate finding if there is one. No `DISPUTED` state, no corrections log — a tracker is not a diary.
|
||||||
|
4. **State** is `OPEN`; it is carried by the `Sev` cell. `FIXED` means the row is gone and its ID is in the bottom list.
|
||||||
|
5. **Fixed findings live in the ID list**, nothing else.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Verification history</b></summary>
|
||||||
|
|
||||||
|
> **Passes 1-3** @ **2026-09-28 12:48:04** — `char.c`, 3 independent reads.
|
||||||
|
> **Pass 4** @ **2026-09-28** — `push_back` / `pop_back` (old char.c:67-157), ASan.
|
||||||
|
> **Pass 5** @ **2026-09-28** — whole file re-read plus 10 harness runs, ASan. Line count moved 804 to 800 with the `create` rewrite.
|
||||||
|
> **Pass 6** @ **2026-10-08** — the `err_t` migration. Full re-read: `char.c` 877 lines, `char.h` 108, `utils.h` 73, `utils.c` 71, `main.c` 105, `CMakeLists.txt` 33. `create` and `is_valid` are gone — `_vec_char_cpy` and `__null_check` replace them — and every function now reports through an `err_t*` out-param. Compiles under `-std=c2x -Wall -Wextra` with one warning (IV-4). Six ASan harness runs. Thirteen rows died with the old code and their IDs are in [Fixed](#fixed).
|
||||||
|
> **Pass 7** @ **2026-10-09** — post-migration cleanup landed in `char.c` (874 lines): `__null_check` dropped the unused `res` and its `ERR_SIZE_ZERO` probe, `empty()` was rewritten (still wrong, AC-2), and both `resize` overloads now branch on `ptr->_internal_size == 0`. Re-verified with 40 ASan harness runs. Eight rows retired — IV-3, IV-4, AC-1, RS-8, AS-8, RZ-9, IN-12, CL-2. The removal made `end()`/`front()`/`back()` reach their wrap/`at` paths on empty vectors (BE-1, FB-1) and turned `pop_back`'s soft refusal into a live underflow write (PB-17 promoted S3→S1). All line refs re-derived.
|
||||||
|
> **Pass 8** @ **2026-10-09** — accessor refactor landed in `char.c` (876 lines) and `char.h`: `data()` went `const char*`, `at()` returns `char*` with `nullptr` on both error paths and sets `err` (+ origin AT) on OOB, and `front`/`back`/`assign` dereference the new pointer. Re-verified with 41 ASan harness runs. Two rows retired — AT-1 and DA-1 — and AT-2 demoted S2→S3. The pointer change made drained `front`/`back` a live null-deref SEGV — FB-1 promoted S2→S1 — and added AT-3. `main.c` dropped its `char.h` include. All line refs re-derived.
|
||||||
|
> **Pass 9** @ **2026-10-09** — `char.c` (887 lines): `clear` switched to `memset` (CL-1 retired) and `at()`'s OOB print moved from `fprintf(stderr)` to `printf(stdout)` (AT-2). `front`/`back` and assign1 gained `at()`-null guards that are **inverted**: front/back on populated now return 0 + `ERR_OUT_OF_BOUNDS` (FB-1), assign1 fails on every non-empty source (AS-7 promoted S3→S2, the Pass-7 AS-8 fix regressed). Drained `front`/`back` SEGV unchanged. Re-verified with 43 ASan harness runs.
|
||||||
|
> **Pass 10** @ **2026-10-09** — editing pass in `char.c` (935 lines) and `char.h` (108). File gained a DESIGN CONTRACT block (7-32: raw byte container, never NUL-terminated, `err` must be non-NULL, spare slots uninitialized); `pop_back` added a size-0 early return (187-191); `push_back`'s blank materialize resets `_internal_size = 0` (148); `delete` is non-const and resets `_internal_arr = nullptr` (345-346); `reserve`'s `max_size = capacity` is commented out (249); one-arg `insert` added a blank-materialize branch (818-831). Re-verified with a fresh 20-case ASan harness. Thirteen rows retired — PB-14, PB-15, PB-16, PB-17, PB-20, PB-21, DL-1, ST-1, RZ-8, AS-4, RS-3 (behavioral fixes / documented-contract resolutions), CR-4 (the DECIDE — the header now pins raw-byte semantics) and CH-7. IN-11 promoted S3→S1: the new blank path writes `arr[1]` past `malloc(1)` (ASan WRITE at char.c:829). RS-7's blast radius widened: blank `reserve` + pushes → WRITE at 173; `resize(VEC_INIT, 4)` → WRITE at 561. PR-8 demoted S3→C (contract documented at char.c:25). Front/back inverted guards (FB-1) and assign1 (AS-7) still live; 6 open S1s remained. All line refs re-derived.
|
||||||
|
> **Pass 11** @ **2026-10-09** — editing pass in `char.c` (936 lines); `char.h` unchanged. `cpy` gained a `max_size` guard (101-106) and reports `ERR_ALLOC_FAIL` on malloc failure (108-112); `reserve`'s blank path switched to `calloc(capacity)` (223), its guard order was restructured (205-219), and truncation became a `memset` — but the truncation runs **after** `ptr->_internal_arr = res` (243), so the tail zeroing now writes past the shrunk block: **RS-1 got worse** (ASan Pass 11: heap-buffer-overflow WRITE at char.c:246, was a use-after-free at 242). `shrink_to_fit` gained a drained no-op (267-270); `swap` guard narrowed to ERR_VEC_NULL with a struct-assign exchange (278-288); `delete` dropped the `(void*)` cast and resets size/capacity (340-346); the old `max_size = capacity` line was deleted entirely, not just commented. Re-verified with a fresh 24-case ASan harness. Nine rows retired — CR-10, CR-11, RS-2, RS-4, RS-7, ST-2, SW-1, DL-3, IN-11 (the blank insert writes `arr[0]`, 818-832, in bounds). FB-1 and IN-10 re-confirmed live (SEGV at 428 and 872). 4 open S1s remain. All line refs re-derived.
|
||||||
|
|
||||||
|
**Reproducer harness:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
clang -std=c2x -g -fsanitize=address -Isrc -Isrc/lib \
|
||||||
|
t.c src/lib/char/char.c src/lib/utils/utils.c
|
||||||
|
```
|
||||||
|
|
||||||
|
`-std=c2x` is required — `char.c` uses `nullptr`, so the project does not compile as C17. Reproducers that ask for a multi-exabyte `calloc` additionally need `ASAN_OPTIONS=allocator_may_return_null=1`, or ASan aborts on the 2^61-byte `calloc` before the interesting line runs. Pass 6-11 harnesses were scratch files outside the tree (only this tracker may be written), not committed.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>ID map</b></summary>
|
||||||
|
|
||||||
|
`FX-1` … `FX-5` are first-pass IDs. FX-1 was a duplicate of PB-6 and was never given a row.
|
||||||
|
|
||||||
|
| Prefix | Scope | Prefix | Scope |
|
||||||
|
|:------:|:------|:------:|:------|
|
||||||
|
| `PB-` | push_back, pop_back | `AT-` | at |
|
||||||
|
| `RS-` | reserve | `FB-` | front, back |
|
||||||
|
| `ST-` | shrink_to_fit | `BE-` | begin, end |
|
||||||
|
| `CR-` | create → cpy | `SW-` | swap |
|
||||||
|
| `AC-` | accessors | `CL-` | clear, print_vec |
|
||||||
|
| `DA-` | data | `IV-` | is_valid → __null_check |
|
||||||
|
| `DL-` | delete | `UT-` | utils.h, utils.c |
|
||||||
|
| `ER-` | erase | `CH-` | char.h |
|
||||||
|
| `RZ-` | resize | `PR-` | project |
|
||||||
|
| `AS-` | assign | `FX-` | retired first-pass IDs |
|
||||||
|
| `IN-` | insert | | |
|
||||||
|
|
||||||
|
</details>
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
+183
-103
@@ -1,27 +1,68 @@
|
|||||||
#include "char.h"
|
#include "char.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file char.c
|
||||||
|
* @brief Dynamic contiguous array specialization for raw char elements.
|
||||||
|
*
|
||||||
|
* DESIGN CONTRACT:
|
||||||
|
* - RAW BYTE CONTAINER:
|
||||||
|
* This vector manages a sequence of independent 'char' values (raw bytes).
|
||||||
|
* It is NOT a C-string container. Buffers are NEVER null-terminated ('\0').
|
||||||
|
* Passing _vec_char_data() directly to string utilities (e.g., strlen, printf
|
||||||
|
* %s) or treating it as a NUL-terminated string is undefined behavior.
|
||||||
|
*
|
||||||
|
* - ELEMENT VALIDITY & BOUNDS:
|
||||||
|
* Valid elements reside strictly in the half-open range [0, _internal_size).
|
||||||
|
* Slots in the range [_internal_size, _internal_capacity) are spare capacity
|
||||||
|
* and must be treated as uninitialized storage. Any access past
|
||||||
|
* _internal_size - 1 is out of bounds.
|
||||||
|
*
|
||||||
|
* - ERROR HANDLING:
|
||||||
|
* Functions taking an `err_t*` out-parameter require a non-NULL pointer.
|
||||||
|
* On error, appropriate bit flags (defined in utils.h) are bitwise OR-ed into
|
||||||
|
* `err->flags`, and the initial failure site is recorded in `err->origin`.
|
||||||
|
*
|
||||||
|
* - MEMORY OWNERSHIP:
|
||||||
|
* Buffers are heap-allocated via malloc/realloc and owned exclusively by
|
||||||
|
* the vector instance. Use _vec_char_delete() to release resources.
|
||||||
|
*/
|
||||||
|
|
||||||
__attribute__((overloadable)) size_t
|
__attribute__((overloadable)) size_t
|
||||||
__null_check(const _VecCharInternal* ptr, err_t* status)
|
__null_check(const _VecCharInternal* ptr, err_t* status)
|
||||||
{
|
{
|
||||||
unsigned int res = 0;
|
if (status == nullptr)
|
||||||
|
{
|
||||||
|
status->flags = ERR_ERR_T_NULL;
|
||||||
|
status->origin = "NULL_CHECK";
|
||||||
|
return ERR_ERR_T_NULL;
|
||||||
|
}
|
||||||
if (ptr == nullptr)
|
if (ptr == nullptr)
|
||||||
{
|
{
|
||||||
status->flags |= ERR_VEC_NULL;
|
status->flags |= ERR_VEC_NULL;
|
||||||
return ERR_VEC_NULL;
|
return ERR_VEC_NULL;
|
||||||
}
|
}
|
||||||
if (ptr->_internal_arr == nullptr) status->flags |= ERR_BUF_NULL;
|
if (ptr->_internal_arr == nullptr)
|
||||||
if (ptr->_internal_size == 0) status->flags |= ERR_SIZE_ZERO;
|
{
|
||||||
|
status->flags |= ERR_BUF_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return status->flags;
|
return status->flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((overloadable)) size_t
|
__attribute__((overloadable)) size_t
|
||||||
__null_check(const char* ptr, err_t* status)
|
__null_check(const char* ptr, err_t* status)
|
||||||
{
|
{
|
||||||
if (ptr == nullptr) status->flags |= ERR_BUF_NULL;
|
if (ptr == nullptr)
|
||||||
|
{
|
||||||
|
status->flags |= ERR_BUF_NULL;
|
||||||
|
}
|
||||||
return status->flags;
|
return status->flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +89,14 @@ __origin_override(err_t* status, char* str)
|
|||||||
void
|
void
|
||||||
_vec_char_print_vec(const _VecCharInternal* ptr, err_t* err)
|
_vec_char_print_vec(const _VecCharInternal* ptr, err_t* err)
|
||||||
{
|
{
|
||||||
if (__null_check(ptr, err) != 0) return;
|
if (__null_check(ptr, err) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (size_t i = 0; i < ptr->_internal_size; i++)
|
for (size_t i = 0; i < ptr->_internal_size; i++)
|
||||||
|
{
|
||||||
printf("%c ", ptr->_internal_arr[i]);
|
printf("%c ", ptr->_internal_arr[i]);
|
||||||
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,14 +119,17 @@ _vec_char_cpy(const char* src, const size_t src_size, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
self._internal_arr = malloc(src_size * sizeof(char));
|
if (src_size > self._internal_max_size)
|
||||||
__null_check(self._internal_arr, &status);
|
|
||||||
if (status.flags != 0)
|
|
||||||
{
|
{
|
||||||
__origin_override(err, "CPY");
|
__origin_override(err, "CPY");
|
||||||
err->flags |= status.flags;
|
err->flags |= ERR_SIZE_OVERFLOW;
|
||||||
free(self._internal_arr);
|
return self;
|
||||||
self._internal_arr = NULL;
|
}
|
||||||
|
self._internal_arr = malloc(src_size * sizeof(char));
|
||||||
|
if (self._internal_arr == nullptr)
|
||||||
|
{
|
||||||
|
__origin_override(err, "CPY");
|
||||||
|
err->flags |= ERR_ALLOC_FAIL;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
memcpy(self._internal_arr, src, src_size * sizeof(char));
|
memcpy(self._internal_arr, src, src_size * sizeof(char));
|
||||||
@@ -120,6 +169,7 @@ _vec_char_push_back(_VecCharInternal* ptr, const char val, err_t* err)
|
|||||||
}
|
}
|
||||||
ptr->_internal_arr = res;
|
ptr->_internal_arr = res;
|
||||||
ptr->_internal_capacity = 1;
|
ptr->_internal_capacity = 1;
|
||||||
|
ptr->_internal_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ptr->_internal_size + 1 > ptr->_internal_capacity)
|
else if (ptr->_internal_size + 1 > ptr->_internal_capacity)
|
||||||
@@ -158,7 +208,13 @@ _vec_char_pop_back(_VecCharInternal* ptr, err_t* err)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->_internal_arr[ptr->_internal_size - 1] = '\0';
|
if (ptr->_internal_size == 0)
|
||||||
|
{
|
||||||
|
__origin_override(err, "POP_BACK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr->_internal_arr[ptr->_internal_size - 1] = 0;
|
||||||
ptr->_internal_size--;
|
ptr->_internal_size--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,33 +223,35 @@ _vec_char_reserve(_VecCharInternal* ptr, const size_t capacity, err_t* err)
|
|||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
char* res;
|
char* res;
|
||||||
|
if (__null_check(ptr, &status) != 0)
|
||||||
|
{
|
||||||
|
if ((status.flags & ERR_VEC_NULL) != 0)
|
||||||
|
{
|
||||||
|
__origin_override(err, "RESERVE");
|
||||||
|
err->flags |= ERR_VEC_NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (capacity == 0)
|
if (capacity == 0)
|
||||||
{
|
{
|
||||||
__origin_override(err, "RESERVE");
|
__origin_override(err, "RESERVE");
|
||||||
err->flags |= ERR_SIZE_ZERO;
|
err->flags |= ERR_SIZE_ZERO;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (__null_check(ptr, &status) != 0)
|
|
||||||
{
|
|
||||||
if ((status.flags & ERR_BUF_NULL) != 0)
|
|
||||||
{
|
|
||||||
res = calloc(ptr->_internal_capacity, sizeof(char));
|
|
||||||
if (res == nullptr)
|
|
||||||
{
|
|
||||||
__origin_override(err, "RESERVE");
|
|
||||||
err->flags |= ERR_ALLOC_FAIL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
if ((status.flags & ERR_BUF_NULL) != 0)
|
||||||
|
{
|
||||||
|
res = calloc(capacity, sizeof(char));
|
||||||
|
if (res == nullptr)
|
||||||
{
|
{
|
||||||
__origin_override(err, "RESERVE");
|
__origin_override(err, "RESERVE");
|
||||||
err->flags |= (status.flags ^ ERR_BUF_NULL);
|
err->flags |= ERR_ALLOC_FAIL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ptr->_internal_arr = res;
|
||||||
|
ptr->_internal_capacity = capacity;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
res = realloc(ptr->_internal_arr, capacity * sizeof(char));
|
res = realloc(ptr->_internal_arr, capacity * sizeof(char));
|
||||||
@@ -203,18 +261,16 @@ _vec_char_reserve(_VecCharInternal* ptr, const size_t capacity, err_t* err)
|
|||||||
err->flags |= ERR_ALLOC_FAIL;
|
err->flags |= ERR_ALLOC_FAIL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ptr->_internal_arr = res;
|
||||||
if (ptr->_internal_size > capacity)
|
if (ptr->_internal_size > capacity)
|
||||||
{
|
{
|
||||||
for (size_t i = capacity; i < ptr->_internal_size; i++)
|
memset(&ptr->_internal_arr[capacity], 0,
|
||||||
{
|
ptr->_internal_size - capacity);
|
||||||
ptr->_internal_arr[i] = '\0';
|
|
||||||
}
|
|
||||||
ptr->_internal_size = capacity;
|
ptr->_internal_size = capacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ptr->_internal_arr = res;
|
ptr->_internal_arr = res;
|
||||||
ptr->_internal_capacity = capacity;
|
ptr->_internal_capacity = capacity;
|
||||||
ptr->_internal_max_size = capacity;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,39 +285,31 @@ _vec_char_shrink_to_fit(_VecCharInternal* ptr, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ptr->_internal_size == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
return _vec_char_reserve(ptr, ptr->_internal_size, err);
|
return _vec_char_reserve(ptr, ptr->_internal_size, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_vec_char_swap(_VecCharInternal* ptr1, _VecCharInternal* ptr2, err_t* err)
|
_vec_char_swap(_VecCharInternal* ptr1, _VecCharInternal* ptr2, err_t* err)
|
||||||
{
|
{
|
||||||
_VecCharInternal self = VEC_INIT;
|
|
||||||
err_t status_1 = ERR_T_INIT, status_2 = ERR_T_INIT;
|
err_t status_1 = ERR_T_INIT, status_2 = ERR_T_INIT;
|
||||||
if (__null_check(ptr1, &status_1) != 0 ||
|
if ((__null_check(ptr1, &status_1) & ERR_VEC_NULL) != 0 ||
|
||||||
__null_check(ptr2, &status_2) != 0)
|
(__null_check(ptr2, &status_2) & ERR_VEC_NULL) != 0)
|
||||||
{
|
{
|
||||||
__origin_override(err, "SWAP");
|
__origin_override(err, "SWAP");
|
||||||
err->flags |= status_1.flags | status_2.flags;
|
err->flags |= ERR_VEC_NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._internal_arr = ptr1->_internal_arr;
|
_VecCharInternal self = *ptr1;
|
||||||
ptr1->_internal_arr = ptr2->_internal_arr;
|
*ptr1 = *ptr2;
|
||||||
ptr2->_internal_arr = self._internal_arr;
|
*ptr2 = self;
|
||||||
|
|
||||||
self._internal_capacity = ptr1->_internal_capacity;
|
|
||||||
self._internal_size = ptr1->_internal_size;
|
|
||||||
self._internal_max_size = ptr1->_internal_max_size;
|
|
||||||
ptr1->_internal_capacity = ptr2->_internal_capacity;
|
|
||||||
ptr1->_internal_size = ptr2->_internal_size;
|
|
||||||
ptr1->_internal_max_size = ptr2->_internal_max_size;
|
|
||||||
|
|
||||||
ptr2->_internal_capacity = self._internal_capacity;
|
|
||||||
ptr2->_internal_size = self._internal_size;
|
|
||||||
ptr2->_internal_max_size = self._internal_max_size;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char*
|
const char*
|
||||||
_vec_char_data(const _VecCharInternal* ptr, err_t* err)
|
_vec_char_data(const _VecCharInternal* ptr, err_t* err)
|
||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
@@ -274,6 +322,19 @@ _vec_char_data(const _VecCharInternal* ptr, err_t* err)
|
|||||||
return &ptr->_internal_arr[0];
|
return &ptr->_internal_arr[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
_vec_char_size(_VecCharInternal* ptr, err_t* err)
|
||||||
|
{
|
||||||
|
err_t status = ERR_T_INIT;
|
||||||
|
if (__null_check(ptr, &status) != 0)
|
||||||
|
{
|
||||||
|
__origin_override(err, "CAPACITY");
|
||||||
|
err->flags |= status.flags;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ptr->_internal_size;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
_vec_char_capacity(const _VecCharInternal* ptr, err_t* err)
|
_vec_char_capacity(const _VecCharInternal* ptr, err_t* err)
|
||||||
{
|
{
|
||||||
@@ -301,7 +362,7 @@ _vec_char_max_size(const _VecCharInternal* ptr, err_t* err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_vec_char_delete(const _VecCharInternal* ptr, err_t* err)
|
_vec_char_delete(_VecCharInternal* ptr, err_t* err)
|
||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
if (__null_check(ptr, &status) != 0)
|
if (__null_check(ptr, &status) != 0)
|
||||||
@@ -310,7 +371,13 @@ _vec_char_delete(const _VecCharInternal* ptr, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
free((void*)ptr->_internal_arr);
|
if (ptr->_internal_arr != nullptr)
|
||||||
|
{
|
||||||
|
free(ptr->_internal_arr);
|
||||||
|
ptr->_internal_arr = nullptr;
|
||||||
|
}
|
||||||
|
ptr->_internal_size = 0;
|
||||||
|
ptr->_internal_capacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -325,15 +392,12 @@ _vec_char_clear(_VecCharInternal* ptr, err_t* err)
|
|||||||
}
|
}
|
||||||
if (ptr->_internal_size > 0)
|
if (ptr->_internal_size > 0)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < ptr->_internal_size; i++)
|
memset(ptr->_internal_arr, 0, ptr->_internal_size);
|
||||||
{
|
|
||||||
ptr->_internal_arr[i] = 0;
|
|
||||||
}
|
|
||||||
ptr->_internal_size = 0;
|
ptr->_internal_size = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char
|
char*
|
||||||
_vec_char_at(const _VecCharInternal* ptr, const size_t idx, err_t* err)
|
_vec_char_at(const _VecCharInternal* ptr, const size_t idx, err_t* err)
|
||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
@@ -341,16 +405,17 @@ _vec_char_at(const _VecCharInternal* ptr, const size_t idx, err_t* err)
|
|||||||
{
|
{
|
||||||
__origin_override(err, "AT");
|
__origin_override(err, "AT");
|
||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return status.flags;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (idx >= ptr->_internal_size)
|
if (idx >= ptr->_internal_size)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
printf("At Index: [%zu] is out of bounds for size: [%zu]\n",
|
||||||
"At Index: [%zu] is out of bounds for size: [%zu]\n",
|
idx, ptr->_internal_size);
|
||||||
idx, ptr->_internal_size);
|
__origin_override(err, "AT");
|
||||||
return ERR_OUT_OF_BOUNDS;
|
err->flags |= ERR_OUT_OF_BOUNDS;
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return ptr->_internal_arr[idx];
|
return &ptr->_internal_arr[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
@@ -376,7 +441,7 @@ _vec_char_end(const _VecCharInternal* ptr, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return status.flags;
|
return status.flags;
|
||||||
}
|
}
|
||||||
return (int)(ptr->_internal_size - 1);
|
return (size_t)(ptr->_internal_size - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
char
|
char
|
||||||
@@ -389,7 +454,12 @@ _vec_char_front(const _VecCharInternal* ptr, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return status.flags;
|
return status.flags;
|
||||||
}
|
}
|
||||||
return _vec_char_at(ptr, 0, err);
|
if (_vec_char_at(ptr, 0, err) != nullptr)
|
||||||
|
{
|
||||||
|
err->flags |= ERR_OUT_OF_BOUNDS;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return *_vec_char_at(ptr, 0, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
char
|
char
|
||||||
@@ -402,7 +472,12 @@ _vec_char_back(const _VecCharInternal* ptr, err_t* err)
|
|||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return status.flags;
|
return status.flags;
|
||||||
}
|
}
|
||||||
return _vec_char_at(ptr, ptr->_internal_size - 1, err);
|
if (_vec_char_at(ptr, 0, err) != nullptr)
|
||||||
|
{
|
||||||
|
err->flags |= ERR_OUT_OF_BOUNDS;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return *_vec_char_at(ptr, ptr->_internal_size - 1, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
@@ -411,15 +486,14 @@ _vec_char_empty(const _VecCharInternal* ptr, err_t* err)
|
|||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
if (__null_check(ptr, &status) != 0)
|
if (__null_check(ptr, &status) != 0)
|
||||||
{
|
{
|
||||||
if ((status.flags & ERR_SIZE_ZERO) != 0)
|
|
||||||
{
|
|
||||||
err->flags ^= ERR_SIZE_ZERO;
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
__origin_override(err, "EMPTY");
|
__origin_override(err, "EMPTY");
|
||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return status.flags;
|
return status.flags;
|
||||||
}
|
}
|
||||||
|
if (ptr->_internal_size == 0)
|
||||||
|
{
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
return status.flags;
|
return status.flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,11 +538,8 @@ _vec_char_erase(_VecCharInternal* ptr, const size_t iter_start,
|
|||||||
size_t diff = iter_end - iter_start;
|
size_t diff = iter_end - iter_start;
|
||||||
memmove(&ptr->_internal_arr[iter_start], &ptr->_internal_arr[iter_end],
|
memmove(&ptr->_internal_arr[iter_start], &ptr->_internal_arr[iter_end],
|
||||||
(ptr->_internal_size - iter_end) * sizeof(char));
|
(ptr->_internal_size - iter_end) * sizeof(char));
|
||||||
for (size_t i = ptr->_internal_size - diff; i < ptr->_internal_size;
|
memset(&ptr->_internal_arr[ptr->_internal_size - diff], 0,
|
||||||
i++)
|
sizeof(char) * ptr->_internal_size);
|
||||||
{
|
|
||||||
ptr->_internal_arr[i] = 0;
|
|
||||||
}
|
|
||||||
ptr->_internal_size -= diff;
|
ptr->_internal_size -= diff;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -478,10 +549,10 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, err_t* err)
|
|||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
__null_check(ptr, &status);
|
__null_check(ptr, &status);
|
||||||
if ((status.flags & (ERR_BUF_NULL | ERR_VEC_NULL)) != 0)
|
if ((status.flags & ERR_VEC_NULL) != 0)
|
||||||
{
|
{
|
||||||
__origin_override(err, "RESIZE");
|
__origin_override(err, "RESIZE");
|
||||||
err->flags |= ERR_BUF_NULL | ERR_VEC_NULL;
|
err->flags |= ERR_VEC_NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +570,7 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, err_t* err)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ((status.flags & ERR_SIZE_ZERO) != 0)
|
else if ((status.flags & ERR_BUF_NULL) != 0 || ptr->_internal_size == 0)
|
||||||
{
|
{
|
||||||
size_t max = ptr->_internal_max_size;
|
size_t max = ptr->_internal_max_size;
|
||||||
err_t verr = ERR_T_INIT;
|
err_t verr = ERR_T_INIT;
|
||||||
@@ -518,7 +589,7 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, err_t* err)
|
|||||||
}
|
}
|
||||||
for (size_t i = 0; i < size; i++)
|
for (size_t i = 0; i < size; i++)
|
||||||
{
|
{
|
||||||
ptr->_internal_arr[i] = '\0';
|
ptr->_internal_arr[i] = 0;
|
||||||
}
|
}
|
||||||
ptr->_internal_max_size = max;
|
ptr->_internal_max_size = max;
|
||||||
ptr->_internal_size = size;
|
ptr->_internal_size = size;
|
||||||
@@ -554,17 +625,12 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, err_t* err)
|
|||||||
}
|
}
|
||||||
if (size < ptr->_internal_size)
|
if (size < ptr->_internal_size)
|
||||||
{
|
{
|
||||||
for (size_t i = size; i < ptr->_internal_size; i++)
|
memset(&ptr->_internal_arr[size], 0, ptr->_internal_size);
|
||||||
{
|
|
||||||
ptr->_internal_arr[i] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (ptr->_internal_size < size && size <= ptr->_internal_capacity)
|
if (ptr->_internal_size < size && size <= ptr->_internal_capacity)
|
||||||
{
|
{
|
||||||
for (size_t i = ptr->_internal_size; i < size; i++)
|
memset(&ptr->_internal_arr[ptr->_internal_size], 0,
|
||||||
{
|
ptr->_internal_arr[size]);
|
||||||
ptr->_internal_arr[i] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ptr->_internal_size = size;
|
ptr->_internal_size = size;
|
||||||
return;
|
return;
|
||||||
@@ -576,10 +642,10 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, const char val,
|
|||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
__null_check(ptr, &status);
|
__null_check(ptr, &status);
|
||||||
if ((status.flags & (ERR_BUF_NULL | ERR_VEC_NULL)) != 0)
|
if ((status.flags & ERR_VEC_NULL) != 0)
|
||||||
{
|
{
|
||||||
__origin_override(err, "RESIZE");
|
__origin_override(err, "RESIZE");
|
||||||
err->flags |= ERR_BUF_NULL | ERR_VEC_NULL;
|
err->flags |= ERR_VEC_NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ptr->_internal_size == size)
|
if (ptr->_internal_size == size)
|
||||||
@@ -596,7 +662,7 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, const char val,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ((status.flags & ERR_SIZE_ZERO) != 0)
|
else if ((status.flags & ERR_BUF_NULL) != 0 || ptr->_internal_size == 0)
|
||||||
{
|
{
|
||||||
size_t max = ptr->_internal_max_size;
|
size_t max = ptr->_internal_max_size;
|
||||||
err_t verr = ERR_T_INIT;
|
err_t verr = ERR_T_INIT;
|
||||||
@@ -645,17 +711,12 @@ _vec_char_resize(_VecCharInternal* ptr, const size_t size, const char val,
|
|||||||
}
|
}
|
||||||
if (size < ptr->_internal_size)
|
if (size < ptr->_internal_size)
|
||||||
{
|
{
|
||||||
for (size_t i = size; i < ptr->_internal_size; i++)
|
memset(&ptr->_internal_arr[size], val, ptr->_internal_size);
|
||||||
{
|
|
||||||
ptr->_internal_arr[i] = val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (ptr->_internal_size < size && size <= ptr->_internal_capacity)
|
if (ptr->_internal_size < size && size <= ptr->_internal_capacity)
|
||||||
{
|
{
|
||||||
for (size_t i = ptr->_internal_size; i < size; i++)
|
memset(&ptr->_internal_arr[ptr->_internal_size], val,
|
||||||
{
|
ptr->_internal_arr[size]);
|
||||||
ptr->_internal_arr[i] = val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ptr->_internal_size = size;
|
ptr->_internal_size = size;
|
||||||
return;
|
return;
|
||||||
@@ -702,7 +763,12 @@ _vec_char_assign(_VecCharInternal* ptr, const _VecCharInternal* iterator,
|
|||||||
}
|
}
|
||||||
for (size_t i = iter_start; i < iter_end; i++)
|
for (size_t i = iter_start; i < iter_end; i++)
|
||||||
{
|
{
|
||||||
_vec_char_push_back(ptr, _vec_char_at(iterator, i, &status),
|
if (_vec_char_at(iterator, i, &status) != nullptr)
|
||||||
|
{
|
||||||
|
err->flags |= ERR_OUT_OF_BOUNDS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_vec_char_push_back(ptr, *_vec_char_at(iterator, i, &status),
|
||||||
&status);
|
&status);
|
||||||
if (status.flags != 0)
|
if (status.flags != 0)
|
||||||
{
|
{
|
||||||
@@ -764,12 +830,27 @@ _vec_char_insert(_VecCharInternal* ptr, const size_t iter, const char val,
|
|||||||
{
|
{
|
||||||
err_t status = ERR_T_INIT;
|
err_t status = ERR_T_INIT;
|
||||||
__null_check(ptr, &status);
|
__null_check(ptr, &status);
|
||||||
if ((status.flags & (ERR_VEC_NULL | ERR_BUF_NULL)) != 0)
|
if ((status.flags & ERR_VEC_NULL) != 0)
|
||||||
{
|
{
|
||||||
__origin_override(err, "INSERT");
|
__origin_override(err, "INSERT");
|
||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((status.flags & ERR_BUF_NULL) != 0)
|
||||||
|
{
|
||||||
|
char* res = malloc(1 * sizeof(char));
|
||||||
|
if (res == nullptr)
|
||||||
|
{
|
||||||
|
__origin_override(err, "INSERT");
|
||||||
|
err->flags |= ERR_BUF_NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ptr->_internal_arr = res;
|
||||||
|
ptr->_internal_capacity = 1;
|
||||||
|
ptr->_internal_arr[0] = val;
|
||||||
|
ptr->_internal_size = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ptr->_internal_max_size < ptr->_internal_size + 1)
|
if (ptr->_internal_max_size < ptr->_internal_size + 1)
|
||||||
{
|
{
|
||||||
__origin_override(err, "INSERT");
|
__origin_override(err, "INSERT");
|
||||||
@@ -843,7 +924,6 @@ _vec_char_insert(_VecCharInternal* ptr, const _VecCharInternal* iterator,
|
|||||||
__null_check(ptr, &status);
|
__null_check(ptr, &status);
|
||||||
__null_check(iterator, &status);
|
__null_check(iterator, &status);
|
||||||
if ((status.flags & (ERR_VEC_NULL | ERR_BUF_NULL)) != 0)
|
if ((status.flags & (ERR_VEC_NULL | ERR_BUF_NULL)) != 0)
|
||||||
|
|
||||||
{
|
{
|
||||||
__origin_override(err, "INSERT");
|
__origin_override(err, "INSERT");
|
||||||
err->flags |= status.flags;
|
err->flags |= status.flags;
|
||||||
|
|||||||
+8
-8
@@ -1,13 +1,10 @@
|
|||||||
#ifndef CHAR_H
|
#ifndef CHAR_H
|
||||||
#define CHAR_H
|
#define CHAR_H
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <utils/utils.h>
|
#include <utils/utils.h>
|
||||||
|
|
||||||
|
// Will add DOCS later
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char* _internal_arr;
|
char* _internal_arr;
|
||||||
@@ -37,9 +34,12 @@ _vec_char_shrink_to_fit(_VecCharInternal* ptr, err_t* err);
|
|||||||
void
|
void
|
||||||
_vec_char_swap(_VecCharInternal* ptr, _VecCharInternal* ptr_other, err_t* err);
|
_vec_char_swap(_VecCharInternal* ptr, _VecCharInternal* ptr_other, err_t* err);
|
||||||
|
|
||||||
char*
|
const char*
|
||||||
_vec_char_data(const _VecCharInternal* ptr, err_t* err);
|
_vec_char_data(const _VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
|
size_t
|
||||||
|
_vec_char_size(_VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
_vec_char_capacity(const _VecCharInternal* ptr, err_t* err);
|
_vec_char_capacity(const _VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
@@ -47,12 +47,12 @@ size_t
|
|||||||
_vec_char_max_size(const _VecCharInternal* ptr, err_t* err);
|
_vec_char_max_size(const _VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
void
|
void
|
||||||
_vec_char_delete(const _VecCharInternal* ptr, err_t* err);
|
_vec_char_delete(_VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
void
|
void
|
||||||
_vec_char_clear(_VecCharInternal* ptr, err_t* err);
|
_vec_char_clear(_VecCharInternal* ptr, err_t* err);
|
||||||
|
|
||||||
char
|
char*
|
||||||
_vec_char_at(const _VecCharInternal* ptr, const size_t idx, err_t* err);
|
_vec_char_at(const _VecCharInternal* ptr, const size_t idx, err_t* err);
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ static const size_t VEC_MAX = SIZE_MAX / sizeof(int);
|
|||||||
"Max :- Vector Max value is overloaded") \
|
"Max :- Vector Max value is overloaded") \
|
||||||
X(ERR_ORIGIN_UNALTERABLE, (1u << 7), \
|
X(ERR_ORIGIN_UNALTERABLE, (1u << 7), \
|
||||||
"Origin :- Origin is non-null and cannot be overridden") \
|
"Origin :- Origin is non-null and cannot be overridden") \
|
||||||
X(ERR_STR_NULL, (1u << 8), "String :- String is null value")
|
X(ERR_STR_NULL, (1u << 8), "String :- String is null value") \
|
||||||
|
X(ERR_ERR_T_NULL, (1u << 9), "ERR_T :- Passed error is null")
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@@ -56,18 +57,11 @@ vec_get_error_desc(ERROR_CODES err);
|
|||||||
void
|
void
|
||||||
vec_print_errs(const err_t err);
|
vec_print_errs(const err_t err);
|
||||||
|
|
||||||
// void
|
|
||||||
// _vec_update_origin(err_t* err, char* str);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
print_bits(unsigned int num);
|
print_bits(unsigned int num);
|
||||||
|
|
||||||
#define LOG(msg) printf("LOG: %s\n", msg) /**< Print log message*/
|
#define LOG(msg) printf("LOG: %s\n", msg) /**< Print log message*/
|
||||||
#define DEBUG(msg) printf("DEBUB: %s\n", msg) /**< Print debug message*/
|
#define DEBUG(msg) printf("DEBUG: %s\n", msg) /**< Print debug message*/
|
||||||
#define LOGERROR(error_code) \
|
|
||||||
printf("ERROR: %s\n", \
|
|
||||||
error_messages[error_code]) /**< Print log error message*/
|
|
||||||
|
|
||||||
#define find_len(arr) sizeof(arr) / sizeof(arr[0])
|
#define find_len(arr) sizeof(arr) / sizeof(arr[0])
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
-58
@@ -12,13 +12,11 @@
|
|||||||
* String 32 Bits with suffix of 's'
|
* String 32 Bits with suffix of 's'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// #include <libvec.h>
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "lib/char/char.h"
|
|
||||||
#include "lib/utils/utils.h"
|
#include "lib/utils/utils.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -39,67 +37,11 @@ main()
|
|||||||
char res[4] = { 'a', 'b', 'c', 'd' };
|
char res[4] = { 'a', 'b', 'c', 'd' };
|
||||||
err_t status = ERR_T_INIT, err = ERR_T_INIT;
|
err_t status = ERR_T_INIT, err = ERR_T_INIT;
|
||||||
status.flags |= ERR_BUF_NULL | ERR_VEC_NULL | ERR_STR_NULL;
|
status.flags |= ERR_BUF_NULL | ERR_VEC_NULL | ERR_STR_NULL;
|
||||||
// _VecCharInternal v1 = VEC_INIT;
|
|
||||||
// _vec_char_push_back(&v1, 'c', &status);
|
|
||||||
// _vec_char_print_vec(&v1, &status);
|
|
||||||
|
|
||||||
unsigned int mask = 0;
|
unsigned int mask = 0;
|
||||||
mask = ERR_BUF_NULL ^ status.flags;
|
mask = ERR_BUF_NULL ^ status.flags;
|
||||||
|
|
||||||
print_bits(mask);
|
print_bits(mask);
|
||||||
print_bits(status.flags);
|
print_bits(status.flags);
|
||||||
|
|
||||||
// if (status.origin)
|
|
||||||
// {
|
|
||||||
// printf("Error started in: %s\n", status.origin);
|
|
||||||
// }
|
|
||||||
// printf("%s: %s", err.origin, vec_print_errs(err));
|
|
||||||
// vec_print_errs(status);
|
|
||||||
// printf("%u\n", status.err);
|
|
||||||
// _vec_update_origin(&err, "Test");
|
|
||||||
// printf("%s", err.origin);
|
|
||||||
// for (int i = 0; i < VEC_FAULT_BITS; i++)
|
|
||||||
// {
|
|
||||||
// print_bits(mark);
|
|
||||||
// mark <<= 1;
|
|
||||||
// }
|
|
||||||
// const char* str = vec_get_error_desc(ERR_ALLOC_FAIL);
|
|
||||||
// const unsigned int error = ERR_BUF_NULL;
|
|
||||||
// if (!(ERR_ALLOC_FAIL & SUCCESS))
|
|
||||||
// {
|
|
||||||
// printf("Hello\n");
|
|
||||||
// }
|
|
||||||
// printf("%s", str);
|
|
||||||
// char ar1[] = { '1', '2', '3', '4' };
|
|
||||||
// char ar2[] = { 'a', 'b', 'c', 'd' };
|
|
||||||
// Vec8_t v1 = create(ar2, find_len(ar2));
|
|
||||||
// v1 = reserve(&v1, 9);
|
|
||||||
// print_vec(&v1);
|
|
||||||
// v1 = insert(&v1, 1, 'x');
|
|
||||||
// v1 = resize(&v1, 12);
|
|
||||||
// print_vec(&v1);
|
|
||||||
// v2 = resize(&v2, 3);
|
|
||||||
|
|
||||||
// print_vec(&v2);
|
|
||||||
|
|
||||||
// if (swap(&v1, &v2))
|
|
||||||
// {
|
|
||||||
// print_vec(&v1);
|
|
||||||
// print_vec(&v2);
|
|
||||||
// }
|
|
||||||
// Vec8_t v1 = create(nullptr, 0);
|
|
||||||
// v1 = reserve(&v1, 10);
|
|
||||||
// v1 = pop_back(&v1);
|
|
||||||
// for (int i = 0; i < 10; i++)
|
|
||||||
// {
|
|
||||||
// v1 = push_back(&v1, 'a');
|
|
||||||
// }
|
|
||||||
// print_vec(&v1);
|
|
||||||
// printf("SIZE: %zu\n", v1.size);
|
|
||||||
// v1 = push_back(&v1, 'a');
|
|
||||||
// print_vec(&v1);
|
|
||||||
// printf("SIZE: %zu\n", v1.size);
|
|
||||||
// delete(&v1);
|
|
||||||
// delete(&v2);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user