Subject: Add malloc attribute to memory allocation functions

From: "Tristan Partin" <[email-masked]>
Date: Wed, 01 Jul 2026 17:51:25 +0000

Given that we now have a tree that compiles fine against 
-Werror=mismatched-dealloc, we need to make sure that we don't regress. 
By adding the malloc attribute[0], we can protect against regressions, 
enable more accurate code coverage with -fanalyzer, and allow the 
compiler to do some optimizations.

Let me know if I missed any allocators. Additionally, does it make sense 
to add the attribute to the various memory context allocator functions 
like AllocSetAlloc(), or only on the higher level functions.

[0]: https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-malloc

-- 
Tristan Partin
PostgreSQL Contributors Team
AWS (https://aws.amazon.com)
From 743eca63166373f67c456c4fbb010f5ae61d2b83 Mon Sep 17 00:00:00 2001
From: Tristan Partin <[email-masked]>
Date: Wed, 1 Jul 2026 16:34:14 +0000
Subject: [PATCH v1] Add malloc attribute to memory allocation functions

GCC supports a malloc attribute that takes an optional argument of the
allocating functions deallocation function. With this additional
information, GCC can do a few things for us:

- Warn if the deallocation function is not matched to the allocation
  function (-Wmismatched-dealloc, -Wanalyzer-mismatching-deallocation)
- Warn if there is a path would double free memory
  (-Wanalyzer-double-free)
- Warn if the returned memory is not checked for NULL and is
  subsequently passed to a non-NULL argument
  (-Wanalyzer-possible-null-dereference,
  -Wanalyzer-possible-null-argument)
- Warn if the memory was used after it was freed
  (-Wanalyzer-use-after-free)
- Warn if the memory is ever leaked (-Wanalyzer-malloc-leak)
- Warn if a deallocation function is called on global on-stack memory
  (-Wanalyzer-free-of-non-heap)
- Optimize callers because GCC can assume based on the annotation that
  memory allocation will fail extremely infrequently, like malloc(3)

Signed-off-by: Tristan Partin <[email-masked]>
---
 src/include/c.h                  | 12 +++++++++++
 src/include/common/fe_memutils.h | 36 ++++++++++++++++----------------
 src/include/utils/palloc.h       | 36 ++++++++++++++++----------------
 3 files changed, 48 insertions(+), 36 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 0e4aea5d5a..2dd5e713ad 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -274,6 +274,18 @@ extern "C++"
 #define pg_attribute_no_sanitize_alignment()
 #endif
 
+/*
+ * The malloc function attribute indicates that the function is malloc-like.
+ * Clang does not currently make use of this function attribute.
+ *
+ * https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-malloc
+ */
+#if __has_attribute(malloc) && !defined(__clang__)
+#define pg_attribute_malloc(...) __attribute__((malloc(__VA_ARGS__)))
+#else
+#define pg_attribute_malloc(...)
+#endif
+
 /*
  * pg_attribute_nonnull means the compiler should warn if the function is
  * called with the listed arguments set to NULL.  If no arguments are
diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h
index d5c6d37bb6..72caf489d3 100644
--- a/src/include/common/fe_memutils.h
+++ b/src/include/common/fe_memutils.h
@@ -34,21 +34,21 @@
  * "Safe" memory allocation functions --- these exit(1) on failure
  * (except pg_malloc_extended with MCXT_ALLOC_NO_OOM)
  */
-extern char *pg_strdup(const char *in);
-extern void *pg_malloc(size_t size);
-extern void *pg_malloc0(size_t size);
-extern void *pg_malloc_extended(size_t size, int flags);
-extern void *pg_realloc(void *ptr, size_t size);
 extern void pg_free(void *ptr);
+extern char *pg_strdup(const char *in) pg_attribute_malloc(pg_free);
+extern void *pg_malloc(size_t size) pg_attribute_malloc(pg_free);
+extern void *pg_malloc0(size_t size) pg_attribute_malloc(pg_free);
+extern void *pg_malloc_extended(size_t size, int flags) pg_attribute_malloc(pg_free);
+extern void *pg_realloc(void *ptr, size_t size);
 
 /*
  * Support for safe calculation of memory request sizes
  */
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
-extern void *pg_malloc_mul(Size s1, Size s2);
-extern void *pg_malloc0_mul(Size s1, Size s2);
-extern void *pg_malloc_mul_extended(Size s1, Size s2, int flags);
+extern void *pg_malloc_mul(Size s1, Size s2) pg_attribute_malloc(pg_free);
+extern void *pg_malloc0_mul(Size s1, Size s2) pg_attribute_malloc(pg_free);
+extern void *pg_malloc_mul_extended(Size s1, Size s2, int flags) pg_attribute_malloc(pg_free);
 extern void *pg_realloc_mul(void *p, Size s1, Size s2);
 
 /*
@@ -75,16 +75,16 @@ extern void *pg_realloc_mul(void *p, Size s1, Size s2);
 #define pg_realloc_array(pointer, type, count) ((type *) pg_realloc_mul(pointer, sizeof(type), count))
 
 /* Equivalent functions, deliberately named the same as backend functions */
-extern char *pstrdup(const char *in);
-extern char *pnstrdup(const char *in, Size size);
-extern void *palloc(Size size);
-extern void *palloc0(Size size);
-extern void *palloc_extended(Size size, int flags);
-extern void *repalloc(void *pointer, Size size);
 extern void pfree(void *pointer);
-extern void *palloc_mul(Size s1, Size s2);
-extern void *palloc0_mul(Size s1, Size s2);
-extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+extern char *pstrdup(const char *in) pg_attribute_malloc(pfree);
+extern char *pnstrdup(const char *in, Size size) pg_attribute_malloc(pfree);
+extern void *palloc(Size size) pg_attribute_malloc(pfree);
+extern void *palloc0(Size size) pg_attribute_malloc(pfree);
+extern void *palloc_extended(Size size, int flags) pg_attribute_malloc(pfree);
+extern void *repalloc(void *pointer, Size size);
+extern void *palloc_mul(Size s1, Size s2) pg_attribute_malloc(pfree);
+extern void *palloc0_mul(Size s1, Size s2) pg_attribute_malloc(pfree);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags) pg_attribute_malloc(pfree);
 extern void *repalloc_mul(void *p, Size s1, Size s2);
 
 #define palloc_object(type) ((type *) palloc(sizeof(type)))
