diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index b285138b77fe7..76ea2489e7038 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -551,6 +551,16 @@ class MCPlusBuilder { return Analysis->isReturn(Inst); } + /// Returns the registers that are trusted at function entry. + /// + /// Each register should be treated as if a successfully authenticated + /// pointer was written to it before entering the function (i.e. the + /// pointer is safe to jump to as well as to be signed). + virtual SmallVector getTrustedLiveInRegs() const { + llvm_unreachable("not implemented"); + return {}; + } + virtual ErrorOr getAuthenticatedReg(const MCInst &Inst) const { llvm_unreachable("not implemented"); return getNoRegister(); diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h index 1a8abffa09c46..700059b814ab9 100644 --- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h +++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h @@ -212,7 +212,7 @@ struct GadgetReport : public Report { // The particular kind of gadget that is detected. const GadgetKind &Kind; // The set of registers related to this gadget report (possibly empty). - SmallVector AffectedRegisters; + SmallVector AffectedRegisters; // The instructions that clobber the affected registers. // There is no one-to-one correspondence with AffectedRegisters: for example, // the same register can be overwritten by different instructions in different @@ -220,9 +220,8 @@ struct GadgetReport : public Report { SmallVector OverwritingInstrs; GadgetReport(const GadgetKind &Kind, MCInstReference Location, - const BitVector &AffectedRegisters) - : Report(Location), Kind(Kind), - AffectedRegisters(AffectedRegisters.set_bits()) {} + MCPhysReg AffectedRegister) + : Report(Location), Kind(Kind), AffectedRegisters({AffectedRegister}) {} void generateReport(raw_ostream &OS, const BinaryContext &BC) const override; diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 84c209ac838f8..badff7a07b5b8 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -126,18 +126,16 @@ class TrackedRegisters { // 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 either: -// (a) never be changed within this function, i.e. have the same value as when -// the function started, or +// that register must be safe-to-dereference. It must either +// (a) be safe-to-dereference at function entry and never be changed within this +// function, i.e. have the same value as when the function started, or // (b) the last write to the register must be by an authentication instruction. // This property is checked by using dataflow analysis to keep track of which -// registers have been written (def-ed), since last authenticated. Those are -// exactly the registers containing values that should not be trusted (as they -// could have changed since the last time they were authenticated). For pac-ret, -// any return instruction using such a register is a gadget to be reported. For -// PAuthABI, probably at least any indirect control flow using such a register -// should be reported. +// registers have been written (def-ed), since last authenticated. For pac-ret, +// any return instruction using a register which is not safe-to-dereference is +// a gadget to be reported. For PAuthABI, probably at least any indirect control +// flow using such a register should be reported. // Furthermore, when producing a diagnostic for a found non-pac-ret protected // return, the analysis also lists the last instructions that wrote to the @@ -156,10 +154,29 @@ class TrackedRegisters { // in the gadgets to be reported. This information is used in the second run // to also track which instructions last wrote to those registers. +/// A state representing which registers are safe to use by an instruction +/// at a given program point. +/// +/// To simplify reasoning, let's stick with the following approach: +/// * when state is updated by the data-flow analysis, the sub-, super- and +/// overlapping registers are marked as needed +/// * when the particular instruction is checked if it represents a gadget, +/// the specific bit of BitVector should be usable to answer this. +/// +/// For example, on AArch64: +/// * An AUTIZA X0 instruction marks both X0 and W0 (as well as W0_HI) as +/// safe-to-dereference. It does not change the state of X0_X1, for example, +/// as super-registers partially retain their old, unsafe values. +/// * LDR X1, [X0] marks as unsafe both X1 itself and anything it overlaps +/// with: W1, W1_HI, X0_X1 and so on. +/// * RET (which is implicitly RET X30) is a protected return if and only if +/// X30 is safe-to-dereference - the state computed for sub- and +/// super-registers is not inspected. struct State { - /// A BitVector containing the registers that have been clobbered, and - /// not authenticated. - BitVector NonAutClobRegs; + /// A BitVector containing the registers that are either safe at function + /// entry and were not clobbered yet, or those not clobbered since being + /// authenticated. + BitVector SafeToDerefRegs; /// A vector of sets, only used in the second data flow run. /// Each element in the vector represents one of the registers for which we /// track the set of last instructions that wrote to this register. For @@ -167,18 +184,32 @@ struct State { /// only use register `X30`, and therefore, this vector will probably have /// length 1 in the second run. std::vector> LastInstWritingReg; + + /// Construct an empty state. State() {} + State(unsigned NumRegs, unsigned NumRegsToTrack) - : NonAutClobRegs(NumRegs), LastInstWritingReg(NumRegsToTrack) {} - State &operator|=(const State &StateIn) { - NonAutClobRegs |= StateIn.NonAutClobRegs; + : SafeToDerefRegs(NumRegs), LastInstWritingReg(NumRegsToTrack) {} + + State &merge(const State &StateIn) { + if (StateIn.empty()) + return *this; + if (empty()) + return (*this = StateIn); + + SafeToDerefRegs &= StateIn.SafeToDerefRegs; for (unsigned I = 0; I < LastInstWritingReg.size(); ++I) for (const MCInst *J : StateIn.LastInstWritingReg[I]) LastInstWritingReg[I].insert(J); return *this; } + + /// Returns true if this object does not store state of any registers - + /// neither safe, nor unsafe ones. + bool empty() const { return SafeToDerefRegs.empty(); } + bool operator==(const State &RHS) const { - return NonAutClobRegs == RHS.NonAutClobRegs && + return SafeToDerefRegs == RHS.SafeToDerefRegs && LastInstWritingReg == RHS.LastInstWritingReg; } bool operator!=(const State &RHS) const { return !((*this) == RHS); } @@ -199,8 +230,12 @@ static void printLastInsts( raw_ostream &operator<<(raw_ostream &OS, const State &S) { OS << "pacret-state<"; - OS << "NonAutClobRegs: " << S.NonAutClobRegs << ", "; - printLastInsts(OS, S.LastInstWritingReg); + if (S.empty()) { + OS << "empty"; + } else { + OS << "SafeToDerefRegs: " << S.SafeToDerefRegs << ", "; + printLastInsts(OS, S.LastInstWritingReg); + } OS << ">"; return OS; } @@ -217,10 +252,16 @@ class PacStatePrinter { void PacStatePrinter::print(raw_ostream &OS, const State &S) const { RegStatePrinter RegStatePrinter(BC); OS << "pacret-state<"; - OS << "NonAutClobRegs: "; - RegStatePrinter.print(OS, S.NonAutClobRegs); - OS << ", "; - printLastInsts(OS, S.LastInstWritingReg); + if (S.empty()) { + assert(S.SafeToDerefRegs.empty()); + assert(S.LastInstWritingReg.empty()); + OS << "empty"; + } else { + OS << "SafeToDerefRegs: "; + RegStatePrinter.print(OS, S.SafeToDerefRegs); + OS << ", "; + printLastInsts(OS, S.LastInstWritingReg); + } OS << ">"; } @@ -257,14 +298,22 @@ class PacRetAnalysis void preflight() {} - State getStartingStateAtBB(const BinaryBasicBlock &BB) { - return State(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); + State createEntryState() { + State S(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); + for (MCPhysReg Reg : BC.MIB->getTrustedLiveInRegs()) + S.SafeToDerefRegs |= BC.MIB->getAliases(Reg, /*OnlySmaller=*/true); + return S; } - State getStartingStateAtPoint(const MCInst &Point) { - return State(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); + 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({ @@ -277,7 +326,7 @@ class PacRetAnalysis dbgs() << ")\n"; }); - StateOut |= StateIn; + StateOut.merge(StateIn); LLVM_DEBUG({ dbgs() << " merged state: "; @@ -297,8 +346,17 @@ class PacRetAnalysis dbgs() << ")\n"; }); + // If this instruction is reachable, a non-empty state will be propagated + // to it from the entry basic block sooner or later. Until then, it is both + // more efficient and easier to reason about to skip computeNext(). + if (Cur.empty()) { + LLVM_DEBUG( + { dbgs() << "Skipping computeNext(Point, Cur) as Cur is empty.\n"; }); + return State(); + } + State Next = Cur; - BitVector Written = BitVector(NumRegs, false); + BitVector Clobbered(NumRegs, false); // Assume a call can clobber all registers, including callee-saved // registers. There's a good chance that callee-saved registers will be // saved on the stack at some point during execution of the callee. @@ -307,36 +365,27 @@ class PacRetAnalysis // Also, not all functions may respect the AAPCS ABI rules about // caller/callee-saved registers. if (BC.MIB->isCall(Point)) - Written.set(); + Clobbered.set(); else - // FIXME: `getWrittenRegs` only sets the register directly written in the - // instruction, and the smaller aliasing registers. It does not set the - // larger aliasing registers. To also set the larger aliasing registers, - // we'd have to call `getClobberedRegs`. - // It is unclear if there is any test case which shows a different - // behaviour between using `getWrittenRegs` vs `getClobberedRegs`. We'd - // first would like to see such a test case before making a decision - // on whether using `getClobberedRegs` below would be better. - // Also see the discussion on this at - // https://github.com/llvm/llvm-project/pull/122304#discussion_r1939511909 - BC.MIB->getWrittenRegs(Point, Written); - Next.NonAutClobRegs |= Written; + BC.MIB->getClobberedRegs(Point, Clobbered); + Next.SafeToDerefRegs.reset(Clobbered); // Keep track of this instruction if it writes to any of the registers we // need to track that for: for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters()) - if (Written[Reg]) + if (Clobbered[Reg]) lastWritingInsts(Next, Reg) = {&Point}; ErrorOr AutReg = BC.MIB->getAuthenticatedReg(Point); if (AutReg && *AutReg != BC.MIB->getNoRegister()) { - // FIXME: should we use `OnlySmaller=false` below? See similar - // FIXME about `getWrittenRegs` above and further discussion about this - // at - // https://github.com/llvm/llvm-project/pull/122304#discussion_r1939515516 - Next.NonAutClobRegs.reset( - BC.MIB->getAliases(*AutReg, /*OnlySmaller=*/true)); - if (RegsToTrackInstsFor.isTracked(*AutReg)) - lastWritingInsts(Next, *AutReg).clear(); + // The sub-registers of *AutReg are also trusted now, but not its + // super-registers (as they retain untrusted register units). + BitVector AuthenticatedSubregs = + BC.MIB->getAliases(*AutReg, /*OnlySmaller=*/true); + for (MCPhysReg Reg : AuthenticatedSubregs.set_bits()) { + Next.SafeToDerefRegs.set(Reg); + if (RegsToTrackInstsFor.isTracked(Reg)) + lastWritingInsts(Next, Reg).clear(); + } } LLVM_DEBUG({ @@ -397,14 +446,11 @@ shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst, }); if (BC.MIB->isAuthenticationOfReg(Inst, RetReg)) return nullptr; - BitVector UsedDirtyRegs = S.NonAutClobRegs; - LLVM_DEBUG({ traceRegMask(BC, "NonAutClobRegs at Ret", UsedDirtyRegs); }); - UsedDirtyRegs &= BC.MIB->getAliases(RetReg, /*OnlySmaller=*/true); - LLVM_DEBUG({ traceRegMask(BC, "Intersection with RetReg", UsedDirtyRegs); }); - if (!UsedDirtyRegs.any()) + LLVM_DEBUG({ traceRegMask(BC, "SafeToDerefRegs", S.SafeToDerefRegs); }); + if (S.SafeToDerefRegs[RetReg]) return nullptr; - return std::make_shared(RetKind, Inst, UsedDirtyRegs); + return std::make_shared(RetKind, Inst, RetReg); } FunctionAnalysisResult @@ -425,6 +471,14 @@ Analysis::findGadgets(BinaryFunction &BF, MCInstReference Inst(&BB, I); const State &S = *PRA.getStateAt(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( + Inst, "Warning: unreachable instruction found")); + continue; + } + if (auto Report = shouldReportReturnGadget(BC, Inst, S)) Result.Diagnostics.push_back(Report); } diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index 613b24c4553e2..d238a1df5c7d7 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -191,6 +191,10 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { return false; } + SmallVector getTrustedLiveInRegs() const override { + return {AArch64::LR}; + } + ErrorOr getAuthenticatedReg(const MCInst &Inst) const override { switch (Inst.getOpcode()) { case AArch64::AUTIAZ: diff --git a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s index 0d263199b376f..01b7cec3272e6 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s +++ b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s @@ -183,17 +183,14 @@ f_tail_called: .globl f_nonx30_ret_non_auted .type f_nonx30_ret_non_auted,@function f_nonx30_ret_non_auted: -// FIXME: x1 is not authenticated, so should this be reported? -// Note that we assume it's fine for x30 to not be authenticated before -// returning to, as assuming that x30 is not attacker controlled at function -// entry is part (implicitly) of the pac-ret hardening scheme. -// It's probably an open question whether for other hardening schemes, such as -// PAuthABI, which registers should be considered "clean" or not at function entry. -// In other words, which registers have to be authenticated before being used as -// a pointer and which ones not? -// For a more detailed discussion, see -// https://github.com/llvm/llvm-project/pull/122304#discussion_r1923662744 -// CHECK-NOT: f_nonx30_ret_non_auted +// x1 is neither authenticated nor implicitly considered safe at function entry. +// Note that we assume it's fine for x30 to not be authenticated before +// returning to, as assuming that x30 is not attacker controlled at function +// entry is part (implicitly) of the pac-ret hardening scheme. +// +// CHECK-LABEL: GS-PAUTH: non-protected ret found in function f_nonx30_ret_non_auted, basic block {{[0-9a-zA-Z.]+}}, 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: ret x1 .size f_nonx30_ret_non_auted, .-f_nonx30_ret_non_auted @@ -230,6 +227,16 @@ f_callclobbered_calleesaved: ret x19 .size f_callclobbered_calleesaved, .-f_callclobbered_calleesaved + .globl f_unreachable_instruction + .type f_unreachable_instruction,@function +f_unreachable_instruction: +// CHECK-LABEL: GS-PAUTH: Warning: unreachable instruction found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: add x0, x1, x2 + b 1f + add x0, x1, x2 +1: + ret + .size f_unreachable_instruction, .-f_unreachable_instruction /// Now do a basic sanity check on every different Authentication instruction: diff --git a/bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s b/bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s index f74e825ed8fc1..bd8edbc676c34 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s +++ b/bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s @@ -17,9 +17,8 @@ f_crossbb1: .size f_crossbb1, .-f_crossbb1 // CHECK-LABEL: GS-PAUTH: non-protected ret found in function f_crossbb1, basic block {{[^,]+}}, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: ret -// CHECK-NEXT: The 2 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]+}}: ldp x29, x30, [sp], #0x10 -// CHECK-NEXT: 2. {{[0-9a-f]+}}: autiasp // A test that checks that the dataflow state tracking across when merging BBs // seems to work: 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 18e0fbfb850e7..30b70b060b94b 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s @@ -40,26 +40,26 @@ simple: // CHECK-NEXT: // CHECK-NEXT: End of Function "simple" // CHECK-EMPTY: -// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #25, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) -// CHECK-NEXT: PacRetAnalysis::ComputeNext( b [[BB1]], pacret-state) -// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #25, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( b [[BB1]], pacret-state) +// CHECK-NEXT: .. result: (pacret-state) // CHECK-NEXT: PacRetAnalysis::Confluence( -// CHECK-NEXT: State 1: pacret-state -// CHECK-NEXT: State 2: pacret-state) -// CHECK-NEXT: merged state: pacret-state -// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #29, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) -// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: State 1: pacret-state +// CHECK-NEXT: State 2: pacret-state) +// CHECK-NEXT: merged state: pacret-state +// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #29, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) // CHECK-NEXT: PacRetAnalysis::Confluence( -// CHECK-NEXT: State 1: pacret-state -// CHECK-NEXT: State 2: pacret-state) -// CHECK-NEXT: merged state: pacret-state -// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #29, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) -// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: State 1: pacret-state +// CHECK-NEXT: State 2: pacret-state) +// CHECK-NEXT: merged state: pacret-state +// CHECK-NEXT: PacRetAnalysis::ComputeNext( hint #29, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) // CHECK-NEXT: After PacRetAnalysis: // CHECK-NEXT: Binary Function "simple" { // CHECK-NEXT: Number : 1 @@ -69,24 +69,23 @@ simple: // CHECK-NEXT: } // CHECK-NEXT: [[BB0]] (2 instructions, align : 1) // CHECK-NEXT: Entry Point -// CHECK-NEXT: 00000000: paciasp # PacRetAnalysis: pacret-state -// CHECK-NEXT: 00000004: b [[BB1]] # PacRetAnalysis: pacret-state +// CHECK-NEXT: 00000000: paciasp # PacRetAnalysis: pacret-state +// CHECK-NEXT: 00000004: b [[BB1]] # PacRetAnalysis: pacret-state // CHECK-NEXT: Successors: [[BB1]] // CHECK-EMPTY: // CHECK-NEXT: [[BB1]] (2 instructions, align : 1) // CHECK-NEXT: Predecessors: [[BB0]] -// CHECK-NEXT: 00000008: autiasp # PacRetAnalysis: pacret-state -// CHECK-NEXT: 0000000c: ret # PacRetAnalysis: pacret-state +// CHECK-NEXT: 00000008: autiasp # PacRetAnalysis: pacret-state +// CHECK-NEXT: 0000000c: ret # PacRetAnalysis: pacret-state // CHECK-EMPTY: // CHECK-NEXT: DWARF CFI Instructions: // CHECK-NEXT: // CHECK-NEXT: End of Function "simple" // CHECK-EMPTY: -// CHECK-NEXT: Found RET inst: 00000000: ret # PacRetAnalysis: pacret-state +// CHECK-NEXT: Found RET inst: 00000000: ret # PacRetAnalysis: pacret-state // CHECK-NEXT: RetReg: LR // CHECK-NEXT: Authenticated reg: (none) -// CHECK-NEXT: NonAutClobRegs at Ret:{{[ \t]*$}} -// CHECK-NEXT: Intersection with RetReg:{{[ \t]*$}} +// CHECK-NEXT: SafeToDerefRegs: LR W30 W30_HI{{[ \t]*$}} .globl clobber .type clobber,@function @@ -97,10 +96,10 @@ clobber: // CHECK-LABEL:Analyzing in function clobber, AllocatorId 1 // ... -// CHECK: PacRetAnalysis::ComputeNext( mov w30, #0x0, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) -// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) +// CHECK: PacRetAnalysis::ComputeNext( mov w30, #0x0, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) // CHECK-NEXT: After PacRetAnalysis: // CHECK-NEXT: Binary Function "clobber" { // ... @@ -109,15 +108,14 @@ clobber: // The above output was printed after first run of analysis // CHECK-EMPTY: -// CHECK-NEXT: Found RET inst: 00000000: ret # PacRetAnalysis: pacret-state +// CHECK-NEXT: Found RET inst: 00000000: ret # PacRetAnalysis: pacret-state // CHECK-NEXT: RetReg: LR // CHECK-NEXT: Authenticated reg: (none) -// CHECK-NEXT: NonAutClobRegs at Ret: W30 -// CHECK-NEXT: Intersection with RetReg: W30 -// CHECK-NEXT: PacRetAnalysis::ComputeNext( mov w30, #0x0, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) -// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) -// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: SafeToDerefRegs: W30_HI{{[ \t]*$}} +// CHECK-NEXT: PacRetAnalysis::ComputeNext( mov w30, #0x0, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) +// CHECK-NEXT: PacRetAnalysis::ComputeNext( ret x30, pacret-state) +// CHECK-NEXT: .. result: (pacret-state) // CHECK-NEXT: After detailed PacRetAnalysis: // CHECK-NEXT: Binary Function "clobber" { // ... @@ -127,7 +125,7 @@ clobber: // Iterating over the reports and attaching clobbering info: // CHECK-EMPTY: -// CHECK-NEXT: Attaching clobbering info to: 00000000: ret # PacRetAnalysis: pacret-state +// CHECK-NEXT: Attaching clobbering info to: 00000000: ret # PacRetAnalysis: pacret-state // CHECK-LABEL:Analyzing in function main, AllocatorId 1