Enable support for GCC 11 -Wmismatched-dealloc.

To help detect common kinds of memory (and other resource) management
bugs, GCC 11 adds support for the detection of mismatched calls to
allocation and deallocation functions.  At each call site to a known
deallocation function GCC checks the set of allocation functions
the former can be paired with and, if the two don't match, issues
a -Wmismatched-dealloc warning (something similar happens in C++
for mismatched calls to new and delete).  GCC also uses the same
mechanism to detect attempts to deallocate objects not allocated
by any allocation function (or pointers past the first byte into
allocated objects) by -Wfree-nonheap-object.

This support is enabled for built-in functions like malloc and free.
To extend it beyond those, GCC extends attribute malloc to designate
a deallocation function to which pointers returned from the allocation
function may be passed to deallocate the allocated objects.  Another,
optional argument designates the positional argument to which
the pointer must be passed.

This change is the first step in enabling this extended support for
Glibc.
This commit is contained in:
Martin Sebor 2021-05-16 15:21:18 -06:00
parent 390c43ba1f
commit c1760eaf3b
13 changed files with 310 additions and 43 deletions

View File

@ -23,11 +23,11 @@
/* Prototypes for a few program-wide used functions. */
extern void *xmalloc (size_t n)
__attribute_malloc__ __attribute_alloc_size__ ((1));
__attribute_malloc__ __attribute_alloc_size__ ((1)) __attr_dealloc_free;
extern void *xcalloc (size_t n, size_t s)
__attribute_malloc__ __attribute_alloc_size__ ((1, 2));
__attribute_malloc__ __attribute_alloc_size__ ((1, 2)) __attr_dealloc_free;
extern void *xrealloc (void *o, size_t n)
__attribute_malloc__ __attribute_alloc_size__ ((2));
extern char *xstrdup (const char *) __attribute_malloc__;
__attribute_malloc__ __attribute_alloc_size__ ((2)) __attr_dealloc_free;
extern char *xstrdup (const char *) __attribute_malloc__ __attr_dealloc_free;
#endif /* xmalloc.h */

View File

@ -61,7 +61,7 @@ tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc \
bug-ungetc2 bug-ftell bug-ungetc3 bug-ungetc4 tst-fopenloc2 \
tst-memstream1 tst-memstream2 tst-memstream3 tst-memstream4 \
tst-wmemstream1 tst-wmemstream2 tst-wmemstream3 tst-wmemstream4 \
bug-memstream1 bug-wmemstream1 \
tst-wmemstream5 bug-memstream1 bug-wmemstream1 \
tst-setvbuf1 tst-popen1 tst-fgetwc bug-wsetpos tst-fseek \
tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler \
tst-ftell-append tst-fputws tst-bz22415 tst-fgetc-after-eof \

View File

