Browse Source

Fix Switch(string)-detection in various cases

pull/887/head
Siegfried Pammer 8 years ago
parent
commit
4394250d71
  1. 82
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

82
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -29,23 +29,29 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -29,23 +29,29 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.SwitchStatementOnString)
return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
foreach (var block in function.Descendants.OfType<Block>()) {
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
SwitchInstruction newSwitch;
Block blockAfterSwitch = null;
if (!MatchCascadingIfStatements(block.Instructions, i, out newSwitch, out blockAfterSwitch) &&
!MatchLegacySwitchOnString(block.Instructions, i, out newSwitch, out blockAfterSwitch) &&
!MatchRoslynSwitchOnString(block.Instructions, i, out newSwitch))
continue;
bool removeExtraStore = false; // the Roslyn switch pattern uses an extra store for hash calculation.
if (!MatchCascadingIfStatements(block.Instructions, i, out newSwitch, out blockAfterSwitch))
if (!MatchLegacySwitchOnString(block.Instructions, i, out newSwitch, out blockAfterSwitch))
if (MatchRoslynSwitchOnString(block.Instructions, i, out newSwitch))
removeExtraStore = true;
else
continue;
if (i + 1 < block.Instructions.Count && block.Instructions[i + 1] is Branch b && blockAfterSwitch != null) {
block.Instructions[i + 1].ReplaceWith(new Branch(blockAfterSwitch));
}
block.Instructions[i].ReplaceWith(newSwitch);
if (newSwitch.Value.MatchLdLoc(out var switchVar) && !block.Instructions[i - 1].MatchLdLoc(switchVar)) {
if (removeExtraStore) {
block.Instructions.RemoveAt(i - 1);
i--;
}
@ -68,37 +74,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -68,37 +74,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
inst = null;
blockAfterSwitch = null;
if (i < 1) return false;
// match first block: checking switch-value for null or first value (Roslyn)
if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump) &&
instructions[i - 1].MatchStLoc(out var switchValueVar, out var switchValue) && switchValueVar.Type.IsKnownType(KnownTypeCode.String)))
if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump)))
return false;
if (!firstBlockJump.MatchBranch(out var firstBlock))
return false;
bool isLegacy;
Block defaultBlock;
List<(string, Block)> values = new List<(string, Block)>();
if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(switchValueVar)) {
if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(out var switchValueVar)) {
isLegacy = true;
defaultBlock = firstBlock;
} else if (MatchStringEqualityComparison(condition, switchValueVar, out string value)) {
} else if (MatchStringEqualityComparison(condition, out switchValueVar, out string value)) {
isLegacy = false;
defaultBlock = null;
values.Add((value, firstBlock));
} else return false;
if (!switchValueVar.IsSingleDefinition)
return false;
if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump))
return false;
Block currentCaseBlock = nextCaseJump.TargetBlock;
Block nextCaseBlock;
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) {
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, ref switchValueVar, out string value, out Block block)) != null) {
values.Add((value, block));
currentCaseBlock = nextCaseBlock;
}
if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock))
BlockContainer container = null;
if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock) && !ExtractLastLeaveFromBlock(currentCaseBlock, out container))
return false;
if (values.Count == 0)
return false;
if (!values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && nextExit == exitBlock))
if (!(values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && nextExit == exitBlock) ||
(exitBlock == null && values.All(b => ExtractLastLeaveFromBlock(b.Item2, out var exitContainer) && exitContainer == container))))
return false;
if (currentCaseBlock.IncomingEdgeCount == (isLegacy ? 2 : 1)) {
var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) }));
@ -120,23 +128,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -120,23 +128,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
Block MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out Block caseBlock)
bool ExtractLastLeaveFromBlock(Block block, out BlockContainer exitBlock)
{
exitBlock = null;
var lastInst = block.Instructions.LastOrDefault();
if (lastInst == null || !lastInst.MatchLeave(out exitBlock, out _))
return false;
return true;
}
Block MatchCaseBlock(Block currentBlock, ref ILVariable switchVariable, out string value, out Block caseBlock)
{
value = null;
caseBlock = null;
if (currentBlock.IncomingEdgeCount != 1 || currentBlock.Instructions.Count != 2)
return null;
if (!currentBlock.Instructions[0].MatchIfInstruction(out var condition, out var caseBlockBranch))
return null;
if (!caseBlockBranch.MatchBranch(out caseBlock))
return null;
if (!MatchStringEqualityComparison(condition, switchVariable, out value))
Block nextBlock;
if (condition.MatchLogicNot(out var inner)) {
condition = inner;
nextBlock = caseBlock;
if (!currentBlock.Instructions[1].MatchBranch(out caseBlock))
return null;
} else {
if (!currentBlock.Instructions[1].MatchBranch(out nextBlock))
return null;
}
if (!MatchStringEqualityComparison(condition, out var newSwitchVariable, out value))
return null;
if (!newSwitchVariable.IsSingleDefinition)
return null;
if (!currentBlock.Instructions[1].MatchBranch(out var nextBlock))
if (switchVariable != newSwitchVariable && !(IsInitializedBy(switchVariable, newSwitchVariable) || IsInitializedBy(newSwitchVariable, switchVariable)))
return null;
if (!newSwitchVariable.Type.IsKnownType(KnownTypeCode.String))
return null;
switchVariable = newSwitchVariable;
return nextBlock;
}
bool IsInitializedBy(ILVariable switchVariable, ILVariable newSwitchVariable)
{
if (!switchVariable.IsSingleDefinition)
return false;
return switchVariable.StoreInstructions.OfType<StLoc>().Single().Value.MatchLdLoc(newSwitchVariable);
}
bool MatchLegacySwitchOnString(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch)
{
inst = null;
@ -148,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -148,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!exitBlockJump.MatchBranch(out var exitBlock))
return false;
if (!(condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.Match(switchValue).Success))
if (!(condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && (left.Match(switchValue).Success || left.MatchLdLoc(switchValueVar))))
return false;
var nextBlockJump = instructions.ElementAtOrDefault(i + 1) as Branch;
if (nextBlockJump == null || nextBlockJump.TargetBlock.IncomingEdgeCount != 1)
@ -291,8 +331,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -291,8 +331,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue)
{
return MatchStringEqualityComparison(condition, out var v, out stringValue) && v == variable;
}
bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue)
{
stringValue = null;
variable = null;
ILInstruction left, right;
if (condition is Call c && c.Method.IsOperator && c.Method.Name == "op_Equality" && c.Arguments.Count == 2) {
left = c.Arguments[0];
@ -301,7 +347,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -301,7 +347,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
} else if (condition.MatchCompEquals(out left, out right) && right.MatchLdNull()) {
} else return false;
return left.MatchLdLoc(variable);
return left.MatchLdLoc(out variable);
}
}
}

Loading…
Cancel
Save