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. 6
      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
} }
} }
public interface ITest
{
int Int();
ITest Next();
}
private int GetInt() private int GetInt()
{ {
return 9; return 9;
@ -209,5 +215,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
c.Property = null; 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 @@
.ver 0:0:0:0 .ver 0:0:0:0
} }
.module NullPropagation.dll .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 ) .custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000 .imagebase 0x10000000
.file alignment 0x00000200 .file alignment 0x00000200
.stackreserve 0x00100000 .stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI .subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY .corflags 0x00000001 // ILONLY
// Image base: 0x003E0000 // Image base: 0x03040000
// =============== CLASS MEMBERS DECLARATION =================== // =============== CLASS MEMBERS DECLARATION ===================
@ -217,6 +217,21 @@
} // end of property MyStruct::Item } // end of property MyStruct::Item
} // end of class MyStruct } // 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 .method private hidebysig instance int32
GetInt() cil managed GetInt() cil managed
{ {
@ -845,6 +860,76 @@
IL_0019: ret IL_0019: ret
} // end of method NullPropagation::Setter } // 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 .method public hidebysig specialname rtspecialname
instance void .ctor() cil managed instance void .ctor() cil managed
{ {

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

@ -25,14 +25,14 @@
.ver 0:0:0:0 .ver 0:0:0:0
} }
.module NullPropagation.dll .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 ) .custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000 .imagebase 0x10000000
.file alignment 0x00000200 .file alignment 0x00000200
.stackreserve 0x00100000 .stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI .subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY .corflags 0x00000001 // ILONLY
// Image base: 0x03130000 // Image base: 0x04950000
// =============== CLASS MEMBERS DECLARATION =================== // =============== CLASS MEMBERS DECLARATION ===================
@ -263,6 +263,21 @@
} // end of property MyStruct::Item } // end of property MyStruct::Item
} // end of class MyStruct } // 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 .method private hidebysig instance int32
GetInt() cil managed GetInt() cil managed
{ {
@ -1005,6 +1020,94 @@
IL_002a: ret IL_002a: ret
} // end of method NullPropagation::Setter } // 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 .method public hidebysig specialname rtspecialname
instance void .ctor() cil managed instance void .ctor() cil managed
{ {

11
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1515,7 +1515,18 @@ namespace ICSharpCode.Decompiler.CSharp
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? member.DeclaringType), this); translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? member.DeclaringType), this);
} }
if (translatedTarget.Expression is DirectionExpression) { if (translatedTarget.Expression is DirectionExpression) {
// (ref x).member => x.member
translatedTarget = translatedTarget.UnwrapChild(((DirectionExpression)translatedTarget).Expression); 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; return translatedTarget;
} }

16
ICSharpCode.Decompiler/IL/Instructions.cs

@ -102,9 +102,13 @@ namespace ICSharpCode.Decompiler.IL
ThreeValuedLogicAnd, 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> /// <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, 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 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, NullableUnwrap,
/// <summary>Serves as jump target for the nullable.unwrap instruction. /// <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> /// 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
} }
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 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 public sealed partial class NullableUnwrap : UnaryInstruction
{ {

9
ICSharpCode.Decompiler/IL/Instructions.tt

@ -160,9 +160,14 @@
CustomClassName("ThreeValuedLogicAnd"), Binary, ResultType("O")), 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.", 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")), 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 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")), Unary, CustomConstructor, CustomWriteTo, HasFlag("InstructionFlags.MayUnwrapNull")),
new OpCode("nullable.rewrap", "Serves as jump target for the nullable.unwrap instruction." + Environment.NewLine 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)." + "If the input evaluates normally, evaluates to the input value (wrapped in Nullable<T> if the input is a non-nullable value type)."

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

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

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

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

Loading…
Cancel
Save