diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs index 26b33c0f6..0510b8c87 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs @@ -321,6 +321,27 @@ namespace ICSharpCode.Decompiler.IL } } + /// + /// Matches 'comp(arg == ldnull)' + /// + public bool MatchCompEqualsNull(out ILInstruction arg) + { + if (!MatchCompEquals(out var left, out var right)) { + arg = null; + return false; + } + if (right.MatchLdNull()) { + arg = left; + return true; + } else if (left.MatchLdNull()) { + arg = right; + return true; + } else { + arg = null; + return false; + } + } + /// /// Matches comp(left != right) or logic.not(comp(left == right)). /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index ddc9ab01d..c37b8fe15 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -80,44 +80,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; List<(string, Block)> values = new List<(string, Block)>(); ILInstruction switchValue = null; - bool extraLoad = false; - bool keepAssignmentBefore = false; - // Roslyn: match call to operator ==(string, string) - if (MatchStringEqualityComparison(condition, out var switchValueVar, out string value)) { - values.Add((value, firstBlock)); - if(!instructions[i - 1].MatchStLoc(switchValueVar, out switchValue)) { - switchValue = new LdLoc(switchValueVar); - } - } else { - // match null check with different variable: - // this is used by the old C# compiler. - if (MatchCompEqualsNull(condition, out var otherSwitchValueVar)) { - values.Add((null, firstBlock)); - } else { - return false; - } + // match call to operator ==(string, string) + if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue)) + return false; + values.Add((firstBlockValue, firstBlock)); + bool extraLoad = false; + if (instructions[i - 1].MatchStLoc(switchValueVar, out switchValue)) { + // stloc switchValueVar(switchValue) + // if (call op_Equality(ldloc switchValueVar, ldstr value)) br firstBlock + } else if (instructions[i - 1] is StLoc stloc && stloc.Value.MatchLdLoc(switchValueVar)) { // in case of optimized legacy code there are two stlocs: // stloc otherSwitchValueVar(ldloc switchValue) // stloc switchValueVar(ldloc otherSwitchValueVar) - // if (comp(ldloc otherSwitchValueVar == ldnull)) br nullCase - if (i > 1 && otherSwitchValueVar != null && instructions[i - 2].MatchStLoc(otherSwitchValueVar, out switchValue) - && instructions[i - 1].MatchStLoc(out switchValueVar, out var switchValueCopyInst) - && switchValueCopyInst.MatchLdLoc(otherSwitchValueVar) && otherSwitchValueVar.IsSingleDefinition && otherSwitchValueVar.LoadCount == 2) { + // if (call op_Equality(ldloc otherSwitchValueVar, ldstr value)) br firstBlock + var otherSwitchValueVar = switchValueVar; + switchValueVar = stloc.Variable; + if (i >= 2 && instructions[i - 2].MatchStLoc(otherSwitchValueVar, out switchValue) + && otherSwitchValueVar.IsSingleDefinition && otherSwitchValueVar.LoadCount == 2) + { extraLoad = true; - } else if (instructions[i - 1].MatchStLoc(out switchValueVar, out switchValue)) { - // unoptimized legacy switch + } else { + switchValue = new LdLoc(otherSwitchValueVar); } + } else { + switchValue = new LdLoc(switchValueVar); } - // 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, switchValueVar, out value, out Block block)) != null) { + while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) { values.Add((value, block)); currentCaseBlock = nextCaseBlock; } @@ -125,6 +121,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (values.Count < 3) return false; // if the switchValueVar is used in other places as well, do not eliminate the store. + bool keepAssignmentBefore = false; if (switchValueVar.LoadCount > values.Count) { keepAssignmentBefore = true; switchValue = new LdLoc(switchValueVar); @@ -187,31 +184,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!currentBlock.Instructions[1].MatchBranch(out nextBlock)) return null; } - if (!MatchStringEqualityComparison(condition, out var v, out value)) { - if (MatchCompEqualsNull(condition, out v)) { - value = null; - } else { + if (!MatchStringEqualityComparison(condition, switchVariable, out value)) { return null; } - } - if (v != switchVariable) - return null; return nextBlock; } - - /// - /// Returns true if is only assigned once and the initialization is done by copying . - /// - bool IsInitializedBy(ILVariable left, ILVariable right) - { - if (!left.IsSingleDefinition) - return false; - var storeInst = left.StoreInstructions.OfType().SingleOrDefault(); - if (storeInst == null) - return false; - return storeInst.Value.MatchLdLoc(right); - } - + /// /// Matches the C# 2.0 switch-on-string pattern, which uses Dictionary<string, int>. /// @@ -475,8 +453,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!bodyBranch.MatchBranch(out Block body)) return false; - if (!MatchStringEqualityOrNullComparison(condition, switchValue.Variable, out string stringValue)) { - if (condition.MatchLogicNot(out condition) && MatchStringEqualityOrNullComparison(condition, switchValue.Variable, out stringValue)) { + if (!MatchStringEqualityComparison(condition, switchValue.Variable, out string stringValue)) { + if (condition.MatchLogicNot(out condition) && MatchStringEqualityComparison(condition, switchValue.Variable, out stringValue)) { if (!target.Instructions[1].MatchBranch(out Block exit)) return false; body = exit; @@ -523,39 +501,36 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool MatchStringEqualityOrNullComparison(ILInstruction condition, ILVariable variable, out string stringValue) + /// + /// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' + /// or 'comp(ldloc(variable) == ldnull)' + /// + bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue) { - if (!MatchStringEqualityComparison(condition, out var v, out stringValue)) { - if (!MatchCompEqualsNull(condition, out v)) - return false; - stringValue = null; + return MatchStringEqualityComparison(condition, out var v, out stringValue) && v == variable; } - return v == variable; - } - + /// + /// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' + /// or 'comp(ldloc(variable) == ldnull)' + /// 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) { + if (condition is Call c && c.Method.IsOperator && c.Method.Name == "op_Equality" + && c.Method.DeclaringType.IsKnownType(KnownTypeCode.String) && c.Arguments.Count == 2) + { left = c.Arguments[0]; right = c.Arguments[1]; - if (!right.MatchLdStr(out stringValue)) - return false; + return left.MatchLdLoc(out variable) && right.MatchLdStr(out stringValue); + } else if (condition.MatchCompEqualsNull(out var arg)) { + stringValue = null; + return arg.MatchLdLoc(out variable); } else { return false; } - return left.MatchLdLoc(out variable); } - - bool MatchCompEqualsNull(ILInstruction condition, out ILVariable variable) - { - variable = null; - if (!condition.MatchCompEquals(out var left, out var right)) - return false; - return right.MatchLdNull() && left.MatchLdLoc(out variable); } } -}