GCC Code Coverage Report


Directory: csrc/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.8% 91 / 0 / 95
Functions: 100.0% 4 / 0 / 4
Branches: 87.8% 65 / 0 / 74

csv.c
Line Branch Exec Source
1 #include <stdint.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #include "evutils/csv.h"
6
7 /* Parse whitespace-delimited integer rows (CSV/TXT event files) into the
8 * caller's per-column output arrays.
9 *
10 * out_arrays[i] destination array for output column i (t/x/y/p)
11 * array_types[i] element width of output column i: 1, 2 or 8 bytes
12 * col_mapping[j] output column that CSV column j feeds, or -1 to skip it
13 * max_csv_cols length of col_mapping
14 * max_events stop after this many rows
15 * events_parsed (out) rows parsed
16 * malformed (out, optional) rows that were short a mapped column and
17 * had to be zero-filled; NULL to ignore
18 *
19 * Strategy: locate each line end with memchr (SIMD-accelerated in glibc), then
20 * parse the *complete* line with no per-byte bounds checks -- the newline is a
21 * hard terminator, so the digit loops stop at the delimiter/newline naturally.
22 * A line with no '\n' before the buffer end is a chunk-boundary fragment: stop
23 * without consuming it so the Python driver re-feeds it in the next block.
24 */
25 164 parser_result_t evutils_read_csv(
26 const char *buffer, size_t buffer_len,
27 char delimiter,
28 void **out_arrays,
29 int *array_types,
30 int *col_mapping,
31 int max_csv_cols,
32 size_t max_events,
33 size_t *events_parsed,
34 size_t *malformed
35 ) {
36 164 const char *cursor = buffer;
37 164 const char *buffer_end = buffer + buffer_len;
38 164 size_t n_parsed = 0;
39 164 size_t n_malformed = 0;
40
41
4/4
✓ Branch 0 taken 309022 times.
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 308928 times.
✓ Branch 3 taken 94 times.
309090 while (cursor < buffer_end && n_parsed < max_events) {
42 /* Skip blank lines (bare CR/LF). */
43
3/4
✓ Branch 0 taken 308885 times.
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 308885 times.
308928 if (*cursor == '\n' || *cursor == '\r') {
44 43 cursor++;
45 43 continue;
46 }
47
48 const char *line_end =
49 308885 (const char *)memchr(cursor, '\n', (size_t)(buffer_end - cursor));
50
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 308883 times.
308885 if (line_end == NULL) {
51 2 break; /* fragment: no complete line in this block */
52 }
53
54 308883 const char *scan = cursor;
55 308883 int csv_col = 0;
56
2/2
✓ Branch 0 taken 1234707 times.
✓ Branch 1 taken 3 times.
1234710 while (scan < line_end) {
57
2/2
✓ Branch 0 taken 1234660 times.
✓ Branch 1 taken 47 times.
1234707 int dest_col = (csv_col < max_csv_cols) ? col_mapping[csv_col] : -1;
58
59
2/2
✓ Branch 0 taken 1234660 times.
✓ Branch 1 taken 47 times.
1234707 if (dest_col != -1) {
60
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1234666 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 1234660 times.
1234670 while (*scan == ' ' || *scan == '\t') scan++; /* bounded by line_end */
61
62 1234660 int negative = 0;
63
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1234655 times.
1234660 if (*scan == '-') { negative = 1; scan++; }
64
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1234654 times.
1234655 else if (*scan == '+') { scan++; }
65
66 /* One comparison per digit: (unsigned char)(c - '0') <= 9 is
67 * false for every non-digit -- including the delimiter and
68 * CR/LF -- so the loop terminates without a separate bounds
69 * test. */
70 1234660 int64_t value = 0;
71 unsigned digit;
72
2/2
✓ Branch 0 taken 3917630 times.
✓ Branch 1 taken 1234660 times.
5152290 while ((digit = (unsigned char)*scan - '0') <= 9u) {
73 3917630 value = value * 10 + (int64_t)digit;
74 3917630 scan++;
75 }
76
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1234655 times.
1234660 if (negative) value = -value;
77
78 1234660 void *dest = out_arrays[dest_col];
79
3/4
✓ Branch 0 taken 617249 times.
✓ Branch 1 taken 308883 times.
✓ Branch 2 taken 308528 times.
✗ Branch 3 not taken.
1234660 switch (array_types[dest_col]) {
80 617249 case 2: ((uint16_t *)dest)[n_parsed] = (uint16_t)value; break;
81 308883 case 8: ((int64_t *)dest)[n_parsed] = value; break;
82 308528 case 1: ((uint8_t *)dest)[n_parsed] = (uint8_t)value; break;
83 default: break;
84 }
85 }
86
87 /* Advance to the next delimiter within the line (usually already
88 * there for clean integer fields, so this rarely steps). */
89
4/4
✓ Branch 0 taken 1025028 times.
✓ Branch 1 taken 308880 times.
✓ Branch 2 taken 99201 times.
✓ Branch 3 taken 925827 times.
1333908 while (scan < line_end && *scan != delimiter) scan++;
90 1234707 csv_col++;
91
3/4
✓ Branch 0 taken 925827 times.
✓ Branch 1 taken 308880 times.
✓ Branch 2 taken 925827 times.
✗ Branch 3 not taken.
1234707 if (scan < line_end && *scan == delimiter) { scan++; continue; }
92 308880 break;
93 }
94
95 // Zero-fill any missing columns for short rows to prevent stale data
96 // leaks. A row missing a *mapped* column is malformed (counted below);
97 // the zero-fill keeps the existing lenient behaviour regardless.
98 308883 int missing_mapped = 0;
99
2/2
✓ Branch 0 taken 872 times.
✓ Branch 1 taken 308883 times.
309755 while (csv_col < max_csv_cols) {
100 872 int dest_col = col_mapping[csv_col];
101
1/2
✓ Branch 0 taken 872 times.
✗ Branch 1 not taken.
872 if (dest_col != -1) {
102 872 missing_mapped = 1;
103 872 void *dest = out_arrays[dest_col];
104
2/4
✓ Branch 0 taken 517 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 355 times.
✗ Branch 3 not taken.
872 switch (array_types[dest_col]) {
105 517 case 2: ((uint16_t *)dest)[n_parsed] = 0; break;
106 case 8: ((int64_t *)dest)[n_parsed] = 0; break;
107 355 case 1: ((uint8_t *)dest)[n_parsed] = 0; break;
108 }
109 }
110 872 csv_col++;
111 }
112
2/2
✓ Branch 0 taken 355 times.
✓ Branch 1 taken 308528 times.
308883 if (missing_mapped) n_malformed++;
113
114 308883 cursor = line_end + 1; /* past the '\n'; a trailing '\r' sits before it */
115 308883 n_parsed++;
116 }
117
118 164 *events_parsed = n_parsed;
119
1/2
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
164 if (malformed) *malformed = n_malformed;
120 parser_result_t res;
121 164 res.current = cursor;
122
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 96 times.
164 if (cursor == buffer_end) {
123 68 res.status = EVUTILS_PARSE_INPUT_EMPTY;
124
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 2 times.
96 } else if (n_parsed == max_events) {
125 94 res.status = EVUTILS_PARSE_OUTPUT_FULL;
126 } else {
127 /* Trailing partial line (chunk-boundary fragment): unconsumed, the
128 * caller must re-feed it with the next block appended. */
129 2 res.status = EVUTILS_PARSE_INCOMPLETE;
130 }
131 164 return res;
132 }
133
134
135 /* Write `value` as decimal into the tail of `buf` (>= 21 bytes) and return a
136 * pointer to the first digit. Writing right-to-left avoids a reversal step. */
137 1950256 static inline char *format_uint(uint64_t value, char *buf) {
138 1950256 char *digits = buf + 20;
139 1950256 *digits = '\0';
140
2/2
✓ Branch 0 taken 244961 times.
✓ Branch 1 taken 1705295 times.
1950256 if (value == 0) {
141 244961 *--digits = '0';
142 244961 return digits;
143 }
144
2/2
✓ Branch 0 taken 6259507 times.
✓ Branch 1 taken 1705295 times.
7964802 while (value > 0) {
145 6259507 *--digits = (char)('0' + (value % 10));
146 6259507 value /= 10;
147 }
148 1705295 return digits;
149 }
150
151 487564 static inline char *format_int(int64_t value, char *buf) {
152
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 487563 times.
487564 if (value < 0) {
153 1 char *digits = format_uint((uint64_t)(-value), buf);
154 1 *--digits = '-';
155 1 return digits;
156 }
157 487563 return format_uint((uint64_t)value, buf);
158 }
159
160 /* Serialise the per-column integer arrays into delimited text rows. Mirrors the
161 * reader's column layout (array_types[c] is the element width of column c). */
162 47 int evutils_write_csv(
163 void **in_arrays,
164 int *array_types,
165 int num_columns,
166 char delimiter,
167 size_t num_events,
168 char *out_buffer,
169 size_t out_buffer_len,
170 size_t *bytes_written,
171 size_t *events_written
172 ) {
173 47 size_t out_pos = 0;
174 char digit_buf[24];
175 47 size_t event_index = 0;
176
177
2/2
✓ Branch 0 taken 487564 times.
✓ Branch 1 taken 47 times.
487611 for (; event_index < num_events; event_index++) {
178 /* Bail before the row if it might not fit (each column <= 21 chars + a
179 * separator). */
180 487564 size_t max_row_len = (size_t)(num_columns * 22);
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 487564 times.
487564 if (out_pos + max_row_len > out_buffer_len) {
182 break;
183 }
184
185
2/2
✓ Branch 0 taken 1950256 times.
✓ Branch 1 taken 487564 times.
2437820 for (int col = 0; col < num_columns; col++) {
186 char *digits;
187
3/4
✓ Branch 0 taken 487564 times.
✓ Branch 1 taken 975128 times.
✓ Branch 2 taken 487564 times.
✗ Branch 3 not taken.
1950256 switch (array_types[col]) {
188 487564 case 1: digits = format_uint(((uint8_t *)in_arrays[col])[event_index], digit_buf); break;
189 975128 case 2: digits = format_uint(((uint16_t *)in_arrays[col])[event_index], digit_buf); break;
190 487564 case 8: digits = format_int(((int64_t *)in_arrays[col])[event_index], digit_buf); break;
191 default: digits = (char *)"0"; break;
192 }
193
194 1950256 size_t digit_len = (size_t)(digit_buf + 20 - digits);
195 1950256 memcpy(out_buffer + out_pos, digits, digit_len);
196 1950256 out_pos += digit_len;
197
198
2/2
✓ Branch 0 taken 1462692 times.
✓ Branch 1 taken 487564 times.
1950256 out_buffer[out_pos++] = (col == num_columns - 1) ? '\n' : delimiter;
199 }
200 }
201
202 47 *bytes_written = out_pos;
203 47 *events_written = event_index;
204 47 return 0;
205 }
206