Support Nesting of Exception Handlers (#199)
* partial progress * progress * progress * progress * fix * add another example * further progress * formatting fix * fixes * undo removal of comment * example null deref * comment edit * fix * add unit test for this * fix test * fix * bug fix * bug fix * fix bug * undo debug stuff * typo * fix * remove stash markers * testclass introduction of bug fix * doc fix * undo regular control flow from throw through finally * remove unecessary method --------- Co-authored-by: Xiaoyu Liu <lixiaoyu@microsoft.com>
This commit is contained in:
Родитель
8f4efbe71c
Коммит
df9aea035c
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Cilsil.Test.Assets
|
||||
|
@ -379,6 +380,51 @@ namespace Cilsil.Test.Assets
|
|||
return returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests control flow translation for a method with nested exception handlers.
|
||||
/// </summary>
|
||||
/// <param name="createNullDeref">If <c>true</c>, the method logic yields a null
|
||||
/// dereference. Otherwise, it does not.</param>
|
||||
public static void NestedExceptionConditionalNullDeref(bool createNullDeref)
|
||||
{
|
||||
object x = new object();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("First try catch");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("Before finally");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x = createNullDeref ? new object() : null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Inner try catch finally");
|
||||
x = createNullDeref ? null : new object();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Outer try catch finally");
|
||||
throw new Exception();
|
||||
}
|
||||
Console.Write("Last instruction of outer try catch");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No resource leak should be reported, as the allocated stream is closed in finally.
|
||||
/// </summary>
|
||||
|
@ -405,5 +451,26 @@ namespace Cilsil.Test.Assets
|
|||
ThrowsIOException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We introduce this case for coverage of a false positive on stream; the return
|
||||
/// statement in the try block was not routing through the finally block first.
|
||||
/// </summary>
|
||||
public static int LeaveEndTryCatchNeedToGoFinally()
|
||||
{
|
||||
var output = new TestClass().GetHashCode();
|
||||
using (var stream = new FileStream("", FileMode.Create))
|
||||
{
|
||||
try
|
||||
{
|
||||
var x = 5;
|
||||
return output;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,6 +78,7 @@ namespace Cilsil.Test.Assets
|
|||
InitializeInstanceObjectField,
|
||||
InitializeStreamReaderObjectField,
|
||||
InitializeInstanceObjectFieldViaReference,
|
||||
NestedExceptionConditionalNullDeref,
|
||||
ReturnElementFromInstanceArrayField,
|
||||
ReturnOneDimArray,
|
||||
ReturnTwoDimArray,
|
||||
|
@ -488,6 +489,13 @@ namespace Cilsil.Test.Assets
|
|||
throw new ArgumentException("TestCastClass requires 1 argument.");
|
||||
}
|
||||
return GetMethodCall(true);
|
||||
case TestClassMethod.NestedExceptionConditionalNullDeref:
|
||||
if (args == null || args.Length != 1)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"NestedExceptionConditionalNullDeref requires 1 argument.");
|
||||
}
|
||||
return GetMethodCall(true);
|
||||
default:
|
||||
throw new NotImplementedException("Unhandled TestClassMethod.");
|
||||
}
|
||||
|
|
|
@ -806,7 +806,7 @@ namespace Cilsil.Test.E2E
|
|||
/// <summary>
|
||||
/// Validates our translation of CastClass.
|
||||
/// </summary>
|
||||
/// <param name="testInputCode">Defines the object to be input to the type-checking
|
||||
/// <param name="inputObjectString">Defines the object to be input to the type-checking
|
||||
/// TestClass method; 0 for successful casting operation. 1 for casting exception.</param>
|
||||
/// <param name="expectedError">The expected error.</param>
|
||||
[DataRow("new TestClass()", InferError.None)]
|
||||
|
@ -824,5 +824,26 @@ namespace Cilsil.Test.E2E
|
|||
inputObjectString
|
||||
})), GetString(expectedError));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates translation of nested exception handler control flow.
|
||||
/// </summary>
|
||||
/// <param name="doNullDeref">If <c>true</c>, invoked method creates the null
|
||||
/// deref. Else, does not.</param>
|
||||
/// <param name="expectedError">The expected error.</param>
|
||||
[DataRow(false, InferError.None)]
|
||||
[DataRow(true, InferError.NULL_DEREFERENCE)]
|
||||
[DataTestMethod]
|
||||
public void NullExceptionTestNestedException(bool doNullDeref, InferError expectedError)
|
||||
{
|
||||
TestRunManager.Run(CallTestClassMethod(
|
||||
TestClassMethod.NestedExceptionConditionalNullDeref,
|
||||
true,
|
||||
args: new string[]
|
||||
{
|
||||
doNullDeref.ToString()
|
||||
.ToLower()
|
||||
}), GetString(expectedError));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -168,20 +168,20 @@ namespace Cilsil.Cil.Parsers
|
|||
var callArgs = new List<Call.CallArg> { new Call.CallArg(arg.Item1, arg.Item2) };
|
||||
|
||||
return setLockedAttribute ? new Call(returnIdentifier,
|
||||
new Tvoid(),
|
||||
new ConstExpression(
|
||||
ProcedureName.BuiltIn__set_locked_attribute),
|
||||
callArgs,
|
||||
callFlags,
|
||||
state.CurrentLocation)
|
||||
: new Call(returnIdentifier,
|
||||
new Tvoid(),
|
||||
new ConstExpression(
|
||||
ProcedureName.
|
||||
new Tvoid(),
|
||||
new ConstExpression(
|
||||
ProcedureName.BuiltIn__set_locked_attribute),
|
||||
callArgs,
|
||||
callFlags,
|
||||
state.CurrentLocation)
|
||||
: new Call(returnIdentifier,
|
||||
new Tvoid(),
|
||||
new ConstExpression(
|
||||
ProcedureName.
|
||||
BuiltIn__delete_locked_attribute),
|
||||
callArgs,
|
||||
callFlags,
|
||||
state.CurrentLocation);
|
||||
callArgs,
|
||||
callFlags,
|
||||
state.CurrentLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,22 +15,55 @@ namespace Cilsil.Cil.Parsers
|
|||
// Throw can end a finally block but it is discouraged by the compiler and should
|
||||
// be rare.
|
||||
case Code.Endfinally:
|
||||
var exceptionHandler = state.MethodExceptionHandlers
|
||||
.GetExceptionHandlerAtInstruction(instruction);
|
||||
// This instruction was reached through non-exceptional control flow.
|
||||
if (!state.FinallyExceptionalTranslation)
|
||||
{
|
||||
// We continue translation with that operand from the end of the finally
|
||||
// block, now that finally block has been translated.
|
||||
state.PushInstruction(state.EndfinallyControlFlow);
|
||||
// In this case, we need to route control through the next finally block.
|
||||
if (exceptionHandler != null &&
|
||||
exceptionHandler.HandlerType == ExceptionHandlerType.Finally)
|
||||
{
|
||||
state.PushInstruction(exceptionHandler.HandlerStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In this case, translation of the finally block was prompted by a
|
||||
// throw instruction; we then terminate the CFG branch with the throw
|
||||
// node via RegisterNode.
|
||||
if (state.EndfinallyThrowNode != null)
|
||||
{
|
||||
RegisterNode(state, state.EndfinallyThrowNode);
|
||||
state.EndfinallyThrowNode = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We continue translation with that operand from the end of the
|
||||
// finally block, now that finally block has been translated.
|
||||
state.PushInstruction(state.EndfinallyControlFlow);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This instruction was reached through exceptional control flow.
|
||||
else
|
||||
{
|
||||
var handler =
|
||||
state.MethodExceptionHandlers.FinallyEndToHandler[instruction];
|
||||
var handler = state.MethodExceptionHandlers.FinallyEndToHandler[instruction];
|
||||
if (!state.FinallyHandlerToExceptionExit.ContainsKey(handler))
|
||||
{
|
||||
state.FinallyHandlerToExceptionExit[handler] =
|
||||
CreateFinallyExceptionExitNode(state, handler);
|
||||
var exceptionExitNode = CreateFinallyExceptionExitNode(state, handler);
|
||||
state.FinallyHandlerToExceptionExit[handler] = exceptionExitNode;
|
||||
|
||||
// We route control flow through the next finally handler, if there is
|
||||
// one.
|
||||
if (exceptionHandler != null &&
|
||||
exceptionHandler.HandlerType == ExceptionHandlerType.Finally)
|
||||
{
|
||||
var finallyBranchNode =
|
||||
CreateFinallyExceptionBranchNode(state, handler);
|
||||
exceptionExitNode.Successors.Add(finallyBranchNode);
|
||||
state.PushInstruction(exceptionHandler.HandlerStart,
|
||||
finallyBranchNode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@ using Mono.Cecil;
|
|||
using Mono.Cecil.Cil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ProcStack = System.Collections.Generic.Stack<(Cilsil.Sil.Expressions.Expression Expression,
|
||||
|
@ -124,12 +125,19 @@ namespace Cilsil.Cil.Parsers
|
|||
state.Cfg.RegisterNode(node);
|
||||
state.PreviousNode.Successors.Add(node);
|
||||
node.BlockEndOffset = state.MethodExceptionHandlers
|
||||
.GetBlockEndOffsetFromOffset(
|
||||
state.CurrentInstruction.Offset);
|
||||
if (state.MethodExceptionHandlers.GetExceptionHandlerAtInstruction(
|
||||
state.CurrentInstruction) != null)
|
||||
.GetBlockEndOffsetFromInstruction(
|
||||
state.CurrentInstruction);
|
||||
|
||||
var exceptionHandler = state.MethodExceptionHandlers
|
||||
.GetExceptionHandlerAtInstruction(state.CurrentInstruction);
|
||||
|
||||
if (exceptionHandler != null)
|
||||
{
|
||||
state.NodesToLinkWithExceptionBlock.Add(node);
|
||||
var exceptionHandlerEntryNode = GetHandlerExceptionalEntryBlock(state, exceptionHandler);
|
||||
if (exceptionHandlerEntryNode != null)
|
||||
{
|
||||
node.ExceptionNodes.Add(exceptionHandlerEntryNode);
|
||||
}
|
||||
}
|
||||
if (RememberNodeOffset)
|
||||
{
|
||||
|
@ -273,6 +281,11 @@ namespace Cilsil.Cil.Parsers
|
|||
|
||||
node.ExceptionNodes.Add(entryNode);
|
||||
|
||||
// Note: this isn't a perfect way of trying to separate exceptional control
|
||||
// flow from regular control flow; because translation proceeds via DFS, this
|
||||
// distinction may not work in the event of i.e. nested finally blocks.
|
||||
// However, this seems not to pose a material issue for correct detection of
|
||||
// issues.
|
||||
state.FinallyExceptionalTranslation = true;
|
||||
state.PushInstruction(handler.HandlerStart, node);
|
||||
state.FinallyExceptionalTranslation = false;
|
||||
|
@ -325,14 +338,21 @@ namespace Cilsil.Cil.Parsers
|
|||
// exception type-matching node.
|
||||
else
|
||||
{
|
||||
if (handlerNode.FinallyBlock != null)
|
||||
var exceptionHandler =
|
||||
state.MethodExceptionHandlers
|
||||
.GetExceptionHandlerAtInstruction(handlerNode.ExceptionHandler
|
||||
.HandlerStart);
|
||||
// If there is a finally exception handler associated with this catch handler, we
|
||||
// route regular control flow through the finally block.
|
||||
if (exceptionHandler != null &&
|
||||
exceptionHandler.HandlerType == ExceptionHandlerType.Finally)
|
||||
{
|
||||
var finallyBranchNode = CreateFinallyExceptionBranchNode(
|
||||
state, handlerNode.ExceptionHandler);
|
||||
falseBranch.Successors
|
||||
.Add(finallyBranchNode);
|
||||
(var finallyLoadCatchVar, _) = GetHandlerCatchVarNode(
|
||||
state, handlerNode.FinallyBlock);
|
||||
state, exceptionHandler);
|
||||
finallyBranchNode.Successors.Add(finallyLoadCatchVar);
|
||||
}
|
||||
else
|
||||
|
@ -372,7 +392,7 @@ namespace Cilsil.Cil.Parsers
|
|||
return state.ExceptionHandlerToCatchVarNode[handler];
|
||||
}
|
||||
|
||||
protected static CfgNode CreateFinallyExceptionalEntryBlock(ProgramState state,
|
||||
private static CfgNode CreateFinallyExceptionalEntryBlock(ProgramState state,
|
||||
ExceptionHandler handler)
|
||||
{
|
||||
(var entryNode, _) = GetHandlerEntryNode(state, handler);
|
||||
|
@ -521,11 +541,11 @@ namespace Cilsil.Cil.Parsers
|
|||
isInstCall, pruneFalseInstruction
|
||||
});
|
||||
pruneTrueNode.BlockEndOffset = state.MethodExceptionHandlers
|
||||
.GetBlockEndOffsetFromOffset(
|
||||
state.CurrentInstruction.Offset);
|
||||
.GetBlockEndOffsetFromInstruction(
|
||||
state.CurrentInstruction);
|
||||
pruneFalseNode.BlockEndOffset = state.MethodExceptionHandlers
|
||||
.GetBlockEndOffsetFromOffset(
|
||||
state.CurrentInstruction.Offset);
|
||||
.GetBlockEndOffsetFromInstruction(
|
||||
state.CurrentInstruction);
|
||||
state.Cfg.RegisterNode(pruneTrueNode);
|
||||
state.Cfg.RegisterNode(pruneFalseNode);
|
||||
return (pruneTrueNode, pruneFalseNode);
|
||||
|
@ -553,18 +573,72 @@ namespace Cilsil.Cil.Parsers
|
|||
new ExnExpression(returnValue),
|
||||
Typ.FromTypeReference(retType),
|
||||
location);
|
||||
|
||||
|
||||
retNode.Instructions.Add(retInstr);
|
||||
retNode.Successors = new List<CfgNode> { state.ProcDesc.ExitNode };
|
||||
if (state.CurrentInstruction.OpCode.Code == Code.Throw)
|
||||
{
|
||||
var builtinFunctionExpression = new ConstExpression(ProcedureName.BuiltIn__throw);
|
||||
var throwCall = new Call(state.GetIdentifier(Identifier.IdentKind.Normal),
|
||||
new Tvoid(),
|
||||
builtinFunctionExpression,
|
||||
new List<Call.CallArg>(),
|
||||
new Call.CallFlags(),
|
||||
state.CurrentLocation);
|
||||
retNode.Instructions.Add(throwCall);
|
||||
}
|
||||
return retNode;
|
||||
}
|
||||
|
||||
protected static void CreateExceptionalEdges(ProgramState state, CfgNode entryNode)
|
||||
/// <summary>
|
||||
/// Gets the entry node for the exception handler, creating the entry block if needed.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="handler">The catch handler for which we are potentially creating
|
||||
/// exceptional control flow.</param>
|
||||
protected static CfgNode GetHandlerExceptionalEntryBlock(ProgramState state,
|
||||
ExceptionHandler handler)
|
||||
{
|
||||
foreach (var node in state.NodesToLinkWithExceptionBlock)
|
||||
if (!(handler.HandlerType == ExceptionHandlerType.Catch ||
|
||||
handler.HandlerType == ExceptionHandlerType.Finally))
|
||||
{
|
||||
node.ExceptionNodes.Add(entryNode);
|
||||
return null;
|
||||
}
|
||||
state.NodesToLinkWithExceptionBlock = new List<CfgNode>();
|
||||
CfgNode entryNode;
|
||||
Identifier exceptionIdentifier;
|
||||
var exnInfo = state.MethodExceptionHandlers;
|
||||
var instruction = handler.TryEnd.Previous;
|
||||
|
||||
if (!state.ExceptionHandlerSetToEntryNode.ContainsKey(handler))
|
||||
{
|
||||
if (handler.HandlerType == ExceptionHandlerType.Catch)
|
||||
{
|
||||
(entryNode, exceptionIdentifier) = GetHandlerEntryNode(
|
||||
state, exnInfo.TryOffsetToCatchHandlers[instruction.Offset]
|
||||
.Item1[0]
|
||||
.ExceptionHandler);
|
||||
// Exceptional control flow routes through the first of the set of
|
||||
// associated catch handlers; this invocation pushes the catch
|
||||
// handler's first instruction onto the stack and continues the
|
||||
// translation from the handler's catch variable load node.
|
||||
CreateCatchHandlerEntryBlock(
|
||||
state,
|
||||
exnInfo.TryOffsetToCatchHandlers[instruction.Offset]
|
||||
.Item1[0],
|
||||
entryNode,
|
||||
exceptionIdentifier);
|
||||
}
|
||||
// Is a finally handler.
|
||||
else
|
||||
{
|
||||
entryNode = CreateFinallyExceptionalEntryBlock(state, handler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
(entryNode, _) = state.ExceptionHandlerSetToEntryNode[handler];
|
||||
}
|
||||
return entryNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -19,99 +19,47 @@ namespace Cilsil.Cil.Parsers
|
|||
// Leave for catch block: target instruction immediately after catch block.
|
||||
case Code.Leave:
|
||||
case Code.Leave_S:
|
||||
case Code.Throw:
|
||||
var exnInfo = state.MethodExceptionHandlers;
|
||||
// This is null if the instruction is throw.
|
||||
var mapType = exnInfo.GetMapTypeFromInstruction(instruction);
|
||||
// This is null only if the instruction is throw, which is handled in a
|
||||
// different case.
|
||||
var target = instruction.Operand as Instruction;
|
||||
|
||||
if (instruction.OpCode.Code == Code.Throw)
|
||||
{
|
||||
(var returnValue, _) = state.Pop();
|
||||
var retNode = CreateExceptionReturnNode(state,
|
||||
returnValue,
|
||||
state.CurrentLocation);
|
||||
RegisterNode(state, retNode);
|
||||
}
|
||||
|
||||
// Leave within try of catch-block.
|
||||
if (exnInfo.TryOffsetToCatchHandlers.ContainsKey(instruction.Offset))
|
||||
if (mapType == MethodExceptionHandlers.MapType.TryToCatch)
|
||||
{
|
||||
if (state.NodesToLinkWithExceptionBlock.Count > 0)
|
||||
HandleFinallyControlFlowForTryToCatchLeave(state, instruction, target);
|
||||
}
|
||||
// Leave occurs within catch block.
|
||||
else if (mapType == MethodExceptionHandlers.MapType.CatchToCatch)
|
||||
{
|
||||
var exceptionHandler =
|
||||
exnInfo.GetExceptionHandlerAtInstruction(instruction);
|
||||
|
||||
// We now handle regular control flow if there's a finally block. Note,
|
||||
// theoretically we might use the same helper method as we do for
|
||||
// TryToCatch, but not completely sure that it would be equivalent.
|
||||
if (exceptionHandler != null &&
|
||||
exceptionHandler.HandlerType == ExceptionHandlerType.Finally)
|
||||
{
|
||||
CfgNode entryNode;
|
||||
Identifier exceptionIdentifier;
|
||||
if (!state.LeaveToExceptionEntryNode.ContainsKey(instruction))
|
||||
{
|
||||
(entryNode, exceptionIdentifier) = GetHandlerEntryNode(
|
||||
state, exnInfo.TryOffsetToCatchHandlers[instruction.Offset][0]
|
||||
.ExceptionHandler);
|
||||
state.LeaveToExceptionEntryNode[instruction] =
|
||||
(entryNode, exceptionIdentifier);
|
||||
// Exceptional control flow routes through the first of the set of
|
||||
// associated catch handlers; this invocation pushes the catch
|
||||
// handler's first instruction onto the stack and continues the
|
||||
// translation from the handler's catch variable load node.
|
||||
CreateCatchHandlerEntryBlock(
|
||||
state,
|
||||
exnInfo.TryOffsetToCatchHandlers[instruction.Offset][0],
|
||||
entryNode,
|
||||
exceptionIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
(entryNode, _) =
|
||||
state.LeaveToExceptionEntryNode[instruction];
|
||||
}
|
||||
CreateExceptionalEdges(state, entryNode);
|
||||
state.PushInstruction(
|
||||
exceptionHandler.HandlerStart,
|
||||
CreateFinallyHandlerNonExceptionalEntry(
|
||||
state, exceptionHandler, target));
|
||||
}
|
||||
if (target != null)
|
||||
else
|
||||
{
|
||||
// Control flow routes directly to the target, as there is no finally
|
||||
// block through which to first route it.
|
||||
state.PushInstruction(target);
|
||||
}
|
||||
}
|
||||
// Leave occurs within catch block.
|
||||
else if (exnInfo.CatchOffsetToCatchHandler.ContainsKey(instruction.Offset))
|
||||
// Narrowest containing interval for offset is the try of a finally block,
|
||||
// since leave can't occur within a finally handler.
|
||||
else if (mapType == MethodExceptionHandlers.MapType.TryToFinally)
|
||||
{
|
||||
var currentHandler = exnInfo.CatchOffsetToCatchHandler[instruction.Offset];
|
||||
|
||||
// Exceptional control flow routes through the finally block, if present,
|
||||
// prior to routing control flow to leave.
|
||||
if (currentHandler.FinallyBlock != null &&
|
||||
state.NodesToLinkWithExceptionBlock.Count > 0)
|
||||
{
|
||||
var finallyEntryNode = CreateFinallyExceptionalEntryBlock(
|
||||
state, currentHandler.FinallyBlock);
|
||||
CreateExceptionalEdges(state, finallyEntryNode);
|
||||
}
|
||||
if (target != null)
|
||||
{
|
||||
if (currentHandler.FinallyBlock != null)
|
||||
{
|
||||
state.PushInstruction(
|
||||
currentHandler.FinallyBlock.HandlerStart,
|
||||
CreateFinallyHandlerNonExceptionalEntry(
|
||||
state, currentHandler.FinallyBlock, target));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Control flow routes directly to the target, as there is no finally
|
||||
// block through which to first route it.
|
||||
state.PushInstruction(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Leave occurs within try of finally block (we leave this as the last option,
|
||||
// as the try block of a finally encompasses all of the try-catch bytecode, in
|
||||
// the case of try-catch-finally.
|
||||
else if (exnInfo.TryOffsetToFinallyHandler.ContainsKey(instruction.Offset))
|
||||
{
|
||||
var finallyHandler = exnInfo.TryOffsetToFinallyHandler[instruction.Offset];
|
||||
if (state.NodesToLinkWithExceptionBlock.Count > 0)
|
||||
{
|
||||
var finallyEntryNode =
|
||||
CreateFinallyExceptionalEntryBlock(state, finallyHandler);
|
||||
CreateExceptionalEdges(state, finallyEntryNode);
|
||||
}
|
||||
var finallyHandler =
|
||||
exnInfo.TryOffsetToFinallyHandler[instruction.Offset].Item1;
|
||||
if (target != null)
|
||||
{
|
||||
state.PushInstruction(finallyHandler.HandlerStart,
|
||||
|
@ -120,6 +68,29 @@ namespace Cilsil.Cil.Parsers
|
|||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case Code.Throw:
|
||||
(var returnValue, _) = state.Pop();
|
||||
var retNode = CreateExceptionReturnNode(state,
|
||||
returnValue,
|
||||
state.CurrentLocation);
|
||||
RegisterNode(state, retNode);
|
||||
return true;
|
||||
case Code.Rethrow:
|
||||
var exceptionType =
|
||||
state.MethodExceptionHandlers
|
||||
.CatchOffsetToCatchHandler[instruction.Offset]
|
||||
.Item1
|
||||
.ExceptionHandler
|
||||
.CatchType;
|
||||
(var memoryAllocationCall, var objectVariable) = CreateMemoryAllocationCall(
|
||||
exceptionType, state);
|
||||
state.PreviousNode.Instructions.Add(memoryAllocationCall);
|
||||
var rethrowNode = CreateExceptionReturnNode(state,
|
||||
objectVariable,
|
||||
state.CurrentLocation);
|
||||
RegisterNode(state, rethrowNode);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -128,9 +99,9 @@ namespace Cilsil.Cil.Parsers
|
|||
/// <summary>
|
||||
/// Creates a new node to ensure finally instructions aren't attached to a body node.
|
||||
/// </summary>
|
||||
private static CfgNode CreateFinallyHandlerNonExceptionalEntry(ProgramState state,
|
||||
ExceptionHandler handler,
|
||||
Instruction leaveTarget)
|
||||
private static CfgNode CreateFinallyHandlerNonExceptionalEntry(
|
||||
ProgramState state, ExceptionHandler handler,
|
||||
Instruction leaveTarget, CfgNode endFinallyThrowNode = null)
|
||||
{
|
||||
CfgNode finallyHandlerStartNode = null;
|
||||
(var nodeOffset, _) = state.GetOffsetNode(handler.HandlerStart.Offset);
|
||||
|
@ -144,9 +115,42 @@ namespace Cilsil.Cil.Parsers
|
|||
state.PreviousNode.Successors.Add(finallyHandlerStartNode);
|
||||
state.AppendToPreviousNode = true;
|
||||
}
|
||||
state.EndfinallyControlFlow = leaveTarget;
|
||||
if (leaveTarget != null)
|
||||
{
|
||||
state.EndfinallyControlFlow = leaveTarget;
|
||||
}
|
||||
if (endFinallyThrowNode != null)
|
||||
{
|
||||
state.EndfinallyThrowNode = endFinallyThrowNode;
|
||||
}
|
||||
return finallyHandlerStartNode;
|
||||
}
|
||||
|
||||
private void HandleFinallyControlFlowForTryToCatchLeave(
|
||||
ProgramState state, Instruction currentInstr, Instruction targetInstr)
|
||||
{
|
||||
var exnInfo = state.MethodExceptionHandlers;
|
||||
exnInfo.TryOffsetToFinallyHandler.TryGetValue(
|
||||
currentInstr.Offset, out var currentFinallyHandler);
|
||||
exnInfo.TryOffsetToFinallyHandler.TryGetValue(
|
||||
targetInstr.Offset, out var targetFinallyHandler);
|
||||
// If target is associated with a different finally handler than is
|
||||
// the present instruction, then we need to route control flow through
|
||||
// this finally block first as we're leaving the finally handler. If we just use
|
||||
// GetExceptionHandlerAtInstruction, this would just yield a catch block, obscuring the
|
||||
// need to first direct flow through the finally block.
|
||||
if (currentFinallyHandler.Item1?.HandlerStart?.Offset !=
|
||||
targetFinallyHandler.Item1?.HandlerStart?.Offset)
|
||||
{
|
||||
state.PushInstruction(
|
||||
currentFinallyHandler.Item1.HandlerStart,
|
||||
CreateFinallyHandlerNonExceptionalEntry(
|
||||
state, currentFinallyHandler.Item1, targetInstr));
|
||||
}
|
||||
else
|
||||
{
|
||||
state.PushInstruction(targetInstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,9 +159,7 @@ namespace Cilsil.Services
|
|||
|
||||
var methodBody = method.Body;
|
||||
var unhandledExceptionCase =
|
||||
programState.MethodExceptionHandlers.UnhandledExceptionBlock ||
|
||||
!programState.MethodExceptionHandlers.NoNestedTryCatchFinally() ||
|
||||
!programState.MethodExceptionHandlers.NoFinallyEndWithThrow();
|
||||
programState.MethodExceptionHandlers.UnhandledExceptionBlock;
|
||||
|
||||
// True if the translation terminates early, false otherwise.
|
||||
var translationUnfinished = false;
|
||||
|
@ -196,19 +194,16 @@ namespace Cilsil.Services
|
|||
MethodExceptionHandlers.DefaultHandlerEndOffset);
|
||||
// We don't reuse nodes of finally handlers.
|
||||
if (nodeAtOffset != null &&
|
||||
!programState.MethodExceptionHandlers
|
||||
.FinallyOffsetToFinallyHandler
|
||||
.ContainsKey(nextInstruction.Offset) &&
|
||||
!programState.MethodExceptionHandlers
|
||||
.CatchOffsetToCatchHandler
|
||||
.ContainsKey(nextInstruction.Offset))
|
||||
programState.MethodExceptionHandlers
|
||||
.GetMapTypeFromInstruction(nextInstruction)
|
||||
!= MethodExceptionHandlers.MapType.CatchToCatch)
|
||||
{
|
||||
programState.PreviousNode.Successors.Add(nodeAtOffset);
|
||||
}
|
||||
else if (unhandledExceptionCase)
|
||||
{
|
||||
Log.WriteWarning($"Unhandled exception-handling.");
|
||||
Log.RecordUnknownInstruction("unhandled-exception");
|
||||
Log.RecordUnknownInstruction("unhandled-exception");
|
||||
Log.RecordUnfinishedMethod(programState.Method.GetCompatibleFullName(),
|
||||
nextInstruction.RemainingInstructionCount());
|
||||
translationUnfinished = true;
|
||||
|
|
|
@ -97,6 +97,16 @@ namespace Cilsil.Sil
|
|||
BuiltInClassName,
|
||||
string.Empty,
|
||||
true);
|
||||
|
||||
/// <summary>
|
||||
/// Standard procedure name for throw calls, interpreted specially by the backend.
|
||||
/// </summary>
|
||||
public static readonly ProcedureName BuiltIn__throw =
|
||||
new ProcedureName("__throw",
|
||||
new List<string>(),
|
||||
BuiltInClassName,
|
||||
string.Empty,
|
||||
true);
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -21,10 +21,6 @@ namespace Cilsil.Utils
|
|||
/// </summary>
|
||||
public ExceptionHandlerNode PreviousCatchBlock;
|
||||
/// <summary>
|
||||
/// The finally block.
|
||||
/// </summary>
|
||||
public ExceptionHandler FinallyBlock;
|
||||
/// <summary>
|
||||
/// The first catch handler.
|
||||
/// </summary>
|
||||
public ExceptionHandler FirstCatchHandler = null;
|
||||
|
@ -34,14 +30,11 @@ namespace Cilsil.Utils
|
|||
/// </summary>
|
||||
/// <param name="exceptionHandler">The exception handler.</param>
|
||||
/// <param name="nextCatchBlock">The next catch clause.</param>
|
||||
/// <param name="finallyBlock">The finally block.</param>
|
||||
public ExceptionHandlerNode(ExceptionHandler exceptionHandler,
|
||||
ExceptionHandlerNode nextCatchBlock = null,
|
||||
ExceptionHandler finallyBlock = null)
|
||||
ExceptionHandlerNode nextCatchBlock = null)
|
||||
{
|
||||
ExceptionHandler = exceptionHandler;
|
||||
NextCatchBlock = nextCatchBlock;
|
||||
FinallyBlock = finallyBlock;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Mono.Cecil.Cil;
|
||||
using System.Collections.Generic;
|
||||
using static Cilsil.Utils.MethodExceptionHandlers;
|
||||
|
||||
namespace Cilsil.Utils
|
||||
{
|
||||
|
@ -42,34 +43,94 @@ namespace Cilsil.Utils
|
|||
/// <summary>
|
||||
/// Maps offsets within try-blocks of try-catch to the corresponding catch handler set.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, List<ExceptionHandlerNode>> TryOffsetToCatchHandlers;
|
||||
public readonly Dictionary<int,
|
||||
(List<ExceptionHandlerNode>, int)> TryOffsetToCatchHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Maps offsets within try-blocks of try-finally to the corresponding finally handler.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, ExceptionHandler> TryOffsetToFinallyHandler;
|
||||
public readonly Dictionary<int, (ExceptionHandler, int)> TryOffsetToFinallyHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Maps offsets within catch-blocks to the corresponding catch handler.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, ExceptionHandlerNode> CatchOffsetToCatchHandler;
|
||||
public readonly Dictionary<int, (ExceptionHandlerNode, int)> CatchOffsetToCatchHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Maps offsets within finally-blocks to the corresponding finally handler.
|
||||
/// Maps offsets within finally-blocks to the corresponding finally handler and the .
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, ExceptionHandler> FinallyOffsetToFinallyHandler;
|
||||
public readonly Dictionary<int, (ExceptionHandler, int)> FinallyOffsetToFinallyHandler;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used for describing the most closely nested handler for an offset.
|
||||
/// </summary>
|
||||
public enum MapType
|
||||
{
|
||||
/// <summary>
|
||||
/// Try offset to corresponding catch handler.
|
||||
/// </summary>
|
||||
TryToCatch,
|
||||
/// <summary>
|
||||
/// Try offset to corresponding finally handler.
|
||||
/// </summary>
|
||||
TryToFinally,
|
||||
/// <summary>
|
||||
/// Catch offset to corresponding catch handler.
|
||||
/// </summary>
|
||||
CatchToCatch,
|
||||
/// <summary>
|
||||
/// Finally offset to corresponding finally handler.
|
||||
/// </summary>
|
||||
FinallyToFinally,
|
||||
/// <summary>
|
||||
/// No handler associated with offset.
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the narrowest interval containing the given offset.
|
||||
/// </summary>
|
||||
/// <param name="instruction">The offset for which to contain the interval.</param>
|
||||
/// <returns>The MapType corresponding to the narrowest interval at the given
|
||||
/// offset.</returns>
|
||||
public MapType GetMapTypeFromInstruction(Instruction instruction)
|
||||
{
|
||||
var offset = instruction.Offset;
|
||||
var mapType = MapType.None;
|
||||
var intervalContainingOffsetSize = int.MaxValue;
|
||||
if (TryOffsetToCatchHandlers.ContainsKey(offset) &&
|
||||
TryOffsetToCatchHandlers[offset].Item2 < intervalContainingOffsetSize)
|
||||
{
|
||||
mapType = MapType.TryToCatch;
|
||||
intervalContainingOffsetSize = TryOffsetToCatchHandlers[offset].Item2;
|
||||
}
|
||||
if (TryOffsetToFinallyHandler.ContainsKey(offset) &&
|
||||
TryOffsetToFinallyHandler[offset].Item2 < intervalContainingOffsetSize)
|
||||
{
|
||||
mapType = MapType.TryToFinally;
|
||||
intervalContainingOffsetSize = TryOffsetToFinallyHandler[offset].Item2;
|
||||
}
|
||||
if (CatchOffsetToCatchHandler.ContainsKey(offset) &&
|
||||
CatchOffsetToCatchHandler[offset].Item2 < intervalContainingOffsetSize)
|
||||
{
|
||||
mapType = MapType.CatchToCatch;
|
||||
intervalContainingOffsetSize = CatchOffsetToCatchHandler[offset].Item2;
|
||||
}
|
||||
if (FinallyOffsetToFinallyHandler.ContainsKey(offset) &&
|
||||
FinallyOffsetToFinallyHandler[offset].Item2 < intervalContainingOffsetSize)
|
||||
{
|
||||
mapType = MapType.FinallyToFinally;
|
||||
}
|
||||
return mapType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default handler end offset, overwritten when it can be found.
|
||||
/// </summary>
|
||||
public const int DefaultHandlerEndOffset = -1;
|
||||
|
||||
private static bool InstructionBlockWithinBounds(
|
||||
(Instruction start, Instruction end) block,
|
||||
(Instruction start, Instruction end) bounds) =>
|
||||
block.start.Offset >= bounds.start.Offset && block.end.Offset <= bounds.end.Offset;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if there is an unsupported exception block; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
|
@ -149,22 +210,10 @@ namespace Cilsil.Utils
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var catchTry in TryBoundsToCatchHandlers.Keys)
|
||||
{
|
||||
foreach (var finallyTry in TryBoundsToFinallyHandlers.Keys)
|
||||
{
|
||||
if (InstructionBlockWithinBounds(catchTry, finallyTry))
|
||||
{
|
||||
foreach (var handler in TryBoundsToCatchHandlers[catchTry])
|
||||
{
|
||||
handler.FinallyBlock = TryBoundsToFinallyHandlers[finallyTry];
|
||||
}
|
||||
}
|
||||
// Each set of handlers can map to only one finally block.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// A different offset does not necessarily indicate a different instruction -- there
|
||||
// are key values (offsets) in these dictionaries which don't actually refer to
|
||||
// instructions. This doesn't create any issue with the representation of actual
|
||||
// existing instructions.
|
||||
TryOffsetToCatchHandlers = ConvertBoundsToOffsets(TryBoundsToCatchHandlers);
|
||||
TryOffsetToFinallyHandler = ConvertBoundsToOffsets(TryBoundsToFinallyHandlers);
|
||||
CatchOffsetToCatchHandler = ConvertBoundsToOffsets(CatchBoundsToCatchHandler);
|
||||
|
@ -172,100 +221,44 @@ namespace Cilsil.Utils
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the exception handler associated with the instruction -- a catch handler in the
|
||||
/// case of an instruction within the try block of a try-catch or try-catch-finally or a
|
||||
/// finally handler for try-finally, and a finally handler in the case of a catch
|
||||
/// instruction in try-catch-finally. We assume there is no try-catch nested within a
|
||||
/// finally block or in a catch-block.
|
||||
/// Returns the most nested exception handler associated with the instruction.
|
||||
/// </summary>
|
||||
/// <param name="instruction">The instruction from which to determine the
|
||||
/// exception-handling control flow.</param>
|
||||
/// <returns>The exception handler in any of the cases described above.</returns>
|
||||
public ExceptionHandler GetExceptionHandlerAtInstruction(Instruction instruction)
|
||||
{
|
||||
if (TryOffsetToCatchHandlers.ContainsKey(instruction.Offset))
|
||||
var offset = instruction.Offset;
|
||||
switch (GetMapTypeFromInstruction(instruction))
|
||||
{
|
||||
return TryOffsetToCatchHandlers[instruction.Offset][0].ExceptionHandler;
|
||||
case MapType.TryToCatch:
|
||||
return TryOffsetToCatchHandlers[offset].Item1[0].ExceptionHandler;
|
||||
case MapType.TryToFinally:
|
||||
return TryOffsetToFinallyHandler[offset].Item1;
|
||||
case MapType.CatchToCatch:
|
||||
case MapType.FinallyToFinally:
|
||||
// Both handlers; we select the narrower handler.
|
||||
if (TryOffsetToCatchHandlers.ContainsKey(offset) &&
|
||||
TryOffsetToFinallyHandler.ContainsKey(offset))
|
||||
{
|
||||
return TryOffsetToCatchHandlers[offset].Item2 < TryOffsetToFinallyHandler[offset].Item2 ?
|
||||
TryOffsetToCatchHandlers[offset].Item1[0].ExceptionHandler :
|
||||
TryOffsetToFinallyHandler[offset].Item1;
|
||||
}
|
||||
// Catch handler but no finally handler.
|
||||
if (TryOffsetToCatchHandlers.ContainsKey(offset))
|
||||
{
|
||||
return TryOffsetToCatchHandlers[offset].Item1[0].ExceptionHandler;
|
||||
}
|
||||
// Finally handler but no catch handler.
|
||||
if (TryOffsetToFinallyHandler.ContainsKey(offset))
|
||||
{
|
||||
return TryOffsetToFinallyHandler[offset].Item1;
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
else if (TryOffsetToFinallyHandler.ContainsKey(instruction.Offset))
|
||||
{
|
||||
return TryOffsetToFinallyHandler[instruction.Offset];
|
||||
}
|
||||
else if (CatchOffsetToCatchHandler.ContainsKey(instruction.Offset))
|
||||
{
|
||||
return CatchOffsetToCatchHandler[instruction.Offset].ExceptionHandler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method determines that there are no nested exception-handling blocks, aside from
|
||||
/// the catch block necessarily being nested in the try block of a try-catch-finally.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the catch try, catch, and finally block offsets are all
|
||||
/// mutually exclusive.</returns>
|
||||
public bool NoNestedTryCatchFinally()
|
||||
{
|
||||
// For any try-catch-finally block, the try associated with the finally block includes
|
||||
// the whole catch block (and therefore also the try associated with the catch block).
|
||||
var boundsList = new List<(int start, int end)>();
|
||||
var finallyBoundsList = new List<(int start, int end)>();
|
||||
|
||||
foreach ((var tryStart, var tryEnd) in TryBoundsToCatchHandlers.Keys)
|
||||
{
|
||||
boundsList.Add((tryStart.Offset, tryEnd.Offset));
|
||||
}
|
||||
foreach (var handler in CatchBoundsToCatchHandler.Values)
|
||||
{
|
||||
boundsList.Add((handler.ExceptionHandler.HandlerStart.Offset,
|
||||
handler.ExceptionHandler.HandlerEnd.Previous.Offset));
|
||||
}
|
||||
foreach (var handler in TryBoundsToFinallyHandlers.Values)
|
||||
{
|
||||
boundsList.Add((handler.HandlerStart.Offset,
|
||||
handler.HandlerEnd.Previous.Offset));
|
||||
|
||||
finallyBoundsList.Add((handler.HandlerStart.Offset,
|
||||
handler.HandlerEnd.Previous.Offset));
|
||||
finallyBoundsList.Add((handler.TryStart.Offset,
|
||||
handler.TryEnd.Previous.Offset));
|
||||
}
|
||||
boundsList.Sort((x, y) => x.end.CompareTo(y.end));
|
||||
finallyBoundsList.Sort((x, y) => x.end.CompareTo(y.end));
|
||||
|
||||
for (int i = 1; i < boundsList.Count; i++)
|
||||
{
|
||||
if (boundsList[i - 1].end > boundsList[i].start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < finallyBoundsList.Count; i++)
|
||||
{
|
||||
if (finallyBoundsList[i - 1].end > finallyBoundsList[i].start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for excluding translation of methods with finally blocks ending in throw.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if there is no finally block that ends with throw, <c>false</c>
|
||||
/// otherwise.</returns>
|
||||
public bool NoFinallyEndWithThrow()
|
||||
{
|
||||
foreach (var finallyEnd in FinallyEndToHandler.Keys)
|
||||
{
|
||||
if (finallyEnd.OpCode.Code == Code.Throw)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -275,47 +268,56 @@ namespace Cilsil.Utils
|
|||
/// of a try-catch, the try-catch try end is returned, as opposed to the try-finally
|
||||
/// try end if there is an enclosing finally block.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="instruction">The offset.</param>
|
||||
/// <returns>The end offset of the most immediate surrounding exception-handling block;
|
||||
/// returns the default value if the offset is not in a block.</returns>
|
||||
public int GetBlockEndOffsetFromOffset(int offset)
|
||||
public int GetBlockEndOffsetFromInstruction(Instruction instruction)
|
||||
{
|
||||
if (TryOffsetToCatchHandlers.ContainsKey(offset))
|
||||
var offset = instruction.Offset;
|
||||
var mapType = GetMapTypeFromInstruction(instruction);
|
||||
if (mapType == MapType.TryToCatch)
|
||||
{
|
||||
return TryOffsetToCatchHandlers[offset][0].ExceptionHandler
|
||||
.TryEnd
|
||||
.Previous
|
||||
.Offset;
|
||||
return TryOffsetToCatchHandlers[offset].Item1[0]
|
||||
.ExceptionHandler
|
||||
.TryEnd
|
||||
.Previous
|
||||
.Offset;
|
||||
}
|
||||
else if (CatchOffsetToCatchHandler.ContainsKey(offset))
|
||||
else if (mapType == MapType.CatchToCatch)
|
||||
{
|
||||
return CatchOffsetToCatchHandler[offset].ExceptionHandler
|
||||
return CatchOffsetToCatchHandler[offset].Item1
|
||||
.ExceptionHandler
|
||||
.HandlerEnd
|
||||
.Previous
|
||||
.Offset;
|
||||
}
|
||||
else if (TryOffsetToFinallyHandler.ContainsKey(offset))
|
||||
else if (mapType == MapType.TryToFinally)
|
||||
{
|
||||
return TryOffsetToFinallyHandler[offset].TryEnd.Previous.Offset;
|
||||
return TryOffsetToFinallyHandler[offset].Item1.TryEnd.Previous.Offset;
|
||||
}
|
||||
|
||||
else if (FinallyOffsetToFinallyHandler.ContainsKey(offset))
|
||||
else if (mapType == MapType.FinallyToFinally)
|
||||
{
|
||||
return FinallyOffsetToFinallyHandler[offset].HandlerEnd.Previous.Offset;
|
||||
return FinallyOffsetToFinallyHandler[offset].Item1.HandlerEnd.Previous.Offset;
|
||||
}
|
||||
return DefaultHandlerEndOffset;
|
||||
;
|
||||
}
|
||||
|
||||
private static Dictionary<int, T> ConvertBoundsToOffsets<T>(
|
||||
private static Dictionary<int, (T, int)> ConvertBoundsToOffsets<T>(
|
||||
Dictionary<(Instruction, Instruction), T> boundsToObject)
|
||||
{
|
||||
var converted = new Dictionary<int, T>();
|
||||
var converted = new Dictionary<int, (T, int)>();
|
||||
foreach ((var start, var end) in boundsToObject.Keys)
|
||||
{
|
||||
// We track the narrowest of all of the intervals of this type containing the
|
||||
// offset.
|
||||
for (int i = start.Offset; i <= end.Offset; i++)
|
||||
{
|
||||
converted[i] = boundsToObject[(start, end)];
|
||||
if (!converted.ContainsKey(i) ||
|
||||
end.Offset - start.Offset < converted[i].Item2)
|
||||
{
|
||||
converted[i] = (boundsToObject[(start, end)], end.Offset - start.Offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
|
|
|
@ -129,13 +129,6 @@ namespace Cilsil.Utils
|
|||
/// </summary>
|
||||
private int NextAvailableSyntheticVariableId;
|
||||
|
||||
/// <summary>
|
||||
/// List of nodes to link to an exception block when a leave instruction is encountered. If
|
||||
/// the translation state is not in a try or catch block, translated body nodes don't need
|
||||
/// to be recorded.
|
||||
/// </summary>
|
||||
public List<CfgNode> NodesToLinkWithExceptionBlock;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if the top instruction is in a try or catch block; <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
|
@ -150,7 +143,7 @@ namespace Cilsil.Utils
|
|||
(CfgNode node, LvarExpression variable)> ExceptionHandlerToCatchVarNode;
|
||||
|
||||
/// <summary>
|
||||
/// The exception handler to its entry node as well as pthe identifier for the unwrapped
|
||||
/// The exception handler to its entry node as well as the identifier for the unwrapped
|
||||
/// exception.
|
||||
/// </summary>
|
||||
public Dictionary<ExceptionHandler,
|
||||
|
@ -170,10 +163,10 @@ namespace Cilsil.Utils
|
|||
public Instruction EndfinallyControlFlow;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a leave instruction offset to the exceptional entry node created for it, as well
|
||||
/// as the associated identifier for the unwrapped exception.
|
||||
/// Null by default. When control flow is exited via a throw instruction, this is the throw
|
||||
/// node to be added at the end of the translation of the finally block, when one exists.
|
||||
/// </summary>
|
||||
public Dictionary<Instruction, (CfgNode, Identifier)> LeaveToExceptionEntryNode;
|
||||
public CfgNode EndfinallyThrowNode;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the program's exception handlers.
|
||||
|
@ -207,7 +200,6 @@ namespace Cilsil.Utils
|
|||
ParsedInstructions = new List<Instruction>();
|
||||
|
||||
MethodExceptionHandlers = new MethodExceptionHandlers(method.Body);
|
||||
NodesToLinkWithExceptionBlock = new List<CfgNode>();
|
||||
InstructionInTryOrCatch = false;
|
||||
|
||||
OffsetToNode = new Dictionary<int, List<(CfgNode Node, ProgramStack Stack, int)>>();
|
||||
|
@ -219,7 +211,6 @@ namespace Cilsil.Utils
|
|||
FinallyHandlerToExceptionExit = new Dictionary<ExceptionHandler, CfgNode>();
|
||||
ExceptionHandlerSetToEntryNode = new Dictionary<ExceptionHandler,
|
||||
(CfgNode node, Identifier id)>();
|
||||
LeaveToExceptionEntryNode = new Dictionary<Instruction, (CfgNode, Identifier)>();
|
||||
|
||||
FinallyExceptionalTranslation = false;
|
||||
|
||||
|
@ -242,8 +233,8 @@ namespace Cilsil.Utils
|
|||
// and the previous node is from a different handler block, we empty the saved stack,
|
||||
// as there should not be anything on the stack when transferring control between
|
||||
// different handler blocks.
|
||||
if (!MethodExceptionHandlers.CatchOffsetToCatchHandler
|
||||
.ContainsKey(CurrentInstruction.Offset) &&
|
||||
if (MethodExceptionHandlers.GetMapTypeFromInstruction(CurrentInstruction) !=
|
||||
MethodExceptionHandlers.MapType.CatchToCatch &&
|
||||
node.BlockEndOffset != PreviousNode.BlockEndOffset)
|
||||
{
|
||||
OffsetToNode
|
||||
|
|
|
@ -36,7 +36,7 @@ public class IsDisposedBooleanField : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// Expect 2 TAINT_ERROR for SQL injection flows.
|
||||
// Expect 4 TAINT_ERROR for SQL injection flows.
|
||||
public class PulseTaintTests
|
||||
{
|
||||
[HttpPost]
|
||||
|
@ -62,6 +62,32 @@ public class PulseTaintTests
|
|||
{
|
||||
subproj.WeatherForecast.runSqlCommandStoredProcedure(InputParameter.ToString());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public void SearchRawData(string query)
|
||||
{
|
||||
var queryPrefix = "prefix";
|
||||
using (var conn = new SqlConnection("readerConnectionString"))
|
||||
{
|
||||
using (var command = new SqlCommand(queryPrefix + query))
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
Console.Write("Hello");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Write(ex.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -110,6 +136,85 @@ public class ThreadSafety
|
|||
{
|
||||
return null;
|
||||
}
|
||||
public static void TestNullDerefExpectError()
|
||||
{
|
||||
object x = new object();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("First try catch");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("Before finally");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x = new object();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Inner try catch finally");
|
||||
x = null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Outer try catch finally");
|
||||
throw new Exception();
|
||||
}
|
||||
Console.Write("Last instruction of outer try catch");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static void TestNullDerefExpectNoError()
|
||||
{
|
||||
object x = new object();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("First try catch");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("Before finally");
|
||||
throw new Exception();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Inner try catch finally");
|
||||
x = new object();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.Write("Outer try catch finally");
|
||||
throw new Exception();
|
||||
}
|
||||
Console.Write("Last instruction of outer try catch");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
x.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MainClass
|
||||
|
@ -124,11 +229,38 @@ public class MainClass
|
|||
}
|
||||
}
|
||||
|
||||
// 17 reports expected (18 with --pulse-increase-leak-recall flag)
|
||||
// 18 reports expected (19 with --pulse-increase-leak-recall flag)
|
||||
class InferResourceLeakTests
|
||||
{
|
||||
private static byte[] myBytes = new byte[] { 10, 4 };
|
||||
|
||||
/// <summary>
|
||||
/// Validates that even though the Dispose occurs within a finally block, the exceptional
|
||||
/// control flow is handled so that the analysis sees that the Dispose is not invoked. Also
|
||||
/// gets coverage over finally blocks ending with throw instead of endfinally.
|
||||
/// </summary>
|
||||
public static void TryFinallyThrow()
|
||||
{
|
||||
var fs = new FileStream("", FileMode.Open);
|
||||
var y = new FileStream("", FileMode.Open);
|
||||
try
|
||||
{
|
||||
Console.Write("hello");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fs != null)
|
||||
{
|
||||
fs.Dispose();
|
||||
}
|
||||
throw new Exception();
|
||||
}
|
||||
if (y != null)
|
||||
{
|
||||
y.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a false positive that occurs when we return a class that owns IDisposable types.
|
||||
/// This occurs because the setter method at the end of the async MoveNext() method which
|
||||
|
@ -428,6 +560,26 @@ class InferResourceLeakTests
|
|||
return new TakeAndDisposeNotDisposable(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should be no leak, as all resources are allocated via using.
|
||||
/// </summary>
|
||||
public static string NestedUsingWithThrownException()
|
||||
{
|
||||
using (var x = new StreamReader(""))
|
||||
{
|
||||
Console.Write("First using");
|
||||
using (var y = new StreamReader(""))
|
||||
{
|
||||
Console.Write("Second using");
|
||||
if (y != null)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that upward casting of stream to IDisposable is not reported on.
|
||||
/// </summary>
|
||||
|
|
Загрузка…
Ссылка в новой задаче