Browse Source

Improve decompilation of ?. in generic code.

pull/1072/merge
Daniel Grunwald 8 years ago
parent
commit
36035de5fa
  1. 21
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
  2. 89
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il
  3. 107
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il
  4. 11
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  5. 16
      ICSharpCode.Decompiler/IL/Instructions.cs
  6. 9
      ICSharpCode.Decompiler/IL/Instructions.tt
  7. 4
      ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs
  8. 7
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

21
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs

@ -61,6 +61,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -61,6 +61,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public interface ITest
{
int Int();
ITest Next();
}
private int GetInt()
{
return 9;
@ -209,5 +215,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -209,5 +215,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
c.Property = null;
}
}
private static int? GenericUnconstrainedInt<T>(T t) where T : ITest
{
return t?.Int();
}
private static int? GenericClassConstraintInt<T>(T t) where T : class, ITest
{
return t?.Int();
}
private static int? GenericStructConstraintInt<T>(T? t) where T : struct, ITest
{
return t?.Int();
}
}
}

89
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il

@ -25,14 +25,14 @@ @@ -25,14 +25,14 @@
.ver 0:0:0:0
}
.module NullPropagation.dll
// MVID: {DDAB2C82-C901-450A-B55D-2D0D7BC34C48}
// MVID: {B228D113-C048-4D76-A7D1-78312CF90769}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x003E0000
// Image base: 0x03040000
// =============== CLASS MEMBERS DECLARATION ===================
@ -217,6 +217,21 @@ @@ -217,6 +217,21 @@
} // end of property MyStruct::Item
} // end of class MyStruct
.class interface abstract auto ansi nested public ITest
{
.method public hidebysig newslot abstract virtual
instance int32 Int() cil managed
{
} // end of method ITest::Int
.method public hidebysig newslot abstract virtual
instance class ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest
Next() cil managed
{
} // end of method ITest::Next
} // end of class ITest
.method private hidebysig instance int32
GetInt() cil managed
{
@ -845,6 +860,76 @@ @@ -845,6 +860,76 @@
IL_0019: ret
} // end of method NullPropagation::Setter
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericUnconstrainedInt<(ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest) T>(!!T t) cil managed
{
// Code size 37 (0x25)
.maxstack 1
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0)
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: brtrue.s IL_0012
IL_0008: ldloca.s V_0
IL_000a: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0010: ldloc.0
IL_0011: ret
IL_0012: ldarga.s t
IL_0014: constrained. !!T
IL_001a: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_001f: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_0024: ret
} // end of method NullPropagation::GenericUnconstrainedInt
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericClassConstraintInt<class (ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest) T>(!!T t) cil managed
{
// Code size 31 (0x1f)
.maxstack 2
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0)
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: dup
IL_0007: brtrue.s IL_0014
IL_0009: pop
IL_000a: ldloca.s V_0
IL_000c: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0012: ldloc.0
IL_0013: ret
IL_0014: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_0019: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_001e: ret
} // end of method NullPropagation::GenericClassConstraintInt
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericStructConstraintInt<valuetype .ctor (ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest, [mscorlib]System.ValueType) T>(valuetype [mscorlib]System.Nullable`1<!!T> t) cil managed
{
// Code size 46 (0x2e)
.maxstack 1
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0,
!!T V_1)
IL_0000: ldarga.s t
IL_0002: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue()
IL_0007: brtrue.s IL_0013
IL_0009: ldloca.s V_0
IL_000b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0011: ldloc.0
IL_0012: ret
IL_0013: ldarga.s t
IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1<!!T>::GetValueOrDefault()
IL_001a: stloc.1
IL_001b: ldloca.s V_1
IL_001d: constrained. !!T
IL_0023: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_0028: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_002d: ret
} // end of method NullPropagation::GenericStructConstraintInt
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{

107
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il

@ -25,14 +25,14 @@ @@ -25,14 +25,14 @@
.ver 0:0:0:0
}
.module NullPropagation.dll
// MVID: {252A7D2B-A310-4825-9B38-400B4B7D3296}
// MVID: {AC5F707E-C73C-480F-9B50-4CCACD230B55}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x03130000
// Image base: 0x04950000
// =============== CLASS MEMBERS DECLARATION ===================
@ -263,6 +263,21 @@ @@ -263,6 +263,21 @@
} // end of property MyStruct::Item
} // end of class MyStruct
.class interface abstract auto ansi nested public ITest
{
.method public hidebysig newslot abstract virtual
instance int32 Int() cil managed
{
} // end of method ITest::Int
.method public hidebysig newslot abstract virtual
instance class ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest
Next() cil managed
{
} // end of method ITest::Next
} // end of class ITest
.method private hidebysig instance int32
GetInt() cil managed
{
@ -1005,6 +1020,94 @@ @@ -1005,6 +1020,94 @@
IL_002a: ret
} // end of method NullPropagation::Setter
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericUnconstrainedInt<(ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest) T>(!!T t) cil managed
{
// Code size 43 (0x2b)
.maxstack 1
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0,
valuetype [mscorlib]System.Nullable`1<int32> V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: box !!T
IL_0007: brtrue.s IL_0014
IL_0009: ldloca.s V_0
IL_000b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0011: ldloc.0
IL_0012: br.s IL_0026
IL_0014: ldarga.s t
IL_0016: constrained. !!T
IL_001c: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_0021: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_0026: stloc.1
IL_0027: br.s IL_0029
IL_0029: ldloc.1
IL_002a: ret
} // end of method NullPropagation::GenericUnconstrainedInt
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericClassConstraintInt<class (ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest) T>(!!T t) cil managed
{
// Code size 37 (0x25)
.maxstack 2
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0,
valuetype [mscorlib]System.Nullable`1<int32> V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: box !!T
IL_0007: dup
IL_0008: brtrue.s IL_0016
IL_000a: pop
IL_000b: ldloca.s V_0
IL_000d: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0013: ldloc.0
IL_0014: br.s IL_0020
IL_0016: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_001b: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_0020: stloc.1
IL_0021: br.s IL_0023
IL_0023: ldloc.1
IL_0024: ret
} // end of method NullPropagation::GenericClassConstraintInt
.method private hidebysig static valuetype [mscorlib]System.Nullable`1<int32>
GenericStructConstraintInt<valuetype .ctor (ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest, [mscorlib]System.ValueType) T>(valuetype [mscorlib]System.Nullable`1<!!T> t) cil managed
{
// Code size 52 (0x34)
.maxstack 1
.locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0,
!!T V_1,
valuetype [mscorlib]System.Nullable`1<int32> V_2)
IL_0000: nop
IL_0001: ldarga.s t
IL_0003: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue()
IL_0008: brtrue.s IL_0015
IL_000a: ldloca.s V_0
IL_000c: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0012: ldloc.0
IL_0013: br.s IL_002f
IL_0015: ldarga.s t
IL_0017: call instance !0 valuetype [mscorlib]System.Nullable`1<!!T>::GetValueOrDefault()
IL_001c: stloc.1
IL_001d: ldloca.s V_1
IL_001f: constrained. !!T
IL_0025: callvirt instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.NullPropagation/ITest::Int()
IL_002a: newobj instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_002f: stloc.2
IL_0030: br.s IL_0032
IL_0032: ldloc.2
IL_0033: ret
} // end of method NullPropagation::GenericStructConstraintInt
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{

11
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1515,7 +1515,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1515,7 +1515,18 @@ namespace ICSharpCode.Decompiler.CSharp
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? member.DeclaringType), this);
}
if (translatedTarget.Expression is DirectionExpression) {
// (ref x).member => x.member
translatedTarget = translatedTarget.UnwrapChild(((DirectionExpression)translatedTarget).Expression);
} else if (translatedTarget.Expression is UnaryOperatorExpression uoe
&& uoe.Operator == UnaryOperatorType.NullConditional
&& uoe.Expression is DirectionExpression) {
// (ref x)?.member => x?.member
translatedTarget = translatedTarget.UnwrapChild(((DirectionExpression)uoe.Expression).Expression);
// note: we need to create a new ResolveResult for the null-conditional operator,
// using the underlying type of the input expression without the DirectionExpression
translatedTarget = new UnaryOperatorExpression(UnaryOperatorType.NullConditional, translatedTarget)
.WithRR(new ResolveResult(NullableType.GetUnderlyingType(translatedTarget.Type)))
.WithoutILInstruction();
}
return translatedTarget;
}

16
ICSharpCode.Decompiler/IL/Instructions.cs

@ -102,9 +102,13 @@ namespace ICSharpCode.Decompiler.IL @@ -102,9 +102,13 @@ namespace ICSharpCode.Decompiler.IL
ThreeValuedLogicAnd,
/// <summary>Three valued logic or. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.or(), does not have short-circuiting behavior.</summary>
ThreeValuedLogicOr,
/// <summary>The input operand must be either a nullable value type or a reference type.
/// <summary>The input operand must be one of:
/// 1. a nullable value type
/// 2. a reference type
/// 3. a managed reference to a type parameter.
/// If the input is non-null, evaluates to the (unwrapped) input.
/// If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction.</summary>
/// If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction.
/// In case 3 (managed reference), the dereferenced value is the input being tested, and the nullable.unwrap instruction returns the managed reference unmodified (if the value is non-null).</summary>
NullableUnwrap,
/// <summary>Serves as jump target for the nullable.unwrap instruction.
/// If the input evaluates normally, evaluates to the input value (wrapped in Nullable<T> if the input is a non-nullable value type).If a nullable.unwrap instruction encounters a null input and jumps to the (endpoint of the) nullable.rewrap instruction,the nullable.rewrap instruction evaluates to null.</summary>
@ -2518,9 +2522,13 @@ namespace ICSharpCode.Decompiler.IL @@ -2518,9 +2522,13 @@ namespace ICSharpCode.Decompiler.IL
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>The input operand must be either a nullable value type or a reference type.
/// <summary>The input operand must be one of:
/// 1. a nullable value type
/// 2. a reference type
/// 3. a managed reference to a type parameter.
/// If the input is non-null, evaluates to the (unwrapped) input.
/// If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction.</summary>
/// If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction.
/// In case 3 (managed reference), the dereferenced value is the input being tested, and the nullable.unwrap instruction returns the managed reference unmodified (if the value is non-null).</summary>
public sealed partial class NullableUnwrap : UnaryInstruction
{

9
ICSharpCode.Decompiler/IL/Instructions.tt

@ -160,9 +160,14 @@ @@ -160,9 +160,14 @@
CustomClassName("ThreeValuedLogicAnd"), Binary, ResultType("O")),
new OpCode("3vl.logic.or", "Three valued logic or. Inputs are of type bool? or I4, output is of type bool?. Unlike logic.or(), does not have short-circuiting behavior.",
CustomClassName("ThreeValuedLogicOr"), Binary, ResultType("O")),
new OpCode("nullable.unwrap", "The input operand must be either a nullable value type or a reference type." + Environment.NewLine
new OpCode("nullable.unwrap", "The input operand must be one of:" + Environment.NewLine
+ " 1. a nullable value type" + Environment.NewLine
+ " 2. a reference type" + Environment.NewLine
+ " 3. a managed reference to a type parameter." + Environment.NewLine
+ "If the input is non-null, evaluates to the (unwrapped) input." + Environment.NewLine
+ "If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction.",
+ "If the input is null, jumps to the innermost nullable.rewrap instruction that contains this instruction." + Environment.NewLine
+ "In case 3 (managed reference), the dereferenced value is the input being tested, and the nullable.unwrap instruction "
+ "returns the managed reference unmodified (if the value is non-null).",
Unary, CustomConstructor, CustomWriteTo, HasFlag("InstructionFlags.MayUnwrapNull")),
new OpCode("nullable.rewrap", "Serves as jump target for the nullable.unwrap instruction." + Environment.NewLine
+ "If the input evaluates normally, evaluates to the input value (wrapped in Nullable<T> if the input is a non-nullable value type)."

4
ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs

@ -32,7 +32,11 @@ namespace ICSharpCode.Decompiler.IL @@ -32,7 +32,11 @@ namespace ICSharpCode.Decompiler.IL
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
if (this.ResultType == StackType.Ref) {
Debug.Assert(Argument.ResultType == StackType.Ref, "nullable.unwrap expects reference to nullable type as input");
} else {
Debug.Assert(Argument.ResultType == StackType.O, "nullable.unwrap expects nullable type as input");
}
Debug.Assert(Ancestors.Any(a => a is NullableRewrap));
}

7
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -176,7 +176,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -176,7 +176,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; // setter/adder/remover cannot be called with ?. syntax
}
inst = call.Arguments[0];
if (call.Method.DeclaringType.IsReferenceType == false && inst.MatchAddressOf(out var arg)) {
if ((call.ConstrainedTo ?? call.Method.DeclaringType).IsReferenceType == false && inst.MatchAddressOf(out var arg)) {
inst = arg;
}
// ensure the access chain does not contain any 'nullable.unwrap' that aren't directly part of the chain
@ -197,7 +197,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -197,7 +197,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool IsValidEndOfChain()
{
if (testedVarHasReferenceType) {
return inst.MatchLdLoc(testedVar);
// either reference type (expect: ldloc(testedVar)) or unconstrained generic type (expect: ldloca(testedVar)).
return inst.MatchLdLocRef(testedVar);
} else {
return NullableLiftingTransform.MatchGetValueOrDefault(inst, testedVar);
}
@ -219,7 +220,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -219,7 +220,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} else {
// Wrap varLoad in nullable.unwrap:
var children = varLoad.Parent.Children;
children[varLoad.ChildIndex] = new NullableUnwrap(testedVar.StackType, varLoad);
children[varLoad.ChildIndex] = new NullableUnwrap(varLoad.ResultType, varLoad);
}
}
}

Loading…
Cancel
Save