Linux SPARSE checker discussions
 help / color / mirror / Atom feed
From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
	Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext
Date: Mon,  9 Nov 2026 12:32:54 -0800	[thread overview]
Message-ID: <20261109203259.362845-69-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Implement evaluation and linearization for __attribute__((cleanup(...)))
so that scope-based resource management in the Linux kernel (such as
guard(mutex)(&lock), scoped_guard, and __free(...) in
include/linux/cleanup.h) is modeled accurately by -Wcontext:
- Validate in parse.c and evaluate.c that the cleanup attribute names a
  function, applies only to local automatic variables, and type-checks
  calls to fn(&var).
- Maintain a lexical cleanup_scope tree in linearize.c across compound
  statements, declarations, iterators, and switches, and record cleanup
  variables as their declarations are linearized.
- Emit cleanup calls in reverse declaration order (LIFO) on block exit,
  loop/switch break and continue, goto, and return after evaluating the
  return expression.
- Copy expanded cleanup expressions correctly in inline.c: handle
  statement expressions that expand_compound() collapsed into a single
  non-compound statement and preserve the constraint flags of asm
  operands since cleanup expressions are not re-evaluated after copying.

Add validation tests in validation/cleanup-context.c,
validation/cleanup-errors.c, validation/cleanup-inline-loop.c,
validation/linear/cleanup-control-flow.c, and
validation/linear/cleanup-stmt-expr-asm.c.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                                |  38 ++
 expand.c                                  |   8 +-
 inline.c                                  | 218 +++++++++-
 linearize.c                               | 478 +++++++++++++++++++++-
 linearize.h                               |   4 +
 parse.c                                   |  22 +-
 parse.h                                   |   4 +
 symbol.h                                  |   1 +
 validation/cleanup-context.c              | 161 ++++++++
 validation/cleanup-errors.c               |  77 ++++
 validation/cleanup-inline-loop.c          |  31 ++
 validation/linear/cleanup-control-flow.c  |  62 +++
 validation/linear/cleanup-stmt-expr-asm.c |  48 +++
 13 files changed, 1129 insertions(+), 23 deletions(-)
 create mode 100644 validation/cleanup-context.c
 create mode 100644 validation/cleanup-errors.c
 create mode 100644 validation/cleanup-inline-loop.c
 create mode 100644 validation/linear/cleanup-control-flow.c
 create mode 100644 validation/linear/cleanup-stmt-expr-asm.c

diff --git a/evaluate.c b/evaluate.c
index 27bfe1c0fe88..6e1be32f929f 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -4236,6 +4236,41 @@ static bool has_invalid_constexpr_qualifiers(struct symbol *type)
 	return false;
 }
 
+static void evaluate_cleanup(struct symbol *sym)
+{
+	struct expression *var_expr, *addr_expr, *fn_expr, *call;
+
+	if (!sym->cleanup || sym->cleanup->type != EXPR_SYMBOL)
+		return;
+
+	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_STATIC | MOD_EXTERN)) {
+		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
+		sym->cleanup = NULL;
+		return;
+	}
+
+	var_expr = alloc_expression(sym->pos, EXPR_SYMBOL);
+	var_expr->symbol = sym;
+	var_expr->symbol_name = sym->ident;
+	evaluate_expression(var_expr);
+
+	addr_expr = alloc_expression(sym->pos, EXPR_PREOP);
+	addr_expr->op = '&';
+	addr_expr->unop = var_expr;
+	evaluate_expression(addr_expr);
+
+	fn_expr = alloc_expression(sym->cleanup->pos, EXPR_SYMBOL);
+	fn_expr->symbol = sym->cleanup->symbol;
+	fn_expr->symbol_name = sym->cleanup->symbol_name;
+
+	call = alloc_expression(sym->pos, EXPR_CALL);
+	call->fn = fn_expr;
+	add_expression(&call->args, addr_expr);
+	evaluate_call(call);
+
+	sym->cleanup = call;
+}
+
 static struct symbol *evaluate_symbol(struct symbol *sym)
 {
 	struct symbol *base_type;
@@ -4276,6 +4311,9 @@ static struct symbol *evaluate_symbol(struct symbol *sym)
 			sym->underspecified = 0;
 	}
 
+	if (sym->cleanup)
+		evaluate_cleanup(sym);
+
 	/* And finally, evaluate the body of the symbol too */
 	if (base_type->type == SYM_FN) {
 		struct symbol *curr = current_fn;
diff --git a/expand.c b/expand.c
index 696662ad11ea..5c3777ff3a23 100644
--- a/expand.c
+++ b/expand.c
@@ -1736,6 +1736,7 @@ int expand_symbol(struct symbol *sym)
 		return 0;
 
 	retval = expand_expression(sym->initializer);
+	expand_expression(sym->cleanup);
 	/* expand the body of the symbol */
 	if (base_type->type == SYM_FN) {
 		if (base_type->stmt)
@@ -1856,13 +1857,18 @@ static int expand_statement(struct statement *stmt)
 	case STMT_IF:
 		return expand_if_statement(stmt);
 
-	case STMT_ITERATOR:
+	case STMT_ITERATOR: {
+		struct symbol *sym;
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			expand_symbol(sym);
+		} END_FOR_EACH_PTR(sym);
 		expand_expression(stmt->iterator_pre_condition);
 		expand_expression(stmt->iterator_post_condition);
 		expand_statement(stmt->iterator_pre_statement);
 		expand_statement(stmt->iterator_statement);
 		expand_statement(stmt->iterator_post_statement);
 		return SIDE_EFFECTS;
+	}
 
 	case STMT_SWITCH:
 		expand_expression(stmt->switch_expression);
diff --git a/inline.c b/inline.c
index c857cd953702..4fa087098160 100644
--- a/inline.c
+++ b/inline.c
@@ -35,7 +35,9 @@
 #include "evaluate.h"
 
 static void copy_statement(struct statement *src, struct statement *dst);
+static struct statement *copy_one_statement(struct statement *stmt);
 static struct expression *copy_expression(struct expression *expr);
+static bool copy_allow_unreplaced;
 
 static struct expression * dup_expression(struct expression *expr)
 {
@@ -48,6 +50,7 @@ static struct statement * dup_statement(struct statement *stmt)
 {
 	struct statement *dup = alloc_statement(stmt->pos, stmt->type);
 	*dup = *stmt;
+	dup->cleanup_scope = NULL;
 	return dup;
 }
 
@@ -58,7 +61,8 @@ static struct symbol *copy_symbol(struct position pos, struct symbol *sym)
 	if (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN | MOD_TOPLEVEL | MOD_INLINE))
 		return sym;
 	if (!sym->replace) {
-		warning(pos, "unreplaced symbol '%s'", show_ident(sym->ident));
+		if (!copy_allow_unreplaced)
+			warning(pos, "unreplaced symbol '%s'", show_ident(sym->ident));
 		return sym;
 	}
 	return sym->replace;
@@ -77,7 +81,7 @@ static struct symbol *copy_type(struct symbol *type)
 		n = alloc_symbol(type->pos, SYM_NODE);
 		*n = *type;
 		n->ctype.base_type = base;
-		if (base && base->bit_size < 0)
+		if (!copy_allow_unreplaced && base && base->bit_size < 0)
 			n->examined = 0;
 		return n;
 	}
