[clang] [CIR] Harden static-local lowering against malformed .cir (PR #224744)

Konstantinos Parasyris via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 18 13:56:42 PDT 2026


https://github.com/koparasy created https://github.com/llvm/llvm-project/pull/224744

Enforce that a static_local_guard is always paired with a static_local_info attribute via a GlobalOp verifier.

Part of #219048 

>From 8ae301a78f1864bf2ac06b28079bb68ceefb64ec Mon Sep 17 00:00:00 2001
From: Konstantinos Parasyris <konstantinos.parasyris at intel.com>
Date: Fri, 18 Sep 2026 13:53:30 -0700
Subject: [PATCH] [CIR] Harden static-local lowering against malformed .cir

---
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp             |  5 +++++
 .../lib/CIR/Dialect/Transforms/LoweringPrepare.cpp  |  7 ++++++-
 clang/test/CIR/IR/invalid-static-local-info.cir     | 13 +++++++++++++
 clang/test/CIR/IR/invalid-static-local.cir          |  3 +++
 clang/test/CIR/IR/invalid-tls.cir                   |  1 +
 clang/test/CIR/IR/static-local.cir                  | 10 +++++++++-
 6 files changed, 37 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-static-local-info.cir

diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 38ef8409634c7b..643e87e69dcec4 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2293,6 +2293,11 @@ mlir::LogicalResult cir::GlobalOp::verify() {
         "Cannot have a static-local global-op with a constructor or "
         "destructor, they require in-function initialization via LocalInitOp");
 
+  // A guard implies the info attribute;
+  if (getStaticLocalGuard().has_value() && !getStaticLocalInfo().has_value())
+    return emitOpError(
+        "'static_local_guard' requires 'static_local_info' to be present");
+
   if (getTlsRefs()) {
     if (getStaticLocalGuard().has_value())
       return emitOpError("cannot have both static local and tls references");
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index b5e00eb331903a..353d3d6c4614b5 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -1365,7 +1365,12 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
   // CIRGen, so this pass does not need a live ASTContext to read them.
   std::optional<cir::StaticLocalInfoAttr> infoOption =
       globalOp.getStaticLocalInfo();
-  assert(infoOption.has_value());
+  // Verified IR guarantees this
+  if (!infoOption.has_value()) {
+    globalOp->emitError(
+        "static-local global with a guard is missing 'static_local_info'");
+    return;
+  }
   cir::StaticLocalInfoAttr info = infoOption.value();
 
   builder.setInsertionPointAfter(localInitOp);
diff --git a/clang/test/CIR/IR/invalid-static-local-info.cir b/clang/test/CIR/IR/invalid-static-local-info.cir
new file mode 100644
index 00000000000000..db866e5d8d9ff6
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-static-local-info.cir
@@ -0,0 +1,13 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+// A guarded static-local relies on 'static_local_info' during LoweringPrepare,
+// so the GlobalOp verifier rejects a 'static_local_guard' without it. This
+// guards the static-local lowering path against hand-written or serialized
+// .cir that would otherwise crash lowering with a missing info attribute.
+
+!s32i = !cir.int<s, 32>
+
+module {
+  // expected-error at +1 {{'static_local_guard' requires 'static_local_info' to be present}}
+  cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
+}
diff --git a/clang/test/CIR/IR/invalid-static-local.cir b/clang/test/CIR/IR/invalid-static-local.cir
index 2a4d22d9ceae0d..ee8d188c58f769 100644
--- a/clang/test/CIR/IR/invalid-static-local.cir
+++ b/clang/test/CIR/IR/invalid-static-local.cir
@@ -23,6 +23,7 @@ module {
 
 // Global is marked static_local_guard, but get_global is not static_local
 cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 
 cir.func @test_static_local_mismatch_reverse() {
   // expected-error @below {{static_local attribute mismatch}}
@@ -40,6 +41,7 @@ module {
 
 // local_init is both static_local and thread_local
 cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 
 cir.func @test_static_local_and_tls() {
   %0 = cir.get_global static_local @_ZZ1fvE1y : !cir.ptr<!s32i>
@@ -63,6 +65,7 @@ module {
 
 // local_init not at function scope.
 cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 
 cir.global "private" internal @_AnotherGlobal = ctor : !s32i {
   // expected-error @below {{'cir.local_init' op expects ancestor op 'cir.func'}}
diff --git a/clang/test/CIR/IR/invalid-tls.cir b/clang/test/CIR/IR/invalid-tls.cir
index 99bd87d235f5b7..27dbe07d0bcc09 100644
--- a/clang/test/CIR/IR/invalid-tls.cir
+++ b/clang/test/CIR/IR/invalid-tls.cir
@@ -18,6 +18,7 @@ module {
 module {
   // expected-error at +1{{op cannot have both static local and tls references}}
 cir.global "private" internal tls_model = tls_dyn tls_refs = <"asdf", "asdf", "asdf"> static_local_guard<"asdf"> @_ZZ1fvE1y : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared>}
 }
 
 // -----
diff --git a/clang/test/CIR/IR/static-local.cir b/clang/test/CIR/IR/static-local.cir
index e0623bbdf09bae..41f76bb58a6f5c 100644
--- a/clang/test/CIR/IR/static-local.cir
+++ b/clang/test/CIR/IR/static-local.cir
@@ -4,17 +4,25 @@
 
 module {
 
-// Test static_local_guard attribute on global and static_local on get_global
+// Test static_local_guard attribute on global and static_local on get_global.
+// The GlobalOp verifier requires static_local_guard and static_local_info to
+// be paired, matching what CIRGen emits, so each guarded global also carries
+// the info attribute here.
 cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 // CHECK: cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
 
 cir.global "private" internal static_local_guard<"_HasInitGuard"> @_HasInit : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 // CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard"> @_HasInit : !s32i
 cir.global "private" internal static_local_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 // CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
 cir.global "private" internal static_local_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 // CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
 cir.global "private" internal static_local_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
+  {static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>}
 // CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
 
 cir.func @test_static_local() {



More information about the cfe-commits mailing list