Branch data Line data Source code
1 : : #include "mem.h"
2 : : #include "mem_internal.h"
3 : : #include <stdint.h>
4 : : #include <string.h>
5 : :
6 : : /**
7 : : * @brief Internal string transfer helper used by both append and copy paths
8 : : *
9 : : * This function is the shared core behind the public string helpers. It knows
10 : : * how to read a source string in several ways and then either append that
11 : : * visible payload to the destination or replace the destination with it
12 : : *
13 : : * The destination must already be a string descriptor in both append and
14 : : * replace modes. Callers that start from a data-mode buffer must convert it
15 : : * first via @ref mem_convert_data_to_string. This function never promotes a
16 : : * data-mode descriptor into string mode by itself
17 : : *
18 : : * Think about the behavior as two independent switches:
19 : : * - source mode answers "how should the source length be understood"
20 : : * - transfer mode answers "should that visible payload be appended or copied
21 : : * over the old destination contents"
22 : : *
23 : : * The source is always interpreted using the destination element width, so the
24 : : * same code works for byte strings and for wider code units
25 : : *
26 : : * Public wrappers such as @ref mem_concat_unbounded_string,
27 : : * @ref mem_concat_bounded_string, @ref mem_copy_unbounded_string,
28 : : * @ref mem_copy_bounded_string, @ref mem_concat_fixed_string, and
29 : : * @ref mem_copy_fixed_string select the matching binary mode flags
30 : : *
31 : : * @par Source flags
32 : : * - `SOURCE_BOUNDED_STRING`: use @p source_range_bytes as the byte limit and
33 : : * stop at the first zero-valued terminator element if one is found earlier
34 : : * - `SOURCE_UNBOUNDED_STRING`: ignore @p source_range_bytes and scan until the
35 : : * first zero-valued terminator element
36 : : * - `SOURCE_FIXED_STRING`: trust that the provided range already ends with one
37 : : * terminator element and do not scan for it again
38 : : *
39 : : * @par Transfer flags
40 : : * - `TRANSFER_APPEND`: keep the old destination text and add the visible
41 : : * source payload after it
42 : : * - `TRANSFER_REPLACE`: discard the old visible destination text and make
43 : : * the visible source payload the new destination text
44 : : *
45 : : * @par Empty-source rules
46 : : * In append mode, an empty source means "change nothing". In replace mode, an
47 : : * empty source means "reset to an empty string", so the result is a descriptor
48 : : * whose visible length is zero and whose storage still contains one terminator
49 : : *
50 : : * @par Self-aliasing
51 : : * The source may point inside the current destination allocation. Since the
52 : : * destination is always required to be in string mode, self-aliased sources
53 : : * are always validated against the visible string bounds. This is an
54 : : * intentional supported scenario, not an accident. For example, replacing
55 : : * `"abcdef"` with a suffix that starts at `"cdef"` is valid
56 : : *
57 : : * The tricky case is internal replace with shrink. A normal `m_resize(...)`
58 : : * can move or rewrite the terminator before the aliased source bytes are read.
59 : : * To avoid corrupting that source slice, the helper first moves the visible
60 : : * payload to the front of the current buffer, restores the terminator there,
61 : : * and only then finalizes the logical shrink
62 : : *
63 : : * @par Validation rules
64 : : * - @p destination must be initialized and must already be a string descriptor
65 : : * in both append and replace modes. Use @ref mem_convert_data_to_string first
66 : : * when starting from a data-mode buffer
67 : : * - bounded and fixed-string flags require @p source_range_bytes to describe
68 : : * whole logical elements
69 : : * - unbounded mode requires an external non-NULL source to be truly
70 : : * terminated by a zero-valued element of the destination element width
71 : : * - internal aliased sources must start on an element boundary and must stay
72 : : * inside the logical destination contents accepted by the selected mode
73 : : * - @p mode combines source-interpretation flags with append-or-replace flags
74 : : *
75 : : * @param mode Binary mode flags that select both source interpretation and
76 : : * append-or-replace behavior
77 : : * @param destination Pointer to the destination descriptor
78 : : * @param source_range_bytes Source byte range. In bounded mode this is the
79 : : * scan limit. In fixed-string mode this is the full source size. In
80 : : * unbounded mode it is ignored. When used, it must be divisible by the
81 : : * destination element size
82 : : * @param source_string Pointer to source string elements. In append mode,
83 : : * `NULL` means "append nothing". In replace mode, `NULL` means
84 : : * "replace with an empty string"
85 : : * @return `SUCCESS` on success; `FAILURE` otherwise
86 : : */
87 : 44235 : Return mem_core_string(
88 : : const MEM_CORE_MODE mode,
89 : : memory *destination,
90 : : const size_t source_range_bytes,
91 : : const void *const source_string)
92 : : {
93 : : /* Status returned by this function through provide()
94 : : Default value assumes successful completion */
95 : 44235 : Return status = SUCCESS;
96 : :
97 : : /* Source-mode subset extracted from the combined mode flags */
98 : 44235 : const MEM_CORE_MODE source_mode = mode & SOURCE_MASK;
99 : :
100 : : /* Transfer-mode subset extracted from the combined mode flags */
101 : 44235 : const MEM_CORE_MODE transfer_mode = mode & TRANSFER_MASK;
102 : :
103 : : /* Visible source prefix length measured in elements */
104 : 44235 : size_t source_length = 0;
105 : :
106 : : /* Offset of a self-aliased source from the beginning of destination data.
107 : : Saved before any resize so the source can be rebuilt afterwards */
108 : 44235 : size_t source_offset = 0;
109 : :
110 : : /* Visible string elements remaining from an internal source start up to the terminator */
111 : 44235 : size_t remaining_visible_elements = 0;
112 : :
113 : : /* True when source_string points into the current destination allocation */
114 : 44235 : bool source_is_inside_destination = false;
115 : :
116 : : /* True when the internal source is validated against string-mode visible bounds */
117 : 44235 : bool source_is_inside_string_destination = false;
118 : :
119 : : /* True when the transfer should behave as if the source payload were empty */
120 : 44235 : bool source_is_effectively_empty = false;
121 : :
122 [ - + ]: 44235 : if(destination == NULL)
123 : : {
124 : 0 : report("Memory management; Destination must be non-NULL");
125 : 0 : provide(FAILURE);
126 : : }
127 : :
128 [ + + - + ]: 44235 : if(destination->length > 0 && destination->data == NULL)
129 : : {
130 : 0 : report("Memory management; Descriptor has non-zero length with NULL data pointer");
131 : 0 : provide(FAILURE);
132 : : }
133 : :
134 [ - + ]: 44235 : if(destination->single_element_size == 0)
135 : : {
136 : 0 : report("Memory management; Destination element size is zero (uninitialized)");
137 : 0 : provide(FAILURE);
138 : : }
139 : :
140 [ + + ]: 44235 : if(destination->is_string == false)
141 : : {
142 : 2 : report("Memory management; Destination must be a string descriptor");
143 : 2 : provide(FAILURE);
144 : : }
145 : :
146 [ + + ]: 44233 : if(source_string == NULL)
147 : : {
148 [ + - ]: 4 : if(transfer_mode & TRANSFER_APPEND)
149 : : {
150 : 4 : provide(status);
151 : : }
152 : :
153 : 0 : source_is_effectively_empty = true;
154 : : }
155 : :
156 : : /* Outside unbounded mode, the byte count must describe a whole number of logical
157 : : string elements. Partial elements would make later scan and copy steps ambiguous.
158 : : Unbounded mode intentionally ignores source_range_bytes, so the check is skipped */
159 [ + - ]: 44229 : if(source_is_effectively_empty == false &&
160 [ + + ]: 44229 : (source_mode & SOURCE_UNBOUNDED_STRING) == 0 &&
161 [ + + ]: 30241 : (source_range_bytes % destination->single_element_size) != 0)
162 : : {
163 : 1 : report("Memory management; Size %zu is not divisible by element size %zu",
164 : : source_range_bytes,
165 : : destination->single_element_size);
166 : 1 : provide(FAILURE);
167 : : }
168 : :
169 [ + + + + ]: 44228 : if((source_mode & SOURCE_UNBOUNDED_STRING) == 0 && source_range_bytes == 0)
170 : : {
171 [ + - ]: 2 : if(transfer_mode & TRANSFER_APPEND)
172 : : {
173 : 2 : provide(status);
174 : : }
175 : :
176 : 0 : source_is_effectively_empty = true;
177 : : }
178 : :
179 : : /* Check whether the source aliases the current destination allocation */
180 [ + - + - ]: 44226 : if((TRIUMPH & status) &&
181 : 44226 : source_string != NULL &&
182 [ + - ]: 44226 : source_is_effectively_empty == false)
183 : : {
184 : : /* Current logical destination size in bytes, used to validate self-aliased sources */
185 : 44226 : size_t destination_bytes = 0;
186 : :
187 : 44226 : run(mem_guarded_byte_size(destination,destination->length,&destination_bytes));
188 : :
189 [ + - + + : 44226 : if((TRIUMPH & status) && destination->data != NULL && destination->actually_allocated_bytes > 0)
+ - ]
190 : : {
191 : : /* Start address of the current destination allocation used for range checks */
192 : 27615 : const uintptr_t destination_begin = (uintptr_t)destination->data;
193 : :
194 : : /* Start address of the source string used for range checks */
195 : 27615 : const uintptr_t source_begin = (uintptr_t)source_string;
196 : :
197 [ + - ]: 27615 : if(destination->actually_allocated_bytes > UINTPTR_MAX - destination_begin ||
198 [ - + ]: 27615 : destination_bytes > UINTPTR_MAX - destination_begin)
199 : : {
200 : 0 : report("Memory management; Destination address range overflows");
201 : 2 : provide(FAILURE);
202 : : }
203 : :
204 : : /* End address of the current allocation, one past the last allocated byte */
205 : 27615 : const uintptr_t allocation_end = destination_begin + destination->actually_allocated_bytes;
206 : :
207 : : /* End address of the current logical destination contents */
208 : 27615 : const uintptr_t logical_end = destination_begin + destination_bytes;
209 : :
210 [ + + + + ]: 27615 : if(source_begin >= destination_begin && source_begin < allocation_end)
211 : : {
212 : 14 : source_is_inside_destination = true;
213 : 14 : source_offset = source_begin - destination_begin;
214 : :
215 [ - + ]: 14 : if((source_offset % destination->single_element_size) != 0)
216 : : {
217 : 0 : report("Memory management; Source start is not aligned to element size %zu",
218 : : destination->single_element_size);
219 : 0 : provide(FAILURE);
220 : : }
221 : :
222 : 14 : source_is_inside_string_destination = true;
223 : :
224 : : {
225 : : /* Element offset of an internal source from the beginning of destination data */
226 : 14 : const size_t source_offset_elements = source_offset / destination->single_element_size;
227 : :
228 [ + + ]: 14 : if(source_mode & SOURCE_UNBOUNDED_STRING)
229 : : {
230 [ + + ]: 5 : if(source_begin >= logical_end)
231 : : {
232 : 1 : report("Memory management; Unbounded source start exceeds destination logical bounds");
233 : 1 : provide(FAILURE);
234 : : }
235 : :
236 : : /* Reject starts past the visible terminator on purpose. Treating them
237 : : as empty input would silently hide caller-side pointer bugs */
238 [ - + ]: 4 : if(source_offset_elements > destination->string_length)
239 : : {
240 : 0 : report("Memory management; Source start exceeds destination visible string bounds");
241 : 0 : provide(FAILURE);
242 : : }
243 : :
244 : 4 : remaining_visible_elements = destination->string_length - source_offset_elements;
245 [ + + ]: 9 : } else if(source_mode & SOURCE_BOUNDED_STRING){
246 [ - + ]: 6 : if(source_begin > logical_end)
247 : : {
248 : 0 : report("Memory management; Bounded source start exceeds destination logical bounds");
249 : 0 : provide(FAILURE);
250 : : }
251 : :
252 : : /* Reject starts past the visible terminator on purpose. Treating them
253 : : as empty input would silently hide caller-side pointer bugs */
254 [ + + ]: 6 : if(source_offset_elements > destination->string_length)
255 : : {
256 : 1 : report("Memory management; Source start exceeds destination visible string bounds");
257 : 1 : provide(FAILURE);
258 : : }
259 : :
260 : 5 : remaining_visible_elements = destination->string_length - source_offset_elements;
261 : : } else {
262 [ + - - + ]: 3 : if(source_begin > logical_end || source_range_bytes > destination_bytes - source_offset)
263 : : {
264 : 0 : report("Memory management; Source range exceeds destination logical bounds");
265 : 0 : provide(FAILURE);
266 : : }
267 : :
268 : : /* Reject starts past the visible terminator on purpose. Treating them
269 : : as empty input would silently hide caller-side pointer bugs */
270 [ - + ]: 3 : if(source_offset_elements > destination->string_length)
271 : : {
272 : 0 : report("Memory management; Source start exceeds destination visible string bounds");
273 : 0 : provide(FAILURE);
274 : : }
275 : :
276 : 3 : remaining_visible_elements = destination->string_length - source_offset_elements;
277 : : }
278 : : }
279 : : }
280 : : }
281 : : }
282 : :
283 [ + - + - ]: 44224 : if((TRIUMPH & status) &&
284 : 44224 : source_string != NULL &&
285 [ + - ]: 44224 : source_is_effectively_empty == false)
286 : : {
287 : : /* Read-only byte view of the source string used by the scanners */
288 : 44224 : const unsigned char *const source_data_view = (const unsigned char *)source_string;
289 : :
290 [ + + ]: 44224 : if(source_mode & SOURCE_BOUNDED_STRING)
291 : : {
292 [ + + ]: 13185 : if(source_is_inside_string_destination == true)
293 : : {
294 : : /* Number of logical source elements implied by the provided byte count */
295 : 5 : const size_t source_elements = source_range_bytes / destination->single_element_size;
296 : :
297 : : /* Clamp the requested internal slice to the remaining visible suffix instead of rescanning */
298 : 5 : source_length = source_elements;
299 : :
300 [ + - ]: 5 : if(remaining_visible_elements < source_length)
301 : : {
302 : 5 : source_length = remaining_visible_elements;
303 : : }
304 : : } else {
305 : : /* Search up to the first zero-valued element within the provided byte limit */
306 : 13180 : run(mem_string_measure_length(
307 : : source_data_view,
308 : : source_range_bytes,
309 : : destination->single_element_size,
310 : : true,
311 : : &source_length,
312 : : NULL));
313 : : }
314 [ + + ]: 31039 : } else if(source_mode & SOURCE_UNBOUNDED_STRING){
315 [ + + ]: 13987 : if(source_is_inside_string_destination == true)
316 : : {
317 : : /* Reuse the known internal string length instead of rescanning destination-owned text */
318 : 4 : source_length = remaining_visible_elements;
319 : : } else {
320 : : /* Search up to the first zero-valued element without an external bound */
321 : 13983 : run(mem_string_measure_length(
322 : : source_data_view,
323 : : 0,
324 : : destination->single_element_size,
325 : : false,
326 : : &source_length,
327 : : NULL));
328 : : }
329 : : } else {
330 : : /* Fixed-string mode trusts that the provided range already ends with one
331 : : terminator element, so the last logical element is accepted as-is */
332 : : /* Number of logical source elements implied by the provided byte count */
333 : 17052 : const size_t source_elements = source_range_bytes / destination->single_element_size;
334 : 17052 : source_length = source_elements - 1;
335 : : }
336 : : }
337 : :
338 [ + - ]: 44224 : if((TRIUMPH & status) &&
339 [ + + ]: 44224 : (transfer_mode & TRANSFER_APPEND) != 0 &&
340 [ + + ]: 26544 : source_length == 0)
341 : : {
342 : 4 : provide(status);
343 : : }
344 : :
345 : : /* Visible destination length before appending. Used as the append offset in elements */
346 : 44220 : size_t destination_length = 0;
347 : :
348 [ + + ]: 44220 : if(transfer_mode & TRANSFER_APPEND)
349 : : {
350 : 26540 : run(mem_string_length(destination,&destination_length));
351 : : }
352 : :
353 : : /* Combined visible string length after the transfer, excluding the trailing terminator */
354 : 44220 : size_t visible_string_length = source_length;
355 : :
356 [ + - + + ]: 44220 : if(TRIUMPH & status && (transfer_mode & TRANSFER_APPEND) != 0)
357 : : {
358 : 26540 : run(mem_guarded_add(destination_length,source_length,&visible_string_length));
359 : :
360 [ - + ]: 26540 : if(CRITICAL & status)
361 : : {
362 : 0 : report("Memory management; Concatenation would overflow element count");
363 : : }
364 : : }
365 : :
366 : : /* Total destination element count after the transfer, including the trailing terminator */
367 : 44220 : size_t new_total_elements = 0;
368 : :
369 [ + - ]: 44220 : if(TRIUMPH & status)
370 : : {
371 : 44220 : run(mem_guarded_add(visible_string_length,1,&new_total_elements));
372 : :
373 [ - + ]: 44220 : if(CRITICAL & status)
374 : : {
375 : 0 : report("Memory management; Not enough room for string terminator");
376 : : }
377 : : }
378 : :
379 : : /* Byte offset where the copied source prefix starts in destination */
380 : 44220 : size_t offset_bytes = 0;
381 : :
382 : : /* Number of source bytes to copy after the source length is known */
383 : 44220 : size_t source_bytes = 0;
384 : :
385 [ + + ]: 44220 : if(transfer_mode & TRANSFER_APPEND)
386 : : {
387 : 26540 : run(mem_guarded_byte_size(destination,destination_length,&offset_bytes));
388 : : }
389 : :
390 : 44220 : run(mem_guarded_byte_size(destination,source_length,&source_bytes));
391 : :
392 [ + - ]: 44220 : if((TRIUMPH & status) &&
393 [ + + + + ]: 44220 : (transfer_mode & TRANSFER_REPLACE) != 0 &&
394 : : source_is_inside_string_destination == true)
395 : 7 : {
396 : : /* Writable byte view of the existing destination buffer before the shrink finalizes */
397 : 7 : unsigned char *destination_data_rewritable = (unsigned char *)destination->data;
398 : :
399 [ - + ]: 7 : if(destination_data_rewritable == NULL)
400 : : {
401 : 0 : report("Memory management; Destination data pointer is NULL during internal replace");
402 : 0 : status = FAILURE;
403 : : } else {
404 [ + + ]: 7 : if(source_bytes > 0)
405 : : {
406 : 5 : memmove(destination_data_rewritable,
407 : 5 : destination_data_rewritable + source_offset,
408 : : source_bytes);
409 : : }
410 : :
411 [ + - ]: 7 : if(TRIUMPH & status)
412 : : {
413 : 7 : run(mem_write_zero_terminator(destination,source_length));
414 : :
415 [ + - ]: 7 : if(TRIUMPH & status)
416 : : {
417 : 7 : destination->string_length = source_length;
418 : 7 : run(m_resize(destination,new_total_elements));
419 : : }
420 : : }
421 : : }
422 : : } else {
423 : : /* m_resize may call realloc, so any source_string pointer into destination data
424 : : becomes invalid here. Self-aliasing remains safe only because the code
425 : : saved source_offset earlier and can rebuild the pointer afterwards */
426 : 44213 : run(m_resize(destination,new_total_elements));
427 : :
428 [ + - ]: 44213 : if(TRIUMPH & status)
429 : : {
430 : : /* Writable byte view of the destination buffer after the possible realloc */
431 : 44213 : unsigned char *destination_data_rewritable = (unsigned char *)destination->data;
432 : :
433 [ - + ]: 44213 : if(destination_data_rewritable == NULL)
434 : : {
435 : 0 : report("Memory management; Destination data pointer is NULL after m_resize");
436 : 0 : status = FAILURE;
437 : : } else {
438 [ + + ]: 44213 : if(source_bytes > 0)
439 : : {
440 : : /* Resolved byte view of the source payload after the optional self-alias rebase */
441 : 44210 : const unsigned char *source_data_view = (const unsigned char *)source_string;
442 : :
443 [ + + ]: 44210 : if(source_is_inside_destination == true)
444 : : {
445 : : /* Rebase the aliased source onto the possibly relocated destination
446 : : buffer before memmove reads from it */
447 : 3 : source_data_view = destination_data_rewritable + source_offset;
448 : : }
449 : :
450 : 44210 : memmove(destination_data_rewritable + offset_bytes,source_data_view,source_bytes);
451 : : }
452 : :
453 [ + - ]: 44213 : if(TRIUMPH & status)
454 : : {
455 : 44213 : run(mem_write_zero_terminator(destination,visible_string_length));
456 : :
457 [ + - ]: 44213 : if(TRIUMPH & status)
458 : : {
459 : 44213 : destination->string_length = visible_string_length;
460 : : }
461 : : }
462 : : }
463 : : }
464 : : }
465 : :
466 : 44220 : provide(status);
467 : : }
|