[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.
+
+.. _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.
 
+.. _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
index 64d7575..44ced48 100644 (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);
     }
   }
 }
index 34c4a07..ae12a24 100644 (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
 
index 2634692..cbe2d03 100644 (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
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/.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: }
+