Skip to content

Remove assert() instances from libkmod #382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

evelikov
Copy link
Collaborator

As mentioned in #255, ideally we shouldn't be having assert() in a shared library.

This series removes them, leaving the ones in depmod as-is. Most instances are unreachable and error handling is added otherwise. Where possible, call sites are annotated as _nonnull_ so the compiler flags future issues.

As a final cleanup, our assert_cc macro becomes a wrapper around the C11 static_assert (should we nuke our wrapper?) and header inclusions are purged.

evelikov added 13 commits June 26, 2025 13:25
In a handful of places, we're using asserts to guard for internal API
misuse. At the same time, having asserts() in a library is generally a
bad idea, especially since distributions may be building with or without
"-DNDEBUG".

Based on code analysis, some instances are not reachable so let's remove
them.

Signed-off-by: Emil Velikov <[email protected]>
The function already requires that all the pointers are non-null, so add
the attribute macro. Thus the compiler will give us a lovely warning
when it detects a potential issue.

As result we can remove the assert(), which really shouldn't exist in a
shared library.

Signed-off-by: Emil Velikov <[email protected]>
Handful of public facing functions check if the respective pointers are
NULL initialized, while others assert() on it. Remove the latter (which
is frowned upon in shared libraries) and return early in that case.

Should this change yield any user-facing changes, this means they were
already getting random corruption and haven't noticed it yet.

