File Coverage

src/pdfmake_arena.c
Criterion Covered Total %
statement 125 165 75.7
branch 52 100 52.0
condition n/a
subroutine n/a
pod n/a
total 177 265 66.7


line stmt bran cond sub pod time code
1             /*
2             * libpdfmake — arena allocator implementation.
3             *
4             * Bump allocator with chained 64KB blocks. One arena per document.
5             * Also handles name interning for O(1) name comparison.
6             */
7              
8             #include "pdfmake_arena.h"
9             #include
10             #include
11              
12             /*----------------------------------------------------------------------------
13             * Internal helpers
14             *--------------------------------------------------------------------------*/
15              
16             /* Align size up to 8 bytes. */
17 28333           static PDFMAKE_INLINE size_t align8(size_t size) {
18 28333           return (size + 7) & ~(size_t)7;
19             }
20              
21             /* FNV-1a hash for name strings. */
22 22959           static uint32_t fnv1a_hash(const char *bytes, size_t len) {
23 22959           uint32_t h = 2166136261u;
24             size_t i;
25 164707 100         for (i = 0; i < len; i++) {
26 141748           h ^= (uint8_t)bytes[i];
27 141748           h *= 16777619u;
28             }
29 22959           return h;
30             }
31              
32             /* Allocate a new block with given capacity. */
33 1077           static pdfmake_arena_block_t *block_new(size_t cap) {
34 1077           pdfmake_arena_block_t *b = malloc(sizeof(*b) + cap);
35 1077 50         if (!b) return NULL;
36 1077           b->next = NULL;
37 1077           b->used = 0;
38 1077           b->cap = cap;
39 1077           return b;
40             }
41              
42             /* Free a block chain starting from `b`. */
43 806           static void block_chain_free(pdfmake_arena_block_t *b) {
44 1662 100         while (b) {
45 856           pdfmake_arena_block_t *next = b->next;
46 856           free(b);
47 856           b = next;
48             }
49 806           }
50              
51             /*----------------------------------------------------------------------------
52             * Arena lifecycle
53             *--------------------------------------------------------------------------*/
54              
55 992           pdfmake_arena_t *pdfmake_arena_new(void) {
56 992           pdfmake_arena_t *arena = malloc(sizeof(*arena));
57 992 50         if (!arena) return NULL;
58              
59 992           arena->head = block_new(PDFMAKE_ARENA_BLOCK_SIZE);
60 992 50         if (!arena->head) {
61 0           free(arena);
62 0           return NULL;
63             }
64 992           arena->current = arena->head;
65 992           arena->total = PDFMAKE_ARENA_BLOCK_SIZE;
66              
67             /* Initialize name table with separate names array and hash table. */
68 992           arena->names.names = calloc(PDFMAKE_NAME_TABLE_INIT_CAP, sizeof(pdfmake_name_entry_t));
69 992           arena->names.hash = calloc(PDFMAKE_NAME_TABLE_INIT_CAP, sizeof(pdfmake_name_hash_entry_t));
70 992 50         if (!arena->names.names || !arena->names.hash) {
    50          
71 0           free(arena->names.names);
72 0           free(arena->names.hash);
73 0           free(arena->head);
74 0           free(arena);
75 0           return NULL;
76             }
77 992           arena->names.names_cap = PDFMAKE_NAME_TABLE_INIT_CAP;
78 992           arena->names.names_len = 0;
79 992           arena->names.hash_cap = PDFMAKE_NAME_TABLE_INIT_CAP;
80              
81 992           return arena;
82             }
83              
84 806           void pdfmake_arena_free(pdfmake_arena_t *arena) {
85 806 50         if (!arena) return;
86 806           block_chain_free(arena->head);
87 806           free(arena->names.names);
88 806           free(arena->names.hash);
89 806           free(arena);
90             }
91              
92 1           void pdfmake_arena_reset(pdfmake_arena_t *arena) {
93 1 50         if (!arena) return;
94              
95             /* Free all blocks except the first. */
96 1 50         if (arena->head && arena->head->next) {
    50          
97 0           block_chain_free(arena->head->next);
98 0           arena->head->next = NULL;
99             }
100              
101             /* Reset first block. */
102 1 50         if (arena->head) {
103 1           arena->head->used = 0;
104 1           arena->total = arena->head->cap;
105             }
106 1           arena->current = arena->head;
107              
108             /* Clear name table (entries point into freed blocks). */
109 1 50         if (arena->names.names) {
110 1           memset(arena->names.names, 0, arena->names.names_cap * sizeof(pdfmake_name_entry_t));
111             }
112 1 50         if (arena->names.hash) {
113 1           memset(arena->names.hash, 0, arena->names.hash_cap * sizeof(pdfmake_name_hash_entry_t));
114             }
115 1           arena->names.names_len = 0;
116             }
117              
118             /*----------------------------------------------------------------------------
119             * Allocation
120             *--------------------------------------------------------------------------*/
121              
122 28333           void *pdfmake_arena_alloc(pdfmake_arena_t *arena, size_t size) {
123             size_t aligned;
124             size_t new_cap;
125             pdfmake_arena_block_t *blk;
126             pdfmake_arena_block_t *new_blk;
127             void *ptr;
128              
129 28333 50         if (!arena) return NULL;
130              
131 28333           aligned = align8(size);
132 28333 100         if (aligned == 0) aligned = 8; /* Always return valid pointer for size=0. */
133              
134 28333           blk = arena->current;
135              
136             /* Try current block first. */
137 28333 50         if (blk && blk->used + aligned <= blk->cap) {
    100          
138 28248           ptr = blk->data + blk->used;
139 28248           blk->used += aligned;
140 28248           return ptr;
141             }
142              
143             /* Need a new block. Size it to fit the request if larger than default. */
144 85           new_cap = PDFMAKE_ARENA_BLOCK_SIZE;
145 85 100         if (aligned > new_cap) {
146 11           new_cap = aligned;
147             }
148              
149 85           new_blk = block_new(new_cap);
150 85 50         if (!new_blk) return NULL;
151              
152             /* Chain new block. */
153 85 50         if (blk) {
154 85           blk->next = new_blk;
155             } else {
156 0           arena->head = new_blk;
157             }
158 85           arena->current = new_blk;
159 85           arena->total += new_cap;
160              
161 85           ptr = new_blk->data;
162 85           new_blk->used = aligned;
163 85           return ptr;
164             }
165              
166 11865           void *pdfmake_arena_calloc(pdfmake_arena_t *arena, size_t size) {
167 11865           void *ptr = pdfmake_arena_alloc(arena, size);
168 11865 50         if (ptr && size > 0) {
    50          
169 11865           memset(ptr, 0, size);
170             }
171 11865           return ptr;
172             }
173              
174 499           char *pdfmake_arena_strdup(pdfmake_arena_t *arena, const char *s) {
175             size_t len;
176             char *dup;
177              
178 499 50         if (!s) return NULL;
179 499           len = strlen(s);
180 499           dup = pdfmake_arena_alloc(arena, len + 1);
181 499 50         if (dup) {
182 499           memcpy(dup, s, len + 1);
183             }
184 499           return dup;
185             }
186              
187 566           void *pdfmake_arena_memdup(pdfmake_arena_t *arena, const void *src, size_t len) {
188             void *dup;
189              
190 566 50         if (!src || len == 0) return NULL;
    50          
191 566           dup = pdfmake_arena_alloc(arena, len);
192 566 50         if (dup) {
193 566           memcpy(dup, src, len);
194             }
195 566           return dup;
196             }
197              
198             /*----------------------------------------------------------------------------
199             * Name interning
200             *--------------------------------------------------------------------------*/
201              
202             /* Grow hash table when load factor exceeds 70%. */
203 0           static int name_hash_grow(pdfmake_arena_t *arena) {
204 0           pdfmake_name_table_t *t = &arena->names;
205 0           size_t new_cap = t->hash_cap * 2;
206             pdfmake_name_hash_entry_t *new_hash;
207             size_t i;
208              
209 0           new_hash = calloc(new_cap, sizeof(pdfmake_name_hash_entry_t));
210 0 0         if (!new_hash) return 0;
211              
212             /* Rehash all entries from names array. */
213 0 0         for (i = 0; i < t->names_len; i++) {
214 0           uint32_t hash = t->names[i].hash;
215 0           size_t idx = hash & (new_cap - 1);
216 0 0         while (new_hash[idx].id != 0) {
217 0           idx = (idx + 1) & (new_cap - 1);
218             }
219 0           new_hash[idx].hash = hash;
220 0           new_hash[idx].id = (uint32_t)(i + 1); /* 1-based id */
221             }
222              
223 0           free(t->hash);
224 0           t->hash = new_hash;
225 0           t->hash_cap = new_cap;
226 0           return 1;
227             }
228              
229             /* Grow names array. */
230 0           static int name_array_grow(pdfmake_arena_t *arena) {
231 0           pdfmake_name_table_t *t = &arena->names;
232 0           size_t new_cap = t->names_cap * 2;
233              
234 0           pdfmake_name_entry_t *new_names = realloc(t->names, new_cap * sizeof(pdfmake_name_entry_t));
235 0 0         if (!new_names) return 0;
236              
237             /* Zero the new entries. */
238 0           memset(new_names + t->names_cap, 0, (new_cap - t->names_cap) * sizeof(pdfmake_name_entry_t));
239              
240 0           t->names = new_names;
241 0           t->names_cap = new_cap;
242 0           return 1;
243             }
244              
245 22959           uint32_t pdfmake_arena_intern_name(pdfmake_arena_t *arena, const char *bytes, size_t len) {
246             pdfmake_name_table_t *t;
247             uint32_t hash;
248             size_t idx;
249             char *dup;
250             uint32_t new_id;
251              
252 22959 50         if (!arena || !bytes) return 0;
    50          
253              
254 22959           t = &arena->names;
255 22959           hash = fnv1a_hash(bytes, len);
256 22959           idx = hash & (t->hash_cap - 1);
257              
258             /* Probe for existing entry in hash table. */
259 24828 100         while (t->hash[idx].id != 0) {
260 15778           uint32_t id = t->hash[idx].id;
261 15778           pdfmake_name_entry_t *entry = &t->names[id - 1];
262 15778 100         if (entry->hash == hash &&
263 13909 50         entry->len == len &&
264 13909 50         memcmp(entry->bytes, bytes, len) == 0) {
265             /* Found existing: return the stable id. */
266 13909           return id;
267             }
268 1869           idx = (idx + 1) & (t->hash_cap - 1);
269             }
270              
271             /* Not found - need to insert new name. */
272              
273             /* Grow names array if needed. */
274 9050 50         if (t->names_len >= t->names_cap) {
275 0 0         if (!name_array_grow(arena)) return 0;
276             }
277              
278             /* Grow hash table if load factor > 70%. */
279 9050 50         if ((t->names_len + 1) * 10 > t->hash_cap * 7) {
280 0 0         if (!name_hash_grow(arena)) return 0;
281             /* Recompute idx after resize. */
282 0           idx = hash & (t->hash_cap - 1);
283 0 0         while (t->hash[idx].id != 0) {
284 0           idx = (idx + 1) & (t->hash_cap - 1);
285             }
286             }
287              
288             /* Copy bytes into arena. */
289 9050           dup = pdfmake_arena_alloc(arena, len + 1);
290 9050 50         if (!dup) return 0;
291 9050           memcpy(dup, bytes, len);
292 9050           dup[len] = '\0';
293              
294             /* Add to names array (id is 1-based). */
295 9050           new_id = (uint32_t)(t->names_len + 1);
296 9050           t->names[t->names_len].bytes = dup;
297 9050           t->names[t->names_len].len = len;
298 9050           t->names[t->names_len].hash = hash;
299 9050           t->names_len++;
300              
301             /* Add to hash table. */
302 9050           t->hash[idx].hash = hash;
303 9050           t->hash[idx].id = new_id;
304              
305 9050           return new_id;
306             }
307              
308 11252           const char *pdfmake_arena_name_bytes(pdfmake_arena_t *arena, uint32_t id) {
309 11252 50         if (!arena || id == 0 || id > arena->names.names_len) return NULL;
    50          
    100          
310 11240           return arena->names.names[id - 1].bytes;
311             }
312              
313 10550           size_t pdfmake_arena_name_len(pdfmake_arena_t *arena, uint32_t id) {
314 10550 50         if (!arena || id == 0 || id > arena->names.names_len) return 0;
    50          
    100          
315 10538           return arena->names.names[id - 1].len;
316             }