[llvm] [BOLT] Gadget scanner: analyze functions without CFG information (PR #133461)

Anatoly Trosinenko via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 8 08:47:01 PDT 2025


https://github.com/atrosinenko updated https://github.com/llvm/llvm-project/pull/133461

>From a6bb0a9c23b608f56cbe0616b8042b38c1803f78 Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Wed, 19 Mar 2025 18:58:32 +0300
Subject: [PATCH 1/6] [BOLT] Gadget scanner: analyze functions without CFG
 information

Support simple analysis of the functions for which BOLT is unable to
reconstruct the CFG. This patch is inspired by the approach implemented
by Kristof Beyls in the original prototype of gadget scanner, but a
CFG-unaware counterpart of the data-flow analysis is implemented
instead of separate version of gadget detector, as multiple gadget kinds
are detected now.
---
 bolt/include/bolt/Core/BinaryFunction.h       |  13 +
 bolt/include/bolt/Passes/PAuthGadgetScanner.h |  24 +
 bolt/lib/Passes/PAuthGadgetScanner.cpp        | 266 +++++---
 .../AArch64/gs-pacret-autiasp.s               |  15 +
 .../binary-analysis/AArch64/gs-pauth-calls.s  | 594 ++++++++++++++++++
 5 files changed, 835 insertions(+), 77 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index d3d11f8c5fb73..5cb2cc95af695 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -799,6 +799,19 @@ class BinaryFunction {
     return iterator_range<const_cfi_iterator>(cie_begin(), cie_end());
   }
 
+  /// Iterate over instructions (only if CFG is unavailable or not built yet).
+  iterator_range<InstrMapType::iterator> instrs() {
+    assert(!hasCFG() && "Iterate over basic blocks instead");
+    return make_range(Instructions.begin(), Instructions.end());
+  }
+  iterator_range<InstrMapType::const_iterator> instrs() const {
+    assert(!hasCFG() && "Iterate over basic blocks instead");
+    return make_range(Instructions.begin(), Instructions.end());
+  }
+
+  /// Returns whether there are any labels at Offset.
+  bool hasLabelAt(unsigned Offset) const { return Labels.count(Offset) != 0; }
+
   /// Iterate over all jump tables associated with this function.
   iterator_range<std::map<uint64_t, JumpTable *>::const_iterator>
   jumpTables() const {
diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
index 622e6721dea55..aa44f8c565639 100644
--- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h
+++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
@@ -67,6 +67,14 @@ struct MCInstInBFReference {
   uint64_t Offset;
   MCInstInBFReference(BinaryFunction *BF, uint64_t Offset)
       : BF(BF), Offset(Offset) {}
+
+  static MCInstInBFReference get(const MCInst *Inst, BinaryFunction &BF) {
+    for (auto &I : BF.instrs())
+      if (Inst == &I.second)
+        return MCInstInBFReference(&BF, I.first);
+    return {};
+  }
+
   MCInstInBFReference() : BF(nullptr), Offset(0) {}
   bool operator==(const MCInstInBFReference &RHS) const {
     return BF == RHS.BF && Offset == RHS.Offset;
@@ -106,6 +114,12 @@ struct MCInstReference {
   MCInstReference(BinaryFunction *BF, uint32_t Offset)
       : MCInstReference(MCInstInBFReference(BF, Offset)) {}
 
+  static MCInstReference get(const MCInst *Inst, BinaryFunction &BF) {
+    if (BF.hasCFG())
+      return MCInstInBBReference::get(Inst, BF);
+    return MCInstInBFReference::get(Inst, BF);
+  }
+
   bool operator<(const MCInstReference &RHS) const {
     if (ParentKind != RHS.ParentKind)
       return ParentKind < RHS.ParentKind;
@@ -140,6 +154,16 @@ struct MCInstReference {
     llvm_unreachable("");
   }
 
+  operator bool() const {
+    switch (ParentKind) {
+    case BasicBlockParent:
+      return U.BBRef.BB != nullptr;
+    case FunctionParent:
+      return U.BFRef.BF != nullptr;
+    }
+    llvm_unreachable("");
+  }
+
   uint64_t getAddress() const {
     switch (ParentKind) {
     case BasicBlockParent:
diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index df9e87bd4e999..f5d224675d749 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -124,6 +124,27 @@ class TrackedRegisters {
   }
 };
 
+// Without CFG, we reset gadget scanning state when encountering an
+// unconditional branch. Note that BC.MIB->isUnconditionalBranch neither
+// considers indirect branches nor annotated tail calls as unconditional.
+static bool isStateTrackingBoundary(const BinaryContext &BC,
+                                    const MCInst &Inst) {
+  const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode());
+  // Adapted from llvm::MCInstrDesc::isUnconditionalBranch().
+  return Desc.isBranch() && Desc.isBarrier();
+}
+
+template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
+  if (BF.hasCFG()) {
+    for (BinaryBasicBlock &BB : BF)
+      for (int64_t I = 0, E = BB.size(); I < E; ++I)
+        Fn(MCInstInBBReference(&BB, I));
+  } else {
+    for (auto I : BF.instrs())
+      Fn(MCInstInBFReference(&BF, I.first));
+  }
+}
+
 // The security property that is checked is:
 // When a register is used as the address to jump to in a return instruction,
 // that register must be safe-to-dereference. It must either
@@ -265,21 +286,24 @@ void PacStatePrinter::print(raw_ostream &OS, const State &S) const {
   OS << ">";
 }
 
-class PacRetAnalysis
-    : public DataflowAnalysis<PacRetAnalysis, State, /*Backward=*/false,
-                              PacStatePrinter> {
-  using Parent =
-      DataflowAnalysis<PacRetAnalysis, State, false, PacStatePrinter>;
-  friend Parent;
-
+class PacRetAnalysis {
 public:
-  PacRetAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+  PacRetAnalysis(BinaryFunction &BF,
                  const std::vector<MCPhysReg> &RegsToTrackInstsFor)
-      : Parent(BF, AllocId), NumRegs(BF.getBinaryContext().MRI->getNumRegs()),
+      : BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()),
         RegsToTrackInstsFor(RegsToTrackInstsFor) {}
+
   virtual ~PacRetAnalysis() {}
 
+  static std::shared_ptr<PacRetAnalysis>
+  create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+         const std::vector<MCPhysReg> &RegsToTrackInstsFor);
+
+  virtual void run() = 0;
+  virtual ErrorOr<const State &> getStateBefore(const MCInst &Inst) const = 0;
+
 protected:
+  BinaryContext &BC;
   const unsigned NumRegs;
   /// RegToTrackInstsFor is the set of registers for which the dataflow analysis
   /// must compute which the last set of instructions writing to it are.
@@ -296,8 +320,6 @@ class PacRetAnalysis
     return S.LastInstWritingReg[Index];
   }
 
-  void preflight() {}
-
   State createEntryState() {
     State S(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
     for (MCPhysReg Reg : BC.MIB->getTrustedLiveInRegs())
@@ -305,36 +327,6 @@ class PacRetAnalysis
     return S;
   }
 
-  State getStartingStateAtBB(const BinaryBasicBlock &BB) {
-    if (BB.isEntryPoint())
-      return createEntryState();
-
-    return State();
-  }
-
-  State getStartingStateAtPoint(const MCInst &Point) { return State(); }
-
-  void doConfluence(State &StateOut, const State &StateIn) {
-    PacStatePrinter P(BC);
-    LLVM_DEBUG({
-      dbgs() << " PacRetAnalysis::Confluence(\n";
-      dbgs() << "   State 1: ";
-      P.print(dbgs(), StateOut);
-      dbgs() << "\n";
-      dbgs() << "   State 2: ";
-      P.print(dbgs(), StateIn);
-      dbgs() << ")\n";
-    });
-
-    StateOut.merge(StateIn);
-
-    LLVM_DEBUG({
-      dbgs() << "   merged state: ";
-      P.print(dbgs(), StateOut);
-      dbgs() << "\n";
-    });
-  }
-
   BitVector getClobberedRegs(const MCInst &Point) const {
     BitVector Clobbered(NumRegs, false);
     // Assume a call can clobber all registers, including callee-saved
@@ -438,8 +430,6 @@ class PacRetAnalysis
     return Next;
   }
 
-  StringRef getAnnotationName() const { return StringRef("PacRetAnalysis"); }
-
 public:
   std::vector<MCInstReference>
   getLastClobberingInsts(const MCInst &Inst, BinaryFunction &BF,
@@ -458,14 +448,139 @@ class PacRetAnalysis
     }
     std::vector<MCInstReference> Result;
     for (const MCInst *Inst : LastWritingInsts) {
-      MCInstInBBReference Ref = MCInstInBBReference::get(Inst, BF);
-      assert(Ref.BB != nullptr && "Expected Inst to be found");
+      MCInstReference Ref = MCInstReference::get(Inst, BF);
+      assert(Ref && "Expected Inst to be found");
       Result.push_back(MCInstReference(Ref));
     }
     return Result;
   }
 };
 
+class PacRetDFAnalysis
+    : public PacRetAnalysis,
+      public DataflowAnalysis<PacRetDFAnalysis, State, /*Backward=*/false,
+                              PacStatePrinter> {
+  using DFParent =
+      DataflowAnalysis<PacRetDFAnalysis, State, false, PacStatePrinter>;
+  friend DFParent;
+
+  using PacRetAnalysis::BC;
+  using PacRetAnalysis::computeNext;
+
+public:
+  PacRetDFAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+                   const std::vector<MCPhysReg> &RegsToTrackInstsFor)
+      : PacRetAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {}
+
+  ErrorOr<const State &> getStateBefore(const MCInst &Inst) const override {
+    return DFParent::getStateBefore(Inst);
+  }
+
+  void run() override { DFParent::run(); }
+
+protected:
+  void preflight() {}
+
+  State getStartingStateAtBB(const BinaryBasicBlock &BB) {
+    if (BB.isEntryPoint())
+      return createEntryState();
+
+    return State();
+  }
+
+  State getStartingStateAtPoint(const MCInst &Point) { return State(); }
+
+  void doConfluence(State &StateOut, const State &StateIn) {
+    PacStatePrinter P(BC);
+    LLVM_DEBUG({
+      dbgs() << " PacRetAnalysis::Confluence(\n";
+      dbgs() << "   State 1: ";
+      P.print(dbgs(), StateOut);
+      dbgs() << "\n";
+      dbgs() << "   State 2: ";
+      P.print(dbgs(), StateIn);
+      dbgs() << ")\n";
+    });
+
+    StateOut.merge(StateIn);
+
+    LLVM_DEBUG({
+      dbgs() << "   merged state: ";
+      P.print(dbgs(), StateOut);
+      dbgs() << "\n";
+    });
+  }
+
+  StringRef getAnnotationName() const { return "PacRetAnalysis"; }
+};
+
+class NoCFGPacRetAnalysis : public PacRetAnalysis {
+  BinaryFunction &BF;
+  MCPlusBuilder::AllocatorIdTy AllocId;
+  unsigned StateAnnotationIndex;
+
+  void cleanStateAnnotations() {
+    for (auto &I : BF.instrs())
+      BC.MIB->removeAnnotation(I.second, StateAnnotationIndex);
+  }
+
+  /// Creates a State with all registers marked unsafe (not to be confused
+  /// with empty state).
+  State createUnsafeState() const {
+    return State(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
+  }
+
+public:
+  NoCFGPacRetAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+                      const std::vector<MCPhysReg> &RegsToTrackInstsFor)
+      : PacRetAnalysis(BF, RegsToTrackInstsFor), BF(BF), AllocId(AllocId) {
+    StateAnnotationIndex =
+        BC.MIB->getOrCreateAnnotationIndex("NoCFGPacRetAnalysis");
+  }
+
+  void run() override {
+    State S = createEntryState();
+    for (auto &I : BF.instrs()) {
+      MCInst &Inst = I.second;
+
+      // If there is a label before this instruction, it is possible that it
+      // can be jumped-to, thus conservatively resetting S.
+      if (BF.hasLabelAt(I.first))
+        S = createUnsafeState();
+
+      // Check if we need to remove an old annotation (this is the case if
+      // this is the second, detailed, run of the analysis).
+      if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex))
+        BC.MIB->removeAnnotation(Inst, StateAnnotationIndex);
+      // Attach the state *before* this instruction.
+      BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId);
+
+      // Compute the state after this instruction.
+      // If this instruction is an unconditional branch (incl. indirect ones),
+      // reset the state.
+      if (isStateTrackingBoundary(BC, Inst))
+        S = createUnsafeState();
+      else
+        S = computeNext(Inst, S);
+    }
+  }
+
+  ErrorOr<const State &> getStateBefore(const MCInst &Inst) const override {
+    return BC.MIB->getAnnotationAs<State>(Inst, StateAnnotationIndex);
+  }
+
+  ~NoCFGPacRetAnalysis() { cleanStateAnnotations(); }
+};
+
+std::shared_ptr<PacRetAnalysis>
+PacRetAnalysis::create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+                       const std::vector<MCPhysReg> &RegsToTrackInstsFor) {
+  if (BF.hasCFG())
+    return std::make_shared<PacRetDFAnalysis>(BF, AllocId, RegsToTrackInstsFor);
+  return std::make_shared<NoCFGPacRetAnalysis>(BF, AllocId,
+                                               RegsToTrackInstsFor);
+}
+
 static std::shared_ptr<Report>
 shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst,
                          const State &S) {
@@ -522,37 +637,34 @@ Analysis::findGadgets(BinaryFunction &BF,
                       MCPlusBuilder::AllocatorIdTy AllocatorId) {
   FunctionAnalysisResult Result;
 
-  PacRetAnalysis PRA(BF, AllocatorId, {});
-  PRA.run();
+  auto PRA = PacRetAnalysis::create(BF, AllocatorId, {});
+  PRA->run();
   LLVM_DEBUG({
     dbgs() << " After PacRetAnalysis:\n";
     BF.dump();
   });
 
   BinaryContext &BC = BF.getBinaryContext();
-  for (BinaryBasicBlock &BB : BF) {
-    for (int64_t I = 0, E = BB.size(); I < E; ++I) {
-      MCInstReference Inst(&BB, I);
-      const State &S = *PRA.getStateBefore(Inst);
-
-      // If non-empty state was never propagated from the entry basic block
-      // to Inst, assume it to be unreachable and report a warning.
-      if (S.empty()) {
-        Result.Diagnostics.push_back(std::make_shared<GenericReport>(
-            Inst, "Warning: unreachable instruction found"));
-        continue;
-      }
-
-      if (auto Report = shouldReportReturnGadget(BC, Inst, S))
-        Result.Diagnostics.push_back(Report);
-
-      if (PacRetGadgetsOnly)
-        continue;
-
-      if (auto Report = shouldReportCallGadget(BC, Inst, S))
-        Result.Diagnostics.push_back(Report);
+  iterateOverInstrs(BF, [&](MCInstReference Inst) {
+    const State &S = *PRA->getStateBefore(Inst);
+
+    // If non-empty state was never propagated from the entry basic block
+    // to Inst, assume it to be unreachable and report a warning.
+    if (S.empty()) {
+      Result.Diagnostics.push_back(std::make_shared<GenericReport>(
+          Inst, "Warning: unreachable instruction found"));
+      return;
     }
-  }
+
+    if (auto Report = shouldReportReturnGadget(BC, Inst, S))
+      Result.Diagnostics.push_back(Report);
+
+    if (PacRetGadgetsOnly)
+      return;
+
+    if (auto Report = shouldReportCallGadget(BC, Inst, S))
+      Result.Diagnostics.push_back(Report);
+  });
   return Result;
 }
 
@@ -568,8 +680,8 @@ void Analysis::computeDetailedInfo(BinaryFunction &BF,
   std::vector<MCPhysReg> RegsToTrackVec(RegsToTrack.begin(), RegsToTrack.end());
 
   // Re-compute the analysis with register tracking.
-  PacRetAnalysis PRWIA(BF, AllocatorId, RegsToTrackVec);
-  PRWIA.run();
+  auto PRWIA = PacRetAnalysis::create(BF, AllocatorId, RegsToTrackVec);
+  PRWIA->run();
   LLVM_DEBUG({
     dbgs() << " After detailed PacRetAnalysis:\n";
     BF.dump();
@@ -580,7 +692,7 @@ void Analysis::computeDetailedInfo(BinaryFunction &BF,
     LLVM_DEBUG(
         { traceInst(BC, "Attaching clobbering info to", Report->Location); });
     (void)BC;
-    Report->setOverwritingInstrs(PRWIA.getLastClobberingInsts(
+    Report->setOverwritingInstrs(PRWIA->getLastClobberingInsts(
         Report->Location, BF, Report->getAffectedRegisters()));
   }
 }
@@ -593,9 +705,6 @@ void Analysis::runOnFunction(BinaryFunction &BF,
     BF.dump();
   });
 
-  if (!BF.hasCFG())
-    return;
-
   FunctionAnalysisResult FAR = findGadgets(BF, AllocatorId);
   if (FAR.Diagnostics.empty())
     return;
@@ -681,8 +790,11 @@ void GadgetReport::generateReport(raw_ostream &OS,
   };
   if (OverwritingInstrs.size() == 1) {
     const MCInstReference OverwInst = OverwritingInstrs[0];
-    assert(OverwInst.ParentKind == MCInstReference::BasicBlockParent);
-    reportFoundGadgetInSingleBBSingleOverwInst(OS, BC, OverwInst, Location);
+    // Printing the details for the MCInstReference::FunctionParent case
+    // is not implemented not to overcomplicate the code, as most functions
+    // are expected to have CFG information.
+    if (OverwInst.ParentKind == MCInstReference::BasicBlockParent)
+      reportFoundGadgetInSingleBBSingleOverwInst(OS, BC, OverwInst, Location);
   }
 }
 
diff --git a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
index d506ec13f4895..2193d40131478 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
@@ -223,6 +223,21 @@ f_unreachable_instruction:
         ret
         .size f_unreachable_instruction, .-f_unreachable_instruction
 
+// Expected false positive: without CFG, the state is reset to all-unsafe
+// after an unconditional branch.
+
+        .globl  state_is_reset_after_indirect_branch_nocfg
+        .type   state_is_reset_after_indirect_branch_nocfg, at function
+state_is_reset_after_indirect_branch_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected ret found in function state_is_reset_after_indirect_branch_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         ret
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        adr     x2, 1f
+        br      x2
+1:
+        ret
+        .size state_is_reset_after_indirect_branch_nocfg, .-state_is_reset_after_indirect_branch_nocfg
+
 /// Now do a basic sanity check on every different Authentication instruction:
 
         .globl  f_autiasp
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
index 0f6c850583dda..86b0c451c8bac 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
@@ -429,6 +429,261 @@ bad_indirect_call_mem_chain_of_auts_multi_bb:
         ret
         .size bad_indirect_call_mem_chain_of_auts_multi_bb, .-bad_indirect_call_mem_chain_of_auts_multi_bb
 
+// Tests for CFG-unaware analysis.
+
+        .globl  good_direct_call_nocfg
+        .type   good_direct_call_nocfg, at function
+good_direct_call_nocfg:
+// CHECK-NOT: good_direct_call_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        bl      callee_ext
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_direct_call_nocfg, .-good_direct_call_nocfg
+
+        .globl  good_indirect_call_arg_nocfg
+        .type   good_indirect_call_arg_nocfg, at function
+good_indirect_call_arg_nocfg:
+// CHECK-NOT: good_indirect_call_arg_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        blr     x0
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_indirect_call_arg_nocfg, .-good_indirect_call_arg_nocfg
+
+        .globl  good_indirect_call_mem_nocfg
+        .type   good_indirect_call_mem_nocfg, at function
+good_indirect_call_mem_nocfg:
+// CHECK-NOT: good_indirect_call_mem_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        autia   x16, x0
+        blr     x16
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_indirect_call_mem_nocfg, .-good_indirect_call_mem_nocfg
+
+        .globl  good_indirect_call_arg_v83_nocfg
+        .type   good_indirect_call_arg_v83_nocfg, at function
+good_indirect_call_arg_v83_nocfg:
+// CHECK-NOT: good_indirect_call_arg_v83_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        blraa   x0, x1
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_indirect_call_arg_v83_nocfg, .-good_indirect_call_arg_v83_nocfg
+
+        .globl  good_indirect_call_mem_v83_nocfg
+        .type   good_indirect_call_mem_v83_nocfg, at function
+good_indirect_call_mem_v83_nocfg:
+// CHECK-NOT: good_indirect_call_mem_v83_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        blraa   x16, x0
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_indirect_call_mem_v83_nocfg, .-good_indirect_call_mem_v83_nocfg
+
+        .globl  bad_indirect_call_arg_nocfg
+        .type   bad_indirect_call_arg_nocfg, at function
+bad_indirect_call_arg_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_arg_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x0
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        blr     x0
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_indirect_call_arg_nocfg, .-bad_indirect_call_arg_nocfg
+
+        .globl  obscure_indirect_call_arg_nocfg
+        .type   obscure_indirect_call_arg_nocfg, at function
+obscure_indirect_call_arg_nocfg:
+// CHECK-NOCFG-LABEL: GS-PAUTH: non-protected call found in function obscure_indirect_call_arg_nocfg, at address
+// CHECK-NOCFG-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x0
+// CHECK-NOCFG-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1 // not observed by the checker
+        b       1f     // ... because of unconditional branch
+1:
+        blr     x0     // reported as non-protected
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size obscure_good_indirect_call_arg_nocfg, .-obscure_good_indirect_call_arg_nocfg
+
+        .globl  bad_indirect_call_mem_nocfg
+        .type   bad_indirect_call_mem_nocfg, at function
+bad_indirect_call_mem_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x16, [x0]
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        blr     x16
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_indirect_call_mem_nocfg, .-bad_indirect_call_mem_nocfg
+
+        .globl  bad_indirect_call_arg_clobber_nocfg
+        .type   bad_indirect_call_arg_clobber_nocfg, at function
+bad_indirect_call_arg_clobber_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_arg_clobber_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x0
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mov     w0, w2
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        autia   x0, x1
+        mov     w0, w2
+        blr     x0
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_indirect_call_arg_clobber_nocfg, .-bad_indirect_call_arg_clobber_nocfg
+
+        .globl  bad_indirect_call_mem_clobber_nocfg
+        .type   bad_indirect_call_mem_clobber_nocfg, at function
+bad_indirect_call_mem_clobber_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem_clobber_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mov     w16, w2
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        autia   x16, x0
+        mov     w16, w2
+        blr     x16
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_indirect_call_mem_clobber_nocfg, .-bad_indirect_call_mem_clobber_nocfg
+
+        .globl  good_indirect_call_mem_chain_of_auts_nocfg
+        .type   good_indirect_call_mem_chain_of_auts_nocfg, at function
+good_indirect_call_mem_chain_of_auts_nocfg:
+// CHECK-NOT: good_indirect_call_mem_chain_of_auts_nocfg
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        autda   x16, x1
+        ldr     x16, [x16]
+        autia   x16, x0
+        blr     x16
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_indirect_call_mem_chain_of_auts_nocfg, .-good_indirect_call_mem_chain_of_auts_nocfg
+
+        .globl  bad_indirect_call_mem_chain_of_auts_nocfg
+        .type   bad_indirect_call_mem_chain_of_auts_nocfg, at function
+bad_indirect_call_mem_chain_of_auts_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem_chain_of_auts_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x16, [x16]
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        autda   x16, x1
+        ldr     x16, [x16]
+        // Missing AUT of x16. The fact that x16 was authenticated above has nothing to do with it.
+        blr     x16
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_indirect_call_mem_chain_of_auts_nocfg, .-bad_indirect_call_mem_chain_of_auts_nocfg
+
 // Test tail calls. To somewhat decrease the number of test cases and not
 // duplicate all of the above, only implement "mem" variant of test cases and
 // mostly test negative cases.
@@ -537,6 +792,109 @@ bad_indirect_tailcall_mem_clobber_multi_bb:
         br      x16
         .size bad_indirect_tailcall_mem_clobber_multi_bb, .-bad_indirect_tailcall_mem_clobber_multi_bb
 
+        .globl  good_direct_tailcall_nocfg
+        .type   good_direct_tailcall_nocfg, at function
+good_direct_tailcall_nocfg:
+// CHECK-NOT: good_direct_tailcall_nocfg
+        adr     x2, 1f
+        br      x2
+1:
+        b       callee_ext
+        .size good_direct_tailcall_nocfg, .-good_direct_tailcall_nocfg
+
+        .globl  good_indirect_tailcall_mem_nocfg
+        .type   good_indirect_tailcall_mem_nocfg, at function
+good_indirect_tailcall_mem_nocfg:
+// CHECK-NOT: good_indirect_tailcall_mem_nocfg
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x16, [x0]
+        autia   x16, x0
+        br      x16
+        .size good_indirect_tailcall_mem_nocfg, .-good_indirect_tailcall_mem_nocfg
+
+        .globl  good_indirect_tailcall_mem_v83_nocfg
+        .type   good_indirect_tailcall_mem_v83_nocfg, at function
+good_indirect_tailcall_mem_v83_nocfg:
+// CHECK-NOT: good_indirect_tailcall_mem_v83_nocfg
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x16, [x0]
+        braa    x16, x0
+        .size good_indirect_tailcall_mem_v83_nocfg, .-good_indirect_tailcall_mem_v83_nocfg
+
+        .globl  bad_indirect_tailcall_mem_nocfg
+        .type   bad_indirect_tailcall_mem_nocfg, at function
+bad_indirect_tailcall_mem_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_tailcall_mem_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         br      x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x16, [x0]
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x16, [x0]
+        br      x16
+        .size bad_indirect_tailcall_mem_nocfg, .-bad_indirect_tailcall_mem_nocfg
+
+        .globl  bad_indirect_tailcall_mem_clobber_nocfg
+        .type   bad_indirect_tailcall_mem_clobber_nocfg, at function
+bad_indirect_tailcall_mem_clobber_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_tailcall_mem_clobber_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         br      x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      mov     w16, w2
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x16, [x0]
+        autia   x16, x0
+        mov     w16, w2
+        br      x16
+        .size bad_indirect_tailcall_mem_clobber_nocfg, .-bad_indirect_tailcall_mem_clobber_nocfg
+
+        .globl  bad_indirect_tailcall_mem_chain_of_auts_nocfg
+        .type   bad_indirect_tailcall_mem_chain_of_auts_nocfg, at function
+bad_indirect_tailcall_mem_chain_of_auts_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_tailcall_mem_chain_of_auts_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         br      x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x16, [x16]
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x16, [x0]
+        autda   x16, x1
+        ldr     x16, [x16]
+        // Missing AUT of x16. The fact that x16 was authenticated above has nothing to do with it.
+        br      x16
+        .size bad_indirect_tailcall_mem_chain_of_auts_nocfg, .-bad_indirect_tailcall_mem_chain_of_auts_nocfg
+
+        .globl  state_is_reset_at_branch_destination_nocfg
+        .type   state_is_reset_at_branch_destination_nocfg, at function
+state_is_reset_at_branch_destination_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function state_is_reset_at_branch_destination_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr      x0
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        b       1f
+        autia   x0, x1  // skipped
+1:
+        blr     x0
+
+        adr     x2, 2f
+        br      x2
+2:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size state_is_reset_at_branch_destination_nocfg, .-state_is_reset_at_branch_destination_nocfg
+
 // Test that calling a function is considered as invalidating safety of every
 // register. Note that we only have to consider "returning" function calls
 // (via branch-with-link), but both direct and indirect variants.
@@ -692,6 +1050,161 @@ indirect_call_invalidates_safety:
         ret
         .size indirect_call_invalidates_safety, .-indirect_call_invalidates_safety
 
+// FIXME: Clobbering instructions are not detected because direct calls are
+//        disassembled like this:
+//
+//            0000000c:   mov     x2, x0
+//            00000010:   autiza  x2
+//            00000014:   bl      .Ltmp40    <-- callee_ext symbol is not understood,
+//                                               address of the next instruction is used instead
+//        .Ltmp40:                           <-- state is reset because of this label
+//            00000018:   blr     x2
+
+        .globl  direct_call_invalidates_safety_nocfg
+        .type   direct_call_invalidates_safety_nocfg, at function
+direct_call_invalidates_safety_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x2
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// FIXME: Print the destination of BL as callee_ext instead of .LtmpN
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x8
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x10
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x16
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x18
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x20
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        mov     x2, x0
+        autiza  x2
+        bl      callee_ext
+        blr     x2
+
+        mov     x8, x0
+        autiza  x8
+        bl      callee_ext
+        blr     x8
+
+        mov     x10, x0
+        autiza  x10
+        bl      callee_ext
+        blr     x10
+
+        mov     x16, x0
+        autiza  x16
+        bl      callee_ext
+        blr     x16
+
+        mov     x18, x0
+        autiza  x18
+        bl      callee_ext
+        blr     x18
+
+        mov     x20, x0
+        autiza  x20
+        bl      callee_ext
+        blr     x20
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size direct_call_invalidates_safety_nocfg, .-direct_call_invalidates_safety_nocfg
+
+        .globl  indirect_call_invalidates_safety_nocfg
+        .type   indirect_call_invalidates_safety_nocfg, at function
+indirect_call_invalidates_safety_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x2
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x2
+// Check that only one error is reported per pair of BLRs.
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x2
+
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x8
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x8
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x8
+
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x10
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x10
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x10
+
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x16
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x16
+
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x18
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x18
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x18
+
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function indirect_call_invalidates_safety_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x20
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blr     x20
+// CHECK-NOT:   The instruction is     {{[0-9a-f]+}}:      blr     x20
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        mov     x2, x0
+        autiza  x2
+        blr     x2              // protected call, but makes x2 unsafe
+        blr     x2              // unprotected call
+
+        mov     x8, x0
+        autiza  x8
+        blr     x8              // protected call, but makes x8 unsafe
+        blr     x8              // unprotected call
+
+        mov     x10, x0
+        autiza  x10
+        blr     x10             // protected call, but makes x10 unsafe
+        blr     x10             // unprotected call
+
+        mov     x16, x0
+        autiza  x16
+        blr     x16             // protected call, but makes x16 unsafe
+        blr     x16             // unprotected call
+
+        mov     x18, x0
+        autiza  x18
+        blr     x18             // protected call, but makes x18 unsafe
+        blr     x18             // unprotected call
+
+        mov     x20, x0
+        autiza  x20
+        blr     x20             // protected call, but makes x20 unsafe
+        blr     x20             // unprotected call
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size indirect_call_invalidates_safety_nocfg, .-indirect_call_invalidates_safety_nocfg
+
 // Test that fused auth+use Armv8.3 instruction do not mark register as safe.
 
         .globl  blraa_no_mark_safe
@@ -722,6 +1235,28 @@ blraa_no_mark_safe:
         ret
         .size blraa_no_mark_safe, .-blraa_no_mark_safe
 
+        .globl  blraa_no_mark_safe_nocfg
+        .type   blraa_no_mark_safe_nocfg, at function
+blraa_no_mark_safe_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function blraa_no_mark_safe_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x0
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      blraa   x0, x1
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        blraa   x0, x1  // safe, no write-back, clobbers everything
+        blr     x0      // detected as unsafe
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size blraa_no_mark_safe_nocfg, .-blraa_no_mark_safe_nocfg
+
 // Check that the correct set of registers is used to compute the set of last
 // writing instructions: both x16 and x17 are tracked in this function, but
 // only one particular register is used to compute the set of clobbering
@@ -774,6 +1309,65 @@ last_insts_writing_to_reg:
         ret
         .size last_insts_writing_to_reg, .-last_insts_writing_to_reg
 
+        .globl  last_insts_writing_to_reg_nocfg
+        .type   last_insts_writing_to_reg_nocfg, at function
+last_insts_writing_to_reg_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function last_insts_writing_to_reg_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x16
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x16, [x0]
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function last_insts_writing_to_reg_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         blr     x17
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x17, [x1]
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        ldr     x16, [x0]
+        blr     x16
+        ldr     x17, [x1]
+        blr     x17
+
+        adr     x2, 1f
+        br      x2
+1:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size last_insts_writing_to_reg_nocfg, .-last_insts_writing_to_reg_nocfg
+
+// Test that the instructions reported to the user are not cluttered with
+// annotations attached by data-flow analysis or its CFG-unaware counterpart.
+
+        .globl  printed_instrs_dataflow
+        .type   printed_instrs_dataflow, at function
+printed_instrs_dataflow:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function printed_instrs_dataflow, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         br      x0 # TAILCALL{{ *$}}
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x0, [x0]{{ *$}}
+// CHECK-NEXT:  This happens in the following basic block:
+// CHECK-NEXT:  {{[0-9a-f]+}}:   ldr     x0, [x0]{{ *$}}
+// CHECK-NEXT:  {{[0-9a-f]+}}:   br      x0 # TAILCALL{{ *$}}
+        ldr     x0, [x0]
+        br      x0
+        .size   printed_instrs_dataflow, .-printed_instrs_dataflow
+
+        .globl  printed_instrs_nocfg
+        .type   printed_instrs_nocfg, at function
+printed_instrs_nocfg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function printed_instrs_nocfg, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:         br      x0 # UNKNOWN CONTROL FLOW # Offset: 12{{ *$}}
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      ldr     x0, [x0]{{ *$}}
+        adr     x2, 1f
+        br      x2
+1:
+        ldr     x0, [x0]
+        br      x0
+        .size   printed_instrs_nocfg, .-printed_instrs_nocfg
+
         .globl  main
         .type   main, at function
 main:

>From 06b037e9e351aaa29917027f6b1ede61e8cdbec0 Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Mon, 31 Mar 2025 20:20:45 +0300
Subject: [PATCH 2/6] Tests: call callee instead of callee_ext

---
 .../binary-analysis/AArch64/gs-pauth-calls.s  | 45 +++++++++----------
 1 file changed, 20 insertions(+), 25 deletions(-)

diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
index 86b0c451c8bac..ff59fb4d08e58 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
@@ -439,7 +439,7 @@ good_direct_call_nocfg:
         stp     x29, x30, [sp, #-16]!
         mov     x29, sp
 
-        bl      callee_ext
+        bl      callee
 
         adr     x2, 1f
         br      x2
@@ -799,7 +799,7 @@ good_direct_tailcall_nocfg:
         adr     x2, 1f
         br      x2
 1:
-        b       callee_ext
+        b       callee
         .size good_direct_tailcall_nocfg, .-good_direct_tailcall_nocfg
 
         .globl  good_indirect_tailcall_mem_nocfg
@@ -1050,70 +1050,65 @@ indirect_call_invalidates_safety:
         ret
         .size indirect_call_invalidates_safety, .-indirect_call_invalidates_safety
 
-// FIXME: Clobbering instructions are not detected because direct calls are
-//        disassembled like this:
-//
-//            0000000c:   mov     x2, x0
-//            00000010:   autiza  x2
-//            00000014:   bl      .Ltmp40    <-- callee_ext symbol is not understood,
-//                                               address of the next instruction is used instead
-//        .Ltmp40:                           <-- state is reset because of this label
-//            00000018:   blr     x2
-
         .globl  direct_call_invalidates_safety_nocfg
         .type   direct_call_invalidates_safety_nocfg, at function
 direct_call_invalidates_safety_nocfg:
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x2
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
-// FIXME: Print the destination of BL as callee_ext instead of .LtmpN
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x8
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x10
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x16
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x18
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
 // CHECK-LABEL: GS-PAUTH: non-protected call found in function direct_call_invalidates_safety_nocfg, at address
 // CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x20
-// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT:  1.     {{[0-9a-f]+}}:      bl      callee
         paciasp
         stp     x29, x30, [sp, #-16]!
         mov     x29, sp
 
         mov     x2, x0
         autiza  x2
-        bl      callee_ext
+        bl      callee
         blr     x2
 
         mov     x8, x0
         autiza  x8
-        bl      callee_ext
+        bl      callee
         blr     x8
 
         mov     x10, x0
         autiza  x10
-        bl      callee_ext
+        bl      callee
         blr     x10
 
         mov     x16, x0
         autiza  x16
-        bl      callee_ext
+        bl      callee
         blr     x16
 
         mov     x18, x0
         autiza  x18
-        bl      callee_ext
+        bl      callee
         blr     x18
 
         mov     x20, x0
         autiza  x20
-        bl      callee_ext
+        bl      callee
         blr     x20
 
         adr     x2, 1f

>From 9046acb478b62240ce564dc6abe22f1d49728a6c Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Wed, 2 Apr 2025 20:15:15 +0300
Subject: [PATCH 3/6] Ignore labels before the first instruction

---
 bolt/lib/Passes/PAuthGadgetScanner.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index f5d224675d749..a768521fb13dc 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -544,8 +544,10 @@ class NoCFGPacRetAnalysis : public PacRetAnalysis {
       MCInst &Inst = I.second;
 
       // If there is a label before this instruction, it is possible that it
-      // can be jumped-to, thus conservatively resetting S.
-      if (BF.hasLabelAt(I.first))
+      // can be jumped-to, thus conservatively resetting S. As an exception,
+      // let's ignore any labels at the beginning of the function, as at least
+      // one label is expected there.
+      if (BF.hasLabelAt(I.first) && &Inst != &BF.instrs().begin()->second)
         S = createUnsafeState();
 
       // Check if we need to remove an old annotation (this is the case if

>From 8b1df05e9b760c975645f4cafd32ab0143abbb6c Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Wed, 2 Apr 2025 20:44:13 +0300
Subject: [PATCH 4/6] Update gs-pauth-debug-output.s test

---
 .../AArch64/gs-pauth-debug-output.s           | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
index b271cda9da62f..8722581f861a5 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
@@ -158,6 +158,85 @@ clobber:
 // CHECK-EMPTY:
 // CHECK-NEXT:   Attaching clobbering info to:     00000000:         ret # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
 
+        .globl  nocfg
+        .type   nocfg, at function
+nocfg:
+        adr     x0, 1f
+        br      x0
+1:
+        ret
+        .size nocfg, .-nocfg
+
+// CHECK-LABEL:Analyzing in function nocfg, AllocatorId 1
+// CHECK-NEXT: Binary Function "nocfg"  {
+// CHECK-NEXT:   Number      : 3
+// CHECK-NEXT:   State       : disassembled
+// ...
+// CHECK:        IsSimple    : 0
+// CHECK-NEXT:   IsMultiEntry: 1
+// CHECK-NEXT:   IsSplit     : 0
+// CHECK-NEXT:   BB Count    : 0
+// CHECK-NEXT:   Secondary Entry Points : __ENTRY_nocfg at 0x[[ENTRY_ADDR:[0-9a-f]+]]
+// CHECK-NEXT: }
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]]
+// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4
+// CHECK-NEXT: __ENTRY_nocfg at 0x[[ENTRY_ADDR]] (Entry Point):
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000008:   ret # Offset: 8
+// CHECK-NEXT: DWARF CFI Instructions:
+// CHECK-NEXT:     <empty>
+// CHECK-NEXT: End of Function "nocfg"
+// CHECK-EMPTY:
+// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: >)
+// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  After PacRetAnalysis:
+// CHECK-NEXT: Binary Function "nocfg"  {
+// CHECK-NEXT:   Number      : 3
+// CHECK-NEXT:   State       : disassembled
+// ...
+// CHECK:        Secondary Entry Points : __ENTRY_nocfg at 0x[[ENTRY_ADDR]]
+// CHECK-NEXT: }
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT: __ENTRY_nocfg at 0x[[ENTRY_ADDR]] (Entry Point):
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000008:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT: DWARF CFI Instructions:
+// CHECK-NEXT:     <empty>
+// CHECK-NEXT: End of Function "nocfg"
+// CHECK-EMPTY:
+// PAUTH-NEXT:   Found call inst:     00000000:        br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// PAUTH-NEXT:     Call destination reg: X0
+// PAUTH-NEXT:     SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI
+// CHECK-NEXT:   Found RET inst:     00000000:         ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     RetReg: LR
+// CHECK-NEXT:     Authenticated reg: (none)
+// CHECK-NEXT:     SafeToDerefRegs:
+// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: [0]()>)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: [0]()>)
+// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: , Insts: [0]()>)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: [0]()>)
+// CHECK-NEXT:  After detailed PacRetAnalysis:
+// CHECK-NEXT: Binary Function "nocfg"  {
+// CHECK-NEXT:   Number      : 3
+// ...
+// CHECK:        Secondary Entry Points : __ENTRY_nocfg at 0x[[ENTRY_ADDR]]
+// CHECK-NEXT: }
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT: __ENTRY_nocfg at 0x[[ENTRY_ADDR]] (Entry Point):
+// CHECK-NEXT: .{{[A-Za-z0-9]+}}:
+// CHECK-NEXT:     00000008:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT: DWARF CFI Instructions:
+// CHECK-NEXT:     <empty>
+// CHECK-NEXT: End of Function "nocfg"
+// CHECK-EMPTY:
+// CHECK-NEXT:   Attaching clobbering info to:     00000000:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
 
 // CHECK-LABEL:Analyzing in function main, AllocatorId 1
         .globl  main

>From b1e246b34efc07595892cc0438b9961485f9fedb Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Mon, 7 Apr 2025 19:51:08 +0300
Subject: [PATCH 5/6] Address the review comments

---
 bolt/include/bolt/Passes/PAuthGadgetScanner.h |   2 +-
 bolt/lib/Passes/PAuthGadgetScanner.cpp        | 181 +++++++++++-------
 .../AArch64/gs-pauth-debug-output.s           | 115 ++++++-----
 3 files changed, 174 insertions(+), 124 deletions(-)

diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
index aa44f8c565639..98f84510d654f 100644
--- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h
+++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h
@@ -199,7 +199,7 @@ raw_ostream &operator<<(raw_ostream &OS, const MCInstReference &);
 
 namespace PAuthGadgetScanner {
 
-class PacRetAnalysis;
+class RegisterSafetyAnalysis;
 struct State;
 
 /// Description of a gadget kind that can be detected. Intended to be
diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index a768521fb13dc..122b7ca0a27d6 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -124,27 +124,6 @@ class TrackedRegisters {
   }
 };
 
-// Without CFG, we reset gadget scanning state when encountering an
-// unconditional branch. Note that BC.MIB->isUnconditionalBranch neither
-// considers indirect branches nor annotated tail calls as unconditional.
-static bool isStateTrackingBoundary(const BinaryContext &BC,
-                                    const MCInst &Inst) {
-  const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode());
-  // Adapted from llvm::MCInstrDesc::isUnconditionalBranch().
-  return Desc.isBranch() && Desc.isBarrier();
-}
-
-template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
-  if (BF.hasCFG()) {
-    for (BinaryBasicBlock &BB : BF)
-      for (int64_t I = 0, E = BB.size(); I < E; ++I)
-        Fn(MCInstInBBReference(&BB, I));
-  } else {
-    for (auto I : BF.instrs())
-      Fn(MCInstInBFReference(&BF, I.first));
-  }
-}
-
 // The security property that is checked is:
 // When a register is used as the address to jump to in a return instruction,
 // that register must be safe-to-dereference. It must either
@@ -286,16 +265,21 @@ void PacStatePrinter::print(raw_ostream &OS, const State &S) const {
   OS << ">";
 }
 
-class PacRetAnalysis {
+/// Computes which registers are safe to be used by control flow instructions.
+///
+/// This is the base class for two implementations: a dataflow-based analysis
+/// which is intended to be used for most functions and a simplified CFG-unaware
+/// version for functions without reconstructed CFG.
+class RegisterSafetyAnalysis {
 public:
-  PacRetAnalysis(BinaryFunction &BF,
-                 const std::vector<MCPhysReg> &RegsToTrackInstsFor)
+  RegisterSafetyAnalysis(BinaryFunction &BF,
+                         const std::vector<MCPhysReg> &RegsToTrackInstsFor)
       : BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()),
         RegsToTrackInstsFor(RegsToTrackInstsFor) {}
 
-  virtual ~PacRetAnalysis() {}
+  virtual ~RegisterSafetyAnalysis() {}
 
-  static std::shared_ptr<PacRetAnalysis>
+  static std::shared_ptr<RegisterSafetyAnalysis>
   create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
          const std::vector<MCPhysReg> &RegsToTrackInstsFor);
 
@@ -373,7 +357,7 @@ class PacRetAnalysis {
   State computeNext(const MCInst &Point, const State &Cur) {
     PacStatePrinter P(BC);
     LLVM_DEBUG({
-      dbgs() << " PacRetAnalysis::ComputeNext(";
+      dbgs() << "  RegisterSafetyAnalysis::ComputeNext(";
       BC.InstPrinter->printInst(&const_cast<MCInst &>(Point), 0, "", *BC.STI,
                                 dbgs());
       dbgs() << ", ";
@@ -422,7 +406,7 @@ class PacRetAnalysis {
     }
 
     LLVM_DEBUG({
-      dbgs() << "  .. result: (";
+      dbgs() << "    .. result: (";
       P.print(dbgs(), Next);
       dbgs() << ")\n";
     });
@@ -456,21 +440,23 @@ class PacRetAnalysis {
   }
 };
 
-class PacRetDFAnalysis
-    : public PacRetAnalysis,
-      public DataflowAnalysis<PacRetDFAnalysis, State, /*Backward=*/false,
-                              PacStatePrinter> {
-  using DFParent =
-      DataflowAnalysis<PacRetDFAnalysis, State, false, PacStatePrinter>;
+class DataflowRegisterSafetyAnalysis
+    : public RegisterSafetyAnalysis,
+      public DataflowAnalysis<DataflowRegisterSafetyAnalysis, State,
+                              /*Backward=*/false, PacStatePrinter> {
+  using DFParent = DataflowAnalysis<DataflowRegisterSafetyAnalysis, State,
+                                    false, PacStatePrinter>;
   friend DFParent;
 
-  using PacRetAnalysis::BC;
-  using PacRetAnalysis::computeNext;
+  using RegisterSafetyAnalysis::BC;
+  using RegisterSafetyAnalysis::computeNext;
 
 public:
-  PacRetDFAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
-                   const std::vector<MCPhysReg> &RegsToTrackInstsFor)
-      : PacRetAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {}
+  DataflowRegisterSafetyAnalysis(
+      BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+      const std::vector<MCPhysReg> &RegsToTrackInstsFor)
+      : RegisterSafetyAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {
+  }
 
   ErrorOr<const State &> getStateBefore(const MCInst &Inst) const override {
     return DFParent::getStateBefore(Inst);
@@ -493,11 +479,11 @@ class PacRetDFAnalysis
   void doConfluence(State &StateOut, const State &StateIn) {
     PacStatePrinter P(BC);
     LLVM_DEBUG({
-      dbgs() << " PacRetAnalysis::Confluence(\n";
-      dbgs() << "   State 1: ";
+      dbgs() << "  DataflowRegisterSafetyAnalysis::Confluence(\n";
+      dbgs() << "    State 1: ";
       P.print(dbgs(), StateOut);
       dbgs() << "\n";
-      dbgs() << "   State 2: ";
+      dbgs() << "    State 2: ";
       P.print(dbgs(), StateIn);
       dbgs() << ")\n";
     });
@@ -505,16 +491,52 @@ class PacRetDFAnalysis
     StateOut.merge(StateIn);
 
     LLVM_DEBUG({
-      dbgs() << "   merged state: ";
+      dbgs() << "    merged state: ";
       P.print(dbgs(), StateOut);
       dbgs() << "\n";
     });
   }
 
-  StringRef getAnnotationName() const { return "PacRetAnalysis"; }
+  StringRef getAnnotationName() const {
+    return "DataflowRegisterSafetyAnalysis";
+  }
 };
 
-class NoCFGPacRetAnalysis : public PacRetAnalysis {
+// A simplified implementation of DataflowRegisterSafetyAnalysis for functions
+// lacking CFG information.
+//
+// Let assume the instructions can only be executed linearly unless there is
+// a label to jump to - this should handle both directly jumping to a location
+// encoded as an immediate operand of a branch instruction, as well as saving a
+// branch destination somewhere and passing it to an indirect branch instruction
+// later, provided no arithmetic is performed on the destination address:
+//
+//     ; good: the destination is directly encoded into the branch instruction
+//     cbz x0, some_label
+//
+//     ; good: the branch destination is first stored and then used as-is
+//     adr x1, some_label
+//     br  x1
+//
+//     ; bad: some clever arithmetic is performed manually
+//     adr x1, some_label
+//     add x1, x1, #4
+//     br  x1
+//     ...
+//   some_label:
+//     ; pessimistically reset the state as we are unsure where we came from
+//     ...
+//     ret
+//   JTI0:
+//     .byte some_label - Ltmp0 ; computing offsets using labels may probably
+//                                work too, provided enough information is
+//                                retained by the assembler and linker
+//
+// Then, a function can be split into a number of disjoint contiguous sequences
+// of instructions without labels in between. These sequences can be processed
+// the same way basic blocks are processed by data-flow analysis, assuming
+// pessimistically that all registers are unsafe at the start of each sequence.
+class CFGUnawareRegisterSafetyAnalysis : public RegisterSafetyAnalysis {
   BinaryFunction &BF;
   MCPlusBuilder::AllocatorIdTy AllocId;
   unsigned StateAnnotationIndex;
@@ -531,11 +553,13 @@ class NoCFGPacRetAnalysis : public PacRetAnalysis {
   }
 
 public:
-  NoCFGPacRetAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
-                      const std::vector<MCPhysReg> &RegsToTrackInstsFor)
-      : PacRetAnalysis(BF, RegsToTrackInstsFor), BF(BF), AllocId(AllocId) {
+  CFGUnawareRegisterSafetyAnalysis(
+      BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+      const std::vector<MCPhysReg> &RegsToTrackInstsFor)
+      : RegisterSafetyAnalysis(BF, RegsToTrackInstsFor), BF(BF),
+        AllocId(AllocId) {
     StateAnnotationIndex =
-        BC.MIB->getOrCreateAnnotationIndex("NoCFGPacRetAnalysis");
+        BC.MIB->getOrCreateAnnotationIndex("CFGUnawareRegisterSafetyAnalysis");
   }
 
   void run() override {
@@ -547,23 +571,22 @@ class NoCFGPacRetAnalysis : public PacRetAnalysis {
       // can be jumped-to, thus conservatively resetting S. As an exception,
       // let's ignore any labels at the beginning of the function, as at least
       // one label is expected there.
-      if (BF.hasLabelAt(I.first) && &Inst != &BF.instrs().begin()->second)
+      if (BF.hasLabelAt(I.first) && &Inst != &BF.instrs().begin()->second) {
+        LLVM_DEBUG({
+          traceInst(BC, "Due to label, resetting the state before", Inst);
+        });
         S = createUnsafeState();
+      }
 
       // Check if we need to remove an old annotation (this is the case if
       // this is the second, detailed, run of the analysis).
       if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex))
         BC.MIB->removeAnnotation(Inst, StateAnnotationIndex);
-      // Attach the state *before* this instruction.
+      // Attach the state *before* this instruction executes.
       BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId);
 
       // Compute the state after this instruction.
-      // If this instruction is an unconditional branch (incl. indirect ones),
-      // reset the state.
-      if (isStateTrackingBoundary(BC, Inst))
-        S = createUnsafeState();
-      else
-        S = computeNext(Inst, S);
+      S = computeNext(Inst, S);
     }
   }
 
@@ -571,16 +594,17 @@ class NoCFGPacRetAnalysis : public PacRetAnalysis {
     return BC.MIB->getAnnotationAs<State>(Inst, StateAnnotationIndex);
   }
 
-  ~NoCFGPacRetAnalysis() { cleanStateAnnotations(); }
+  ~CFGUnawareRegisterSafetyAnalysis() { cleanStateAnnotations(); }
 };
 
-std::shared_ptr<PacRetAnalysis>
-PacRetAnalysis::create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
-                       const std::vector<MCPhysReg> &RegsToTrackInstsFor) {
+std::shared_ptr<RegisterSafetyAnalysis> RegisterSafetyAnalysis::create(
+    BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
+    const std::vector<MCPhysReg> &RegsToTrackInstsFor) {
   if (BF.hasCFG())
-    return std::make_shared<PacRetDFAnalysis>(BF, AllocId, RegsToTrackInstsFor);
-  return std::make_shared<NoCFGPacRetAnalysis>(BF, AllocId,
-                                               RegsToTrackInstsFor);
+    return std::make_shared<DataflowRegisterSafetyAnalysis>(
+        BF, AllocId, RegsToTrackInstsFor);
+  return std::make_shared<CFGUnawareRegisterSafetyAnalysis>(
+      BF, AllocId, RegsToTrackInstsFor);
 }
 
 static std::shared_ptr<Report>
@@ -634,21 +658,33 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
   return std::make_shared<GadgetReport>(CallKind, Inst, DestReg);
 }
 
+template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
+  if (BF.hasCFG()) {
+    for (BinaryBasicBlock &BB : BF)
+      for (int64_t I = 0, E = BB.size(); I < E; ++I)
+        Fn(MCInstInBBReference(&BB, I));
+  } else {
+    for (auto I : BF.instrs())
+      Fn(MCInstInBFReference(&BF, I.first));
+  }
+}
+
 FunctionAnalysisResult
 Analysis::findGadgets(BinaryFunction &BF,
                       MCPlusBuilder::AllocatorIdTy AllocatorId) {
   FunctionAnalysisResult Result;
 
-  auto PRA = PacRetAnalysis::create(BF, AllocatorId, {});
-  PRA->run();
+  auto RSA = RegisterSafetyAnalysis::create(BF, AllocatorId, {});
+  LLVM_DEBUG({ dbgs() << "Running register safety analysis...\n"; });
+  RSA->run();
   LLVM_DEBUG({
-    dbgs() << " After PacRetAnalysis:\n";
+    dbgs() << "After register safety analysis:\n";
     BF.dump();
   });
 
   BinaryContext &BC = BF.getBinaryContext();
   iterateOverInstrs(BF, [&](MCInstReference Inst) {
-    const State &S = *PRA->getStateBefore(Inst);
+    const State &S = *RSA->getStateBefore(Inst);
 
     // If non-empty state was never propagated from the entry basic block
     // to Inst, assume it to be unreachable and report a warning.
@@ -682,10 +718,11 @@ void Analysis::computeDetailedInfo(BinaryFunction &BF,
   std::vector<MCPhysReg> RegsToTrackVec(RegsToTrack.begin(), RegsToTrack.end());
 
   // Re-compute the analysis with register tracking.
-  auto PRWIA = PacRetAnalysis::create(BF, AllocatorId, RegsToTrackVec);
-  PRWIA->run();
+  auto RSWIA = RegisterSafetyAnalysis::create(BF, AllocatorId, RegsToTrackVec);
+  LLVM_DEBUG({ dbgs() << "\nRunning detailed register safety analysis...\n"; });
+  RSWIA->run();
   LLVM_DEBUG({
-    dbgs() << " After detailed PacRetAnalysis:\n";
+    dbgs() << "After detailed register safety analysis:\n";
     BF.dump();
   });
 
@@ -694,7 +731,7 @@ void Analysis::computeDetailedInfo(BinaryFunction &BF,
     LLVM_DEBUG(
         { traceInst(BC, "Attaching clobbering info to", Report->Location); });
     (void)BC;
-    Report->setOverwritingInstrs(PRWIA->getLastClobberingInsts(
+    Report->setOverwritingInstrs(RSWIA->getLastClobberingInsts(
         Report->Location, BF, Report->getAffectedRegisters()));
   }
 }
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
index 8722581f861a5..860584dc88b0e 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s
@@ -50,41 +50,42 @@ simple:
 // CHECK-NEXT:     <empty>
 // CHECK-NEXT: End of Function "simple"
 // CHECK-EMPTY:
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   hint    #25, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT: Running register safety analysis...
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   hint    #25, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   stp     x29, x30, [sp, #-0x10]!, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   stp     x29, x30, [sp, #-0x10]!, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   b       [[BB1]], pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   b       [[BB1]], pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::Confluence(
+// CHECK-NEXT:  DataflowRegisterSafetyAnalysis::Confluence(
 // CHECK-NEXT:    State 1: pacret-state<empty>
 // CHECK-NEXT:    State 2: pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:    merged state: pacret-state<SafeToDerefRegs: , Insts: >
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   autiza  x0, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   autiza  x0, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   blr     x0, pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   blr     x0, pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ldp     x29, x30, [sp], #0x10, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   ldp     x29, x30, [sp], #0x10, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   hint    #29, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   hint    #29, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::Confluence(
+// CHECK-NEXT:  DataflowRegisterSafetyAnalysis::Confluence(
 // CHECK-NEXT:    State 1: pacret-state<SafeToDerefRegs: , Insts: >
 // CHECK-NEXT:    State 2: pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:    merged state: pacret-state<SafeToDerefRegs: , Insts: >
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   autiza  x0, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   autiza  x0, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   blr     x0, pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   blr     x0, pacret-state<SafeToDerefRegs: W0 X0 W0_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ldp     x29, x30, [sp], #0x10, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   ldp     x29, x30, [sp], #0x10, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   hint    #29, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   hint    #29, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
-// CHECK-NEXT:  After PacRetAnalysis:
+// CHECK-NEXT: After register safety analysis:
 // CHECK-NEXT: Binary Function "simple"  {
 // CHECK-NEXT:   Number      : 1
 // CHECK-NEXT:   State       : CFG constructed
@@ -93,27 +94,27 @@ simple:
 // CHECK-NEXT: }
 // CHECK-NEXT: [[BB0]] (3 instructions, align : 1)
 // CHECK-NEXT:   Entry Point
-// CHECK-NEXT:     00000000:   paciasp # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000004:   stp     x29, x30, [sp, #-0x10]! # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000008:   b       [[BB1]] # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000000:   paciasp # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000004:   stp     x29, x30, [sp, #-0x10]! # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000008:   b       [[BB1]] # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT:   Successors: [[BB1]]
 // CHECK-EMPTY:
 // CHECK-NEXT: [[BB1]] (5 instructions, align : 1)
 // CHECK-NEXT:   Predecessors: [[BB0]]
-// CHECK-NEXT:     0000000c:   autiza  x0 # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000010:   blr     x0 # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000014:   ldp     x29, x30, [sp], #0x10 # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000018:   autiasp # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     0000001c:   ret # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     0000000c:   autiza  x0 # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000010:   blr     x0 # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000014:   ldp     x29, x30, [sp], #0x10 # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000018:   autiasp # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     0000001c:   ret # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-EMPTY:
 // CHECK-NEXT: DWARF CFI Instructions:
 // CHECK-NEXT:     <empty>
 // CHECK-NEXT: End of Function "simple"
 // CHECK-EMPTY:
-// PAUTH-NEXT:   Found call inst:     00000000:        blr     x0 # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// PAUTH-NEXT:   Found call inst:     00000000:        blr     x0 # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // PAUTH-NEXT:     Call destination reg: X0
 // PAUTH-NEXT:     SafeToDerefRegs: W0 X0 W0_HI{{[ \t]*$}}
-// CHECK-NEXT:   Found RET inst:     00000000:         ret # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:   Found RET inst:     00000000:         ret # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT:     RetReg: LR
 // CHECK-NEXT:     Authenticated reg: (none)
 // CHECK-NEXT:     SafeToDerefRegs: LR W30 W30_HI{{[ \t]*$}}
@@ -127,11 +128,12 @@ clobber:
 
 // CHECK-LABEL:Analyzing in function clobber, AllocatorId 1
 // ...
-// CHECK:       PacRetAnalysis::ComputeNext(   mov     w30, #0x0, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
-// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
-// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
-// CHECK-NEXT:  After PacRetAnalysis:
+// CHECK:      Running register safety analysis...
+// CHECK-NEXT: RegisterSafetyAnalysis::ComputeNext(   mov     w30, #0x0, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT:  .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
+// CHECK-NEXT: RegisterSafetyAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
+// CHECK-NEXT:  .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: >)
+// CHECK-NEXT: After register safety analysis:
 // CHECK-NEXT: Binary Function "clobber"  {
 // ...
 // CHECK:      End of Function "clobber"
@@ -139,15 +141,17 @@ clobber:
 // The above output was printed after first run of analysis
 
 // CHECK-EMPTY:
-// CHECK-NEXT:   Found RET inst:     00000000:         ret # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:   Found RET inst:     00000000:         ret # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT:     RetReg: LR
 // CHECK-NEXT:     Authenticated reg: (none)
 // CHECK-NEXT:     SafeToDerefRegs: W30_HI{{[ \t]*$}}
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   mov     w30, #0x0, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: [0]()>)
+// CHECK-EMPTY:
+// CHECK-NEXT: Running detailed register safety analysis...
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   mov     w30, #0x0, pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: [0]()>)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>)
-// CHECK-NEXT:  After detailed PacRetAnalysis:
+// CHECK-NEXT: After detailed register safety analysis:
 // CHECK-NEXT: Binary Function "clobber"  {
 // ...
 // CHECK:      End of Function "clobber"
@@ -156,7 +160,7 @@ clobber:
 // Iterating over the reports and attaching clobbering info:
 
 // CHECK-EMPTY:
-// CHECK-NEXT:   Attaching clobbering info to:     00000000:         ret # PacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
+// CHECK-NEXT:   Attaching clobbering info to:     00000000:         ret # DataflowRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
 
         .globl  nocfg
         .type   nocfg, at function
@@ -188,11 +192,15 @@ nocfg:
 // CHECK-NEXT:     <empty>
 // CHECK-NEXT: End of Function "nocfg"
 // CHECK-EMPTY:
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT: Running register safety analysis...
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: >)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: >)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(  br      x0, pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: >)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: , Insts: >)
+// CHECK-NEXT:  Due to label, resetting the state before:     00000000:       ret # Offset: 8
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(  ret     x30, pacret-state<SafeToDerefRegs: , Insts: >)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: >)
-// CHECK-NEXT:  After PacRetAnalysis:
+// CHECK-NEXT: After register safety analysis:
 // CHECK-NEXT: Binary Function "nocfg"  {
 // CHECK-NEXT:   Number      : 3
 // CHECK-NEXT:   State       : disassembled
@@ -200,43 +208,48 @@ nocfg:
 // CHECK:        Secondary Entry Points : __ENTRY_nocfg at 0x[[ENTRY_ADDR]]
 // CHECK-NEXT: }
 // CHECK-NEXT: .{{[A-Za-z0-9]+}}:
-// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
-// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT: __ENTRY_nocfg at 0x[[ENTRY_ADDR]] (Entry Point):
 // CHECK-NEXT: .{{[A-Za-z0-9]+}}:
-// CHECK-NEXT:     00000008:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:     00000008:   ret # Offset: 8 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT: DWARF CFI Instructions:
 // CHECK-NEXT:     <empty>
 // CHECK-NEXT: End of Function "nocfg"
 // CHECK-EMPTY:
-// PAUTH-NEXT:   Found call inst:     00000000:        br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// PAUTH-NEXT:   Found call inst:     00000000:        br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // PAUTH-NEXT:     Call destination reg: X0
 // PAUTH-NEXT:     SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI
-// CHECK-NEXT:   Found RET inst:     00000000:         ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
+// CHECK-NEXT:   Found RET inst:     00000000:         ret # Offset: 8 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: >
 // CHECK-NEXT:     RetReg: LR
 // CHECK-NEXT:     Authenticated reg: (none)
 // CHECK-NEXT:     SafeToDerefRegs:
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: [0]()>)
+// CHECK-EMPTY:
+// CHECK-NEXT: Running detailed register safety analysis...
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]], pacret-state<SafeToDerefRegs: LR W30 W30_HI , Insts: [0]()>)
+// CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: [0]()>)
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(  br      x0, pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: [0]()>)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI , Insts: [0]()>)
-// CHECK-NEXT:  PacRetAnalysis::ComputeNext(   ret     x30, pacret-state<SafeToDerefRegs: , Insts: [0]()>)
+// CHECK-NEXT:  Due to label, resetting the state before:     00000000:       ret # Offset: 8
+// CHECK-NEXT:  RegisterSafetyAnalysis::ComputeNext(  ret     x30, pacret-state<SafeToDerefRegs: , Insts: [0]()>)
 // CHECK-NEXT:   .. result: (pacret-state<SafeToDerefRegs: , Insts: [0]()>)
