Browse Source

Merge branch 'fix/3465'

null-coalescing-assignment
Daniel Grunwald 4 months ago
parent
commit
317b4e8add
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 8
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  3. 22
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.cs
  4. 60
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.il
  5. 15
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  6. 5
      ICSharpCode.Decompiler/IL/ILReader.cs
  7. 24
      ICSharpCode.Decompiler/IL/Instructions/Comp.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -101,6 +101,7 @@ @@ -101,6 +101,7 @@
<None Include="TestCases\ILPretty\Issue2260SwitchString.il" />
<None Include="TestCases\ILPretty\Issue3442.il" />
<None Include="TestCases\ILPretty\Issue3344CkFinite.il" />
<None Include="testcases\ilpretty\Issue3465.il" />
<None Include="TestCases\ILPretty\Issue3466.il" />
<None Include="testcases\ilpretty\Issue3504.il" />
<None Include="testcases\ilpretty\Issue3524.il" />
@ -145,6 +146,7 @@ @@ -145,6 +146,7 @@
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\ILPretty\Issue3344CkFinite.cs" />
<Compile Include="TestCases\ILPretty\Issue3421.cs" />
<Compile Include="TestCases\ILPretty\Issue3465.cs" />
<Compile Include="TestCases\ILPretty\Issue3442.cs" />
<Compile Include="TestCases\ILPretty\Issue3466.cs" />
<Compile Include="TestCases\ILPretty\Issue3524.cs" />

8
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -225,6 +225,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -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 @@ -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));
}

22
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.cs

@ -0,0 +1,22 @@ @@ -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<Program, UIntPtr>(ref program) > System.Runtime.CompilerServices.Unsafe.As<Program, UIntPtr>(ref programNull);
}
}
}

60
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3465.il

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
.class private auto ansi '<Module>'
{
} // end of class <Module>
.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

15
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1110,6 +1110,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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<object, UIntPtr>(ref left) op Unsafe.As<object, UIntPtr>(ref right)
// TTo Unsafe.As<TFrom, TTo>(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),

5
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1852,6 +1852,11 @@ namespace ICSharpCode.Decompiler.IL @@ -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

24
ICSharpCode.Decompiler/IL/Instructions/Comp.cs

@ -229,5 +229,29 @@ namespace ICSharpCode.Decompiler.IL @@ -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;
}
}
}

Loading…
Cancel
Save