# \[PATCH 00/10\] Reftable coverity fixes (page 1 of 2)

- Subsystems: THE REST
- Maintainers: Linus Torvalds
- Series: 10 patches touching 17 files, +360/-376
- Status: SUPERSEDED (Superseded by v7 posted 2022-01-24)
- Superseded by v7 (posted 2022-01-24).
- Revision v1 (7 revisions archived).
- List: git
- 145 messages, 7 authors
- Page: https://ratatoskr.run/git/2021/12/7914155/t
- Next page: https://ratatoskr.run/git/2021/12/7914155/t/2.md

## \[PATCH 00/10\] Reftable coverity fixes

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:29 
- Message: https://ratatoskr.run/git/2021/12/7914155.md

```text
This series was targeted to 'next'.

This addresses some complaints from Coverity that Peff reported.

CI on GGG shows a segfault on windows
(https://github.com/git/git/runs/4446635428?check_suite_focus=true), which I
can't make sense of. Any hints would be greatly appreciated.

Han-Wen Nienhuys (10):
  reftable: fix OOB stack write in print functions
  reftable: fix resource leak in error path
  reftable: fix resource leak blocksource.c
  reftable: check reftable_stack_auto_compact() return value
  reftable: ignore remove() return value in stack_test.c
  reftable: fix resource warning
  reftable: fix NULL derefs in error paths
  reftable: order unittests by complexity
  reftable: drop stray printf in readwrite_test
  reftable: make reftable_record a tagged union

 reftable/block.c          |  29 ++++--
 reftable/block_test.c     |  22 ++---
 reftable/blocksource.c    |   6 +-
 reftable/generic.c        |  35 ++++----
 reftable/iter.c           |   4 +-
 reftable/merged.c         |  37 ++++----
 reftable/pq.c             |   3 +-
 reftable/pq_test.c        |  31 +++----
 reftable/reader.c         | 108 ++++++++++++-----------
 reftable/readwrite_test.c |   1 -
 reftable/record.c         | 180 +++++++++++++++-----------------------
 reftable/record.h         |  45 ++++------
 reftable/record_test.c    | 162 +++++++++++++++++-----------------
 reftable/stack.c          |  15 ++--
 reftable/stack_test.c     |   3 +-
 reftable/writer.c         |  46 +++++-----
 t/helper/test-reftable.c  |   9 +-
 17 files changed, 360 insertions(+), 376 deletions(-)


base-commit: a8338297339b1ab064e799c15c4fc56c122ef967
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1152%2Fhanwen%2Freftable-coverity-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1152/hanwen/reftable-coverity-v1
Pull-Request: https://github.com/git/git/pull/1152
-- 
gitgitgadget
```

## \[PATCH 01/10\] reftable: fix OOB stack write in print functions

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:30 
- Message: https://ratatoskr.run/git/2021/12/7914156.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc6..8536bd03aa9 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -254,7 +254,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 void reftable_ref_record_print(struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
-	char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 	switch (ref->value_type) {
 	case REFTABLE_REF_SYMREF:
@@ -586,7 +586,7 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 void reftable_log_record_print(struct reftable_log_record *log,
 			       uint32_t hash_id)
 {
-	char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-- 
gitgitgadget

```

## \[PATCH 02/10\] reftable: fix resource leak in error path

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:31 
- Message: https://ratatoskr.run/git/2021/12/7914157.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered by corrupt files, so it doesn't have test coverage. This
was discovered by a Coverity scan.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c947..d7347bb3152 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	uint32_t full_block_size = table_block_size;
 	uint8_t typ = block->data[header_off];
 	uint32_t sz = get_be24(block->data + header_off + 1);
-
+	int err = 0;
 	uint16_t restart_count = 0;
 	uint32_t restart_start = 0;
 	uint8_t *restart_bytes = NULL;
+	uint8_t *uncompressed = NULL;
 
-	if (!reftable_is_block_type(typ))
-		return REFTABLE_FORMAT_ERROR;
+	if (!reftable_is_block_type(typ)) {
+		err =  REFTABLE_FORMAT_ERROR;
+		goto done;
+	}
 
 	if (typ == BLOCK_TYPE_LOG) {
 		int block_header_skip = 4 + header_off;
@@ -213,15 +216,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
 			reftable_free(uncompressed);
-			return REFTABLE_ZLIB_ERROR;
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz)
-			return REFTABLE_FORMAT_ERROR;
+		if (dst_len + block_header_skip != sz) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		}
 
 		/* We're done with the input data. */
 		reftable_block_done(block);
 		block->data = uncompressed;
+		uncompressed = NULL;
 		block->len = sz;
 		block->source = malloc_block_source();
 		full_block_size = src_len + block_header_skip;
@@ -251,7 +258,11 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	br->restart_count = restart_count;
 	br->restart_bytes = restart_bytes;
 
-	return 0;
+done:
+	if (uncompressed) {
+		reftable_free(uncompressed);
+	}
+	return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
-- 
gitgitgadget

```

## \[PATCH 03/10\] reftable: fix resource leak blocksource.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:32 
- Message: https://ratatoskr.run/git/2021/12/7914158.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered in the unlikely event of fstat() failing on an opened
file.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/blocksource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9aa..2605371c28d 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
 	}
 
 	err = fstat(fd, &st);
-	if (err < 0)
-		return -1;
+	if (err < 0) {
+		close(fd);
+		return REFTABLE_IO_ERROR;
+	}
 
 	p = reftable_calloc(sizeof(struct file_block_source));
 	p->size = st.st_size;
-- 
gitgitgadget

```

## \[PATCH 04/10\] reftable: check reftable\_stack\_auto\_compact() return value

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:33 
- Message: https://ratatoskr.run/git/2021/12/7914159.md

```text
From: Han-Wen Nienhuys <redacted>

Fixes a problem detected by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index eb0b7228b0c..d628420e63a 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -814,6 +814,7 @@ static void test_reftable_stack_auto_compaction(void)
 		EXPECT_ERR(err);
 
 		err = reftable_stack_auto_compact(st);
+		EXPECT_ERR(err);
 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
 	}
 
-- 
gitgitgadget

```

## \[PATCH 05/10\] reftable: ignore remove() return value in stack\_test.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:35 
- Message: https://ratatoskr.run/git/2021/12/7914160.md

```text
From: Han-Wen Nienhuys <redacted>

If the cleanup fails, there is nothing we can do.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index d628420e63a..4b7292945c3 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -89,7 +89,7 @@ static void test_read_file(void)
 		EXPECT(0 == strcmp(want[i], names[i]));
 	}
 	free_names(names);
-	remove(fn);
+	(void) remove(fn);
 }
 
 static void test_parse_names(void)
-- 
gitgitgadget

```

## \[PATCH 06/10\] reftable: fix resource warning

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:35 
- Message: https://ratatoskr.run/git/2021/12/7914161.md

```text
From: Han-Wen Nienhuys <redacted>

This would trigger in the unlikely event that we are compacting, and the next
available file handle is 0.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/reftable/stack.c b/reftable/stack.c
index df5021ebf08..10dfd370e8e 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -877,7 +877,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	struct strbuf new_table_path = STRBUF_INIT;
 	int err = 0;
 	int have_lock = 0;
-	int lock_file_fd = 0;
+	int lock_file_fd = -1;
 	int compact_count = last - first + 1;
 	char **listp = NULL;
 	char **delete_on_success =
@@ -911,7 +911,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	}
 	/* Don't want to write to the lock for now.  */
 	close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 
 	have_lock = 1;
 	err = stack_uptodate(st);
@@ -932,7 +932,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 
 		sublock_file_fd = open(subtab_lock.buf,
 				       O_EXCL | O_CREAT | O_WRONLY, 0644);
-		if (sublock_file_fd > 0) {
+		if (sublock_file_fd >= 0) {
 			close(sublock_file_fd);
 		} else if (sublock_file_fd < 0) {
 			if (errno == EEXIST) {
@@ -1013,7 +1013,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 		goto done;
 	}
 	err = close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 	if (err < 0) {
 		err = REFTABLE_IO_ERROR;
 		unlink(new_table_path.buf);
@@ -1050,9 +1050,9 @@ done:
 		listp++;
 	}
 	free_names(subtable_locks);
-	if (lock_file_fd > 0) {
+	if (lock_file_fd >= 0) {
 		close(lock_file_fd);
-		lock_file_fd = 0;
+		lock_file_fd = -1;
 	}
 	if (have_lock) {
 		unlink(lock_file_name.buf);
-- 
gitgitgadget

```

## \[PATCH 07/10\] reftable: fix NULL derefs in error paths

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:36 
- Message: https://ratatoskr.run/git/2021/12/7914162.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reader.c | 3 ++-
 reftable/stack.c  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..8d308d858f8 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -796,6 +796,7 @@ int reftable_reader_print_file(const char *tablename)
 	reftable_table_from_reader(&tab, r);
 	err = reftable_table_print(&tab);
 done:
-	reftable_reader_free(r);
+	if (r)
+		reftable_reader_free(r);
 	return err;
 }
diff --git a/reftable/stack.c b/reftable/stack.c
index 10dfd370e8e..eb03b6c74f6 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -707,7 +707,8 @@ done:
 	strbuf_release(&temp_tab_file_name);
 	strbuf_release(&tab_file_name);
 	strbuf_release(&next_name);
-	reftable_writer_free(wr);
+	if (wr)
+		reftable_writer_free(wr);
 	return err;
 }
 
-- 
gitgitgadget

```

## \[PATCH 08/10\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:37 
- Message: https://ratatoskr.run/git/2021/12/7914163.md

```text
From: Han-Wen Nienhuys <redacted>

This is a more pratical ordering when working on refactorings of the reftable
code.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 t/helper/test-reftable.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b789..f08d66df949 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	// test from simple to complex.
 	basics_test_main(argc, argv);
+	record_test_main(argc, argv);
 	block_test_main(argc, argv);
-	merged_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	pq_test_main(argc, argv);
-	record_test_main(argc, argv);
-	refname_test_main(argc, argv);
 	readwrite_test_main(argc, argv);
+	merged_test_main(argc, argv);
 	stack_test_main(argc, argv);
-	tree_test_main(argc, argv);
+	refname_test_main(argc, argv);
 	return 0;
 }
 
-- 
gitgitgadget

```

## \[PATCH 09/10\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:41 
- Message: https://ratatoskr.run/git/2021/12/7914164.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/readwrite_test.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 5f6bcc2f775..7c40b9b77f3 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -597,7 +597,6 @@ static void test_write_key_order(void)
 	err = reftable_writer_add_ref(w, &refs[0]);
 	EXPECT_ERR(err);
 	err = reftable_writer_add_ref(w, &refs[1]);
-	printf("%d\n", err);
 	EXPECT(err == REFTABLE_API_ERROR);
 	reftable_writer_close(w);
 	reftable_writer_free(w);
-- 
gitgitgadget

```

## \[PATCH 10/10\] reftable: make reftable\_record a tagged union

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-07 17:45:42 
- Message: https://ratatoskr.run/git/2021/12/7914165.md

```text
From: Han-Wen Nienhuys <redacted>

This reduces the amount of glue code, because we don't need a void pointer or
vtable within the structure.

The only snag is that reftable_index_record contain a strbuf, so it cannot be
zero-initialized. To address this, introduce reftable_record_for() to create a
fresh instance, given a record type.

Thanks to Peff for the suggestion.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c       |   4 +-
 reftable/block_test.c  |  22 +++---
 reftable/generic.c     |  35 ++++----
 reftable/iter.c        |   4 +-
 reftable/merged.c      |  37 ++++-----
 reftable/pq.c          |   3 +-
 reftable/pq_test.c     |  31 ++++----
 reftable/reader.c      | 105 ++++++++++++------------
 reftable/record.c      | 176 ++++++++++++++++-------------------------
 reftable/record.h      |  45 +++++------
 reftable/record_test.c | 162 +++++++++++++++++++------------------
 reftable/writer.c      |  46 ++++++-----
 12 files changed, 319 insertions(+), 351 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index d7347bb3152..ad713caa57b 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -382,7 +382,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 		.key = *want,
 		.r = br,
 	};
-	struct reftable_record rec = reftable_new_record(block_reader_type(br));
+	struct reftable_record rec = reftable_record_for(block_reader_type(br));
 	struct strbuf key = STRBUF_INIT;
 	int err = 0;
 	struct block_iter next = {
@@ -424,7 +424,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 done:
 	strbuf_release(&key);
 	strbuf_release(&next.last_key);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 
 	return err;
 }
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 4b3ea262dcb..670329f2224 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
 	struct block_writer bw = {
 		.last_key = STRBUF_INIT,
 	};
-	struct reftable_ref_record ref = { NULL };
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+	};
 	int i = 0;
 	int n;
 	struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
 	block.source = malloc_block_source();
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
-	reftable_record_from_ref(&rec, &ref);
 
 	for (i = 0; i < N; i++) {
 		char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
 		snprintf(name, sizeof(name), "branch%02d", i);
 		memset(hash, i, sizeof(hash));
 
-		ref.refname = name;
-		ref.value_type = REFTABLE_REF_VAL1;
-		ref.value.val1 = hash;
+		rec.ref.refname = name;
+		rec.ref.value_type = REFTABLE_REF_VAL1;
+		rec.ref.value.val1 = hash;
 
 		names[i] = xstrdup(name);
 		n = block_writer_add(&bw, &rec);
-		ref.refname = NULL;
-		ref.value_type = REFTABLE_REF_DELETION;
+		rec.ref.refname = NULL;
+		rec.ref.value_type = REFTABLE_REF_DELETION;
 		EXPECT(n == 0);
 	}
 
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
 		if (r > 0) {
 			break;
 		}
-		EXPECT_STREQ(names[j], ref.refname);
+		EXPECT_STREQ(names[j], rec.ref.refname);
 		j++;
 	}
 
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
 
-		EXPECT_STREQ(names[i], ref.refname);
+		EXPECT_STREQ(names[i], rec.ref.refname);
 
 		want.len--;
 		n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
 
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
-		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+		EXPECT_STREQ(names[10 * (i / 10)], rec.ref.refname);
 
 		block_iter_close(&it);
 	}
diff --git a/reftable/generic.c b/reftable/generic.c
index 7a8a738d860..7f00aa9cdbe 100644
--- a/reftable/generic.c
+++ b/reftable/generic.c
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
 */
 
 #include "basics.h"
