apply: refactor `struct image` to use a `struct strbuf`

The `struct image` uses a character array to track the pre- or postimage of a patch operation. This has multiple downsides: - It is somewhat hard to track memory ownership. In fact, we have several memory leaks in git-apply(1) because we do not (and cannot easily) free the buffer in all situations. - We have to reinvent the wheel and manually implement a lot of functionality that would already be provided by `struct strbuf`. - We have to carefully track whether `update_pre_post_images()` can do an in-place update of the postimage or whether it has to allocate a new buffer for it. This is all rather cumbersome, and especially `update_pre_post_images()` is really hard to understand as a consequence even though what it is doing is rather trivial. Refactor the code to use a `struct strbuf` instead, addressing all of the above. Like this we can easily perform in-place updates in all situations, the logic to perform those updates becomes way simpler and the lifetime of the buffer becomes a ton easier to track. This refactoring also plugs some leaking buffers as a side effect. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Sep 17, 2024 at 12:08 UTC 3fc4eab466a3758ff57d8c823f244e29550a48d2
6 files changed +79 -123
apply.c
+73 -121
@@ -277,12 +277,13 @@ struct line {
277 * This represents a "file", which is an array of "lines".
278 */
279 struct image {
280 - char *buf;
281 - size_t len;
280 + struct strbuf buf;
281 struct line *line;
282 size_t line_nr, line_alloc;
283 };
285 -#define IMAGE_INIT { 0 }
284 +#define IMAGE_INIT { \
285 + .buf = STRBUF_INIT, \
286 +}
287
288 static void image_init(struct image *image)
289 {
@@ -292,7 +293,7 @@ static void image_init(struct image *image)
293
294 static void image_clear(struct image *image)
295 {
295 - free(image->buf);
296 + strbuf_release(&image->buf);
297 free(image->line);
298 image_init(image);
299 }
@@ -329,14 +330,13 @@ static void image_prepare(struct image *image, char *buf, size_t len,
330 const char *cp, *ep;
331
332 image_clear(image);
332 - image->buf = buf;
333 - image->len = len;
333 + strbuf_attach(&image->buf, buf, len, len + 1);
334
335 if (!prepare_linetable)
336 return;
337
338 - ep = image->buf + image->len;
339 - cp = image->buf;
338 + ep = image->buf.buf + image->buf.len;
339 + cp = image->buf.buf;
340 while (cp < ep) {
341 const char *next;
342 for (next = cp; next < ep && *next != '\n'; next++)
@@ -350,8 +350,7 @@ static void image_prepare(struct image *image, char *buf, size_t len,
350
351 static void image_remove_first_line(struct image *img)
352 {
353 - img->buf += img->line[0].len;
354 - img->len -= img->line[0].len;
353 + strbuf_remove(&img->buf, 0, img->line[0].len);
354 img->line_nr--;
355 if (img->line_nr)
356 MOVE_ARRAY(img->line, img->line + 1, img->line_nr);
@@ -359,7 +358,9 @@ static void image_remove_first_line(struct image *img)
358
359 static void image_remove_last_line(struct image *img)
360 {
362 - img->len -= img->line[--img->line_nr].len;
361 + size_t last_line_len = img->line[img->line_nr - 1].len;
362 + strbuf_setlen(&img->buf, img->buf.len - last_line_len);
363 + img->line_nr--;
364 }
365
366 /* fmt must contain _one_ %s and no other substitution */
@@ -2308,19 +2309,16 @@ static int read_old_data(struct stat *st, struct patch *patch,
2309
2310 /*
2311 * Update the preimage, and the common lines in postimage,
2311 - * from buffer buf of length len. If postlen is 0 the postimage
2312 - * is updated in place, otherwise it's updated on a new buffer
2313 - * of length postlen
2312 + * from buffer buf of length len.
2313 */
2315 -
2314 static void update_pre_post_images(struct image *preimage,
2315 struct image *postimage,
2318 - char *buf,
2319 - size_t len, size_t postlen)
2316 + char *buf, size_t len)
2317 {
2321 - int i, ctx, reduced;
2322 - char *new_buf, *old_buf, *fixed;
2318 struct image fixed_preimage = IMAGE_INIT;
2319 + size_t insert_pos = 0;
2320 + int i, ctx, reduced;
2321 + const char *fixed;
2322
2323 /*
2324 * Update the preimage with whitespace fixes. Note that we
@@ -2328,43 +2326,24 @@ static void update_pre_post_images(struct image *preimage,
2326 * free "oldlines".
2327 */
2328 image_prepare(&fixed_preimage, buf, len, 1);
2331 - assert(postlen
2332 - ? fixed_preimage.line_nr == preimage->line_nr
2333 - : fixed_preimage.line_nr <= preimage->line_nr);
2329 for (i = 0; i < fixed_preimage.line_nr; i++)
2330 fixed_preimage.line[i].flag = preimage->line[i].flag;
2336 - free(preimage->line);
2331 + image_clear(preimage);
2332 *preimage = fixed_preimage;
2333 + fixed = preimage->buf.buf;
2334
2335 /*
2340 - * Adjust the common context lines in postimage. This can be
2341 - * done in-place when we are shrinking it with whitespace
2342 - * fixing, but needs a new buffer when ignoring whitespace or
2343 - * expanding leading tabs to spaces.
2344 - *
2345 - * We trust the caller to tell us if the update can be done
2346 - * in place (postlen==0) or not.
2336 + * Adjust the common context lines in postimage.
2337 */
2348 - old_buf = postimage->buf;
2349 - if (postlen)
2350 - new_buf = postimage->buf = xmalloc(postlen);
2351 - else
2352 - new_buf = old_buf;
2353 - fixed = preimage->buf;
2354 -
2338 for (i = reduced = ctx = 0; i < postimage->line_nr; i++) {
2339 size_t l_len = postimage->line[i].len;
2340 +
2341 if (!(postimage->line[i].flag & LINE_COMMON)) {
2342 /* an added line -- no counterparts in preimage */
2359 - memmove(new_buf, old_buf, l_len);
2360 - old_buf += l_len;
2361 - new_buf += l_len;
2343 + insert_pos += l_len;
2344 continue;
2345 }
2346
2365 - /* a common context -- skip it in the original postimage */
2366 - old_buf += l_len;
2367 -
2347 /* and find the corresponding one in the fixed preimage */
2348 while (ctx < preimage->line_nr &&
2349 !(preimage->line[ctx].flag & LINE_COMMON)) {
@@ -2383,21 +2362,15 @@ static void update_pre_post_images(struct image *preimage,
2362
2363 /* and copy it in, while fixing the line length */
2364 l_len = preimage->line[ctx].len;
2386 - memcpy(new_buf, fixed, l_len);
2387 - new_buf += l_len;
2365 + strbuf_splice(&postimage->buf, insert_pos, postimage->line[i].len,
2366 + fixed, l_len);
2367 + insert_pos += l_len;
2368 fixed += l_len;
2369 postimage->line[i].len = l_len;
2370 ctx++;
2371 }
2372
2393 - if (postlen
2394 - ? postlen < new_buf - postimage->buf
2395 - : postimage->len < new_buf - postimage->buf)
2396 - BUG("caller miscounted postlen: asked %d, orig = %d, used = %d",
2397 - (int)postlen, (int) postimage->len, (int)(new_buf - postimage->buf));
2398 -
2373 /* Fix the length of the whole thing */
2400 - postimage->len = new_buf - postimage->buf;
2374 postimage->line_nr -= reduced;
2375 }
2376
@@ -2447,7 +2420,6 @@ static int line_by_line_fuzzy_match(struct image *img,
2420 int i;
2421 size_t imgoff = 0;
2422 size_t preoff = 0;
2450 - size_t postlen = postimage->len;
2423 size_t extra_chars;
2424 char *buf;
2425 char *preimage_eof;
@@ -2460,11 +2432,9 @@ static int line_by_line_fuzzy_match(struct image *img,
2432 size_t prelen = preimage->line[i].len;
2433 size_t imglen = img->line[current_lno+i].len;
2434
2463 - if (!fuzzy_matchlines(img->buf + current + imgoff, imglen,
2464 - preimage->buf + preoff, prelen))
2435 + if (!fuzzy_matchlines(img->buf.buf + current + imgoff, imglen,
2436 + preimage->buf.buf + preoff, prelen))
2437 return 0;
2466 - if (preimage->line[i].flag & LINE_COMMON)
2467 - postlen += imglen - prelen;
2438 imgoff += imglen;
2439 preoff += prelen;
2440 }
@@ -2480,10 +2450,10 @@ static int line_by_line_fuzzy_match(struct image *img,
2450 * are whitespace characters. (This can only happen if
2451 * we are removing blank lines at the end of the file.)
2452 */
2483 - buf = preimage_eof = preimage->buf + preoff;
2453 + buf = preimage_eof = preimage->buf.buf + preoff;
2454 for ( ; i < preimage->line_nr; i++)
2455 preoff += preimage->line[i].len;
2486 - preimage_end = preimage->buf + preoff;
2456 + preimage_end = preimage->buf.buf + preoff;
2457 for ( ; buf < preimage_end; buf++)
2458 if (!isspace(*buf))
2459 return 0;
@@ -2497,11 +2467,11 @@ static int line_by_line_fuzzy_match(struct image *img,
2467 */
2468 extra_chars = preimage_end - preimage_eof;
2469 strbuf_init(&fixed, imgoff + extra_chars);
2500 - strbuf_add(&fixed, img->buf + current, imgoff);
2470 + strbuf_add(&fixed, img->buf.buf + current, imgoff);
2471 strbuf_add(&fixed, preimage_eof, extra_chars);
2472 fixed_buf = strbuf_detach(&fixed, &fixed_len);
2473 update_pre_post_images(preimage, postimage,
2504 - fixed_buf, fixed_len, postlen);
2474 + fixed_buf, fixed_len);
2475 return 1;
2476 }
2477
@@ -2517,7 +2487,8 @@ static int match_fragment(struct apply_state *state,
2487 int i;
2488 const char *orig, *target;
2489 struct strbuf fixed = STRBUF_INIT;
2520 - size_t postlen;
2490 + char *fixed_buf;
2491 + size_t fixed_len;
2492 int preimage_limit;
2493 int ret;
2494
@@ -2573,9 +2544,9 @@ static int match_fragment(struct apply_state *state,
2544 * exactly.
2545 */
2546 if ((match_end
2576 - ? (current + preimage->len == img->len)
2577 - : (current + preimage->len <= img->len)) &&
2578 - !memcmp(img->buf + current, preimage->buf, preimage->len)) {
2547 + ? (current + preimage->buf.len == img->buf.len)
2548 + : (current + preimage->buf.len <= img->buf.len)) &&
2549 + !memcmp(img->buf.buf + current, preimage->buf.buf, preimage->buf.len)) {
2550 ret = 1;
2551 goto out;
2552 }
@@ -2589,7 +2560,7 @@ static int match_fragment(struct apply_state *state,
2560 */
2561 const char *buf, *buf_end;
2562
2592 - buf = preimage->buf;
2563 + buf = preimage->buf.buf;
2564 buf_end = buf;
2565 for (i = 0; i < preimage_limit; i++)
2566 buf_end += preimage->line[i].len;
@@ -2634,21 +2605,14 @@ static int match_fragment(struct apply_state *state,
2605 * fixed.
2606 */
2607
2637 - /* First count added lines in postimage */
2638 - postlen = 0;
2639 - for (i = 0; i < postimage->line_nr; i++) {
2640 - if (!(postimage->line[i].flag & LINE_COMMON))
2641 - postlen += postimage->line[i].len;
2642 - }
2643 -
2608 /*
2609 * The preimage may extend beyond the end of the file,
2610 * but in this loop we will only handle the part of the
2611 * preimage that falls within the file.
2612 */
2649 - strbuf_grow(&fixed, preimage->len + 1);
2650 - orig = preimage->buf;
2651 - target = img->buf + current;
2613 + strbuf_grow(&fixed, preimage->buf.len + 1);
2614 + orig = preimage->buf.buf;
2615 + target = img->buf.buf + current;
2616 for (i = 0; i < preimage_limit; i++) {
2617 size_t oldlen = preimage->line[i].len;
2618 size_t tgtlen = img->line[current_lno + i].len;
@@ -2677,10 +2641,6 @@ static int match_fragment(struct apply_state *state,
2641 !memcmp(tgtfix.buf, fixed.buf + fixstart,
2642 fixed.len - fixstart));
2643
2680 - /* Add the length if this is common with the postimage */
2681 - if (preimage->line[i].flag & LINE_COMMON)
2682 - postlen += tgtfix.len;
2683 -
2644 strbuf_release(&tgtfix);
2645 if (!match) {
2646 ret = 0;
@@ -2722,10 +2682,9 @@ static int match_fragment(struct apply_state *state,
2682 * has whitespace breakages unfixed, and fixing them makes the
2683 * hunk match. Update the context lines in the postimage.
2684 */
2725 - if (postlen < postimage->len)
2726 - postlen = 0;
2685 + fixed_buf = strbuf_detach(&fixed, &fixed_len);
2686 update_pre_post_images(preimage, postimage,
2728 - fixed.buf, fixed.len, postlen);
2687 + fixed_buf, fixed_len);
2688
2689 ret = 1;
2690
@@ -2839,6 +2798,7 @@ static void update_image(struct apply_state *state,
2798 */
2799 int i, nr;
2800 size_t remove_count, insert_count, applied_at = 0;
2801 + size_t result_alloc;
2802 char *result;
2803 int preimage_limit;
2804
@@ -2861,19 +2821,18 @@ static void update_image(struct apply_state *state,
2821 remove_count = 0;
2822 for (i = 0; i < preimage_limit; i++)
2823 remove_count += img->line[applied_pos + i].len;
2864 - insert_count = postimage->len;
2824 + insert_count = postimage->buf.len;
2825
2826 /* Adjust the contents */
2867 - result = xmalloc(st_add3(st_sub(img->len, remove_count), insert_count, 1));
2868 - memcpy(result, img->buf, applied_at);
2869 - memcpy(result + applied_at, postimage->buf, postimage->len);
2870 - memcpy(result + applied_at + postimage->len,
2871 - img->buf + (applied_at + remove_count),
2872 - img->len - (applied_at + remove_count));
2873 - free(img->buf);
2874 - img->buf = result;
2875 - img->len += insert_count - remove_count;
2876 - result[img->len] = '\0';
2827 + result_alloc = st_add3(st_sub(img->buf.len, remove_count), insert_count, 1);
2828 + result = xmalloc(result_alloc);
2829 + memcpy(result, img->buf.buf, applied_at);
2830 + memcpy(result + applied_at, postimage->buf.buf, postimage->buf.len);
2831 + memcpy(result + applied_at + postimage->buf.len,
2832 + img->buf.buf + (applied_at + remove_count),
2833 + img->buf.len - (applied_at + remove_count));
2834 + strbuf_attach(&img->buf, result, postimage->buf.len + img->buf.len - remove_count,
2835 + result_alloc);
2836
2837 /* Adjust the line table */
2838 nr = img->line_nr + postimage->line_nr - preimage_limit;
@@ -3054,10 +3013,8 @@ static int apply_one_fragment(struct apply_state *state,
3013 match_end = !state->unidiff_zero && !trailing;
3014
3015 pos = frag->newpos ? (frag->newpos - 1) : 0;
3057 - preimage.buf = oldlines;
3058 - preimage.len = old - oldlines;
3059 - postimage.buf = newlines.buf;
3060 - postimage.len = newlines.len;
3016 + strbuf_add(&preimage.buf, oldlines, old - oldlines);
3017 + strbuf_swap(&postimage.buf, &newlines);
3018
3019 for (;;) {
3020
@@ -3145,8 +3102,8 @@ static int apply_one_fragment(struct apply_state *state,
3102 out:
3103 free(oldlines);
3104 strbuf_release(&newlines);
3148 - free(preimage.line);
3149 - free(postimage.line);
3105 + image_clear(&preimage);
3106 + image_clear(&postimage);
3107
3108 return (applied_pos < 0);
3109 }
@@ -3176,18 +3133,16 @@ static int apply_binary_fragment(struct apply_state *state,
3133 }
3134 switch (fragment->binary_patch_method) {
3135 case BINARY_DELTA_DEFLATED:
3179 - dst = patch_delta(img->buf, img->len, fragment->patch,
3136 + dst = patch_delta(img->buf.buf, img->buf.len, fragment->patch,
3137 fragment->size, &len);
3138 if (!dst)
3139 return -1;
3140 image_clear(img);
3184 - img->buf = dst;
3185 - img->len = len;
3141 + strbuf_attach(&img->buf, dst, len, len + 1);
3142 return 0;
3143 case BINARY_LITERAL_DEFLATED:
3144 image_clear(img);
3189 - img->len = fragment->size;
3190 - img->buf = xmemdupz(fragment->patch, img->len);
3145 + strbuf_add(&img->buf, fragment->patch, fragment->size);
3146 return 0;
3147 }
3148 return -1;
@@ -3223,8 +3178,8 @@ static int apply_binary(struct apply_state *state,
3178 * See if the old one matches what the patch
3179 * applies to.
3180 */
3226 - hash_object_file(the_hash_algo, img->buf, img->len, OBJ_BLOB,
3227 - &oid);
3181 + hash_object_file(the_hash_algo, img->buf.buf, img->buf.len,
3182 + OBJ_BLOB, &oid);
3183 if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix))
3184 return error(_("the patch applies to '%s' (%s), "
3185 "which does not match the "
@@ -3233,7 +3188,7 @@ static int apply_binary(struct apply_state *state,
3188 }
3189 else {
3190 /* Otherwise, the old one must be empty. */
3236 - if (img->len)
3191 + if (img->buf.len)
3192 return error(_("the patch applies to an empty "
3193 "'%s' but it is not empty"), name);
3194 }
@@ -3257,8 +3212,7 @@ static int apply_binary(struct apply_state *state,
3212 "'%s' cannot be read"),
3213 patch->new_oid_prefix, name);
3214 image_clear(img);
3260 - img->buf = result;
3261 - img->len = size;
3215 + strbuf_attach(&img->buf, result, size, size + 1);
3216 } else {
3217 /*
3218 * We have verified buf matches the preimage;
@@ -3270,7 +3224,7 @@ static int apply_binary(struct apply_state *state,
3224 name);
3225
3226 /* verify that the result matches */
3273 - hash_object_file(the_hash_algo, img->buf, img->len, OBJ_BLOB,
3227 + hash_object_file(the_hash_algo, img->buf.buf, img->buf.len, OBJ_BLOB,
3228 &oid);
3229 if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix))
3230 return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
@@ -3540,14 +3494,14 @@ static int resolve_to(struct image *image, const struct object_id *result_id)
3494 {
3495 unsigned long size;
3496 enum object_type type;
3497 + char *data;
3498
3499 image_clear(image);
3500
3546 - image->buf = repo_read_object_file(the_repository, result_id, &type,
3547 - &size);
3548 - if (!image->buf || type != OBJ_BLOB)
3501 + data = repo_read_object_file(the_repository, result_id, &type, &size);
3502 + if (!data || type != OBJ_BLOB)
3503 die("unable to read blob object %s", oid_to_hex(result_id));
3550 - image->len = size;
3504 + strbuf_attach(&image->buf, data, size, size + 1);
3505
3506 return 0;
3507 }
@@ -3589,8 +3543,7 @@ static int three_way_merge(struct apply_state *state,
3543 return -1;
3544 }
3545 image_clear(image);
3592 - image->buf = result.ptr;
3593 - image->len = result.size;
3546 + strbuf_attach(&image->buf, result.ptr, result.size, result.size);
3547
3548 return status;
3549 }
@@ -3677,7 +3630,7 @@ static int try_threeway(struct apply_state *state,
3630 return -1;
3631 }
3632 /* post_oid is theirs */
3680 - write_object_file(tmp_image.buf, tmp_image.len, OBJ_BLOB, &post_oid);
3633 + write_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &post_oid);
3634 image_clear(&tmp_image);
3635
3636 /* our_oid is ours */
@@ -3690,7 +3643,7 @@ static int try_threeway(struct apply_state *state,
3643 return error(_("cannot read the current contents of '%s'"),
3644 patch->old_name);
3645 }
3693 - write_object_file(tmp_image.buf, tmp_image.len, OBJ_BLOB, &our_oid);
3646 + write_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &our_oid);
3647 image_clear(&tmp_image);
3648
3649 /* in-core three-way merge between post and our using pre as base */
@@ -3743,8 +3696,7 @@ static int apply_data(struct apply_state *state, struct patch *patch,
3696 return -1;
3697 }
3698 }
3746 - patch->result = image.buf;
3747 - patch->resultsize = image.len;
3699 + patch->result = strbuf_detach(&image.buf, &patch->resultsize);
3700 add_to_fn_table(state, patch);
3701 free(image.line);
3702
t/t3436-rebase-more-options.sh
+1
@@ -5,6 +5,7 @@
5
6 test_description='tests to ensure compatibility between am and interactive backends'
7
8 +TEST_PASSES_SANITIZE_LEAK=true
9 . ./test-lib.sh
10
11 . "$TEST_DIRECTORY"/lib-rebase.sh
t/t4107-apply-ignore-whitespace.sh
+2 -2
@@ -3,9 +3,9 @@
3 # Copyright (c) 2009 Giuseppe Bilotta
4 #
5
6 -test_description='git-apply --ignore-whitespace.
6 +test_description='git-apply --ignore-whitespace.'
7
8 -'
8 +TEST_PASSES_SANITIZE_LEAK=true
9 . ./test-lib.sh
10
11 # This primes main.c file that indents without using HT at all.
t/t4124-apply-ws-rule.sh
+1
@@ -2,6 +2,7 @@
2
3 test_description='core.whitespace rules and git apply'
4
5 +TEST_PASSES_SANITIZE_LEAK=true
6 . ./test-lib.sh
7
8 prepare_test_file () {
t/t4125-apply-ws-fuzz.sh
+1
@@ -2,6 +2,7 @@
2
3 test_description='applying patch that has broken whitespaces in context'
4
5 +TEST_PASSES_SANITIZE_LEAK=true
6 . ./test-lib.sh
7
8 test_expect_success setup '
t/t4138-apply-ws-expansion.sh
+1
@@ -5,6 +5,7 @@
5
6 test_description='git apply test patches with whitespace expansion.'
7
8 +TEST_PASSES_SANITIZE_LEAK=true
9 . ./test-lib.sh
10
11 test_expect_success setup '