@ -165,22 +165,34 @@ extern int renameat2 (int __oldfd, const char *__old, int __newfd,
const char *__new, unsigned int __flags) __THROW;
#endif
/* Close STREAM.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int fclose (FILE *__stream);
#undef __attr_dealloc_fclose
#define __attr_dealloc_fclose __attr_dealloc (fclose, 1)
/* Create a temporary file and open it read/write.
This function is a possible cancellation point and therefore not
marked with __THROW. */
#ifndef __USE_FILE_OFFSET64
extern FILE *tmpfile (void) __wur;
extern FILE *tmpfile (void)
__attribute_malloc__ __attr_dealloc_fclose __wur;
#else
# ifdef __REDIRECT
extern FILE *__REDIRECT (tmpfile, (void), tmpfile64) __wur;
extern FILE *__REDIRECT (tmpfile, (void), tmpfile64)
__attribute_malloc__ __attr_dealloc_fclose __wur;
# else
# define tmpfile tmpfile64
# endif
#endif
#ifdef __USE_LARGEFILE64
extern FILE *tmpfile64 (void) __wur;
extern FILE *tmpfile64 (void)
__attribute_malloc__ __attr_dealloc_fclose __wur;
#endif
/* Generate a temporary filename. */
@ -202,15 +214,9 @@ extern char *tmpnam_r (char __s[L_tmpnam]) __THROW __wur;
P_tmpdir is tried and finally "/tmp". The storage for the filename
is allocated by `malloc'. */
extern char *tempnam (const char *__dir, const char *__pfx)
__THROW __attribute_malloc__ __wur;
__THROW __attribute_malloc__ __wur __attr_dealloc_free;
#endif
/* Close STREAM.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int fclose (FILE *__stream);
/* Flush STREAM, or all streams if STREAM is NULL.
This function is a possible cancellation point and therefore not
@ -244,7 +250,8 @@ extern int fcloseall (void);
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern FILE *fopen (const char *__restrict __filename,
const char *__restrict __modes) __wur;
const char *__restrict __modes)
__attribute_malloc__ __attr_dealloc_fclose __wur;
/* Open a file, replacing an existing stream with it.
This function is a possible cancellation point and therefore not
@ -256,7 +263,7 @@ extern FILE *freopen (const char *__restrict __filename,
# ifdef __REDIRECT
extern FILE *__REDIRECT (fopen, (const char *__restrict __filename,
const char *__restrict __modes), fopen64)
__wur;
__attribute_malloc__ __attr_dealloc_fclose __wur;
extern FILE *__REDIRECT (freopen, (const char *__restrict __filename,
const char *__restrict __modes,
FILE *__restrict __stream), freopen64)
@ -268,7 +275,8 @@ extern FILE *__REDIRECT (freopen, (const char *__restrict __filename,
#endif
#ifdef __USE_LARGEFILE64
extern FILE *fopen64 (const char *__restrict __filename,
const char *__restrict __modes) __wur;
const char *__restrict __modes)
__attribute_malloc__ __attr_dealloc_fclose __wur;
extern FILE *freopen64 (const char *__restrict __filename,
const char *__restrict __modes,
FILE *__restrict __stream) __wur;
@ -276,7 +284,8 @@ extern FILE *freopen64 (const char *__restrict __filename,
#ifdef __USE_POSIX
/* Create a new stream that refers to an existing system file descriptor. */
extern FILE *fdopen (int __fd, const char *__modes) __THROW __wur;
extern FILE *fdopen (int __fd, const char *__modes) __THROW
__attribute_malloc__ __attr_dealloc_fclose __wur;
#endif
#ifdef __USE_GNU
@ -284,20 +293,29 @@ extern FILE *fdopen (int __fd, const char *__modes) __THROW __wur;
and uses the given functions for input and output. */
extern FILE *fopencookie (void *__restrict __magic_cookie,
const char *__restrict __modes,
cookie_io_functions_t __io_funcs) __THROW __wur;
cookie_io_functions_t __io_funcs) __THROW
__attribute_malloc__ __attr_dealloc_fclose __wur;
#endif
#if defined __USE_XOPEN2K8 || __GLIBC_USE (LIB_EXT2)
/* Create a new stream that refers to a memory buffer. */
extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)
__THROW __wur;
__THROW __attribute_malloc__ __attr_dealloc_fclose __wur;
/* Open a stream that writes into a malloc'd buffer that is expanded as
necessary. *BUFLOC and *SIZELOC are updated with the buffer's location
and the number of characters written on fflush or fclose. */
extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __THROW __wur;
#endif
extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __THROW
__attribute_malloc__ __attr_dealloc_fclose __wur;
#ifdef _WCHAR_H
/* Like OPEN_MEMSTREAM, but the stream is wide oriented and produces
a wide character string. Declared here only to add attribute malloc
and only if <wchar.h> has been previously #included. */
extern __FILE *open_wmemstream (wchar_t **__bufloc, size_t *__sizeloc) __THROW
__attribute_malloc__ __attr_dealloc_fclose;
# endif
#endif
/* If BUF is NULL, make STREAM unbuffered.
Else make it use buffer BUF, of size BUFSIZ. */
@ -792,17 +810,19 @@ extern int fileno_unlocked (FILE *__stream) __THROW __wur;
#ifdef __USE_POSIX2
/* Create a new stream connected to a pipe running the given command.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern FILE *popen (const char *__command, const char *__modes) __wur;
/* Close a stream opened by popen and return the status of its child.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int pclose (FILE *__stream);
/* Create a new stream connected to a pipe running the given command.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern FILE *popen (const char *__command, const char *__modes)
__attribute_malloc__ __attr_dealloc (pclose, 1) __wur;
#endif

View File

@ -81,6 +81,42 @@ do_test_basic (void)
fclose (f);
}
#if defined __GNUC__ && __GNUC__ >= 11
/* Force an error to detect incorrectly making freopen a deallocator
for its last argument via attribute malloc. The function closes
the stream without deallocating it so either the argument or
the pointer returned from the function (but not both) can be passed
to fclose. */
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wmismatched-dealloc"
#endif
/* Verify that freopen returns stream. */
static void
do_test_return_stream (void)
{
FILE *f1 = fopen (name, "r");
if (f1 == NULL)
FAIL_EXIT1 ("fopen: %m");
FILE *f2 = freopen (name, "r+", f1);
if (f2 == NULL)
FAIL_EXIT1 ("freopen: %m");
/* Verify that freopen isn't declared with the no-argument attribute
malloc (which could let GCC fold the inequality to false). */
if (f1 != f2)
FAIL_EXIT1 ("freopen returned a different stream");
/* This shouldn't trigger -Wmismatched-dealloc. */
fclose (f1);
}
#if defined __GNUC__ && __GNUC__ >= 11
/* Pop -Wmismatched-dealloc set to error above. */
# pragma GCC diagnostic pop
#endif
/* Test for BZ#21398, where it tries to freopen stdio after the close
of its file descriptor. */
static void
@ -105,6 +141,7 @@ do_test (void)
{
do_test_basic ();
do_test_bz21398 ();
do_test_return_stream ();
return 0;
}

View File

@ -21,7 +21,7 @@ do_test (void)
res = 1;
}
fclose (fp);
pclose (fp);
}
fp = popen ("echo hello", "re");
@ -39,7 +39,7 @@ do_test (void)
res = 1;
}
fclose (fp);
pclose (fp);
}
return res;

View File

@ -1,5 +1,40 @@
#include <wchar.h>
extern int fclose (FILE*);
#if defined __GNUC__ && __GNUC__ >= 11
/* Verify that calling fclose on the result of open_wmemstream doesn't
trigger GCC -Wmismatched-dealloc with fclose forward-declared and
without <stdio.h> included first (it is included later, in.
"tst-memstream1.c"). */
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wmismatched-dealloc"
#endif
int test_open_wmemstream_no_stdio (void)
{
{
wchar_t *buf;
size_t size;
FILE *f = open_wmemstream (&buf, &size);
fclose (f);
}
{
FILE* (*pf)(wchar_t**, size_t*) = open_wmemstream;
wchar_t *buf;
size_t size;
FILE *f = pf (&buf, &size);
fclose (f);
}
return 0;
}
#if defined __GNUC__ && __GNUC__ >= 11
/* Restore -Wmismatched-dealloc setting. */
# pragma GCC diagnostic pop
#endif
#define CHAR_T wchar_t
#define W(o) L##o
#define OPEN_MEMSTREAM open_wmemstream

57
libio/tst-wmemstream5.c Normal file
View File

@ -0,0 +1,57 @@
/* Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <wchar.h>
extern int fclose (FILE*);
#if defined __GNUC__ && __GNUC__ >= 11
/* Verify that calling fclose on the result of open_wmemstream doesn't
trigger GCC -Wmismatched-dealloc with fclose forward-declared and
without <stdio.h> included in the same translation unit. */
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wmismatched-dealloc"
#endif
static int
do_test (void)
{
{
wchar_t *buf;
size_t size;
FILE *f = open_wmemstream (&buf, &size);
fclose (f);
}
{
FILE* (*pf)(wchar_t**, size_t*) = open_wmemstream;
wchar_t *buf;
size_t size;
FILE *f = pf (&buf, &size);
fclose (f);
}
return 0;
}
#if defined __GNUC__ && __GNUC__ >= 11
/* Restore -Wmismatched-dealloc setting. */
# pragma GCC diagnostic pop
#endif
#define TEST_FUNCTION do_test ()
#include "../test-skeleton.c"

View File

@ -56,22 +56,25 @@ __THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2));
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *reallocarray (void *__ptr, size_t __nmemb, size_t __size)
__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2, 3));
__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2, 3))
__attr_dealloc_free;
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
extern void free (void *__ptr) __THROW;
/* Allocate SIZE bytes allocated to ALIGNMENT bytes. */
extern void *memalign (size_t __alignment, size_t __size)
__THROW __attribute_malloc__ __attribute_alloc_size__ ((2)) __wur;
__THROW __attribute_malloc__ __attribute_alloc_size__ ((2)) __wur
__attr_dealloc_free;
/* Allocate SIZE bytes on a page boundary. */
extern void *valloc (size_t __size) __THROW __attribute_malloc__
__attribute_alloc_size__ ((1)) __wur;
__attribute_alloc_size__ ((1)) __wur __attr_dealloc_free;
/* Equivalent to valloc(minimum-page-that-holds(n)), that is, round up
__size to nearest pagesize. */
extern void *pvalloc (size_t __size) __THROW __attribute_malloc__ __wur;
extern void *pvalloc (size_t __size) __THROW __attribute_malloc__
__wur __attr_dealloc_free;
/* Underlying allocation function; successive calls should return
contiguous pieces of memory. */

View File

@ -603,6 +603,17 @@ _Static_assert (0, "IEEE 128-bits long double requires redirection on this platf
# define __attr_access_none(argno)
#endif
#if __GNUC_PREREQ (11, 0)
/* Designates dealloc as a function to call to deallocate objects
allocated by the declared function. */
# define __attr_dealloc(dealloc, argno) \
__attribute__ ((__malloc__ (dealloc, argno)))
# define __attr_dealloc_free __attr_dealloc (__builtin_free, 1)
#else
# define __attr_dealloc(dealloc, argno)
# define __attr_dealloc_free
#endif
/* Specify that a function such as setjmp or vfork may return
twice. */
#if __GNUC_PREREQ (4, 1)

View File

@ -87,7 +87,8 @@ tests := tst-strtol tst-strtod testmb testrand testsort testdiv \
tst-makecontext-align test-bz22786 tst-strtod-nan-sign \
tst-swapcontext1 tst-setcontext4 tst-setcontext5 \
tst-setcontext6 tst-setcontext7 tst-setcontext8 \
tst-setcontext9 tst-bz20544 tst-canon-bz26341
tst-setcontext9 tst-bz20544 tst-canon-bz26341 \
tst-realpath
tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
tst-tls-atexit tst-tls-atexit-nodelete

View File

@ -550,6 +550,9 @@ extern void *calloc (size_t __nmemb, size_t __size)
extern void *realloc (void *__ptr, size_t __size)
__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2));
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
extern void free (void *__ptr) __THROW;
#ifdef __USE_MISC
/* Re-allocate the previously allocated block in PTR, making the new
block large enough for NMEMB elements of SIZE bytes each. */
@ -558,11 +561,13 @@ extern void *realloc (void *__ptr, size_t __size)
between objects pointed by the old and new pointers. */
extern void *reallocarray (void *__ptr, size_t __nmemb, size_t __size)
__THROW __attribute_warn_unused_result__
__attribute_alloc_size__ ((2, 3));
#endif
__attribute_alloc_size__ ((2, 3))
__attr_dealloc_free;
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
extern void free (void *__ptr) __THROW;
/* Add reallocarray as its own deallocator. */
extern void *reallocarray (void *__ptr, size_t __nmemb, size_t __size)
__THROW __attr_dealloc (reallocarray, 1);
#endif
#ifdef __USE_MISC
# include <alloca.h>
@ -788,7 +793,8 @@ extern int system (const char *__command) __wur;
/* Return a malloc'd string containing the canonical absolute name of the
existing named file. */
extern char *canonicalize_file_name (const char *__name)
__THROW __nonnull ((1)) __wur;
__THROW __nonnull ((1)) __attribute_malloc__
__attr_dealloc_free __wur;
#endif
#if defined __USE_MISC || defined __USE_XOPEN_EXTENDED

82
stdlib/tst-realpath.c Normal file
View File

@ -0,0 +1,82 @@
/* Test to verify that realpath() doesn't cause false positives due
to GCC attribute malloc.
Test failure exposes the presence of the attribute in the following
declaration:
__attribute__ ((__malloc__ (free, 1))) char*
realpath (const char *, char *);
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#if defined __GNUC__ && __GNUC__ >= 11
/* Turn GCC -Wmismatched-dealloc warnings into errors to expose false
positives. */
# pragma GCC diagnostic push
# pragma GCC diagnostic error "-Wmismatched-dealloc"
/* Associate dealloc as the only deallocator suitable for pointers
returned from alloc.
GCC automatically disables inlining of allocator and deallocator
functions marked with the argument form of attribute malloc but
it doesn't hurt to disable it explicitly. */
__attribute ((noipa)) void dealloc (void *);
__attribute ((malloc (dealloc, 1))) char* alloc (void);
#endif
void dealloc (void *p)
{
free (p);
}
char* alloc (void)
{
return (char *)malloc (8);
}
static int
do_test (void)
{
char *resolved_path = alloc ();
char *ret = realpath ("/", resolved_path);
dealloc (ret);
resolved_path = alloc ();
ret = realpath ("/", resolved_path);
dealloc (resolved_path);
/* The following should emit a warning (but doesn't with GCC 11):
resolved_path = alloc ();
ret = realpath ("/", resolved_path);
free (ret); // expect -Wmismatched-dealloc
*/
return 0;
}
#if defined __GNUC__ && __GNUC__ >= 11
/* Restore -Wmismatched-dealloc setting. */
# pragma GCC diagnostic pop
#endif
#define TEST_FUNCTION do_test ()
#include "../test-skeleton.c"

View File

@ -151,7 +151,8 @@ extern size_t wcsxfrm_l (wchar_t *__s1, const wchar_t *__s2,
size_t __n, locale_t __loc) __THROW;
/* Duplicate S, returning an identical malloc'd string. */
extern wchar_t *wcsdup (const wchar_t *__s) __THROW __attribute_malloc__;
extern wchar_t *wcsdup (const wchar_t *__s) __THROW
__attribute_malloc__ __attr_dealloc_free;
#endif
/* Find the first occurrence of WC in WCS. */
@ -562,9 +563,23 @@ extern wchar_t *wcpncpy (wchar_t *__restrict __dest,
/* Wide character I/O functions. */
#if defined __USE_XOPEN2K8 || __GLIBC_USE (LIB_EXT2)
# ifndef __attr_dealloc_fclose
# if defined __has_builtin
# if __has_builtin (__builtin_fclose)
/* If the attribute macro hasn't been defined yet (by <stdio.h>) and
fclose is a built-in, use it. */
# define __attr_dealloc_fclose __attr_dealloc (__builtin_fclose, 1)
# endif
# endif
# endif
# ifndef __attr_dealloc_fclose
# define __attr_dealloc_fclose /* empty */
# endif
/* Like OPEN_MEMSTREAM, but the stream is wide oriented and produces
a wide character string. */
extern __FILE *open_wmemstream (wchar_t **__bufloc, size_t *__sizeloc) __THROW;
extern __FILE *open_wmemstream (wchar_t **__bufloc, size_t *__sizeloc) __THROW
__attribute_malloc__ __attr_dealloc_fclose;
#endif
#if defined __USE_ISOC95 || defined __USE_UNIX98