From 2d81d0d6194fb6df98f37f624943e80e37a27fc4 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 29 Apr 2026 12:08:26 -0400
Subject: [PATCH v6 3/3] xlog: Verify all block references are used during
 replay

It's unclear if this makes sense to actually commit. It requires that
blocks read during recovery go through XLogReadBufferForRedoExtended()
and has a special case for handling XLOG_FPI_FOR_HINT when full page
writes are disabled.

Author: Andres Freund <andres@anarazel.de>
Co-authored-by: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/oqcsevg35xjan2327x5kdfth6q4fgeqboxfo3v3imeyih2uiny%406sez5dzxl6nt
---
 src/backend/access/transam/xlog.c         | 10 ++++++++++
 src/backend/access/transam/xlogreader.c   |  4 ++++
 src/backend/access/transam/xlogrecovery.c | 16 ++++++++++++++++
 src/backend/access/transam/xlogutils.c    |  6 ++++++
 src/include/access/xlogreader.h           |  4 ++++
 5 files changed, 40 insertions(+)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f8b939853e9..40ba0ac7279 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9084,6 +9084,16 @@ xlog_redo(XLogReaderState *record)
 			{
 				if (info == XLOG_FPI)
 					elog(ERROR, "XLOG_FPI record did not contain a full-page image");
+
+#ifdef USE_ASSERT_CHECKING
+
+				/*
+				 * If full_page_writes are disabled, we don't want to error
+				 * out because we didn't read the block.
+				 */
+				if (info == XLOG_FPI_FOR_HINT)
+					record->record->blocks[block_id].used_read = true;
+#endif
 				continue;
 			}
 
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 946907a2507..8679a2435ed 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -1796,6 +1796,10 @@ DecodeXLogRecord(XLogReaderState *state,
 
 			blk = &decoded->blocks[block_id];
 			blk->in_use = true;
+#ifdef USE_ASSERT_CHECKING
+			blk->used_read = false;
+#endif
+
 			blk->apply_image = false;
 
 			COPY_HEADER_FIELD(&fork_flags, sizeof(uint8));
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a9ebac2d0ef..a5dfbfbdcef 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1965,6 +1965,22 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
 	/* Now apply the WAL record itself */
 	GetRmgr(record->xl_rmid).rm_redo(xlogreader);
 
+	/*
+	 * Verify that all block references were used during replay. This helps
+	 * detect bugs in redo routines. Every referenced block needs to be
+	 * replayed with XLogReadBufferForRedoExtended(), possibly via a helper,
+	 * to ensure that we replay FPIs, extend the relation if necessary, and
+	 * other perform other similar protections.
+	 */
+#ifdef USE_ASSERT_CHECKING
+	for (int block_id = 0; block_id <= xlogreader->record->max_block_id; block_id++)
+	{
+		DecodedBkpBlock *blk = &xlogreader->record->blocks[block_id];
+
+		Assert(!blk->in_use || blk->used_read);
+	}
+#endif
+
 	/*
 	 * After redo, check whether the backup pages associated with the WAL
 	 * record are consistent with the existing pages. This check is done only
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 58b9dab6a90..cc7c8a06089 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -381,6 +381,12 @@ XLogReadBufferForRedoExtended(XLogReaderState *record,
 			 block_id);
 	}
 
+#ifdef USE_ASSERT_CHECKING
+	/* Shouldn't have multiple read references to block */
+	Assert(!record->record->blocks[block_id].used_read);
+	record->record->blocks[block_id].used_read = true;
+#endif
+
 	/*
 	 * Make sure that if the block is marked with WILL_INIT, the caller is
 	 * going to initialize it. And vice versa.
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 4a9a687e879..5b598b20859 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -145,6 +145,10 @@ typedef struct
 	bool		has_data;
 	char	   *data;
 	uint16		data_len;
+
+#ifdef USE_ASSERT_CHECKING
+	bool		used_read;
+#endif
 } DecodedBkpBlock;
 
 /*
-- 
2.47.3

