diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs index dafe2f5483..f0e829739b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/SwitchExpressions.cs @@ -159,5 +159,59 @@ public static string Issue2222() _ => "default", }; } +#pragma warning disable CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). + public static int Issue3382(StringComparison c) + { + return c switch { + StringComparison.Ordinal => 0, + StringComparison.OrdinalIgnoreCase => 1, + }; + } + + public static void Issue3382b(ref StringComparison? c) + { + StringComparison? stringComparison = c; + c = stringComparison switch { + null => StringComparison.Ordinal, + StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase, + StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture, + }; + } + + public static void Issue3382c(StringComparison? c) + { + c = c switch { + null => StringComparison.Ordinal, + StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase, + StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture, + }; + } + + public static void Issue3382d(ref StringComparison c) + { + StringComparison stringComparison = c; + c = stringComparison switch { + StringComparison.Ordinal => StringComparison.OrdinalIgnoreCase, + StringComparison.OrdinalIgnoreCase => StringComparison.InvariantCulture, + }; + } + + public static int SwitchOnStringImplicitDefault(string s) + { + return s switch { + "Hello" => 42, + "World" => 4711, + "!" => 7, + "Foo" => 13, + "Bar" => 21, + "Baz" => 84, + "Qux" => 168, + "Quux" => 336, + "Corge" => 672, + "Grault" => 1344, + "Garply" => 2688, + }; + } +#pragma warning restore CS8509 // The switch expression does not handle all possible values of its input type (it is not exhaustive). } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 1039aa45de..fd8cfa7f21 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4135,10 +4135,13 @@ protected internal override TranslatedExpression VisitSwitchInstruction(SwitchIn switchExpr.SwitchSections.Add(ses); } - var defaultSES = new SwitchExpressionSection(); - defaultSES.Pattern = new IdentifierExpression("_"); - defaultSES.Body = TranslateSectionBody(defaultSection); - switchExpr.SwitchSections.Add(defaultSES); + if (!defaultSection.IsCompilerGeneratedDefaultSection) + { + var defaultSES = new SwitchExpressionSection(); + defaultSES.Pattern = new IdentifierExpression("_"); + defaultSES.Body = TranslateSectionBody(defaultSection); + switchExpr.SwitchSections.Add(defaultSES); + } return switchExpr.WithILInstruction(inst).WithRR(new ResolveResult(resultType)); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs index 29d16e1ccf..4212f812fe 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -16,8 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; @@ -216,12 +218,118 @@ void ProcessBlock(Block block, ref bool blockContainerNeedsCleanup) // (1st pass was in ControlFlowSimplification) SimplifySwitchInstruction(block, context); } + + InlineSwitchExpressionDefaultCaseThrowHelper(block, context); + } + + // call ThrowInvalidOperationException(...) + // leave IL_0000 (ldloc temp) + // + // -or- + // + // call ThrowSwitchExpressionException(...) + // leave IL_0000 (ldloc temp) + // + // -to- + // + // throw(newobj SwitchExpressionException(...)) + internal static void InlineSwitchExpressionDefaultCaseThrowHelper(Block block, ILTransformContext context) + { +#nullable enable + // due to our of of basic blocks at this point, + // switch instructions can only appear as last instruction + var sw = block.Instructions.LastOrDefault() as SwitchInstruction; + if (sw == null) + return; + + IMethod[] exceptionCtorTable = new IMethod[2]; + + exceptionCtorTable[0] = FindConstructor("System.InvalidOperationException")!; + exceptionCtorTable[1] = FindConstructor("System.Runtime.CompilerServices.SwitchExpressionException", typeof(object))!; + + if (exceptionCtorTable[0] == null && exceptionCtorTable[1] == null) + return; + + if (sw.GetDefaultSection() is not { Body: Branch { TargetBlock: Block defaultBlock } } defaultSection) + return; + if (defaultBlock is { Instructions: [var call, Branch or Leave] }) + { + if (!MatchThrowHelperCall(call, out IMethod? exceptionCtor, out ILInstruction? value)) + return; + context.Step("SwitchExpressionDefaultCaseTransform", block.Instructions[0]); + var newObj = new NewObj(exceptionCtor); + if (value != null) + newObj.Arguments.Add(value); + defaultBlock.Instructions[0] = new Throw(newObj).WithILRange(defaultBlock.Instructions[0]).WithILRange(defaultBlock.Instructions[1]); + defaultBlock.Instructions.RemoveAt(1); + defaultSection.IsCompilerGeneratedDefaultSection = true; + } + else if (defaultBlock is { Instructions: [Throw { Argument: NewObj { Method: var ctor, Arguments: [var arg] } newObj }] } + && ctor.Equals(exceptionCtorTable[1])) + { + defaultSection.IsCompilerGeneratedDefaultSection = true; + } + else + { + return; + } + + bool MatchThrowHelperCall(ILInstruction inst, [NotNullWhen(true)] out IMethod? exceptionCtor, out ILInstruction? value) + { + exceptionCtor = null; + value = null; + if (inst is not Call call) + return false; + if (call.Method.DeclaringType.FullName != "") + return false; + switch (call.Arguments.Count) + { + case 0: + if (call.Method.Name != "ThrowInvalidOperationException") + return false; + exceptionCtor = exceptionCtorTable[0]; + break; + case 1: + if (call.Method.Name != "ThrowSwitchExpressionException") + return false; + exceptionCtor = exceptionCtorTable[1]; + value = call.Arguments[0]; + break; + default: + return false; + } + return exceptionCtor != null; + } + + IMethod? FindConstructor(string fullTypeName, params Type[] argumentTypes) + { + IType exceptionType = context.TypeSystem.FindType(new FullTypeName(fullTypeName)); + var types = argumentTypes.SelectArray(context.TypeSystem.FindType); + + foreach (var ctor in exceptionType.GetConstructors(m => !m.IsStatic && m.Parameters.Count == argumentTypes.Length)) + { + bool found = true; + foreach (var pair in ctor.Parameters.Select(p => p.Type).Zip(types)) + { + if (!NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(pair.Item1, pair.Item2)) + { + found = false; + break; + } + } + if (found) + return ctor; + } + + return null; + } +#nullable restore } internal static void SimplifySwitchInstruction(Block block, ILTransformContext context) { // due to our of of basic blocks at this point, - // switch instructions can only appear as last insturction + // switch instructions can only appear as last instruction var sw = block.Instructions.LastOrDefault() as SwitchInstruction; if (sw == null) return; @@ -445,7 +553,9 @@ private void AddNullCase(List flowNodes, List !nullableBlock.Instructions.SecondToLastOrDefault().MatchIfInstruction(out var cond, out var trueInst) || !cond.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction nullableInst)) + { return; + } // could check that nullableInst is ldloc or ldloca and that the switch variable matches a GetValueOrDefault // but the effect of adding an incorrect block to the flowBlock list would only be disasterous if it branched directly diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs index ad0d0a9a4b..8800626202 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -202,6 +202,12 @@ public SwitchSection() /// public bool HasNullLabel { get; set; } + /// + /// If true, this section only contains a compiler-generated throw helper + /// used in a switch expression and will not be visible in the decompiled source code. + /// + public bool IsCompilerGeneratedDefaultSection { get; set; } + /// /// The set of labels that cause execution to jump to this switch section. /// @@ -221,6 +227,8 @@ public override InstructionFlags DirectFlags { public override void WriteTo(ITextOutput output, ILAstWritingOptions options) { WriteILRange(output, options); + if (IsCompilerGeneratedDefaultSection) + output.Write("generated."); output.WriteLocalReference("case", this, isDefinition: true); output.Write(' '); if (HasNullLabel) diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 1d4bb18cb3..4f2f5e82be 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -1169,7 +1169,11 @@ SwitchInstruction ReplaceWithSwitchInstruction(int offset) } var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values, switchValueLoad.Variable.Type)); newSwitch.Sections.AddRange(sections); - newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body }); + newSwitch.Sections.Add(new SwitchSection { + Labels = defaultLabel, + Body = defaultSection.Body, + IsCompilerGeneratedDefaultSection = defaultSection.IsCompilerGeneratedDefaultSection + }); instructions[offset].ReplaceWith(newSwitch); return newSwitch; } @@ -1310,6 +1314,7 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i) } instructions[i] = newSwitch; instructions.RemoveRange(i + 1, instructions.Count - (i + 1)); + SwitchDetection.InlineSwitchExpressionDefaultCaseThrowHelper(block, context); return true; bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index)