@@ -95,7 +95,7 @@ extern void *repalloc_mul(void *p, Size s1, Size s2);
 #define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
-extern char *psprintf(const char *fmt, ...) pg_attribute_printf(1, 2);
+extern char *psprintf(const char *fmt, ...) pg_attribute_printf(1, 2) pg_attribute_malloc(pfree);
 extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0);
 
 #endif							/* FE_MEMUTILS_H */
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index 0e934158b6..8180272628 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -68,31 +68,31 @@ extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
 /*
  * Fundamental memory-allocation operations (more are in utils/memutils.h)
  */
-extern void *MemoryContextAlloc(MemoryContext context, Size size);
-extern void *MemoryContextAllocZero(MemoryContext context, Size size);
+extern void *MemoryContextAlloc(MemoryContext context, Size size) pg_attribute_malloc();
+extern void *MemoryContextAllocZero(MemoryContext context, Size size) pg_attribute_malloc();
 extern void *MemoryContextAllocExtended(MemoryContext context,
-										Size size, int flags);
+										Size size, int flags) pg_attribute_malloc();
 extern void *MemoryContextAllocAligned(MemoryContext context,
-									   Size size, Size alignto, int flags);
+									   Size size, Size alignto, int flags) pg_attribute_malloc();
 
-extern void *palloc(Size size);
-extern void *palloc0(Size size);
-extern void *palloc_extended(Size size, int flags);
-extern void *palloc_aligned(Size size, Size alignto, int flags);
+extern void pfree(void *pointer);
+extern void *palloc(Size size) pg_attribute_malloc(pfree);
+extern void *palloc0(Size size) pg_attribute_malloc(pfree);
+extern void *palloc_extended(Size size, int flags) pg_attribute_malloc(pfree);
+extern void *palloc_aligned(Size size, Size alignto, int flags) pg_attribute_malloc(pfree);
 pg_nodiscard extern void *repalloc(void *pointer, Size size);
 pg_nodiscard extern void *repalloc_extended(void *pointer,
 											Size size, int flags);
 pg_nodiscard extern void *repalloc0(void *pointer, Size oldsize, Size size);