-// CHECK-NEXT:  After detailed PacRetAnalysis:
+// CHECK-NEXT: After detailed register safety analysis:
 // CHECK-NEXT: Binary Function "nocfg"  {
 // CHECK-NEXT:   Number      : 3
 // ...
 // CHECK:        Secondary Entry Points : __ENTRY_nocfg at 0x[[ENTRY_ADDR]]
 // CHECK-NEXT: }
 // CHECK-NEXT: .{{[A-Za-z0-9]+}}:
-// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
-// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT:     00000000:   adr     x0, __ENTRY_nocfg at 0x[[ENTRY_ADDR]] # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT:     00000004:   br      x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
 // CHECK-NEXT: __ENTRY_nocfg at 0x[[ENTRY_ADDR]] (Entry Point):
 // CHECK-NEXT: .{{[A-Za-z0-9]+}}:
-// CHECK-NEXT:     00000008:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT:     00000008:   ret # Offset: 8 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
 // CHECK-NEXT: DWARF CFI Instructions:
 // CHECK-NEXT:     <empty>
 // CHECK-NEXT: End of Function "nocfg"
 // CHECK-EMPTY:
-// CHECK-NEXT:   Attaching clobbering info to:     00000000:   ret # Offset: 8 # NoCFGPacRetAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
+// CHECK-NEXT:   Attaching clobbering info to:     00000000:   ret # Offset: 8 # CFGUnawareRegisterSafetyAnalysis: pacret-state<SafeToDerefRegs: BitVector, Insts: [0]()>
 
 // CHECK-LABEL:Analyzing in function main, AllocatorId 1
         .globl  main

