From c676665a613548ea8cf9c113c4c14fd233d246a5 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 19 Apr 2020 03:39:34 +0200 Subject: [PATCH] #1940: Refactor TransformExpressionTrees to support rolling back all changes if the transform fails. --- .../IL/Transforms/TransformExpressionTrees.cs | 593 +++++++++++------- 1 file changed, 364 insertions(+), 229 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 3c93013a2..f5e869224 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -30,6 +30,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms { /// /// Converts LINQ Expression Trees to ILFunctions/ILAst instructions. + /// + /// We build a tree of Func{ILInstruction}s, which are only executed, if the whole transform succeeds. /// public class TransformExpressionTrees : IStatementTransform { @@ -122,9 +124,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (MightBeExpressionTree(instruction, statement)) { var (lambda, type) = ConvertLambda((CallInstruction)instruction); if (lambda != null) { - SetExpressionTreeFlag((ILFunction)lambda, (CallInstruction)instruction); context.Step("Convert Expression Tree", instruction); - instruction.ReplaceWith(lambda); + var newLambda = (ILFunction)lambda(); + SetExpressionTreeFlag(newLambda, (CallInstruction)instruction); + instruction.ReplaceWith(newLambda); return true; } return false; @@ -142,7 +145,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// Converts a Expression.Lambda call into an ILFunction. /// If the conversion fails, null is returned. /// - (ILInstruction, IType) ConvertLambda(CallInstruction instruction) + (Func, IType) ConvertLambda(CallInstruction instruction) { if (instruction.Method.Name != "Lambda" || instruction.Arguments.Count != 2 || instruction.Method.ReturnType.FullName != "System.Linq.Expressions.Expression" || instruction.Method.ReturnType.TypeArguments.Count != 1) return (null, SpecialType.UnknownType); @@ -164,34 +167,49 @@ namespace ICSharpCode.Decompiler.IL.Transforms lambdaStack.Pop(); if (bodyInstruction == null) return (null, SpecialType.UnknownType); - container.ExpectedResultType = bodyInstruction.ResultType; - container.Blocks.Add(new Block() { Instructions = { new Leave(container, bodyInstruction) } }); - // Replace all other usages of the parameter variable - foreach (var mapping in parameterMapping) { - foreach (var load in mapping.Key.LoadInstructions.ToArray()) { - if (load.IsDescendantOf(instruction)) - continue; - load.ReplaceWith(new LdLoc(mapping.Value)); + return (BuildFunction, function.DelegateType); + + ILFunction BuildFunction() + { + lambdaStack.Push(function); + var convertedBody = bodyInstruction(); + lambdaStack.Pop(); + container.ExpectedResultType = convertedBody.ResultType; + container.Blocks.Add(new Block() { Instructions = { new Leave(container, convertedBody) } }); + // Replace all other usages of the parameter variable + foreach (var mapping in parameterMapping) { + foreach (var load in mapping.Key.LoadInstructions.ToArray()) { + if (load.IsDescendantOf(instruction)) + continue; + load.ReplaceWith(new LdLoc(mapping.Value)); + } } + return function; } - return (function, function.DelegateType); } - (ILInstruction, IType) ConvertQuote(CallInstruction invocation) + (Func, IType) ConvertQuote(CallInstruction invocation) { if (invocation.Arguments.Count != 1) return (null, SpecialType.UnknownType); var argument = invocation.Arguments.Single(); if (argument is ILFunction function) { - return (function, function.DelegateType); + return (() => function, function.DelegateType); } else { - var converted = ConvertInstruction(argument); + var (converted, type) = ConvertInstruction(argument); + if (converted == null) + return (converted, type); + return (BuildQuote, type); - if (converted.Item1 is ILFunction lambda && argument is CallInstruction call) { - SetExpressionTreeFlag(lambda, call); - } + ILInstruction BuildQuote() + { + var f = converted(); + if (f is ILFunction lambda && argument is CallInstruction call) { + SetExpressionTreeFlag(lambda, call); + } - return converted; + return f; + } } } @@ -232,22 +250,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - (ILInstruction, IType) ConvertInstruction(ILInstruction instruction, IType typeHint = null) + (Func, IType) ConvertInstruction(ILInstruction instruction, IType typeHint = null) { - var result = Convert(); - if (result.Item1 != null) { - Debug.Assert(result.Item2 != null, "IType must be non-null!"); - Debug.Assert(result.Item1.ResultType == result.Item2.GetStackType(), "StackTypes must match!"); + var (inst, type) = Convert(); + + if (inst == null) + return (null, type); + + ILInstruction DoConvert() + { + var result = inst(); + Debug.Assert(type != null, "IType must be non-null!"); + Debug.Assert(result.ResultType == type.GetStackType(), "StackTypes must match!"); if (typeHint != null) { - var inst = result.Item1; - if (inst.ResultType != typeHint.GetStackType()) { - return (new Conv(inst, typeHint.GetStackType().ToPrimitiveType(), false, typeHint.GetSign()), typeHint); + if (result.ResultType != typeHint.GetStackType()) { + return new Conv(result, typeHint.GetStackType().ToPrimitiveType(), false, typeHint.GetSign()); } } + return result; } - return result; + return (DoConvert, typeHint ?? type); - (ILInstruction, IType) Convert() { + (Func, IType) Convert() { switch (instruction) { case CallInstruction invocation: if (invocation.Method.DeclaringType.FullName != "System.Linq.Expressions.Expression") @@ -348,19 +372,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return (null, SpecialType.UnknownType); case ILFunction function: - if (function.Kind == ILFunctionKind.ExpressionTree) { - function.DelegateType = UnwrapExpressionTree(function.DelegateType); - function.Kind = ILFunctionKind.Delegate; + ILFunction ApplyChangesToILFunction() + { + if (function.Kind == ILFunctionKind.ExpressionTree) { + function.DelegateType = UnwrapExpressionTree(function.DelegateType); + function.Kind = ILFunctionKind.Delegate; + } + return function; } - return (function, function.DelegateType); + return (ApplyChangesToILFunction, function.DelegateType); case LdLoc ldloc: if (IsExpressionTreeParameter(ldloc.Variable)) { // Replace an already mapped parameter with the actual ILVariable, // we generated earlier. if (parameterMapping.TryGetValue(ldloc.Variable, out var v)) { if (typeHint.SkipModifiers() is ByReferenceType && !v.Type.IsByRefLike) - return (new LdLoca(v), typeHint); - return (new LdLoc(v), v.Type); + return (() => new LdLoca(v), typeHint); + return (() => new LdLoc(v), v.Type); } // This is a parameter variable from an outer scope. // We can't replace these variables just yet, because the transform works backwards. @@ -368,9 +396,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms // so our transform can continue normally. // Later, we will replace all references to unmapped variables, // with references to mapped parameters. - if (ldloc.Variable.IsSingleDefinition && ldloc.Variable.StoreInstructions[0] is ILInstruction inst) { - if (MatchParameterVariableAssignment(inst, out _, out var type, out _)) - return (new ExpressionTreeCast(type, ldloc, false), type); + if (ldloc.Variable.IsSingleDefinition && ldloc.Variable.StoreInstructions[0] is ILInstruction instr) { + if (MatchParameterVariableAssignment(instr, out _, out var t, out _)) + return (() => new ExpressionTreeCast(t, ldloc, false), t); } } return (null, SpecialType.UnknownType); @@ -392,7 +420,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return delegateType; } - (ILInstruction, IType) ConvertArrayIndex(CallInstruction invocation) + (Func, IType) ConvertArrayIndex(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -403,26 +431,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (!MatchArgumentList(invocation.Arguments[1], out var arguments)) arguments = new[] { invocation.Arguments[1] }; - for (int i = 0; i < arguments.Count; i++) { - var (converted, indexType) = ConvertInstruction(arguments[i]); - if (converted == null) - return (null, SpecialType.UnknownType); - arguments[i] = converted; + + ILInstruction Convert() + { + Func[] toBeConverted = new Func[arguments.Count]; + for (int i = 0; i < arguments.Count; i++) { + var (converted, indexType) = ConvertInstruction(arguments[i]); + if (converted == null) + return null; + toBeConverted[i] = converted; + } + return new LdObj(new LdElema(type.ElementType, array(), toBeConverted.SelectArray(f => f())), type.ElementType); } - return (new LdObj(new LdElema(type.ElementType, array, arguments.ToArray()), type.ElementType), type.ElementType); + return (Convert, type.ElementType); } - (ILInstruction, IType) ConvertArrayLength(CallInstruction invocation) + (Func, IType) ConvertArrayLength(CallInstruction invocation) { if (invocation.Arguments.Count != 1) return (null, SpecialType.UnknownType); - var (converted, arrayType) = ConvertInstruction(invocation.Arguments[0]); + var (converted, _) = ConvertInstruction(invocation.Arguments[0]); if (converted == null) return (null, SpecialType.UnknownType); - return (new LdLen(StackType.I4, converted), context.TypeSystem.FindType(KnownTypeCode.Int32)); + return (() => new LdLen(StackType.I4, converted()), context.TypeSystem.FindType(KnownTypeCode.Int32)); } - (ILInstruction, IType) ConvertBinaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, bool? isChecked = null) + (Func, IType) ConvertBinaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, bool? isChecked = null) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); @@ -442,12 +476,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!rightType.Equals(leftType)) return (null, SpecialType.UnknownType); } - return (new BinaryNumericInstruction(op, left, right, isChecked == true, leftType.GetSign()), leftType); + return (() => new BinaryNumericInstruction(op, left(), right(), isChecked == true, leftType.GetSign()), leftType); case 3: if (!MatchGetMethodFromHandle(invocation.Arguments[2], out method)) return (null, SpecialType.UnknownType); - return (new Call((IMethod)method) { - Arguments = { left, right } + return (() => new Call((IMethod)method) { + Arguments = { left(), right() } }, method.ReturnType); case 4: if (!invocation.Arguments[2].MatchLdcI4(out var isLifted)) @@ -456,15 +490,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (isLifted != 0) method = CSharpOperators.LiftUserDefinedOperator((IMethod)method); - return (new Call((IMethod)method) { - Arguments = { left, right } + return (() => new Call((IMethod)method) { + Arguments = { left(), right() } }, method.ReturnType); default: return (null, SpecialType.UnknownType); } } - (ILInstruction, IType) ConvertBind(CallInstruction invocation, ILVariable targetVariable) + (Func, IType) ConvertBind(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -478,19 +512,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms } switch (member) { case IMethod method: - return (new Call(method) { Arguments = { new LdLoc(targetVariable), value } }, method.ReturnType); + return (targetVariable => new Call(method) { Arguments = { new LdLoc(targetVariable), value() } }, method.ReturnType); case IField field: - return (new StObj(new LdFlda(new LdLoc(targetVariable), (IField)member), value, member.ReturnType), field.ReturnType); + return (targetVariable => new StObj(new LdFlda(new LdLoc(targetVariable), (IField)member), value(), member.ReturnType), field.ReturnType); } return (null, SpecialType.UnknownType); } - (ILInstruction, IType) ConvertCall(CallInstruction invocation) + (Func, IType) ConvertCall(CallInstruction invocation) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); IList arguments = null; - ILInstruction target = null; + Func targetConverter = null; IType targetType = null; if (MatchGetMethodFromHandle(invocation.Arguments[0], out var member)) { // static method @@ -502,36 +536,42 @@ namespace ICSharpCode.Decompiler.IL.Transforms arguments = new List(invocation.Arguments.Skip(2)); } if (!invocation.Arguments[0].MatchLdNull()) { - (target, targetType) = ConvertInstruction(invocation.Arguments[0]); - if (target == null) + (targetConverter, targetType) = ConvertInstruction(invocation.Arguments[0]); + if (targetConverter == null) return (null, SpecialType.UnknownType); } } if (arguments == null) return (null, SpecialType.UnknownType); IMethod method = (IMethod)member; - if (!ConvertCallArguments(arguments, method)) + var convertedArguments = ConvertCallArguments(arguments, method); + if (convertedArguments == null) return (null, SpecialType.UnknownType); if (method.FullName == "System.Reflection.MethodInfo.CreateDelegate" && method.Parameters.Count == 2) { - if (!MatchGetMethodFromHandle(target, out var targetMethod)) + if (!MatchGetMethodFromHandle(UnpackConstant(invocation.Arguments[0]), out var targetMethod)) return (null, SpecialType.UnknownType); - if (!MatchGetTypeFromHandle(arguments[0], out var delegateType)) + if (!MatchGetTypeFromHandle(UnpackConstant(arguments[0]), out var delegateType)) return (null, SpecialType.UnknownType); - return (new NewObj(delegateType.GetConstructors().Single()) { - Arguments = { arguments[1], new LdFtn((IMethod)targetMethod) } + return (() => new NewObj(delegateType.GetConstructors().Single()) { + Arguments = { convertedArguments[1](), new LdFtn((IMethod)targetMethod) } }, delegateType); } - CallInstruction call; - if (method.IsAbstract || method.IsVirtual || method.IsOverride) { - call = new CallVirt(method); - } else { - call = new Call(method); - } - if (target != null) { - call.Arguments.Add(PrepareCallTarget(method.DeclaringType, target, targetType)); + + CallInstruction BuildCall() + { + CallInstruction call; + if (method.IsAbstract || method.IsVirtual || method.IsOverride) { + call = new CallVirt(method); + } else { + call = new Call(method); + } + if (targetConverter != null) { + call.Arguments.Add(PrepareCallTarget(method.DeclaringType, targetConverter(), targetType)); + } + call.Arguments.AddRange(convertedArguments.Select(f => f())); + return call; } - call.Arguments.AddRange(arguments); - return (call, method.ReturnType); + return (BuildCall, method.ReturnType); } ILInstruction PrepareCallTarget(IType expectedType, ILInstruction target, IType targetType) @@ -556,20 +596,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - bool ConvertCallArguments(IList arguments, IMethod method) + ILInstruction UnpackConstant(ILInstruction inst) + { + if (!(inst is CallInstruction call && call.Method.FullName == "System.Linq.Expressions.Expression.Constant" && call.Arguments.Count == 2)) + return inst; + return call.Arguments[0]; + } + + Func[] ConvertCallArguments(IList arguments, IMethod method) { + var converted = new Func[arguments.Count]; Debug.Assert(arguments.Count == method.Parameters.Count); for (int i = 0; i < arguments.Count; i++) { var expectedType = method.Parameters[i].Type; var argument = ConvertInstruction(arguments[i], expectedType).Item1; if (argument == null) - return false; - arguments[i] = argument; + return null; + converted[i] = argument; } - return true; + return converted; } - (ILInstruction, IType) ConvertCast(CallInstruction invocation, bool isChecked) + (Func, IType) ConvertCast(CallInstruction invocation, bool isChecked) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); @@ -580,10 +628,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (exprType.IsSmallIntegerType() && targetType.IsKnownType(KnownTypeCode.Int32)) return (expr, targetType); - return (new ExpressionTreeCast(targetType, expr, isChecked), targetType); + return (() => new ExpressionTreeCast(targetType, expr(), isChecked), targetType); } - (ILInstruction, IType) ConvertCoalesce(CallInstruction invocation) + (Func, IType) ConvertCoalesce(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -604,12 +652,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { targetType = fallbackInstType; } - return (new NullCoalescingInstruction(kind, trueInst, fallbackInst) { + return (() => new NullCoalescingInstruction(kind, trueInst(), fallbackInst()) { UnderlyingResultType = trueInstTypeNonNullable.GetStackType() }, targetType); } - (ILInstruction, IType) ConvertComparison(CallInstruction invocation, ComparisonKind kind) + (Func, IType) ConvertComparison(CallInstruction invocation, ComparisonKind kind) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); @@ -623,11 +671,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (isLifted != 0) { method = CSharpOperators.LiftUserDefinedOperator((IMethod)method); } - return (new Call((IMethod)method) { Arguments = { left, right } }, method.ReturnType); + return (() => new Call((IMethod)method) { Arguments = { left(), right() } }, method.ReturnType); } var rr = resolver.ResolveBinaryOperator(kind.ToBinaryOperatorType(), new ResolveResult(leftType), new ResolveResult(rightType)) as OperatorResolveResult; if (rr != null && !rr.IsError && rr.UserDefinedOperatorMethod != null) { - return (new Call(rr.UserDefinedOperatorMethod) { Arguments = { left, right } }, rr.UserDefinedOperatorMethod.ReturnType); + return (() => new Call(rr.UserDefinedOperatorMethod) { Arguments = { left(), right() } }, rr.UserDefinedOperatorMethod.ReturnType); } if (leftType.IsKnownType(KnownTypeCode.String) && rightType.IsKnownType(KnownTypeCode.String)) { IMethod operatorMethod; @@ -645,15 +693,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms default: return (null, SpecialType.UnknownType); } - return (new Call(operatorMethod) { Arguments = { left, right } }, operatorMethod.ReturnType); + return (() => new Call(operatorMethod) { Arguments = { left(), right() } }, operatorMethod.ReturnType); } var resultType = context.TypeSystem.FindType(KnownTypeCode.Boolean); var lifting = NullableType.IsNullable(leftType) ? ComparisonLiftingKind.CSharp : ComparisonLiftingKind.None; var utype = NullableType.GetUnderlyingType(leftType); - return (new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), left, right), resultType); + return (() => new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), left(), right()), resultType); } - (ILInstruction, IType) ConvertCondition(CallInstruction invocation) + (Func, IType) ConvertCondition(CallInstruction invocation) { if (invocation.Arguments.Count != 3) return (null, SpecialType.UnknownType); @@ -668,23 +716,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (!trueInstType.Equals(falseInstType)) return (null, SpecialType.UnknownType); - return (new IfInstruction(condition, trueInst, falseInst), trueInstType); + return (() => new IfInstruction(condition(), trueInst(), falseInst()), trueInstType); } - (ILInstruction, IType) ConvertConstant(CallInstruction invocation) + (Func, IType) ConvertConstant(CallInstruction invocation) { if (!MatchConstantCall(invocation, out var value, out var type)) return (null, SpecialType.UnknownType); if (value.MatchBox(out var arg, out var boxType)) { if (boxType.Kind == TypeKind.Enum || boxType.IsKnownType(KnownTypeCode.Boolean)) - return (new ExpressionTreeCast(boxType, ConvertValue(arg, invocation), false), boxType); - value = ConvertValue(arg, invocation); - return (value, type); + return (() => new ExpressionTreeCast(boxType, ConvertValue(arg, invocation), false), boxType); + return (() => ConvertValue(arg, invocation), type); } - return (ConvertValue(value, invocation), type); + return (() => ConvertValue(value, invocation), type); } - (ILInstruction, IType) ConvertElementInit(CallInstruction invocation) + (Func, IType) ConvertElementInit(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -692,77 +739,97 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (!MatchArgumentList(invocation.Arguments[1], out var arguments)) return (null, SpecialType.UnknownType); - CallInstruction call = new Call((IMethod)member); + var args = new Func[arguments.Count]; for (int i = 0; i < arguments.Count; i++) { - ILInstruction arg = ConvertInstruction(arguments[i]).Item1; + var arg = ConvertInstruction(arguments[i]).Item1; if (arg == null) return (null, SpecialType.UnknownType); - arguments[i] = arg; + args[i] = arg; } - call.Arguments.AddRange(arguments); - return (call, member.ReturnType); + + ILInstruction BuildCall() + { + CallInstruction call = new Call((IMethod)member); + call.Arguments.AddRange(args.Select(f => f())); + return call; + } + return (BuildCall, member.ReturnType); } - (ILInstruction, IType) ConvertField(CallInstruction invocation, IType typeHint) + (Func, IType) ConvertField(CallInstruction invocation, IType typeHint) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); - ILInstruction target = null; + Func targetConverter = null; if (!invocation.Arguments[0].MatchLdNull()) { - target = ConvertInstruction(invocation.Arguments[0]).Item1; - if (target == null) + targetConverter = ConvertInstruction(invocation.Arguments[0]).Item1; + if (targetConverter == null) return (null, SpecialType.UnknownType); } if (!MatchGetFieldFromHandle(invocation.Arguments[1], out var member)) return (null, SpecialType.UnknownType); IType type = member.ReturnType; - ILInstruction inst; - if (target == null) { - inst = new LdsFlda((IField)member); - } else { - if (member.DeclaringType.IsReferenceType == true) { - inst = new LdFlda(target, (IField)member); + if (typeHint.SkipModifiers() is ByReferenceType && !member.ReturnType.IsByRefLike) { + type = typeHint; + } + return (BuildField, type); + + ILInstruction BuildField() + { + ILInstruction inst; + if (targetConverter == null) { + inst = new LdsFlda((IField)member); } else { - inst = new LdFlda(new AddressOf(target, member.DeclaringType), (IField)member); + var target = targetConverter(); + if (member.DeclaringType.IsReferenceType == true) { + inst = new LdFlda(target, (IField)member); + } else { + inst = new LdFlda(new AddressOf(target, member.DeclaringType), (IField)member); + } } + if (!(typeHint.SkipModifiers() is ByReferenceType && !member.ReturnType.IsByRefLike)) { + inst = new LdObj(inst, member.ReturnType); + } + return inst; } - if (typeHint.SkipModifiers() is ByReferenceType brt && !member.ReturnType.IsByRefLike) { - type = typeHint; - } else { - inst = new LdObj(inst, member.ReturnType); - } - return (inst, type); } - (ILInstruction, IType) ConvertInvoke(CallInstruction invocation) + (Func, IType) ConvertInvoke(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); - var (target, targetType) = ConvertInstruction(invocation.Arguments[0]); - if (target == null) + var (targetConverter, targetType) = ConvertInstruction(invocation.Arguments[0]); + if (targetConverter == null) return (null, SpecialType.UnknownType); var invokeMethod = targetType.GetDelegateInvokeMethod(); if (invokeMethod == null) return (null, SpecialType.UnknownType); if (!MatchArgumentList(invocation.Arguments[1], out var arguments)) return (null, SpecialType.UnknownType); - if (!ConvertCallArguments(arguments, invokeMethod)) + var convertedArguments = ConvertCallArguments(arguments, invokeMethod); + if (convertedArguments == null) return (null, SpecialType.UnknownType); - var call = new CallVirt(invokeMethod); - call.Arguments.Add(target); - call.Arguments.AddRange(arguments); - return (call, invokeMethod.ReturnType); + + ILInstruction BuildCall() + { + var call = new CallVirt(invokeMethod); + call.Arguments.Add(targetConverter()); + call.Arguments.AddRange(convertedArguments.Select(f => f())); + return call; + } + return (BuildCall, invokeMethod.ReturnType); } - (ILInstruction, IType) ConvertListInit(CallInstruction invocation) + (Func, IType) ConvertListInit(CallInstruction invocation) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); - var newObj = ConvertInstruction(invocation.Arguments[0]).Item1 as NewObj; + var newObj = ConvertInstruction(invocation.Arguments[0]).Item1; if (newObj == null) return (null, SpecialType.UnknownType); - IList arguments = null; - ILFunction function = lambdaStack.Peek(); + if (!MatchNew((CallInstruction)invocation.Arguments[0], out var ctor)) + return (null, SpecialType.UnknownType); + IList arguments; if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var member)) { if (!MatchArgumentList(invocation.Arguments[1], out arguments)) return (null, SpecialType.UnknownType); @@ -772,29 +839,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (arguments == null || arguments.Count == 0) return (null, SpecialType.UnknownType); - var initializer = function.RegisterVariable(VariableKind.InitializerTarget, newObj.Method.DeclaringType); + Func[] convertedArguments = new Func[arguments.Count]; for (int i = 0; i < arguments.Count; i++) { - ILInstruction arg; if (arguments[i] is CallInstruction elementInit && elementInit.Method.FullName == "System.Linq.Expressions.Expression.ElementInit") { - arg = ConvertElementInit(elementInit).Item1; + var arg = ConvertElementInit(elementInit).Item1; if (arg == null) return (null, SpecialType.UnknownType); - ((CallInstruction)arg).Arguments.Insert(0, new LdLoc(initializer)); + + convertedArguments[i] = v => { var a = arg(); ((CallInstruction)a).Arguments.Insert(0, new LdLoc(v)); return a; }; } else { - arg = ConvertInstruction(arguments[i]).Item1; + var arg = ConvertInstruction(arguments[i]).Item1; if (arg == null) return (null, SpecialType.UnknownType); + convertedArguments[i] = v => arg(); } - arguments[i] = arg; } - var initializerBlock = new Block(BlockKind.CollectionInitializer); - initializerBlock.FinalInstruction = new LdLoc(initializer); - initializerBlock.Instructions.Add(new StLoc(initializer, newObj)); - initializerBlock.Instructions.AddRange(arguments); - return (initializerBlock, initializer.Type); + + Block BuildBlock() + { + var initializerBlock = new Block(BlockKind.CollectionInitializer); + + ILFunction function = lambdaStack.Peek(); + var initializer = function.RegisterVariable(VariableKind.InitializerTarget, ctor.DeclaringType); + initializerBlock.FinalInstruction = new LdLoc(initializer); + initializerBlock.Instructions.Add(new StLoc(initializer, newObj())); + initializerBlock.Instructions.AddRange(convertedArguments.Select(f => f(initializer))); + return initializerBlock; + } + return (BuildBlock, ctor.DeclaringType); } - (ILInstruction, IType) ConvertLogicOperator(CallInstruction invocation, bool and) + (Func, IType) ConvertLogicOperator(CallInstruction invocation, bool and) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); @@ -808,12 +883,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms switch (invocation.Arguments.Count) { case 2: var resultType = context.TypeSystem.FindType(KnownTypeCode.Boolean); - return (and ? IfInstruction.LogicAnd(left, right) : IfInstruction.LogicOr(left, right), resultType); + return (() => and ? IfInstruction.LogicAnd(left(), right()) : IfInstruction.LogicOr(left(), right()), resultType); case 3: if (!MatchGetMethodFromHandle(invocation.Arguments[2], out method)) return (null, SpecialType.UnknownType); - return (new Call((IMethod)method) { - Arguments = { left, right } + return (() => new Call((IMethod)method) { + Arguments = { left(), right() } }, method.ReturnType); case 4: if (!invocation.Arguments[2].MatchLdcI4(out var isLifted)) @@ -822,46 +897,59 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (isLifted != 0) method = CSharpOperators.LiftUserDefinedOperator((IMethod)method); - return (new Call((IMethod)method) { - Arguments = { left, right } + return (() => new Call((IMethod)method) { + Arguments = { left(), right() } }, method.ReturnType); default: return (null, SpecialType.UnknownType); } } - (ILInstruction, IType) ConvertMemberInit(CallInstruction invocation) + (Func, IType) ConvertMemberInit(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); - var newObj = ConvertInstruction(invocation.Arguments[0]).Item1 as NewObj; + var newObj = ConvertInstruction(invocation.Arguments[0]).Item1; if (newObj == null) return (null, SpecialType.UnknownType); + if (!MatchNew((CallInstruction)invocation.Arguments[0], out var ctor)) + return (null, SpecialType.UnknownType); if (!MatchArgumentList(invocation.Arguments[1], out var arguments)) return (null, SpecialType.UnknownType); if (arguments == null || arguments.Count == 0) return (null, SpecialType.UnknownType); - var function = lambdaStack.Peek(); - var initializer = function.RegisterVariable(VariableKind.InitializerTarget, newObj.Method.DeclaringType); + + Func[] convertedArguments = new Func[arguments.Count]; for (int i = 0; i < arguments.Count; i++) { - ILInstruction arg; + Func arg; if (arguments[i] is CallInstruction bind && bind.Method.FullName == "System.Linq.Expressions.Expression.Bind") { - arg = ConvertBind(bind, initializer).Item1; + arg = ConvertBind(bind).Item1; if (arg == null) return (null, SpecialType.UnknownType); } else { return (null, SpecialType.UnknownType); } - arguments[i] = arg; + convertedArguments[i] = arg; + } + + ILInstruction BuildBlock() + { + var function = lambdaStack.Peek(); + var initializer = function.RegisterVariable(VariableKind.InitializerTarget, ctor.DeclaringType); + + var initializerBlock = new Block(BlockKind.CollectionInitializer); + initializerBlock.FinalInstruction = new LdLoc(initializer); + initializerBlock.Instructions.Add(new StLoc(initializer, newObj())); + initializerBlock.Instructions.AddRange(convertedArguments.Select(f => f(initializer))); + + return initializerBlock; } - var initializerBlock = new Block(BlockKind.CollectionInitializer); - initializerBlock.FinalInstruction = new LdLoc(initializer); - initializerBlock.Instructions.Add(new StLoc(initializer, newObj)); - initializerBlock.Instructions.AddRange(arguments); - return (initializerBlock, initializer.Type); + + + return (BuildBlock, ctor.DeclaringType); } - (ILInstruction, IType) ConvertNewArrayBounds(CallInstruction invocation) + (Func, IType) ConvertNewArrayBounds(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -871,17 +959,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (arguments.Count == 0) return (null, SpecialType.UnknownType); - var indices = new ILInstruction[arguments.Count]; + var indices = new Func[arguments.Count]; for (int i = 0; i < arguments.Count; i++) { var index = ConvertInstruction(arguments[i]).Item1; if (index == null) return (null, SpecialType.UnknownType); indices[i] = index; } - return (new NewArr(type, indices), new ArrayType(context.TypeSystem, type, arguments.Count)); + return (() => new NewArr(type, indices.SelectArray(f => f())), new ArrayType(context.TypeSystem, type, arguments.Count)); } - (ILInstruction, IType) ConvertNewArrayInit(CallInstruction invocation) + (Func, IType) ConvertNewArrayInit(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -891,67 +979,107 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); ArrayType arrayType = new ArrayType(context.BlockContext.TypeSystem, type); if (arguments.Count == 0) - return (new NewArr(type, new LdcI4(0)), arrayType); - var block = (Block)invocation.Arguments[1]; - var function = lambdaStack.Peek(); - var variable = function.RegisterVariable(VariableKind.InitializerTarget, arrayType); - Block initializer = new Block(BlockKind.ArrayInitializer); - int i = 0; - initializer.Instructions.Add(new StLoc(variable, new NewArr(type, new LdcI4(arguments.Count)))); - foreach (var item in arguments) { + return (() => new NewArr(type, new LdcI4(0)), arrayType); + var convertedArguments = new Func[arguments.Count]; + for (int i = 0; i < arguments.Count; i++) { + ILInstruction item = arguments[i]; var value = ConvertInstruction(item).Item1; if (value == null) return (null, SpecialType.UnknownType); - initializer.Instructions.Add(new StObj(new LdElema(type, new LdLoc(variable), new LdcI4(i)), value, type)); + convertedArguments[i] = value; } - initializer.FinalInstruction = new LdLoc(variable); - return (initializer, variable.Type); + + ILInstruction BuildInitializer() + { + var block = (Block)invocation.Arguments[1]; + var function = lambdaStack.Peek(); + var variable = function.RegisterVariable(VariableKind.InitializerTarget, arrayType); + Block initializer = new Block(BlockKind.ArrayInitializer); + initializer.Instructions.Add(new StLoc(variable, new NewArr(type, new LdcI4(convertedArguments.Length)))); + for (int i = 0; i < convertedArguments.Length; i++) { + initializer.Instructions.Add(new StObj(new LdElema(type, new LdLoc(variable), new LdcI4(i)), convertedArguments[i](), type)); + } + initializer.FinalInstruction = new LdLoc(variable); + return initializer; + } + + return (BuildInitializer, arrayType); } - (ILInstruction, IType) ConvertNewObject(CallInstruction invocation) + bool MatchNew(CallInstruction invocation, out IMethod ctor) + { + ctor = null; + if (invocation.Method.Name != "New") + return false; + switch (invocation.Arguments.Count) { + case 1: + if (MatchGetTypeFromHandle(invocation.Arguments[0], out var type)) { + ctor = type.GetConstructors(c => c.Parameters.Count == 0).FirstOrDefault(); + return ctor != null; + } + if (MatchGetConstructorFromHandle(invocation.Arguments[0], out var member)) { + ctor = (IMethod)member; + return true; + } + return false; + case 2: + case 3: + if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member)) + return false; + ctor = (IMethod)member; + return true; + default: + return false; + } + } + + (Func, IType) ConvertNewObject(CallInstruction invocation) { - IMember member; - IList arguments; - NewObj newObj; switch (invocation.Arguments.Count) { case 1: if (MatchGetTypeFromHandle(invocation.Arguments[0], out var type)) { var ctor = type.GetConstructors(c => c.Parameters.Count == 0).FirstOrDefault(); if (ctor == null) return (null, SpecialType.UnknownType); - return (new NewObj(ctor), type); + return (() => new NewObj(ctor), type); } - if (MatchGetConstructorFromHandle(invocation.Arguments[0], out member)) { - return (new NewObj((IMethod)member), member.DeclaringType); + if (MatchGetConstructorFromHandle(invocation.Arguments[0], out var member)) { + return (() => new NewObj((IMethod)member), member.DeclaringType); } return (null, SpecialType.UnknownType); case 2: if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member)) return (null, SpecialType.UnknownType); - if (!MatchArgumentList(invocation.Arguments[1], out arguments)) + if (!MatchArgumentList(invocation.Arguments[1], out var arguments)) return (null, SpecialType.UnknownType); IMethod method = (IMethod)member; - if (!ConvertCallArguments(arguments, method)) + Func[] convertedArguments = ConvertCallArguments(arguments, method); + if (convertedArguments == null) return (null, SpecialType.UnknownType); - newObj = new NewObj(method); - newObj.Arguments.AddRange(arguments); - return (newObj, member.DeclaringType); + return (() => BuildNewObj(method, convertedArguments), member.DeclaringType); case 3: if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member)) return (null, SpecialType.UnknownType); if (!MatchArgumentList(invocation.Arguments[1], out arguments)) return (null, SpecialType.UnknownType); method = (IMethod)member; - if (!ConvertCallArguments(arguments, method)) + convertedArguments = ConvertCallArguments(arguments, method); + if (convertedArguments == null) return (null, SpecialType.UnknownType); - newObj = new NewObj(method); - newObj.Arguments.AddRange(arguments); - return (newObj, member.DeclaringType); + return (() => BuildNewObj(method, convertedArguments), member.DeclaringType); } + + ILInstruction BuildNewObj(IMethod method, Func[] args) + { + var newObj = new NewObj(method); + newObj.Arguments.AddRange(args.Select(f => f())); + return newObj; + } + return (null, SpecialType.UnknownType); } - (ILInstruction, IType) ConvertNotOperator(CallInstruction invocation) + (Func, IType) ConvertNotOperator(CallInstruction invocation) { if (invocation.Arguments.Count < 1) return (null, SpecialType.UnknownType); @@ -960,27 +1088,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); switch (invocation.Arguments.Count) { case 1: - return (argumentType.IsKnownType(KnownTypeCode.Boolean) ? Comp.LogicNot(argument) : (ILInstruction)new BitNot(argument), argumentType); + return (() => argumentType.IsKnownType(KnownTypeCode.Boolean) ? Comp.LogicNot(argument()) : (ILInstruction)new BitNot(argument()), argumentType); case 2: if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var method)) return (null, SpecialType.UnknownType); - return (new Call((IMethod)method) { - Arguments = { argument } + return (() => new Call((IMethod)method) { + Arguments = { argument() } }, method.ReturnType); default: return (null, SpecialType.UnknownType); } } - (ILInstruction, IType) ConvertProperty(CallInstruction invocation) + (Func, IType) ConvertProperty(CallInstruction invocation) { if (invocation.Arguments.Count < 2) return (null, SpecialType.UnknownType); - ILInstruction target = null; + Func targetConverter = null; IType targetType = null; if (!invocation.Arguments[0].MatchLdNull()) { - (target, targetType) = ConvertInstruction(invocation.Arguments[0]); - if (target == null) + (targetConverter, targetType) = ConvertInstruction(invocation.Arguments[0]); + if (targetConverter == null) return (null, SpecialType.UnknownType); } if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var member)) @@ -988,24 +1116,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms IList arguments; if (invocation.Arguments.Count != 3 || !MatchArgumentList(invocation.Arguments[2], out arguments)) { arguments = new List(); - } else { - if (!ConvertCallArguments(arguments, (IMethod)member)) - return (null, SpecialType.UnknownType); - } - CallInstruction call; - if (member.IsAbstract || member.IsVirtual || member.IsOverride) { - call = new CallVirt((IMethod)member); - } else { - call = new Call((IMethod)member); } - if (target != null) { - call.Arguments.Add(PrepareCallTarget(member.DeclaringType, target, targetType)); + var convertedArguments = ConvertCallArguments(arguments, (IMethod)member); + if (convertedArguments == null) + return (null, SpecialType.UnknownType); + ILInstruction BuildProperty() + { + CallInstruction call; + if (member.IsAbstract || member.IsVirtual || member.IsOverride) { + call = new CallVirt((IMethod)member); + } else { + call = new Call((IMethod)member); + } + if (targetConverter != null) { + call.Arguments.Add(PrepareCallTarget(member.DeclaringType, targetConverter(), targetType)); + } + call.Arguments.AddRange(convertedArguments.Select(f => f())); + return call; } - call.Arguments.AddRange(arguments); - return (call, member.ReturnType); + return (BuildProperty, member.ReturnType); } - (ILInstruction, IType) ConvertTypeAs(CallInstruction invocation) + (Func, IType) ConvertTypeAs(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -1014,16 +1146,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); if (converted == null) return (null, SpecialType.UnknownType); - - ILInstruction inst = new IsInst(converted, type); - // We must follow ECMA-335, III.4.6: - // If typeTok is a nullable type, Nullable, it is interpreted as "boxed" T. - if (type.IsKnownType(KnownTypeCode.NullableOfT)) - inst = new UnboxAny(inst, type); - return (inst, type); + ILInstruction BuildTypeAs() + { + ILInstruction inst = new IsInst(converted(), type); + // We must follow ECMA-335, III.4.6: + // If typeTok is a nullable type, Nullable, it is interpreted as "boxed" T. + if (type.IsKnownType(KnownTypeCode.NullableOfT)) + inst = new UnboxAny(inst, type); + return inst; + } + return (BuildTypeAs, type); } - (ILInstruction, IType) ConvertTypeIs(CallInstruction invocation) + (Func, IType) ConvertTypeIs(CallInstruction invocation) { if (invocation.Arguments.Count != 2) return (null, SpecialType.UnknownType); @@ -1032,11 +1167,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, SpecialType.UnknownType); var resultType = context.TypeSystem.FindType(KnownTypeCode.Boolean); if (converted != null) - return (new Comp(ComparisonKind.Inequality, Sign.None, new IsInst(converted, type), new LdNull()), resultType); + return (() => new Comp(ComparisonKind.Inequality, Sign.None, new IsInst(converted(), type), new LdNull()), resultType); return (null, SpecialType.UnknownType); } - (ILInstruction, IType) ConvertUnaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, bool? isChecked = null) + (Func, IType) ConvertUnaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, bool? isChecked = null) { if (invocation.Arguments.Count < 1) return (null, SpecialType.UnknownType); @@ -1046,7 +1181,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms switch (invocation.Arguments.Count) { case 1: ILInstruction left; - switch (argument.ResultType) { + switch (argumentType.GetStackType()) { case StackType.I4: left = new LdcI4(0); break; @@ -1065,12 +1200,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms default: return (null, SpecialType.UnknownType); } - return (new BinaryNumericInstruction(op, left, argument, isChecked == true, argumentType.GetSign()), argumentType); + return (() => new BinaryNumericInstruction(op, left, argument(), isChecked == true, argumentType.GetSign()), argumentType); case 2: if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var method)) return (null, SpecialType.UnknownType); - return (new Call((IMethod)method) { - Arguments = { argument } + return (() => new Call((IMethod)method) { + Arguments = { argument() } }, method.ReturnType); } return (null, SpecialType.UnknownType); @@ -1082,11 +1217,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms case LdLoc ldloc: if (IsExpressionTreeParameter(ldloc.Variable)) { if (!parameterMapping.TryGetValue(ldloc.Variable, out var v)) - return ldloc; + return ldloc.Clone(); if (context is CallInstruction parentCall && parentCall.Method.FullName == "System.Linq.Expressions.Expression.Call" && v.StackType.IsIntegerType()) - return new LdLoca(v); + return new LdLoca(v).WithILRange(ldloc); return null; } else if (IsClosureReference(ldloc.Variable)) { if (ldloc.Variable.Kind == VariableKind.Local) {