Hi,
I played around with adding
__attribute__((malloc(free_func), malloc(another_free_func)))
annotations to a few functions in pg. See
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
Adding them to pg_list.h seems to have found two valid issues when compiling
without optimization:
[1001/2331 22 42%] Compiling C object
src/backend/postgres_lib.a.p/commands_tablecmds.c.o
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c: In
function ‘ATExecAttachPartition’:
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c:17966:25:
warning: pointer ‘partBoundConstraint’ may be used after ‘list_concat’
[-Wuse-after-free]
17966 |
get_proposed_default_constraint(partBoundConstraint);
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../../../home/andres/src/postgresql/src/backend/commands/tablecmds.c:17919:26:
note: call to ‘list_concat’ here
17919 | partConstraint = list_concat(partBoundConstraint,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17920 |
RelationGetPartitionQual(rel));
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[1233/2331 22 52%] Compiling C object
src/backend/postgres_lib.a.p/rewrite_rewriteHandler.c.o
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c: In
function ‘rewriteRuleAction’:
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c:550:41:
warning: pointer ‘newjointree’ may be used after ‘list_concat’
[-Wuse-after-free]
550 | checkExprHasSubLink((Node *)
newjointree);
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../../../home/andres/src/postgresql/src/backend/rewrite/rewriteHandler.c:542:33:
note: call to ‘list_concat’ here
542 | list_concat(newjointree,
sub_action->jointree->fromlist);
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Both of these bugs seem somewhat older, the latter going back to 19ff959bff0,
in 2005. I'm a bit surprised that we haven't hit them before, via
DEBUG_LIST_MEMORY_USAGE?
When compiling with optimization, another issue is reported:
[508/1814 22 28%] Compiling C object
src/backend/postgres_lib.a.p/commands_explain.c.o
../../../../home/andres/src/postgresql/src/backend/commands/explain.c: In
function 'ExplainNode':
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2037:25:
warning: pointer 'ancestors' may be used after 'lcons' [-Wuse-after-free]
2037 | show_upper_qual(plan->qual, "Filter",
planstate, ancestors, es);
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function 'show_group_keys',
inlined from 'ExplainNode' at
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2036:4:
../../../../home/andres/src/postgresql/src/backend/commands/explain.c:2564:21:
note: call to 'lcons' here
2564 | ancestors = lcons(plan, ancestors);
| ^~~~~~~~~~~~~~~~~~~~~~
which looks like it might be valid - the caller's "ancestors" variable could
now be freed? There do appear to be further instances of the issue, e.g. in
show_agg_keys(), that aren't flagged for some reason.
For something like pg_list.h the malloc(free) attribute is a bit awkward to
use, because one a) needs to list ~30 functions that can free a list and b)
the referenced functions need to be declared.
In my quick hack I just duplicated the relevant part of pg_list.h and added
the appropriate attributes to the copy of the original declaration.
I also added such attributes to bitmapset.h and palloc() et al, but that
didn't find existing bugs. It does find use-after-free instances if I add
some, similarly it does find cases of mismatching palloc with free etc.
The attached prototype is quite rough and will likely fail on anything but a
recent gcc (likely >= 12).
Do others think this would be useful enough to be worth polishing? And do you
agree the warnings above are bugs?
Greetings,
Andres Freund
>From e05c1260ee70457e9a899d5c32e5b85702193739 Mon Sep 17 00:00:00 2001
From: Andres Freund <[email protected]>
Date: Mon, 26 Jun 2023 12:17:30 -0700
Subject: [PATCH v1 1/2] Add allocator attributes.
Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
src/include/c.h | 9 ++++
src/include/nodes/bitmapset.h | 40 ++++++++++-----
src/include/nodes/pg_list.h | 97 +++++++++++++++++++++++++++++++++++
src/include/utils/palloc.h | 55 ++++++++++++--------
4 files changed, 167 insertions(+), 34 deletions(-)
diff --git a/src/include/c.h b/src/include/c.h
index f69d739be57..920fdf983a1 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -295,6 +295,15 @@
#define unlikely(x) ((x) != 0)
#endif
+#if defined(__GNUC__) && !defined(__clang__)
+#define pg_malloc_attr(deallocator) malloc(deallocator)
+#define pg_malloc_attr_i(deallocator, ptr_at) malloc(deallocator, ptr_at)
+#else
+#define pg_malloc_attr(deallocator)
+#define pg_malloc_attr_i(deallocator, ptr_at)
+#endif
+
+
/*
* CppAsString
* Convert the argument to a string, using the C preprocessor.
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 14de6a9ff1e..5037f6907ec 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -78,15 +78,27 @@ typedef enum
* function prototypes in nodes/bitmapset.c
*/
-extern Bitmapset *bms_copy(const Bitmapset *a);
-extern bool bms_equal(const Bitmapset *a, const Bitmapset *b);
-extern int bms_compare(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_make_singleton(int x);
+extern Bitmapset *bms_add_member(Bitmapset *a, int x);
+extern Bitmapset *bms_del_member(Bitmapset *a, int x);
+extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper);
+extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b);
extern void bms_free(Bitmapset *a);
-extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b);
+#define BMS_ALLOC_ATTRIBUTES __attribute__((pg_malloc_attr(bms_free), pg_malloc_attr(bms_add_member), pg_malloc_attr(bms_del_member), pg_malloc_attr(bms_add_members), pg_malloc_attr(bms_add_range), pg_malloc_attr(bms_int_members), pg_malloc_attr(bms_del_members), pg_malloc_attr(bms_join), warn_unused_result))
+
+extern Bitmapset *bms_copy(const Bitmapset *a) BMS_ALLOC_ATTRIBUTES;
+extern bool bms_equal(const Bitmapset *a, const Bitmapset *b);
+extern int bms_compare(const Bitmapset *a, const Bitmapset *b);
+extern Bitmapset *bms_make_singleton(int x) BMS_ALLOC_ATTRIBUTES;
+extern void bms_free(Bitmapset *a);
+
+extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+
extern bool bms_is_subset(const Bitmapset *a, const Bitmapset *b);
extern BMS_Comparison bms_subset_compare(const Bitmapset *a, const Bitmapset *b);
extern bool bms_is_member(int x, const Bitmapset *a);
@@ -106,13 +118,13 @@ extern BMS_Membership bms_membership(const Bitmapset *a);
/* these routines recycle (modify or free) their non-const inputs: */
-extern Bitmapset *bms_add_member(Bitmapset *a, int x);
-extern Bitmapset *bms_del_member(Bitmapset *a, int x);
-extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper);
-extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b);
-extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b);
+extern Bitmapset *bms_add_member(Bitmapset *a, int x) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_del_member(Bitmapset *a, int x) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_add_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_add_range(Bitmapset *a, int lower, int upper) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_int_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_del_members(Bitmapset *a, const Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
+extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b) BMS_ALLOC_ATTRIBUTES;
/* support for iterating through the integer elements of a set: */
extern int bms_next_member(const Bitmapset *a, int prevbit);
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 529a382d284..285d8c2c7ed 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -632,4 +632,101 @@ extern void list_sort(List *list, list_sort_comparator cmp);
extern int list_int_cmp(const ListCell *p1, const ListCell *p2);
extern int list_oid_cmp(const ListCell *p1, const ListCell *p2);
+
+#define PG_LIST_ALLOC_ATTR __attribute__(( \
+ pg_malloc_attr(lappend), \
+ pg_malloc_attr(lappend_int), \
+ pg_malloc_attr(lappend_oid), \
+ pg_malloc_attr(lappend_xid), \
+ pg_malloc_attr(list_insert_nth), \
+ pg_malloc_attr(list_insert_nth_int), \
+ pg_malloc_attr(list_insert_nth_oid), \
+ pg_malloc_attr_i(lcons, 2), \
+ pg_malloc_attr_i(lcons_int, 2), \
+ pg_malloc_attr_i(lcons_oid, 2), \
+ pg_malloc_attr(list_concat), \
+ pg_malloc_attr(list_truncate), \
+ pg_malloc_attr(list_delete), \
+ pg_malloc_attr(list_delete_ptr), \
+ pg_malloc_attr(list_delete_int), \
+ pg_malloc_attr(list_delete_oid), \
+ pg_malloc_attr(list_delete_first), \
+ pg_malloc_attr(list_delete_last), \
+ pg_malloc_attr(list_delete_first_n), \
+ pg_malloc_attr(list_delete_nth_cell), \
+ pg_malloc_attr(list_delete_cell), \
+ pg_malloc_attr(list_union), \
+ pg_malloc_attr(list_append_unique), \
+ pg_malloc_attr(list_append_unique_ptr), \
+ pg_malloc_attr(list_append_unique_int), \
+ pg_malloc_attr(list_append_unique_oid), \
+ pg_malloc_attr(list_concat_unique), \
+ pg_malloc_attr(list_concat_unique_ptr), \
+ pg_malloc_attr(list_concat_unique_int), \
+ pg_malloc_attr(list_concat_unique_oid), \
+ pg_malloc_attr(list_free), \
+ pg_malloc_attr(list_free_deep), \
+ warn_unused_result))
+
+extern PG_LIST_ALLOC_ATTR List *list_make1_impl(NodeTag t, ListCell datum1);
+extern PG_LIST_ALLOC_ATTR List *list_make2_impl(NodeTag t, ListCell datum1, ListCell datum2);
+extern PG_LIST_ALLOC_ATTR List *list_make3_impl(NodeTag t, ListCell datum1, ListCell datum2,
+ ListCell datum3);
+extern PG_LIST_ALLOC_ATTR List *list_make4_impl(NodeTag t, ListCell datum1, ListCell datum2,
+ ListCell datum3, ListCell datum4);
+extern PG_LIST_ALLOC_ATTR List *list_make5_impl(NodeTag t, ListCell datum1, ListCell datum2,
+ ListCell datum3, ListCell datum4,
+ ListCell datum5);
+
+extern PG_LIST_ALLOC_ATTR List *lappend(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_oid(List *list, Oid datum);
+extern PG_LIST_ALLOC_ATTR List *lappend_xid(List *list, TransactionId datum);
+
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth(List *list, int pos, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth_int(List *list, int pos, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_insert_nth_oid(List *list, int pos, Oid datum);
+
+extern PG_LIST_ALLOC_ATTR List *lcons(void *datum, List *list);
+extern PG_LIST_ALLOC_ATTR List *lcons_int(int datum, List *list);
+extern PG_LIST_ALLOC_ATTR List *lcons_oid(Oid datum, List *list);
+
+extern PG_LIST_ALLOC_ATTR List *list_concat(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_copy(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_truncate(List *list, int new_size);
+
+extern PG_LIST_ALLOC_ATTR List *list_delete(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_ptr(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_oid(List *list, Oid datum);
+extern PG_LIST_ALLOC_ATTR List *list_delete_first(List *list);
+extern PG_LIST_ALLOC_ATTR List *list_delete_last(List *list);
+extern PG_LIST_ALLOC_ATTR List *list_delete_first_n(List *list, int n);
+extern PG_LIST_ALLOC_ATTR List *list_delete_nth_cell(List *list, int n);
+extern PG_LIST_ALLOC_ATTR List *list_delete_cell(List *list, ListCell *cell);
+
+extern PG_LIST_ALLOC_ATTR List *list_union(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_ptr(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_int(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_union_oid(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_intersection(const List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_intersection_int(const List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_append_unique(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_ptr(List *list, void *datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_int(List *list, int datum);
+extern PG_LIST_ALLOC_ATTR List *list_append_unique_oid(List *list, Oid datum);
+
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_ptr(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_int(List *list1, const List *list2);
+extern PG_LIST_ALLOC_ATTR List *list_concat_unique_oid(List *list1, const List *list2);
+
+extern PG_LIST_ALLOC_ATTR List *list_copy(const List *oldlist);
+extern PG_LIST_ALLOC_ATTR List *list_copy_head(const List *oldlist, int len);
+extern PG_LIST_ALLOC_ATTR List *list_copy_tail(const List *oldlist, int nskip);
+extern PG_LIST_ALLOC_ATTR List *list_copy_deep(const List *oldlist);
+
#endif /* PG_LIST_H */
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index d1146c12351..a9e8063023f 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -65,25 +65,40 @@ extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
#define MCXT_ALLOC_NO_OOM 0x02 /* no failure if out-of-memory */
#define MCXT_ALLOC_ZERO 0x04 /* zero allocated memory */
+#define pg_alloc_attributes(size_at) \
+ __attribute__((malloc, pg_malloc_attr_i(pfree, 1), alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), returns_nonnull, warn_unused_result))
+#define pg_alloc_noerr_attributes(size_at) \
+ __attribute__((malloc, pg_malloc_attr_i(pfree, 1), alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), warn_unused_result))
+#define pg_realloc_attributes(old_at, size_at) \
+ __attribute__((alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), \
+ nonnull(old_at), returns_nonnull, warn_unused_result))
+#define pg_realloc_noerr_attributes(old_at, size_at) \
+ __attribute__((alloc_size(size_at), assume_aligned(MAXIMUM_ALIGNOF), \
+ nonnull(old_at), warn_unused_result))
+#define pg_dup_attributes(source_at) \
+ __attribute__((malloc, pg_malloc_attr_i(pfree, 1), assume_aligned(MAXIMUM_ALIGNOF), returns_nonnull, nonnull(source_at), warn_unused_result))
+
+extern void pfree(void *pointer);
+
/*
* 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 *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
+extern void *MemoryContextAlloc(MemoryContext context, Size size) pg_alloc_attributes(2);
+extern void *MemoryContextAllocZero(MemoryContext context, Size size) pg_alloc_attributes(2);
+extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size) pg_alloc_attributes(2);
extern void *MemoryContextAllocExtended(MemoryContext context,
- Size size, int flags);
+ Size size, int flags) pg_alloc_noerr_attributes(2);
extern void *MemoryContextAllocAligned(MemoryContext context,
- Size size, Size alignto, int flags);
+ Size size, Size alignto, int flags) pg_alloc_noerr_attributes(2);
-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 pg_nodiscard void *repalloc(void *pointer, Size size);
+extern void *palloc(Size size) pg_alloc_attributes(1);
+extern void *palloc0(Size size) pg_alloc_attributes(1);
+extern void *palloc_extended(Size size, int flags) pg_alloc_noerr_attributes(1);
+extern void *palloc_aligned(Size size, Size alignto, int flags) pg_alloc_noerr_attributes(1);
+extern pg_nodiscard void *repalloc(void *pointer, Size size) pg_realloc_attributes(1, 2);
extern pg_nodiscard void *repalloc_extended(void *pointer,
- Size size, int flags);
-extern pg_nodiscard void *repalloc0(void *pointer, Size oldsize, Size size);
+ Size size, int flags) pg_realloc_noerr_attributes(1, 2);
+extern pg_nodiscard void *repalloc0(void *pointer, Size oldsize, Size size) pg_realloc_attributes(1, 2);
extern void pfree(void *pointer);
/*
@@ -123,8 +138,8 @@ extern void pfree(void *pointer);
MemoryContextAllocZero(CurrentMemoryContext, sz) )
/* Higher-limit allocators. */
-extern void *MemoryContextAllocHuge(MemoryContext context, Size size);
-extern pg_nodiscard void *repalloc_huge(void *pointer, Size size);
+extern void *MemoryContextAllocHuge(MemoryContext context, Size size) pg_alloc_attributes(2);
+extern pg_nodiscard void *repalloc_huge(void *pointer, Size size) pg_realloc_attributes(1, 2);
/*
* Although this header file is nominally backend-only, certain frontend
@@ -152,14 +167,14 @@ extern void MemoryContextRegisterResetCallback(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_dup_attributes(2);
+extern char *pstrdup(const char *in) pg_dup_attributes(1);
+extern char *pnstrdup(const char *in, Size len) pg_dup_attributes(1);
-extern char *pchomp(const char *in);
+extern char *pchomp(const char *in) pg_dup_attributes(1);
/* sprintf into a palloc'd buffer --- these are in psprintf.c */
-extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2);
-extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0);
+extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2) __attribute__((malloc, returns_nonnull, warn_unused_result));
+extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0) __attribute__((warn_unused_result));
#endif /* PALLOC_H */
--
2.38.0