From fcbfada2e0eb9e57c1a78bfd06bbc8462ffa0e46 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 11 Oct 2017 11:31:06 +0200 Subject: [PATCH] Add legacy switch-on-nullable transform --- .../CSharp/CSharpDecompiler.cs | 1 + .../ICSharpCode.Decompiler.csproj | 1 + .../Transforms/SwitchOnNullableTransform.cs | 117 ++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 647dcdaf5..355464e64 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -83,6 +83,7 @@ namespace ICSharpCode.Decompiler.CSharp new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements new SwitchDetection(), new SwitchOnStringTransform(), + new SwitchOnNullableTransform(), new BlockILTransform { // per-block transforms PostOrderTransforms = { // Even though it's a post-order block-transform as most other transforms, diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 534af2cbe..7fee5ff0d 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -277,6 +277,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs new file mode 100644 index 000000000..30005e06b --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs @@ -0,0 +1,117 @@ +// Copyright (c) 2017 Siegfried Pammer +// +// 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.Collections.Generic; +using System.Linq; +using ICSharpCode.Decompiler.IL.ControlFlow; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Detects switch-on-nullable patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction. + /// + class SwitchOnNullableTransform : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + if (!context.Settings.LiftNullables) + return; + + HashSet changedContainers = new HashSet(); + + foreach (var block in function.Descendants.OfType()) { + bool changed = false; + for (int i = block.Instructions.Count - 1; i >= 0; i--) { + SwitchInstruction newSwitch; + if (MatchSwitchOnNullable(block.Instructions, i, out newSwitch)) { + block.Instructions[i + 1].ReplaceWith(newSwitch); + block.Instructions.RemoveRange(i - 2, 3); + i -= 2; + changed = true; + continue; + } + } + if (!changed) continue; + SwitchDetection.SimplifySwitchInstruction(block); + if (block.Parent is BlockContainer container) + changedContainers.Add(container); + } + + foreach (var container in changedContainers) + container.SortBlocks(deleteUnreachableBlocks: true); + } + + bool MatchSwitchOnNullable(InstructionCollection instructions, int i, out SwitchInstruction newSwitch) + { + newSwitch = null; + // match first block: + // stloc tmp(ldloca switchValueVar) + // stloc switchVariable(call GetValueOrDefault(ldloc tmp)) + // if (logic.not(call get_HasValue(ldloc tmp))) br nullCaseBlock + // br switchBlock + if (i < 2) return false; + if (!instructions[i - 2].MatchStLoc(out var tmp, out var ldloca) || + !instructions[i - 1].MatchStLoc(out var switchVariable, out var getValueOrDefault) || + !instructions[i].MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock)) + return false; + if (!ldloca.MatchLdLoca(out var switchValueVar)) + return false; + if (!condition.MatchLogicNot(out var getHasValue)) + return false; + if (!(getValueOrDefault is Call getValueOrDefaultCall) || getValueOrDefaultCall.Method.FullName != "System.Nullable.GetValueOrDefault" || + getValueOrDefaultCall.Method.DeclaringType.TypeParameterCount != 1) + return false; + if (!(getHasValue is Call getHasValueCall) || !getHasValueCall.Method.IsAccessor || getHasValueCall.Method.FullName != "System.Nullable.get_HasValue" || + getHasValueCall.Method.DeclaringType.TypeParameterCount != 1) + return false; + if (getHasValueCall.Arguments.Count != 1 || getValueOrDefaultCall.Arguments.Count != 1) + return false; + if (!getHasValueCall.Arguments[0].MatchLdLoc(tmp) || !getValueOrDefaultCall.Arguments[0].MatchLdLoc(tmp)) + return false; + // match second block: switchBlock + // switch (ldloc swtichVariable) { + // case [0..1): br caseBlock1 + // ... more cases ... + // case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock + // } + if (switchBlock.Instructions.Count != 1 || switchBlock.IncomingEdgeCount != 1) + return false; + if (!(switchBlock.Instructions[0] is SwitchInstruction switchInst)) + return false; + newSwitch = new SwitchInstruction(new LdLoc(switchValueVar)); + newSwitch.IsLifted = true; + SwitchSection defaultSection = null; + foreach (var section in switchInst.Sections) { + if (defaultSection == null || section.Labels.Count() >= defaultSection.Labels.Count()) + defaultSection = section; + newSwitch.Sections.Add(section); + } + if (defaultSection.Body.MatchBranch(out var defaultBlock) && defaultBlock == nullCaseBlock) + defaultSection.HasNullLabel = true; + else { + newSwitch.Sections.Add(new SwitchSection { Body = new Branch(nullCaseBlock), HasNullLabel = true }); + } + return true; + } + } +}