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 <[email protected]>
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@+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@+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() {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to