Signed-off-by: Emil Velikov <[email protected]>
The only caller of module_do_install_commands() already knows the
command (+ ensures it's non-null). So let's just pass it as argument,
drop the duplicate kmod_module_get_install_commands() call and
unreachable assert().

Signed-off-by: Emil Velikov <[email protected]>
The only caller of kmod_module_get_probe_list() already ensures that the
respective arguments are not NULL. Thus we can drop the asserts().

Signed-off-by: Emil Velikov <[email protected]>
We cannot reach the assert() even though the current code-paths, do not
make that abundantly clear. A follow-up commit can resolve that, but
for now we can remove the assert.

Signed-off-by: Emil Velikov <[email protected]>
Effectively we have two callers of the said APIs -
kmod_module_new_from_lookup() and kmod_module_new_from_name_lookup().

Both of those ensure the list (pointer) is initialized, so we can drop
the unreachable asserts.

Signed-off-by: Emil Velikov <[email protected]>
Drop the asserts and instead annotate the function as _nonnull_all_
which means we'll get a compiler warning, instead of a runtime crash
should a NULL is passed to the function.

Signed-off-by: Emil Velikov <[email protected]>
Remove the final assert calls from the library and the respective test.
The latter being the only xfail test in the whole project and covering only
one of the asserts being removed.

In addition, since assert() uses abort() the code coverage of the
function is completely thrown away... Which isn't great.

We could reintroduce some of these checks (asserts remove here and
previous commits) and/or tests, although it should probably be done
differently. Having any assert() in a public/shared library is a no-go.

Signed-off-by: Emil Velikov <[email protected]>
Two of the three instances are unreachable, while the last one can be
replaced with proper error handling.

Signed-off-by: Emil Velikov <[email protected]>
Currently we use the GNU extension _Static_assert, with a fallback for
older compilers. At the same time, we require a C11 compiler, which by
definition has the ISO static_assert().

Use the C11 one, remove the build-time check and diverging code path.

Signed-off-by: Emil Velikov <[email protected]>
With (nearly) all assert() instances gone and the static_assert()
instance properly pulling assert.h we no longer need these includes.

Signed-off-by: Emil Velikov <[email protected]>
Copy link

codecov bot commented Jun 26, 2025

Codecov Report

Attention: Patch coverage is 33.33333% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
libkmod/libkmod-module.c 25.00% 1 Missing and 5 partials ⚠️
testsuite/init_module.c 42.85% 2 Missing and 2 partials ⚠️
Files with missing lines Coverage Δ
libkmod/libkmod-elf.c 51.50% <ø> (ø)
libkmod/libkmod-index.c 56.08% <ø> (ø)
libkmod/libkmod.c 50.60% <ø> (ø)
shared/array.c 91.66% <ø> (ø)
shared/macro.h 100.00% <ø> (ø)
shared/strbuf.c 87.03% <ø> (ø)
shared/strbuf.h 75.00% <ø> (ø)
shared/util.c 65.84% <ø> (ø)
testsuite/delete_module.c 70.47% <ø> (ø)
testsuite/path.c 43.58% <ø> (ø)
... and 4 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@evelikov
Copy link
Collaborator Author

evelikov commented Jul 5, 2025

As an alternative, we could keep the assert() that are used to track internal misuse and where _nonnull_ is not sufficient.

In that case, we should probably check:

  • fail for -D build-tests=true and -DNDEBUG is set (by meson or otherwise), and/or
  • fail for -D build-tests=false and -UNDEBUG

Although in that case, any distribution which wants to build and run tests against the same build will be stuffed.
So I'm sort of leaning that purging them all is the better option.

@@ -662,8 +655,6 @@ int kmod_elf_strip(const struct kmod_elf *elf, unsigned int flags, const void **
uint8_t *changed;
int err = 0;

assert(flags & (KMOD_INSERT_FORCE_MODVERSION | KMOD_INSERT_FORCE_VERMAGIC));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixed feelings with these assert() removals. They were actually added for programming mistakes. Not for input errors handling. For this case, the input control is in do_init_module().

The assert here is because the function only handles KMOD_INSERT_FORCE_MODVERSION and KMOD_INSERT_FORCE_VERMAGIC. If we passed flags that doesn't contain those, it's not the end of the world: the function will continue to do its job, but it's likely wrong: it would mean we probably added another flag and forgot to add its handling in this function. By removing it we are making it silent rather than simply crash the program/test during development.

So IMO the statement "we shouldn't have asserts in a shared library" is wrong. It shouldn't be used for input control or error handling, but it's still useful to catch program programming mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And true, some of them shouldn't be there and we should add the error handling, but I will need to review case by case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to keep some for the short run, until we get more extensive tests to cover the code-paths, or you prefer to keep them even in that case?

As a whole, it feels slightly awkward having them available (potentially reachable) in distribution builds. Perhaps they can be "enabled" with debug-messages or another meson toggle?

Fwiw most distributions are not* be using debug-messages these days.

  • Fedora should stop using it any day now. While Gentoo/Yocto have it accessible with their modular build setups.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For enabling/disabling maybe we should have this instead?

diff --git a/meson.build b/meson.build
index 5740e13..75343ed 100644
--- a/meson.build
+++ b/meson.build
@@ -7,6 +7,7 @@ project(
   default_options : [
     'c_std=gnu11',
     'b_pie=true',
+    'b_ndebug=if-release',
     'warning_level=2',
     'prefix=/usr',
     'sysconfdir=/etc',

... to be merged after the wrong asserts are removed, so we still pass the testsuite tests.

Distros can also control it with -Db_ndebug=...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm correctly parsing the meson documentation this is the default already. The issue is that due to $reasons, at least some distributions are building with an explicit --build-type plain.

At a glance we can easily swap the assert(static literal...) checks for assert_cc()` as below. It seems like the better option IMHO since it's a compile-time check. What do you think?

diff --git a/shared/array.c b/shared/array.c
index 85f8955e..da40d36e 100644
--- a/shared/array.c
+++ b/shared/array.c
@@ -36,7 +36,7 @@ static void array_trim(struct array *array)
 	}
 }
 
-void array_init(struct array *array, size_t step)
+void __array_init(struct array *array, size_t step)
 {
 	array->array = NULL;
 	array->count = 0;
diff --git a/shared/array.h b/shared/array.h
index 8facc52d..1cdcaed0 100644
--- a/shared/array.h
+++ b/shared/array.h
@@ -13,7 +13,10 @@ struct array {
 	size_t step;
 };
 
-void array_init(struct array *array, size_t step);
+void __array_init(struct array *array, size_t step);
+#define array_init(a, s) \
+	assert_cc(s > 0); \
+	__array_init(a, s);
 int array_append(struct array *array, const void *element);
 int array_append_unique(struct array *array, const void *element);
 void array_pop(struct array *array);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants