]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
coccinelle: add checks for pointer access without NULL check
authorMichael Vogt <michael@amutable.com>
Fri, 13 Mar 2026 13:46:37 +0000 (14:46 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 26 Mar 2026 17:13:17 +0000 (18:13 +0100)
The fix in 8f1751a111 made me wonder if we could automatically detect
when pointers are accessed but when this might not be safe. Systemd
is already using a lot of `assert(dst)` and this change now forces
us to use them.

So this commit (ab)uses coccinelle to flag any pointer parameter
dereference not preceded by assert(param), ASSERT_PTR(param), or an
explicit NULL check. It adds integration into meson as a new "coccinelle"
test suite (just like clang-tidy) and is run in CI. The check is not
perfect but seems a reasonable heuristic.

For this RFC commit it is scoped to a subset, it excludes 25 dirs right
now and includes around 100. About 300 warnings left. Busywork that I am
happy to do if there is agreement that it is worth it.

With this in place we would have caught the bug from 8f1751a111 in CI:
```
FAIL: check-pointer-deref.cocci found issues in systemd/src/boot:
diff -u -p systemd/src/boot/measure.c /tmp/nothing/measure.c
--- systemd/src/boot/measure.c
+++ /tmp/nothing/measure.c
@@ -312,7 +312,6 @@ EFI_STATUS tpm_log_tagged_event(
         if (err != EFI_SUCCESS)
                 return err;

-        *ret_measured = true;
         return EFI_SUCCESS;
 }
```

This also adds a new POINTER_MAY_BE_NULL() for the cases when the
called function will do the NULL check (like `iovec_is_set()`).

43 files changed:
.github/workflows/linter.yml
coccinelle/check-pointer-deref.cocci [new file with mode: 0644]
meson.build
mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf
mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf
src/analyze/analyze-critical-chain.c
src/analyze/analyze-security.c
src/analyze/analyze-verify-util.c
src/analyze/analyze.c
src/boot/boot.c
src/boot/chid.c
src/boot/console.c
src/boot/efi-string.c
src/boot/initrd.c
src/boot/util.c
src/bootctl/bootctl-install.c
src/busctl/busctl.c
src/cgtop/cgtop.c
src/coredump/coredumpctl.c
src/cryptsetup/cryptsetup.c
src/fundamental/assert-fundamental.h
src/hostname/hostnamed.c
src/journal-remote/journal-gatewayd.c
src/journal-remote/journal-remote-main.c
src/journal-remote/microhttpd-util.c
src/libudev/test-libudev.c
src/machine/machinectl.c
src/oom/oomd-manager.c
src/portable/portable.c
src/portable/portablectl.c
src/repart/repart.c
src/storagetm/storagetm.c
src/sysext/sysext.c
src/systemctl/systemctl-list-dependencies.c
src/systemctl/systemctl-show.c
src/tmpfiles/tmpfiles.c
src/udev/udev-builtin-keyboard.c
src/udev/udev-builtin-path_id.c
src/udev/udev-rules.c
src/veritysetup/veritysetup.c
src/xdg-autostart-generator/xdg-autostart-service.c
tools/check-coccinelle.sh [new file with mode: 0755]
tools/meson.build

index ba293cf8be1356ff92f0d534ab2fa5c7a53c8da5..775b4f3f9d6fd803f234cddd1b5ed2a6535f85ca 100644 (file)
@@ -80,6 +80,9 @@ jobs:
       - name: Run clang-tidy
         run: mkosi box -- meson test -C build --suite=clang-tidy --print-errorlogs --no-stdsplit --quiet
 
+      - name: Run coccinelle checks
+        run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit
+
       - name: Build with musl
         run: |
           mkosi box -- \
diff --git a/coccinelle/check-pointer-deref.cocci b/coccinelle/check-pointer-deref.cocci
new file mode 100644 (file)
index 0000000..e2376a3
--- /dev/null
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Detect pointer parameters that are dereferenced without a prior NULL check
+ * or assertion. In systemd style, non-optional pointer parameters should have
+ * an assert() at the top of the function.
+ *
+ * Usage:
+ *   spatch --sp-file coccinelle/check-pointer-deref.cocci --dir src/boot/
+ *
+ * Note: this is a context-mode rule (flags, does not auto-fix). Each flagged
+ * dereference should be reviewed: if the parameter is never NULL, add
+ * assert(param) at the top. If it can legitimately be NULL, add an if() guard.
+ */
+@@
+identifier fn, param;
+type T;
+position p;
+@@
+
+fn(..., T *param, ...) {
+  ... when != assert(param)
+      when != assert(param != NULL)
+      when != assert_se(param)
+      when != assert_se(param != NULL)
+      when != assert_return(param, ...)
+      when != ASSERT_PTR(param)
+      when != POINTER_MAY_BE_NULL(param)
+      /* NULL-safe helpers used commonly enough in assert() to warrant inclusion
+       * here. For less common cases, use POINTER_MAY_BE_NULL(param) instead of
+       * extending this list. */
+      when != assert(pidref_is_set(param))
+      when != \( param == NULL \| param != NULL \| !param \)
+* *param@p
+  ...
+}
index 2893bea332f29b21f6ced5164d717bd91395c8fe..36025023db1748c214f95596399914a5cfc8378a 100644 (file)
@@ -2972,6 +2972,45 @@ if meson.version().version_compare('>=1.4.0')
         endforeach
 endif
 
+spatch = find_program('spatch', required : false)
+if spatch.found()
+        # Directories excluded from coccinelle checks until their warnings are fixed.
+        # Remove directories from this list as they are cleaned up.
+        coccinelle_exclude = [
+                'src/basic/',
+                'src/core/',
+                'src/import/',
+                'src/journal/',
+                'src/libc/',
+                'src/libsystemd/',
+                'src/libsystemd-network/',
+                'src/network/',
+                'src/nspawn/',
+                'src/nss-systemd/',
+                'src/resolve/',
+                'src/shared/',
+                'src/test/',
+        ]
+
+        coccinelle_src_dirs = run_command(
+                'sh', '-c', 'printf "%s\n" src/*/',
+                check : true,
+        ).stdout().strip().split('\n')
+
+        foreach dir : coccinelle_src_dirs
+                if dir not in coccinelle_exclude
+                        test(
+                                'coccinelle-@0@'.format(fs.name(dir)),
+                                check_coccinelle_sh,
+                                args : [meson.project_source_root() / dir,
+                                        meson.project_source_root() / 'coccinelle'],
+                                suite : 'coccinelle',
+                                timeout : 120,
+                        )
+                endif
+        endforeach
+endif
+
 symbol_analysis_exes = []
 foreach name, exe : executables_by_name
         symbol_analysis_exes += exe
index 7a9301c566cd12dd749174b75609617ebdf8e352..e687fd788e266d27f9bd64bde921960db62f2192 100644 (file)
@@ -13,4 +13,5 @@ Packages=
         musl-clang
         musl-gcc
         ruff
+        coccinelle
         shellcheck
index 8a6711f901ce6b3231de65e46c828bfdce674429..6f24649c54c6c282a256b63b2317307d367be860 100644 (file)
@@ -7,6 +7,7 @@ Distribution=opensuse
 PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.prepare
 Packages=
         clang-tools
+        coccinelle
         gh
         lcov
         libtss2-tcti-device0
index ea6d83d417cb69f22693d3764eb3f41a0f508394..659d1b564c596900739c56cdc49bfc0d1e01aa4d 100644 (file)
@@ -67,6 +67,9 @@ static int list_dependencies_compare(char *const *a, char *const *b) {
         usec_t usa = 0, usb = 0;
         UnitTimes *times;
 
+        assert(a);
+        assert(b);
+
         times = hashmap_get(unit_times_hashmap, *a);
         if (times)
                 usa = times->activated;
index bdbd44910bfba3332393e46edbd4cf6fad85b886..5e9db877b7106930242918882227644b6e4f1686 100644 (file)
@@ -558,6 +558,8 @@ static int assess_system_call_architectures(
 }
 
 static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) {
+        assert(ret_offending_syscall);
+
         NULSTR_FOREACH(syscall, f->value) {
                 if (syscall[0] == '@') {
                         const SyscallFilterSet *g;
index dfa1cf7b3bc8a47117094fc26db25272f48a05fe..e7ffae5a287873cb72def8293f61396822a32a70 100644 (file)
@@ -268,6 +268,8 @@ static int verify_unit(Unit *u, bool check_man, const char *root) {
 }
 
 static void set_destroy_ignore_pointer_max(Set **s) {
+        assert(s);
+
         if (*s == POINTER_MAX)
                 return;
         set_free(*s);
index 4d078a483851e4c90caeb31092c4c99974213a38..e23b0038a9944bb89301c25946d7d1032ccb7304 100644 (file)
@@ -124,6 +124,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 int acquire_bus(sd_bus **bus, bool *use_full_bus) {
         int r;
 
+        POINTER_MAY_BE_NULL(use_full_bus);
+
         if (use_full_bus && *use_full_bus) {
                 r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, bus);
                 if (IN_SET(r, 0, -EHOSTDOWN))
index 4a7e616faa68872115dcc3885910924e803c4272..bffedf78e2928a880470d3d4583fbf9e0c8c8dc9 100644 (file)
@@ -1046,6 +1046,8 @@ static BootEntry* boot_entry_free(BootEntry *entry) {
 DEFINE_TRIVIAL_CLEANUP_FUNC(BootEntry *, boot_entry_free);
 
 static EFI_STATUS config_timeout_sec_from_string(const char *value, uint64_t *dst) {
+        assert(dst);
+
         if (streq8(value, "menu-disabled"))
                 *dst = TIMEOUT_MENU_DISABLED;
         else if (streq8(value, "menu-force"))
@@ -1555,6 +1557,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
         EFI_STATUS err;
 
         assert(root_dir);
+        assert(config);
 
         *config = (Config) {
                 .editor = true,
index 28f2b7b8984354ab8c4c83684c7d1d3a542cc7af..8abd9de47ceea51dce1b5780be9c2d80c1507bc3 100644 (file)
@@ -99,6 +99,8 @@ static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX]
 EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t match_type, const Device **ret_device) {
         EFI_STATUS status;
 
+        assert(ret_device);
+
         if ((uintptr_t) hwid_buffer % alignof(Device) != 0)
                 return EFI_INVALID_PARAMETER;
 
index 21b36e5a6e5f950750902ee99518e8596322beae..81a5641d40579b1ee3699c722b34d53ed3f00f89 100644 (file)
@@ -11,6 +11,8 @@
 #define VIEWPORT_RATIO 10
 
 static void event_closep(EFI_EVENT *event) {
+        assert(event);
+
         if (!*event)
                 return;
 
@@ -191,6 +193,9 @@ EFI_STATUS query_screen_resolution(uint32_t *ret_w, uint32_t *ret_h) {
         EFI_STATUS err;
         EFI_GRAPHICS_OUTPUT_PROTOCOL *go;
 
+        assert(ret_w);
+        assert(ret_h);
+
         err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &go);
         if (err != EFI_SUCCESS)
                 return err;
index 0f8986b5984b96098bce3102f819f2c4f7066cdb..d0be7a1e160d38176be7bfcd5edfac559e93fb56 100644 (file)
@@ -349,6 +349,8 @@ static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char1
 
 /* Patterns are fnmatch-compatible (with reduced feature support). */
 bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) {
+        assert(haystack);
+
         /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we
          * simply have to make sure the very first simple pattern matches the start of haystack. Then we just
          * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra
index d8cbe7deed425a52a65137bc44c0f79229513cf3..b8086ac633759f1184b1b53ba34b50b6d3cc32e6 100644 (file)
@@ -74,6 +74,7 @@ EFI_STATUS initrd_register(
         EFI_HANDLE handle;
         struct initrd_loader *loader;
 
+        POINTER_MAY_BE_NULL(initrd);
         assert(ret_initrd_handle);
 
         /* If no initrd is specified we'll not install any. This avoids registration of the protocol for that
index 4a4c4e9365012c2b0e9260a5d7fa3a77c9e0af5e..c40a9aad65b0d663414da10cf95e2b41fe1f921a 100644 (file)
@@ -344,6 +344,7 @@ EFI_STATUS open_directory(
         EFI_STATUS err;
 
         assert(root);
+        assert(ret);
 
         /* Opens a file, and then verifies it is actually a directory */
 
index c4693293ab9096b8ca92d3f5e1642f64c31b1829..fc89ce143b94bc5771dbbdb439c7de3f79d7286c 100644 (file)
@@ -1230,6 +1230,8 @@ static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
 static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) {
         _cleanup_free_ uint16_t *options = NULL;
 
+        assert(id);
+
         int n = efi_get_boot_options(&options);
         if (n < 0)
                 return n;
index 48635fad64c4c7fa963629964ca174cdc1467d9b..aa04c9bbf0e5dbfe9b490bef44cdc9682ba01b0d 100644 (file)
@@ -393,6 +393,8 @@ static int verb_list_bus_names(int argc, char *argv[], uintptr_t _data, void *us
 }
 
 static void print_subtree(const char *prefix, const char *path, char **l) {
+        assert(l);
+
         /* We assume the list is sorted. Let's first skip over the
          * entry we are looking at. */
         for (;;) {
index 60181caffc1229fd72ff15605ed3632310a31d32..a9bf64a61651dc77c0d2e6256df435a1d74a2701 100644 (file)
@@ -509,7 +509,7 @@ static int refresh(
 }
 
 static int group_compare(Group * const *a, Group * const *b) {
-        const Group *x = *a, *y = *b;
+        const Group *x = *ASSERT_PTR(a), *y = *ASSERT_PTR(b);
         int r;
 
         if (arg_order != ORDER_TASKS || arg_recursive) {
index f41d6fef310f5c5748d9046ce31279b772be2911..96724de12b6357f592c317070feb0949f76564a5 100644 (file)
@@ -415,6 +415,8 @@ static int retrieve(const void *data,
         size_t ident;
         char *v;
 
+        assert(var);
+
         ident = strlen(name) + 1; /* name + "=" */
 
         if (len < ident)
@@ -529,6 +531,8 @@ static int resolve_filename(const char *root, char **p) {
         char *resolved = NULL;
         int r;
 
+        assert(p);
+
         if (!*p)
                 return 0;
 
index 64cd3813b872858e13c96691d4138d5f5420a19f..04eeb6223e2199893be5f0dcac729607a8f1b024 100644 (file)
@@ -2540,6 +2540,8 @@ static uint32_t determine_flags(void) {
 static void remove_and_erasep(const char **p) {
         int r;
 
+        assert(p);
+
         if (!*p)
                 return;
 
index 3168e5699aa93c01779b035dacadd579d9e9b20e..e7f662512bff51ed9fafb6a640bdc76bdaa5d31a 100644 (file)
@@ -100,3 +100,8 @@ static inline int __coverity_check_and_return__(int condition) {
                 assert_se(_expr_ >= _zero);              \
                 _expr_;                                  \
         })
+
+/* Mark a pointer parameter as intentionally nullable. This is a no-op at runtime but suppresses
+ * the coccinelle check-pointer-deref warning for parameters that are safely handled before any
+ * dereference (e.g. passed to a NULL-safe helper like iovec_is_set()). */
+#define POINTER_MAY_BE_NULL(ptr) ({ (void) (ptr); })
index dcd1264534970040bcc89e89ab009288c9fe9b4a..49462c6a65d89e28dbbee1d5df0ea8572211ee8e 100644 (file)
@@ -822,6 +822,8 @@ static int context_update_kernel_hostname(
 }
 
 static void unset_statp(struct stat **p) {
+        assert(p);
+
         if (!*p)
                 return;
 
index ee190827615e267f6f5886e8efbf06ee6555f4ed..c140c406cb6b69c473dd9033b275404834d6cf73 100644 (file)
@@ -104,8 +104,10 @@ static void request_meta_free(
                 struct MHD_Connection *connection,
                 void **connection_cls,
                 enum MHD_RequestTerminationCode toe) {
+        RequestMeta *m;
 
-        RequestMeta *m = *connection_cls;
+        assert(connection_cls);
+        m = *connection_cls;
 
         if (!m)
                 return;
index 35ab12578b2a0fb58efe6c37ada95abe726abb1d..0ff44ede6fc1c8da6436c545ab44e2cff2ef0dc6 100644 (file)
@@ -1103,6 +1103,8 @@ static int parse_argv(int argc, char *argv[]) {
 static int load_certificates(char **key, char **cert, char **trust) {
         int r;
 
+        assert(trust);
+
         r = read_full_file_full(
                         AT_FDCWD, arg_key ?: PRIV_KEY_FILE, UINT64_MAX, SIZE_MAX,
                         READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
index 73b6ed4c9466c4949fe3ba527229aa6fe61fed69..32751e85e1c34d9537d5b793a575f3b2a10efe9b 100644 (file)
@@ -230,6 +230,8 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
 }
 
 static void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
+        assert(p);
+
         gnutls_x509_crt_deinit(*p);
 }
 
index 63c24031240e69f4312c54268dafe24857565446..f15cbc3a91edc333fef4a7bb5f6a4d93bf275498 100644 (file)
@@ -416,6 +416,9 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char *
         };
         int c;
 
+        assert(syspath);
+        assert(subsystem);
+
         while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0)
                 switch (c) {
                 case 'p':
index d9ee2fe2bb64c8e045a2f136ff031bbe1c5be6d0..733b1a19ef100e714ad0790e3ab5bce8a5272010 100644 (file)
@@ -1308,6 +1308,9 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid)
         char *_uid = NULL;
         const char *_machine = NULL;
 
+        assert(uid);
+        assert(machine);
+
         if (spec) {
                 const char *at;
 
index 97ad9c0a9f77f0c37afb68859a6441700065ac91..382a246c2dddbb78328584e353c85f89b1e687b6 100644 (file)
@@ -438,6 +438,8 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void
 }
 
 static void clear_candidate_hashmapp(Manager **m) {
+        assert(m);
+
         if (*m)
                 hashmap_clear((*m)->monitored_mem_pressure_cgroup_contexts_candidates);
 }
index a7f3ce9dfd0527785c171780af688992975be275..bae23b1a5115e83acc073a289148b628dadead32 100644 (file)
@@ -137,6 +137,9 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
 }
 
 static int compare_metadata(PortableMetadata *const *x, PortableMetadata *const *y) {
+        assert(x);
+        assert(y);
+
         return strcmp((*x)->name, (*y)->name);
 }
 
@@ -146,6 +149,8 @@ int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetad
         PortableMetadata *item;
         size_t k = 0;
 
+        assert(ret);
+
         sorted = new(PortableMetadata*, hashmap_size(unit_files));
         if (!sorted)
                 return -ENOMEM;
index 98a1657a720a88072e6107a0b65568c1b305935c..0cd461a997e23b0c65f503e0df39295d8861a55b 100644 (file)
@@ -57,6 +57,8 @@ static bool is_portable_managed(const char *unit) {
 static int determine_image(const char *image, bool permit_non_existing, char **ret) {
         int r;
 
+        assert(ret);
+
         /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
          * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
          * (among other things, to make the path independent of the client's working directory) before passing it
@@ -235,6 +237,8 @@ static int acquire_bus(sd_bus **bus) {
 static int maybe_reload(sd_bus **bus) {
         int r;
 
+        assert(bus);
+
         if (!arg_reload)
                 return 0;
 
index 1bdeec6ea5644dc650d6c0d1c2da78bd5f1f57bc..7dbcfd6d5824ecca54eb3cdb148b835a6824431d 100644 (file)
@@ -652,6 +652,8 @@ static int calculate_verity_hash_size(
                 uint64_t data_block_size,
                 uint64_t *ret_bytes) {
 
+        assert(ret_bytes);
+
         /* The calculation here is based on the documented on-disk format of the dm-verity
          * https://docs.kernel.org/admin-guide/device-mapper/verity.html#hash-tree
          *
@@ -1264,6 +1266,8 @@ static uint64_t free_area_available_for_new_partitions(Context *context, const F
 }
 
 static int free_area_compare(FreeArea *const *a, FreeArea *const*b, Context *context) {
+        assert(a);
+        assert(b);
         assert(context);
 
         return CMP(free_area_available_for_new_partitions(context, *a),
@@ -4042,6 +4046,8 @@ static void context_unload_partition_table(Context *context) {
 static int format_size_change(uint64_t from, uint64_t to, char **ret) {
         char *t;
 
+        assert(ret);
+
         if (from != UINT64_MAX) {
                 if (from == to || to == UINT64_MAX)
                         t = strdup(FORMAT_BYTES(from));
index c6caaa1260ba3fb88d02db71ea2e68d39d476a5e..7a4596e4724ef79c5832268a44a64692590f83e1 100644 (file)
@@ -746,6 +746,8 @@ static int plymouth_notify_port(NvmePort *port, struct local_address *a) {
 }
 
 static int nvme_port_report(NvmePort *port, bool *plymouth_done) {
+        POINTER_MAY_BE_NULL(plymouth_done);
+
         if (!port)
                 return 0;
 
index 32de5a454c8cb03847623e2dc3faa207638071ec..157bcb0a0b0db8052d9890239fb7033ba8c56d80 100644 (file)
@@ -1785,6 +1785,9 @@ static int merge_hierarchy(
 }
 
 static int strverscmp_improvedp(char *const* a, char *const* b) {
+        assert(a);
+        assert(b);
+
         /* usable in qsort() for sorting a string array with strverscmp_improved() */
         return strverscmp_improved(*a, *b);
 }
index 8e5736ef3531f5ceae392e5407121fb9d30f1463..4e7c12e6b9e123da16f082525b9f81871346fc0c 100644 (file)
@@ -82,6 +82,9 @@ static int list_dependencies_print(const char *name, UnitActiveState state, int
 }
 
 static int list_dependencies_compare(char * const *a, char * const *b) {
+        assert(a);
+        assert(b);
+
         if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET)
                 return 1;
         if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET)
index 0872f82c2a3f26bc6bce46a54ade0c504346cb73..570aab73659225f6bff8f0cae55cb8d4a3236c89 100644 (file)
@@ -320,6 +320,9 @@ static void unit_status_info_done(UnitStatusInfo *info) {
 }
 
 static void format_active_state(const char *active_state, const char **active_on, const char **active_off) {
+        assert(active_on);
+        assert(active_off);
+
         if (streq_ptr(active_state, "failed")) {
                 *active_on = ansi_highlight_red();
                 *active_off = ansi_normal();
index 7885a668fd483fe459215c06951d06cb44fa3971..17f263790eda01c50cce0897681ff5aa8e8458e7 100644 (file)
@@ -4433,6 +4433,7 @@ static int parse_arguments(
         int r;
 
         assert(c);
+        assert(invalid_config);
 
         STRV_FOREACH(arg, args) {
                 if (arg_inline) {
index 5ab40a35526d36561f3800ab76b042eb00fd38a2..3ced8ad91ca040f2758e1e78c3f1978de46b265e 100644 (file)
@@ -90,6 +90,8 @@ static const char* parse_token(const char *current, int32_t *val_out) {
         char *next;
         int32_t val;
 
+        assert(val_out);
+
         if (!current)
                 return NULL;
 
index cdd8da3203fea53e106e82d869cfa52b7cc29381..a252ec99dd79bdd97191c196f0ccbe8957275481 100644 (file)
@@ -398,6 +398,8 @@ static sd_device* handle_scsi_hyperv(sd_device *parent, char **path, size_t guid
 static sd_device* handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) {
         const char *id, *name;
 
+        assert(supported_parent);
+
         if (device_is_devtype(parent, "scsi_device") <= 0)
                 return parent;
 
@@ -454,6 +456,8 @@ static sd_device* handle_cciss(sd_device *parent, char **path) {
 static void handle_scsi_tape(sd_device *dev, char **path) {
         const char *name;
 
+        assert(path);
+
         /* must be the last device in the syspath */
         if (*path)
                 return;
index f0e4fbccfd79194893d7c5b563a5a72180994abb..691230d7535cee77200eca7b53298c51bf5fcee3 100644 (file)
@@ -1359,6 +1359,7 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
         assert(line);
         assert(*line);
         assert(ret_key);
+        assert(ret_attr);
         assert(ret_op);
         assert(ret_value);
         assert(ret_is_case_insensitive);
index b2f6d3b8af726036da435499efc4b39a29831b7d..4a244ae83dc42835aba64a60cdac82e7ab195430 100644 (file)
@@ -116,6 +116,8 @@ static int parse_block_size(const char *t, uint64_t *size) {
         uint64_t u;
         int r;
 
+        assert(size);
+
         r = parse_size(t, 1024, &u);
         if (r < 0)
                 return r;
index 62ddce1815e399aeab8823cdc636e48a4afce394..ad77e476c83e4c53c0cd83bf6b7a89c0b84c9dd0 100644 (file)
@@ -291,6 +291,9 @@ static int xdg_config_item_table_lookup(
                 void *userdata) {
 
         assert(lvalue);
+        assert(ret_func);
+        assert(ret_ltype);
+        assert(ret_data);
 
         /* Ignore any keys with [] as those are translations. */
         if (strchr(lvalue, '[')) {
diff --git a/tools/check-coccinelle.sh b/tools/check-coccinelle.sh
new file mode 100755 (executable)
index 0000000..c7d1f6f
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eu
+set -o pipefail
+
+SRC_DIR="${1:?}"
+COCCI_DIR="${2:?}"
+
+FOUND=0
+
+for cocci in "$COCCI_DIR"/check-*.cocci; do
+    [[ -f "$cocci" ]] || continue
+    output=$(spatch --very-quiet --sp-file "$cocci" --dir "$SRC_DIR" 2>&1)
+    if [[ -n "$output" ]]; then
+        echo "FAIL: $(basename "$cocci") found issues in $SRC_DIR:"
+        echo "$output"
+        FOUND=1
+    fi
+done
+
+if [[ "$FOUND" -ne 0 ]]; then
+    echo ""
+    echo "Coccinelle check(s) failed. For each flagged dereference, either:"
+    echo "  - Add assert(param)/ASSERT_PTR(param) at the top of the function (if the parameter must not be NULL)"
+    echo "  - Add an if (param) guard before the dereference (if NULL is valid)"
+    echo "  - Add POINTER_MAY_BE_NULL(param) if NULL is okay for param"
+    exit 1
+fi
index 3132eeddba51fdb93fd554973911b636ec952b12..e8b3133d9c8cafe51599fce2e00304713ed5db42 100644 (file)
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 check_api_docs_sh              = files('check-api-docs.sh')
+check_coccinelle_sh            = files('check-coccinelle.sh')
 check_efi_alignment_py         = files('check-efi-alignment.py')
 check_help_sh                  = files('check-help.sh')
 check_version_history_py       = files('check-version-history.py')