midx-write.c: introduce `midx_pack_perm()` helper

The `ctx->pack_perm` array can be considered as a permutation between the original `pack_int_id` of some given pack to its position in the `ctx->info` array containing all packs. Today we can always index into this array with any known `pack_int_id`, since there is never a `pack_int_id` which is greater than or equal to the value `ctx->nr`. That is not necessarily the case with MIDX compaction. For example, suppose we have a MIDX chain with three layers, each containing three packs. The base of the MIDX chain will have packs with IDs 0, 1, and 2, the next layer 3, 4, and 5, and so on. If we are compacting the topmost two layers, we'll have input `pack_int_id` values between [3, 8], but `ctx->nr` will only be 6. In that example, if we want to know where the pack whose original `pack_int_id` value was, say, 7, we would compute `ctx->pack_perm[7]`, leading to an uninitialized read, since there are only 6 entries allocated in that array. To address this, there are a couple of options: - We could allocate enough entries in `ctx->pack_perm` to accommodate the largest `orig_pack_int_id` value. - Or, we could internally shift the input values by the number of packs in the base layer of the lower end of the MIDX compaction range. This patch prepare us to take the latter approach, since it does not allocate more memory than strictly necessary. (In our above example, the base of the lower end of the compaction range is the first MIDX layer (having three packs), so we would end up indexing `ctx->pack_perm[7-3]`, which is a valid read.) Note that this patch does not actually implement that approach yet, but merely performs a behavior-preserving refactoring which will make the change easier to carry out in the future. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Feb 24, 2026 at 14:00 UTC 4f8543255efc3cc1d5247fa16a9f7597ad565993
1 file changed +12 -6
midx-write.c
+12 -6
@@ -119,6 +119,12 @@ struct write_midx_context {
119 struct odb_source *source;
120 };
121
122 +static uint32_t midx_pack_perm(struct write_midx_context *ctx,
123 + uint32_t orig_pack_int_id)
124 +{
125 + return ctx->pack_perm[orig_pack_int_id];
126 +}
127 +
128 static int should_include_pack(const struct write_midx_context *ctx,
129 const char *file_name)
130 {
@@ -521,12 +527,12 @@ static int write_midx_object_offsets(struct hashfile *f,
527 for (i = 0; i < ctx->entries_nr; i++) {
528 struct pack_midx_entry *obj = list++;
529
524 - if (ctx->pack_perm[obj->pack_int_id] == PACK_EXPIRED)
530 + if (midx_pack_perm(ctx, obj->pack_int_id) == PACK_EXPIRED)
531 BUG("object %s is in an expired pack with int-id %d",
532 oid_to_hex(&obj->oid),
533 obj->pack_int_id);
534
529 - hashwrite_be32(f, ctx->pack_perm[obj->pack_int_id]);
535 + hashwrite_be32(f, midx_pack_perm(ctx, obj->pack_int_id));
536
537 if (ctx->large_offsets_needed && obj->offset >> 31)
538 hashwrite_be32(f, MIDX_LARGE_OFFSET_NEEDED | nr_large_offset++);
@@ -627,7 +633,7 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx)
633 for (i = 0; i < ctx->entries_nr; i++) {
634 struct pack_midx_entry *e = &ctx->entries[i];
635 data[i].nr = i;
630 - data[i].pack = ctx->pack_perm[e->pack_int_id];
636 + data[i].pack = midx_pack_perm(ctx, e->pack_int_id);
637 if (!e->preferred)
638 data[i].pack |= (1U << 31);
639 data[i].offset = e->offset;
@@ -637,7 +643,7 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx)
643
644 for (i = 0; i < ctx->entries_nr; i++) {
645 struct pack_midx_entry *e = &ctx->entries[data[i].nr];
640 - struct pack_info *pack = &ctx->info[ctx->pack_perm[e->pack_int_id]];
646 + struct pack_info *pack = &ctx->info[midx_pack_perm(ctx, e->pack_int_id)];
647 if (pack->bitmap_pos == BITMAP_POS_UNKNOWN)
648 pack->bitmap_pos = i + base_objects;
649 pack->bitmap_nr++;
@@ -698,7 +704,7 @@ static void prepare_midx_packing_data(struct packing_data *pdata,
704 struct object_entry *to = packlist_alloc(pdata, &from->oid);
705
706 oe_set_in_pack(pdata, to,
701 - ctx->info[ctx->pack_perm[from->pack_int_id]].p);
707 + ctx->info[midx_pack_perm(ctx, from->pack_int_id)].p);
708 }
709
710 trace2_region_leave("midx", "prepare_midx_packing_data", ctx->repo);
@@ -1384,7 +1390,7 @@ static int write_midx_internal(struct write_midx_opts *opts)
1390 sizeof(*ctx.info),
1391 idx_or_pack_name_cmp);
1392 if (preferred) {
1387 - uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id];
1393 + uint32_t perm = midx_pack_perm(&ctx, preferred->orig_pack_int_id);
1394 if (perm == PACK_EXPIRED)
1395 warning(_("preferred pack '%s' is expired"),
1396 opts->preferred_pack_name);