@@ -91,7 +95,8 @@ static struct symbol *copy_type(struct symbol *type)
 		*arr = *type;
 		arr->ctype.base_type = elem;
 		arr->array_size = size;
-		if (type->array_size || elem != type->ctype.base_type)
+		if (!copy_allow_unreplaced &&
+		    (type->array_size || elem != type->ctype.base_type))
 			arr->examined = 0;
 		return arr;
 	}
@@ -114,7 +119,8 @@ static struct symbol *copy_type(struct symbol *type)
 		t = alloc_symbol(type->pos, type->type);
 		*t = *type;
 		t->initializer = init;
-		t->examined = 0;
+		if (!copy_allow_unreplaced)
+			t->examined = 0;
 		return t;
 	}
 	default:
@@ -265,8 +271,20 @@ static struct expression * copy_expression(struct expression *expr)
 
 	/* Statement expression */
 	case EXPR_STATEMENT: {
-		struct statement *stmt = alloc_statement(expr->pos, STMT_COMPOUND);
-		copy_statement(expr->statement, stmt);
+		struct statement *stmt;
+
+		/*
+		 * expand_compound() collapses a compound statement that
+		 * contains a single statement into that statement. Hence,
+		 * after expansion, the statement of a statement expression
+		 * is not necessarily a compound statement.
+		 */
+		if (expr->statement->type == STMT_COMPOUND) {
+			stmt = alloc_statement(expr->pos, STMT_COMPOUND);
+			copy_statement(expr->statement, stmt);
+		} else {
+			stmt = copy_one_statement(expr->statement);
+		}
 		expr = dup_expression(expr);
 		expr->statement = stmt;
 		break;
@@ -382,7 +400,11 @@ static struct asm_operand_list *copy_asm_operands(struct asm_operand_list *in)
 
 	FOR_EACH_PTR(in, old) {
 		struct asm_operand *new = __alloc_asm_operand(0);
-		new->name = old->name;
+		/*
+		 * Copy the constraint flags too since cleanup expressions
+		 * are copied after evaluation and are not re-evaluated.
+		 */
+		*new = *old;
 		new->constraint = copy_expression(old->constraint);
 		new->expr = copy_expression(old->expr);
 		add_ptr_list(&out, new);
@@ -431,6 +453,7 @@ static struct statement *copy_one_statement(struct statement *stmt)
 			if (newsym != sym) {
 				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
 				newsym->initializer = copy_expression(sym->initializer);
+				newsym->cleanup = copy_expression(sym->cleanup);
 			}
 			add_symbol(&newstmt->declaration, newsym);
 		} END_FOR_EACH_PTR(sym);
@@ -522,6 +545,7 @@ static struct statement *copy_one_statement(struct statement *stmt)
 			if (newsym != sym) {
 				newsym->ctype.base_type = copy_type(newsym->ctype.base_type);
 				newsym->initializer = copy_expression(sym->initializer);
+				newsym->cleanup = copy_expression(sym->cleanup);
 			}
 			add_symbol(&stmt->iterator_syms, newsym);
 		} END_FOR_EACH_PTR(sym);
@@ -591,6 +615,7 @@ static struct symbol *create_copy_symbol(struct symbol *orig)
 		*sym = *orig;
 		sym->bb_target = NULL;
 		sym->pseudo = NULL;
+		sym->cleanup_scope = NULL;
 		set_replace(orig, sym);
 		orig = sym;
 	}
@@ -609,6 +634,184 @@ static struct symbol_list *create_symbol_list(struct symbol_list *src)
 	return dst;
 }
 
