From 17584bc5632da492b7a01a55993ae78d61d846e0 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 29 Apr 2026 10:20:20 -0400
Subject: [PATCH v6 1/3] Add VM corruption check to verify_heapam()

pg_check_visible() can catch cases were tuples are not visible to all
and the VM is set, but it doesn't specifically check page hints. This
doesn't check, for example, an INSERT failing to clear PD_ALL_VISIBLE.

This also helps users who have access to amcheck but not pg_visibility
to diagnose corruption.

Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/flat/CAAKRu_bn%2Be7F4yPFBgFbnP%2BsyJRKyNK092bjD2LKvZW7O4Svag%40mail.gmail.com
---
 contrib/amcheck/verify_heapam.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 20ff58aa782..823f05e5916 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -481,6 +481,7 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 	while ((ctx.buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
 	{
+		uint8		vmbits;
 		OffsetNumber maxoff;
 		OffsetNumber predecessor[MaxOffsetNumber];
 		OffsetNumber successor[MaxOffsetNumber];
@@ -499,6 +500,24 @@ verify_heapam(PG_FUNCTION_ARGS)
 		ctx.blkno = BufferGetBlockNumber(ctx.buffer);
 		ctx.page = BufferGetPage(ctx.buffer);
 
+		/*
+		 * It is corruption if the page-level PD_ALL_VISIBLE flag is clear and
+		 * the visibility map bits corresponding to this heap page are set.
+		 * Any corruption on the VM page itself won't be caught, and won't
+		 * cause visibilitymap_get_status() to fail, because the VM page is
+		 * read with ZERO_ON_ERROR.
+		 */
+		vmbits = visibilitymap_get_status(ctx.rel, ctx.blkno, &vmbuffer);
+
+		if (!PageIsAllVisible(ctx.page) &&
+			(vmbits & VISIBILITYMAP_VALID_BITS) != 0)
+		{
+			ctx.offnum = InvalidOffsetNumber;
+			ctx.attnum = -1;
+			report_corruption(&ctx,
+							  psprintf("page is not marked all-visible in page header but visibility map bit is set"));
+		}
+
 		/* Perform tuple checks */
 		maxoff = PageGetMaxOffsetNumber(ctx.page);
 		for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;
-- 
2.47.3

