emitExceptHandlerTable(MF);
else if (Per == EHPersonality::MSVC_CXX)
emitCXXFrameHandler3Table(MF);
+ else if (Per == EHPersonality::CoreCLR)
+ emitCLRExceptionTable(MF);
else
emitExceptionTable();
Asm->OutContext);
}
+const MCExpr *WinException::getOffset(const MCSymbol *OffsetOf,
+ const MCSymbol *OffsetFrom) {
+ return MCBinaryExpr::createSub(
+ MCSymbolRefExpr::create(OffsetOf, Asm->OutContext),
+ MCSymbolRefExpr::create(OffsetFrom, Asm->OutContext), Asm->OutContext);
+}
+
+const MCExpr *WinException::getOffsetPlusOne(const MCSymbol *OffsetOf,
+ const MCSymbol *OffsetFrom) {
+ return MCBinaryExpr::createAdd(getOffset(OffsetOf, OffsetFrom),
+ MCConstantExpr::create(1, Asm->OutContext),
+ Asm->OutContext);
+}
+
int WinException::getFrameIndexOffset(int FrameIndex) {
const TargetFrameLowering &TFI = *Asm->MF->getSubtarget().getFrameLowering();
unsigned UnusedReg;
Ctx.createTempSymbol("lsda_begin", /*AlwaysAddSuffix=*/true);
MCSymbol *TableEnd =
Ctx.createTempSymbol("lsda_end", /*AlwaysAddSuffix=*/true);
- const MCExpr *LabelDiff =
- MCBinaryExpr::createSub(MCSymbolRefExpr::create(TableEnd, Ctx),
- MCSymbolRefExpr::create(TableBegin, Ctx), Ctx);
+ const MCExpr *LabelDiff = getOffset(TableEnd, TableBegin);
const MCExpr *EntrySize = MCConstantExpr::create(16, Ctx);
const MCExpr *EntryCount = MCBinaryExpr::createDiv(LabelDiff, EntrySize, Ctx);
OS.EmitValue(EntryCount, 4);
OS.EmitValue(create32bitRef(ExceptOrFinally), 4); // Except/Finally
}
}
+
+static int getRank(WinEHFuncInfo &FuncInfo, int State) {
+ int Rank = 0;
+ while (State != -1) {
+ ++Rank;
+ State = FuncInfo.ClrEHUnwindMap[State].Parent;
+ }
+ return Rank;
+}
+
+static int getAncestor(WinEHFuncInfo &FuncInfo, int Left, int Right) {
+ int LeftRank = getRank(FuncInfo, Left);
+ int RightRank = getRank(FuncInfo, Right);
+
+ while (LeftRank < RightRank) {
+ Right = FuncInfo.ClrEHUnwindMap[Right].Parent;
+ --RightRank;
+ }
+
+ while (RightRank < LeftRank) {
+ Left = FuncInfo.ClrEHUnwindMap[Left].Parent;
+ --LeftRank;
+ }
+
+ while (Left != Right) {
+ Left = FuncInfo.ClrEHUnwindMap[Left].Parent;
+ Right = FuncInfo.ClrEHUnwindMap[Right].Parent;
+ }
+
+ return Left;
+}
+
+void WinException::emitCLRExceptionTable(const MachineFunction *MF) {
+ // CLR EH "states" are really just IDs that identify handlers/funclets;
+ // states, handlers, and funclets all have 1:1 mappings between them, and a
+ // handler/funclet's "state" is its index in the ClrEHUnwindMap.
+ MCStreamer &OS = *Asm->OutStreamer;
+ const Function *F = MF->getFunction();
+ WinEHFuncInfo &FuncInfo = MMI->getWinEHFuncInfo(F);
+ MCSymbol *FuncBeginSym = Asm->getFunctionBegin();
+ MCSymbol *FuncEndSym = Asm->getFunctionEnd();
+
+ // A ClrClause describes a protected region.
+ struct ClrClause {
+ const MCSymbol *StartLabel; // Start of protected region
+ const MCSymbol *EndLabel; // End of protected region
+ int State; // Index of handler protecting the protected region
+ int EnclosingState; // Index of funclet enclosing the protected region
+ };
+ SmallVector<ClrClause, 8> Clauses;
+
+ // Build a map from handler MBBs to their corresponding states (i.e. their
+ // indices in the ClrEHUnwindMap).
+ int NumStates = FuncInfo.ClrEHUnwindMap.size();
+ assert(NumStates > 0 && "Don't need exception table!");
+ DenseMap<const MachineBasicBlock *, int> HandlerStates;
+ for (int State = 0; State < NumStates; ++State) {
+ MachineBasicBlock *HandlerBlock =
+ FuncInfo.ClrEHUnwindMap[State].Handler.get<MachineBasicBlock *>();
+ HandlerStates[HandlerBlock] = State;
+ // Use this loop through all handlers to verify our assumption (used in
+ // the MinEnclosingState computation) that ancestors have lower state
+ // numbers than their descendants.
+ assert(FuncInfo.ClrEHUnwindMap[State].Parent < State &&
+ "ill-formed state numbering");
+ }
+ // Map the main function to the NullState.
+ HandlerStates[MF->begin()] = NullState;
+
+ // Write out a sentinel indicating the end of the standard (Windows) xdata
+ // and the start of the additional (CLR) info.
+ OS.EmitIntValue(0xffffffff, 4);
+ // Write out the number of funclets
+ OS.EmitIntValue(NumStates, 4);
+
+ // Walk the machine blocks/instrs, computing and emitting a few things:
+ // 1. Emit a list of the offsets to each handler entry, in lexical order.
+ // 2. Compute a map (EndSymbolMap) from each funclet to the symbol at its end.
+ // 3. Compute the list of ClrClauses, in the required order (inner before
+ // outer, earlier before later; the order by which a forward scan with
+ // early termination will find the innermost enclosing clause covering
+ // a given address).
+ // 4. A map (MinClauseMap) from each handler index to the index of the
+ // outermost funclet/function which contains a try clause targeting the
+ // key handler. This will be used to determine IsDuplicate-ness when
+ // emitting ClrClauses. The NullState value is used to indicate that the
+ // top-level function contains a try clause targeting the key handler.
+ // HandlerStack is a stack of (PendingStartLabel, PendingState) pairs for
+ // try regions we entered before entering the PendingState try but which
+ // we haven't yet exited.
+ SmallVector<std::pair<const MCSymbol *, int>, 4> HandlerStack;
+ // EndSymbolMap and MinClauseMap are maps described above.
+ std::unique_ptr<MCSymbol *[]> EndSymbolMap(new MCSymbol *[NumStates]);
+ SmallVector<int, 4> MinClauseMap((size_t)NumStates, NumStates);
+
+ // Visit the root function and each funclet.
+
+ for (MachineFunction::const_iterator FuncletStart = MF->begin(),
+ FuncletEnd = MF->begin(),
+ End = MF->end();
+ FuncletStart != End; FuncletStart = FuncletEnd) {
+ int FuncletState = HandlerStates[FuncletStart];
+ // Find the end of the funclet
+ MCSymbol *EndSymbol = FuncEndSym;
+ while (++FuncletEnd != End) {
+ if (FuncletEnd->isEHFuncletEntry()) {
+ EndSymbol = getMCSymbolForMBB(Asm, FuncletEnd);
+ break;
+ }
+ }
+ // Emit the function/funclet end and, if this is a funclet (and not the
+ // root function), record it in the EndSymbolMap.
+ OS.EmitValue(getOffset(EndSymbol, FuncBeginSym), 4);
+ if (FuncletState != NullState) {
+ // Record the end of the handler.
+ EndSymbolMap[FuncletState] = EndSymbol;
+ }
+
+ // Walk the state changes in this function/funclet and compute its clauses.
+ // Funclets always start in the null state.
+ const MCSymbol *CurrentStartLabel = nullptr;
+ int CurrentState = NullState;
+ assert(HandlerStack.empty());
+ for (const auto &StateChange :
+ InvokeStateChangeIterator::range(FuncInfo, FuncletStart, FuncletEnd)) {
+ // Close any try regions we're not still under
+ int AncestorState =
+ getAncestor(FuncInfo, CurrentState, StateChange.NewState);
+ while (CurrentState != AncestorState) {
+ assert(CurrentState != NullState && "Failed to find ancestor!");
+ // Close the pending clause
+ Clauses.push_back({CurrentStartLabel, StateChange.PreviousEndLabel,
+ CurrentState, FuncletState});
+ // Now the parent handler is current
+ CurrentState = FuncInfo.ClrEHUnwindMap[CurrentState].Parent;
+ // Pop the new start label from the handler stack if we've exited all
+ // descendants of the corresponding handler.
+ if (HandlerStack.back().second == CurrentState)
+ CurrentStartLabel = HandlerStack.pop_back_val().first;
+ }
+
+ if (StateChange.NewState != CurrentState) {
+ // For each clause we're starting, update the MinClauseMap so we can
+ // know which is the topmost funclet containing a clause targeting
+ // it.
+ for (int EnteredState = StateChange.NewState;
+ EnteredState != CurrentState;
+ EnteredState = FuncInfo.ClrEHUnwindMap[EnteredState].Parent) {
+ int &MinEnclosingState = MinClauseMap[EnteredState];
+ if (FuncletState < MinEnclosingState)
+ MinEnclosingState = FuncletState;
+ }
+ // Save the previous current start/label on the stack and update to
+ // the newly-current start/state.
+ HandlerStack.emplace_back(CurrentStartLabel, CurrentState);
+ CurrentStartLabel = StateChange.NewStartLabel;
+ CurrentState = StateChange.NewState;
+ }
+ }
+ assert(HandlerStack.empty());
+ }
+
+ // Now emit the clause info, starting with the number of clauses.
+ OS.EmitIntValue(Clauses.size(), 4);
+ for (ClrClause &Clause : Clauses) {
+ // Emit a CORINFO_EH_CLAUSE :
+ /*
+ struct CORINFO_EH_CLAUSE
+ {
+ CORINFO_EH_CLAUSE_FLAGS Flags; // actually a CorExceptionFlag
+ DWORD TryOffset;
+ DWORD TryLength; // actually TryEndOffset
+ DWORD HandlerOffset;
+ DWORD HandlerLength; // actually HandlerEndOffset
+ union
+ {
+ DWORD ClassToken; // use for catch clauses
+ DWORD FilterOffset; // use for filter clauses
+ };
+ };
+
+ enum CORINFO_EH_CLAUSE_FLAGS
+ {
+ CORINFO_EH_CLAUSE_NONE = 0,
+ CORINFO_EH_CLAUSE_FILTER = 0x0001, // This clause is for a filter
+ CORINFO_EH_CLAUSE_FINALLY = 0x0002, // This clause is a finally clause
+ CORINFO_EH_CLAUSE_FAULT = 0x0004, // This clause is a fault clause
+ };
+ typedef enum CorExceptionFlag
+ {
+ COR_ILEXCEPTION_CLAUSE_NONE,
+ COR_ILEXCEPTION_CLAUSE_FILTER = 0x0001, // This is a filter clause
+ COR_ILEXCEPTION_CLAUSE_FINALLY = 0x0002, // This is a finally clause
+ COR_ILEXCEPTION_CLAUSE_FAULT = 0x0004, // This is a fault clause
+ COR_ILEXCEPTION_CLAUSE_DUPLICATED = 0x0008, // duplicated clause. This
+ // clause was duplicated
+ // to a funclet which was
+ // pulled out of line
+ } CorExceptionFlag;
+ */
+ // Add 1 to the start/end of the EH clause; the IP associated with a
+ // call when the runtime does its scan is the IP of the next instruction
+ // (the one to which control will return after the call), so we need
+ // to add 1 to the end of the clause to cover that offset. We also add
+ // 1 to the start of the clause to make sure that the ranges reported
+ // for all clauses are disjoint. Note that we'll need some additional
+ // logic when machine traps are supported, since in that case the IP
+ // that the runtime uses is the offset of the faulting instruction
+ // itself; if such an instruction immediately follows a call but the
+ // two belong to different clauses, we'll need to insert a nop between
+ // them so the runtime can distinguish the point to which the call will
+ // return from the point at which the fault occurs.
+
+ const MCExpr *ClauseBegin =
+ getOffsetPlusOne(Clause.StartLabel, FuncBeginSym);
+ const MCExpr *ClauseEnd = getOffsetPlusOne(Clause.EndLabel, FuncBeginSym);
+
+ ClrEHUnwindMapEntry &Entry = FuncInfo.ClrEHUnwindMap[Clause.State];
+ MachineBasicBlock *HandlerBlock = Entry.Handler.get<MachineBasicBlock *>();
+ MCSymbol *BeginSym = getMCSymbolForMBB(Asm, HandlerBlock);
+ const MCExpr *HandlerBegin = getOffset(BeginSym, FuncBeginSym);
+ MCSymbol *EndSym = EndSymbolMap[Clause.State];
+ const MCExpr *HandlerEnd = getOffset(EndSym, FuncBeginSym);
+
+ uint32_t Flags = 0;
+ switch (Entry.HandlerType) {
+ case ClrHandlerType::Catch:
+ // Leaving bits 0-2 clear indicates catch.
+ break;
+ case ClrHandlerType::Filter:
+ Flags |= 1;
+ break;
+ case ClrHandlerType::Finally:
+ Flags |= 2;
+ break;
+ case ClrHandlerType::Fault:
+ Flags |= 4;
+ break;
+ }
+ if (Clause.EnclosingState != MinClauseMap[Clause.State]) {
+ // This is a "duplicate" clause; the handler needs to be entered from a
+ // frame above the one holding the invoke.
+ assert(Clause.EnclosingState > MinClauseMap[Clause.State]);
+ Flags |= 8;
+ }
+ OS.EmitIntValue(Flags, 4);
+
+ // Write the clause start/end
+ OS.EmitValue(ClauseBegin, 4);
+ OS.EmitValue(ClauseEnd, 4);
+
+ // Write out the handler start/end
+ OS.EmitValue(HandlerBegin, 4);
+ OS.EmitValue(HandlerEnd, 4);
+
+ // Write out the type token or filter offset
+ assert(Entry.HandlerType != ClrHandlerType::Filter && "NYI: filters");
+ OS.EmitIntValue(Entry.TypeToken, 4);
+ }
+}
--- /dev/null
+; RUN: llc -mtriple=x86_64-pc-windows-coreclr < %s | FileCheck %s
+
+declare void @ProcessCLRException()
+declare void @f(i32)
+
+; Simplified IR for pseudo-C# like the following:
+; void test1() {
+; try {
+; f(1);
+; try {
+; f(2);
+; } catch (type1) {
+; f(3);
+; } catch (type2) [
+; f(4);
+; try {
+; f(5);
+; } fault {
+; f(6);
+; }
+; }
+; } finally {
+; f(7);
+; }
+; f(8);
+; }
+
+; CHECK-LABEL: test1: # @test1
+; CHECK-NEXT: [[L_begin:.*func_begin.*]]:
+define void @test1() personality i8* bitcast (void ()* @ProcessCLRException to i8*) {
+entry:
+; CHECK: # %entry
+; CHECK: .seh_endprologue
+; CHECK: [[L_before_f1:.+]]:
+; CHECK-NEXT: movl $1, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f1:.+]]:
+ invoke void @f(i32 1)
+ to label %inner_try unwind label %finally.pad
+inner_try:
+; CHECK: # %inner_try
+; CHECK: [[L_before_f2:.+]]:
+; CHECK-NEXT: movl $2, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f2:.+]]:
+ invoke void @f(i32 2)
+ to label %finally.clone unwind label %catch1.pad
+catch1.pad:
+; CHECK: .seh_proc [[L_catch1:[^ ]+]]
+ %catch1 = catchpad [i32 1]
+ to label %catch1.body unwind label %catch2.pad
+catch1.body:
+; CHECK: .seh_endprologue
+; CHECK: [[L_before_f3:.+]]:
+; CHECK-NEXT: movl $3, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f3:.+]]:
+ invoke void @f(i32 3)
+ to label %catch1.ret unwind label %catch.end
+catch1.ret:
+ catchret %catch1 to label %finally.clone
+catch2.pad:
+; CHECK: .seh_proc [[L_catch2:[^ ]+]]
+ %catch2 = catchpad [i32 2]
+ to label %catch2.body unwind label %catch.end
+catch2.body:
+; CHECK: .seh_endprologue
+; CHECK: [[L_before_f4:.+]]:
+; CHECK-NEXT: movl $4, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f4:.+]]:
+ invoke void @f(i32 4)
+ to label %try_in_catch unwind label %catch.end
+try_in_catch:
+; CHECK: # %try_in_catch
+; CHECK: [[L_before_f5:.+]]:
+; CHECK-NEXT: movl $5, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f5:.+]]:
+ invoke void @f(i32 5)
+ to label %catch2.ret unwind label %fault.pad
+fault.pad:
+; CHECK: .seh_proc [[L_fault:[^ ]+]]
+ %fault = cleanuppad [i32 undef]
+; CHECK: .seh_endprologue
+; CHECK: [[L_before_f6:.+]]:
+; CHECK-NEXT: movl $6, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f6:.+]]:
+ invoke void @f(i32 6)
+ to label %fault.ret unwind label %fault.end
+fault.ret:
+ cleanupret %fault unwind label %catch.end
+fault.end:
+ cleanupendpad %fault unwind label %catch.end
+catch2.ret:
+ catchret %catch2 to label %finally.clone
+catch.end:
+ catchendpad unwind label %finally.pad
+finally.clone:
+ call void @f(i32 7)
+ br label %tail
+finally.pad:
+; CHECK: .seh_proc [[L_finally:[^ ]+]]
+ %finally = cleanuppad []
+; CHECK: .seh_endprologue
+; CHECK: [[L_before_f7:.+]]:
+; CHECK-NEXT: movl $7, %ecx
+; CHECK-NEXT: callq f
+; CHECK-NEXT: [[L_after_f7:.+]]:
+ invoke void @f(i32 7)
+ to label %finally.ret unwind label %finally.end
+finally.ret:
+ cleanupret %finally unwind to caller
+finally.end:
+ cleanupendpad %finally unwind to caller
+tail:
+ call void @f(i32 8)
+ ret void
+; CHECK: [[L_end:.*func_end.*]]:
+}
+
+; Now check for EH table in xdata (following standard xdata)
+; CHECK-LABEL: .section .xdata
+; standard xdata comes here
+; CHECK: .long 4{{$}}
+; ^ number of funclets
+; CHECK-NEXT: .long [[L_catch1]]-[[L_begin]]
+; ^ offset from L_begin to start of 1st funclet
+; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]]
+; ^ offset from L_begin to start of 2nd funclet
+; CHECK-NEXT: .long [[L_fault]]-[[L_begin]]
+; ^ offset from L_begin to start of 3rd funclet
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset from L_begin to start of 4th funclet
+; CHECK-NEXT: .long [[L_end]]-[[L_begin]]
+; ^ offset from L_begin to end of last funclet
+; CHECK-NEXT: .long 7
+; ^ number of EH clauses
+; Clause 1: call f(2) is guarded by catch1
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_catch1]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 1
+; ^ type token of catch (from catchpad)
+; Clause 2: call f(2) is also guarded by catch2
+; CHECK-NEXT: .long 0
+; ^ flags (0 => catch handler)
+; CHECK-NEXT: .long ([[L_before_f2]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_catch2]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_fault]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 2
+; ^ type token of catch (from catchpad)
+; Clause 3: calls f(1) and f(2) are guarded by finally
+; CHECK-NEXT: .long 2
+; ^ flags (2 => finally handler)
+; CHECK-NEXT: .long ([[L_before_f1]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f2]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_end]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for finally)
+; Clause 4: call f(3) is guarded by finally
+; This is a "duplicate" because the protected range (f(3))
+; is in funclet catch1 but the finally's immediate parent
+; is the main function, not that funclet.
+; CHECK-NEXT: .long 10
+; ^ flags (2 => finally handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[L_before_f3]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f3]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_end]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for finally)
+; Clause 5: call f(5) is guarded by fault
+; CHECK-NEXT: .long 4
+; ^ flags (4 => fault handler)
+; CHECK-NEXT: .long ([[L_before_f5]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_fault]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for fault)
+; Clause 6: calls f(4) and f(5) are guarded by finally
+; This is a "duplicate" because the protected range (f(4)-f(5))
+; is in funclet catch2 but the finally's immediate parent
+; is the main function, not that funclet.
+; CHECK-NEXT: .long 10
+; ^ flags (2 => finally handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[L_before_f4]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f5]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_end]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for finally)
+; Clause 7: call f(6) is guarded by finally
+; This is a "duplicate" because the protected range (f(3))
+; is in funclet catch1 but the finally's immediate parent
+; is the main function, not that funclet.
+; CHECK-NEXT: .long 10
+; ^ flags (2 => finally handler | 8 => duplicate)
+; CHECK-NEXT: .long ([[L_before_f6]]-[[L_begin]])+1
+; ^ offset of start of clause
+; CHECK-NEXT: .long ([[L_after_f6]]-[[L_begin]])+1
+; ^ offset of end of clause
+; CHECK-NEXT: .long [[L_finally]]-[[L_begin]]
+; ^ offset of start of handler
+; CHECK-NEXT: .long [[L_end]]-[[L_begin]]
+; ^ offset of end of handler
+; CHECK-NEXT: .long 0
+; ^ type token slot (null for finally)