Skip to content

Conversation

@cooperp
Copy link
Contributor

@cooperp cooperp commented Dec 22, 2025

This attribute is currently only permitted on functions, but sometimes it can be useful to opt out a specific local variable. That way the function can still get a stack protector if other locals require it, but locals which are opted out won't have the overhead of a stack protector if not needed.

This is paired with an LLVM change which has already landed, which uses the new metadata to opt specific alloca instructions out of requiring a stack protector.

This attribute is currently only permitted on functions, but sometimes it can
be useful to opt out a specific local variable.  That way the function can still
get a stack protector if other locals require it, but locals which are opted out
won't have the overhead of a stack protector if not needed.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Dec 22, 2025
@cooperp cooperp requested a review from ahatanak December 22, 2025 21:30
@llvmbot
Copy link
Member

llvmbot commented Dec 22, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: None (cooperp)

Changes

This attribute is currently only permitted on functions, but sometimes it can be useful to opt out a specific local variable. That way the function can still get a stack protector if other locals require it, but locals which are opted out won't have the overhead of a stack protector if not needed.

This is paired with an LLVM change which has already landed, which uses the new metadata to opt specific alloca instructions out of requiring a stack protector.


Full diff: https://github.com/llvm/llvm-project/pull/173311.diff

5 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+1-1)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+8)
  • (added) clang/test/CodeGen/stack-protector-vars.c (+25)
  • (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+1-1)
  • (modified) clang/test/Sema/no_stack_protector.c (+6-1)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f85d2da21eab9..10cb3c64f54d2 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2726,7 +2726,7 @@ def : MutualExclusions<[AlwaysInline, NotTailCalled]>;
 def NoStackProtector : InheritableAttr {
   let Spellings = [Clang<"no_stack_protector">, CXX11<"gnu", "no_stack_protector">,
                    C23<"gnu", "no_stack_protector">, Declspec<"safebuffers">];
-  let Subjects = SubjectList<[Function]>;
+  let Subjects = SubjectList<[Function, LocalVar]>;
   let Documentation = [NoStackProtectorDocs];
   let SimpleHandler = 1;
 }
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 8b1cd83af2396..ae8ca92d8860f 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1604,6 +1604,14 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) {
                                  allocaAlignment, D.getName(),
                                  /*ArraySize=*/nullptr, &AllocaAddr);
 
+      if (D.hasAttr<NoStackProtectorAttr>()) {
+        if (auto *AI = dyn_cast<llvm::AllocaInst>(address.getBasePointer())) {
+          llvm::LLVMContext &Ctx = Builder.getContext();
+          auto *Operand = llvm::ConstantAsMetadata::get(Builder.getInt32(0));
+          AI->setMetadata("stack-protector", llvm::MDNode::get(Ctx, {Operand}));
+        }
+      }
+
       // Don't emit lifetime markers for MSVC catch parameters. The lifetime of
       // the catch parameter starts in the catchpad instruction, and we can't
       // insert code in those basic blocks.
diff --git a/clang/test/CodeGen/stack-protector-vars.c b/clang/test/CodeGen/stack-protector-vars.c
new file mode 100644
index 0000000000000..218cdc33b3010
--- /dev/null
+++ b/clang/test/CodeGen/stack-protector-vars.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+
+typedef __SIZE_TYPE__ size_t;
+
+int printf(const char * _Format, ...);
+char *strcpy(char *s1, const char *s2);
+
+// CHECK: define {{.*}}void @test1
+// CHECK: %a = alloca [1000 x i8], align 1, !stack-protector ![[A:.*]]
+void test1(const char *msg) {
+  __attribute__((no_stack_protector))
+  char a[1000];
+  strcpy(a, msg);
+  printf("%s\n", a);
+}
+
+// CHECK: define {{.*}}void @test2
+// CHECK-NOT: %b = alloca [1000 x i8], align 1, !stack-protector
+void test2(const char *msg) {
+  char b[1000];
+  strcpy(b, msg);
+  printf("%s\n", b);
+}
+
+// CHECK: ![[A]] = !{i32 0}
\ No newline at end of file
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 3114d2fc9e693..6420a298f4dc9 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -138,7 +138,7 @@
 // CHECK-NEXT: NoSanitizeThread (SubjectMatchRule_function)
 // CHECK-NEXT: NoSpeculativeLoadHardening (SubjectMatchRule_function, SubjectMatchRule_objc_method)
 // CHECK-NEXT: NoSplitStack (SubjectMatchRule_function)
-// CHECK-NEXT: NoStackProtector (SubjectMatchRule_function)
+// CHECK-NEXT: NoStackProtector (SubjectMatchRule_function, SubjectMatchRule_variable_is_local)
 // CHECK-NEXT: NoThreadSafetyAnalysis (SubjectMatchRule_function)
 // CHECK-NEXT: NoThrow (SubjectMatchRule_hasType_functionType)
 // CHECK-NEXT: NoUwtable (SubjectMatchRule_hasType_functionType)
