[clang] [analyzer] Model GCC 'cleanup' attribute function calls (PR #221110)

Arseniy Zaostrovnykh via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 11 02:36:22 PDT 2026


================
@@ -149,3 +150,93 @@ int early_return_shape(void) {
     return 1;
   return 0;
 } // no leak on either path: the cleanup frees *p at the return.
+
+//===----------------------------------------------------------------------===//
+// Scope-exit shapes: goto, cleanup ordering and nesting.
+//===----------------------------------------------------------------------===//
+
+// The cleanup runs on every exit from the scope, including jumps.
+
+static void goto_cleanup(int *p) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  (void)p;
+}
+
+void goto_out_of_block_scope(void) {
+  {
+    int x __attribute__((cleanup(goto_cleanup)));
+    x = 1;
+    goto out;
+  }
+out:;
+}
+
+static void goto_cleanup_at_function_scope(int *p) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  (void)p;
+}
+
+void goto_at_function_scope(void) {
+  int x __attribute__((cleanup(goto_cleanup_at_function_scope)));
+  x = 1;
+  goto out;
+out:;
+}
+
+// Two cleanup handlers in the same scope run in reverse declaration order,
+// as in GCC.
+
+static int order_probe_global;
+
+static void order_probe(int *p) {
+  clang_analyzer_dump_int(order_probe_global); // expected-warning {{2 S32b}}
+  (void)p;
+}
+
+static void order_side_effect(int *p) {
+  order_probe_global = 2;
+  (void)p;
+}
+
+void cleanup_runs_in_reverse_declaration_order(void) {
+  int x __attribute__((cleanup(order_probe)));
+  int y __attribute__((cleanup(order_side_effect)));
+  x = 1;
+  y = 2;
+} // order_side_effect (declared last) runs first, so the dump above prints 2.
+
+// A cleanup handler can declare cleanup-attributed variables of its own:
+// the nested cleanup runs when the inlined handler exits.
+
+static void nested_cleanup(int *p) {
+  clang_analyzer_dump_int(*p); // expected-warning {{3 S32b}}
+}
+
+static void nested_handler(int *p) {
+  int z __attribute__((cleanup(nested_cleanup)));
+  z = 3;
+  (void)p;
+}
+
+void cleanup_nested_in_cleanup(void) {
+  int x __attribute__((cleanup(nested_handler)));
+  x = 42;
+}
+
+//===----------------------------------------------------------------------===//
+// A cleanup-annotated variable in a function that the analyzer inlines: the
+// cleanup call is processed within the inlined stack frame.
+//===----------------------------------------------------------------------===//
+
+static void inlined_function_cleanup(int *p) {
+  clang_analyzer_dump_int(*p); // expected-warning {{42 S32b}}
+}
+
+static void inlined_function_with_cleanup(void) {
+  int x __attribute__((cleanup(inlined_function_cleanup)));
+  x = 42;
+}
+
+void cleanup_in_inlined_function(void) {
+  inlined_function_with_cleanup();
+}
----------------
necto wrote:

Again, without some state influenced by `cleanup_in_inlined_function` it is not clear whether it is involved or not in the case.

https://github.com/llvm/llvm-project/pull/221110


More information about the cfe-commits mailing list