On 11/03/2026 13:07, Alexander Kuzmenkov wrote:
On Wed, Mar 11, 2026 at 11:45 AM Alexander Kuzmenkov
<[email protected]> wrote:

On Tue, Mar 10, 2026 at 11:09 PM Heikki Linnakangas <[email protected]> wrote:
+1 for initializing all padding in WAL records. In fact I thought that
we already did that. (Except in this case, apparently)

I found 42 exceptions like this. See the attached patch, it
initializes some WAL records and removes the WAL-related Valgrind
suppressions. The regression tests pass under Valgrind with these
changes.

I think I'm making some unneeded changes here though. For example in
ginxlogInsertListPage for a two-int struct with no padding. I'll need
to check them again one by one.

I experimented with this a little more. Valgrind complained about one more place on 'master': the xl_multixact_create got padding, when MultiXactOffset was widened to 64 bits. That could be fixed by swapping the fields.

Another thing I did to find possible initializations: I ran 'pahole bin/postgres' and search for all the "xl_*" structs with padding, and then looked at where they're initialized. Attached patch (0003) shows a few places that look suspicious to me. I don't think I caught all structs used in WAL records, though, like the ginxlogInsertListPage thing mentioned.

I wish we could just mark all WAL record structs with pg_attribute_packed(). Unfortunately pg_attribute_packed() is not available on all compilers we support.

- Heikki
From 2b9a360952aae94eb09afd7a46b0e4170f916464 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Mar 2026 12:42:11 +0200
Subject: [PATCH 1/4] Initialize WAL record structs

Author: Alexander Kuzmenkov <[email protected]>
---
 src/backend/access/brin/brin.c          |  2 ++
 src/backend/access/brin/brin_pageops.c  |  6 ++++++
 src/backend/access/brin/brin_revmap.c   |  4 ++++
 src/backend/access/gin/ginbtree.c       |  4 ++++
 src/backend/access/gin/ginfast.c        |  6 ++++++
 src/backend/access/gin/ginutil.c        |  2 ++
 src/backend/access/gin/ginvacuum.c      |  2 ++
 src/backend/access/gist/gistxlog.c      |  8 ++++++++
 src/backend/access/heap/heapam.c        |  3 +++
 src/backend/access/heap/pruneheap.c     |  5 ++++-
 src/backend/access/nbtree/nbtinsert.c   |  5 +++++
 src/backend/access/nbtree/nbtpage.c     |  6 ++++++
 src/backend/access/spgist/spgdoinsert.c | 10 ++++++++++
 src/backend/access/spgist/spgvacuum.c   |  5 +++++
 src/backend/storage/ipc/standby.c       |  2 ++
 src/tools/valgrind.supp                 | 17 -----------------
 16 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 1909c3254b5..88e32c7fb74 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1141,6 +1141,8 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 		XLogRecPtr	recptr;
 		Page		page;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+
 		xlrec.version = BRIN_CURRENT_VERSION;
 		xlrec.pagesPerRange = BrinGetPagesPerRange(index);
 
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..dc56932ab21 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -187,6 +187,8 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 			XLogRecPtr	recptr;
 			uint8		info = XLOG_BRIN_SAMEPAGE_UPDATE;
 
+			memset(&xlrec, 0, sizeof(xlrec));
+
 			xlrec.offnum = oldoff;
 
 			XLogBeginInsert();
@@ -271,6 +273,8 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
 			XLogRecPtr	recptr;
 			uint8		info;
 
+			memset(&xlrec, 0, sizeof(xlrec));
+
 			info = XLOG_BRIN_UPDATE | (extended ? XLOG_BRIN_INIT_PAGE : 0);
 
 			xlrec.insert.offnum = newoff;
@@ -427,6 +431,8 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
 		XLogRecPtr	recptr;
 		uint8		info;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+
 		info = XLOG_BRIN_INSERT | (extended ? XLOG_BRIN_INIT_PAGE : 0);
 		xlrec.heapBlk = heapBlk;
 		xlrec.pagesPerRange = pagesPerRange;
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..c9db4e59822 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -411,6 +411,8 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 		xl_brin_desummarize xlrec;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+
 		xlrec.pagesPerRange = revmap->rm_pagesPerRange;
 		xlrec.heapBlk = heapBlk;
 		xlrec.regOffset = regOffset;
