reftable: check for trailing newline in 'tables.list'

In the reftable format, the 'tables.list' file contains a newline separated list of tables. While we parse this file, we do not check or care about the last newline. Tighten the parser in `parse_names()` to return an appropriate error if the last newline is missing. This requires modification to `parse_names()` to now return the error while accepting the output as a third argument. Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Oct 7, 2025 at 14:11 UTC f6442063775b68d9eeaeb9088379fba3298c80ac
4 files changed +49 -26
reftable/basics.c
+24 -13
@@ -195,44 +195,55 @@ size_t names_length(const char **names)
195 return p - names;
196 }
197
198 -char **parse_names(char *buf, int size)
198 +int parse_names(char *buf, int size, char ***out)
199 {
200 char **names = NULL;
201 size_t names_cap = 0;
202 size_t names_len = 0;
203 char *p = buf;
204 char *end = buf + size;
205 + int err = 0;
206
207 while (p < end) {
208 char *next = strchr(p, '\n');
208 - if (next && next < end) {
209 - *next = 0;
209 + if (!next) {
210 + err = REFTABLE_FORMAT_ERROR;
211 + goto done;
212 + } else if (next < end) {
213 + *next = '\0';
214 } else {
215 next = end;
216 }
217 +
218 if (p < next) {
219 if (REFTABLE_ALLOC_GROW(names, names_len + 1,
215 - names_cap))
216 - goto err;
220 + names_cap)) {
221 + err = REFTABLE_OUT_OF_MEMORY_ERROR;
222 + goto done;
223 + }
224
225 names[names_len] = reftable_strdup(p);
219 - if (!names[names_len++])
220 - goto err;
226 + if (!names[names_len++]) {
227 + err = REFTABLE_OUT_OF_MEMORY_ERROR;
228 + goto done;
229 + }
230 }
231 p = next + 1;
232 }
233
225 - if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap))
226 - goto err;
234 + if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) {
235 + err = REFTABLE_OUT_OF_MEMORY_ERROR;
236 + goto done;
237 + }
238 names[names_len] = NULL;
239
229 - return names;
230 -
231 -err:
240 + *out = names;
241 + return 0;
242 +done:
243 for (size_t i = 0; i < names_len; i++)
244 reftable_free(names[i]);
245 reftable_free(names);
235 - return NULL;
246 + return err;
247 }
248
249 int names_equal(const char **a, const char **b)
reftable/basics.h
+4 -3
@@ -167,10 +167,11 @@ void free_names(char **a);
167
168 /*
169 * Parse a newline separated list of names. `size` is the length of the buffer,
170 - * without terminating '\0'. Empty names are discarded. Returns a `NULL`
171 - * pointer when allocations fail.
170 + * without terminating '\0'. Empty names are discarded.
171 + *
172 + * Returns 0 on success, a reftable error code on error.
173 */
173 -char **parse_names(char *buf, int size);
174 +int parse_names(char *buf, int size, char ***out);
175
176 /* compares two NULL-terminated arrays of strings. */
177 int names_equal(const char **a, const char **b);
reftable/stack.c
+1 -6
@@ -169,12 +169,7 @@ static int fd_read_lines(int fd, char ***namesp)
169 }
170 buf[size] = 0;
171
172 - *namesp = parse_names(buf, size);
173 - if (!*namesp) {
174 - err = REFTABLE_OUT_OF_MEMORY_ERROR;
175 - goto done;
176 - }
177 -
172 + err = parse_names(buf, size, namesp);
173 done:
174 reftable_free(buf);
175 return err;
t/unit-tests/u-reftable-basics.c
+20 -4
@@ -9,6 +9,7 @@ https://developers.google.com/open-source/licenses/bsd
9 #include "unit-test.h"
10 #include "lib-reftable.h"
11 #include "reftable/basics.h"
12 +#include "reftable/reftable-error.h"
13
14 struct integer_needle_lesseq_args {
15 int needle;
@@ -79,14 +80,18 @@ void test_reftable_basics__names_equal(void)
80 void test_reftable_basics__parse_names(void)
81 {
82 char in1[] = "line\n";
82 - char in2[] = "a\nb\nc";
83 - char **out = parse_names(in1, strlen(in1));
83 + char in2[] = "a\nb\nc\n";
84 + char **out = NULL;
85 + int err = parse_names(in1, strlen(in1), &out);
86 + cl_assert(err == 0);
87 cl_assert(out != NULL);
88 cl_assert_equal_s(out[0], "line");
89 cl_assert(!out[1]);
90 free_names(out);
91
89 - out = parse_names(in2, strlen(in2));
92 + out = NULL;
93 + err = parse_names(in2, strlen(in2), &out);
94 + cl_assert(err == 0);
95 cl_assert(out != NULL);
96 cl_assert_equal_s(out[0], "a");
97 cl_assert_equal_s(out[1], "b");
@@ -95,10 +100,21 @@ void test_reftable_basics__parse_names(void)
100 free_names(out);
101 }
102
103 +void test_reftable_basics__parse_names_missing_newline(void)
104 +{
105 + char in1[] = "line\nline2";
106 + char **out = NULL;
107 + int err = parse_names(in1, strlen(in1), &out);
108 + cl_assert(err == REFTABLE_FORMAT_ERROR);
109 + cl_assert(out == NULL);
110 +}
111 +
112 void test_reftable_basics__parse_names_drop_empty_string(void)
113 {
114 char in[] = "a\n\nb\n";
101 - char **out = parse_names(in, strlen(in));
115 + char **out = NULL;
116 + int err = parse_names(in, strlen(in), &out);
117 + cl_assert(err == 0);
118 cl_assert(out != NULL);
119 cl_assert_equal_s(out[0], "a");
120 /* simply '\n' should be dropped as empty string */