mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 10:42:39 +01:00
[WinEH] Verify unwind edges against EH pad tree
Summary: Funclet EH personalities require a tree-like nesting among funclets (enforced by the ParentPad linkage in the IR), and also require that unwind edges conform to certain rules with respect to the tree: - An unwind edge may exit 0 or more ancestor pads - An unwind edge must enter exactly one EH pad, which must be distinct from any exited pads - A cleanupret's edge must exit its cleanuppad Describe these rules in the LangRef, and enforce them in the verifier. Reviewers: rnk, majnemer, andrew.w.kaylor Subscribers: llvm-commits Differential Revision: http://reviews.llvm.org/D15961 llvm-svn: 257272
This commit is contained in:
parent
eb0f129b1b
commit
c511cef4af
@ -775,3 +775,46 @@ C++ code:
|
||||
|
||||
The "inner" ``catchswitch`` consumes ``%1`` which is produced by the outer
|
||||
catchswitch.
|
||||
|
||||
.. _wineh-constraints:
|
||||
|
||||
Funclet transitions
|
||||
-----------------------
|
||||
|
||||
The EH tables for personalities that use funclets make implicit use of the
|
||||
funclet nesting relationship to encode unwind destinations, and so are
|
||||
constrained in the set of funclet transitions they can represent. The related
|
||||
LLVM IR instructions accordingly have constraints that ensure encodability of
|
||||
the EH edges in the flow graph.
|
||||
|
||||
A ``catchswitch``, ``catchpad``, or ``cleanuppad`` is said to be "entered"
|
||||
when it executes. It may subsequently be "exited" by any of the following
|
||||
means:
|
||||
|
||||
* A ``catchswitch`` is immediately exited when none of its constituent
|
||||
``catchpad``\ s are appropriate for the in-flight exception and it unwinds
|
||||
to its unwind destination or the caller.
|
||||
* A ``catchpad`` and its parent ``catchswitch`` are both exited when a
|
||||
``catchret`` from the ``catchpad`` is executed.
|
||||
* A ``cleanuppad`` is exited when a ``cleanupret`` from it is executed.
|
||||
* Any of these pads is exited when control unwinds to the function's caller,
|
||||
either by a ``call`` which unwinds all the way to the function's caller,
|
||||
a nested ``catchswitch`` marked "``unwinds to caller``", or a nested
|
||||
``cleanuppad``\ 's ``cleanupret`` marked "``unwinds to caller"``.
|
||||
* Any of these pads is exited when an unwind edge (from an ``invoke``,
|
||||
nested ``catchswitch``, or nested ``cleanuppad``\ 's ``cleanupret``)
|
||||
unwinds to a destination pad that is not a descendant of the given pad.
|
||||
|
||||
Note that the ``ret`` instruction is *not* a valid way to exit a funclet pad;
|
||||
it is undefined behavior to execute a ``ret`` when a pad has been entered but
|
||||
not exited.
|
||||
|
||||
A single unwind edge may exit any number of pads (with the restrictions that
|
||||
the edge from a ``catchswitch`` must exit at least itself, and the edge from
|
||||
a ``cleanupret`` must exit at least its ``cleanuppad``), and then must enter
|
||||
exactly one pad, which must be distinct from all the exited pads. The parent
|
||||
of the pad that an unwind edge enters must be the most-recently-entered
|
||||
not-yet-exited pad (after exiting from any pads that the unwind edge exits),
|
||||
or "none" if there is no such pad. This ensures that the stack of executing
|
||||
funclets at run-time always corresponds to some path in the funclet pad tree
|
||||
that the parent tokens encode.
|
||||
|
@ -1579,6 +1579,8 @@ caller's deoptimization state to the callee's deoptimization state is
|
||||
semantically equivalent to composing the caller's deoptimization
|
||||
continuation after the callee's deoptimization continuation.
|
||||
|
||||
.. _ob_funclet:
|
||||
|
||||
Funclet Operand Bundles
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -1588,6 +1590,18 @@ is within a particular funclet. There can be at most one
|
||||
``"funclet"`` operand bundle attached to a call site and it must have
|
||||
exactly one bundle operand.
|
||||
|
||||
If any funclet EH pads have been "entered" but not "exited" (per the
|
||||
`description in the EH doc\ <ExceptionHandling.html#wineh-constraints>`_),
|
||||
it is undefined behavior to execute a ``call`` or ``invoke`` which:
|
||||
|
||||
* does not have a ``"funclet"`` bundle and is not a ``call`` to a nounwind
|
||||
intrinsic, or
|
||||
* has a ``"funclet"`` bundle whose operand is not the most-recently-entered
|
||||
not-yet-exited funclet EH pad.
|
||||
|
||||
Similarly, if no funclet EH pads have been entered-but-not-yet-exited,
|
||||
executing a ``call`` or ``invoke`` with a ``"funclet"`` bundle is undefined behavior.
|
||||
|
||||
.. _moduleasm:
|
||||
|
||||
Module-Level Inline Assembly
|
||||
@ -5404,10 +5418,12 @@ The ``parent`` argument is the token of the funclet that contains the
|
||||
``catchswitch`` instruction. If the ``catchswitch`` is not inside a funclet,
|
||||
this operand may be the token ``none``.
|
||||
|
||||
The ``default`` argument is the label of another basic block beginning with a
|
||||
"pad" instruction, one of ``cleanuppad`` or ``catchswitch``.
|
||||
The ``default`` argument is the label of another basic block beginning with
|
||||
either a ``cleanuppad`` or ``catchswitch`` instruction. This unwind destination
|
||||
must be a legal target with respect to the ``parent`` links, as described in
|
||||
the `exception handling documentation\ <ExceptionHandling.html#wineh-constraints>`_.
|
||||
|
||||
The ``handlers`` are a list of successor blocks that each begin with a
|
||||
The ``handlers`` are a nonempty list of successor blocks that each begin with a
|
||||
:ref:`catchpad <i_catchpad>` instruction.
|
||||
|
||||
Semantics:
|
||||
@ -5481,20 +5497,12 @@ instruction must be the first non-phi of its parent basic block.
|
||||
|
||||
The meaning of the tokens produced and consumed by ``catchpad`` and other "pad"
|
||||
instructions is described in the
|
||||
`Windows exception handling documentation <ExceptionHandling.html#wineh>`.
|
||||
`Windows exception handling documentation\ <ExceptionHandling.html#wineh>`_.
|
||||
|
||||
Executing a ``catchpad`` instruction constitutes "entering" that pad.
|
||||
The pad may then be "exited" in one of three ways:
|
||||
|
||||
1) explicitly via a ``catchret`` that consumes it. Executing such a ``catchret``
|
||||
is undefined behavior if any descendant pads have been entered but not yet
|
||||
exited.
|
||||
2) implicitly via a call (which unwinds all the way to the current function's caller),
|
||||
or via a ``catchswitch`` or a ``cleanupret`` that unwinds to caller.
|
||||
3) implicitly via an unwind edge whose destination EH pad isn't a descendant of
|
||||
the ``catchpad``. When the ``catchpad`` is exited in this manner, it is
|
||||
undefined behavior if the destination EH pad has a parent which is not an
|
||||
ancestor of the ``catchpad`` being exited.
|
||||
When a ``catchpad`` has been "entered" but not yet "exited" (as
|
||||
described in the `EH documentation\ <ExceptionHandling.html#wineh-constraints>`_),
|
||||
it is undefined behavior to execute a :ref:`call <i_call>` or :ref:`invoke <i_invoke>`
|
||||
that does not carry an appropriate :ref:`"funclet" bundle <ob_funclet>`.
|
||||
|
||||
Example:
|
||||
""""""""
|
||||
@ -5543,11 +5551,10 @@ unwinding was interrupted with a :ref:`catchpad <i_catchpad>` instruction. The
|
||||
code to, for example, destroy the active exception. Control then transfers to
|
||||
``normal``.
|
||||
|
||||
The ``token`` argument must be a token produced by a dominating ``catchpad``
|
||||
instruction. The ``catchret`` destroys the physical frame established by
|
||||
``catchpad``, so executing multiple returns on the same token without
|
||||
re-executing the ``catchpad`` will result in undefined behavior.
|
||||
See :ref:`catchpad <i_catchpad>` for more details.
|
||||
The ``token`` argument must be a token produced by a ``catchpad`` instruction.
|
||||
If the specified ``catchpad`` is not the most-recently-entered not-yet-exited
|
||||
funclet pad (as described in the `EH documentation\ <ExceptionHandling.html#wineh-constraints>`_),
|
||||
the ``catchret``'s behavior is undefined.
|
||||
|
||||
Example:
|
||||
""""""""
|
||||
@ -5581,7 +5588,15 @@ Arguments:
|
||||
|
||||
The '``cleanupret``' instruction requires one argument, which indicates
|
||||
which ``cleanuppad`` it exits, and must be a :ref:`cleanuppad <i_cleanuppad>`.
|
||||
It also has an optional successor, ``continue``.
|
||||
If the specified ``cleanuppad`` is not the most-recently-entered not-yet-exited
|
||||
funclet pad (as described in the `EH documentation\ <ExceptionHandling.html#wineh-constraints>`_),
|
||||
the ``cleanupret``'s behavior is undefined.
|
||||
|
||||
The '``cleanupret``' instruction also has an optional successor, ``continue``,
|
||||
which must be the label of another basic block beginning with either a
|
||||
``cleanuppad`` or ``catchswitch`` instruction. This unwind destination must
|
||||
be a legal target with respect to the ``parent`` links, as described in the
|
||||
`exception handling documentation\ <ExceptionHandling.html#wineh-constraints>`_.
|
||||
|
||||
Semantics:
|
||||
""""""""""
|
||||
@ -5591,13 +5606,6 @@ The '``cleanupret``' instruction indicates to the
|
||||
:ref:`cleanuppad <i_cleanuppad>` it transferred control to has ended.
|
||||
It transfers control to ``continue`` or unwinds out of the function.
|
||||
|
||||
The unwind destination ``continue``, if present, must be an EH pad
|
||||
whose parent is either ``none`` or an ancestor of the ``cleanuppad``
|
||||
being returned from. This constitutes an exceptional exit from all
|
||||
ancestors of the completed ``cleanuppad``, up to but not including
|
||||
the parent of ``continue``.
|
||||
See :ref:`cleanuppad <i_cleanuppad>` for more details.
|
||||
|
||||
Example:
|
||||
""""""""
|
||||
|
||||
@ -8644,18 +8652,10 @@ The ``cleanuppad`` instruction has several restrictions:
|
||||
- A basic block that is not a cleanup block may not include a
|
||||
'``cleanuppad``' instruction.
|
||||
|
||||
Executing a ``cleanuppad`` instruction constitutes "entering" that pad.
|
||||
The pad may then be "exited" in one of three ways:
|
||||
|
||||
1) explicitly via a ``cleanupret`` that consumes it. Executing such a ``cleanupret``
|
||||
is undefined behavior if any descendant pads have been entered but not yet
|
||||
exited.
|
||||
2) implicitly via a call (which unwinds all the way to the current function's caller),
|
||||
or via a ``catchswitch`` or a ``cleanupret`` that unwinds to caller.
|
||||
3) implicitly via an unwind edge whose destination EH pad isn't a descendant of
|
||||
the ``cleanuppad``. When the ``cleanuppad`` is exited in this manner, it is
|
||||
undefined behavior if the destination EH pad has a parent which is not an
|
||||
ancestor of the ``cleanuppad`` being exited.
|
||||
When a ``cleanuppad`` has been "entered" but not yet "exited" (as
|
||||
described in the `EH documentation\ <ExceptionHandling.html#wineh-constraints>`_),
|
||||
it is undefined behavior to execute a :ref:`call <i_call>` or :ref:`invoke <i_invoke>`
|
||||
that does not carry an appropriate :ref:`"funclet" bundle <ob_funclet>`.
|
||||
|
||||
It is undefined behavior for the ``cleanuppad`` to exit via an unwind edge which
|
||||
does not transitively unwind to the same destination as a constituent
|
||||
|
@ -2895,6 +2895,13 @@ void Verifier::visitInsertValueInst(InsertValueInst &IVI) {
|
||||
visitInstruction(IVI);
|
||||
}
|
||||
|
||||
static Value *getParentPad(Value *EHPad) {
|
||||
if (auto *FPI = dyn_cast<FuncletPadInst>(EHPad))
|
||||
return FPI->getParentPad();
|
||||
|
||||
return cast<CatchSwitchInst>(EHPad)->getParentPad();
|
||||
}
|
||||
|
||||
void Verifier::visitEHPadPredecessors(Instruction &I) {
|
||||
assert(I.isEHPad());
|
||||
|
||||
@ -2925,13 +2932,39 @@ void Verifier::visitEHPadPredecessors(Instruction &I) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify that each pred has a legal terminator with a legal to/from EH
|
||||
// pad relationship.
|
||||
Instruction *ToPad = &I;
|
||||
Value *ToPadParent = getParentPad(ToPad);
|
||||
for (BasicBlock *PredBB : predecessors(BB)) {
|
||||
TerminatorInst *TI = PredBB->getTerminator();
|
||||
Value *FromPad;
|
||||
if (auto *II = dyn_cast<InvokeInst>(TI)) {
|
||||
Assert(II->getUnwindDest() == BB && II->getNormalDest() != BB,
|
||||
"EH pad must be jumped to via an unwind edge", &I, II);
|
||||
} else if (!isa<CleanupReturnInst>(TI) && !isa<CatchSwitchInst>(TI)) {
|
||||
Assert(false, "EH pad must be jumped to via an unwind edge", &I, TI);
|
||||
"EH pad must be jumped to via an unwind edge", ToPad, II);
|
||||
if (auto Bundle = II->getOperandBundle(LLVMContext::OB_funclet))
|
||||
FromPad = Bundle->Inputs[0];
|
||||
else
|
||||
FromPad = ConstantTokenNone::get(II->getContext());
|
||||
} else if (auto *CRI = dyn_cast<CleanupReturnInst>(TI)) {
|
||||
FromPad = CRI->getCleanupPad();
|
||||
Assert(FromPad != ToPadParent, "A cleanupret must exit its cleanup", CRI);
|
||||
} else if (auto *CSI = dyn_cast<CatchSwitchInst>(TI)) {
|
||||
FromPad = CSI;
|
||||
} else {
|
||||
Assert(false, "EH pad must be jumped to via an unwind edge", ToPad, TI);
|
||||
}
|
||||
|
||||
// The edge may exit from zero or more nested pads.
|
||||
for (;; FromPad = getParentPad(FromPad)) {
|
||||
Assert(FromPad != ToPad,
|
||||
"EH pad cannot handle exceptions raised within it", FromPad, TI);
|
||||
if (FromPad == ToPadParent) {
|
||||
// This is a legal unwind edge.
|
||||
break;
|
||||
}
|
||||
Assert(!isa<ConstantTokenNone>(FromPad),
|
||||
"A single unwind edge may only enter one EH pad", TI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -885,7 +885,8 @@ catchpad:
|
||||
; CHECK-NEXT: br label %body
|
||||
|
||||
body:
|
||||
invoke void @f.ccc() to label %continue unwind label %terminate.inner
|
||||
invoke void @f.ccc() [ "funclet"(token %catch) ]
|
||||
to label %continue unwind label %terminate.inner
|
||||
catchret from %catch to label %return
|
||||
; CHECK: catchret from %catch to label %return
|
||||
|
||||
|
@ -43,7 +43,7 @@ entry:
|
||||
invoke void @_Z3quxv() optsize
|
||||
to label %exit unwind label %pad
|
||||
cleanup:
|
||||
cleanupret from %cp unwind label %pad
|
||||
cleanupret from %cp unwind to caller
|
||||
pad:
|
||||
%cp = cleanuppad within none []
|
||||
br label %cleanup
|
||||
@ -57,7 +57,7 @@ entry:
|
||||
invoke void @_Z3quxv() optsize
|
||||
to label %exit unwind label %pad
|
||||
cleanup:
|
||||
cleanupret from %0 unwind label %pad
|
||||
cleanupret from %0 unwind to caller
|
||||
pad:
|
||||
%0 = cleanuppad within none []
|
||||
br label %cleanup
|
||||
|
@ -6,6 +6,11 @@
|
||||
; RUN: sed -e s/.T6:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK6 %s
|
||||
; RUN: sed -e s/.T7:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK7 %s
|
||||
; RUN: sed -e s/.T8:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK8 %s
|
||||
; RUN: sed -e s/.T9:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK9 %s
|
||||
; RUN: sed -e s/.T10:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK10 %s
|
||||
; RUN: sed -e s/.T11:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK11 %s
|
||||
; RUN: sed -e s/.T12:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK12 %s
|
||||
; RUN: sed -e s/.T13:// %s | not opt -verify -disable-output 2>&1 | FileCheck --check-prefix=CHECK13 %s
|
||||
|
||||
declare void @g()
|
||||
|
||||
@ -96,3 +101,85 @@ declare void @g()
|
||||
;T8: %cs1 = catchswitch within none [ label %switch1 ] unwind to caller
|
||||
;T8: ; CHECK8: CatchSwitchInst handlers must be catchpads
|
||||
;T8: }
|
||||
|
||||
;T9: define void @f() personality void ()* @g {
|
||||
;T9: entry:
|
||||
;T9: ret void
|
||||
;T9: cleanup:
|
||||
;T9: %cp = cleanuppad within none []
|
||||
;T9: invoke void @g() [ "funclet"(token %cp) ]
|
||||
;T9: to label %exit unwind label %cleanup
|
||||
;T9: ; CHECK9: EH pad cannot handle exceptions raised within it
|
||||
;T9: ; CHECK9-NEXT: %cp = cleanuppad within none []
|
||||
;T9: ; CHECK9-NEXT: invoke void @g() [ "funclet"(token %cp) ]
|
||||
;T9: exit:
|
||||
;T9: ret void
|
||||
;T9: }
|
||||
|
||||
;T10: define void @f() personality void ()* @g {
|
||||
;T10: entry:
|
||||
;T10: ret void
|
||||
;T10: cleanup1:
|
||||
;T10: %cp1 = cleanuppad within none []
|
||||
;T10: unreachable
|
||||
;T10: switch:
|
||||
;T10: %cs = catchswitch within %cp1 [label %catch] unwind to caller
|
||||
;T10: catch:
|
||||
;T10: %catchp1 = catchpad within %cs [i32 1]
|
||||
;T10: unreachable
|
||||
;T10: cleanup2:
|
||||
;T10: %cp2 = cleanuppad within %catchp1 []
|
||||
;T10: unreachable
|
||||
;T10: cleanup3:
|
||||
;T10: %cp3 = cleanuppad within %cp2 []
|
||||
;T10: cleanupret from %cp3 unwind label %switch
|
||||
;T10: ; CHECK10: EH pad cannot handle exceptions raised within it
|
||||
;T10: ; CHECK10-NEXT: %cs = catchswitch within %cp1 [label %catch] unwind to caller
|
||||
;T10: ; CHECK10-NEXT: cleanupret from %cp3 unwind label %switch
|
||||
;T10: }
|
||||
|
||||
;T11: define void @f() personality void ()* @g {
|
||||
;T11: entry:
|
||||
;T11: ret void
|
||||
;T11: cleanup1:
|
||||
;T11: %cp1 = cleanuppad within none []
|
||||
;T11: unreachable
|
||||
;T11: cleanup2:
|
||||
;T11: %cp2 = cleanuppad within %cp1 []
|
||||
;T11: unreachable
|
||||
;T11: switch:
|
||||
;T11: %cs = catchswitch within none [label %catch] unwind label %cleanup2
|
||||
;T11: ; CHECK11: A single unwind edge may only enter one EH pad
|
||||
;T11: ; CHECK11-NEXT: %cs = catchswitch within none [label %catch] unwind label %cleanup2
|
||||
;T11: catch:
|
||||
;T11: catchpad within %cs [i32 1]
|
||||
;T11: unreachable
|
||||
;T11: }
|
||||
|
||||
;T12: define void @f() personality void ()* @g {
|
||||
;T12: entry:
|
||||
;T12: ret void
|
||||
;T12: cleanup:
|
||||
;T12: %cp = cleanuppad within none []
|
||||
;T12: cleanupret from %cp unwind label %switch
|
||||
;T12: ; CHECK12: A cleanupret must exit its cleanup
|
||||
;T12: ; CHECK12-NEXT: cleanupret from %cp unwind label %switch
|
||||
;T12: switch:
|
||||
;T12: %cs = catchswitch within %cp [label %catch] unwind to caller
|
||||
;T12: catch:
|
||||
;T12: catchpad within %cs [i32 1]
|
||||
;T12: unreachable
|
||||
;T12: }
|
||||
|
||||
;T13: define void @f() personality void ()* @g {
|
||||
;T13: entry:
|
||||
;T13: ret void
|
||||
;T13: switch:
|
||||
;T13: %cs = catchswitch within none [label %catch] unwind label %switch
|
||||
;T13: ; CHECK13: EH pad cannot handle exceptions raised within it
|
||||
;T13: ; CHECK13-NEXT: %cs = catchswitch within none [label %catch] unwind label %switch
|
||||
;T13: catch:
|
||||
;T13: catchpad within %cs [i32 0]
|
||||
;T13: unreachable
|
||||
;T13: }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user