+#include "constants.h"
 #include "record.h"
 #include "generic.h"
 #include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
 int reftable_table_seek_ref(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+				       .ref = {
+					       .refname = (char *)name,
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
 int reftable_table_seek_log(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = ~((uint64_t)0),
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+				       .log = {
+					       .refname = (char *)name,
+					       .update_index = ~((uint64_t)0),
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
@@ -129,17 +128,19 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
 			       struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, ref);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
+	int err = iterator_next(it, &rec);
+	*ref = rec.ref;
+	return err;
 }
 
 int reftable_iterator_next_log(struct reftable_iterator *it,
 			       struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, log);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
+	int err = iterator_next(it, &rec);
+	*log = rec.log;
+	return err;
 }
 
 int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
diff --git a/reftable/iter.c b/reftable/iter.c
index 93d04f735b8..fedf5d8ab0b 100644
--- a/reftable/iter.c
+++ b/reftable/iter.c
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
 				       struct reftable_record *rec)
 {
 	struct filtering_ref_iterator *fri = iter_arg;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->ref;
 	int err = 0;
 	while (1) {
 		err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
 static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
 {
 	struct indexed_table_ref_iter *it = p;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->ref;
 
 	while (1) {
 		int err = block_iter_next(&it->cur, rec);
diff --git a/reftable/merged.c b/reftable/merged.c
index e5b53da6db3..e69955d7c2d 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -22,7 +22,7 @@ static int merged_iter_init(struct merged_iter *mi)
 {
 	int i = 0;
 	for (i = 0; i < mi->stack_len; i++) {
-		struct reftable_record rec = reftable_new_record(mi->typ);
+		struct reftable_record rec = reftable_record_for(mi->typ);
 		int err = iterator_next(&mi->stack[i], &rec);
 		if (err < 0) {
 			return err;
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
 
 		if (err > 0) {
 			reftable_iterator_destroy(&mi->stack[i]);
-			reftable_record_destroy(&rec);
+			reftable_record_release(&rec);
 		} else {
 			struct pq_entry e = {
 				.rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
 static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
 					       size_t idx)
 {
-	struct reftable_record rec = reftable_new_record(mi->typ);
 	struct pq_entry e = {
-		.rec = rec,
+		.rec = reftable_record_for(mi->typ),
 		.index = idx,
 	};
-	int err = iterator_next(&mi->stack[idx], &rec);
+	int err = iterator_next(&mi->stack[idx], &e.rec);
 	if (err < 0)
 		return err;
 
 	if (err > 0) {
 		reftable_iterator_destroy(&mi->stack[idx]);
-		reftable_record_destroy(&rec);
+		reftable_record_release(&e.rec);
 		return 0;
 	}
 
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
 		if (err < 0) {
 			return err;
 		}
-		reftable_record_destroy(&top.rec);
+		reftable_record_release(&top.rec);
 	}
 
 	reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
-	reftable_record_destroy(&entry.rec);
+	reftable_record_release(&entry.rec);
 	strbuf_release(&entry_key);
 	return 0;
 }
@@ -246,7 +245,7 @@ static int merged_table_seek_record(struct reftable_merged_table *mt,
 		sizeof(struct reftable_iterator) * mt->stack_len);
 	struct merged_iter merged = {
 		.stack = iters,
-		.typ = reftable_record_type(rec),
+		.typ = rec->type,
 		.hash_id = mt->hash_id,
 		.suppress_deletions = mt->suppress_deletions,
 	};
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
 				   struct reftable_iterator *it,
 				   const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
@@ -302,12 +302,13 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
 				      struct reftable_iterator *it,
 				      const char *name, uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
diff --git a/reftable/pq.c b/reftable/pq.c
index efc474017a2..96ca6dd37b3 100644
--- a/reftable/pq.c
+++ b/reftable/pq.c
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
 {
 	int i = 0;
+
 	if (pq->len == pq->cap) {
 		pq->cap = 2 * pq->cap + 1;
 		pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
 	int i = 0;
 	for (i = 0; i < pq->len; i++) {
-		reftable_record_destroy(&pq->heap[i].rec);
+		reftable_record_release(&pq->heap[i].rec);
 	}
 	FREE_AND_NULL(pq->heap);
 	pq->len = pq->cap = 0;
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index c9bb05e37b7..89d069d674d 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -31,7 +31,7 @@ static void test_pq(void)
 	int N = ARRAY_SIZE(names) - 1;
 
 	struct merged_iter_pqueue pq = { NULL };
-	const char *last = NULL;
+	char *last = NULL;
 
 	int i = 0;
 	for (i = 0; i < N; i++) {
@@ -42,12 +42,14 @@ static void test_pq(void)
 
 	i = 1;
 	do {
-		struct reftable_record rec =
-			reftable_new_record(BLOCK_TYPE_REF);
-		struct pq_entry e = { 0 };
-
-		reftable_record_as_ref(&rec)->refname = names[i];
-		e.rec = rec;
+		struct pq_entry e = {
+			.rec = {
+				.type = BLOCK_TYPE_REF,
+				.ref = {
+					.refname = names[i],
+				}
+			}
+		};
 		merged_iter_pqueue_add(&pq, e);
 		merged_iter_pqueue_check(pq);
 		i = (i * 7) % N;
@@ -55,19 +57,18 @@ static void test_pq(void)
 
 	while (!merged_iter_pqueue_is_empty(pq)) {
 		struct pq_entry e = merged_iter_pqueue_remove(&pq);
-		struct reftable_ref_record *ref =
-			reftable_record_as_ref(&e.rec);
-
+		struct reftable_record *rec = &e.rec;
 		merged_iter_pqueue_check(pq);
 
+		EXPECT(rec->type == BLOCK_TYPE_REF);
 		if (last) {
-			EXPECT(strcmp(last, ref->refname) < 0);
+			EXPECT(strcmp(last, rec->ref.refname) < 0);
 		}
-		last = ref->refname;
-		ref->refname = NULL;
-		reftable_free(ref);
+		// this is names[i], so don't dealloc.
+		last = rec->ref.refname;
+		rec->ref.refname = NULL;
+		reftable_record_release(rec);
 	}
-
 	for (i = 0; i < N; i++) {
 		reftable_free(names[i]);
 	}
diff --git a/reftable/reader.c b/reftable/reader.c
index 8d308d858f8..0159f131122 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -238,9 +238,8 @@ static int table_iter_next_in_block(struct table_iter *ti,
 				    struct reftable_record *rec)
 {
 	int res = block_iter_next(&ti->bi, rec);
-	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
-		((struct reftable_ref_record *)rec->data)->update_index +=
-			ti->r->min_update_index;
+	if (res == 0 && rec->type == BLOCK_TYPE_REF) {
+		rec->ref.update_index += ti->r->min_update_index;
 	}
 
 	return res;
@@ -345,7 +344,7 @@ static int table_iter_next_block(struct table_iter *dest,
 
 static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)
 {
-	if (reftable_record_type(rec) != ti->typ)
+	if (rec->type != ti->typ)
 		return REFTABLE_API_ERROR;
 
 	while (1) {
@@ -437,8 +436,7 @@ static int reader_start(struct reftable_reader *r, struct table_iter *ti,
 static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 			      struct reftable_record *want)
 {
-	struct reftable_record rec =
-		reftable_new_record(reftable_record_type(want));
+	struct reftable_record rec = reftable_record_for(want->type);
 	struct strbuf want_key = STRBUF_INIT;
 	struct strbuf got_key = STRBUF_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
@@ -475,7 +473,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 
 done:
 	block_iter_close(&next.bi);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 	strbuf_release(&want_key);
 	strbuf_release(&got_key);
 	return err;
@@ -485,38 +483,40 @@ static int reader_seek_indexed(struct reftable_reader *r,
 			       struct reftable_iterator *it,
 			       struct reftable_record *rec)
 {
-	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-	struct reftable_record want_index_rec = { NULL };
-	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-	struct reftable_record index_result_rec = { NULL };
+	struct reftable_record want_index = {
+		.type = BLOCK_TYPE_INDEX,
+		.idx = { .last_key = STRBUF_INIT }
+	};
+	struct reftable_record index_result = {
+		.type = BLOCK_TYPE_INDEX,
+		.idx = { .last_key = STRBUF_INIT },
+	};
 	struct table_iter index_iter = TABLE_ITER_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
 	int err = 0;
 
-	reftable_record_key(rec, &want_index.last_key);
-	reftable_record_from_index(&want_index_rec, &want_index);
-	reftable_record_from_index(&index_result_rec, &index_result);
-
-	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
+	reftable_record_key(rec, &want_index.idx.last_key);
+	err = reader_start(r, &index_iter, rec->type, 1);
 	if (err < 0)
 		goto done;
 
-	err = reader_seek_linear(r, &index_iter, &want_index_rec);
+	err = reader_seek_linear(r, &index_iter, &want_index);
 	while (1) {
-		err = table_iter_next(&index_iter, &index_result_rec);
+		err = table_iter_next(&index_iter, &index_result);
 		table_iter_block_done(&index_iter);
 		if (err != 0)
 			goto done;
 
-		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		err = reader_table_iter_at(r, &next, index_result.idx.offset,
+					   0);
 		if (err != 0)
 			goto done;
 
-		err = block_iter_seek(&next.bi, &want_index.last_key);
+		err = block_iter_seek(&next.bi, &want_index.idx.last_key);
 		if (err < 0)
 			goto done;
 
-		if (next.typ == reftable_record_type(rec)) {
+		if (next.typ == rec->type) {
 			err = 0;
 			break;
 		}
@@ -540,8 +540,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
 done:
 	block_iter_close(&next.bi);
 	table_iter_close(&index_iter);
-	reftable_record_release(&want_index_rec);
-	reftable_record_release(&index_result_rec);
+	reftable_record_release(&want_index);
+	reftable_record_release(&index_result);
 	return err;
 }
 
@@ -549,15 +549,14 @@ static int reader_seek_internal(struct reftable_reader *r,
 				struct reftable_iterator *it,
 				struct reftable_record *rec)
 {
-	struct reftable_reader_offsets *offs =
-		reader_offsets_for(r, reftable_record_type(rec));
+	struct reftable_reader_offsets *offs = reader_offsets_for(r, rec->type);
 	uint64_t idx = offs->index_offset;
 	struct table_iter ti = TABLE_ITER_INIT;
 	int err = 0;
 	if (idx > 0)
 		return reader_seek_indexed(r, it, rec);
 
-	err = reader_start(r, &ti, reftable_record_type(rec), 0);
+	err = reader_start(r, &ti, rec->type, 0);
 	if (err < 0)
 		return err;
 	err = reader_seek_linear(r, &ti, rec);
@@ -576,7 +575,7 @@ static int reader_seek_internal(struct reftable_reader *r,
 static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 		       struct reftable_record *rec)
 {
-	uint8_t typ = reftable_record_type(rec);
+	uint8_t typ = rec->type;
 
 	struct reftable_reader_offsets *offs = reader_offsets_for(r, typ);
 	if (!offs->is_present) {
@@ -590,11 +589,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 int reftable_reader_seek_ref(struct reftable_reader *r,
 			     struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return reader_seek(r, it, &rec);
 }
 
@@ -602,12 +602,13 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
 				struct reftable_iterator *it, const char *name,
 				uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return reader_seek(r, it, &rec);
 }
 
@@ -649,31 +650,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 					    struct reftable_iterator *it,
 					    uint8_t *oid)
 {
-	struct reftable_obj_record want = {
-		.hash_prefix = oid,
-		.hash_prefix_len = r->object_id_len,
+	struct reftable_record want = {
+		.type = BLOCK_TYPE_OBJ,
+		.obj = {
+			.hash_prefix = oid,
+			.hash_prefix_len = r->object_id_len,
+		},
 	};
-	struct reftable_record want_rec = { NULL };
 	struct reftable_iterator oit = { NULL };
-	struct reftable_obj_record got = { NULL };
-	struct reftable_record got_rec = { NULL };
+	struct reftable_record got = {
+		.type = BLOCK_TYPE_OBJ,
+		.obj = { 0 },
+	};
 	int err = 0;
 	struct indexed_table_ref_iter *itr = NULL;
 
 	/* Look through the reverse index. */
-	reftable_record_from_obj(&want_rec, &want);
-	err = reader_seek(r, &oit, &want_rec);
+	err = reader_seek(r, &oit, &want);
 	if (err != 0)
 		goto done;
 
 	/* read out the reftable_obj_record */
-	reftable_record_from_obj(&got_rec, &got);
-	err = iterator_next(&oit, &got_rec);
+	err = iterator_next(&oit, &got);
 	if (err < 0)
 		goto done;
 
-	if (err > 0 ||
-	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+	if (err > 0 || memcmp(want.obj.hash_prefix, got.obj.hash_prefix,
+			      r->object_id_len)) {
 		/* didn't find it; return empty iterator */
 		iterator_set_empty(it);
 		err = 0;
@@ -681,15 +684,15 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 	}
 
 	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
-					 got.offsets, got.offset_len);
+					 got.obj.offsets, got.obj.offset_len);
 	if (err < 0)
 		goto done;
-	got.offsets = NULL;
+	got.obj.offsets = NULL;
 	iterator_from_indexed_table_ref_iter(it, itr);
 
 done:
 	reftable_iterator_destroy(&oit);
-	reftable_record_release(&got_rec);
+	reftable_record_release(&got);
 	return err;
 }
 
diff --git a/reftable/record.c b/reftable/record.c
index 8536bd03aa9..b665feb709f 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "basics.h"
 
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
 int get_var_int(uint64_t *dest, struct string_view *in)
 {
 	int ptr = 0;
@@ -926,58 +930,6 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.is_deletion = &reftable_log_record_is_deletion_void,
 };
 
-struct reftable_record reftable_new_record(uint8_t typ)
-{
-	struct reftable_record rec = { NULL };
-	switch (typ) {
-	case BLOCK_TYPE_REF: {
-		struct reftable_ref_record *r =
-			reftable_calloc(sizeof(struct reftable_ref_record));
-		reftable_record_from_ref(&rec, r);
-		return rec;
-	}
-
-	case BLOCK_TYPE_OBJ: {
-		struct reftable_obj_record *r =
-			reftable_calloc(sizeof(struct reftable_obj_record));
-		reftable_record_from_obj(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_LOG: {
-		struct reftable_log_record *r =
-			reftable_calloc(sizeof(struct reftable_log_record));
-		reftable_record_from_log(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_INDEX: {
-		struct reftable_index_record empty = { .last_key =
-							       STRBUF_INIT };
-		struct reftable_index_record *r =
-			reftable_calloc(sizeof(struct reftable_index_record));
-		*r = empty;
-		reftable_record_from_index(&rec, r);
-		return rec;
-	}
-	}
-	abort();
-	return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
-	void *p = rec->data;
-	rec->data = NULL;
-	return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
-	reftable_record_release(rec);
-	reftable_free(reftable_record_yield(rec));
-}
-
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
 {
 	const struct reftable_index_record *rec = r;
@@ -1055,91 +1007,47 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 {
-	rec->ops->key(rec->data, dest);
-}
-
-uint8_t reftable_record_type(struct reftable_record *rec)
-{
-	return rec->ops->type;
+	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
 			   int hash_size)
 {
-	return rec->ops->encode(rec->data, dest, hash_size);
+	return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+						   dest, hash_size);
 }
 
 void reftable_record_copy_from(struct reftable_record *rec,
 			       struct reftable_record *src, int hash_size)
 {
-	assert(src->ops->type == rec->ops->type);
+	assert(src->type == rec->type);
 
-	rec->ops->copy_from(rec->data, src->data, hash_size);
+	reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+					       reftable_record_data(src),
+					       hash_size);
 }
 
 uint8_t reftable_record_val_type(struct reftable_record *rec)
 {
-	return rec->ops->val_type(rec->data);
+	return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   uint8_t extra, struct string_view src, int hash_size)
 {
-	return rec->ops->decode(rec->data, key, extra, src, hash_size);
+	return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+						   key, extra, src, hash_size);
 }
 
 void reftable_record_release(struct reftable_record *rec)
 {
-	rec->ops->release(rec->data);
+	reftable_record_vtable(rec)->release(reftable_record_data(rec));
 }
 
 int reftable_record_is_deletion(struct reftable_record *rec)
 {
-	return rec->ops->is_deletion(rec->data);
-}
-
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *ref_rec)
-{
-	assert(!rec->ops);
-	rec->data = ref_rec;
-	rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *obj_rec)
-{
-	assert(!rec->ops);
-	rec->data = obj_rec;
-	rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *index_rec)
-{
-	assert(!rec->ops);
-	rec->data = index_rec;
-	rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *log_rec)
-{
-	assert(!rec->ops);
-	rec->data = log_rec;
-	rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
-	return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
-	return rec->data;
+	return reftable_record_vtable(rec)->is_deletion(
+		reftable_record_data(rec));
 }
 
 static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1210,3 +1118,53 @@ void string_view_consume(struct string_view *s, int n)
 	s->buf += n;
 	s->len -= n;
 }
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &rec->ref;
+	case BLOCK_TYPE_LOG:
+		return &rec->log;
+	case BLOCK_TYPE_INDEX:
+		return &rec->idx;
+	case BLOCK_TYPE_OBJ:
+		return &rec->obj;
+	}
+	abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &reftable_ref_record_vtable;
+	case BLOCK_TYPE_LOG:
+		return &reftable_log_record_vtable;
+	case BLOCK_TYPE_INDEX:
+		return &reftable_index_record_vtable;
+	case BLOCK_TYPE_OBJ:
+		return &reftable_obj_record_vtable;
+	}
+	abort();
+}
+
+struct reftable_record reftable_record_for(uint8_t typ)
+{
+	struct reftable_record clean_idx = {
+		.type = BLOCK_TYPE_INDEX,
+		.idx = {
+			.last_key = STRBUF_INIT,
+		},
+	};
+	struct reftable_record clean = {
+		.type = typ,
+	};
+
+	if (typ == BLOCK_TYPE_INDEX) {
+		return clean_idx;
+	}
+
+	return clean;
+}
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf4..a702c67e857 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -60,18 +60,9 @@ struct reftable_record_vtable {
 	int (*is_deletion)(const void *rec);
 };
 
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
-	void *data;
-	struct reftable_record_vtable *ops;
-};
-
 /* returns true for recognized block types. Block start with the block type. */
 int reftable_is_block_type(uint8_t typ);
 
-/* creates a malloced record of the given type. Dispose with record_destroy */
-struct reftable_record reftable_new_record(uint8_t typ);
-
 /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
  * number of bytes written. */
 int reftable_encode_key(int *is_restart, struct string_view dest,
@@ -97,8 +88,26 @@ struct reftable_obj_record {
 	int offset_len;
 };
 
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_record_for() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+	uint8_t type;
+	union {
+		struct reftable_ref_record ref;
+		struct reftable_log_record log;
+		struct reftable_obj_record obj;
+		struct reftable_index_record idx;
+	};
+};
+
 /* see struct record_vtable */
 
+/* return an initialized record for the given type */
+struct reftable_record reftable_record_for(uint8_t typ);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
 uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
@@ -111,25 +120,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   int hash_size);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
 /* for qsort. */
 int reftable_ref_record_compare_name(const void *a, const void *b);
 
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace41..23abfb037b3 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -16,24 +16,22 @@
 
 static void test_copy(struct reftable_record *rec)
 {
-	struct reftable_record copy =
-		reftable_new_record(reftable_record_type(rec));
+	struct reftable_record copy = reftable_record_for(rec->type);
+
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-	switch (reftable_record_type(&copy)) {
+	switch (copy.type) {
 	case BLOCK_TYPE_REF:
-		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-						 reftable_record_as_ref(rec),
+		EXPECT(reftable_ref_record_equal(&copy.ref, &rec->ref,
 						 GIT_SHA1_RAWSZ));
 		break;
 	case BLOCK_TYPE_LOG:
-		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-						 reftable_record_as_log(rec),
+		EXPECT(reftable_log_record_equal(&copy.log, &rec->log,
 						 GIT_SHA1_RAWSZ));
 		break;
 	}
-	reftable_record_destroy(&copy);
+	reftable_record_release(&copy);
 }
 
 static void test_varint_roundtrip(void)
@@ -106,61 +104,60 @@ static void test_reftable_ref_record_roundtrip(void)
 	int i = 0;
 
 	for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-		struct reftable_ref_record in = { NULL };
-		struct reftable_ref_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_REF,
+		};
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_REF
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_record rec = { NULL };
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-
 		int n, m;
 
-		in.value_type = i;
+		in.ref.value_type = i;
 		switch (i) {
 		case REFTABLE_REF_DELETION:
 			break;
 		case REFTABLE_REF_VAL1:
-			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val1, 1);
+			in.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.ref.value.val1, 1);
 			break;
 		case REFTABLE_REF_VAL2:
-			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.value, 1);
-			in.value.val2.target_value =
+			in.ref.value.val2.value =
+				reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.ref.value.val2.value, 1);
+			in.ref.value.val2.target_value =
 				reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.target_value, 2);
+			set_hash(in.ref.value.val2.target_value, 2);
 			break;
 		case REFTABLE_REF_SYMREF:
-			in.value.symref = xstrdup("target");
+			in.ref.value.symref = xstrdup("target");
 			break;
 		}
-		in.refname = xstrdup("refs/heads/master");
+		in.ref.refname = xstrdup("refs/heads/master");
 
-		reftable_record_from_ref(&rec, &in);
-		test_copy(&rec);
+		test_copy(&in);
 
-		EXPECT(reftable_record_val_type(&rec) == i);
+		EXPECT(reftable_record_val_type(&in) == i);
 
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
 
 		/* decode into a non-zero reftable_record to test for leaks. */
-
-		reftable_record_from_ref(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, i, dest,
-					   GIT_SHA1_RAWSZ);
+		m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
-		reftable_record_release(&rec_out);
+		EXPECT(reftable_ref_record_equal(&in.ref, &out.ref,
+						 GIT_SHA1_RAWSZ));
+		reftable_record_release(&in);
 
 		strbuf_release(&key);
-		reftable_ref_record_release(&in);
+		reftable_record_release(&out);
 	}
 }
 
@@ -213,7 +210,9 @@ static void test_reftable_log_record_roundtrip(void)
 	set_test_hash(in[0].value.update.new_hash, 1);
 	set_test_hash(in[0].value.update.old_hash, 2);
 	for (i = 0; i < ARRAY_SIZE(in); i++) {
-		struct reftable_record rec = { NULL };
+		struct reftable_record rec = {
+			.type = BLOCK_TYPE_LOG
+		};
 		struct strbuf key = STRBUF_INIT;
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
@@ -221,8 +220,10 @@ static void test_reftable_log_record_roundtrip(void)
 			.len = sizeof(buffer),
 		};
 		/* populate out, to check for leaks. */
-		struct reftable_log_record out = {
-			.refname = xstrdup("old name"),
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_LOG,
+			.log = {
+				.refname = xstrdup("old name"),
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value = {
 				.update = {
@@ -233,11 +234,11 @@ static void test_reftable_log_record_roundtrip(void)
 					.message = xstrdup("old message"),
 				},
 			},
+			},
 		};
-		struct reftable_record rec_out = { NULL };
 		int n, m, valtype;
 
-		reftable_record_from_log(&rec, &in[i]);
+		rec.log = in[i];
 
 		test_copy(&rec);
 
@@ -245,16 +246,16 @@ static void test_reftable_log_record_roundtrip(void)
 
 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n >= 0);
-		reftable_record_from_log(&rec_out, &out);
 		valtype = reftable_record_val_type(&rec);
-		m = reftable_record_decode(&rec_out, key, valtype, dest,
+		m = reftable_record_decode(&out, key, valtype, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+		EXPECT(reftable_log_record_equal(&in[i], &out.log,
+						 GIT_SHA1_RAWSZ));
 		reftable_log_record_release(&in[i]);
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
@@ -322,47 +323,51 @@ static void test_reftable_obj_record_roundtrip(void)
 					       } };
 	int i = 0;
 	for (i = 0; i < ARRAY_SIZE(recs); i++) {
-		struct reftable_obj_record in = recs[i];
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-		struct reftable_record rec = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_OBJ,
+			.obj = recs[i],
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_obj_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_OBJ
+		};
 		int n, m;
 		uint8_t extra;
 
-		reftable_record_from_obj(&rec, &in);
-		test_copy(&rec);
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		test_copy(&in);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
-		extra = reftable_record_val_type(&rec);
-		reftable_record_from_obj(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, extra, dest,
+		extra = reftable_record_val_type(&in);
+		m = reftable_record_decode(&out, key, extra, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-		EXPECT(in.offset_len == out.offset_len);
+		EXPECT(in.obj.hash_prefix_len == out.obj.hash_prefix_len);
+		EXPECT(in.obj.offset_len == out.obj.offset_len);
 
-		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-			       in.hash_prefix_len));
-		EXPECT(0 == memcmp(in.offsets, out.offsets,
-				   sizeof(uint64_t) * in.offset_len));
+		EXPECT(!memcmp(in.obj.hash_prefix, out.obj.hash_prefix,
+			       in.obj.hash_prefix_len));
+		EXPECT(0 == memcmp(in.obj.offsets, out.obj.offsets,
+				   sizeof(uint64_t) * in.obj.offset_len));
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
 static void test_reftable_index_record_roundtrip(void)
 {
-	struct reftable_index_record in = {
-		.offset = 42,
-		.last_key = STRBUF_INIT,
+	struct reftable_record in = {
+		.type = BLOCK_TYPE_INDEX,
+		.idx = {
+			.offset = 42,
+			.last_key = STRBUF_INIT,
+		},
 	};
 	uint8_t buffer[1024] = { 0 };
 	struct string_view dest = {
@@ -370,31 +375,30 @@ static void test_reftable_index_record_roundtrip(void)
 		.len = sizeof(buffer),
 	};
 	struct strbuf key = STRBUF_INIT;
-	struct reftable_record rec = { NULL };
-	struct reftable_index_record out = { .last_key = STRBUF_INIT };
-	struct reftable_record out_rec = { NULL };
+	struct reftable_record out = {
+		.type = BLOCK_TYPE_INDEX,
+		.idx = { .last_key = STRBUF_INIT },
+	};
 	int n, m;
 	uint8_t extra;
 
-	strbuf_addstr(&in.last_key, "refs/heads/master");
-	reftable_record_from_index(&rec, &in);
-	reftable_record_key(&rec, &key);
-	test_copy(&rec);
+	strbuf_addstr(&in.idx.last_key, "refs/heads/master");
+	reftable_record_key(&in, &key);
+	test_copy(&in);
 
-	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
-	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+	EXPECT(0 == strbuf_cmp(&key, &in.idx.last_key));
+	n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 	EXPECT(n > 0);
 
-	extra = reftable_record_val_type(&rec);
-	reftable_record_from_index(&out_rec, &out);
-	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+	extra = reftable_record_val_type(&in);
+	m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
 	EXPECT(m == n);
 
-	EXPECT(in.offset == out.offset);
+	EXPECT(in.idx.offset == out.idx.offset);
 
-	reftable_record_release(&out_rec);
+	reftable_record_release(&out);
 	strbuf_release(&key);
-	strbuf_release(&in.last_key);
+	strbuf_release(&in.idx.last_key);
 }
 
 int record_test_main(int argc, const char *argv[])
diff --git a/reftable/writer.c b/reftable/writer.c
index 3ca721e9f64..df256f3a8b5 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -221,10 +221,10 @@ static int writer_add_record(struct reftable_writer *w,
 	strbuf_reset(&w->last_key);
 	strbuf_addbuf(&w->last_key, &key);
 	if (w->block_writer == NULL) {
-		writer_reinit_block_writer(w, reftable_record_type(rec));
+		writer_reinit_block_writer(w, rec->type);
 	}
 
-	assert(block_writer_type(w->block_writer) == reftable_record_type(rec));
+	assert(block_writer_type(w->block_writer) == rec->type);
 
 	if (block_writer_add(w->block_writer, rec) == 0) {
 		err = 0;
@@ -236,7 +236,7 @@ static int writer_add_record(struct reftable_writer *w,
 		goto done;
 	}
 
-	writer_reinit_block_writer(w, reftable_record_type(rec));
+	writer_reinit_block_writer(w, rec->type);
 	err = block_writer_add(w->block_writer, rec);
 	if (err < 0) {
 		goto done;
@@ -251,8 +251,10 @@ done:
 int reftable_writer_add_ref(struct reftable_writer *w,
 			    struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	struct reftable_ref_record copy = *ref;
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.ref = *ref,
+	};
 	int err = 0;
 
 	if (ref->refname == NULL)
@@ -261,8 +263,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
 	    ref->update_index > w->max_update_index)
 		return REFTABLE_API_ERROR;
 
-	reftable_record_from_ref(&rec, &copy);
-	copy.update_index -= w->min_update_index;
+	rec.ref.update_index -= w->min_update_index;
 
 	err = writer_add_record(w, &rec);
 	if (err < 0)
@@ -301,7 +302,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
 static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 					    struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.log = *log,
+	};
 	if (w->block_writer &&
 	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
 		int err = writer_finish_public_section(w);
@@ -311,8 +315,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 
 	w->next -= w->pending_padding;
 	w->pending_padding = 0;
-
-	reftable_record_from_log(&rec, log);
 	return writer_add_record(w, &rec);
 }
 
@@ -393,8 +395,10 @@ static int writer_finish_section(struct reftable_writer *w)
 		w->index_len = 0;
 		w->index_cap = 0;
 		for (i = 0; i < idx_len; i++) {
-			struct reftable_record rec = { NULL };
-			reftable_record_from_index(&rec, idx + i);
+			struct reftable_record rec = {
+				.type = BLOCK_TYPE_INDEX,
+				.idx = idx[i],
+			};
 			if (block_writer_add(w->block_writer, &rec) == 0) {
 				continue;
 			}
@@ -462,17 +466,18 @@ static void write_object_record(void *void_arg, void *key)
 {
 	struct write_record_arg *arg = void_arg;
 	struct obj_index_tree_node *entry = key;
-	struct reftable_obj_record obj_rec = {
-		.hash_prefix = (uint8_t *)entry->hash.buf,
-		.hash_prefix_len = arg->w->stats.object_id_len,
-		.offsets = entry->offsets,
-		.offset_len = entry->offset_len,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_OBJ,
+		.obj = {
+			.hash_prefix = (uint8_t *)entry->hash.buf,
+			.hash_prefix_len = arg->w->stats.object_id_len,
+			.offsets = entry->offsets,
+			.offset_len = entry->offset_len,
+		}
 	};
-	struct reftable_record rec = { NULL };
 	if (arg->err < 0)
 		goto done;
 
-	reftable_record_from_obj(&rec, &obj_rec);
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
@@ -485,7 +490,8 @@ static void write_object_record(void *void_arg, void *key)
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
-	obj_rec.offset_len = 0;
+
+	rec.obj.offset_len = 0;
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 
 	/* Should be able to write into a fresh block. */
-- 
gitgitgadget
```

## Re: \[PATCH 02/10\] reftable: fix resource leak in error path

- From: Derrick Stolee \<hidden\>
- Date: 2021-12-08 14:30:47 
- Message: https://ratatoskr.run/git/2021/12/7914300.md

```text
On 12/7/2021 12:45 PM, Han-Wen Nienhuys via GitGitGadget wrote:
> From: Han-Wen Nienhuys <redacted>
> 
> This would be triggered by corrupt files, so it doesn't have test coverage. This
> was discovered by a Coverity scan.
> 
> Signed-off-by: Han-Wen Nienhuys <redacted>
> ---
>  reftable/block.c | 25 ++++++++++++++++++-------
>  1 file changed, 18 insertions(+), 7 deletions(-)
> 
> diff --git a/reftable/block.c b/reftable/block.c
> index 855e3f5c947..d7347bb3152 100644
> --- a/reftable/block.c
> +++ b/reftable/block.c
> @@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
>  	uint32_t full_block_size = table_block_size;
>  	uint8_t typ = block->data[header_off];
>  	uint32_t sz = get_be24(block->data + header_off + 1);
> -
> +	int err = 0;
>  	uint16_t restart_count = 0;
>  	uint32_t restart_start = 0;
>  	uint8_t *restart_bytes = NULL;
> +	uint8_t *uncompressed = NULL;

You define this here...

>  
> -	if (!reftable_is_block_type(typ))
> -		return REFTABLE_FORMAT_ERROR;
> +	if (!reftable_is_block_type(typ)) {
> +		err =  REFTABLE_FORMAT_ERROR;
> +		goto done;
> +	}
>  
>  	if (typ == BLOCK_TYPE_LOG) {
>  		int block_header_skip = 4 + header_off;
> @@ -213,15 +216,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
>  		    uncompress2(uncompressed + block_header_skip, &dst_len,
>  				block->data + block_header_skip, &src_len)) {
>  			reftable_free(uncompressed);

But it is already used here, because it is defined within the if()
block. You need to remove that definition, too, or your change here
does nothing.

...

>  		/* We're done with the input data. */
>  		reftable_block_done(block);
>  		block->data = uncompressed;
> +		uncompressed = NULL;

For example, this nullifies the local version of uncompressed.

(I had to double-check that this even compiled, which was
surprising to me.)

> +done:
> +	if (uncompressed) {
> +		reftable_free(uncompressed);
> +	}
> +	return err;

Remove that local declaration and this will be a good patch.

Thanks,
-Stolee
```

## Re: \[PATCH 08/10\] reftable: order unittests by complexity

- From: Derrick Stolee \<hidden\>
- Date: 2021-12-08 14:32:59 
- Message: https://ratatoskr.run/git/2021/12/7914301.md

```text
On 12/7/2021 12:45 PM, Han-Wen Nienhuys via GitGitGadget wrote:
> From: Han-Wen Nienhuys <redacted>
...
>  int cmd__reftable(int argc, const char **argv)
>  {
> +	// test from simple to complex.

Use /* ... */ style comments, even for a single line.

Thanks,
-Stolee
```

## Re: \[PATCH 10/10\] reftable: make reftable\_record a tagged union

- From: Derrick Stolee \<hidden\>
- Date: 2021-12-08 14:35:39 
- Message: https://ratatoskr.run/git/2021/12/7914302.md

```text
On 12/7/2021 12:45 PM, Han-Wen Nienhuys via GitGitGadget wrote:
> From: Han-Wen Nienhuys <redacted>
> 
> This reduces the amount of glue code, because we don't need a void pointer or
> vtable within the structure.
> 
> The only snag is that reftable_index_record contain a strbuf, so it cannot be
> zero-initialized. To address this, introduce reftable_record_for() to create a
> fresh instance, given a record type.
> 
> Thanks to Peff for the suggestion.
> 
> Signed-off-by: Han-Wen Nienhuys <redacted>
> ---
>  reftable/block.c       |   4 +-
>  reftable/block_test.c  |  22 +++---
>  reftable/generic.c     |  35 ++++----
>  reftable/iter.c        |   4 +-
>  reftable/merged.c      |  37 ++++-----
>  reftable/pq.c          |   3 +-
>  reftable/pq_test.c     |  31 ++++----
>  reftable/reader.c      | 105 ++++++++++++------------
>  reftable/record.c      | 176 ++++++++++++++++-------------------------
>  reftable/record.h      |  45 +++++------
>  reftable/record_test.c | 162 +++++++++++++++++++------------------
>  reftable/writer.c      |  46 ++++++-----

This is a HUGE diff, especially compared to the previous changes
in this series. I recommend splitting this out into its own series
and finding a way to break it down into smaller changes.

Thanks,
-Stolee
```

## Re: \[PATCH 10/10\] reftable: make reftable\_record a tagged union

- From: Han-Wen Nienhuys \<hidden\>
- Date: 2021-12-08 14:48:22 
- Message: https://ratatoskr.run/git/2021/12/7914303.md

```text
On Wed, Dec 8, 2021 at 3:35 PM Derrick Stolee [ref] wrote:
> This is a HUGE diff, especially compared to the previous changes
> in this series. I recommend splitting this out into its own series
> and finding a way to break it down into smaller changes.

Would you have a suggestion how? The reftable_record type is used
across the reftable library, so if we change its definition, that
impacts most callsites.

-- 
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
```

## Re: \[PATCH 10/10\] reftable: make reftable\_record a tagged union

- From: Derrick Stolee \<hidden\>
- Date: 2021-12-08 18:17:46 
- Message: https://ratatoskr.run/git/2021/12/7915129.md

```text
On 12/8/2021 9:48 AM, Han-Wen Nienhuys wrote:
> On Wed, Dec 8, 2021 at 3:35 PM Derrick Stolee [ref] wrote:
>> This is a HUGE diff, especially compared to the previous changes
>> in this series. I recommend splitting this out into its own series
>> and finding a way to break it down into smaller changes.
> 
> Would you have a suggestion how? The reftable_record type is used
> across the reftable library, so if we change its definition, that
> impacts most callsites.
 
Looking at the diff, I'm seeing a lot of things that don't seem
necessary to changing the struct, like renaming "reftable_new_record"
to "reftable_record_for". Yes, the implementation changes, but that
implementation is related to the struct changing and can be done in
a more isolated way.

Some things can be split out easier, such as the addition of
"uint8_t type" into the struct and replacing all reftable_record_type()
calls with <var>.type.

The big one is definitely the addition of a union, but it could be
done in a way that only changes the implementation of methods with
names such as reftable_record_from_obj() then we can replace those with
"<var>.obj" later.

Some of these changes are definitely going to have a lot of lines,
but when it's the same kind of change it is easy to verify. I'm
getting lost in this diff because it's doing too many things at once.

Thanks,
-Stolee
```

## \[PATCH v2 01/11\] reftable: fix OOB stack write in print functions

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:49:53 
- Message: https://ratatoskr.run/git/2021/12/7915159.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc6..8536bd03aa9 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -254,7 +254,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 void reftable_ref_record_print(struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
-	char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 	switch (ref->value_type) {
 	case REFTABLE_REF_SYMREF:
@@ -586,7 +586,7 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 void reftable_log_record_print(struct reftable_log_record *log,
 			       uint32_t hash_id)
 {
-	char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-- 
gitgitgadget

```

## \[PATCH v2 00/11\] Reftable coverity fixes

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:49:53 
- Message: https://ratatoskr.run/git/2021/12/7915161.md

```text
This series was targeted to 'next'.

This addresses some complaints from Coverity that Peff reported.

CI on GGG shows a segfault on windows
(https://github.com/git/git/runs/4446635428?check_suite_focus=true), which I
can't make sense of. Any hints would be greatly appreciated.

Han-Wen Nienhuys (11):
  reftable: fix OOB stack write in print functions
  reftable: fix resource leak in error path
  reftable: fix resource leak blocksource.c
  reftable: check reftable_stack_auto_compact() return value
  reftable: ignore remove() return value in stack_test.c
  reftable: fix resource warning
  reftable: fix NULL derefs in error paths
  reftable: order unittests by complexity
  reftable: drop stray printf in readwrite_test
  reftable: handle null refnames in reftable_ref_record_equal
  reftable: make reftable_record a tagged union

 reftable/block.c          |  31 +++++--
 reftable/block_test.c     |  22 ++---
 reftable/blocksource.c    |   6 +-
 reftable/generic.c        |  35 +++----
 reftable/iter.c           |   4 +-
 reftable/merged.c         |  37 ++++----
 reftable/pq.c             |   3 +-
 reftable/pq_test.c        |  31 ++++---
 reftable/reader.c         | 108 +++++++++++-----------
 reftable/readwrite_test.c |   1 -
 reftable/record.c         | 188 +++++++++++++++-----------------------
 reftable/record.h         |  45 ++++-----
 reftable/record_test.c    | 181 ++++++++++++++++++------------------
 reftable/stack.c          |  15 +--
 reftable/stack_test.c     |   3 +-
 reftable/writer.c         |  46 ++++++----
 t/helper/test-reftable.c  |   9 +-
 17 files changed, 376 insertions(+), 389 deletions(-)


base-commit: a8338297339b1ab064e799c15c4fc56c122ef967
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1152%2Fhanwen%2Freftable-coverity-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1152/hanwen/reftable-coverity-v2
Pull-Request: https://github.com/git/git/pull/1152

Range-diff vs v1:

  1:  7c033815183 =  1:  7c033815183 reftable: fix OOB stack write in print functions
  2:  603bd1d4f6e !  2:  1ddcfe61ebc reftable: fix resource leak in error path
     @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
       
       	if (typ == BLOCK_TYPE_LOG) {
       		int block_header_skip = 4 + header_off;
     +@@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable_block *block,
     + 		uLongf src_len = block->len - block_header_skip;
     + 		/* Log blocks specify the *uncompressed* size in their header.
     + 		 */
     +-		uint8_t *uncompressed = reftable_malloc(sz);
     ++		uncompressed = reftable_malloc(sz);
     + 
     + 		/* Copy over the block header verbatim. It's not compressed. */
     + 		memcpy(uncompressed, block->data, block_header_skip);
      @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable_block *block,
       		    uncompress2(uncompressed + block_header_skip, &dst_len,
       				block->data + block_header_skip, &src_len)) {
  3:  97279040dde =  3:  e052b2a61d6 reftable: fix resource leak blocksource.c
  4:  9288289ebcb =  4:  9063137457b reftable: check reftable_stack_auto_compact() return value
  5:  9269977a1c3 =  5:  5020be156ae reftable: ignore remove() return value in stack_test.c
  6:  eab71ab008e =  6:  64c18d01cad reftable: fix resource warning
  7:  47fef87eb4a =  7:  700387ac5d3 reftable: fix NULL derefs in error paths
  8:  458adf0fa9b =  8:  713f1d09f68 reftable: order unittests by complexity
  9:  cd5d8d3607d =  9:  cb601b51a47 reftable: drop stray printf in readwrite_test
  -:  ----------- > 10:  a0f83eff19f reftable: handle null refnames in reftable_ref_record_equal
 10:  8deccc3a1df ! 11:  a2743033cfd reftable: make reftable_record a tagged union
     @@ Commit message
      
          Thanks to Peff for the suggestion.
      
     +    Helped-by: Jeff King [ref]
          Signed-off-by: Han-Wen Nienhuys [ref]
      
       ## reftable/block.c ##
     @@ reftable/block_test.c: static void test_block_read_write(void)
      -		ref.refname = name;
      -		ref.value_type = REFTABLE_REF_VAL1;
      -		ref.value.val1 = hash;
     -+		rec.ref.refname = name;
     -+		rec.ref.value_type = REFTABLE_REF_VAL1;
     -+		rec.ref.value.val1 = hash;
     ++		rec.u.ref.refname = name;
     ++		rec.u.ref.value_type = REFTABLE_REF_VAL1;
     ++		rec.u.ref.value.val1 = hash;
       
       		names[i] = xstrdup(name);
       		n = block_writer_add(&bw, &rec);
      -		ref.refname = NULL;
      -		ref.value_type = REFTABLE_REF_DELETION;
     -+		rec.ref.refname = NULL;
     -+		rec.ref.value_type = REFTABLE_REF_DELETION;
     ++		rec.u.ref.refname = NULL;
     ++		rec.u.ref.value_type = REFTABLE_REF_DELETION;
       		EXPECT(n == 0);
       	}
       
     @@ reftable/block_test.c: static void test_block_read_write(void)
       			break;
       		}
      -		EXPECT_STREQ(names[j], ref.refname);
     -+		EXPECT_STREQ(names[j], rec.ref.refname);
     ++		EXPECT_STREQ(names[j], rec.u.ref.refname);
       		j++;
       	}
       
     @@ reftable/block_test.c: static void test_block_read_write(void)
       		EXPECT(n == 0);
       
      -		EXPECT_STREQ(names[i], ref.refname);
     -+		EXPECT_STREQ(names[i], rec.ref.refname);
     ++		EXPECT_STREQ(names[i], rec.u.ref.refname);
       
       		want.len--;
       		n = block_reader_seek(&br, &it, &want);
     @@ reftable/block_test.c: static void test_block_read_write(void)
       		n = block_iter_next(&it, &rec);
       		EXPECT(n == 0);
      -		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
     -+		EXPECT_STREQ(names[10 * (i / 10)], rec.ref.refname);
     ++		EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
       
       		block_iter_close(&it);
       	}
     @@ reftable/generic.c: https://developers.google.com/open-source/licenses/bsd
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_ref(&rec, &ref);
      +	struct reftable_record rec = { .type = BLOCK_TYPE_REF,
     -+				       .ref = {
     ++				       .u.ref = {
      +					       .refname = (char *)name,
      +				       } };
       	return tab->ops->seek_record(tab->table_arg, it, &rec);
     @@ reftable/generic.c: https://developers.google.com/open-source/licenses/bsd
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_log(&rec, &log);
      +	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
     -+				       .log = {
     ++				       .u.log = {
      +					       .refname = (char *)name,
      +					       .update_index = ~((uint64_t)0),
      +				       } };
     @@ reftable/generic.c: void reftable_iterator_destroy(struct reftable_iterator *it)
      -	return iterator_next(it, &rec);
      +	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
      +	int err = iterator_next(it, &rec);
     -+	*ref = rec.ref;
     ++	*ref = rec.u.ref;
      +	return err;
       }
       
     @@ reftable/generic.c: void reftable_iterator_destroy(struct reftable_iterator *it)
      -	return iterator_next(it, &rec);
      +	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
      +	int err = iterator_next(it, &rec);
     -+	*log = rec.log;
     ++	*log = rec.u.log;
      +	return err;
       }
       
     @@ reftable/iter.c: static int filtering_ref_iterator_next(void *iter_arg,
       {
       	struct filtering_ref_iterator *fri = iter_arg;
      -	struct reftable_ref_record *ref = rec->data;
     -+	struct reftable_ref_record *ref = &rec->ref;
     ++	struct reftable_ref_record *ref = &rec->u.ref;
       	int err = 0;
       	while (1) {
       		err = reftable_iterator_next_ref(&fri->it, ref);
     @@ reftable/iter.c: static int indexed_table_ref_iter_next_block(struct indexed_tab
       {
       	struct indexed_table_ref_iter *it = p;
      -	struct reftable_ref_record *ref = rec->data;
     -+	struct reftable_ref_record *ref = &rec->ref;
     ++	struct reftable_ref_record *ref = &rec->u.ref;
       
       	while (1) {
       		int err = block_iter_next(&it->cur, rec);
     @@ reftable/merged.c: int reftable_merged_table_seek_ref(struct reftable_merged_tab
      -		.refname = (char *)name,
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_REF,
     -+		.ref = {
     ++		.u.ref = {
      +			.refname = (char *)name,
      +		},
       	};
     @@ reftable/merged.c: int reftable_merged_table_seek_log_at(struct reftable_merged_
      -		.update_index = update_index,
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_LOG,
     -+		.log = {
     ++		.u.log = {
      +			.refname = (char *)name,
      +			.update_index = update_index,
      +		}
     @@ reftable/pq_test.c: static void test_pq(void)
      +		struct pq_entry e = {
      +			.rec = {
      +				.type = BLOCK_TYPE_REF,
     -+				.ref = {
     ++				.u.ref = {
      +					.refname = names[i],
      +				}
      +			}
     @@ reftable/pq_test.c: static void test_pq(void)
      +		EXPECT(rec->type == BLOCK_TYPE_REF);
       		if (last) {
      -			EXPECT(strcmp(last, ref->refname) < 0);
     -+			EXPECT(strcmp(last, rec->ref.refname) < 0);
     ++			EXPECT(strcmp(last, rec->u.ref.refname) < 0);
       		}
      -		last = ref->refname;
      -		ref->refname = NULL;
      -		reftable_free(ref);
      +		// this is names[i], so don't dealloc.
     -+		last = rec->ref.refname;
     -+		rec->ref.refname = NULL;
     ++		last = rec->u.ref.refname;
     ++		rec->u.ref.refname = NULL;
      +		reftable_record_release(rec);
       	}
      -
     @@ reftable/reader.c: static int table_iter_next_in_block(struct table_iter *ti,
      -		((struct reftable_ref_record *)rec->data)->update_index +=
      -			ti->r->min_update_index;
      +	if (res == 0 && rec->type == BLOCK_TYPE_REF) {
     -+		rec->ref.update_index += ti->r->min_update_index;
     ++		rec->u.ref.update_index += ti->r->min_update_index;
       	}
       
       	return res;
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
      -	struct reftable_record index_result_rec = { NULL };
      +	struct reftable_record want_index = {
      +		.type = BLOCK_TYPE_INDEX,
     -+		.idx = { .last_key = STRBUF_INIT }
     ++		.u.idx = { .last_key = STRBUF_INIT }
      +	};
      +	struct reftable_record index_result = {
      +		.type = BLOCK_TYPE_INDEX,
     -+		.idx = { .last_key = STRBUF_INIT },
     ++		.u.idx = { .last_key = STRBUF_INIT },
      +	};
       	struct table_iter index_iter = TABLE_ITER_INIT;
       	struct table_iter next = TABLE_ITER_INIT;
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
      -	reftable_record_from_index(&index_result_rec, &index_result);
      -
      -	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
     -+	reftable_record_key(rec, &want_index.idx.last_key);
     ++	reftable_record_key(rec, &want_index.u.idx.last_key);
      +	err = reader_start(r, &index_iter, rec->type, 1);
       	if (err < 0)
       		goto done;
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
       			goto done;
       
      -		err = reader_table_iter_at(r, &next, index_result.offset, 0);
     -+		err = reader_table_iter_at(r, &next, index_result.idx.offset,
     ++		err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
      +					   0);
       		if (err != 0)
       			goto done;
       
      -		err = block_iter_seek(&next.bi, &want_index.last_key);
     -+		err = block_iter_seek(&next.bi, &want_index.idx.last_key);
     ++		err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
       		if (err < 0)
       			goto done;
       
     @@ reftable/reader.c: static int reader_seek(struct reftable_reader *r, struct reft
      -		.refname = (char *)name,
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_REF,
     -+		.ref = {
     ++		.u.ref = {
      +			.refname = (char *)name,
      +		},
       	};
     @@ reftable/reader.c: int reftable_reader_seek_log_at(struct reftable_reader *r,
      -		.update_index = update_index,
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_LOG,
     -+		.log = {
     ++		.u.log = {
      +			.refname = (char *)name,
      +			.update_index = update_index,
      +		}
     @@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_r
      -		.hash_prefix_len = r->object_id_len,
      +	struct reftable_record want = {
      +		.type = BLOCK_TYPE_OBJ,
     -+		.obj = {
     ++		.u.obj = {
      +			.hash_prefix = oid,
      +			.hash_prefix_len = r->object_id_len,
      +		},
     @@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_r
      -	struct reftable_record got_rec = { NULL };
      +	struct reftable_record got = {
      +		.type = BLOCK_TYPE_OBJ,
     -+		.obj = { 0 },
     ++		.u.obj = { 0 },
      +	};
       	int err = 0;
       	struct indexed_table_ref_iter *itr = NULL;
     @@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_r
       
      -	if (err > 0 ||
      -	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
     -+	if (err > 0 || memcmp(want.obj.hash_prefix, got.obj.hash_prefix,
     ++	if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
      +			      r->object_id_len)) {
       		/* didn't find it; return empty iterator */
       		iterator_set_empty(it);
     @@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_r
       
       	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
      -					 got.offsets, got.offset_len);
     -+					 got.obj.offsets, got.obj.offset_len);
     ++					 got.u.obj.offsets, got.u.obj.offset_len);
       	if (err < 0)
       		goto done;
      -	got.offsets = NULL;
     -+	got.obj.offsets = NULL;
     ++	got.u.obj.offsets = NULL;
       	iterator_from_indexed_table_ref_iter(it, itr);
       
       done:
     @@ reftable/record.c: void string_view_consume(struct string_view *s, int n)
      +{
      +	switch (rec->type) {
      +	case BLOCK_TYPE_REF:
     -+		return &rec->ref;
     ++		return &rec->u.ref;
      +	case BLOCK_TYPE_LOG:
     -+		return &rec->log;
     ++		return &rec->u.log;
      +	case BLOCK_TYPE_INDEX:
     -+		return &rec->idx;
     ++		return &rec->u.idx;
      +	case BLOCK_TYPE_OBJ:
     -+		return &rec->obj;
     ++		return &rec->u.obj;
      +	}
      +	abort();
      +}
     @@ reftable/record.c: void string_view_consume(struct string_view *s, int n)
      +{
      +	struct reftable_record clean_idx = {
      +		.type = BLOCK_TYPE_INDEX,
     -+		.idx = {
     ++		.u.idx = {
      +			.last_key = STRBUF_INIT,
      +		},
      +	};
     @@ reftable/record.h: struct reftable_obj_record {
      +		struct reftable_log_record log;
      +		struct reftable_obj_record obj;
      +		struct reftable_index_record idx;
     -+	};
     ++	} u;
      +};
      +
       /* see struct record_vtable */
     @@ reftable/record_test.c
       	case BLOCK_TYPE_REF:
      -		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
      -						 reftable_record_as_ref(rec),
     -+		EXPECT(reftable_ref_record_equal(&copy.ref, &rec->ref,
     ++		EXPECT(reftable_ref_record_equal(&copy.u.ref, &rec->u.ref,
       						 GIT_SHA1_RAWSZ));
       		break;
       	case BLOCK_TYPE_LOG:
      -		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
      -						 reftable_record_as_log(rec),
     -+		EXPECT(reftable_log_record_equal(&copy.log, &rec->log,
     ++		EXPECT(reftable_log_record_equal(&copy.u.log, &rec->u.log,
       						 GIT_SHA1_RAWSZ));
       		break;
       	}
     @@ reftable/record_test.c: static void test_reftable_ref_record_roundtrip(void)
       		int n, m;
       
      -		in.value_type = i;
     -+		in.ref.value_type = i;
     ++		in.u.ref.value_type = i;
       		switch (i) {
       		case REFTABLE_REF_DELETION:
       			break;
       		case REFTABLE_REF_VAL1:
      -			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
      -			set_hash(in.value.val1, 1);
     -+			in.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
     -+			set_hash(in.ref.value.val1, 1);
     ++			in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
     ++			set_hash(in.u.ref.value.val1, 1);
       			break;
       		case REFTABLE_REF_VAL2:
      -			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
      -			set_hash(in.value.val2.value, 1);
      -			in.value.val2.target_value =
     -+			in.ref.value.val2.value =
     ++			in.u.ref.value.val2.value =
      +				reftable_malloc(GIT_SHA1_RAWSZ);
     -+			set_hash(in.ref.value.val2.value, 1);
     -+			in.ref.value.val2.target_value =
     ++			set_hash(in.u.ref.value.val2.value, 1);
     ++			in.u.ref.value.val2.target_value =
       				reftable_malloc(GIT_SHA1_RAWSZ);
      -			set_hash(in.value.val2.target_value, 2);
     -+			set_hash(in.ref.value.val2.target_value, 2);
     ++			set_hash(in.u.ref.value.val2.target_value, 2);
       			break;
       		case REFTABLE_REF_SYMREF:
      -			in.value.symref = xstrdup("target");
     -+			in.ref.value.symref = xstrdup("target");
     ++			in.u.ref.value.symref = xstrdup("target");
       			break;
       		}
      -		in.refname = xstrdup("refs/heads/master");
     -+		in.ref.refname = xstrdup("refs/heads/master");
     ++		in.u.ref.refname = xstrdup("refs/heads/master");
       
      -		reftable_record_from_ref(&rec, &in);
      -		test_copy(&rec);
     @@ reftable/record_test.c: static void test_reftable_ref_record_roundtrip(void)
       
      -		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
      -		reftable_record_release(&rec_out);
     -+		EXPECT(reftable_ref_record_equal(&in.ref, &out.ref,
     ++		EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
      +						 GIT_SHA1_RAWSZ));
      +		reftable_record_release(&in);
       
     @@ reftable/record_test.c: static void test_reftable_log_record_roundtrip(void)
       		/* populate out, to check for leaks. */
      -		struct reftable_log_record out = {
      -			.refname = xstrdup("old name"),
     +-			.value_type = REFTABLE_LOG_UPDATE,
     +-			.value = {
     +-				.update = {
     +-					.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
     +-					.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
     +-					.name = xstrdup("old name"),
     +-					.email = xstrdup("old@email"),
     +-					.message = xstrdup("old message"),
      +		struct reftable_record out = {
      +			.type = BLOCK_TYPE_LOG,
     -+			.log = {
     ++			.u.log = {
      +				.refname = xstrdup("old name"),
     - 			.value_type = REFTABLE_LOG_UPDATE,
     - 			.value = {
     - 				.update = {
     -@@ reftable/record_test.c: static void test_reftable_log_record_roundtrip(void)
     - 					.message = xstrdup("old message"),
     ++				.value_type = REFTABLE_LOG_UPDATE,
     ++				.value = {
     ++					.update = {
     ++						.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
     ++						.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
     ++						.name = xstrdup("old name"),
     ++						.email = xstrdup("old@email"),
     ++						.message = xstrdup("old message"),
     ++					},
       				},
       			},
     -+			},
       		};
      -		struct reftable_record rec_out = { NULL };
       		int n, m, valtype;
       
      -		reftable_record_from_log(&rec, &in[i]);
     -+		rec.log = in[i];
     ++		rec.u.log = in[i];
       
       		test_copy(&rec);
       
     @@ reftable/record_test.c: static void test_reftable_log_record_roundtrip(void)
       		EXPECT(n == m);
       
      -		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
     -+		EXPECT(reftable_log_record_equal(&in[i], &out.log,
     ++		EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
      +						 GIT_SHA1_RAWSZ));
       		reftable_log_record_release(&in[i]);
       		strbuf_release(&key);
     @@ reftable/record_test.c: static void test_reftable_obj_record_roundtrip(void)
      -		struct reftable_record rec = { NULL };
      +		struct reftable_record in = {
      +			.type = BLOCK_TYPE_OBJ,
     -+			.obj = recs[i],
     ++			.u.obj = recs[i],
      +		};
       		struct strbuf key = STRBUF_INIT;
      -		struct reftable_obj_record out = { NULL };
     @@ reftable/record_test.c: static void test_reftable_obj_record_roundtrip(void)
       
      -		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
      -		EXPECT(in.offset_len == out.offset_len);
     -+		EXPECT(in.obj.hash_prefix_len == out.obj.hash_prefix_len);
     -+		EXPECT(in.obj.offset_len == out.obj.offset_len);
     - 
     +-
      -		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
      -			       in.hash_prefix_len));
      -		EXPECT(0 == memcmp(in.offsets, out.offsets,
      -				   sizeof(uint64_t) * in.offset_len));
     -+		EXPECT(!memcmp(in.obj.hash_prefix, out.obj.hash_prefix,
     -+			       in.obj.hash_prefix_len));
     -+		EXPECT(0 == memcmp(in.obj.offsets, out.obj.offsets,
     -+				   sizeof(uint64_t) * in.obj.offset_len));
     ++		EXPECT(in.u.obj.hash_prefix_len == out.u.obj.hash_prefix_len);
     ++		EXPECT(in.u.obj.offset_len == out.u.obj.offset_len);
     ++		if (in.u.obj.hash_prefix_len)
     ++			EXPECT(!memcmp(in.u.obj.hash_prefix, out.u.obj.hash_prefix,
     ++				       in.u.obj.hash_prefix_len));
     ++		if (in.u.obj.offset_len)
     ++			EXPECT(!memcmp(in.u.obj.offsets, out.u.obj.offsets,
     ++					   sizeof(uint64_t) * in.u.obj.offset_len));
       		strbuf_release(&key);
      -		reftable_record_release(&rec_out);
      +		reftable_record_release(&out);
     @@ reftable/record_test.c: static void test_reftable_obj_record_roundtrip(void)
      -		.last_key = STRBUF_INIT,
      +	struct reftable_record in = {
      +		.type = BLOCK_TYPE_INDEX,
     -+		.idx = {
     ++		.u.idx = {
      +			.offset = 42,
      +			.last_key = STRBUF_INIT,
      +		},
     @@ reftable/record_test.c: static void test_reftable_index_record_roundtrip(void)
      -	struct reftable_record out_rec = { NULL };
      +	struct reftable_record out = {
      +		.type = BLOCK_TYPE_INDEX,
     -+		.idx = { .last_key = STRBUF_INIT },
     ++		.u.idx = { .last_key = STRBUF_INIT },
      +	};
       	int n, m;
       	uint8_t extra;
     @@ reftable/record_test.c: static void test_reftable_index_record_roundtrip(void)
      -	reftable_record_from_index(&rec, &in);
      -	reftable_record_key(&rec, &key);
      -	test_copy(&rec);
     -+	strbuf_addstr(&in.idx.last_key, "refs/heads/master");
     ++	strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
      +	reftable_record_key(&in, &key);
      +	test_copy(&in);
       
      -	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
      -	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
     -+	EXPECT(0 == strbuf_cmp(&key, &in.idx.last_key));
     ++	EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
      +	n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
       	EXPECT(n > 0);
       
     @@ reftable/record_test.c: static void test_reftable_index_record_roundtrip(void)
       	EXPECT(m == n);
       
      -	EXPECT(in.offset == out.offset);
     -+	EXPECT(in.idx.offset == out.idx.offset);
     ++	EXPECT(in.u.idx.offset == out.u.idx.offset);
       
      -	reftable_record_release(&out_rec);
      +	reftable_record_release(&out);
       	strbuf_release(&key);
      -	strbuf_release(&in.last_key);
     -+	strbuf_release(&in.idx.last_key);
     ++	strbuf_release(&in.u.idx.last_key);
       }
       
       int record_test_main(int argc, const char *argv[])
     @@ reftable/writer.c: done:
      -	struct reftable_ref_record copy = *ref;
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_REF,
     -+		.ref = *ref,
     ++		.u.ref = *ref,
      +	};
       	int err = 0;
       
     @@ reftable/writer.c: int reftable_writer_add_ref(struct reftable_writer *w,
       
      -	reftable_record_from_ref(&rec, &copy);
      -	copy.update_index -= w->min_update_index;
     -+	rec.ref.update_index -= w->min_update_index;
     ++	rec.u.ref.update_index -= w->min_update_index;
       
       	err = writer_add_record(w, &rec);
       	if (err < 0)
     @@ reftable/writer.c: int reftable_writer_add_refs(struct reftable_writer *w,
      -	struct reftable_record rec = { NULL };
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_LOG,
     -+		.log = *log,
     ++		.u.log = *log,
      +	};
       	if (w->block_writer &&
       	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
     @@ reftable/writer.c: static int writer_finish_section(struct reftable_writer *w)
      -			reftable_record_from_index(&rec, idx + i);
      +			struct reftable_record rec = {
      +				.type = BLOCK_TYPE_INDEX,
     -+				.idx = idx[i],
     ++				.u.idx = idx[i],
      +			};
       			if (block_writer_add(w->block_writer, &rec) == 0) {
       				continue;
     @@ reftable/writer.c: static void write_object_record(void *void_arg, void *key)
      -		.offset_len = entry->offset_len,
      +	struct reftable_record rec = {
      +		.type = BLOCK_TYPE_OBJ,
     -+		.obj = {
     ++		.u.obj = {
      +			.hash_prefix = (uint8_t *)entry->hash.buf,
      +			.hash_prefix_len = arg->w->stats.object_id_len,
      +			.offsets = entry->offsets,
     @@ reftable/writer.c: static void write_object_record(void *void_arg, void *key)
       		goto done;
      -	obj_rec.offset_len = 0;
      +
     -+	rec.obj.offset_len = 0;
     ++	rec.u.obj.offset_len = 0;
       	arg->err = block_writer_add(arg->w->block_writer, &rec);
       
       	/* Should be able to write into a fresh block. */

-- 
gitgitgadget
```

## \[PATCH v2 02/11\] reftable: fix resource leak in error path

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:49:55 
- Message: https://ratatoskr.run/git/2021/12/7915160.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered by corrupt files, so it doesn't have test coverage. This
was discovered by a Coverity scan.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c947..79b6f3aac72 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	uint32_t full_block_size = table_block_size;
 	uint8_t typ = block->data[header_off];
 	uint32_t sz = get_be24(block->data + header_off + 1);
-
+	int err = 0;
 	uint16_t restart_count = 0;
 	uint32_t restart_start = 0;
 	uint8_t *restart_bytes = NULL;
+	uint8_t *uncompressed = NULL;
 
-	if (!reftable_is_block_type(typ))
-		return REFTABLE_FORMAT_ERROR;
+	if (!reftable_is_block_type(typ)) {
+		err =  REFTABLE_FORMAT_ERROR;
+		goto done;
+	}
 
 	if (typ == BLOCK_TYPE_LOG) {
 		int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		uLongf src_len = block->len - block_header_skip;
 		/* Log blocks specify the *uncompressed* size in their header.
 		 */
-		uint8_t *uncompressed = reftable_malloc(sz);
+		uncompressed = reftable_malloc(sz);
 
 		/* Copy over the block header verbatim. It's not compressed. */
 		memcpy(uncompressed, block->data, block_header_skip);
@@ -213,15 +216,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
 			reftable_free(uncompressed);
-			return REFTABLE_ZLIB_ERROR;
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz)
-			return REFTABLE_FORMAT_ERROR;
+		if (dst_len + block_header_skip != sz) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		}
 
 		/* We're done with the input data. */
 		reftable_block_done(block);
 		block->data = uncompressed;
+		uncompressed = NULL;
 		block->len = sz;
 		block->source = malloc_block_source();
 		full_block_size = src_len + block_header_skip;
@@ -251,7 +258,11 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	br->restart_count = restart_count;
 	br->restart_bytes = restart_bytes;
 
-	return 0;
+done:
+	if (uncompressed) {
+		reftable_free(uncompressed);
+	}
+	return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
-- 
gitgitgadget

```

## \[PATCH v2 03/11\] reftable: fix resource leak blocksource.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:49:56 
- Message: https://ratatoskr.run/git/2021/12/7915162.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered in the unlikely event of fstat() failing on an opened
file.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/blocksource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9aa..2605371c28d 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
 	}
 
 	err = fstat(fd, &st);
-	if (err < 0)
-		return -1;
+	if (err < 0) {
+		close(fd);
+		return REFTABLE_IO_ERROR;
+	}
 
 	p = reftable_calloc(sizeof(struct file_block_source));
 	p->size = st.st_size;
-- 
gitgitgadget

```

## \[PATCH v2 04/11\] reftable: check reftable\_stack\_auto\_compact() return value

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:49:59 
- Message: https://ratatoskr.run/git/2021/12/7915163.md

```text
From: Han-Wen Nienhuys <redacted>

Fixes a problem detected by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index eb0b7228b0c..d628420e63a 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -814,6 +814,7 @@ static void test_reftable_stack_auto_compaction(void)
 		EXPECT_ERR(err);
 
 		err = reftable_stack_auto_compact(st);
+		EXPECT_ERR(err);
 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
 	}
 
-- 
gitgitgadget

```

## \[PATCH v2 05/11\] reftable: ignore remove() return value in stack\_test.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:04 
- Message: https://ratatoskr.run/git/2021/12/7915164.md

```text
From: Han-Wen Nienhuys <redacted>

If the cleanup fails, there is nothing we can do.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index d628420e63a..4b7292945c3 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -89,7 +89,7 @@ static void test_read_file(void)
 		EXPECT(0 == strcmp(want[i], names[i]));
 	}
 	free_names(names);
-	remove(fn);
+	(void) remove(fn);
 }
 
 static void test_parse_names(void)
-- 
gitgitgadget

```

## \[PATCH v2 06/11\] reftable: fix resource warning

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:05 
- Message: https://ratatoskr.run/git/2021/12/7915165.md

```text
From: Han-Wen Nienhuys <redacted>

This would trigger in the unlikely event that we are compacting, and the next
available file handle is 0.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/reftable/stack.c b/reftable/stack.c
index df5021ebf08..10dfd370e8e 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -877,7 +877,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	struct strbuf new_table_path = STRBUF_INIT;
 	int err = 0;
 	int have_lock = 0;
-	int lock_file_fd = 0;
+	int lock_file_fd = -1;
 	int compact_count = last - first + 1;
 	char **listp = NULL;
 	char **delete_on_success =
@@ -911,7 +911,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	}
 	/* Don't want to write to the lock for now.  */
 	close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 
 	have_lock = 1;
 	err = stack_uptodate(st);
@@ -932,7 +932,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 
 		sublock_file_fd = open(subtab_lock.buf,
 				       O_EXCL | O_CREAT | O_WRONLY, 0644);
-		if (sublock_file_fd > 0) {
+		if (sublock_file_fd >= 0) {
 			close(sublock_file_fd);
 		} else if (sublock_file_fd < 0) {
 			if (errno == EEXIST) {
@@ -1013,7 +1013,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 		goto done;
 	}
 	err = close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 	if (err < 0) {
 		err = REFTABLE_IO_ERROR;
 		unlink(new_table_path.buf);
@@ -1050,9 +1050,9 @@ done:
 		listp++;
 	}
 	free_names(subtable_locks);
-	if (lock_file_fd > 0) {
+	if (lock_file_fd >= 0) {
 		close(lock_file_fd);
-		lock_file_fd = 0;
+		lock_file_fd = -1;
 	}
 	if (have_lock) {
 		unlink(lock_file_name.buf);
-- 
gitgitgadget

```

## \[PATCH v2 07/11\] reftable: fix NULL derefs in error paths

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:06 
- Message: https://ratatoskr.run/git/2021/12/7915166.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reader.c | 3 ++-
 reftable/stack.c  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..8d308d858f8 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -796,6 +796,7 @@ int reftable_reader_print_file(const char *tablename)
 	reftable_table_from_reader(&tab, r);
 	err = reftable_table_print(&tab);
 done:
-	reftable_reader_free(r);
+	if (r)
+		reftable_reader_free(r);
 	return err;
 }
diff --git a/reftable/stack.c b/reftable/stack.c
index 10dfd370e8e..eb03b6c74f6 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -707,7 +707,8 @@ done:
 	strbuf_release(&temp_tab_file_name);
 	strbuf_release(&tab_file_name);
 	strbuf_release(&next_name);
-	reftable_writer_free(wr);
+	if (wr)
+		reftable_writer_free(wr);
 	return err;
 }
 
-- 
gitgitgadget

```

## \[PATCH v2 08/11\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:09 
- Message: https://ratatoskr.run/git/2021/12/7915167.md

```text
From: Han-Wen Nienhuys <redacted>

This is a more pratical ordering when working on refactorings of the reftable
code.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 t/helper/test-reftable.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b789..f08d66df949 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	// test from simple to complex.
 	basics_test_main(argc, argv);
+	record_test_main(argc, argv);
 	block_test_main(argc, argv);
-	merged_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	pq_test_main(argc, argv);
-	record_test_main(argc, argv);
-	refname_test_main(argc, argv);
 	readwrite_test_main(argc, argv);
+	merged_test_main(argc, argv);
 	stack_test_main(argc, argv);
-	tree_test_main(argc, argv);
+	refname_test_main(argc, argv);
 	return 0;
 }
 
-- 
gitgitgadget

```

## \[PATCH v2 09/11\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:13 
- Message: https://ratatoskr.run/git/2021/12/7915168.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/readwrite_test.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 5f6bcc2f775..7c40b9b77f3 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -597,7 +597,6 @@ static void test_write_key_order(void)
 	err = reftable_writer_add_ref(w, &refs[0]);
 	EXPECT_ERR(err);
 	err = reftable_writer_add_ref(w, &refs[1]);
-	printf("%d\n", err);
 	EXPECT(err == REFTABLE_API_ERROR);
 	reftable_writer_close(w);
 	reftable_writer_free(w);
-- 
gitgitgadget

```

## \[PATCH v2 10/11\] reftable: handle null refnames in reftable\_ref\_record\_equal

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:14 
- Message: https://ratatoskr.run/git/2021/12/7915169.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 8536bd03aa9..8bbcbff1e69 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -1154,9 +1154,11 @@ int reftable_ref_record_equal(struct reftable_ref_record *a,
 			      struct reftable_ref_record *b, int hash_size)
 {
 	assert(hash_size > 0);
-	if (!(0 == strcmp(a->refname, b->refname) &&
-	      a->update_index == b->update_index &&
-	      a->value_type == b->value_type))
+	if (!null_streq(a->refname, b->refname))
+		return 0;
+
+	if (a->update_index != b->update_index ||
+	    a->value_type != b->value_type)
 		return 0;
 
 	switch (a->value_type) {
-- 
gitgitgadget

```

## \[PATCH v2 11/11\] reftable: make reftable\_record a tagged union

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-08 21:50:15 
- Message: https://ratatoskr.run/git/2021/12/7915170.md

```text
From: Han-Wen Nienhuys <redacted>

This reduces the amount of glue code, because we don't need a void pointer or
vtable within the structure.

The only snag is that reftable_index_record contain a strbuf, so it cannot be
zero-initialized. To address this, introduce reftable_record_for() to create a
fresh instance, given a record type.

Thanks to Peff for the suggestion.

Helped-by: Jeff King [ref]
Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c       |   4 +-
 reftable/block_test.c  |  22 ++---
 reftable/generic.c     |  35 ++++----
 reftable/iter.c        |   4 +-
 reftable/merged.c      |  37 +++++----
 reftable/pq.c          |   3 +-
 reftable/pq_test.c     |  31 +++----
 reftable/reader.c      | 105 ++++++++++++------------
 reftable/record.c      | 176 +++++++++++++++------------------------
 reftable/record.h      |  45 +++++-----
 reftable/record_test.c | 181 +++++++++++++++++++++--------------------
 reftable/writer.c      |  46 ++++++-----
 12 files changed, 329 insertions(+), 360 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 79b6f3aac72..0bc4a5d84b3 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -382,7 +382,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 		.key = *want,
 		.r = br,
 	};
-	struct reftable_record rec = reftable_new_record(block_reader_type(br));
+	struct reftable_record rec = reftable_record_for(block_reader_type(br));
 	struct strbuf key = STRBUF_INIT;
 	int err = 0;
 	struct block_iter next = {
@@ -424,7 +424,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 done:
 	strbuf_release(&key);
 	strbuf_release(&next.last_key);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 
 	return err;
 }
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 4b3ea262dcb..fa2ee092ec0 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
 	struct block_writer bw = {
 		.last_key = STRBUF_INIT,
 	};
-	struct reftable_ref_record ref = { NULL };
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+	};
 	int i = 0;
 	int n;
 	struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
 	block.source = malloc_block_source();
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
-	reftable_record_from_ref(&rec, &ref);
 
 	for (i = 0; i < N; i++) {
 		char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
 		snprintf(name, sizeof(name), "branch%02d", i);
 		memset(hash, i, sizeof(hash));
 
-		ref.refname = name;
-		ref.value_type = REFTABLE_REF_VAL1;
-		ref.value.val1 = hash;
+		rec.u.ref.refname = name;
+		rec.u.ref.value_type = REFTABLE_REF_VAL1;
+		rec.u.ref.value.val1 = hash;
 
 		names[i] = xstrdup(name);
 		n = block_writer_add(&bw, &rec);
-		ref.refname = NULL;
-		ref.value_type = REFTABLE_REF_DELETION;
+		rec.u.ref.refname = NULL;
+		rec.u.ref.value_type = REFTABLE_REF_DELETION;
 		EXPECT(n == 0);
 	}
 
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
 		if (r > 0) {
 			break;
 		}
-		EXPECT_STREQ(names[j], ref.refname);
+		EXPECT_STREQ(names[j], rec.u.ref.refname);
 		j++;
 	}
 
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
 
-		EXPECT_STREQ(names[i], ref.refname);
+		EXPECT_STREQ(names[i], rec.u.ref.refname);
 
 		want.len--;
 		n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
 
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
-		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+		EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
 
 		block_iter_close(&it);
 	}
diff --git a/reftable/generic.c b/reftable/generic.c
index 7a8a738d860..4446b8ed360 100644
--- a/reftable/generic.c
+++ b/reftable/generic.c
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
 */
 
 #include "basics.h"
+#include "constants.h"
 #include "record.h"
 #include "generic.h"
 #include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
 int reftable_table_seek_ref(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+				       .u.ref = {
+					       .refname = (char *)name,
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
 int reftable_table_seek_log(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = ~((uint64_t)0),
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+				       .u.log = {
+					       .refname = (char *)name,
+					       .update_index = ~((uint64_t)0),
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
@@ -129,17 +128,19 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
 			       struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, ref);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
+	int err = iterator_next(it, &rec);
+	*ref = rec.u.ref;
+	return err;
 }
 
 int reftable_iterator_next_log(struct reftable_iterator *it,
 			       struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, log);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
+	int err = iterator_next(it, &rec);
+	*log = rec.u.log;
+	return err;
 }
 
 int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
diff --git a/reftable/iter.c b/reftable/iter.c
index 93d04f735b8..a8d174c0406 100644
--- a/reftable/iter.c
+++ b/reftable/iter.c
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
 				       struct reftable_record *rec)
 {
 	struct filtering_ref_iterator *fri = iter_arg;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 	int err = 0;
 	while (1) {
 		err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
 static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
 {
 	struct indexed_table_ref_iter *it = p;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 
 	while (1) {
 		int err = block_iter_next(&it->cur, rec);
diff --git a/reftable/merged.c b/reftable/merged.c
index e5b53da6db3..e9b0edec97d 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -22,7 +22,7 @@ static int merged_iter_init(struct merged_iter *mi)
 {
 	int i = 0;
 	for (i = 0; i < mi->stack_len; i++) {
-		struct reftable_record rec = reftable_new_record(mi->typ);
+		struct reftable_record rec = reftable_record_for(mi->typ);
 		int err = iterator_next(&mi->stack[i], &rec);
 		if (err < 0) {
 			return err;
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
 
 		if (err > 0) {
 			reftable_iterator_destroy(&mi->stack[i]);
-			reftable_record_destroy(&rec);
+			reftable_record_release(&rec);
 		} else {
 			struct pq_entry e = {
 				.rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
 static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
 					       size_t idx)
 {
-	struct reftable_record rec = reftable_new_record(mi->typ);
 	struct pq_entry e = {
-		.rec = rec,
+		.rec = reftable_record_for(mi->typ),
 		.index = idx,
 	};
-	int err = iterator_next(&mi->stack[idx], &rec);
+	int err = iterator_next(&mi->stack[idx], &e.rec);
 	if (err < 0)
 		return err;
 
 	if (err > 0) {
 		reftable_iterator_destroy(&mi->stack[idx]);
-		reftable_record_destroy(&rec);
+		reftable_record_release(&e.rec);
 		return 0;
 	}
 
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
 		if (err < 0) {
 			return err;
 		}
-		reftable_record_destroy(&top.rec);
+		reftable_record_release(&top.rec);
 	}
 
 	reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
-	reftable_record_destroy(&entry.rec);
+	reftable_record_release(&entry.rec);
 	strbuf_release(&entry_key);
 	return 0;
 }
@@ -246,7 +245,7 @@ static int merged_table_seek_record(struct reftable_merged_table *mt,
 		sizeof(struct reftable_iterator) * mt->stack_len);
 	struct merged_iter merged = {
 		.stack = iters,
-		.typ = reftable_record_type(rec),
+		.typ = rec->type,
 		.hash_id = mt->hash_id,
 		.suppress_deletions = mt->suppress_deletions,
 	};
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
 				   struct reftable_iterator *it,
 				   const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
@@ -302,12 +302,13 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
 				      struct reftable_iterator *it,
 				      const char *name, uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
diff --git a/reftable/pq.c b/reftable/pq.c
index efc474017a2..96ca6dd37b3 100644
--- a/reftable/pq.c
+++ b/reftable/pq.c
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
 {
 	int i = 0;
+
 	if (pq->len == pq->cap) {
 		pq->cap = 2 * pq->cap + 1;
 		pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
 	int i = 0;
 	for (i = 0; i < pq->len; i++) {
-		reftable_record_destroy(&pq->heap[i].rec);
+		reftable_record_release(&pq->heap[i].rec);
 	}
 	FREE_AND_NULL(pq->heap);
 	pq->len = pq->cap = 0;
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index c9bb05e37b7..9f04d84fed6 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -31,7 +31,7 @@ static void test_pq(void)
 	int N = ARRAY_SIZE(names) - 1;
 
 	struct merged_iter_pqueue pq = { NULL };
-	const char *last = NULL;
+	char *last = NULL;
 
 	int i = 0;
 	for (i = 0; i < N; i++) {
@@ -42,12 +42,14 @@ static void test_pq(void)
 
 	i = 1;
 	do {
-		struct reftable_record rec =
-			reftable_new_record(BLOCK_TYPE_REF);
-		struct pq_entry e = { 0 };
-
-		reftable_record_as_ref(&rec)->refname = names[i];
-		e.rec = rec;
+		struct pq_entry e = {
+			.rec = {
+				.type = BLOCK_TYPE_REF,
+				.u.ref = {
+					.refname = names[i],
+				}
+			}
+		};
 		merged_iter_pqueue_add(&pq, e);
 		merged_iter_pqueue_check(pq);
 		i = (i * 7) % N;
@@ -55,19 +57,18 @@ static void test_pq(void)
 
 	while (!merged_iter_pqueue_is_empty(pq)) {
 		struct pq_entry e = merged_iter_pqueue_remove(&pq);
-		struct reftable_ref_record *ref =
-			reftable_record_as_ref(&e.rec);
-
+		struct reftable_record *rec = &e.rec;
 		merged_iter_pqueue_check(pq);
 
+		EXPECT(rec->type == BLOCK_TYPE_REF);
 		if (last) {
-			EXPECT(strcmp(last, ref->refname) < 0);
+			EXPECT(strcmp(last, rec->u.ref.refname) < 0);
 		}
-		last = ref->refname;
-		ref->refname = NULL;
-		reftable_free(ref);
+		// this is names[i], so don't dealloc.
+		last = rec->u.ref.refname;
+		rec->u.ref.refname = NULL;
+		reftable_record_release(rec);
 	}
-
 	for (i = 0; i < N; i++) {
 		reftable_free(names[i]);
 	}
diff --git a/reftable/reader.c b/reftable/reader.c
index 8d308d858f8..ef322791fda 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -238,9 +238,8 @@ static int table_iter_next_in_block(struct table_iter *ti,
 				    struct reftable_record *rec)
 {
 	int res = block_iter_next(&ti->bi, rec);
-	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
-		((struct reftable_ref_record *)rec->data)->update_index +=
-			ti->r->min_update_index;
+	if (res == 0 && rec->type == BLOCK_TYPE_REF) {
+		rec->u.ref.update_index += ti->r->min_update_index;
 	}
 
 	return res;
@@ -345,7 +344,7 @@ static int table_iter_next_block(struct table_iter *dest,
 
 static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)
 {
-	if (reftable_record_type(rec) != ti->typ)
+	if (rec->type != ti->typ)
 		return REFTABLE_API_ERROR;
 
 	while (1) {
@@ -437,8 +436,7 @@ static int reader_start(struct reftable_reader *r, struct table_iter *ti,
 static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 			      struct reftable_record *want)
 {
-	struct reftable_record rec =
-		reftable_new_record(reftable_record_type(want));
+	struct reftable_record rec = reftable_record_for(want->type);
 	struct strbuf want_key = STRBUF_INIT;
 	struct strbuf got_key = STRBUF_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
@@ -475,7 +473,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 
 done:
 	block_iter_close(&next.bi);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 	strbuf_release(&want_key);
 	strbuf_release(&got_key);
 	return err;
@@ -485,38 +483,40 @@ static int reader_seek_indexed(struct reftable_reader *r,
 			       struct reftable_iterator *it,
 			       struct reftable_record *rec)
 {
-	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-	struct reftable_record want_index_rec = { NULL };
-	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-	struct reftable_record index_result_rec = { NULL };
+	struct reftable_record want_index = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT }
+	};
+	struct reftable_record index_result = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	struct table_iter index_iter = TABLE_ITER_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
 	int err = 0;
 
-	reftable_record_key(rec, &want_index.last_key);
-	reftable_record_from_index(&want_index_rec, &want_index);
-	reftable_record_from_index(&index_result_rec, &index_result);
-
-	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
+	reftable_record_key(rec, &want_index.u.idx.last_key);
+	err = reader_start(r, &index_iter, rec->type, 1);
 	if (err < 0)
 		goto done;
 
-	err = reader_seek_linear(r, &index_iter, &want_index_rec);
+	err = reader_seek_linear(r, &index_iter, &want_index);
 	while (1) {
-		err = table_iter_next(&index_iter, &index_result_rec);
+		err = table_iter_next(&index_iter, &index_result);
 		table_iter_block_done(&index_iter);
 		if (err != 0)
 			goto done;
 
-		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+					   0);
 		if (err != 0)
 			goto done;
 
-		err = block_iter_seek(&next.bi, &want_index.last_key);
+		err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
 		if (err < 0)
 			goto done;
 
-		if (next.typ == reftable_record_type(rec)) {
+		if (next.typ == rec->type) {
 			err = 0;
 			break;
 		}
@@ -540,8 +540,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
 done:
 	block_iter_close(&next.bi);
 	table_iter_close(&index_iter);
-	reftable_record_release(&want_index_rec);
-	reftable_record_release(&index_result_rec);
+	reftable_record_release(&want_index);
+	reftable_record_release(&index_result);
 	return err;
 }
 
@@ -549,15 +549,14 @@ static int reader_seek_internal(struct reftable_reader *r,
 				struct reftable_iterator *it,
 				struct reftable_record *rec)
 {
-	struct reftable_reader_offsets *offs =
-		reader_offsets_for(r, reftable_record_type(rec));
+	struct reftable_reader_offsets *offs = reader_offsets_for(r, rec->type);
 	uint64_t idx = offs->index_offset;
 	struct table_iter ti = TABLE_ITER_INIT;
 	int err = 0;
 	if (idx > 0)
 		return reader_seek_indexed(r, it, rec);
 
-	err = reader_start(r, &ti, reftable_record_type(rec), 0);
+	err = reader_start(r, &ti, rec->type, 0);
 	if (err < 0)
 		return err;
 	err = reader_seek_linear(r, &ti, rec);
@@ -576,7 +575,7 @@ static int reader_seek_internal(struct reftable_reader *r,
 static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 		       struct reftable_record *rec)
 {
-	uint8_t typ = reftable_record_type(rec);
+	uint8_t typ = rec->type;
 
 	struct reftable_reader_offsets *offs = reader_offsets_for(r, typ);
 	if (!offs->is_present) {
@@ -590,11 +589,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 int reftable_reader_seek_ref(struct reftable_reader *r,
 			     struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return reader_seek(r, it, &rec);
 }
 
@@ -602,12 +602,13 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
 				struct reftable_iterator *it, const char *name,
 				uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return reader_seek(r, it, &rec);
 }
 
@@ -649,31 +650,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 					    struct reftable_iterator *it,
 					    uint8_t *oid)
 {
-	struct reftable_obj_record want = {
-		.hash_prefix = oid,
-		.hash_prefix_len = r->object_id_len,
+	struct reftable_record want = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = oid,
+			.hash_prefix_len = r->object_id_len,
+		},
 	};
-	struct reftable_record want_rec = { NULL };
 	struct reftable_iterator oit = { NULL };
-	struct reftable_obj_record got = { NULL };
-	struct reftable_record got_rec = { NULL };
+	struct reftable_record got = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = { 0 },
+	};
 	int err = 0;
 	struct indexed_table_ref_iter *itr = NULL;
 
 	/* Look through the reverse index. */
-	reftable_record_from_obj(&want_rec, &want);
-	err = reader_seek(r, &oit, &want_rec);
+	err = reader_seek(r, &oit, &want);
 	if (err != 0)
 		goto done;
 
 	/* read out the reftable_obj_record */
-	reftable_record_from_obj(&got_rec, &got);
-	err = iterator_next(&oit, &got_rec);
+	err = iterator_next(&oit, &got);
 	if (err < 0)
 		goto done;
 
-	if (err > 0 ||
-	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+	if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+			      r->object_id_len)) {
 		/* didn't find it; return empty iterator */
 		iterator_set_empty(it);
 		err = 0;
@@ -681,15 +684,15 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 	}
 
 	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
-					 got.offsets, got.offset_len);
+					 got.u.obj.offsets, got.u.obj.offset_len);
 	if (err < 0)
 		goto done;
-	got.offsets = NULL;
+	got.u.obj.offsets = NULL;
 	iterator_from_indexed_table_ref_iter(it, itr);
 
 done:
 	reftable_iterator_destroy(&oit);
-	reftable_record_release(&got_rec);
+	reftable_record_release(&got);
 	return err;
 }
 
diff --git a/reftable/record.c b/reftable/record.c
index 8bbcbff1e69..8cfd67b21e6 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "basics.h"
 
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
 int get_var_int(uint64_t *dest, struct string_view *in)
 {
 	int ptr = 0;
@@ -926,58 +930,6 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.is_deletion = &reftable_log_record_is_deletion_void,
 };
 
-struct reftable_record reftable_new_record(uint8_t typ)
-{
-	struct reftable_record rec = { NULL };
-	switch (typ) {
-	case BLOCK_TYPE_REF: {
-		struct reftable_ref_record *r =
-			reftable_calloc(sizeof(struct reftable_ref_record));
-		reftable_record_from_ref(&rec, r);
-		return rec;
-	}
-
-	case BLOCK_TYPE_OBJ: {
-		struct reftable_obj_record *r =
-			reftable_calloc(sizeof(struct reftable_obj_record));
-		reftable_record_from_obj(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_LOG: {
-		struct reftable_log_record *r =
-			reftable_calloc(sizeof(struct reftable_log_record));
-		reftable_record_from_log(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_INDEX: {
-		struct reftable_index_record empty = { .last_key =
-							       STRBUF_INIT };
-		struct reftable_index_record *r =
-			reftable_calloc(sizeof(struct reftable_index_record));
-		*r = empty;
-		reftable_record_from_index(&rec, r);
-		return rec;
-	}
-	}
-	abort();
-	return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
-	void *p = rec->data;
-	rec->data = NULL;
-	return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
-	reftable_record_release(rec);
-	reftable_free(reftable_record_yield(rec));
-}
-
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
 {
 	const struct reftable_index_record *rec = r;
@@ -1055,91 +1007,47 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 {
-	rec->ops->key(rec->data, dest);
-}
-
-uint8_t reftable_record_type(struct reftable_record *rec)
-{
-	return rec->ops->type;
+	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
 			   int hash_size)
 {
-	return rec->ops->encode(rec->data, dest, hash_size);
+	return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+						   dest, hash_size);
 }
 
 void reftable_record_copy_from(struct reftable_record *rec,
 			       struct reftable_record *src, int hash_size)
 {
-	assert(src->ops->type == rec->ops->type);
+	assert(src->type == rec->type);
 
-	rec->ops->copy_from(rec->data, src->data, hash_size);
+	reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+					       reftable_record_data(src),
+					       hash_size);
 }
 
 uint8_t reftable_record_val_type(struct reftable_record *rec)
 {
-	return rec->ops->val_type(rec->data);
+	return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   uint8_t extra, struct string_view src, int hash_size)
 {
-	return rec->ops->decode(rec->data, key, extra, src, hash_size);
+	return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+						   key, extra, src, hash_size);
 }
 
 void reftable_record_release(struct reftable_record *rec)
 {
-	rec->ops->release(rec->data);
+	reftable_record_vtable(rec)->release(reftable_record_data(rec));
 }
 
 int reftable_record_is_deletion(struct reftable_record *rec)
 {
-	return rec->ops->is_deletion(rec->data);
-}
-
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *ref_rec)
-{
-	assert(!rec->ops);
-	rec->data = ref_rec;
-	rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *obj_rec)
-{
-	assert(!rec->ops);
-	rec->data = obj_rec;
-	rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *index_rec)
-{
-	assert(!rec->ops);
-	rec->data = index_rec;
-	rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *log_rec)
-{
-	assert(!rec->ops);
-	rec->data = log_rec;
-	rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
-	return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
-	return rec->data;
+	return reftable_record_vtable(rec)->is_deletion(
+		reftable_record_data(rec));
 }
 
 static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1212,3 +1120,53 @@ void string_view_consume(struct string_view *s, int n)
 	s->buf += n;
 	s->len -= n;
 }
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &rec->u.ref;
+	case BLOCK_TYPE_LOG:
+		return &rec->u.log;
+	case BLOCK_TYPE_INDEX:
+		return &rec->u.idx;
+	case BLOCK_TYPE_OBJ:
+		return &rec->u.obj;
+	}
+	abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &reftable_ref_record_vtable;
+	case BLOCK_TYPE_LOG:
+		return &reftable_log_record_vtable;
+	case BLOCK_TYPE_INDEX:
+		return &reftable_index_record_vtable;
+	case BLOCK_TYPE_OBJ:
+		return &reftable_obj_record_vtable;
+	}
+	abort();
+}
+
+struct reftable_record reftable_record_for(uint8_t typ)
+{
+	struct reftable_record clean_idx = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.last_key = STRBUF_INIT,
+		},
+	};
+	struct reftable_record clean = {
+		.type = typ,
+	};
+
+	if (typ == BLOCK_TYPE_INDEX) {
+		return clean_idx;
+	}
+
+	return clean;
+}
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf4..1fe0c14a198 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -60,18 +60,9 @@ struct reftable_record_vtable {
 	int (*is_deletion)(const void *rec);
 };
 
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
-	void *data;
-	struct reftable_record_vtable *ops;
-};
-
 /* returns true for recognized block types. Block start with the block type. */
 int reftable_is_block_type(uint8_t typ);
 
-/* creates a malloced record of the given type. Dispose with record_destroy */
-struct reftable_record reftable_new_record(uint8_t typ);
-
 /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
  * number of bytes written. */
 int reftable_encode_key(int *is_restart, struct string_view dest,
@@ -97,8 +88,26 @@ struct reftable_obj_record {
 	int offset_len;
 };
 
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_record_for() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+	uint8_t type;
+	union {
+		struct reftable_ref_record ref;
+		struct reftable_log_record log;
+		struct reftable_obj_record obj;
+		struct reftable_index_record idx;
+	} u;
+};
+
 /* see struct record_vtable */
 
+/* return an initialized record for the given type */
+struct reftable_record reftable_record_for(uint8_t typ);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
 uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
@@ -111,25 +120,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   int hash_size);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
 /* for qsort. */
 int reftable_ref_record_compare_name(const void *a, const void *b);
 
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace41..12161c2895a 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -16,24 +16,22 @@
 
 static void test_copy(struct reftable_record *rec)
 {
-	struct reftable_record copy =
-		reftable_new_record(reftable_record_type(rec));
+	struct reftable_record copy = reftable_record_for(rec->type);
+
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-	switch (reftable_record_type(&copy)) {
+	switch (copy.type) {
 	case BLOCK_TYPE_REF:
-		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-						 reftable_record_as_ref(rec),
+		EXPECT(reftable_ref_record_equal(&copy.u.ref, &rec->u.ref,
 						 GIT_SHA1_RAWSZ));
 		break;
 	case BLOCK_TYPE_LOG:
-		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-						 reftable_record_as_log(rec),
+		EXPECT(reftable_log_record_equal(&copy.u.log, &rec->u.log,
 						 GIT_SHA1_RAWSZ));
 		break;
 	}
-	reftable_record_destroy(&copy);
+	reftable_record_release(&copy);
 }
 
 static void test_varint_roundtrip(void)
@@ -106,61 +104,60 @@ static void test_reftable_ref_record_roundtrip(void)
 	int i = 0;
 
 	for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-		struct reftable_ref_record in = { NULL };
-		struct reftable_ref_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_REF,
+		};
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_REF
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_record rec = { NULL };
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-
 		int n, m;
 
-		in.value_type = i;
+		in.u.ref.value_type = i;
 		switch (i) {
 		case REFTABLE_REF_DELETION:
 			break;
 		case REFTABLE_REF_VAL1:
-			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val1, 1);
+			in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val1, 1);
 			break;
 		case REFTABLE_REF_VAL2:
-			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.value, 1);
-			in.value.val2.target_value =
+			in.u.ref.value.val2.value =
+				reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val2.value, 1);
+			in.u.ref.value.val2.target_value =
 				reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.target_value, 2);
+			set_hash(in.u.ref.value.val2.target_value, 2);
 			break;
 		case REFTABLE_REF_SYMREF:
-			in.value.symref = xstrdup("target");
+			in.u.ref.value.symref = xstrdup("target");
 			break;
 		}
-		in.refname = xstrdup("refs/heads/master");
+		in.u.ref.refname = xstrdup("refs/heads/master");
 
-		reftable_record_from_ref(&rec, &in);
-		test_copy(&rec);
+		test_copy(&in);
 
-		EXPECT(reftable_record_val_type(&rec) == i);
+		EXPECT(reftable_record_val_type(&in) == i);
 
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
 
 		/* decode into a non-zero reftable_record to test for leaks. */
-
-		reftable_record_from_ref(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, i, dest,
-					   GIT_SHA1_RAWSZ);
+		m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
-		reftable_record_release(&rec_out);
+		EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+						 GIT_SHA1_RAWSZ));
+		reftable_record_release(&in);
 
 		strbuf_release(&key);
