diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index c13352fec..c43c7db0d 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -101,6 +101,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 4bfbfb812..1f02fcd73 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -225,6 +225,12 @@ namespace ICSharpCode.Decompiler.Tests await Run(); } + [Test] + public async Task Issue3465() + { + await Run(); + } + [Test] public async Task Issue3466() { @@ -325,7 +331,7 @@ namespace ICSharpCode.Decompiler.Tests var executable = await Tester.AssembleIL(ilFile, assemblerOptions).ConfigureAwait(false); var decompiled = await Tester.DecompileCSharp(executable, settings).ConfigureAwait(false); - CodeAssert.FilesAreEqual(csFile, decompiled); + CodeAssert.FilesAreEqual(csFile, decompiled, ["EXPECTED_OUTPUT"]); Tester.RepeatOnIOError(() => File.Delete(decompiled)); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.cs new file mode 100644 index 000000000..8f0aa9432 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.cs @@ -0,0 +1,22 @@ +using System; +#if EXPECTED_OUTPUT +using System.Runtime.CompilerServices; +#endif +namespace Issue3465 +{ + internal class Program + { + private static Program programNull; + + private static Program GetProgram() + { + return null; + } + + private static bool Test3465() + { + Program program = GetProgram(); + return System.Runtime.CompilerServices.Unsafe.As(ref program) > System.Runtime.CompilerServices.Unsafe.As(ref programNull); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.il new file mode 100644 index 000000000..7aa0b7f6d --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.il @@ -0,0 +1,60 @@ +.class private auto ansi '' +{ +} // end of class + +.class private auto ansi beforefieldinit Issue3465.Program + extends [System.Runtime]System.Object +{ + // Fields + .field private static class Issue3465.Program programNull + + // Methods + .method private hidebysig static + class Issue3465.Program GetProgram () cil managed + { + // Method begins at RVA 0x2050 + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: ret + } // end of method Issue3465.Program::GetProgram + + .method private hidebysig static + bool Test3465 () cil managed + { + // Method begins at RVA 0x2054 + // Header size: 12 + // Code size: 7 (0x7) + .maxstack 1 + .locals init ( + [0] bool + ) + + IL_0000: call class Issue3465.Program Issue3465.Program::GetProgram() + IL_0001: ldsfld class Issue3465.Program Issue3465.Program::programNull + cgt.un + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + + IL_0005: ldloc.0 + IL_0006: ret + } // end of method Program::Test3465 + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2067 + // Header size: 1 + // Code size: 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Issue3465.Program::.ctor + +} // end of class Issue3465.Program + diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 91010db44..4c038c621 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1110,6 +1110,21 @@ namespace ICSharpCode.Decompiler.CSharp left = left.ConvertTo(inputType, this); right = right.ConvertTo(inputType, this); } + else if (inst.InputType == StackType.O) + { + // Unsafe.As(ref left) op Unsafe.As(ref right) + // TTo Unsafe.As(ref TFrom source) + var integerType = compilation.FindType(inst.Sign == Sign.Signed ? KnownTypeCode.IntPtr : KnownTypeCode.UIntPtr); + left = WrapInUnsafeAs(left, inst.Left); + right = WrapInUnsafeAs(right, inst.Right); + + TranslatedExpression WrapInUnsafeAs(TranslatedExpression expr, ILInstruction inst) + { + var type = expr.Type; + expr = WrapInRef(expr, new ByReferenceType(type)); + return CallUnsafeIntrinsic("As", [expr], integerType, typeArguments: [type, integerType]); + } + } return new BinaryOperatorExpression(left.Expression, op, right.Expression) .WithILInstruction(inst) .WithRR(new OperatorResolveResult(compilation.FindType(TypeCode.Boolean), diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 7f070479a..9e6236a89 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -1852,6 +1852,11 @@ namespace ICSharpCode.Decompiler.IL ILInstruction Comparison(ComparisonKind kind, bool un = false) { + if (!kind.IsEqualityOrInequality() && PeekStackType() == StackType.O) + { + FlushExpressionStack(); + } + var right = Pop(); var left = Pop(); // left will run before right, thus preserving the evaluation order diff --git a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs index 406d3847e..26c482c57 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Comp.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Comp.cs @@ -229,5 +229,29 @@ namespace ICSharpCode.Decompiler.IL var liftingKind = isLifted ? ComparisonLiftingKind.ThreeValuedLogic : ComparisonLiftingKind.None; return new Comp(ComparisonKind.Equality, liftingKind, StackType.I4, Sign.None, arg, new LdcI4(0)); } + + internal override bool CanInlineIntoSlot(int childIndex, ILInstruction expressionBeingMoved) + { + // ExpressionBuilder translates comp.o(a op b) for op not in (==, !=) into + // Unsafe.As(ref a) op Unsafe.As(ref b), which requires that a and b are variables + // and not expressions. Returning false in those cases prevents inlining. + // However if one of the arguments is LdNull, then we don't need the Unsafe.As trickery, and can always inline. + if (kind.IsEqualityOrInequality() || this.InputType != StackType.O) + { + // OK, won't need Unsafe.As. + return true; + } + if (expressionBeingMoved is LdLoc || expressionBeingMoved.MatchLdsFld(out _)) + { + // OK, can use variable/field name with Unsafe.As(ref x) + return true; + } + if (Sign != Sign.Signed && (expressionBeingMoved is LdNull || Left is LdNull || Right is LdNull)) + { + // OK, this is the "compare with null" special case that doesn't need Unsafe.As() + return true; + } + return false; + } } }