From b6ddc887453446dafc28782d876517293b2b8a88 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 29 Apr 2026 10:20:20 -0400
Subject: [PATCH v2_PG19 6/8] 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.43.0

