diff --git a/ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs
index ae5138358..7245316c4 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs
@@ -23,61 +23,73 @@ using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms
{
///
- /// This transform duplicates return blocks if they return a local variable that was assigned right before thie return.
+ /// This transform duplicates return blocks if they return a local variable that was assigned right before the return.
///
class InlineReturnTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
var instructionsToModify = new List<(BlockContainer, Block, Branch)>();
- var possibleReturnVars = new Queue<(ILVariable, Block)>();
- var tempList = new List<(BlockContainer, Block, Branch)>();
+ // Process all leave instructions in a leave-block, that is a block consisting solely of a leave instruction.
foreach (var leave in function.Descendants.OfType()) {
- if (!(leave.Parent is Block b && b.Instructions.Count == 1))
+ if (!(leave.Parent is Block leaveBlock && leaveBlock.Instructions.Count == 1))
continue;
+ // Skip, if the leave instruction has no value or the value is not a load of a local variable.
if (!leave.Value.MatchLdLoc(out var returnVar) || returnVar.Kind != VariableKind.Local)
continue;
- possibleReturnVars.Enqueue((returnVar, b));
+ // If all instructions can be modified, add item to the global list.
+ if (CanModifyInstructions(returnVar, leaveBlock, out var list));
+ instructionsToModify.AddRange(list);
}
- while (possibleReturnVars.Count > 0) {
- var (returnVar, leaveBlock) = possibleReturnVars.Dequeue();
- bool transform = true;
- foreach (StLoc store in returnVar.StoreInstructions.OfType()) {
- if (!(store.Parent is Block storeBlock)) {
- transform = false;
- break;
- }
- if (store.ChildIndex + 2 != storeBlock.Instructions.Count) {
- transform = false;
- break;
- }
- if (!(storeBlock.Instructions[store.ChildIndex + 1] is Branch br)) {
- transform = false;
- break;
- }
- if (br.TargetBlock != leaveBlock) {
- transform = false;
- break;
- }
- var targetBlockContainer = BlockContainer.FindClosestContainer(store);
- if (targetBlockContainer == null) {
- transform = false;
- break;
- }
- tempList.Add((targetBlockContainer, leaveBlock, br));
+ foreach (var (container, b, br) in instructionsToModify) {
+ Block block = b;
+ // if there is only one branch to this return block, move it to the matching container.
+ // otherwise duplicate the return block.
+ if (block.IncomingEdgeCount == 1) {
+ block.Remove();
+ } else {
+ block = (Block)block.Clone();
}
- if (transform)
- instructionsToModify.AddRange(tempList);
- tempList.Clear();
+ container.Blocks.Add(block);
+ // adjust the target of the branch to the newly created block.
+ br.TargetBlock = block;
}
+ }
- foreach (var (container, block, br) in instructionsToModify) {
- var newBlock = (Block)block.Clone();
- container.Blocks.Add(newBlock);
- br.TargetBlock = newBlock;
+ ///
+ /// Determines a list of all store instructions that write to a given .
+ /// Returns false if any of these instructions does not meet the following criteria:
+ /// - must be a stloc
+ /// - must be a direct child of a block
+ /// - must be the penultimate instruction
+ /// - must be followed by a branch instruction to
+ /// - must have a BlockContainer as ancestor.
+ /// Returns true, if all instructions meet these criteria, and contains a list of 3-tuples.
+ /// Each tuple consists of the target block container, the leave block, and the branch instruction that should be modified.
+ ///
+ static bool CanModifyInstructions(ILVariable returnVar, Block leaveBlock, out List<(BlockContainer, Block, Branch)> instructionsToModify)
+ {
+ instructionsToModify = new List<(BlockContainer, Block, Branch)>();
+ foreach (var inst in returnVar.StoreInstructions) {
+ if (!(inst is StLoc store))
+ return false;
+ if (!(store.Parent is Block storeBlock))
+ return false;
+ if (store.ChildIndex + 2 != storeBlock.Instructions.Count)
+ return false;
+ if (!(storeBlock.Instructions[store.ChildIndex + 1] is Branch br))
+ return false;
+ if (br.TargetBlock != leaveBlock)
+ return false;
+ var targetBlockContainer = BlockContainer.FindClosestContainer(store);
+ if (targetBlockContainer == null)
+ return false;
+ instructionsToModify.Add((targetBlockContainer, leaveBlock, br));
}
+
+ return true;
}
}
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
index f0bbd13ae..70f0903f0 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
@@ -25,6 +25,9 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
+ ///
+ /// Detects switch-on-string patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction.
+ ///
class SwitchOnStringTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
@@ -78,6 +81,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst = null;
blockAfterSwitch = null;
// match first block: checking switch-value for null or first value (Roslyn)
+ // if (call op_Equality(ldloc switchValueVar, ldstr value)) br firstBlock
+ // -or-
+ // if (comp(ldloc switchValueVar == ldnull)) br defaultBlock
if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump)))
return false;
if (!firstBlockJump.MatchBranch(out var firstBlock))
@@ -85,32 +91,43 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool isLegacy;
Block defaultBlock;
List<(string, Block)> values = new List<(string, Block)>();
+ // match null check: this is used by the old C# compiler.
if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(out var switchValueVar)) {
isLegacy = true;
defaultBlock = firstBlock;
+ // Roslyn: match call to operator ==(string, string)
} else if (MatchStringEqualityComparison(condition, out switchValueVar, out string value)) {
isLegacy = false;
defaultBlock = null;
values.Add((value, firstBlock));
} else return false;
+ // switchValueVar must be assigned only once.
if (!switchValueVar.IsSingleDefinition)
return false;
+ // if instruction must be followed by a branch to the next case
if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump))
return false;
+ // extract all cases and add them to the values list.
Block currentCaseBlock = nextCaseJump.TargetBlock;
Block nextCaseBlock;
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, ref switchValueVar, out string value, out Block block)) != null) {
values.Add((value, block));
currentCaseBlock = nextCaseBlock;
}
+ // the last instruction of the last case/default block must be either
+ // a branch to the a block after the switch statement or a leave instruction.
var container = BlockContainer.FindClosestContainer(firstBlock);
if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock) && !ExtractLastLeaveFromBlock(currentCaseBlock, container))
return false;
+ // We didn't find any cases, exit
if (values.Count == 0)
return false;
+ // All case blocks must either leave the current block container or branch to the same block after the switch statement.
if (!(values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && IsExitBlock(nextExit, container)) ||
(exitBlock == null && values.All(b => ExtractLastLeaveFromBlock(b.Item2, container)))))
return false;
+ // if the block after the switch has the correct number of branches, generate the switch statement
+ // and return it and the block.
if (currentCaseBlock.IncomingEdgeCount == (isLegacy ? 2 : 1)) {
var sections = new List(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) }));
var stringToInt = new StringToInt(new LdLoc(switchValueVar), values.SelectArray(item => item.Item1));
@@ -146,6 +163,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return b == container;
}
+ ///
+ /// Each case consists of two blocks:
+ /// 1. block:
+ /// if (call op_Equality(ldloc switchVariable, ldstr value)) br caseBlock
+ /// br nextBlock
+ /// This method matches the above pattern or its inverted form:
+ /// the call to ==(string, string) is wrapped in logic.not and the branch targets are reversed.
+ /// Returns the next block that follows in the block-chain.
+ /// The is updated if the value gets copied to a different variable.
+ /// See comments below for more info.
+ ///
Block MatchCaseBlock(Block currentBlock, ref ILVariable switchVariable, out string value, out Block caseBlock)
{
value = null;
@@ -167,10 +195,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!currentBlock.Instructions[1].MatchBranch(out nextBlock))
return null;
}
+ // Sometimes the switch pattern uses one variable at the beginning for null checks
+ // and another variable for the if-else-if-else-pattern.
+ // both variables must be only assigned once and be of the type: System.String.
if (!MatchStringEqualityComparison(condition, out var newSwitchVariable, out value))
return null;
if (!newSwitchVariable.IsSingleDefinition)
return null;
+ // if the used variable differs and both variables are not related, return null:
if (switchVariable != newSwitchVariable && !(IsInitializedBy(switchVariable, newSwitchVariable) || IsInitializedBy(newSwitchVariable, switchVariable)))
return null;
if (!newSwitchVariable.Type.IsKnownType(KnownTypeCode.String))
@@ -179,14 +211,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return nextBlock;
}
- bool IsInitializedBy(ILVariable switchVariable, ILVariable newSwitchVariable)
+ ///
+ /// Returns true if is only assigned once and the initialization is done by copying .
+ ///
+ bool IsInitializedBy(ILVariable left, ILVariable right)
{
- if (!switchVariable.IsSingleDefinition)
+ if (!left.IsSingleDefinition)
return false;
- var storeInst = switchVariable.StoreInstructions.OfType().SingleOrDefault();
+ var storeInst = left.StoreInstructions.OfType().SingleOrDefault();
if (storeInst == null)
return false;
- return storeInst.Value.MatchLdLoc(newSwitchVariable);
+ return storeInst.Value.MatchLdLoc(right);
}
bool MatchLegacySwitchOnString(InstructionCollection instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch)