util: add various helper functions to use __attribute__(cleanup)

Taken from libei, with slight modifications. The general approach is:
basic data types use _autofoo_ to call the maching foo function on
cleanup. Struct types use _unref_, _destory_, _free_, whichever applies
to that struct.

Notably: attribute syntax depends on where it's declared [1] so in the
following examles only a, b, and d have the autofree attribute:
   _autofree_ char *a, *b;
   char *c, _autofree *d;

Simplest way to ensure it's all correct to keep the declarations one per
line.

[1] https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1184>
This commit is contained in:
Peter Hutterer 2025-04-01 11:25:58 +10:00
parent 436eb42044
commit 76c87d2486
5 changed files with 234 additions and 0 deletions

View file

@ -1,5 +1,7 @@
# optin.core.unix.Malloc: disabled so we can use __attribute__((cleanup)) without leak complaints
# optin.core.EnumCastOutOfRange: disabled because we use a lot of "wrong" enum values for testing
# and internally and don't want those values leak into the public API
Checks: >
-clang-analyzer-unix.Malloc,
-clang-analyzer-optin.core.EnumCastOutOfRange
WarningsAsErrors: true

View file

@ -31,6 +31,7 @@
#include <libudev.h>
#include "libinput.h"
#include "util-mem.h"
/**
* Handle to the quirks context.
@ -179,6 +180,8 @@ quirks_init_subsystem(const char *data_path,
struct quirks_context *
quirks_context_unref(struct quirks_context *ctx);
DEFINE_UNREF_CLEANUP_FUNC(quirks_context);
struct quirks_context *
quirks_context_ref(struct quirks_context *ctx);

154
src/util-mem.h Normal file
View file

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
/**
* Use: _cleanup_(somefunction) struct foo *bar;
*/
#define _cleanup_(_x) __attribute__((cleanup(_x)))
/**
* Use: _unref_(foo) struct foo *bar;
*
* This requires foo_unrefp() to be present, use DEFINE_UNREF_CLEANUP_FUNC.
*/
#define _unref_(_type) __attribute__((cleanup(_type##_unrefp))) struct _type
/**
* Use: _destroy_(foo) struct foo *bar;
*
* This requires foo_destroyp() to be present, use DEFINE_UNREF_CLEANUP_FUNC.
*/
#define _destroy_(_type) __attribute__((cleanup(_type##_destroyp))) struct _type
/**
* Use: _free_(foo) struct foo *bar;
*
* This requires foo_freep() to be present, use DEFINE_FREE_CLEANUP_FUNC.
*/
#define _free_(_type) __attribute__((cleanup(_type##_freep))) struct _type
static inline void _free_ptr_(void *p) { free(*(void**)p); }
/**
* Use: _autofree_ char *data;
*/
#define _autofree_ _cleanup_(_free_ptr_)
static inline void _close_fd_(int *fd) { if (*fd != -1) close(*fd); }
/**
* Use: _autoclose_ int fd = open(...);
*/
#define _autoclose_ _cleanup_(_close_fd_)
static inline void _close_file_(FILE **fp) { if (*fp) fclose(*fp); }
/**
* Use: _autofclose_ FILE *fp = fopen(...);
*/
#define _autofclose_ _cleanup_(_close_file_)
/**
* Use:
* DEFINE_TRIVIAL_CLEANUP_FUNC(struct foo *, foo_unref)
* _cleanup_(foo_unrefp) struct foo *bar;
*/
#define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \
static inline void _func##p(_type *_p) { \
if (*_p) \
_func(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
/**
* Define a cleanup function for the struct type foo with a matching
* foo_unref(). Use:
* DEFINE_UNREF_CLEANUP_FUNC(foo)
* _unref_(foo) struct foo *bar;
*/
#define DEFINE_UNREF_CLEANUP_FUNC(_type) \
static inline void _type##_unrefp(struct _type **_p) { \
if (*_p) \
_type##_unref(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
/**
* Define a cleanup function for the struct type foo with a matching
* foo_destroy(). Use:
* DEFINE_DESTROY_CLEANUP_FUNC(foo)
* _destroy_(foo) struct foo *bar;
*/
#define DEFINE_DESTROY_CLEANUP_FUNC(_type) \
static inline void _type##_destroyp(struct _type **_p) {\
if (*_p) \
_type##_destroy(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
/**
* Define a cleanup function for the struct type foo with a matching
* foo_free(). Use:
* DEFINE_FREE_CLEANUP_FUNC(foo)
* _free_(foo) struct foo *bar;
*/
#define DEFINE_FREE_CLEANUP_FUNC(_type) \
static inline void _type##_freep(struct _type **_p) {\
if (*_p) \
_type##_free(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
static inline void*
_steal(void *ptr) {
void **original = (void**)ptr;
void *swapped = *original;
*original = NULL;
return swapped;
}
/**
* Resets the pointer content and resets the data to NULL.
* This circumvents _cleanup_ handling for that pointer.
* Use:
* _cleanup_free_ char *data = malloc();
* return steal(&data);
*
*/
#define steal(ptr_) \
(typeof(*ptr_))_steal(ptr_)
static inline int
steal_fd(int *fd) {
int copy = *fd;
*fd = -1;
return copy;
}

View file

@ -44,6 +44,7 @@
#endif
#include "util-macros.h"
#include "util-mem.h"
#define yesno(b) ((b) ? "yes" : "no")
#define truefalse(b) ((b) ? "true" : "false")
@ -334,6 +335,13 @@ strv_free(char **strv) {
free (strv);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free);
/**
* Use: _autostrvfree_ char **strv = ...;
*/
#define _autostrvfree_ _cleanup_(strv_freep)
/**
* parse a string containing a list of doubles into a double array.
*

View file

@ -42,6 +42,7 @@
#include "util-ratelimit.h"
#include "util-stringbuf.h"
#include "util-matrix.h"
#include "util-mem.h"
#include "util-input-event.h"
#include "util-newtype.h"
@ -2119,6 +2120,71 @@ START_TEST(newtype_test)
}
END_TEST
struct sunref {};
struct sdestroy {};
struct sfree{};
static void sunref_unref(struct sunref *s) { free(s); };
static void sdestroy_destroy(struct sdestroy *s) { free(s); };
static void sfree_free(struct sfree *s) { free(s); };
DEFINE_UNREF_CLEANUP_FUNC(sunref);
DEFINE_DESTROY_CLEANUP_FUNC(sdestroy);
DEFINE_FREE_CLEANUP_FUNC(sfree);
START_TEST(attribute_cleanup)
{
/* These tests will likely only show up in valgrind,
* the various asserts are just to shut up the compiler
* about unused variables
*/
{
_autofree_ char *autofree = zalloc(64);
litest_assert(autofree);
}
{
_autofree_ char *stolen = zalloc(64);
free(steal(&stolen));
}
{
_autoclose_ int fd = open("/proc/self/cmdline", O_RDONLY);
litest_assert_int_ge(fd, 0);
_autoclose_ int badfd = -1;
litest_assert_int_eq(badfd, -1);
_autoclose_ int stealfd = open("/proc/self/cmdline", O_RDONLY);
steal_fd(&stealfd);
litest_assert_int_eq(stealfd, -1);
}
{
_autostrvfree_ char **strv = zalloc(3 * sizeof(*strv));
for (int i = 0; i < 2; i++) {
strv[i] = strdup_printf("element %d", i);
}
_autostrvfree_ char **badstrv = NULL;
litest_assert_ptr_null(badstrv);
}
{
_autofclose_ FILE *fp = fopen("/proc/self/cmdline", "r");
litest_assert_ptr_notnull(fp);
_autofclose_ FILE *badfd = NULL;
litest_assert_ptr_null(badfd);
}
{
_unref_(sunref) *s = zalloc(sizeof(*s));
}
{
_destroy_(sdestroy) *s = zalloc(sizeof(*s));
}
{
_free_(sfree) *s = zalloc(sizeof(*s));
}
}
END_TEST
int main(void)
{
struct litest_runner *runner = litest_runner_new();
@ -2189,6 +2255,7 @@ int main(void)
ADD_TEST(multivalue_test);
ADD_TEST(newtype_test);
ADD_TEST(attribute_cleanup);
enum litest_runner_result result = litest_runner_run_tests(runner);
litest_runner_destroy(runner);