@@ -624,6 +626,8 @@ revmap_physical_extend(BrinRevmap *revmap)
 		xl_brin_revmap_extend xlrec;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+
 		xlrec.targetBlk = mapBlk;
 
 		XLogBeginInsert();
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..6eaeebf79bc 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -421,6 +421,8 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 			ginxlogInsert xlrec;
 			BlockIdData childblknos[2];
 
+			memset(&xlrec, 0, sizeof(xlrec));
+
 			xlrec.flags = xlflags;
 
 			XLogRegisterData(&xlrec, sizeof(ginxlogInsert));
@@ -461,6 +463,8 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack,
 		Buffer		lbuffer = InvalidBuffer;
 		Page		newrootpg = NULL;
 
+		memset(&data, 0, sizeof(data));
+
 		/* Get a new index page to become the right page */
 		rbuffer = GinNewBuffer(btree->index);
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb65a..710c16887df 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -118,6 +118,8 @@ writeListPage(Relation index, Buffer buffer,
 		ginxlogInsertListPage data;
 		XLogRecPtr	recptr;
 
+		memset(&data, 0, sizeof(data));
+
 		data.rightlink = rightlink;
 		data.ntuples = ntuples;
 
@@ -230,6 +232,8 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 	int			cleanupSize;
 	bool		needWal;
 
+	memset(&data, 0, sizeof(data));
+
 	if (collector->ntuples == 0)
 		return;
 
@@ -571,6 +575,8 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 		Buffer		buffers[GIN_NDELETE_AT_ONCE];
 		BlockNumber freespace[GIN_NDELETE_AT_ONCE];
 
+		memset(&data, 0, sizeof(data));
+
 		data.ndeleted = 0;
 		while (data.ndeleted < GIN_NDELETE_AT_ONCE && blknoToDelete != newHead)
 		{
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index ff927279cc3..76be56e932b 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -650,6 +650,8 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 		XLogRecPtr	recptr;
 		ginxlogUpdateMeta data;
 
+		memset(&data, 0, sizeof(data));
+
 		data.locator = index->rd_locator;
 		data.ntuples = 0;
 		data.newRightlink = data.prevTail = InvalidBlockNumber;
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index d5c8bef5ceb..ad3c4115593 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -200,6 +200,8 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 		XLogRecPtr	recptr;
 		ginxlogDeletePage data;
 
+		memset(&data, 0, sizeof(data));
+
 		/*
 		 * We can't pass REGBUF_STANDARD for the deleted page, because we
 		 * didn't set pd_lower on pre-9.4 versions. The page might've been
diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c
index c783838495f..8629514e265 100644
--- a/src/backend/access/gist/gistxlog.c
+++ b/src/backend/access/gist/gistxlog.c
@@ -501,6 +501,8 @@ gistXLogSplit(bool page_is_leaf,
 	XLogRecPtr	recptr;
 	int			i;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	for (ptr = dist; ptr; ptr = ptr->next)
 		npage++;
 
@@ -553,6 +555,8 @@ gistXLogPageDelete(Buffer buffer, FullTransactionId xid,
 	gistxlogPageDelete xlrec;
 	XLogRecPtr	recptr;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.deleteXid = xid;
 	xlrec.downlinkOffset = downlinkOffset;
 
@@ -633,6 +637,8 @@ gistXLogUpdate(Buffer buffer,
 	int			i;
 	XLogRecPtr	recptr;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.ntodelete = ntodelete;
 	xlrec.ntoinsert = ituplen;
 
@@ -671,6 +677,8 @@ gistXLogDelete(Buffer buffer, OffsetNumber *todelete, int ntodelete,
 	gistxlogDelete xlrec;
 	XLogRecPtr	recptr;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(heaprel);
 	xlrec.snapshotConflictHorizon = snapshotConflictHorizon;
 	xlrec.ntodelete = ntodelete;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8f1c11a9350..b11dba81aef 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2428,6 +2428,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 	int			npages = 0;
 	int			npages_used = 0;
 
+	memset(&scratch, 0, sizeof(scratch));
+
 	/* currently not needed (thus unsupported) for heap_multi_insert() */
 	Assert(!(options & HEAP_INSERT_NO_LOGICAL));
 
@@ -6654,6 +6656,7 @@ heap_inplace_update_and_unlock(Relation relation,
 		BlockNumber blkno;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
 		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
 		xlrec.dbId = MyDatabaseId;
 		xlrec.tsId = MyDatabaseTableSpace;
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 6beeb6956e3..54b4a5ab0c8 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -2058,6 +2058,7 @@ heap_log_freeze_cmp(const void *arg1, const void *arg2)
 static inline void
 heap_log_freeze_new_plan(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
 {
+	memset(plan, 0, sizeof(*plan));
 	plan->xmax = frz->xmax;
 	plan->t_infomask2 = frz->t_infomask2;
 	plan->t_infomask = frz->t_infomask;
@@ -2182,7 +2183,9 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 
 	Assert((vmflags & VISIBILITYMAP_VALID_BITS) == vmflags);
 
-	xlrec.flags = 0;
+	memset(&xlrec, 0, sizeof(xlrec));
+	memset(&freeze_plans, 0, sizeof(freeze_plans));
+
 	regbuf_flags_heap = REGBUF_STANDARD;
 
 	/*
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 796e1513ddf..a4a6d4e8995 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -1336,6 +1336,8 @@ _bt_insertonpg(Relation rel,
 			XLogRecPtr	recptr;
 			uint16		upostingoff;
 
+			memset(&xlrec, 0, sizeof(xlrec));
+			memset(&xlmeta, 0, sizeof(xlmeta));
 			xlrec.offnum = newitemoff;
 
 			XLogBeginInsert();
@@ -1996,6 +1998,7 @@ _bt_split(Relation rel, Relation heaprel, BTScanInsert itup_key, Buffer buf,
 		uint8		xlinfo;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
 		xlrec.level = ropaque->btpo_level;
 		/* See comments below on newitem, orignewitem, and posting lists */
 		xlrec.firstrightoff = firstrightoff;
@@ -2585,6 +2588,8 @@ _bt_newlevel(Relation rel, Relation heaprel, Buffer lbuf, Buffer rbuf)
 		XLogRecPtr	recptr;
 		xl_btree_metadata md;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+		memset(&md, 0, sizeof(md));
 		xlrec.rootblk = rootblknum;
 		xlrec.level = metad->btm_level;
 
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..176090e6558 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -288,6 +288,7 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 		xl_btree_metadata md;
 		XLogRecPtr	recptr;
 
+		memset(&md, 0, sizeof(md));
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD);
 
@@ -476,6 +477,8 @@ _bt_getroot(Relation rel, Relation heaprel, int access)
 			XLogRecPtr	recptr;
 			xl_btree_metadata md;
 
+			memset(&xlrec, 0, sizeof(xlrec));
+			memset(&md, 0, sizeof(md));
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, rootbuf, REGBUF_WILL_INIT);
 			XLogRegisterBuffer(2, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD);
@@ -2255,6 +2258,7 @@ _bt_mark_page_halfdead(Relation rel, Relation heaprel, Buffer leafbuf,
 		xl_btree_mark_page_halfdead xlrec;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
 		xlrec.poffset = poffset;
 		xlrec.leafblk = leafblkno;
 		if (topparent != leafblkno)
@@ -2678,6 +2682,8 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 		uint8		xlinfo;
 		XLogRecPtr	recptr;
 
+		memset(&xlrec, 0, sizeof(xlrec));
+		memset(&xlmeta, 0, sizeof(xlmeta));
 		XLogBeginInsert();
 
 		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..e96b20c30bd 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -204,6 +204,8 @@ addLeafTuple(Relation index, SpGistState *state, SpGistLeafTuple leafTuple,
 {
 	spgxlogAddLeaf xlrec;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.newPage = isNew;
 	xlrec.storesNulls = isNulls;
 
@@ -403,6 +405,8 @@ moveLeafs(Relation index, SpGistState *state,
 	char	   *leafdata,
 			   *leafptr;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	/* This doesn't work on root page */
 	Assert(parent->buffer != InvalidBuffer);
 	Assert(parent->buffer != current->buffer);
@@ -710,6 +714,8 @@ doPickSplit(Relation index, SpGistState *state,
 				nToInsert,
 				maxToInclude;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	in.level = level;
 
 	/*
@@ -1514,6 +1520,8 @@ spgAddNodeAction(Relation index, SpGistState *state,
 	SpGistInnerTuple newInnerTuple;
 	spgxlogAddNode xlrec;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	/* Should not be applied to nulls */
 	Assert(!SpGistPageStoresNulls(current->page));
 
@@ -1722,6 +1730,8 @@ spgSplitNodeAction(Relation index, SpGistState *state,
 	spgxlogSplitTuple xlrec;
 	Buffer		newBuffer = InvalidBuffer;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	/* Should not be applied to nulls */
 	Assert(!SpGistPageStoresNulls(current->page));
 
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..9e6acb873ef 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -140,6 +140,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 	OffsetNumber i,
 				max = PageGetMaxOffsetNumber(page);
 
+	memset(&xlrec, 0, sizeof(xlrec));
 	memset(predecessor, 0, sizeof(predecessor));
 	memset(deletable, 0, sizeof(deletable));
 	nDeletable = 0;
@@ -414,6 +415,8 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 	OffsetNumber i,
 				max = PageGetMaxOffsetNumber(page);
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.nDelete = 0;
 
 	/* Scan page, identify tuples to delete, accumulate stats */
@@ -505,6 +508,8 @@ vacuumRedirectAndPlaceholder(Relation index, Relation heaprel, Buffer buffer)
 	spgxlogVacuumRedirect xlrec;
 	GlobalVisState *vistest;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(heaprel);
 	xlrec.nToPlaceholder = 0;
 	xlrec.snapshotConflictHorizon = InvalidTransactionId;
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index f3ad90c7c7a..4ae5be2792b 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -1357,6 +1357,8 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts)
 	xl_running_xacts xlrec;
 	XLogRecPtr	recptr;
 
+	memset(&xlrec, 0, sizeof(xlrec));
+
 	xlrec.xcnt = CurrRunningXacts->xcnt;
 	xlrec.subxcnt = CurrRunningXacts->subxcnt;
 	xlrec.subxid_overflow = (CurrRunningXacts->subxid_status != SUBXIDS_IN_ARRAY);
diff --git a/src/tools/valgrind.supp b/src/tools/valgrind.supp
index d56794b4f7e..f2ab0c8a052 100644
--- a/src/tools/valgrind.supp
+++ b/src/tools/valgrind.supp
@@ -23,23 +23,6 @@
 	fun:pgstat_write_statsfiles
 }
 
-{
-	padding_XLogRecData_CRC
-	Memcheck:Value8
-
-	fun:pg_comp_crc32c*
-	fun:XLogRecordAssemble
-}
-
-{
-	padding_XLogRecData_write
-	Memcheck:Param
-	pwrite64(buf)
-
-	...
-	fun:XLogWrite
-}
-
 {
 	padding_relcache
 	Memcheck:Param
-- 
2.47.3

From 74d0084faef3bc6d748bd1e26e39c2683d27de1a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Mar 2026 14:30:13 +0200
Subject: [PATCH 2/4] Avoid padding in xl_multixact_create WAL record

When MultiXactOffset was widened to 64 bits in commit bd8d9c9bdf, the
struct started to need alignment padding for the 'moff' field. Reorder
the fields to avoid it.

Discussion: https://www.postgresql.org/message-id/CALzhyqzKTRVsQGj+qDDRVs3Oo0EvffuQvVO0v4rbpWU=sox...@mail.gmail.com
---
 src/include/access/multixact.h     | 2 +-
 src/include/access/xlog_internal.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 2ae8b571dcc..757c631e725 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -72,8 +72,8 @@ typedef struct MultiXactMember
 typedef struct xl_multixact_create
 {
 	MultiXactId mid;			/* new MultiXact's ID */
-	MultiXactOffset moff;		/* its starting offset in members file */
 	int32		nmembers;		/* number of member XIDs */
+	MultiXactOffset moff;		/* its starting offset in members file */
 	MultiXactMember members[FLEXIBLE_ARRAY_MEMBER];
 } xl_multixact_create;
 
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 58ae12bb20f..629ac3a7d3e 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -31,7 +31,7 @@
 /*
  * Each page of XLOG file has a header like this:
  */
-#define XLOG_PAGE_MAGIC 0xD11C	/* can be used as WAL version indicator */
+#define XLOG_PAGE_MAGIC 0xD11D	/* can be used as WAL version indicator */
 
 typedef struct XLogPageHeaderData
 {
-- 
2.47.3

From a60e3cb4911911fff968eadb4e0f318c2edd1dbb Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Mar 2026 14:40:25 +0200
Subject: [PATCH 3/4] XXX: a few more places that maybe need clearing?

I spotted these by doing 'pahole bin/postgres', and searching for
"xl_*" structs that have padding in them. Then I searched where
they're initialized.
---
 src/backend/access/hash/hashinsert.c      | 2 ++
 src/backend/access/heap/heapam.c          | 6 ++++++
 src/backend/access/heap/rewriteheap.c     | 2 ++
 src/backend/access/transam/twophase.c     | 2 ++
 src/backend/commands/tablecmds.c          | 2 ++
 src/backend/replication/logical/message.c | 2 ++
 6 files changed, 16 insertions(+)

diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..45e613ceddb 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -426,6 +426,8 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf)
 			xl_hash_vacuum_one_page xlrec;
 			XLogRecPtr	recptr;
 
+			/* XXX: clear padding? */
+
 			xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(hrel);
 			xlrec.snapshotConflictHorizon = snapshotConflictHorizon;
 			xlrec.ntuples = ndeletable;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b11dba81aef..54943be6126 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2616,6 +2616,12 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 			xlrec = (xl_heap_multi_insert *) scratchptr;
 			scratchptr += SizeOfHeapMultiInsert;
 
+			/*
+			 * XXX: need to clear padding in xl_heap_multi_insert? The scratch
+			 * area was zeroed at the top of the function, but we're reusing
+			 * it for multiple records.
+			 */
+
 			/*
 			 * Allocate offsets array. Unless we're reinitializing the page,
 			 * in that case the tuples are stored in order starting at
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..b6d784572ec 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -842,6 +842,8 @@ logical_heap_rewrite_flush_mappings(RewriteState state)
 		else
 			dboid = MyDatabaseId;
 
+		/* XXX: clear padding? */
+
 		xlrec.num_mappings = num_mappings;
 		xlrec.mapped_rel = RelationGetRelid(state->rs_old_rel);
 		xlrec.mapped_xid = src->xid;
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 55b9f38927d..c22ab93965e 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1076,6 +1076,8 @@ StartPrepare(GlobalTransaction gxact)
 
 	records.total_len = 0;
 
+	/* XXX: Clear padding? */
+
 	/* Create header */
 	hdr.magic = TWOPHASE_MAGIC;
 	hdr.total_len = 0;			/* EndPrepare will fill this in */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a7c32679b22..1bf59f77ae1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2325,6 +2325,8 @@ ExecuteTruncateGuts(List *explicit_rels,
 		foreach(cell, relids_logged)
 			logrelids[i++] = lfirst_oid(cell);
 
+		/* XXX: clear padding? */
+
 		xlrec.dbId = MyDatabaseId;
 		xlrec.nrelids = list_length(relids_logged);
 		xlrec.flags = 0;
diff --git a/src/backend/replication/logical/message.c b/src/backend/replication/logical/message.c
index 06825d66e7f..40d032a4ffc 100644
--- a/src/backend/replication/logical/message.c
+++ b/src/backend/replication/logical/message.c
@@ -55,6 +55,8 @@ LogLogicalMessage(const char *prefix, const char *message, size_t size,
 		GetCurrentTransactionId();
 	}
 
+	/* XXX: clear padding? */
+
 	xlrec.dbId = MyDatabaseId;
 	xlrec.transactional = transactional;
 	/* trailing zero is critical; see logicalmsg_desc */
-- 
2.47.3

From f36c2e1a052ae30d6fbdc946f990ab079234e332 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Mar 2026 12:52:41 +0200
Subject: [PATCH 4/4] Add an explicit valgrind check in XLogInsert() for
 uninitialized data

Even without this, Valgrind complains if uninitialized memory copied
into the WAL, when the WAL is flushed to disk. With this commit, it's
caught earlier, in XLogInsert(), which makes it more clear where the
uninitialized data is coming from.
---
 src/backend/access/transam/xlog.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b9b678f3722..8eae29a3548 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -98,6 +98,7 @@
 #include "utils/guc_hooks.h"
 #include "utils/guc_tables.h"
 #include "utils/injection_point.h"
+#include "utils/memdebug.h"
 #include "utils/pgstat_internal.h"
 #include "utils/ps_status.h"
 #include "utils/relmapper.h"
@@ -1259,6 +1260,8 @@ CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,
 		const char *rdata_data = rdata->data;
 		int			rdata_len = rdata->len;
 
+		VALGRIND_CHECK_MEM_IS_DEFINED(rdata_data, rdata_len);
+
 		while (rdata_len > freespace)
 		{
 			/*
-- 
2.47.3

Reply via email to