You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
8.2 KiB
229 lines
8.2 KiB
// Copyright (c) 2014 Daniel Grunwald
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using ICSharpCode.Decompiler.IL.Transforms;
|
|
using System.Threading;
|
|
using System.Collections.Generic;
|
|
|
|
namespace ICSharpCode.Decompiler.IL.ControlFlow
|
|
{
|
|
/// <summary>
|
|
/// Detect suitable exit points for BlockContainers.
|
|
///
|
|
/// An "exit point" is an instruction that causes control flow
|
|
/// to leave the container (a branch or leave instruction).
|
|
///
|
|
/// If an "exit point" instruction is placed immediately following a
|
|
/// block container, each equivalent exit point within the container
|
|
/// can be replaced with a "leave container" instruction.
|
|
///
|
|
/// This transform performs this replacement: any exit points
|
|
/// equivalent to the exit point following the container are
|
|
/// replaced with a leave instruction.
|
|
/// Additionally, if the container is not yet followed by an exit point,
|
|
/// but has room to introduce such an exit point (i.e. iff the container's
|
|
/// end point is currently unreachable), we pick one of the non-return
|
|
/// exit points within the container, move it to the position following the
|
|
/// container, and replace all instances within the container with a leave
|
|
/// instruction.
|
|
///
|
|
/// This makes it easier for the following transforms to construct
|
|
/// control flow that falls out of blocks instead of using goto/break statements.
|
|
/// </summary>
|
|
public class DetectExitPoints : ILVisitor, IILTransform
|
|
{
|
|
static readonly Nop ExitNotYetDetermined = new Nop { Comment = "ExitNotYetDetermined" };
|
|
static readonly Nop NoExit = new Nop { Comment = "NoExit" };
|
|
|
|
bool canIntroduceExitForReturn;
|
|
|
|
public DetectExitPoints(bool canIntroduceExitForReturn)
|
|
{
|
|
this.canIntroduceExitForReturn = canIntroduceExitForReturn;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the next instruction after <paramref name="inst"/> is executed.
|
|
/// Returns NoExit when the next instruction cannot be identified;
|
|
/// returns <c>null</c> when the end of a Block is reached (so that we could insert an arbitrary instruction)
|
|
/// </summary>
|
|
internal static ILInstruction GetExit(ILInstruction inst)
|
|
{
|
|
SlotInfo slot = inst.SlotInfo;
|
|
if (slot == Block.InstructionSlot) {
|
|
Block block = (Block)inst.Parent;
|
|
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined;
|
|
} else if (slot == TryInstruction.TryBlockSlot
|
|
|| slot == TryCatchHandler.BodySlot
|
|
|| slot == TryCatch.HandlerSlot
|
|
|| slot == PinnedRegion.BodySlot)
|
|
{
|
|
return GetExit(inst.Parent);
|
|
}
|
|
return NoExit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true iff exit1 and exit2 are both exit instructions
|
|
/// (branch or leave) and both represent the same exit.
|
|
/// </summary>
|
|
internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
|
|
{
|
|
if (exit1 == null || exit2 == null || exit1.OpCode != exit2.OpCode)
|
|
return false;
|
|
switch (exit1.OpCode) {
|
|
case OpCode.Branch:
|
|
Branch br1 = (Branch)exit1;
|
|
Branch br2 = (Branch)exit2;
|
|
return br1.TargetBlock == br2.TargetBlock;
|
|
case OpCode.Leave:
|
|
Leave leave1 = (Leave)exit1;
|
|
Leave leave2 = (Leave)exit2;
|
|
return leave1.TargetContainer == leave2.TargetContainer && leave1.Value.MatchNop() && leave2.Value.MatchNop();
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CancellationToken cancellationToken;
|
|
BlockContainer currentContainer;
|
|
|
|
/// <summary>
|
|
/// The instruction that will be executed next after leaving the currentContainer.
|
|
/// <c>ExitNotYetDetermined</c> means the container is last in its parent block, and thus does not
|
|
/// yet have any leave instructions. This means we can move any exit instruction of
|
|
/// our choice our of the container and replace it with a leave instruction.
|
|
/// </summary>
|
|
ILInstruction currentExit;
|
|
|
|
/// <summary>
|
|
/// If <c>currentExit==ExitNotYetDetermined</c>, holds the list of potential exit instructions.
|
|
/// After the currentContainer was visited completely, one of these will be selected as exit instruction.
|
|
/// </summary>
|
|
List<ILInstruction> potentialExits;
|
|
|
|
public void Run(ILFunction function, ILTransformContext context)
|
|
{
|
|
cancellationToken = context.CancellationToken;
|
|
currentExit = NoExit;
|
|
function.AcceptVisitor(this);
|
|
}
|
|
|
|
protected override void Default(ILInstruction inst)
|
|
{
|
|
foreach (var child in inst.Children)
|
|
child.AcceptVisitor(this);
|
|
}
|
|
|
|
protected internal override void VisitBlockContainer(BlockContainer container)
|
|
{
|
|
var oldExit = currentExit;
|
|
var oldContainer = currentContainer;
|
|
var oldPotentialExits = potentialExits;
|
|
var thisExit = GetExit(container);
|
|
currentExit = thisExit;
|
|
currentContainer = container;
|
|
potentialExits = (thisExit == ExitNotYetDetermined ? new List<ILInstruction>() : null);
|
|
base.VisitBlockContainer(container);
|
|
if (thisExit == ExitNotYetDetermined && potentialExits.Count > 0) {
|
|
// This transform determined an exit point.
|
|
currentExit = ChooseExit(potentialExits);
|
|
foreach (var exit in potentialExits) {
|
|
if (CompatibleExitInstruction(currentExit, exit)) {
|
|
exit.ReplaceWith(new Leave(currentContainer) { ILRange = exit.ILRange });
|
|
}
|
|
}
|
|
Debug.Assert(!currentExit.MatchLeave(currentContainer));
|
|
ILInstruction inst = container;
|
|
// traverse up to the block (we'll always find one because GetExit
|
|
// only returns ExitNotYetDetermined if there's a block)
|
|
while (inst.Parent.OpCode != OpCode.Block)
|
|
inst = inst.Parent;
|
|
Block block = (Block)inst.Parent;
|
|
block.Instructions.Add(currentExit);
|
|
} else {
|
|
Debug.Assert(thisExit == currentExit);
|
|
}
|
|
currentExit = oldExit;
|
|
currentContainer = oldContainer;
|
|
potentialExits = oldPotentialExits;
|
|
}
|
|
|
|
static ILInstruction ChooseExit(List<ILInstruction> potentialExits)
|
|
{
|
|
ILInstruction first = potentialExits[0];
|
|
if (first is Leave l && l.IsLeavingFunction) {
|
|
for (int i = 1; i < potentialExits.Count; i++) {
|
|
var exit = potentialExits[i];
|
|
if (!(exit is Leave l2 && l2.IsLeavingFunction))
|
|
return exit;
|
|
}
|
|
}
|
|
return first;
|
|
}
|
|
|
|
protected internal override void VisitBlock(Block block)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
// Don't use foreach loop, because the children might add to the block
|
|
for (int i = 0; i < block.Instructions.Count; i++) {
|
|
block.Instructions[i].AcceptVisitor(this);
|
|
}
|
|
}
|
|
|
|
void HandleExit(ILInstruction inst)
|
|
{
|
|
if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst)) {
|
|
potentialExits.Add(inst);
|
|
} else if (CompatibleExitInstruction(inst, currentExit)) {
|
|
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
|
|
}
|
|
}
|
|
|
|
private bool CanIntroduceAsExit(ILInstruction inst)
|
|
{
|
|
if (currentContainer.LeaveCount > 0) {
|
|
// if we're re-running on a block container that already has an exit,
|
|
// we can't introduce any additional exits
|
|
return false;
|
|
}
|
|
if (inst is Leave l && l.IsLeavingFunction) {
|
|
return canIntroduceExitForReturn;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected internal override void VisitBranch(Branch inst)
|
|
{
|
|
if (!inst.TargetBlock.IsDescendantOf(currentContainer)) {
|
|
HandleExit(inst);
|
|
}
|
|
}
|
|
|
|
protected internal override void VisitLeave(Leave inst)
|
|
{
|
|
base.VisitLeave(inst);
|
|
if (!inst.Value.MatchNop())
|
|
return;
|
|
HandleExit(inst);
|
|
}
|
|
}
|
|
}
|