-		reftable_ref_record_release(&in);
+		reftable_record_release(&out);
 	}
 }
 
@@ -213,7 +210,9 @@ static void test_reftable_log_record_roundtrip(void)
 	set_test_hash(in[0].value.update.new_hash, 1);
 	set_test_hash(in[0].value.update.old_hash, 2);
 	for (i = 0; i < ARRAY_SIZE(in); i++) {
-		struct reftable_record rec = { NULL };
+		struct reftable_record rec = {
+			.type = BLOCK_TYPE_LOG
+		};
 		struct strbuf key = STRBUF_INIT;
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
@@ -221,23 +220,25 @@ static void test_reftable_log_record_roundtrip(void)
 			.len = sizeof(buffer),
 		};
 		/* populate out, to check for leaks. */
-		struct reftable_log_record out = {
-			.refname = xstrdup("old name"),
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = {
-				.update = {
-					.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.name = xstrdup("old name"),
-					.email = xstrdup("old@email"),
-					.message = xstrdup("old message"),
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_LOG,
+			.u.log = {
+				.refname = xstrdup("old name"),
+				.value_type = REFTABLE_LOG_UPDATE,
+				.value = {
+					.update = {
+						.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.name = xstrdup("old name"),
+						.email = xstrdup("old@email"),
+						.message = xstrdup("old message"),
+					},
 				},
 			},
 		};
-		struct reftable_record rec_out = { NULL };
 		int n, m, valtype;
 
-		reftable_record_from_log(&rec, &in[i]);
+		rec.u.log = in[i];
 
 		test_copy(&rec);
 
@@ -245,16 +246,16 @@ static void test_reftable_log_record_roundtrip(void)
 
 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n >= 0);
-		reftable_record_from_log(&rec_out, &out);
 		valtype = reftable_record_val_type(&rec);
-		m = reftable_record_decode(&rec_out, key, valtype, dest,
+		m = reftable_record_decode(&out, key, valtype, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+		EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+						 GIT_SHA1_RAWSZ));
 		reftable_log_record_release(&in[i]);
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
@@ -322,47 +323,52 @@ static void test_reftable_obj_record_roundtrip(void)
 					       } };
 	int i = 0;
 	for (i = 0; i < ARRAY_SIZE(recs); i++) {
-		struct reftable_obj_record in = recs[i];
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-		struct reftable_record rec = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj = recs[i],
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_obj_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_OBJ
+		};
 		int n, m;
 		uint8_t extra;
 