-extern void pfree(void *pointer);
 
 /*
  * Support for safe calculation of memory request sizes
  */
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
-extern void *palloc_mul(Size s1, Size s2);
-extern void *palloc0_mul(Size s1, Size s2);
-extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+extern void *palloc_mul(Size s1, Size s2) pg_attribute_malloc(pfree);
+extern void *palloc0_mul(Size s1, Size s2) pg_attribute_malloc(pfree);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags) pg_attribute_malloc(pfree);
 pg_nodiscard extern void *repalloc_mul(void *p, Size s1, Size s2);
 pg_nodiscard extern void *repalloc_mul_extended(void *p, Size s1, Size s2,
 												int flags);
@@ -123,7 +123,7 @@ pg_nodiscard extern void *repalloc_mul_extended(void *p, Size s1, Size s2,
 #define repalloc_array_extended(pointer, type, count, flags) ((type *) repalloc_mul_extended(pointer, sizeof(type), count, flags))
 
 /* Higher-limit allocators. */
-extern void *MemoryContextAllocHuge(MemoryContext context, Size size);
+extern void *MemoryContextAllocHuge(MemoryContext context, Size size) pg_attribute_malloc();
 pg_nodiscard extern void *repalloc_huge(void *pointer, Size size);
 
 /*
@@ -154,14 +154,14 @@ extern void MemoryContextUnregisterResetCallback(MemoryContext context,
  * These are like standard strdup() except the copied string is
  * allocated in a context, not with malloc().
  */
-extern char *MemoryContextStrdup(MemoryContext context, const char *string);
-extern char *pstrdup(const char *in);
-extern char *pnstrdup(const char *in, Size len);
+extern char *MemoryContextStrdup(MemoryContext context, const char *string) pg_attribute_malloc();
+extern char *pstrdup(const char *in) pg_attribute_malloc(pfree);
+extern char *pnstrdup(const char *in, Size len) pg_attribute_malloc(pfree);
 
-extern char *pchomp(const char *in);
+extern char *pchomp(const char *in) pg_attribute_malloc(pfree);
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
-extern char *psprintf(const char *fmt, ...) pg_attribute_printf(1, 2);
+extern char *psprintf(const char *fmt, ...) pg_attribute_printf(1, 2) pg_attribute_malloc(pfree);
 extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0);
 
 #endif							/* PALLOC_H */
-- 
Tristan Partin
https://tristan.partin.io

---

From: solai v <[email-masked]>
Date: Mon, 6 Jul 2026 09:24:25 +0530

Hi Tristan,
I tested this patch on the current master branch.
Before applying the patch, I checked that functions like palloc(),
MemoryContextAlloc(), and pstrdup() were not annotated with a malloc
attribute.
After applying the patch, I verified that pg_attribute_malloc is added
in src/include/c.h and that the relevant backend and frontend memory
allocation functions are annotated appropriately. I also confirmed
that functions intended to be freed with pfree() use
pg_attribute_malloc(pfree), while the frontend allocation functions
use pg_attribute_malloc(pg_free).
The patch applied cleanly, PostgreSQL built successfully, and make
check completed successfully with all 245 tests passing. I didn't
notice any regressions during testing.

Thanks for working on this patch.

Regards,
Solai

---

From: Tom Lane <[email-masked]>
Date: Mon, 06 Jul 2026 00:26:54 -0400

> compiler to do some optimizations.

