diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 29015d86ae..3e05ecf6eb 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -253,6 +253,8 @@
+
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.Expected.cs
new file mode 100644
index 0000000000..efd76dceed
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.Expected.cs
@@ -0,0 +1,33 @@
+namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
+{
+ internal class NoUnsignedRightShift
+ {
+ public struct CustomStruct
+ {
+ public short ShortField;
+ }
+
+ public class CustomClass
+ {
+ public short ShortField;
+ }
+
+ public static void ClassField(CustomClass c)
+ {
+ c.ShortField = (short)((uint)c.ShortField >> 5);
+ }
+
+ public static void StructField(CustomStruct s)
+ {
+ ref CustomStruct reference = ref s;
+ reference.ShortField = (short)((uint)reference.ShortField >> 5);
+ }
+
+ public static void ArrayElement(short[] a)
+ {
+ short[] array = a;
+ int num = 0;
+ array[num] = (short)((uint)array[num] >> 5);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.cs
new file mode 100644
index 0000000000..b01d6a1d40
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoUnsignedRightShift.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2026 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.
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
+{
+ // The unsigned right shift operator requires C# 11, so at a lower language version the
+ // compound assignments below have to be expanded into a plain assignment. That turns the
+ // stored value into a non-pure expression, which the target of the store must not be
+ // copy-propagated across.
+ internal class NoUnsignedRightShift
+ {
+ public struct CustomStruct
+ {
+ public short ShortField;
+ }
+
+ public class CustomClass
+ {
+ public short ShortField;
+ }
+
+ public static void ClassField(CustomClass c)
+ {
+ c.ShortField >>>= 5;
+ }
+
+ public static void StructField(CustomStruct s)
+ {
+ s.ShortField >>>= 5;
+ }
+
+ public static void ArrayElement(short[] a)
+ {
+ a[0] >>>= 5;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs
index 6256ec52ac..4dde3afa02 100644
--- a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs
@@ -144,6 +144,12 @@ public async Task AggressiveScalarReplacementOfAggregates([ValueSource(nameof(ro
});
}
+ [Test]
+ public async Task NoUnsignedRightShift([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
+ {
+ await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6));
+ }
+
[Test]
public async Task NoNewOfT([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
index f55bee55a5..18284867e3 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
@@ -168,6 +168,22 @@ static void DoPropagate(ILVariable v, ILInstruction copiedExpr, Block block, ref
}
// We are copying an expression from far away, reusing the ILRange would result in incorrect sequence points.
clone.SetILRange(new Interval());
+ if (!context.Settings.UseRefLocalsForAccurateOrderOfEvaluation
+ && expr.SlotInfo == StObj.TargetSlot && clone.HasDirectFlag(InstructionFlags.MayThrow)
+ && !expr.Parent.SatisfiesSlotRestrictionForInlining(expr.ChildIndex, clone))
+ {
+ // A LdFlda/LdElema used as StObj target has to delay its exception, because C#
+ // computes the value to be stored before dereferencing the target. Accept the
+ // changed point at which the exception is thrown in order to avoid introducing a
+ // ref local. This mirrors InliningOptions.AllowChangingOrderOfEvaluationForExceptions
+ // in ILInlining, and is bound to the same setting: callers of the public Propagate()
+ // reach this code path without passing through CanPerformCopyPropagation, so the
+ // setting has to be honored here rather than assumed.
+ if (clone is LdFlda ldflda)
+ ldflda.DelayExceptions = true;
+ else if (clone is LdElema ldelema)
+ ldelema.DelayExceptions = true;
+ }
expr.ReplaceWith(clone);
}
block.Instructions.RemoveAt(i);