diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
index 6ffefade4c..1d6c29e16e 100644
--- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
@@ -339,6 +339,12 @@ public async Task ExtensionEncodingV2()
await Run();
}
+ [Test]
+ public async Task SortSwitchSections()
+ {
+ await Run(settings: new DecompilerSettings { SortSwitchSections = true, FileScopedNamespaces = false });
+ }
+
async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null,
AssemblerOptions assemblerOptions = AssemblerOptions.Library)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.cs
new file mode 100644
index 0000000000..2b0c1b0b72
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.cs
@@ -0,0 +1,35 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
+{
+ public static class SortSwitchSections
+ {
+ public static int PickValue(int n)
+ {
+ return n switch {
+ 0 => 100,
+ 1 => 200,
+ 2 => 300,
+ 3 => 400,
+ 4 => 500,
+ _ => -1,
+ };
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.il
new file mode 100644
index 0000000000..a76bf07598
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/SortSwitchSections.il
@@ -0,0 +1,64 @@
+// Test fixture for the SortSwitchSections decompiler setting.
+//
+// The IL `switch` table targets are placed at non-monotonic offsets,
+// simulating obfuscator block-reordering. With SortSwitchSections=false
+// (default) the decompiler would emit cases in IL-offset order (3, 0, 4, 1, 2);
+// with the setting enabled they are emitted in label-value order (0..4).
+// The matching .cs file is the expected output with the setting enabled.
+
+.assembly extern mscorlib
+{
+ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
+ .ver 4:0:0:0
+}
+.assembly SortSwitchSections
+{
+ .ver 1:0:0:0
+}
+.module SortSwitchSections.dll
+.imagebase 0x10000000
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003 // WINDOWS_CUI
+.corflags 0x00000001 // ILONLY
+
+.class public auto ansi abstract sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.SortSwitchSections
+ extends [mscorlib]System.Object
+{
+ .method public hidebysig static int32 PickValue(int32 n) cil managed
+ {
+ .maxstack 1
+ IL_0000: ldarg.0
+ IL_0001: switch (
+ LBL_0,
+ LBL_1,
+ LBL_2,
+ LBL_3,
+ LBL_4)
+ IL_001a: br.s LBL_DEFAULT
+
+ LBL_3:
+ IL_001c: ldc.i4 400
+ IL_0021: ret
+
+ LBL_0:
+ IL_0022: ldc.i4 100
+ IL_0027: ret
+
+ LBL_4:
+ IL_0028: ldc.i4 500
+ IL_002d: ret
+
+ LBL_1:
+ IL_002e: ldc.i4 200
+ IL_0033: ret
+
+ LBL_2:
+ IL_0034: ldc.i4 300
+ IL_0039: ret
+
+ LBL_DEFAULT:
+ IL_003a: ldc.i4.m1
+ IL_003b: ret
+ }
+}
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index e9aeefb733..c9da954d68 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -2375,6 +2375,26 @@ public bool SortCustomAttributes {
}
}
+ bool sortSwitchSections = false;
+
+ ///
+ /// Sort switch sections by their label value instead of by IL offset.
+ /// Useful when diffing decompiler output across rebuilds of obfuscated assemblies,
+ /// where IL block layout is unstable but the case-to-value mapping is not.
+ ///
+ [Category("DecompilerSettings.Other")]
+ [Description("DecompilerSettings.SortSwitchSections")]
+ public bool SortSwitchSections {
+ get { return sortSwitchSections; }
+ set {
+ if (sortSwitchSections != value)
+ {
+ sortSwitchSections = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool checkForOverflowUnderflow = false;
///
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
index 29d16e1ccf..edc2d43b93 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
@@ -208,7 +208,7 @@ void ProcessBlock(Block block, ref bool blockContainerNeedsCleanup)
controlFlowGraph = null; // control flow graph is no-longer valid
blockContainerNeedsCleanup = true;
- SortSwitchSections(sw);
+ SortSwitchSections(sw, context);
}
else
{
@@ -249,16 +249,23 @@ internal static void SimplifySwitchInstruction(Block block, ILTransformContext c
return false;
});
AdjustLabels(sw, context);
- SortSwitchSections(sw);
+ SortSwitchSections(sw, context);
}
- static void SortSwitchSections(SwitchInstruction sw)
+ static void SortSwitchSections(SwitchInstruction sw, ILTransformContext context)
{
- sw.Sections.ReplaceList(sw.Sections.OrderBy(s => s.Body switch {
- Branch b => b.TargetILOffset,
- Leave l => l.StartILOffset,
- _ => (int?)null
- }).ThenBy(s => s.Labels.Values.FirstOrDefault()));
+ if (context.Settings.SortSwitchSections)
+ {
+ sw.Sections.ReplaceList(sw.Sections.OrderBy(s => s.Labels.Values.FirstOrDefault()));
+ }
+ else
+ {
+ sw.Sections.ReplaceList(sw.Sections.OrderBy(s => s.Body switch {
+ Branch b => b.TargetILOffset,
+ Leave l => l.StartILOffset,
+ _ => (int?)null
+ }).ThenBy(s => s.Labels.Values.FirstOrDefault()));
+ }
}
static void AdjustLabels(SwitchInstruction sw, ILTransformContext context)
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index ba7df50e34..a3f3f8f0f3 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -1405,7 +1405,16 @@ public static string DecompilerSettings_SortCustomAttributes {
return ResourceManager.GetString("DecompilerSettings.SortCustomAttributes", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to Sort switch sections by case label value.
+ ///
+ public static string DecompilerSettings_SortSwitchSections {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.SortSwitchSections", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Detect switch on integer even if IL code does not use a jump table.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index 5accafe41c..3b94c69b85 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -489,6 +489,9 @@ Are you sure you want to continue?
Sort custom attributes
+
+ Sort switch sections by case label value
+
Detect switch on integer even if IL code does not use a jump table