* [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE()
2026-09-11 22:14 [PATCH 0/3] Catch automatic storage in IDA and Maple Tree definitions Yury Norov
@ 2026-09-11 22:14 ` Yury Norov
2026-09-12 5:57 ` Andrew Morton
2026-09-11 22:14 ` [PATCH 2/3] idr: assert static storage for DEFINE_IDA() Yury Norov
2026-09-11 22:14 ` [PATCH 3/3] maple_tree: assert static storage for DEFINE_MTREE() Yury Norov
2 siblings, 1 reply; 6+ messages in thread
From: Yury Norov @ 2026-09-11 22:14 UTC (permalink / raw)
To: Andrew Morton, Matthew Wilcox, Liam R. Howlett
Cc: Yury Norov, Chris Li, Alice Ryhl, Andrew Ballance, Yury Norov,
linux-sparse, linux-kernel, linux-fsdevel, linux-mm, maple-tree
Static lock initializers rely on a persistent object address when lockdep
assigns a lock-class key. Using such an initializer for an automatic local
object can compile successfully but disable lockdep on the first lock
acquisition.
Add ASSERT_STATIC_STORAGE() for declaration macros that require
static storage duration. It declares an unused static pointer initialized
with the object's address. An automatic local object's address is not a
valid static initializer, so the compiler rejects it.
Mirror the helper in tools/include/linux/compiler.h because the userspace
radix-tree tests include the kernel IDA and Maple Tree headers with the
tools compiler definitions.
For example:
void example(void)
{
int object;
ASSERT_STATIC_STORAGE(object);
}
GCC reports:
error: initializer element is not constant
name##_storage_check = &(name)
^
note: in expansion of macro 'ASSERT_STATIC_STORAGE'
ASSERT_STATIC_STORAGE(object);
File-scope objects and static local objects remain valid. The helper takes
an object identifier and must be used as a declaration after that object
has been declared.
Link: https://lore.kernel.org/all/20260911155244.1406122-1-ynorov@nvidia.com/
Signed-off-by: Yury Norov <ynorov@nvidia.com>
---
include/linux/compiler.h | 5 +++++
tools/include/linux/compiler.h | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/include/linux/compiler.h b/include/linux/compiler.h
index cb2f6050bdf7..ef9036fa5413 100644
--- a/include/linux/compiler.h
+++ b/include/linux/compiler.h
@@ -275,6 +275,11 @@ static inline void *offset_to_ptr(const int *off)
#define __ADDRESSABLE(sym) \
___ADDRESSABLE(sym, __section(".discard.addressable"))
+/* Enforce static storage duration. */
+#define ASSERT_STATIC_STORAGE(name) \
+ static typeof(name) * const __always_unused \
+ name##_storage_check = &(name)
+
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h
index f2f54b038168..03ecf9086643 100644
--- a/tools/include/linux/compiler.h
+++ b/tools/include/linux/compiler.h
@@ -73,6 +73,11 @@
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#endif
+/* Enforce static storage duration. */
+#define ASSERT_STATIC_STORAGE(name) \
+ static typeof(name) * const __always_unused \
+ name##_storage_check = &(name)
+
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE()
2026-09-11 22:14 ` [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE() Yury Norov
@ 2026-09-12 5:57 ` Andrew Morton
2026-09-12 22:31 ` Yury Norov
0 siblings, 1 reply; 6+ messages in thread
From: Andrew Morton @ 2026-09-12 5:57 UTC (permalink / raw)
To: Yury Norov
Cc: Matthew Wilcox, Liam R. Howlett, Yury Norov, Chris Li,
Alice Ryhl, Andrew Ballance, linux-sparse, linux-kernel,
linux-fsdevel, linux-mm, maple-tree
On Fri, 11 Sep 2026 18:14:41 -0400 Yury Norov <yury.norov@gmail.com> wrote:
> Static lock initializers rely on a persistent object address when lockdep
> assigns a lock-class key. Using such an initializer for an automatic local
> object can compile successfully but disable lockdep on the first lock
> acquisition.
>
> Add ASSERT_STATIC_STORAGE() for declaration macros that require
> static storage duration. It declares an unused static pointer initialized
> with the object's address. An automatic local object's address is not a
> valid static initializer, so the compiler rejects it.
>
> Mirror the helper in tools/include/linux/compiler.h because the userspace
> radix-tree tests include the kernel IDA and Maple Tree headers with the
> tools compiler definitions.
>
> For example:
>
> void example(void)
> {
> int object;
> ASSERT_STATIC_STORAGE(object);
> }
>
> GCC reports:
>
> error: initializer element is not constant
> name##_storage_check = &(name)
> ^
> note: in expansion of macro 'ASSERT_STATIC_STORAGE'
> ASSERT_STATIC_STORAGE(object);
>
> File-scope objects and static local objects remain valid. The helper takes
> an object identifier and must be used as a declaration after that object
> has been declared.
>
Thanks, I'll queue it.
Sashiko wants the same treatment for a few other macros:
https://sashiko.dev/#/patchset/20260911221444.1523311-1-ynorov@nvidia.com
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE()
2026-09-12 5:57 ` Andrew Morton
@ 2026-09-12 22:31 ` Yury Norov
0 siblings, 0 replies; 6+ messages in thread
From: Yury Norov @ 2026-09-12 22:31 UTC (permalink / raw)
To: Andrew Morton
Cc: Yury Norov, Matthew Wilcox, Liam R. Howlett, Chris Li,
Alice Ryhl, Andrew Ballance, linux-sparse, linux-kernel,
linux-fsdevel, linux-mm, maple-tree
On Fri, Sep 11, 2026 at 10:57:49PM -0700, Andrew Morton wrote:
> On Fri, 11 Sep 2026 18:14:41 -0400 Yury Norov <yury.norov@gmail.com> wrote:
>
> > Static lock initializers rely on a persistent object address when lockdep
> > assigns a lock-class key. Using such an initializer for an automatic local
> > object can compile successfully but disable lockdep on the first lock
> > acquisition.
> >
> > Add ASSERT_STATIC_STORAGE() for declaration macros that require
> > static storage duration. It declares an unused static pointer initialized
> > with the object's address. An automatic local object's address is not a
> > valid static initializer, so the compiler rejects it.
> >
> > Mirror the helper in tools/include/linux/compiler.h because the userspace
> > radix-tree tests include the kernel IDA and Maple Tree headers with the
> > tools compiler definitions.
> >
> > For example:
> >
> > void example(void)
> > {
> > int object;
> > ASSERT_STATIC_STORAGE(object);
> > }
> >
> > GCC reports:
> >
> > error: initializer element is not constant
> > name##_storage_check = &(name)
> > ^
> > note: in expansion of macro 'ASSERT_STATIC_STORAGE'
> > ASSERT_STATIC_STORAGE(object);
> >
> > File-scope objects and static local objects remain valid. The helper takes
> > an object identifier and must be used as a declaration after that object
> > has been declared.
> >
>
> Thanks, I'll queue it.
>
> Sashiko wants the same treatment for a few other macros:
> https://sashiko.dev/#/patchset/20260911221444.1523311-1-ynorov@nvidia.com
Below is the list of candidates that my AI finds relevant:
/* Primitive locks */
DEFINE_RAW_SPINLOCK()
DEFINE_SPINLOCK() /* both non-RT and RT definitions */
DEFINE_RWLOCK() /* both non-RT and RT definitions */
DEFINE_MUTEX() /* both non-RT and RT definitions */
DEFINE_RT_MUTEX()
DECLARE_RWSEM() /* both non-RT and RT definitions */
DEFINE_SEMAPHORE()
DEFINE_SEQLOCK()
/* Wait queues and completions — preserve _ONSTACK aliases */
DECLARE_WAIT_QUEUE_HEAD()
DECLARE_SWAIT_QUEUE_HEAD()
DECLARE_COMPLETION()
/* Containers */
DEFINE_XARRAY_FLAGS() /* covers DEFINE_XARRAY(),
* DEFINE_XARRAY_ALLOC(),
* DEFINE_XARRAY_ALLOC1() */
DEFINE_IDA() /* already enforced */
DEFINE_MTREE() /* already enforced */
DEFINE_KLIST()
/* Work and timers */
DECLARE_WORK()
DECLARE_DELAYED_WORK()
DECLARE_DEFERRABLE_WORK()
DEFINE_TIMER()
DEFINE_KTHREAD_DELAYED_WORK()
/* Other objects containing locks */
DEFINE_RATELIMIT_STATE()
ATOMIC_NOTIFIER_HEAD()
BLOCKING_NOTIFIER_HEAD()
DEFINE_RCU_SYNC()
DEFINE_CTL_TABLE_POLL()
DEFINE_BPF_STORAGE_CACHE()
Lower-priority or configuration-dependent candidates:
DEFINE_IDR() /* normal operations don't use embedded lock */
RADIX_TREE() /* normal operations don't use embedded lock */
__DEFINE_PERCPU_RWSEM() /* covers DEFINE_PERCPU_RWSEM(),
* DEFINE_STATIC_PERCPU_RWSEM() */
__DEFINE_SRCU() /* Tree SRCU: covers all six wrappers */
DEFINE_SRCU() /* Tiny SRCU: shared non-static declaration */
_SRCU_NOTIFIER_HEAD() /* covers SRCU_NOTIFIER_HEAD(),
* SRCU_NOTIFIER_HEAD_STATIC() */
I planned to submit this hardening for them per the relevant subsystems
after at least landing the ASSERT_STATIC_STORAGE() in -next.
If you're OK to move that material through your tree all in one,
I can prepare it as a single series.
Thanks,
Yury
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/3] idr: assert static storage for DEFINE_IDA()
2026-09-11 22:14 [PATCH 0/3] Catch automatic storage in IDA and Maple Tree definitions Yury Norov
2026-09-11 22:14 ` [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE() Yury Norov
@ 2026-09-11 22:14 ` Yury Norov
2026-09-11 22:14 ` [PATCH 3/3] maple_tree: assert static storage for DEFINE_MTREE() Yury Norov
2 siblings, 0 replies; 6+ messages in thread
From: Yury Norov @ 2026-09-11 22:14 UTC (permalink / raw)
To: Andrew Morton, Matthew Wilcox, Liam R. Howlett
Cc: Yury Norov, Chris Li, Alice Ryhl, Andrew Ballance, Yury Norov,
linux-sparse, linux-kernel, linux-fsdevel, linux-mm, maple-tree
DEFINE_IDA() uses IDA_INIT(), which initializes the embedded XArray lock
with a static spinlock initializer. For an automatic local IDA, lockdep
cannot use the lock address as a persistent class key and reports
"INFO: trying to register non-static key" before disabling itself.
Apply ASSERT_STATIC_STORAGE() to DEFINE_IDA() so that this misuse is
rejected at compile time. For example:
void example(void)
{
DEFINE_IDA(ida);
ida_destroy(&ida);
}
GCC reports:
error: initializer element is not constant
name##_storage_check = &(name)
^
note: in expansion of macro 'ASSERT_STATIC_STORAGE'
ASSERT_STATIC_STORAGE(name)
note: in expansion of macro 'DEFINE_IDA'
DEFINE_IDA(ida);
File-scope definitions and static DEFINE_IDA() within a function remain
valid. Automatic local IDAs must instead be initialized with ida_init().
Direct uses of IDA_INIT() are not covered by this declaration check.
Convert the five automatic local IDAs in the userspace radix-tree tests
to ida_init() so they satisfy the new requirement.
Validated file-scope and static local definitions with a kernel object
build, and confirmed that an automatic local definition fails to compile.
Signed-off-by: Yury Norov <ynorov@nvidia.com>
---
include/linux/idr.h | 5 ++++-
tools/testing/radix-tree/idr-test.c | 20 +++++++++++++++-----
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/include/linux/idr.h b/include/linux/idr.h
index 789e23e67444..e2a4b6298511 100644
--- a/include/linux/idr.h
+++ b/include/linux/idr.h
@@ -16,6 +16,7 @@
#include <linux/gfp.h>
#include <linux/percpu.h>
#include <linux/cleanup.h>
+#include <linux/compiler.h>
struct idr {
struct radix_tree_root idr_rt;
@@ -269,7 +270,9 @@ struct ida {
#define IDA_INIT(name) { \
.xa = XARRAY_INIT(name, IDA_INIT_FLAGS) \
}
-#define DEFINE_IDA(name) struct ida name = IDA_INIT(name)
+#define DEFINE_IDA(name) \
+ struct ida name = IDA_INIT(name); \
+ ASSERT_STATIC_STORAGE(name)
int ida_alloc_range(struct ida *, unsigned int min, unsigned int max, gfp_t);
void ida_free(struct ida *, unsigned int id);
diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c
index 945144e98507..6fcba5b5870b 100644
--- a/tools/testing/radix-tree/idr-test.c
+++ b/tools/testing/radix-tree/idr-test.c
@@ -460,9 +460,11 @@ void ida_dump(struct ida *);
*/
void ida_check_nomem(void)
{
- DEFINE_IDA(ida);
+ struct ida ida;
int id;
+ ida_init(&ida);
+
id = ida_alloc_min(&ida, 256, GFP_NOWAIT);
IDA_BUG_ON(&ida, id != -ENOMEM);
id = ida_alloc_min(&ida, 1UL << 30, GFP_NOWAIT);
@@ -475,9 +477,11 @@ void ida_check_nomem(void)
*/
void ida_check_conv_user(void)
{
- DEFINE_IDA(ida);
+ struct ida ida;
unsigned long i;
+ ida_init(&ida);
+
for (i = 0; i < 1000000; i++) {
int id = ida_alloc(&ida, GFP_NOWAIT);
if (id == -ENOMEM) {
@@ -496,11 +500,13 @@ void ida_check_conv_user(void)
void ida_check_random(void)
{
- DEFINE_IDA(ida);
+ struct ida ida;
DECLARE_BITMAP(bitmap, 2048);
unsigned int i;
time_t s = time(NULL);
+ ida_init(&ida);
+
repeat:
memset(bitmap, 0, sizeof(bitmap));
for (i = 0; i < 100000; i++) {
@@ -522,9 +528,11 @@ void ida_check_random(void)
void ida_alloc_free_test(void)
{
- DEFINE_IDA(ida);
+ struct ida ida;
unsigned long i;
+ ida_init(&ida);
+
for (i = 0; i < 10000; i++)
assert(ida_alloc_max(&ida, 20000, GFP_KERNEL) == i);
assert(ida_alloc_range(&ida, 5, 30, GFP_KERNEL) < 0);
@@ -576,10 +584,12 @@ static void *ida_leak_fn(void *arg)
void ida_thread_tests(void)
{
- DEFINE_IDA(ida);
+ struct ida ida;
pthread_t threads[20];
int i;
+ ida_init(&ida);
+
for (i = 0; i < ARRAY_SIZE(threads); i++)
if (pthread_create(&threads[i], NULL, ida_random_fn, NULL)) {
perror("creating ida thread");
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/3] maple_tree: assert static storage for DEFINE_MTREE()
2026-09-11 22:14 [PATCH 0/3] Catch automatic storage in IDA and Maple Tree definitions Yury Norov
2026-09-11 22:14 ` [PATCH 1/3] compiler.h: add ASSERT_STATIC_STORAGE() Yury Norov
2026-09-11 22:14 ` [PATCH 2/3] idr: assert static storage for DEFINE_IDA() Yury Norov
@ 2026-09-11 22:14 ` Yury Norov
2 siblings, 0 replies; 6+ messages in thread
From: Yury Norov @ 2026-09-11 22:14 UTC (permalink / raw)
To: Andrew Morton, Matthew Wilcox, Liam R. Howlett
Cc: Yury Norov, Chris Li, Alice Ryhl, Andrew Ballance, Yury Norov,
linux-sparse, linux-kernel, linux-fsdevel, linux-mm, maple-tree
DEFINE_MTREE() uses MTREE_INIT(), which initializes the tree's embedded
lock with a static spinlock initializer. If the tree is an automatic
local object, lockdep rejects its address as a non-static class key and
disables locking validation on the first lock acquisition.
Apply ASSERT_STATIC_STORAGE() to DEFINE_MTREE() to catch automatic
local definitions at compile time. For example:
void example(void)
{
DEFINE_MTREE(mt);
mtree_destroy(&mt);
}
GCC reports:
error: initializer element is not constant
name##_storage_check = &(name)
^
note: in expansion of macro 'ASSERT_STATIC_STORAGE'
ASSERT_STATIC_STORAGE(name)
note: in expansion of macro 'DEFINE_MTREE'
DEFINE_MTREE(mt);
File-scope definitions and static local trees remain valid. Automatic
local trees must instead use mt_init() or mt_init_flags(). Direct uses of
MTREE_INIT() and MTREE_INIT_EXT() are unchanged.
Replace the local DEFINE_MTREE() in the interval-tree span test with a
plain declaration; the test already initializes the tree with
mt_init_flags() before use. Convert the three local Maple Trees in the
userspace radix-tree tests to mt_init().
Validated file-scope and static local definitions with a kernel object
build, and confirmed that an automatic local definition fails to compile.
The Maple Tree test and region allocation benchmark objects also build
with lockdep enabled.
Signed-off-by: Yury Norov <ynorov@nvidia.com>
---
include/linux/maple_tree.h | 4 +++-
lib/interval_tree_test.c | 2 +-
tools/testing/radix-tree/maple.c | 12 +++++++++---
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h
index e595ae5cd0ee..e30e57f5f3df 100644
--- a/include/linux/maple_tree.h
+++ b/include/linux/maple_tree.h
@@ -9,6 +9,7 @@
*/
#include <linux/kernel.h>
+#include <linux/compiler.h>
#include <linux/rcupdate.h>
#include <linux/spinlock.h>
@@ -297,7 +298,8 @@ struct maple_tree {
#endif
#define DEFINE_MTREE(name) \
- struct maple_tree name = MTREE_INIT(name, 0)
+ struct maple_tree name = MTREE_INIT(name, 0); \
+ ASSERT_STATIC_STORAGE(name)
#define mtree_lock(mt) spin_lock((&(mt)->ma_lock))
#define mtree_lock_nested(mas, subclass) \
diff --git a/lib/interval_tree_test.c b/lib/interval_tree_test.c
index b0b07270ce7c..06f77fb3179f 100644
--- a/lib/interval_tree_test.c
+++ b/lib/interval_tree_test.c
@@ -244,7 +244,7 @@ static int span_iteration_check(void)
unsigned long start, last;
struct interval_tree_span_iter span, mas_span;
- DEFINE_MTREE(tree);
+ struct maple_tree tree;
MA_STATE(mas, &tree, 0, 0);
diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c
index d967e76a3c06..bfe4b8626c42 100644
--- a/tools/testing/radix-tree/maple.c
+++ b/tools/testing/radix-tree/maple.c
@@ -36022,10 +36022,12 @@ static noinline void __init check_erase_rebalance(struct maple_tree *mt)
static noinline void __init check_mtree_dup(struct maple_tree *mt)
{
- DEFINE_MTREE(new);
+ struct maple_tree new;
int i, j, ret, count = 0;
unsigned int rand_seed = 17, rand;
+ mt_init(&new);
+
/* store a value at [0, 0] */
mt_init_flags(mt, 0);
mtree_store_range(mt, 0, 0, xa_mk_value(0), GFP_KERNEL);
@@ -36319,7 +36321,9 @@ static inline int check_vma_modification(struct maple_tree *mt)
void farmer_tests(void)
{
struct maple_node *node;
- DEFINE_MTREE(tree);
+ struct maple_tree tree;
+
+ mt_init(&tree);
mt_dump(&tree, mt_dump_dec);
@@ -36432,9 +36436,11 @@ static unsigned long get_last_index(struct ma_state *mas)
static void test_spanning_store_regression(void)
{
unsigned long from = 0, to = 0;
- DEFINE_MTREE(tree);
+ struct maple_tree tree;
MA_STATE(mas, &tree, 0, 0);
+ mt_init(&tree);
+
/*
* Build a 3-level tree. We require a parent node below the root node
* and 2 leaf nodes under it, so we can span the entirety of the right
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread