diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index 17606500d..eff113b87 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/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() { return 9; @@ -209,5 +215,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty c.Property = null; } } + + private static int? GenericUnconstrainedInt(T t) where T : ITest + { + return t?.Int(); + } + + private static int? GenericClassConstraintInt(T t) where T : class, ITest + { + return t?.Int(); + } + + private static int? GenericStructConstraintInt(T? t) where T : struct, ITest + { + return t?.Int(); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il index 38bb4cd34..f21499b34 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.opt.roslyn.il @@ -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 @@ } // 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 @@ IL_0019: ret } // end of method NullPropagation::Setter + .method private hidebysig static valuetype [mscorlib]System.Nullable`1 + 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 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 + 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::.ctor(!0) + IL_0024: ret + } // end of method NullPropagation::GenericUnconstrainedInt + + .method private hidebysig static valuetype [mscorlib]System.Nullable`1 + GenericClassConstraintInt(!!T t) cil managed + { + // Code size 31 (0x1f) + .maxstack 2 + .locals init (valuetype [mscorlib]System.Nullable`1 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 + 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::.ctor(!0) + IL_001e: ret + } // end of method NullPropagation::GenericClassConstraintInt + + .method private hidebysig static valuetype [mscorlib]System.Nullable`1 + GenericStructConstraintInt(valuetype [mscorlib]System.Nullable`1 t) cil managed + { + // Code size 46 (0x2e) + .maxstack 1 + .locals init (valuetype [mscorlib]System.Nullable`1 V_0, + !!T V_1) + IL_0000: ldarga.s t + IL_0002: call instance bool valuetype [mscorlib]System.Nullable`1::get_HasValue() + IL_0007: brtrue.s IL_0013 + + IL_0009: ldloca.s V_0 + IL_000b: initobj valuetype [mscorlib]System.Nullable`1 + IL_0011: ldloc.0 + IL_0012: ret + + IL_0013: ldarga.s t + IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1::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::.ctor(!0) + IL_002d: ret + } // end of method NullPropagation::GenericStructConstraintInt + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il index 1d717edd7..b18750c0e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.roslyn.il @@ -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 @@ } // 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 @@ IL_002a: ret } // end of method NullPropagation::Setter + .method private hidebysig static valuetype [mscorlib]System.Nullable`1 + 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 V_0, + valuetype [mscorlib]System.Nullable`1 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 + 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::.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 + GenericClassConstraintInt(!!T t) cil managed + { + // Code size 37 (0x25) + .maxstack 2 + .locals init (valuetype [mscorlib]System.Nullable`1 V_0, + valuetype [mscorlib]System.Nullable`1 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 + 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::.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 + GenericStructConstraintInt(valuetype [mscorlib]System.Nullable`1 t) cil managed + { + // Code size 52 (0x34) + .maxstack 1 + .locals init (valuetype [mscorlib]System.Nullable`1 V_0, + !!T V_1, + valuetype [mscorlib]System.Nullable`1 V_2) + IL_0000: nop + IL_0001: ldarga.s t + IL_0003: call instance bool valuetype [mscorlib]System.Nullable`1::get_HasValue() + IL_0008: brtrue.s IL_0015 + + IL_000a: ldloca.s V_0 + IL_000c: initobj valuetype [mscorlib]System.Nullable`1 + 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::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::.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 { diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 930d2d072..15c594d4b 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -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; } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 126b4db66..f88839143 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -102,9 +102,13 @@ namespace ICSharpCode.Decompiler.IL ThreeValuedLogicAnd, /// 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. ThreeValuedLogicOr, - /// The input operand must be either a nullable value type or a reference type. + /// 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. + /// 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). NullableUnwrap, /// Serves as jump target for the nullable.unwrap instruction. /// If the input evaluates normally, evaluates to the input value (wrapped in Nullable 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. @@ -2518,9 +2522,13 @@ namespace ICSharpCode.Decompiler.IL } namespace ICSharpCode.Decompiler.IL { - /// The input operand must be either a nullable value type or a reference type. + /// 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. + /// 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). public sealed partial class NullableUnwrap : UnaryInstruction { diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 0362a1e91..667369b9e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -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 if the input is a non-nullable value type)." diff --git a/ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs index ef12373ea..a973ef40b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/NullableInstructions.cs @@ -32,7 +32,11 @@ namespace ICSharpCode.Decompiler.IL internal override void CheckInvariant(ILPhase 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)); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 6065f0847..196dc511e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/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 } 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 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 } 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); } } }