>From ceb9d04b92a843788e22e17dca5b542ab2780f4b Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Tue, 8 Apr 2025 16:02:07 +0300
Subject: [PATCH 6/6] Update the tests

---
 .../binary-analysis/AArch64/gs-pauth-calls.s  | 67 ++++++++++++++++++-
 1 file changed, 65 insertions(+), 2 deletions(-)

diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
index ff59fb4d08e58..0b6bd5509a42a 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
@@ -430,6 +430,23 @@ bad_indirect_call_mem_chain_of_auts_multi_bb:
         .size bad_indirect_call_mem_chain_of_auts_multi_bb, .-bad_indirect_call_mem_chain_of_auts_multi_bb
 
 // Tests for CFG-unaware analysis.
+//
+// All these tests use an instruction sequence like this
+//
+//      adr x2, 1f
+//      br  x2
+//    1:
+//      ; ...
+//
+// to make BOLT unable to reconstruct the control flow. Note that one can easily
+// tell whether the report corresponds to a function with or without CFG:
+// normally, the location of the gadget is described like this:
+//
+//     ... found in function <function_name>, basic block <basic block name>, at address <address>
+//
+// When CFG information is not available, this is reduced to
+//
+//     ... found in function <function_name>, at address <address>
 
         .globl  good_direct_call_nocfg
         .type   good_direct_call_nocfg, at function
