using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.Decompiler.Ast.Transforms; using ICSharpCode.Decompiler.ILAst; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.Utils; using Mono.Cecil; using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.Ast { using Ast = ICSharpCode.NRefactory.CSharp; using Cecil = Mono.Cecil; public class AstMethodBodyBuilder { MethodDefinition methodDef; TypeSystem typeSystem; DecompilerContext context; HashSet localVariablesToDefine = new HashSet(); // local variables that are missing a definition /// /// Creates the body for the method definition. /// /// Method definition to decompile. /// Decompilation context. /// Parameter declarations of the method being decompiled. /// These are used to update the parameter names when the decompiler generates names for the parameters. /// Local variables storage that will be filled/updated with the local variables. /// Block for the method body public static BlockStatement CreateMethodBody(MethodDefinition methodDef, DecompilerContext context, IEnumerable parameters = null, ConcurrentDictionary> localVariables = null) { if (localVariables == null) localVariables = new ConcurrentDictionary>(); MethodDefinition oldCurrentMethod = context.CurrentMethod; Debug.Assert(oldCurrentMethod == null || oldCurrentMethod == methodDef); context.CurrentMethod = methodDef; try { AstMethodBodyBuilder builder = new AstMethodBodyBuilder(); builder.methodDef = methodDef; builder.context = context; builder.typeSystem = methodDef.Module.TypeSystem; if (Debugger.IsAttached) { return builder.CreateMethodBody(parameters, localVariables); } else { try { return builder.CreateMethodBody(parameters, localVariables); } catch (OperationCanceledException) { throw; } catch (Exception ex) { throw new ICSharpCode.Decompiler.DecompilerException(methodDef, ex); } } } finally { context.CurrentMethod = oldCurrentMethod; } } public BlockStatement CreateMethodBody(IEnumerable parameters, ConcurrentDictionary> localVariables) { if (methodDef.Body == null) return null; if (localVariables == null) throw new ArgumentException("localVariables must be instantiated"); context.CancellationToken.ThrowIfCancellationRequested(); ILBlock ilMethod = new ILBlock(); ILAstBuilder astBuilder = new ILAstBuilder(); ilMethod.Body = astBuilder.Build(methodDef, true); context.CancellationToken.ThrowIfCancellationRequested(); ILAstOptimizer bodyGraph = new ILAstOptimizer(); bodyGraph.Optimize(context, ilMethod); context.CancellationToken.ThrowIfCancellationRequested(); var allVariables = ilMethod.GetSelfAndChildrenRecursive().Select(e => e.Operand as ILVariable) .Where(v => v != null && !v.IsParameter).Distinct(); Debug.Assert(context.CurrentMethod == methodDef); NameVariables.AssignNamesToVariables(context, astBuilder.Parameters, allVariables, ilMethod); if (parameters != null) { foreach (var pair in (from p in parameters join v in astBuilder.Parameters on p.Annotation() equals v.OriginalParameter select new { p, v.Name })) { pair.p.Name = pair.Name; } } context.CancellationToken.ThrowIfCancellationRequested(); Ast.BlockStatement astBlock = TransformBlock(ilMethod); CommentStatement.ReplaceAll(astBlock); // convert CommentStatements to Comments Statement insertionPoint = astBlock.Statements.FirstOrDefault(); foreach (ILVariable v in localVariablesToDefine) { AstType type; if (v.Type.ContainsAnonymousType()) type = new SimpleType("var"); else type = AstBuilder.ConvertType(v.Type); var newVarDecl = new VariableDeclarationStatement(type, v.Name); astBlock.Statements.InsertBefore(insertionPoint, newVarDecl); } // store the variables - used for debugger int token = methodDef.MetadataToken.ToInt32(); localVariables.AddOrUpdate(token, allVariables, (key, oldValue) => allVariables); return astBlock; } Ast.BlockStatement TransformBlock(ILBlock block) { Ast.BlockStatement astBlock = new BlockStatement(); if (block != null) { foreach(ILNode node in block.GetChildren()) { astBlock.AddRange(TransformNode(node)); } } return astBlock; } IEnumerable TransformNode(ILNode node) { if (node is ILLabel) { yield return new Ast.LabelStatement { Label = ((ILLabel)node).Name }; } else if (node is ILExpression) { List ilRanges = ILRange.OrderAndJoint(node.GetSelfAndChildrenRecursive().SelectMany(e => e.ILRanges)); AstNode codeExpr = TransformExpression((ILExpression)node); if (codeExpr != null) { codeExpr = codeExpr.WithAnnotation(ilRanges); if (codeExpr is Ast.Expression) { yield return new Ast.ExpressionStatement { Expression = (Ast.Expression)codeExpr }; } else if (codeExpr is Ast.Statement) { yield return (Ast.Statement)codeExpr; } else { throw new Exception(); } } } else if (node is ILWhileLoop) { ILWhileLoop ilLoop = (ILWhileLoop)node; WhileStatement whileStmt = new WhileStatement() { Condition = ilLoop.Condition != null ? (Expression)TransformExpression(ilLoop.Condition) : new PrimitiveExpression(true), EmbeddedStatement = TransformBlock(ilLoop.BodyBlock) }; yield return whileStmt; } else if (node is ILCondition) { ILCondition conditionalNode = (ILCondition)node; bool hasFalseBlock = conditionalNode.FalseBlock.EntryGoto != null || conditionalNode.FalseBlock.Body.Count > 0; yield return new Ast.IfElseStatement { Condition = (Expression)TransformExpression(conditionalNode.Condition), TrueStatement = TransformBlock(conditionalNode.TrueBlock), FalseStatement = hasFalseBlock ? TransformBlock(conditionalNode.FalseBlock) : null }; } else if (node is ILSwitch) { ILSwitch ilSwitch = (ILSwitch)node; SwitchStatement switchStmt = new SwitchStatement() { Expression = (Expression)TransformExpression(ilSwitch.Condition) }; foreach (var caseBlock in ilSwitch.CaseBlocks) { SwitchSection section = new SwitchSection(); if (caseBlock.Values != null) { section.CaseLabels.AddRange(caseBlock.Values.Select(i => new CaseLabel() { Expression = AstBuilder.MakePrimitive(i, ilSwitch.Condition.InferredType) })); } else { section.CaseLabels.Add(new CaseLabel()); } section.Statements.Add(TransformBlock(caseBlock)); switchStmt.SwitchSections.Add(section); } yield return switchStmt; } else if (node is ILTryCatchBlock) { ILTryCatchBlock tryCatchNode = ((ILTryCatchBlock)node); var tryCatchStmt = new Ast.TryCatchStatement(); tryCatchStmt.TryBlock = TransformBlock(tryCatchNode.TryBlock); foreach (var catchClause in tryCatchNode.CatchBlocks) { if (catchClause.ExceptionVariable == null && (catchClause.ExceptionType == null || catchClause.ExceptionType.MetadataType == MetadataType.Object)) { tryCatchStmt.CatchClauses.Add(new Ast.CatchClause { Body = TransformBlock(catchClause) }); } else { tryCatchStmt.CatchClauses.Add( new Ast.CatchClause { Type = AstBuilder.ConvertType(catchClause.ExceptionType), VariableName = catchClause.ExceptionVariable == null ? null : catchClause.ExceptionVariable.Name, Body = TransformBlock(catchClause) }); } } if (tryCatchNode.FinallyBlock != null) tryCatchStmt.FinallyBlock = TransformBlock(tryCatchNode.FinallyBlock); if (tryCatchNode.FaultBlock != null) { CatchClause cc = new CatchClause(); cc.Body = TransformBlock(tryCatchNode.FaultBlock); cc.Body.Add(new ThrowStatement()); // rethrow tryCatchStmt.CatchClauses.Add(cc); } yield return tryCatchStmt; } else if (node is ILFixedStatement) { ILFixedStatement fixedNode = (ILFixedStatement)node; FixedStatement fixedStatement = new FixedStatement(); foreach (ILExpression initializer in fixedNode.Initializers) { Debug.Assert(initializer.Code == ILCode.Stloc); ILVariable v = (ILVariable)initializer.Operand; fixedStatement.Variables.Add( new VariableInitializer { Name = v.Name, Initializer = (Expression)TransformExpression(initializer.Arguments[0]) }.WithAnnotation(v)); } fixedStatement.Type = AstBuilder.ConvertType(((ILVariable)fixedNode.Initializers[0].Operand).Type); fixedStatement.EmbeddedStatement = TransformBlock(fixedNode.BodyBlock); yield return fixedStatement; } else if (node is ILBlock) { yield return TransformBlock((ILBlock)node); } else { throw new Exception("Unknown node type"); } } AstNode TransformExpression(ILExpression expr) { AstNode node = TransformByteCode(expr); Expression astExpr = node as Expression; // get IL ranges - used in debugger List ilRanges = ILRange.OrderAndJoint(expr.GetSelfAndChildrenRecursive().SelectMany(e => e.ILRanges)); AstNode result; if (astExpr != null) result = Convert(astExpr, expr.InferredType, expr.ExpectedType); else result = node; if (result != null) return result.WithAnnotation(ilRanges); return result; } AstNode TransformByteCode(ILExpression byteCode) { object operand = byteCode.Operand; AstType operandAsTypeRef = AstBuilder.ConvertType(operand as Cecil.TypeReference); List args = new List(); foreach(ILExpression arg in byteCode.Arguments) { args.Add((Ast.Expression)TransformExpression(arg)); } Ast.Expression arg1 = args.Count >= 1 ? args[0] : null; Ast.Expression arg2 = args.Count >= 2 ? args[1] : null; Ast.Expression arg3 = args.Count >= 3 ? args[2] : null; switch (byteCode.Code) { #region Arithmetic case ILCode.Add: case ILCode.Add_Ovf: case ILCode.Add_Ovf_Un: { BinaryOperatorExpression boe; if (byteCode.InferredType is PointerType) { if (byteCode.Arguments[0].ExpectedType is PointerType) { arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); boe.AddAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); } else if (byteCode.Arguments[1].ExpectedType is PointerType) { arg1 = DivideBySize(arg1, ((PointerType)byteCode.InferredType).ElementType); boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); boe.AddAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); } } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); } boe.AddAnnotation(byteCode.Code == ILCode.Add ? AddCheckedBlocks.UncheckedAnnotation : AddCheckedBlocks.CheckedAnnotation); return boe; } case ILCode.Sub: case ILCode.Sub_Ovf: case ILCode.Sub_Ovf_Un: { BinaryOperatorExpression boe; if (byteCode.InferredType is PointerType) { if (byteCode.Arguments[0].ExpectedType is PointerType) { arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); boe.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); } } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); } boe.AddAnnotation(byteCode.Code == ILCode.Sub ? AddCheckedBlocks.UncheckedAnnotation : AddCheckedBlocks.CheckedAnnotation); return boe; } case ILCode.Div: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); case ILCode.Div_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); case ILCode.Mul: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.UncheckedAnnotation); case ILCode.Mul_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.CheckedAnnotation); case ILCode.Mul_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.CheckedAnnotation); case ILCode.Rem: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); case ILCode.Rem_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); case ILCode.And: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseAnd, arg2); case ILCode.Or: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseOr, arg2); case ILCode.Xor: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ExclusiveOr, arg2); case ILCode.Shl: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftLeft, arg2); case ILCode.Shr: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftRight, arg2); case ILCode.Shr_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftRight, arg2); case ILCode.Neg: return new Ast.UnaryOperatorExpression(UnaryOperatorType.Minus, arg1).WithAnnotation(AddCheckedBlocks.UncheckedAnnotation); case ILCode.Not: return new Ast.UnaryOperatorExpression(UnaryOperatorType.BitNot, arg1); case ILCode.PostIncrement: case ILCode.PostIncrement_Ovf: case ILCode.PostIncrement_Ovf_Un: { if (arg1 is DirectionExpression) arg1 = ((DirectionExpression)arg1).Expression.Detach(); var uoe = new Ast.UnaryOperatorExpression( (int)byteCode.Operand > 0 ? UnaryOperatorType.PostIncrement : UnaryOperatorType.PostDecrement, arg1); uoe.AddAnnotation((byteCode.Code == ILCode.PostIncrement) ? AddCheckedBlocks.UncheckedAnnotation : AddCheckedBlocks.CheckedAnnotation); return uoe; } #endregion #region Arrays case ILCode.Newarr: case ILCode.InitArray: { var ace = new Ast.ArrayCreateExpression(); ace.Type = operandAsTypeRef; ComposedType ct = operandAsTypeRef as ComposedType; if (ct != null) { // change "new (int[,])[10] to new int[10][,]" ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); } if (byteCode.Code == ILCode.InitArray) { ace.Initializer = new ArrayInitializerExpression(); ace.Initializer.Elements.AddRange(args); } else { ace.Arguments.Add(arg1); } return ace; } case ILCode.Ldlen: return arg1.Member("Length"); case ILCode.Ldelem_I: case ILCode.Ldelem_I1: case ILCode.Ldelem_I2: case ILCode.Ldelem_I4: case ILCode.Ldelem_I8: case ILCode.Ldelem_U1: case ILCode.Ldelem_U2: case ILCode.Ldelem_U4: case ILCode.Ldelem_R4: case ILCode.Ldelem_R8: case ILCode.Ldelem_Ref: case ILCode.Ldelem_Any: return arg1.Indexer(arg2); case ILCode.Ldelema: return MakeRef(arg1.Indexer(arg2)); case ILCode.Stelem_I: case ILCode.Stelem_I1: case ILCode.Stelem_I2: case ILCode.Stelem_I4: case ILCode.Stelem_I8: case ILCode.Stelem_R4: case ILCode.Stelem_R8: case ILCode.Stelem_Ref: case ILCode.Stelem_Any: return new Ast.AssignmentExpression(arg1.Indexer(arg2), arg3); case ILCode.CompoundAssignment: { CastExpression cast = arg1 as CastExpression; BinaryOperatorExpression boe = (BinaryOperatorExpression)(cast != null ? cast.Expression : arg1); var assignment = new Ast.AssignmentExpression { Left = boe.Left.Detach(), Operator = ReplaceMethodCallsWithOperators.GetAssignmentOperatorForBinaryOperator(boe.Operator), Right = boe.Right.Detach() }.CopyAnnotationsFrom(boe); // We do not mark the resulting assignment as RestoreOriginalAssignOperatorAnnotation, because // the operator cannot be translated back to the expanded form (as the left-hand expression // would be evaluated twice, and might have side-effects) if (cast != null) { cast.Expression = assignment; return cast; } else { return assignment; } } #endregion #region Comparison case ILCode.Ceq: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Equality, arg2); case ILCode.Cgt: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.GreaterThan, arg2); case ILCode.Cgt_Un: { // can also mean Inequality, when used with object references TypeReference arg1Type = byteCode.Arguments[0].InferredType; if (arg1Type != null && !arg1Type.IsValueType) return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.InEquality, arg2); else return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.GreaterThan, arg2); } case ILCode.Clt: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.LessThan, arg2); case ILCode.Clt_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.LessThan, arg2); #endregion #region Logical case ILCode.LogicNot: return new Ast.UnaryOperatorExpression(UnaryOperatorType.Not, arg1); case ILCode.LogicAnd: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ConditionalAnd, arg2); case ILCode.LogicOr: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ConditionalOr, arg2); case ILCode.TernaryOp: return new Ast.ConditionalExpression() { Condition = arg1, TrueExpression = arg2, FalseExpression = arg3 }; case ILCode.NullCoalescing: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.NullCoalescing, arg2); #endregion #region Branch case ILCode.Br: return new Ast.GotoStatement(((ILLabel)byteCode.Operand).Name); case ILCode.Brtrue: return new Ast.IfElseStatement() { Condition = arg1, TrueStatement = new BlockStatement() { new Ast.GotoStatement(((ILLabel)byteCode.Operand).Name) } }; case ILCode.LoopOrSwitchBreak: return new Ast.BreakStatement(); case ILCode.LoopContinue: return new Ast.ContinueStatement(); #endregion #region Conversions case ILCode.Conv_I1: case ILCode.Conv_I2: case ILCode.Conv_I4: case ILCode.Conv_I8: case ILCode.Conv_U1: case ILCode.Conv_U2: case ILCode.Conv_U4: case ILCode.Conv_U8: case ILCode.Conv_I: case ILCode.Conv_U: { // conversion was handled by Convert() function using the info from type analysis CastExpression cast = arg1 as CastExpression; if (cast != null) { cast.AddAnnotation(AddCheckedBlocks.UncheckedAnnotation); } return arg1; } case ILCode.Conv_R4: case ILCode.Conv_R8: case ILCode.Conv_R_Un: // TODO return arg1; case ILCode.Conv_Ovf_I1: case ILCode.Conv_Ovf_I2: case ILCode.Conv_Ovf_I4: case ILCode.Conv_Ovf_I8: case ILCode.Conv_Ovf_U1: case ILCode.Conv_Ovf_U2: case ILCode.Conv_Ovf_U4: case ILCode.Conv_Ovf_U8: case ILCode.Conv_Ovf_I1_Un: case ILCode.Conv_Ovf_I2_Un: case ILCode.Conv_Ovf_I4_Un: case ILCode.Conv_Ovf_I8_Un: case ILCode.Conv_Ovf_U1_Un: case ILCode.Conv_Ovf_U2_Un: case ILCode.Conv_Ovf_U4_Un: case ILCode.Conv_Ovf_U8_Un: { // conversion was handled by Convert() function using the info from type analysis CastExpression cast = arg1 as CastExpression; if (cast != null) { cast.AddAnnotation(AddCheckedBlocks.CheckedAnnotation); } return arg1; } case ILCode.Conv_Ovf_I: return arg1.CastTo(typeof(IntPtr)); // TODO case ILCode.Conv_Ovf_U: return arg1.CastTo(typeof(UIntPtr)); case ILCode.Conv_Ovf_I_Un: return arg1.CastTo(typeof(IntPtr)); case ILCode.Conv_Ovf_U_Un: return arg1.CastTo(typeof(UIntPtr)); case ILCode.Castclass: return arg1.CastTo(operandAsTypeRef); case ILCode.Unbox_Any: // unboxing does not require a cast if the argument was an isinst instruction if (arg1 is AsExpression && byteCode.Arguments[0].Code == ILCode.Isinst && TypeAnalysis.IsSameType(operand as TypeReference, byteCode.Arguments[0].Operand as TypeReference)) return arg1; else return arg1.CastTo(operandAsTypeRef); case ILCode.Isinst: return arg1.CastAs(operandAsTypeRef); case ILCode.Box: return arg1; case ILCode.Unbox: return MakeRef(arg1.CastTo(operandAsTypeRef)); #endregion #region Indirect case ILCode.Ldind_Ref: case ILCode.Ldobj: if (arg1 is DirectionExpression) return ((DirectionExpression)arg1).Expression.Detach(); else return new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg1); case ILCode.Stind_Ref: case ILCode.Stobj: if (arg1 is DirectionExpression) return new AssignmentExpression(((DirectionExpression)arg1).Expression.Detach(), arg2); else return new AssignmentExpression(new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg1), arg2); #endregion case ILCode.Arglist: return new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.ArgListAccess }; case ILCode.Break: return InlineAssembly(byteCode, args); case ILCode.Call: case ILCode.CallGetter: case ILCode.CallSetter: return TransformCall(false, byteCode, args); case ILCode.Callvirt: case ILCode.CallvirtGetter: case ILCode.CallvirtSetter: return TransformCall(true, byteCode, args); case ILCode.Ldftn: { Cecil.MethodReference cecilMethod = ((MethodReference)operand); var expr = new Ast.IdentifierExpression(cecilMethod.Name); expr.TypeArguments.AddRange(ConvertTypeArguments(cecilMethod)); expr.AddAnnotation(cecilMethod); return new IdentifierExpression("ldftn").Invoke(expr) .WithAnnotation(new Transforms.DelegateConstruction.Annotation(false)); } case ILCode.Ldvirtftn: { Cecil.MethodReference cecilMethod = ((MethodReference)operand); var expr = new Ast.IdentifierExpression(cecilMethod.Name); expr.TypeArguments.AddRange(ConvertTypeArguments(cecilMethod)); expr.AddAnnotation(cecilMethod); return new IdentifierExpression("ldvirtftn").Invoke(expr) .WithAnnotation(new Transforms.DelegateConstruction.Annotation(true)); } case ILCode.Calli: return InlineAssembly(byteCode, args); case ILCode.Ckfinite: return InlineAssembly(byteCode, args); case ILCode.Constrained: return InlineAssembly(byteCode, args); case ILCode.Cpblk: return InlineAssembly(byteCode, args); case ILCode.Cpobj: return InlineAssembly(byteCode, args); case ILCode.Dup: return arg1; case ILCode.Endfilter: return InlineAssembly(byteCode, args); case ILCode.Endfinally: return null; case ILCode.Initblk: return InlineAssembly(byteCode, args); case ILCode.Initobj: return InlineAssembly(byteCode, args); case ILCode.DefaultValue: return MakeDefaultValue((TypeReference)operand); case ILCode.Jmp: return InlineAssembly(byteCode, args); case ILCode.Ldc_I4: return AstBuilder.MakePrimitive((int)operand, byteCode.InferredType); case ILCode.Ldc_I8: return AstBuilder.MakePrimitive((long)operand, byteCode.InferredType); case ILCode.Ldc_R4: case ILCode.Ldc_R8: case ILCode.Ldc_Decimal: return new Ast.PrimitiveExpression(operand); case ILCode.Ldfld: if (arg1 is DirectionExpression) arg1 = ((DirectionExpression)arg1).Expression.Detach(); return arg1.Member(((FieldReference) operand).Name).WithAnnotation(operand); case ILCode.Ldsfld: return AstBuilder.ConvertType(((FieldReference)operand).DeclaringType) .Member(((FieldReference)operand).Name).WithAnnotation(operand); case ILCode.Stfld: if (arg1 is DirectionExpression) arg1 = ((DirectionExpression)arg1).Expression.Detach(); return new AssignmentExpression(arg1.Member(((FieldReference) operand).Name).WithAnnotation(operand), arg2); case ILCode.Stsfld: return new AssignmentExpression( AstBuilder.ConvertType(((FieldReference)operand).DeclaringType) .Member(((FieldReference)operand).Name).WithAnnotation(operand), arg1); case ILCode.Ldflda: if (arg1 is DirectionExpression) arg1 = ((DirectionExpression)arg1).Expression.Detach(); return MakeRef(arg1.Member(((FieldReference) operand).Name).WithAnnotation(operand)); case ILCode.Ldsflda: return MakeRef( AstBuilder.ConvertType(((FieldReference)operand).DeclaringType) .Member(((FieldReference)operand).Name).WithAnnotation(operand)); case ILCode.Ldloc: { ILVariable v = (ILVariable)operand; if (!v.IsParameter) localVariablesToDefine.Add((ILVariable)operand); Expression expr; if (v.IsParameter && v.OriginalParameter.Index < 0) expr = new ThisReferenceExpression(); else expr = new Ast.IdentifierExpression(((ILVariable)operand).Name).WithAnnotation(operand); return v.IsParameter && v.Type is ByReferenceType ? MakeRef(expr) : expr; } case ILCode.Ldloca: { ILVariable v = (ILVariable)operand; if (v.IsParameter && v.OriginalParameter.Index < 0) return MakeRef(new ThisReferenceExpression()); if (!v.IsParameter) localVariablesToDefine.Add((ILVariable)operand); return MakeRef(new Ast.IdentifierExpression(((ILVariable)operand).Name).WithAnnotation(operand)); } case ILCode.Ldnull: return new Ast.NullReferenceExpression(); case ILCode.Ldstr: return new Ast.PrimitiveExpression(operand); case ILCode.Ldtoken: if (operand is Cecil.TypeReference) { return new Ast.TypeOfExpression { Type = AddEmptyTypeArgumentsForUnboundGenerics(operandAsTypeRef) }.Member("TypeHandle"); } else { return InlineAssembly(byteCode, args); } case ILCode.Leave: return new GotoStatement() { Label = ((ILLabel)operand).Name }; case ILCode.Localloc: { PointerType ptrType = byteCode.InferredType as PointerType; TypeReference type; if (ptrType != null) { type = ptrType.ElementType; } else { type = typeSystem.Byte; } return new StackAllocExpression { Type = AstBuilder.ConvertType(type), CountExpression = DivideBySize(arg1, type) }; } case ILCode.Mkrefany: { DirectionExpression dir = arg1 as DirectionExpression; if (dir != null) { return new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.MakeRef, Arguments = { dir.Expression.Detach() } }; } else { return InlineAssembly(byteCode, args); } } case ILCode.Refanytype: return new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.RefType, Arguments = { arg1 } }.Member("TypeHandle"); case ILCode.Refanyval: return MakeRef( new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.RefValue, Arguments = { arg1, new TypeReferenceExpression(operandAsTypeRef) } }); case ILCode.Newobj: { Cecil.TypeReference declaringType = ((MethodReference)operand).DeclaringType; if (declaringType is ArrayType) { ComposedType ct = AstBuilder.ConvertType((ArrayType)declaringType) as ComposedType; if (ct != null && ct.ArraySpecifiers.Count >= 1) { var ace = new Ast.ArrayCreateExpression(); ct.ArraySpecifiers.First().Remove(); ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); ace.Type = ct; ace.Arguments.AddRange(args); return ace; } } var oce = new Ast.ObjectCreateExpression(); if (declaringType.IsAnonymousType()) { MethodDefinition ctor = ((MethodReference)operand).Resolve(); if (methodDef != null) { oce.Initializer = new ArrayInitializerExpression(); for (int i = 0; i < args.Count; i++) { oce.Initializer.Elements.Add( new NamedArgumentExpression { Identifier = ctor.Parameters[i].Name, Expression = args[i] }); } } return oce; } oce.Type = AstBuilder.ConvertType(declaringType); oce.Arguments.AddRange(args); return oce.WithAnnotation(operand); } case ILCode.No: return InlineAssembly(byteCode, args); case ILCode.Nop: return null; case ILCode.Pop: return arg1; case ILCode.Readonly: return InlineAssembly(byteCode, args); case ILCode.Ret: if (methodDef.ReturnType.FullName != "System.Void") { return new Ast.ReturnStatement { Expression = arg1 }; } else { return new Ast.ReturnStatement(); } case ILCode.Rethrow: return new Ast.ThrowStatement(); case ILCode.Sizeof: return new Ast.SizeOfExpression { Type = operandAsTypeRef }; case ILCode.Stloc: { ILVariable locVar = (ILVariable)operand; if (!locVar.IsParameter) localVariablesToDefine.Add(locVar); return new Ast.AssignmentExpression(new Ast.IdentifierExpression(locVar.Name).WithAnnotation(locVar), arg1); } case ILCode.Switch: return InlineAssembly(byteCode, args); case ILCode.Tail: return InlineAssembly(byteCode, args); case ILCode.Throw: return new Ast.ThrowStatement { Expression = arg1 }; case ILCode.Unaligned: return InlineAssembly(byteCode, args); case ILCode.Volatile: return InlineAssembly(byteCode, args); case ILCode.YieldBreak: return new Ast.YieldBreakStatement(); case ILCode.YieldReturn: return new Ast.YieldStatement { Expression = arg1 }; case ILCode.InitObject: case ILCode.InitCollection: { ArrayInitializerExpression initializer = new ArrayInitializerExpression(); for (int i = 1; i < args.Count; i++) { Match m = objectInitializerPattern.Match(args[i]); if (m.Success) { MemberReferenceExpression mre = m.Get("left").Single(); initializer.Elements.Add( new NamedArgumentExpression { Identifier = mre.MemberName, Expression = m.Get("right").Single().Detach() }.CopyAnnotationsFrom(mre)); } else { m = collectionInitializerPattern.Match(args[i]); if (m.Success) { if (m.Get("arg").Count() == 1) { initializer.Elements.Add(m.Get("arg").Single().Detach()); } else { ArrayInitializerExpression argList = new ArrayInitializerExpression(); foreach (var expr in m.Get("arg")) { argList.Elements.Add(expr.Detach()); } initializer.Elements.Add(argList); } } else { initializer.Elements.Add(args[i]); } } } ObjectCreateExpression oce = arg1 as ObjectCreateExpression; if (oce != null) { oce.Initializer = initializer; return oce; } else { return new AssignmentExpression(arg1, initializer); } } case ILCode.InitializedObject: return new InitializedObjectExpression(); case ILCode.AddressOf: return MakeRef(arg1); default: throw new Exception("Unknown OpCode: " + byteCode.Code); } } AstType AddEmptyTypeArgumentsForUnboundGenerics(AstType type) { TypeReference typeRef = type.Annotation(); if (typeRef == null) return type; TypeDefinition typeDef = typeRef.Resolve(); // need to resolve to figure out the number of type parameters if (typeDef == null || !typeDef.HasGenericParameters) return type; SimpleType sType = type as SimpleType; MemberType mType = type as MemberType; if (sType != null) { while (typeDef.GenericParameters.Count > sType.TypeArguments.Count) { sType.TypeArguments.Add(new SimpleType("")); } } if (mType != null) { AddEmptyTypeArgumentsForUnboundGenerics(mType.Target); int outerTypeParamCount = typeDef.DeclaringType == null ? 0 : typeDef.DeclaringType.GenericParameters.Count; while (typeDef.GenericParameters.Count - outerTypeParamCount > mType.TypeArguments.Count) { mType.TypeArguments.Add(new SimpleType("")); } } return type; } static readonly AstNode objectInitializerPattern = new AssignmentExpression( new MemberReferenceExpression { Target = new InitializedObjectExpression() }.WithName("left"), new AnyNode("right") ); static readonly AstNode collectionInitializerPattern = new InvocationExpression { Target = new MemberReferenceExpression { Target = new InitializedObjectExpression(), MemberName = "Add" }, Arguments = { new Repeat(new AnyNode("arg")) } }; sealed class InitializedObjectExpression : IdentifierExpression { public InitializedObjectExpression() : base("__initialized_object__") {} protected override bool DoMatch(AstNode other, Match match) { return other is InitializedObjectExpression; } } /// /// Divides expr by the size of 'type'. /// Expression DivideBySize(Expression expr, TypeReference type) { CastExpression cast = expr as CastExpression; if (cast != null && cast.Type is PrimitiveType && ((PrimitiveType)cast.Type).Keyword == "int") expr = cast.Expression.Detach(); Expression sizeOfExpression; switch (TypeAnalysis.GetInformationAmount(type)) { case 1: case 8: sizeOfExpression = new PrimitiveExpression(1); break; case 16: sizeOfExpression = new PrimitiveExpression(2); break; case 32: sizeOfExpression = new PrimitiveExpression(4); break; case 64: sizeOfExpression = new PrimitiveExpression(8); break; default: sizeOfExpression = new SizeOfExpression { Type = AstBuilder.ConvertType(type) }; break; } BinaryOperatorExpression boe = expr as BinaryOperatorExpression; if (boe != null && boe.Operator == BinaryOperatorType.Multiply && sizeOfExpression.IsMatch(boe.Right)) return boe.Left.Detach(); if (sizeOfExpression.IsMatch(expr)) return new PrimitiveExpression(1); return new BinaryOperatorExpression(expr, BinaryOperatorType.Divide, sizeOfExpression); } Expression MakeDefaultValue(TypeReference type) { TypeDefinition typeDef = type.Resolve(); if (typeDef != null) { if (TypeAnalysis.IsIntegerOrEnum(typeDef)) return AstBuilder.MakePrimitive(0, typeDef); else if (!typeDef.IsValueType) return new NullReferenceExpression(); switch (typeDef.FullName) { case "System.Nullable`1": return new NullReferenceExpression(); case "System.Single": return new PrimitiveExpression(0f); case "System.Double": return new PrimitiveExpression(0.0); case "System.Decimal": return new PrimitiveExpression(0m); } } return new DefaultValueExpression { Type = AstBuilder.ConvertType(type) }; } AstNode TransformCall(bool isVirtual, ILExpression byteCode, List args) { Cecil.MethodReference cecilMethod = (MethodReference)byteCode.Operand; Cecil.MethodDefinition cecilMethodDef = cecilMethod.Resolve(); Ast.Expression target; List methodArgs = new List(args); if (cecilMethod.HasThis) { target = methodArgs[0]; methodArgs.RemoveAt(0); // Unpack any DirectionExpression that is used as target for the call // (calling methods on value types implicitly passes the first argument by reference) if (target is DirectionExpression) { target = ((DirectionExpression)target).Expression; target.Remove(); // detach from DirectionExpression } if (cecilMethodDef != null && cecilMethodDef.DeclaringType.IsInterface) { TypeReference tr = byteCode.Arguments[0].InferredType; if (tr != null) { TypeDefinition td = tr.Resolve(); if (td != null && !td.IsInterface) { // Calling an interface method on a non-interface object: // we need to introduce an explicit cast target = target.CastTo(AstBuilder.ConvertType(cecilMethod.DeclaringType)); } } } } else { target = new TypeReferenceExpression { Type = AstBuilder.ConvertType(cecilMethod.DeclaringType) }; } if (target is ThisReferenceExpression && !isVirtual) { // a non-virtual call on "this" might be a "base"-call. if (cecilMethod.DeclaringType.GetElementType() != methodDef.DeclaringType) { // If we're not calling a method in the current class; we must be calling one in the base class. target = new BaseReferenceExpression(); } } if (cecilMethod.Name == ".ctor" && cecilMethod.DeclaringType.IsValueType) { // On value types, the constructor can be called. // This is equivalent to 'target = new ValueType(args);'. ObjectCreateExpression oce = new ObjectCreateExpression(); oce.Type = AstBuilder.ConvertType(cecilMethod.DeclaringType); AdjustArgumentsForMethodCall(cecilMethod, methodArgs); oce.Arguments.AddRange(methodArgs); return new AssignmentExpression(target, oce); } if (cecilMethod.Name == "Get" && cecilMethod.DeclaringType is ArrayType && methodArgs.Count > 1) { return target.Indexer(methodArgs); } else if (cecilMethod.Name == "Set" && cecilMethod.DeclaringType is ArrayType && methodArgs.Count > 2) { return new AssignmentExpression(target.Indexer(methodArgs.GetRange(0, methodArgs.Count - 1)), methodArgs.Last()); } // Test whether the method is an accessor: if (cecilMethodDef != null) { if (cecilMethodDef.IsGetter && methodArgs.Count == 0) { foreach (var prop in cecilMethodDef.DeclaringType.Properties) { if (prop.GetMethod == cecilMethodDef) return target.Member(prop.Name).WithAnnotation(prop); } } else if (cecilMethodDef.IsGetter) { // with parameters PropertyDefinition indexer = GetIndexer(cecilMethodDef); if (indexer != null) return target.Indexer(methodArgs).WithAnnotation(indexer); } else if (cecilMethodDef.IsSetter && methodArgs.Count == 1) { foreach (var prop in cecilMethodDef.DeclaringType.Properties) { if (prop.SetMethod == cecilMethodDef) return new Ast.AssignmentExpression(target.Member(prop.Name).WithAnnotation(prop), methodArgs[0]); } } else if (cecilMethodDef.IsSetter && methodArgs.Count > 1) { PropertyDefinition indexer = GetIndexer(cecilMethodDef); if (indexer != null) return new AssignmentExpression( target.Indexer(methodArgs.GetRange(0, methodArgs.Count - 1)).WithAnnotation(indexer), methodArgs[methodArgs.Count - 1] ); } else if (cecilMethodDef.IsAddOn && methodArgs.Count == 1) { foreach (var ev in cecilMethodDef.DeclaringType.Events) { if (ev.AddMethod == cecilMethodDef) { return new Ast.AssignmentExpression { Left = target.Member(ev.Name).WithAnnotation(ev), Operator = AssignmentOperatorType.Add, Right = methodArgs[0] }; } } } else if (cecilMethodDef.IsRemoveOn && methodArgs.Count == 1) { foreach (var ev in cecilMethodDef.DeclaringType.Events) { if (ev.RemoveMethod == cecilMethodDef) { return new Ast.AssignmentExpression { Left = target.Member(ev.Name).WithAnnotation(ev), Operator = AssignmentOperatorType.Subtract, Right = methodArgs[0] }; } } } else if (cecilMethodDef.Name == "Invoke" && cecilMethodDef.DeclaringType.BaseType != null && cecilMethodDef.DeclaringType.BaseType.FullName == "System.MulticastDelegate") { AdjustArgumentsForMethodCall(cecilMethod, methodArgs); return target.Invoke(methodArgs); } } // Default invocation AdjustArgumentsForMethodCall(cecilMethodDef ?? cecilMethod, methodArgs); return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod); } static void AdjustArgumentsForMethodCall(MethodReference cecilMethod, List methodArgs) { // Convert 'ref' into 'out' where necessary for (int i = 0; i < methodArgs.Count && i < cecilMethod.Parameters.Count; i++) { DirectionExpression dir = methodArgs[i] as DirectionExpression; if (dir != null && cecilMethod.Parameters[i].IsOut) dir.FieldDirection = FieldDirection.Out; } } static PropertyDefinition GetIndexer(MethodDefinition cecilMethodDef) { TypeDefinition typeDef = cecilMethodDef.DeclaringType; string indexerName = null; foreach (CustomAttribute ca in typeDef.CustomAttributes) { if (ca.Constructor.FullName == "System.Void System.Reflection.DefaultMemberAttribute::.ctor(System.String)") { indexerName = ca.ConstructorArguments.Single().Value as string; break; } } if (indexerName == null) return null; foreach (PropertyDefinition prop in typeDef.Properties) { if (prop.Name == indexerName) { if (prop.GetMethod == cecilMethodDef || prop.SetMethod == cecilMethodDef) return prop; } } return null; } #if DEBUG static readonly ConcurrentDictionary unhandledOpcodes = new ConcurrentDictionary(); #endif [Conditional("DEBUG")] public static void ClearUnhandledOpcodes() { #if DEBUG unhandledOpcodes.Clear(); #endif } [Conditional("DEBUG")] public static void PrintNumberOfUnhandledOpcodes() { #if DEBUG foreach (var pair in unhandledOpcodes) { Debug.WriteLine("AddMethodBodyBuilder unhandled opcode: {1}x {0}", pair.Key, pair.Value); } #endif } static Expression InlineAssembly(ILExpression byteCode, List args) { #if DEBUG unhandledOpcodes.AddOrUpdate(byteCode.Code, c => 1, (c, n) => n+1); #endif // Output the operand of the unknown IL code as well if (byteCode.Operand != null) { args.Insert(0, new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand))); } return new IdentifierExpression(byteCode.Code.GetName()).Invoke(args); } static string FormatByteCodeOperand(object operand) { if (operand == null) { return string.Empty; //} else if (operand is ILExpression) { // return string.Format("IL_{0:X2}", ((ILExpression)operand).Offset); } else if (operand is MethodReference) { return ((MethodReference)operand).Name + "()"; } else if (operand is Cecil.TypeReference) { return ((Cecil.TypeReference)operand).FullName; } else if (operand is VariableDefinition) { return ((VariableDefinition)operand).Name; } else if (operand is ParameterDefinition) { return ((ParameterDefinition)operand).Name; } else if (operand is FieldReference) { return ((FieldReference)operand).Name; } else if (operand is string) { return "\"" + operand + "\""; } else if (operand is int) { return operand.ToString(); } else { return operand.ToString(); } } static IEnumerable ConvertTypeArguments(MethodReference cecilMethod) { GenericInstanceMethod g = cecilMethod as GenericInstanceMethod; if (g == null) return null; if (g.GenericArguments.Any(ta => ta.ContainsAnonymousType())) return null; return g.GenericArguments.Select(t => AstBuilder.ConvertType(t)); } static Ast.DirectionExpression MakeRef(Ast.Expression expr) { return new DirectionExpression { Expression = expr, FieldDirection = FieldDirection.Ref }; } Ast.Expression Convert(Ast.Expression expr, Cecil.TypeReference actualType, Cecil.TypeReference reqType) { if (actualType == null || reqType == null || TypeAnalysis.IsSameType(actualType, reqType)) { return expr; } else if (actualType is ByReferenceType && reqType is PointerType && expr is DirectionExpression) { return Convert( new UnaryOperatorExpression(UnaryOperatorType.AddressOf, ((DirectionExpression)expr).Expression.Detach()), new PointerType(((ByReferenceType)actualType).ElementType), reqType); } else if (actualType is PointerType && reqType is ByReferenceType) { expr = Convert(expr, actualType, new PointerType(((ByReferenceType)reqType).ElementType)); return new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new UnaryOperatorExpression(UnaryOperatorType.Dereference, expr) }; } else if (actualType is PointerType && reqType is PointerType) { if (actualType.FullName != reqType.FullName) return expr.CastTo(AstBuilder.ConvertType(reqType)); else return expr; } else { bool actualIsIntegerOrEnum = TypeAnalysis.IsIntegerOrEnum(actualType); bool requiredIsIntegerOrEnum = TypeAnalysis.IsIntegerOrEnum(reqType); if (TypeAnalysis.IsBoolean(reqType)) { if (TypeAnalysis.IsBoolean(actualType)) return expr; if (actualIsIntegerOrEnum) { return new BinaryOperatorExpression(expr, BinaryOperatorType.InEquality, AstBuilder.MakePrimitive(0, actualType)); } else { return new BinaryOperatorExpression(expr, BinaryOperatorType.InEquality, new NullReferenceExpression()); } } if (TypeAnalysis.IsBoolean(actualType) && requiredIsIntegerOrEnum) { return new ConditionalExpression { Condition = expr, TrueExpression = AstBuilder.MakePrimitive(1, reqType), FalseExpression = AstBuilder.MakePrimitive(0, reqType) }; } if (expr is PrimitiveExpression && !requiredIsIntegerOrEnum && TypeAnalysis.IsEnum(actualType)) { return expr.CastTo(AstBuilder.ConvertType(actualType)); } bool actualIsPrimitiveType = actualIsIntegerOrEnum || actualType.MetadataType == MetadataType.Single || actualType.MetadataType == MetadataType.Double; bool requiredIsPrimitiveType = requiredIsIntegerOrEnum || reqType.MetadataType == MetadataType.Single || reqType.MetadataType == MetadataType.Double; if (actualIsPrimitiveType && requiredIsPrimitiveType) { return expr.CastTo(AstBuilder.ConvertType(reqType)); } return expr; } } } }