I'm skeptical that this is going to lead to anything but grief.
In particular, since gcc has never heard of memory contexts,
I don't see how we are not going to get buried in bogus
-Wanalyzer-malloc-leak warnings.  It doesn't really help
to add compiler annotations that only sort-of match our semantics.

(This opinion is based on years of dismissing useless Coverity
warnings of this kind.)

			regards, tom lane

---

From: "Tristan Partin" <[email-masked]>
Date: Mon, 06 Jul 2026 16:34:58 +0000

> (This opinion is based on years of dismissing useless Coverity

This is a fair criticism. On master, the number of 
-Wanalyzer-malloc-leak warnings is 62. With this patch applied, it 
balloons to 598.

---

From: Peter Eisentraut <[email-masked]>
Date: Tue, 7 Jul 2026 17:34:43 +0200

> -Wanalyzer-malloc-leak warnings is 62. With this patch applied, it

But this can also check for a lot more, such as

- mismatching deallocator
- double free
- use after free
- free of things that are not an allocation

If we could tell it, check for all these things but don't worry about 
the leaks, that could be useful.

Also, for frontend tools, libpq, etc. that don't use memory contexts.

---

From: Tom Lane <[email-masked]>
Date: Tue, 07 Jul 2026 11:53:36 -0400

> Also, for frontend tools, libpq, etc. that don't use memory contexts.

Yeah, I was thinking about that last point.  The frontend environment
is a lot closer to the semantics these markers expect, so we could
try doing this in frontend only and see how well that works.

There are still places that I'd expect to be trouble.  For example,
Coverity has never understood the pattern we use in pg_dump's data
collection subroutines, ie, malloc a big array of structs, fill
the individual structs and insert pointers to them into the hash
tables, done.  It always thinks we leaked the array, and I suspect
tools like this will too.  In Coverity's case there's enough
infrastructure to dismiss individual false-positive complaints,
and then it won't bug you about them (until somebody changes the
relevant code enough that the dismissal doesn't match :-().  Unless
there's some similar way to silence individual reports, I don't
foresee tools like this to be usable.  We're not going to change
coding patterns like that one just because some static analyzer
doesn't understand them.

			regards, tom lane

---

From: "Tristan Partin" <[email-masked]>
Date: Tue, 07 Jul 2026 15:54:45 +0000

> Also, for frontend tools, libpq, etc. that don't use memory contexts.

We could add -Wno-analyzer-malloc-leak to backend code.

---

From: Michael Paquier <[email-masked]>
Date: Wed, 8 Jul 2026 07:58:14 +0900

> coding patterns like that one just because some static analyzer

Additional question.  Does this help with requirements like the one
listed in fe-exec.c for PQfreemem() under WIN32?  If the answer to
this question is yes, then it would sound like a win for me, we'd had
our share of issues in the past where we would use a free() call that
interacts with an allocation done in a completely different context
library-wise.  That's something WIN32 cares a lot about, to mention
one place.
--
Michael

---

From: Tom Lane <[email-masked]>
Date: Tue, 07 Jul 2026 19:54:06 -0400

Michael Paquier <[email-masked]> writes:
> listed in fe-exec.c for PQfreemem() under WIN32?

Yeah, I was wondering about that point too.  It would be really nice
to have automated checks for that.  Maybe the right thing to do here
(to start anyway) is to put in targeted annotations that address
specific pain points like that one.

One issue that'd have to be dealt with is that that would involve
putting annotations into the public header file libpq-fe.h.  We'd need
to be sure we do not break things for applications using compilers
other than what we built libpq with.  That seems reasonably easy to do
with some macro trickery, but it's a point to keep in mind.

			regards, tom lane

---

From: "Tristan Partin" <[email-masked]>
Date: Tue, 07 Jul 2026 23:57:23 +0000

> with some macro trickery, but it's a point to keep in mind.

Ok, I think the path forward is two-fold:

1. Add the annotations to specific pain points as Tom suggested 
   (PGfreemem(), frontend memory allocators)
2. Add the annotations to the backend allocators knowing that the patch 
   may not be committed