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