OSDN Git Service

[WinEH] Verify unwind edges against EH pad tree
authorJoseph Tremoulet <jotrem@microsoft.com>
Sun, 10 Jan 2016 04:28:38 +0000 (04:28 +0000)
committerJoseph Tremoulet <jotrem@microsoft.com>
Sun, 10 Jan 2016 04:28:38 +0000 (04:28 +0000)
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

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@257272 91177308-0d34-0410-b5e6-96231b3b80d8

docs/ExceptionHandling.rst
docs/LangRef.rst
lib/IR/Verifier.cpp
test/Bitcode/compatibility.ll
test/Feature/exception.ll
test/Verifier/invalid-eh.ll

index 74827c0..2524856 100644 (file)
@@ -775,3 +775,46 @@ C++ code:
 
 The "inner" ``catchswitch`` consumes ``%1`` which is produced by the outer
 catchswitch.
 
 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.
index 103d876..650d18b 100644 (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.
 
 semantically equivalent to composing the caller's deoptimization
 continuation after the callee's deoptimization continuation.
 
+.. _ob_funclet:
+
 Funclet Operand Bundles
 ^^^^^^^^^^^^^^^^^^^^^^^
 
 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.
 
 ``"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
 .. _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``.
 
 ``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:
 :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
 
 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:
 """"""""
 
 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``.
 
 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:
 """"""""
 
 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>`.
 
 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:
 """"""""""
 
 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.
 
 :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:
 """"""""
 
 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.
 
 -  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
 
 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
index 64d7575..44ced48 100644 (file)
@@ -2895,6 +2895,13 @@ void Verifier::visitInsertValueInst(InsertValueInst &IVI) {
   visitInstruction(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());
 
 void Verifier::visitEHPadPredecessors(Instruction &I) {
   assert(I.isEHPad());
 
@@ -2925,13 +2932,39 @@ void Verifier::visitEHPadPredecessors(Instruction &I) {
     return;
   }
 
     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();
   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,
     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);
     }
   }
 }
     }
   }
 }
index 34c4a07..ae12a24 100644 (file)
@@ -885,7 +885,8 @@ catchpad:
   ; CHECK-NEXT: br label %body
 
 body:
   ; 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
 
   catchret from %catch to label %return
   ; CHECK: catchret from %catch to label %return
 
index 2634692..cbe2d03 100644 (file)
@@ -43,7 +43,7 @@ entry:
   invoke void @_Z3quxv() optsize
           to label %exit unwind label %pad
 cleanup:
   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
 pad:
   %cp = cleanuppad within none []
   br label %cleanup
@@ -57,7 +57,7 @@ entry:
   invoke void @_Z3quxv() optsize
           to label %exit unwind label %pad
 cleanup:
   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
 pad:
   %0 = cleanuppad within none []
   br label %cleanup
index 21e88d4..e43a676 100644 (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/.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()
 
 
 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: }
 ;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: }
+