[PATCH] kconfig: warn on dead default

Julian Braha posted 1 patch 1 day, 14 hours ago
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
[PATCH] kconfig: warn on dead default
Posted by Julian Braha 1 day, 14 hours ago
The dead default check was originally introduced with kconfirm:
https://lore.kernel.org/all/6ec4df6d-1445-48ca-8f54-1d1a83c4716d@gmail.com/

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@gmail.com>
---
 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
Re: [PATCH] kconfig: warn on dead default
Posted by Sam Ravnborg 1 day, 14 hours ago
Hi Julian.

On Sat, Jun 06, 2026 at 03:00:08PM +0100, Julian Braha wrote:
> The dead default check was originally introduced with kconfirm:
> https://lore.kernel.org/all/6ec4df6d-1445-48ca-8f54-1d1a83c4716d@gmail.com/
> 
> 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.

A drive-by comment below.

> 
> Signed-off-by: Julian Braha <julianbraha@gmail.com>
> ---
>  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) {
This part iterates through all properties of sym.
>  		switch (prop->type) {
>  		case P_DEFAULT:
> +			for_all_defaults(sym, prev) {
And this part iterates through all properties of sym and will match only
P_DEFAULT.

I looks redundant with the two loops were we for the latter knows it is
a P_DEFAULT.

> +				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,

	Sam
Re: [PATCH] kconfig: warn on dead default
Posted by Julian Braha 1 day, 13 hours ago
On 6/6/26 15:23, Sam Ravnborg wrote:
> I looks redundant with the two loops were we for the latter knows it is
> a P_DEFAULT.

Hi Sam,

It may look redundant, but we do need to check each default we encounter
in the
```
for (prop = sym->prop; prop; prop = prop->next) {
```
loop against the previous defaults to know if we've encountered a
duplicate default condition.

I guess the other approach would be to copy each default as we encounter
them into a hashtable and check against that, instead? Let me know if
you have another, preferred approach.

- Julian Braha
Re: [PATCH] kconfig: warn on dead default
Posted by Sam Ravnborg 1 day, 10 hours ago
Hi Julian.

On Sat, Jun 06, 2026 at 04:01:34PM +0100, Julian Braha wrote:
> On 6/6/26 15:23, Sam Ravnborg wrote:
> > I looks redundant with the two loops were we for the latter knows it is
> > a P_DEFAULT.
> 
> Hi Sam,
> 
> It may look redundant, but we do need to check each default we encounter
> in the
> ```
> for (prop = sym->prop; prop; prop = prop->next) {
> ```
> loop against the previous defaults to know if we've encountered a
> duplicate default condition.
> 
> I guess the other approach would be to copy each default as we encounter
> them into a hashtable and check against that, instead? Let me know if
> you have another, preferred approach.

It was just a drive-by comment, keep it as simple as possible.

	Sam