+static void collect_stmt_local_symbols(struct statement *stmt, struct symbol_list **list);
+
+static void register_local_sym(struct symbol *sym, struct symbol_list **list)
+{
+	if (!sym || sym->replace)
+		return;
+	if (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN | MOD_TOPLEVEL | MOD_INLINE))
+		return;
+	add_symbol(list, create_copy_symbol(sym));
+}
+
+static void collect_expr_local_symbols(struct expression *expr, struct symbol_list **list)
+{
+	if (!expr)
+		return;
+	switch (expr->type) {
+	case EXPR_STATEMENT:
+		collect_stmt_local_symbols(expr->statement, list);
+		break;
+	case EXPR_PREOP:
+	case EXPR_POSTOP:
+		collect_expr_local_symbols(expr->unop, list);
+		break;
+	case EXPR_SLICE:
+		collect_expr_local_symbols(expr->base, list);
+		break;
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_COMPARE:
+	case EXPR_LOGICAL:
+	case EXPR_ASSIGNMENT:
+		collect_expr_local_symbols(expr->left, list);
+		collect_expr_local_symbols(expr->right, list);
+		break;
+	case EXPR_DEREF:
+		collect_expr_local_symbols(expr->deref, list);
+		break;
+	case EXPR_CAST:
+	case EXPR_FORCE_CAST:
+	case EXPR_IMPLIED_CAST:
+	case EXPR_SIZEOF:
+	case EXPR_PTRSIZEOF:
+	case EXPR_ALIGNOF:
+		collect_expr_local_symbols(expr->cast_expression, list);
+		break;
+	case EXPR_SELECT:
+	case EXPR_CONDITIONAL:
+		collect_expr_local_symbols(expr->conditional, list);
+		collect_expr_local_symbols(expr->cond_true, list);
+		collect_expr_local_symbols(expr->cond_false, list);
+		break;
+	case EXPR_CALL: {
+		struct expression *arg;
+		collect_expr_local_symbols(expr->fn, list);
+		FOR_EACH_PTR(expr->args, arg) {
+			collect_expr_local_symbols(arg, list);
+		} END_FOR_EACH_PTR(arg);
+		break;
+	}
+	case EXPR_INITIALIZER: {
+		struct expression *entry;
+		FOR_EACH_PTR(expr->expr_list, entry) {
+			collect_expr_local_symbols(entry, list);
+		} END_FOR_EACH_PTR(entry);
+		break;
+	}
+	case EXPR_INDEX:
+		collect_expr_local_symbols(expr->idx_expression, list);
+		break;
+	case EXPR_IDENTIFIER:
+		collect_expr_local_symbols(expr->ident_expression, list);
+		break;
+	case EXPR_POS:
+		collect_expr_local_symbols(expr->init_expr, list);
+		break;
+	default:
+		break;
+	}
+}
+
+static void collect_stmt_local_symbols(struct statement *stmt, struct symbol_list **list)
+{
+	struct symbol *sym;
+
+	if (!stmt)
+		return;
+	switch (stmt->type) {
+	case STMT_COMPOUND: {
+		struct statement *s;
+		register_local_sym(stmt->ret, list);
+		collect_stmt_local_symbols(stmt->args, list);
+		FOR_EACH_PTR(stmt->stmts, s) {
+			collect_stmt_local_symbols(s, list);
+		} END_FOR_EACH_PTR(s);
+		break;
+	}
+	case STMT_DECLARATION:
+		FOR_EACH_PTR(stmt->declaration, sym) {
+			register_local_sym(sym, list);
+			collect_expr_local_symbols(sym->initializer, list);
+			collect_expr_local_symbols(sym->cleanup, list);
+		} END_FOR_EACH_PTR(sym);
+		break;
+	case STMT_ITERATOR:
+		register_local_sym(stmt->iterator_break, list);
+		register_local_sym(stmt->iterator_continue, list);
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			register_local_sym(sym, list);
+			collect_expr_local_symbols(sym->initializer, list);
+			collect_expr_local_symbols(sym->cleanup, list);
+		} END_FOR_EACH_PTR(sym);
+		collect_stmt_local_symbols(stmt->iterator_pre_statement, list);
+		collect_expr_local_symbols(stmt->iterator_pre_condition, list);
+		collect_stmt_local_symbols(stmt->iterator_statement, list);
+		collect_stmt_local_symbols(stmt->iterator_post_statement, list);
+		collect_expr_local_symbols(stmt->iterator_post_condition, list);
+		break;
+	case STMT_SWITCH:
+		register_local_sym(stmt->switch_break, list);
+		register_local_sym(stmt->switch_case, list);
+		collect_expr_local_symbols(stmt->switch_expression, list);
+		collect_stmt_local_symbols(stmt->switch_statement, list);
+		break;
+	case STMT_CASE:
+		register_local_sym(stmt->case_label, list);
+		collect_stmt_local_symbols(stmt->case_statement, list);
+		break;
+	case STMT_LABEL:
+		register_local_sym(stmt->label_identifier, list);
+		collect_stmt_local_symbols(stmt->label_statement, list);
+		break;
+	case STMT_IF:
+		collect_expr_local_symbols(stmt->if_conditional, list);
+		collect_stmt_local_symbols(stmt->if_true, list);
+		collect_stmt_local_symbols(stmt->if_false, list);
+		break;
+	case STMT_EXPRESSION:
+	case STMT_CONTEXT:
+		collect_expr_local_symbols(stmt->expression, list);
+		break;
+	case STMT_RETURN:
+		collect_expr_local_symbols(stmt->ret_value, list);
+		break;
+	case STMT_GOTO:
+		collect_expr_local_symbols(stmt->goto_expression, list);
+		break;
+	case STMT_ASM: {
+		struct asm_operand *op;
+		FOR_EACH_PTR(stmt->asm_inputs, op) {
+			collect_expr_local_symbols(op->expr, list);
+		} END_FOR_EACH_PTR(op);
+		FOR_EACH_PTR(stmt->asm_outputs, op) {
+			collect_expr_local_symbols(op->expr, list);
+		} END_FOR_EACH_PTR(op);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+struct expression *copy_cleanup_expression(struct expression *expr)
+{
+	struct symbol_list *syms = NULL;
+	struct expression *dup;
+
+	if (!expr || expr->type != EXPR_STATEMENT)
+		return expr;
+
+	collect_expr_local_symbols(expr, &syms);
+	copy_allow_unreplaced = true;
+	dup = copy_expression(expr);
+	copy_allow_unreplaced = false;
+	unset_replace_list(syms);
+	free_ptr_list(&syms);
+	return dup;
+}
+
 int inline_function(struct expression *expr, struct symbol *sym)
 {
 	struct symbol_list * fn_symbol_list;
@@ -641,6 +844,7 @@ int inline_function(struct expression *expr, struct symbol *sym)
 			*a = *name;
 			a->bb_target = NULL;
 			a->pseudo = NULL;
+			a->cleanup_scope = NULL;
 			set_replace(name, a);
 			add_symbol(&fn_symbol_list, a);
 			a->initializer = arg;
diff --git a/linearize.c b/linearize.c
index f43502577649..9d36415551d6 100644
--- a/linearize.c
+++ b/linearize.c
@@ -2039,16 +2039,383 @@ static pseudo_t linearize_one_symbol(struct entrypoint *ep, struct symbol *sym)
 	return value;
 }
 
+struct cleanup_scope {
+	struct cleanup_scope *parent;
+	struct statement *stmt;
+	struct symbol_list *cleanups;
+	int depth;
+};
+
+ALLOCATOR(cleanup_scope, "cleanup scope");
+
+static inline struct cleanup_scope *alloc_cleanup_scope(struct cleanup_scope *parent, struct statement *stmt)
+{
+	struct cleanup_scope *scope = __alloc_cleanup_scope(0);
+	scope->parent = parent;
+	scope->stmt = stmt;
+	scope->cleanups = NULL;
+	scope->depth = parent ? parent->depth + 1 : 0;
+	return scope;
+}
+
+static struct cleanup_scope *cleanup_scope_lca(struct cleanup_scope *s1, struct cleanup_scope *s2)
+{
+	if (!s1 || !s2)
+		return NULL;
+	while (s1->depth > s2->depth)
+		s1 = s1->parent;
+	while (s2->depth > s1->depth)
+		s2 = s2->parent;
+	while (s1 && s2 && s1 != s2) {
+		s1 = s1->parent;
+		s2 = s2->parent;
+	}
+	return s1;
+}
+
+static struct symbol *first_cleanup_decl(struct symbol_list *list)
+{
+	struct symbol *sym;
+
+	FOR_EACH_PTR(list, sym) {
+		if (sym->cleanup)
+			return sym;
+	} END_FOR_EACH_PTR(sym);
+	return NULL;
+}
+
+static bool has_cleanup_decl(struct symbol_list *list)
+{
+	return first_cleanup_decl(list) != NULL;
+}
+
+static struct symbol *bypasses_cleanup_decl(struct cleanup_scope *from, struct cleanup_scope *to)
+{
+	struct cleanup_scope *lca = cleanup_scope_lca(from, to);
+	struct cleanup_scope *s;
+
+	for (s = to; s && s != lca; s = s->parent) {
+		if (!s->stmt)
+			continue;
+		if (s->stmt->type == STMT_DECLARATION) {
+			struct symbol *sym = first_cleanup_decl(s->stmt->declaration);
+			if (sym)
+				return sym;
+		} else if (s->stmt->type == STMT_ITERATOR) {
+			struct symbol *sym = first_cleanup_decl(s->stmt->iterator_syms);
+			if (sym)
+				return sym;
+		}
+	}
+	return NULL;
+}
+
+static int count_cleanups_to(struct cleanup_scope *from, struct cleanup_scope *to)
+{
+	struct cleanup_scope *s;
+	int count = 0;
+
+	for (s = from; s && s != to; s = s->parent)
+		count += symbol_list_size(s->cleanups);
+	return count;
+}
+
+static void prepare_expr_cleanup_scopes(struct expression *expr, struct cleanup_scope *parent);
+
+static void prepare_cleanup_scopes(struct statement *stmt, struct cleanup_scope *parent)
+{
+	if (!stmt)
+		return;
+
+	switch (stmt->type) {
+	case STMT_COMPOUND: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		struct cleanup_scope *cur;
+		struct statement *s;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->ret) {
+			stmt->ret->cleanup_scope = parent;
+			stmt->ret->bb_target = NULL;
+		}
+		if (stmt->args)
+			prepare_cleanup_scopes(stmt->args, scope);
+		cur = scope;
+		FOR_EACH_PTR(stmt->stmts, s) {
+			struct statement *inner = s;
+			prepare_cleanup_scopes(s, cur);
+			while (inner && (inner->type == STMT_LABEL || inner->type == STMT_CASE))
+				inner = (inner->type == STMT_LABEL) ? inner->label_statement : inner->case_statement;
+			if (inner && inner->type == STMT_DECLARATION && inner->cleanup_scope)
+				cur = inner->cleanup_scope;
+		} END_FOR_EACH_PTR(s);
+		break;
+	}
+	case STMT_DECLARATION: {
+		struct cleanup_scope *scope = NULL;
+		struct symbol *sym;
+
+		if (has_cleanup_decl(stmt->declaration)) {
+			scope = stmt->cleanup_scope;
+			if (!scope) {
+				scope = alloc_cleanup_scope(parent, stmt);
+				stmt->cleanup_scope = scope;
+			} else {
+				scope->parent = parent;
+				scope->depth = parent ? parent->depth + 1 : 0;
+				scope->cleanups = NULL;
+			}
+		}
+		FOR_EACH_PTR(stmt->declaration, sym) {
+			sym->initialized = 0;
+			prepare_expr_cleanup_scopes(sym->initializer,
+						    scope ? scope : parent);
+		} END_FOR_EACH_PTR(sym);
+		break;
+	}
+	case STMT_SWITCH: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->switch_break) {
+			stmt->switch_break->cleanup_scope = parent;
+			stmt->switch_break->bb_target = NULL;
+		}
+		if (stmt->switch_case) {
+			struct symbol *sym;
+			FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
+				sym->bb_target = NULL;
+			} END_FOR_EACH_PTR(sym);
+		}
+		prepare_expr_cleanup_scopes(stmt->switch_expression, parent);
+		prepare_cleanup_scopes(stmt->switch_statement, scope);
+		break;
+	}
+	case STMT_ITERATOR: {
+		struct cleanup_scope *scope = stmt->cleanup_scope;
+		struct symbol *sym;
+		if (!scope) {
+			scope = alloc_cleanup_scope(parent, stmt);
+			stmt->cleanup_scope = scope;
+		} else {
+			scope->parent = parent;
+			scope->depth = parent ? parent->depth + 1 : 0;
+			scope->cleanups = NULL;
+		}
+		if (stmt->iterator_break) {
+			stmt->iterator_break->cleanup_scope = scope;
+			stmt->iterator_break->bb_target = NULL;
+		}
+		if (stmt->iterator_continue) {
+			stmt->iterator_continue->cleanup_scope = scope;
+			stmt->iterator_continue->bb_target = NULL;
+		}
+		FOR_EACH_PTR(stmt->iterator_syms, sym) {
+			sym->initialized = 0;
+			prepare_expr_cleanup_scopes(sym->initializer, scope);
+		} END_FOR_EACH_PTR(sym);
+		prepare_cleanup_scopes(stmt->iterator_pre_statement, scope);
+		prepare_expr_cleanup_scopes(stmt->iterator_pre_condition, scope);
+		prepare_cleanup_scopes(stmt->iterator_statement, scope);
+		prepare_cleanup_scopes(stmt->iterator_post_statement, scope);
+		prepare_expr_cleanup_scopes(stmt->iterator_post_condition, scope);
+		break;
+	}
+	case STMT_LABEL: {
+		struct symbol *label = stmt->label_identifier;
+		if (label) {
+			label->cleanup_scope = parent;
+			label->bb_target = NULL;
+		}
+		prepare_cleanup_scopes(stmt->label_statement, parent);
+		break;
+	}
+	case STMT_CASE:
+		if (stmt->case_label) {
+			stmt->case_label->cleanup_scope = parent;
+			stmt->case_label->bb_target = NULL;
+		}
+		prepare_expr_cleanup_scopes(stmt->case_expression, parent);
+		prepare_expr_cleanup_scopes(stmt->case_to, parent);
+		prepare_cleanup_scopes(stmt->case_statement, parent);
+		break;
+	case STMT_IF:
+		prepare_expr_cleanup_scopes(stmt->if_conditional, parent);
+		prepare_cleanup_scopes(stmt->if_true, parent);
+		prepare_cleanup_scopes(stmt->if_false, parent);
+		break;
+	case STMT_EXPRESSION:
+		prepare_expr_cleanup_scopes(stmt->expression, parent);
+		break;
+	case STMT_RETURN:
+		prepare_expr_cleanup_scopes(stmt->ret_value, parent);
+		break;
+	case STMT_CONTEXT:
+		prepare_expr_cleanup_scopes(stmt->expression, parent);
+		prepare_expr_cleanup_scopes(stmt->context, parent);
+		break;
+	case STMT_RANGE:
+		prepare_expr_cleanup_scopes(stmt->range_expression, parent);
+		prepare_expr_cleanup_scopes(stmt->range_low, parent);
+		prepare_expr_cleanup_scopes(stmt->range_high, parent);
+		break;
+	case STMT_GOTO:
+		prepare_expr_cleanup_scopes(stmt->goto_expression, parent);
+		break;
+	case STMT_ASM: {
+		struct asm_operand *op;
+		FOR_EACH_PTR(stmt->asm_inputs, op) {
+			prepare_expr_cleanup_scopes(op->expr, parent);
+		} END_FOR_EACH_PTR(op);
+		FOR_EACH_PTR(stmt->asm_outputs, op) {
+			prepare_expr_cleanup_scopes(op->expr, parent);
+		} END_FOR_EACH_PTR(op);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void prepare_expr_cleanup_scopes(struct expression *expr, struct cleanup_scope *parent)
+{
+	if (!expr)
+		return;
+	switch (expr->type) {
+	case EXPR_STATEMENT:
+		prepare_cleanup_scopes(expr->statement, parent);
+		break;
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_COMPARE:
+	case EXPR_LOGICAL:
+	case EXPR_ASSIGNMENT:
+		prepare_expr_cleanup_scopes(expr->left, parent);
+		prepare_expr_cleanup_scopes(expr->right, parent);
+		break;
+	case EXPR_PREOP:
+	case EXPR_POSTOP:
+		prepare_expr_cleanup_scopes(expr->unop, parent);
+		break;
+	case EXPR_CONDITIONAL:
+	case EXPR_SELECT:
+		prepare_expr_cleanup_scopes(expr->conditional, parent);
+		prepare_expr_cleanup_scopes(expr->cond_true, parent);
+		prepare_expr_cleanup_scopes(expr->cond_false, parent);
+		break;
+	case EXPR_CALL: {
+		struct expression *arg;
+		prepare_expr_cleanup_scopes(expr->fn, parent);
+		FOR_EACH_PTR(expr->args, arg) {
+			prepare_expr_cleanup_scopes(arg, parent);
+		} END_FOR_EACH_PTR(arg);
+		break;
+	}
+	case EXPR_CAST:
+	case EXPR_FORCE_CAST:
+	case EXPR_IMPLIED_CAST:
+	case EXPR_SIZEOF:
+	case EXPR_PTRSIZEOF:
+	case EXPR_ALIGNOF:
+		prepare_expr_cleanup_scopes(expr->cast_expression, parent);
+		break;
+	case EXPR_SLICE:
+		prepare_expr_cleanup_scopes(expr->base, parent);
+		break;
+	case EXPR_DEREF:
+		prepare_expr_cleanup_scopes(expr->deref, parent);
+		break;
+	case EXPR_INITIALIZER: {
+		struct expression *entry;
+		FOR_EACH_PTR(expr->expr_list, entry) {
+			prepare_expr_cleanup_scopes(entry, parent);
+		} END_FOR_EACH_PTR(entry);
+		break;
+	}
+	case EXPR_POS:
+		prepare_expr_cleanup_scopes(expr->init_expr, parent);
+		break;
+	case EXPR_INDEX:
+		prepare_expr_cleanup_scopes(expr->idx_expression, parent);
+		break;
+	case EXPR_IDENTIFIER:
+		prepare_expr_cleanup_scopes(expr->ident_expression, parent);
+		break;
+	case EXPR_GENERIC: {
+		struct type_expression *map;
+		prepare_expr_cleanup_scopes(expr->control, parent);
+		prepare_expr_cleanup_scopes(expr->def, parent);
+		for (map = expr->map; map; map = map->next)
+			prepare_expr_cleanup_scopes(map->expr, parent);
+		break;
+	}
+	case EXPR_OFFSETOF:
+		prepare_expr_cleanup_scopes(expr->down, parent);
+		if (expr->op != '.')
+			prepare_expr_cleanup_scopes(expr->index, parent);
+		break;
+	case EXPR_HAS_ATTRIBUTE:
+		prepare_expr_cleanup_scopes(expr->has_attr_expr, parent);
+		break;
+	default:
+		break;
+	}
+}
+
+static void emit_cleanups_to(struct entrypoint *ep, struct cleanup_scope *target_scope)
+{
+	struct cleanup_scope *saved_scope = ep->cleanup_scope;
+	struct cleanup_scope *scope;
+
+	if (!ep->active || !bb_reachable(ep->active))
+		return;
+
+	for (scope = saved_scope; scope && scope != target_scope; scope = scope->parent) {
+		struct symbol *sym;
+		ep->cleanup_scope = scope;
+		FOR_EACH_PTR_REVERSE(scope->cleanups, sym) {
+			if (sym->cleanup && sym->cleanup->ctype) {
+				struct expression *cleanup = copy_cleanup_expression(sym->cleanup);
+				prepare_expr_cleanup_scopes(cleanup, scope);
+				linearize_expression(ep, cleanup);
+			}
+		} END_FOR_EACH_PTR_REVERSE(sym);
+	}
+	ep->cleanup_scope = saved_scope;
+}
+
 static pseudo_t linearize_compound_statement(struct entrypoint *ep, struct statement *stmt)
 {
+	struct cleanup_scope *scope = stmt->cleanup_scope;
 	pseudo_t pseudo;
 	struct statement *s;
 
+	if (!scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		scope = stmt->cleanup_scope;
+	}
+
+	ep->cleanup_scope = scope;
 	pseudo = VOID;
 	FOR_EACH_PTR(stmt->stmts, s) {
 		pseudo = linearize_statement(ep, s);
 	} END_FOR_EACH_PTR(s);
 
+	emit_cleanups_to(ep, scope->parent);
+	ep->cleanup_scope = scope->parent;
+
 	return pseudo;
 }
 
@@ -2266,8 +2633,14 @@ static pseudo_t linearize_declaration(struct entrypoint *ep, struct statement *s
 
 	concat_symbol_list(stmt->declaration, &ep->syms);
 
+	if (stmt->cleanup_scope) {
+		stmt->cleanup_scope->cleanups = NULL;
+		ep->cleanup_scope = stmt->cleanup_scope;
+	}
 	FOR_EACH_PTR(stmt->declaration, sym) {
 		linearize_one_symbol(ep, sym);
+		if (sym->cleanup && ep->cleanup_scope)
+			add_symbol(&ep->cleanup_scope->cleanups, sym);
 	} END_FOR_EACH_PTR(sym);
 	return VOID;
 }
@@ -2277,10 +2650,10 @@ static pseudo_t linearize_return(struct entrypoint *ep, struct statement *stmt)
 	struct expression *expr = stmt->expression;
 	struct symbol *ret = stmt->ret_target;
 	struct basic_block *bb_return = get_bound_block(ep, ret);
-	struct basic_block *active;
 	pseudo_t src = linearize_expression(ep, expr);
-	active = ep->active;
-	if (active && !is_void_type(ret)) {
+
+	emit_cleanups_to(ep, ret ? ret->cleanup_scope : NULL);
+	if (ep->active && !is_void_type(ret)) {
 		add_return(ep, bb_return, ret, src);
 	}
 	add_goto(ep, bb_return);
@@ -2311,10 +2684,27 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 	add_one_insn(ep, switch_ins);
 	finish_block(ep);
 
+	struct cleanup_scope *switch_scope = stmt->cleanup_scope;
+	if (!switch_scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		switch_scope = stmt->cleanup_scope;
+	}
+	ep->cleanup_scope = switch_scope;
+	stmt->switch_break->cleanup_scope = switch_scope->parent;
+
 	default_case = NULL;
 	FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
 		struct statement *case_stmt = sym->stmt;
-		struct basic_block *bb_case = get_bound_block(ep, sym);
+		struct symbol *bypass = bypasses_cleanup_decl(switch_scope, sym->cleanup_scope);
+		struct basic_block *bb_case;
+
+		if (bypass) {
+			sparse_error(case_stmt->pos,
+				     "jump bypasses initialization of variable '%s' with cleanup attribute",
+				     show_ident(bypass->ident));
+			continue;
+		}
+		bb_case = get_bound_block(ep, sym);
 
 		if (!case_stmt->case_expression) {
 			default_case = bb_case;
@@ -2349,6 +2739,7 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 
 	/* And linearize the actual statement */
 	linearize_statement(ep, stmt->switch_statement);
+	emit_cleanups_to(ep, switch_scope->parent);
 	set_activeblock(ep, switch_end);
 
 	if (!default_case)
@@ -2360,6 +2751,7 @@ static pseudo_t linearize_switch(struct entrypoint *ep, struct statement *stmt)
 	add_bb(&active->children, default_case);
 	sort_switch_cases(switch_ins);
 
+	ep->cleanup_scope = switch_scope->parent;
 	return VOID;
 }
 
@@ -2372,9 +2764,18 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 	struct expression *post_condition = stmt->iterator_post_condition;
 	struct basic_block *loop_top, *loop_body, *loop_continue, *loop_end;
 	struct symbol *sym;
+	struct cleanup_scope *iter_scope = stmt->cleanup_scope;
+	if (!iter_scope) {
+		prepare_cleanup_scopes(stmt, ep->cleanup_scope);
+		iter_scope = stmt->cleanup_scope;
+	}
+
+	ep->cleanup_scope = iter_scope;
 
 	FOR_EACH_PTR(stmt->iterator_syms, sym) {
 		linearize_one_symbol(ep, sym);
+		if (sym->cleanup)
+			add_symbol(&iter_scope->cleanups, sym);
 	} END_FOR_EACH_PTR(sym);
 	concat_symbol_list(stmt->iterator_syms, &ep->syms);
 	linearize_statement(ep, pre_statement);
@@ -2383,6 +2784,9 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 	loop_continue = alloc_basic_block(ep, stmt->pos);
 	loop_end = alloc_basic_block(ep, stmt->pos);
 
+	stmt->iterator_continue->cleanup_scope = iter_scope;
+	stmt->iterator_break->cleanup_scope = iter_scope;
+
 	/* An empty post-condition means that it's the same as the pre-condition */
 	if (!post_condition) {
 		loop_top = alloc_basic_block(ep, stmt->pos);
@@ -2407,6 +2811,9 @@ static pseudo_t linearize_iterator(struct entrypoint *ep, struct statement *stmt
 		linearize_cond_branch(ep, post_condition, loop_top, loop_end);
 	set_activeblock(ep, loop_end);
 
+	emit_cleanups_to(ep, iter_scope->parent);
+	ep->cleanup_scope = iter_scope->parent;
+
 	return VOID;
 }
 
@@ -2464,28 +2871,72 @@ static pseudo_t linearize_statement(struct entrypoint *ep, struct statement *stm
 		struct expression *expr;
 		struct instruction *goto_ins;
 		struct basic_block *active;
+		struct cleanup_scope *common_lca = NULL;
+		int expected_cleanups = -1;
 		pseudo_t pseudo;
 
 		active = ep->active;
 		if (!bb_reachable(active))
 			break;
 
-		if (stmt->goto_label) {
-			add_goto(ep, get_bound_block(ep, stmt->goto_label));
+		sym = stmt->goto_label;
+		expr = stmt->goto_expression;
+		if (!sym && expr && expr->type == EXPR_LABEL)
+			sym = expr->label_symbol;
+
+		if (sym) {
+			struct symbol *bypass = bypasses_cleanup_decl(ep->cleanup_scope, sym->cleanup_scope);
+			if (bypass) {
+				sparse_error(stmt->pos,
+					     "jump bypasses initialization of variable '%s' with cleanup attribute",
+					     show_ident(bypass->ident));
+				add_unreachable(ep);
+				break;
+			}
+			emit_cleanups_to(ep, cleanup_scope_lca(ep->cleanup_scope, sym->cleanup_scope));
+			add_goto(ep, get_bound_block(ep, sym));
 			break;
 		}
 
-		expr = stmt->goto_expression;
 		if (!expr)
 			break;
 
-		/* This can happen as part of simplification */
-		if (expr->type == EXPR_LABEL) {
-			add_goto(ep, get_bound_block(ep, expr->label_symbol));
+		pseudo = linearize_expression(ep, expr);
+		active = ep->active;
+		if (!bb_reachable(active))
 			break;
+
+		FOR_EACH_PTR(stmt->target_list, sym) {
+			struct symbol *bypass = bypasses_cleanup_decl(ep->cleanup_scope, sym->cleanup_scope);
+			struct cleanup_scope *lca;
+			int n;
+
+			if (bypass) {
+				sparse_error(stmt->pos,
+					     "jump bypasses initialization of variable '%s' with cleanup attribute",
+					     show_ident(bypass->ident));
+				add_unreachable(ep);
+				goto out_goto;
+			}
+			lca = cleanup_scope_lca(ep->cleanup_scope, sym->cleanup_scope);
+			n = count_cleanups_to(ep->cleanup_scope, lca);
+			if (expected_cleanups < 0) {
+				expected_cleanups = n;
+				common_lca = lca;
+			} else if (n != expected_cleanups) {
+				sparse_error(stmt->pos, "computed goto crosses cleanup scope");
+				add_unreachable(ep);
+				goto out_goto;
+			}
+		} END_FOR_EACH_PTR(sym);
+
+		if (expected_cleanups > 0) {
+			emit_cleanups_to(ep, common_lca);
+			active = ep->active;
+			if (!bb_reachable(active))
+				break;
 		}
 
-		pseudo = linearize_expression(ep, expr);
 		goto_ins = alloc_instruction(OP_COMPUTEDGOTO, 0);
 		use_pseudo(goto_ins, pseudo, &goto_ins->src);
 		add_one_insn(ep, goto_ins);
@@ -2494,11 +2945,12 @@ static pseudo_t linearize_statement(struct entrypoint *ep, struct statement *stm
 			struct basic_block *bb_computed = get_bound_block(ep, sym);
 			struct multijmp *jmp = alloc_multijmp(bb_computed, 1, 0);
 			add_multijmp(&goto_ins->multijmp_list, jmp);
-			add_bb(&bb_computed->parents, ep->active);
+			add_bb(&bb_computed->parents, active);
 			add_bb(&active->children, bb_computed);
 		} END_FOR_EACH_PTR(sym);
 
 		finish_block(ep);
+	out_goto:
 		break;
 	}
 
@@ -2613,6 +3065,8 @@ static struct entrypoint *linearize_fn(struct symbol *sym, struct symbol *base_t
 	if (!stmt || sym->bogus_linear)
 		return NULL;
 
+	prepare_cleanup_scopes(stmt, NULL);
+
 	ep = alloc_entrypoint();
 	ep->name = sym;
 	sym->ep = ep;
diff --git a/linearize.h b/linearize.h
index 3412494edbef..7b828859238e 100644
--- a/linearize.h
+++ b/linearize.h
@@ -321,6 +321,9 @@ static inline void replace_bb_in_list(struct basic_block_list **list,
 	replace_ptr_list_entry((struct ptr_list **)list, old, new, count);
 }
 
+struct cleanup_scope;
+DECLARE_ALLOCATOR(cleanup_scope);
+
 struct entrypoint {
 	struct symbol *name;
 	struct symbol_list *syms;
@@ -329,6 +332,7 @@ struct entrypoint {
 	struct basic_block *active;
 	struct instruction *entry;
 	unsigned int dom_levels;	/* max levels in the dom tree */
+	struct cleanup_scope *cleanup_scope;
 };
 
 extern void insert_select(struct basic_block *bb, struct instruction *br, struct instruction *phi, pseudo_t if_true, pseudo_t if_false);
diff --git a/parse.c b/parse.c
index 7010d412fdab..ae799370203a 100644
--- a/parse.c
+++ b/parse.c
@@ -1512,7 +1512,7 @@ static struct token *attribute_cleanup(struct token *token, struct symbol *attr,
 		else if (token_type(token) != TOKEN_IDENT)
 			sparse_error(token->pos, "argument is not an identifier");
 		token = primary_expression(token, &expr);
-		if (expr && expr->type == EXPR_SYMBOL)
+		if (expr && expr->type == EXPR_SYMBOL && match_op(token, ')'))
 			ctx->cleanup = expr;
 		return expect(token, ')', "after attribute's argument'");
 	}
@@ -2609,7 +2609,8 @@ static struct token *declaration_list(struct token *token, struct symbol_list **
 
 		decl->ctype = ctx.ctype;
 		decl->ctype.modifiers |= mod;
-		decl->cleanup = ctx.cleanup;
+		if (ctx.cleanup)
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
 		decl->packed = ctx.packed;
 		decl->explicit_alignment = ctx.explicit_alignment;
 		apply_fn_ptr_modifiers(decl, ctx.f_modifiers);
@@ -2681,6 +2682,8 @@ static struct token *parameter_declaration(struct token *token, struct symbol *s
 	apply_fn_ptr_modifiers(sym, ctx.f_modifiers);
 	sym->endpos = token->pos;
 	sym->forced_arg = ctx.forced;
+	if (ctx.cleanup)
+		sparse_error(sym->pos, "cleanup attribute may only be applied to local variables");
 	if (ctx.has_auto || ctx.autotype) {
 		sparse_error(sym->pos,
 			     "parameter '%s' cannot be declared %s",
@@ -3845,6 +3848,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	struct symbol *decl;
 	struct decl_state ctx = { .ident = &ident };
 	struct ctype saved;
+	struct expression *saved_cleanup;
 	struct symbol *base_type;
 	unsigned long mod, saved_f_modifiers;
 	unsigned int saved_explicit_alignment, saved_td_align;
@@ -3889,6 +3893,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 	saved_td_align = ctx.typedef_alignment;
 	saved_f_modifiers = ctx.f_modifiers;
 	saved = ctx.ctype;
+	saved_cleanup = ctx.cleanup;
 	token = declarator(token, &ctx);
 	token = handle_asm_name(token, &ctx);
 	token = handle_attributes(token, &ctx);
@@ -3945,6 +3950,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 			}
 		}
 	} else if (base_type && base_type->type == SYM_FN) {
+		if (decl->cleanup) {
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+			decl->cleanup = NULL;
+		}
 		if (ctx.is_constexpr) {
 			sparse_error(
 				decl->pos,
@@ -4033,6 +4042,9 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 				add_symbol(list, decl);
 				fn_local_symbol(decl);
 			}
+		} else if (decl->cleanup) {
+			sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+			decl->cleanup = NULL;
 		}
 		check_declaration(decl);
 		if (decl->same_symbol) {
@@ -4161,7 +4173,7 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		ctx.typedef_alignment = saved_td_align;
 		ctx.f_modifiers = saved_f_modifiers;
 		ctx.ctype = saved;
-		ctx.cleanup = NULL;
+		ctx.cleanup = saved_cleanup;
 		ctx.ident_modifiers = 0;
 		token = handle_attributes(token, &ctx);
 		token = declarator(token, &ctx);
@@ -4195,6 +4207,10 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis
 		/* Function declarations are automatically extern unless specifically static */
 		base_type = decl->ctype.base_type;
 		if (!is_typedef && base_type && base_type->type == SYM_FN) {
+			if (decl->cleanup) {
+				sparse_error(decl->pos, "cleanup attribute may only be applied to local variables");
+				decl->cleanup = NULL;
+			}
 			decl->ctype.modifiers |= ctx.f_modifiers;
 			if (standard >= STANDARD_C23 && base_type->kr_args)
 				sparse_error(decl->pos,
diff --git a/parse.h b/parse.h
index 6a528bc8ca4d..83c39c3cecbc 100644
--- a/parse.h
+++ b/parse.h
@@ -44,9 +44,12 @@ enum statement_type {
 	STMT_RANGE,
 };
 
+struct cleanup_scope;
+
 struct statement {
 	enum statement_type type;
 	struct position pos;
+	struct cleanup_scope *cleanup_scope;
 	union {
 		struct /* declaration */ {
 			struct symbol_list *declaration;
@@ -138,6 +141,7 @@ extern struct symbol *ctype_integer(int size, int want_unsigned);
 
 extern int inline_function(struct expression *expr, struct symbol *sym);
 extern void uninline(struct symbol *sym);
+extern struct expression *copy_cleanup_expression(struct expression *expr);
 extern void init_parser(int);
 extern void init_parser_finalize(void);
 
diff --git a/symbol.h b/symbol.h
index cd9268703192..721b6c7d05a4 100644
--- a/symbol.h
+++ b/symbol.h
@@ -251,6 +251,7 @@ struct symbol {
 		};
 	};
 	pseudo_t pseudo;
+	struct cleanup_scope *cleanup_scope;
 };
 
 /* Modifiers */
diff --git a/validation/cleanup-context.c b/validation/cleanup-context.c
new file mode 100644
index 000000000000..d07936359466
--- /dev/null
+++ b/validation/cleanup-context.c
@@ -0,0 +1,161 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+#define __context_lock(x) __attribute__((context(0, 1)))
+#define __context_unlock(x) __attribute__((context(1, 0)))
+
+static void lock(int *x) __context_lock(x)
+{
+	__context__(1);
+}
+
+static void unlock(int **x) __context_unlock(x)
+{
+	__context__(-1);
+}
+
+static void test_normal_exit(int *m)
+{
+	int *guard __cleanup(unlock) = (lock(m), m);
+}
+
+static int test_return(int *m, int cond)
+{
+	int *guard __cleanup(unlock) = (lock(m), m);
+	if (cond)
+		return 1;
+	return 0;
+}
+
+static int test_nested_return(int *m1, int *m2, int cond)
+{
+	int *g1 __cleanup(unlock) = (lock(m1), m1);
+	{
+		int *g2 __cleanup(unlock) = (lock(m2), m2);
+		if (cond)
+			return -1;
+	}
+	return 0;
+}
+
+static void test_loop_break(int *m, int n)
+{
+	for (int i = 0; i < n; i++) {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (i == 2)
+			break;
+	}
+}
+
+static void test_loop_continue(int *m, int n)
+{
+	for (int i = 0; i < n; i++) {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (i == 2)
+			continue;
+	}
+}
+
+static void test_backward_goto(int *m, int cond)
+{
+again:
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (cond) {
+			cond = 0;
+			goto again;
+		}
+	}
+}
+
+static void test_forward_goto(int *m, int cond)
+{
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		if (cond)
+			goto out;
+	}
+out:
+	return;
+}
+
+static void test_loop_header_break(int *m, int n)
+{
+	for (int *guard __cleanup(unlock) = (lock(m), m); n > 0; n--) {
+		if (n == 2)
+			break;
+	}
+}
+
+static void test_switch_break(int *m, int val)
+{
+	switch (val) {
+	case 1: {
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void test_switch_case_decl(int *m, int val)
+{
+	switch (val) {
+	default:
+		break;
+	case 1: ;
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	}
+}
+
+static void test_computed_goto_const(int *m)
+{
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		goto *&&out;
+	}
+out:
+	return;
+}
+
+static void test_computed_goto_dyn(int *m, int cond)
+{
+	void *target = cond ? &&out1 : &&out2;
+	{
+		int *guard __cleanup(unlock) = (lock(m), m);
+		goto *target;
+	}
+out1:
+	return;
+out2:
+	return;
+}
+
+static void test_multi_declarator(int *m1, int *m2)
+{
+	int __cleanup(unlock) *g1 = (lock(m1), m1), *g2 = (lock(m2), m2);
+}
+
+static void test_stmt_expr_init_backward_goto(int *m, int cond)
+{
+again:
+	int *guard __cleanup(unlock) = ({ int *p = (lock(m), m); p; });
+	if (cond) {
+		cond = 0;
+		goto again;
+	}
+}
+
+static void test_warn_imbalance(int *m)
+{
+	lock(m);
+}
+
+/*
+ * check-name: cleanup attribute in context analysis
+ * check-command: sparse -Wcontext $file
+ *
+ * check-error-start
+cleanup-context.c:149:13: warning: context imbalance in 'test_warn_imbalance' - wrong count at exit
+ * check-error-end
+ */
diff --git a/validation/cleanup-errors.c b/validation/cleanup-errors.c
new file mode 100644
index 000000000000..0aed01fbbfde
--- /dev/null
+++ b/validation/cleanup-errors.c
@@ -0,0 +1,77 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+
+static void cl(int *p)
+{
+	(void)p;
+}
+
+static void lock(int *m) __attribute__((context(0, 1)))
+{
+	(void)m;
+	__context__(1);
+}
+
+static void unlock(int **m) __attribute__((context(1, 0)))
+{
+	(void)m;
+	__context__(-1);
+}
+
+static void bad_param(int x __cleanup(cl))
+{
+	(void)x;
+}
+
+struct bad_struct {
+	int member __cleanup(cl);
+};
+
+typedef int bad_typedef __cleanup(cl);
+
+static void bad_fn(void) __cleanup(cl);
+
+static void bad_forward_goto(int *m, int c)
+{
+	if (c)
+		goto inside;
+	int *guard __cleanup(unlock) = (lock(m), m);
+inside:
+	return;
+}
+
+static void bad_switch_bypass(int *m, int v)
+{
+	switch (v) {
+	case 1: ;
+		int *guard __cleanup(unlock) = (lock(m), m);
+		break;
+	case 2:
+		break;
+	}
+}
+
+static void bad_cgoto_into_scope(void *p)
+{
+	goto *p;
+	{
+		int x __cleanup(cl) = 1;
+	inner:
+		(void)&&inner;
+		(void)x;
+	}
+}
+
+/*
+ * check-name: cleanup attribute error diagnostics
+ * check-command: sparse -Wcontext $file
+ *
+ * check-error-start
+cleanup-errors.c:20:23: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:26:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:29:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:31:13: error: cleanup attribute may only be applied to local variables
+cleanup-errors.c:36:17: error: jump bypasses initialization of variable 'guard' with cleanup attribute
+cleanup-errors.c:48:9: error: jump bypasses initialization of variable 'guard' with cleanup attribute
+cleanup-errors.c:55:9: error: jump bypasses initialization of variable 'x' with cleanup attribute
+ * check-error-end
+ */
diff --git a/validation/cleanup-inline-loop.c b/validation/cleanup-inline-loop.c
new file mode 100644
index 000000000000..539cb39f8d98
--- /dev/null
+++ b/validation/cleanup-inline-loop.c
@@ -0,0 +1,31 @@
+#define __cleanup(f) __attribute__((cleanup(f)))
+
+static inline void cleanup_fn(int *p)
+{
+	int tmp = *p;
+	while (tmp > 0) {
+		tmp--;
+		if (tmp == 0)
+			goto done;
+	}
+done:
+	*p = tmp;
+}
+
+static int test_multiple_exits(int cond)
+{
+	int x __cleanup(cleanup_fn) = 2;
+	if (cond == 1)
+		return 1;
+	if (cond == 2)
+		return 2;
+	return 0;
+}
+
+/*
+ * check-name: inline cleanup with loop across multiple exits
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-excludes: VOID
+ */
diff --git a/validation/linear/cleanup-control-flow.c b/validation/linear/cleanup-control-flow.c
new file mode 100644
index 000000000000..269df60917e4
--- /dev/null
+++ b/validation/linear/cleanup-control-flow.c
@@ -0,0 +1,62 @@
+void cl1(int *p);
+void cl2(int *p);
+void cl3(int *p);
+
+/* 1. break and continue inside loop with cleanup variable */
+void loop_cleanup(int a, int b)
+{
+	while (a > 0) {
+		int x __attribute__((cleanup(cl1))) = a;
+		if (b == 1)
+			break;
+		if (b == 2)
+			continue;
+		a--;
+	}
+}
+
+/* 2. goto out of nested cleanup scopes */
+int goto_nested_cleanup(int c)
+{
+	int outer __attribute__((cleanup(cl1))) = 1;
+	{
+		int inner __attribute__((cleanup(cl2))) = 2;
+		if (c)
+			goto out;
+	}
+out:
+	return outer;
+}
+
+/* 3. Cleanup declaration inside unbraced switch case runs once, not twice */
+void switch_case_cleanup(int v)
+{
+	switch (v) {
+	default:
+		break;
+	case 1: ;
+		int s __attribute__((cleanup(cl3))) = v;
+		break;
+	}
+}
+
+/* 4. Multi-declarator cleanup and statement-expression initializer + backward goto */
+void multi_and_stmt_expr_cleanup(int c)
+{
+again:
+	int __attribute__((cleanup(cl3))) a = ({ int t = c; t; }), b = 2;
+	if (c) {
+		c = 0;
+		goto again;
+	}
+}
+
+/*
+ * check-name: cleanup-control-flow
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-ignore
+ * check-output-pattern(4): call.*cl1
+ * check-output-pattern(2): call.*cl2
+ * check-output-pattern(5): call.*cl3
+ */
diff --git a/validation/linear/cleanup-stmt-expr-asm.c b/validation/linear/cleanup-stmt-expr-asm.c
new file mode 100644
index 000000000000..82c79c3fbee1
--- /dev/null
+++ b/validation/linear/cleanup-stmt-expr-asm.c
@@ -0,0 +1,48 @@
+/*
+ * Cleanup expressions are copied after expansion. expand_compound()
+ * collapses single-statement compound statements, so the statement of
+ * a statement expression is not necessarily a compound statement anymore
+ * when the cleanup expression is copied. Additionally, asm memory
+ * operands have already been converted into addresses and must keep
+ * their constraint flags when copied.
+ */
+struct s {
+	int a;
+	int counter;
+};
+
+static inline void release(struct s **p)
+{
+	struct s *v = *p;
+
+	({ asm volatile("inc %0" : "+m" (v->counter)); });
+	({ asm volatile("dec %0" : "=m" (v->a) : "m" (v->a)); });
+}
+
+int f(struct s *v)
+{
+	struct s *__attribute__((cleanup(release))) x = v;
+
+	return 0;
+}
+
+/*
+ * check-name: cleanup-stmt-expr-asm
+ * check-command: test-linearize -Wno-decl $file
+ *
+ * check-output-start
+f:
+.L0:
+	<entry-point>
+	add.64      %r6 <- %arg1, $4
+	asm         "inc %0"
+		out: "+m" (%r6)
+	asm         "dec %0"
+		out: "=m" (%arg1)
+		in: "m" (%arg1)
+	# call      release, %r2
+	ret.32      $0
+
+
+ * check-output-end
+ */

  parent reply	other threads:[~2026-11-09 20:34 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 61/73] c23: Add support for _BitInt(N) Bart Van Assche
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261109203259.362845-69-bvanassche@acm.org \
    --to=bvanassche@acm.org \
    --cc=linux-sparse@vger.kernel.org \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox