diff --git a/.clang-tidy b/.clang-tidy index ad3e652b..20c7cf54 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -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 diff --git a/src/quirks.h b/src/quirks.h index ba4df631..ad6f6aa3 100644 --- a/src/quirks.h +++ b/src/quirks.h @@ -31,6 +31,7 @@ #include #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); diff --git a/src/util-mem.h b/src/util-mem.h new file mode 100644 index 00000000..7e472464 --- /dev/null +++ b/src/util-mem.h @@ -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 +#include +#include + +/** + * 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; +} diff --git a/src/util-strings.h b/src/util-strings.h index 190cae11..c245f9d3 100644 --- a/src/util-strings.h +++ b/src/util-strings.h @@ -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. * diff --git a/test/test-utils.c b/test/test-utils.c index 8aae01de..f5a7bbf8 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -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);