-		reftable_record_from_obj(&rec, &in);
-		test_copy(&rec);
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		test_copy(&in);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
-		extra = reftable_record_val_type(&rec);
-		reftable_record_from_obj(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, extra, dest,
+		extra = reftable_record_val_type(&in);
+		m = reftable_record_decode(&out, key, extra, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-		EXPECT(in.offset_len == out.offset_len);
-
-		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-			       in.hash_prefix_len));
-		EXPECT(0 == memcmp(in.offsets, out.offsets,
-				   sizeof(uint64_t) * in.offset_len));
+		EXPECT(in.u.obj.hash_prefix_len == out.u.obj.hash_prefix_len);
+		EXPECT(in.u.obj.offset_len == out.u.obj.offset_len);
+		if (in.u.obj.hash_prefix_len)
+			EXPECT(!memcmp(in.u.obj.hash_prefix, out.u.obj.hash_prefix,
+				       in.u.obj.hash_prefix_len));
+		if (in.u.obj.offset_len)
+			EXPECT(!memcmp(in.u.obj.offsets, out.u.obj.offsets,
+					   sizeof(uint64_t) * in.u.obj.offset_len));
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
 static void test_reftable_index_record_roundtrip(void)
 {
-	struct reftable_index_record in = {
-		.offset = 42,
-		.last_key = STRBUF_INIT,
+	struct reftable_record in = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.offset = 42,
+			.last_key = STRBUF_INIT,
+		},
 	};
 	uint8_t buffer[1024] = { 0 };
 	struct string_view dest = {
@@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void)
 		.len = sizeof(buffer),
 	};
 	struct strbuf key = STRBUF_INIT;
-	struct reftable_record rec = { NULL };
-	struct reftable_index_record out = { .last_key = STRBUF_INIT };
-	struct reftable_record out_rec = { NULL };
+	struct reftable_record out = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	int n, m;
 	uint8_t extra;
 
-	strbuf_addstr(&in.last_key, "refs/heads/master");
-	reftable_record_from_index(&rec, &in);
-	reftable_record_key(&rec, &key);
-	test_copy(&rec);
+	strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+	reftable_record_key(&in, &key);
+	test_copy(&in);
 
-	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
-	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+	EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+	n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 	EXPECT(n > 0);
 
-	extra = reftable_record_val_type(&rec);
-	reftable_record_from_index(&out_rec, &out);
-	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+	extra = reftable_record_val_type(&in);
+	m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
 	EXPECT(m == n);
 
-	EXPECT(in.offset == out.offset);
+	EXPECT(in.u.idx.offset == out.u.idx.offset);
 
-	reftable_record_release(&out_rec);
+	reftable_record_release(&out);
 	strbuf_release(&key);
-	strbuf_release(&in.last_key);
+	strbuf_release(&in.u.idx.last_key);
 }
 
 int record_test_main(int argc, const char *argv[])
