diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs index f32d2fb6b..7dcb5d98d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs @@ -28,6 +28,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private int[] field3; private short field4; + public int InstanceProperty { + get; + set; + } + public static int StaticProperty { + get; + set; + } + public void SimpleInlineWithLocals() { int value; @@ -56,7 +65,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty this.UseShort(this.field4 = this.UseShort(0)); Console.WriteLine(this.field4); } - + public short UseShort(short s) { Console.WriteLine(s); @@ -112,5 +121,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return this.GetArray()[this.GetIndex()] = this.GetValue(this.GetIndex()); } + + public int StaticPropertyTest() + { + return InlineAssignmentTest.StaticProperty = this.GetIndex(); + } + + public int InstancePropertyTest() + { + return this.InstanceProperty = this.GetIndex(); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il index 9a1797896..891b11997 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il @@ -10,7 +10,7 @@ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } -.assembly w1xqybg1 +.assembly dcbsz5ie { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx @@ -20,15 +20,15 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 } -.module w1xqybg1.dll -// MVID: {1BE5829A-0D5E-43DC-8C02-E3012B1780A2} +.module dcbsz5ie.dll +// MVID: {CFA464E9-510B-40EC-AD90-3FB26B76D1A6} .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: 0x033B0000 +// Image base: 0x00690000 // =============== CLASS MEMBERS DECLARATION =================== @@ -40,6 +40,64 @@ .field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2 .field private int32[] field3 .field private int16 field4 + .field private int32 'k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field private static int32 'k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .method public hidebysig specialname instance int32 + get_InstanceProperty() cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 11 (0xb) + .maxstack 1 + .locals init (int32 V_0) + IL_0000: ldarg.0 + IL_0001: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0006: stloc.0 + IL_0007: br.s IL_0009 + + IL_0009: ldloc.0 + IL_000a: ret + } // end of method InlineAssignmentTest::get_InstanceProperty + + .method public hidebysig specialname instance void + set_InstanceProperty(int32 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0007: ret + } // end of method InlineAssignmentTest::set_InstanceProperty + + .method public hidebysig specialname static + int32 get_StaticProperty() cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 10 (0xa) + .maxstack 1 + .locals init (int32 V_0) + IL_0000: ldsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0005: stloc.0 + IL_0006: br.s IL_0008 + + IL_0008: ldloc.0 + IL_0009: ret + } // end of method InlineAssignmentTest::get_StaticProperty + + .method public hidebysig specialname static + void set_StaticProperty(int32 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: stsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0006: ret + } // end of method InlineAssignmentTest::set_StaticProperty + .method public hidebysig instance void SimpleInlineWithLocals() cil managed { @@ -378,6 +436,48 @@ IL_0021: ret } // end of method InlineAssignmentTest::ArrayUsageWithMethods + .method public hidebysig instance int32 + StaticPropertyTest() cil managed + { + // Code size 19 (0x13) + .maxstack 2 + .locals init (int32 V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex() + IL_0007: dup + IL_0008: call void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32) + IL_000d: nop + IL_000e: stloc.0 + IL_000f: br.s IL_0011 + + IL_0011: ldloc.0 + IL_0012: ret + } // end of method InlineAssignmentTest::StaticPropertyTest + + .method public hidebysig instance int32 + InstancePropertyTest() cil managed + { + // Code size 22 (0x16) + .maxstack 3 + .locals init (int32 V_0, + int32 V_1) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.0 + IL_0003: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex() + IL_0008: dup + IL_0009: stloc.1 + IL_000a: call instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32) + IL_000f: nop + IL_0010: ldloc.1 + IL_0011: stloc.0 + IL_0012: br.s IL_0014 + + IL_0014: ldloc.0 + IL_0015: ret + } // end of method InlineAssignmentTest::InstancePropertyTest + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { @@ -388,6 +488,16 @@ IL_0006: ret } // end of method InlineAssignmentTest::.ctor + .property instance int32 InstanceProperty() + { + .get instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_InstanceProperty() + .set instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32) + } // end of property InlineAssignmentTest::InstanceProperty + .property int32 StaticProperty() + { + .get int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_StaticProperty() + .set void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32) + } // end of property InlineAssignmentTest::StaticProperty } // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il index 2f944dee2..5b0daf795 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il @@ -10,7 +10,7 @@ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } -.assembly f1ibwsev +.assembly idzuatf2 { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx @@ -20,15 +20,15 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 } -.module f1ibwsev.dll -// MVID: {8DB4E6AA-A0C2-49F3-8E9C-A28CE30F145A} +.module idzuatf2.dll +// MVID: {0E6B135E-0CD7-486D-88EB-95340860F028} .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: 0x01400000 +// Image base: 0x02A70000 // =============== CLASS MEMBERS DECLARATION =================== @@ -40,6 +40,54 @@ .field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2 .field private int32[] field3 .field private int16 field4 + .field private int32 'k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field private static int32 'k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .method public hidebysig specialname instance int32 + get_InstanceProperty() cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0006: ret + } // end of method InlineAssignmentTest::get_InstanceProperty + + .method public hidebysig specialname instance void + set_InstanceProperty(int32 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0007: ret + } // end of method InlineAssignmentTest::set_InstanceProperty + + .method public hidebysig specialname static + int32 get_StaticProperty() cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 6 (0x6) + .maxstack 8 + IL_0000: ldsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0005: ret + } // end of method InlineAssignmentTest::get_StaticProperty + + .method public hidebysig specialname static + void set_StaticProperty(int32 'value') cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: stsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'k__BackingField' + IL_0006: ret + } // end of method InlineAssignmentTest::set_StaticProperty + .method public hidebysig instance void SimpleInlineWithLocals() cil managed { @@ -309,6 +357,34 @@ IL_001c: ret } // end of method InlineAssignmentTest::ArrayUsageWithMethods + .method public hidebysig instance int32 + StaticPropertyTest() cil managed + { + // Code size 13 (0xd) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex() + IL_0006: dup + IL_0007: call void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32) + IL_000c: ret + } // end of method InlineAssignmentTest::StaticPropertyTest + + .method public hidebysig instance int32 + InstancePropertyTest() cil managed + { + // Code size 16 (0x10) + .maxstack 3 + .locals init (int32 V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex() + IL_0007: dup + IL_0008: stloc.0 + IL_0009: call instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32) + IL_000e: ldloc.0 + IL_000f: ret + } // end of method InlineAssignmentTest::InstancePropertyTest + .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { @@ -319,6 +395,16 @@ IL_0006: ret } // end of method InlineAssignmentTest::.ctor + .property instance int32 InstanceProperty() + { + .get instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_InstanceProperty() + .set instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32) + } // end of property InlineAssignmentTest::InstanceProperty + .property int32 StaticProperty() + { + .get int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_StaticProperty() + .set void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32) + } // end of property InlineAssignmentTest::StaticProperty } // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 061e3015c..412f6370d 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -109,9 +109,6 @@ namespace ICSharpCode.Decompiler.CSharp // CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform // and must run before NullCoalescingTransform new CachedDelegateInitialization(), - new ILInlining(), - new TransformAssignment(), // must run before CopyPropagation - new CopyPropagation(), new StatementTransform( // per-block transforms that depend on each other, and thus need to // run interleaved (statement by statement). @@ -121,11 +118,13 @@ namespace ICSharpCode.Decompiler.CSharp // Inlining must be first, because it doesn't trigger re-runs. // Any other transform that opens up new inlining opportunities should call RequestRerun(). new ExpressionTransforms(), + new TransformAssignment(), // inline and compound assignments new NullCoalescingTransform(), new NullableLiftingStatementTransform(), new TransformArrayInitializers(), new TransformCollectionAndObjectInitializers() - ) + ), + new CopyPropagation() } }, new ProxyCallReplacer(), diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 3dd0e777a..33dfb7845 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -47,28 +47,32 @@ namespace ICSharpCode.Decompiler.CSharp public TranslatedExpression Build(CallInstruction inst) { - IMethod method = inst.Method; + if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.IsDelegateConstruction(newobj, true)) { + return HandleDelegateConstruction(newobj); + } + return Build(inst.OpCode, inst.Method, inst.Arguments).WithILInstruction(inst); + } + + public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method, IReadOnlyList callArguments) + { // Used for Call, CallVirt and NewObj TranslatedExpression target; - if (inst.OpCode == OpCode.NewObj) { - if (IL.Transforms.DelegateConstruction.IsDelegateConstruction((NewObj)inst, true)) { - return HandleDelegateConstruction(inst); - } + if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target } else { - target = expressionBuilder.TranslateTarget(method, inst.Arguments.FirstOrDefault(), inst.OpCode == OpCode.Call); + target = expressionBuilder.TranslateTarget(method, callArguments.FirstOrDefault(), callOpCode == OpCode.Call); } - int firstParamIndex = (method.IsStatic || inst.OpCode == OpCode.NewObj) ? 0 : 1; + int firstParamIndex = (method.IsStatic || callOpCode == OpCode.NewObj) ? 0 : 1; // Translate arguments to the expected parameter types var arguments = new List(method.Parameters.Count); - Debug.Assert(inst.Arguments.Count == firstParamIndex + method.Parameters.Count); + Debug.Assert(callArguments.Count == firstParamIndex + method.Parameters.Count); var expectedParameters = method.Parameters.ToList(); bool isExpandedForm = false; for (int i = 0; i < method.Parameters.Count; i++) { var parameter = expectedParameters[i]; - var arg = expressionBuilder.Translate(inst.Arguments[firstParamIndex + i]); + var arg = expressionBuilder.Translate(callArguments[firstParamIndex + i]); if (parameter.IsParams && i + 1 == method.Parameters.Count) { // Parameter is marked params // If the argument is an array creation, inline all elements into the call and add missing default values. @@ -90,7 +94,7 @@ namespace ICSharpCode.Decompiler.CSharp expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction()); } } - if (IsUnambiguousCall(inst, target, method, Array.Empty(), expandedArguments) == OverloadResolutionErrors.None) { + if (IsUnambiguousCall(callOpCode, target, method, Array.Empty(), expandedArguments) == OverloadResolutionErrors.None) { isExpandedForm = true; expectedParameters = expandedParameters; arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach())); @@ -124,7 +128,7 @@ namespace ICSharpCode.Decompiler.CSharp ResolveResult rr = new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm); - if (inst.OpCode == OpCode.NewObj) { + if (callOpCode == OpCode.NewObj) { if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) { var argumentExpressions = arguments.SelectArray(arg => arg.Expression); AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); @@ -140,11 +144,10 @@ namespace ICSharpCode.Decompiler.CSharp } } return atce - .WithILInstruction(inst) .WithRR(rr); } else { - if (IsUnambiguousCall(inst, target, method, Array.Empty(), arguments) != OverloadResolutionErrors.None) { + if (IsUnambiguousCall(callOpCode, target, method, Array.Empty(), arguments) != OverloadResolutionErrors.None) { for (int i = 0; i < arguments.Count; i++) { if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType()) { if (arguments[i].Expression is LambdaExpression lambda) { @@ -155,20 +158,20 @@ namespace ICSharpCode.Decompiler.CSharp } } } - return new ObjectCreateExpression(expressionBuilder.ConvertType(inst.Method.DeclaringType), arguments.SelectArray(arg => arg.Expression)) - .WithILInstruction(inst).WithRR(rr); + return new ObjectCreateExpression(expressionBuilder.ConvertType(method.DeclaringType), arguments.SelectArray(arg => arg.Expression)) + .WithRR(rr); } } else { int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0); if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) { - return HandleAccessorCall(inst, target, method, arguments.ToList()); + return HandleAccessorCall(callOpCode == OpCode.CallVirt, target, method, arguments.ToList()); } else if (method.Name == "Invoke" && method.DeclaringType.Kind == TypeKind.Delegate) { - return new InvocationExpression(target, arguments.Select(arg => arg.Expression)).WithILInstruction(inst).WithRR(rr); + return new InvocationExpression(target, arguments.Select(arg => arg.Expression)).WithRR(rr); } else if (IsDelegateEqualityComparison(method, arguments)) { return HandleDelegateEqualityComparison(method, arguments) - .WithILInstruction(inst).WithRR(rr); + .WithRR(rr); } else if (method.IsOperator && method.Name == "op_Implicit" && arguments.Count == 1) { - return HandleImplicitConversion(inst, arguments[0]); + return HandleImplicitConversion(method, arguments[0]); } else { bool requireTypeArguments = false; bool targetCasted = false; @@ -176,7 +179,7 @@ namespace ICSharpCode.Decompiler.CSharp IType[] typeArguments = Array.Empty(); OverloadResolutionErrors errors; - while ((errors = IsUnambiguousCall(inst, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) { + while ((errors = IsUnambiguousCall(callOpCode, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) { switch (errors) { case OverloadResolutionErrors.TypeInferenceFailed: case OverloadResolutionErrors.WrongNumberOfTypeArguments: @@ -213,7 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp Expression targetExpr = target.Expression; string methodName = method.Name; // HACK : convert this.Dispose() to ((IDisposable)this).Dispose(), if Dispose is an explicitly implemented interface method. - if (inst.Method.IsExplicitInterfaceImplementation && targetExpr is ThisReferenceExpression) { + if (method.IsExplicitInterfaceImplementation && targetExpr is ThisReferenceExpression) { targetExpr = new CastExpression(expressionBuilder.ConvertType(method.ImplementedInterfaceMembers[0].DeclaringType), targetExpr); methodName = method.ImplementedInterfaceMembers[0].Name; } @@ -221,7 +224,7 @@ namespace ICSharpCode.Decompiler.CSharp if (requireTypeArguments && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()))) mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); var argumentExpressions = arguments.Select(arg => arg.Expression); - return new InvocationExpression(mre, argumentExpressions).WithILInstruction(inst).WithRR(rr); + return new InvocationExpression(mre, argumentExpressions).WithRR(rr); } } } @@ -271,28 +274,27 @@ namespace ICSharpCode.Decompiler.CSharp ); } - private TranslatedExpression HandleImplicitConversion(CallInstruction call, TranslatedExpression argument) + private ExpressionWithResolveResult HandleImplicitConversion(IMethod method, TranslatedExpression argument) { var conversions = CSharpConversions.Get(expressionBuilder.compilation); - IType targetType = call.Method.ReturnType; + IType targetType = method.ReturnType; var conv = conversions.ImplicitConversion(argument.Type, targetType); - if (!(conv.IsUserDefined && conv.Method.Equals(call.Method))) { + if (!(conv.IsUserDefined && conv.Method.Equals(method))) { // implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type - argument = argument.ConvertTo(call.Method.Parameters[0].Type, expressionBuilder); + argument = argument.ConvertTo(method.Parameters[0].Type, expressionBuilder); conv = conversions.ImplicitConversion(argument.Type, targetType); } return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression) - .WithILInstruction(call) .WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, conv)); } - OverloadResolutionErrors IsUnambiguousCall(ILInstruction inst, TranslatedExpression target, IMethod method, IType[] typeArguments, IList arguments) + OverloadResolutionErrors IsUnambiguousCall(OpCode callOpCode, TranslatedExpression target, IMethod method, IType[] typeArguments, IList arguments) { var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); - if (inst is NewObj newObj) { - foreach (IMethod ctor in newObj.Method.DeclaringType.GetConstructors()) { - if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == newObj.Method.DeclaringTypeDefinition)) { + if (callOpCode == OpCode.NewObj) { + foreach (IMethod ctor in method.DeclaringType.GetConstructors()) { + if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition)) { or.AddCandidate(ctor); } } @@ -304,7 +306,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (or.BestCandidateErrors != OverloadResolutionErrors.None) return or.BestCandidateErrors; - if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) + if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), callOpCode == OpCode.CallVirt)) return OverloadResolutionErrors.AmbiguousMatch; return OverloadResolutionErrors.None; } @@ -327,12 +329,12 @@ namespace ICSharpCode.Decompiler.CSharp return true; } - TranslatedExpression HandleAccessorCall(ILInstruction inst, TranslatedExpression target, IMethod method, IList arguments) + ExpressionWithResolveResult HandleAccessorCall(bool isVirtCall, TranslatedExpression target, IMethod method, IList arguments) { var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); var result = lookup.Lookup(target.ResolveResult, method.AccessorOwner.Name, EmptyList.Instance, isInvocation: false); - if (result.IsError || (result is MemberResolveResult && !IsAppropriateCallTarget(method.AccessorOwner, ((MemberResolveResult)result).Member, inst.OpCode == OpCode.CallVirt))) + if (result.IsError || (result is MemberResolveResult && !IsAppropriateCallTarget(method.AccessorOwner, ((MemberResolveResult)result).Member, isVirtCall))) target = target.ConvertTo(method.AccessorOwner.DeclaringType, expressionBuilder); var rr = new MemberResolveResult(target.ResolveResult, method.AccessorOwner); @@ -356,12 +358,12 @@ namespace ICSharpCode.Decompiler.CSharp op = AssignmentOperatorType.Subtract; } } - return new AssignmentExpression(expr, op, value.Expression).WithILInstruction(inst).WithRR(new TypeResolveResult(method.AccessorOwner.ReturnType)); + return new AssignmentExpression(expr, op, value.Expression).WithRR(new TypeResolveResult(method.AccessorOwner.ReturnType)); } else { if (arguments.Count == 0) - return new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name).WithILInstruction(inst).WithRR(rr); + return new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name).WithRR(rr); else - return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression)).WithILInstruction(inst).WithRR(rr); + return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression)).WithRR(rr); } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 1fcace7ee..8e3b12ea0 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1694,11 +1694,26 @@ namespace ICSharpCode.Decompiler.CSharp return TranslateObjectAndCollectionInitializer(block); case BlockType.PostfixOperator: return TranslatePostfixOperator(block); + case BlockType.CallInlineAssign: + return TranslateSetterCallAssignment(block); default: return ErrorExpression("Unknown block type: " + block.Type); } } + private TranslatedExpression TranslateSetterCallAssignment(Block block) + { + if (!block.MatchInlineAssignBlock(out var call, out var value)) { + // should never happen unless the ILAst is invalid + return ErrorExpression("Error: MatchInlineAssignBlock() returned false"); + } + var arguments = call.Arguments.ToList(); + arguments[arguments.Count - 1] = value; + return new CallBuilder(this, typeSystem, settings) + .Build(call.OpCode, call.Method, arguments) + .WithILInstruction(call); + } + TranslatedExpression TranslateObjectAndCollectionInitializer(Block block) { var stloc = block.Instructions.FirstOrDefault() as StLoc; diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index e32ede62f..06cdc509b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -108,8 +108,13 @@ namespace ICSharpCode.Decompiler.IL // only the last instruction may have an unreachable endpoint Debug.Assert(!Instructions[i].HasFlag(InstructionFlags.EndPointUnreachable)); } - if (this.Type == BlockType.ControlFlow) { - Debug.Assert(finalInstruction.OpCode == OpCode.Nop); + switch (this.Type) { + case BlockType.ControlFlow: + Debug.Assert(finalInstruction.OpCode == OpCode.Nop); + break; + case BlockType.CallInlineAssign: + Debug.Assert(MatchInlineAssignBlock(out _, out _)); + break; } } @@ -251,13 +256,62 @@ namespace ICSharpCode.Decompiler.IL } return inst; } + + public bool MatchInlineAssignBlock(out CallInstruction call, out ILInstruction value) + { + call = null; + value = null; + if (this.Type != BlockType.CallInlineAssign) + return false; + if (this.Instructions.Count != 1) + return false; + call = this.Instructions[0] as CallInstruction; + if (call == null || call.Arguments.Count == 0) + return false; + if (!call.Arguments.Last().MatchStLoc(out var tmp, out value)) + return false; + if (!(tmp.IsSingleDefinition && tmp.LoadCount == 1)) + return false; + return this.FinalInstruction.MatchLdLoc(tmp); + } } - public enum BlockType { + public enum BlockType + { + /// + /// Block is used for control flow. + /// All blocks in block containers must have this type. + /// Control flow blocks cannot evaluate to a value (FinalInstruction must be Nop). + /// ControlFlow, + /// + /// Block is used for array initializers, e.g. `new int[] { expr1, expr2 }`. + /// ArrayInitializer, CollectionInitializer, ObjectInitializer, - PostfixOperator + /// + /// Block is used for postfix operator on local variable. + /// + /// + /// Postfix operators on non-locals use CompoundAssignmentInstruction with CompoundAssignmentType.EvaluatesToOldValue. + /// + PostfixOperator, + /// + /// Block is used for using the result of a property setter inline. + /// Example: Use(this.Property = value); + /// This is only for inline assignments to property or indexers; other inline assignments work + /// by using the result value of the stloc/stobj instructions. + /// + /// Constructed by TransformAssignment. + /// Can be deconstructed using Block.MatchInlineAssignBlock(). + /// + /// + /// Block { + /// call setter(..., stloc s(...)) + /// final: ldloc s + /// } + /// + CallInlineAssign } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs index 1b37fbec4..0575e469d 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs @@ -107,7 +107,7 @@ namespace ICSharpCode.Decompiler.IL internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst) { switch (inst.OpCode) { - case OpCode.LdLoc: + // case OpCode.LdLoc: -- not valid -- does not mark the variable as written to case OpCode.LdObj: return true; case OpCode.Call: diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs index 587084d91..f2e6d3254 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs @@ -21,43 +21,49 @@ using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { /// /// Constructs compound assignments and inline assignments. /// - public class TransformAssignment : IBlockTransform + public class TransformAssignment : IStatementTransform { - BlockTransformContext context; + StatementTransformContext context; - void IBlockTransform.Run(Block block, BlockTransformContext context) + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { this.context = context; - for (int i = block.Instructions.Count - 1; i >= 0; i--) { - if (TransformPostIncDecOperatorOnAddress(block, i) || TransformPostIncDecOnStaticField(block, i) || TransformCSharp4PostIncDecOperatorOnAddress(block, i)) { - block.Instructions.RemoveAt(i); - continue; - } - if (TransformPostIncDecOperator(block, i)) { - block.Instructions.RemoveAt(i); - continue; - } - if (TransformInlineAssignmentStObj(block, i) || TransformInlineAssignmentLocal(block, i)) - continue; - if (TransformInlineCompoundAssignmentCall(block, i)) - continue; - if (TransformRoslynCompoundAssignmentCall(block, i)) - continue; - if (TransformRoslynPostIncDecOperatorOnAddress(block, i)) - continue; + /*if (TransformPostIncDecOperatorOnAddress(block, i) || TransformPostIncDecOnStaticField(block, i) || TransformCSharp4PostIncDecOperatorOnAddress(block, i)) { + block.Instructions.RemoveAt(i); + continue; + } + if (TransformPostIncDecOperator(block, i)) { + block.Instructions.RemoveAt(i); + continue; + }*/ + if (TransformInlineAssignmentStObjOrCall(block, pos) || TransformInlineAssignmentLocal(block, pos)) { + // both inline assignments create a top-level stloc which might affect inlining + context.RequestRerun(); + return; } + /* + TransformInlineCompoundAssignmentCall(block, pos); + TransformRoslynCompoundAssignmentCall(block, pos); + // TODO: post-increment on local + // post-increment on address (e.g. field or array element) + TransformPostIncDecOperatorOnAddress(block, pos); + TransformRoslynPostIncDecOperatorOnAddress(block, pos); + // TODO: post-increment on call + */ } /// /// stloc s(value) /// stloc l(ldloc s) /// stobj(..., ldloc s) + /// where ... is pure and does not use s or l /// --> /// stloc l(stobj (..., value)) /// @@ -72,7 +78,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc s(stobj (..., value)) /// /// e.g. used for inline assignment to static field - bool TransformInlineAssignmentStObj(Block block, int pos) + /// + /// -or- + /// + /// + /// stloc s(value) + /// call set_Property(..., ldloc s) + /// where the '...' arguments are free of side-effects and not using 's' + /// --> + /// stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i }) + /// + bool TransformInlineAssignmentStObjOrCall(Block block, int pos) { var inst = block.Instructions[pos] as StLoc; // in some cases it can be a compiler-generated local @@ -96,22 +112,75 @@ namespace ICSharpCode.Decompiler.IL.Transforms localStore = null; nextPos = pos + 1; } - if (!(block.Instructions[nextPos] is StObj stobj)) - return false; - if (!stobj.Value.MatchLdLoc(inst.Variable) || inst.Variable.IsUsedWithin(stobj.Target)) - return false; - if (IsImplicitTruncation(inst.Value, stobj.Type)) { - // 'stloc s' is implicitly truncating the value + if (block.Instructions[nextPos] is StObj stobj) { + if (!stobj.Value.MatchLdLoc(inst.Variable)) + return false; + if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target)) + return false; + if (IsImplicitTruncation(inst.Value, stobj.Type)) { + // 'stobj' is implicitly truncating the value + return false; + } + context.Step("Inline assignment stobj", stobj); + block.Instructions.Remove(localStore); + block.Instructions.Remove(stobj); + stobj.Value = inst.Value; + inst.ReplaceWith(new StLoc(local, stobj)); + return true; + } else if (block.Instructions[nextPos] is CallInstruction call) { + // call must be a setter call: + if (!(call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)) + return false; + if (call.ResultType != StackType.Void || call.Arguments.Count == 0) + return false; + if (!call.Method.Equals((call.Method.AccessorOwner as IProperty)?.Setter)) + return false; + if (!call.Arguments.Last().MatchLdLoc(inst.Variable)) + return false; + foreach (var arg in call.Arguments.SkipLast(1)) { + if (!SemanticHelper.IsPure(arg.Flags) || inst.Variable.IsUsedWithin(arg)) + return false; + } + if (IsImplicitTruncation(inst.Value, call.Method.Parameters.Last().Type)) { + // setter call is implicitly truncating the value + return false; + } + // stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i }) + context.Step("Inline assignment call", call); + block.Instructions.Remove(localStore); + block.Instructions.Remove(call); + var newVar = context.Function.RegisterVariable(VariableKind.StackSlot, call.Method.Parameters.Last().Type); + call.Arguments[call.Arguments.Count - 1] = new StLoc(newVar, inst.Value); + inst.ReplaceWith(new StLoc(local, new Block(BlockType.CallInlineAssign) { + Instructions = { call }, + FinalInstruction = new LdLoc(newVar) + })); + return true; + } else { return false; } - context.Step("Inline assignment stobj", stobj); - block.Instructions.Remove(localStore); - block.Instructions.Remove(stobj); - stobj.Value = inst.Value; - inst.ReplaceWith(new StLoc(local, stobj)); - return true; } + static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv) + { + conv = inst as Conv; + if (conv != null && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) { + // for compound assignments to small integers, the compiler emits a "conv" instruction + return conv.Argument; + } else { + return inst; + } + } + + static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType) + { + if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) + return false; + if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) + return false; // conv does not match binary operation + return true; + } + /// /// stloc s(binary(callvirt(getter), value)) /// callvirt (setter, ldloc s) @@ -125,14 +194,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // in some cases it can be a compiler-generated local if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local)) return false; - ILInstruction value = mainStLoc.Value; - if (value is Conv conv && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) { - // for compound assignments to small integers, the compiler emits a "conv" instruction - value = conv.Argument; - } else { - conv = null; - } - BinaryNumericInstruction binary = value as BinaryNumericInstruction; + BinaryNumericInstruction binary = UnwrapSmallIntegerConv(mainStLoc.Value, out var conv) as BinaryNumericInstruction; ILVariable localVariable = mainStLoc.Variable; if (!localVariable.IsSingleDefinition) return false; @@ -151,10 +213,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1) return false; IType targetType = getterCall.Method.ReturnType; - if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) + if (!ValidateCompoundAssign(binary, conv, targetType)) return false; - if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) - return false; // conv does not match binary operation context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); block.Instructions.RemoveAt(i + 1); // remove setter call mainStLoc.Value = new CompoundAssignmentInstruction( @@ -267,14 +327,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context) { - ILInstruction value = inst.Value; - if (value is Conv conv && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) { - // for compound assignments to small integers, the compiler emits a "conv" instruction - value = conv.Argument; - } else { - conv = null; - } - if (!(value is BinaryNumericInstruction binary)) + if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary)) return false; if (!(binary.Left is LdObj ldobj)) return false; @@ -293,10 +346,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { targetType = ldobj.Type; } - if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) + if (!ValidateCompoundAssign(binary, conv, targetType)) return false; - if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) - return false; // conv does not match binary operation context.Step("compound assignment", inst); inst.ReplaceWith(new CompoundAssignmentInstruction( binary, binary.Left, binary.Right, @@ -310,16 +361,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// --> /// stloc s(stloc l(value)) /// - bool TransformInlineAssignmentLocal(Block block, int i) + bool TransformInlineAssignmentLocal(Block block, int pos) { - var inst = block.Instructions[i] as StLoc; - var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; + var inst = block.Instructions[pos] as StLoc; + var nextInst = block.Instructions.ElementAtOrDefault(pos + 1) as StLoc; if (inst == null || nextInst == null) return false; if (inst.Variable.Kind != VariableKind.StackSlot) return false; Debug.Assert(!inst.Variable.Type.IsSmallIntegerType()); - if (nextInst.Variable.Kind != VariableKind.Local || !nextInst.Value.MatchLdLoc(inst.Variable)) + if (!(nextInst.Variable.Kind == VariableKind.Local || nextInst.Variable.Kind == VariableKind.Parameter)) + return false; + if (!nextInst.Value.MatchLdLoc(inst.Variable)) return false; if (IsImplicitTruncation(inst.Value, nextInst.Variable.Type)) { // 'stloc l' is implicitly truncating the stack value @@ -329,7 +382,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var value = inst.Value; var var = nextInst.Variable; var stackVar = inst.Variable; - block.Instructions.RemoveAt(i); + block.Instructions.RemoveAt(pos); nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value))); return true; } @@ -408,48 +461,49 @@ namespace ICSharpCode.Decompiler.IL.Transforms nextInst.ReplaceWith(new StLoc(inst.Variable, assignment)); return true; } - - /// ldaddress ::= ldelema | ldflda | ldsflda; + /// - /// stloc s(ldaddress) - /// stloc l(ldobj(ldloc s)) - /// stobj(ldloc s, binary.op(ldloc l, ldc.i4 1)) + /// stobj(target, binary.add(stloc l(ldobj(target)), ldc.i4 1)) + /// where target is pure and does not use 'l' /// --> - /// stloc l(compound.op.old(ldobj(ldaddress), ldc.i4 1)) + /// stloc l(compound.op.old(ldobj(target), ldc.i4 1)) /// bool TransformPostIncDecOperatorOnAddress(Block block, int i) { - var inst = block.Instructions[i] as StLoc; - var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; - var stobj = block.Instructions.ElementAtOrDefault(i + 2) as StObj; - if (inst == null || nextInst == null || stobj == null) + if (!(block.Instructions[i] is StObj stobj)) return false; - if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 2) + var binary = UnwrapSmallIntegerConv(stobj.Value, out var conv) as BinaryNumericInstruction; + if (binary == null || !binary.Right.MatchLdcI4(1)) return false; - if (!(inst.Value is LdElema || inst.Value is LdFlda || inst.Value is LdsFlda)) + if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; - ILInstruction target; - IType targetType; - if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdObj(out target, out targetType) || !target.MatchLdLoc(inst.Variable)) + if (!(binary.Left is StLoc stloc)) return false; - if (!stobj.Target.MatchLdLoc(inst.Variable)) + if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot)) return false; - var binary = stobj.Value as BinaryNumericInstruction; - if (binary == null || !binary.Left.MatchLdLoc(nextInst.Variable) || !binary.Right.MatchLdcI4(1) - || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub)) + if (!(stloc.Value is LdObj ldobj)) return false; - context.Step($"TransformPostIncDecOperator", inst); - var assignment = new CompoundAssignmentInstruction(binary, new LdObj(inst.Value, targetType), binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue); - stobj.ReplaceWith(new StLoc(nextInst.Variable, assignment)); - block.Instructions.RemoveAt(i + 1); + if (!SemanticHelper.IsPure(ldobj.Target.Flags)) + return false; + if (!ldobj.Target.Match(stobj.Target).Success) + return false; + if (stloc.Variable.IsUsedWithin(ldobj.Target)) + return false; + IType targetType = ldobj.Type; + if (!ValidateCompoundAssign(binary, conv, targetType)) + return false; + context.Step("TransformPostIncDecOperatorOnAddress", stobj); + block.Instructions[i] = new StLoc(stloc.Variable, new CompoundAssignmentInstruction( + binary, ldobj, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue)); return true; } /// - /// stloc l(ldobj(ldflda(target))) - /// stobj(ldflda(target), binary.op(ldloc l, ldc.i4 1)) + /// stloc l(ldobj(target)) + /// stobj(target, binary.op(ldloc l, ldc.i4 1)) + /// target is pure and does not use 'l' /// --> - /// compound.op.old(ldobj(ldflda(target)), ldc.i4 1) + /// stloc l(compound.op.old(ldobj(target), ldc.i4 1)) /// bool TransformRoslynPostIncDecOperatorOnAddress(Block block, int i) { @@ -457,21 +511,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms var stobj = block.Instructions.ElementAtOrDefault(i + 1) as StObj; if (inst == null || stobj == null) return false; - if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 1) + if (!(inst.Value is LdObj ldobj)) return false; - if (!inst.Value.MatchLdObj(out var loadTarget, out var loadType) || !loadTarget.MatchLdFlda(out var fieldTarget, out var field)) + if (!SemanticHelper.IsPure(ldobj.Target.Flags)) return false; - if (!stobj.Target.MatchLdFlda(out var fieldTarget2, out var field2)) + if (!ldobj.Target.Match(stobj.Target).Success) return false; - if (!fieldTarget.Match(fieldTarget2).Success || !field.Equals(field2)) + if (inst.Variable.IsUsedWithin(ldobj.Target)) return false; - var binary = stobj.Value as BinaryNumericInstruction; - if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1) - || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub)) + var binary = UnwrapSmallIntegerConv(stobj.Value, out var conv) as BinaryNumericInstruction; + if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1)) + return false; + if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false; - context.Step("TransformRoslynPostIncDecOperator", inst); - stobj.ReplaceWith(new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, loadType, CompoundAssignmentType.EvaluatesToOldValue)); - block.Instructions.RemoveAt(i); + var targetType = ldobj.Type; + if (!ValidateCompoundAssign(binary, conv, targetType)) + return false; + context.Step("TransformRoslynPostIncDecOperatorOnAddress", inst); + inst.Value = new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue); + block.Instructions.RemoveAt(i + 1); return true; }