diff --git a/clang/test/Sema/no_stack_protector.c b/clang/test/Sema/no_stack_protector.c
index 1ecd46bc624ce..fab3df667883a 100644
--- a/clang/test/Sema/no_stack_protector.c
+++ b/clang/test/Sema/no_stack_protector.c
@@ -4,5 +4,10 @@
 [[clang::no_stack_protector]] void test2(void) {}
 
 void __attribute__((no_stack_protector)) foo(void) {}
-int __attribute__((no_stack_protector)) var; // expected-warning {{'no_stack_protector' attribute only applies to functions}}
+int __attribute__((no_stack_protector)) var; // expected-warning {{'no_stack_protector' attribute only applies to functions and local variables}}
 void  __attribute__((no_stack_protector(2))) bar(void) {} // expected-error {{'no_stack_protector' attribute takes no arguments}}
+
+void func()
+{
+	int __attribute__((no_stack_protector)) localvar;
+}
\ No newline at end of file

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 85209 tests passed
  • 1191 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.CodeGen/stack-protector-vars.c
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/22/include -nostdsysteminc -emit-llvm -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/22/include -nostdsysteminc -emit-llvm -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c
# note: command had no output on stdout or stderr
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c:9:11: error: CHECK: expected string not found in input
# | // CHECK: %a = alloca [1000 x i8], align 1, !stack-protector ![[A:.*]]
# |           ^
# | <stdin>:9:29: note: scanning from here
# | define dso_local void @test1(ptr noundef %msg) #0 {
# |                             ^
# | <stdin>:12:2: note: possible intended match here
# |  %a = alloca [1000 x i8], align 16, !stack-protector !2
# |  ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |            1: ; ModuleID = '/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c' 
# |            2: source_filename = "/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGen/stack-protector-vars.c" 
# |            3: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" 
# |            4: target triple = "x86_64-unknown-linux-gnu" 
# |            5:  
# |            6: @.str = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1 
# |            7:  
# |            8: ; Function Attrs: noinline nounwind optnone 
# |            9: define dso_local void @test1(ptr noundef %msg) #0 { 
# | check:9'0                                 X~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |           10: entry: 
# | check:9'0     ~~~~~~~
# |           11:  %msg.addr = alloca ptr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           12:  %a = alloca [1000 x i8], align 16, !stack-protector !2 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | check:9'1      ?                                                       possible intended match
# |           13:  store ptr %msg, ptr %msg.addr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           14:  %arraydecay = getelementptr inbounds [1000 x i8], ptr %a, i64 0, i64 0 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           15:  %0 = load ptr, ptr %msg.addr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           16:  %call = call ptr @strcpy(ptr noundef %arraydecay, ptr noundef %0) #3 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           17:  %arraydecay1 = getelementptr inbounds [1000 x i8], ptr %a, i64 0, i64 0 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            .
# |            .
# |            .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@github-actions
Copy link

🪟 Windows x64 Test Results

  • 51627 tests passed
  • 889 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.CodeGen/stack-protector-vars.c
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang.exe -cc1 -internal-isystem C:\_work\llvm-project\llvm-project\build\lib\clang\22\include -nostdsysteminc -emit-llvm -o - C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\clang.exe' -cc1 -internal-isystem 'C:\_work\llvm-project\llvm-project\build\lib\clang\22\include' -nostdsysteminc -emit-llvm -o - 'C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c'
# note: command had no output on stdout or stderr
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c:9:11: error: CHECK: expected string not found in input
# | // CHECK: %a = alloca [1000 x i8], align 1, !stack-protector ![[A:.*]]
# |           ^
# | <stdin>:11:29: note: scanning from here
# | define dso_local void @test1(ptr noundef %msg) #0 {
# |                             ^
# | <stdin>:14:2: note: possible intended match here
# |  %a = alloca [1000 x i8], align 16, !stack-protector !6
# |  ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\clang\test\CodeGen\stack-protector-vars.c
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |            .
# |            .
# |            .
# |            6: $"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = comdat any 
# |            7:  
# |            8: @"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [4 x i8] c"%s\0A\00", comdat, align 1 
# |            9:  
# |           10: ; Function Attrs: noinline nounwind optnone 
# |           11: define dso_local void @test1(ptr noundef %msg) #0 { 
# | check:9'0                                 X~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |           12: entry: 
# | check:9'0     ~~~~~~~
# |           13:  %msg.addr = alloca ptr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           14:  %a = alloca [1000 x i8], align 16, !stack-protector !6 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | check:9'1      ?                                                       possible intended match
# |           15:  store ptr %msg, ptr %msg.addr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           16:  %0 = load ptr, ptr %msg.addr, align 8 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           17:  %arraydecay = getelementptr inbounds [1000 x i8], ptr %a, i64 0, i64 0 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           18:  %call = call ptr @strcpy(ptr noundef %arraydecay, ptr noundef %0) #3 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           19:  %arraydecay1 = getelementptr inbounds [1000 x i8], ptr %a, i64 0, i64 0 
# | check:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            .
# |            .
# |            .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants