Skip to contentSkip to conversation

Support Clang context analysis for ext2

PATCH In review Timothy Day 4 participants 17 messages Aug 11 – Sep 11

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v2 cover Support Clang context analysis for ext2

This description is mostly copied from v1:

This series adds annotations for Clang's context analysis to ext2. Clang context analysis was recently added in a series by Marco Elver [1]. This allows the compiler to validate different locking patterns at compile time.

This series enables context analysis, fixes pre-existing warnings, and adds new annotations. It is inspired by similar series in the block layer (NVMe host driver, for example [2]).

I'm starting with ext2 since it's smaller and simpler compared to ext4/btrfs/etc. After ext2, I'd be interested in converting the other filesystems and infrastructure code in fs/. I think the ultimate goal would be to enable this by default across all of fs/.

The series was built and tested with Clang 23 with CONFIG_WARN_CONTEXT_ANALYSIS enabled. I based on 7.2-rc7.

Thanks!

Changes from v1:

* A false positive has been fixed in Clang [3] and ported to Clang 23. Hence, the first patch (silencing that false positive) has been dropped.
* Use guard() and scoped_guard() instead of context_unsafe(), when possible, to express that certain fields are being protected by a newly initialized lock during init.

Link to v1: https://lore.kernel.org/linux-fsdevel/[email protected]/

[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://github.com/llvm/llvm-project/pull/209796

Timothy Day (8):
ext2: mark s_next_generation as guarded by s_next_gen_lock
ext2: annotate ext2_update_dynamic_rev() as requiring s_lock
ext2: mark statfs overhead cache as guarded by s_lock
ext2: mark s_mount_state as guarded by s_lock
ext2: annotate ext2_init_block_alloc_info() as requiring
truncate_mutex
ext2: annotate block-mapping helpers as requiring truncate_mutex
ext2: annotate s_rsv_window_root as requiring s_rsv_window_lock
ext2: enable context analysis support for ext2 filesystem

 fs/ext2/Makefile |  2 ++
 fs/ext2/balloc.c |  4 ++++
 fs/ext2/ext2.h   | 19 +++++++++++--------
 fs/ext2/inode.c  |  7 +++++++
 fs/ext2/super.c  | 35 +++++++++++++++++++----------------
 5 files changed, 43 insertions(+), 24 deletions(-)
Signature · 1 line
2.43.0
#1 lore raw

PATCH v2 1/8 ext2: mark s_next_generation as guarded by s_next_gen_lock

s_next_generation is only ever modified while holding s_next_gen_lock (in ext2_new_inode()), so annotate it with __guarded_by() for Clang's context analysis.

The only other write is the initialisation in ext2_fill_super(), which runs before the superblock is live. No concurrent access should be possible. Convert the spinlock initialization to use scoped_guard(spinlock_init, ...) and place the write under the guard to prevent a warning.

---
 fs/ext2/ext2.h  | 2 +-
 fs/ext2/super.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)
fs/ext2/ext2.h +1 -1
index 79f7b395258c..e851d2a6be66 100644
@@ -93,7 +93,7 @@ struct ext2_sb_info {
 	int s_inode_size;
 	int s_first_ino;
 	spinlock_t s_next_gen_lock;
-	u32 s_next_generation;
+	u32 s_next_generation __guarded_by(&s_next_gen_lock);
 	unsigned long s_dir_count;
 	u8 *s_debts;
 	struct percpu_counter s_freeblocks_counter;
fs/ext2/super.c +2 -2
index 3999f8f3b156..b8bb1f88a620 100644
@@ -1125,8 +1125,8 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc)
 		goto failed_mount2;
 	}
 	sbi->s_gdb_count = db_count;
-	sbi->s_next_generation = get_random_u32();
-	spin_lock_init(&sbi->s_next_gen_lock);
+	scoped_guard(spinlock_init, &sbi->s_next_gen_lock)
+		sbi->s_next_generation = get_random_u32();
 
 	/* per filesystem reservation list head & lock */
 	spin_lock_init(&sbi->s_rsv_window_lock);
Signature · 1 line
2.43.0
#2 lore raw

PATCH v2 2/8 ext2: annotate ext2_update_dynamic_rev() as requiring s_lock

In reply to Timothy Day

ext2_update_dynamic_rev() modifies several fields within 'struct ext2_super_block' and is already documented as requiring s_lock. Express that with __must_hold() for Clang's context analysis.

