From 743eca63166373f67c456c4fbb010f5ae61d2b83 Mon Sep 17 00:00:00 2001
From: Tristan Partin <tristan@partin.io>
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 <tristan@partin.io>
---
 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

