Skip to content

[clang][PAC] add support for options parameter to __ptrauth #136828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented Apr 23, 2025

[clang][PAC] add support for options parameter to __ptrauth

This PR adds support for an 'options' parameter for the __ptrauth
qualifier.

The initial version only exposes the authehntication modes:

  • "strip"
  • "sign-and-strip"
  • "sign-and-auth"

We also support parsing the options but not yet the implementation

  • "isa-pointer"
  • "authenticates-null-values"

The initial support for authentication mode controls exist to support
ABI changes over time, and as a byproduct support basic initial tests
for option parsing.

@ojhunt ojhunt requested review from erichkeane, ahatanak, cor3ntin and AaronBallman and removed request for erichkeane April 23, 2025 08:49
Copy link

github-actions bot commented Apr 23, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp,c,h -- clang/test/CodeGen/ptrauth-stripping.c clang/test/Sema/ptrauth-qualifier-options.c clang/test/SemaCXX/ptrauth-qualifier-constexpr-options.cpp clang/include/clang/Basic/LangOptions.h clang/lib/AST/TypePrinter.cpp clang/lib/CodeGen/CGExprConstant.cpp clang/lib/Parse/ParseDecl.cpp clang/lib/Sema/SemaType.cpp clang/test/Parser/ptrauth-qualifier.c clang/test/Sema/ptrauth-qualifier.c
View the diff from clang-format here.
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d1fdedc76..4f7c9f167 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8399,7 +8399,8 @@ static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
     };
     auto DiagnoseInvalidOptionsParameter = [&](llvm::StringRef Reason) {
       S.Diag(AuthenticationOptionsRange.getBegin(),
-             diag::err_ptrauth_invalid_option) << Reason;
+             diag::err_ptrauth_invalid_option)
+          << Reason;
       Attr.setInvalid();
       IsInvalid = true;
       ReportEvaluationOfExpressionIfNeeded();
@@ -8494,7 +8495,7 @@ static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
                   StringRef LeadingOption = Option.slice(0, WhitespaceIndex);
                   S.Diag(AuthenticationOptionsRange.getBegin(),
                          diag::err_ptrauth_option_missing_comma)
-                        << LeadingOption;
+                      << LeadingOption;
                 } else {
                   S.Diag(AuthenticationOptionsRange.getBegin(),
                          diag::err_ptrauth_unknown_authentication_option)

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 23, 2025

The tests in this PR are dependent on #136828, and the included tests have non-sequential numbers as the tests include other options that are not supported by this PR, to try and limit the complexity of this PR to just the parsing and existing functionality.

return;
}
OptionsString = OptionsStringLiteral->getString();
} else if (S.EvaluateAsString(AuthenticationOptionsArg, OptionsString, S.Context, Sema::StringEvaluationContext::PtrauthOptions, /*ErrorOnInvalidMessage=*/false)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation I was upstreaming had a very very restricted version of @cor3ntin's constant evaluated string, so I have adopted that.

bool HasSeenOption = false;
unsigned CurrentIdx = 0;

auto OptionStringIdxLocation = [&](unsigned Idx) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is moderately concise parser of the grammar:

option :- [a-z-]+
options :- option (',' option)*

The reason for the complexity - vs. just a split(',').map(trim) - is just error messaging. We could simplify if people think it's warranted, but the error message tests are already reduced from what it could be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, if there is an error in the nth item, recompute the location by finding the nth comma and point to that. I feel like more granularity than that adds more complexity than it is worth.

return std::make_pair(OptionStartIdx, OptionEndIdx);
};

auto OptionHandler = [&](StringRef TokenStr, SourceRange TokenRange,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more generic than is strictly needed at this point, because future PRs add additional option types and flags

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would much prefer a 3 if/else :)

"__ptrauth qualifier takes between 1 and 3 arguments");
assert((Attr.getNumArgs() > 0 && Attr.getNumArgs() <= 4) &&
"__ptrauth qualifier takes between 1 and 4 arguments");
StringRef AttrName = Attr.getAttrName()->getName();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AttrName is used because the diagnostics need the actual attribute name, and there's a future PR to add the __ptrauth_restricted_intptr qualifier which means it is not a constant. We can remove AttrName now, but it is immediately needed in the next PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

following the __ptrauth for ints PR __ptrauth_restricted_intptr is gone, I thought I had resolved all of these

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 23, 2025

This PR is a draft as it will need to be updated to merged correctly, and verify test output (conflicts, unintentional mismatches coming from splitting out the PR) after #136783