diff --git a/reftable/writer.c b/reftable/writer.c
index 3ca721e9f64..f9544c5f97b 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -221,10 +221,10 @@ static int writer_add_record(struct reftable_writer *w,
 	strbuf_reset(&w->last_key);
 	strbuf_addbuf(&w->last_key, &key);
 	if (w->block_writer == NULL) {
-		writer_reinit_block_writer(w, reftable_record_type(rec));
+		writer_reinit_block_writer(w, rec->type);
 	}
 
-	assert(block_writer_type(w->block_writer) == reftable_record_type(rec));
+	assert(block_writer_type(w->block_writer) == rec->type);
 
 	if (block_writer_add(w->block_writer, rec) == 0) {
 		err = 0;
@@ -236,7 +236,7 @@ static int writer_add_record(struct reftable_writer *w,
 		goto done;
 	}
 
-	writer_reinit_block_writer(w, reftable_record_type(rec));
+	writer_reinit_block_writer(w, rec->type);
 	err = block_writer_add(w->block_writer, rec);
 	if (err < 0) {
 		goto done;
@@ -251,8 +251,10 @@ done:
 int reftable_writer_add_ref(struct reftable_writer *w,
 			    struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	struct reftable_ref_record copy = *ref;
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = *ref,
+	};
 	int err = 0;
 
 	if (ref->refname == NULL)
@@ -261,8 +263,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
 	    ref->update_index > w->max_update_index)
 		return REFTABLE_API_ERROR;
 
-	reftable_record_from_ref(&rec, &copy);
-	copy.update_index -= w->min_update_index;
+	rec.u.ref.update_index -= w->min_update_index;
 
 	err = writer_add_record(w, &rec);
 	if (err < 0)
@@ -301,7 +302,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
 static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 					    struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = *log,
+	};
 	if (w->block_writer &&
 	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
 		int err = writer_finish_public_section(w);
@@ -311,8 +315,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 
 	w->next -= w->pending_padding;
 	w->pending_padding = 0;
-
-	reftable_record_from_log(&rec, log);
 	return writer_add_record(w, &rec);
 }
 
@@ -393,8 +395,10 @@ static int writer_finish_section(struct reftable_writer *w)
 		w->index_len = 0;
 		w->index_cap = 0;
 		for (i = 0; i < idx_len; i++) {
-			struct reftable_record rec = { NULL };
-			reftable_record_from_index(&rec, idx + i);
+			struct reftable_record rec = {
+				.type = BLOCK_TYPE_INDEX,
+				.u.idx = idx[i],
+			};
 			if (block_writer_add(w->block_writer, &rec) == 0) {
 				continue;
 			}
@@ -462,17 +466,18 @@ static void write_object_record(void *void_arg, void *key)
 {
 	struct write_record_arg *arg = void_arg;
 	struct obj_index_tree_node *entry = key;
-	struct reftable_obj_record obj_rec = {
-		.hash_prefix = (uint8_t *)entry->hash.buf,
-		.hash_prefix_len = arg->w->stats.object_id_len,
-		.offsets = entry->offsets,
-		.offset_len = entry->offset_len,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = (uint8_t *)entry->hash.buf,
+			.hash_prefix_len = arg->w->stats.object_id_len,
+			.offsets = entry->offsets,
+			.offset_len = entry->offset_len,
+		}
 	};
-	struct reftable_record rec = { NULL };
 	if (arg->err < 0)
 		goto done;
 
-	reftable_record_from_obj(&rec, &obj_rec);
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
@@ -485,7 +490,8 @@ static void write_object_record(void *void_arg, void *key)
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
-	obj_rec.offset_len = 0;
+
+	rec.u.obj.offset_len = 0;
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 
 	/* Should be able to write into a fresh block. */
-- 
gitgitgadget
```

## Re: \[PATCH v2 00/11\] Reftable coverity fixes

- From: Jeff King \<hidden\>
- Date: 2021-12-09 05:31:41 
- Message: https://ratatoskr.run/git/2021/12/7915243.md

```text
On Wed, Dec 08, 2021 at 09:49:35PM +0000, Han-Wen Nienhuys via GitGitGadget wrote:

>   2:  603bd1d4f6e !  2:  1ddcfe61ebc reftable: fix resource leak in error path
>      @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
>        
>        	if (typ == BLOCK_TYPE_LOG) {
>        		int block_header_skip = 4 + header_off;
>      +@@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable_block *block,
>      + 		uLongf src_len = block->len - block_header_skip;
>      + 		/* Log blocks specify the *uncompressed* size in their header.
>      + 		 */
>      +-		uint8_t *uncompressed = reftable_malloc(sz);
>      ++		uncompressed = reftable_malloc(sz);
>      + 
>      + 		/* Copy over the block header verbatim. It's not compressed. */
>      + 		memcpy(uncompressed, block->data, block_header_skip);

This drops the shadowed variable, but it needs a little more. In one of
the error blocks we still free the variable, and then jump to the
"done" label, which will try to free it again. So you need:

diff --git a/reftable/block.c b/reftable/block.c
index 0bc4a5d84b..6d55817e0d 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -215,7 +215,6 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		if (Z_OK !=
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
-			reftable_free(uncompressed);
 			err = REFTABLE_ZLIB_ERROR;
 			goto done;
 		}

to avoid the double-free. I think it's otherwise OK. The other error
case does not try to free, and in the success case we hand off ownership
of "uncompressed" and set it back to NULL.

I doubt this is the cause of the segfault on Windows, though (I saw it
via Coverity). That one still puzzles me, and I suspect will need more
output to figure out. It's possible to get an interactive session on the
Windows VM running the CI:

  https://github.com/marketplace/actions/debugging-with-tmate

but I haven't tried it myself. I'm also not sure what tools are
available there (gdb sure would be nice, but even a backtrace of the
segfault would be quite a lot; I wonder if we could build with ASan on
Windows).

-Peff
```

## \[PATCH v3 00/11\] Reftable coverity fixes

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:01:54 
- Message: https://ratatoskr.run/git/2021/12/7915625.md

```text
This series was targeted to 'next'.

This addresses some complaints from Coverity that Peff reported.

v3:

 * revert some changes suggested by stolee
 * add unittest for zlib corruption.

Han-Wen Nienhuys (11):
  reftable: fix OOB stack write in print functions
  reftable: fix resource leak in error path
  reftable: fix resource leak blocksource.c
  reftable: check reftable_stack_auto_compact() return value
  reftable: ignore remove() return value in stack_test.c
  reftable: fix resource warning
  reftable: fix NULL derefs in error paths
  reftable: order unittests by complexity
  reftable: drop stray printf in readwrite_test
  reftable: handle null refnames in reftable_ref_record_equal
  reftable: make reftable_record a tagged union

 reftable/block.c          |  30 ++++---
 reftable/block_test.c     |  22 ++---
 reftable/blocksource.c    |   6 +-
 reftable/generic.c        |  35 ++++----
 reftable/iter.c           |   4 +-
 reftable/merged.c         |  33 +++----
 reftable/pq.c             |   3 +-
 reftable/pq_test.c        |  31 +++----
 reftable/reader.c         |  96 ++++++++++----------
 reftable/readwrite_test.c |  68 +++++++++++++-
 reftable/record.c         | 185 ++++++++++++++++----------------------
 reftable/record.h         |  42 ++++-----
 reftable/record_test.c    | 181 +++++++++++++++++++------------------
 reftable/stack.c          |  15 ++--
 reftable/stack_test.c     |   3 +-
 reftable/writer.c         |  40 +++++----
 t/helper/test-reftable.c  |   9 +-
 17 files changed, 431 insertions(+), 372 deletions(-)


base-commit: fae76fe5da3df25d752f2251b7ccda3f62813aa9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1152%2Fhanwen%2Freftable-coverity-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1152/hanwen/reftable-coverity-v3
Pull-Request: https://github.com/git/git/pull/1152

Range-diff vs v2:

  1:  7c033815183 =  1:  1c1a3ff92bd reftable: fix OOB stack write in print functions
  2:  1ddcfe61ebc !  2:  975a570d388 reftable: fix resource leak in error path
     @@ Metadata
       ## Commit message ##
          reftable: fix resource leak in error path
      
     -    This would be triggered by corrupt files, so it doesn't have test coverage. This
     -    was discovered by a Coverity scan.
     +    Add test coverage for corrupt zlib data.
     +
     +    This problem was discovered by a Coverity scan.
      
          Signed-off-by: Han-Wen Nienhuys [ref]
      
     @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
       		/* Copy over the block header verbatim. It's not compressed. */
       		memcpy(uncompressed, block->data, block_header_skip);
      @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable_block *block,
     + 		if (Z_OK !=
       		    uncompress2(uncompressed + block_header_skip, &dst_len,
       				block->data + block_header_skip, &src_len)) {
     - 			reftable_free(uncompressed);
     +-			reftable_free(uncompressed);
      -			return REFTABLE_ZLIB_ERROR;
      +			err = REFTABLE_ZLIB_ERROR;
      +			goto done;
     @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
       }
       
       static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
     +
     + ## reftable/readwrite_test.c ##
     +@@ reftable/readwrite_test.c: static void test_log_write_read(void)
     + 	reader_close(&rd);
     + }
     + 
     ++static void test_log_zlib_corruption(void)
     ++{
     ++	struct reftable_write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++	struct reftable_iterator it = { NULL };
     ++	struct reftable_reader rd = { NULL };
     ++	struct reftable_block_source source = { NULL };
     ++	struct strbuf buf = STRBUF_INIT;
     ++	struct reftable_writer *w =
     ++		reftable_new_writer(&strbuf_add_void, &buf, &opts);
     ++	const struct reftable_stats *stats = NULL;
     ++	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
     ++	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
     ++	char message[100] = { 0 };
     ++	int err, i, n;
     ++
     ++	struct reftable_log_record log = {
     ++		.refname = "refname",
     ++		.value_type = REFTABLE_LOG_UPDATE,
     ++		.value = {
     ++			.update = {
     ++				.new_hash = hash1,
     ++				.old_hash = hash2,
     ++				.name = "My Name",
     ++				.email = "myname@invalid",
     ++				.message = message,
     ++			},
     ++		},
     ++	};
     ++
     ++	for (i = 0; i < sizeof(message)-1; i++) {
     ++		message[i] = (uint8_t)(rand() % 64 + ' ');
     ++	}
     ++
     ++	reftable_writer_set_limits(w, 1, 1);
     ++
     ++	err = reftable_writer_add_log(w, &log);
     ++	EXPECT_ERR(err);
     ++
     ++	n = reftable_writer_close(w);
     ++	EXPECT(n == 0);
     ++
     ++	stats = writer_stats(w);
     ++	EXPECT(stats->log_stats.blocks > 0);
     ++	reftable_writer_free(w);
     ++	w = NULL;
     ++
     ++	/* corrupt the data. */
     ++	buf.buf[50] ^= 0x99;
     ++
     ++	block_source_from_strbuf(&source, &buf);
     ++
     ++	err = init_reader(&rd, &source, "file.log");
     ++	EXPECT_ERR(err);
     ++
     ++	err = reftable_reader_seek_log(&rd, &it, "refname");
     ++	EXPECT(err == REFTABLE_ZLIB_ERROR);
     ++
     ++	reftable_iterator_destroy(&it);
     ++
     ++	/* cleanup. */
     ++	strbuf_release(&buf);
     ++	reader_close(&rd);
     ++}
     ++
     + static void test_table_read_write_sequential(void)
     + {
     + 	char **names;
     +@@ reftable/readwrite_test.c: static void test_corrupt_table(void)
     + 
     + int readwrite_test_main(int argc, const char *argv[])
     + {
     ++	RUN_TEST(test_log_zlib_corruption);
     + 	RUN_TEST(test_corrupt_table);
     + 	RUN_TEST(test_corrupt_table_empty);
     + 	RUN_TEST(test_log_write_read);
  3:  e052b2a61d6 =  3:  0b9c7176d71 reftable: fix resource leak blocksource.c
  4:  9063137457b =  4:  1dda4ee717f reftable: check reftable_stack_auto_compact() return value
  5:  5020be156ae =  5:  36858e2070b reftable: ignore remove() return value in stack_test.c
  6:  64c18d01cad =  6:  80b1988b885 reftable: fix resource warning
  7:  700387ac5d3 =  7:  2939286924c reftable: fix NULL derefs in error paths
  8:  713f1d09f68 =  8:  9dce18d7349 reftable: order unittests by complexity
  9:  cb601b51a47 =  9:  6b0af68f0b9 reftable: drop stray printf in readwrite_test
 10:  a0f83eff19f = 10:  bff85cb0809 reftable: handle null refnames in reftable_ref_record_equal
 11:  a2743033cfd ! 11:  b3e592b9c27 reftable: make reftable_record a tagged union
     @@ Commit message
          vtable within the structure.
      
          The only snag is that reftable_index_record contain a strbuf, so it cannot be
     -    zero-initialized. To address this, introduce reftable_record_for() to create a
     -    fresh instance, given a record type.
     +    zero-initialized. To address this, use reftable_new_record() to return fresh
     +    instance, given a record type. Since reftable_new_record() doesn't cause heap
     +    allocation anymore, it should be balanced with reftable_record_release() rather
     +    than reftable_record_destroy().
      
          Thanks to Peff for the suggestion.
      
     @@ Commit message
          Signed-off-by: Han-Wen Nienhuys [ref]
      
       ## reftable/block.c ##
     -@@ reftable/block.c: int block_reader_seek(struct block_reader *br, struct block_iter *it,
     - 		.key = *want,
     - 		.r = br,
     - 	};
     --	struct reftable_record rec = reftable_new_record(block_reader_type(br));
     -+	struct reftable_record rec = reftable_record_for(block_reader_type(br));
     - 	struct strbuf key = STRBUF_INIT;
     - 	int err = 0;
     - 	struct block_iter next = {
      @@ reftable/block.c: int block_reader_seek(struct block_reader *br, struct block_iter *it,
       done:
       	strbuf_release(&key);
     @@ reftable/iter.c: static int indexed_table_ref_iter_next_block(struct indexed_tab
      
       ## reftable/merged.c ##
      @@ reftable/merged.c: static int merged_iter_init(struct merged_iter *mi)
     - {
     - 	int i = 0;
     - 	for (i = 0; i < mi->stack_len; i++) {
     --		struct reftable_record rec = reftable_new_record(mi->typ);
     -+		struct reftable_record rec = reftable_record_for(mi->typ);
     - 		int err = iterator_next(&mi->stack[i], &rec);
     - 		if (err < 0) {
     - 			return err;
     -@@ reftable/merged.c: static int merged_iter_init(struct merged_iter *mi)
       
       		if (err > 0) {
       			reftable_iterator_destroy(&mi->stack[i]);
     @@ reftable/merged.c: static void merged_iter_close(void *p)
      -	struct reftable_record rec = reftable_new_record(mi->typ);
       	struct pq_entry e = {
      -		.rec = rec,
     -+		.rec = reftable_record_for(mi->typ),
     ++		.rec = reftable_new_record(mi->typ),
       		.index = idx,
       	};
      -	int err = iterator_next(&mi->stack[idx], &rec);
     @@ reftable/merged.c: static int merged_iter_next_entry(struct merged_iter *mi,
       	strbuf_release(&entry_key);
       	return 0;
       }
     -@@ reftable/merged.c: static int merged_table_seek_record(struct reftable_merged_table *mt,
     - 		sizeof(struct reftable_iterator) * mt->stack_len);
     - 	struct merged_iter merged = {
     - 		.stack = iters,
     --		.typ = reftable_record_type(rec),
     -+		.typ = rec->type,
     - 		.hash_id = mt->hash_id,
     - 		.suppress_deletions = mt->suppress_deletions,
     - 	};
      @@ reftable/merged.c: int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
       				   struct reftable_iterator *it,
       				   const char *name)
     @@ reftable/pq_test.c: static void test_pq(void)
      +		struct reftable_record *rec = &e.rec;
       		merged_iter_pqueue_check(pq);
       
     -+		EXPECT(rec->type == BLOCK_TYPE_REF);
     ++		EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
       		if (last) {
      -			EXPECT(strcmp(last, ref->refname) < 0);
      +			EXPECT(strcmp(last, rec->u.ref.refname) < 0);
     @@ reftable/pq_test.c: static void test_pq(void)
      
       ## reftable/reader.c ##
      @@ reftable/reader.c: static int table_iter_next_in_block(struct table_iter *ti,
     - 				    struct reftable_record *rec)
       {
       	int res = block_iter_next(&ti->bi, rec);
     --	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
     + 	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
      -		((struct reftable_ref_record *)rec->data)->update_index +=
      -			ti->r->min_update_index;
     -+	if (res == 0 && rec->type == BLOCK_TYPE_REF) {
      +		rec->u.ref.update_index += ti->r->min_update_index;
       	}
       
       	return res;
     -@@ reftable/reader.c: static int table_iter_next_block(struct table_iter *dest,
     - 
     - static int table_iter_next(struct table_iter *ti, struct reftable_record *rec)
     - {
     --	if (reftable_record_type(rec) != ti->typ)
     -+	if (rec->type != ti->typ)
     - 		return REFTABLE_API_ERROR;
     - 
     - 	while (1) {
      @@ reftable/reader.c: static int reader_start(struct reftable_reader *r, struct table_iter *ti,
       static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
       			      struct reftable_record *want)
       {
      -	struct reftable_record rec =
      -		reftable_new_record(reftable_record_type(want));
     -+	struct reftable_record rec = reftable_record_for(want->type);
     ++	struct reftable_record rec = reftable_new_record(reftable_record_type(want));
       	struct strbuf want_key = STRBUF_INIT;
       	struct strbuf got_key = STRBUF_INIT;
       	struct table_iter next = TABLE_ITER_INIT;
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
      -	reftable_record_from_index(&want_index_rec, &want_index);
      -	reftable_record_from_index(&index_result_rec, &index_result);
      -
     --	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
      +	reftable_record_key(rec, &want_index.u.idx.last_key);
     -+	err = reader_start(r, &index_iter, rec->type, 1);
     + 	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
       	if (err < 0)
       		goto done;
       
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
       		if (err < 0)
       			goto done;
       
     --		if (next.typ == reftable_record_type(rec)) {
     -+		if (next.typ == rec->type) {
     - 			err = 0;
     - 			break;
     - 		}
      @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
       done:
       	block_iter_close(&next.bi);
     @@ reftable/reader.c: static int reader_seek_internal(struct reftable_reader *r,
       {
      -	struct reftable_reader_offsets *offs =
      -		reader_offsets_for(r, reftable_record_type(rec));
     -+	struct reftable_reader_offsets *offs = reader_offsets_for(r, rec->type);
     ++	struct reftable_reader_offsets *offs = reader_offsets_for(r, reftable_record_type(rec));
       	uint64_t idx = offs->index_offset;
       	struct table_iter ti = TABLE_ITER_INIT;
       	int err = 0;
     - 	if (idx > 0)
     - 		return reader_seek_indexed(r, it, rec);
     - 
     --	err = reader_start(r, &ti, reftable_record_type(rec), 0);
     -+	err = reader_start(r, &ti, rec->type, 0);
     - 	if (err < 0)
     - 		return err;
     - 	err = reader_seek_linear(r, &ti, rec);
     -@@ reftable/reader.c: static int reader_seek_internal(struct reftable_reader *r,
     - static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
     - 		       struct reftable_record *rec)
     - {
     --	uint8_t typ = reftable_record_type(rec);
     -+	uint8_t typ = rec->type;
     - 
     - 	struct reftable_reader_offsets *offs = reader_offsets_for(r, typ);
     - 	if (!offs->is_present) {
      @@ reftable/reader.c: static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
       int reftable_reader_seek_ref(struct reftable_reader *r,
       			     struct reftable_iterator *it, const char *name)
     @@ reftable/record.c: static struct reftable_record_vtable reftable_index_record_vt
       void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
       {
      -	rec->ops->key(rec->data, dest);
     --}
     --
     --uint8_t reftable_record_type(struct reftable_record *rec)
     --{
     --	return rec->ops->type;
      +	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
       }
       
     + uint8_t reftable_record_type(struct reftable_record *rec)
     + {
     +-	return rec->ops->type;
     ++	return rec->type;
     + }
     + 
       int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
       			   int hash_size)
       {
     @@ reftable/record.c: void string_view_consume(struct string_view *s, int n)
      +	abort();
      +}
      +
     -+struct reftable_record reftable_record_for(uint8_t typ)
     ++struct reftable_record reftable_new_record(uint8_t typ)
      +{
      +	struct reftable_record clean_idx = {
      +		.type = BLOCK_TYPE_INDEX,
     @@ reftable/record.h: struct reftable_record_vtable {
       int reftable_is_block_type(uint8_t typ);
       
      -/* creates a malloced record of the given type. Dispose with record_destroy */
     --struct reftable_record reftable_new_record(uint8_t typ);
     --
     ++/* return an initialized record for the given type */
     + struct reftable_record reftable_new_record(uint8_t typ);
     + 
       /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
     -  * number of bytes written. */
     - int reftable_encode_key(int *is_restart, struct string_view dest,
      @@ reftable/record.h: struct reftable_obj_record {
       	int offset_len;
       };
     @@ reftable/record.h: struct reftable_obj_record {
      +/* record is a generic wrapper for different types of records. It is normally
      + * created on the stack, or embedded within another struct. If the type is
      + * known, a fresh instance can be initialized explicitly. Otherwise, use
     -+ * reftable_record_for() to initialize generically (as the index_record is not
     ++ * reftable_new_record() to initialize generically (as the index_record is not
      + * valid as 0-initialized structure)
      + */
      +struct reftable_record {
     @@ reftable/record.h: struct reftable_obj_record {
      +
       /* see struct record_vtable */
       
     -+/* return an initialized record for the given type */
     -+struct reftable_record reftable_record_for(uint8_t typ);
       void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
     - uint8_t reftable_record_type(struct reftable_record *rec);
     - void reftable_record_copy_from(struct reftable_record *rec,
      @@ reftable/record.h: int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
       			   int hash_size);
       int reftable_record_is_deletion(struct reftable_record *rec);
     @@ reftable/record_test.c
       {
      -	struct reftable_record copy =
      -		reftable_new_record(reftable_record_type(rec));
     -+	struct reftable_record copy = reftable_record_for(rec->type);
     ++	struct reftable_record copy = reftable_new_record(reftable_record_type(rec));
      +
       	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
       	/* do it twice to catch memory leaks */
     @@ reftable/record_test.c: static void test_reftable_index_record_roundtrip(void)
       int record_test_main(int argc, const char *argv[])
      
       ## reftable/writer.c ##
     -@@ reftable/writer.c: static int writer_add_record(struct reftable_writer *w,
     - 	strbuf_reset(&w->last_key);
     - 	strbuf_addbuf(&w->last_key, &key);
     - 	if (w->block_writer == NULL) {
     --		writer_reinit_block_writer(w, reftable_record_type(rec));
     -+		writer_reinit_block_writer(w, rec->type);
     - 	}
     - 
     --	assert(block_writer_type(w->block_writer) == reftable_record_type(rec));
     -+	assert(block_writer_type(w->block_writer) == rec->type);
     - 
     - 	if (block_writer_add(w->block_writer, rec) == 0) {
     - 		err = 0;
     -@@ reftable/writer.c: static int writer_add_record(struct reftable_writer *w,
     - 		goto done;
     - 	}
     - 
     --	writer_reinit_block_writer(w, reftable_record_type(rec));
     -+	writer_reinit_block_writer(w, rec->type);
     - 	err = block_writer_add(w->block_writer, rec);
     - 	if (err < 0) {
     - 		goto done;
      @@ reftable/writer.c: done:
       int reftable_writer_add_ref(struct reftable_writer *w,
       			    struct reftable_ref_record *ref)

-- 
gitgitgadget
```

## \[PATCH v3 01/11\] reftable: fix OOB stack write in print functions

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:01:57 
- Message: https://ratatoskr.run/git/2021/12/7915626.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc6..8536bd03aa9 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -254,7 +254,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 void reftable_ref_record_print(struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
-	char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 	switch (ref->value_type) {
 	case REFTABLE_REF_SYMREF:
@@ -586,7 +586,7 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 void reftable_log_record_print(struct reftable_log_record *log,
 			       uint32_t hash_id)
 {
-	char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-- 
gitgitgadget

```

## \[PATCH v3 02/11\] reftable: fix resource leak in error path

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:01:59 
- Message: https://ratatoskr.run/git/2021/12/7915627.md

```text
From: Han-Wen Nienhuys <redacted>

Add test coverage for corrupt zlib data.

This problem was discovered by a Coverity scan.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c          | 28 ++++++++++------
 reftable/readwrite_test.c | 67 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 9 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c947..37a85b9576d 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	uint32_t full_block_size = table_block_size;
 	uint8_t typ = block->data[header_off];
 	uint32_t sz = get_be24(block->data + header_off + 1);
-
+	int err = 0;
 	uint16_t restart_count = 0;
 	uint32_t restart_start = 0;
 	uint8_t *restart_bytes = NULL;
+	uint8_t *uncompressed = NULL;
 
-	if (!reftable_is_block_type(typ))
-		return REFTABLE_FORMAT_ERROR;
+	if (!reftable_is_block_type(typ)) {
+		err =  REFTABLE_FORMAT_ERROR;
+		goto done;
+	}
 
 	if (typ == BLOCK_TYPE_LOG) {
 		int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		uLongf src_len = block->len - block_header_skip;
 		/* Log blocks specify the *uncompressed* size in their header.
 		 */
-		uint8_t *uncompressed = reftable_malloc(sz);
+		uncompressed = reftable_malloc(sz);
 
 		/* Copy over the block header verbatim. It's not compressed. */
 		memcpy(uncompressed, block->data, block_header_skip);
@@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		if (Z_OK !=
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
-			reftable_free(uncompressed);
-			return REFTABLE_ZLIB_ERROR;
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz)
-			return REFTABLE_FORMAT_ERROR;
+		if (dst_len + block_header_skip != sz) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		}
 
 		/* We're done with the input data. */
 		reftable_block_done(block);
 		block->data = uncompressed;
+		uncompressed = NULL;
 		block->len = sz;
 		block->source = malloc_block_source();
 		full_block_size = src_len + block_header_skip;
@@ -251,7 +257,11 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	br->restart_count = restart_count;
 	br->restart_bytes = restart_bytes;
 
-	return 0;
+done:
+	if (uncompressed) {
+		reftable_free(uncompressed);
+	}
+	return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 5f6bcc2f775..42caf0bde4c 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -254,6 +254,72 @@ static void test_log_write_read(void)
 	reader_close(&rd);
 }
 
+static void test_log_zlib_corruption(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_iterator it = { NULL };
+	struct reftable_reader rd = { NULL };
+	struct reftable_block_source source = { NULL };
+	struct strbuf buf = STRBUF_INIT;
+	struct reftable_writer *w =
+		reftable_new_writer(&strbuf_add_void, &buf, &opts);
+	const struct reftable_stats *stats = NULL;
+	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+	char message[100] = { 0 };
+	int err, i, n;
+
+	struct reftable_log_record log = {
+		.refname = "refname",
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.new_hash = hash1,
+				.old_hash = hash2,
+				.name = "My Name",
+				.email = "myname@invalid",
+				.message = message,
+			},
+		},
+	};
+
+	for (i = 0; i < sizeof(message)-1; i++) {
+		message[i] = (uint8_t)(rand() % 64 + ' ');
+	}
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	err = reftable_writer_add_log(w, &log);
+	EXPECT_ERR(err);
+
+	n = reftable_writer_close(w);
+	EXPECT(n == 0);
+
+	stats = writer_stats(w);
+	EXPECT(stats->log_stats.blocks > 0);
+	reftable_writer_free(w);
+	w = NULL;
+
+	/* corrupt the data. */
+	buf.buf[50] ^= 0x99;
+
+	block_source_from_strbuf(&source, &buf);
+
+	err = init_reader(&rd, &source, "file.log");
+	EXPECT_ERR(err);
+
+	err = reftable_reader_seek_log(&rd, &it, "refname");
+	EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+	reftable_iterator_destroy(&it);
+
+	/* cleanup. */
+	strbuf_release(&buf);
+	reader_close(&rd);
+}
+
 static void test_table_read_write_sequential(void)
 {
 	char **names;
@@ -633,6 +699,7 @@ static void test_corrupt_table(void)
 
 int readwrite_test_main(int argc, const char *argv[])
 {
+	RUN_TEST(test_log_zlib_corruption);
 	RUN_TEST(test_corrupt_table);
 	RUN_TEST(test_corrupt_table_empty);
 	RUN_TEST(test_log_write_read);
-- 
gitgitgadget

```

## \[PATCH v3 03/11\] reftable: fix resource leak blocksource.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:02 
- Message: https://ratatoskr.run/git/2021/12/7915628.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered in the unlikely event of fstat() failing on an opened
file.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/blocksource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9aa..2605371c28d 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
 	}
 
 	err = fstat(fd, &st);
-	if (err < 0)
-		return -1;
+	if (err < 0) {
+		close(fd);
+		return REFTABLE_IO_ERROR;
+	}
 
 	p = reftable_calloc(sizeof(struct file_block_source));
 	p->size = st.st_size;
-- 
gitgitgadget

```

## \[PATCH v3 05/11\] reftable: ignore remove() return value in stack\_test.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:04 
- Message: https://ratatoskr.run/git/2021/12/7915629.md

```text
From: Han-Wen Nienhuys <redacted>

If the cleanup fails, there is nothing we can do.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index d628420e63a..4b7292945c3 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -89,7 +89,7 @@ static void test_read_file(void)
 		EXPECT(0 == strcmp(want[i], names[i]));
 	}
 	free_names(names);
-	remove(fn);
+	(void) remove(fn);
 }
 
 static void test_parse_names(void)
-- 
gitgitgadget

```

## \[PATCH v3 04/11\] reftable: check reftable\_stack\_auto\_compact() return value

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:06 
- Message: https://ratatoskr.run/git/2021/12/7915630.md

```text
From: Han-Wen Nienhuys <redacted>

Fixes a problem detected by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index eb0b7228b0c..d628420e63a 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -814,6 +814,7 @@ static void test_reftable_stack_auto_compaction(void)
 		EXPECT_ERR(err);
 
 		err = reftable_stack_auto_compact(st);
+		EXPECT_ERR(err);
 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
 	}
 
-- 
gitgitgadget

```

## \[PATCH v3 06/11\] reftable: fix resource warning

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:07 
- Message: https://ratatoskr.run/git/2021/12/7915631.md

```text
From: Han-Wen Nienhuys <redacted>

This would trigger in the unlikely event that we are compacting, and the next
available file handle is 0.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/reftable/stack.c b/reftable/stack.c
index df5021ebf08..10dfd370e8e 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -877,7 +877,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	struct strbuf new_table_path = STRBUF_INIT;
 	int err = 0;
 	int have_lock = 0;
-	int lock_file_fd = 0;
+	int lock_file_fd = -1;
 	int compact_count = last - first + 1;
 	char **listp = NULL;
 	char **delete_on_success =
@@ -911,7 +911,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	}
 	/* Don't want to write to the lock for now.  */
 	close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 
 	have_lock = 1;
 	err = stack_uptodate(st);
@@ -932,7 +932,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 
 		sublock_file_fd = open(subtab_lock.buf,
 				       O_EXCL | O_CREAT | O_WRONLY, 0644);
-		if (sublock_file_fd > 0) {
+		if (sublock_file_fd >= 0) {
 			close(sublock_file_fd);
 		} else if (sublock_file_fd < 0) {
 			if (errno == EEXIST) {
@@ -1013,7 +1013,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 		goto done;
 	}
 	err = close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 	if (err < 0) {
 		err = REFTABLE_IO_ERROR;
 		unlink(new_table_path.buf);
@@ -1050,9 +1050,9 @@ done:
 		listp++;
 	}
 	free_names(subtable_locks);
-	if (lock_file_fd > 0) {
+	if (lock_file_fd >= 0) {
 		close(lock_file_fd);
-		lock_file_fd = 0;
+		lock_file_fd = -1;
 	}
 	if (have_lock) {
 		unlink(lock_file_name.buf);
-- 
gitgitgadget

```

## \[PATCH v3 07/11\] reftable: fix NULL derefs in error paths

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:07 
- Message: https://ratatoskr.run/git/2021/12/7915632.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reader.c | 3 ++-
 reftable/stack.c  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..8d308d858f8 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -796,6 +796,7 @@ int reftable_reader_print_file(const char *tablename)
 	reftable_table_from_reader(&tab, r);
 	err = reftable_table_print(&tab);
 done:
-	reftable_reader_free(r);
+	if (r)
+		reftable_reader_free(r);
 	return err;
 }
diff --git a/reftable/stack.c b/reftable/stack.c
index 10dfd370e8e..eb03b6c74f6 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -707,7 +707,8 @@ done:
 	strbuf_release(&temp_tab_file_name);
 	strbuf_release(&tab_file_name);
 	strbuf_release(&next_name);
-	reftable_writer_free(wr);
+	if (wr)
+		reftable_writer_free(wr);
 	return err;
 }
 
-- 
gitgitgadget

```

## \[PATCH v3 08/11\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:09 
- Message: https://ratatoskr.run/git/2021/12/7915633.md

```text
From: Han-Wen Nienhuys <redacted>

This is a more pratical ordering when working on refactorings of the reftable
code.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 t/helper/test-reftable.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b789..f08d66df949 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	// test from simple to complex.
 	basics_test_main(argc, argv);
+	record_test_main(argc, argv);
 	block_test_main(argc, argv);
-	merged_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	pq_test_main(argc, argv);
-	record_test_main(argc, argv);
-	refname_test_main(argc, argv);
 	readwrite_test_main(argc, argv);
+	merged_test_main(argc, argv);
 	stack_test_main(argc, argv);
-	tree_test_main(argc, argv);
+	refname_test_main(argc, argv);
 	return 0;
 }
 
-- 
gitgitgadget

```

## \[PATCH v3 10/11\] reftable: handle null refnames in reftable\_ref\_record\_equal

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:10 
- Message: https://ratatoskr.run/git/2021/12/7915634.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 8536bd03aa9..8bbcbff1e69 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -1154,9 +1154,11 @@ int reftable_ref_record_equal(struct reftable_ref_record *a,
 			      struct reftable_ref_record *b, int hash_size)
 {
 	assert(hash_size > 0);
-	if (!(0 == strcmp(a->refname, b->refname) &&
-	      a->update_index == b->update_index &&
-	      a->value_type == b->value_type))
+	if (!null_streq(a->refname, b->refname))
+		return 0;
+
+	if (a->update_index != b->update_index ||
+	    a->value_type != b->value_type)
 		return 0;
 
 	switch (a->value_type) {
-- 
gitgitgadget

```

## \[PATCH v3 09/11\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:12 
- Message: https://ratatoskr.run/git/2021/12/7915635.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/readwrite_test.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 42caf0bde4c..f6546d7b8d1 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -663,7 +663,6 @@ static void test_write_key_order(void)
 	err = reftable_writer_add_ref(w, &refs[0]);
 	EXPECT_ERR(err);
 	err = reftable_writer_add_ref(w, &refs[1]);
-	printf("%d\n", err);
 	EXPECT(err == REFTABLE_API_ERROR);
 	reftable_writer_close(w);
 	reftable_writer_free(w);
-- 
gitgitgadget

```

## \[PATCH v3 11/11\] reftable: make reftable\_record a tagged union

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-13 16:02:13 
- Message: https://ratatoskr.run/git/2021/12/7915636.md

```text
From: Han-Wen Nienhuys <redacted>

This reduces the amount of glue code, because we don't need a void pointer or
vtable within the structure.

The only snag is that reftable_index_record contain a strbuf, so it cannot be
zero-initialized. To address this, use reftable_new_record() to return fresh
instance, given a record type. Since reftable_new_record() doesn't cause heap
allocation anymore, it should be balanced with reftable_record_release() rather
than reftable_record_destroy().

Thanks to Peff for the suggestion.

Helped-by: Jeff King [ref]
Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c       |   2 +-
 reftable/block_test.c  |  22 ++---
 reftable/generic.c     |  35 ++++----
 reftable/iter.c        |   4 +-
 reftable/merged.c      |  33 ++++----
 reftable/pq.c          |   3 +-
 reftable/pq_test.c     |  31 +++----
 reftable/reader.c      |  93 +++++++++++----------
 reftable/record.c      | 173 ++++++++++++++++-----------------------
 reftable/record.h      |  42 ++++------
 reftable/record_test.c | 181 +++++++++++++++++++++--------------------
 reftable/writer.c      |  40 +++++----
 12 files changed, 317 insertions(+), 342 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 37a85b9576d..872bedaa2f0 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -423,7 +423,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 done:
 	strbuf_release(&key);
 	strbuf_release(&next.last_key);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 
 	return err;
 }
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 4b3ea262dcb..fa2ee092ec0 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
 	struct block_writer bw = {
 		.last_key = STRBUF_INIT,
 	};
-	struct reftable_ref_record ref = { NULL };
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+	};
 	int i = 0;
 	int n;
 	struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
 	block.source = malloc_block_source();
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
-	reftable_record_from_ref(&rec, &ref);
 
 	for (i = 0; i < N; i++) {
 		char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
 		snprintf(name, sizeof(name), "branch%02d", i);
 		memset(hash, i, sizeof(hash));
 
-		ref.refname = name;
-		ref.value_type = REFTABLE_REF_VAL1;
-		ref.value.val1 = hash;
+		rec.u.ref.refname = name;
+		rec.u.ref.value_type = REFTABLE_REF_VAL1;
+		rec.u.ref.value.val1 = hash;
 
 		names[i] = xstrdup(name);
 		n = block_writer_add(&bw, &rec);
-		ref.refname = NULL;
-		ref.value_type = REFTABLE_REF_DELETION;
+		rec.u.ref.refname = NULL;
+		rec.u.ref.value_type = REFTABLE_REF_DELETION;
 		EXPECT(n == 0);
 	}
 
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
 		if (r > 0) {
 			break;
 		}
-		EXPECT_STREQ(names[j], ref.refname);
+		EXPECT_STREQ(names[j], rec.u.ref.refname);
 		j++;
 	}
 
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
 
-		EXPECT_STREQ(names[i], ref.refname);
+		EXPECT_STREQ(names[i], rec.u.ref.refname);
 
 		want.len--;
 		n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
 
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
-		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+		EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
 
 		block_iter_close(&it);
 	}
diff --git a/reftable/generic.c b/reftable/generic.c
index 7a8a738d860..4446b8ed360 100644
--- a/reftable/generic.c
+++ b/reftable/generic.c
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
 */
 
 #include "basics.h"
+#include "constants.h"
 #include "record.h"
 #include "generic.h"
 #include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
 int reftable_table_seek_ref(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+				       .u.ref = {
+					       .refname = (char *)name,
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
 int reftable_table_seek_log(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = ~((uint64_t)0),
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+				       .u.log = {
+					       .refname = (char *)name,
+					       .update_index = ~((uint64_t)0),
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
@@ -129,17 +128,19 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
 			       struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, ref);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
+	int err = iterator_next(it, &rec);
+	*ref = rec.u.ref;
+	return err;
 }
 
 int reftable_iterator_next_log(struct reftable_iterator *it,
 			       struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, log);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
+	int err = iterator_next(it, &rec);
+	*log = rec.u.log;
+	return err;
 }
 
 int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
diff --git a/reftable/iter.c b/reftable/iter.c
index 93d04f735b8..a8d174c0406 100644
--- a/reftable/iter.c
+++ b/reftable/iter.c
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
 				       struct reftable_record *rec)
 {
 	struct filtering_ref_iterator *fri = iter_arg;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 	int err = 0;
 	while (1) {
 		err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
 static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
 {
 	struct indexed_table_ref_iter *it = p;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 
 	while (1) {
 		int err = block_iter_next(&it->cur, rec);
diff --git a/reftable/merged.c b/reftable/merged.c
index e5b53da6db3..a7c0df91ff0 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
 
 		if (err > 0) {
 			reftable_iterator_destroy(&mi->stack[i]);
-			reftable_record_destroy(&rec);
+			reftable_record_release(&rec);
 		} else {
 			struct pq_entry e = {
 				.rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
 static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
 					       size_t idx)
 {
-	struct reftable_record rec = reftable_new_record(mi->typ);
 	struct pq_entry e = {
-		.rec = rec,
+		.rec = reftable_new_record(mi->typ),
 		.index = idx,
 	};
-	int err = iterator_next(&mi->stack[idx], &rec);
+	int err = iterator_next(&mi->stack[idx], &e.rec);
 	if (err < 0)
 		return err;
 
 	if (err > 0) {
 		reftable_iterator_destroy(&mi->stack[idx]);
-		reftable_record_destroy(&rec);
+		reftable_record_release(&e.rec);
 		return 0;
 	}
 
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
 		if (err < 0) {
 			return err;
 		}
-		reftable_record_destroy(&top.rec);
+		reftable_record_release(&top.rec);
 	}
 
 	reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
-	reftable_record_destroy(&entry.rec);
+	reftable_record_release(&entry.rec);
 	strbuf_release(&entry_key);
 	return 0;
 }
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
 				   struct reftable_iterator *it,
 				   const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
@@ -302,12 +302,13 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
 				      struct reftable_iterator *it,
 				      const char *name, uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
diff --git a/reftable/pq.c b/reftable/pq.c
index efc474017a2..96ca6dd37b3 100644
--- a/reftable/pq.c
+++ b/reftable/pq.c
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
 {
 	int i = 0;
+
 	if (pq->len == pq->cap) {
 		pq->cap = 2 * pq->cap + 1;
 		pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
 	int i = 0;
 	for (i = 0; i < pq->len; i++) {
-		reftable_record_destroy(&pq->heap[i].rec);
+		reftable_record_release(&pq->heap[i].rec);
 	}
 	FREE_AND_NULL(pq->heap);
 	pq->len = pq->cap = 0;
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index c9bb05e37b7..e0f5c584ada 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -31,7 +31,7 @@ static void test_pq(void)
 	int N = ARRAY_SIZE(names) - 1;
 
 	struct merged_iter_pqueue pq = { NULL };
-	const char *last = NULL;
+	char *last = NULL;
 
 	int i = 0;
 	for (i = 0; i < N; i++) {
@@ -42,12 +42,14 @@ static void test_pq(void)
 
 	i = 1;
 	do {
-		struct reftable_record rec =
-			reftable_new_record(BLOCK_TYPE_REF);
-		struct pq_entry e = { 0 };
-
-		reftable_record_as_ref(&rec)->refname = names[i];
-		e.rec = rec;
+		struct pq_entry e = {
+			.rec = {
+				.type = BLOCK_TYPE_REF,
+				.u.ref = {
+					.refname = names[i],
+				}
+			}
+		};
 		merged_iter_pqueue_add(&pq, e);
 		merged_iter_pqueue_check(pq);
 		i = (i * 7) % N;
@@ -55,19 +57,18 @@ static void test_pq(void)
 
 	while (!merged_iter_pqueue_is_empty(pq)) {
 		struct pq_entry e = merged_iter_pqueue_remove(&pq);
-		struct reftable_ref_record *ref =
-			reftable_record_as_ref(&e.rec);
-
+		struct reftable_record *rec = &e.rec;
 		merged_iter_pqueue_check(pq);
 
+		EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
 		if (last) {
-			EXPECT(strcmp(last, ref->refname) < 0);
+			EXPECT(strcmp(last, rec->u.ref.refname) < 0);
 		}
-		last = ref->refname;
-		ref->refname = NULL;
-		reftable_free(ref);
+		// this is names[i], so don't dealloc.
+		last = rec->u.ref.refname;
+		rec->u.ref.refname = NULL;
+		reftable_record_release(rec);
 	}
-
 	for (i = 0; i < N; i++) {
 		reftable_free(names[i]);
 	}
diff --git a/reftable/reader.c b/reftable/reader.c
index 8d308d858f8..4a25c1d44f4 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -239,8 +239,7 @@ static int table_iter_next_in_block(struct table_iter *ti,
 {
 	int res = block_iter_next(&ti->bi, rec);
 	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
-		((struct reftable_ref_record *)rec->data)->update_index +=
-			ti->r->min_update_index;
+		rec->u.ref.update_index += ti->r->min_update_index;
 	}
 
 	return res;
@@ -437,8 +436,7 @@ static int reader_start(struct reftable_reader *r, struct table_iter *ti,
 static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 			      struct reftable_record *want)
 {
-	struct reftable_record rec =
-		reftable_new_record(reftable_record_type(want));
+	struct reftable_record rec = reftable_new_record(reftable_record_type(want));
 	struct strbuf want_key = STRBUF_INIT;
 	struct strbuf got_key = STRBUF_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
@@ -475,7 +473,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 
 done:
 	block_iter_close(&next.bi);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 	strbuf_release(&want_key);
 	strbuf_release(&got_key);
 	return err;
@@ -485,34 +483,36 @@ static int reader_seek_indexed(struct reftable_reader *r,
 			       struct reftable_iterator *it,
 			       struct reftable_record *rec)
 {
-	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-	struct reftable_record want_index_rec = { NULL };
-	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-	struct reftable_record index_result_rec = { NULL };
+	struct reftable_record want_index = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT }
+	};
+	struct reftable_record index_result = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	struct table_iter index_iter = TABLE_ITER_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
 	int err = 0;
 
-	reftable_record_key(rec, &want_index.last_key);
-	reftable_record_from_index(&want_index_rec, &want_index);
-	reftable_record_from_index(&index_result_rec, &index_result);
-
+	reftable_record_key(rec, &want_index.u.idx.last_key);
 	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
 	if (err < 0)
 		goto done;
 
-	err = reader_seek_linear(r, &index_iter, &want_index_rec);
+	err = reader_seek_linear(r, &index_iter, &want_index);
 	while (1) {
-		err = table_iter_next(&index_iter, &index_result_rec);
+		err = table_iter_next(&index_iter, &index_result);
 		table_iter_block_done(&index_iter);
 		if (err != 0)
 			goto done;
 
-		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+					   0);
 		if (err != 0)
 			goto done;
 
-		err = block_iter_seek(&next.bi, &want_index.last_key);
+		err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
 		if (err < 0)
 			goto done;
 
@@ -540,8 +540,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
 done:
 	block_iter_close(&next.bi);
 	table_iter_close(&index_iter);
-	reftable_record_release(&want_index_rec);
-	reftable_record_release(&index_result_rec);
+	reftable_record_release(&want_index);
+	reftable_record_release(&index_result);
 	return err;
 }
 
@@ -549,8 +549,7 @@ static int reader_seek_internal(struct reftable_reader *r,
 				struct reftable_iterator *it,
 				struct reftable_record *rec)
 {
-	struct reftable_reader_offsets *offs =
-		reader_offsets_for(r, reftable_record_type(rec));
+	struct reftable_reader_offsets *offs = reader_offsets_for(r, reftable_record_type(rec));
 	uint64_t idx = offs->index_offset;
 	struct table_iter ti = TABLE_ITER_INIT;
 	int err = 0;
@@ -590,11 +589,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 int reftable_reader_seek_ref(struct reftable_reader *r,
 			     struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return reader_seek(r, it, &rec);
 }
 
@@ -602,12 +602,13 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
 				struct reftable_iterator *it, const char *name,
 				uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return reader_seek(r, it, &rec);
 }
 
@@ -649,31 +650,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 					    struct reftable_iterator *it,
 					    uint8_t *oid)
 {
-	struct reftable_obj_record want = {
-		.hash_prefix = oid,
-		.hash_prefix_len = r->object_id_len,
+	struct reftable_record want = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = oid,
+			.hash_prefix_len = r->object_id_len,
+		},
 	};
-	struct reftable_record want_rec = { NULL };
 	struct reftable_iterator oit = { NULL };
-	struct reftable_obj_record got = { NULL };
-	struct reftable_record got_rec = { NULL };
+	struct reftable_record got = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = { 0 },
+	};
 	int err = 0;
 	struct indexed_table_ref_iter *itr = NULL;
 
 	/* Look through the reverse index. */
-	reftable_record_from_obj(&want_rec, &want);
-	err = reader_seek(r, &oit, &want_rec);
+	err = reader_seek(r, &oit, &want);
 	if (err != 0)
 		goto done;
 
 	/* read out the reftable_obj_record */
-	reftable_record_from_obj(&got_rec, &got);
-	err = iterator_next(&oit, &got_rec);
+	err = iterator_next(&oit, &got);
 	if (err < 0)
 		goto done;
 
-	if (err > 0 ||
-	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+	if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+			      r->object_id_len)) {
 		/* didn't find it; return empty iterator */
 		iterator_set_empty(it);
 		err = 0;
@@ -681,15 +684,15 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 	}
 
 	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
-					 got.offsets, got.offset_len);
+					 got.u.obj.offsets, got.u.obj.offset_len);
 	if (err < 0)
 		goto done;
-	got.offsets = NULL;
+	got.u.obj.offsets = NULL;
 	iterator_from_indexed_table_ref_iter(it, itr);
 
 done:
 	reftable_iterator_destroy(&oit);
-	reftable_record_release(&got_rec);
+	reftable_record_release(&got);
 	return err;
 }
 
diff --git a/reftable/record.c b/reftable/record.c
index 8bbcbff1e69..f766ee20ace 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "basics.h"
 
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
 int get_var_int(uint64_t *dest, struct string_view *in)
 {
 	int ptr = 0;
@@ -926,58 +930,6 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.is_deletion = &reftable_log_record_is_deletion_void,
 };
 
-struct reftable_record reftable_new_record(uint8_t typ)
-{
-	struct reftable_record rec = { NULL };
-	switch (typ) {
-	case BLOCK_TYPE_REF: {
-		struct reftable_ref_record *r =
-			reftable_calloc(sizeof(struct reftable_ref_record));
-		reftable_record_from_ref(&rec, r);
-		return rec;
-	}
-
-	case BLOCK_TYPE_OBJ: {
-		struct reftable_obj_record *r =
-			reftable_calloc(sizeof(struct reftable_obj_record));
-		reftable_record_from_obj(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_LOG: {
-		struct reftable_log_record *r =
-			reftable_calloc(sizeof(struct reftable_log_record));
-		reftable_record_from_log(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_INDEX: {
-		struct reftable_index_record empty = { .last_key =
-							       STRBUF_INIT };
-		struct reftable_index_record *r =
-			reftable_calloc(sizeof(struct reftable_index_record));
-		*r = empty;
-		reftable_record_from_index(&rec, r);
-		return rec;
-	}
-	}
-	abort();
-	return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
-	void *p = rec->data;
-	rec->data = NULL;
-	return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
-	reftable_record_release(rec);
-	reftable_free(reftable_record_yield(rec));
-}
-
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
 {
 	const struct reftable_index_record *rec = r;
@@ -1055,91 +1007,52 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 {
-	rec->ops->key(rec->data, dest);
+	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
 uint8_t reftable_record_type(struct reftable_record *rec)
 {
-	return rec->ops->type;
+	return rec->type;
 }
 
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
 			   int hash_size)
 {
-	return rec->ops->encode(rec->data, dest, hash_size);
+	return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+						   dest, hash_size);
 }
 
 void reftable_record_copy_from(struct reftable_record *rec,
 			       struct reftable_record *src, int hash_size)
 {
-	assert(src->ops->type == rec->ops->type);
+	assert(src->type == rec->type);
 
-	rec->ops->copy_from(rec->data, src->data, hash_size);
+	reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+					       reftable_record_data(src),
+					       hash_size);
 }
 
 uint8_t reftable_record_val_type(struct reftable_record *rec)
 {
-	return rec->ops->val_type(rec->data);
+	return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   uint8_t extra, struct string_view src, int hash_size)
 {
-	return rec->ops->decode(rec->data, key, extra, src, hash_size);
+	return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+						   key, extra, src, hash_size);
 }
 
 void reftable_record_release(struct reftable_record *rec)
 {
-	rec->ops->release(rec->data);
+	reftable_record_vtable(rec)->release(reftable_record_data(rec));
 }
 
 int reftable_record_is_deletion(struct reftable_record *rec)
 {
-	return rec->ops->is_deletion(rec->data);
-}
-
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *ref_rec)
-{
-	assert(!rec->ops);
-	rec->data = ref_rec;
-	rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *obj_rec)
-{
-	assert(!rec->ops);
-	rec->data = obj_rec;
-	rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *index_rec)
-{
-	assert(!rec->ops);
-	rec->data = index_rec;
-	rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *log_rec)
-{
-	assert(!rec->ops);
-	rec->data = log_rec;
-	rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
-	return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
-	return rec->data;
+	return reftable_record_vtable(rec)->is_deletion(
+		reftable_record_data(rec));
 }
 
 static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1212,3 +1125,53 @@ void string_view_consume(struct string_view *s, int n)
 	s->buf += n;
 	s->len -= n;
 }
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &rec->u.ref;
+	case BLOCK_TYPE_LOG:
+		return &rec->u.log;
+	case BLOCK_TYPE_INDEX:
+		return &rec->u.idx;
+	case BLOCK_TYPE_OBJ:
+		return &rec->u.obj;
+	}
+	abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &reftable_ref_record_vtable;
+	case BLOCK_TYPE_LOG:
+		return &reftable_log_record_vtable;
+	case BLOCK_TYPE_INDEX:
+		return &reftable_index_record_vtable;
+	case BLOCK_TYPE_OBJ:
+		return &reftable_obj_record_vtable;
+	}
+	abort();
+}
+
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+	struct reftable_record clean_idx = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.last_key = STRBUF_INIT,
+		},
+	};
+	struct reftable_record clean = {
+		.type = typ,
+	};
+
+	if (typ == BLOCK_TYPE_INDEX) {
+		return clean_idx;
+	}
+
+	return clean;
+}
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf4..407cdaba018 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -60,16 +60,10 @@ struct reftable_record_vtable {
 	int (*is_deletion)(const void *rec);
 };
 
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
-	void *data;
-	struct reftable_record_vtable *ops;
-};
-
 /* returns true for recognized block types. Block start with the block type. */
 int reftable_is_block_type(uint8_t typ);
 
-/* creates a malloced record of the given type. Dispose with record_destroy */
+/* return an initialized record for the given type */
 struct reftable_record reftable_new_record(uint8_t typ);
 
 /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
@@ -97,6 +91,22 @@ struct reftable_obj_record {
 	int offset_len;
 };
 
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_new_record() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+	uint8_t type;
+	union {
+		struct reftable_ref_record ref;
+		struct reftable_log_record log;
+		struct reftable_obj_record obj;
+		struct reftable_index_record idx;
+	} u;
+};
+
 /* see struct record_vtable */
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
@@ -111,25 +121,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   int hash_size);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
 /* for qsort. */
 int reftable_ref_record_compare_name(const void *a, const void *b);
 
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace41..96f65035c1b 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -16,24 +16,22 @@
 
 static void test_copy(struct reftable_record *rec)
 {
-	struct reftable_record copy =
-		reftable_new_record(reftable_record_type(rec));
+	struct reftable_record copy = reftable_new_record(reftable_record_type(rec));
+
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-	switch (reftable_record_type(&copy)) {
+	switch (copy.type) {
 	case BLOCK_TYPE_REF:
-		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-						 reftable_record_as_ref(rec),
+		EXPECT(reftable_ref_record_equal(&copy.u.ref, &rec->u.ref,
 						 GIT_SHA1_RAWSZ));
 		break;
 	case BLOCK_TYPE_LOG:
-		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-						 reftable_record_as_log(rec),
+		EXPECT(reftable_log_record_equal(&copy.u.log, &rec->u.log,
 						 GIT_SHA1_RAWSZ));
 		break;
 	}
-	reftable_record_destroy(&copy);
+	reftable_record_release(&copy);
 }
 
 static void test_varint_roundtrip(void)
@@ -106,61 +104,60 @@ static void test_reftable_ref_record_roundtrip(void)
 	int i = 0;
 
 	for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-		struct reftable_ref_record in = { NULL };
-		struct reftable_ref_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_REF,
+		};
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_REF
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_record rec = { NULL };
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-
 		int n, m;
 
-		in.value_type = i;
+		in.u.ref.value_type = i;
 		switch (i) {
 		case REFTABLE_REF_DELETION:
 			break;
 		case REFTABLE_REF_VAL1:
-			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val1, 1);
+			in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val1, 1);
 			break;
 		case REFTABLE_REF_VAL2:
-			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.value, 1);
-			in.value.val2.target_value =
+			in.u.ref.value.val2.value =
+				reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val2.value, 1);
+			in.u.ref.value.val2.target_value =
 				reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.target_value, 2);
+			set_hash(in.u.ref.value.val2.target_value, 2);
 			break;
 		case REFTABLE_REF_SYMREF:
-			in.value.symref = xstrdup("target");
+			in.u.ref.value.symref = xstrdup("target");
 			break;
 		}
-		in.refname = xstrdup("refs/heads/master");
+		in.u.ref.refname = xstrdup("refs/heads/master");
 
-		reftable_record_from_ref(&rec, &in);
-		test_copy(&rec);
+		test_copy(&in);
 
-		EXPECT(reftable_record_val_type(&rec) == i);
+		EXPECT(reftable_record_val_type(&in) == i);
 
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
 
 		/* decode into a non-zero reftable_record to test for leaks. */
-
-		reftable_record_from_ref(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, i, dest,
-					   GIT_SHA1_RAWSZ);
+		m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
-		reftable_record_release(&rec_out);
+		EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+						 GIT_SHA1_RAWSZ));
+		reftable_record_release(&in);
 
 		strbuf_release(&key);
-		reftable_ref_record_release(&in);
+		reftable_record_release(&out);
 	}
 }
 
@@ -213,7 +210,9 @@ static void test_reftable_log_record_roundtrip(void)
 	set_test_hash(in[0].value.update.new_hash, 1);
 	set_test_hash(in[0].value.update.old_hash, 2);
 	for (i = 0; i < ARRAY_SIZE(in); i++) {
-		struct reftable_record rec = { NULL };
+		struct reftable_record rec = {
+			.type = BLOCK_TYPE_LOG
+		};
 		struct strbuf key = STRBUF_INIT;
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
@@ -221,23 +220,25 @@ static void test_reftable_log_record_roundtrip(void)
 			.len = sizeof(buffer),
 		};
 		/* populate out, to check for leaks. */
-		struct reftable_log_record out = {
-			.refname = xstrdup("old name"),
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = {
-				.update = {
-					.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.name = xstrdup("old name"),
-					.email = xstrdup("old@email"),
-					.message = xstrdup("old message"),
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_LOG,
+			.u.log = {
+				.refname = xstrdup("old name"),
+				.value_type = REFTABLE_LOG_UPDATE,
+				.value = {
+					.update = {
+						.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.name = xstrdup("old name"),
+						.email = xstrdup("old@email"),
+						.message = xstrdup("old message"),
+					},
 				},
 			},
 		};
-		struct reftable_record rec_out = { NULL };
 		int n, m, valtype;
 
-		reftable_record_from_log(&rec, &in[i]);
+		rec.u.log = in[i];
 
 		test_copy(&rec);
 
@@ -245,16 +246,16 @@ static void test_reftable_log_record_roundtrip(void)
 
 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n >= 0);
-		reftable_record_from_log(&rec_out, &out);
 		valtype = reftable_record_val_type(&rec);
-		m = reftable_record_decode(&rec_out, key, valtype, dest,
+		m = reftable_record_decode(&out, key, valtype, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+		EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+						 GIT_SHA1_RAWSZ));
 		reftable_log_record_release(&in[i]);
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
@@ -322,47 +323,52 @@ static void test_reftable_obj_record_roundtrip(void)
 					       } };
 	int i = 0;
 	for (i = 0; i < ARRAY_SIZE(recs); i++) {
-		struct reftable_obj_record in = recs[i];
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-		struct reftable_record rec = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj = recs[i],
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_obj_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_OBJ
+		};
 		int n, m;
 		uint8_t extra;
 
-		reftable_record_from_obj(&rec, &in);
-		test_copy(&rec);
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		test_copy(&in);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
-		extra = reftable_record_val_type(&rec);
-		reftable_record_from_obj(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, extra, dest,
+		extra = reftable_record_val_type(&in);
+		m = reftable_record_decode(&out, key, extra, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-		EXPECT(in.offset_len == out.offset_len);
-
-		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-			       in.hash_prefix_len));
-		EXPECT(0 == memcmp(in.offsets, out.offsets,
-				   sizeof(uint64_t) * in.offset_len));
+		EXPECT(in.u.obj.hash_prefix_len == out.u.obj.hash_prefix_len);
+		EXPECT(in.u.obj.offset_len == out.u.obj.offset_len);
+		if (in.u.obj.hash_prefix_len)
+			EXPECT(!memcmp(in.u.obj.hash_prefix, out.u.obj.hash_prefix,
+				       in.u.obj.hash_prefix_len));
+		if (in.u.obj.offset_len)
+			EXPECT(!memcmp(in.u.obj.offsets, out.u.obj.offsets,
+					   sizeof(uint64_t) * in.u.obj.offset_len));
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
 static void test_reftable_index_record_roundtrip(void)
 {
-	struct reftable_index_record in = {
-		.offset = 42,
-		.last_key = STRBUF_INIT,
+	struct reftable_record in = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.offset = 42,
+			.last_key = STRBUF_INIT,
+		},
 	};
 	uint8_t buffer[1024] = { 0 };
 	struct string_view dest = {
@@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void)
 		.len = sizeof(buffer),
 	};
 	struct strbuf key = STRBUF_INIT;
-	struct reftable_record rec = { NULL };
-	struct reftable_index_record out = { .last_key = STRBUF_INIT };
-	struct reftable_record out_rec = { NULL };
+	struct reftable_record out = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	int n, m;
 	uint8_t extra;
 
-	strbuf_addstr(&in.last_key, "refs/heads/master");
-	reftable_record_from_index(&rec, &in);
-	reftable_record_key(&rec, &key);
-	test_copy(&rec);
+	strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+	reftable_record_key(&in, &key);
+	test_copy(&in);
 
-	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
-	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+	EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+	n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 	EXPECT(n > 0);
 
-	extra = reftable_record_val_type(&rec);
-	reftable_record_from_index(&out_rec, &out);
-	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+	extra = reftable_record_val_type(&in);
+	m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
 	EXPECT(m == n);
 
-	EXPECT(in.offset == out.offset);
+	EXPECT(in.u.idx.offset == out.u.idx.offset);
 
-	reftable_record_release(&out_rec);
+	reftable_record_release(&out);
 	strbuf_release(&key);
-	strbuf_release(&in.last_key);
+	strbuf_release(&in.u.idx.last_key);
 }
 
 int record_test_main(int argc, const char *argv[])
diff --git a/reftable/writer.c b/reftable/writer.c
index 3ca721e9f64..990f78ff7ed 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -251,8 +251,10 @@ done:
 int reftable_writer_add_ref(struct reftable_writer *w,
 			    struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	struct reftable_ref_record copy = *ref;
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = *ref,
+	};
 	int err = 0;
 
 	if (ref->refname == NULL)
@@ -261,8 +263,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
 	    ref->update_index > w->max_update_index)
 		return REFTABLE_API_ERROR;
 
-	reftable_record_from_ref(&rec, &copy);
-	copy.update_index -= w->min_update_index;
+	rec.u.ref.update_index -= w->min_update_index;
 
 	err = writer_add_record(w, &rec);
 	if (err < 0)
@@ -301,7 +302,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
 static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 					    struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = *log,
+	};
 	if (w->block_writer &&
 	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
 		int err = writer_finish_public_section(w);
@@ -311,8 +315,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 
 	w->next -= w->pending_padding;
 	w->pending_padding = 0;
-
-	reftable_record_from_log(&rec, log);
 	return writer_add_record(w, &rec);
 }
 
@@ -393,8 +395,10 @@ static int writer_finish_section(struct reftable_writer *w)
 		w->index_len = 0;
 		w->index_cap = 0;
 		for (i = 0; i < idx_len; i++) {
-			struct reftable_record rec = { NULL };
-			reftable_record_from_index(&rec, idx + i);
+			struct reftable_record rec = {
+				.type = BLOCK_TYPE_INDEX,
+				.u.idx = idx[i],
+			};
 			if (block_writer_add(w->block_writer, &rec) == 0) {
 				continue;
 			}
@@ -462,17 +466,18 @@ static void write_object_record(void *void_arg, void *key)
 {
 	struct write_record_arg *arg = void_arg;
 	struct obj_index_tree_node *entry = key;
-	struct reftable_obj_record obj_rec = {
-		.hash_prefix = (uint8_t *)entry->hash.buf,
-		.hash_prefix_len = arg->w->stats.object_id_len,
-		.offsets = entry->offsets,
-		.offset_len = entry->offset_len,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = (uint8_t *)entry->hash.buf,
+			.hash_prefix_len = arg->w->stats.object_id_len,
+			.offsets = entry->offsets,
+			.offset_len = entry->offset_len,
+		}
 	};
-	struct reftable_record rec = { NULL };
 	if (arg->err < 0)
 		goto done;
 
-	reftable_record_from_obj(&rec, &obj_rec);
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
@@ -485,7 +490,8 @@ static void write_object_record(void *void_arg, void *key)
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
-	obj_rec.offset_len = 0;
+
+	rec.u.obj.offset_len = 0;
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 
 	/* Should be able to write into a fresh block. */
-- 
gitgitgadget
```

## Re: \[PATCH v3 02/11\] reftable: fix resource leak in error path

- From: Ævar Arnfjörð Bjarmason \<hidden\>
- Date: 2021-12-13 16:22:02 
- Message: https://ratatoskr.run/git/2021/12/7915640.md

```text

On Mon, Dec 13 2021, Han-Wen Nienhuys via GitGitGadget wrote:

> From: Han-Wen Nienhuys <redacted>
> [...]
> -	return 0;
> +done:
> +	if (uncompressed) {
> +		reftable_free(uncompressed);
> +	}
> +	return err;
>  }

Other things in the codebase don't check for NULL before feeding things
to reftable_free(), and our own free() has a coccicheck rule to catch
this sort of code, we should probably add reftable_free to that list...

>  
>  static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
> diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
> index 5f6bcc2f775..42caf0bde4c 100644
> --- a/reftable/readwrite_test.c
> +++ b/reftable/readwrite_test.c
> @@ -254,6 +254,72 @@ static void test_log_write_read(void)
>  	reader_close(&rd);
>  }
>  
> +static void test_log_zlib_corruption(void)
> +{
> +	struct reftable_write_options opts = {
> +		.block_size = 256,
> +	};
> +	struct reftable_iterator it = { NULL };
> +	struct reftable_reader rd = { NULL };
> +	struct reftable_block_source source = { NULL };

Nit: It doesn't matter for semantics, but usually we use "{ 0 }", and
your 1/11 does too. Would be better to do that here for consistency.

> +	for (i = 0; i < sizeof(message)-1; i++) {
> +		message[i] = (uint8_t)(rand() % 64 + ' ');
> +	}

style: braces not needede.
```

## Re: \[PATCH v3 07/11\] reftable: fix NULL derefs in error paths

- From: Ævar Arnfjörð Bjarmason \<hidden\>
- Date: 2021-12-13 16:25:21 
- Message: https://ratatoskr.run/git/2021/12/7915641.md

```text

On Mon, Dec 13 2021, Han-Wen Nienhuys via GitGitGadget wrote:

> From: Han-Wen Nienhuys <redacted>
>
> Spotted by Coverity.
>
> Signed-off-by: Han-Wen Nienhuys <redacted>
> ---
>  reftable/reader.c | 3 ++-
>  reftable/stack.c  | 3 ++-
>  2 files changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/reftable/reader.c b/reftable/reader.c
> index 006709a645a..8d308d858f8 100644
> --- a/reftable/reader.c
> +++ b/reftable/reader.c
> @@ -796,6 +796,7 @@ int reftable_reader_print_file(const char *tablename)
>  	reftable_table_from_reader(&tab, r);
>  	err = reftable_table_print(&tab);
>  done:
> -	reftable_reader_free(r);
> +	if (r)
> +		reftable_reader_free(r);
>  	return err;
>  }
> diff --git a/reftable/stack.c b/reftable/stack.c
> index 10dfd370e8e..eb03b6c74f6 100644
> --- a/reftable/stack.c
> +++ b/reftable/stack.c
> @@ -707,7 +707,8 @@ done:
>  	strbuf_release(&temp_tab_file_name);
>  	strbuf_release(&tab_file_name);
>  	strbuf_release(&next_name);
> -	reftable_writer_free(wr);
> +	if (wr)
> +		reftable_writer_free(wr);
>  	return err;
>  }

I'd think this would be better:

diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..3eec915830d 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -641,7 +641,8 @@ int reftable_new_reader(struct reftable_reader **p,
 
 void reftable_reader_free(struct reftable_reader *r)
 {
-       reader_close(r);
+       if (r)
+               reader_close(r);
        reftable_free(r);
 }

No?
```

## Re: \[PATCH v3 08/11\] reftable: order unittests by complexity

- From: Ævar Arnfjörð Bjarmason \<hidden\>
- Date: 2021-12-13 16:26:22 
- Message: https://ratatoskr.run/git/2021/12/7915642.md

```text

On Mon, Dec 13 2021, Han-Wen Nienhuys via GitGitGadget wrote:

> From: Han-Wen Nienhuys <redacted>
>
> This is a more pratical ordering when working on refactorings of the reftable
> code.
>
> Signed-off-by: Han-Wen Nienhuys <redacted>
> ---
>  t/helper/test-reftable.c | 9 +++++----
>  1 file changed, 5 insertions(+), 4 deletions(-)
>
> diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
> index 26b03d7b789..f08d66df949 100644
> --- a/t/helper/test-reftable.c
> +++ b/t/helper/test-reftable.c
> @@ -3,15 +3,16 @@
>  
>  int cmd__reftable(int argc, const char **argv)
>  {
> +	// test from simple to complex.

A C99 comment, which I don't mind, but is explicitly forbidden by the
style guide (and when I mentioned recently that we didn't have any
portability reasons not to use these, there wasn't much/any interest in
changing that...).
```

## Re: \[PATCH v3 09/11\] reftable: drop stray printf in readwrite\_test

- From: Ævar Arnfjörð Bjarmason \<hidden\>
- Date: 2021-12-13 16:27:02 
- Message: https://ratatoskr.run/git/2021/12/7915643.md

```text

On Mon, Dec 13 2021, Han-Wen Nienhuys via GitGitGadget wrote:

> From: Han-Wen Nienhuys <redacted>
>
> Signed-off-by: Han-Wen Nienhuys <redacted>
> ---
>  reftable/readwrite_test.c | 1 -
>  1 file changed, 1 deletion(-)
>
> diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
> index 42caf0bde4c..f6546d7b8d1 100644
> --- a/reftable/readwrite_test.c
> +++ b/reftable/readwrite_test.c
> @@ -663,7 +663,6 @@ static void test_write_key_order(void)
>  	err = reftable_writer_add_ref(w, &refs[0]);
>  	EXPECT_ERR(err);
>  	err = reftable_writer_add_ref(w, &refs[1]);
> -	printf("%d\n", err);
>  	EXPECT(err == REFTABLE_API_ERROR);
>  	reftable_writer_close(w);
>  	reftable_writer_free(w);

Is this something coverity raised and I'm missing why it's bad (per the
CL), or just a stray unrelated cleanup?
```

## Re: \[PATCH v3 02/11\] reftable: fix resource leak in error path

- From: Han-Wen Nienhuys \<hidden\>
- Date: 2021-12-13 16:44:59 
- Message: https://ratatoskr.run/git/2021/12/7915645.md

```text
On Mon, Dec 13, 2021 at 5:21 PM Ævar Arnfjörð Bjarmason
[ref] wrote:
> Other things in the codebase don't check for NULL before feeding things
> to reftable_free(), and our own free() has a coccicheck rule to catch
> this sort of code, we should probably add reftable_free to that list...

Thanks, changed.

> > +     struct reftable_block_source source = { NULL };
>
> Nit: It doesn't matter for semantics, but usually we use "{ 0 }", and
> your 1/11 does too. Would be better to do that here for consistency.

I got a past review where someone complained about this. I don't mind
either way, but would rather not flipflop.

>
> > +     for (i = 0; i < sizeof(message)-1; i++) {
> > +             message[i] = (uint8_t)(rand() % 64 + ' ');
> > +     }
>
> style: braces not needede.

Done.

-- 
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
```

## Re: \[PATCH v3 08/11\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys \<hidden\>
- Date: 2021-12-13 16:45:43 
- Message: https://ratatoskr.run/git/2021/12/7915646.md

```text
On Mon, Dec 13, 2021 at 5:26 PM Ævar Arnfjörð Bjarmason
[ref] wrote:
> A C99 comment, which I don't mind, but is explicitly forbidden by the
> style guide (and when I mentioned recently that we didn't have any
> portability reasons not to use these, there wasn't much/any interest in
> changing that...).

sorry, I missed this comment in a previous round. Fixed now.

-- 
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
```

## Re: \[PATCH v3 09/11\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys \<hidden\>
- Date: 2021-12-13 16:46:41 
- Message: https://ratatoskr.run/git/2021/12/7915648.md

```text
On Mon, Dec 13, 2021 at 5:26 PM Ævar Arnfjörð Bjarmason
[ref] wrote:
> Is this something coverity raised and I'm missing why it's bad (per the
> CL), or just a stray unrelated cleanup?

I spotted it while working on this series. It prints something
unexpected into the output of the unittest, so better remove it.

-- 
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
```

## \[PATCH v4 00/11\] Reftable coverity fixes

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:01 
- Message: https://ratatoskr.run/git/2021/12/7915724.md

```text
This series was targeted to 'next'.

This addresses some complaints from Coverity that Peff reported.

v3:

 * revert some changes suggested by stolee
 * add unittest for zlib corruption.

Han-Wen Nienhuys (11):
  reftable: fix OOB stack write in print functions
  reftable: fix resource leak in block.c error path
  reftable: fix resource leak blocksource.c
  reftable: check reftable_stack_auto_compact() return value
  reftable: ignore remove() return value in stack_test.c
  reftable: fix resource warning
  reftable: all xxx_free() functions accept NULL arguments
  reftable: order unittests by complexity
  reftable: drop stray printf in readwrite_test
  reftable: handle null refnames in reftable_ref_record_equal
  reftable: make reftable_record a tagged union

 reftable/block.c          |  28 +++---
 reftable/block_test.c     |  22 ++---
 reftable/blocksource.c    |   6 +-
 reftable/generic.c        |  35 ++++----
 reftable/iter.c           |   4 +-
 reftable/merged.c         |  33 +++----
 reftable/pq.c             |   3 +-
 reftable/pq_test.c        |  31 +++----
 reftable/reader.c         |  95 ++++++++++----------
 reftable/readwrite_test.c |  67 +++++++++++++-
 reftable/record.c         | 185 ++++++++++++++++----------------------
 reftable/record.h         |  42 ++++-----
 reftable/record_test.c    | 181 +++++++++++++++++++------------------
 reftable/stack.c          |  12 +--
 reftable/stack_test.c     |   3 +-
 reftable/writer.c         |  42 +++++----
 t/helper/test-reftable.c  |   9 +-
 17 files changed, 428 insertions(+), 370 deletions(-)


base-commit: fae76fe5da3df25d752f2251b7ccda3f62813aa9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1152%2Fhanwen%2Freftable-coverity-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1152/hanwen/reftable-coverity-v4
Pull-Request: https://github.com/git/git/pull/1152

Range-diff vs v3:

  1:  1c1a3ff92bd =  1:  1c1a3ff92bd reftable: fix OOB stack write in print functions
  2:  975a570d388 !  2:  923293b79b4 reftable: fix resource leak in error path
     @@ Metadata
      Author: Han-Wen Nienhuys [ref]
      
       ## Commit message ##
     -    reftable: fix resource leak in error path
     +    reftable: fix resource leak in block.c error path
      
          Add test coverage for corrupt zlib data.
      
     @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
       
      -	return 0;
      +done:
     -+	if (uncompressed) {
     -+		reftable_free(uncompressed);
     -+	}
     ++	reftable_free(uncompressed);
      +	return err;
       }
       
     @@ reftable/readwrite_test.c: static void test_log_write_read(void)
      +	struct reftable_write_options opts = {
      +		.block_size = 256,
      +	};
     -+	struct reftable_iterator it = { NULL };
     -+	struct reftable_reader rd = { NULL };
     -+	struct reftable_block_source source = { NULL };
     ++	struct reftable_iterator it = { 0 };
     ++	struct reftable_reader rd = { 0 };
     ++	struct reftable_block_source source = { 0 };
      +	struct strbuf buf = STRBUF_INIT;
      +	struct reftable_writer *w =
      +		reftable_new_writer(&strbuf_add_void, &buf, &opts);
     @@ reftable/readwrite_test.c: static void test_log_write_read(void)
      +		},
      +	};
      +
     -+	for (i = 0; i < sizeof(message)-1; i++) {
     ++	for (i = 0; i < sizeof(message)-1; i++)
      +		message[i] = (uint8_t)(rand() % 64 + ' ');
     -+	}
      +
      +	reftable_writer_set_limits(w, 1, 1);
      +
  3:  0b9c7176d71 =  3:  7a914f77756 reftable: fix resource leak blocksource.c
  4:  1dda4ee717f =  4:  51b4a17a2e1 reftable: check reftable_stack_auto_compact() return value
  5:  36858e2070b =  5:  43989afcb5a reftable: ignore remove() return value in stack_test.c
  6:  80b1988b885 =  6:  1c7f15d811c reftable: fix resource warning
  7:  2939286924c <  -:  ----------- reftable: fix NULL derefs in error paths
  -:  ----------- >  7:  47ba5ddceb8 reftable: all xxx_free() functions accept NULL arguments
  8:  9dce18d7349 !  8:  aba8b8113ad reftable: order unittests by complexity
     @@ Metadata
       ## Commit message ##
          reftable: order unittests by complexity
      
     -    This is a more pratical ordering when working on refactorings of the reftable
     +    This is a more practical ordering when working on refactorings of the reftable
          code.
      
          Signed-off-by: Han-Wen Nienhuys [ref]
     @@ t/helper/test-reftable.c
       
       int cmd__reftable(int argc, const char **argv)
       {
     -+	// test from simple to complex.
     ++	/* test from simple to complex. */
       	basics_test_main(argc, argv);
      +	record_test_main(argc, argv);
       	block_test_main(argc, argv);
  9:  6b0af68f0b9 =  9:  f2af404d7f1 reftable: drop stray printf in readwrite_test
 10:  bff85cb0809 = 10:  d2ce1bfef9f reftable: handle null refnames in reftable_ref_record_equal
 11:  b3e592b9c27 = 11:  0ffc1cf0e42 reftable: make reftable_record a tagged union

-- 
gitgitgadget
```

## \[PATCH v4 01/11\] reftable: fix OOB stack write in print functions

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:04 
- Message: https://ratatoskr.run/git/2021/12/7915725.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc6..8536bd03aa9 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -254,7 +254,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 void reftable_ref_record_print(struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
-	char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 	switch (ref->value_type) {
 	case REFTABLE_REF_SYMREF:
@@ -586,7 +586,7 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 void reftable_log_record_print(struct reftable_log_record *log,
 			       uint32_t hash_id)
 {
-	char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-- 
gitgitgadget

```

## \[PATCH v4 02/11\] reftable: fix resource leak in block.c error path

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:11 
- Message: https://ratatoskr.run/git/2021/12/7915726.md

```text
From: Han-Wen Nienhuys <redacted>

Add test coverage for corrupt zlib data.

This problem was discovered by a Coverity scan.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c          | 26 +++++++++------
 reftable/readwrite_test.c | 66 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+), 9 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c947..6c8e8705205 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	uint32_t full_block_size = table_block_size;
 	uint8_t typ = block->data[header_off];
 	uint32_t sz = get_be24(block->data + header_off + 1);
-
+	int err = 0;
 	uint16_t restart_count = 0;
 	uint32_t restart_start = 0;
 	uint8_t *restart_bytes = NULL;
+	uint8_t *uncompressed = NULL;
 
-	if (!reftable_is_block_type(typ))
-		return REFTABLE_FORMAT_ERROR;
+	if (!reftable_is_block_type(typ)) {
+		err =  REFTABLE_FORMAT_ERROR;
+		goto done;
+	}
 
 	if (typ == BLOCK_TYPE_LOG) {
 		int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		uLongf src_len = block->len - block_header_skip;
 		/* Log blocks specify the *uncompressed* size in their header.
 		 */
-		uint8_t *uncompressed = reftable_malloc(sz);
+		uncompressed = reftable_malloc(sz);
 
 		/* Copy over the block header verbatim. It's not compressed. */
 		memcpy(uncompressed, block->data, block_header_skip);
@@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		if (Z_OK !=
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
-			reftable_free(uncompressed);
-			return REFTABLE_ZLIB_ERROR;
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz)
-			return REFTABLE_FORMAT_ERROR;
+		if (dst_len + block_header_skip != sz) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		}
 
 		/* We're done with the input data. */
 		reftable_block_done(block);
 		block->data = uncompressed;
+		uncompressed = NULL;
 		block->len = sz;
 		block->source = malloc_block_source();
 		full_block_size = src_len + block_header_skip;
@@ -251,7 +257,9 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	br->restart_count = restart_count;
 	br->restart_bytes = restart_bytes;
 
-	return 0;
+done:
+	reftable_free(uncompressed);
+	return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 5f6bcc2f775..6e88182a83a 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -254,6 +254,71 @@ static void test_log_write_read(void)
 	reader_close(&rd);
 }
 
+static void test_log_zlib_corruption(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader rd = { 0 };
+	struct reftable_block_source source = { 0 };
+	struct strbuf buf = STRBUF_INIT;
+	struct reftable_writer *w =
+		reftable_new_writer(&strbuf_add_void, &buf, &opts);
+	const struct reftable_stats *stats = NULL;
+	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+	char message[100] = { 0 };
+	int err, i, n;
+
+	struct reftable_log_record log = {
+		.refname = "refname",
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.new_hash = hash1,
+				.old_hash = hash2,
+				.name = "My Name",
+				.email = "myname@invalid",
+				.message = message,
+			},
+		},
+	};
+
+	for (i = 0; i < sizeof(message)-1; i++)
+		message[i] = (uint8_t)(rand() % 64 + ' ');
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	err = reftable_writer_add_log(w, &log);
+	EXPECT_ERR(err);
+
+	n = reftable_writer_close(w);
+	EXPECT(n == 0);
+
+	stats = writer_stats(w);
+	EXPECT(stats->log_stats.blocks > 0);
+	reftable_writer_free(w);
+	w = NULL;
+
+	/* corrupt the data. */
+	buf.buf[50] ^= 0x99;
+
+	block_source_from_strbuf(&source, &buf);
+
+	err = init_reader(&rd, &source, "file.log");
+	EXPECT_ERR(err);
+
+	err = reftable_reader_seek_log(&rd, &it, "refname");
+	EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+	reftable_iterator_destroy(&it);
+
+	/* cleanup. */
+	strbuf_release(&buf);
+	reader_close(&rd);
+}
+
 static void test_table_read_write_sequential(void)
 {
 	char **names;
@@ -633,6 +698,7 @@ static void test_corrupt_table(void)
 
 int readwrite_test_main(int argc, const char *argv[])
 {
+	RUN_TEST(test_log_zlib_corruption);
 	RUN_TEST(test_corrupt_table);
 	RUN_TEST(test_corrupt_table_empty);
 	RUN_TEST(test_log_write_read);
-- 
gitgitgadget

```

## \[PATCH v4 04/11\] reftable: check reftable\_stack\_auto\_compact() return value

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:14 
- Message: https://ratatoskr.run/git/2021/12/7915727.md

```text
From: Han-Wen Nienhuys <redacted>

Fixes a problem detected by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index eb0b7228b0c..d628420e63a 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -814,6 +814,7 @@ static void test_reftable_stack_auto_compaction(void)
 		EXPECT_ERR(err);
 
 		err = reftable_stack_auto_compact(st);
+		EXPECT_ERR(err);
 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
 	}
 
-- 
gitgitgadget

```

## \[PATCH v4 03/11\] reftable: fix resource leak blocksource.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:17 
- Message: https://ratatoskr.run/git/2021/12/7915728.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered in the unlikely event of fstat() failing on an opened
file.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/blocksource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9aa..2605371c28d 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
 	}
 
 	err = fstat(fd, &st);
-	if (err < 0)
-		return -1;
+	if (err < 0) {
+		close(fd);
+		return REFTABLE_IO_ERROR;
+	}
 
 	p = reftable_calloc(sizeof(struct file_block_source));
 	p->size = st.st_size;
-- 
gitgitgadget

```

## \[PATCH v4 05/11\] reftable: ignore remove() return value in stack\_test.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:18 
- Message: https://ratatoskr.run/git/2021/12/7915729.md

```text
From: Han-Wen Nienhuys <redacted>

If the cleanup fails, there is nothing we can do.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index d628420e63a..4b7292945c3 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -89,7 +89,7 @@ static void test_read_file(void)
 		EXPECT(0 == strcmp(want[i], names[i]));
 	}
 	free_names(names);
-	remove(fn);
+	(void) remove(fn);
 }
 
 static void test_parse_names(void)
-- 
gitgitgadget

```

## \[PATCH v4 06/11\] reftable: fix resource warning

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:21 
- Message: https://ratatoskr.run/git/2021/12/7915730.md

```text
From: Han-Wen Nienhuys <redacted>

This would trigger in the unlikely event that we are compacting, and the next
available file handle is 0.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/reftable/stack.c b/reftable/stack.c
index df5021ebf08..10dfd370e8e 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -877,7 +877,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	struct strbuf new_table_path = STRBUF_INIT;
 	int err = 0;
 	int have_lock = 0;
-	int lock_file_fd = 0;
+	int lock_file_fd = -1;
 	int compact_count = last - first + 1;
 	char **listp = NULL;
 	char **delete_on_success =
@@ -911,7 +911,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	}
 	/* Don't want to write to the lock for now.  */
 	close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 
 	have_lock = 1;
 	err = stack_uptodate(st);
@@ -932,7 +932,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 
 		sublock_file_fd = open(subtab_lock.buf,
 				       O_EXCL | O_CREAT | O_WRONLY, 0644);
-		if (sublock_file_fd > 0) {
+		if (sublock_file_fd >= 0) {
 			close(sublock_file_fd);
 		} else if (sublock_file_fd < 0) {
 			if (errno == EEXIST) {
@@ -1013,7 +1013,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 		goto done;
 	}
 	err = close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 	if (err < 0) {
 		err = REFTABLE_IO_ERROR;
 		unlink(new_table_path.buf);
@@ -1050,9 +1050,9 @@ done:
 		listp++;
 	}
 	free_names(subtable_locks);
-	if (lock_file_fd > 0) {
+	if (lock_file_fd >= 0) {
 		close(lock_file_fd);
-		lock_file_fd = 0;
+		lock_file_fd = -1;
 	}
 	if (have_lock) {
 		unlink(lock_file_name.buf);
-- 
gitgitgadget

```

## \[PATCH v4 07/11\] reftable: all xxx\_free() functions accept NULL arguments

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:22 
- Message: https://ratatoskr.run/git/2021/12/7915731.md

```text
From: Han-Wen Nienhuys <redacted>

This fixes NULL derefs in error paths. Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reader.c | 2 ++
 reftable/writer.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..733509606a9 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -641,6 +641,8 @@ int reftable_new_reader(struct reftable_reader **p,
 
 void reftable_reader_free(struct reftable_reader *r)
 {
+	if (!r)
+		return;
 	reader_close(r);
 	reftable_free(r);
 }
diff --git a/reftable/writer.c b/reftable/writer.c
index 3ca721e9f64..9fd24fa93ce 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -150,6 +150,8 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
 
 void reftable_writer_free(struct reftable_writer *w)
 {
+	if (!w)
+		return;
 	reftable_free(w->block);
 	reftable_free(w);
 }
-- 
gitgitgadget

```

## \[PATCH v4 08/11\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:23 
- Message: https://ratatoskr.run/git/2021/12/7915732.md

```text
From: Han-Wen Nienhuys <redacted>

This is a more practical ordering when working on refactorings of the reftable
code.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 t/helper/test-reftable.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b789..1f0a28cbb64 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	/* test from simple to complex. */
 	basics_test_main(argc, argv);
+	record_test_main(argc, argv);
 	block_test_main(argc, argv);
-	merged_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	pq_test_main(argc, argv);
-	record_test_main(argc, argv);
-	refname_test_main(argc, argv);
 	readwrite_test_main(argc, argv);
+	merged_test_main(argc, argv);
 	stack_test_main(argc, argv);
-	tree_test_main(argc, argv);
+	refname_test_main(argc, argv);
 	return 0;
 }
 
-- 
gitgitgadget

```

## \[PATCH v4 09/11\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:27 
- Message: https://ratatoskr.run/git/2021/12/7915733.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/readwrite_test.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 6e88182a83a..9b89a5da103 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -662,7 +662,6 @@ static void test_write_key_order(void)
 	err = reftable_writer_add_ref(w, &refs[0]);
 	EXPECT_ERR(err);
 	err = reftable_writer_add_ref(w, &refs[1]);
-	printf("%d\n", err);
 	EXPECT(err == REFTABLE_API_ERROR);
 	reftable_writer_close(w);
 	reftable_writer_free(w);
-- 
gitgitgadget

```

## \[PATCH v4 10/11\] reftable: handle null refnames in reftable\_ref\_record\_equal

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:29 
- Message: https://ratatoskr.run/git/2021/12/7915734.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 8536bd03aa9..8bbcbff1e69 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -1154,9 +1154,11 @@ int reftable_ref_record_equal(struct reftable_ref_record *a,
 			      struct reftable_ref_record *b, int hash_size)
 {
 	assert(hash_size > 0);
-	if (!(0 == strcmp(a->refname, b->refname) &&
-	      a->update_index == b->update_index &&
-	      a->value_type == b->value_type))
+	if (!null_streq(a->refname, b->refname))
+		return 0;
+
+	if (a->update_index != b->update_index ||
+	    a->value_type != b->value_type)
 		return 0;
 
 	switch (a->value_type) {
-- 
gitgitgadget

```

## \[PATCH v4 11/11\] reftable: make reftable\_record a tagged union

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-14 11:48:30 
- Message: https://ratatoskr.run/git/2021/12/7915735.md

```text
From: Han-Wen Nienhuys <redacted>

This reduces the amount of glue code, because we don't need a void pointer or
vtable within the structure.

The only snag is that reftable_index_record contain a strbuf, so it cannot be
zero-initialized. To address this, use reftable_new_record() to return fresh
instance, given a record type. Since reftable_new_record() doesn't cause heap
allocation anymore, it should be balanced with reftable_record_release() rather
than reftable_record_destroy().

Thanks to Peff for the suggestion.

Helped-by: Jeff King [ref]
Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c       |   2 +-
 reftable/block_test.c  |  22 ++---
 reftable/generic.c     |  35 ++++----
 reftable/iter.c        |   4 +-
 reftable/merged.c      |  33 ++++----
 reftable/pq.c          |   3 +-
 reftable/pq_test.c     |  31 +++----
 reftable/reader.c      |  93 +++++++++++----------
 reftable/record.c      | 173 ++++++++++++++++-----------------------
 reftable/record.h      |  42 ++++------
 reftable/record_test.c | 181 +++++++++++++++++++++--------------------
 reftable/writer.c      |  40 +++++----
 12 files changed, 317 insertions(+), 342 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 6c8e8705205..2170748c5e9 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -421,7 +421,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
 done:
 	strbuf_release(&key);
 	strbuf_release(&next.last_key);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 
 	return err;
 }
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 4b3ea262dcb..fa2ee092ec0 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
 	struct block_writer bw = {
 		.last_key = STRBUF_INIT,
 	};
-	struct reftable_ref_record ref = { NULL };
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+	};
 	int i = 0;
 	int n;
 	struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
 	block.source = malloc_block_source();
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
-	reftable_record_from_ref(&rec, &ref);
 
 	for (i = 0; i < N; i++) {
 		char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
 		snprintf(name, sizeof(name), "branch%02d", i);
 		memset(hash, i, sizeof(hash));
 
-		ref.refname = name;
-		ref.value_type = REFTABLE_REF_VAL1;
-		ref.value.val1 = hash;
+		rec.u.ref.refname = name;
+		rec.u.ref.value_type = REFTABLE_REF_VAL1;
+		rec.u.ref.value.val1 = hash;
 
 		names[i] = xstrdup(name);
 		n = block_writer_add(&bw, &rec);
-		ref.refname = NULL;
-		ref.value_type = REFTABLE_REF_DELETION;
+		rec.u.ref.refname = NULL;
+		rec.u.ref.value_type = REFTABLE_REF_DELETION;
 		EXPECT(n == 0);
 	}
 
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
 		if (r > 0) {
 			break;
 		}
-		EXPECT_STREQ(names[j], ref.refname);
+		EXPECT_STREQ(names[j], rec.u.ref.refname);
 		j++;
 	}
 
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
 
-		EXPECT_STREQ(names[i], ref.refname);
+		EXPECT_STREQ(names[i], rec.u.ref.refname);
 
 		want.len--;
 		n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
 
 		n = block_iter_next(&it, &rec);
 		EXPECT(n == 0);
-		EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+		EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
 
 		block_iter_close(&it);
 	}
diff --git a/reftable/generic.c b/reftable/generic.c
index 7a8a738d860..4446b8ed360 100644
--- a/reftable/generic.c
+++ b/reftable/generic.c
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
 */
 
 #include "basics.h"
+#include "constants.h"
 #include "record.h"
 #include "generic.h"
 #include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
 int reftable_table_seek_ref(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+				       .u.ref = {
+					       .refname = (char *)name,
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
 int reftable_table_seek_log(struct reftable_table *tab,
 			    struct reftable_iterator *it, const char *name)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = ~((uint64_t)0),
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+				       .u.log = {
+					       .refname = (char *)name,
+					       .update_index = ~((uint64_t)0),
+				       } };
 	return tab->ops->seek_record(tab->table_arg, it, &rec);
 }
 
@@ -129,17 +128,19 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
 int reftable_iterator_next_ref(struct reftable_iterator *it,
 			       struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, ref);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
+	int err = iterator_next(it, &rec);
+	*ref = rec.u.ref;
+	return err;
 }
 
 int reftable_iterator_next_log(struct reftable_iterator *it,
 			       struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, log);
-	return iterator_next(it, &rec);
+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
+	int err = iterator_next(it, &rec);
+	*log = rec.u.log;
+	return err;
 }
 
 int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
diff --git a/reftable/iter.c b/reftable/iter.c
index 93d04f735b8..a8d174c0406 100644
--- a/reftable/iter.c
+++ b/reftable/iter.c
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
 				       struct reftable_record *rec)
 {
 	struct filtering_ref_iterator *fri = iter_arg;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 	int err = 0;
 	while (1) {
 		err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
 static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
 {
 	struct indexed_table_ref_iter *it = p;
-	struct reftable_ref_record *ref = rec->data;
+	struct reftable_ref_record *ref = &rec->u.ref;
 
 	while (1) {
 		int err = block_iter_next(&it->cur, rec);
diff --git a/reftable/merged.c b/reftable/merged.c
index e5b53da6db3..a7c0df91ff0 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
 
 		if (err > 0) {
 			reftable_iterator_destroy(&mi->stack[i]);
-			reftable_record_destroy(&rec);
+			reftable_record_release(&rec);
 		} else {
 			struct pq_entry e = {
 				.rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
 static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
 					       size_t idx)
 {
-	struct reftable_record rec = reftable_new_record(mi->typ);
 	struct pq_entry e = {
-		.rec = rec,
+		.rec = reftable_new_record(mi->typ),
 		.index = idx,
 	};
-	int err = iterator_next(&mi->stack[idx], &rec);
+	int err = iterator_next(&mi->stack[idx], &e.rec);
 	if (err < 0)
 		return err;
 
 	if (err > 0) {
 		reftable_iterator_destroy(&mi->stack[idx]);
-		reftable_record_destroy(&rec);
+		reftable_record_release(&e.rec);
 		return 0;
 	}
 
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
 		if (err < 0) {
 			return err;
 		}
-		reftable_record_destroy(&top.rec);
+		reftable_record_release(&top.rec);
 	}
 
 	reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
-	reftable_record_destroy(&entry.rec);
+	reftable_record_release(&entry.rec);
 	strbuf_release(&entry_key);
 	return 0;
 }
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
 				   struct reftable_iterator *it,
 				   const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
@@ -302,12 +302,13 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
 				      struct reftable_iterator *it,
 				      const char *name, uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return merged_table_seek_record(mt, it, &rec);
 }
 
diff --git a/reftable/pq.c b/reftable/pq.c
index efc474017a2..96ca6dd37b3 100644
--- a/reftable/pq.c
+++ b/reftable/pq.c
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
 {
 	int i = 0;
+
 	if (pq->len == pq->cap) {
 		pq->cap = 2 * pq->cap + 1;
 		pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
 	int i = 0;
 	for (i = 0; i < pq->len; i++) {
-		reftable_record_destroy(&pq->heap[i].rec);
+		reftable_record_release(&pq->heap[i].rec);
 	}
 	FREE_AND_NULL(pq->heap);
 	pq->len = pq->cap = 0;
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index c9bb05e37b7..e0f5c584ada 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -31,7 +31,7 @@ static void test_pq(void)
 	int N = ARRAY_SIZE(names) - 1;
 
 	struct merged_iter_pqueue pq = { NULL };
-	const char *last = NULL;
+	char *last = NULL;
 
 	int i = 0;
 	for (i = 0; i < N; i++) {
@@ -42,12 +42,14 @@ static void test_pq(void)
 
 	i = 1;
 	do {
-		struct reftable_record rec =
-			reftable_new_record(BLOCK_TYPE_REF);
-		struct pq_entry e = { 0 };
-
-		reftable_record_as_ref(&rec)->refname = names[i];
-		e.rec = rec;
+		struct pq_entry e = {
+			.rec = {
+				.type = BLOCK_TYPE_REF,
+				.u.ref = {
+					.refname = names[i],
+				}
+			}
+		};
 		merged_iter_pqueue_add(&pq, e);
 		merged_iter_pqueue_check(pq);
 		i = (i * 7) % N;
@@ -55,19 +57,18 @@ static void test_pq(void)
 
 	while (!merged_iter_pqueue_is_empty(pq)) {
 		struct pq_entry e = merged_iter_pqueue_remove(&pq);
-		struct reftable_ref_record *ref =
-			reftable_record_as_ref(&e.rec);
-
+		struct reftable_record *rec = &e.rec;
 		merged_iter_pqueue_check(pq);
 
+		EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
 		if (last) {
-			EXPECT(strcmp(last, ref->refname) < 0);
+			EXPECT(strcmp(last, rec->u.ref.refname) < 0);
 		}
-		last = ref->refname;
-		ref->refname = NULL;
-		reftable_free(ref);
+		// this is names[i], so don't dealloc.
+		last = rec->u.ref.refname;
+		rec->u.ref.refname = NULL;
+		reftable_record_release(rec);
 	}
-
 	for (i = 0; i < N; i++) {
 		reftable_free(names[i]);
 	}
diff --git a/reftable/reader.c b/reftable/reader.c
index 733509606a9..0790f654366 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -239,8 +239,7 @@ static int table_iter_next_in_block(struct table_iter *ti,
 {
 	int res = block_iter_next(&ti->bi, rec);
 	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
-		((struct reftable_ref_record *)rec->data)->update_index +=
-			ti->r->min_update_index;
+		rec->u.ref.update_index += ti->r->min_update_index;
 	}
 
 	return res;
@@ -437,8 +436,7 @@ static int reader_start(struct reftable_reader *r, struct table_iter *ti,
 static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 			      struct reftable_record *want)
 {
-	struct reftable_record rec =
-		reftable_new_record(reftable_record_type(want));
+	struct reftable_record rec = reftable_new_record(reftable_record_type(want));
 	struct strbuf want_key = STRBUF_INIT;
 	struct strbuf got_key = STRBUF_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
@@ -475,7 +473,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
 
 done:
 	block_iter_close(&next.bi);
-	reftable_record_destroy(&rec);
+	reftable_record_release(&rec);
 	strbuf_release(&want_key);
 	strbuf_release(&got_key);
 	return err;
@@ -485,34 +483,36 @@ static int reader_seek_indexed(struct reftable_reader *r,
 			       struct reftable_iterator *it,
 			       struct reftable_record *rec)
 {
-	struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
-	struct reftable_record want_index_rec = { NULL };
-	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
-	struct reftable_record index_result_rec = { NULL };
+	struct reftable_record want_index = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT }
+	};
+	struct reftable_record index_result = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	struct table_iter index_iter = TABLE_ITER_INIT;
 	struct table_iter next = TABLE_ITER_INIT;
 	int err = 0;
 
-	reftable_record_key(rec, &want_index.last_key);
-	reftable_record_from_index(&want_index_rec, &want_index);
-	reftable_record_from_index(&index_result_rec, &index_result);
-
+	reftable_record_key(rec, &want_index.u.idx.last_key);
 	err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
 	if (err < 0)
 		goto done;
 
-	err = reader_seek_linear(r, &index_iter, &want_index_rec);
+	err = reader_seek_linear(r, &index_iter, &want_index);
 	while (1) {
-		err = table_iter_next(&index_iter, &index_result_rec);
+		err = table_iter_next(&index_iter, &index_result);
 		table_iter_block_done(&index_iter);
 		if (err != 0)
 			goto done;
 
-		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+					   0);
 		if (err != 0)
 			goto done;
 
-		err = block_iter_seek(&next.bi, &want_index.last_key);
+		err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
 		if (err < 0)
 			goto done;
 
@@ -540,8 +540,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
 done:
 	block_iter_close(&next.bi);
 	table_iter_close(&index_iter);
-	reftable_record_release(&want_index_rec);
-	reftable_record_release(&index_result_rec);
+	reftable_record_release(&want_index);
+	reftable_record_release(&index_result);
 	return err;
 }
 
@@ -549,8 +549,7 @@ static int reader_seek_internal(struct reftable_reader *r,
 				struct reftable_iterator *it,
 				struct reftable_record *rec)
 {
-	struct reftable_reader_offsets *offs =
-		reader_offsets_for(r, reftable_record_type(rec));
+	struct reftable_reader_offsets *offs = reader_offsets_for(r, reftable_record_type(rec));
 	uint64_t idx = offs->index_offset;
 	struct table_iter ti = TABLE_ITER_INIT;
 	int err = 0;
@@ -590,11 +589,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
 int reftable_reader_seek_ref(struct reftable_reader *r,
 			     struct reftable_iterator *it, const char *name)
 {
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = {
+			.refname = (char *)name,
+		},
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
 	return reader_seek(r, it, &rec);
 }
 
@@ -602,12 +602,13 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
 				struct reftable_iterator *it, const char *name,
 				uint64_t update_index)
 {
-	struct reftable_log_record log = {
-		.refname = (char *)name,
-		.update_index = update_index,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = {
+			.refname = (char *)name,
+			.update_index = update_index,
+		}
 	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, &log);
 	return reader_seek(r, it, &rec);
 }
 
@@ -651,31 +652,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 					    struct reftable_iterator *it,
 					    uint8_t *oid)
 {
-	struct reftable_obj_record want = {
-		.hash_prefix = oid,
-		.hash_prefix_len = r->object_id_len,
+	struct reftable_record want = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = oid,
+			.hash_prefix_len = r->object_id_len,
+		},
 	};
-	struct reftable_record want_rec = { NULL };
 	struct reftable_iterator oit = { NULL };
-	struct reftable_obj_record got = { NULL };
-	struct reftable_record got_rec = { NULL };
+	struct reftable_record got = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = { 0 },
+	};
 	int err = 0;
 	struct indexed_table_ref_iter *itr = NULL;
 
 	/* Look through the reverse index. */
-	reftable_record_from_obj(&want_rec, &want);
-	err = reader_seek(r, &oit, &want_rec);
+	err = reader_seek(r, &oit, &want);
 	if (err != 0)
 		goto done;
 
 	/* read out the reftable_obj_record */
-	reftable_record_from_obj(&got_rec, &got);
-	err = iterator_next(&oit, &got_rec);
+	err = iterator_next(&oit, &got);
 	if (err < 0)
 		goto done;
 
-	if (err > 0 ||
-	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+	if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+			      r->object_id_len)) {
 		/* didn't find it; return empty iterator */
 		iterator_set_empty(it);
 		err = 0;
@@ -683,15 +686,15 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
 	}
 
 	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
-					 got.offsets, got.offset_len);
+					 got.u.obj.offsets, got.u.obj.offset_len);
 	if (err < 0)
 		goto done;
-	got.offsets = NULL;
+	got.u.obj.offsets = NULL;
 	iterator_from_indexed_table_ref_iter(it, itr);
 
 done:
 	reftable_iterator_destroy(&oit);
-	reftable_record_release(&got_rec);
+	reftable_record_release(&got);
 	return err;
 }
 
diff --git a/reftable/record.c b/reftable/record.c
index 8bbcbff1e69..f766ee20ace 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "basics.h"
 
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
 int get_var_int(uint64_t *dest, struct string_view *in)
 {
 	int ptr = 0;
@@ -926,58 +930,6 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.is_deletion = &reftable_log_record_is_deletion_void,
 };
 
-struct reftable_record reftable_new_record(uint8_t typ)
-{
-	struct reftable_record rec = { NULL };
-	switch (typ) {
-	case BLOCK_TYPE_REF: {
-		struct reftable_ref_record *r =
-			reftable_calloc(sizeof(struct reftable_ref_record));
-		reftable_record_from_ref(&rec, r);
-		return rec;
-	}
-
-	case BLOCK_TYPE_OBJ: {
-		struct reftable_obj_record *r =
-			reftable_calloc(sizeof(struct reftable_obj_record));
-		reftable_record_from_obj(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_LOG: {
-		struct reftable_log_record *r =
-			reftable_calloc(sizeof(struct reftable_log_record));
-		reftable_record_from_log(&rec, r);
-		return rec;
-	}
-	case BLOCK_TYPE_INDEX: {
-		struct reftable_index_record empty = { .last_key =
-							       STRBUF_INIT };
-		struct reftable_index_record *r =
-			reftable_calloc(sizeof(struct reftable_index_record));
-		*r = empty;
-		reftable_record_from_index(&rec, r);
-		return rec;
-	}
-	}
-	abort();
-	return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
-	void *p = rec->data;
-	rec->data = NULL;
-	return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
-	reftable_record_release(rec);
-	reftable_free(reftable_record_yield(rec));
-}
-
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
 {
 	const struct reftable_index_record *rec = r;
@@ -1055,91 +1007,52 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
 {
-	rec->ops->key(rec->data, dest);
+	reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
 uint8_t reftable_record_type(struct reftable_record *rec)
 {
-	return rec->ops->type;
+	return rec->type;
 }
 
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
 			   int hash_size)
 {
-	return rec->ops->encode(rec->data, dest, hash_size);
+	return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+						   dest, hash_size);
 }
 
 void reftable_record_copy_from(struct reftable_record *rec,
 			       struct reftable_record *src, int hash_size)
 {
-	assert(src->ops->type == rec->ops->type);
+	assert(src->type == rec->type);
 
-	rec->ops->copy_from(rec->data, src->data, hash_size);
+	reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+					       reftable_record_data(src),
+					       hash_size);
 }
 
 uint8_t reftable_record_val_type(struct reftable_record *rec)
 {
-	return rec->ops->val_type(rec->data);
+	return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   uint8_t extra, struct string_view src, int hash_size)
 {
-	return rec->ops->decode(rec->data, key, extra, src, hash_size);
+	return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+						   key, extra, src, hash_size);
 }
 
 void reftable_record_release(struct reftable_record *rec)
 {
-	rec->ops->release(rec->data);
+	reftable_record_vtable(rec)->release(reftable_record_data(rec));
 }
 
 int reftable_record_is_deletion(struct reftable_record *rec)
 {
-	return rec->ops->is_deletion(rec->data);
-}
-
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *ref_rec)
-{
-	assert(!rec->ops);
-	rec->data = ref_rec;
-	rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *obj_rec)
-{
-	assert(!rec->ops);
-	rec->data = obj_rec;
-	rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *index_rec)
-{
-	assert(!rec->ops);
-	rec->data = index_rec;
-	rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *log_rec)
-{
-	assert(!rec->ops);
-	rec->data = log_rec;
-	rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
-	return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
-	assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
-	return rec->data;
+	return reftable_record_vtable(rec)->is_deletion(
+		reftable_record_data(rec));
 }
 
 static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1212,3 +1125,53 @@ void string_view_consume(struct string_view *s, int n)
 	s->buf += n;
 	s->len -= n;
 }
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &rec->u.ref;
+	case BLOCK_TYPE_LOG:
+		return &rec->u.log;
+	case BLOCK_TYPE_INDEX:
+		return &rec->u.idx;
+	case BLOCK_TYPE_OBJ:
+		return &rec->u.obj;
+	}
+	abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+	switch (rec->type) {
+	case BLOCK_TYPE_REF:
+		return &reftable_ref_record_vtable;
+	case BLOCK_TYPE_LOG:
+		return &reftable_log_record_vtable;
+	case BLOCK_TYPE_INDEX:
+		return &reftable_index_record_vtable;
+	case BLOCK_TYPE_OBJ:
+		return &reftable_obj_record_vtable;
+	}
+	abort();
+}
+
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+	struct reftable_record clean_idx = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.last_key = STRBUF_INIT,
+		},
+	};
+	struct reftable_record clean = {
+		.type = typ,
+	};
+
+	if (typ == BLOCK_TYPE_INDEX) {
+		return clean_idx;
+	}
+
+	return clean;
+}
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf4..407cdaba018 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -60,16 +60,10 @@ struct reftable_record_vtable {
 	int (*is_deletion)(const void *rec);
 };
 
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
-	void *data;
-	struct reftable_record_vtable *ops;
-};
-
 /* returns true for recognized block types. Block start with the block type. */
 int reftable_is_block_type(uint8_t typ);
 
-/* creates a malloced record of the given type. Dispose with record_destroy */
+/* return an initialized record for the given type */
 struct reftable_record reftable_new_record(uint8_t typ);
 
 /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
@@ -97,6 +91,22 @@ struct reftable_obj_record {
 	int offset_len;
 };
 
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_new_record() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+	uint8_t type;
+	union {
+		struct reftable_ref_record ref;
+		struct reftable_log_record log;
+		struct reftable_obj_record obj;
+		struct reftable_index_record idx;
+	} u;
+};
+
 /* see struct record_vtable */
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
@@ -111,25 +121,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
 			   int hash_size);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
-			      struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
-				struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
-			      struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
-			      struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
 /* for qsort. */
 int reftable_ref_record_compare_name(const void *a, const void *b);
 
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace41..96f65035c1b 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -16,24 +16,22 @@
 
 static void test_copy(struct reftable_record *rec)
 {
-	struct reftable_record copy =
-		reftable_new_record(reftable_record_type(rec));
+	struct reftable_record copy = reftable_new_record(reftable_record_type(rec));
+
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-	switch (reftable_record_type(&copy)) {
+	switch (copy.type) {
 	case BLOCK_TYPE_REF:
-		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-						 reftable_record_as_ref(rec),
+		EXPECT(reftable_ref_record_equal(&copy.u.ref, &rec->u.ref,
 						 GIT_SHA1_RAWSZ));
 		break;
 	case BLOCK_TYPE_LOG:
-		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-						 reftable_record_as_log(rec),
+		EXPECT(reftable_log_record_equal(&copy.u.log, &rec->u.log,
 						 GIT_SHA1_RAWSZ));
 		break;
 	}
-	reftable_record_destroy(&copy);
+	reftable_record_release(&copy);
 }
 
 static void test_varint_roundtrip(void)
@@ -106,61 +104,60 @@ static void test_reftable_ref_record_roundtrip(void)
 	int i = 0;
 
 	for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-		struct reftable_ref_record in = { NULL };
-		struct reftable_ref_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_REF,
+		};
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_REF
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_record rec = { NULL };
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-
 		int n, m;
 
-		in.value_type = i;
+		in.u.ref.value_type = i;
 		switch (i) {
 		case REFTABLE_REF_DELETION:
 			break;
 		case REFTABLE_REF_VAL1:
-			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val1, 1);
+			in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val1, 1);
 			break;
 		case REFTABLE_REF_VAL2:
-			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.value, 1);
-			in.value.val2.target_value =
+			in.u.ref.value.val2.value =
+				reftable_malloc(GIT_SHA1_RAWSZ);
+			set_hash(in.u.ref.value.val2.value, 1);
+			in.u.ref.value.val2.target_value =
 				reftable_malloc(GIT_SHA1_RAWSZ);
-			set_hash(in.value.val2.target_value, 2);
+			set_hash(in.u.ref.value.val2.target_value, 2);
 			break;
 		case REFTABLE_REF_SYMREF:
-			in.value.symref = xstrdup("target");
+			in.u.ref.value.symref = xstrdup("target");
 			break;
 		}
-		in.refname = xstrdup("refs/heads/master");
+		in.u.ref.refname = xstrdup("refs/heads/master");
 
-		reftable_record_from_ref(&rec, &in);
-		test_copy(&rec);
+		test_copy(&in);
 
-		EXPECT(reftable_record_val_type(&rec) == i);
+		EXPECT(reftable_record_val_type(&in) == i);
 
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
 
 		/* decode into a non-zero reftable_record to test for leaks. */
-
-		reftable_record_from_ref(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, i, dest,
-					   GIT_SHA1_RAWSZ);
+		m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
-		reftable_record_release(&rec_out);
+		EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+						 GIT_SHA1_RAWSZ));
+		reftable_record_release(&in);
 
 		strbuf_release(&key);
-		reftable_ref_record_release(&in);
+		reftable_record_release(&out);
 	}
 }
 
@@ -213,7 +210,9 @@ static void test_reftable_log_record_roundtrip(void)
 	set_test_hash(in[0].value.update.new_hash, 1);
 	set_test_hash(in[0].value.update.old_hash, 2);
 	for (i = 0; i < ARRAY_SIZE(in); i++) {
-		struct reftable_record rec = { NULL };
+		struct reftable_record rec = {
+			.type = BLOCK_TYPE_LOG
+		};
 		struct strbuf key = STRBUF_INIT;
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
@@ -221,23 +220,25 @@ static void test_reftable_log_record_roundtrip(void)
 			.len = sizeof(buffer),
 		};
 		/* populate out, to check for leaks. */
-		struct reftable_log_record out = {
-			.refname = xstrdup("old name"),
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = {
-				.update = {
-					.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
-					.name = xstrdup("old name"),
-					.email = xstrdup("old@email"),
-					.message = xstrdup("old message"),
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_LOG,
+			.u.log = {
+				.refname = xstrdup("old name"),
+				.value_type = REFTABLE_LOG_UPDATE,
+				.value = {
+					.update = {
+						.new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+						.name = xstrdup("old name"),
+						.email = xstrdup("old@email"),
+						.message = xstrdup("old message"),
+					},
 				},
 			},
 		};
-		struct reftable_record rec_out = { NULL };
 		int n, m, valtype;
 
-		reftable_record_from_log(&rec, &in[i]);
+		rec.u.log = in[i];
 
 		test_copy(&rec);
 
@@ -245,16 +246,16 @@ static void test_reftable_log_record_roundtrip(void)
 
 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n >= 0);
-		reftable_record_from_log(&rec_out, &out);
 		valtype = reftable_record_val_type(&rec);
-		m = reftable_record_decode(&rec_out, key, valtype, dest,
+		m = reftable_record_decode(&out, key, valtype, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+		EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+						 GIT_SHA1_RAWSZ));
 		reftable_log_record_release(&in[i]);
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
@@ -322,47 +323,52 @@ static void test_reftable_obj_record_roundtrip(void)
 					       } };
 	int i = 0;
 	for (i = 0; i < ARRAY_SIZE(recs); i++) {
-		struct reftable_obj_record in = recs[i];
 		uint8_t buffer[1024] = { 0 };
 		struct string_view dest = {
 			.buf = buffer,
 			.len = sizeof(buffer),
 		};
-		struct reftable_record rec = { NULL };
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj = recs[i],
+		};
 		struct strbuf key = STRBUF_INIT;
-		struct reftable_obj_record out = { NULL };
-		struct reftable_record rec_out = { NULL };
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_OBJ
+		};
 		int n, m;
 		uint8_t extra;
 
-		reftable_record_from_obj(&rec, &in);
-		test_copy(&rec);
-		reftable_record_key(&rec, &key);
-		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+		test_copy(&in);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 		EXPECT(n > 0);
-		extra = reftable_record_val_type(&rec);
-		reftable_record_from_obj(&rec_out, &out);
-		m = reftable_record_decode(&rec_out, key, extra, dest,
+		extra = reftable_record_val_type(&in);
+		m = reftable_record_decode(&out, key, extra, dest,
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-		EXPECT(in.offset_len == out.offset_len);
-
-		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-			       in.hash_prefix_len));
-		EXPECT(0 == memcmp(in.offsets, out.offsets,
-				   sizeof(uint64_t) * in.offset_len));
+		EXPECT(in.u.obj.hash_prefix_len == out.u.obj.hash_prefix_len);
+		EXPECT(in.u.obj.offset_len == out.u.obj.offset_len);
+		if (in.u.obj.hash_prefix_len)
+			EXPECT(!memcmp(in.u.obj.hash_prefix, out.u.obj.hash_prefix,
+				       in.u.obj.hash_prefix_len));
+		if (in.u.obj.offset_len)
+			EXPECT(!memcmp(in.u.obj.offsets, out.u.obj.offsets,
+					   sizeof(uint64_t) * in.u.obj.offset_len));
 		strbuf_release(&key);
-		reftable_record_release(&rec_out);
+		reftable_record_release(&out);
 	}
 }
 
 static void test_reftable_index_record_roundtrip(void)
 {
-	struct reftable_index_record in = {
-		.offset = 42,
-		.last_key = STRBUF_INIT,
+	struct reftable_record in = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.offset = 42,
+			.last_key = STRBUF_INIT,
+		},
 	};
 	uint8_t buffer[1024] = { 0 };
 	struct string_view dest = {
@@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void)
 		.len = sizeof(buffer),
 	};
 	struct strbuf key = STRBUF_INIT;
-	struct reftable_record rec = { NULL };
-	struct reftable_index_record out = { .last_key = STRBUF_INIT };
-	struct reftable_record out_rec = { NULL };
+	struct reftable_record out = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = STRBUF_INIT },
+	};
 	int n, m;
 	uint8_t extra;
 
-	strbuf_addstr(&in.last_key, "refs/heads/master");
-	reftable_record_from_index(&rec, &in);
-	reftable_record_key(&rec, &key);
-	test_copy(&rec);
+	strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+	reftable_record_key(&in, &key);
+	test_copy(&in);
 
-	EXPECT(0 == strbuf_cmp(&key, &in.last_key));
-	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+	EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+	n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
 	EXPECT(n > 0);
 
-	extra = reftable_record_val_type(&rec);
-	reftable_record_from_index(&out_rec, &out);
-	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+	extra = reftable_record_val_type(&in);
+	m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
 	EXPECT(m == n);
 
-	EXPECT(in.offset == out.offset);
+	EXPECT(in.u.idx.offset == out.u.idx.offset);
 
-	reftable_record_release(&out_rec);
+	reftable_record_release(&out);
 	strbuf_release(&key);
-	strbuf_release(&in.last_key);
+	strbuf_release(&in.u.idx.last_key);
 }
 
 int record_test_main(int argc, const char *argv[])
diff --git a/reftable/writer.c b/reftable/writer.c
index 9fd24fa93ce..51066c36a61 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -253,8 +253,10 @@ done:
 int reftable_writer_add_ref(struct reftable_writer *w,
 			    struct reftable_ref_record *ref)
 {
-	struct reftable_record rec = { NULL };
-	struct reftable_ref_record copy = *ref;
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+		.u.ref = *ref,
+	};
 	int err = 0;
 
 	if (ref->refname == NULL)
@@ -263,8 +265,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
 	    ref->update_index > w->max_update_index)
 		return REFTABLE_API_ERROR;
 
-	reftable_record_from_ref(&rec, &copy);
-	copy.update_index -= w->min_update_index;
+	rec.u.ref.update_index -= w->min_update_index;
 
 	err = writer_add_record(w, &rec);
 	if (err < 0)
@@ -303,7 +304,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
 static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 					    struct reftable_log_record *log)
 {
-	struct reftable_record rec = { NULL };
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+		.u.log = *log,
+	};
 	if (w->block_writer &&
 	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
 		int err = writer_finish_public_section(w);
@@ -313,8 +317,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
 
 	w->next -= w->pending_padding;
 	w->pending_padding = 0;
-
-	reftable_record_from_log(&rec, log);
 	return writer_add_record(w, &rec);
 }
 
@@ -395,8 +397,10 @@ static int writer_finish_section(struct reftable_writer *w)
 		w->index_len = 0;
 		w->index_cap = 0;
 		for (i = 0; i < idx_len; i++) {
-			struct reftable_record rec = { NULL };
-			reftable_record_from_index(&rec, idx + i);
+			struct reftable_record rec = {
+				.type = BLOCK_TYPE_INDEX,
+				.u.idx = idx[i],
+			};
 			if (block_writer_add(w->block_writer, &rec) == 0) {
 				continue;
 			}
@@ -464,17 +468,18 @@ static void write_object_record(void *void_arg, void *key)
 {
 	struct write_record_arg *arg = void_arg;
 	struct obj_index_tree_node *entry = key;
-	struct reftable_obj_record obj_rec = {
-		.hash_prefix = (uint8_t *)entry->hash.buf,
-		.hash_prefix_len = arg->w->stats.object_id_len,
-		.offsets = entry->offsets,
-		.offset_len = entry->offset_len,
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_OBJ,
+		.u.obj = {
+			.hash_prefix = (uint8_t *)entry->hash.buf,
+			.hash_prefix_len = arg->w->stats.object_id_len,
+			.offsets = entry->offsets,
+			.offset_len = entry->offset_len,
+		}
 	};
-	struct reftable_record rec = { NULL };
 	if (arg->err < 0)
 		goto done;
 
-	reftable_record_from_obj(&rec, &obj_rec);
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
@@ -487,7 +492,8 @@ static void write_object_record(void *void_arg, void *key)
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 	if (arg->err == 0)
 		goto done;
-	obj_rec.offset_len = 0;
+
+	rec.u.obj.offset_len = 0;
 	arg->err = block_writer_add(arg->w->block_writer, &rec);
 
 	/* Should be able to write into a fresh block. */
-- 
gitgitgadget
```

## \[PATCH v5 01/16\] reftable: fix OOB stack write in print functions

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:40 
- Message: https://ratatoskr.run/git/2021/12/7916645.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc6..8536bd03aa9 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -254,7 +254,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 void reftable_ref_record_print(struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
-	char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
 	switch (ref->value_type) {
 	case REFTABLE_REF_SYMREF:
@@ -586,7 +586,7 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 void reftable_log_record_print(struct reftable_log_record *log,
 			       uint32_t hash_id)
 {
-	char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-- 
gitgitgadget

```

## \[PATCH v5 00/16\] Reftable coverity fixes

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:42 
- Message: https://ratatoskr.run/git/2021/12/7916646.md

```text
This series was targeted to 'next'.

This addresses some complaints from Coverity that Peff reported.

v5:

 * add generic record equality
 * add generic record printing
 * const correctness for record-reftable.h
 * fix windows crash.
 * 0-length memcpy paranoia
 * drop unused file.

Han-Wen Nienhuys (16):
  reftable: fix OOB stack write in print functions
  reftable: fix resource leak in block.c error path
  reftable: fix resource leak blocksource.c
  reftable: check reftable_stack_auto_compact() return value
  reftable: ignore remove() return value in stack_test.c
  reftable: fix resource warning
  reftable: all xxx_free() functions accept NULL arguments
  reftable: order unittests by complexity
  reftable: drop stray printf in readwrite_test
  reftable: handle null refnames in reftable_ref_record_equal
  reftable: make reftable-record.h function signatures const correct
  reftable: implement record equality generically
  reftable: remove outdated file reftable.c
  reftable: make reftable_record a tagged union
  reftable: add print functions to the record types
  reftable: be more paranoid about 0-length memcpy calls

 reftable/block.c           |  28 ++-
 reftable/block_test.c      |  22 +--
 reftable/blocksource.c     |   6 +-
 reftable/generic.c         |  41 ++--
 reftable/iter.c            |   4 +-
 reftable/merged.c          |  33 ++--
 reftable/pq.c              |   3 +-
 reftable/pq_test.c         |  27 ++-
 reftable/reader.c          | 113 ++++++-----
 reftable/readwrite_test.c  |  67 ++++++-
 reftable/record.c          | 376 ++++++++++++++++++++++++-------------
 reftable/record.h          |  49 ++---
 reftable/record_test.c     | 197 +++++++++----------
 reftable/reftable-record.h |  14 +-
 reftable/reftable.c        | 115 ------------
 reftable/stack.c           |  12 +-
 reftable/stack_test.c      |   3 +-
 reftable/writer.c          |  43 +++--
 t/helper/test-reftable.c   |   9 +-
 19 files changed, 629 insertions(+), 533 deletions(-)
 delete mode 100644 reftable/reftable.c


base-commit: 69a9c10c95e28df457e33b3c7400b16caf2e2962
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1152%2Fhanwen%2Freftable-coverity-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1152/hanwen/reftable-coverity-v5
Pull-Request: https://github.com/git/git/pull/1152

Range-diff vs v4:

  1:  1c1a3ff92bd =  1:  e7f1be7bbec reftable: fix OOB stack write in print functions
  2:  923293b79b4 !  2:  9ab631a3b29 reftable: fix resource leak in block.c error path
     @@ Metadata
       ## Commit message ##
          reftable: fix resource leak in block.c error path
      
     -    Add test coverage for corrupt zlib data.
     +    Add test coverage for corrupt zlib data. Fix memory leaks demonstrated by
     +    unittest.
      
          This problem was discovered by a Coverity scan.
      
     @@ reftable/block.c: int block_reader_init(struct block_reader *br, struct reftable
       
       static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
      
     + ## reftable/reader.c ##
     +@@ reftable/reader.c: int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
     + 
     + 	err = reader_get_block(r, &block, next_off, guess_block_size);
     + 	if (err < 0)
     +-		return err;
     ++		goto done;
     + 
     + 	block_size = extract_block_size(block.data, &block_typ, next_off,
     + 					r->version);
     +-	if (block_size < 0)
     +-		return block_size;
     +-
     ++	if (block_size < 0) {
     ++		err = block_size;
     ++		goto done;
     ++	}
     + 	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
     +-		reftable_block_done(&block);
     +-		return 1;
     ++		err = 1;
     ++		goto done;
     + 	}
     + 
     + 	if (block_size > guess_block_size) {
     + 		reftable_block_done(&block);
     + 		err = reader_get_block(r, &block, next_off, block_size);
     + 		if (err < 0) {
     +-			return err;
     ++			goto done;
     + 		}
     + 	}
     + 
     +-	return block_reader_init(br, &block, header_off, r->block_size,
     +-				 hash_size(r->hash_id));
     ++	err = block_reader_init(br, &block, header_off, r->block_size,
     ++				hash_size(r->hash_id));
     ++done:
     ++	if (err)
     ++		reftable_block_done(&block);
     ++
     ++	return err;
     + }
     + 
     + static int table_iter_next_block(struct table_iter *dest,
     +
       ## reftable/readwrite_test.c ##
      @@ reftable/readwrite_test.c: static void test_log_write_read(void)
       	reader_close(&rd);
  3:  7a914f77756 =  3:  8301000e83b reftable: fix resource leak blocksource.c
  4:  51b4a17a2e1 =  4:  e6e53aabc39 reftable: check reftable_stack_auto_compact() return value
  5:  43989afcb5a =  5:  7551bcdd917 reftable: ignore remove() return value in stack_test.c
  6:  1c7f15d811c =  6:  700a4e247e7 reftable: fix resource warning
  7:  47ba5ddceb8 =  7:  30016fee4c8 reftable: all xxx_free() functions accept NULL arguments
  8:  aba8b8113ad =  8:  f90846cc103 reftable: order unittests by complexity
  9:  f2af404d7f1 =  9:  425e12d3667 reftable: drop stray printf in readwrite_test
 10:  d2ce1bfef9f = 10:  67d858ec59e reftable: handle null refnames in reftable_ref_record_equal
  -:  ----------- > 11:  1e50924894f reftable: make reftable-record.h function signatures const correct
  -:  ----------- > 12:  c81b17ad57e reftable: implement record equality generically
  -:  ----------- > 13:  4175089ec43 reftable: remove outdated file reftable.c
 11:  0ffc1cf0e42 ! 14:  ede2e792ab6 reftable: make reftable_record a tagged union
     @@ reftable/generic.c: void reftable_iterator_destroy(struct reftable_iterator *it)
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_ref(&rec, ref);
      -	return iterator_next(it, &rec);
     -+	struct reftable_record rec = { .type = BLOCK_TYPE_REF };
     ++	struct reftable_record rec = {
     ++		.type = BLOCK_TYPE_REF,
     ++		.u.ref = *ref,
     ++	};
      +	int err = iterator_next(it, &rec);
      +	*ref = rec.u.ref;
      +	return err;
     @@ reftable/generic.c: void reftable_iterator_destroy(struct reftable_iterator *it)
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_log(&rec, log);
      -	return iterator_next(it, &rec);
     -+	struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
     ++	struct reftable_record rec = {
     ++		.type = BLOCK_TYPE_LOG,
     ++		.u.log = *log,
     ++	};
      +	int err = iterator_next(it, &rec);
      +	*log = rec.u.log;
      +	return err;
     @@ reftable/merged.c: int reftable_merged_table_seek_log_at(struct reftable_merged_
      -	struct reftable_log_record log = {
      -		.refname = (char *)name,
      -		.update_index = update_index,
     -+	struct reftable_record rec = {
     -+		.type = BLOCK_TYPE_LOG,
     -+		.u.log = {
     -+			.refname = (char *)name,
     -+			.update_index = update_index,
     -+		}
     - 	};
     +-	};
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_log(&rec, &log);
     ++	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
     ++				       .u.log = {
     ++					       .refname = (char *)name,
     ++					       .update_index = update_index,
     ++				       } };
       	return merged_table_seek_record(mt, it, &rec);
       }
       
     @@ reftable/pq_test.c: static void test_pq(void)
      -
      -		reftable_record_as_ref(&rec)->refname = names[i];
      -		e.rec = rec;
     -+		struct pq_entry e = {
     -+			.rec = {
     -+				.type = BLOCK_TYPE_REF,
     -+				.u.ref = {
     -+					.refname = names[i],
     -+				}
     -+			}
     -+		};
     ++		struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
     ++					       .u.ref = {
     ++						       .refname = names[i],
     ++					       } } };
       		merged_iter_pqueue_add(&pq, e);
       		merged_iter_pqueue_check(pq);
       		i = (i * 7) % N;
     @@ reftable/reader.c: static int table_iter_next_in_block(struct table_iter *ti,
       	}
       
       	return res;
     -@@ reftable/reader.c: static int reader_start(struct reftable_reader *r, struct table_iter *ti,
     - static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
     - 			      struct reftable_record *want)
     - {
     --	struct reftable_record rec =
     --		reftable_new_record(reftable_record_type(want));
     -+	struct reftable_record rec = reftable_new_record(reftable_record_type(want));
     - 	struct strbuf want_key = STRBUF_INIT;
     - 	struct strbuf got_key = STRBUF_INIT;
     - 	struct table_iter next = TABLE_ITER_INIT;
      @@ reftable/reader.c: static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
       
       done:
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
      -	struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
      -	struct reftable_record index_result_rec = { NULL };
      +	struct reftable_record want_index = {
     -+		.type = BLOCK_TYPE_INDEX,
     -+		.u.idx = { .last_key = STRBUF_INIT }
     ++		.type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT }
      +	};
      +	struct reftable_record index_result = {
      +		.type = BLOCK_TYPE_INDEX,
     @@ reftable/reader.c: static int reader_seek_indexed(struct reftable_reader *r,
       	return err;
       }
       
     -@@ reftable/reader.c: static int reader_seek_internal(struct reftable_reader *r,
     - 				struct reftable_iterator *it,
     - 				struct reftable_record *rec)
     - {
     --	struct reftable_reader_offsets *offs =
     --		reader_offsets_for(r, reftable_record_type(rec));
     -+	struct reftable_reader_offsets *offs = reader_offsets_for(r, reftable_record_type(rec));
     - 	uint64_t idx = offs->index_offset;
     - 	struct table_iter ti = TABLE_ITER_INIT;
     - 	int err = 0;
      @@ reftable/reader.c: static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
       int reftable_reader_seek_ref(struct reftable_reader *r,
       			     struct reftable_iterator *it, const char *name)
     @@ reftable/reader.c: int reftable_reader_seek_log_at(struct reftable_reader *r,
      -	struct reftable_log_record log = {
      -		.refname = (char *)name,
      -		.update_index = update_index,
     -+	struct reftable_record rec = {
     -+		.type = BLOCK_TYPE_LOG,
     -+		.u.log = {
     -+			.refname = (char *)name,
     -+			.update_index = update_index,
     -+		}
     - 	};
     +-	};
      -	struct reftable_record rec = { NULL };
      -	reftable_record_from_log(&rec, &log);
     ++	struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
     ++				       .u.log = {
     ++					       .refname = (char *)name,
     ++					       .update_index = update_index,
     ++				       } };
       	return reader_seek(r, it, &rec);
       }
       
     @@ reftable/reader.c: static int reftable_reader_refs_for_indexed(struct reftable_r
       
       	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
      -					 got.offsets, got.offset_len);
     -+					 got.u.obj.offsets, got.u.obj.offset_len);
     ++					 got.u.obj.offsets,
     ++					 got.u.obj.offset_len);
       	if (err < 0)
       		goto done;
      -	got.offsets = NULL;
     @@ reftable/record.c: https://developers.google.com/open-source/licenses/bsd
       int get_var_int(uint64_t *dest, struct string_view *in)
       {
       	int ptr = 0;
     +@@ reftable/record.c: static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
     + 		(const struct reftable_obj_record *)src_rec;
     + 
     + 	reftable_obj_record_release(obj);
     +-	*obj = *src;
     +-	obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
     +-	memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
     ++	obj->hash_prefix = reftable_malloc(src->hash_prefix_len);
     ++	obj->hash_prefix_len = src->hash_prefix_len;
     ++	if (src->hash_prefix_len)
     ++		memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
     + 
     +-	obj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));
     +-	COPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);
     ++	obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t));
     ++	obj->offset_len = src->offset_len;
     ++	COPY_ARRAY(obj->offsets, src->offsets, src->offset_len);
     + }
     + 
     + static uint8_t reftable_obj_record_val_type(const void *rec)
      @@ reftable/record.c: static struct reftable_record_vtable reftable_log_record_vtable = {
     - 	.is_deletion = &reftable_log_record_is_deletion_void,
     + 	.equal = &reftable_log_record_equal_void
       };
       
      -struct reftable_record reftable_new_record(uint8_t typ)
     @@ reftable/record.c: static struct reftable_record_vtable reftable_index_record_vt
       int reftable_record_is_deletion(struct reftable_record *rec)
       {
      -	return rec->ops->is_deletion(rec->data);
     ++	return reftable_record_vtable(rec)->is_deletion(
     ++		reftable_record_data(rec));
     + }
     + 
     + int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
     + {
     +-	if (a->ops != b->ops)
     ++	if (a->type != b->type)
     + 		return 0;
     +-	return a->ops->equal(a->data, b->data, hash_size);
      -}
      -
      -void reftable_record_from_ref(struct reftable_record *rec,
     @@ reftable/record.c: static struct reftable_record_vtable reftable_index_record_vt
      -{
      -	assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
      -	return rec->data;
     -+	return reftable_record_vtable(rec)->is_deletion(
     -+		reftable_record_data(rec));
     ++	return reftable_record_vtable(a)->equal(
     ++		reftable_record_data(a), reftable_record_data(b), hash_size);
       }
       
       static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
     @@ reftable/record.c: void string_view_consume(struct string_view *s, int n)
      +
      +struct reftable_record reftable_new_record(uint8_t typ)
      +{
     -+	struct reftable_record clean_idx = {
     -+		.type = BLOCK_TYPE_INDEX,
     -+		.u.idx = {
     -+			.last_key = STRBUF_INIT,
     -+		},
     -+	};
      +	struct reftable_record clean = {
      +		.type = typ,
      +	};
      +
     -+	if (typ == BLOCK_TYPE_INDEX) {
     -+		return clean_idx;
     ++	/* the following is involved, but the naive solution (just return
     ++	 * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage
     ++	 * clean.u.obj.offsets pointer on Windows VS CI.  Go figure.
     ++	 */
     ++	switch (typ) {
     ++	case BLOCK_TYPE_OBJ:
     ++	{
     ++		struct reftable_obj_record obj = { 0 };
     ++		clean.u.obj = obj;
     ++		break;
     ++	}
     ++	case BLOCK_TYPE_INDEX:
     ++	{
     ++		struct reftable_index_record idx = {
     ++			.last_key = STRBUF_INIT,
     ++		};
     ++		clean.u.idx = idx;
     ++		break;
     ++	}
     ++	case BLOCK_TYPE_REF:
     ++	{
     ++		struct reftable_ref_record ref = { 0 };
     ++		clean.u.ref = ref;
     ++		break;
     ++	}
     ++	case BLOCK_TYPE_LOG:
     ++	{
     ++		struct reftable_log_record log = { 0 };
     ++		clean.u.log = log;
     ++		break;
     ++	}
      +	}
     -+
      +	return clean;
      +}
      
       ## reftable/record.h ##
      @@ reftable/record.h: struct reftable_record_vtable {
     - 	int (*is_deletion)(const void *rec);
     + 	int (*equal)(const void *a, const void *b, int hash_size);
       };
       
      -/* record is a generic wrapper for different types of records. */
     @@ reftable/record.h: struct reftable_obj_record {
      +};
      +
       /* see struct record_vtable */
     - 
     + int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
       void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
      @@ reftable/record.h: int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
       			   int hash_size);
     @@ reftable/record_test.c
       {
      -	struct reftable_record copy =
      -		reftable_new_record(reftable_record_type(rec));
     -+	struct reftable_record copy = reftable_new_record(reftable_record_type(rec));
     ++	struct reftable_record copy = { 0 };
     ++	uint8_t typ;
      +
     ++	typ = reftable_record_type(rec);
     ++	copy = reftable_new_record(typ);
       	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
       	/* do it twice to catch memory leaks */
       	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
     --	switch (reftable_record_type(&copy)) {
     -+	switch (copy.type) {
     - 	case BLOCK_TYPE_REF:
     --		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
     --						 reftable_record_as_ref(rec),
     -+		EXPECT(reftable_ref_record_equal(&copy.u.ref, &rec->u.ref,
     - 						 GIT_SHA1_RAWSZ));
     - 		break;
     - 	case BLOCK_TYPE_LOG:
     --		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
     --						 reftable_record_as_log(rec),
     -+		EXPECT(reftable_log_record_equal(&copy.u.log, &rec->u.log,
     - 						 GIT_SHA1_RAWSZ));
     - 		break;
     - 	}
     + 	EXPECT(reftable_record_equal(rec, &copy, GIT_SHA1_RAWSZ));
      -	reftable_record_destroy(&copy);
      +	reftable_record_release(&copy);
       }
     @@ reftable/record_test.c: static void test_reftable_ref_record_roundtrip(void)
      +		struct reftable_record in = {
      +			.type = BLOCK_TYPE_REF,
      +		};
     -+		struct reftable_record out = {
     -+			.type = BLOCK_TYPE_REF
     -+		};
     ++		struct reftable_record out = { .type = BLOCK_TYPE_REF };
       		struct strbuf key = STRBUF_INIT;
      -		struct reftable_record rec = { NULL };
       		uint8_t buffer[1024] = { 0 };
     @@ reftable/record_test.c: static void test_reftable_ref_record_roundtrip(void)
      -			set_hash(in.value.val2.value, 1);
      -			in.value.val2.target_value =
      +			in.u.ref.value.val2.value =
     -+				reftable_malloc(GIT_SHA1_RAWSZ);
     -+			set_hash(in.u.ref.value.val2.value, 1);
     -+			in.u.ref.value.val2.target_value =
       				reftable_malloc(GIT_SHA1_RAWSZ);
      -			set_hash(in.value.val2.target_value, 2);
     ++			set_hash(in.u.ref.value.val2.value, 1);
     ++			in.u.ref.value.val2.target_value =
     ++				reftable_malloc(GIT_SHA1_RAWSZ);
      +			set_hash(in.u.ref.value.val2.target_value, 2);
       			break;
       		case REFTABLE_REF_SYMREF:
     @@ reftable/record_test.c: static void test_reftable_log_record_roundtrip(void)
       	set_test_hash(in[0].value.update.old_hash, 2);
       	for (i = 0; i < ARRAY_SIZE(in); i++) {
      -		struct reftable_record rec = { NULL };
     -+		struct reftable_record rec = {
     -+			.type = BLOCK_TYPE_LOG
     -+		};
     ++		struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
       		struct strbuf key = STRBUF_INIT;
       		uint8_t buffer[1024] = { 0 };
       		struct string_view dest = {
     @@ reftable/record_test.c: static void test_reftable_obj_record_roundtrip(void)
       		struct strbuf key = STRBUF_INIT;
      -		struct reftable_obj_record out = { NULL };
      -		struct reftable_record rec_out = { NULL };
     -+		struct reftable_record out = {
     -+			.type = BLOCK_TYPE_OBJ
     -+		};
     ++		struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
       		int n, m;
       		uint8_t extra;
       
     @@ reftable/record_test.c: static void test_reftable_obj_record_roundtrip(void)
       					   GIT_SHA1_RAWSZ);
       		EXPECT(n == m);
       
     --		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
     --		EXPECT(in.offset_len == out.offset_len);
     --
     --		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
     --			       in.hash_prefix_len));
     --		EXPECT(0 == memcmp(in.offsets, out.offsets,
     --				   sizeof(uint64_t) * in.offset_len));
     -+		EXPECT(in.u.obj.hash_prefix_len == out.u.obj.hash_prefix_len);
     -+		EXPECT(in.u.obj.offset_len == out.u.obj.offset_len);
     -+		if (in.u.obj.hash_prefix_len)
     -+			EXPECT(!memcmp(in.u.obj.hash_prefix, out.u.obj.hash_prefix,
     -+				       in.u.obj.hash_prefix_len));
     -+		if (in.u.obj.offset_len)
     -+			EXPECT(!memcmp(in.u.obj.offsets, out.u.obj.offsets,
     -+					   sizeof(uint64_t) * in.u.obj.offset_len));
     +-		EXPECT(reftable_record_equal(&rec, &rec_out, GIT_SHA1_RAWSZ));
     ++		EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
       		strbuf_release(&key);
      -		reftable_record_release(&rec_out);
      +		reftable_record_release(&out);
     @@ reftable/record_test.c: static void test_reftable_index_record_roundtrip(void)
      +	m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
       	EXPECT(m == n);
       
     --	EXPECT(in.offset == out.offset);
     -+	EXPECT(in.u.idx.offset == out.u.idx.offset);
     +-	EXPECT(reftable_record_equal(&rec, &out_rec, GIT_SHA1_RAWSZ));
     ++	EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
       
      -	reftable_record_release(&out_rec);
      +	reftable_record_release(&out);
     @@ reftable/writer.c: static void write_object_record(void *void_arg, void *key)
      -		.hash_prefix_len = arg->w->stats.object_id_len,
      -		.offsets = entry->offsets,
      -		.offset_len = entry->offset_len,
     -+	struct reftable_record rec = {
     -+		.type = BLOCK_TYPE_OBJ,
     -+		.u.obj = {
     -+			.hash_prefix = (uint8_t *)entry->hash.buf,
     -+			.hash_prefix_len = arg->w->stats.object_id_len,
     -+			.offsets = entry->offsets,
     -+			.offset_len = entry->offset_len,
     -+		}
     - 	};
     +-	};
      -	struct reftable_record rec = { NULL };
     ++	struct reftable_record
     ++		rec = { .type = BLOCK_TYPE_OBJ,
     ++			.u.obj = {
     ++				.hash_prefix = (uint8_t *)entry->hash.buf,
     ++				.hash_prefix_len = arg->w->stats.object_id_len,
     ++				.offsets = entry->offsets,
     ++				.offset_len = entry->offset_len,
     ++			} };
       	if (arg->err < 0)
       		goto done;
       
  -:  ----------- > 15:  6c06bd91662 reftable: add print functions to the record types
  -:  ----------- > 16:  e16bf0c5212 reftable: be more paranoid about 0-length memcpy calls

-- 
gitgitgadget
```

## \[PATCH v5 02/16\] reftable: fix resource leak in block.c error path

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:44 
- Message: https://ratatoskr.run/git/2021/12/7916647.md

```text
From: Han-Wen Nienhuys <redacted>

Add test coverage for corrupt zlib data. Fix memory leaks demonstrated by
unittest.

This problem was discovered by a Coverity scan.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/block.c          | 26 +++++++++------
 reftable/reader.c         | 24 ++++++++------
 reftable/readwrite_test.c | 66 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 98 insertions(+), 18 deletions(-)

diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c947..6c8e8705205 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	uint32_t full_block_size = table_block_size;
 	uint8_t typ = block->data[header_off];
 	uint32_t sz = get_be24(block->data + header_off + 1);
-
+	int err = 0;
 	uint16_t restart_count = 0;
 	uint32_t restart_start = 0;
 	uint8_t *restart_bytes = NULL;
+	uint8_t *uncompressed = NULL;
 
-	if (!reftable_is_block_type(typ))
-		return REFTABLE_FORMAT_ERROR;
+	if (!reftable_is_block_type(typ)) {
+		err =  REFTABLE_FORMAT_ERROR;
+		goto done;
+	}
 
 	if (typ == BLOCK_TYPE_LOG) {
 		int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		uLongf src_len = block->len - block_header_skip;
 		/* Log blocks specify the *uncompressed* size in their header.
 		 */
-		uint8_t *uncompressed = reftable_malloc(sz);
+		uncompressed = reftable_malloc(sz);
 
 		/* Copy over the block header verbatim. It's not compressed. */
 		memcpy(uncompressed, block->data, block_header_skip);
@@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		if (Z_OK !=
 		    uncompress2(uncompressed + block_header_skip, &dst_len,
 				block->data + block_header_skip, &src_len)) {
-			reftable_free(uncompressed);
-			return REFTABLE_ZLIB_ERROR;
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz)
-			return REFTABLE_FORMAT_ERROR;
+		if (dst_len + block_header_skip != sz) {
+			err = REFTABLE_FORMAT_ERROR;
+			goto done;
+		}
 
 		/* We're done with the input data. */
 		reftable_block_done(block);
 		block->data = uncompressed;
+		uncompressed = NULL;
 		block->len = sz;
 		block->source = malloc_block_source();
 		full_block_size = src_len + block_header_skip;
@@ -251,7 +257,9 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	br->restart_count = restart_count;
 	br->restart_bytes = restart_bytes;
 
-	return 0;
+done:
+	reftable_free(uncompressed);
+	return err;
 }
 
 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645a..0d16b098f5e 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -290,28 +290,34 @@ int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
 
 	err = reader_get_block(r, &block, next_off, guess_block_size);
 	if (err < 0)
-		return err;
+		goto done;
 
 	block_size = extract_block_size(block.data, &block_typ, next_off,
 					r->version);
-	if (block_size < 0)
-		return block_size;
-
+	if (block_size < 0) {
+		err = block_size;
+		goto done;
+	}
 	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
-		reftable_block_done(&block);
-		return 1;
+		err = 1;
+		goto done;
 	}
 
 	if (block_size > guess_block_size) {
 		reftable_block_done(&block);
 		err = reader_get_block(r, &block, next_off, block_size);
 		if (err < 0) {
-			return err;
+			goto done;
 		}
 	}
 
-	return block_reader_init(br, &block, header_off, r->block_size,
-				 hash_size(r->hash_id));
+	err = block_reader_init(br, &block, header_off, r->block_size,
+				hash_size(r->hash_id));
+done:
+	if (err)
+		reftable_block_done(&block);
+
+	return err;
 }
 
 static int table_iter_next_block(struct table_iter *dest,
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 5f6bcc2f775..6e88182a83a 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -254,6 +254,71 @@ static void test_log_write_read(void)
 	reader_close(&rd);
 }
 
+static void test_log_zlib_corruption(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader rd = { 0 };
+	struct reftable_block_source source = { 0 };
+	struct strbuf buf = STRBUF_INIT;
+	struct reftable_writer *w =
+		reftable_new_writer(&strbuf_add_void, &buf, &opts);
+	const struct reftable_stats *stats = NULL;
+	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+	char message[100] = { 0 };
+	int err, i, n;
+
+	struct reftable_log_record log = {
+		.refname = "refname",
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.new_hash = hash1,
+				.old_hash = hash2,
+				.name = "My Name",
+				.email = "myname@invalid",
+				.message = message,
+			},
+		},
+	};
+
+	for (i = 0; i < sizeof(message)-1; i++)
+		message[i] = (uint8_t)(rand() % 64 + ' ');
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	err = reftable_writer_add_log(w, &log);
+	EXPECT_ERR(err);
+
+	n = reftable_writer_close(w);
+	EXPECT(n == 0);
+
+	stats = writer_stats(w);
+	EXPECT(stats->log_stats.blocks > 0);
+	reftable_writer_free(w);
+	w = NULL;
+
+	/* corrupt the data. */
+	buf.buf[50] ^= 0x99;
+
+	block_source_from_strbuf(&source, &buf);
+
+	err = init_reader(&rd, &source, "file.log");
+	EXPECT_ERR(err);
+
+	err = reftable_reader_seek_log(&rd, &it, "refname");
+	EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+	reftable_iterator_destroy(&it);
+
+	/* cleanup. */
+	strbuf_release(&buf);
+	reader_close(&rd);
+}
+
 static void test_table_read_write_sequential(void)
 {
 	char **names;
@@ -633,6 +698,7 @@ static void test_corrupt_table(void)
 
 int readwrite_test_main(int argc, const char *argv[])
 {
+	RUN_TEST(test_log_zlib_corruption);
 	RUN_TEST(test_corrupt_table);
 	RUN_TEST(test_corrupt_table_empty);
 	RUN_TEST(test_log_write_read);
-- 
gitgitgadget

```

## \[PATCH v5 03/16\] reftable: fix resource leak blocksource.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:45 
- Message: https://ratatoskr.run/git/2021/12/7916648.md

```text
From: Han-Wen Nienhuys <redacted>

This would be triggered in the unlikely event of fstat() failing on an opened
file.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/blocksource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9aa..2605371c28d 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
 	}
 
 	err = fstat(fd, &st);
-	if (err < 0)
-		return -1;
+	if (err < 0) {
+		close(fd);
+		return REFTABLE_IO_ERROR;
+	}
 
 	p = reftable_calloc(sizeof(struct file_block_source));
 	p->size = st.st_size;
-- 
gitgitgadget

```

## \[PATCH v5 04/16\] reftable: check reftable\_stack\_auto\_compact() return value

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:46 
- Message: https://ratatoskr.run/git/2021/12/7916649.md

```text
From: Han-Wen Nienhuys <redacted>

Fixes a problem detected by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index eb0b7228b0c..d628420e63a 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -814,6 +814,7 @@ static void test_reftable_stack_auto_compaction(void)
 		EXPECT_ERR(err);
 
 		err = reftable_stack_auto_compact(st);
+		EXPECT_ERR(err);
 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
 	}
 
-- 
gitgitgadget

```

## \[PATCH v5 05/16\] reftable: ignore remove() return value in stack\_test.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:47 
- Message: https://ratatoskr.run/git/2021/12/7916650.md

```text
From: Han-Wen Nienhuys <redacted>

If the cleanup fails, there is nothing we can do.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index d628420e63a..4b7292945c3 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -89,7 +89,7 @@ static void test_read_file(void)
 		EXPECT(0 == strcmp(want[i], names[i]));
 	}
 	free_names(names);
-	remove(fn);
+	(void) remove(fn);
 }
 
 static void test_parse_names(void)
-- 
gitgitgadget

```

## \[PATCH v5 06/16\] reftable: fix resource warning

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:49 
- Message: https://ratatoskr.run/git/2021/12/7916651.md

```text
From: Han-Wen Nienhuys <redacted>

This would trigger in the unlikely event that we are compacting, and the next
available file handle is 0.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/stack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/reftable/stack.c b/reftable/stack.c
index df5021ebf08..10dfd370e8e 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -877,7 +877,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	struct strbuf new_table_path = STRBUF_INIT;
 	int err = 0;
 	int have_lock = 0;
-	int lock_file_fd = 0;
+	int lock_file_fd = -1;
 	int compact_count = last - first + 1;
 	char **listp = NULL;
 	char **delete_on_success =
@@ -911,7 +911,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 	}
 	/* Don't want to write to the lock for now.  */
 	close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 
 	have_lock = 1;
 	err = stack_uptodate(st);
@@ -932,7 +932,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 
 		sublock_file_fd = open(subtab_lock.buf,
 				       O_EXCL | O_CREAT | O_WRONLY, 0644);
-		if (sublock_file_fd > 0) {
+		if (sublock_file_fd >= 0) {
 			close(sublock_file_fd);
 		} else if (sublock_file_fd < 0) {
 			if (errno == EEXIST) {
@@ -1013,7 +1013,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
 		goto done;
 	}
 	err = close(lock_file_fd);
-	lock_file_fd = 0;
+	lock_file_fd = -1;
 	if (err < 0) {
 		err = REFTABLE_IO_ERROR;
 		unlink(new_table_path.buf);
@@ -1050,9 +1050,9 @@ done:
 		listp++;
 	}
 	free_names(subtable_locks);
-	if (lock_file_fd > 0) {
+	if (lock_file_fd >= 0) {
 		close(lock_file_fd);
-		lock_file_fd = 0;
+		lock_file_fd = -1;
 	}
 	if (have_lock) {
 		unlink(lock_file_name.buf);
-- 
gitgitgadget

```

## \[PATCH v5 08/16\] reftable: order unittests by complexity

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:54 
- Message: https://ratatoskr.run/git/2021/12/7916652.md

```text
From: Han-Wen Nienhuys <redacted>

This is a more practical ordering when working on refactorings of the reftable
code.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 t/helper/test-reftable.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b789..1f0a28cbb64 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
 
 int cmd__reftable(int argc, const char **argv)
 {
+	/* test from simple to complex. */
 	basics_test_main(argc, argv);
+	record_test_main(argc, argv);
 	block_test_main(argc, argv);
-	merged_test_main(argc, argv);
+	tree_test_main(argc, argv);
 	pq_test_main(argc, argv);
-	record_test_main(argc, argv);
-	refname_test_main(argc, argv);
 	readwrite_test_main(argc, argv);
+	merged_test_main(argc, argv);
 	stack_test_main(argc, argv);
-	tree_test_main(argc, argv);
+	refname_test_main(argc, argv);
 	return 0;
 }
 
-- 
gitgitgadget

```

## \[PATCH v5 07/16\] reftable: all xxx\_free() functions accept NULL arguments

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:55 
- Message: https://ratatoskr.run/git/2021/12/7916653.md

```text
From: Han-Wen Nienhuys <redacted>

This fixes NULL derefs in error paths. Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reader.c | 2 ++
 reftable/writer.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/reftable/reader.c b/reftable/reader.c
index 0d16b098f5e..c7a32331c76 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -647,6 +647,8 @@ int reftable_new_reader(struct reftable_reader **p,
 
 void reftable_reader_free(struct reftable_reader *r)
 {
+	if (!r)
+		return;
 	reader_close(r);
 	reftable_free(r);
 }
diff --git a/reftable/writer.c b/reftable/writer.c
index 3ca721e9f64..9fd24fa93ce 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -150,6 +150,8 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
 
 void reftable_writer_free(struct reftable_writer *w)
 {
+	if (!w)
+		return;
 	reftable_free(w->block);
 	reftable_free(w);
 }
-- 
gitgitgadget

```

## \[PATCH v5 09/16\] reftable: drop stray printf in readwrite\_test

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:56 
- Message: https://ratatoskr.run/git/2021/12/7916654.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/readwrite_test.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 6e88182a83a..9b89a5da103 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -662,7 +662,6 @@ static void test_write_key_order(void)
 	err = reftable_writer_add_ref(w, &refs[0]);
 	EXPECT_ERR(err);
 	err = reftable_writer_add_ref(w, &refs[1]);
-	printf("%d\n", err);
 	EXPECT(err == REFTABLE_API_ERROR);
 	reftable_writer_close(w);
 	reftable_writer_free(w);
-- 
gitgitgadget

```

## \[PATCH v5 10/16\] reftable: handle null refnames in reftable\_ref\_record\_equal

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:57 
- Message: https://ratatoskr.run/git/2021/12/7916655.md

```text
From: Han-Wen Nienhuys <redacted>

Spotted by Coverity.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 8536bd03aa9..8bbcbff1e69 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -1154,9 +1154,11 @@ int reftable_ref_record_equal(struct reftable_ref_record *a,
 			      struct reftable_ref_record *b, int hash_size)
 {
 	assert(hash_size > 0);
-	if (!(0 == strcmp(a->refname, b->refname) &&
-	      a->update_index == b->update_index &&
-	      a->value_type == b->value_type))
+	if (!null_streq(a->refname, b->refname))
+		return 0;
+
+	if (a->update_index != b->update_index ||
+	    a->value_type != b->value_type)
 		return 0;
 
 	switch (a->value_type) {
-- 
gitgitgadget

```

## \[PATCH v5 11/16\] reftable: make reftable-record.h function signatures const correct

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:58 
- Message: https://ratatoskr.run/git/2021/12/7916656.md

```text
From: Han-Wen Nienhuys <redacted>

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c          | 14 +++++++-------
 reftable/reftable-record.h | 14 +++++++-------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index 8bbcbff1e69..f7c77c51539 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -72,7 +72,7 @@ int reftable_is_block_type(uint8_t typ)
 	return 0;
 }
 
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
 {
 	switch (rec->value_type) {
 	case REFTABLE_REF_VAL1:
@@ -84,7 +84,7 @@ uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
 	}
 }
 
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
 {
 	switch (rec->value_type) {
 	case REFTABLE_REF_VAL2:
@@ -251,7 +251,7 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 	}
 }
 
-void reftable_ref_record_print(struct reftable_ref_record *ref,
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
 			       uint32_t hash_id)
 {
 	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
@@ -881,8 +881,8 @@ static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
 	return !memcmp(a, b, sz);
 }
 
-int reftable_log_record_equal(struct reftable_log_record *a,
-			      struct reftable_log_record *b, int hash_size)
+int reftable_log_record_equal(const struct reftable_log_record *a,
+			      const struct reftable_log_record *b, int hash_size)
 {
 	if (!(null_streq(a->refname, b->refname) &&
 	      a->update_index == b->update_index &&
@@ -1150,8 +1150,8 @@ static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
 	return a == b;
 }
 
-int reftable_ref_record_equal(struct reftable_ref_record *a,
-			      struct reftable_ref_record *b, int hash_size)
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+			      const struct reftable_ref_record *b, int hash_size)
 {
 	assert(hash_size > 0);
 	if (!null_streq(a->refname, b->refname))
diff --git a/reftable/reftable-record.h b/reftable/reftable-record.h
index 5370d2288c7..67104f8fbfe 100644
--- a/reftable/reftable-record.h
+++ b/reftable/reftable-record.h
@@ -49,25 +49,25 @@ struct reftable_ref_record {
 
 /* Returns the first hash, or NULL if `rec` is not of type
  * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec);
 
 /* Returns the second hash, or NULL if `rec` is not of type
  * REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec);
 
 /* returns whether 'ref' represents a deletion */
 int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
 
 /* prints a reftable_ref_record onto stdout. Useful for debugging. */
-void reftable_ref_record_print(struct reftable_ref_record *ref,
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
 			       uint32_t hash_id);
 
 /* frees and nulls all pointer values inside `ref`. */
 void reftable_ref_record_release(struct reftable_ref_record *ref);
 
 /* returns whether two reftable_ref_records are the same. Useful for testing. */
-int reftable_ref_record_equal(struct reftable_ref_record *a,
-			      struct reftable_ref_record *b, int hash_size);
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+			      const struct reftable_ref_record *b, int hash_size);
 
 /* reftable_log_record holds a reflog entry */
 struct reftable_log_record {
@@ -104,8 +104,8 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log);
 void reftable_log_record_release(struct reftable_log_record *log);
 
 /* returns whether two records are equal. Useful for testing. */
-int reftable_log_record_equal(struct reftable_log_record *a,
-			      struct reftable_log_record *b, int hash_size);
+int reftable_log_record_equal(const struct reftable_log_record *a,
+			      const struct reftable_log_record *b, int hash_size);
 
 /* dumps a reftable_log_record on stdout, for debugging/testing. */
 void reftable_log_record_print(struct reftable_log_record *log,
-- 
gitgitgadget

```

## \[PATCH v5 12/16\] reftable: implement record equality generically

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:56:59 
- Message: https://ratatoskr.run/git/2021/12/7916657.md

```text
From: Han-Wen Nienhuys <redacted>

This simplifies unittests a little, and provides further coverage for
reftable_record_copy().

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c      | 57 +++++++++++++++++++++++++++++++++++++++++-
 reftable/record.h      |  5 +++-
 reftable/record_test.c | 23 +++--------------
 3 files changed, 63 insertions(+), 22 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index f7c77c51539..2a9e41a992e 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -430,6 +430,15 @@ static int reftable_ref_record_is_deletion_void(const void *p)
 		(const struct reftable_ref_record *)p);
 }
 
+
+static int reftable_ref_record_equal_void(const void *a,
+					  const void *b, int hash_size)
+{
+	struct reftable_ref_record *ra = (struct reftable_ref_record *) a;
+	struct reftable_ref_record *rb = (struct reftable_ref_record *) b;
+	return reftable_ref_record_equal(ra, rb, hash_size);
+}
+
 static struct reftable_record_vtable reftable_ref_record_vtable = {
 	.key = &reftable_ref_record_key,
 	.type = BLOCK_TYPE_REF,
@@ -439,6 +448,7 @@ static struct reftable_record_vtable reftable_ref_record_vtable = {
 	.decode = &reftable_ref_record_decode,
 	.release = &reftable_ref_record_release_void,
 	.is_deletion = &reftable_ref_record_is_deletion_void,
+	.equal = &reftable_ref_record_equal_void,
 };
 
 static void reftable_obj_record_key(const void *r, struct strbuf *dest)
@@ -572,6 +582,25 @@ static int not_a_deletion(const void *p)
 	return 0;
 }
 
+static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size)
+{
+	struct reftable_obj_record *ra = (struct reftable_obj_record *) a;
+	struct reftable_obj_record *rb = (struct reftable_obj_record *) b;
+
+	if (ra->hash_prefix_len != rb->hash_prefix_len
+	    || ra->offset_len != rb->offset_len)
+		return 0;
+
+	if (ra->hash_prefix_len &&
+	    memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len))
+		return 0;
+	if (ra->offset_len &&
+	    memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t)))
+		return 0;
+
+	return 1;
+}
+
 static struct reftable_record_vtable reftable_obj_record_vtable = {
 	.key = &reftable_obj_record_key,
 	.type = BLOCK_TYPE_OBJ,
@@ -580,7 +609,8 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 	.encode = &reftable_obj_record_encode,
 	.decode = &reftable_obj_record_decode,
 	.release = &reftable_obj_record_release,
-	.is_deletion = not_a_deletion,
+	.is_deletion = &not_a_deletion,
+	.equal = &reftable_obj_record_equal_void,
 };
 
 void reftable_log_record_print(struct reftable_log_record *log,
@@ -881,6 +911,14 @@ static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
 	return !memcmp(a, b, sz);
 }
 
+static int reftable_log_record_equal_void(const void *a,
+					  const void *b, int hash_size)
+{
+	return reftable_log_record_equal((struct reftable_log_record *) a,
+					 (struct reftable_log_record *) b,
+					 hash_size);
+}
+
 int reftable_log_record_equal(const struct reftable_log_record *a,
 			      const struct reftable_log_record *b, int hash_size)
 {
@@ -924,6 +962,7 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.decode = &reftable_log_record_decode,
 	.release = &reftable_log_record_release_void,
 	.is_deletion = &reftable_log_record_is_deletion_void,
+	.equal = &reftable_log_record_equal_void
 };
 
 struct reftable_record reftable_new_record(uint8_t typ)
@@ -1042,6 +1081,14 @@ static int reftable_index_record_decode(void *rec, struct strbuf key,
 	return start.len - in.len;
 }
 
+static int reftable_index_record_equal(const void *a, const void *b, int hash_size)
+{
+	struct reftable_index_record *ia = (struct reftable_index_record *) a;
+	struct reftable_index_record *ib = (struct reftable_index_record *) b;
+
+	return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
+}
+
 static struct reftable_record_vtable reftable_index_record_vtable = {
 	.key = &reftable_index_record_key,
 	.type = BLOCK_TYPE_INDEX,
@@ -1051,6 +1098,7 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 	.decode = &reftable_index_record_decode,
 	.release = &reftable_index_record_release,
 	.is_deletion = &not_a_deletion,
+	.equal = &reftable_index_record_equal,
 };
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
@@ -1098,6 +1146,13 @@ int reftable_record_is_deletion(struct reftable_record *rec)
 	return rec->ops->is_deletion(rec->data);
 }
 
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
+{
+	if (a->ops != b->ops)
+		return 0;
+	return a->ops->equal(a->data, b->data, hash_size);
+}
+
 void reftable_record_from_ref(struct reftable_record *rec,
 			      struct reftable_ref_record *ref_rec)
 {
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf4..da75d7d1f11 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -58,6 +58,9 @@ struct reftable_record_vtable {
 
 	/* is this a tombstone? */
 	int (*is_deletion)(const void *rec);
+
+	/* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */
+	int (*equal)(const void *a, const void *b, int hash_size);
 };
 
 /* record is a generic wrapper for different types of records. */
@@ -98,7 +101,7 @@ struct reftable_obj_record {
 };
 
 /* see struct record_vtable */
-
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
 uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace41..92680848156 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -21,18 +21,7 @@ static void test_copy(struct reftable_record *rec)
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
-	switch (reftable_record_type(&copy)) {
-	case BLOCK_TYPE_REF:
-		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
-						 reftable_record_as_ref(rec),
-						 GIT_SHA1_RAWSZ));
-		break;
-	case BLOCK_TYPE_LOG:
-		EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
-						 reftable_record_as_log(rec),
-						 GIT_SHA1_RAWSZ));
-		break;
-	}
+	EXPECT(reftable_record_equal(rec, &copy, GIT_SHA1_RAWSZ));
 	reftable_record_destroy(&copy);
 }
 
@@ -346,13 +335,7 @@ static void test_reftable_obj_record_roundtrip(void)
 					   GIT_SHA1_RAWSZ);
 		EXPECT(n == m);
 
-		EXPECT(in.hash_prefix_len == out.hash_prefix_len);
-		EXPECT(in.offset_len == out.offset_len);
-
-		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
-			       in.hash_prefix_len));
-		EXPECT(0 == memcmp(in.offsets, out.offsets,
-				   sizeof(uint64_t) * in.offset_len));
+		EXPECT(reftable_record_equal(&rec, &rec_out, GIT_SHA1_RAWSZ));
 		strbuf_release(&key);
 		reftable_record_release(&rec_out);
 	}
@@ -390,7 +373,7 @@ static void test_reftable_index_record_roundtrip(void)
 	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
 	EXPECT(m == n);
 
-	EXPECT(in.offset == out.offset);
+	EXPECT(reftable_record_equal(&rec, &out_rec, GIT_SHA1_RAWSZ));
 
 	reftable_record_release(&out_rec);
 	strbuf_release(&key);
-- 
gitgitgadget

```

## \[PATCH v5 13/16\] reftable: remove outdated file reftable.c

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:57:05 
- Message: https://ratatoskr.run/git/2021/12/7916658.md

```text
From: Han-Wen Nienhuys <redacted>

This was renamed to generic.c, but the origin was never removed

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/reftable.c | 115 --------------------------------------------
 1 file changed, 115 deletions(-)
 delete mode 100644 reftable/reftable.c

diff --git a/reftable/reftable.c b/reftable/reftable.c
deleted file mode 100644
index 0e4607a7cd6..00000000000
--- a/reftable/reftable.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "basics.h"
-#include "record.h"
-#include "generic.h"
-#include "reftable-iterator.h"
-#include "reftable-generic.h"
-
-int reftable_table_seek_ref(struct reftable_table *tab,
-			    struct reftable_iterator *it, const char *name)
-{
-	struct reftable_ref_record ref = {
-		.refname = (char *)name,
-	};
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, &ref);
-	return tab->ops->seek_record(tab->table_arg, it, &rec);
-}
-
-int reftable_table_read_ref(struct reftable_table *tab, const char *name,
-			    struct reftable_ref_record *ref)
-{
-	struct reftable_iterator it = { NULL };
-	int err = reftable_table_seek_ref(tab, &it, name);
-	if (err)
-		goto done;
-
-	err = reftable_iterator_next_ref(&it, ref);
-	if (err)
-		goto done;
-
-	if (strcmp(ref->refname, name) ||
-	    reftable_ref_record_is_deletion(ref)) {
-		reftable_ref_record_release(ref);
-		err = 1;
-		goto done;
-	}
-
-done:
-	reftable_iterator_destroy(&it);
-	return err;
-}
-
-uint64_t reftable_table_max_update_index(struct reftable_table *tab)
-{
-	return tab->ops->max_update_index(tab->table_arg);
-}
-
-uint64_t reftable_table_min_update_index(struct reftable_table *tab)
-{
-	return tab->ops->min_update_index(tab->table_arg);
-}
-
-uint32_t reftable_table_hash_id(struct reftable_table *tab)
-{
-	return tab->ops->hash_id(tab->table_arg);
-}
-
-void reftable_iterator_destroy(struct reftable_iterator *it)
-{
-	if (!it->ops) {
-		return;
-	}
-	it->ops->close(it->iter_arg);
-	it->ops = NULL;
-	FREE_AND_NULL(it->iter_arg);
-}
-
-int reftable_iterator_next_ref(struct reftable_iterator *it,
-			       struct reftable_ref_record *ref)
-{
-	struct reftable_record rec = { NULL };
-	reftable_record_from_ref(&rec, ref);
-	return iterator_next(it, &rec);
-}
-
-int reftable_iterator_next_log(struct reftable_iterator *it,
-			       struct reftable_log_record *log)
-{
-	struct reftable_record rec = { NULL };
-	reftable_record_from_log(&rec, log);
-	return iterator_next(it, &rec);
-}
-
-int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
-{
-	return it->ops->next(it->iter_arg, rec);
-}
-
-static int empty_iterator_next(void *arg, struct reftable_record *rec)
-{
-	return 1;
-}
-
-static void empty_iterator_close(void *arg)
-{
-}
-
-static struct reftable_iterator_vtable empty_vtable = {
-	.next = &empty_iterator_next,
-	.close = &empty_iterator_close,
-};
-
-void iterator_set_empty(struct reftable_iterator *it)
-{
-	assert(!it->ops);
-	it->iter_arg = NULL;
-	it->ops = &empty_vtable;
-}
-- 
gitgitgadget

```

## \[PATCH v5 15/16\] reftable: add print functions to the record types

- From: Han-Wen Nienhuys via GitGitGadget \<hidden\>
- Date: 2021-12-22 18:57:06 
- Message: https://ratatoskr.run/git/2021/12/7916659.md

```text
From: Han-Wen Nienhuys <redacted>

This isn't used per se, but it is useful for debugging, especially
Windows CI failures.

Signed-off-by: Han-Wen Nienhuys <redacted>
---
 reftable/record.c      | 85 +++++++++++++++++++++++++++++++++++-------
 reftable/record.h      |  4 ++
 reftable/record_test.c | 21 ++++++++++-
 3 files changed, 95 insertions(+), 15 deletions(-)

diff --git a/reftable/record.c b/reftable/record.c
index a8cee628942..fbaa1fbef56 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -255,8 +255,8 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
 	}
 }
 
-void reftable_ref_record_print(const struct reftable_ref_record *ref,
-			       uint32_t hash_id)
+static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref,
+					 int hash_size)
 {
 	char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
 	printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
@@ -265,14 +265,14 @@ void reftable_ref_record_print(const struct reftable_ref_record *ref,
 		printf("=> %s", ref->value.symref);
 		break;
 	case REFTABLE_REF_VAL2:
-		hex_format(hex, ref->value.val2.value, hash_size(hash_id));
+		hex_format(hex, ref->value.val2.value, hash_size);
 		printf("val 2 %s", hex);
 		hex_format(hex, ref->value.val2.target_value,
-			   hash_size(hash_id));
+			   hash_size);
 		printf("(T %s)", hex);
 		break;
 	case REFTABLE_REF_VAL1:
-		hex_format(hex, ref->value.val1, hash_size(hash_id));
+		hex_format(hex, ref->value.val1, hash_size);
 		printf("val 1 %s", hex);
 		break;
 	case REFTABLE_REF_DELETION:
@@ -282,6 +282,11 @@ void reftable_ref_record_print(const struct reftable_ref_record *ref,
 	printf("}\n");
 }
 
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
+			       uint32_t hash_id) {
+	reftable_ref_record_print_sz(ref, hash_size(hash_id));
+}
+
 static void reftable_ref_record_release_void(void *rec)
 {
 	reftable_ref_record_release(rec);
@@ -443,6 +448,12 @@ static int reftable_ref_record_equal_void(const void *a,
 	return reftable_ref_record_equal(ra, rb, hash_size);
 }
 
+static void reftable_ref_record_print_void(const void *rec,
+					   int hash_size)
+{
+	reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size);
+}
+
 static struct reftable_record_vtable reftable_ref_record_vtable = {
 	.key = &reftable_ref_record_key,
 	.type = BLOCK_TYPE_REF,
@@ -453,6 +464,7 @@ static struct reftable_record_vtable reftable_ref_record_vtable = {
 	.release = &reftable_ref_record_release_void,
 	.is_deletion = &reftable_ref_record_is_deletion_void,
 	.equal = &reftable_ref_record_equal_void,
+	.print = &reftable_ref_record_print_void,
 };
 
 static void reftable_obj_record_key(const void *r, struct strbuf *dest)
@@ -471,6 +483,21 @@ static void reftable_obj_record_release(void *rec)
 	memset(obj, 0, sizeof(struct reftable_obj_record));
 }
 
+static void reftable_obj_record_print(const void *rec, int hash_size)
+{
+	const struct reftable_obj_record *obj = rec;
+	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
+	struct strbuf offset_str = STRBUF_INIT;
+	int i;
+
+	for (i = 0; i < obj->offset_len; i++)
+		strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]);
+	hex_format(hex, obj->hash_prefix, obj->hash_prefix_len);
+	printf("prefix %s (len %d), offsets [%s]\n",
+	       hex, obj->hash_prefix_len, offset_str.buf);
+	strbuf_release(&offset_str);
+}
+
 static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
 					  int hash_size)
 {
@@ -617,31 +644,41 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
 	.release = &reftable_obj_record_release,
 	.is_deletion = &not_a_deletion,
 	.equal = &reftable_obj_record_equal_void,
+	.print = &reftable_obj_record_print,
 };
 
-void reftable_log_record_print(struct reftable_log_record *log,
-			       uint32_t hash_id)
+static void reftable_log_record_print_sz(struct reftable_log_record *log,
+					 int hash_size)
 {
 	char hex[GIT_MAX_HEXSZ + 1] = { 0 };
 
 	switch (log->value_type) {
 	case REFTABLE_LOG_DELETION:
-		printf("log{%s(%" PRIu64 ") delete", log->refname,
+		printf("log{%s(%" PRIu64 ") delete\n", log->refname,
 		       log->update_index);
 		break;
 	case REFTABLE_LOG_UPDATE:
 		printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
-		       log->refname, log->update_index, log->value.update.name,
-		       log->value.update.email, log->value.update.time,
+		       log->refname, log->update_index,
+		       log->value.update.name ? log->value.update.name : "",
+		       log->value.update.email ? log->value.update.email : "",
+		       log->value.update.time,
 		       log->value.update.tz_offset);
-		hex_format(hex, log->value.update.old_hash, hash_size(hash_id));
+		hex_format(hex, log->value.update.old_hash, hash_size);
 		printf("%s => ", hex);
-		hex_format(hex, log->value.update.new_hash, hash_size(hash_id));
-		printf("%s\n\n%s\n}\n", hex, log->value.update.message);
+		hex_format(hex, log->value.update.new_hash, hash_size);
+		printf("%s\n\n%s\n}\n", hex,
+		       log->value.update.message ? log->value.update.message : "");
 		break;
 	}
 }
 
+void reftable_log_record_print(struct reftable_log_record *log,
+				      uint32_t hash_id)
+{
+	reftable_log_record_print_sz(log, hash_size(hash_id));
+}
+
 static void reftable_log_record_key(const void *r, struct strbuf *dest)
 {
 	const struct reftable_log_record *rec =
@@ -959,6 +996,11 @@ static int reftable_log_record_is_deletion_void(const void *p)
 		(const struct reftable_log_record *)p);
 }
 
+static void reftable_log_record_print_void(const void *rec, int hash_size)
+{
+	reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size);
+}
+
 static struct reftable_record_vtable reftable_log_record_vtable = {
 	.key = &reftable_log_record_key,
 	.type = BLOCK_TYPE_LOG,
@@ -968,7 +1010,8 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
 	.decode = &reftable_log_record_decode,
 	.release = &reftable_log_record_release_void,
 	.is_deletion = &reftable_log_record_is_deletion_void,
-	.equal = &reftable_log_record_equal_void
+	.equal = &reftable_log_record_equal_void,
+	.print = &reftable_log_record_print_void,
 };
 
 static void reftable_index_record_key(const void *r, struct strbuf *dest)
@@ -1043,6 +1086,13 @@ static int reftable_index_record_equal(const void *a, const void *b, int hash_si
 	return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
 }
 
+static void reftable_index_record_print(const void *rec, int hash_size)
+{
+	const struct reftable_index_record *idx = rec;
+	/* TODO: escape null chars? */
+	printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset);
+}
+
 static struct reftable_record_vtable reftable_index_record_vtable = {
 	.key = &reftable_index_record_key,
 	.type = BLOCK_TYPE_INDEX,
@@ -1053,6 +1103,7 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
 	.release = &reftable_index_record_release,
 	.is_deletion = &not_a_deletion,
 	.equal = &reftable_index_record_equal,
+	.print = &reftable_index_record_print,
 };
 
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
@@ -1255,3 +1306,9 @@ struct reftable_record reftable_new_record(uint8_t typ)
 	}
 	return clean;
 }
+
+void reftable_record_print(struct reftable_record *rec, int hash_size)
+{
+	printf("'%c': ", rec->type);
+	reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size);
+}
diff --git a/reftable/record.h b/reftable/record.h
index 010a322e901..fd80cd451d5 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -61,6 +61,9 @@ struct reftable_record_vtable {
 
 	/* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */
 	int (*equal)(const void *a, const void *b, int hash_size);
+
+	/* Print on stdout, for debugging. */
+	void (*print)(const void *rec, int hash_size);
 };
 
 /* returns true for recognized block types. Block start with the block type. */
@@ -112,6 +115,7 @@ struct reftable_record {
 
 /* see struct record_vtable */
 int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
+void reftable_record_print(struct reftable_record *rec, int hash_size);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
 uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
diff --git a/reftable/record_test.c b/reftable/record_test.c
index c6fdd1925a9..f91ea5e8830 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -25,6 +25,10 @@ static void test_copy(struct reftable_record *rec)
 	/* do it twice to catch memory leaks */
 	reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
 	EXPECT(reftable_record_equal(rec, &copy, GIT_SHA1_RAWSZ));
+
+	puts("testing print coverage:\n");
+	reftable_record_print(&copy, GIT_SHA1_RAWSZ);
+
 	reftable_record_release(&copy);
 }
 
@@ -176,7 +180,8 @@ static void test_reftable_log_record_equal(void)
 static void test_reftable_log_record_roundtrip(void)
 {
 	int i;
-	struct reftable_log_record in[2] = {
+
+	struct reftable_log_record in[] = {
 		{
 			.refname = xstrdup("refs/heads/master"),
 			.update_index = 42,
@@ -197,10 +202,24 @@ static void test_reftable_log_record_roundtrip(void)
 			.refname = xstrdup("refs/heads/master"),
 			.update_index = 22,
 			.value_type = REFTABLE_LOG_DELETION,
+		},
+		{
+			.refname = xstrdup("branch"),
+			.update_index = 33,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value = {
+				.update = {
+					.old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+					.new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+					/* rest of fields left empty. */
+				},
+			},
 		}
 	};
 	set_test_hash(in[0].value.update.new_hash, 1);
 	set_test_hash(in[0].value.update.old_hash, 2);
+	set_test_hash(in[2].value.update.new_hash, 3);
+	set_test_hash(in[2].value.update.old_hash, 4);
 	for (i = 0; i < ARRAY_SIZE(in); i++) {
 		struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
 		struct strbuf key = STRBUF_INIT;
-- 
gitgitgadget

```