Both callers ext2_xattr_update_super_block() and __ext2_write_inode() already hold s_lock.

---
 fs/ext2/ext2.h  | 3 ++-
 fs/ext2/super.c | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)
fs/ext2/ext2.h +2 -1
index e851d2a6be66..c8bf2e69675d 100644
@@ -760,7 +760,8 @@ extern __printf(3, 4)
 void ext2_error(struct super_block *, const char *, const char *, ...);
 extern __printf(3, 4)
 void ext2_msg(struct super_block *, const char *, const char *, ...);
-extern void ext2_update_dynamic_rev (struct super_block *sb);
+extern void ext2_update_dynamic_rev(struct super_block *sb)
+	__must_hold(&EXT2_SB(sb)->s_lock);
 extern void ext2_sync_super(struct super_block *sb, struct ext2_super_block *es,
 			    int wait);
 
fs/ext2/super.c +1 -0
index b8bb1f88a620..5bf63943828b 100644
@@ -127,6 +127,7 @@ void ext2_msg(struct super_block *sb, const char *prefix,
  * This must be called with sbi->s_lock held.
  */
 void ext2_update_dynamic_rev(struct super_block *sb)
+	__must_hold(&EXT2_SB(sb)->s_lock)
 {
 	struct ext2_super_block *es = EXT2_SB(sb)->s_es;
 
Signature · 1 line
2.43.0
#3 lore raw

PATCH v2 3/8 ext2: mark statfs overhead cache as guarded by s_lock

In reply to Timothy Day

s_overhead_last and s_blocks_last cache the filesystem overhead computation and are only ever accessed in ext2_statfs() under s_lock. Annotate them with __guarded_by() for Clang's context analysis.

---
 fs/ext2/ext2.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/ext2/ext2.h +2 -2
index c8bf2e69675d..ecca898653eb 100644
@@ -77,8 +77,8 @@ struct ext2_sb_info {
 	unsigned long s_gdb_count;	/* Number of group descriptor blocks */
 	unsigned long s_desc_per_block;	/* Number of group descriptors per block */
 	unsigned long s_groups_count;	/* Number of groups in the fs */
-	unsigned long s_overhead_last;  /* Last calculated overhead */
-	unsigned long s_blocks_last;    /* Last seen block count */
+	unsigned long s_overhead_last __guarded_by(&s_lock);  /* Last calculated overhead */
+	unsigned long s_blocks_last __guarded_by(&s_lock);    /* Last seen block count */
 	struct buffer_head * s_sbh;	/* Buffer containing the super block */
 	struct ext2_super_block * s_es;	/* Pointer to the super block in the buffer */
 	struct buffer_head ** s_group_desc;
Signature · 1 line
2.43.0
#4 lore raw

PATCH v2 4/8 ext2: mark s_mount_state as guarded by s_lock

In reply to Timothy Day

s_mount_state tracks the filesystem's mount/error state and is protected by s_lock everywhere it is accessed. Annotate it with __guarded_by() for Clang's context analysis.

ext2_setup_super() reads s_mount_state and is called with s_lock held from the remount path, so annotate it with __must_hold().

There are two accesses (the initial read in ext2_fill_super() and the setup_super() call) in the mount flow. This is before the superblock is live, so no concurrent access should be possible. Convert the spinlock initialization to use guard(spinlock_init) to prevent warnings on the writes later in initialization.

---
 fs/ext2/ext2.h  | 2 +-
 fs/ext2/super.c | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)
fs/ext2/ext2.h +1 -1
index ecca898653eb..4c30d6f5c972 100644
@@ -86,7 +86,7 @@ struct ext2_sb_info {
 	unsigned long s_sb_block;
 	kuid_t s_resuid;
 	kgid_t s_resgid;
-	unsigned short s_mount_state;
+	unsigned short s_mount_state __guarded_by(&s_lock);
 	unsigned short s_pad;
 	int s_addr_per_block_bits;
 	int s_desc_per_block_bits;
fs/ext2/super.c +2 -1
index 5bf63943828b..0fa7914e5f04 100644
@@ -633,6 +633,7 @@ static int ext2_parse_param(struct fs_context *fc, struct fs_parameter *param)
 static int ext2_setup_super (struct super_block * sb,
 			      struct ext2_super_block * es,
 			      int read_only)
+	__must_hold(&EXT2_SB(sb)->s_lock)
 {
 	int res = 0;
 	struct ext2_sb_info *sbi = EXT2_SB(sb);
@@ -894,7 +895,7 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc)
 	sb->s_fs_info = sbi;
 	sbi->s_sb_block = sb_block;
 
-	spin_lock_init(&sbi->s_lock);
+	guard(spinlock_init)(&EXT2_SB(sb)->s_lock);
 	ret = -EINVAL;
 
 	/*
Signature · 1 line
2.43.0
#5 lore raw

PATCH v2 5/8 ext2: annotate ext2_init_block_alloc_info() as requiring truncate_mutex

In reply to Timothy Day

ext2_init_block_alloc_info() sets up the inode's block reservation info and is already documented as needing truncate_mutex protection. Express that with __must_hold() for Clang's context analysis.

Both callers (ext2_get_blocks() and ext2_ioctl()) already hold truncate_mutex.

---
 fs/ext2/balloc.c | 1 +
 fs/ext2/ext2.h   | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)
fs/ext2/balloc.c +1 -0
index adf0f31fbddd..53c91cb38bde 100644
@@ -414,6 +414,7 @@ static inline int rsv_is_empty(struct ext2_reserve_window *rsv)
  * Needs truncate_mutex protection prior to calling this function.
  */
 void ext2_init_block_alloc_info(struct inode *inode)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	struct ext2_inode_info *ei = EXT2_I(inode);
 	struct ext2_block_alloc_info *block_i;
fs/ext2/ext2.h +2 -1
index 4c30d6f5c972..b6ae29b2e6ec 100644
@@ -710,7 +710,8 @@ extern struct ext2_group_desc * ext2_get_group_desc(struct super_block * sb,
 						    struct buffer_head ** bh);
 extern void ext2_discard_reservation (struct inode *);
 extern int ext2_should_retry_alloc(struct super_block *sb, int *retries);
-extern void ext2_init_block_alloc_info(struct inode *);
+extern void ext2_init_block_alloc_info(struct inode *inode)
+	__must_hold(&EXT2_I(inode)->truncate_mutex);
 extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv);
 
 /* dir.c */
Signature · 1 line
2.43.0
#6 lore raw

PATCH v2 6/8 ext2: annotate block-mapping helpers as requiring truncate_mutex

In reply to Timothy Day

The indirect-block mapping helpers in inode.c all run under the inode's truncate_mutex.

ext2_find_goal(), ext2_alloc_blocks(), ext2_alloc_branch() and ext2_splice_branch() are reached only from ext2_get_blocks() while it holds the mutex. ext2_find_shared(), ext2_free_data() and ext2_free_branches() are called from __ext2_truncate_blocks() while it holds the mutex.

Add __must_hold() annotations for the Clang's context analysis to reflect this.

---
 fs/ext2/inode.c | 7 +++++++
 1 file changed, 7 insertions(+)
fs/ext2/inode.c +7 -0
index 29808629cce5..f0be5236d04e 100644
@@ -329,6 +329,7 @@ static ext2_fsblk_t ext2_find_near(struct inode *inode, Indirect *ind)
 
 static inline ext2_fsblk_t ext2_find_goal(struct inode *inode, long block,
 					  Indirect *partial)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	struct ext2_block_alloc_info *block_i;
 
@@ -399,6 +400,7 @@ ext2_blks_to_allocate(Indirect * branch, int k, unsigned long blks,
 static int ext2_alloc_blocks(struct inode *inode,
 			ext2_fsblk_t goal, int indirect_blks, int blks,
 			ext2_fsblk_t new_blocks[4], int *err)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	int target, i;
 	unsigned long count = 0;
@@ -479,6 +481,7 @@ static int ext2_alloc_blocks(struct inode *inode,
 static int ext2_alloc_branch(struct inode *inode,
 			int indirect_blks, int *blks, ext2_fsblk_t goal,
 			int *offsets, Indirect *branch)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	int blocksize = inode->i_sb->s_blocksize;
 	int i, n = 0;
@@ -560,6 +563,7 @@ static int ext2_alloc_branch(struct inode *inode,
  */
 static void ext2_splice_branch(struct inode *inode,
 			long block, Indirect *where, int num, int blks)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	int i;
 	struct ext2_block_alloc_info *block_i;
@@ -1002,6 +1006,7 @@ static Indirect *ext2_find_shared(struct inode *inode,
 				int offsets[4],
 				Indirect chain[4],
 				__le32 *top)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	Indirect *partial, *p;
 	int k, err;
@@ -1057,6 +1062,7 @@ static Indirect *ext2_find_shared(struct inode *inode,
  *	appropriately.
  */
 static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	ext2_fsblk_t block_to_free = 0, count = 0;
 	ext2_fsblk_t nr;
@@ -1097,6 +1103,7 @@ static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q)
  *	appropriately.
  */
 static void ext2_free_branches(struct inode *inode, __le32 *p, __le32 *q, int depth)
+	__must_hold(&EXT2_I(inode)->truncate_mutex)
 {
 	struct buffer_head * bh;
 	ext2_fsblk_t nr;
Signature · 1 line
2.43.0
#7 lore raw

PATCH v2 7/8 ext2: annotate s_rsv_window_root as requiring s_rsv_window_lock

In reply to Timothy Day

The per-filesystem reservation window rb-tree (s_rsv_window_root) is protected by s_rsv_window_lock. Mark the s_rsv_window_root field with __guarded_by() for Clang's context analysis.

The helpers (ext2_rsv_window_add, rsv_window_remove, and find_next_reservable_window) that mutate or walk the tree are all called with the s_rsv_window_lock held. Annotate these helpers with __must_hold().

The accesses in ext2_fill_super() are before the superblock is live. Since no concurrent access should be possible, s_rsv_window_lock is not taken. Convert the spinlock initialization to use scoped_guard(spinlock_init, ...) and place the writes under the guard to prevent warnings.

---
 fs/ext2/balloc.c |  3 +++
 fs/ext2/ext2.h   |  5 +++--
 fs/ext2/super.c  | 27 ++++++++++++++-------------
 3 files changed, 20 insertions(+), 15 deletions(-)
fs/ext2/balloc.c +3 -0
index 53c91cb38bde..80acc1e19387 100644
@@ -334,6 +334,7 @@ search_reserve_window(struct rb_root *root, ext2_fsblk_t goal)
  */
 void ext2_rsv_window_add(struct super_block *sb,
 		    struct ext2_reserve_window_node *rsv)
+	__must_hold(&EXT2_SB(sb)->s_rsv_window_lock)
 {
 	struct rb_root *root = &EXT2_SB(sb)->s_rsv_window_root;
 	struct rb_node *node = &rsv->rsv_node;
@@ -373,6 +374,7 @@ void ext2_rsv_window_add(struct super_block *sb,
  */
 static void rsv_window_remove(struct super_block *sb,
 			      struct ext2_reserve_window_node *rsv)
+	__must_hold(&EXT2_SB(sb)->s_rsv_window_lock)
 {
 	rsv->rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
 	rsv->rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
@@ -759,6 +761,7 @@ static int find_next_reservable_window(
 				struct super_block * sb,
 				ext2_fsblk_t start_block,
 				ext2_fsblk_t last_block)
+	__must_hold(&EXT2_SB(sb)->s_rsv_window_lock)
 {
 	struct rb_node *next;
 	struct ext2_reserve_window_node *rsv, *prev;
fs/ext2/ext2.h +3 -2
index b6ae29b2e6ec..404f4223a6bd 100644
@@ -102,7 +102,7 @@ struct ext2_sb_info {
 	struct blockgroup_lock *s_blockgroup_lock;
 	/* root of the per fs reservation window tree */
 	spinlock_t s_rsv_window_lock;
-	struct rb_root s_rsv_window_root;
+	struct rb_root s_rsv_window_root __guarded_by(&s_rsv_window_lock);
 	struct ext2_reserve_window_node s_rsv_window_head;
 	/*
 	 * s_lock protects against concurrent modifications of s_mount_state,
@@ -712,7 +712,8 @@ extern void ext2_discard_reservation (struct inode *);
 extern int ext2_should_retry_alloc(struct super_block *sb, int *retries);
 extern void ext2_init_block_alloc_info(struct inode *inode)
 	__must_hold(&EXT2_I(inode)->truncate_mutex);
-extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv);
+extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv)
+	__must_hold(&EXT2_SB(sb)->s_rsv_window_lock);
 
 /* dir.c */
 int ext2_add_link(struct dentry *, struct inode *);
fs/ext2/super.c +14 -13
index 0fa7914e5f04..b7f042e42009 100644
@@ -1131,19 +1131,20 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc)
 		sbi->s_next_generation = get_random_u32();
 
 	/* per filesystem reservation list head & lock */
-	spin_lock_init(&sbi->s_rsv_window_lock);
-	sbi->s_rsv_window_root = RB_ROOT;
-	/*
-	 * Add a single, static dummy reservation to the start of the
-	 * reservation window list --- it gives us a placeholder for
-	 * append-at-start-of-list which makes the allocation logic
-	 * _much_ simpler.
-	 */
-	sbi->s_rsv_window_head.rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
-	sbi->s_rsv_window_head.rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
-	sbi->s_rsv_window_head.rsv_alloc_hit = 0;
-	sbi->s_rsv_window_head.rsv_goal_size = 0;
-	ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
+	scoped_guard(spinlock_init, &EXT2_SB(sb)->s_rsv_window_lock) {
+		sbi->s_rsv_window_root = RB_ROOT;
+		/*
+		 * Add a single, static dummy reservation to the start of the
+		 * reservation window list --- it gives us a placeholder for
+		 * append-at-start-of-list which makes the allocation logic
+		 * _much_ simpler.
+		 */
+		sbi->s_rsv_window_head.rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
+		sbi->s_rsv_window_head.rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED;
+		sbi->s_rsv_window_head.rsv_alloc_hit = 0;
+		sbi->s_rsv_window_head.rsv_goal_size = 0;
+		ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
+	}
 
 	err = percpu_counter_init(&sbi->s_freeblocks_counter,
 				ext2_count_free_blocks(sb), GFP_KERNEL);
Signature · 1 line
2.43.0
#8 lore raw

PATCH v2 8/8 ext2: enable context analysis support for ext2 filesystem

In reply to Timothy Day

Update ext2 Makefile to support context analysis [1].

[1] https://docs.kernel.org/dev-tools/context-analysis.html

---
 fs/ext2/Makefile | 2 ++
 1 file changed, 2 insertions(+)
fs/ext2/Makefile +2 -0
index 8860948ef9ca..33db2e9dc908 100644
@@ -3,6 +3,8 @@
 # Makefile for the linux ext2-filesystem routines.
 #
 
+CONTEXT_ANALYSIS := y
+
 obj-$(CONFIG_EXT2_FS) += ext2.o
 
 ext2-y := balloc.o dir.o file.o ialloc.o inode.o \
Signature · 1 line
2.43.0
Acked-by on the v2 series
#9 lore raw
In reply to Timothy Day
Quoted 22 lines · Timothy Day

This description is mostly copied from v1:

This series adds annotations for Clang's context analysis to ext2. Clang context analysis was recently added in a series by Marco Elver [1]. This allows the compiler to validate different locking patterns at compile time.

This series enables context analysis, fixes pre-existing warnings, and adds new annotations. It is inspired by similar series in the block layer (NVMe host driver, for example [2]).

I'm starting with ext2 since it's smaller and simpler compared to ext4/btrfs/etc. After ext2, I'd be interested in converting the other filesystems and infrastructure code in fs/. I think the ultimate goal would be to enable this by default across all of fs/.

The series was built and tested with Clang 23 with CONFIG_WARN_CONTEXT_ANALYSIS enabled. I based on 7.2-rc7.

Thanks!

Quoted 7 lines

Changes from v1:

* A false positive has been fixed in Clang [3] and ported to Clang 23. Hence, the first patch (silencing that false positive) has been dropped.
* Use guard() and scoped_guard() instead of context_unsafe(), when possible, to express that certain fields are being protected by a newly initialized lock during init.

But ultimately up to maintainers. Also, thanks for helping improve the Clang side (FWIW, Clang 23 will release August 23)!

Quoted 27 lines

Link to v1: https://lore.kernel.org/linux-fsdevel/[email protected]/

[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://github.com/llvm/llvm-project/pull/209796

Timothy Day (8):
ext2: mark s_next_generation as guarded by s_next_gen_lock
ext2: annotate ext2_update_dynamic_rev() as requiring s_lock
ext2: mark statfs overhead cache as guarded by s_lock
ext2: mark s_mount_state as guarded by s_lock
ext2: annotate ext2_init_block_alloc_info() as requiring
truncate_mutex
ext2: annotate block-mapping helpers as requiring truncate_mutex
ext2: annotate s_rsv_window_root as requiring s_rsv_window_lock
ext2: enable context analysis support for ext2 filesystem

 fs/ext2/Makefile |  2 ++
 fs/ext2/balloc.c |  4 ++++
 fs/ext2/ext2.h   | 19 +++++++++++--------
 fs/ext2/inode.c  |  7 +++++++
 fs/ext2/super.c  | 35 +++++++++++++++++++----------------
 5 files changed, 43 insertions(+), 24 deletions(-)

--
2.43.0

reviewer
#10 lore raw
In reply to Timothy Day

Hello!

Quoted 11 lines · Timothy Day

This description is mostly copied from v1:

This series adds annotations for Clang's context analysis to ext2. Clang context analysis was recently added in a series by Marco Elver [1]. This allows the compiler to validate different locking patterns at compile time.

This series enables context analysis, fixes pre-existing warnings, and adds new annotations. It is inspired by similar series in the block layer (NVMe host driver, for example [2]).

Quoted 7 lines

I'm starting with ext2 since it's smaller and simpler compared to ext4/btrfs/etc. After ext2, I'd be interested in converting the other filesystems and infrastructure code in fs/. I think the ultimate goal would be to enable this by default across all of fs/.

The series was built and tested with Clang 23 with CONFIG_WARN_CONTEXT_ANALYSIS enabled. I based on 7.2-rc7.

Thanks for the patches! They look good to me. Once the merge window is over I'll queue them to my tree. The only thing I'm not fully sure is how much I like the spinlock_init scoped guards - they looked quite confusing to me at the first sight (as much as I understand the convenience, conceptually how can initialization of a global lock be scoped?). I'll sleep over it, maybe I'll change them to just spinlock_init() + scoped_guard for the lock itself or maybe I'll get used to them. Anyway, no action on your side needed :).

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
#11 lore raw
Quoted 20 lines · Jan Kara
Quoted 11 lines · Timothy Day

This description is mostly copied from v1:

This series adds annotations for Clang's context analysis to ext2. Clang context analysis was recently added in a series by Marco Elver [1]. This allows the compiler to validate different locking patterns at compile time.

This series enables context analysis, fixes pre-existing warnings, and adds new annotations. It is inspired by similar series in the block layer (NVMe host driver, for example [2]).

Quoted 7 lines

I'm starting with ext2 since it's smaller and simpler compared to ext4/btrfs/etc. After ext2, I'd be interested in converting the other filesystems and infrastructure code in fs/. I think the ultimate goal would be to enable this by default across all of fs/.

The series was built and tested with Clang 23 with CONFIG_WARN_CONTEXT_ANALYSIS enabled. I based on 7.2-rc7.

Quoted 7 lines

Thanks for the patches! They look good to me. Once the merge window is over I'll queue them to my tree. The only thing I'm not fully sure is how much I like the spinlock_init scoped guards - they looked quite confusing to me at the first sight (as much as I understand the convenience, conceptually how can initialization of a global lock be scoped?). I'll sleep over it, maybe I'll change them to just spinlock_init() + scoped_guard for the lock itself or maybe I'll get used to them. Anyway, no action on your side needed :).

This series is now in -next, where I see the following warnings (or errors with CONFIG_WERROR=y / W=e) with various configurations, such as ARCH=arm allmodconfig, when building with LLVM 23.1.0

fs/ext2/xattr.c:825:6: error: rw_semaphore 'EXT2_I().xattr_sem' is not held on every path through here [-Werror,-Wthread-safety-analysis]

    825 |         if (WARN_ON_ONCE(!down_write_trylock(&EXT2_I(inode)->xattr_sem)))
        |             ^
  include/asm-generic/bug.h:180:2: note: expanded from macro 'WARN_ON_ONCE'
    180 |         DO_ONCE_LITE_IF(condition, WARN_ON, 1)
        |         ^
  include/linux/once_lite.h:30:7: note: expanded from macro 'DO_ONCE_LITE_IF'
     30 |                 if (__ONCE_LITE_IF(__ret_do_once))                      \
        |                     ^
  include/linux/once_lite.h:23:3: note: expanded from macro '__ONCE_LITE_IF'
     23 |                 unlikely(__ret_once);                                   \
        |                 ^
  include/linux/compiler.h:77:22: note: expanded from macro 'unlikely'
     77 | # define unlikely(x)    __builtin_expect(!!(x), 0)
        |                         ^
  fs/ext2/xattr.c:825:20: note: rw_semaphore acquired here
    825 |         if (WARN_ON_ONCE(!down_write_trylock(&EXT2_I(inode)->xattr_sem)))
        |                           ^

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

Signature · 2 lines
Cheers,
Nathan
#12 lore raw
Quoted 43 lines · Nathan Chancellor
Quoted 20 lines · Jan Kara
Quoted 11 lines · Timothy Day

This description is mostly copied from v1:

This series adds annotations for Clang's context analysis to ext2. Clang context analysis was recently added in a series by Marco Elver [1]. This allows the compiler to validate different locking patterns at compile time.

This series enables context analysis, fixes pre-existing warnings, and adds new annotations. It is inspired by similar series in the block layer (NVMe host driver, for example [2]).

Quoted 7 lines

I'm starting with ext2 since it's smaller and simpler compared to ext4/btrfs/etc. After ext2, I'd be interested in converting the other filesystems and infrastructure code in fs/. I think the ultimate goal would be to enable this by default across all of fs/.

The series was built and tested with Clang 23 with CONFIG_WARN_CONTEXT_ANALYSIS enabled. I based on 7.2-rc7.

Quoted 7 lines

Thanks for the patches! They look good to me. Once the merge window is over I'll queue them to my tree. The only thing I'm not fully sure is how much I like the spinlock_init scoped guards - they looked quite confusing to me at the first sight (as much as I understand the convenience, conceptually how can initialization of a global lock be scoped?). I'll sleep over it, maybe I'll change them to just spinlock_init() + scoped_guard for the lock itself or maybe I'll get used to them. Anyway, no action on your side needed :).

This series is now in -next, where I see the following warnings (or errors with CONFIG_WERROR=y / W=e) with various configurations, such as ARCH=arm allmodconfig, when building with LLVM 23.1.0

fs/ext2/xattr.c:825:6: error: rw_semaphore 'EXT2_I().xattr_sem' is not held on every path through here [-Werror,-Wthread-safety-analysis]

    825 |         if (WARN_ON_ONCE(!down_write_trylock(&EXT2_I(inode)->xattr_sem)))
        |             ^
  include/asm-generic/bug.h:180:2: note: expanded from macro 'WARN_ON_ONCE'
    180 |         DO_ONCE_LITE_IF(condition, WARN_ON, 1)
        |         ^
  include/linux/once_lite.h:30:7: note: expanded from macro 'DO_ONCE_LITE_IF'
     30 |                 if (__ONCE_LITE_IF(__ret_do_once))                      \
        |                     ^

include/linux/once_lite.h:23:3: note: expanded from macro '__ONCE_LITE_IF'

Quoted 8 lines
     23 |                 unlikely(__ret_once);                                   \
        |                 ^
  include/linux/compiler.h:77:22: note: expanded from macro 'unlikely'
     77 | # define unlikely(x)    __builtin_expect(!!(x), 0)
        |                         ^
  fs/ext2/xattr.c:825:20: note: rw_semaphore acquired here
    825 |         if (WARN_ON_ONCE(!down_write_trylock(&EXT2_I(inode)->xattr_sem)))
        |                           ^

This one can be fixed with (should also be a bit more efficient):
https://lore.kernel.org/all/[email protected]/

Quoted 4 lines

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

sbi was just allocated, and EXT2_SB(sb) and sbi are pointing to the same object (unless I misread the code), but the compiler can't tell since aliases can't be tracked through non-local objects. So that scoped_guard could just become:

  scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) {
  ...

But I'll leave that to Jan and Tim.

Thanks,
-- Marco

reviewer
#13 lore raw
Quoted 11 lines · Marco Elver
Quoted 4 lines · Nathan Chancellor

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

sbi was just allocated, and EXT2_SB(sb) and sbi are pointing to the same object (unless I misread the code), but the compiler can't tell since aliases can't be tracked through non-local objects. So that scoped_guard could just become:

Quoted 4 lines
  scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) {
  ...

But I'll leave that to Jan and Tim.

Yes, at the beginning of ext4_fill_super() we do:

	sbi = kzalloc_obj(*sbi);
	...
	sb->s_fs_info = sbi;

and EXT2_SB(sb) is just sb->s_fs_info. Are you saying that clang is not able to infer that sb->s_fs_info and sbi are still pointing to the same memory later in the function where we do scoped_guard()?

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
#14 lore raw
Quoted 20 lines · Jan Kara
Quoted 11 lines · Marco Elver
Quoted 4 lines · Nathan Chancellor

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

sbi was just allocated, and EXT2_SB(sb) and sbi are pointing to the same object (unless I misread the code), but the compiler can't tell since aliases can't be tracked through non-local objects. So that scoped_guard could just become:

Quoted 4 lines
  scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) {
  ...

But I'll leave that to Jan and Tim.

Yes, at the beginning of ext4_fill_super() we do:

Quoted 7 lines
        sbi = kzalloc_obj(*sbi);
        ...
        sb->s_fs_info = sbi;

and EXT2_SB(sb) is just sb->s_fs_info. Are you saying that clang is not able to infer that sb->s_fs_info and sbi are still pointing to the same memory later in the function where we do scoped_guard()?

Yes - alias tracking is only done through local aliases. sb is non-local, along with additional member indirection; unfortunately, the C language doesn't give us the guarantees that it wasn't modified somewhere in between, say after a function call (this rule is applied also for local aliases if clang sees that they "escape" their local scope via non-const pointer to pointer).

reviewer
#15 lore raw
Quoted 29 lines · Marco Elver
Quoted 20 lines · Jan Kara
Quoted 11 lines · Marco Elver
Quoted 4 lines · Nathan Chancellor

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

sbi was just allocated, and EXT2_SB(sb) and sbi are pointing to the same object (unless I misread the code), but the compiler can't tell since aliases can't be tracked through non-local objects. So that scoped_guard could just become:

Quoted 4 lines
  scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) {
  ...

But I'll leave that to Jan and Tim.

Yes, at the beginning of ext4_fill_super() we do:

Quoted 7 lines
        sbi = kzalloc_obj(*sbi);
        ...
        sb->s_fs_info = sbi;

and EXT2_SB(sb) is just sb->s_fs_info. Are you saying that clang is not able to infer that sb->s_fs_info and sbi are still pointing to the same memory later in the function where we do scoped_guard()?

Quoted 6 lines

Yes - alias tracking is only done through local aliases. sb is non-local, along with additional member indirection; unfortunately, the C language doesn't give us the guarantees that it wasn't modified somewhere in between, say after a function call (this rule is applied also for local aliases if clang sees that they "escape" their local scope via non-const pointer to pointer).

Hrm, ok, understood (but still it's annoying ;)). I've pushed out the patch with the suggested fixup.

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
#16 lore raw
Quoted 37 lines · Jan Kara
Quoted 29 lines · Marco Elver
Quoted 20 lines · Jan Kara
Quoted 11 lines · Marco Elver
Quoted 4 lines · Nathan Chancellor

fs/ext2/super.c:1149:3: error: calling function 'ext2_rsv_window_add' requires holding spinlock 'EXT2_SB(sb).s_rsv_window_lock' exclusively [-Werror,-Wthread-safety-precise]

   1149 |                 ext2_rsv_window_add(sb, &sbi->s_rsv_window_head);
        |                 ^

fs/ext2/super.c:1149:3: note: found near match '_res->s_rsv_window_lock'

sbi was just allocated, and EXT2_SB(sb) and sbi are pointing to the same object (unless I misread the code), but the compiler can't tell since aliases can't be tracked through non-local objects. So that scoped_guard could just become:

Quoted 4 lines
  scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) {
  ...

But I'll leave that to Jan and Tim.

Yes, at the beginning of ext4_fill_super() we do:

Quoted 7 lines
        sbi = kzalloc_obj(*sbi);
        ...
        sb->s_fs_info = sbi;

and EXT2_SB(sb) is just sb->s_fs_info. Are you saying that clang is not able to infer that sb->s_fs_info and sbi are still pointing to the same memory later in the function where we do scoped_guard()?

Quoted 6 lines

Yes - alias tracking is only done through local aliases. sb is non-local, along with additional member indirection; unfortunately, the C language doesn't give us the guarantees that it wasn't modified somewhere in between, say after a function call (this rule is applied also for local aliases if clang sees that they "escape" their local scope via non-const pointer to pointer).

Quoted 2 lines

Hrm, ok, understood (but still it's annoying ;)). I've pushed out the patch with the suggested fixup.

Sorry for the late reply. I was out last month due to lung trouble (spontaneous pneumothorax corrected via surgery). Thanks Jan for pulling the series. I took a look at your branch [2] and everything seems fine. I agree the scoped guards can be peculiar. If you wanted to make some minor changes, I have no objection.

Thanks Marco for the compilation fix in the other thread [1]. It seems like a reasonable fix. If there is anything else needed from me, let me know.

Now that I'm back, I'm hoping to resume work on some more context analysis patches. I have a few that were close to done that I hope to share soon.

Tim Day

[1] https://lore.kernel.org/all/[email protected]/
[2] https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git/log/?h=for_next