Comment on lines 8423 to 8429
for (char Ch : OptionsString) {
if (!Ch || !isascii(Ch)) {
DiagnoseInvalidOptionsParameter(
"contains invalid characters", Ch,
AuthenticationOptionsArg->getSourceRange());
return;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need that? we can just say "\0签名" is not a valid option

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests with a string-view-like type? I would not be surprised if we need more changes in the attribute parsing code to be able to have string_view as StringArgument argument

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cor3ntin re: string_view: I'm trying not to add anything beyond the bare minimum here, I thought the existing behavior of EvaluateString is a superset of what we know existing code requires so I didn't want to trying to add more complexity to a pile of already complex PRs, that said it looks like I haven't pulled those specific tests across so I could be wrong.

Re: non-ascii: I'm not sure I understand the semantics of StringLiteral::toString and EvaluateString(...) if presented w.r.t utf8 or wide chars so I wanted consistent behavior between them, and the easiest way to ensure that was an early out on non-ascii. StringLiteral::getString() specifically has an assertion specifically about expecting char sized characters which was worrying to me. If that assertion is actually trying to say sizeof(string code point)==sizeof(char) then that's much simpler.

bool HasSeenOption = false;
unsigned CurrentIdx = 0;

auto OptionStringIdxLocation = [&](unsigned Idx) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, if there is an error in the nth item, recompute the location by finding the nth comma and point to that. I feel like more granularity than that adds more complexity than it is worth.

Comment on lines 8412 to 8417
if (OptionsStringLiteral->containsNonAsciiOrNull()) {
DiagnoseInvalidOptionsParameter(
"contains invalid characters", std::nullopt,
AuthenticationOptionsArg->getSourceRange());
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as below, if we accept exactly 3 strings, we don;t need to check anything else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more than just those three strings, but adding the additional ones implies the ability to test them, which means pulling in the implementation of those options.

I went for this specific subset because PointerAuthQualifier already has the required functionality, and so this PR could be very close to just the parsing.

Comment on lines 8404 to 8410
if (AuthenticationOptionsArg->isValueDependent() ||
AuthenticationOptionsArg->isTypeDependent()) {
DiagnoseInvalidOptionsParameter(
"is dependent", std::nullopt,
AuthenticationOptionsArg->getSourceRange());
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if that can just be delayed to later? Maybe a separate patch though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this somewhat ad hoc, the behavior I was upstreaming basically just did if (StringLiteral) { ... } else if (const eval produces a char* + length) { ... }, which just happens to exclude dependent values, but EvaluateString has an assertion that the values are not dependent. That might be something we want to fix in future, but it seemed that the sensible thing at the moment was just adding a dependent value/type check as we have no support for dependent ptrauth qualifiers :-/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cor3ntin oh, when you say delayed until later do you mean template instantiation? That would be nice but currently the ptrauth qualifier does not support dependent parameters at all - it's something I've wanted to add support for (because it would be very useful) but is never high enough on the priority list, and seems to require an inordinate amount of seemingly mechanical work to achieve :-/

return std::make_pair(OptionStartIdx, OptionEndIdx);
};

auto OptionHandler = [&](StringRef TokenStr, SourceRange TokenRange,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would much prefer a 3 if/else :)

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 23, 2025

@cor3ntin the option selection down stream is a StringSwitch on the authentication mode, and then a sequence of (not large, I think just two) if (option == constant) checks to set up specific options.

Previous PRs have suggested maps, etc for such conditions, and a map of option=>handler seemed consistent with that in this case, but I'm really on the fence about which. Absent the map approach (just typing directly into the comment w/o checking code) I think the alternative is that the option handler looks like:

switchswitch(option)
.Case("strip", ...)
.Case("sign-and-strip", ...)
.Case("sign-and-auth", ...);
// 'else' here I think is handled by a flag
else if (option == thing1) { .... }
else if (option == thing2) { ... }

Any thoughts on which you consider preferable? I think either works as the gross portion of this code is already just the tokenisation.

@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-options branch 4 times, most recently from d32a1bd to d51ed1b Compare May 3, 2025 04:50
@ojhunt ojhunt self-assigned this May 5, 2025
@ojhunt ojhunt changed the title [clang][ptrauth] add support for options parameter to __ptrauth [clang][PAC] add support for options parameter to __ptrauth May 9, 2025
@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-options branch 4 times, most recently from d28aa40 to 6f161d3 Compare May 10, 2025 04:57
@@ -65,6 +65,17 @@ enum class PointerAuthenticationMode : unsigned {
SignAndAuth
};

static constexpr llvm::StringLiteral PointerAuthenticationOptionStrip = "strip";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cor3ntin @AaronBallman is there a better/more idiomatic way of doing these?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the language option level, I think it's more idiomatic to use an enumeration instead of string literals and doing string processing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest doing a string-switch on it, then storing an enum in the AST. We can then print it/whatever on the way out. But a pair of conversion functions is typically all you should need. Though, it doesn't seem that these are leaving the handle function anyway, so IDK.

Either way, LangOpts is a little bit of an odd place for this to live.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not stored as a string (or even an attribute), they're stored as flags and enums in the pointer auth qualifier on the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was meaning "is there a more idiomatic way to have these string constants specified?" :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I hadn't included the updated to stringifying the qualifier so these were only used in one place.

} else {
Expr::EvalResult Eval;
bool Result = AuthenticationOptionsArg->EvaluateAsRValue(Eval, Ctx);
if (Result && Eval.Val.isLValue()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section can be replaced once #135864 or similar is landed as it supports const char* constant expressions

@ojhunt ojhunt marked this pull request as ready for review May 10, 2025 06:01
@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 May 10, 2025
@llvmbot
Copy link
Member

llvmbot commented May 10, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

[clang][PAC] add support for options parameter to __ptrauth

This PR adds support for an 'options' parameter for the __ptrauth
qualifier.

The initial version only exposes the authehntication modes:

  • "strip"
  • "sign-and-strip"
  • "sign-and-auth"

We also support parsing the options but not yet the implementation

  • "isa-pointer"
  • "authenticates-null-values"

The initial support for authentication mode controls exist to support
ABI changes over time, and as a byproduct support basic initial tests
for option parsing.


Patch is 44.28 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/136828.diff

12 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+3-3)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+2-2)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+17-1)
  • (modified) clang/include/clang/Basic/LangOptions.h (+11)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+17-7)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaType.cpp (+150-9)
  • (added) clang/test/CodeGen/ptrauth-stripping.c (+327)
  • (modified) clang/test/Parser/ptrauth-qualifier.c (+1-1)
  • (added) clang/test/Sema/ptrauth-qualifier-options.c (+65)
  • (modified) clang/test/Sema/ptrauth-qualifier.c (+33-6)
  • (added) clang/test/SemaCXX/ptrauth-qualifier-constexpr-options.cpp (+65)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 37c80ac90182c..1941bb2d1febc 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3595,9 +3595,9 @@ def ObjCRequiresPropertyDefs : InheritableAttr {
 
 def PointerAuth : TypeAttr {
   let Spellings = [CustomKeyword<"__ptrauth">];
-  let Args = [IntArgument<"Key">,
-              BoolArgument<"AddressDiscriminated", 1>,
-              IntArgument<"ExtraDiscriminator", 1>];
+  let Args = [IntArgument<"Key">, BoolArgument<"AddressDiscriminated", 1>,
+              IntArgument<"ExtraDiscriminator", 1>,
+              StringArgument<"Options", 1>];
   let Documentation = [PtrAuthDocs];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 3bbdc49946dac..8e0f818714c42 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1735,8 +1735,8 @@ def warn_pragma_unroll_cuda_value_in_parens : Warning<
   "argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
   InGroup<CudaCompat>;
 
-def err_ptrauth_qualifier_bad_arg_count : Error<
-  "'__ptrauth' qualifier must take between 1 and 3 arguments">;
+def err_ptrauth_qualifier_bad_arg_count
+    : Error<"'__ptrauth' qualifier must take between 1 and 4 arguments">;
 
 def warn_cuda_attr_lambda_position : Warning<
   "nvcc does not allow '__%0__' to appear after the parameter list in lambdas">,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 89b2d664d66a0..0ae2c09b1e4fb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1045,6 +1045,23 @@ def err_ptrauth_extra_discriminator_invalid : Error<
   "invalid extra discriminator flag '%0'; '__ptrauth' requires a value between "
   "'0' and '%1'">;
 
+// __ptrauth qualifier options string
+def note_ptrauth_evaluating_options
+    : Note<"options parameter evaluated to '%0'">;
+def err_ptrauth_invalid_option : Error<"'%0' options parameter %1">;
+def err_ptrauth_unknown_authentication_option
+    : Error<"unknown '%0' authentication option '%1'">;
+def err_ptrauth_repeated_authentication_option
+    : Error<"repeated '%0' authentication %select{mode|option}1%select{, prior mode was '%3'| '%2'}1">;
+def note_ptrauth_previous_authentication_option
+    : Note<"previous '%0' authentication %select{mode|option}1">;
+def err_ptrauth_unexpected_option_end
+    : Error<"unexpected end of options parameter for %0">;
+def err_ptrauth_option_unexpected_token
+    : Error<"unexpected character '%0' in '%1' options">;
+def err_ptrauth_option_missing_comma
+    : Error<"missing comma after '%1' option in '%0' qualifier">;
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -1735,7 +1752,6 @@ def err_static_assert_requirement_failed : Error<
 def note_expr_evaluates_to : Note<
   "expression evaluates to '%0 %1 %2'">;
 
-
 def subst_user_defined_msg : TextSubstitution<
   "%select{the message|the expression}0 in "
   "%select{a static assertion|this asm operand}0">;
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 491e8bee9fd5c..3944946374d30 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -65,6 +65,17 @@ enum class PointerAuthenticationMode : unsigned {
   SignAndAuth
 };
 
+static constexpr llvm::StringLiteral PointerAuthenticationOptionStrip = "strip";
+static constexpr llvm::StringLiteral PointerAuthenticationOptionSignAndStrip =
+    "sign-and-strip";
+static constexpr llvm::StringLiteral PointerAuthenticationOptionSignAndAuth =
+    "sign-and-auth";
+static constexpr llvm::StringLiteral PointerAuthenticationOptionIsaPointer =
+    "isa-pointer";
+static constexpr llvm::StringLiteral
+    PointerAuthenticationOptionAuthenticatesNullValues =
+        "authenticates-null-values";
+
 /// Bitfields of LangOptions, split out from LangOptions in order to ensure that
 /// this large collection of bitfields is a trivial class type.
 class LangOptionsBase {
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 6ba45f42db4d1..1de3d8a181343 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2129,6 +2129,13 @@ class ConstantLValueEmitter : public ConstStmtVisitor<ConstantLValueEmitter,
 
 }
 
+static bool shouldSignPointer(const PointerAuthQualifier &PointerAuth) {
+  PointerAuthenticationMode AuthenticationMode =
+      PointerAuth.getAuthenticationMode();
+  return AuthenticationMode == PointerAuthenticationMode::SignAndStrip ||
+         AuthenticationMode == PointerAuthenticationMode::SignAndAuth;
+}
+
 llvm::Constant *ConstantLValueEmitter::tryEmit() {
   const APValue::LValueBase &base = Value.getLValueBase();
 
@@ -2162,7 +2169,8 @@ llvm::Constant *ConstantLValueEmitter::tryEmit() {
 
   // Apply pointer-auth signing from the destination type.
   if (PointerAuthQualifier PointerAuth = DestType.getPointerAuth();
-      PointerAuth && !result.HasDestPointerAuth) {
+      PointerAuth && !result.HasDestPointerAuth &&
+      shouldSignPointer(PointerAuth)) {
     value = Emitter.tryEmitConstantSignedPointer(value, PointerAuth);
     if (!value)
       return nullptr;
@@ -2210,8 +2218,9 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
     if (D->hasAttr<WeakRefAttr>())
       return CGM.GetWeakRefReference(D).getPointer();
 
-    auto PtrAuthSign = [&](llvm::Constant *C) {
-      if (PointerAuthQualifier PointerAuth = DestType.getPointerAuth()) {
+    auto PtrAuthSign = [&](llvm::Constant *C, bool IsFunction) {
+      if (PointerAuthQualifier PointerAuth = DestType.getPointerAuth();
+          PointerAuth && shouldSignPointer(PointerAuth)) {
         C = applyOffset(C);
         C = Emitter.tryEmitConstantSignedPointer(C, PointerAuth);
         return ConstantLValue(C, /*applied offset*/ true, /*signed*/ true);
@@ -2219,7 +2228,7 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
 
       CGPointerAuthInfo AuthInfo;
 
-      if (EnablePtrAuthFunctionTypeDiscrimination)
+      if (IsFunction && EnablePtrAuthFunctionTypeDiscrimination)
         AuthInfo = CGM.getFunctionPointerAuthInfo(DestType);
 
       if (AuthInfo) {
@@ -2237,17 +2246,18 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
     };
 
     if (const auto *FD = dyn_cast<FunctionDecl>(D))
-      return PtrAuthSign(CGM.getRawFunctionPointer(FD));
+      return PtrAuthSign(CGM.getRawFunctionPointer(FD), /*IsFunction=*/true);
 
     if (const auto *VD = dyn_cast<VarDecl>(D)) {
       // We can never refer to a variable with local storage.
       if (!VD->hasLocalStorage()) {
         if (VD->isFileVarDecl() || VD->hasExternalStorage())
-          return CGM.GetAddrOfGlobalVar(VD);
+          return PtrAuthSign(CGM.GetAddrOfGlobalVar(VD), /*IsFunction=*/false);
 
         if (VD->isLocalVarDecl()) {
-          return CGM.getOrCreateStaticVarDecl(
+          llvm::Constant *C = CGM.getOrCreateStaticVarDecl(
               *VD, CGM.getLLVMLinkageVarDefinition(VD));
+          return PtrAuthSign(C, /*IsFunction=*/false);
         }
       }
     }
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 4fe3565687905..259ce5029271e 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -3428,7 +3428,7 @@ void Parser::ParsePtrauthQualifier(ParsedAttributes &Attrs) {
   T.consumeClose();
   SourceLocation EndLoc = T.getCloseLocation();
 
-  if (ArgExprs.empty() || ArgExprs.size() > 3) {
+  if (ArgExprs.empty() || ArgExprs.size() > 4) {
     Diag(KwLoc, diag::err_ptrauth_qualifier_bad_arg_count);
     return;
   }
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a8e85c885069e..654ac4d903f51 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8350,14 +8350,16 @@ static void HandleNeonVectorTypeAttr(QualType &CurType, const ParsedAttr &Attr,
 /// Handle the __ptrauth qualifier.
 static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
                                    const ParsedAttr &Attr, Sema &S) {
-
-  assert((Attr.getNumArgs() > 0 && Attr.getNumArgs() <= 3) &&
-         "__ptrauth qualifier takes between 1 and 3 arguments");
+  assert((Attr.getNumArgs() > 0 && Attr.getNumArgs() <= 4) &&
+         "__ptrauth qualifier takes between 1 and 4 arguments");
+  StringRef AttrName = Attr.getAttrName()->getName();
   Expr *KeyArg = Attr.getArgAsExpr(0);
   Expr *IsAddressDiscriminatedArg =
       Attr.getNumArgs() >= 2 ? Attr.getArgAsExpr(1) : nullptr;
   Expr *ExtraDiscriminatorArg =
       Attr.getNumArgs() >= 3 ? Attr.getArgAsExpr(2) : nullptr;
+  Expr *AuthenticationOptionsArg =
+      Attr.getNumArgs() >= 4 ? Attr.getArgAsExpr(3) : nullptr;
 
   unsigned Key;
   if (S.checkConstantPointerAuthKey(KeyArg, Key)) {
@@ -8373,10 +8375,140 @@ static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
                                                    IsAddressDiscriminated);
   IsInvalid |= !S.checkPointerAuthDiscriminatorArg(
       ExtraDiscriminatorArg, PointerAuthDiscArgKind::Extra, ExtraDiscriminator);
+  std::string LastAuthenticationMode;
+  std::optional<PointerAuthenticationMode> AuthenticationMode = std::nullopt;
+  bool IsIsaPointer = false;
+  bool AuthenticatesNullValues = false;
+
+  if (AuthenticationOptionsArg && !AuthenticationOptionsArg->containsErrors()) {
+    StringRef OptionsString;
+    std::string EvaluatedString;
+    bool HasEvaluatedOptionsString = false;
+    const StringLiteral *OptionsStringLiteral =
+        dyn_cast<StringLiteral>(AuthenticationOptionsArg);
+    SourceRange AuthenticationOptionsRange =
+        AuthenticationOptionsArg->getSourceRange();
+    bool ReportedEvaluation = false;
+    auto ReportEvaluationOfExpressionIfNeeded = [&]() {
+      if (OptionsStringLiteral || !HasEvaluatedOptionsString ||
+          ReportedEvaluation)
+        return;
+      ReportedEvaluation = true;
+      S.Diag(AuthenticationOptionsRange.getBegin(),
+             diag::note_ptrauth_evaluating_options)
+          << OptionsString << AuthenticationOptionsRange;
+    };
+    auto DiagnoseInvalidOptionsParameter = [&](llvm::StringRef Reason) {
+      S.Diag(AuthenticationOptionsRange.getBegin(),
+             diag::err_ptrauth_invalid_option)
+          << AttrName << Reason;
+      Attr.setInvalid();
+      IsInvalid = true;
+      ReportEvaluationOfExpressionIfNeeded();
+    };
+    if (AuthenticationOptionsArg->isValueDependent() ||
+        AuthenticationOptionsArg->isTypeDependent()) {
+      DiagnoseInvalidOptionsParameter("is dependent");
+      return;
+    }
+    if (OptionsStringLiteral) {
+      OptionsString = OptionsStringLiteral->getString();
+      HasEvaluatedOptionsString = true;
+    } else {
+      Expr::EvalResult Eval;
+      bool Result = AuthenticationOptionsArg->EvaluateAsRValue(Eval, Ctx);
+      if (Result && Eval.Val.isLValue()) {
+        auto *BaseExpr = Eval.Val.getLValueBase().dyn_cast<const Expr *>();
+        const StringLiteral *EvaluatedStringLiteral =
+            dyn_cast<StringLiteral>(const_cast<Expr *>(BaseExpr));
+        if (EvaluatedStringLiteral) {
+          CharUnits StartOffset = Eval.Val.getLValueOffset();
+          EvaluatedString = EvaluatedStringLiteral->getString().drop_front(
+              StartOffset.getQuantity());
+          OptionsString = EvaluatedString;
+          HasEvaluatedOptionsString = true;
+        }
+      }
+    }
+    if (!HasEvaluatedOptionsString) {
+      DiagnoseInvalidOptionsParameter(
+          "must be a string of comma separated flags");
+      return;
+    }
+    for (char Ch : OptionsString) {
+      if (Ch != '-' && Ch != ',' && !isWhitespace(Ch) && !isalpha(Ch)) {
+        DiagnoseInvalidOptionsParameter("contains invalid characters");
+        return;
+      }
+    }
+    HasEvaluatedOptionsString = true;
+    OptionsString = OptionsString.trim();
+    llvm::SmallVector<StringRef> Options;
+    if (!OptionsString.empty())
+      OptionsString.split(Options, ',');
+
+    auto OptionHandler = [&](auto Value, auto *Option,
+                             std::string *LastOption = nullptr) {
+      return [&, Value, Option, LastOption](StringRef OptionString) {
+        if (!*Option) {
+          *Option = Value;
+          if (LastOption)
+            *LastOption = OptionString;
+          return true;
+        }
+        bool IsAuthenticationMode =
+            std::is_same_v<decltype(Value), PointerAuthenticationMode>;
+        S.Diag(AuthenticationOptionsRange.getBegin(),
+               diag::err_ptrauth_repeated_authentication_option)
+            << AttrName << !IsAuthenticationMode << OptionString
+            << (LastOption ? *LastOption : "");
+        return false;
+      };
+    };
 
-  if (IsInvalid) {
-    Attr.setInvalid();
-    return;
+    for (unsigned Idx = 0; Idx < Options.size(); ++Idx) {
+      StringRef Option = Options[Idx].trim();
+      if (Option.empty()) {
+        bool IsLastOption = Idx == (Options.size() - 1);
+        DiagnoseInvalidOptionsParameter(
+            IsLastOption ? "has a trailing comma" : "contains an empty option");
+        continue;
+      }
+      auto SelectedHandler =
+          llvm::StringSwitch<std::function<bool(StringRef)>>(Option)
+              .Case(PointerAuthenticationOptionStrip,
+                    OptionHandler(PointerAuthenticationMode::Strip,
+                                  &AuthenticationMode, &LastAuthenticationMode))
+              .Case(PointerAuthenticationOptionSignAndStrip,
+                    OptionHandler(PointerAuthenticationMode::SignAndStrip,
+                                  &AuthenticationMode, &LastAuthenticationMode))
+              .Case(PointerAuthenticationOptionSignAndAuth,
+                    OptionHandler(PointerAuthenticationMode::SignAndAuth,
+                                  &AuthenticationMode, &LastAuthenticationMode))
+              .Case(PointerAuthenticationOptionIsaPointer,
+                    OptionHandler(true,
+                                  &IsIsaPointer))
+              .Case(PointerAuthenticationOptionAuthenticatesNullValues,
+                    OptionHandler(true,
+                                  &AuthenticatesNullValues))
+              .Default([&](StringRef Option) {
+                if (size_t WhitespaceIndex =
+                        Option.find_first_of(" \t\n\v\f\r");
+                    WhitespaceIndex != Option.npos) {
+                  StringRef LeadingOption = Option.slice(0, WhitespaceIndex);
+                  S.Diag(AuthenticationOptionsRange.getBegin(),
+                         diag::err_ptrauth_option_missing_comma)
+                      << AttrName << LeadingOption;
+                } else {
+                  S.Diag(AuthenticationOptionsRange.getBegin(),
+                         diag::err_ptrauth_unknown_authentication_option)
+                      << AttrName << Option;
+                }
+                return false;
+              });
+      if (!SelectedHandler(Option))
+        IsInvalid = true;
+    }
   }
 
   if (!T->isSignableType(Ctx) && !T->isDependentType()) {
@@ -8385,6 +8517,9 @@ static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
     return;
   }
 
+  if (!AuthenticationMode)
+    AuthenticationMode = PointerAuthenticationMode::SignAndAuth;
+
   if (T.getPointerAuth()) {
     S.Diag(Attr.getLoc(), diag::err_ptrauth_qualifier_redundant) << T;
     Attr.setInvalid();
@@ -8397,13 +8532,19 @@ static void HandlePtrAuthQualifier(ASTContext &Ctx, QualType &T,
     return;
   }
 
+  if (IsInvalid) {
+    Attr.setInvalid();
+    return;
+  }
+
   assert((!IsAddressDiscriminatedArg || IsAddressDiscriminated <= 1) &&
          "address discriminator arg should be either 0 or 1");
   PointerAuthQualifier Qual = PointerAuthQualifier::Create(
-      Key, IsAddressDiscriminated, ExtraDiscriminator,
-      PointerAuthenticationMode::SignAndAuth, /*IsIsaPointer=*/false,
-      /*AuthenticatesNullValues=*/false);
+      Key, IsAddressDiscriminated, ExtraDiscriminator, *AuthenticationMode,
+      IsIsaPointer, AuthenticatesNullValues);
+  assert(Qual.getAuthenticationMode() == *AuthenticationMode);
   T = S.Context.getPointerAuthType(T, Qual);
+  assert(T.getPointerAuth().getAuthenticationMode() == *AuthenticationMode);
 }
 
 /// HandleArmSveVectorBitsTypeAttr - The "arm_sve_vector_bits" attribute is
diff --git a/clang/test/CodeGen/ptrauth-stripping.c b/clang/test/CodeGen/ptrauth-stripping.c
new file mode 100644
index 0000000000000..4e187d8debdc7
--- /dev/null
+++ b/clang/test/CodeGen/ptrauth-stripping.c
@@ -0,0 +1,327 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -fptrauth-calls -fptrauth-intrinsics -emit-llvm %s -o - | FileCheck %s 
+
+typedef void *NonePointer;
+typedef void *__ptrauth(1, 1, 101, "strip") StripPointer;
+typedef void *__ptrauth(1, 1, 102, "sign-and-strip") SignAndStripPointer;
+typedef void *__ptrauth(1, 1, 103, "sign-and-auth") SignAndAuthPointer;
+typedef __UINT64_TYPE__ NoneIntptr;
+typedef __UINT64_TYPE__ __ptrauth(1, 0, 105, "strip") StripIntptr;
+typedef __UINT64_TYPE__ __ptrauth(1, 0, 106, "sign-and-strip") SignAndStripIntptr;
+typedef __UINT64_TYPE__ __ptrauth(1, 0, 107, "sign-and-auth") SignAndAuthIntptr;
+
+NonePointer globalNonePointer = "foo0";
+StripPointer globalStripPointer = "foo1";
+SignAndStripPointer globalSignAndStripPointer = "foo2";
+SignAndAuthPointer globalSignAndAuthPointer = "foo3";
+NoneIntptr globalNoneIntptr = (__UINT64_TYPE__)&globalNonePointer;
+StripIntptr globalStripIntptr = (__UINT64_TYPE__)&globalStripPointer;
+SignAndStripIntptr globalSignAndStripIntptr = (__UINT64_TYPE__)&globalSignAndStripPointer;
+SignAndAuthIntptr globalSignAndAuthIntptr = (__UINT64_TYPE__)&globalSignAndAuthPointer;
+
+// CHECK: @.str = private unnamed_addr constant [5 x i8] c"foo0\00", align 1
+// CHECK: @globalNonePointer = global ptr @.str, align 8
+// CHECK: @.str.1 = private unnamed_addr constant [5 x i8] c"foo1\00", align 1
+// CHECK: @globalStripPointer = global ptr @.str.1, align 8
+// CHECK: @.str.2 = private unnamed_addr constant [5 x i8] c"foo2\00", align 1
+// CHECK: @globalSignAndStripPointer = global ptr ptrauth (ptr @.str.2, i32 1, i64 102, ptr @globalSignAndStripPointer), align 8
+// CHECK: @.str.3 = private unnamed_addr constant [5 x i8] c"foo3\00", align 1
+// CHECK: @globalSignAndAuthPointer = global ptr ptrauth (ptr @.str.3, i32 1, i64 103, ptr @globalSignAndAuthPointer), align 8
+// CHECK: @globalNoneIntptr = global i64 ptrtoint (ptr @globalNonePointer to i64), align 8
+// CHECK: @globalStripIntptr = global i64 ptrtoint (ptr @globalStripPointer to i64), align 8
+// CHECK: @globalSignAndStripIntptr = global i64 ptrtoint (ptr ptrauth (ptr @globalSignAndStripPointer, i32 1, i64 106) to i64), align 8
+// CHECK: @globalSignAndAuthIntptr = global i64 ptrtoint (ptr ptrauth (ptr @globalSignAndAuthPointer, i32 1, i64 107) to i64), align 8
+
+typedef struct {
+  NonePointer ptr;
+  NoneIntptr i;
+} NoneStruct;
+typedef struct {
+  StripPointer ptr;
+  StripIntptr i;
+} StripStruct;
+typedef struct {
+  SignAndStripPointer ptr;
+  SignAndStripIntptr i;
+} SignAndStripStruct;
+typedef struct {
+  SignAndAuthPointer ptr;
+  SignAndAuthIntptr i;
+} SignAndAuthStruct;
+
+// CHECK-LABEL: @testNone
+NoneStruct testNone(NoneStruct *a, NoneStruct *b, NoneStruct c) {
+  globalNonePointer += 1;
+  // CHECK: [[GLOBALP:%.*]] = load ptr, ptr @globalNonePointer
+  // CHECK: [[GLOBALPP:%.*]] = getelementptr inbounds i8, ptr [[GLOBALP]], i64 1
+  // CHECK: store ptr [[GLOBALPP]], ptr @globalNonePointer
+  globalNoneIntptr += 1;
+  // CHECK: [[GLOBALI:%.*]] = load i64, ptr @globalNoneIntptr
+  // CHECK: [[GLOBALIP:%.*]] = add i64 [[GLOBALI]], 1
+  // CHECK: store i64 [[GLOBALIP]], ptr @globalNoneIntptr
+  a->ptr += 1;
+  // CHECK: [[PTR:%.*]] = load ptr, ptr %a.addr, align 8
+  // CHECK: [[PTR_PTR:%.*]] = getelementptr inbounds nuw %struct.NoneStruct, ptr [[PTR]], i32 0, i32 0
+  // CHECK: [[PTR:%.*]] = load ptr, ptr [[PTR_PTR]], align 8
+  // CHECK: [[AP:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 1
+  // CHECK: stor...
[truncated]

@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-options branch from 6f161d3 to 8d42eca Compare May 10, 2025 06:02
@@ -1735,8 +1735,8 @@ def warn_pragma_unroll_cuda_value_in_parens : Warning<
"argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
InGroup<CudaCompat>;

def err_ptrauth_qualifier_bad_arg_count : Error<
"'__ptrauth' qualifier must take between 1 and 3 arguments">;
def err_ptrauth_qualifier_bad_arg_count
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clang-format induced (aside from the 3 vs 4 change obviously) I can more the Error< back but it might be worth trying to work out how to stop clang-format from making this choice

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend manually formatting diagnostic files rather than using clang-format, at least until the tool learns (or can be configured for) our conventions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I wonder if we can make clang-format exclude the diagnostic files

IntArgument<"ExtraDiscriminator", 1>];
let Args = [IntArgument<"Key">, BoolArgument<"AddressDiscriminated", 1>,
IntArgument<"ExtraDiscriminator", 1>,
StringArgument<"Options", 1>];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an update to AttrDocs.td for the new argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if there's any existing documentation as the attribute can't be specified by attribute syntax

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the PointerAuthentication document

@@ -1735,8 +1735,8 @@ def warn_pragma_unroll_cuda_value_in_parens : Warning<
"argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
InGroup<CudaCompat>;

def err_ptrauth_qualifier_bad_arg_count : Error<
"'__ptrauth' qualifier must take between 1 and 3 arguments">;
def err_ptrauth_qualifier_bad_arg_count
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend manually formatting diagnostic files rather than using clang-format, at least until the tool learns (or can be configured for) our conventions.

@@ -65,6 +65,17 @@ enum class PointerAuthenticationMode : unsigned {
SignAndAuth
};

static constexpr llvm::StringLiteral PointerAuthenticationOptionStrip = "strip";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the language option level, I think it's more idiomatic to use an enumeration instead of string literals and doing string processing.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is on the right path, but others have good comments here so far.

@@ -65,6 +65,17 @@ enum class PointerAuthenticationMode : unsigned {
SignAndAuth
};

static constexpr llvm::StringLiteral PointerAuthenticationOptionStrip = "strip";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest doing a string-switch on it, then storing an enum in the AST. We can then print it/whatever on the way out. But a pair of conversion functions is typically all you should need. Though, it doesn't seem that these are leaving the handle function anyway, so IDK.

Either way, LangOpts is a little bit of an odd place for this to live.

This PR adds support for an 'options' parameter for the __ptrauth
qualifier.

The initial version only exposes the authehntication modes:
 * "strip"
 * "sign-and-strip"
 * "sign-and-auth"

We also support parsing the options but not yet the implementation
 * "isa-pointer"
 * "authenticates-null-values"

The initial support for authentication mode controls exist to support
ABI changes over time, and as a byproduct support basic initial tests
for option parsing.
@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-options branch from f6f11a5 to 927380b Compare May 21, 2025 20:47
separated modifiers to the normal authentication behavior. Currently supported
options are

- Authentication mode: This is one of ``strip``, ``sign-and-strip``, and
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR does not actually provide the implementation of this option, as it touches a number of places unrelated to parsing the option.

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
Status: No status
Development

Successfully merging this pull request may close these issues.

5 participants