diff options
Diffstat (limited to 'ICSharpCode.Decompiler/Ast')
27 files changed, 9746 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/Ast/Annotations.cs b/ICSharpCode.Decompiler/Ast/Annotations.cs new file mode 100644 index 00000000..ec9fd61f --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Annotations.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast +{ + public class TypeInformation + { + public readonly TypeReference InferredType; + public readonly TypeReference ExpectedType; + + public TypeInformation(TypeReference inferredType, TypeReference expectedType) + { + InferredType = inferredType; + ExpectedType = expectedType; + } + } + + public class LdTokenAnnotation {} + + /// <summary> + /// Annotation that is applied to the body expression of an Expression.Lambda() call. + /// </summary> + public class ParameterDeclarationAnnotation + { + public readonly List<ParameterDeclaration> Parameters = new List<ParameterDeclaration>(); + + public ParameterDeclarationAnnotation(ILExpression expr) + { + Debug.Assert(expr.Code == ILCode.ExpressionTreeParameterDeclarations); + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + ILExpression p = expr.Arguments[i]; + // p looks like this: + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + ILVariable v = (ILVariable)p.Operand; + TypeReference typeRef = (TypeReference)p.Arguments[0].Arguments[0].Arguments[0].Operand; + string name = (string)p.Arguments[0].Arguments[1].Operand; + Parameters.Add(new ParameterDeclaration(AstBuilder.ConvertType(typeRef), name).WithAnnotation(v)); + } + } + } + + /// <summary> + /// Annotation that is applied to a LambdaExpression that was produced by an expression tree. + /// </summary> + public class ExpressionTreeLambdaAnnotation + { + } +} diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs new file mode 100644 index 00000000..faab2725 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -0,0 +1,1690 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast.Transforms; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.Utils; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace ICSharpCode.Decompiler.Ast +{ + using Ast = NRefactory.CSharp; + using VarianceModifier = NRefactory.TypeSystem.VarianceModifier; + + [Flags] + public enum ConvertTypeOptions + { + None = 0, + IncludeNamespace = 1, + IncludeTypeParameterDefinitions = 2, + DoNotUsePrimitiveTypeNames = 4 + } + + public class AstBuilder + { + DecompilerContext context; + SyntaxTree syntaxTree = new SyntaxTree(); + Dictionary<string, NamespaceDeclaration> astNamespaces = new Dictionary<string, NamespaceDeclaration>(); + bool transformationsHaveRun; + + public AstBuilder(DecompilerContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + this.context = context; + DecompileMethodBodies = true; + } + + public static bool MemberIsHidden(MemberReference member, DecompilerSettings settings) + { + MethodDefinition method = member as MethodDefinition; + if (method != null) { + if (method.IsGetter || method.IsSetter || method.IsAddOn || method.IsRemoveOn) + return true; + if (settings.AnonymousMethods && method.HasGeneratedName() && method.IsCompilerGenerated()) + return true; + } + + TypeDefinition type = member as TypeDefinition; + if (type != null) { + if (type.DeclaringType != null) { + if (settings.AnonymousMethods && IsClosureType(type)) + return true; + if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) + return true; + if (settings.AsyncAwait && AsyncDecompiler.IsCompilerGeneratedStateMachine(type)) + return true; + } else if (type.IsCompilerGenerated()) { + if (type.Name.StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal)) + return true; + if (type.IsAnonymousType()) + return true; + } + } + + FieldDefinition field = member as FieldDefinition; + if (field != null) { + if (field.IsCompilerGenerated()) { + if (settings.AnonymousMethods && IsAnonymousMethodCacheField(field)) + return true; + if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field)) + return true; + if (settings.SwitchStatementOnString && IsSwitchOnStringCache(field)) + return true; + } + // event-fields are not [CompilerGenerated] + if (settings.AutomaticEvents && field.DeclaringType.Events.Any(ev => ev.Name == field.Name)) + return true; + } + + return false; + } + + static bool IsSwitchOnStringCache(FieldDefinition field) + { + return field.Name.StartsWith("<>f__switch", StringComparison.Ordinal); + } + + static bool IsAutomaticPropertyBackingField(FieldDefinition field) + { + return field.HasGeneratedName() && field.Name.EndsWith("BackingField", StringComparison.Ordinal); + } + + static bool IsAnonymousMethodCacheField(FieldDefinition field) + { + return field.Name.StartsWith("CS$<>", StringComparison.Ordinal) || field.Name.StartsWith("<>f__am", StringComparison.Ordinal); + } + + static bool IsClosureType(TypeDefinition type) + { + return type.HasGeneratedName() && type.IsCompilerGenerated() && (type.Name.Contains("DisplayClass") || type.Name.Contains("AnonStorey")); + } + + /// <summary> + /// Runs the C# transformations on the compilation unit. + /// </summary> + public void RunTransformations() + { + RunTransformations(null); + } + + public void RunTransformations(Predicate<IAstTransform> transformAbortCondition) + { + TransformationPipeline.RunTransformationsUntil(syntaxTree, transformAbortCondition, context); + transformationsHaveRun = true; + } + + /// <summary> + /// Gets the abstract source tree. + /// </summary> + public SyntaxTree SyntaxTree { + get { return syntaxTree; } + } + + /// <summary> + /// Generates C# code from the abstract source tree. + /// </summary> + /// <remarks>This method adds ParenthesizedExpressions into the AST, and will run transformations if <see cref="RunTransformations"/> was not called explicitly</remarks> + public void GenerateCode(ITextOutput output) + { + if (!transformationsHaveRun) + RunTransformations(); + + syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); + var outputFormatter = new TextTokenWriter(output, context) { FoldBraces = context.Settings.FoldBraces }; + var formattingPolicy = context.Settings.CSharpFormattingOptions; + syntaxTree.AcceptVisitor(new CSharpOutputVisitor(outputFormatter, formattingPolicy)); + } + + public void AddAssembly(AssemblyDefinition assemblyDefinition, bool onlyAssemblyLevel = false) + { + AddAssembly(assemblyDefinition.MainModule, onlyAssemblyLevel); + } + + public void AddAssembly(ModuleDefinition moduleDefinition, bool onlyAssemblyLevel = false) + { + if (moduleDefinition.Assembly != null && moduleDefinition.Assembly.Name.Version != null) { + syntaxTree.AddChild( + new AttributeSection { + AttributeTarget = "assembly", + Attributes = { + new NRefactory.CSharp.Attribute { + Type = new SimpleType("AssemblyVersion") + .WithAnnotation(new TypeReference( + "System.Reflection", "AssemblyVersionAttribute", + moduleDefinition, moduleDefinition.TypeSystem.Corlib)), + Arguments = { + new PrimitiveExpression(moduleDefinition.Assembly.Name.Version.ToString()) + } + } + } + }, EntityDeclaration.AttributeRole); + } + + if (moduleDefinition.Assembly != null) { + ConvertCustomAttributes(syntaxTree, moduleDefinition.Assembly, "assembly"); + ConvertSecurityAttributes(syntaxTree, moduleDefinition.Assembly, "assembly"); + } + ConvertCustomAttributes(syntaxTree, moduleDefinition, "module"); + AddTypeForwarderAttributes(syntaxTree, moduleDefinition, "assembly"); + + if (!onlyAssemblyLevel) { + foreach (TypeDefinition typeDef in moduleDefinition.Types) { + // Skip the <Module> class + if (typeDef.Name == "<Module>") continue; + // Skip any hidden types + if (MemberIsHidden(typeDef, context.Settings)) + continue; + + AddType(typeDef); + } + } + } + + void AddTypeForwarderAttributes(SyntaxTree astCompileUnit, ModuleDefinition module, string target) + { + if (!module.HasExportedTypes) + return; + foreach (ExportedType type in module.ExportedTypes) { + if (type.IsForwarder) { + var forwardedType = CreateTypeOfExpression(new TypeReference(type.Namespace, type.Name, module, type.Scope)); + astCompileUnit.AddChild( + new AttributeSection { + AttributeTarget = target, + Attributes = { + new NRefactory.CSharp.Attribute { + Type = new SimpleType("TypeForwardedTo") + .WithAnnotation(new TypeReference( + "System.Runtime.CompilerServices", "TypeForwardedToAttribute", + module, module.TypeSystem.Corlib)), + Arguments = { forwardedType } + } + } + }, EntityDeclaration.AttributeRole); + } + } + } + + NamespaceDeclaration GetCodeNamespace(string name) + { + if (string.IsNullOrEmpty(name)) { + return null; + } + if (astNamespaces.ContainsKey(name)) { + return astNamespaces[name]; + } else { + // Create the namespace + NamespaceDeclaration astNamespace = new NamespaceDeclaration { Name = name }; + syntaxTree.Members.Add(astNamespace); + astNamespaces[name] = astNamespace; + return astNamespace; + } + } + + public void AddType(TypeDefinition typeDef) + { + var astType = CreateType(typeDef); + NamespaceDeclaration astNS = GetCodeNamespace(typeDef.Namespace); + if (astNS != null) { + astNS.Members.Add(astType); + } else { + syntaxTree.Members.Add(astType); + } + } + + public void AddMethod(MethodDefinition method) + { + AstNode node = method.IsConstructor ? (AstNode)CreateConstructor(method) : CreateMethod(method); + syntaxTree.Members.Add(node); + } + + public void AddProperty(PropertyDefinition property) + { + syntaxTree.Members.Add(CreateProperty(property)); + } + + public void AddField(FieldDefinition field) + { + syntaxTree.Members.Add(CreateField(field)); + } + + public void AddEvent(EventDefinition ev) + { + syntaxTree.Members.Add(CreateEvent(ev)); + } + + /// <summary> + /// Creates the AST for a type definition. + /// </summary> + /// <param name="typeDef"></param> + /// <returns>TypeDeclaration or DelegateDeclaration.</returns> + public EntityDeclaration CreateType(TypeDefinition typeDef) + { + // create type + TypeDefinition oldCurrentType = context.CurrentType; + context.CurrentType = typeDef; + TypeDeclaration astType = new TypeDeclaration(); + ConvertAttributes(astType, typeDef); + astType.AddAnnotation(typeDef); + astType.Modifiers = ConvertModifiers(typeDef); + astType.Name = CleanName(typeDef.Name); + + if (typeDef.IsEnum) { // NB: Enum is value type + astType.ClassType = ClassType.Enum; + astType.Modifiers &= ~Modifiers.Sealed; + } else if (typeDef.IsValueType) { + astType.ClassType = ClassType.Struct; + astType.Modifiers &= ~Modifiers.Sealed; + } else if (typeDef.IsInterface) { + astType.ClassType = ClassType.Interface; + astType.Modifiers &= ~Modifiers.Abstract; + } else { + astType.ClassType = ClassType.Class; + } + + IEnumerable<GenericParameter> genericParameters = typeDef.GenericParameters; + if (typeDef.DeclaringType != null && typeDef.DeclaringType.HasGenericParameters) + genericParameters = genericParameters.Skip(typeDef.DeclaringType.GenericParameters.Count); + astType.TypeParameters.AddRange(MakeTypeParameters(genericParameters)); + astType.Constraints.AddRange(MakeConstraints(genericParameters)); + + EntityDeclaration result = astType; + if (typeDef.IsEnum) { + long expectedEnumMemberValue = 0; + bool forcePrintingInitializers = IsFlagsEnum(typeDef); + TypeCode baseType = TypeCode.Int32; + foreach (FieldDefinition field in typeDef.Fields) { + if (!field.IsStatic) { + // the value__ field + if (field.FieldType != typeDef.Module.TypeSystem.Int32) { + astType.AddChild(ConvertType(field.FieldType), Roles.BaseType); + baseType = TypeAnalysis.GetTypeCode(field.FieldType); + } + } else { + EnumMemberDeclaration enumMember = new EnumMemberDeclaration(); + enumMember.AddAnnotation(field); + enumMember.Name = CleanName(field.Name); + long memberValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, field.Constant, false); + if (forcePrintingInitializers || memberValue != expectedEnumMemberValue) { + enumMember.AddChild(new PrimitiveExpression(CSharpPrimitiveCast.Cast(baseType, field.Constant, false)), EnumMemberDeclaration.InitializerRole); + } + expectedEnumMemberValue = memberValue + 1; + astType.AddChild(enumMember, Roles.TypeMemberRole); + } + } + } else if (typeDef.BaseType != null && typeDef.BaseType.FullName == "System.MulticastDelegate") { + DelegateDeclaration dd = new DelegateDeclaration(); + dd.Modifiers = astType.Modifiers & ~Modifiers.Sealed; + dd.Name = astType.Name; + dd.AddAnnotation(typeDef); + astType.Attributes.MoveTo(dd.Attributes); + astType.TypeParameters.MoveTo(dd.TypeParameters); + astType.Constraints.MoveTo(dd.Constraints); + foreach (var m in typeDef.Methods) { + if (m.Name == "Invoke") { + dd.ReturnType = ConvertType(m.ReturnType, m.MethodReturnType); + dd.Parameters.AddRange(MakeParameters(m)); + ConvertAttributes(dd, m.MethodReturnType, m.Module); + } + } + result = dd; + } else { + // Base type + if (typeDef.BaseType != null && !typeDef.IsValueType && typeDef.BaseType.FullName != "System.Object") { + astType.AddChild(ConvertType(typeDef.BaseType), Roles.BaseType); + } + foreach (var i in typeDef.Interfaces) + astType.AddChild(ConvertType(i), Roles.BaseType); + + AddTypeMembers(astType, typeDef); + + if (astType.Members.OfType<IndexerDeclaration>().Any(idx => idx.PrivateImplementationType.IsNull)) { + // Remove the [DefaultMember] attribute if the class contains indexers + foreach (AttributeSection section in astType.Attributes) { + foreach (Ast.Attribute attr in section.Attributes) { + TypeReference tr = attr.Type.Annotation<TypeReference>(); + if (tr != null && tr.Name == "DefaultMemberAttribute" && tr.Namespace == "System.Reflection") { + attr.Remove(); + } + } + if (section.Attributes.Count == 0) + section.Remove(); + } + } + } + + context.CurrentType = oldCurrentType; + return result; + } + + internal static string CleanName(string name) + { + int pos = name.LastIndexOf('`'); + if (pos >= 0) + name = name.Substring(0, pos); + pos = name.LastIndexOf('.'); + if (pos >= 0) + name = name.Substring(pos + 1); + return name; + } + + #region Create TypeOf Expression + /// <summary> + /// Creates a typeof-expression for the specified type. + /// </summary> + public static TypeOfExpression CreateTypeOfExpression(TypeReference type) + { + return new TypeOfExpression(AddEmptyTypeArgumentsForUnboundGenerics(ConvertType(type))); + } + + static AstType AddEmptyTypeArgumentsForUnboundGenerics(AstType type) + { + TypeReference typeRef = type.Annotation<TypeReference>(); + 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; + } + #endregion + + #region Convert Type Reference + /// <summary> + /// Converts a type reference. + /// </summary> + /// <param name="type">The Cecil type reference that should be converted into + /// a type system type reference.</param> + /// <param name="typeAttributes">Attributes associated with the Cecil type reference. + /// This is used to support the 'dynamic' type.</param> + public static AstType ConvertType(TypeReference type, ICustomAttributeProvider typeAttributes = null, ConvertTypeOptions options = ConvertTypeOptions.None) + { + int typeIndex = 0; + return ConvertType(type, typeAttributes, ref typeIndex, options); + } + + static AstType ConvertType(TypeReference type, ICustomAttributeProvider typeAttributes, ref int typeIndex, ConvertTypeOptions options) + { + while (type is OptionalModifierType || type is RequiredModifierType) { + type = ((TypeSpecification)type).ElementType; + } + if (type == null) { + return AstType.Null; + } + + if (type is ByReferenceType) { + typeIndex++; + // by reference type cannot be represented in C#; so we'll represent it as a pointer instead + return ConvertType((type as ByReferenceType).ElementType, typeAttributes, ref typeIndex, options) + .MakePointerType(); + } else if (type is PointerType) { + typeIndex++; + return ConvertType((type as PointerType).ElementType, typeAttributes, ref typeIndex, options) + .MakePointerType(); + } else if (type is ArrayType) { + typeIndex++; + return ConvertType((type as ArrayType).ElementType, typeAttributes, ref typeIndex, options) + .MakeArrayType((type as ArrayType).Rank); + } else if (type is GenericInstanceType) { + GenericInstanceType gType = (GenericInstanceType)type; + if (gType.ElementType.Namespace == "System" && gType.ElementType.Name == "Nullable`1" && gType.GenericArguments.Count == 1) { + typeIndex++; + return new ComposedType { + BaseType = ConvertType(gType.GenericArguments[0], typeAttributes, ref typeIndex, options), + HasNullableSpecifier = true + }; + } + AstType baseType = ConvertType(gType.ElementType, typeAttributes, ref typeIndex, options & ~ConvertTypeOptions.IncludeTypeParameterDefinitions); + List<AstType> typeArguments = new List<AstType>(); + foreach (var typeArgument in gType.GenericArguments) { + typeIndex++; + typeArguments.Add(ConvertType(typeArgument, typeAttributes, ref typeIndex, options)); + } + ApplyTypeArgumentsTo(baseType, typeArguments); + return baseType; + } else if (type is GenericParameter) { + return new SimpleType(type.Name); + } else if (type.IsNested) { + AstType typeRef = ConvertType(type.DeclaringType, typeAttributes, ref typeIndex, options & ~ConvertTypeOptions.IncludeTypeParameterDefinitions); + string namepart = NRefactory.TypeSystem.ReflectionHelper.SplitTypeParameterCountFromReflectionName(type.Name); + MemberType memberType = new MemberType { Target = typeRef, MemberName = namepart }; + memberType.AddAnnotation(type); + if ((options & ConvertTypeOptions.IncludeTypeParameterDefinitions) == ConvertTypeOptions.IncludeTypeParameterDefinitions) { + AddTypeParameterDefininitionsTo(type, memberType); + } + return memberType; + } else { + string ns = type.Namespace ?? string.Empty; + string name = type.Name; + if (name == null) + throw new InvalidOperationException("type.Name returned null. Type: " + type.ToString()); + + if (name == "Object" && ns == "System" && HasDynamicAttribute(typeAttributes, typeIndex)) { + return new PrimitiveType("dynamic"); + } else { + if (ns == "System") { + if ((options & ConvertTypeOptions.DoNotUsePrimitiveTypeNames) + != ConvertTypeOptions.DoNotUsePrimitiveTypeNames) { + switch (name) { + case "SByte": + return new PrimitiveType("sbyte"); + case "Int16": + return new PrimitiveType("short"); + case "Int32": + return new PrimitiveType("int"); + case "Int64": + return new PrimitiveType("long"); + case "Byte": + return new PrimitiveType("byte"); + case "UInt16": + return new PrimitiveType("ushort"); + case "UInt32": + return new PrimitiveType("uint"); + case "UInt64": + return new PrimitiveType("ulong"); + case "String": + return new PrimitiveType("string"); + case "Single": + return new PrimitiveType("float"); + case "Double": + return new PrimitiveType("double"); + case "Decimal": + return new PrimitiveType("decimal"); + case "Char": + return new PrimitiveType("char"); + case "Boolean": + return new PrimitiveType("bool"); + case "Void": + return new PrimitiveType("void"); + case "Object": + return new PrimitiveType("object"); + } + } + } + + name = NRefactory.TypeSystem.ReflectionHelper.SplitTypeParameterCountFromReflectionName(name); + + AstType astType; + if ((options & ConvertTypeOptions.IncludeNamespace) == ConvertTypeOptions.IncludeNamespace && ns.Length > 0) { + string[] parts = ns.Split('.'); + AstType nsType = new SimpleType(parts[0]); + for (int i = 1; i < parts.Length; i++) { + nsType = new MemberType { Target = nsType, MemberName = parts[i] }; + } + astType = new MemberType { Target = nsType, MemberName = name }; + } else { + astType = new SimpleType(name); + } + astType.AddAnnotation(type); + + if ((options & ConvertTypeOptions.IncludeTypeParameterDefinitions) == ConvertTypeOptions.IncludeTypeParameterDefinitions) { + AddTypeParameterDefininitionsTo(type, astType); + } + return astType; + } + } + } + + static void AddTypeParameterDefininitionsTo(TypeReference type, AstType astType) + { + if (type.HasGenericParameters) { + List<AstType> typeArguments = new List<AstType>(); + foreach (GenericParameter gp in type.GenericParameters) { + typeArguments.Add(new SimpleType(gp.Name)); + } + ApplyTypeArgumentsTo(astType, typeArguments); + } + } + + static void ApplyTypeArgumentsTo(AstType baseType, List<AstType> typeArguments) + { + SimpleType st = baseType as SimpleType; + if (st != null) { + st.TypeArguments.AddRange(typeArguments); + } + MemberType mt = baseType as MemberType; + if (mt != null) { + TypeReference type = mt.Annotation<TypeReference>(); + if (type != null) { + int typeParameterCount; + NRefactory.TypeSystem.ReflectionHelper.SplitTypeParameterCountFromReflectionName(type.Name, out typeParameterCount); + if (typeParameterCount > typeArguments.Count) + typeParameterCount = typeArguments.Count; + mt.TypeArguments.AddRange(typeArguments.GetRange(typeArguments.Count - typeParameterCount, typeParameterCount)); + typeArguments.RemoveRange(typeArguments.Count - typeParameterCount, typeParameterCount); + if (typeArguments.Count > 0) + ApplyTypeArgumentsTo(mt.Target, typeArguments); + } else { + mt.TypeArguments.AddRange(typeArguments); + } + } + } + + const string DynamicAttributeFullName = "System.Runtime.CompilerServices.DynamicAttribute"; + + static bool HasDynamicAttribute(ICustomAttributeProvider attributeProvider, int typeIndex) + { + if (attributeProvider == null || !attributeProvider.HasCustomAttributes) + return false; + foreach (CustomAttribute a in attributeProvider.CustomAttributes) { + if (a.Constructor.DeclaringType.FullName == DynamicAttributeFullName) { + if (a.ConstructorArguments.Count == 1) { + CustomAttributeArgument[] values = a.ConstructorArguments[0].Value as CustomAttributeArgument[]; + if (values != null && typeIndex < values.Length && values[typeIndex].Value is bool) + return (bool)values[typeIndex].Value; + } + return true; + } + } + return false; + } + #endregion + + #region ConvertModifiers + Modifiers ConvertModifiers(TypeDefinition typeDef) + { + Modifiers modifiers = Modifiers.None; + if (typeDef.IsNestedPrivate) + modifiers |= Modifiers.Private; + else if (typeDef.IsNestedAssembly || typeDef.IsNestedFamilyAndAssembly || typeDef.IsNotPublic) + modifiers |= Modifiers.Internal; + else if (typeDef.IsNestedFamily) + modifiers |= Modifiers.Protected; + else if (typeDef.IsNestedFamilyOrAssembly) + modifiers |= Modifiers.Protected | Modifiers.Internal; + else if (typeDef.IsPublic || typeDef.IsNestedPublic) + modifiers |= Modifiers.Public; + + if (typeDef.IsAbstract && typeDef.IsSealed) + modifiers |= Modifiers.Static; + else if (typeDef.IsAbstract) + modifiers |= Modifiers.Abstract; + else if (typeDef.IsSealed) + modifiers |= Modifiers.Sealed; + + return modifiers; + } + + Modifiers ConvertModifiers(FieldDefinition fieldDef) + { + Modifiers modifiers = Modifiers.None; + if (fieldDef.IsPrivate) + modifiers |= Modifiers.Private; + else if (fieldDef.IsAssembly || fieldDef.IsFamilyAndAssembly) + modifiers |= Modifiers.Internal; + else if (fieldDef.IsFamily) + modifiers |= Modifiers.Protected; + else if (fieldDef.IsFamilyOrAssembly) + modifiers |= Modifiers.Protected | Modifiers.Internal; + else if (fieldDef.IsPublic) + modifiers |= Modifiers.Public; + + if (fieldDef.IsLiteral) { + modifiers |= Modifiers.Const; + } else { + if (fieldDef.IsStatic) + modifiers |= Modifiers.Static; + + if (fieldDef.IsInitOnly) + modifiers |= Modifiers.Readonly; + } + + RequiredModifierType modreq = fieldDef.FieldType as RequiredModifierType; + if (modreq != null && modreq.ModifierType.FullName == typeof(IsVolatile).FullName) + modifiers |= Modifiers.Volatile; + + return modifiers; + } + + Modifiers ConvertModifiers(MethodDefinition methodDef) + { + if (methodDef == null) + return Modifiers.None; + Modifiers modifiers = Modifiers.None; + if (methodDef.IsPrivate) + modifiers |= Modifiers.Private; + else if (methodDef.IsAssembly || methodDef.IsFamilyAndAssembly) + modifiers |= Modifiers.Internal; + else if (methodDef.IsFamily) + modifiers |= Modifiers.Protected; + else if (methodDef.IsFamilyOrAssembly) + modifiers |= Modifiers.Protected | Modifiers.Internal; + else if (methodDef.IsPublic) + modifiers |= Modifiers.Public; + + if (methodDef.IsStatic) + modifiers |= Modifiers.Static; + + if (methodDef.IsAbstract) { + modifiers |= Modifiers.Abstract; + if (!methodDef.IsNewSlot) + modifiers |= Modifiers.Override; + } else if (methodDef.IsFinal) { + if (!methodDef.IsNewSlot) { + modifiers |= Modifiers.Sealed | Modifiers.Override; + } + } else if (methodDef.IsVirtual) { + if (methodDef.IsNewSlot) + modifiers |= Modifiers.Virtual; + else + modifiers |= Modifiers.Override; + } + if (!methodDef.HasBody && !methodDef.IsAbstract) + modifiers |= Modifiers.Extern; + + return modifiers; + } + + #endregion + + void AddTypeMembers(TypeDeclaration astType, TypeDefinition typeDef) + { + // Nested types + foreach (TypeDefinition nestedTypeDef in typeDef.NestedTypes) { + if (MemberIsHidden(nestedTypeDef, context.Settings)) + continue; + var nestedType = CreateType(nestedTypeDef); + SetNewModifier(nestedType); + astType.AddChild(nestedType, Roles.TypeMemberRole); + } + + // Add fields + foreach(FieldDefinition fieldDef in typeDef.Fields) { + if (MemberIsHidden(fieldDef, context.Settings)) continue; + astType.AddChild(CreateField(fieldDef), Roles.TypeMemberRole); + } + + // Add events + foreach(EventDefinition eventDef in typeDef.Events) { + astType.AddChild(CreateEvent(eventDef), Roles.TypeMemberRole); + } + + // Add properties + foreach(PropertyDefinition propDef in typeDef.Properties) { + astType.Members.Add(CreateProperty(propDef)); + } + + // Add methods + foreach(MethodDefinition methodDef in typeDef.Methods) { + if (MemberIsHidden(methodDef, context.Settings)) continue; + + if (methodDef.IsConstructor) + astType.Members.Add(CreateConstructor(methodDef)); + else + astType.Members.Add(CreateMethod(methodDef)); + } + } + + EntityDeclaration CreateMethod(MethodDefinition methodDef) + { + MethodDeclaration astMethod = new MethodDeclaration(); + astMethod.AddAnnotation(methodDef); + astMethod.ReturnType = ConvertType(methodDef.ReturnType, methodDef.MethodReturnType); + astMethod.Name = CleanName(methodDef.Name); + astMethod.TypeParameters.AddRange(MakeTypeParameters(methodDef.GenericParameters)); + astMethod.Parameters.AddRange(MakeParameters(methodDef)); + // constraints for override and explicit interface implementation methods are inherited from the base method, so they cannot be specified directly + if (!methodDef.IsVirtual || (methodDef.IsNewSlot && !methodDef.IsPrivate)) astMethod.Constraints.AddRange(MakeConstraints(methodDef.GenericParameters)); + if (!methodDef.DeclaringType.IsInterface) { + if (IsExplicitInterfaceImplementation(methodDef)) { + astMethod.PrivateImplementationType = ConvertType(methodDef.Overrides.First().DeclaringType); + } else { + astMethod.Modifiers = ConvertModifiers(methodDef); + if (methodDef.IsVirtual == methodDef.IsNewSlot) + SetNewModifier(astMethod); + } + astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters); + if (context.CurrentMethodIsAsync) { + astMethod.Modifiers |= Modifiers.Async; + context.CurrentMethodIsAsync = false; + } + } + ConvertAttributes(astMethod, methodDef); + if (methodDef.HasCustomAttributes && astMethod.Parameters.Count > 0) { + foreach (CustomAttribute ca in methodDef.CustomAttributes) { + if (ca.AttributeType.Name == "ExtensionAttribute" && ca.AttributeType.Namespace == "System.Runtime.CompilerServices") { + astMethod.Parameters.First().ParameterModifier = ParameterModifier.This; + } + } + } + + // Convert MethodDeclaration to OperatorDeclaration if possible + if (methodDef.IsSpecialName && !methodDef.HasGenericParameters) { + OperatorType? opType = OperatorDeclaration.GetOperatorType(methodDef.Name); + if (opType.HasValue) { + OperatorDeclaration op = new OperatorDeclaration(); + op.CopyAnnotationsFrom(astMethod); + op.ReturnType = astMethod.ReturnType.Detach(); + op.OperatorType = opType.Value; + op.Modifiers = astMethod.Modifiers; + astMethod.Parameters.MoveTo(op.Parameters); + astMethod.Attributes.MoveTo(op.Attributes); + op.Body = astMethod.Body.Detach(); + return op; + } + } + return astMethod; + } + + bool IsExplicitInterfaceImplementation(MethodDefinition methodDef) + { + return methodDef.HasOverrides && methodDef.IsPrivate; + } + + IEnumerable<TypeParameterDeclaration> MakeTypeParameters(IEnumerable<GenericParameter> genericParameters) + { + foreach (var gp in genericParameters) { + TypeParameterDeclaration tp = new TypeParameterDeclaration(); + tp.Name = CleanName(gp.Name); + if (gp.IsContravariant) + tp.Variance = VarianceModifier.Contravariant; + else if (gp.IsCovariant) + tp.Variance = VarianceModifier.Covariant; + ConvertCustomAttributes(tp, gp); + yield return tp; + } + } + + IEnumerable<Constraint> MakeConstraints(IEnumerable<GenericParameter> genericParameters) + { + foreach (var gp in genericParameters) { + Constraint c = new Constraint(); + c.TypeParameter = new SimpleType(CleanName(gp.Name)); + // class/struct must be first + if (gp.HasReferenceTypeConstraint) + c.BaseTypes.Add(new PrimitiveType("class")); + if (gp.HasNotNullableValueTypeConstraint) + c.BaseTypes.Add(new PrimitiveType("struct")); + + foreach (var constraintType in gp.Constraints) { + if (gp.HasNotNullableValueTypeConstraint && constraintType.FullName == "System.ValueType") + continue; + c.BaseTypes.Add(ConvertType(constraintType)); + } + + if (gp.HasDefaultConstructorConstraint && !gp.HasNotNullableValueTypeConstraint) + c.BaseTypes.Add(new PrimitiveType("new")); // new() must be last + if (c.BaseTypes.Any()) + yield return c; + } + } + + ConstructorDeclaration CreateConstructor(MethodDefinition methodDef) + { + ConstructorDeclaration astMethod = new ConstructorDeclaration(); + astMethod.AddAnnotation(methodDef); + astMethod.Modifiers = ConvertModifiers(methodDef); + if (methodDef.IsStatic) { + // don't show visibility for static ctors + astMethod.Modifiers &= ~Modifiers.VisibilityMask; + } + astMethod.Name = CleanName(methodDef.DeclaringType.Name); + astMethod.Parameters.AddRange(MakeParameters(methodDef)); + astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters); + ConvertAttributes(astMethod, methodDef); + if (methodDef.IsStatic && methodDef.DeclaringType.IsBeforeFieldInit && !astMethod.Body.IsNull) { + astMethod.Body.InsertChildAfter(null, new Comment(" Note: this type is marked as 'beforefieldinit'."), Roles.Comment); + } + return astMethod; + } + + Modifiers FixUpVisibility(Modifiers m) + { + Modifiers v = m & Modifiers.VisibilityMask; + // If any of the modifiers is public, use that + if ((v & Modifiers.Public) == Modifiers.Public) + return Modifiers.Public | (m & ~Modifiers.VisibilityMask); + // If both modifiers are private, no need to fix anything + if (v == Modifiers.Private) + return m; + // Otherwise, use the other modifiers (internal and/or protected) + return m & ~Modifiers.Private; + } + + EntityDeclaration CreateProperty(PropertyDefinition propDef) + { + PropertyDeclaration astProp = new PropertyDeclaration(); + astProp.AddAnnotation(propDef); + var accessor = propDef.GetMethod ?? propDef.SetMethod; + Modifiers getterModifiers = Modifiers.None; + Modifiers setterModifiers = Modifiers.None; + if (IsExplicitInterfaceImplementation(accessor)) { + astProp.PrivateImplementationType = ConvertType(accessor.Overrides.First().DeclaringType); + } else if (!propDef.DeclaringType.IsInterface) { + getterModifiers = ConvertModifiers(propDef.GetMethod); + setterModifiers = ConvertModifiers(propDef.SetMethod); + astProp.Modifiers = FixUpVisibility(getterModifiers | setterModifiers); + try { + if (accessor.IsVirtual && !accessor.IsNewSlot && (propDef.GetMethod == null || propDef.SetMethod == null)) { + foreach (var basePropDef in TypesHierarchyHelpers.FindBaseProperties(propDef)) { + if (basePropDef.GetMethod != null && basePropDef.SetMethod != null) { + var propVisibilityModifiers = ConvertModifiers(basePropDef.GetMethod) | ConvertModifiers(basePropDef.SetMethod); + astProp.Modifiers = FixUpVisibility((astProp.Modifiers & ~Modifiers.VisibilityMask) | (propVisibilityModifiers & Modifiers.VisibilityMask)); + break; + } else if ((basePropDef.GetMethod ?? basePropDef.SetMethod).IsNewSlot) { + break; + } + } + } + } catch (ReferenceResolvingException) { + // TODO: add some kind of notification (a comment?) about possible problems with decompiled code due to unresolved references. + } + } + astProp.Name = CleanName(propDef.Name); + astProp.ReturnType = ConvertType(propDef.PropertyType, propDef); + + if (propDef.GetMethod != null) { + astProp.Getter = new Accessor(); + astProp.Getter.Body = CreateMethodBody(propDef.GetMethod); + astProp.Getter.AddAnnotation(propDef.GetMethod); + ConvertAttributes(astProp.Getter, propDef.GetMethod); + + if ((getterModifiers & Modifiers.VisibilityMask) != (astProp.Modifiers & Modifiers.VisibilityMask)) + astProp.Getter.Modifiers = getterModifiers & Modifiers.VisibilityMask; + } + if (propDef.SetMethod != null) { + astProp.Setter = new Accessor(); + astProp.Setter.Body = CreateMethodBody(propDef.SetMethod); + astProp.Setter.AddAnnotation(propDef.SetMethod); + ConvertAttributes(astProp.Setter, propDef.SetMethod); + ParameterDefinition lastParam = propDef.SetMethod.Parameters.LastOrDefault(); + if (lastParam != null) { + ConvertCustomAttributes(astProp.Setter, lastParam, "param"); + if (lastParam.HasMarshalInfo) { + astProp.Setter.Attributes.Add(new AttributeSection(ConvertMarshalInfo(lastParam, propDef.Module)) { AttributeTarget = "param" }); + } + } + + if ((setterModifiers & Modifiers.VisibilityMask) != (astProp.Modifiers & Modifiers.VisibilityMask)) + astProp.Setter.Modifiers = setterModifiers & Modifiers.VisibilityMask; + } + ConvertCustomAttributes(astProp, propDef); + + EntityDeclaration member = astProp; + if(propDef.IsIndexer()) + member = ConvertPropertyToIndexer(astProp, propDef); + if(!accessor.HasOverrides && !accessor.DeclaringType.IsInterface) + if (accessor.IsVirtual == accessor.IsNewSlot) + SetNewModifier(member); + return member; + } + + IndexerDeclaration ConvertPropertyToIndexer(PropertyDeclaration astProp, PropertyDefinition propDef) + { + var astIndexer = new IndexerDeclaration(); + astIndexer.CopyAnnotationsFrom(astProp); + astProp.Attributes.MoveTo(astIndexer.Attributes); + astIndexer.Modifiers = astProp.Modifiers; + astIndexer.PrivateImplementationType = astProp.PrivateImplementationType.Detach(); + astIndexer.ReturnType = astProp.ReturnType.Detach(); + astIndexer.Getter = astProp.Getter.Detach(); + astIndexer.Setter = astProp.Setter.Detach(); + astIndexer.Parameters.AddRange(MakeParameters(propDef.Parameters)); + return astIndexer; + } + + EntityDeclaration CreateEvent(EventDefinition eventDef) + { + if (eventDef.AddMethod != null && eventDef.AddMethod.IsAbstract) { + // An abstract event cannot be custom + EventDeclaration astEvent = new EventDeclaration(); + ConvertCustomAttributes(astEvent, eventDef); + astEvent.AddAnnotation(eventDef); + astEvent.Variables.Add(new VariableInitializer(CleanName(eventDef.Name))); + astEvent.ReturnType = ConvertType(eventDef.EventType, eventDef); + if (!eventDef.DeclaringType.IsInterface) + astEvent.Modifiers = ConvertModifiers(eventDef.AddMethod); + return astEvent; + } else { + CustomEventDeclaration astEvent = new CustomEventDeclaration(); + ConvertCustomAttributes(astEvent, eventDef); + astEvent.AddAnnotation(eventDef); + astEvent.Name = CleanName(eventDef.Name); + astEvent.ReturnType = ConvertType(eventDef.EventType, eventDef); + if (eventDef.AddMethod == null || !IsExplicitInterfaceImplementation(eventDef.AddMethod)) + astEvent.Modifiers = ConvertModifiers(eventDef.AddMethod); + else + astEvent.PrivateImplementationType = ConvertType(eventDef.AddMethod.Overrides.First().DeclaringType); + + if (eventDef.AddMethod != null) { + astEvent.AddAccessor = new Accessor { + Body = CreateMethodBody(eventDef.AddMethod) + }.WithAnnotation(eventDef.AddMethod); + ConvertAttributes(astEvent.AddAccessor, eventDef.AddMethod); + } + if (eventDef.RemoveMethod != null) { + astEvent.RemoveAccessor = new Accessor { + Body = CreateMethodBody(eventDef.RemoveMethod) + }.WithAnnotation(eventDef.RemoveMethod); + ConvertAttributes(astEvent.RemoveAccessor, eventDef.RemoveMethod); + } + MethodDefinition accessor = eventDef.AddMethod ?? eventDef.RemoveMethod; + if (accessor.IsVirtual == accessor.IsNewSlot) { + SetNewModifier(astEvent); + } + return astEvent; + } + } + + public bool DecompileMethodBodies { get; set; } + + BlockStatement CreateMethodBody(MethodDefinition method, IEnumerable<ParameterDeclaration> parameters = null) + { + if (DecompileMethodBodies) + return AstMethodBodyBuilder.CreateMethodBody(method, context, parameters); + else + return null; + } + + FieldDeclaration CreateField(FieldDefinition fieldDef) + { + FieldDeclaration astField = new FieldDeclaration(); + astField.AddAnnotation(fieldDef); + VariableInitializer initializer = new VariableInitializer(CleanName(fieldDef.Name)); + astField.AddChild(initializer, Roles.Variable); + astField.ReturnType = ConvertType(fieldDef.FieldType, fieldDef); + astField.Modifiers = ConvertModifiers(fieldDef); + if (fieldDef.HasConstant) { + initializer.Initializer = CreateExpressionForConstant(fieldDef.Constant, fieldDef.FieldType, fieldDef.DeclaringType.IsEnum); + } + ConvertAttributes(astField, fieldDef); + SetNewModifier(astField); + return astField; + } + + static Expression CreateExpressionForConstant(object constant, TypeReference type, bool isEnumMemberDeclaration = false) + { + if (constant == null) { + if (type.IsValueType && !(type.Namespace == "System" && type.Name == "Nullable`1")) + return new DefaultValueExpression(ConvertType(type)); + else + return new NullReferenceExpression(); + } else { + TypeCode c = Type.GetTypeCode(constant.GetType()); + if (c >= TypeCode.SByte && c <= TypeCode.UInt64 && !isEnumMemberDeclaration) { + return MakePrimitive((long)CSharpPrimitiveCast.Cast(TypeCode.Int64, constant, false), type); + } else { + return new PrimitiveExpression(constant); + } + } + } + + public static IEnumerable<ParameterDeclaration> MakeParameters(MethodDefinition method, bool isLambda = false) + { + var parameters = MakeParameters(method.Parameters, isLambda); + if (method.CallingConvention == MethodCallingConvention.VarArg) { + return parameters.Concat(new[] { new ParameterDeclaration { Type = new PrimitiveType("__arglist") } }); + } else { + return parameters; + } + } + + public static IEnumerable<ParameterDeclaration> MakeParameters(IEnumerable<ParameterDefinition> paramCol, bool isLambda = false) + { + foreach(ParameterDefinition paramDef in paramCol) { + ParameterDeclaration astParam = new ParameterDeclaration(); + astParam.AddAnnotation(paramDef); + if (!(isLambda && paramDef.ParameterType.ContainsAnonymousType())) + astParam.Type = ConvertType(paramDef.ParameterType, paramDef); + astParam.Name = paramDef.Name; + + if (paramDef.ParameterType is ByReferenceType) { + astParam.ParameterModifier = (!paramDef.IsIn && paramDef.IsOut) ? ParameterModifier.Out : ParameterModifier.Ref; + ComposedType ct = astParam.Type as ComposedType; + if (ct != null && ct.PointerRank > 0) + ct.PointerRank--; + } + + if (paramDef.HasCustomAttributes) { + foreach (CustomAttribute ca in paramDef.CustomAttributes) { + if (ca.AttributeType.Name == "ParamArrayAttribute" && ca.AttributeType.Namespace == "System") + astParam.ParameterModifier = ParameterModifier.Params; + } + } + if (paramDef.IsOptional) { + astParam.DefaultExpression = CreateExpressionForConstant(paramDef.Constant, paramDef.ParameterType); + } + + ConvertCustomAttributes(astParam, paramDef); + ModuleDefinition module = ((MethodDefinition)paramDef.Method).Module; + if (paramDef.HasMarshalInfo) { + astParam.Attributes.Add(new AttributeSection(ConvertMarshalInfo(paramDef, module))); + } + if (astParam.ParameterModifier != ParameterModifier.Out) { + if (paramDef.IsIn) + astParam.Attributes.Add(new AttributeSection(CreateNonCustomAttribute(typeof(InAttribute), module))); + if (paramDef.IsOut) + astParam.Attributes.Add(new AttributeSection(CreateNonCustomAttribute(typeof(OutAttribute), module))); + } + yield return astParam; + } + } + + #region ConvertAttributes + void ConvertAttributes(EntityDeclaration attributedNode, TypeDefinition typeDefinition) + { + ConvertCustomAttributes(attributedNode, typeDefinition); + ConvertSecurityAttributes(attributedNode, typeDefinition); + + // Handle the non-custom attributes: + #region SerializableAttribute + if (typeDefinition.IsSerializable) + attributedNode.Attributes.Add(new AttributeSection(CreateNonCustomAttribute(typeof(SerializableAttribute)))); + #endregion + + #region ComImportAttribute + if (typeDefinition.IsImport) + attributedNode.Attributes.Add(new AttributeSection(CreateNonCustomAttribute(typeof(ComImportAttribute)))); + #endregion + + #region StructLayoutAttribute + LayoutKind layoutKind = LayoutKind.Auto; + switch (typeDefinition.Attributes & TypeAttributes.LayoutMask) { + case TypeAttributes.SequentialLayout: + layoutKind = LayoutKind.Sequential; + break; + case TypeAttributes.ExplicitLayout: + layoutKind = LayoutKind.Explicit; + break; + } + CharSet charSet = CharSet.None; + switch (typeDefinition.Attributes & TypeAttributes.StringFormatMask) { + case TypeAttributes.AnsiClass: + charSet = CharSet.Ansi; + break; + case TypeAttributes.AutoClass: + charSet = CharSet.Auto; + break; + case TypeAttributes.UnicodeClass: + charSet = CharSet.Unicode; + break; + } + LayoutKind defaultLayoutKind = (typeDefinition.IsValueType && !typeDefinition.IsEnum) ? LayoutKind.Sequential: LayoutKind.Auto; + if (layoutKind != defaultLayoutKind || charSet != CharSet.Ansi || typeDefinition.PackingSize > 0 || typeDefinition.ClassSize > 0) { + var structLayout = CreateNonCustomAttribute(typeof(StructLayoutAttribute)); + structLayout.Arguments.Add(new IdentifierExpression("LayoutKind").Member(layoutKind.ToString())); + if (charSet != CharSet.Ansi) { + structLayout.AddNamedArgument("CharSet", new IdentifierExpression("CharSet").Member(charSet.ToString())); + } + if (typeDefinition.PackingSize > 0) { + structLayout.AddNamedArgument("Pack", new PrimitiveExpression((int)typeDefinition.PackingSize)); + } + if (typeDefinition.ClassSize > 0) { + structLayout.AddNamedArgument("Size", new PrimitiveExpression((int)typeDefinition.ClassSize)); + } + attributedNode.Attributes.Add(new AttributeSection(structLayout)); + } + #endregion + } + + void ConvertAttributes(EntityDeclaration attributedNode, MethodDefinition methodDefinition) + { + ConvertCustomAttributes(attributedNode, methodDefinition); + ConvertSecurityAttributes(attributedNode, methodDefinition); + + MethodImplAttributes implAttributes = methodDefinition.ImplAttributes & ~MethodImplAttributes.CodeTypeMask; + + #region DllImportAttribute + if (methodDefinition.HasPInvokeInfo && methodDefinition.PInvokeInfo != null) { + PInvokeInfo info = methodDefinition.PInvokeInfo; + Ast.Attribute dllImport = CreateNonCustomAttribute(typeof(DllImportAttribute)); + dllImport.Arguments.Add(new PrimitiveExpression(info.Module.Name)); + + if (info.IsBestFitDisabled) + dllImport.AddNamedArgument("BestFitMapping", new PrimitiveExpression(false)); + if (info.IsBestFitEnabled) + dllImport.AddNamedArgument("BestFitMapping", new PrimitiveExpression(true)); + + CallingConvention callingConvention; + switch (info.Attributes & PInvokeAttributes.CallConvMask) { + case PInvokeAttributes.CallConvCdecl: + callingConvention = CallingConvention.Cdecl; + break; + case PInvokeAttributes.CallConvFastcall: + callingConvention = CallingConvention.FastCall; + break; + case PInvokeAttributes.CallConvStdCall: + callingConvention = CallingConvention.StdCall; + break; + case PInvokeAttributes.CallConvThiscall: + callingConvention = CallingConvention.ThisCall; + break; + case PInvokeAttributes.CallConvWinapi: + callingConvention = CallingConvention.Winapi; + break; + default: + throw new NotSupportedException("unknown calling convention"); + } + if (callingConvention != CallingConvention.Winapi) + dllImport.AddNamedArgument("CallingConvention", new IdentifierExpression("CallingConvention").Member(callingConvention.ToString())); + + CharSet charSet = CharSet.None; + switch (info.Attributes & PInvokeAttributes.CharSetMask) { + case PInvokeAttributes.CharSetAnsi: + charSet = CharSet.Ansi; + break; + case PInvokeAttributes.CharSetAuto: + charSet = CharSet.Auto; + break; + case PInvokeAttributes.CharSetUnicode: + charSet = CharSet.Unicode; + break; + } + if (charSet != CharSet.None) + dllImport.AddNamedArgument("CharSet", new IdentifierExpression("CharSet").Member(charSet.ToString())); + + if (!string.IsNullOrEmpty(info.EntryPoint) && info.EntryPoint != methodDefinition.Name) + dllImport.AddNamedArgument("EntryPoint", new PrimitiveExpression(info.EntryPoint)); + + if (info.IsNoMangle) + dllImport.AddNamedArgument("ExactSpelling", new PrimitiveExpression(true)); + + if ((implAttributes & MethodImplAttributes.PreserveSig) == MethodImplAttributes.PreserveSig) + implAttributes &= ~MethodImplAttributes.PreserveSig; + else + dllImport.AddNamedArgument("PreserveSig", new PrimitiveExpression(false)); + + if (info.SupportsLastError) + dllImport.AddNamedArgument("SetLastError", new PrimitiveExpression(true)); + + if (info.IsThrowOnUnmappableCharDisabled) + dllImport.AddNamedArgument("ThrowOnUnmappableChar", new PrimitiveExpression(false)); + if (info.IsThrowOnUnmappableCharEnabled) + dllImport.AddNamedArgument("ThrowOnUnmappableChar", new PrimitiveExpression(true)); + + attributedNode.Attributes.Add(new AttributeSection(dllImport)); + } + #endregion + + #region PreserveSigAttribute + if (implAttributes == MethodImplAttributes.PreserveSig) { + attributedNode.Attributes.Add(new AttributeSection(CreateNonCustomAttribute(typeof(PreserveSigAttribute)))); + implAttributes = 0; + } + #endregion + + #region MethodImplAttribute + if (implAttributes != 0) { + Ast.Attribute methodImpl = CreateNonCustomAttribute(typeof(MethodImplAttribute)); + TypeReference methodImplOptions = new TypeReference( + "System.Runtime.CompilerServices", "MethodImplOptions", + methodDefinition.Module, methodDefinition.Module.TypeSystem.Corlib); + methodImpl.Arguments.Add(MakePrimitive((long)implAttributes, methodImplOptions)); + attributedNode.Attributes.Add(new AttributeSection(methodImpl)); + } + #endregion + + ConvertAttributes(attributedNode, methodDefinition.MethodReturnType, methodDefinition.Module); + } + + void ConvertAttributes(EntityDeclaration attributedNode, MethodReturnType methodReturnType, ModuleDefinition module) + { + ConvertCustomAttributes(attributedNode, methodReturnType, "return"); + if (methodReturnType.HasMarshalInfo) { + var marshalInfo = ConvertMarshalInfo(methodReturnType, module); + attributedNode.Attributes.Add(new AttributeSection(marshalInfo) { AttributeTarget = "return" }); + } + } + + internal static void ConvertAttributes(EntityDeclaration attributedNode, FieldDefinition fieldDefinition, string attributeTarget = null) + { + ConvertCustomAttributes(attributedNode, fieldDefinition); + + #region FieldOffsetAttribute + if (fieldDefinition.HasLayoutInfo) { + Ast.Attribute fieldOffset = CreateNonCustomAttribute(typeof(FieldOffsetAttribute), fieldDefinition.Module); + fieldOffset.Arguments.Add(new PrimitiveExpression(fieldDefinition.Offset)); + attributedNode.Attributes.Add(new AttributeSection(fieldOffset) { AttributeTarget = attributeTarget }); + } + #endregion + + #region NonSerializedAttribute + if (fieldDefinition.IsNotSerialized) { + Ast.Attribute nonSerialized = CreateNonCustomAttribute(typeof(NonSerializedAttribute), fieldDefinition.Module); + attributedNode.Attributes.Add(new AttributeSection(nonSerialized) { AttributeTarget = attributeTarget }); + } + #endregion + + if (fieldDefinition.HasMarshalInfo) { + attributedNode.Attributes.Add(new AttributeSection(ConvertMarshalInfo(fieldDefinition, fieldDefinition.Module)) { AttributeTarget = attributeTarget }); + } + } + + #region MarshalAsAttribute (ConvertMarshalInfo) + static Ast.Attribute ConvertMarshalInfo(IMarshalInfoProvider marshalInfoProvider, ModuleDefinition module) + { + MarshalInfo marshalInfo = marshalInfoProvider.MarshalInfo; + Ast.Attribute attr = CreateNonCustomAttribute(typeof(MarshalAsAttribute), module); + var unmanagedType = new TypeReference("System.Runtime.InteropServices", "UnmanagedType", module, module.TypeSystem.Corlib); + attr.Arguments.Add(MakePrimitive((int)marshalInfo.NativeType, unmanagedType)); + + FixedArrayMarshalInfo fami = marshalInfo as FixedArrayMarshalInfo; + if (fami != null) { + attr.AddNamedArgument("SizeConst", new PrimitiveExpression(fami.Size)); + if (fami.ElementType != NativeType.None) + attr.AddNamedArgument("ArraySubType", MakePrimitive((int)fami.ElementType, unmanagedType)); + } + SafeArrayMarshalInfo sami = marshalInfo as SafeArrayMarshalInfo; + if (sami != null && sami.ElementType != VariantType.None) { + var varEnum = new TypeReference("System.Runtime.InteropServices", "VarEnum", module, module.TypeSystem.Corlib); + attr.AddNamedArgument("SafeArraySubType", MakePrimitive((int)sami.ElementType, varEnum)); + } + ArrayMarshalInfo ami = marshalInfo as ArrayMarshalInfo; + if (ami != null) { + if (ami.ElementType != NativeType.Max) + attr.AddNamedArgument("ArraySubType", MakePrimitive((int)ami.ElementType, unmanagedType)); + if (ami.Size >= 0) + attr.AddNamedArgument("SizeConst", new PrimitiveExpression(ami.Size)); + if (ami.SizeParameterMultiplier != 0 && ami.SizeParameterIndex >= 0) + attr.AddNamedArgument("SizeParamIndex", new PrimitiveExpression(ami.SizeParameterIndex)); + } + CustomMarshalInfo cmi = marshalInfo as CustomMarshalInfo; + if (cmi != null) { + attr.AddNamedArgument("MarshalType", new PrimitiveExpression(cmi.ManagedType.FullName)); + if (!string.IsNullOrEmpty(cmi.Cookie)) + attr.AddNamedArgument("MarshalCookie", new PrimitiveExpression(cmi.Cookie)); + } + FixedSysStringMarshalInfo fssmi = marshalInfo as FixedSysStringMarshalInfo; + if (fssmi != null) { + attr.AddNamedArgument("SizeConst", new PrimitiveExpression(fssmi.Size)); + } + return attr; + } + #endregion + + Ast.Attribute CreateNonCustomAttribute(Type attributeType) + { + return CreateNonCustomAttribute(attributeType, context.CurrentType != null ? context.CurrentType.Module : null); + } + + static Ast.Attribute CreateNonCustomAttribute(Type attributeType, ModuleDefinition module) + { + Debug.Assert(attributeType.Name.EndsWith("Attribute", StringComparison.Ordinal)); + Ast.Attribute attr = new Ast.Attribute(); + attr.Type = new SimpleType(attributeType.Name.Substring(0, attributeType.Name.Length - "Attribute".Length)); + if (module != null) { + attr.Type.AddAnnotation(new TypeReference(attributeType.Namespace, attributeType.Name, module, module.TypeSystem.Corlib)); + } + return attr; + } + + static void ConvertCustomAttributes(AstNode attributedNode, ICustomAttributeProvider customAttributeProvider, string attributeTarget = null) + { + EntityDeclaration entityDecl = attributedNode as EntityDeclaration; + if (customAttributeProvider.HasCustomAttributes) { + var attributes = new List<NRefactory.CSharp.Attribute>(); + foreach (var customAttribute in customAttributeProvider.CustomAttributes.OrderBy(a => a.AttributeType.FullName)) { + if (customAttribute.AttributeType.Name == "ExtensionAttribute" && customAttribute.AttributeType.Namespace == "System.Runtime.CompilerServices") { + // don't show the ExtensionAttribute (it's converted to the 'this' modifier) + continue; + } + if (customAttribute.AttributeType.Name == "ParamArrayAttribute" && customAttribute.AttributeType.Namespace == "System") { + // don't show the ParamArrayAttribute (it's converted to the 'params' modifier) + continue; + } + // if the method is async, remove [DebuggerStepThrough] and [Async + if (entityDecl != null && entityDecl.HasModifier(Modifiers.Async)) { + if (customAttribute.AttributeType.Name == "DebuggerStepThroughAttribute" && customAttribute.AttributeType.Namespace == "System.Diagnostics") { + continue; + } + if (customAttribute.AttributeType.Name == "AsyncStateMachineAttribute" && customAttribute.AttributeType.Namespace == "System.Runtime.CompilerServices") { + continue; + } + } + + var attribute = new NRefactory.CSharp.Attribute(); + attribute.AddAnnotation(customAttribute); + attribute.Type = ConvertType(customAttribute.AttributeType); + attributes.Add(attribute); + + SimpleType st = attribute.Type as SimpleType; + if (st != null && st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) { + st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - "Attribute".Length); + } + + if(customAttribute.HasConstructorArguments) { + foreach (var parameter in customAttribute.ConstructorArguments) { + Expression parameterValue = ConvertArgumentValue(parameter); + attribute.Arguments.Add(parameterValue); + } + } + if (customAttribute.HasProperties) { + TypeDefinition resolvedAttributeType = customAttribute.AttributeType.Resolve(); + foreach (var propertyNamedArg in customAttribute.Properties) { + var propertyReference = resolvedAttributeType != null ? resolvedAttributeType.Properties.FirstOrDefault(pr => pr.Name == propertyNamedArg.Name) : null; + var propertyName = new IdentifierExpression(propertyNamedArg.Name).WithAnnotation(propertyReference); + var argumentValue = ConvertArgumentValue(propertyNamedArg.Argument); + attribute.Arguments.Add(new AssignmentExpression(propertyName, argumentValue)); + } + } + + if (customAttribute.HasFields) { + TypeDefinition resolvedAttributeType = customAttribute.AttributeType.Resolve(); + foreach (var fieldNamedArg in customAttribute.Fields) { + var fieldReference = resolvedAttributeType != null ? resolvedAttributeType.Fields.FirstOrDefault(f => f.Name == fieldNamedArg.Name) : null; + var fieldName = new IdentifierExpression(fieldNamedArg.Name).WithAnnotation(fieldReference); + var argumentValue = ConvertArgumentValue(fieldNamedArg.Argument); + attribute.Arguments.Add(new AssignmentExpression(fieldName, argumentValue)); + } + } + } + + if (attributeTarget == "module" || attributeTarget == "assembly") { + // use separate section for each attribute + foreach (var attribute in attributes) { + var section = new AttributeSection(); + section.AttributeTarget = attributeTarget; + section.Attributes.Add(attribute); + attributedNode.AddChild(section, EntityDeclaration.AttributeRole); + } + } else if (attributes.Count > 0) { + // use single section for all attributes + var section = new AttributeSection(); + section.AttributeTarget = attributeTarget; + section.Attributes.AddRange(attributes); + attributedNode.AddChild(section, EntityDeclaration.AttributeRole); + } + } + } + + static void ConvertSecurityAttributes(AstNode attributedNode, ISecurityDeclarationProvider secDeclProvider, string attributeTarget = null) + { + if (!secDeclProvider.HasSecurityDeclarations) + return; + var attributes = new List<NRefactory.CSharp.Attribute>(); + foreach (var secDecl in secDeclProvider.SecurityDeclarations.OrderBy(d => d.Action)) { + foreach (var secAttribute in secDecl.SecurityAttributes.OrderBy(a => a.AttributeType.FullName)) { + var attribute = new NRefactory.CSharp.Attribute(); + attribute.AddAnnotation(secAttribute); + attribute.Type = ConvertType(secAttribute.AttributeType); + attributes.Add(attribute); + + SimpleType st = attribute.Type as SimpleType; + if (st != null && st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) { + st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - "Attribute".Length); + } + + var module = secAttribute.AttributeType.Module; + var securityActionType = new TypeReference("System.Security.Permissions", "SecurityAction", module, module.TypeSystem.Corlib); + attribute.Arguments.Add(MakePrimitive((int)secDecl.Action, securityActionType)); + + if (secAttribute.HasProperties) { + TypeDefinition resolvedAttributeType = secAttribute.AttributeType.Resolve(); + foreach (var propertyNamedArg in secAttribute.Properties) { + var propertyReference = resolvedAttributeType != null ? resolvedAttributeType.Properties.FirstOrDefault(pr => pr.Name == propertyNamedArg.Name) : null; + var propertyName = new IdentifierExpression(propertyNamedArg.Name).WithAnnotation(propertyReference); + var argumentValue = ConvertArgumentValue(propertyNamedArg.Argument); + attribute.Arguments.Add(new AssignmentExpression(propertyName, argumentValue)); + } + } + + if (secAttribute.HasFields) { + TypeDefinition resolvedAttributeType = secAttribute.AttributeType.Resolve(); + foreach (var fieldNamedArg in secAttribute.Fields) { + var fieldReference = resolvedAttributeType != null ? resolvedAttributeType.Fields.FirstOrDefault(f => f.Name == fieldNamedArg.Name) : null; + var fieldName = new IdentifierExpression(fieldNamedArg.Name).WithAnnotation(fieldReference); + var argumentValue = ConvertArgumentValue(fieldNamedArg.Argument); + attribute.Arguments.Add(new AssignmentExpression(fieldName, argumentValue)); + } + } + } + } + if (attributeTarget == "module" || attributeTarget == "assembly") { + // use separate section for each attribute + foreach (var attribute in attributes) { + var section = new AttributeSection(); + section.AttributeTarget = attributeTarget; + section.Attributes.Add(attribute); + attributedNode.AddChild(section, EntityDeclaration.AttributeRole); + } + } else if (attributes.Count > 0) { + // use single section for all attributes + var section = new AttributeSection(); + section.AttributeTarget = attributeTarget; + section.Attributes.AddRange(attributes); + attributedNode.AddChild(section, EntityDeclaration.AttributeRole); + } + } + + static Expression ConvertArgumentValue(CustomAttributeArgument argument) + { + if (argument.Value is CustomAttributeArgument[]) { + ArrayInitializerExpression arrayInit = new ArrayInitializerExpression(); + foreach (CustomAttributeArgument element in (CustomAttributeArgument[])argument.Value) { + arrayInit.Elements.Add(ConvertArgumentValue(element)); + } + ArrayType arrayType = argument.Type as ArrayType; + return new ArrayCreateExpression { + Type = ConvertType(arrayType != null ? arrayType.ElementType : argument.Type), + AdditionalArraySpecifiers = { new ArraySpecifier() }, + Initializer = arrayInit + }; + } else if (argument.Value is CustomAttributeArgument) { + // occurs with boxed arguments + return ConvertArgumentValue((CustomAttributeArgument)argument.Value); + } + var type = argument.Type.Resolve(); + if (type != null && type.IsEnum) { + return MakePrimitive(Convert.ToInt64(argument.Value), type); + } else if (argument.Value is TypeReference) { + return CreateTypeOfExpression((TypeReference)argument.Value); + } else { + return new PrimitiveExpression(argument.Value); + } + } + #endregion + + internal static Expression MakePrimitive(long val, TypeReference type) + { + if (TypeAnalysis.IsBoolean(type) && val == 0) + return new PrimitiveExpression(false); + else if (TypeAnalysis.IsBoolean(type) && val == 1) + return new PrimitiveExpression(true); + else if (val == 0 && type is PointerType) + return new NullReferenceExpression(); + if (type != null) + { // cannot rely on type.IsValueType, it's not set for typerefs (but is set for typespecs) + TypeDefinition enumDefinition = type.Resolve(); + if (enumDefinition != null && enumDefinition.IsEnum) { + TypeCode enumBaseTypeCode = TypeCode.Int32; + foreach (FieldDefinition field in enumDefinition.Fields) { + if (field.IsStatic && Equals(CSharpPrimitiveCast.Cast(TypeCode.Int64, field.Constant, false), val)) + return ConvertType(type).Member(field.Name).WithAnnotation(field); + else if (!field.IsStatic) + enumBaseTypeCode = TypeAnalysis.GetTypeCode(field.FieldType); // use primitive type of the enum + } + if (IsFlagsEnum(enumDefinition)) { + long enumValue = val; + Expression expr = null; + long negatedEnumValue = ~val; + // limit negatedEnumValue to the appropriate range + switch (enumBaseTypeCode) { + case TypeCode.Byte: + case TypeCode.SByte: + negatedEnumValue &= byte.MaxValue; + break; + case TypeCode.Int16: + case TypeCode.UInt16: + negatedEnumValue &= ushort.MaxValue; + break; + case TypeCode.Int32: + case TypeCode.UInt32: + negatedEnumValue &= uint.MaxValue; + break; + } + Expression negatedExpr = null; + foreach (FieldDefinition field in enumDefinition.Fields.Where(fld => fld.IsStatic)) { + long fieldValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, field.Constant, false); + if (fieldValue == 0) + continue; // skip None enum value + + if ((fieldValue & enumValue) == fieldValue) { + var fieldExpression = ConvertType(type).Member(field.Name).WithAnnotation(field); + if (expr == null) + expr = fieldExpression; + else + expr = new BinaryOperatorExpression(expr, BinaryOperatorType.BitwiseOr, fieldExpression); + + enumValue &= ~fieldValue; + } + if ((fieldValue & negatedEnumValue) == fieldValue) { + var fieldExpression = ConvertType(type).Member(field.Name).WithAnnotation(field); + if (negatedExpr == null) + negatedExpr = fieldExpression; + else + negatedExpr = new BinaryOperatorExpression(negatedExpr, BinaryOperatorType.BitwiseOr, fieldExpression); + + negatedEnumValue &= ~fieldValue; + } + } + if (enumValue == 0 && expr != null) { + if (!(negatedEnumValue == 0 && negatedExpr != null && negatedExpr.Descendants.Count() < expr.Descendants.Count())) { + return expr; + } + } + if (negatedEnumValue == 0 && negatedExpr != null) { + return new UnaryOperatorExpression(UnaryOperatorType.BitNot, negatedExpr); + } + } + return new PrimitiveExpression(CSharpPrimitiveCast.Cast(enumBaseTypeCode, val, false)).CastTo(ConvertType(type)); + } + } + TypeCode code = TypeAnalysis.GetTypeCode(type); + if (code == TypeCode.Object || code == TypeCode.Empty) + code = TypeCode.Int32; + return new PrimitiveExpression(CSharpPrimitiveCast.Cast(code, val, false)); + } + + static bool IsFlagsEnum(TypeDefinition type) + { + if (!type.HasCustomAttributes) + return false; + + return type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.FlagsAttribute"); + } + + /// <summary> + /// Sets new modifier if the member hides some other member from a base type. + /// </summary> + /// <param name="member">The node of the member which new modifier state should be determined.</param> + static void SetNewModifier(EntityDeclaration member) + { + try { + bool addNewModifier = false; + if (member is IndexerDeclaration) { + var propertyDef = member.Annotation<PropertyDefinition>(); + var baseProperties = + TypesHierarchyHelpers.FindBaseProperties(propertyDef); + addNewModifier = baseProperties.Any(); + } else + addNewModifier = HidesBaseMember(member); + + if (addNewModifier) + member.Modifiers |= Modifiers.New; + } + catch (ReferenceResolvingException) { + // TODO: add some kind of notification (a comment?) about possible problems with decompiled code due to unresolved references. + } + } + + static bool HidesBaseMember(EntityDeclaration member) + { + var memberDefinition = member.Annotation<IMemberDefinition>(); + bool addNewModifier = false; + var methodDefinition = memberDefinition as MethodDefinition; + if (methodDefinition != null) { + addNewModifier = HidesByName(memberDefinition, includeBaseMethods: false); + if (!addNewModifier) + addNewModifier = TypesHierarchyHelpers.FindBaseMethods(methodDefinition).Any(); + } else + addNewModifier = HidesByName(memberDefinition, includeBaseMethods: true); + return addNewModifier; + } + + /// <summary> + /// Determines whether any base class member has the same name as the given member. + /// </summary> + /// <param name="member">The derived type's member.</param> + /// <param name="includeBaseMethods">true if names of methods declared in base types should also be checked.</param> + /// <returns>true if any base member has the same name as given member, otherwise false.</returns> + static bool HidesByName(IMemberDefinition member, bool includeBaseMethods) + { + Debug.Assert(!(member is PropertyDefinition) || !((PropertyDefinition)member).IsIndexer()); + + if (member.DeclaringType.BaseType != null) { + var baseTypeRef = member.DeclaringType.BaseType; + while (baseTypeRef != null) { + var baseType = baseTypeRef.ResolveOrThrow(); + if (baseType.HasProperties && AnyIsHiddenBy(baseType.Properties, member, m => !m.IsIndexer())) + return true; + if (baseType.HasEvents && AnyIsHiddenBy(baseType.Events, member)) + return true; + if (baseType.HasFields && AnyIsHiddenBy(baseType.Fields, member)) + return true; + if (includeBaseMethods && baseType.HasMethods + && AnyIsHiddenBy(baseType.Methods, member, m => !m.IsSpecialName)) + return true; + if (baseType.HasNestedTypes && AnyIsHiddenBy(baseType.NestedTypes, member)) + return true; + baseTypeRef = baseType.BaseType; + } + } + return false; + } + + static bool AnyIsHiddenBy<T>(IEnumerable<T> members, IMemberDefinition derived, Predicate<T> condition = null) + where T : IMemberDefinition + { + return members.Any(m => m.Name == derived.Name + && (condition == null || condition(m)) + && TypesHierarchyHelpers.IsVisibleFromDerived(m, derived.DeclaringType)); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs new file mode 100644 index 00000000..a4d80e59 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -0,0 +1,1220 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +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 = NRefactory.CSharp; + using Cecil = Mono.Cecil; + + public class AstMethodBodyBuilder + { + MethodDefinition methodDef; + TypeSystem typeSystem; + DecompilerContext context; + HashSet<ILVariable> localVariablesToDefine = new HashSet<ILVariable>(); // local variables that are missing a definition + + /// <summary> + /// Creates the body for the method definition. + /// </summary> + /// <param name="methodDef">Method definition to decompile.</param> + /// <param name="context">Decompilation context.</param> + /// <param name="parameters">Parameter declarations of the method being decompiled. + /// These are used to update the parameter names when the decompiler generates names for the parameters.</param> + /// <returns>Block for the method body</returns> + public static BlockStatement CreateMethodBody(MethodDefinition methodDef, + DecompilerContext context, + IEnumerable<ParameterDeclaration> parameters = null) + { + MethodDefinition oldCurrentMethod = context.CurrentMethod; + Debug.Assert(oldCurrentMethod == null || oldCurrentMethod == methodDef); + context.CurrentMethod = methodDef; + context.CurrentMethodIsAsync = false; + try { + AstMethodBodyBuilder builder = new AstMethodBodyBuilder(); + builder.methodDef = methodDef; + builder.context = context; + builder.typeSystem = methodDef.Module.TypeSystem; + if (Debugger.IsAttached) { + return builder.CreateMethodBody(parameters); + } else { + try { + return builder.CreateMethodBody(parameters); + } catch (OperationCanceledException) { + throw; + } catch (Exception ex) { + throw new DecompilerException(methodDef, ex); + } + } + } finally { + context.CurrentMethod = oldCurrentMethod; + } + } + + public BlockStatement CreateMethodBody(IEnumerable<ParameterDeclaration> parameters) + { + if (methodDef.Body == null) { + return null; + } + + context.CancellationToken.ThrowIfCancellationRequested(); + ILBlock ilMethod = new ILBlock(); + ILAstBuilder astBuilder = new ILAstBuilder(); + ilMethod.Body = astBuilder.Build(methodDef, true, context); + + context.CancellationToken.ThrowIfCancellationRequested(); + ILAstOptimizer bodyGraph = new ILAstOptimizer(); + bodyGraph.Optimize(context, ilMethod); + context.CancellationToken.ThrowIfCancellationRequested(); + + var localVariables = ilMethod.GetSelfAndChildrenRecursive<ILExpression>().Select(e => e.Operand as ILVariable) + .Where(v => v != null && !v.IsParameter).Distinct(); + Debug.Assert(context.CurrentMethod == methodDef); + NameVariables.AssignNamesToVariables(context, astBuilder.Parameters, localVariables, ilMethod); + + if (parameters != null) { + foreach (var pair in (from p in parameters + join v in astBuilder.Parameters on p.Annotation<ParameterDefinition>() equals v.OriginalParameter + select new { p, v.Name })) + { + pair.p.Name = pair.Name; + } + } + + context.CancellationToken.ThrowIfCancellationRequested(); + 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); + newVarDecl.Variables.Single().AddAnnotation(v); + astBlock.Statements.InsertBefore(insertionPoint, newVarDecl); + } + + astBlock.AddAnnotation(new MethodDebugSymbols(methodDef) { LocalVariables = localVariables.ToList() }); + + return astBlock; + } + + BlockStatement TransformBlock(ILBlock block) + { + BlockStatement astBlock = new BlockStatement(); + if (block != null) { + foreach(ILNode node in block.GetChildren()) { + astBlock.Statements.AddRange(TransformNode(node)); + } + } + return astBlock; + } + + IEnumerable<Statement> TransformNode(ILNode node) + { + if (node is ILLabel) { + yield return new LabelStatement { Label = ((ILLabel)node).Name }; + } else if (node is ILExpression) { + List<ILRange> ilRanges = ILRange.OrderAndJoin(node.GetSelfAndChildrenRecursive<ILExpression>().SelectMany(e => e.ILRanges)); + AstNode codeExpr = TransformExpression((ILExpression)node); + if (codeExpr != null) { + codeExpr = codeExpr.WithAnnotation(ilRanges); + if (codeExpr is Expression) { + yield return new ExpressionStatement { Expression = (Expression)codeExpr }; + } else if (codeExpr is Statement) { + yield return (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 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; + if (TypeAnalysis.IsBoolean(ilSwitch.Condition.InferredType) && ( + from cb in ilSwitch.CaseBlocks + where cb.Values != null + from val in cb.Values + select val + ).Any(val => val != 0 && val != 1)) + { + // If switch cases contain values other then 0 and 1, force the condition to be non-boolean + ilSwitch.Condition.ExpectedType = typeSystem.Int32; + } + 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.ExpectedType ?? 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 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 CatchClause { Body = TransformBlock(catchClause) }); + } else { + tryCatchStmt.CatchClauses.Add( + new CatchClause { + Type = AstBuilder.ConvertType(catchClause.ExceptionType), + VariableName = catchClause.ExceptionVariable == null ? null : catchClause.ExceptionVariable.Name, + Body = TransformBlock(catchClause) + }.WithAnnotation(catchClause.ExceptionVariable)); + } + } + 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<ILRange> ilRanges = ILRange.OrderAndJoin(expr.GetSelfAndChildrenRecursive<ILExpression>().SelectMany(e => e.ILRanges)); + AstNode result; + + if (astExpr != null) + result = Convert(astExpr, expr.InferredType, expr.ExpectedType); + else + result = node; + + if (result != null) + result = result.WithAnnotation(new TypeInformation(expr.InferredType, expr.ExpectedType)); + + if (result != null) + return result.WithAnnotation(ilRanges); + + return result; + } + + AstNode TransformByteCode(ILExpression byteCode) + { + object operand = byteCode.Operand; + AstType operandAsTypeRef = AstBuilder.ConvertType(operand as TypeReference); + + List<Expression> args = new List<Expression>(); + foreach(ILExpression arg in byteCode.Arguments) { + args.Add((Expression)TransformExpression(arg)); + } + Expression arg1 = args.Count >= 1 ? args[0] : null; + Expression arg2 = args.Count >= 2 ? args[1] : null; + 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) { + boe = new BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); + if (byteCode.Arguments[0].ExpectedType is PointerType || + byteCode.Arguments[1].ExpectedType is PointerType) { + boe.AddAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); + } + } else { + boe = new 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) { + boe = new BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); + if (byteCode.Arguments[0].ExpectedType is PointerType) { + boe.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); + } + } else { + boe = new BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); + } + boe.AddAnnotation(byteCode.Code == ILCode.Sub ? AddCheckedBlocks.UncheckedAnnotation : AddCheckedBlocks.CheckedAnnotation); + return boe; + } + case ILCode.Div: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); + case ILCode.Div_Un: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); + case ILCode.Mul: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.UncheckedAnnotation); + case ILCode.Mul_Ovf: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.CheckedAnnotation); + case ILCode.Mul_Ovf_Un: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2).WithAnnotation(AddCheckedBlocks.CheckedAnnotation); + case ILCode.Rem: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); + case ILCode.Rem_Un: return new BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); + case ILCode.And: return new BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseAnd, arg2); + case ILCode.Or: return new BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseOr, arg2); + case ILCode.Xor: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ExclusiveOr, arg2); + case ILCode.Shl: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftLeft, arg2); + case ILCode.Shr: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftRight, arg2); + case ILCode.Shr_Un: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ShiftRight, arg2); + case ILCode.Neg: return new UnaryOperatorExpression(UnaryOperatorType.Minus, arg1).WithAnnotation(AddCheckedBlocks.UncheckedAnnotation); + case ILCode.Not: return new 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 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: { + var ace = new 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.InitArray: { + var ace = new ArrayCreateExpression(); + ace.Type = operandAsTypeRef; + ComposedType ct = operandAsTypeRef as ComposedType; + var arrayType = (ArrayType) operand; + if (ct != null) + { + // change "new (int[,])[10] to new int[10][,]" + ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); + ace.Initializer = new ArrayInitializerExpression(); + } + var newArgs = new List<Expression>(); + foreach (var arrayDimension in arrayType.Dimensions.Skip(1).Reverse()) + { + int length = (int)arrayDimension.UpperBound - (int)arrayDimension.LowerBound; + for (int j = 0; j < args.Count; j += length) + { + var child = new ArrayInitializerExpression(); + child.Elements.AddRange(args.GetRange(j, length)); + newArgs.Add(child); + } + var temp = args; + args = newArgs; + newArgs = temp; + newArgs.Clear(); + } + ace.Initializer.Elements.AddRange(args); + 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 AssignmentExpression(arg1.Indexer(arg2), arg3); + case ILCode.CompoundAssignment: + { + CastExpression cast = arg1 as CastExpression; + var boe = cast != null ? (BinaryOperatorExpression)cast.Expression : arg1 as BinaryOperatorExpression; + // AssignmentExpression doesn't support overloaded operators so they have to be processed to BinaryOperatorExpression + if (boe == null) { + var tmp = new ParenthesizedExpression(arg1); + ReplaceMethodCallsWithOperators.ProcessInvocationExpression((InvocationExpression)arg1); + boe = (BinaryOperatorExpression)tmp.Expression; + } + var assignment = new 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 BinaryOperatorExpression(arg1, BinaryOperatorType.Equality, arg2); + case ILCode.Cne: return new BinaryOperatorExpression(arg1, BinaryOperatorType.InEquality, arg2); + case ILCode.Cgt: return new 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) goto case ILCode.Cne; + + // when comparing signed integral values using Cgt_Un with 0 + // the Ast should actually contain InEquality since "(uint)a > 0u" is identical to "a != 0" + if (arg1Type.IsSignedIntegralType()) + { + var p = arg2 as PrimitiveExpression; + if (p != null && p.Value.IsZero()) goto case ILCode.Cne; + } + + goto case ILCode.Cgt; + } + case ILCode.Cle_Un: { + // can also mean Equality, when used with object references + TypeReference arg1Type = byteCode.Arguments[0].InferredType; + if (arg1Type != null && !arg1Type.IsValueType) goto case ILCode.Ceq; + + // when comparing signed integral values using Cle_Un with 0 + // the Ast should actually contain Equality since "(uint)a <= 0u" is identical to "a == 0" + if (arg1Type.IsSignedIntegralType()) + { + var p = arg2 as PrimitiveExpression; + if (p != null && p.Value.IsZero()) goto case ILCode.Ceq; + } + + goto case ILCode.Cle; + } + case ILCode.Cle: return new BinaryOperatorExpression(arg1, BinaryOperatorType.LessThanOrEqual, arg2); + case ILCode.Cge_Un: + case ILCode.Cge: return new BinaryOperatorExpression(arg1, BinaryOperatorType.GreaterThanOrEqual, arg2); + case ILCode.Clt_Un: + case ILCode.Clt: return new BinaryOperatorExpression(arg1, BinaryOperatorType.LessThan, arg2); + #endregion + #region Logical + case ILCode.LogicNot: return new UnaryOperatorExpression(UnaryOperatorType.Not, arg1); + case ILCode.LogicAnd: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ConditionalAnd, arg2); + case ILCode.LogicOr: return new BinaryOperatorExpression(arg1, BinaryOperatorType.ConditionalOr, arg2); + case ILCode.TernaryOp: return new ConditionalExpression() { Condition = arg1, TrueExpression = arg2, FalseExpression = arg3 }; + case ILCode.NullCoalescing: return new BinaryOperatorExpression(arg1, BinaryOperatorType.NullCoalescing, arg2); + #endregion + #region Branch + case ILCode.Br: return new GotoStatement(((ILLabel)byteCode.Operand).Name); + case ILCode.Brtrue: + return new IfElseStatement() { + Condition = arg1, + TrueStatement = new BlockStatement() { + new GotoStatement(((ILLabel)byteCode.Operand).Name) + } + }; + case ILCode.LoopOrSwitchBreak: return new BreakStatement(); + case ILCode.LoopContinue: return new 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: + case ILCode.Conv_Ovf_I: + case ILCode.Conv_Ovf_U: + case ILCode.Conv_Ovf_I_Un: + case ILCode.Conv_Ovf_U_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.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 + goto case ILCode.Castclass; + case ILCode.Castclass: + if ((byteCode.Arguments[0].InferredType != null && byteCode.Arguments[0].InferredType.IsGenericParameter) || ((TypeReference)operand).IsGenericParameter) + return arg1.CastTo(new PrimitiveType("object")).CastTo(operandAsTypeRef); + 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: { + MethodReference cecilMethod = ((MethodReference)operand); + var expr = new IdentifierExpression(cecilMethod.Name); + expr.TypeArguments.AddRange(ConvertTypeArguments(cecilMethod)); + expr.AddAnnotation(cecilMethod); + return new IdentifierExpression("ldftn").Invoke(expr) + .WithAnnotation(new DelegateConstruction.Annotation(false)); + } + case ILCode.Ldvirtftn: { + MethodReference cecilMethod = ((MethodReference)operand); + var expr = new IdentifierExpression(cecilMethod.Name); + expr.TypeArguments.AddRange(ConvertTypeArguments(cecilMethod)); + expr.AddAnnotation(cecilMethod); + return new IdentifierExpression("ldvirtftn").Invoke(expr) + .WithAnnotation(new 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 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 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 IdentifierExpression(((ILVariable)operand).Name).WithAnnotation(operand)); + } + case ILCode.Ldnull: return new NullReferenceExpression(); + case ILCode.Ldstr: return new PrimitiveExpression(operand); + case ILCode.Ldtoken: + if (operand is TypeReference) { + return AstBuilder.CreateTypeOfExpression((TypeReference)operand).Member("TypeHandle"); + } else { + Expression referencedEntity; + string loadName; + string handleName; + if (operand is FieldReference) { + loadName = "fieldof"; + handleName = "FieldHandle"; + FieldReference fr = (FieldReference)operand; + referencedEntity = AstBuilder.ConvertType(fr.DeclaringType).Member(fr.Name).WithAnnotation(fr); + } else if (operand is MethodReference) { + loadName = "methodof"; + handleName = "MethodHandle"; + MethodReference mr = (MethodReference)operand; + var methodParameters = mr.Parameters.Select(p => new TypeReferenceExpression(AstBuilder.ConvertType(p.ParameterType))); + referencedEntity = AstBuilder.ConvertType(mr.DeclaringType).Invoke(mr.Name, methodParameters).WithAnnotation(mr); + } else { + loadName = "ldtoken"; + handleName = "Handle"; + referencedEntity = new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand)); + } + return new IdentifierExpression(loadName).Invoke(referencedEntity).WithAnnotation(new LdTokenAnnotation()).Member(handleName); + } + 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 = arg1 + }; + } + 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: { + 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 ArrayCreateExpression(); + ct.ArraySpecifiers.First().Remove(); + ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); + ace.Type = ct; + ace.Arguments.AddRange(args); + return ace; + } + } + if (declaringType.IsAnonymousType()) { + MethodDefinition ctor = ((MethodReference)operand).Resolve(); + if (methodDef != null) { + AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); + if (CanInferAnonymousTypePropertyNamesFromArguments(args, ctor.Parameters)) { + atce.Initializers.AddRange(args); + } else { + for (int i = 0; i < args.Count; i++) { + atce.Initializers.Add( + new NamedExpression { + Name = ctor.Parameters[i].Name, + Expression = args[i] + }); + } + } + return atce; + } + } + var oce = new ObjectCreateExpression(); + 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 ReturnStatement { Expression = arg1 }; + } else { + return new ReturnStatement(); + } + case ILCode.Rethrow: return new ThrowStatement(); + case ILCode.Sizeof: return new SizeOfExpression { Type = operandAsTypeRef }; + case ILCode.Stloc: { + ILVariable locVar = (ILVariable)operand; + if (!locVar.IsParameter) + localVariablesToDefine.Add(locVar); + return new AssignmentExpression(new 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 ThrowStatement { Expression = arg1 }; + case ILCode.Unaligned: return InlineAssembly(byteCode, args); + case ILCode.Volatile: return InlineAssembly(byteCode, args); + case ILCode.YieldBreak: + return new YieldBreakStatement(); + case ILCode.YieldReturn: + return new YieldReturnStatement { 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<MemberReferenceExpression>("left").Single(); + initializer.Elements.Add( + new NamedExpression { + Name = mre.MemberName, + Expression = m.Get<Expression>("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<Expression>("arg").Single().Detach()); + } else { + ArrayInitializerExpression argList = new ArrayInitializerExpression(); + foreach (var expr in m.Get<Expression>("arg")) { + argList.Elements.Add(expr.Detach()); + } + initializer.Elements.Add(argList); + } + } else { + initializer.Elements.Add(args[i]); + } + } + } + ObjectCreateExpression oce = arg1 as ObjectCreateExpression; + DefaultValueExpression dve = arg1 as DefaultValueExpression; + if (oce != null) { + oce.Initializer = initializer; + return oce; + } else if (dve != null) { + oce = new ObjectCreateExpression(dve.Type.Detach()); + oce.CopyAnnotationsFrom(dve); + oce.Initializer = initializer; + return oce; + } else { + return new AssignmentExpression(arg1, initializer); + } + } + case ILCode.InitializedObject: + return new InitializedObjectExpression(); + case ILCode.Wrap: + return arg1.WithAnnotation(PushNegation.LiftedOperatorAnnotation); + case ILCode.AddressOf: + return MakeRef(arg1); + case ILCode.ExpressionTreeParameterDeclarations: + args[args.Count - 1].AddAnnotation(new ParameterDeclarationAnnotation(byteCode)); + return args[args.Count - 1]; + case ILCode.Await: + return new UnaryOperatorExpression(UnaryOperatorType.Await, UnpackDirectionExpression(arg1)); + case ILCode.NullableOf: + case ILCode.ValueOf: + return arg1; + default: + throw new Exception("Unknown OpCode: " + byteCode.Code); + } + } + + internal static bool CanInferAnonymousTypePropertyNamesFromArguments(IList<Expression> args, IList<ParameterDefinition> parameters) + { + for (int i = 0; i < args.Count; i++) { + string inferredName; + if (args[i] is IdentifierExpression) + inferredName = ((IdentifierExpression)args[i]).Identifier; + else if (args[i] is MemberReferenceExpression) + inferredName = ((MemberReferenceExpression)args[i]).MemberName; + else + inferredName = null; + + if (inferredName != parameters[i].Name) { + return false; + } + } + return true; + } + + static readonly AstNode objectInitializerPattern = new AssignmentExpression( + new MemberReferenceExpression { + Target = new InitializedObjectExpression(), + MemberName = Pattern.AnyString + }.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; + } + } + + 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<Expression> args) + { + MethodReference cecilMethod = (MethodReference)byteCode.Operand; + MethodDefinition cecilMethodDef = cecilMethod.Resolve(); + Expression target; + List<Expression> methodArgs = new List<Expression>(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) + target = UnpackDirectionExpression(target); + + if (cecilMethodDef != null) { + // convert null.ToLower() to ((string)null).ToLower() + if (target is NullReferenceExpression) + target = target.CastTo(AstBuilder.ConvertType(cecilMethod.DeclaringType)); + + if (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); + oce.AddAnnotation(cecilMethod); + 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).WithAnnotation(cecilMethod); + } + } else if (cecilMethodDef.IsGetter) { // with parameters + PropertyDefinition indexer = GetIndexer(cecilMethodDef); + if (indexer != null) + return target.Indexer(methodArgs).WithAnnotation(indexer).WithAnnotation(cecilMethod); + } else if (cecilMethodDef.IsSetter && methodArgs.Count == 1) { + foreach (var prop in cecilMethodDef.DeclaringType.Properties) { + if (prop.SetMethod == cecilMethodDef) + return new AssignmentExpression(target.Member(prop.Name).WithAnnotation(prop).WithAnnotation(cecilMethod), 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).WithAnnotation(cecilMethod), + methodArgs[methodArgs.Count - 1] + ); + } else if (cecilMethodDef.IsAddOn && methodArgs.Count == 1) { + foreach (var ev in cecilMethodDef.DeclaringType.Events) { + if (ev.AddMethod == cecilMethodDef) { + return new AssignmentExpression { + Left = target.Member(ev.Name).WithAnnotation(ev).WithAnnotation(cecilMethod), + 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 AssignmentExpression { + Left = target.Member(ev.Name).WithAnnotation(ev).WithAnnotation(cecilMethod), + 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).WithAnnotation(cecilMethod); + } + } + // Default invocation + AdjustArgumentsForMethodCall(cecilMethodDef ?? cecilMethod, methodArgs); + return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod); + } + + static Expression UnpackDirectionExpression(Expression target) + { + if (target is DirectionExpression) { + return ((DirectionExpression)target).Expression.Detach(); + } else { + return target; + } + } + + static void AdjustArgumentsForMethodCall(MethodReference cecilMethod, List<Expression> 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; + ParameterDefinition p = cecilMethod.Parameters[i]; + if (dir != null && p.IsOut && !p.IsIn) + dir.FieldDirection = FieldDirection.Out; + } + } + + internal 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<ILCode, int> unhandledOpcodes = new ConcurrentDictionary<ILCode, int>(); + #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<Expression> 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 TypeReference) { + return ((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<AstType> 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 DirectionExpression MakeRef(Expression expr) + { + return new DirectionExpression { Expression = expr, FieldDirection = FieldDirection.Ref }; + } + + Expression Convert(Expression expr, TypeReference actualType, 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; + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/CommentStatement.cs b/ICSharpCode.Decompiler/Ast/CommentStatement.cs new file mode 100644 index 00000000..39bc2450 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/CommentStatement.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.Decompiler.Ast +{ + /// <summary> + /// Allows storing comments inside IEnumerable{Statement}. Used in the AstMethodBuilder. + /// CommentStatement nodes are replaced with regular comments later on. + /// </summary> + internal class CommentStatement : Statement + { + string comment; + + public CommentStatement(string comment) + { + if (comment == null) + throw new ArgumentNullException("comment"); + this.comment = comment; + } + + public override void AcceptVisitor(IAstVisitor visitor) + { + } + + public override T AcceptVisitor<T>(IAstVisitor<T> visitor) + { + return default(T); + } + + public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) + { + return default(S); + } + + public static void ReplaceAll(AstNode tree) + { + foreach (var cs in tree.Descendants.OfType<CommentStatement>()) { + cs.Parent.InsertChildBefore(cs, new Comment(cs.comment), Roles.Comment); + cs.Remove(); + } + } + + protected override bool DoMatch(AstNode other, Match match) + { + CommentStatement o = other as CommentStatement; + return o != null && MatchString(comment, o.comment); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/DecompilerContext.cs b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs new file mode 100644 index 00000000..d055de1d --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/DecompilerContext.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Threading; +using ICSharpCode.Decompiler.Ast; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler +{ + public class DecompilerContext + { + public ModuleDefinition CurrentModule; + public CancellationToken CancellationToken; + public TypeDefinition CurrentType; + public MethodDefinition CurrentMethod; + public DecompilerSettings Settings = new DecompilerSettings(); + public bool CurrentMethodIsAsync; + +// public ITypeResolveContext TypeResolveContext; +// public IProjectContent ProjectContent; + + public DecompilerContext(ModuleDefinition currentModule) + { + if (currentModule == null) + throw new ArgumentNullException("currentModule"); + CurrentModule = currentModule; + +// this.ProjectContent = new CecilTypeResolveContext(currentModule); +// List<ITypeResolveContext> resolveContexts = new List<ITypeResolveContext>(); +// resolveContexts.Add(this.ProjectContent); +// foreach (AssemblyNameReference r in currentModule.AssemblyReferences) { +// AssemblyDefinition d = currentModule.AssemblyResolver.Resolve(r); +// if (d != null) { +// resolveContexts.Add(new CecilTypeResolveContext(d.MainModule)); +// } +// } +// this.TypeResolveContext = new CompositeTypeResolveContext(resolveContexts); + } + + /// <summary> + /// Used to pass variable names from a method to its anonymous methods. + /// </summary> + internal List<string> ReservedVariableNames = new List<string>(); + + public DecompilerContext Clone() + { + DecompilerContext ctx = (DecompilerContext)MemberwiseClone(); + ctx.ReservedVariableNames = new List<string>(ctx.ReservedVariableNames); + return ctx; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs b/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs new file mode 100644 index 00000000..80730061 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/NRefactoryExtensions.cs @@ -0,0 +1,86 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.Decompiler.Ast +{ + internal static class NRefactoryExtensions + { + public static T WithAnnotation<T>(this T node, object annotation) where T : AstNode + { + if (annotation != null) + node.AddAnnotation(annotation); + return node; + } + + public static T CopyAnnotationsFrom<T>(this T node, AstNode other) where T : AstNode + { + foreach (object annotation in other.Annotations) { + node.AddAnnotation(annotation); + } + return node; + } + + public static T Detach<T>(this T node) where T : AstNode + { + node.Remove(); + return node; + } + + public static Expression WithName(this Expression node, string patternGroupName) + { + return new NamedNode(patternGroupName, node); + } + + public static Statement WithName(this Statement node, string patternGroupName) + { + return new NamedNode(patternGroupName, node); + } + + public static void AddNamedArgument(this NRefactory.CSharp.Attribute attribute, string name, Expression argument) + { + attribute.Arguments.Add(new AssignmentExpression(new IdentifierExpression(name), argument)); + } + + public static AstType ToType(this Pattern pattern) + { + return pattern; + } + + public static Expression ToExpression(this Pattern pattern) + { + return pattern; + } + + public static Statement ToStatement(this Pattern pattern) + { + return pattern; + } + + public static Statement GetNextStatement(this Statement statement) + { + AstNode next = statement.NextSibling; + while (next != null && !(next is Statement)) + next = next.NextSibling; + return (Statement)next; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/NameVariables.cs b/ICSharpCode.Decompiler/Ast/NameVariables.cs new file mode 100644 index 00000000..3da808cb --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/NameVariables.cs @@ -0,0 +1,347 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; + +using ICSharpCode.Decompiler.ILAst; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast +{ + public class NameVariables + { + static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> { + { "System.Boolean", "flag" }, + { "System.Byte", "b" }, + { "System.SByte", "b" }, + { "System.Int16", "num" }, + { "System.Int32", "num" }, + { "System.Int64", "num" }, + { "System.UInt16", "num" }, + { "System.UInt32", "num" }, + { "System.UInt64", "num" }, + { "System.Single", "num" }, + { "System.Double", "num" }, + { "System.Decimal", "num" }, + { "System.String", "text" }, + { "System.Object", "obj" }, + { "System.Char", "c" } + }; + + + public static void AssignNamesToVariables(DecompilerContext context, IEnumerable<ILVariable> parameters, IEnumerable<ILVariable> variables, ILBlock methodBody) + { + NameVariables nv = new NameVariables(); + nv.context = context; + nv.fieldNamesInCurrentType = context.CurrentType.Fields.Select(f => f.Name).ToList(); + // First mark existing variable names as reserved. + foreach (string name in context.ReservedVariableNames) + nv.AddExistingName(name); + foreach (var p in parameters) + nv.AddExistingName(p.Name); + foreach (var v in variables) { + if (v.IsGenerated) { + // don't introduce names for variables generated by ILSpy - keep "expr"/"arg" + nv.AddExistingName(v.Name); + } else if (v.OriginalVariable != null && context.Settings.UseDebugSymbols) { + string varName = v.OriginalVariable.Name; + if (string.IsNullOrEmpty(varName) || varName.StartsWith("V_", StringComparison.Ordinal) || !IsValidName(varName)) + { + // don't use the name from the debug symbols if it looks like a generated name + v.Name = null; + } else { + // use the name from the debug symbols + // (but ensure we don't use the same name for two variables) + v.Name = nv.GetAlternativeName(varName); + } + } else { + v.Name = null; + } + } + // Now generate names: + foreach (ILVariable p in parameters) { + if (string.IsNullOrEmpty(p.Name)) + p.Name = nv.GenerateNameForVariable(p, methodBody); + } + foreach (ILVariable varDef in variables) { + if (string.IsNullOrEmpty(varDef.Name)) + varDef.Name = nv.GenerateNameForVariable(varDef, methodBody); + } + } + + static bool IsValidName(string varName) + { + if (string.IsNullOrEmpty(varName)) + return false; + if (!(char.IsLetter(varName[0]) || varName[0] == '_')) + return false; + for (int i = 1; i < varName.Length; i++) { + if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_')) + return false; + } + return true; + } + + DecompilerContext context; + List<string> fieldNamesInCurrentType; + Dictionary<string, int> typeNames = new Dictionary<string, int>(); + + public void AddExistingName(string name) + { + if (string.IsNullOrEmpty(name)) + return; + int number; + string nameWithoutDigits = SplitName(name, out number); + int existingNumber; + if (typeNames.TryGetValue(nameWithoutDigits, out existingNumber)) { + typeNames[nameWithoutDigits] = Math.Max(number, existingNumber); + } else { + typeNames.Add(nameWithoutDigits, number); + } + } + + string SplitName(string name, out int number) + { + // First, identify whether the name already ends with a number: + int pos = name.Length; + while (pos > 0 && name[pos-1] >= '0' && name[pos-1] <= '9') + pos--; + if (pos < name.Length) { + if (int.TryParse(name.Substring(pos), out number)) { + return name.Substring(0, pos); + } + } + number = 1; + return name; + } + + const char maxLoopVariableName = 'n'; + + public string GetAlternativeName(string oldVariableName) + { + if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) { + for (char c = 'i'; c <= maxLoopVariableName; c++) { + if (!typeNames.ContainsKey(c.ToString())) { + typeNames.Add(c.ToString(), 1); + return c.ToString(); + } + } + } + + int number; + string nameWithoutDigits = SplitName(oldVariableName, out number); + + if (!typeNames.ContainsKey(nameWithoutDigits)) { + typeNames.Add(nameWithoutDigits, number - 1); + } + int count = ++typeNames[nameWithoutDigits]; + if (count != 1) { + return nameWithoutDigits + count.ToString(); + } else { + return nameWithoutDigits; + } + } + + string GenerateNameForVariable(ILVariable variable, ILBlock methodBody) + { + string proposedName = null; + if (variable.Type == context.CurrentType.Module.TypeSystem.Int32) { + // test whether the variable might be a loop counter + bool isLoopCounter = false; + foreach (ILWhileLoop loop in methodBody.GetSelfAndChildrenRecursive<ILWhileLoop>()) { + ILExpression expr = loop.Condition; + while (expr != null && expr.Code == ILCode.LogicNot) + expr = expr.Arguments[0]; + if (expr != null) { + switch (expr.Code) { + case ILCode.Clt: + case ILCode.Clt_Un: + case ILCode.Cgt: + case ILCode.Cgt_Un: + case ILCode.Cle: + case ILCode.Cle_Un: + case ILCode.Cge: + case ILCode.Cge_Un: + ILVariable loadVar; + if (expr.Arguments[0].Match(ILCode.Ldloc, out loadVar) && loadVar == variable) { + isLoopCounter = true; + } + break; + } + } + } + if (isLoopCounter) { + // For loop variables, use i,j,k,l,m,n + for (char c = 'i'; c <= maxLoopVariableName; c++) { + if (!typeNames.ContainsKey(c.ToString())) { + proposedName = c.ToString(); + break; + } + } + } + } + if (string.IsNullOrEmpty(proposedName)) { + var proposedNameForStores = + (from expr in methodBody.GetSelfAndChildrenRecursive<ILExpression>() + where expr.Code == ILCode.Stloc && expr.Operand == variable + select GetNameFromExpression(expr.Arguments.Single()) + ).Except(fieldNamesInCurrentType).ToList(); + if (proposedNameForStores.Count == 1) { + proposedName = proposedNameForStores[0]; + } + } + if (string.IsNullOrEmpty(proposedName)) { + var proposedNameForLoads = + (from expr in methodBody.GetSelfAndChildrenRecursive<ILExpression>() + from i in Enumerable.Range(0, expr.Arguments.Count) + let arg = expr.Arguments[i] + where arg.Code == ILCode.Ldloc && arg.Operand == variable + select GetNameForArgument(expr, i) + ).Except(fieldNamesInCurrentType).ToList(); + if (proposedNameForLoads.Count == 1) { + proposedName = proposedNameForLoads[0]; + } + } + if (string.IsNullOrEmpty(proposedName)) { + proposedName = GetNameByType(variable.Type); + } + + // remove any numbers from the proposed name + int number; + proposedName = SplitName(proposedName, out number); + + if (!typeNames.ContainsKey(proposedName)) { + typeNames.Add(proposedName, 0); + } + int count = ++typeNames[proposedName]; + if (count > 1) { + return proposedName + count.ToString(); + } else { + return proposedName; + } + } + + static string GetNameFromExpression(ILExpression expr) + { + switch (expr.Code) { + case ILCode.Ldfld: + case ILCode.Ldsfld: + return CleanUpVariableName(((FieldReference)expr.Operand).Name); + case ILCode.Call: + case ILCode.Callvirt: + case ILCode.CallGetter: + case ILCode.CallvirtGetter: + MethodReference mr = (MethodReference)expr.Operand; + if (mr.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && mr.Parameters.Count == 0) { + // use name from properties, but not from indexers + return CleanUpVariableName(mr.Name.Substring(4)); + } else if (mr.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && mr.Name.Length >= 4 && char.IsUpper(mr.Name[3])) { + // use name from Get-methods + return CleanUpVariableName(mr.Name.Substring(3)); + } + break; + } + return null; + } + + static string GetNameForArgument(ILExpression parent, int i) + { + switch (parent.Code) { + case ILCode.Stfld: + case ILCode.Stsfld: + if (i == parent.Arguments.Count - 1) // last argument is stored value + return CleanUpVariableName(((FieldReference)parent.Operand).Name); + else + break; + case ILCode.Call: + case ILCode.Callvirt: + case ILCode.Newobj: + case ILCode.CallGetter: + case ILCode.CallvirtGetter: + case ILCode.CallSetter: + case ILCode.CallvirtSetter: + MethodReference methodRef = (MethodReference)parent.Operand; + if (methodRef.Parameters.Count == 1 && i == parent.Arguments.Count - 1) { + // argument might be value of a setter + if (methodRef.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase)) { + return CleanUpVariableName(methodRef.Name.Substring(4)); + } else if (methodRef.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && methodRef.Name.Length >= 4 && char.IsUpper(methodRef.Name[3])) { + return CleanUpVariableName(methodRef.Name.Substring(3)); + } + } + MethodDefinition methodDef = methodRef.Resolve(); + if (methodDef != null) { + var p = methodDef.Parameters.ElementAtOrDefault((parent.Code != ILCode.Newobj && methodDef.HasThis) ? i - 1 : i); + if (p != null && !string.IsNullOrEmpty(p.Name)) + return CleanUpVariableName(p.Name); + } + break; + case ILCode.Ret: + return "result"; + } + return null; + } + + string GetNameByType(TypeReference type) + { + type = TypeAnalysis.UnpackModifiers(type); + + GenericInstanceType git = type as GenericInstanceType; + if (git != null && git.ElementType.FullName == "System.Nullable`1" && git.GenericArguments.Count == 1) { + type = ((GenericInstanceType)type).GenericArguments[0]; + } + + string name; + if (type.IsArray) { + name = "array"; + } else if (type.IsPointer || type.IsByReference) { + name = "ptr"; + } else if (type.Name.EndsWith("Exception", StringComparison.Ordinal)) { + name = "ex"; + } else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) { + name = type.Name; + // remove the 'I' for interfaces + if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) + name = name.Substring(1); + name = CleanUpVariableName(name); + } + return name; + } + + static string CleanUpVariableName(string name) + { + // remove the backtick (generics) + int pos = name.IndexOf('`'); + if (pos >= 0) + name = name.Substring(0, pos); + + // remove field prefix: + if (name.Length > 2 && name.StartsWith("m_", StringComparison.Ordinal)) + name = name.Substring(2); + else if (name.Length > 1 && name[0] == '_') + name = name.Substring(1); + + if (name.Length == 0) + return "obj"; + else + return char.ToLower(name[0]) + name.Substring(1); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs b/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs new file mode 100644 index 00000000..fed08aaa --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/TextTokenWriter.cs @@ -0,0 +1,369 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast +{ + public class TextTokenWriter : TokenWriter + { + readonly ITextOutput output; + readonly DecompilerContext context; + readonly Stack<AstNode> nodeStack = new Stack<AstNode>(); + int braceLevelWithinType = -1; + bool inDocumentationComment = false; + bool firstUsingDeclaration; + bool lastUsingDeclaration; + + TextLocation? lastEndOfLine; + + public bool FoldBraces = false; + + public TextTokenWriter(ITextOutput output, DecompilerContext context) + { + if (output == null) + throw new ArgumentNullException("output"); + if (context == null) + throw new ArgumentNullException("context"); + this.output = output; + this.context = context; + } + + public override void WriteIdentifier(Identifier identifier) + { + var definition = GetCurrentDefinition(); + if (definition != null) { + output.WriteDefinition(identifier.Name, definition, false); + return; + } + + object memberRef = GetCurrentMemberReference(); + + if (memberRef != null) { + output.WriteReference(identifier.Name, memberRef); + return; + } + + definition = GetCurrentLocalDefinition(); + if (definition != null) { + output.WriteDefinition(identifier.Name, definition); + return; + } + + memberRef = GetCurrentLocalReference(); + if (memberRef != null) { + output.WriteReference(identifier.Name, memberRef, true); + return; + } + + if (firstUsingDeclaration) { + output.MarkFoldStart(defaultCollapsed: true); + firstUsingDeclaration = false; + } + + output.Write(identifier.Name); + } + + MemberReference GetCurrentMemberReference() + { + AstNode node = nodeStack.Peek(); + MemberReference memberRef = node.Annotation<MemberReference>(); + if (memberRef == null && node.Role == Roles.TargetExpression && (node.Parent is InvocationExpression || node.Parent is ObjectCreateExpression)) { + memberRef = node.Parent.Annotation<MemberReference>(); + } + if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && memberRef != null) { + var declaringType = memberRef.DeclaringType.Resolve(); + if (declaringType != null && declaringType.IsDelegate()) + return null; + } + return FilterMemberReference(memberRef); + } + + MemberReference FilterMemberReference(MemberReference memberRef) + { + if (memberRef == null) + return null; + + if (context.Settings.AutomaticEvents && memberRef is FieldDefinition) { + var field = (FieldDefinition)memberRef; + return field.DeclaringType.Events.FirstOrDefault(ev => ev.Name == field.Name) ?? memberRef; + } + + return memberRef; + } + + object GetCurrentLocalReference() + { + AstNode node = nodeStack.Peek(); + ILVariable variable = node.Annotation<ILVariable>(); + if (variable != null) { + if (variable.OriginalParameter != null) + return variable.OriginalParameter; + //if (variable.OriginalVariable != null) + // return variable.OriginalVariable; + return variable; + } + + var gotoStatement = node as GotoStatement; + if (gotoStatement != null) + { + var method = nodeStack.Select(nd => nd.Annotation<MethodReference>()).FirstOrDefault(mr => mr != null); + if (method != null) + return method.ToString() + gotoStatement.Label; + } + + return null; + } + + object GetCurrentLocalDefinition() + { + AstNode node = nodeStack.Peek(); + if (node is Identifier && node.Parent != null) + node = node.Parent; + + var parameterDef = node.Annotation<ParameterDefinition>(); + if (parameterDef != null) + return parameterDef; + + if (node is VariableInitializer || node is CatchClause || node is ForeachStatement) { + var variable = node.Annotation<ILVariable>(); + if (variable != null) { + if (variable.OriginalParameter != null) + return variable.OriginalParameter; + //if (variable.OriginalVariable != null) + // return variable.OriginalVariable; + return variable; + } + } + + var label = node as LabelStatement; + if (label != null) { + var method = nodeStack.Select(nd => nd.Annotation<MethodReference>()).FirstOrDefault(mr => mr != null); + if (method != null) + return method.ToString() + label.Label; + } + + return null; + } + + object GetCurrentDefinition() + { + if (nodeStack == null || nodeStack.Count == 0) + return null; + + var node = nodeStack.Peek(); + if (node is Identifier) + node = node.Parent; + if (IsDefinition(node)) + return node.Annotation<MemberReference>(); + + return null; + } + + public override void WriteKeyword(Role role, string keyword) + { + output.Write(keyword); + } + + public override void WriteToken(Role role, string token) + { + // Attach member reference to token only if there's no identifier in the current node. + MemberReference memberRef = GetCurrentMemberReference(); + var node = nodeStack.Peek(); + if (memberRef != null && node.GetChildByRole(Roles.Identifier).IsNull) + output.WriteReference(token, memberRef); + else + output.Write(token); + } + + public override void Space() + { + output.Write(' '); + } + + public void OpenBrace(BraceStyle style) + { + if (braceLevelWithinType >= 0 || nodeStack.Peek() is TypeDeclaration) + braceLevelWithinType++; + if (nodeStack.OfType<BlockStatement>().Count() <= 1 || FoldBraces) { + output.MarkFoldStart(defaultCollapsed: braceLevelWithinType == 1); + } + output.WriteLine(); + output.WriteLine("{"); + output.Indent(); + } + + public void CloseBrace(BraceStyle style) + { + output.Unindent(); + output.Write('}'); + if (nodeStack.OfType<BlockStatement>().Count() <= 1 || FoldBraces) + output.MarkFoldEnd(); + if (braceLevelWithinType >= 0) + braceLevelWithinType--; + } + + public override void Indent() + { + output.Indent(); + } + + public override void Unindent() + { + output.Unindent(); + } + + public override void NewLine() + { + if (lastUsingDeclaration) { + output.MarkFoldEnd(); + lastUsingDeclaration = false; + } + lastEndOfLine = output.Location; + output.WriteLine(); + } + + public override void WriteComment(CommentType commentType, string content) + { + switch (commentType) { + case CommentType.SingleLine: + output.Write("//"); + output.WriteLine(content); + break; + case CommentType.MultiLine: + output.Write("/*"); + output.Write(content); + output.Write("*/"); + break; + case CommentType.Documentation: + bool isLastLine = !(nodeStack.Peek().NextSibling is Comment); + if (!inDocumentationComment && !isLastLine) { + inDocumentationComment = true; + output.MarkFoldStart("///" + content, true); + } + output.Write("///"); + output.Write(content); + if (inDocumentationComment && isLastLine) { + inDocumentationComment = false; + output.MarkFoldEnd(); + } + output.WriteLine(); + break; + default: + output.Write(content); + break; + } + } + + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + // pre-processor directive must start on its own line + output.Write('#'); + output.Write(type.ToString().ToLowerInvariant()); + if (!string.IsNullOrEmpty(argument)) { + output.Write(' '); + output.Write(argument); + } + output.WriteLine(); + } + + public override void WritePrimitiveValue(object value, string literalValue = null) + { + new TextWriterTokenWriter(new TextOutputWriter(output)).WritePrimitiveValue(value, literalValue); + } + + public override void WritePrimitiveType(string type) + { + output.Write(type); + if (type == "new") { + output.Write("()"); + } + } + + Stack<TextLocation> startLocations = new Stack<TextLocation>(); + Stack<MethodDebugSymbols> symbolsStack = new Stack<MethodDebugSymbols>(); + + public override void StartNode(AstNode node) + { + if (nodeStack.Count == 0) { + if (IsUsingDeclaration(node)) { + firstUsingDeclaration = !IsUsingDeclaration(node.PrevSibling); + lastUsingDeclaration = !IsUsingDeclaration(node.NextSibling); + } else { + firstUsingDeclaration = false; + lastUsingDeclaration = false; + } + } + nodeStack.Push(node); + startLocations.Push(output.Location); + + if (node is EntityDeclaration && node.Annotation<MemberReference>() != null && node.GetChildByRole(Roles.Identifier).IsNull) + output.WriteDefinition("", node.Annotation<MemberReference>(), false); + + if (node.Annotation<MethodDebugSymbols>() != null) { + symbolsStack.Push(node.Annotation<MethodDebugSymbols>()); + symbolsStack.Peek().StartLocation = startLocations.Peek(); + } + } + + bool IsUsingDeclaration(AstNode node) + { + return node is UsingDeclaration || node is UsingAliasDeclaration; + } + + public override void EndNode(AstNode node) + { + if (nodeStack.Pop() != node) + throw new InvalidOperationException(); + + var startLocation = startLocations.Pop(); + + // code mappings + var ranges = node.Annotation<List<ILRange>>(); + if (symbolsStack.Count > 0 && ranges != null && ranges.Count > 0) { + // Ignore the newline which was printed at the end of the statement + TextLocation endLocation = (node is Statement) ? (lastEndOfLine ?? output.Location) : output.Location; + symbolsStack.Peek().SequencePoints.Add( + new SequencePoint() { + ILRanges = ILRange.OrderAndJoin(ranges).ToArray(), + StartLocation = startLocation, + EndLocation = endLocation + }); + } + + if (node.Annotation<MethodDebugSymbols>() != null) { + symbolsStack.Peek().EndLocation = output.Location; + output.AddDebugSymbols(symbolsStack.Pop()); + } + } + + static bool IsDefinition(AstNode node) + { + return node is EntityDeclaration + || (node is VariableInitializer && node.Parent is FieldDeclaration) + || node is FixedVariableInitializer; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/AddCheckedBlocks.cs b/ICSharpCode.Decompiler/Ast/Transforms/AddCheckedBlocks.cs new file mode 100644 index 00000000..dc018eb1 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/AddCheckedBlocks.cs @@ -0,0 +1,368 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Add checked/unchecked blocks. + /// </summary> + public class AddCheckedBlocks : IAstTransform + { + #region Annotation + sealed class CheckedUncheckedAnnotation { + /// <summary> + /// true=checked, false=unchecked + /// </summary> + public bool IsChecked; + } + + public static readonly object CheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = true }; + public static readonly object UncheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = false }; + #endregion + + /* + We treat placing checked/unchecked blocks as an optimization problem, with the following goals: + 1. Use minimum number of checked blocks+expressions + 2. Prefer checked expressions over checked blocks + 3. Make the scope of checked expressions as small as possible + 4. Open checked blocks as late as possible, and close checked blocks as late as possible + (where goal 1 has the highest priority) + + Goal 4a (open checked blocks as late as possible) is necessary so that we don't move variable declarations + into checked blocks, as the variable might still be used after the checked block. + (this could cause DeclareVariables to omit the variable declaration, producing incorrect code) + Goal 4b (close checked blocks as late as possible) makes the code look nicer in this case: + checked { + int c = a + b; + int r = a + c; + return r; + } + If the checked block was closed as early as possible, the variable r would have to be declared outside + (this would work, but look badly) + */ + + #region struct Cost + struct Cost + { + // highest possible cost so that the Blocks+Expressions addition doesn't overflow + public static readonly Cost Infinite = new Cost(0x3fffffff, 0x3fffffff); + + public readonly int Blocks; + public readonly int Expressions; + + public Cost(int blocks, int expressions) + { + Blocks = blocks; + Expressions = expressions; + } + + public static bool operator <(Cost a, Cost b) + { + return a.Blocks + a.Expressions < b.Blocks + b.Expressions + || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks < b.Blocks; + } + + public static bool operator >(Cost a, Cost b) + { + return a.Blocks + a.Expressions > b.Blocks + b.Expressions + || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks > b.Blocks; + } + + public static bool operator <=(Cost a, Cost b) + { + return a.Blocks + a.Expressions < b.Blocks + b.Expressions + || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks <= b.Blocks; + } + + public static bool operator >=(Cost a, Cost b) + { + return a.Blocks + a.Expressions > b.Blocks + b.Expressions + || a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks >= b.Blocks; + } + + public static Cost operator +(Cost a, Cost b) + { + return new Cost(a.Blocks + b.Blocks, a.Expressions + b.Expressions); + } + + public override string ToString() + { + return string.Format("[{0} + {1}]", Blocks, Expressions); + } + } + #endregion + + #region class InsertedNode + /// <summary> + /// Holds the blocks and expressions that should be inserted + /// </summary> + abstract class InsertedNode + { + public static InsertedNode operator +(InsertedNode a, InsertedNode b) + { + if (a == null) + return b; + if (b == null) + return a; + return new InsertedNodeList(a, b); + } + + public abstract void Insert(); + } + + class InsertedNodeList : InsertedNode + { + readonly InsertedNode child1, child2; + + public InsertedNodeList(InsertedNode child1, InsertedNode child2) + { + this.child1 = child1; + this.child2 = child2; + } + + public override void Insert() + { + child1.Insert(); + child2.Insert(); + } + } + + class InsertedExpression : InsertedNode + { + readonly Expression expression; + readonly bool isChecked; + + public InsertedExpression(Expression expression, bool isChecked) + { + this.expression = expression; + this.isChecked = isChecked; + } + + public override void Insert() + { + if (isChecked) + expression.ReplaceWith(e => new CheckedExpression { Expression = e }); + else + expression.ReplaceWith(e => new UncheckedExpression { Expression = e }); + } + } + + class ConvertCompoundAssignment : InsertedNode + { + readonly Expression expression; + readonly bool isChecked; + + public ConvertCompoundAssignment(Expression expression, bool isChecked) + { + this.expression = expression; + this.isChecked = isChecked; + } + + public override void Insert() + { + AssignmentExpression assign = expression.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>().Restore(expression); + expression.ReplaceWith(assign); + if (isChecked) + assign.Right = new CheckedExpression { Expression = assign.Right.Detach() }; + else + assign.Right = new UncheckedExpression { Expression = assign.Right.Detach() }; + } + } + + class InsertedBlock : InsertedNode + { + readonly Statement firstStatement; // inclusive + readonly Statement lastStatement; // exclusive + readonly bool isChecked; + + public InsertedBlock(Statement firstStatement, Statement lastStatement, bool isChecked) + { + this.firstStatement = firstStatement; + this.lastStatement = lastStatement; + this.isChecked = isChecked; + } + + public override void Insert() + { + BlockStatement newBlock = new BlockStatement(); + // Move all statements except for the first + Statement next; + for (Statement stmt = firstStatement.GetNextStatement(); stmt != lastStatement; stmt = next) { + next = stmt.GetNextStatement(); + newBlock.Add(stmt.Detach()); + } + // Replace the first statement with the new (un)checked block + if (isChecked) + firstStatement.ReplaceWith(new CheckedStatement { Body = newBlock }); + else + firstStatement.ReplaceWith(new UncheckedStatement { Body = newBlock }); + // now also move the first node into the new block + newBlock.Statements.InsertAfter(null, firstStatement); + } + } + #endregion + + #region class Result + /// <summary> + /// Holds the result of an insertion operation. + /// </summary> + class Result + { + public Cost CostInCheckedContext; + public InsertedNode NodesToInsertInCheckedContext; + public Cost CostInUncheckedContext; + public InsertedNode NodesToInsertInUncheckedContext; + } + #endregion + + public void Run(AstNode node) + { + BlockStatement block = node as BlockStatement; + if (block == null) { + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + Run(child); + } + } else { + Result r = GetResultFromBlock(block); + if (r.NodesToInsertInUncheckedContext != null) + r.NodesToInsertInUncheckedContext.Insert(); + } + } + + Result GetResultFromBlock(BlockStatement block) + { + // For a block, we are tracking 4 possibilities: + // a) context is checked, no unchecked block open + Cost costCheckedContext = new Cost(0, 0); + InsertedNode nodesCheckedContext = null; + // b) context is checked, an unchecked block is open + Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite; + InsertedNode nodesCheckedContextUncheckedBlockOpen = null; + Statement uncheckedBlockStart = null; + // c) context is unchecked, no checked block open + Cost costUncheckedContext = new Cost(0, 0); + InsertedNode nodesUncheckedContext = null; + // d) context is unchecked, a checked block is open + Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite; + InsertedNode nodesUncheckedContextCheckedBlockOpen = null; + Statement checkedBlockStart = null; + + Statement statement = block.Statements.FirstOrDefault(); + while (true) { + // Blocks can be closed 'for free'. We use '<=' so that blocks are closed as late as possible (goal 4b) + if (costCheckedContextUncheckedBlockOpen <= costCheckedContext) { + costCheckedContext = costCheckedContextUncheckedBlockOpen; + nodesCheckedContext = nodesCheckedContextUncheckedBlockOpen + new InsertedBlock(uncheckedBlockStart, statement, false); + } + if (costUncheckedContextCheckedBlockOpen <= costUncheckedContext) { + costUncheckedContext = costUncheckedContextCheckedBlockOpen; + nodesUncheckedContext = nodesUncheckedContextCheckedBlockOpen + new InsertedBlock(checkedBlockStart, statement, true); + } + if (statement == null) + break; + // Now try opening blocks. We use '<=' so that blocks are opened as late as possible. (goal 4a) + if (costCheckedContext + new Cost(1, 0) <= costCheckedContextUncheckedBlockOpen) { + costCheckedContextUncheckedBlockOpen = costCheckedContext + new Cost(1, 0); + nodesCheckedContextUncheckedBlockOpen = nodesCheckedContext; + uncheckedBlockStart = statement; + } + if (costUncheckedContext + new Cost(1, 0) <= costUncheckedContextCheckedBlockOpen) { + costUncheckedContextCheckedBlockOpen = costUncheckedContext + new Cost(1, 0); + nodesUncheckedContextCheckedBlockOpen = nodesUncheckedContext; + checkedBlockStart = statement; + } + // Now handle the statement + Result stmtResult = GetResult(statement); + + costCheckedContext += stmtResult.CostInCheckedContext; + nodesCheckedContext += stmtResult.NodesToInsertInCheckedContext; + costCheckedContextUncheckedBlockOpen += stmtResult.CostInUncheckedContext; + nodesCheckedContextUncheckedBlockOpen += stmtResult.NodesToInsertInUncheckedContext; + costUncheckedContext += stmtResult.CostInUncheckedContext; + nodesUncheckedContext += stmtResult.NodesToInsertInUncheckedContext; + costUncheckedContextCheckedBlockOpen += stmtResult.CostInCheckedContext; + nodesUncheckedContextCheckedBlockOpen += stmtResult.NodesToInsertInCheckedContext; + + statement = statement.GetNextStatement(); + } + + return new Result { + CostInCheckedContext = costCheckedContext, NodesToInsertInCheckedContext = nodesCheckedContext, + CostInUncheckedContext = costUncheckedContext, NodesToInsertInUncheckedContext = nodesUncheckedContext + }; + } + + Result GetResult(AstNode node) + { + if (node is BlockStatement) + return GetResultFromBlock((BlockStatement)node); + Result result = new Result(); + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + Result childResult = GetResult(child); + result.CostInCheckedContext += childResult.CostInCheckedContext; + result.NodesToInsertInCheckedContext += childResult.NodesToInsertInCheckedContext; + result.CostInUncheckedContext += childResult.CostInUncheckedContext; + result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext; + } + Expression expr = node as Expression; + if (expr != null) { + CheckedUncheckedAnnotation annotation = expr.Annotation<CheckedUncheckedAnnotation>(); + if (annotation != null) { + // If the annotation requires this node to be in a specific context, add a huge cost to the other context + // That huge cost gives us the option to ignore a required checked/unchecked expression when there wouldn't be any + // solution otherwise. (e.g. "for (checked(M().x += 1); true; unchecked(M().x += 2)) {}") + if (annotation.IsChecked) + result.CostInUncheckedContext += new Cost(10000, 0); + else + result.CostInCheckedContext += new Cost(10000, 0); + } + // Embed this node in an checked/unchecked expression: + if (expr.Parent is ExpressionStatement) { + // We cannot use checked/unchecked for top-level-expressions. + // However, we could try converting a compound assignment (checked(a+=b);) or unary operator (checked(a++);) + // back to its old form. + if (expr.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>() != null) { + // We use '<' so that expressions are introduced on the deepest level possible (goal 3) + if (result.CostInCheckedContext + new Cost(1, 1) < result.CostInUncheckedContext) { + result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(1, 1); + result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new ConvertCompoundAssignment(expr, true); + } else if (result.CostInUncheckedContext + new Cost(1, 1) < result.CostInCheckedContext) { + result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(1, 1); + result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new ConvertCompoundAssignment(expr, false); + } + } + } else if (expr.Role.IsValid(Expression.Null)) { + // We use '<' so that expressions are introduced on the deepest level possible (goal 3) + if (result.CostInCheckedContext + new Cost(0, 1) < result.CostInUncheckedContext) { + result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(0, 1); + result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new InsertedExpression(expr, true); + } else if (result.CostInUncheckedContext + new Cost(0, 1) < result.CostInCheckedContext) { + result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(0, 1); + result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new InsertedExpression(expr, false); + } + } + } + return result; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs new file mode 100644 index 00000000..a0d1ca8c --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/CombineQueryExpressions.cs @@ -0,0 +1,179 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Combines query expressions and removes transparent identifiers. + /// </summary> + public class CombineQueryExpressions : IAstTransform + { + readonly DecompilerContext context; + + public CombineQueryExpressions(DecompilerContext context) + { + this.context = context; + } + + public void Run(AstNode compilationUnit) + { + if (!context.Settings.QueryExpressions) + return; + CombineQueries(compilationUnit); + } + + static readonly InvocationExpression castPattern = new InvocationExpression { + Target = new MemberReferenceExpression { + Target = new AnyNode("inExpr"), + MemberName = "Cast", + TypeArguments = { new AnyNode("targetType") } + }}; + + void CombineQueries(AstNode node) + { + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + CombineQueries(child); + } + QueryExpression query = node as QueryExpression; + if (query != null) { + QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); + QueryExpression innerQuery = fromClause.Expression as QueryExpression; + if (innerQuery != null) { + if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery)) { + RemoveTransparentIdentifierReferences(query); + } else { + QueryContinuationClause continuation = new QueryContinuationClause(); + continuation.PrecedingQuery = innerQuery.Detach(); + continuation.Identifier = fromClause.Identifier; + fromClause.ReplaceWith(continuation); + } + } else { + Match m = castPattern.Match(fromClause.Expression); + if (m.Success) { + fromClause.Type = m.Get<AstType>("targetType").Single().Detach(); + fromClause.Expression = m.Get<Expression>("inExpr").Single().Detach(); + } + } + } + } + + static readonly QuerySelectClause selectTransparentIdentifierPattern = new QuerySelectClause { + Expression = new Choice { + new AnonymousTypeCreateExpression { + Initializers = { + new NamedExpression { + Name = Pattern.AnyString, + Expression = new IdentifierExpression(Pattern.AnyString) + }.WithName("nae1"), + new NamedExpression { + Name = Pattern.AnyString, + Expression = new AnyNode("nae2Expr") + }.WithName("nae2") + } + }, + new AnonymousTypeCreateExpression { + Initializers = { + new NamedNode("identifier", new IdentifierExpression(Pattern.AnyString)), + new AnyNode("nae2Expr") + } + } + }}; + + bool IsTransparentIdentifier(string identifier) + { + return identifier.StartsWith("<>", StringComparison.Ordinal) && identifier.Contains("TransparentIdentifier"); + } + + bool TryRemoveTransparentIdentifier(QueryExpression query, QueryFromClause fromClause, QueryExpression innerQuery) + { + if (!IsTransparentIdentifier(fromClause.Identifier)) + return false; + Match match = selectTransparentIdentifierPattern.Match(innerQuery.Clauses.Last()); + if (!match.Success) + return false; + QuerySelectClause selectClause = (QuerySelectClause)innerQuery.Clauses.Last(); + NamedExpression nae1 = match.Get<NamedExpression>("nae1").SingleOrDefault(); + NamedExpression nae2 = match.Get<NamedExpression>("nae2").SingleOrDefault(); + if (nae1 != null && nae1.Name != ((IdentifierExpression)nae1.Expression).Identifier) + return false; + Expression nae2Expr = match.Get<Expression>("nae2Expr").Single(); + IdentifierExpression nae2IdentExpr = nae2Expr as IdentifierExpression; + if (nae2IdentExpr != null && (nae2 == null || nae2.Name == nae2IdentExpr.Identifier)) { + // from * in (from x in ... select new { x = x, y = y }) ... + // => + // from x in ... ... + fromClause.Remove(); + selectClause.Remove(); + // Move clauses from innerQuery to query + QueryClause insertionPos = null; + foreach (var clause in innerQuery.Clauses) { + query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); + } + } else { + // from * in (from x in ... select new { x = x, y = expr }) ... + // => + // from x in ... let y = expr ... + fromClause.Remove(); + selectClause.Remove(); + // Move clauses from innerQuery to query + QueryClause insertionPos = null; + foreach (var clause in innerQuery.Clauses) { + query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); + } + string ident; + if (nae2 != null) + ident = nae2.Name; + else if (nae2Expr is IdentifierExpression) + ident = ((IdentifierExpression)nae2Expr).Identifier; + else if (nae2Expr is MemberReferenceExpression) + ident = ((MemberReferenceExpression)nae2Expr).MemberName; + else + throw new InvalidOperationException("Could not infer name from initializer in AnonymousTypeCreateExpression"); + query.Clauses.InsertAfter(insertionPos, new QueryLetClause { Identifier = ident, Expression = nae2Expr.Detach() }); + } + return true; + } + + /// <summary> + /// Removes all occurrences of transparent identifiers + /// </summary> + void RemoveTransparentIdentifierReferences(AstNode node) + { + foreach (AstNode child in node.Children) { + RemoveTransparentIdentifierReferences(child); + } + MemberReferenceExpression mre = node as MemberReferenceExpression; + if (mre != null) { + IdentifierExpression ident = mre.Target as IdentifierExpression; + if (ident != null && IsTransparentIdentifier(ident.Identifier)) { + IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); + mre.TypeArguments.MoveTo(newIdent.TypeArguments); + newIdent.CopyAnnotationsFrom(mre); + newIdent.RemoveAnnotations<PropertyDeclaration>(); // remove the reference to the property of the anonymous type + mre.ReplaceWith(newIdent); + return; + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ContextTrackingVisitor.cs b/ICSharpCode.Decompiler/Ast/Transforms/ContextTrackingVisitor.cs new file mode 100644 index 00000000..1d1f3d08 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ContextTrackingVisitor.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Base class for AST visitors that need the current type/method context info. + /// </summary> + public abstract class ContextTrackingVisitor<TResult> : DepthFirstAstVisitor<object, TResult>, IAstTransform + { + protected readonly DecompilerContext context; + + protected ContextTrackingVisitor(DecompilerContext context) + { + if (context == null) + throw new ArgumentNullException("context"); + this.context = context; + } + + public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) + { + TypeDefinition oldType = context.CurrentType; + try { + context.CurrentType = typeDeclaration.Annotation<TypeDefinition>(); + return base.VisitTypeDeclaration(typeDeclaration, data); + } finally { + context.CurrentType = oldType; + } + } + + public override TResult VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) + { + Debug.Assert(context.CurrentMethod == null); + try { + context.CurrentMethod = methodDeclaration.Annotation<MethodDefinition>(); + return base.VisitMethodDeclaration(methodDeclaration, data); + } finally { + context.CurrentMethod = null; + } + } + + public override TResult VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) + { + Debug.Assert(context.CurrentMethod == null); + try { + context.CurrentMethod = constructorDeclaration.Annotation<MethodDefinition>(); + return base.VisitConstructorDeclaration(constructorDeclaration, data); + } finally { + context.CurrentMethod = null; + } + } + + public override TResult VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, object data) + { + Debug.Assert(context.CurrentMethod == null); + try { + context.CurrentMethod = destructorDeclaration.Annotation<MethodDefinition>(); + return base.VisitDestructorDeclaration(destructorDeclaration, data); + } finally { + context.CurrentMethod = null; + } + } + + public override TResult VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) + { + Debug.Assert(context.CurrentMethod == null); + try { + context.CurrentMethod = operatorDeclaration.Annotation<MethodDefinition>(); + return base.VisitOperatorDeclaration(operatorDeclaration, data); + } finally { + context.CurrentMethod = null; + } + } + + public override TResult VisitAccessor(Accessor accessor, object data) + { + Debug.Assert(context.CurrentMethod == null); + try { + context.CurrentMethod = accessor.Annotation<MethodDefinition>(); + return base.VisitAccessor(accessor, data); + } finally { + context.CurrentMethod = null; + } + } + + void IAstTransform.Run(AstNode node) + { + node.AcceptVisitor(this, null); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs new file mode 100644 index 00000000..36811c18 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs @@ -0,0 +1,183 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// If the first element of a constructor is a chained constructor call, convert it into a constructor initializer. + /// </summary> + public class ConvertConstructorCallIntoInitializer : DepthFirstAstVisitor<object, object>, IAstTransform + { + public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) + { + ExpressionStatement stmt = constructorDeclaration.Body.Statements.FirstOrDefault() as ExpressionStatement; + if (stmt == null) + return null; + InvocationExpression invocation = stmt.Expression as InvocationExpression; + if (invocation == null) + return null; + MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + if (mre != null && mre.MemberName == ".ctor") { + ConstructorInitializer ci = new ConstructorInitializer(); + if (mre.Target is ThisReferenceExpression) + ci.ConstructorInitializerType = ConstructorInitializerType.This; + else if (mre.Target is BaseReferenceExpression) + ci.ConstructorInitializerType = ConstructorInitializerType.Base; + else + return null; + // Move arguments from invocation to initializer: + invocation.Arguments.MoveTo(ci.Arguments); + // Add the initializer: (unless it is the default 'base()') + if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0)) + constructorDeclaration.Initializer = ci.WithAnnotation(invocation.Annotation<MethodReference>()); + // Remove the statement: + stmt.Remove(); + } + return null; + } + + static readonly ExpressionStatement fieldInitializerPattern = new ExpressionStatement { + Expression = new AssignmentExpression { + Left = new NamedNode("fieldAccess", new MemberReferenceExpression { + Target = new ThisReferenceExpression(), + MemberName = Pattern.AnyString + }), + Operator = AssignmentOperatorType.Assign, + Right = new AnyNode("initializer") + } + }; + + static readonly AstNode thisCallPattern = new ExpressionStatement(new ThisReferenceExpression().Invoke(".ctor", new Repeat(new AnyNode()))); + + public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) + { + // Handle initializers on instance fields + HandleInstanceFieldInitializers(typeDeclaration.Members); + + // Now convert base constructor calls to initializers: + base.VisitTypeDeclaration(typeDeclaration, data); + + // Remove single empty constructor: + RemoveSingleEmptyConstructor(typeDeclaration); + + // Handle initializers on static fields: + HandleStaticFieldInitializers(typeDeclaration.Members); + return null; + } + + void HandleInstanceFieldInitializers(IEnumerable<AstNode> members) + { + var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); + var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => !thisCallPattern.IsMatch(ctor.Body.Statements.FirstOrDefault())).ToArray(); + if (instanceCtorsNotChainingWithThis.Length > 0) { + MethodDefinition ctorMethodDef = instanceCtorsNotChainingWithThis[0].Annotation<MethodDefinition>(); + if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsValueType) + return; + + // Recognize field initializers: + // Convert first statement in all ctors (if all ctors have the same statement) into a field initializer. + bool allSame; + do { + Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault()); + if (!m.Success) + break; + + FieldDefinition fieldDef = m.Get<AstNode>("fieldAccess").Single().Annotation<FieldReference>().ResolveWithinSameModule(); + if (fieldDef == null) + break; + AstNode fieldOrEventDecl = members.FirstOrDefault(f => f.Annotation<FieldDefinition>() == fieldDef); + if (fieldOrEventDecl == null) + break; + Expression initializer = m.Get<Expression>("initializer").Single(); + // 'this'/'base' cannot be used in field initializers + if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression)) + break; + + allSame = true; + for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++) { + if (!instanceCtors[0].Body.First().IsMatch(instanceCtorsNotChainingWithThis[i].Body.FirstOrDefault())) + allSame = false; + } + if (allSame) { + foreach (var ctor in instanceCtorsNotChainingWithThis) + ctor.Body.First().Remove(); + fieldOrEventDecl.GetChildrenByRole(Roles.Variable).Single().Initializer = initializer.Detach(); + } + } while (allSame); + } + } + + void RemoveSingleEmptyConstructor(TypeDeclaration typeDeclaration) + { + var instanceCtors = typeDeclaration.Members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); + if (instanceCtors.Length == 1) { + ConstructorDeclaration emptyCtor = new ConstructorDeclaration(); + emptyCtor.Modifiers = ((typeDeclaration.Modifiers & Modifiers.Abstract) == Modifiers.Abstract ? Modifiers.Protected : Modifiers.Public); + emptyCtor.Body = new BlockStatement(); + if (emptyCtor.IsMatch(instanceCtors[0])) + instanceCtors[0].Remove(); + } + } + + void HandleStaticFieldInitializers(IEnumerable<AstNode> members) + { + // Convert static constructor into field initializers if the class is BeforeFieldInit + var staticCtor = members.OfType<ConstructorDeclaration>().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static); + if (staticCtor != null) { + MethodDefinition ctorMethodDef = staticCtor.Annotation<MethodDefinition>(); + if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsBeforeFieldInit) { + while (true) { + ExpressionStatement es = staticCtor.Body.Statements.FirstOrDefault() as ExpressionStatement; + if (es == null) + break; + AssignmentExpression assignment = es.Expression as AssignmentExpression; + if (assignment == null || assignment.Operator != AssignmentOperatorType.Assign) + break; + FieldDefinition fieldDef = assignment.Left.Annotation<FieldReference>().ResolveWithinSameModule(); + if (fieldDef == null || !fieldDef.IsStatic) + break; + FieldDeclaration fieldDecl = members.OfType<FieldDeclaration>().FirstOrDefault(f => f.Annotation<FieldDefinition>() == fieldDef); + if (fieldDecl == null) + break; + fieldDecl.Variables.Single().Initializer = assignment.Right.Detach(); + es.Remove(); + } + if (staticCtor.Body.Statements.Count == 0) + staticCtor.Remove(); + } + } + } + + void IAstTransform.Run(AstNode node) + { + // If we're viewing some set of members (fields are direct children of CompilationUnit), + // we also need to handle those: + HandleInstanceFieldInitializers(node.Children); + HandleStaticFieldInitializers(node.Children); + + node.AcceptVisitor(this, null); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs b/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs new file mode 100644 index 00000000..b80c56af --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Reflection; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + internal sealed class TypePattern : Pattern + { + readonly string ns; + readonly string name; + + public TypePattern(Type type) + { + ns = type.Namespace; + name = type.Name; + } + + public override bool DoMatch(INode other, Match match) + { + ComposedType ct = other as ComposedType; + AstType o; + if (ct != null && !ct.HasNullableSpecifier && ct.PointerRank == 0 && !ct.ArraySpecifiers.Any()) { + // Special case: ILSpy sometimes produces a ComposedType but then removed all array specifiers + // from it. In that case, we need to look at the base type for the annotations. + o = ct.BaseType; + } else { + o = other as AstType; + if (o == null) + return false; + } + TypeReference tr = o.Annotation<TypeReference>(); + return tr != null && tr.Namespace == ns && tr.Name == name; + } + + public override string ToString() + { + return name; + } + } + + internal sealed class LdTokenPattern : Pattern + { + AnyNode childNode; + + public LdTokenPattern(string groupName) + { + childNode = new AnyNode(groupName); + } + + public override bool DoMatch(INode other, Match match) + { + InvocationExpression ie = other as InvocationExpression; + if (ie != null && ie.Annotation<LdTokenAnnotation>() != null && ie.Arguments.Count == 1) { + return childNode.DoMatch(ie.Arguments.Single(), match); + } + return false; + } + + public override string ToString() + { + return "ldtoken(...)"; + } + } + + /// <summary> + /// typeof-Pattern that applies on the expanded form of typeof (prior to ReplaceMethodCallsWithOperators) + /// </summary> + internal sealed class TypeOfPattern : Pattern + { + INode childNode; + + public TypeOfPattern(string groupName) + { + childNode = new TypePattern(typeof(Type)).ToType().Invoke( + "GetTypeFromHandle", new TypeOfExpression(new AnyNode(groupName)).Member("TypeHandle")); + } + + public override bool DoMatch(INode other, Match match) + { + return childNode.DoMatch(other, match); + } + + public override string ToString() + { + return "typeof(...)"; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DecimalConstantTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/DecimalConstantTransform.cs new file mode 100644 index 00000000..298682af --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/DecimalConstantTransform.cs @@ -0,0 +1,58 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Transforms decimal constant fields. + /// </summary> + public class DecimalConstantTransform : DepthFirstAstVisitor<object, object>, IAstTransform + { + static readonly PrimitiveType decimalType = new PrimitiveType("decimal"); + + public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) + { + const Modifiers staticReadOnly = Modifiers.Static | Modifiers.Readonly; + if ((fieldDeclaration.Modifiers & staticReadOnly) == staticReadOnly && decimalType.IsMatch(fieldDeclaration.ReturnType)) { + foreach (var attributeSection in fieldDeclaration.Attributes) { + foreach (var attribute in attributeSection.Attributes) { + TypeReference tr = attribute.Type.Annotation<TypeReference>(); + if (tr != null && tr.Name == "DecimalConstantAttribute" && tr.Namespace == "System.Runtime.CompilerServices") { + attribute.Remove(); + if (attributeSection.Attributes.Count == 0) + attributeSection.Remove(); + fieldDeclaration.Modifiers = (fieldDeclaration.Modifiers & ~staticReadOnly) | Modifiers.Const; + return null; + } + } + } + } + return null; + } + + public void Run(AstNode compilationUnit) + { + compilationUnit.AcceptVisitor(this, null); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs new file mode 100644 index 00000000..a27f30b4 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs @@ -0,0 +1,368 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.Analysis; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Moves variable declarations to improved positions. + /// </summary> + public class DeclareVariables : IAstTransform + { + sealed class VariableToDeclare + { + public AstType Type; + public string Name; + public ILVariable ILVariable; + + public AssignmentExpression ReplacedAssignment; + public Statement InsertionPoint; + } + + readonly CancellationToken cancellationToken; + List<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>(); + + public DeclareVariables(DecompilerContext context) + { + cancellationToken = context.CancellationToken; + } + + public void Run(AstNode node) + { + Run(node, null); + // Declare all the variables at the end, after all the logic has run. + // This is done so that definite assignment analysis can work on a single representation and doesn't have to be updated + // when we change the AST. + foreach (var v in variablesToDeclare) { + if (v.ReplacedAssignment == null) { + BlockStatement block = (BlockStatement)v.InsertionPoint.Parent; + var decl = new VariableDeclarationStatement((AstType)v.Type.Clone(), v.Name); + if (v.ILVariable != null) + decl.Variables.Single().AddAnnotation(v.ILVariable); + block.Statements.InsertBefore( + v.InsertionPoint, + decl); + } + } + // First do all the insertions, then do all the replacements. This is necessary because a replacement might remove our reference point from the AST. + foreach (var v in variablesToDeclare) { + if (v.ReplacedAssignment != null) { + // We clone the right expression so that it doesn't get removed from the old ExpressionStatement, + // which might be still in use by the definite assignment graph. + VariableInitializer initializer = new VariableInitializer(v.Name, v.ReplacedAssignment.Right.Detach()).CopyAnnotationsFrom(v.ReplacedAssignment).WithAnnotation(v.ILVariable); + VariableDeclarationStatement varDecl = new VariableDeclarationStatement { + Type = (AstType)v.Type.Clone(), + Variables = { initializer } + }; + ExpressionStatement es = v.ReplacedAssignment.Parent as ExpressionStatement; + if (es != null) { + // Note: if this crashes with 'Cannot replace the root node', check whether two variables were assigned the same name + es.ReplaceWith(varDecl.CopyAnnotationsFrom(es)); + } else { + v.ReplacedAssignment.ReplaceWith(varDecl); + } + } + } + variablesToDeclare = null; + } + + void Run(AstNode node, DefiniteAssignmentAnalysis daa) + { + BlockStatement block = node as BlockStatement; + if (block != null) { + var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement) + .Cast<VariableDeclarationStatement>().ToList(); + if (variables.Count > 0) { + // remove old variable declarations: + foreach (VariableDeclarationStatement varDecl in variables) { + Debug.Assert(varDecl.Variables.Single().Initializer.IsNull); + varDecl.Remove(); + } + if (daa == null) { + // If possible, reuse the DefiniteAssignmentAnalysis that was created for the parent block + daa = new DefiniteAssignmentAnalysis(block, cancellationToken); + } + foreach (VariableDeclarationStatement varDecl in variables) { + VariableInitializer initializer = varDecl.Variables.Single(); + string variableName = initializer.Name; + ILVariable v = initializer.Annotation<ILVariable>(); + bool allowPassIntoLoops = initializer.Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null; + DeclareVariableInBlock(daa, block, varDecl.Type, variableName, v, allowPassIntoLoops); + } + } + } + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + Run(child, daa); + } + } + + void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, ILVariable v, bool allowPassIntoLoops) + { + // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block + Statement declarationPoint = null; + // Check whether we can move down the variable into the sub-blocks + bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); + if (declarationPoint == null) { + // The variable isn't used at all + return; + } + if (canMoveVariableIntoSubBlocks) { + // Declare the variable within the sub-blocks + foreach (Statement stmt in block.Statements) { + ForStatement forStmt = stmt as ForStatement; + if (forStmt != null && forStmt.Initializers.Count == 1) { + // handle the special case of moving a variable into the for initializer + if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName)) + continue; + } + UsingStatement usingStmt = stmt as UsingStatement; + if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) { + // handle the special case of moving a variable into a using statement + if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName)) + continue; + } + IfElseStatement ies = stmt as IfElseStatement; + if (ies != null) { + foreach (var child in IfElseChainChildren(ies)) { + BlockStatement subBlock = child as BlockStatement; + if (subBlock != null) + DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); + } + continue; + } + foreach (AstNode child in stmt.Children) { + BlockStatement subBlock = child as BlockStatement; + if (subBlock != null) { + DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); + } else if (HasNestedBlocks(child)) { + foreach (BlockStatement nestedSubBlock in child.Children.OfType<BlockStatement>()) { + DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, v, allowPassIntoLoops); + } + } + } + } + } else { + // Try converting an assignment expression into a VariableDeclarationStatement + if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) { + // Declare the variable in front of declarationPoint + variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = v, InsertionPoint = declarationPoint }); + } + } + } + + bool TryConvertAssignmentExpressionIntoVariableDeclaration(Statement declarationPoint, AstType type, string variableName) + { + // convert the declarationPoint into a VariableDeclarationStatement + ExpressionStatement es = declarationPoint as ExpressionStatement; + if (es != null) { + return TryConvertAssignmentExpressionIntoVariableDeclaration(es.Expression, type, variableName); + } + return false; + } + + bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName) + { + AssignmentExpression ae = expression as AssignmentExpression; + if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { + IdentifierExpression ident = ae.Left as IdentifierExpression; + if (ident != null && ident.Identifier == variableName) { + variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = ident.Annotation<ILVariable>(), ReplacedAssignment = ae }); + return true; + } + } + return false; + } + + /// <summary> + /// Finds the declaration point for the variable within the specified block. + /// </summary> + /// <param name="daa"> + /// Definite assignment analysis, must be prepared for 'block' or one of its parents. + /// </param> + /// <param name="varDecl">The variable to declare</param> + /// <param name="block">The block in which the variable should be declared</param> + /// <param name="declarationPoint"> + /// Output parameter: the first statement within 'block' where the variable needs to be declared. + /// </param> + /// <returns> + /// Returns whether it is possible to move the variable declaration into sub-blocks. + /// </returns> + public static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, VariableDeclarationStatement varDecl, BlockStatement block, out Statement declarationPoint) + { + string variableName = varDecl.Variables.Single().Name; + bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null; + return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); + } + + static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint) + { + // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block + declarationPoint = null; + foreach (Statement stmt in block.Statements) { + if (UsesVariable(stmt, variableName)) { + if (declarationPoint == null) + declarationPoint = stmt; + if (!CanMoveVariableUseIntoSubBlock(stmt, variableName, allowPassIntoLoops)) { + // If it's not possible to move the variable use into a nested block, + // we need to declare the variable in this block + return false; + } + // If we can move the variable into the sub-block, we need to ensure that the remaining code + // does not use the value that was assigned by the first sub-block + Statement nextStatement = stmt.GetNextStatement(); + if (nextStatement != null) { + // Analyze the range from the next statement to the end of the block + daa.SetAnalyzedRange(nextStatement, block); + daa.Analyze(variableName); + if (daa.UnassignedVariableUses.Count > 0) { + return false; + } + } + } + } + return true; + } + + static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops) + { + if (!allowPassIntoLoops && (stmt is ForStatement || stmt is ForeachStatement || stmt is DoWhileStatement || stmt is WhileStatement)) + return false; + + ForStatement forStatement = stmt as ForStatement; + if (forStatement != null && forStatement.Initializers.Count == 1) { + // for-statement is special case: we can move variable declarations into the initializer + ExpressionStatement es = forStatement.Initializers.Single() as ExpressionStatement; + if (es != null) { + AssignmentExpression ae = es.Expression as AssignmentExpression; + if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { + IdentifierExpression ident = ae.Left as IdentifierExpression; + if (ident != null && ident.Identifier == variableName) { + return !UsesVariable(ae.Right, variableName); + } + } + } + } + + UsingStatement usingStatement = stmt as UsingStatement; + if (usingStatement != null) { + // using-statement is special case: we can move variable declarations into the initializer + AssignmentExpression ae = usingStatement.ResourceAcquisition as AssignmentExpression; + if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { + IdentifierExpression ident = ae.Left as IdentifierExpression; + if (ident != null && ident.Identifier == variableName) { + return !UsesVariable(ae.Right, variableName); + } + } + } + + IfElseStatement ies = stmt as IfElseStatement; + if (ies != null) { + foreach (var child in IfElseChainChildren(ies)) { + if (!(child is BlockStatement) && UsesVariable(child, variableName)) + return false; + } + return true; + } + + // We can move the variable into a sub-block only if the variable is used in only that sub-block (and not in expressions such as the loop condition) + for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) { + if (!(child is BlockStatement) && UsesVariable(child, variableName)) { + if (HasNestedBlocks(child)) { + // catch clauses/switch sections can contain nested blocks + for (AstNode grandchild = child.FirstChild; grandchild != null; grandchild = grandchild.NextSibling) { + if (!(grandchild is BlockStatement) && UsesVariable(grandchild, variableName)) + return false; + } + } else { + return false; + } + } + } + return true; + } + + static IEnumerable<AstNode> IfElseChainChildren(IfElseStatement ies) + { + IfElseStatement prev; + do { + yield return ies.Condition; + yield return ies.TrueStatement; + prev = ies; + ies = ies.FalseStatement as IfElseStatement; + } while (ies != null); + if (!prev.FalseStatement.IsNull) + yield return prev.FalseStatement; + } + + static bool HasNestedBlocks(AstNode node) + { + return node is CatchClause || node is SwitchSection; + } + + static bool UsesVariable(AstNode node, string variableName) + { + IdentifierExpression ie = node as IdentifierExpression; + if (ie != null && ie.Identifier == variableName) + return true; + + FixedStatement fixedStatement = node as FixedStatement; + if (fixedStatement != null) { + foreach (VariableInitializer v in fixedStatement.Variables) { + if (v.Name == variableName) + return false; // no need to introduce the variable here + } + } + + ForeachStatement foreachStatement = node as ForeachStatement; + if (foreachStatement != null) { + if (foreachStatement.VariableName == variableName) + return false; // no need to introduce the variable here + } + + UsingStatement usingStatement = node as UsingStatement; + if (usingStatement != null) { + VariableDeclarationStatement varDecl = usingStatement.ResourceAcquisition as VariableDeclarationStatement; + if (varDecl != null) { + foreach (VariableInitializer v in varDecl.Variables) { + if (v.Name == variableName) + return false; // no need to introduce the variable here + } + } + } + + CatchClause catchClause = node as CatchClause; + if (catchClause != null && catchClause.VariableName == variableName) { + return false; // no need to introduce the variable here + } + + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + if (UsesVariable(child, variableName)) + return true; + } + return false; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs new file mode 100644 index 00000000..04b2293d --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -0,0 +1,502 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)". + /// For anonymous methods, creates an AnonymousMethodExpression. + /// Also gets rid of any "Display Classes" left over after inlining an anonymous method. + /// </summary> + public class DelegateConstruction : ContextTrackingVisitor<object> + { + internal sealed class Annotation + { + /// <summary> + /// ldftn or ldvirtftn? + /// </summary> + public readonly bool IsVirtual; + + public Annotation(bool isVirtual) + { + IsVirtual = isVirtual; + } + } + + internal sealed class CapturedVariableAnnotation + { + } + + List<string> currentlyUsedVariableNames = new List<string>(); + + public DelegateConstruction(DecompilerContext context) : base(context) + { + } + + public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) + { + if (objectCreateExpression.Arguments.Count == 2) { + Expression obj = objectCreateExpression.Arguments.First(); + Expression func = objectCreateExpression.Arguments.Last(); + Annotation annotation = func.Annotation<Annotation>(); + if (annotation != null) { + IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single(); + MethodReference method = methodIdent.Annotation<MethodReference>(); + if (method != null) { + if (HandleAnonymousMethod(objectCreateExpression, obj, method)) + return null; + // Perform the transformation to "new Action(obj.func)". + obj.Remove(); + methodIdent.Remove(); + if (!annotation.IsVirtual && obj is ThisReferenceExpression) { + // maybe it's getting the pointer of a base method? + if (method.DeclaringType.GetElementType() != context.CurrentType) { + obj = new BaseReferenceExpression(); + } + } + if (!annotation.IsVirtual && obj is NullReferenceExpression && !method.HasThis) { + // We're loading a static method. + // However it is possible to load extension methods with an instance, so we compare the number of arguments: + bool isExtensionMethod = false; + TypeReference delegateType = objectCreateExpression.Type.Annotation<TypeReference>(); + if (delegateType != null) { + TypeDefinition delegateTypeDef = delegateType.Resolve(); + if (delegateTypeDef != null) { + MethodDefinition invokeMethod = delegateTypeDef.Methods.FirstOrDefault(m => m.Name == "Invoke"); + if (invokeMethod != null) { + isExtensionMethod = (invokeMethod.Parameters.Count + 1 == method.Parameters.Count); + } + } + } + if (!isExtensionMethod) { + obj = new TypeReferenceExpression { Type = AstBuilder.ConvertType(method.DeclaringType) }; + } + } + // now transform the identifier into a member reference + MemberReferenceExpression mre = new MemberReferenceExpression(); + mre.Target = obj; + mre.MemberName = methodIdent.Identifier; + methodIdent.TypeArguments.MoveTo(mre.TypeArguments); + mre.AddAnnotation(method); + objectCreateExpression.Arguments.Clear(); + objectCreateExpression.Arguments.Add(mre); + return null; + } + } + } + return base.VisitObjectCreateExpression(objectCreateExpression, data); + } + + internal static bool IsAnonymousMethod(DecompilerContext context, MethodDefinition method) + { + if (method == null || !(method.HasGeneratedName() || method.Name.Contains("$"))) + return false; + if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType))) + return false; + return true; + } + + bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) + { + if (!context.Settings.AnonymousMethods) + return false; // anonymous method decompilation is disabled + if (target != null && !(target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression)) + return false; // don't copy arbitrary expressions, deal with identifiers only + + // Anonymous methods are defined in the same assembly + MethodDefinition method = methodRef.ResolveWithinSameModule(); + if (!IsAnonymousMethod(context, method)) + return false; + + // Create AnonymousMethodExpression and prepare parameters + AnonymousMethodExpression ame = new AnonymousMethodExpression(); + ame.CopyAnnotationsFrom(objectCreateExpression); // copy ILRanges etc. + ame.RemoveAnnotations<MethodReference>(); // remove reference to delegate ctor + ame.AddAnnotation(method); // add reference to anonymous method + ame.Parameters.AddRange(AstBuilder.MakeParameters(method, isLambda: true)); + ame.HasParameterList = true; + + // rename variables so that they don't conflict with the parameters: + foreach (ParameterDeclaration pd in ame.Parameters) { + EnsureVariableNameIsAvailable(objectCreateExpression, pd.Name); + } + + // Decompile the anonymous method: + + DecompilerContext subContext = context.Clone(); + subContext.CurrentMethod = method; + subContext.CurrentMethodIsAsync = false; + subContext.ReservedVariableNames.AddRange(currentlyUsedVariableNames); + BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext, ame.Parameters); + TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext); + body.AcceptVisitor(this, null); + + + bool isLambda = false; + if (ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) { + isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement); + } + // Remove the parameter list from an AnonymousMethodExpression if the original method had no names, + // and the parameters are not used in the method body + if (!isLambda && method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) { + var parameterReferencingIdentifiers = + from ident in body.Descendants.OfType<IdentifierExpression>() + let v = ident.Annotation<ILVariable>() + where v != null && v.IsParameter && method.Parameters.Contains(v.OriginalParameter) + select ident; + if (!parameterReferencingIdentifiers.Any()) { + ame.Parameters.Clear(); + ame.HasParameterList = false; + } + } + + // Replace all occurrences of 'this' in the method body with the delegate's target: + foreach (AstNode node in body.Descendants) { + if (node is ThisReferenceExpression) + node.ReplaceWith(target.Clone()); + } + Expression replacement; + if (isLambda) { + LambdaExpression lambda = new LambdaExpression(); + lambda.CopyAnnotationsFrom(ame); + ame.Parameters.MoveTo(lambda.Parameters); + Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression; + returnExpr.Remove(); + lambda.Body = returnExpr; + replacement = lambda; + } else { + ame.Body = body; + replacement = ame; + } + var expectedType = objectCreateExpression.Annotation<TypeInformation>().ExpectedType.Resolve(); + if (expectedType != null && !expectedType.IsDelegate()) { + var simplifiedDelegateCreation = (ObjectCreateExpression)objectCreateExpression.Clone(); + simplifiedDelegateCreation.Arguments.Clear(); + simplifiedDelegateCreation.Arguments.Add(replacement); + replacement = simplifiedDelegateCreation; + } + objectCreateExpression.ReplaceWith(replacement); + return true; + } + + internal static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass) + { + if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + return false; + // check that methodContainingType is within containingType + while (potentialDisplayClass != context.CurrentType) { + potentialDisplayClass = potentialDisplayClass.DeclaringType; + if (potentialDisplayClass == null) + return false; + } + return true; + } + + public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data) + { + if (context.Settings.ExpressionTrees && ExpressionTreeConverter.CouldBeExpressionTree(invocationExpression)) { + Expression converted = ExpressionTreeConverter.TryConvert(context, invocationExpression); + if (converted != null) { + invocationExpression.ReplaceWith(converted); + return converted.AcceptVisitor(this, data); + } + } + return base.VisitInvocationExpression(invocationExpression, data); + } + + #region Track current variables + public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) + { + Debug.Assert(currentlyUsedVariableNames.Count == 0); + try { + currentlyUsedVariableNames.AddRange(methodDeclaration.Parameters.Select(p => p.Name)); + return base.VisitMethodDeclaration(methodDeclaration, data); + } finally { + currentlyUsedVariableNames.Clear(); + } + } + + public override object VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) + { + Debug.Assert(currentlyUsedVariableNames.Count == 0); + try { + currentlyUsedVariableNames.AddRange(operatorDeclaration.Parameters.Select(p => p.Name)); + return base.VisitOperatorDeclaration(operatorDeclaration, data); + } finally { + currentlyUsedVariableNames.Clear(); + } + } + + public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) + { + Debug.Assert(currentlyUsedVariableNames.Count == 0); + try { + currentlyUsedVariableNames.AddRange(constructorDeclaration.Parameters.Select(p => p.Name)); + return base.VisitConstructorDeclaration(constructorDeclaration, data); + } finally { + currentlyUsedVariableNames.Clear(); + } + } + + public override object VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data) + { + Debug.Assert(currentlyUsedVariableNames.Count == 0); + try { + currentlyUsedVariableNames.AddRange(indexerDeclaration.Parameters.Select(p => p.Name)); + return base.VisitIndexerDeclaration(indexerDeclaration, data); + } finally { + currentlyUsedVariableNames.Clear(); + } + } + + public override object VisitAccessor(Accessor accessor, object data) + { + try { + currentlyUsedVariableNames.Add("value"); + return base.VisitAccessor(accessor, data); + } finally { + currentlyUsedVariableNames.RemoveAt(currentlyUsedVariableNames.Count - 1); + } + } + + public override object VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data) + { + foreach (VariableInitializer v in variableDeclarationStatement.Variables) + currentlyUsedVariableNames.Add(v.Name); + return base.VisitVariableDeclarationStatement(variableDeclarationStatement, data); + } + + public override object VisitFixedStatement(FixedStatement fixedStatement, object data) + { + foreach (VariableInitializer v in fixedStatement.Variables) + currentlyUsedVariableNames.Add(v.Name); + return base.VisitFixedStatement(fixedStatement, data); + } + #endregion + + static readonly ExpressionStatement displayClassAssignmentPattern = + new ExpressionStatement(new AssignmentExpression( + new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), + new ObjectCreateExpression { Type = new AnyNode("type") } + )); + + public override object VisitBlockStatement(BlockStatement blockStatement, object data) + { + int numberOfVariablesOutsideBlock = currentlyUsedVariableNames.Count; + base.VisitBlockStatement(blockStatement, data); + foreach (ExpressionStatement stmt in blockStatement.Statements.OfType<ExpressionStatement>().ToArray()) { + Match displayClassAssignmentMatch = displayClassAssignmentPattern.Match(stmt); + if (!displayClassAssignmentMatch.Success) + continue; + + ILVariable variable = displayClassAssignmentMatch.Get<AstNode>("variable").Single().Annotation<ILVariable>(); + if (variable == null) + continue; + TypeDefinition type = variable.Type.ResolveWithinSameModule(); + if (!IsPotentialClosure(context, type)) + continue; + if (displayClassAssignmentMatch.Get<AstType>("type").Single().Annotation<TypeReference>().ResolveWithinSameModule() != type) + continue; + + // Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses: + bool ok = true; + foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) { + if (identExpr.Identifier == variable.Name && identExpr != displayClassAssignmentMatch.Get("variable").Single()) { + if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation<FieldReference>() != null)) + ok = false; + } + } + if (!ok) + continue; + Dictionary<FieldReference, AstNode> dict = new Dictionary<FieldReference, AstNode>(); + + // Delete the variable declaration statement: + VariableDeclarationStatement displayClassVarDecl = PatternStatementTransform.FindVariableDeclaration(stmt, variable.Name); + if (displayClassVarDecl != null) + displayClassVarDecl.Remove(); + + // Delete the assignment statement: + AstNode cur = stmt.NextSibling; + stmt.Remove(); + + // Delete any following statements as long as they assign parameters to the display class + BlockStatement rootBlock = blockStatement.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? blockStatement; + List<ILVariable> parameterOccurrances = rootBlock.Descendants.OfType<IdentifierExpression>() + .Select(n => n.Annotation<ILVariable>()).Where(p => p != null && p.IsParameter).ToList(); + AstNode next; + for (; cur != null; cur = next) { + next = cur.NextSibling; + + // Test for the pattern: + // "variableName.MemberName = right;" + ExpressionStatement closureFieldAssignmentPattern = new ExpressionStatement( + new AssignmentExpression( + new NamedNode("left", new MemberReferenceExpression { + Target = new IdentifierExpression(variable.Name), + MemberName = Pattern.AnyString + }), + new AnyNode("right") + ) + ); + Match m = closureFieldAssignmentPattern.Match(cur); + if (m.Success) { + FieldDefinition fieldDef = m.Get<MemberReferenceExpression>("left").Single().Annotation<FieldReference>().ResolveWithinSameModule(); + AstNode right = m.Get<AstNode>("right").Single(); + bool isParameter = false; + bool isDisplayClassParentPointerAssignment = false; + if (right is ThisReferenceExpression) { + isParameter = true; + } else if (right is IdentifierExpression) { + // handle parameters only if the whole method contains no other occurrence except for 'right' + ILVariable v = right.Annotation<ILVariable>(); + isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1; + if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) { + // parent display class within the same method + // (closure2.localsX = closure1;) + isDisplayClassParentPointerAssignment = true; + } + } else if (right is MemberReferenceExpression) { + // copy of parent display class reference from an outer lambda + // closure2.localsX = this.localsY + MemberReferenceExpression mre = m.Get<MemberReferenceExpression>("right").Single(); + do { + // descend into the targets of the mre as long as the field types are closures + FieldDefinition fieldDef2 = mre.Annotation<FieldReference>().ResolveWithinSameModule(); + if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) { + break; + } + // if we finally get to a this reference, it's copying a display class parent pointer + if (mre.Target is ThisReferenceExpression) { + isDisplayClassParentPointerAssignment = true; + } + mre = mre.Target as MemberReferenceExpression; + } while (mre != null); + } + if (isParameter || isDisplayClassParentPointerAssignment) { + dict[fieldDef] = right; + cur.Remove(); + } else { + break; + } + } else { + break; + } + } + + // Now create variables for all fields of the display class (except for those that we already handled as parameters) + List<Tuple<AstType, ILVariable>> variablesToDeclare = new List<Tuple<AstType, ILVariable>>(); + foreach (FieldDefinition field in type.Fields) { + if (field.IsStatic) + continue; // skip static fields + if (dict.ContainsKey(field)) // skip field if it already was handled as parameter + continue; + string capturedVariableName = field.Name; + if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10) + capturedVariableName = capturedVariableName.Substring(10); + EnsureVariableNameIsAvailable(blockStatement, capturedVariableName); + currentlyUsedVariableNames.Add(capturedVariableName); + ILVariable ilVar = new ILVariable + { + IsGenerated = true, + Name = capturedVariableName, + Type = field.FieldType, + }; + variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), ilVar)); + dict[field] = new IdentifierExpression(capturedVariableName).WithAnnotation(ilVar); + } + + // Now figure out where the closure was accessed and use the simpler replacement expression there: + foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) { + if (identExpr.Identifier == variable.Name) { + MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent; + AstNode replacement; + if (dict.TryGetValue(mre.Annotation<FieldReference>().ResolveWithinSameModule(), out replacement)) { + mre.ReplaceWith(replacement.Clone()); + } + } + } + // Now insert the variable declarations (we can do this after the replacements only so that the scope detection works): + Statement insertionPoint = blockStatement.Statements.FirstOrDefault(); + foreach (var tuple in variablesToDeclare) { + var newVarDecl = new VariableDeclarationStatement(tuple.Item1, tuple.Item2.Name); + newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation()); + newVarDecl.Variables.Single().AddAnnotation(tuple.Item2); + blockStatement.Statements.InsertBefore(insertionPoint, newVarDecl); + } + } + currentlyUsedVariableNames.RemoveRange(numberOfVariablesOutsideBlock, currentlyUsedVariableNames.Count - numberOfVariablesOutsideBlock); + return null; + } + + void EnsureVariableNameIsAvailable(AstNode currentNode, string name) + { + int pos = currentlyUsedVariableNames.IndexOf(name); + if (pos < 0) { + // name is still available + return; + } + // Naming conflict. Let's rename the existing variable so that the field keeps the name from metadata. + NameVariables nv = new NameVariables(); + // Add currently used variable and parameter names + foreach (string nameInUse in currentlyUsedVariableNames) + nv.AddExistingName(nameInUse); + // variables declared in child nodes of this block + foreach (VariableInitializer vi in currentNode.Descendants.OfType<VariableInitializer>()) + nv.AddExistingName(vi.Name); + // parameters in child lambdas + foreach (ParameterDeclaration pd in currentNode.Descendants.OfType<ParameterDeclaration>()) + nv.AddExistingName(pd.Name); + + string newName = nv.GetAlternativeName(name); + currentlyUsedVariableNames[pos] = newName; + + // find top-most block + AstNode topMostBlock = currentNode.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? currentNode; + + // rename identifiers + foreach (IdentifierExpression ident in topMostBlock.Descendants.OfType<IdentifierExpression>()) { + if (ident.Identifier == name) { + ident.Identifier = newName; + ILVariable v = ident.Annotation<ILVariable>(); + if (v != null) + v.Name = newName; + } + } + // rename variable declarations + foreach (VariableInitializer vi in topMostBlock.Descendants.OfType<VariableInitializer>()) { + if (vi.Name == name) { + vi.Name = newName; + ILVariable v = vi.Annotation<ILVariable>(); + if (v != null) + v.Name = newName; + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs b/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs new file mode 100644 index 00000000..2327200d --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs @@ -0,0 +1,875 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + public class ExpressionTreeConverter + { + #region static TryConvert method + public static bool CouldBeExpressionTree(InvocationExpression expr) + { + if (expr != null && expr.Arguments.Count == 2) { + MethodReference mr = expr.Annotation<MethodReference>(); + return mr != null && mr.Name == "Lambda" && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression"; + } + return false; + } + + public static Expression TryConvert(DecompilerContext context, Expression expr) + { + Expression converted = new ExpressionTreeConverter(context).Convert(expr); + if (converted != null) { + converted.AddAnnotation(new ExpressionTreeLambdaAnnotation()); + } + return converted; + } + #endregion + + readonly DecompilerContext context; + Stack<LambdaExpression> activeLambdas = new Stack<LambdaExpression>(); + + ExpressionTreeConverter(DecompilerContext context) + { + this.context = context; + } + + #region Main Convert method + Expression Convert(Expression expr) + { + InvocationExpression invocation = expr as InvocationExpression; + if (invocation != null) { + MethodReference mr = invocation.Annotation<MethodReference>(); + if (mr != null && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression") { + switch (mr.Name) { + case "Add": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Add, false); + case "AddChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Add, true); + case "AddAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, false); + case "AddAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, true); + case "And": + return ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseAnd); + case "AndAlso": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalAnd); + case "AndAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseAnd); + case "ArrayAccess": + case "ArrayIndex": + return ConvertArrayIndex(invocation); + case "ArrayLength": + return ConvertArrayLength(invocation); + case "Assign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Assign); + case "Call": + return ConvertCall(invocation); + case "Coalesce": + return ConvertBinaryOperator(invocation, BinaryOperatorType.NullCoalescing); + case "Condition": + return ConvertCondition(invocation); + case "Constant": + if (invocation.Arguments.Count >= 1) + return invocation.Arguments.First().Clone(); + else + return NotSupported(expr); + case "Convert": + return ConvertCast(invocation, false); + case "ConvertChecked": + return ConvertCast(invocation, true); + case "Divide": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Divide); + case "DivideAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Divide); + case "Equal": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Equality); + case "ExclusiveOr": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ExclusiveOr); + case "ExclusiveOrAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ExclusiveOr); + case "Field": + return ConvertField(invocation); + case "GreaterThan": + return ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThan); + case "GreaterThanOrEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThanOrEqual); + case "Invoke": + return ConvertInvoke(invocation); + case "Lambda": + return ConvertLambda(invocation); + case "LeftShift": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftLeft); + case "LeftShiftAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftLeft); + case "LessThan": + return ConvertBinaryOperator(invocation, BinaryOperatorType.LessThan); + case "LessThanOrEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.LessThanOrEqual); + case "ListInit": + return ConvertListInit(invocation); + case "MemberInit": + return ConvertMemberInit(invocation); + case "Modulo": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Modulus); + case "ModuloAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Modulus); + case "Multiply": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, false); + case "MultiplyChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, true); + case "MultiplyAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, false); + case "MultiplyAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, true); + case "Negate": + return ConvertUnaryOperator(invocation, UnaryOperatorType.Minus, false); + case "NegateChecked": + return ConvertUnaryOperator(invocation, UnaryOperatorType.Minus, true); + case "New": + return ConvertNewObject(invocation); + case "NewArrayBounds": + return ConvertNewArrayBounds(invocation); + case "NewArrayInit": + return ConvertNewArrayInit(invocation); + case "Not": + return ConvertUnaryOperator(invocation, UnaryOperatorType.Not); + case "NotEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.InEquality); + case "OnesComplement": + return ConvertUnaryOperator(invocation, UnaryOperatorType.BitNot); + case "Or": + return ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseOr); + case "OrAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseOr); + case "OrElse": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalOr); + case "Property": + return ConvertProperty(invocation); + case "Quote": + if (invocation.Arguments.Count == 1) + return Convert(invocation.Arguments.Single()); + else + return NotSupported(invocation); + case "RightShift": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftRight); + case "RightShiftAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftRight); + case "Subtract": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, false); + case "SubtractChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, true); + case "SubtractAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, false); + case "SubtractAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, true); + case "TypeAs": + return ConvertTypeAs(invocation); + case "TypeIs": + return ConvertTypeIs(invocation); + } + } + } + IdentifierExpression ident = expr as IdentifierExpression; + if (ident != null) { + ILVariable v = ident.Annotation<ILVariable>(); + if (v != null) { + foreach (LambdaExpression lambda in activeLambdas) { + foreach (ParameterDeclaration p in lambda.Parameters) { + if (p.Annotation<ILVariable>() == v) + return new IdentifierExpression(p.Name).WithAnnotation(v); + } + } + } + } + return NotSupported(expr); + } + + Expression NotSupported(Expression expr) + { + Debug.WriteLine("Expression Tree Conversion Failed: '" + expr + "' is not supported"); + return null; + } + #endregion + + #region Convert Lambda + static readonly Expression emptyArrayPattern = new ArrayCreateExpression { + Type = new AnyNode(), + Arguments = { new PrimitiveExpression(0) } + }; + + Expression ConvertLambda(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + LambdaExpression lambda = new LambdaExpression(); + Expression body = invocation.Arguments.First(); + ArrayCreateExpression parameterArray = invocation.Arguments.Last() as ArrayCreateExpression; + if (parameterArray == null) + return NotSupported(invocation); + + var annotation = body.Annotation<ParameterDeclarationAnnotation>(); + if (annotation != null) { + lambda.Parameters.AddRange(annotation.Parameters); + } else { + // No parameter declaration annotation found. + if (!emptyArrayPattern.IsMatch(parameterArray)) + return null; + } + + activeLambdas.Push(lambda); + Expression convertedBody = Convert(body); + activeLambdas.Pop(); + if (convertedBody == null) + return null; + lambda.Body = convertedBody; + return lambda; + } + #endregion + + #region Convert Field + static readonly Expression getFieldFromHandlePattern = + new TypePattern(typeof(FieldInfo)).ToType().Invoke( + "GetFieldFromHandle", + new LdTokenPattern("field").ToExpression().Member("FieldHandle"), + new OptionalNode(new TypeOfExpression(new AnyNode("declaringType")).Member("TypeHandle")) + ); + + Expression ConvertField(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Expression fieldInfoExpr = invocation.Arguments.ElementAt(1); + Match m = getFieldFromHandlePattern.Match(fieldInfoExpr); + if (!m.Success) + return NotSupported(invocation); + + FieldReference fr = m.Get<AstNode>("field").Single().Annotation<FieldReference>(); + if (fr == null) + return null; + + Expression target = invocation.Arguments.ElementAt(0); + Expression convertedTarget; + if (target is NullReferenceExpression) { + if (m.Has("declaringType")) + convertedTarget = new TypeReferenceExpression(m.Get<AstType>("declaringType").Single().Clone()); + else + convertedTarget = new TypeReferenceExpression(AstBuilder.ConvertType(fr.DeclaringType)); + } else { + convertedTarget = Convert(target); + if (convertedTarget == null) + return null; + } + + return convertedTarget.Member(fr.Name).WithAnnotation(fr); + } + #endregion + + #region Convert Property + static readonly Expression getMethodFromHandlePattern = + new TypePattern(typeof(MethodBase)).ToType().Invoke( + "GetMethodFromHandle", + new LdTokenPattern("method").ToExpression().Member("MethodHandle"), + new OptionalNode(new TypeOfExpression(new AnyNode("declaringType")).Member("TypeHandle")) + ).CastTo(new TypePattern(typeof(MethodInfo))); + + Expression ConvertProperty(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Match m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(1)); + if (!m.Success) + return NotSupported(invocation); + + MethodReference mr = m.Get<AstNode>("method").Single().Annotation<MethodReference>(); + if (mr == null) + return null; + + Expression target = invocation.Arguments.ElementAt(0); + Expression convertedTarget; + if (target is NullReferenceExpression) { + if (m.Has("declaringType")) + convertedTarget = new TypeReferenceExpression(m.Get<AstType>("declaringType").Single().Clone()); + else + convertedTarget = new TypeReferenceExpression(AstBuilder.ConvertType(mr.DeclaringType)); + } else { + convertedTarget = Convert(target); + if (convertedTarget == null) + return null; + } + + return convertedTarget.Member(GetPropertyName(mr)).WithAnnotation(mr); + } + + string GetPropertyName(MethodReference accessor) + { + string name = accessor.Name; + if (name.StartsWith("get_", StringComparison.Ordinal) || name.StartsWith("set_", StringComparison.Ordinal)) + name = name.Substring(4); + return name; + } + #endregion + + #region Convert Call + Expression ConvertCall(InvocationExpression invocation) + { + if (invocation.Arguments.Count < 2) + return NotSupported(invocation); + + Expression target; + int firstArgumentPosition; + + Match m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(0)); + if (m.Success) { + target = null; + firstArgumentPosition = 1; + } else { + m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(1)); + if (!m.Success) + return NotSupported(invocation); + target = invocation.Arguments.ElementAt(0); + firstArgumentPosition = 2; + } + + MethodReference mr = m.Get<AstNode>("method").Single().Annotation<MethodReference>(); + if (mr == null) + return null; + + Expression convertedTarget; + if (target == null || target is NullReferenceExpression) { + // static method + if (m.Has("declaringType")) + convertedTarget = new TypeReferenceExpression(m.Get<AstType>("declaringType").Single().Clone()); + else + convertedTarget = new TypeReferenceExpression(AstBuilder.ConvertType(mr.DeclaringType)); + } else { + convertedTarget = Convert(target); + if (convertedTarget == null) + return null; + } + + MemberReferenceExpression mre = convertedTarget.Member(mr.Name); + GenericInstanceMethod gim = mr as GenericInstanceMethod; + if (gim != null) { + foreach (TypeReference tr in gim.GenericArguments) { + mre.TypeArguments.Add(AstBuilder.ConvertType(tr)); + } + } + IList<Expression> arguments = null; + if (invocation.Arguments.Count == firstArgumentPosition + 1) { + Expression argumentArray = invocation.Arguments.ElementAt(firstArgumentPosition); + arguments = ConvertExpressionsArray(argumentArray); + } + if (arguments == null) { + arguments = new List<Expression>(); + foreach (Expression argument in invocation.Arguments.Skip(firstArgumentPosition)) { + Expression convertedArgument = Convert(argument); + if (convertedArgument == null) + return null; + arguments.Add(convertedArgument); + } + } + MethodDefinition methodDef = mr.Resolve(); + if (methodDef != null && methodDef.IsGetter) { + PropertyDefinition indexer = AstMethodBodyBuilder.GetIndexer(methodDef); + if (indexer != null) + return new IndexerExpression(mre.Target.Detach(), arguments).WithAnnotation(indexer); + } + return new InvocationExpression(mre, arguments).WithAnnotation(mr); + } + + Expression ConvertInvoke(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Expression convertedTarget = Convert(invocation.Arguments.ElementAt(0)); + IList<Expression> convertedArguments = ConvertExpressionsArray(invocation.Arguments.ElementAt(1)); + if (convertedTarget != null && convertedArguments != null) + return new InvocationExpression(convertedTarget, convertedArguments); + else + return null; + } + #endregion + + #region Convert Binary Operator + static readonly Pattern trueOrFalse = new Choice { + new PrimitiveExpression(true), + new PrimitiveExpression(false) + }; + + Expression ConvertBinaryOperator(InvocationExpression invocation, BinaryOperatorType op, bool? isChecked = null) + { + if (invocation.Arguments.Count < 2) + return NotSupported(invocation); + + Expression left = Convert(invocation.Arguments.ElementAt(0)); + if (left == null) + return null; + Expression right = Convert(invocation.Arguments.ElementAt(1)); + if (right == null) + return null; + + BinaryOperatorExpression boe = new BinaryOperatorExpression(left, op, right); + if (isChecked != null) + boe.AddAnnotation(isChecked.Value ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); + + switch (invocation.Arguments.Count) { + case 2: + return boe; + case 3: + Match m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(2)); + if (m.Success) + return boe.WithAnnotation(m.Get<AstNode>("method").Single().Annotation<MethodReference>()); + else + return null; + case 4: + if (!trueOrFalse.IsMatch(invocation.Arguments.ElementAt(2))) + return null; + m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(3)); + if (m.Success) + return boe.WithAnnotation(m.Get<AstNode>("method").Single().Annotation<MethodReference>()); + else + return null; + default: + return NotSupported(invocation); + } + } + #endregion + + #region Convert Assignment Operator + Expression ConvertAssignmentOperator(InvocationExpression invocation, AssignmentOperatorType op, bool? isChecked = null) + { + return NotSupported(invocation); + } + #endregion + + #region Convert Unary Operator + Expression ConvertUnaryOperator(InvocationExpression invocation, UnaryOperatorType op, bool? isChecked = null) + { + if (invocation.Arguments.Count < 1) + return NotSupported(invocation); + + Expression expr = Convert(invocation.Arguments.ElementAt(0)); + if (expr == null) + return null; + + UnaryOperatorExpression uoe = new UnaryOperatorExpression(op, expr); + if (isChecked != null) + uoe.AddAnnotation(isChecked.Value ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); + + switch (invocation.Arguments.Count) { + case 1: + return uoe; + case 2: + Match m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(1)); + if (m.Success) + return uoe.WithAnnotation(m.Get<AstNode>("method").Single().Annotation<MethodReference>()); + else + return null; + default: + return NotSupported(invocation); + } + } + #endregion + + #region Convert Condition Operator + Expression ConvertCondition(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 3) + return NotSupported(invocation); + + Expression condition = Convert(invocation.Arguments.ElementAt(0)); + Expression trueExpr = Convert(invocation.Arguments.ElementAt(1)); + Expression falseExpr = Convert(invocation.Arguments.ElementAt(2)); + if (condition != null && trueExpr != null && falseExpr != null) + return new ConditionalExpression(condition, trueExpr, falseExpr); + else + return null; + } + #endregion + + #region Convert New Object + static readonly Expression newObjectCtorPattern = new TypePattern(typeof(MethodBase)).ToType().Invoke + ( + "GetMethodFromHandle", + new LdTokenPattern("ctor").ToExpression().Member("MethodHandle"), + new OptionalNode(new TypeOfExpression(new AnyNode("declaringType")).Member("TypeHandle")) + ).CastTo(new TypePattern(typeof(ConstructorInfo))); + + Expression ConvertNewObject(InvocationExpression invocation) + { + if (invocation.Arguments.Count < 1 || invocation.Arguments.Count > 3) + return NotSupported(invocation); + + Match m = newObjectCtorPattern.Match(invocation.Arguments.First()); + if (!m.Success) + return NotSupported(invocation); + + MethodReference ctor = m.Get<AstNode>("ctor").Single().Annotation<MethodReference>(); + if (ctor == null) + return null; + + AstType declaringTypeNode; + TypeReference declaringType; + if (m.Has("declaringType")) { + declaringTypeNode = m.Get<AstType>("declaringType").Single().Clone(); + declaringType = declaringTypeNode.Annotation<TypeReference>(); + } else { + declaringTypeNode = AstBuilder.ConvertType(ctor.DeclaringType); + declaringType = ctor.DeclaringType; + } + if (declaringTypeNode == null) + return null; + + ObjectCreateExpression oce = new ObjectCreateExpression(declaringTypeNode); + if (invocation.Arguments.Count >= 2) { + IList<Expression> arguments = ConvertExpressionsArray(invocation.Arguments.ElementAtOrDefault(1)); + if (arguments == null) + return null; + oce.Arguments.AddRange(arguments); + } + if (invocation.Arguments.Count >= 3 && declaringType.IsAnonymousType()) { + MethodDefinition resolvedCtor = ctor.Resolve(); + if (resolvedCtor == null || resolvedCtor.Parameters.Count != oce.Arguments.Count) + return null; + AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); + var arguments = oce.Arguments.ToArray(); + if (AstMethodBodyBuilder.CanInferAnonymousTypePropertyNamesFromArguments(arguments, resolvedCtor.Parameters)) { + oce.Arguments.MoveTo(atce.Initializers); + } else { + for (int i = 0; i < resolvedCtor.Parameters.Count; i++) { + atce.Initializers.Add( + new NamedExpression { + Name = resolvedCtor.Parameters[i].Name, + Expression = arguments[i].Detach() + }); + } + } + return atce; + } + + return oce; + } + #endregion + + #region Convert ListInit + static readonly Pattern elementInitArrayPattern = ArrayInitializationPattern( + typeof(System.Linq.Expressions.ElementInit), + new TypePattern(typeof(System.Linq.Expressions.Expression)).ToType().Invoke("ElementInit", new AnyNode("methodInfos"), new AnyNode("addArgumentsArrays")) + ); + + Expression ConvertListInit(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + ObjectCreateExpression oce = Convert(invocation.Arguments.ElementAt(0)) as ObjectCreateExpression; + if (oce == null) + return null; + Expression elementsArray = invocation.Arguments.ElementAt(1); + ArrayInitializerExpression initializer = ConvertElementInit(elementsArray); + if (initializer != null) { + oce.Initializer = initializer; + return oce; + } else { + return null; + } + } + + ArrayInitializerExpression ConvertElementInit(Expression elementsArray) + { + IList<Expression> elements = ConvertExpressionsArray(elementsArray); + if (elements != null) { + return new ArrayInitializerExpression(elements); + } + Match m = elementInitArrayPattern.Match(elementsArray); + if (!m.Success) + return null; + ArrayInitializerExpression result = new ArrayInitializerExpression(); + foreach (var elementInit in m.Get<Expression>("addArgumentsArrays")) { + IList<Expression> arguments = ConvertExpressionsArray(elementInit); + if (arguments == null) + return null; + result.Elements.Add(new ArrayInitializerExpression(arguments)); + } + return result; + } + #endregion + + #region Convert MemberInit + Expression ConvertMemberInit(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + ObjectCreateExpression oce = Convert(invocation.Arguments.ElementAt(0)) as ObjectCreateExpression; + if (oce == null) + return null; + Expression elementsArray = invocation.Arguments.ElementAt(1); + ArrayInitializerExpression bindings = ConvertMemberBindings(elementsArray); + if (bindings == null) + return null; + oce.Initializer = bindings; + return oce; + } + + static readonly Pattern memberBindingArrayPattern = ArrayInitializationPattern(typeof(System.Linq.Expressions.MemberBinding), new AnyNode("binding")); + static readonly INode expressionTypeReference = new TypeReferenceExpression(new TypePattern(typeof(System.Linq.Expressions.Expression))); + + ArrayInitializerExpression ConvertMemberBindings(Expression elementsArray) + { + Match m = memberBindingArrayPattern.Match(elementsArray); + if (!m.Success) + return null; + ArrayInitializerExpression result = new ArrayInitializerExpression(); + foreach (var binding in m.Get<Expression>("binding")) { + InvocationExpression bindingInvocation = binding as InvocationExpression; + if (bindingInvocation == null || bindingInvocation.Arguments.Count != 2) + return null; + MemberReferenceExpression bindingMRE = bindingInvocation.Target as MemberReferenceExpression; + if (bindingMRE == null || !expressionTypeReference.IsMatch(bindingMRE.Target)) + return null; + + Expression bindingTarget = bindingInvocation.Arguments.ElementAt(0); + Expression bindingValue = bindingInvocation.Arguments.ElementAt(1); + + string memberName; + Match m2 = getMethodFromHandlePattern.Match(bindingTarget); + if (m2.Success) { + MethodReference setter = m2.Get<AstNode>("method").Single().Annotation<MethodReference>(); + if (setter == null) + return null; + memberName = GetPropertyName(setter); + } else { + return null; + } + + Expression convertedValue; + switch (bindingMRE.MemberName) { + case "Bind": + convertedValue = Convert(bindingValue); + break; + case "MemberBind": + convertedValue = ConvertMemberBindings(bindingValue); + break; + case "ListBind": + convertedValue = ConvertElementInit(bindingValue); + break; + default: + return null; + } + if (convertedValue == null) + return null; + result.Elements.Add(new NamedExpression(memberName, convertedValue)); + } + return result; + } + #endregion + + #region Convert Cast + Expression ConvertCast(InvocationExpression invocation, bool isChecked) + { + if (invocation.Arguments.Count < 2) + return null; + Expression converted = Convert(invocation.Arguments.ElementAt(0)); + AstType type = ConvertTypeReference(invocation.Arguments.ElementAt(1)); + if (converted != null && type != null) { + CastExpression cast = converted.CastTo(type); + cast.AddAnnotation(isChecked ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); + switch (invocation.Arguments.Count) { + case 2: + return cast; + case 3: + Match m = getMethodFromHandlePattern.Match(invocation.Arguments.ElementAt(2)); + if (m.Success) + return cast.WithAnnotation(m.Get<AstNode>("method").Single().Annotation<MethodReference>()); + else + return null; + } + } + return null; + } + #endregion + + #region ConvertExpressionsArray + static Pattern ArrayInitializationPattern(Type arrayElementType, INode elementPattern) + { + return new Choice { + new ArrayCreateExpression { + Type = new TypePattern(arrayElementType), + Arguments = { new PrimitiveExpression(0) } + }, + new ArrayCreateExpression { + Type = new TypePattern(arrayElementType), + AdditionalArraySpecifiers = { new ArraySpecifier() }, + Initializer = new ArrayInitializerExpression { + Elements = { new Repeat(elementPattern) } + } + } + }; + } + + static readonly Pattern expressionArrayPattern = ArrayInitializationPattern(typeof(System.Linq.Expressions.Expression), new AnyNode("elements")); + + IList<Expression> ConvertExpressionsArray(Expression arrayExpression) + { + Match m = expressionArrayPattern.Match(arrayExpression); + if (m.Success) { + List<Expression> result = new List<Expression>(); + foreach (Expression expr in m.Get<Expression>("elements")) { + Expression converted = Convert(expr); + if (converted == null) + return null; + result.Add(converted); + } + return result; + } + return null; + } + #endregion + + #region Convert TypeAs/TypeIs + static readonly TypeOfPattern typeOfPattern = new TypeOfPattern("type"); + + AstType ConvertTypeReference(Expression typeOfExpression) + { + Match m = typeOfPattern.Match(typeOfExpression); + if (m.Success) + return m.Get<AstType>("type").Single().Clone(); + else + return null; + } + + Expression ConvertTypeAs(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return null; + Expression converted = Convert(invocation.Arguments.ElementAt(0)); + AstType type = ConvertTypeReference(invocation.Arguments.ElementAt(1)); + if (converted != null && type != null) + return new AsExpression(converted, type); + return null; + } + + Expression ConvertTypeIs(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return null; + Expression converted = Convert(invocation.Arguments.ElementAt(0)); + AstType type = ConvertTypeReference(invocation.Arguments.ElementAt(1)); + if (converted != null && type != null) + return new IsExpression { Expression = converted, Type = type }; + return null; + } + #endregion + + #region Convert Array + Expression ConvertArrayIndex(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Expression targetConverted = Convert(invocation.Arguments.First()); + if (targetConverted == null) + return null; + + Expression index = invocation.Arguments.ElementAt(1); + Expression indexConverted = Convert(index); + if (indexConverted != null) { + return new IndexerExpression(targetConverted, indexConverted); + } + IList<Expression> indexesConverted = ConvertExpressionsArray(index); + if (indexesConverted != null) { + return new IndexerExpression(targetConverted, indexesConverted); + } + return null; + } + + Expression ConvertArrayLength(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 1) + return NotSupported(invocation); + + Expression targetConverted = Convert(invocation.Arguments.Single()); + if (targetConverted != null) + return targetConverted.Member("Length"); + else + return null; + } + + Expression ConvertNewArrayInit(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + AstType elementType = ConvertTypeReference(invocation.Arguments.ElementAt(0)); + IList<Expression> elements = ConvertExpressionsArray(invocation.Arguments.ElementAt(1)); + if (elementType != null && elements != null) { + if (ContainsAnonymousType(elementType)) { + elementType = null; + } + return new ArrayCreateExpression { + Type = elementType, + AdditionalArraySpecifiers = { new ArraySpecifier() }, + Initializer = new ArrayInitializerExpression(elements) + }; + } + return null; + } + + Expression ConvertNewArrayBounds(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + AstType elementType = ConvertTypeReference(invocation.Arguments.ElementAt(0)); + IList<Expression> arguments = ConvertExpressionsArray(invocation.Arguments.ElementAt(1)); + if (elementType != null && arguments != null) { + if (ContainsAnonymousType(elementType)) { + elementType = null; + } + ArrayCreateExpression ace = new ArrayCreateExpression(); + ace.Type = elementType; + ace.Arguments.AddRange(arguments); + return ace; + } + return null; + } + + bool ContainsAnonymousType(AstType type) + { + foreach (AstType t in type.DescendantsAndSelf.OfType<AstType>()) { + TypeReference tr = t.Annotation<TypeReference>(); + if (tr != null && tr.IsAnonymousType()) + return true; + } + return false; + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/FlattenSwitchBlocks.cs b/ICSharpCode.Decompiler/Ast/Transforms/FlattenSwitchBlocks.cs new file mode 100644 index 00000000..9595e81b --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/FlattenSwitchBlocks.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + internal class FlattenSwitchBlocks : IAstTransform + { + public void Run(AstNode compilationUnit) + { + foreach (var switchSection in compilationUnit.Descendants.OfType<SwitchSection>()) + { + if (switchSection.Statements.Count != 1) + continue; + + var blockStatement = switchSection.Statements.First() as BlockStatement; + if (blockStatement == null || blockStatement.Statements.Any(st => st is VariableDeclarationStatement)) + continue; + + blockStatement.Remove(); + blockStatement.Statements.MoveTo(switchSection.Statements); + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs new file mode 100644 index 00000000..9f05285e --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceExtensionMethods.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Converts extension method calls into infix syntax. + /// </summary> + public class IntroduceExtensionMethods : IAstTransform + { + readonly DecompilerContext context; + + public IntroduceExtensionMethods(DecompilerContext context) + { + this.context = context; + } + + public void Run(AstNode compilationUnit) + { + foreach (InvocationExpression invocation in compilationUnit.Descendants.OfType<InvocationExpression>()) { + MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + MethodReference methodReference = invocation.Annotation<MethodReference>(); + if (mre != null && mre.Target is TypeReferenceExpression && methodReference != null && invocation.Arguments.Any()) { + MethodDefinition d = methodReference.Resolve(); + if (d != null) { + foreach (var ca in d.CustomAttributes) { + if (ca.AttributeType.Name == "ExtensionAttribute" && ca.AttributeType.Namespace == "System.Runtime.CompilerServices") { + var firstArgument = invocation.Arguments.First(); + if (firstArgument is NullReferenceExpression) + firstArgument = firstArgument.ReplaceWith(expr => expr.CastTo(AstBuilder.ConvertType(d.Parameters.First().ParameterType))); + else + mre.Target = firstArgument.Detach(); + if (invocation.Arguments.Any()) { + // HACK: removing type arguments should be done indepently from whether a method is an extension method, + // just by testing whether the arguments can be inferred + mre.TypeArguments.Clear(); + } + break; + } + } + } + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs new file mode 100644 index 00000000..0da56fe9 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceQueryExpressions.cs @@ -0,0 +1,295 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Decompiles query expressions. + /// Based on C# 4.0 spec, §7.16.2 Query expression translation + /// </summary> + public class IntroduceQueryExpressions : IAstTransform + { + readonly DecompilerContext context; + + public IntroduceQueryExpressions(DecompilerContext context) + { + this.context = context; + } + + public void Run(AstNode compilationUnit) + { + if (!context.Settings.QueryExpressions) + return; + DecompileQueries(compilationUnit); + // After all queries were decompiled, detect degenerate queries (queries not property terminated with 'select' or 'group') + // and fix them, either by adding a degenerate select, or by combining them with another query. + foreach (QueryExpression query in compilationUnit.Descendants.OfType<QueryExpression>()) { + QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); + if (IsDegenerateQuery(query)) { + // introduce select for degenerate query + query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(fromClause.Identifier) }); + } + // See if the data source of this query is a degenerate query, + // and combine the queries if possible. + QueryExpression innerQuery = fromClause.Expression as QueryExpression; + while (IsDegenerateQuery(innerQuery)) { + QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); + if (fromClause.Identifier != innerFromClause.Identifier) + break; + // Replace the fromClause with all clauses from the inner query + fromClause.Remove(); + QueryClause insertionPos = null; + foreach (var clause in innerQuery.Clauses) { + query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); + } + fromClause = innerFromClause; + innerQuery = fromClause.Expression as QueryExpression; + } + } + } + + bool IsDegenerateQuery(QueryExpression query) + { + if (query == null) + return false; + var lastClause = query.Clauses.LastOrDefault(); + return !(lastClause is QuerySelectClause || lastClause is QueryGroupClause); + } + + void DecompileQueries(AstNode node) + { + QueryExpression query = DecompileQuery(node as InvocationExpression); + if (query != null) + node.ReplaceWith(query); + for (AstNode child = (query ?? node).FirstChild; child != null; child = child.NextSibling) { + DecompileQueries(child); + } + } + + QueryExpression DecompileQuery(InvocationExpression invocation) + { + if (invocation == null) + return null; + MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + if (mre == null) + return null; + switch (mre.MemberName) { + case "Select": + { + if (invocation.Arguments.Count != 1) + return null; + string parameterName; + Expression body; + if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); + query.Clauses.Add(new QuerySelectClause { Expression = body.Detach() }); + return query; + } + return null; + } + case "GroupBy": + { + if (invocation.Arguments.Count == 2) { + string parameterName1, parameterName2; + Expression keySelector, elementSelector; + if (MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName1, out keySelector) + && MatchSimpleLambda(invocation.Arguments.ElementAt(1), out parameterName2, out elementSelector) + && parameterName1 == parameterName2) + { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = parameterName1, Expression = mre.Target.Detach() }); + query.Clauses.Add(new QueryGroupClause { Projection = elementSelector.Detach(), Key = keySelector.Detach() }); + return query; + } + } else if (invocation.Arguments.Count == 1) { + string parameterName; + Expression keySelector; + if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out keySelector)) { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); + query.Clauses.Add(new QueryGroupClause { Projection = new IdentifierExpression(parameterName), Key = keySelector.Detach() }); + return query; + } + } + return null; + } + case "SelectMany": + { + if (invocation.Arguments.Count != 2) + return null; + string parameterName; + Expression collectionSelector; + if (!MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName, out collectionSelector)) + return null; + LambdaExpression lambda = invocation.Arguments.ElementAt(1) as LambdaExpression; + if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { + ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); + ParameterDeclaration p2 = lambda.Parameters.ElementAt(1); + if (p1.Name == parameterName) { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = p1.Name, Expression = mre.Target.Detach() }); + query.Clauses.Add(new QueryFromClause { Identifier = p2.Name, Expression = collectionSelector.Detach() }); + query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() }); + return query; + } + } + return null; + } + case "Where": + { + if (invocation.Arguments.Count != 1) + return null; + string parameterName; + Expression body; + if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); + query.Clauses.Add(new QueryWhereClause { Condition = body.Detach() }); + return query; + } + return null; + } + case "OrderBy": + case "OrderByDescending": + case "ThenBy": + case "ThenByDescending": + { + if (invocation.Arguments.Count != 1) + return null; + string parameterName; + Expression orderExpression; + if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out orderExpression)) { + if (ValidateThenByChain(invocation, parameterName)) { + QueryOrderClause orderClause = new QueryOrderClause(); + InvocationExpression tmp = invocation; + while (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") { + // insert new ordering at beginning + orderClause.Orderings.InsertAfter( + null, new QueryOrdering { + Expression = orderExpression.Detach(), + Direction = (mre.MemberName == "ThenBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending) + }); + + tmp = (InvocationExpression)mre.Target; + mre = (MemberReferenceExpression)tmp.Target; + MatchSimpleLambda(tmp.Arguments.Single(), out parameterName, out orderExpression); + } + // insert new ordering at beginning + orderClause.Orderings.InsertAfter( + null, new QueryOrdering { + Expression = orderExpression.Detach(), + Direction = (mre.MemberName == "OrderBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending) + }); + + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); + query.Clauses.Add(orderClause); + return query; + } + } + return null; + } + case "Join": + case "GroupJoin": + { + if (invocation.Arguments.Count != 4) + return null; + Expression source1 = mre.Target; + Expression source2 = invocation.Arguments.ElementAt(0); + string elementName1, elementName2; + Expression key1, key2; + if (!MatchSimpleLambda(invocation.Arguments.ElementAt(1), out elementName1, out key1)) + return null; + if (!MatchSimpleLambda(invocation.Arguments.ElementAt(2), out elementName2, out key2)) + return null; + LambdaExpression lambda = invocation.Arguments.ElementAt(3) as LambdaExpression; + if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { + ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); + ParameterDeclaration p2 = lambda.Parameters.ElementAt(1); + if (p1.Name == elementName1 && (p2.Name == elementName2 || mre.MemberName == "GroupJoin")) { + QueryExpression query = new QueryExpression(); + query.Clauses.Add(new QueryFromClause { Identifier = elementName1, Expression = source1.Detach() }); + QueryJoinClause joinClause = new QueryJoinClause(); + joinClause.JoinIdentifier = elementName2; // join elementName2 + joinClause.InExpression = source2.Detach(); // in source2 + joinClause.OnExpression = key1.Detach(); // on key1 + joinClause.EqualsExpression = key2.Detach(); // equals key2 + if (mre.MemberName == "GroupJoin") { + joinClause.IntoIdentifier = p2.Name; // into p2.Name + } + query.Clauses.Add(joinClause); + query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() }); + return query; + } + } + return null; + } + default: + return null; + } + } + + /// <summary> + /// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation. + /// </summary> + bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName) + { + if (invocation == null || invocation.Arguments.Count != 1) + return false; + MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; + if (mre == null) + return false; + string parameterName; + Expression body; + if (!MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) + return false; + if (parameterName != expectedParameterName) + return false; + + if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending") + return true; + else if (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") + return ValidateThenByChain(mre.Target as InvocationExpression, expectedParameterName); + else + return false; + } + + /// <summary>Matches simple lambdas of the form "a => b"</summary> + bool MatchSimpleLambda(Expression expr, out string parameterName, out Expression body) + { + LambdaExpression lambda = expr as LambdaExpression; + if (lambda != null && lambda.Parameters.Count == 1 && lambda.Body is Expression) { + ParameterDeclaration p = lambda.Parameters.Single(); + if (p.ParameterModifier == ParameterModifier.None) { + parameterName = p.Name; + body = (Expression)lambda.Body; + return true; + } + } + parameterName = null; + body = null; + return false; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs new file mode 100644 index 00000000..43548e38 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + public class IntroduceUnsafeModifier : DepthFirstAstVisitor<object, bool>, IAstTransform + { + public static readonly object PointerArithmeticAnnotation = new PointerArithmetic(); + + sealed class PointerArithmetic {} + + public void Run(AstNode compilationUnit) + { + compilationUnit.AcceptVisitor(this, null); + } + + protected override bool VisitChildren(AstNode node, object data) + { + bool result = false; + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + result |= child.AcceptVisitor(this, data); + } + if (result && node is EntityDeclaration && !(node is Accessor)) { + ((EntityDeclaration)node).Modifiers |= Modifiers.Unsafe; + return false; + } + return result; + } + + public override bool VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, object data) + { + base.VisitPointerReferenceExpression(pointerReferenceExpression, data); + return true; + } + + public override bool VisitComposedType(ComposedType composedType, object data) + { + if (composedType.PointerRank > 0) + return true; + else + return base.VisitComposedType(composedType, data); + } + + public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) + { + bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression, data); + if (unaryOperatorExpression.Operator == UnaryOperatorType.Dereference) { + BinaryOperatorExpression bop = unaryOperatorExpression.Expression as BinaryOperatorExpression; + if (bop != null && bop.Operator == BinaryOperatorType.Add && bop.Annotation<PointerArithmetic>() != null) { + // transform "*(ptr + int)" to "ptr[int]" + IndexerExpression indexer = new IndexerExpression(); + indexer.Target = bop.Left.Detach(); + indexer.Arguments.Add(bop.Right.Detach()); + indexer.CopyAnnotationsFrom(unaryOperatorExpression); + indexer.CopyAnnotationsFrom(bop); + unaryOperatorExpression.ReplaceWith(indexer); + } + return true; + } else if (unaryOperatorExpression.Operator == UnaryOperatorType.AddressOf) { + return true; + } else { + return result; + } + } + + public override bool VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data) + { + bool result = base.VisitMemberReferenceExpression(memberReferenceExpression, data); + UnaryOperatorExpression uoe = memberReferenceExpression.Target as UnaryOperatorExpression; + if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) { + PointerReferenceExpression pre = new PointerReferenceExpression(); + pre.Target = uoe.Expression.Detach(); + pre.MemberName = memberReferenceExpression.MemberName; + memberReferenceExpression.TypeArguments.MoveTo(pre.TypeArguments); + pre.CopyAnnotationsFrom(uoe); + pre.CopyAnnotationsFrom(memberReferenceExpression); + memberReferenceExpression.ReplaceWith(pre); + } + return result; + } + + public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data) + { + base.VisitStackAllocExpression(stackAllocExpression, data); + return true; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUsingDeclarations.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUsingDeclarations.cs new file mode 100644 index 00000000..6e9cc4f5 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUsingDeclarations.cs @@ -0,0 +1,359 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Introduces using declarations. + /// </summary> + public class IntroduceUsingDeclarations : IAstTransform + { + DecompilerContext context; + + public IntroduceUsingDeclarations(DecompilerContext context) + { + this.context = context; + } + + public void Run(AstNode compilationUnit) + { + // First determine all the namespaces that need to be imported: + compilationUnit.AcceptVisitor(new FindRequiredImports(this), null); + + importedNamespaces.Add("System"); // always import System, even when not necessary + + if (context.Settings.UsingDeclarations) { + // Now add using declarations for those namespaces: + foreach (string ns in importedNamespaces.OrderByDescending(n => n)) { + // we go backwards (OrderByDescending) through the list of namespaces because we insert them backwards + // (always inserting at the start of the list) + string[] parts = ns.Split('.'); + AstType nsType = new SimpleType(parts[0]); + for (int i = 1; i < parts.Length; i++) { + nsType = new MemberType { Target = nsType, MemberName = parts[i] }; + } + compilationUnit.InsertChildAfter(null, new UsingDeclaration { Import = nsType }, SyntaxTree.MemberRole); + } + } + + if (!context.Settings.FullyQualifyAmbiguousTypeNames) + return; + + FindAmbiguousTypeNames(context.CurrentModule, internalsVisible: true); + foreach (AssemblyNameReference r in context.CurrentModule.AssemblyReferences) { + AssemblyDefinition d = context.CurrentModule.AssemblyResolver.Resolve(r); + if (d != null) + FindAmbiguousTypeNames(d.MainModule, internalsVisible: false); + } + + // verify that the SimpleTypes refer to the correct type (no ambiguities) + compilationUnit.AcceptVisitor(new FullyQualifyAmbiguousTypeNamesVisitor(this), null); + } + + readonly HashSet<string> declaredNamespaces = new HashSet<string>() { string.Empty }; + readonly HashSet<string> importedNamespaces = new HashSet<string>(); + + // Note that we store type names with `n suffix, so we automatically disambiguate based on number of type parameters. + readonly HashSet<string> availableTypeNames = new HashSet<string>(); + readonly HashSet<string> ambiguousTypeNames = new HashSet<string>(); + + sealed class FindRequiredImports : DepthFirstAstVisitor<object, object> + { + readonly IntroduceUsingDeclarations transform; + string currentNamespace; + + public FindRequiredImports(IntroduceUsingDeclarations transform) + { + this.transform = transform; + currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty; + } + + bool IsParentOfCurrentNamespace(string ns) + { + if (ns.Length == 0) + return true; + if (currentNamespace.StartsWith(ns, StringComparison.Ordinal)) { + if (currentNamespace.Length == ns.Length) + return true; + if (currentNamespace[ns.Length] == '.') + return true; + } + return false; + } + + public override object VisitSimpleType(SimpleType simpleType, object data) + { + TypeReference tr = simpleType.Annotation<TypeReference>(); + if (tr != null && !IsParentOfCurrentNamespace(tr.Namespace)) { + transform.importedNamespaces.Add(tr.Namespace); + } + return base.VisitSimpleType(simpleType, data); // also visit type arguments + } + + public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) + { + string oldNamespace = currentNamespace; + foreach (string ident in namespaceDeclaration.Identifiers) { + currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident); + transform.declaredNamespaces.Add(currentNamespace); + } + base.VisitNamespaceDeclaration(namespaceDeclaration, data); + currentNamespace = oldNamespace; + return null; + } + } + + void FindAmbiguousTypeNames(ModuleDefinition module, bool internalsVisible) + { + foreach (TypeDefinition type in module.Types) { + if (internalsVisible || type.IsPublic) { + if (importedNamespaces.Contains(type.Namespace) || declaredNamespaces.Contains(type.Namespace)) { + if (!availableTypeNames.Add(type.Name)) + ambiguousTypeNames.Add(type.Name); + } + } + } + } + + sealed class FullyQualifyAmbiguousTypeNamesVisitor : DepthFirstAstVisitor<object, object> + { + readonly IntroduceUsingDeclarations transform; + string currentNamespace; + HashSet<string> currentMemberTypes; + Dictionary<string, MemberReference> currentMembers; + bool isWithinTypeReferenceExpression; + + public FullyQualifyAmbiguousTypeNamesVisitor(IntroduceUsingDeclarations transform) + { + this.transform = transform; + currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty; + } + + public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) + { + string oldNamespace = currentNamespace; + foreach (string ident in namespaceDeclaration.Identifiers) { + currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident); + } + base.VisitNamespaceDeclaration(namespaceDeclaration, data); + currentNamespace = oldNamespace; + return null; + } + + public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) + { + HashSet<string> oldMemberTypes = currentMemberTypes; + currentMemberTypes = currentMemberTypes != null ? new HashSet<string>(currentMemberTypes) : new HashSet<string>(); + + Dictionary<string, MemberReference> oldMembers = currentMembers; + currentMembers = new Dictionary<string, MemberReference>(); + + TypeDefinition typeDef = typeDeclaration.Annotation<TypeDefinition>(); + bool privateMembersVisible = true; + ModuleDefinition internalMembersVisibleInModule = typeDef.Module; + while (typeDef != null) { + foreach (GenericParameter gp in typeDef.GenericParameters) { + currentMemberTypes.Add(gp.Name); + } + foreach (TypeDefinition t in typeDef.NestedTypes) { + if (privateMembersVisible || IsVisible(t, internalMembersVisibleInModule)) + currentMemberTypes.Add(t.Name.Substring(t.Name.LastIndexOf('+') + 1)); + } + + foreach (MethodDefinition method in typeDef.Methods) { + if (privateMembersVisible || IsVisible(method, internalMembersVisibleInModule)) + AddCurrentMember(method); + } + foreach (PropertyDefinition property in typeDef.Properties) { + if (privateMembersVisible || IsVisible(property.GetMethod, internalMembersVisibleInModule) || IsVisible(property.SetMethod, internalMembersVisibleInModule)) + AddCurrentMember(property); + } + foreach (EventDefinition ev in typeDef.Events) { + if (privateMembersVisible || IsVisible(ev.AddMethod, internalMembersVisibleInModule) || IsVisible(ev.RemoveMethod, internalMembersVisibleInModule)) + AddCurrentMember(ev); + } + foreach (FieldDefinition f in typeDef.Fields) { + if (privateMembersVisible || IsVisible(f, internalMembersVisibleInModule)) + AddCurrentMember(f); + } + // repeat with base class: + if (typeDef.BaseType != null) + typeDef = typeDef.BaseType.Resolve(); + else + typeDef = null; + privateMembersVisible = false; + } + + // Now add current members from outer classes: + if (oldMembers != null) { + foreach (var pair in oldMembers) { + // add members from outer classes only if the inner class doesn't define the member + if (!currentMembers.ContainsKey(pair.Key)) + currentMembers.Add(pair.Key, pair.Value); + } + } + + base.VisitTypeDeclaration(typeDeclaration, data); + currentMembers = oldMembers; + return null; + } + + void AddCurrentMember(MemberReference m) + { + MemberReference existingMember; + if (currentMembers.TryGetValue(m.Name, out existingMember)) { + // We keep the existing member assignment if it was from another class (=from a derived class), + // because members in derived classes have precedence over members in base classes. + if (existingMember != null && existingMember.DeclaringType == m.DeclaringType) { + // Use null as value to signalize multiple members with the same name + currentMembers[m.Name] = null; + } + } else { + currentMembers.Add(m.Name, m); + } + } + + bool IsVisible(MethodDefinition m, ModuleDefinition internalMembersVisibleInModule) + { + if (m == null) + return false; + switch (m.Attributes & MethodAttributes.MemberAccessMask) { + case MethodAttributes.FamANDAssem: + case MethodAttributes.Assembly: + return m.Module == internalMembersVisibleInModule; + case MethodAttributes.Family: + case MethodAttributes.FamORAssem: + case MethodAttributes.Public: + return true; + default: + return false; + } + } + + bool IsVisible(FieldDefinition f, ModuleDefinition internalMembersVisibleInModule) + { + if (f == null) + return false; + switch (f.Attributes & FieldAttributes.FieldAccessMask) { + case FieldAttributes.FamANDAssem: + case FieldAttributes.Assembly: + return f.Module == internalMembersVisibleInModule; + case FieldAttributes.Family: + case FieldAttributes.FamORAssem: + case FieldAttributes.Public: + return true; + default: + return false; + } + } + + bool IsVisible(TypeDefinition t, ModuleDefinition internalMembersVisibleInModule) + { + if (t == null) + return false; + switch (t.Attributes & TypeAttributes.VisibilityMask) { + case TypeAttributes.NotPublic: + case TypeAttributes.NestedAssembly: + case TypeAttributes.NestedFamANDAssem: + return t.Module == internalMembersVisibleInModule; + case TypeAttributes.NestedFamily: + case TypeAttributes.NestedFamORAssem: + case TypeAttributes.NestedPublic: + case TypeAttributes.Public: + return true; + default: + return false; + } + } + + public override object VisitSimpleType(SimpleType simpleType, object data) + { + // Handle type arguments first, so that the fixed-up type arguments get moved over to the MemberType, + // if we're also creating one here. + base.VisitSimpleType(simpleType, data); + TypeReference tr = simpleType.Annotation<TypeReference>(); + // Fully qualify any ambiguous type names. + if (tr != null && IsAmbiguous(tr.Namespace, tr.Name)) { + AstType ns; + if (string.IsNullOrEmpty(tr.Namespace)) { + ns = new SimpleType("global"); + } else { + string[] parts = tr.Namespace.Split('.'); + if (IsAmbiguous(string.Empty, parts[0])) { + // conflict between namespace and type name/member name + ns = new MemberType { Target = new SimpleType("global"), IsDoubleColon = true, MemberName = parts[0] }; + } else { + ns = new SimpleType(parts[0]); + } + for (int i = 1; i < parts.Length; i++) { + ns = new MemberType { Target = ns, MemberName = parts[i] }; + } + } + MemberType mt = new MemberType(); + mt.Target = ns; + mt.IsDoubleColon = string.IsNullOrEmpty(tr.Namespace); + mt.MemberName = simpleType.Identifier; + mt.CopyAnnotationsFrom(simpleType); + simpleType.TypeArguments.MoveTo(mt.TypeArguments); + simpleType.ReplaceWith(mt); + } + return null; + } + + public override object VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, object data) + { + isWithinTypeReferenceExpression = true; + base.VisitTypeReferenceExpression(typeReferenceExpression, data); + isWithinTypeReferenceExpression = false; + return null; + } + + bool IsAmbiguous(string ns, string name) + { + // If the type name conflicts with an inner class/type parameter, we need to fully-qualify it: + if (currentMemberTypes != null && currentMemberTypes.Contains(name)) + return true; + // If the type name conflicts with a field/property etc. on the current class, we need to fully-qualify it, + // if we're inside an expression. + if (isWithinTypeReferenceExpression && currentMembers != null) { + MemberReference mr; + if (currentMembers.TryGetValue(name, out mr)) { + // However, in the special case where the member is a field or property with the same type + // as is requested, then we can use the short name (if it's not otherwise ambiguous) + PropertyDefinition prop = mr as PropertyDefinition; + FieldDefinition field = mr as FieldDefinition; + if (!(prop != null && prop.PropertyType.Namespace == ns && prop.PropertyType.Name == name) + && !(field != null && field.FieldType.Namespace == ns && field.FieldType.Name == name)) + return true; + } + } + // If the type is defined in the current namespace, + // then we can use the short name even if we imported type with same name from another namespace. + if (ns == currentNamespace && !string.IsNullOrEmpty(ns)) + return false; + return transform.ambiguousTypeNames.Contains(name); + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs new file mode 100644 index 00000000..d3ae46c1 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -0,0 +1,1123 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.Analysis; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Finds the expanded form of using statements using pattern matching and replaces it with a UsingStatement. + /// </summary> + public sealed class PatternStatementTransform : ContextTrackingVisitor<AstNode>, IAstTransform + { + public PatternStatementTransform(DecompilerContext context) : base(context) + { + } + + #region Visitor Overrides + protected override AstNode VisitChildren(AstNode node, object data) + { + // Go through the children, and keep visiting a node as long as it changes. + // Because some transforms delete/replace nodes before and after the node being transformed, we rely + // on the transform's return value to know where we need to keep iterating. + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + AstNode oldChild; + do { + oldChild = child; + child = child.AcceptVisitor(this, data); + Debug.Assert(child != null && child.Parent == node); + } while (child != oldChild); + } + return node; + } + + public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement, object data) + { + AstNode result; + if (context.Settings.UsingStatement) { + result = TransformUsings(expressionStatement); + if (result != null) + return result; + result = TransformNonGenericForEach(expressionStatement); + if (result != null) + return result; + } + result = TransformFor(expressionStatement); + if (result != null) + return result; + if (context.Settings.LockStatement) { + result = TransformLock(expressionStatement); + if (result != null) + return result; + } + return base.VisitExpressionStatement(expressionStatement, data); + } + + public override AstNode VisitUsingStatement(UsingStatement usingStatement, object data) + { + if (context.Settings.ForEachStatement) { + AstNode result = TransformForeach(usingStatement); + if (result != null) + return result; + } + return base.VisitUsingStatement(usingStatement, data); + } + + public override AstNode VisitWhileStatement(WhileStatement whileStatement, object data) + { + return TransformDoWhile(whileStatement) ?? base.VisitWhileStatement(whileStatement, data); + } + + public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement, object data) + { + if (context.Settings.SwitchStatementOnString) { + AstNode result = TransformSwitchOnString(ifElseStatement); + if (result != null) + return result; + } + AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); + if (simplifiedIfElse != null) + return simplifiedIfElse; + return base.VisitIfElseStatement(ifElseStatement, data); + } + + public override AstNode VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data) + { + if (context.Settings.AutomaticProperties) { + AstNode result = TransformAutomaticProperties(propertyDeclaration); + if (result != null) + return result; + } + return base.VisitPropertyDeclaration(propertyDeclaration, data); + } + + public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration, object data) + { + // first apply transforms to the accessor bodies + base.VisitCustomEventDeclaration(eventDeclaration, data); + if (context.Settings.AutomaticEvents) { + AstNode result = TransformAutomaticEvents(eventDeclaration); + if (result != null) + return result; + } + return eventDeclaration; + } + + public override AstNode VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) + { + return TransformDestructor(methodDeclaration) ?? base.VisitMethodDeclaration(methodDeclaration, data); + } + + public override AstNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, object data) + { + return TransformTryCatchFinally(tryCatchStatement) ?? base.VisitTryCatchStatement(tryCatchStatement, data); + } + #endregion + + /// <summary> + /// $variable = $initializer; + /// </summary> + static readonly AstNode variableAssignPattern = new ExpressionStatement( + new AssignmentExpression( + new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), + new AnyNode("initializer") + )); + + #region using + static Expression InvokeDispose(Expression identifier) + { + return new Choice { + identifier.Invoke("Dispose"), + identifier.Clone().CastTo(new TypePattern(typeof(IDisposable))).Invoke("Dispose") + }; + } + + static readonly AstNode usingTryCatchPattern = new TryCatchStatement { + TryBlock = new AnyNode(), + FinallyBlock = new BlockStatement { + new Choice { + { "valueType", + new ExpressionStatement(InvokeDispose(new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)))) + }, + { "referenceType", + new IfElseStatement { + Condition = new BinaryOperatorExpression( + new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)), + BinaryOperatorType.InEquality, + new NullReferenceExpression() + ), + TrueStatement = new BlockStatement { + new ExpressionStatement(InvokeDispose(new Backreference("ident"))) + } + } + } + }.ToStatement() + } + }; + + public UsingStatement TransformUsings(ExpressionStatement node) + { + Match m1 = variableAssignPattern.Match(node); + if (!m1.Success) return null; + TryCatchStatement tryCatch = node.NextSibling as TryCatchStatement; + Match m2 = usingTryCatchPattern.Match(tryCatch); + if (!m2.Success) return null; + string variableName = m1.Get<IdentifierExpression>("variable").Single().Identifier; + if (variableName != m2.Get<IdentifierExpression>("ident").Single().Identifier) + return null; + if (m2.Has("valueType")) { + // if there's no if(x!=null), then it must be a value type + ILVariable v = m1.Get<AstNode>("variable").Single().Annotation<ILVariable>(); + if (v == null || v.Type == null || !v.Type.IsValueType) + return null; + } + + // There are two variants of the using statement: + // "using (var a = init)" and "using (expr)". + // The former declares a read-only variable 'a', and the latter declares an unnamed read-only variable + // to store the original value of 'expr'. + // This means that in order to introduce a using statement, in both cases we need to detect a read-only + // variable that is used only within that block. + + if (HasAssignment(tryCatch, variableName)) + return null; + + VariableDeclarationStatement varDecl = FindVariableDeclaration(node, variableName); + if (varDecl == null || !(varDecl.Parent is BlockStatement)) + return null; + + // Validate that the variable is not used after the using statement: + if (!IsVariableValueUnused(varDecl, tryCatch)) + return null; + + node.Remove(); + + UsingStatement usingStatement = new UsingStatement(); + usingStatement.EmbeddedStatement = tryCatch.TryBlock.Detach(); + tryCatch.ReplaceWith(usingStatement); + + // If possible, we'll eliminate the variable completely: + if (usingStatement.EmbeddedStatement.Descendants.OfType<IdentifierExpression>().Any(ident => ident.Identifier == variableName)) { + // variable is used, so we'll create a variable declaration + usingStatement.ResourceAcquisition = new VariableDeclarationStatement { + Type = (AstType)varDecl.Type.Clone(), + Variables = { + new VariableInitializer { + Name = variableName, + Initializer = m1.Get<Expression>("initializer").Single().Detach() + }.CopyAnnotationsFrom(node.Expression) + .WithAnnotation(m1.Get<AstNode>("variable").Single().Annotation<ILVariable>()) + } + }.CopyAnnotationsFrom(node); + } else { + // the variable is never used; eliminate it: + usingStatement.ResourceAcquisition = m1.Get<Expression>("initializer").Single().Detach(); + } + return usingStatement; + } + + internal static VariableDeclarationStatement FindVariableDeclaration(AstNode node, string identifier) + { + while (node != null) { + while (node.PrevSibling != null) { + node = node.PrevSibling; + VariableDeclarationStatement varDecl = node as VariableDeclarationStatement; + if (varDecl != null && varDecl.Variables.Count == 1 && varDecl.Variables.Single().Name == identifier) { + return varDecl; + } + } + node = node.Parent; + } + return null; + } + + /// <summary> + /// Gets whether the old variable value (assigned inside 'targetStatement' or earlier) + /// is read anywhere in the remaining scope of the variable declaration. + /// </summary> + bool IsVariableValueUnused(VariableDeclarationStatement varDecl, Statement targetStatement) + { + Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent)); + BlockStatement block = (BlockStatement)varDecl.Parent; + DefiniteAssignmentAnalysis daa = new DefiniteAssignmentAnalysis(block, context.CancellationToken); + daa.SetAnalyzedRange(targetStatement, block, startInclusive: false); + daa.Analyze(varDecl.Variables.Single().Name); + return daa.UnassignedVariableUses.Count == 0; + } + + // I used this in the first implementation of the using-statement transform, but now no longer + // because there were problems when multiple using statements were using the same variable + // - no single using statement could be transformed without making the C# code invalid, + // but transforming both would work. + // We now use 'IsVariableValueUnused' which will perform the transform + // even if it results in two variables with the same name and overlapping scopes. + // (this issue could be fixed later by renaming one of the variables) + + // I'm not sure whether the other consumers of 'CanMoveVariableDeclarationIntoStatement' should be changed the same way. + bool CanMoveVariableDeclarationIntoStatement(VariableDeclarationStatement varDecl, Statement targetStatement, out Statement declarationPoint) + { + Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent)); + // Find all blocks between targetStatement and varDecl.Parent + List<BlockStatement> blocks = targetStatement.Ancestors.TakeWhile(block => block != varDecl.Parent).OfType<BlockStatement>().ToList(); + blocks.Add((BlockStatement)varDecl.Parent); // also handle the varDecl.Parent block itself + blocks.Reverse(); // go from parent blocks to child blocks + DefiniteAssignmentAnalysis daa = new DefiniteAssignmentAnalysis(blocks[0], context.CancellationToken); + declarationPoint = null; + foreach (BlockStatement block in blocks) { + if (!DeclareVariables.FindDeclarationPoint(daa, varDecl, block, out declarationPoint)) { + return false; + } + } + return true; + } + + /// <summary> + /// Gets whether there is an assignment to 'variableName' anywhere within the given node. + /// </summary> + bool HasAssignment(AstNode root, string variableName) + { + foreach (AstNode node in root.DescendantsAndSelf) { + IdentifierExpression ident = node as IdentifierExpression; + if (ident != null && ident.Identifier == variableName) { + if (ident.Parent is AssignmentExpression && ident.Role == AssignmentExpression.LeftRole + || ident.Parent is DirectionExpression) + { + return true; + } + } + } + return false; + } + #endregion + + #region foreach (generic) + static readonly UsingStatement genericForeachPattern = new UsingStatement { + ResourceAcquisition = new VariableDeclarationStatement { + Type = new AnyNode("enumeratorType"), + Variables = { + new NamedNode( + "enumeratorVariable", + new VariableInitializer { + Name = Pattern.AnyString, + Initializer = new AnyNode("collection").ToExpression().Invoke("GetEnumerator") + } + ) + } + }, + EmbeddedStatement = new BlockStatement { + new Repeat( + new VariableDeclarationStatement { Type = new AnyNode(), Variables = { new VariableInitializer(Pattern.AnyString) } }.WithName("variablesOutsideLoop") + ).ToStatement(), + new WhileStatement { + Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"), + EmbeddedStatement = new BlockStatement { + new Repeat( + new VariableDeclarationStatement { + Type = new AnyNode(), + Variables = { new VariableInitializer(Pattern.AnyString) } + }.WithName("variablesInsideLoop") + ).ToStatement(), + new AssignmentExpression { + Left = new IdentifierExpression(Pattern.AnyString).WithName("itemVariable"), + Operator = AssignmentOperatorType.Assign, + Right = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current") + }, + new Repeat(new AnyNode("statement")).ToStatement() + } + }.WithName("loop") + }}; + + public ForeachStatement TransformForeach(UsingStatement node) + { + Match m = genericForeachPattern.Match(node); + if (!m.Success) + return null; + if (!(node.Parent is BlockStatement) && m.Has("variablesOutsideLoop")) { + // if there are variables outside the loop, we need to put those into the parent block, and that won't work if the direct parent isn't a block + return null; + } + VariableInitializer enumeratorVar = m.Get<VariableInitializer>("enumeratorVariable").Single(); + IdentifierExpression itemVar = m.Get<IdentifierExpression>("itemVariable").Single(); + WhileStatement loop = m.Get<WhileStatement>("loop").Single(); + + // Find the declaration of the item variable: + // Because we look only outside the loop, we won't make the mistake of moving a captured variable across the loop boundary + VariableDeclarationStatement itemVarDecl = FindVariableDeclaration(loop, itemVar.Identifier); + if (itemVarDecl == null || !(itemVarDecl.Parent is BlockStatement)) + return null; + + // Now verify that we can move the variable declaration in front of the loop: + Statement declarationPoint; + CanMoveVariableDeclarationIntoStatement(itemVarDecl, loop, out declarationPoint); + // We ignore the return value because we don't care whether we can move the variable into the loop + // (that is possible only with non-captured variables). + // We just care that we can move it in front of the loop: + if (declarationPoint != loop) + return null; + + BlockStatement newBody = new BlockStatement(); + foreach (Statement stmt in m.Get<Statement>("variablesInsideLoop")) + newBody.Add(stmt.Detach()); + foreach (Statement stmt in m.Get<Statement>("statement")) + newBody.Add(stmt.Detach()); + + ForeachStatement foreachStatement = new ForeachStatement { + VariableType = (AstType)itemVarDecl.Type.Clone(), + VariableName = itemVar.Identifier, + InExpression = m.Get<Expression>("collection").Single().Detach(), + EmbeddedStatement = newBody + }.WithAnnotation(itemVarDecl.Variables.Single().Annotation<ILVariable>()); + if (foreachStatement.InExpression is BaseReferenceExpression) { + foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); + } + node.ReplaceWith(foreachStatement); + foreach (Statement stmt in m.Get<Statement>("variablesOutsideLoop")) { + ((BlockStatement)foreachStatement.Parent).Statements.InsertAfter(null, stmt.Detach()); + } + return foreachStatement; + } + #endregion + + #region foreach (non-generic) + ExpressionStatement getEnumeratorPattern = new ExpressionStatement( + new AssignmentExpression( + new NamedNode("left", new IdentifierExpression(Pattern.AnyString)), + new AnyNode("collection").ToExpression().Invoke("GetEnumerator") + )); + + TryCatchStatement nonGenericForeachPattern = new TryCatchStatement { + TryBlock = new BlockStatement { + new WhileStatement { + Condition = new IdentifierExpression(Pattern.AnyString).WithName("enumerator").Invoke("MoveNext"), + EmbeddedStatement = new BlockStatement { + new AssignmentExpression( + new IdentifierExpression(Pattern.AnyString).WithName("itemVar"), + new Choice { + new Backreference("enumerator").ToExpression().Member("Current"), + new CastExpression { + Type = new AnyNode("castType"), + Expression = new Backreference("enumerator").ToExpression().Member("Current") + } + } + ), + new Repeat(new AnyNode("stmt")).ToStatement() + } + }.WithName("loop") + }, + FinallyBlock = new BlockStatement { + new AssignmentExpression( + new IdentifierExpression(Pattern.AnyString).WithName("disposable"), + new Backreference("enumerator").ToExpression().CastAs(new TypePattern(typeof(IDisposable))) + ), + new IfElseStatement { + Condition = new BinaryOperatorExpression { + Left = new Backreference("disposable"), + Operator = BinaryOperatorType.InEquality, + Right = new NullReferenceExpression() + }, + TrueStatement = new BlockStatement { + new Backreference("disposable").ToExpression().Invoke("Dispose") + } + } + }}; + + public ForeachStatement TransformNonGenericForEach(ExpressionStatement node) + { + Match m1 = getEnumeratorPattern.Match(node); + if (!m1.Success) return null; + AstNode tryCatch = node.NextSibling; + Match m2 = nonGenericForeachPattern.Match(tryCatch); + if (!m2.Success) return null; + + IdentifierExpression enumeratorVar = m2.Get<IdentifierExpression>("enumerator").Single(); + IdentifierExpression itemVar = m2.Get<IdentifierExpression>("itemVar").Single(); + WhileStatement loop = m2.Get<WhileStatement>("loop").Single(); + + // verify that the getEnumeratorPattern assigns to the same variable as the nonGenericForeachPattern is reading from + if (!enumeratorVar.IsMatch(m1.Get("left").Single())) + return null; + + VariableDeclarationStatement enumeratorVarDecl = FindVariableDeclaration(loop, enumeratorVar.Identifier); + if (enumeratorVarDecl == null || !(enumeratorVarDecl.Parent is BlockStatement)) + return null; + + // Find the declaration of the item variable: + // Because we look only outside the loop, we won't make the mistake of moving a captured variable across the loop boundary + VariableDeclarationStatement itemVarDecl = FindVariableDeclaration(loop, itemVar.Identifier); + if (itemVarDecl == null || !(itemVarDecl.Parent is BlockStatement)) + return null; + + // Now verify that we can move the variable declaration in front of the loop: + Statement declarationPoint; + CanMoveVariableDeclarationIntoStatement(itemVarDecl, loop, out declarationPoint); + // We ignore the return value because we don't care whether we can move the variable into the loop + // (that is possible only with non-captured variables). + // We just care that we can move it in front of the loop: + if (declarationPoint != loop) + return null; + + ForeachStatement foreachStatement = new ForeachStatement + { + VariableType = itemVarDecl.Type.Clone(), + VariableName = itemVar.Identifier, + }.WithAnnotation(itemVarDecl.Variables.Single().Annotation<ILVariable>()); + BlockStatement body = new BlockStatement(); + foreachStatement.EmbeddedStatement = body; + ((BlockStatement)node.Parent).Statements.InsertBefore(node, foreachStatement); + + body.Add(node.Detach()); + body.Add((Statement)tryCatch.Detach()); + + // Now that we moved the whole try-catch into the foreach loop; verify that we can + // move the enumerator into the foreach loop: + CanMoveVariableDeclarationIntoStatement(enumeratorVarDecl, foreachStatement, out declarationPoint); + if (declarationPoint != foreachStatement) { + // oops, the enumerator variable can't be moved into the foreach loop + // Undo our AST changes: + ((BlockStatement)foreachStatement.Parent).Statements.InsertBefore(foreachStatement, node.Detach()); + foreachStatement.ReplaceWith(tryCatch); + return null; + } + + // Now create the correct body for the foreach statement: + foreachStatement.InExpression = m1.Get<Expression>("collection").Single().Detach(); + if (foreachStatement.InExpression is BaseReferenceExpression) { + foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); + } + body.Statements.Clear(); + body.Statements.AddRange(m2.Get<Statement>("stmt").Select(stmt => stmt.Detach())); + + return foreachStatement; + } + #endregion + + #region for + static readonly WhileStatement forPattern = new WhileStatement { + Condition = new BinaryOperatorExpression { + Left = new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)), + Operator = BinaryOperatorType.Any, + Right = new AnyNode("endExpr") + }, + EmbeddedStatement = new BlockStatement { + Statements = { + new Repeat(new AnyNode("statement")), + new NamedNode( + "increment", + new ExpressionStatement( + new AssignmentExpression { + Left = new Backreference("ident"), + Operator = AssignmentOperatorType.Any, + Right = new AnyNode() + })) + } + }}; + + public ForStatement TransformFor(ExpressionStatement node) + { + Match m1 = variableAssignPattern.Match(node); + if (!m1.Success) return null; + AstNode next = node.NextSibling; + Match m2 = forPattern.Match(next); + if (!m2.Success) return null; + // ensure the variable in the for pattern is the same as in the declaration + if (m1.Get<IdentifierExpression>("variable").Single().Identifier != m2.Get<IdentifierExpression>("ident").Single().Identifier) + return null; + WhileStatement loop = (WhileStatement)next; + node.Remove(); + BlockStatement newBody = new BlockStatement(); + foreach (Statement stmt in m2.Get<Statement>("statement")) + newBody.Add(stmt.Detach()); + ForStatement forStatement = new ForStatement(); + forStatement.Initializers.Add(node); + forStatement.Condition = loop.Condition.Detach(); + forStatement.Iterators.Add(m2.Get<Statement>("increment").Single().Detach()); + forStatement.EmbeddedStatement = newBody; + loop.ReplaceWith(forStatement); + return forStatement; + } + #endregion + + #region doWhile + static readonly WhileStatement doWhilePattern = new WhileStatement { + Condition = new PrimitiveExpression(true), + EmbeddedStatement = new BlockStatement { + Statements = { + new Repeat(new AnyNode("statement")), + new IfElseStatement { + Condition = new AnyNode("condition"), + TrueStatement = new BlockStatement { new BreakStatement() } + } + } + }}; + + public DoWhileStatement TransformDoWhile(WhileStatement whileLoop) + { + Match m = doWhilePattern.Match(whileLoop); + if (m.Success) { + DoWhileStatement doLoop = new DoWhileStatement(); + doLoop.Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, m.Get<Expression>("condition").Single().Detach()); + doLoop.Condition.AcceptVisitor(new PushNegation(), null); + BlockStatement block = (BlockStatement)whileLoop.EmbeddedStatement; + block.Statements.Last().Remove(); // remove if statement + doLoop.EmbeddedStatement = block.Detach(); + whileLoop.ReplaceWith(doLoop); + + // we may have to extract variable definitions out of the loop if they were used in the condition: + foreach (var varDecl in block.Statements.OfType<VariableDeclarationStatement>()) { + VariableInitializer v = varDecl.Variables.Single(); + if (doLoop.Condition.DescendantsAndSelf.OfType<IdentifierExpression>().Any(i => i.Identifier == v.Name)) { + AssignmentExpression assign = new AssignmentExpression(new IdentifierExpression(v.Name), v.Initializer.Detach()); + // move annotations from v to assign: + assign.CopyAnnotationsFrom(v); + v.RemoveAnnotations<object>(); + // remove varDecl with assignment; and move annotations from varDecl to the ExpressionStatement: + varDecl.ReplaceWith(new ExpressionStatement(assign).CopyAnnotationsFrom(varDecl)); + varDecl.RemoveAnnotations<object>(); + + // insert the varDecl above the do-while loop: + doLoop.Parent.InsertChildBefore(doLoop, varDecl, BlockStatement.StatementRole); + } + } + return doLoop; + } + return null; + } + #endregion + + #region lock + static readonly AstNode lockFlagInitPattern = new ExpressionStatement( + new AssignmentExpression( + new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), + new PrimitiveExpression(false) + )); + + static readonly AstNode lockTryCatchPattern = new TryCatchStatement { + TryBlock = new BlockStatement { + new OptionalNode(new VariableDeclarationStatement()).ToStatement(), + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke( + "Enter", new AnyNode("enter"), + new DirectionExpression { + FieldDirection = FieldDirection.Ref, + Expression = new NamedNode("flag", new IdentifierExpression(Pattern.AnyString)) + }), + new Repeat(new AnyNode()).ToStatement() + }, + FinallyBlock = new BlockStatement { + new IfElseStatement { + Condition = new Backreference("flag"), + TrueStatement = new BlockStatement { + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) + } + } + }}; + + static readonly AstNode oldMonitorCallPattern = new ExpressionStatement( + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Enter", new AnyNode("enter")) + ); + + static readonly AstNode oldLockTryCatchPattern = new TryCatchStatement + { + TryBlock = new BlockStatement { + new Repeat(new AnyNode()).ToStatement() + }, + FinallyBlock = new BlockStatement { + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) + } + }; + + bool AnalyzeLockV2(ExpressionStatement node, out Expression enter, out Expression exit) + { + enter = null; + exit = null; + Match m1 = oldMonitorCallPattern.Match(node); + if (!m1.Success) return false; + Match m2 = oldLockTryCatchPattern.Match(node.NextSibling); + if (!m2.Success) return false; + enter = m1.Get<Expression>("enter").Single(); + exit = m2.Get<Expression>("exit").Single(); + return true; + } + + bool AnalyzeLockV4(ExpressionStatement node, out Expression enter, out Expression exit) + { + enter = null; + exit = null; + Match m1 = lockFlagInitPattern.Match(node); + if (!m1.Success) return false; + Match m2 = lockTryCatchPattern.Match(node.NextSibling); + if (!m2.Success) return false; + enter = m2.Get<Expression>("enter").Single(); + exit = m2.Get<Expression>("exit").Single(); + return m1.Get<IdentifierExpression>("variable").Single().Identifier == m2.Get<IdentifierExpression>("flag").Single().Identifier; + } + + public LockStatement TransformLock(ExpressionStatement node) + { + Expression enter, exit; + bool isV2 = AnalyzeLockV2(node, out enter, out exit); + if (isV2 || AnalyzeLockV4(node, out enter, out exit)) { + AstNode tryCatch = node.NextSibling; + if (!exit.IsMatch(enter)) { + // If exit and enter are not the same, then enter must be "exit = ..." + AssignmentExpression assign = enter as AssignmentExpression; + if (assign == null) + return null; + if (!exit.IsMatch(assign.Left)) + return null; + enter = assign.Right; + // TODO: verify that 'obj' variable can be removed + } + // TODO: verify that 'flag' variable can be removed + // transform the code into a lock statement: + LockStatement l = new LockStatement(); + l.Expression = enter.Detach(); + l.EmbeddedStatement = ((TryCatchStatement)tryCatch).TryBlock.Detach(); + if (!isV2) // Remove 'Enter()' call + ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); + tryCatch.ReplaceWith(l); + node.Remove(); // remove flag variable + return l; + } + return null; + } + #endregion + + #region switch on strings + static readonly IfElseStatement switchOnStringPattern = new IfElseStatement { + Condition = new BinaryOperatorExpression { + Left = new AnyNode("switchExpr"), + Operator = BinaryOperatorType.InEquality, + Right = new NullReferenceExpression() + }, + TrueStatement = new BlockStatement { + new IfElseStatement { + Condition = new BinaryOperatorExpression { + Left = new AnyNode("cachedDict"), + Operator = BinaryOperatorType.Equality, + Right = new NullReferenceExpression() + }, + TrueStatement = new AnyNode("dictCreation") + }, + new IfElseStatement { + Condition = new Backreference("cachedDict").ToExpression().Invoke( + "TryGetValue", + new NamedNode("switchVar", new IdentifierExpression(Pattern.AnyString)), + new DirectionExpression { + FieldDirection = FieldDirection.Out, + Expression = new IdentifierExpression(Pattern.AnyString).WithName("intVar") + }), + TrueStatement = new BlockStatement { + Statements = { + new NamedNode( + "switch", new SwitchStatement { + Expression = new IdentifierExpressionBackreference("intVar"), + SwitchSections = { new Repeat(new AnyNode()) } + }) + } + } + }, + new Repeat(new AnyNode("nonNullDefaultStmt")).ToStatement() + }, + FalseStatement = new OptionalNode("nullStmt", new BlockStatement { Statements = { new Repeat(new AnyNode()) } }) + }; + + public SwitchStatement TransformSwitchOnString(IfElseStatement node) + { + Match m = switchOnStringPattern.Match(node); + if (!m.Success) + return null; + // switchVar must be the same as switchExpr; or switchExpr must be an assignment and switchVar the left side of that assignment + if (!m.Get("switchVar").Single().IsMatch(m.Get("switchExpr").Single())) { + AssignmentExpression assign = m.Get("switchExpr").Single() as AssignmentExpression; + if (!(assign != null && m.Get("switchVar").Single().IsMatch(assign.Left))) + return null; + } + FieldReference cachedDictField = m.Get<AstNode>("cachedDict").Single().Annotation<FieldReference>(); + if (cachedDictField == null) + return null; + List<Statement> dictCreation = m.Get<BlockStatement>("dictCreation").Single().Statements.ToList(); + List<KeyValuePair<string, int>> dict = BuildDictionary(dictCreation); + SwitchStatement sw = m.Get<SwitchStatement>("switch").Single(); + sw.Expression = m.Get<Expression>("switchExpr").Single().Detach(); + foreach (SwitchSection section in sw.SwitchSections) { + List<CaseLabel> labels = section.CaseLabels.ToList(); + section.CaseLabels.Clear(); + foreach (CaseLabel label in labels) { + PrimitiveExpression expr = label.Expression as PrimitiveExpression; + if (expr == null || !(expr.Value is int)) + continue; + int val = (int)expr.Value; + foreach (var pair in dict) { + if (pair.Value == val) + section.CaseLabels.Add(new CaseLabel { Expression = new PrimitiveExpression(pair.Key) }); + } + } + } + if (m.Has("nullStmt")) { + SwitchSection section = new SwitchSection(); + section.CaseLabels.Add(new CaseLabel { Expression = new NullReferenceExpression() }); + BlockStatement block = m.Get<BlockStatement>("nullStmt").Single(); + block.Statements.Add(new BreakStatement()); + section.Statements.Add(block.Detach()); + sw.SwitchSections.Add(section); + } else if (m.Has("nonNullDefaultStmt")) { + sw.SwitchSections.Add( + new SwitchSection { + CaseLabels = { new CaseLabel { Expression = new NullReferenceExpression() } }, + Statements = { new BlockStatement { new BreakStatement() } } + }); + } + if (m.Has("nonNullDefaultStmt")) { + SwitchSection section = new SwitchSection(); + section.CaseLabels.Add(new CaseLabel()); + BlockStatement block = new BlockStatement(); + block.Statements.AddRange(m.Get<Statement>("nonNullDefaultStmt").Select(s => s.Detach())); + block.Add(new BreakStatement()); + section.Statements.Add(block); + sw.SwitchSections.Add(section); + } + node.ReplaceWith(sw); + return sw; + } + + List<KeyValuePair<string, int>> BuildDictionary(List<Statement> dictCreation) + { + if (context.Settings.ObjectOrCollectionInitializers && dictCreation.Count == 1) + return BuildDictionaryFromInitializer(dictCreation[0]); + + return BuildDictionaryFromAddMethodCalls(dictCreation); + } + + static readonly Statement assignInitializedDictionary = new ExpressionStatement { + Expression = new AssignmentExpression { + Left = new AnyNode().ToExpression(), + Right = new ObjectCreateExpression { + Type = new AnyNode(), + Arguments = { new Repeat(new AnyNode()) }, + Initializer = new ArrayInitializerExpression { + Elements = { new Repeat(new AnyNode("dictJumpTable")) } + } + }, + }, + }; + + List<KeyValuePair<string, int>> BuildDictionaryFromInitializer(Statement statement) + { + List<KeyValuePair<string, int>> dict = new List<KeyValuePair<string, int>>(); + Match m = assignInitializedDictionary.Match(statement); + if (!m.Success) + return dict; + + foreach (ArrayInitializerExpression initializer in m.Get<ArrayInitializerExpression>("dictJumpTable")) { + KeyValuePair<string, int> pair; + if (TryGetPairFrom(initializer.Elements, out pair)) + dict.Add(pair); + } + + return dict; + } + + static List<KeyValuePair<string, int>> BuildDictionaryFromAddMethodCalls(List<Statement> dictCreation) + { + List<KeyValuePair<string, int>> dict = new List<KeyValuePair<string, int>>(); + for (int i = 0; i < dictCreation.Count; i++) { + ExpressionStatement es = dictCreation[i] as ExpressionStatement; + if (es == null) + continue; + InvocationExpression ie = es.Expression as InvocationExpression; + if (ie == null) + continue; + + KeyValuePair<string, int> pair; + if (TryGetPairFrom(ie.Arguments, out pair)) + dict.Add(pair); + } + return dict; + } + + static bool TryGetPairFrom(AstNodeCollection<Expression> expressions, out KeyValuePair<string, int> pair) + { + PrimitiveExpression arg1 = expressions.ElementAtOrDefault(0) as PrimitiveExpression; + PrimitiveExpression arg2 = expressions.ElementAtOrDefault(1) as PrimitiveExpression; + if (arg1 != null && arg2 != null && arg1.Value is string && arg2.Value is int) { + pair = new KeyValuePair<string, int>((string)arg1.Value, (int)arg2.Value); + return true; + } + + pair = default(KeyValuePair<string, int>); + return false; + } + + #endregion + + #region Automatic Properties + static readonly PropertyDeclaration automaticPropertyPattern = new PropertyDeclaration { + Attributes = { new Repeat(new AnyNode()) }, + Modifiers = Modifiers.Any, + ReturnType = new AnyNode(), + PrivateImplementationType = new OptionalNode(new AnyNode()), + Name = Pattern.AnyString, + Getter = new Accessor { + Attributes = { new Repeat(new AnyNode()) }, + Modifiers = Modifiers.Any, + Body = new BlockStatement { + new ReturnStatement { + Expression = new AnyNode("fieldReference") + } + } + }, + Setter = new Accessor { + Attributes = { new Repeat(new AnyNode()) }, + Modifiers = Modifiers.Any, + Body = new BlockStatement { + new AssignmentExpression { + Left = new Backreference("fieldReference"), + Right = new IdentifierExpression("value") + } + }}}; + + PropertyDeclaration TransformAutomaticProperties(PropertyDeclaration property) + { + PropertyDefinition cecilProperty = property.Annotation<PropertyDefinition>(); + if (cecilProperty == null || cecilProperty.GetMethod == null || cecilProperty.SetMethod == null) + return null; + if (!(cecilProperty.GetMethod.IsCompilerGenerated() && cecilProperty.SetMethod.IsCompilerGenerated())) + return null; + Match m = automaticPropertyPattern.Match(property); + if (m.Success) { + FieldDefinition field = m.Get<AstNode>("fieldReference").Single().Annotation<FieldReference>().ResolveWithinSameModule(); + if (field.IsCompilerGenerated() && field.DeclaringType == cecilProperty.DeclaringType) { + RemoveCompilerGeneratedAttribute(property.Getter.Attributes); + RemoveCompilerGeneratedAttribute(property.Setter.Attributes); + property.Getter.Body = null; + property.Setter.Body = null; + } + } + // Since the event instance is not changed, we can continue in the visitor as usual, so return null + return null; + } + + void RemoveCompilerGeneratedAttribute(AstNodeCollection<AttributeSection> attributeSections) + { + foreach (AttributeSection section in attributeSections) { + foreach (var attr in section.Attributes) { + TypeReference tr = attr.Type.Annotation<TypeReference>(); + if (tr != null && tr.Namespace == "System.Runtime.CompilerServices" && tr.Name == "CompilerGeneratedAttribute") { + attr.Remove(); + } + } + if (section.Attributes.Count == 0) + section.Remove(); + } + } + #endregion + + #region Automatic Events + static readonly Accessor automaticEventPatternV4 = new Accessor { + Attributes = { new Repeat(new AnyNode()) }, + Body = new BlockStatement { + new VariableDeclarationStatement { Type = new AnyNode("type"), Variables = { new AnyNode() } }, + new VariableDeclarationStatement { Type = new Backreference("type"), Variables = { new AnyNode() } }, + new VariableDeclarationStatement { Type = new Backreference("type"), Variables = { new AnyNode() } }, + new AssignmentExpression { + Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)), + Operator = AssignmentOperatorType.Assign, + Right = new NamedNode( + "field", + new MemberReferenceExpression { + Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } }, + MemberName = Pattern.AnyString + }) + }, + new DoWhileStatement { + EmbeddedStatement = new BlockStatement { + new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")), + new AssignmentExpression { + Left = new NamedNode("var3", new IdentifierExpression(Pattern.AnyString)), + Operator = AssignmentOperatorType.Assign, + Right = new AnyNode("delegateCombine").ToExpression().Invoke( + new IdentifierExpressionBackreference("var2"), + new IdentifierExpression("value") + ).CastTo(new Backreference("type")) + }, + new AssignmentExpression { + Left = new IdentifierExpressionBackreference("var1"), + Right = new TypePattern(typeof(System.Threading.Interlocked)).ToType().Invoke( + "CompareExchange", + new AstType[] { new Backreference("type") }, // type argument + new Expression[] { // arguments + new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }, + new IdentifierExpressionBackreference("var3"), + new IdentifierExpressionBackreference("var2") + } + )} + }, + Condition = new BinaryOperatorExpression { + Left = new IdentifierExpressionBackreference("var1"), + Operator = BinaryOperatorType.InEquality, + Right = new IdentifierExpressionBackreference("var2") + }} + }}; + + bool CheckAutomaticEventV4Match(Match m, CustomEventDeclaration ev, bool isAddAccessor) + { + if (!m.Success) + return false; + if (m.Get<MemberReferenceExpression>("field").Single().MemberName != ev.Name) + return false; // field name must match event name + if (!ev.ReturnType.IsMatch(m.Get("type").Single())) + return false; // variable types must match event type + var combineMethod = m.Get<AstNode>("delegateCombine").Single().Parent.Annotation<MethodReference>(); + if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) + return false; + return combineMethod.DeclaringType.FullName == "System.Delegate"; + } + + EventDeclaration TransformAutomaticEvents(CustomEventDeclaration ev) + { + Match m1 = automaticEventPatternV4.Match(ev.AddAccessor); + if (!CheckAutomaticEventV4Match(m1, ev, true)) + return null; + Match m2 = automaticEventPatternV4.Match(ev.RemoveAccessor); + if (!CheckAutomaticEventV4Match(m2, ev, false)) + return null; + EventDeclaration ed = new EventDeclaration(); + ev.Attributes.MoveTo(ed.Attributes); + foreach (var attr in ev.AddAccessor.Attributes) { + attr.AttributeTarget = "method"; + ed.Attributes.Add(attr.Detach()); + } + ed.ReturnType = ev.ReturnType.Detach(); + ed.Modifiers = ev.Modifiers; + ed.Variables.Add(new VariableInitializer(ev.Name)); + ed.CopyAnnotationsFrom(ev); + + EventDefinition eventDef = ev.Annotation<EventDefinition>(); + if (eventDef != null) { + FieldDefinition field = eventDef.DeclaringType.Fields.FirstOrDefault(f => f.Name == ev.Name); + if (field != null) { + ed.AddAnnotation(field); + AstBuilder.ConvertAttributes(ed, field, "field"); + } + } + + ev.ReplaceWith(ed); + return ed; + } + #endregion + + #region Destructor + static readonly MethodDeclaration destructorPattern = new MethodDeclaration { + Attributes = { new Repeat(new AnyNode()) }, + Modifiers = Modifiers.Any, + ReturnType = new PrimitiveType("void"), + Name = "Finalize", + Body = new BlockStatement { + new TryCatchStatement { + TryBlock = new AnyNode("body"), + FinallyBlock = new BlockStatement { + new BaseReferenceExpression().Invoke("Finalize") + } + } + } + }; + + DestructorDeclaration TransformDestructor(MethodDeclaration methodDef) + { + Match m = destructorPattern.Match(methodDef); + if (m.Success) { + DestructorDeclaration dd = new DestructorDeclaration(); + methodDef.Attributes.MoveTo(dd.Attributes); + dd.Modifiers = methodDef.Modifiers & ~(Modifiers.Protected | Modifiers.Override); + dd.Body = m.Get<BlockStatement>("body").Single().Detach(); + dd.Name = AstBuilder.CleanName(context.CurrentType.Name); + methodDef.ReplaceWith(dd); + return dd; + } + return null; + } + #endregion + + #region Try-Catch-Finally + static readonly TryCatchStatement tryCatchFinallyPattern = new TryCatchStatement { + TryBlock = new BlockStatement { + new TryCatchStatement { + TryBlock = new AnyNode(), + CatchClauses = { new Repeat(new AnyNode()) } + } + }, + FinallyBlock = new AnyNode() + }; + + /// <summary> + /// Simplify nested 'try { try {} catch {} } finally {}'. + /// This transformation must run after the using/lock tranformations. + /// </summary> + TryCatchStatement TransformTryCatchFinally(TryCatchStatement tryFinally) + { + if (tryCatchFinallyPattern.IsMatch(tryFinally)) { + TryCatchStatement tryCatch = (TryCatchStatement)tryFinally.TryBlock.Statements.Single(); + tryFinally.TryBlock = tryCatch.TryBlock.Detach(); + tryCatch.CatchClauses.MoveTo(tryFinally.CatchClauses); + } + // Since the tryFinally instance is not changed, we can continue in the visitor as usual, so return null + return null; + } + #endregion + + #region Simplify cascading if-else-if statements + static readonly IfElseStatement cascadingIfElsePattern = new IfElseStatement + { + Condition = new AnyNode(), + TrueStatement = new AnyNode(), + FalseStatement = new BlockStatement { + Statements = { + new NamedNode( + "nestedIfStatement", + new IfElseStatement { + Condition = new AnyNode(), + TrueStatement = new AnyNode(), + FalseStatement = new OptionalNode(new AnyNode()) + } + ) + } + } + }; + + AstNode SimplifyCascadingIfElseStatements(IfElseStatement node) + { + Match m = cascadingIfElsePattern.Match(node); + if (m.Success) { + IfElseStatement elseIf = m.Get<IfElseStatement>("nestedIfStatement").Single(); + node.FalseStatement = elseIf.Detach(); + } + + return null; + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PushNegation.cs b/ICSharpCode.Decompiler/Ast/Transforms/PushNegation.cs new file mode 100644 index 00000000..193c5e69 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/PushNegation.cs @@ -0,0 +1,164 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + public class PushNegation: DepthFirstAstVisitor<object, object>, IAstTransform + { + sealed class LiftedOperator { } + /// <summary> + /// Annotation for lifted operators that cannot be transformed by PushNegation + /// </summary> + public static readonly object LiftedOperatorAnnotation = new LiftedOperator(); + + public override object VisitUnaryOperatorExpression(UnaryOperatorExpression unary, object data) + { + // lifted operators can't be transformed + if (unary.Annotation<LiftedOperator>() != null || unary.Expression.Annotation<LiftedOperator>() != null) + return base.VisitUnaryOperatorExpression(unary, data); + + // Remove double negation + // !!a + if (unary.Operator == UnaryOperatorType.Not && + unary.Expression is UnaryOperatorExpression && + (unary.Expression as UnaryOperatorExpression).Operator == UnaryOperatorType.Not) + { + AstNode newNode = (unary.Expression as UnaryOperatorExpression).Expression; + unary.ReplaceWith(newNode); + return newNode.AcceptVisitor(this, data); + } + + // Push through binary operation + // !((a) op (b)) + BinaryOperatorExpression binaryOp = unary.Expression as BinaryOperatorExpression; + if (unary.Operator == UnaryOperatorType.Not && binaryOp != null) { + bool successful = true; + switch (binaryOp.Operator) { + case BinaryOperatorType.Equality: + binaryOp.Operator = BinaryOperatorType.InEquality; + break; + case BinaryOperatorType.InEquality: + binaryOp.Operator = BinaryOperatorType.Equality; + break; + case BinaryOperatorType.GreaterThan: // TODO: these are invalid for floats (stupid NaN) + binaryOp.Operator = BinaryOperatorType.LessThanOrEqual; + break; + case BinaryOperatorType.GreaterThanOrEqual: + binaryOp.Operator = BinaryOperatorType.LessThan; + break; + case BinaryOperatorType.LessThanOrEqual: + binaryOp.Operator = BinaryOperatorType.GreaterThan; + break; + case BinaryOperatorType.LessThan: + binaryOp.Operator = BinaryOperatorType.GreaterThanOrEqual; + break; + default: + successful = false; + break; + } + if (successful) { + unary.ReplaceWith(binaryOp); + return binaryOp.AcceptVisitor(this, data); + } + + successful = true; + switch (binaryOp.Operator) { + case BinaryOperatorType.ConditionalAnd: + binaryOp.Operator = BinaryOperatorType.ConditionalOr; + break; + case BinaryOperatorType.ConditionalOr: + binaryOp.Operator = BinaryOperatorType.ConditionalAnd; + break; + default: + successful = false; + break; + } + if (successful) { + binaryOp.Left.ReplaceWith(e => new UnaryOperatorExpression(UnaryOperatorType.Not, e)); + binaryOp.Right.ReplaceWith(e => new UnaryOperatorExpression(UnaryOperatorType.Not, e)); + unary.ReplaceWith(binaryOp); + return binaryOp.AcceptVisitor(this, data); + } + } + return base.VisitUnaryOperatorExpression(unary, data); + } + + readonly static AstNode asCastIsNullPattern = new BinaryOperatorExpression( + new AnyNode("expr").ToExpression().CastAs(new AnyNode("type")), + BinaryOperatorType.Equality, + new NullReferenceExpression() + ); + + readonly static AstNode asCastIsNotNullPattern = new BinaryOperatorExpression( + new AnyNode("expr").ToExpression().CastAs(new AnyNode("type")), + BinaryOperatorType.InEquality, + new NullReferenceExpression() + ); + + public override object VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, object data) + { + // lifted operators can't be transformed + if (binaryOperatorExpression.Annotation<LiftedOperator>() != null) + return base.VisitBinaryOperatorExpression(binaryOperatorExpression, data); + + BinaryOperatorType op = binaryOperatorExpression.Operator; + bool? rightOperand = null; + if (binaryOperatorExpression.Right is PrimitiveExpression) + rightOperand = ((PrimitiveExpression)binaryOperatorExpression.Right).Value as bool?; + if (op == BinaryOperatorType.Equality && rightOperand == true || op == BinaryOperatorType.InEquality && rightOperand == false) { + // 'b == true' or 'b != false' is useless + binaryOperatorExpression.Left.AcceptVisitor(this, data); + binaryOperatorExpression.ReplaceWith(binaryOperatorExpression.Left); + return null; + } else if (op == BinaryOperatorType.Equality && rightOperand == false || op == BinaryOperatorType.InEquality && rightOperand == true) { + // 'b == false' or 'b != true' is a negation: + Expression left = binaryOperatorExpression.Left; + left.Remove(); + UnaryOperatorExpression uoe = new UnaryOperatorExpression(UnaryOperatorType.Not, left); + binaryOperatorExpression.ReplaceWith(uoe); + return uoe.AcceptVisitor(this, data); + } else { + bool negate = false; + Match m = asCastIsNotNullPattern.Match(binaryOperatorExpression); + if (!m.Success) { + m = asCastIsNullPattern.Match(binaryOperatorExpression); + negate = true; + } + if (m.Success) { + Expression expr = m.Get<Expression>("expr").Single().Detach().IsType(m.Get<AstType>("type").Single().Detach()); + if (negate) + expr = new UnaryOperatorExpression(UnaryOperatorType.Not, expr); + binaryOperatorExpression.ReplaceWith(expr); + return expr.AcceptVisitor(this, data); + } else { + return base.VisitBinaryOperatorExpression(binaryOperatorExpression, data); + } + } + } + void IAstTransform.Run(AstNode node) + { + node.AcceptVisitor(this, null); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs new file mode 100644 index 00000000..6a3f8f97 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs @@ -0,0 +1,356 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; +using Ast = ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + /// <summary> + /// Replaces method calls with the appropriate operator expressions. + /// Also simplifies "x = x op y" into "x op= y" where possible. + /// </summary> + public class ReplaceMethodCallsWithOperators : DepthFirstAstVisitor<object, object>, IAstTransform + { + static readonly MemberReferenceExpression typeHandleOnTypeOfPattern = new MemberReferenceExpression { + Target = new Choice { + new TypeOfExpression(new AnyNode()), + new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.RefType, Arguments = { new AnyNode() } } + }, + MemberName = "TypeHandle" + }; + + DecompilerContext context; + + public ReplaceMethodCallsWithOperators(DecompilerContext context) + { + this.context = context; + } + + public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data) + { + base.VisitInvocationExpression(invocationExpression, data); + ProcessInvocationExpression(invocationExpression); + return null; + } + + internal static void ProcessInvocationExpression(InvocationExpression invocationExpression) + { + MethodReference methodRef = invocationExpression.Annotation<MethodReference>(); + if (methodRef == null) + return; + var arguments = invocationExpression.Arguments.ToArray(); + + // Reduce "String.Concat(a, b)" to "a + b" + if (methodRef.Name == "Concat" && methodRef.DeclaringType.FullName == "System.String" && arguments.Length >= 2) + { + invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression + Expression expr = arguments[0]; + for (int i = 1; i < arguments.Length; i++) { + expr = new BinaryOperatorExpression(expr, BinaryOperatorType.Add, arguments[i]); + } + invocationExpression.ReplaceWith(expr); + return; + } + + switch (methodRef.FullName) { + case "System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)": + if (arguments.Length == 1) { + if (typeHandleOnTypeOfPattern.IsMatch(arguments[0])) { + invocationExpression.ReplaceWith(((MemberReferenceExpression)arguments[0]).Target); + return; + } + } + break; + case "System.Reflection.FieldInfo System.Reflection.FieldInfo::GetFieldFromHandle(System.RuntimeFieldHandle)": + if (arguments.Length == 1) { + MemberReferenceExpression mre = arguments[0] as MemberReferenceExpression; + if (mre != null && mre.MemberName == "FieldHandle" && mre.Target.Annotation<LdTokenAnnotation>() != null) { + invocationExpression.ReplaceWith(mre.Target); + return; + } + } + break; + case "System.Reflection.FieldInfo System.Reflection.FieldInfo::GetFieldFromHandle(System.RuntimeFieldHandle,System.RuntimeTypeHandle)": + if (arguments.Length == 2) { + MemberReferenceExpression mre1 = arguments[0] as MemberReferenceExpression; + MemberReferenceExpression mre2 = arguments[1] as MemberReferenceExpression; + if (mre1 != null && mre1.MemberName == "FieldHandle" && mre1.Target.Annotation<LdTokenAnnotation>() != null) { + if (mre2 != null && mre2.MemberName == "TypeHandle" && mre2.Target is TypeOfExpression) { + Expression oldArg = ((InvocationExpression)mre1.Target).Arguments.Single(); + FieldReference field = oldArg.Annotation<FieldReference>(); + if (field != null) { + AstType declaringType = ((TypeOfExpression)mre2.Target).Type.Detach(); + oldArg.ReplaceWith(declaringType.Member(field.Name).WithAnnotation(field)); + invocationExpression.ReplaceWith(mre1.Target); + return; + } + } + } + } + break; + } + + BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(methodRef.Name); + if (bop != null && arguments.Length == 2) { + invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression + invocationExpression.ReplaceWith( + new BinaryOperatorExpression(arguments[0], bop.Value, arguments[1]).WithAnnotation(methodRef) + ); + return; + } + UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(methodRef.Name); + if (uop != null && arguments.Length == 1) { + arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith( + new UnaryOperatorExpression(uop.Value, arguments[0]).WithAnnotation(methodRef) + ); + return; + } + if (methodRef.Name == "op_Explicit" && arguments.Length == 1) { + arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith( + arguments[0].CastTo(AstBuilder.ConvertType(methodRef.ReturnType, methodRef.MethodReturnType)) + .WithAnnotation(methodRef) + ); + return; + } + if (methodRef.Name == "op_Implicit" && arguments.Length == 1) { + invocationExpression.ReplaceWith(arguments[0]); + return; + } + if (methodRef.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == Roles.Condition) { + invocationExpression.ReplaceWith(arguments[0]); + return; + } + + return; + } + + static BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name) + { + switch (name) { + case "op_Addition": + return BinaryOperatorType.Add; + case "op_Subtraction": + return BinaryOperatorType.Subtract; + case "op_Multiply": + return BinaryOperatorType.Multiply; + case "op_Division": + return BinaryOperatorType.Divide; + case "op_Modulus": + return BinaryOperatorType.Modulus; + case "op_BitwiseAnd": + return BinaryOperatorType.BitwiseAnd; + case "op_BitwiseOr": + return BinaryOperatorType.BitwiseOr; + case "op_ExclusiveOr": + return BinaryOperatorType.ExclusiveOr; + case "op_LeftShift": + return BinaryOperatorType.ShiftLeft; + case "op_RightShift": + return BinaryOperatorType.ShiftRight; + case "op_Equality": + return BinaryOperatorType.Equality; + case "op_Inequality": + return BinaryOperatorType.InEquality; + case "op_LessThan": + return BinaryOperatorType.LessThan; + case "op_LessThanOrEqual": + return BinaryOperatorType.LessThanOrEqual; + case "op_GreaterThan": + return BinaryOperatorType.GreaterThan; + case "op_GreaterThanOrEqual": + return BinaryOperatorType.GreaterThanOrEqual; + default: + return null; + } + } + + static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name) + { + switch (name) { + case "op_LogicalNot": + return UnaryOperatorType.Not; + case "op_OnesComplement": + return UnaryOperatorType.BitNot; + case "op_UnaryNegation": + return UnaryOperatorType.Minus; + case "op_UnaryPlus": + return UnaryOperatorType.Plus; + case "op_Increment": + return UnaryOperatorType.Increment; + case "op_Decrement": + return UnaryOperatorType.Decrement; + default: + return null; + } + } + + /// <summary> + /// This annotation is used to convert a compound assignment "a += 2;" or increment operator "a++;" + /// back to the original "a = a + 2;". This is sometimes necessary when the checked/unchecked semantics + /// cannot be guaranteed otherwise (see CheckedUnchecked.ForWithCheckedInitializerAndUncheckedIterator test) + /// </summary> + public class RestoreOriginalAssignOperatorAnnotation + { + readonly BinaryOperatorExpression binaryOperatorExpression; + + public RestoreOriginalAssignOperatorAnnotation(BinaryOperatorExpression binaryOperatorExpression) + { + this.binaryOperatorExpression = binaryOperatorExpression; + } + + public AssignmentExpression Restore(Expression expression) + { + expression.RemoveAnnotations<RestoreOriginalAssignOperatorAnnotation>(); + AssignmentExpression assign = expression as AssignmentExpression; + if (assign == null) { + UnaryOperatorExpression uoe = (UnaryOperatorExpression)expression; + assign = new AssignmentExpression(uoe.Expression.Detach(), new PrimitiveExpression(1)); + } else { + assign.Operator = AssignmentOperatorType.Assign; + } + binaryOperatorExpression.Right = assign.Right.Detach(); + assign.Right = binaryOperatorExpression; + return assign; + } + } + + public override object VisitAssignmentExpression(AssignmentExpression assignment, object data) + { + base.VisitAssignmentExpression(assignment, data); + // Combine "x = x op y" into "x op= y" + BinaryOperatorExpression binary = assignment.Right as BinaryOperatorExpression; + if (binary != null && assignment.Operator == AssignmentOperatorType.Assign) { + if (CanConvertToCompoundAssignment(assignment.Left) && assignment.Left.IsMatch(binary.Left)) { + assignment.Operator = GetAssignmentOperatorForBinaryOperator(binary.Operator); + if (assignment.Operator != AssignmentOperatorType.Assign) { + // If we found a shorter operator, get rid of the BinaryOperatorExpression: + assignment.CopyAnnotationsFrom(binary); + assignment.Right = binary.Right; + assignment.AddAnnotation(new RestoreOriginalAssignOperatorAnnotation(binary)); + } + } + } + if (context.Settings.IntroduceIncrementAndDecrement && (assignment.Operator == AssignmentOperatorType.Add || assignment.Operator == AssignmentOperatorType.Subtract)) { + // detect increment/decrement + if (assignment.Right.IsMatch(new PrimitiveExpression(1))) { + // only if it's not a custom operator + if (assignment.Annotation<MethodReference>() == null) { + UnaryOperatorType type; + // When the parent is an expression statement, pre- or post-increment doesn't matter; + // so we can pick post-increment which is more commonly used (for (int i = 0; i < x; i++)) + if (assignment.Parent is ExpressionStatement) + type = (assignment.Operator == AssignmentOperatorType.Add) ? UnaryOperatorType.PostIncrement : UnaryOperatorType.PostDecrement; + else + type = (assignment.Operator == AssignmentOperatorType.Add) ? UnaryOperatorType.Increment : UnaryOperatorType.Decrement; + assignment.ReplaceWith(new UnaryOperatorExpression(type, assignment.Left.Detach()).CopyAnnotationsFrom(assignment)); + } + } + } + return null; + } + + public static AssignmentOperatorType GetAssignmentOperatorForBinaryOperator(BinaryOperatorType bop) + { + switch (bop) { + case BinaryOperatorType.Add: + return AssignmentOperatorType.Add; + case BinaryOperatorType.Subtract: + return AssignmentOperatorType.Subtract; + case BinaryOperatorType.Multiply: + return AssignmentOperatorType.Multiply; + case BinaryOperatorType.Divide: + return AssignmentOperatorType.Divide; + case BinaryOperatorType.Modulus: + return AssignmentOperatorType.Modulus; + case BinaryOperatorType.ShiftLeft: + return AssignmentOperatorType.ShiftLeft; + case BinaryOperatorType.ShiftRight: + return AssignmentOperatorType.ShiftRight; + case BinaryOperatorType.BitwiseAnd: + return AssignmentOperatorType.BitwiseAnd; + case BinaryOperatorType.BitwiseOr: + return AssignmentOperatorType.BitwiseOr; + case BinaryOperatorType.ExclusiveOr: + return AssignmentOperatorType.ExclusiveOr; + default: + return AssignmentOperatorType.Assign; + } + } + + static bool CanConvertToCompoundAssignment(Expression left) + { + MemberReferenceExpression mre = left as MemberReferenceExpression; + if (mre != null) + return IsWithoutSideEffects(mre.Target); + IndexerExpression ie = left as IndexerExpression; + if (ie != null) + return IsWithoutSideEffects(ie.Target) && ie.Arguments.All(IsWithoutSideEffects); + UnaryOperatorExpression uoe = left as UnaryOperatorExpression; + if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) + return IsWithoutSideEffects(uoe.Expression); + return IsWithoutSideEffects(left); + } + + static bool IsWithoutSideEffects(Expression left) + { + return left is ThisReferenceExpression || left is IdentifierExpression || left is TypeReferenceExpression || left is BaseReferenceExpression; + } + + static readonly Expression getMethodOrConstructorFromHandlePattern = + new TypePattern(typeof(MethodBase)).ToType().Invoke( + "GetMethodFromHandle", + new NamedNode("ldtokenNode", new LdTokenPattern("method")).ToExpression().Member("MethodHandle"), + new OptionalNode(new TypeOfExpression(new AnyNode("declaringType")).Member("TypeHandle")) + ).CastTo(new Choice { + new TypePattern(typeof(MethodInfo)), + new TypePattern(typeof(ConstructorInfo)) + }); + + public override object VisitCastExpression(CastExpression castExpression, object data) + { + base.VisitCastExpression(castExpression, data); + // Handle methodof + Match m = getMethodOrConstructorFromHandlePattern.Match(castExpression); + if (m.Success) { + MethodReference method = m.Get<AstNode>("method").Single().Annotation<MethodReference>(); + if (m.Has("declaringType")) { + Expression newNode = m.Get<AstType>("declaringType").Single().Detach().Member(method.Name); + newNode = newNode.Invoke(method.Parameters.Select(p => new TypeReferenceExpression(AstBuilder.ConvertType(p.ParameterType, p)))); + newNode.AddAnnotation(method); + m.Get<AstNode>("method").Single().ReplaceWith(newNode); + } + castExpression.ReplaceWith(m.Get<AstNode>("ldtokenNode").Single()); + } + return null; + } + + void IAstTransform.Run(AstNode node) + { + node.AcceptVisitor(this, null); + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs new file mode 100644 index 00000000..3091a109 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; +using ICSharpCode.NRefactory.CSharp; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + public interface IAstTransform + { + void Run(AstNode compilationUnit); + } + + public static class TransformationPipeline + { + public static IAstTransform[] CreatePipeline(DecompilerContext context) + { + return new IAstTransform[] { + new PushNegation(), + new DelegateConstruction(context), + new PatternStatementTransform(context), + new ReplaceMethodCallsWithOperators(context), + new IntroduceUnsafeModifier(), + new AddCheckedBlocks(), + new DeclareVariables(context), // should run after most transforms that modify statements + new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables + new DecimalConstantTransform(), + new IntroduceUsingDeclarations(context), + new IntroduceExtensionMethods(context), // must run after IntroduceUsingDeclarations + new IntroduceQueryExpressions(context), // must run after IntroduceExtensionMethods + new CombineQueryExpressions(context), + new FlattenSwitchBlocks(), + }; + } + + public static void RunTransformationsUntil(AstNode node, Predicate<IAstTransform> abortCondition, DecompilerContext context) + { + if (node == null) + return; + + foreach (var transform in CreatePipeline(context)) { + context.CancellationToken.ThrowIfCancellationRequested(); + if (abortCondition != null && abortCondition(transform)) + return; + transform.Run(node); + } + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs b/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs new file mode 100644 index 00000000..e4131904 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/TypesHierarchyHelpers.cs @@ -0,0 +1,528 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast +{ + public static class TypesHierarchyHelpers + { + public static bool IsBaseType(TypeDefinition baseType, TypeDefinition derivedType, bool resolveTypeArguments) + { + if (resolveTypeArguments) + return BaseTypes(derivedType).Any(t => t.Item == baseType); + else { + var comparableBaseType = baseType.Resolve(); + if (comparableBaseType == null) + return false; + while (derivedType.BaseType != null) { + var resolvedBaseType = derivedType.BaseType.Resolve(); + if (resolvedBaseType == null) + return false; + if (comparableBaseType == resolvedBaseType) + return true; + derivedType = resolvedBaseType; + } + return false; + } + } + + /// <summary> + /// Determines whether one method overrides or hides another method. + /// </summary> + /// <param name="parentMethod">The method declared in a base type.</param> + /// <param name="childMethod">The method declared in a derived type.</param> + /// <returns>true if <paramref name="childMethod"/> hides or overrides <paramref name="parentMethod"/>, + /// otherwise false.</returns> + public static bool IsBaseMethod(MethodDefinition parentMethod, MethodDefinition childMethod) + { + if (parentMethod == null) + throw new ArgumentNullException("parentMethod"); + if (childMethod == null) + throw new ArgumentNullException("childMethod"); + + if (parentMethod.Name != childMethod.Name) + return false; + + if (parentMethod.HasParameters || childMethod.HasParameters) + if (!parentMethod.HasParameters || !childMethod.HasParameters || parentMethod.Parameters.Count != childMethod.Parameters.Count) + return false; + + return FindBaseMethods(childMethod).Any(m => m == parentMethod);// || (parentMethod.HasGenericParameters && m.); + } + + /// <summary> + /// Determines whether a property overrides or hides another property. + /// </summary> + /// <param name="parentProperty">The property declared in a base type.</param> + /// <param name="childProperty">The property declared in a derived type.</param> + /// <returns>true if the <paramref name="childProperty"/> hides or overrides <paramref name="parentProperty"/>, + /// otherwise false.</returns> + public static bool IsBaseProperty(PropertyDefinition parentProperty, PropertyDefinition childProperty) + { + if (parentProperty == null) + throw new ArgumentNullException("parentProperty"); + if (childProperty == null) + throw new ArgumentNullException("childProperty"); + + if (parentProperty.Name != childProperty.Name) + return false; + + if (parentProperty.HasParameters || childProperty.HasParameters) + if (!parentProperty.HasParameters || !childProperty.HasParameters || parentProperty.Parameters.Count != childProperty.Parameters.Count) + return false; + + return FindBaseProperties(childProperty).Any(m => m == parentProperty); + } + + public static bool IsBaseEvent(EventDefinition parentEvent, EventDefinition childEvent) + { + if (parentEvent.Name != childEvent.Name) + return false; + + return FindBaseEvents(childEvent).Any(m => m == parentEvent); + } + + /// <summary> + /// Finds all methods from base types overridden or hidden by the specified method. + /// </summary> + /// <param name="method">The method which overrides or hides methods from base types.</param> + /// <returns>Methods overriden or hidden by the specified method.</returns> + public static IEnumerable<MethodDefinition> FindBaseMethods(MethodDefinition method) + { + if (method == null) + throw new ArgumentNullException("method"); + + var typeContext = CreateGenericContext(method.DeclaringType); + var gMethod = typeContext.ApplyTo(method); + + foreach (var baseType in BaseTypes(method.DeclaringType)) + foreach (var baseMethod in baseType.Item.Methods) + if (MatchMethod(baseType.ApplyTo(baseMethod), gMethod) && IsVisibleFromDerived(baseMethod, method.DeclaringType)) { + yield return baseMethod; + if (baseMethod.IsNewSlot == baseMethod.IsVirtual) + yield break; + } + } + + /// <summary> + /// Finds all properties from base types overridden or hidden by the specified property. + /// </summary> + /// <param name="property">The property which overrides or hides properties from base types.</param> + /// <returns>Properties overriden or hidden by the specified property.</returns> + public static IEnumerable<PropertyDefinition> FindBaseProperties(PropertyDefinition property) + { + if (property == null) + throw new ArgumentNullException("property"); + + if ((property.GetMethod ?? property.SetMethod).HasOverrides) + yield break; + + var typeContext = CreateGenericContext(property.DeclaringType); + var gProperty = typeContext.ApplyTo(property); + bool isIndexer = property.IsIndexer(); + + foreach (var baseType in BaseTypes(property.DeclaringType)) + foreach (var baseProperty in baseType.Item.Properties) + if (MatchProperty(baseType.ApplyTo(baseProperty), gProperty) + && IsVisibleFromDerived(baseProperty, property.DeclaringType)) { + if (isIndexer != baseProperty.IsIndexer()) + continue; + yield return baseProperty; + var anyPropertyAccessor = baseProperty.GetMethod ?? baseProperty.SetMethod; + if (anyPropertyAccessor.IsNewSlot == anyPropertyAccessor.IsVirtual) + yield break; + } + } + + public static IEnumerable<EventDefinition> FindBaseEvents(EventDefinition eventDef) + { + if (eventDef == null) + throw new ArgumentNullException("eventDef"); + + var typeContext = CreateGenericContext(eventDef.DeclaringType); + var gEvent = typeContext.ApplyTo(eventDef); + + foreach (var baseType in BaseTypes(eventDef.DeclaringType)) + foreach (var baseEvent in baseType.Item.Events) + if (MatchEvent(baseType.ApplyTo(baseEvent), gEvent) && IsVisibleFromDerived(baseEvent, eventDef.DeclaringType)) { + yield return baseEvent; + var anyEventAccessor = baseEvent.AddMethod ?? baseEvent.RemoveMethod; + if (anyEventAccessor.IsNewSlot == anyEventAccessor.IsVirtual) + yield break; + } + + } + + /// <summary> + /// Determinates whether member of the base type is visible from a derived type. + /// </summary> + /// <param name="baseMember">The member which visibility is checked.</param> + /// <param name="derivedType">The derived type.</param> + /// <returns>true if the member is visible from derived type, othewise false.</returns> + public static bool IsVisibleFromDerived(IMemberDefinition baseMember, TypeDefinition derivedType) + { + if (baseMember == null) + throw new ArgumentNullException("baseMember"); + if (derivedType == null) + throw new ArgumentNullException("derivedType"); + + MethodAttributes attrs = GetAccessAttributes(baseMember) & MethodAttributes.MemberAccessMask; + if (attrs == MethodAttributes.Private) + return false; + + if (baseMember.DeclaringType.Module == derivedType.Module) + return true; + + if (attrs == MethodAttributes.Assembly || attrs == MethodAttributes.FamANDAssem) { + var derivedTypeAsm = derivedType.Module.Assembly; + var asm = baseMember.DeclaringType.Module.Assembly; + + if (asm.HasCustomAttributes) { + var attributes = asm.CustomAttributes + .Where(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"); + foreach (var attribute in attributes) { + string assemblyName = attribute.ConstructorArguments[0].Value as string; + assemblyName = assemblyName.Split(',')[0]; // strip off any public key info + if (assemblyName == derivedTypeAsm.Name.Name) + return true; + } + } + + return false; + } + + return true; + } + + static MethodAttributes GetAccessAttributes(IMemberDefinition member) + { + var fld = member as FieldDefinition; + if (fld != null) + return (MethodAttributes)fld.Attributes; + + var method = member as MethodDefinition; + if (method != null) + return method.Attributes; + + var prop = member as PropertyDefinition; + if (prop != null) { + return (prop.GetMethod ?? prop.SetMethod).Attributes; + } + + var evnt = member as EventDefinition; + if (evnt != null) { + return (evnt.AddMethod ?? evnt.RemoveMethod).Attributes; + } + + var nestedType = member as TypeDefinition; + if (nestedType != null) { + if (nestedType.IsNestedPrivate) + return MethodAttributes.Private; + if (nestedType.IsNestedAssembly || nestedType.IsNestedFamilyAndAssembly) + return MethodAttributes.Assembly; + return MethodAttributes.Public; + } + + throw new NotSupportedException(); + } + + static bool MatchMethod(GenericContext<MethodDefinition> candidate, GenericContext<MethodDefinition> method) + { + var mCandidate = candidate.Item; + var mMethod = method.Item; + if (mCandidate.Name != mMethod.Name) + return false; + + if (mCandidate.HasOverrides) + return false; + + if (mCandidate.IsSpecialName != method.Item.IsSpecialName) + return false; + + if (mCandidate.HasGenericParameters || mMethod.HasGenericParameters) { + if (!mCandidate.HasGenericParameters || !mMethod.HasGenericParameters || mCandidate.GenericParameters.Count != mMethod.GenericParameters.Count) + return false; + } + + if (mCandidate.HasParameters || mMethod.HasParameters) { + if (!mCandidate.HasParameters || !mMethod.HasParameters || mCandidate.Parameters.Count != mMethod.Parameters.Count) + return false; + + for (int index = 0; index < mCandidate.Parameters.Count; index++) { + if (!MatchParameters(candidate.ApplyTo(mCandidate.Parameters[index]), method.ApplyTo(mMethod.Parameters[index]))) + return false; + } + } + + return true; + } + + public static bool MatchInterfaceMethod(MethodDefinition candidate, MethodDefinition method, TypeReference interfaceContextType) + { + var candidateContext = CreateGenericContext(candidate.DeclaringType); + var gCandidate = candidateContext.ApplyTo(candidate); + + if (interfaceContextType is GenericInstanceType) { + var methodContext = new GenericContext<TypeDefinition>(interfaceContextType.Resolve(), ((GenericInstanceType)interfaceContextType).GenericArguments); + var gMethod = methodContext.ApplyTo(method); + return MatchMethod(gCandidate, gMethod); + } else { + var methodContext = CreateGenericContext(interfaceContextType.Resolve()); + var gMethod = candidateContext.ApplyTo(method); + return MatchMethod(gCandidate, gMethod); + } + } + + static bool MatchProperty(GenericContext<PropertyDefinition> candidate, GenericContext<PropertyDefinition> property) + { + var mCandidate = candidate.Item; + var mProperty = property.Item; + if (mCandidate.Name != mProperty.Name) + return false; + + if ((mCandidate.GetMethod ?? mCandidate.SetMethod).HasOverrides) + return false; + + if (mCandidate.HasParameters || mProperty.HasParameters) { + if (!mCandidate.HasParameters || !mProperty.HasParameters || mCandidate.Parameters.Count != mProperty.Parameters.Count) + return false; + + for (int index = 0; index < mCandidate.Parameters.Count; index++) { + if (!MatchParameters(candidate.ApplyTo(mCandidate.Parameters[index]), property.ApplyTo(mProperty.Parameters[index]))) + return false; + } + } + + return true; + } + + static bool MatchEvent(GenericContext<EventDefinition> candidate, GenericContext<EventDefinition> ev) + { + var mCandidate = candidate.Item; + var mEvent = ev.Item; + if (mCandidate.Name != mEvent.Name) + return false; + + if ((mCandidate.AddMethod ?? mCandidate.RemoveMethod).HasOverrides) + return false; + + if (!IsSameType(candidate.ResolveWithContext(mCandidate.EventType), ev.ResolveWithContext(mEvent.EventType))) + return false; + + return true; + } + + static bool MatchParameters(GenericContext<ParameterDefinition> baseParameterType, GenericContext<ParameterDefinition> parameterType) + { + if (baseParameterType.Item.IsIn != parameterType.Item.IsIn || + baseParameterType.Item.IsOut != parameterType.Item.IsOut) + return false; + var baseParam = baseParameterType.ResolveWithContext(baseParameterType.Item.ParameterType); + var param = parameterType.ResolveWithContext(parameterType.Item.ParameterType); + return IsSameType(baseParam, param); + } + + static bool IsSameType(TypeReference tr1, TypeReference tr2) + { + if (tr1 == tr2) + return true; + if (tr1 == null || tr2 == null) + return false; + + if (tr1.GetType() != tr2.GetType()) + return false; + + if (tr1.Name == tr2.Name && tr1.FullName == tr2.FullName) + return true; + + return false; + } + + static IEnumerable<GenericContext<TypeDefinition>> BaseTypes(TypeDefinition type) + { + return BaseTypes(CreateGenericContext(type)); + } + + static IEnumerable<GenericContext<TypeDefinition>> BaseTypes(GenericContext<TypeDefinition> type) + { + while (type.Item.BaseType != null) { + var baseType = type.Item.BaseType; + var genericBaseType = baseType as GenericInstanceType; + if (genericBaseType != null) { + type = new GenericContext<TypeDefinition>(genericBaseType.ResolveOrThrow(), + genericBaseType.GenericArguments.Select(t => type.ResolveWithContext(t))); + } else + type = new GenericContext<TypeDefinition>(baseType.ResolveOrThrow()); + yield return type; + } + } + + static GenericContext<TypeDefinition> CreateGenericContext(TypeDefinition type) + { + return type.HasGenericParameters + ? new GenericContext<TypeDefinition>(type, type.GenericParameters) + : new GenericContext<TypeDefinition>(type); + } + + struct GenericContext<T> where T : class + { + static readonly ReadOnlyCollection<TypeReference> Empty = new ReadOnlyCollection<TypeReference>(new List<TypeReference>()); + + static readonly GenericParameter UnresolvedGenericTypeParameter = + new DummyGenericParameterProvider(false).DummyParameter; + + static readonly GenericParameter UnresolvedGenericMethodParameter = + new DummyGenericParameterProvider(true).DummyParameter; + + public readonly T Item; + public readonly ReadOnlyCollection<TypeReference> TypeArguments; + + public GenericContext(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + + Item = item; + TypeArguments = Empty; + } + + public GenericContext(T item, IEnumerable<TypeReference> typeArguments) + { + if (item == null) + throw new ArgumentNullException("item"); + + Item = item; + var list = new List<TypeReference>(); + foreach (var arg in typeArguments) { + var resolved = arg != null ? arg.Resolve() : arg; + list.Add(resolved != null ? resolved : arg); + } + TypeArguments = new ReadOnlyCollection<TypeReference>(list); + } + + GenericContext(T item, ReadOnlyCollection<TypeReference> typeArguments) + { + Item = item; + TypeArguments = typeArguments; + } + + public TypeReference ResolveWithContext(TypeReference type) + { + var genericParameter = type as GenericParameter; + if (genericParameter != null) + if (genericParameter.Owner.GenericParameterType == GenericParameterType.Type) + return TypeArguments[genericParameter.Position]; + else + return genericParameter.Owner.GenericParameterType == GenericParameterType.Type + ? UnresolvedGenericTypeParameter : UnresolvedGenericMethodParameter; + var typeSpecification = type as TypeSpecification; + if (typeSpecification != null) { + var resolvedElementType = ResolveWithContext(typeSpecification.ElementType); + return ReplaceElementType(typeSpecification, resolvedElementType); + } + return type.ResolveOrThrow(); + } + + TypeReference ReplaceElementType(TypeSpecification ts, TypeReference newElementType) + { + var arrayType = ts as ArrayType; + if (arrayType != null) { + if (newElementType == arrayType.ElementType) + return arrayType; + var newArrayType = new ArrayType(newElementType, arrayType.Rank); + for (int dimension = 0; dimension < arrayType.Rank; dimension++) + newArrayType.Dimensions[dimension] = arrayType.Dimensions[dimension]; + return newArrayType; + } + var byReferenceType = ts as ByReferenceType; + if (byReferenceType != null) { + return new ByReferenceType(newElementType); + } + // TODO: should we throw an exception instead calling Resolve method? + return ts.ResolveOrThrow(); + } + + public GenericContext<T2> ApplyTo<T2>(T2 item) where T2 : class + { + return new GenericContext<T2>(item, TypeArguments); + } + + class DummyGenericParameterProvider : IGenericParameterProvider + { + readonly GenericParameterType type; + readonly Mono.Collections.Generic.Collection<GenericParameter> parameters; + + public DummyGenericParameterProvider(bool methodTypeParameter) + { + type = methodTypeParameter ? GenericParameterType.Method : + GenericParameterType.Type; + parameters = new Mono.Collections.Generic.Collection<GenericParameter>(1); + parameters.Add(new GenericParameter(this)); + } + + public GenericParameter DummyParameter + { + get { return parameters[0]; } + } + + bool IGenericParameterProvider.HasGenericParameters + { + get { throw new NotImplementedException(); } + } + + bool IGenericParameterProvider.IsDefinition + { + get { throw new NotImplementedException(); } + } + + ModuleDefinition IGenericParameterProvider.Module + { + get { throw new NotImplementedException(); } + } + + Mono.Collections.Generic.Collection<GenericParameter> IGenericParameterProvider.GenericParameters + { + get { return parameters; } + } + + GenericParameterType IGenericParameterProvider.GenericParameterType + { + get { return type; } + } + + MetadataToken IMetadataTokenProvider.MetadataToken + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + } + } + } +} |