LCOV - code coverage report
Current view: top level - libs/mem/src - mem_string_truncate.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 80.9 % 47 38
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 1 1
Branches: 65.4 % 52 34

             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,&current_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                 :             : }
        

Generated by: LCOV version 2.0-1