Branch data Line data Source code
1 : : #include "mem.h"
2 : : #include "mem_internal.h"
3 : :
4 : : /**
5 : : * @brief Shorten the visible string without shrinking the reserved buffer
6 : : *
7 : : * Use this helper when the string should become logically shorter, but the
8 : : * current allocation should stay in place. The helper writes a zero-valued
9 : : * terminator at @p new_visible_length and updates `string_length`, while the
10 : : * descriptor's total `length` is left unchanged. In string mode that is
11 : : * intentional: `string_length` tracks only the visible prefix, while `length`
12 : : * tracks the whole logical descriptor span. For example, a descriptor may
13 : : * hold visible text `"alphabet"` with `string_length == 8` and `length == 32`
14 : : * after earlier reserve growth; truncating it to `5` makes the visible text
15 : : * `"alpha"` and updates `string_length` to `5`, but `length` still remains `32`
16 : : *
17 : : * If @p new_visible_length is greater than the current visible string length,
18 : : * the call succeeds as a no-op. If the requested visible length already
19 : : * matches the current one, the helper may still rewrite the terminator slot so
20 : : * the string stays well-formed. Descriptors whose reserved byte count no
21 : : * longer covers the current logical payload are rejected instead of being
22 : : * treated as no-ops
23 : : *
24 : : * Small example:
25 : : * @code
26 : : * if((TRIUMPH & mem_string_truncate(name,5)) == 0) { return FAILURE; }
27 : : * // "alphabet" becomes visible as "alpha", but the descriptor keeps the same reserve
28 : : * @endcode
29 : : *
30 : : * @param destination String descriptor to truncate
31 : : * @param new_visible_length Requested visible string length measured in elements
32 : : * @return `SUCCESS` on success; `FAILURE` otherwise
33 : : */
34 : 70632 : Return mem_string_truncate(
35 : : memory *destination,
36 : : size_t new_visible_length)
37 : : {
38 : : /* Status returned by this function through provide()
39 : : Default value assumes successful completion */
40 : 70632 : Return status = SUCCESS;
41 : :
42 : : /* Byte size of the payload currently claimed by destination, used for reserve validation */
43 : 70632 : size_t current_payload_bytes = 0;
44 : :
45 [ - + ]: 70632 : if(destination == NULL)
46 : : {
47 : 0 : report("Memory management; string truncate destination must be non-NULL");
48 : 0 : provide(FAILURE);
49 : : }
50 : :
51 [ + - - + ]: 70632 : if((TRIUMPH & status) && destination->single_element_size == 0)
52 : : {
53 : 0 : report("Memory management; String truncate destination element size is zero");
54 : 0 : provide(FAILURE);
55 : : }
56 : :
57 [ + - + + : 70632 : if((TRIUMPH & status) && destination->length > 0 && destination->data == NULL)
- + ]
58 : : {
59 : 0 : report("Memory management; Descriptor has non-zero length with NULL data pointer");
60 : 0 : provide(FAILURE);
61 : : }
62 : :
63 [ + - ]: 70632 : if((TRIUMPH & status) &&
64 [ + + ]: 70632 : destination->actually_allocated_bytes > 0 &&
65 [ - + ]: 70631 : destination->data == NULL)
66 : : {
67 : 0 : report("Memory management; Descriptor has reserved bytes with NULL data pointer during string truncate");
68 : 0 : provide(FAILURE);
69 : : }
70 : :
71 [ + - + + ]: 70632 : if((TRIUMPH & status) && destination->is_string == false)
72 : : {
73 : 2 : report("Memory management; string truncate requires a string descriptor");
74 : 2 : provide(FAILURE);
75 : : }
76 : :
77 [ + - + - ]: 70630 : if((TRIUMPH & status) && destination->length > 0)
78 : : {
79 : 70630 : run(mem_guarded_byte_size(destination,destination->length,¤t_payload_bytes));
80 : : }
81 : :
82 [ + - ]: 70630 : if((TRIUMPH & status) &&
83 [ + - ]: 70630 : destination->length > 0 &&
84 [ + + ]: 70630 : destination->actually_allocated_bytes < current_payload_bytes)
85 : : {
86 : 2 : report("Memory management; Descriptor reserve is smaller than logical payload during string truncate");
87 : 2 : provide(FAILURE);
88 : : }
89 : :
90 [ + - ]: 70628 : if(TRIUMPH & status)
91 : : {
92 [ - + ]: 70628 : if(destination->length == 0)
93 : : {
94 : 0 : provide(status);
95 : : } else {
96 : : /* Cached visible string length before truncate decides whether it is a no-op or a real cut */
97 : 70628 : const size_t current_visible_length = destination->string_length;
98 : :
99 [ + + ]: 70628 : if(current_visible_length >= destination->length)
100 : : {
101 : 1 : report("Memory management; String descriptor is inconsistent during truncate");
102 : 1 : provide(FAILURE);
103 : : }
104 : :
105 [ + - + + ]: 70627 : if((TRIUMPH & status) && new_visible_length > current_visible_length)
106 : : {
107 : 2 : provide(status);
108 : : }
109 : :
110 [ + - + + ]: 70625 : if((TRIUMPH & status) && new_visible_length == current_visible_length)
111 : : {
112 : : /* This branch is intentionally not a plain "redundant truncate" no-op.
113 : : mem_resize() relies on it when string capacity changes but the visible
114 : : payload length does not. In that flow the resize logic wants to keep
115 : : string_length unchanged while still guaranteeing that the element at
116 : : string_length is a valid zero terminator after any reallocation or
117 : : shrink-to-fit operation.
118 : :
119 : : The fast path below preserves that contract efficiently: if the
120 : : current terminator is already present, truncate returns without an
121 : : unnecessary memset(). If low-level code damaged that one element but
122 : : left the rest of the metadata consistent, the function falls through
123 : : to mem_write_zero_terminator() and repairs the terminator in place.
124 : :
125 : : Because this branch dereferences the terminator slot directly, all
126 : : reserve-consistency checks above must stay ahead of it. Removing this
127 : : branch would silently change the agreed behavior of mem_resize(), and
128 : : moving it ahead of the reserve validation would reintroduce an
129 : : out-of-bounds read risk on corrupted descriptors */
130 : : /* Byte offset of the current terminator element inside destination->data */
131 : 70616 : size_t terminator_offset = 0;
132 : :
133 : 70616 : run(mem_guarded_byte_size(destination,current_visible_length,&terminator_offset));
134 : :
135 [ + - ]: 70616 : if(TRIUMPH & status)
136 : : {
137 : : /* Read-only byte view used to inspect whether the existing terminator is already valid */
138 : 70616 : const unsigned char *destination_data_view = (const unsigned char *)destination->data;
139 : :
140 [ + + ]: 70616 : if(mem_is_zero_element(destination_data_view + terminator_offset,destination->single_element_size) == true)
141 : : {
142 : 46111 : provide(status);
143 : : }
144 : : }
145 : : }
146 : : }
147 : : }
148 : :
149 : 24514 : run(mem_write_zero_terminator(destination,new_visible_length));
150 : :
151 [ + - ]: 24514 : if(TRIUMPH & status)
152 : : {
153 : 24514 : destination->string_length = new_visible_length;
154 : : }
155 : :
156 : 24514 : provide(status);
157 : : }
|