[PATCH] kconfig: warn on dead default

From: Julian Braha

Date: Sat Jun 06 2026 - 10:00:23 EST


The dead default check was originally introduced with kconfirm:
https://lore.kernel.org/all/6ec4df6d-1445-48ca-8f54-1d1a83c4716d@xxxxxxxxx/

While I'm still working on that tool, it's not yet ready for inclusion
into the tree. I am currently waiting for common distro packagers to
package the parsing library before submitting the next RFC iteration.

However, the dead default check is more impactful than the other checks:
all 4 dead defaults that were detected should not have been dead and could
cause misconfiguration bugs. But fortunately, these were just for kunit
tests. The 3 patches to fix them have all since been merged:
commit aef656a0e6c0 ("powerpc: fix dead default for GUEST_STATE_BUFFER_TEST")
commit 30cc5e2ad826 ("s390/Kconfig: Cleanup defaults for selftests")
commit df75430515c3 ("drm: fix dead default for DRM_TTM_KUNIT_TEST")

We can actually check for dead defaults while evaluating Kconfig, which
should be even more effective at preventing future instances than keeping
it in a static checker.

Note that this patch will only trigger a warning when the default values
are different, in other words, pure duplicate defaults won't cause a
warning, as they are simply redundant.

Signed-off-by: Julian Braha <julianbraha@xxxxxxxxx>
---
scripts/kconfig/menu.c | 22 +++++++++-
.../kconfig/tests/warn_dead_default/Kconfig | 40 +++++++++++++++++++
.../tests/warn_dead_default/__init__.py | 8 ++++
.../tests/warn_dead_default/expected_stderr | 4 ++
4 files changed, 73 insertions(+), 1 deletion(-)
create mode 100644 scripts/kconfig/tests/warn_dead_default/Kconfig
create mode 100644 scripts/kconfig/tests/warn_dead_default/__init__.py
create mode 100644 scripts/kconfig/tests/warn_dead_default/expected_stderr

diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c
index b2d8d4e11e07..8c280292f9cd 100644
--- a/scripts/kconfig/menu.c
+++ b/scripts/kconfig/menu.c
@@ -242,13 +242,33 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2)

static void sym_check_prop(struct symbol *sym)
{
- struct property *prop;
+ struct property *prev, *prop;
struct symbol *sym2;
char *use;

for (prop = sym->prop; prop; prop = prop->next) {
switch (prop->type) {
case P_DEFAULT:
+ for_all_defaults(sym, prev) {
+ if (prev == prop)
+ break;
+ if (expr_is_yes(prev->visible.expr)) {
+ if (!expr_eq(prev->expr, prop->expr))
+ prop_warn(prop,
+ "default for '%s' is unreachable: earlier default at %s:%d is unconditional",
+ sym->name ? sym->name : "<choice>",
+ prev->filename, prev->lineno);
+ break;
+ }
+ if (expr_eq(prev->visible.expr, prop->visible.expr)) {
+ if (!expr_eq(prev->expr, prop->expr))
+ prop_warn(prop,
+ "default for '%s' has the same condition as the earlier default at %s:%d",
+ sym->name ? sym->name : "<choice>",
+ prev->filename, prev->lineno);
+ break;
+ }
+ }
if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) &&
prop->expr->type != E_SYMBOL)
prop_warn(prop,
diff --git a/scripts/kconfig/tests/warn_dead_default/Kconfig b/scripts/kconfig/tests/warn_dead_default/Kconfig
new file mode 100644
index 000000000000..adf421d73dbd
--- /dev/null
+++ b/scripts/kconfig/tests/warn_dead_default/Kconfig
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config A
+ bool
+
+config B
+ bool
+
+config UNCONDITIONAL
+ int
+ default 1
+ default 2
+
+config CONDITIONAL
+ int
+ default 1 if A
+ default 2 if A
+ default 3 if B
+
+config CONDITIONAL_COMMUTATIVE
+ int
+ default 1 if A && B
+ default 2 if B && A
+
+config CONTROL
+ int
+ default 1 if A
+ default 2 if B
+ default 3
+
+choice
+ prompt "test choice"
+ default C
+ default D
+
+ config C
+ bool "C"
+ config D
+ bool "D"
+endchoice
diff --git a/scripts/kconfig/tests/warn_dead_default/__init__.py b/scripts/kconfig/tests/warn_dead_default/__init__.py
new file mode 100644
index 000000000000..911b30ce19fe
--- /dev/null
+++ b/scripts/kconfig/tests/warn_dead_default/__init__.py
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Test detection of dead defaults (different defaults that can never be active).
+"""
+
+def test(conf):
+ assert conf.olddefconfig() == 0
+ assert conf.stderr_contains('expected_stderr')
diff --git a/scripts/kconfig/tests/warn_dead_default/expected_stderr b/scripts/kconfig/tests/warn_dead_default/expected_stderr
new file mode 100644
index 000000000000..baa20bf33910
--- /dev/null
+++ b/scripts/kconfig/tests/warn_dead_default/expected_stderr
@@ -0,0 +1,4 @@
+Kconfig:12:warning: default for 'UNCONDITIONAL' is unreachable: earlier default at Kconfig:11 is unconditional
+Kconfig:17:warning: default for 'CONDITIONAL' has the same condition as the earlier default at Kconfig:16
+Kconfig:23:warning: default for 'CONDITIONAL_COMMUTATIVE' has the same condition as the earlier default at Kconfig:22
+Kconfig:34:warning: default for '<choice>' is unreachable: earlier default at Kconfig:33 is unconditional
--
2.53.0