@@ -556,9 +573,12 @@ obscure_indirect_call_arg_nocfg:
         mov     x29, sp
 
         autia   x0, x1 // not observed by the checker
-        b       1f     // ... because of unconditional branch
+        b       1f
 1:
-        blr     x0     // reported as non-protected
+        // The register state is pessimistically reset after a label, thus
+        // the below branch instruction is reported as non-protected - this is
+        // a known false-positive.
+        blr     x0
 
         adr     x2, 1f
         br      x2
@@ -568,6 +588,49 @@ obscure_indirect_call_arg_nocfg:
         ret
         .size obscure_good_indirect_call_arg_nocfg, .-obscure_good_indirect_call_arg_nocfg
 
+        .globl  safe_lr_at_function_entry_nocfg
+        .type   safe_lr_at_function_entry_nocfg, at function
+safe_lr_at_function_entry_nocfg:
+// CHECK-NOT: safe_lr_at_function_entry_nocfg
+        cbz     x0, 1f
+        ret                            // LR is safe at the start of the function
+1:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        adr     x2, 2f
+        br      x2
+2:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size safe_lr_at_function_entry_nocfg, .-safe_lr_at_function_entry_nocfg
+
+        .globl  lr_is_never_unsafe_before_first_inst_nocfg
+        .type   lr_is_never_unsafe_before_first_inst_nocfg, at function
+// CHECK-NOT: lr_is_never_unsafe_before_first_inst_nocfg
+lr_is_never_unsafe_before_first_inst_nocfg:
+1:
+        // The register state is never reset before the first instruction of
+        // the function. This can lead to a known false-negative if LR is
+        // clobbered and then a jump to the very first instruction of the
+        // function is performed.
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        mov     x30, x0
+        cbz     x1, 1b
+
+        adr     x2, 2f
+        br      x2
+2:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size lr_is_never_unsafe_before_first_inst_nocfg, .-lr_is_never_unsafe_before_first_inst_nocfg
+
         .globl  bad_indirect_call_mem_nocfg
         .type   bad_indirect_call_mem_nocfg, at function
 bad_indirect_call_mem_nocfg:



More information about the llvm-commits mailing list