[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:
Joseph Tremoulet 2016-01-10 04:28:38 +00:00
parent ca4d93a82f
commit e28885e693
6 changed files with 211 additions and 47 deletions

View File

@ -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.

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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: }