GCC Code Coverage Report


Directory: ./
File: properties.c
Date: 2025-12-31 16:19:05
Exec Total Coverage
Lines: 167 167 100.0%
Functions: 7 7 100.0%
Branches: 102 118 86.4%

Line Branch Exec Source
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "cx/properties.h"
30
31 #include <assert.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 const CxPropertiesConfig cx_properties_config_default = {
37 '=',
38 '#',
39 '\0',
40 '\0',
41 '\\',
42 };
43
44 20 void cxPropertiesInit(
45 CxProperties *prop,
46 CxPropertiesConfig config
47 ) {
48 20 memset(prop, 0, sizeof(CxProperties));
49 20 prop->config = config;
50 20 }
51
52 20 void cxPropertiesDestroy(CxProperties *prop) {
53 20 cxBufferDestroy(&prop->input);
54 20 cxBufferDestroy(&prop->buffer);
55 20 }
56
57 3 void cxPropertiesReset(CxProperties *prop) {
58 3 CxPropertiesConfig config = prop->config;
59 3 cxPropertiesDestroy(prop);
60 3 cxPropertiesInit(prop, config);
61 3 }
62
63 89 int cxPropertiesFilln(
64 CxProperties *prop,
65 const char *buf,
66 size_t len
67 ) {
68
2/2
✓ Branch 0 (3→4) taken 86 times.
✓ Branch 1 (3→7) taken 3 times.
89 if (cxBufferEof(&prop->input)) {
69 // destroy a possible previously initialized buffer
70 86 cxBufferDestroy(&prop->input);
71 86 cxBufferInit(&prop->input, NULL, (void*) buf,
72 len, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
73 86 prop->input.size = len;
74 } else {
75
1/2
✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→10) taken 3 times.
3 if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1;
76 }
77 89 return 0;
78 }
79
80 6 void cxPropertiesUseStack(
81 CxProperties *prop,
82 char *buf,
83 size_t capacity
84 ) {
85 6 cxBufferInit(&prop->buffer, NULL, buf, capacity, CX_BUFFER_COPY_ON_EXTEND);
86 6 }
87
88 147 CxPropertiesStatus cxPropertiesNext(
89 CxProperties *prop,
90 cxstring *key,
91 cxstring *value
92 ) {
93 // check if we have a text buffer
94
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 146 times.
147 if (prop->input.space == NULL) {
95 1 return CX_PROPERTIES_NULL_INPUT;
96 }
97
98 // a pointer to the buffer we want to read from
99 146 CxBuffer *current_buffer = &prop->input;
100
101 146 char comment1 = prop->config.comment1;
102 146 char comment2 = prop->config.comment2;
103 146 char comment3 = prop->config.comment3;
104 146 char delimiter = prop->config.delimiter;
105 146 char continuation = prop->config.continuation;
106
107 // check if we have rescued data
108
2/2
✓ Branch 0 (5→6) taken 50 times.
✓ Branch 1 (5→49) taken 96 times.
146 if (!cxBufferEof(&prop->buffer)) {
109 // check if we can now get a complete line
110 50 cxstring input = cx_strn(prop->input.space + prop->input.pos,
111 50 prop->input.size - prop->input.pos);
112 50 cxstring nl = cx_strchr(input, '\n');
113
2/2
✓ Branch 0 (37→11) taken 15 times.
✓ Branch 1 (37→38) taken 39 times.
54 while (nl.length > 0) {
114 // check for line continuation
115
3/4
✓ Branch 0 (11→12) taken 7 times.
✓ Branch 1 (11→13) taken 8 times.
✗ Branch 2 (21→22) not taken.
✓ Branch 3 (21→23) taken 8 times.
39 char previous = nl.ptr > input.ptr ? nl.ptr[-1] : cx_strat(cx_bstr(&prop->buffer), -1);
116
2/2
✓ Branch 0 (28→29) taken 4 times.
✓ Branch 1 (28→35) taken 11 times.
15 if (previous == continuation) {
117 // this nl is a line continuation, check the next newline
118 8 nl = cx_strchr(cx_strsubs(nl, 1), '\n');
119 } else {
120 11 break;
121 }
122 }
123
124
2/2
✓ Branch 0 (38→39) taken 11 times.
✓ Branch 1 (38→43) taken 39 times.
50 if (nl.length > 0) {
125 // we add as much data to the rescue buffer as we need
126 // to complete the line
127 11 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1;
128
129
1/2
✗ Branch 0 (40→41) not taken.
✓ Branch 1 (40→42) taken 11 times.
11 if (cxBufferAppend(input.ptr, 1,
130 len_until_nl, &prop->buffer) < len_until_nl) {
131 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
132 }
133
134 // advance the position in the input buffer
135 11 prop->input.pos += len_until_nl;
136
137 // we now want to read from the rescue buffer
138 11 current_buffer = &prop->buffer;
139 } else {
140 // still not enough data, copy input buffer to internal buffer
141 39 if (cxBufferAppend(input.ptr, 1,
142
1/2
✗ Branch 0 (44→45) not taken.
✓ Branch 1 (44→46) taken 39 times.
39 input.length, &prop->buffer) < input.length) {
143 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
144 }
145 // reset the input buffer (make way for a re-fill)
146 39 cxBufferReset(&prop->input);
147 39 return CX_PROPERTIES_INCOMPLETE_DATA;
148 }
149 }
150
151 // get one line and parse it
152
2/2
✓ Branch 0 (138→50) taken 155 times.
✓ Branch 1 (138→139) taken 24 times.
179 while (!cxBufferEof(current_buffer)) {
153 155 const char *buf = current_buffer->space + current_buffer->pos;
154 155 size_t len = current_buffer->size - current_buffer->pos;
155
156 /*
157 * First we check if we have at least one line. We also get indices of
158 * delimiter and comment chars
159 */
160 155 size_t delimiter_index = 0;
161 155 size_t comment_index = 0;
162 155 bool has_comment = false;
163 155 bool has_continuation = false;
164
165 155 size_t i = 0;
166 155 char c = 0;
167
2/2
✓ Branch 0 (69→51) taken 7825 times.
✓ Branch 1 (69→70) taken 11 times.
7836 for (; i < len; i++) {
168 7825 c = buf[i];
169
4/6
✓ Branch 0 (51→52) taken 7783 times.
✓ Branch 1 (51→54) taken 42 times.
✓ Branch 2 (52→53) taken 7783 times.
✗ Branch 3 (52→54) not taken.
✗ Branch 4 (53→54) not taken.
✓ Branch 5 (53→57) taken 7783 times.
7825 if (c == comment1 || c == comment2 || c == comment3) {
170
1/2
✓ Branch 0 (54→55) taken 42 times.
✗ Branch 1 (54→56) not taken.
42 if (comment_index == 0) {
171 42 comment_index = i;
172 42 has_comment = true;
173 }
174
2/2
✓ Branch 0 (57→58) taken 87 times.
✓ Branch 1 (57→61) taken 7696 times.
7783 } else if (c == delimiter) {
175
3/4
✓ Branch 0 (58→59) taken 87 times.
✗ Branch 1 (58→68) not taken.
✓ Branch 2 (59→60) taken 77 times.
✓ Branch 3 (59→68) taken 10 times.
87 if (delimiter_index == 0 && !has_comment) {
176 77 delimiter_index = i;
177 }
178
7/8
✓ Branch 0 (61→62) taken 5200 times.
✓ Branch 1 (61→66) taken 2496 times.
✓ Branch 2 (62→63) taken 13 times.
✓ Branch 3 (62→66) taken 5187 times.
✓ Branch 4 (63→64) taken 13 times.
✗ Branch 5 (63→66) not taken.
✓ Branch 6 (64→65) taken 12 times.
✓ Branch 7 (64→66) taken 1 times.
7696 } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') {
179 12 has_continuation = true;
180 12 i++;
181
2/2
✓ Branch 0 (66→67) taken 144 times.
✓ Branch 1 (66→68) taken 7540 times.
7684 } else if (c == '\n') {
182 144 break;
183 }
184 }
185
186
2/2
✓ Branch 0 (70→71) taken 11 times.
✓ Branch 1 (70→79) taken 144 times.
155 if (c != '\n') {
187 // we don't have enough data for a line, use the rescue buffer
188 assert(current_buffer != &prop->buffer);
189 // make sure that the rescue buffer does not already contain something
190 assert(cxBufferEof(&prop->buffer));
191
2/2
✓ Branch 0 (71→72) taken 4 times.
✓ Branch 1 (71→73) taken 7 times.
11 if (prop->buffer.space == NULL) {
192 // initialize a rescue buffer, if the user did not provide one
193 4 cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND);
194 } else {
195 // from a previous rescue there might be already read data
196 // reset the buffer to avoid unnecessary buffer extension
197 7 cxBufferReset(&prop->buffer);
198 }
199
1/2
✗ Branch 0 (75→76) not taken.
✓ Branch 1 (75→77) taken 11 times.
11 if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) {
200 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
201 }
202 // reset the input buffer (make way for a re-fill)
203 11 cxBufferReset(&prop->input);
204 11 return CX_PROPERTIES_INCOMPLETE_DATA;
205 }
206
207 cxstring line = has_comment ?
208
2/2
✓ Branch 0 (79→80) taken 41 times.
✓ Branch 1 (79→83) taken 103 times.
144 cx_strn(buf, comment_index) :
209 cx_strn(buf, i);
210 // check line
211
2/2
✓ Branch 0 (86→87) taken 74 times.
✓ Branch 1 (86→99) taken 70 times.
144 if (delimiter_index == 0) {
212 // if line is not blank ...
213 74 line = cx_strtrim(line);
214 // ... either no delimiter found, or key is empty
215
2/2
✓ Branch 0 (90→91) taken 2 times.
✓ Branch 1 (90→94) taken 72 times.
74 if (line.length > 0) {
216
2/2
✓ Branch 0 (91→92) taken 1 times.
✓ Branch 1 (91→93) taken 1 times.
2 if (line.ptr[0] == delimiter) {
217 1 return CX_PROPERTIES_INVALID_EMPTY_KEY;
218 } else {
219 1 return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
220 }
221 } else {
222 // skip blank line
223 // if it was the rescue buffer, return to the original buffer
224
2/2
✓ Branch 0 (94→95) taken 1 times.
✓ Branch 1 (94→97) taken 71 times.
72 if (current_buffer == &prop->buffer) {
225 // assert that the rescue buffer really does not contain more data
226 assert(current_buffer->pos + i + 1 == current_buffer->size);
227 // reset the rescue buffer, but don't destroy it!
228 1 cxBufferReset(&prop->buffer);
229 // continue with the input buffer
230 1 current_buffer = &prop->input;
231 } else {
232 // if it was the input buffer already, just advance the position
233 71 current_buffer->pos += i + 1;
234 }
235 72 continue;
236 }
237 } else {
238 cxstring k = cx_strn(buf, delimiter_index);
239 70 cxstring val = cx_strn(
240 70 buf + delimiter_index + 1,
241 70 line.length - delimiter_index - 1);
242 70 k = cx_strtrim(k);
243 70 val = cx_strtrim(val);
244
2/2
✓ Branch 0 (109→110) taken 68 times.
✓ Branch 1 (109→134) taken 2 times.
70 if (k.length > 0) {
245 68 current_buffer->pos += i + 1;
246 assert(current_buffer->pos <= current_buffer->size);
247 assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size);
248
249
2/2
✓ Branch 0 (110→111) taken 6 times.
✓ Branch 1 (110→133) taken 62 times.
68 if (has_continuation) {
250 6 char *ptr = (char*)val.ptr;
251
2/2
✓ Branch 0 (111→112) taken 4 times.
✓ Branch 1 (111→118) taken 2 times.
6 if (current_buffer != &prop->buffer) {
252 // move value to the rescue buffer
253
2/2
✓ Branch 0 (112→113) taken 1 times.
✓ Branch 1 (112→114) taken 3 times.
4 if (prop->buffer.space == NULL) {
254 1 cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND);
255 }
256 4 prop->buffer.size = 0;
257 4 prop->buffer.pos = 0;
258
1/2
✗ Branch 0 (115→116) not taken.
✓ Branch 1 (115→117) taken 4 times.
4 if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) {
259 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
260 }
261 4 val.ptr = prop->buffer.space;
262 4 ptr = prop->buffer.space;
263 }
264 // value.ptr is now inside the rescue buffer and we can
265 // remove the continuation character from the value
266 6 bool trim = false;
267 6 size_t x = 0;
268
2/2
✓ Branch 0 (131→119) taken 119 times.
✓ Branch 1 (131→132) taken 6 times.
125 for(size_t j=0;j<val.length;j++) {
269 119 c = ptr[j];
270
5/6
✓ Branch 0 (119→120) taken 113 times.
✓ Branch 1 (119→123) taken 6 times.
✓ Branch 2 (120→121) taken 12 times.
✓ Branch 3 (120→123) taken 101 times.
✓ Branch 4 (121→122) taken 12 times.
✗ Branch 5 (121→123) not taken.
119 if (j+1 < val.length && c == '\\' && ptr[j+1] == '\n') {
271 // skip continuation and newline character
272 12 j++;
273 12 trim = true; // enable trim in the next line
274 12 continue;
275 }
276
2/2
✓ Branch 0 (123→124) taken 67 times.
✓ Branch 1 (123→129) taken 40 times.
107 if (j > x) {
277
2/2
✓ Branch 0 (124→125) taken 20 times.
✓ Branch 1 (124→128) taken 47 times.
67 if (trim) {
278
2/2
✓ Branch 0 (125→126) taken 10 times.
✓ Branch 1 (125→127) taken 10 times.
20 if (isspace((unsigned char)c)) {
279 10 continue;
280 }
281 10 trim = false;
282 }
283 57 ptr[x] = c;
284 }
285 97 x++;
286 }
287 6 val.length = x;
288 }
289 68 *key = k;
290 68 *value = val;
291
292 68 return CX_PROPERTIES_NO_ERROR;
293 } else {
294 2 return CX_PROPERTIES_INVALID_EMPTY_KEY;
295 }
296 }
297 }
298
299 // when we come to this point, all data must have been read
300 assert(cxBufferEof(&prop->buffer));
301 assert(cxBufferEof(&prop->input));
302
303 24 return CX_PROPERTIES_NO_DATA;
304 }
305
306 #ifndef CX_PROPERTIES_LOAD_FILL_SIZE
307 #define CX_PROPERTIES_LOAD_FILL_SIZE 1024
308 #endif
309 const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE;
310 #ifndef CX_PROPERTIES_LOAD_BUF_SIZE
311 #define CX_PROPERTIES_LOAD_BUF_SIZE 256
312 #endif
313 const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE;
314
315 7 CxPropertiesStatus cx_properties_load(const CxAllocator *allocator,
316 cxstring filename, CxMap *target, CxPropertiesConfig config) {
317
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 6 times.
7 if (allocator == NULL) {
318 1 allocator = cxDefaultAllocator;
319 }
320
321 // sanity check for the map
322 7 const bool use_cstring = cxCollectionStoresPointers(target);
323
4/4
✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→7) taken 5 times.
✓ Branch 2 (5→6) taken 1 times.
✓ Branch 3 (5→7) taken 1 times.
7 if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) {
324 1 return CX_PROPERTIES_MAP_ERROR;
325 }
326
327 // create a duplicate to guarantee zero-termination
328 12 cxmutstr fname = cx_strdup(filename);
329
1/2
✗ Branch 0 (12→13) not taken.
✓ Branch 1 (12→14) taken 6 times.
6 if (fname.ptr == NULL) {
330 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
331 }
332
333 // open the file
334 6 FILE *f = fopen(fname.ptr, "r");
335
2/2
✓ Branch 0 (15→16) taken 1 times.
✓ Branch 1 (15→18) taken 5 times.
6 if (f == NULL) {
336 1 cx_strfree(&fname);
337 1 return CX_PROPERTIES_FILE_ERROR;
338 }
339
340 // initialize the parser
341 char linebuf[CX_PROPERTIES_LOAD_BUF_SIZE];
342 char fillbuf[CX_PROPERTIES_LOAD_FILL_SIZE];
343 CxPropertiesStatus status;
344 CxProperties parser;
345 5 cxPropertiesInit(&parser, config);
346 5 cxPropertiesUseStack(&parser, linebuf, CX_PROPERTIES_LOAD_BUF_SIZE);
347
348 // read/fill/parse loop
349 5 status = CX_PROPERTIES_NO_DATA;
350 5 size_t keys_found = 0;
351 5 while (true) {
352 10 size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f);
353
1/2
✗ Branch 0 (23→24) not taken.
✓ Branch 1 (23→25) taken 10 times.
10 if (ferror(f)) {
354 // LCOV_EXCL_START
355 status = CX_PROPERTIES_FILE_ERROR;
356 break;
357 // LCOV_EXCL_STOP
358 }
359
2/2
✓ Branch 0 (25→26) taken 4 times.
✓ Branch 1 (25→27) taken 6 times.
10 if (r == 0) {
360 4 break;
361 }
362
1/2
✗ Branch 0 (28→29) not taken.
✓ Branch 1 (28→30) taken 6 times.
6 if (cxPropertiesFilln(&parser, fillbuf, r)) {
363 // LCOV_EXCL_START
364 status = CX_PROPERTIES_BUFFER_ALLOC_FAILED;
365 break;
366 // LCOV_EXCL_STOP
367 }
368 cxstring key, value;
369 while (true) {
370 14 status = cxPropertiesNext(&parser, &key, &value);
371
2/2
✓ Branch 0 (31→32) taken 6 times.
✓ Branch 1 (31→33) taken 8 times.
14 if (status != CX_PROPERTIES_NO_ERROR) {
372 6 break;
373 } else {
374 16 cxmutstr v = cx_strdup_a(allocator, value);
375 // LCOV_EXCL_START
376 if (v.ptr == NULL) {
377 status = CX_PROPERTIES_MAP_ERROR;
378 break;
379 }
380 // LCOV_EXCL_STOP
381
2/2
✓ Branch 0 (40→41) taken 6 times.
✓ Branch 1 (40→42) taken 2 times.
8 void *mv = use_cstring ? (void*)v.ptr : &v;
382
1/2
✗ Branch 0 (46→47) not taken.
✓ Branch 1 (46→49) taken 8 times.
8 if (cxMapPut(target, key, mv)) {
383 // LCOV_EXCL_START
384 cx_strfree(&v);
385 status = CX_PROPERTIES_MAP_ERROR;
386 break;
387 // LCOV_EXCL_STOP
388 }
389 8 keys_found++;
390 }
391 }
392
2/2
✓ Branch 0 (51→52) taken 1 times.
✓ Branch 1 (51→53) taken 5 times.
6 if (status > CX_PROPERTIES_OK) {
393 1 break;
394 }
395 }
396
397 // cleanup and exit
398 5 fclose(f);
399 5 cxPropertiesDestroy(&parser);
400 5 cx_strfree(&fname);
401
4/4
✓ Branch 0 (57→58) taken 4 times.
✓ Branch 1 (57→60) taken 1 times.
✓ Branch 2 (58→59) taken 2 times.
✓ Branch 3 (58→60) taken 2 times.
5 if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) {
402 2 return CX_PROPERTIES_NO_ERROR;
403 } else {
404 3 return status;
405 }
406 }
407