diff --git a/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 0b12dff5b..ee5e00268 100644 --- a/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -215,6 +215,7 @@ + IBackgroundRenderer.cs diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index b4860eb7d..c0258db0b 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -125,8 +125,28 @@ namespace ICSharpCode.Decompiler.Ast public void AddAssembly(AssemblyDefinition assemblyDefinition, bool onlyAssemblyLevel = false) { + if (assemblyDefinition.Name.Version != null) { + astCompileUnit.AddChild( + new AttributeSection { + AttributeTarget = "assembly", + Attributes = { + new NRefactory.CSharp.Attribute { + Type = new SimpleType("AssemblyVersion") + .WithAnnotation(new TypeReference( + "System.Reflection", "AssemblyVersionAttribute", + assemblyDefinition.MainModule, assemblyDefinition.MainModule.TypeSystem.Corlib)), + Arguments = { + new PrimitiveExpression(assemblyDefinition.Name.Version.ToString()) + } + } + } + }, AttributedNode.AttributeRole); + } + ConvertCustomAttributes(astCompileUnit, assemblyDefinition, "assembly"); + ConvertSecurityAttributes(astCompileUnit, assemblyDefinition, "assembly"); ConvertCustomAttributes(astCompileUnit, assemblyDefinition.MainModule, "module"); + AddTypeForwarderAttributes(astCompileUnit, assemblyDefinition.MainModule, "assembly"); if (!onlyAssemblyLevel) { foreach (TypeDefinition typeDef in assemblyDefinition.MainModule.Types) { @@ -141,6 +161,30 @@ namespace ICSharpCode.Decompiler.Ast } } + void AddTypeForwarderAttributes(CompilationUnit 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 } + } + } + }, AttributedNode.AttributeRole); + } + } + } + NamespaceDeclaration GetCodeNamespace(string name) { if (string.IsNullOrEmpty(name)) { @@ -228,15 +272,6 @@ namespace ICSharpCode.Decompiler.Ast astType.TypeParameters.AddRange(MakeTypeParameters(genericParameters)); astType.Constraints.AddRange(MakeConstraints(genericParameters)); - // Nested types - foreach (TypeDefinition nestedTypeDef in typeDef.NestedTypes) { - if (MemberIsHidden(nestedTypeDef, context.Settings)) - continue; - var nestedType = CreateType(nestedTypeDef); - SetNewModifier(nestedType); - astType.AddChild(nestedType, TypeDeclaration.MemberRole); - } - AttributedNode result = astType; if (typeDef.IsEnum) { long expectedEnumMemberValue = 0; @@ -314,6 +349,45 @@ namespace ICSharpCode.Decompiler.Ast return name; } + #region Create TypeOf Expression + /// + /// Creates a typeof-expression for the specified type. + /// + public static TypeOfExpression CreateTypeOfExpression(TypeReference type) + { + return new TypeOfExpression(AddEmptyTypeArgumentsForUnboundGenerics(ConvertType(type))); + } + + static AstType AddEmptyTypeArgumentsForUnboundGenerics(AstType type) + { + TypeReference typeRef = type.Annotation(); + if (typeRef == null) + return type; + TypeDefinition typeDef = typeRef.Resolve(); // need to resolve to figure out the number of type parameters + if (typeDef == null || !typeDef.HasGenericParameters) + return type; + SimpleType sType = type as SimpleType; + MemberType mType = type as MemberType; + if (sType != null) { + while (typeDef.GenericParameters.Count > sType.TypeArguments.Count) { + sType.TypeArguments.Add(new SimpleType("")); + } + } + + if (mType != null) { + AddEmptyTypeArgumentsForUnboundGenerics(mType.Target); + + int outerTypeParamCount = typeDef.DeclaringType == null ? 0 : typeDef.DeclaringType.GenericParameters.Count; + + while (typeDef.GenericParameters.Count - outerTypeParamCount > mType.TypeArguments.Count) { + mType.TypeArguments.Add(new SimpleType("")); + } + } + + return type; + } + #endregion + #region Convert Type Reference /// /// Converts a type reference. @@ -597,6 +671,15 @@ namespace ICSharpCode.Decompiler.Ast 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, TypeDeclaration.MemberRole); + } + // Add fields foreach(FieldDefinition fieldDef in typeDef.Fields) { if (MemberIsHidden(fieldDef, context.Settings)) continue; @@ -729,6 +812,9 @@ namespace ICSharpCode.Decompiler.Ast astMethod.Body = CreateMethodBody(methodDef, astMethod.Parameters); ConvertAttributes(astMethod, methodDef); astMethod.WithAnnotation(methodMapping); + if (methodDef.IsStatic && methodDef.DeclaringType.IsBeforeFieldInit && !astMethod.Body.IsNull) { + astMethod.Body.InsertChildAfter(null, new Comment(" Note: this type is marked as 'beforefieldinit'."), AstNode.Roles.Comment); + } return astMethod; } @@ -798,7 +884,13 @@ namespace ICSharpCode.Decompiler.Ast astProp.Setter.Body = CreateMethodBody(propDef.SetMethod); astProp.Setter.AddAnnotation(propDef.SetMethod); ConvertAttributes(astProp.Setter, propDef.SetMethod); - ConvertCustomAttributes(astProp.Setter, propDef.SetMethod.Parameters.Last(), "param"); + 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; @@ -981,6 +1073,7 @@ namespace ICSharpCode.Decompiler.Ast void ConvertAttributes(AttributedNode attributedNode, TypeDefinition typeDefinition) { ConvertCustomAttributes(attributedNode, typeDefinition); + ConvertSecurityAttributes(attributedNode, typeDefinition); // Handle the non-custom attributes: #region SerializableAttribute @@ -1031,6 +1124,7 @@ namespace ICSharpCode.Decompiler.Ast void ConvertAttributes(AttributedNode attributedNode, MethodDefinition methodDefinition) { ConvertCustomAttributes(attributedNode, methodDefinition); + ConvertSecurityAttributes(attributedNode, methodDefinition); MethodImplAttributes implAttributes = methodDefinition.ImplAttributes & ~MethodImplAttributes.CodeTypeMask; @@ -1167,6 +1261,37 @@ namespace ICSharpCode.Decompiler.Ast 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 @@ -1256,6 +1381,65 @@ namespace ICSharpCode.Decompiler.Ast } } + static void ConvertSecurityAttributes(AstNode attributedNode, ISecurityDeclarationProvider secDeclProvider, string attributeTarget = null) + { + if (!secDeclProvider.HasSecurityDeclarations) + return; + var attributes = new List(); + foreach (var secDecl in secDeclProvider.SecurityDeclarations) { + foreach (var secAttribute in secDecl.SecurityAttributes) { + var attribute = new ICSharpCode.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, AttributedNode.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, AttributedNode.AttributeRole); + } + } + private static Expression ConvertArgumentValue(CustomAttributeArgument argument) { if (argument.Value is CustomAttributeArgument[]) { @@ -1276,9 +1460,7 @@ namespace ICSharpCode.Decompiler.Ast if (type != null && type.IsEnum) { return MakePrimitive(Convert.ToInt64(argument.Value), type); } else if (argument.Value is TypeReference) { - return new TypeOfExpression() { - Type = ConvertType((TypeReference)argument.Value), - }; + return CreateTypeOfExpression((TypeReference)argument.Value); } else { return new PrimitiveExpression(argument.Value); } @@ -1433,7 +1615,7 @@ namespace ICSharpCode.Decompiler.Ast if (baseType.HasFields && AnyIsHiddenBy(baseType.Fields, member)) return true; if (includeBaseMethods && baseType.HasMethods - && AnyIsHiddenBy(baseType.Methods, member, m => !m.IsSpecialName)) + && AnyIsHiddenBy(baseType.Methods, member, m => !m.IsSpecialName)) return true; if (baseType.HasNestedTypes && AnyIsHiddenBy(baseType.NestedTypes, member)) return true; @@ -1447,8 +1629,8 @@ namespace ICSharpCode.Decompiler.Ast where T : IMemberDefinition { return members.Any(m => m.Name == derived.Name - && (condition == null || condition(m)) - && TypesHierarchyHelpers.IsVisibleFromDerived(m, derived.DeclaringType)); + && (condition == null || condition(m)) + && TypesHierarchyHelpers.IsVisibleFromDerived(m, derived.DeclaringType)); } /// diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index b3deb77d2..0e0e8dbd3 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -490,9 +490,12 @@ namespace ICSharpCode.Decompiler.Ast return arg1; else return arg1.CastTo(operandAsTypeRef); - case ILCode.Isinst: return arg1.CastAs(operandAsTypeRef); - case ILCode.Box: return arg1; - case ILCode.Unbox: return InlineAssembly(byteCode, args); + 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: @@ -544,11 +547,7 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Endfilter: return InlineAssembly(byteCode, args); case ILCode.Endfinally: return null; case ILCode.Initblk: return InlineAssembly(byteCode, args); - case ILCode.Initobj: - if (args[0] is DirectionExpression) - return new AssignmentExpression(((DirectionExpression)args[0]).Expression.Detach(), MakeDefaultValue((TypeReference)operand)); - else - 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); @@ -607,7 +606,7 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Ldstr: return new Ast.PrimitiveExpression(operand); case ILCode.Ldtoken: if (operand is Cecil.TypeReference) { - return new Ast.TypeOfExpression { Type = operandAsTypeRef }.Member("TypeHandle"); + return AstBuilder.CreateTypeOfExpression((TypeReference)operand).Member("TypeHandle"); } else { return InlineAssembly(byteCode, args); } diff --git a/ICSharpCode.Decompiler/Ast/NameVariables.cs b/ICSharpCode.Decompiler/Ast/NameVariables.cs index 66f50ff13..c7ac8f385 100644 --- a/ICSharpCode.Decompiler/Ast/NameVariables.cs +++ b/ICSharpCode.Decompiler/Ast/NameVariables.cs @@ -279,6 +279,8 @@ namespace ICSharpCode.Decompiler.Ast 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 diff --git a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs index 1e6915872..d0edf5bcc 100644 --- a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs @@ -17,6 +17,7 @@ namespace ICSharpCode.Decompiler.Ast readonly ITextOutput output; readonly Stack nodeStack = new Stack(); int braceLevelWithinType = -1; + bool inDocumentationComment = false; public TextOutputFormatter(ITextOutput output) { @@ -115,8 +116,17 @@ namespace ICSharpCode.Decompiler.Ast output.Write("*/"); break; case CommentType.Documentation: + if (!inDocumentationComment) + output.MarkFoldStart("///" + content, true); output.Write("///"); - output.WriteLine(content); + output.Write(content); + inDocumentationComment = true; + bool isLastLine = !(nodeStack.Peek().NextSibling is Comment); + if (isLastLine) { + inDocumentationComment = false; + output.MarkFoldEnd(); + } + output.WriteLine(); break; } } @@ -127,7 +137,7 @@ namespace ICSharpCode.Decompiler.Ast if (ranges != null && ranges.Count > 0) { // find the ancestor that has method mapping as annotation - if (node.Ancestors != null && node.Ancestors.Count() > 0) + if (node.Parent != null) { var n = node.Ancestors.FirstOrDefault(a => a.Annotation() != null); if (n != null) { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs index 716754595..054d3066e 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ConvertConstructorCallIntoInitializer.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Linq; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.PatternMatching; @@ -69,9 +70,29 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) { - var instanceCtors = typeDeclaration.Members.OfType().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); + // 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 members) + { + var instanceCtors = members.OfType().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => !thisCallPattern.IsMatch(ctor.Body.Statements.FirstOrDefault())).ToArray(); - if (instanceCtorsNotChainingWithThis.Length > 0 && typeDeclaration.ClassType == NRefactory.TypeSystem.ClassType.Class) { + if (instanceCtorsNotChainingWithThis.Length > 0) { + MethodDefinition ctorMethodDef = instanceCtorsNotChainingWithThis[0].Annotation(); + 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; @@ -83,7 +104,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms FieldDefinition fieldDef = m.Get("fieldAccess").Single().Annotation().ResolveWithinSameModule(); if (fieldDef == null) break; - AttributedNode fieldOrEventDecl = typeDeclaration.Members.FirstOrDefault(f => f.Annotation() == fieldDef); + AstNode fieldOrEventDecl = members.FirstOrDefault(f => f.Annotation() == fieldDef); if (fieldOrEventDecl == null) break; @@ -99,11 +120,11 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } } while (allSame); } - - // Now convert base constructor calls to initializers: - base.VisitTypeDeclaration(typeDeclaration, data); - - // Remove single empty constructor: + } + + void RemoveSingleEmptyConstructor(TypeDeclaration typeDeclaration) + { + var instanceCtors = typeDeclaration.Members.OfType().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); @@ -111,12 +132,15 @@ namespace ICSharpCode.Decompiler.Ast.Transforms if (emptyCtor.IsMatch(instanceCtors[0])) instanceCtors[0].Remove(); } - + } + + void HandleStaticFieldInitializers(IEnumerable members) + { // Convert static constructor into field initializers if the class is BeforeFieldInit - var staticCtor = typeDeclaration.Members.OfType().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static); + var staticCtor = members.OfType().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static); if (staticCtor != null) { - TypeDefinition typeDef = typeDeclaration.Annotation(); - if (typeDef != null && typeDef.IsBeforeFieldInit) { + MethodDefinition ctorMethodDef = staticCtor.Annotation(); + if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsBeforeFieldInit) { while (true) { ExpressionStatement es = staticCtor.Body.Statements.FirstOrDefault() as ExpressionStatement; if (es == null) @@ -127,7 +151,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms FieldDefinition fieldDef = assignment.Left.Annotation().ResolveWithinSameModule(); if (fieldDef == null || !fieldDef.IsStatic) break; - FieldDeclaration fieldDecl = typeDeclaration.Members.OfType().FirstOrDefault(f => f.Annotation() == fieldDef); + FieldDeclaration fieldDecl = members.OfType().FirstOrDefault(f => f.Annotation() == fieldDef); if (fieldDecl == null) break; fieldDecl.Variables.Single().Initializer = assignment.Right.Detach(); @@ -137,11 +161,15 @@ namespace ICSharpCode.Decompiler.Ast.Transforms staticCtor.Remove(); } } - return null; } 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/DecimalConstantTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/DecimalConstantTransform.cs new file mode 100644 index 000000000..298682afb --- /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 +{ + /// + /// Transforms decimal constant fields. + /// + public class DecimalConstantTransform : DepthFirstAstVisitor, 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(); + 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/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 6a5256b44..85ac9da1e 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) { - if (objectCreateExpression.Arguments.Count() == 2) { + if (objectCreateExpression.Arguments.Count == 2) { Expression obj = objectCreateExpression.Arguments.First(); Expression func = objectCreateExpression.Arguments.Last(); Annotation annotation = func.Annotation(); @@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms internal static bool IsAnonymousMethod(DecompilerContext context, MethodDefinition method) { - if (method == null || !method.Name.StartsWith("<", StringComparison.Ordinal)) + if (method == null || !(method.Name.StartsWith("<", StringComparison.Ordinal) || method.Name.Contains("$"))) return false; if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType))) return false; @@ -388,10 +388,13 @@ namespace ICSharpCode.Decompiler.Ast.Transforms continue; // skip static fields if (dict.ContainsKey(field)) // skip field if it already was handled as parameter continue; - EnsureVariableNameIsAvailable(blockStatement, field.Name); - currentlyUsedVariableNames.Add(field.Name); - variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), field.Name)); - dict[field] = new IdentifierExpression(field.Name); + string capturedVariableName = field.Name; + if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10) + capturedVariableName = capturedVariableName.Substring(10); + EnsureVariableNameIsAvailable(blockStatement, capturedVariableName); + currentlyUsedVariableNames.Add(capturedVariableName); + variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), capturedVariableName)); + dict[field] = new IdentifierExpression(capturedVariableName); } // Now figure out where the closure was accessed and use the simpler replacement expression there: diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index bc17d13cc..3d5c0e596 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } static readonly AstNode usingTryCatchPattern = new TryCatchStatement { - TryBlock = new AnyNode("body"), + TryBlock = new AnyNode(), FinallyBlock = new BlockStatement { new Choice { { "valueType", @@ -180,44 +180,60 @@ namespace ICSharpCode.Decompiler.Ast.Transforms { Match m1 = variableAssignPattern.Match(node); if (!m1.Success) return null; - AstNode tryCatch = node.NextSibling; + TryCatchStatement tryCatch = node.NextSibling as TryCatchStatement; Match m2 = usingTryCatchPattern.Match(tryCatch); if (!m2.Success) return null; string variableName = m1.Get("variable").Single().Identifier; - if (variableName == m2.Get("ident").Single().Identifier) { - if (m2.Has("valueType")) { - // if there's no if(x!=null), then it must be a value type - ILVariable v = m1.Get("variable").Single().Annotation(); - if (v == null || v.Type == null || !v.Type.IsValueType) - return null; - } - node.Remove(); - BlockStatement body = m2.Get("body").Single(); - UsingStatement usingStatement = new UsingStatement(); - usingStatement.ResourceAcquisition = node.Expression.Detach(); - usingStatement.EmbeddedStatement = body.Detach(); - tryCatch.ReplaceWith(usingStatement); - // Move the variable declaration into the resource acquisition, if possible - // This is necessary for the foreach-pattern to work on the result of the using-pattern - VariableDeclarationStatement varDecl = FindVariableDeclaration(usingStatement, variableName); - if (varDecl != null && varDecl.Parent is BlockStatement) { - Statement declarationPoint; - if (CanMoveVariableDeclarationIntoStatement(varDecl, usingStatement, out declarationPoint)) { - // Moving the variable into the UsingStatement is allowed: - usingStatement.ResourceAcquisition = new VariableDeclarationStatement { - Type = (AstType)varDecl.Type.Clone(), - Variables = { - new VariableInitializer { - Name = variableName, - Initializer = m1.Get("initializer").Single().Detach() - }.CopyAnnotationsFrom(usingStatement.ResourceAcquisition) - } - }.CopyAnnotationsFrom(node); + if (variableName != m2.Get("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("variable").Single().Annotation(); + 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().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("initializer").Single().Detach() + }.CopyAnnotationsFrom(node.Expression) } - } - return usingStatement; + }.CopyAnnotationsFrom(node); + } else { + // the variable is never used; eliminate it: + usingStatement.ResourceAcquisition = m1.Get("initializer").Single().Detach(); } - return null; + return usingStatement; } internal static VariableDeclarationStatement FindVariableDeclaration(AstNode node, string identifier) @@ -235,6 +251,29 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return null; } + /// + /// Gets whether the old variable value (assigned inside 'targetStatement' or earlier) + /// is read anywhere in the remaining scope of the variable declaration. + /// + 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)); @@ -251,6 +290,24 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } return true; } + + /// + /// Gets whether there is an assignment to 'variableName' anywhere within the given node. + /// + 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) @@ -648,7 +705,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms // 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 (!m.Get("switchVar").Single().IsMatch(assign.Left)) + if (!(assign != null && m.Get("switchVar").Single().IsMatch(assign.Left))) return null; } FieldReference cachedDictField = m.Get("cachedDict").Single().Annotation(); diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs index 7a9eaa365..ba641e77d 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/ReplaceMethodCallsWithOperators.cs @@ -50,7 +50,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms var arguments = invocationExpression.Arguments.ToArray(); // Reduce "String.Concat(a, b)" to "a + b" - if (methodRef != null && methodRef.Name == "Concat" && methodRef.DeclaringType.FullName == "System.String" && arguments.Length >= 2) + if (methodRef.Name == "Concat" && methodRef.DeclaringType.FullName == "System.String" && arguments.Length >= 2) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression Expression expr = arguments[0]; @@ -97,7 +97,10 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return null; } if (methodRef.Name == "op_Implicit" && arguments.Length == 1) { - arguments[0].Remove(); // detach argument + invocationExpression.ReplaceWith(arguments[0]); + return null; + } + if (methodRef.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == AstNode.Roles.Condition) { invocationExpression.ReplaceWith(arguments[0]); return null; } diff --git a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs index 686a73a61..13730ea1d 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs @@ -40,6 +40,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms 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 diff --git a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 0060b71d8..df66c2650 100644 --- a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -17,11 +17,32 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.Disassembler { + public enum ILNameSyntax + { + /// + /// class/valuetype + TypeName (built-in types use keyword syntax) + /// + Signature, + /// + /// Like signature, but always refers to type parameters using their position + /// + SignatureNoNamedTypeParameters, + /// + /// [assembly]Full.Type.Name (even for built-in types) + /// + TypeName, + /// + /// Name (but built-in types use keyword syntax) + /// + ShortTypeName + } + public static class DisassemblerHelpers { public static void WriteOffsetReference(ITextOutput writer, Instruction instruction) @@ -35,6 +56,7 @@ namespace ICSharpCode.Decompiler.Disassembler WriteOffsetReference(writer, exceptionHandler.TryStart); writer.Write('-'); WriteOffsetReference(writer, exceptionHandler.TryEnd); + writer.Write(' '); writer.Write(exceptionHandler.HandlerType.ToString()); if (exceptionHandler.FilterStart != null) { writer.Write(' '); @@ -56,8 +78,14 @@ namespace ICSharpCode.Decompiler.Disassembler writer.WriteDefinition(CecilExtensions.OffsetToString(instruction.Offset), instruction); writer.Write(": "); writer.WriteReference(instruction.OpCode.Name, instruction.OpCode); - if(null != instruction.Operand) { + if (instruction.Operand != null) { writer.Write(' '); + if (instruction.OpCode == OpCodes.Ldtoken) { + if (instruction.Operand is MethodReference) + writer.Write("method "); + else if (instruction.Operand is FieldReference) + writer.Write("field "); + } WriteOperand(writer, instruction.Operand); } } @@ -82,46 +110,131 @@ namespace ICSharpCode.Decompiler.Disassembler public static void WriteTo(this MethodReference method, ITextOutput writer) { - if (method.HasThis) + if (method.ExplicitThis) { + writer.Write("instance explicit "); + } + else if (method.HasThis) { writer.Write("instance "); - method.ReturnType.WriteTo(writer); + } + method.ReturnType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); writer.Write(' '); if (method.DeclaringType != null) { - method.DeclaringType.WriteTo(writer, true); + method.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write("::"); } - writer.WriteReference(method.Name, method); + MethodDefinition md = method as MethodDefinition; + if (md != null && md.IsCompilerControlled) { + writer.WriteReference(Escape(method.Name + "$PST" + method.MetadataToken.ToInt32().ToString("X8")), method); + } else { + writer.WriteReference(Escape(method.Name), method); + } + GenericInstanceMethod gim = method as GenericInstanceMethod; + if (gim != null) { + writer.Write('<'); + for (int i = 0; i < gim.GenericArguments.Count; i++) { + if (i > 0) + writer.Write(", "); + gim.GenericArguments[i].WriteTo(writer); + } + writer.Write('>'); + } writer.Write("("); var parameters = method.Parameters; for(int i = 0; i < parameters.Count; ++i) { if (i > 0) writer.Write(", "); - parameters[i].ParameterType.WriteTo(writer); + parameters[i].ParameterType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); } writer.Write(")"); } static void WriteTo(this FieldReference field, ITextOutput writer) { - field.FieldType.WriteTo(writer); + field.FieldType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); writer.Write(' '); - field.DeclaringType.WriteTo(writer); + field.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write("::"); - writer.WriteReference(field.Name, field); + writer.WriteReference(Escape(field.Name), field); + } + + static bool IsValidIdentifierCharacter(char c) + { + return c == '_' || c == '$' || c == '@' || c == '?' || c == '`'; + } + + static bool IsValidIdentifier(string identifier) + { + if (string.IsNullOrEmpty(identifier)) + return false; + if (!(char.IsLetter(identifier[0]) || IsValidIdentifierCharacter(identifier[0]))) { + // As a special case, .ctor and .cctor are valid despite starting with a dot + return identifier == ".ctor" || identifier == ".cctor"; + } + for (int i = 1; i < identifier.Length; i++) { + if (!(char.IsLetterOrDigit(identifier[i]) || IsValidIdentifierCharacter(identifier[i]) || identifier[i] == '.')) + return false; + } + return true; + } + + static readonly HashSet ilKeywords = BuildKeywordList( + "abstract", "algorithm", "alignment", "ansi", "any", "arglist", + "array", "as", "assembly", "assert", "at", "auto", "autochar", "beforefieldinit", + "blob", "blob_object", "bool", "brnull", "brnull.s", "brzero", "brzero.s", "bstr", + "bytearray", "byvalstr", "callmostderived", "carray", "catch", "cdecl", "cf", + "char", "cil", "class", "clsid", "const", "currency", "custom", "date", "decimal", + "default", "demand", "deny", "endmac", "enum", "error", "explicit", "extends", "extern", + "false", "famandassem", "family", "famorassem", "fastcall", "fault", "field", "filetime", + "filter", "final", "finally", "fixed", "float", "float32", "float64", "forwardref", + "fromunmanaged", "handler", "hidebysig", "hresult", "idispatch", "il", "illegal", + "implements", "implicitcom", "implicitres", "import", "in", "inheritcheck", "init", + "initonly", "instance", "int", "int16", "int32", "int64", "int8", "interface", "internalcall", + "iunknown", "lasterr", "lcid", "linkcheck", "literal", "localloc", "lpstr", "lpstruct", "lptstr", + "lpvoid", "lpwstr", "managed", "marshal", "method", "modopt", "modreq", "native", "nested", + "newslot", "noappdomain", "noinlining", "nomachine", "nomangle", "nometadata", "noncasdemand", + "noncasinheritance", "noncaslinkdemand", "noprocess", "not", "not_in_gc_heap", "notremotable", + "notserialized", "null", "nullref", "object", "objectref", "opt", "optil", "out", + "permitonly", "pinned", "pinvokeimpl", "prefix1", "prefix2", "prefix3", "prefix4", "prefix5", "prefix6", + "prefix7", "prefixref", "prejitdeny", "prejitgrant", "preservesig", "private", "privatescope", "protected", + "public", "record", "refany", "reqmin", "reqopt", "reqrefuse", "reqsecobj", "request", "retval", + "rtspecialname", "runtime", "safearray", "sealed", "sequential", "serializable", "special", "specialname", + "static", "stdcall", "storage", "stored_object", "stream", "streamed_object", "string", "struct", + "synchronized", "syschar", "sysstring", "tbstr", "thiscall", "tls", "to", "true", "typedref", + "unicode", "unmanaged", "unmanagedexp", "unsigned", "unused", "userdefined", "value", "valuetype", + "vararg", "variant", "vector", "virtual", "void", "wchar", "winapi", "with", "wrapper", + + // These are not listed as keywords in spec, but ILAsm treats them as such + "property", "type", "flags", "callconv", "strict" + ); + + static HashSet BuildKeywordList(params string[] keywords) + { + HashSet s = new HashSet(keywords); + foreach (var field in typeof(OpCodes).GetFields()) { + s.Add(((OpCode)field.GetValue(null)).Name); + } + return s; } public static string Escape(string identifier) { - return identifier; + if (IsValidIdentifier(identifier) && !ilKeywords.Contains(identifier)) { + return identifier; + } else { + // The ECMA specification says that ' inside SQString should be ecaped using an octal escape sequence, + // but we follow Microsoft's ILDasm and use \'. + return "'" + NRefactory.CSharp.OutputVisitor.ConvertString(identifier).Replace("'", "\\'") + "'"; + } } - public static void WriteTo(this TypeReference type, ITextOutput writer, bool onlyName = false, bool shortName = false) + public static void WriteTo(this TypeReference type, ITextOutput writer, ILNameSyntax syntax = ILNameSyntax.Signature) { + ILNameSyntax syntaxForElementTypes = syntax == ILNameSyntax.SignatureNoNamedTypeParameters ? syntax : ILNameSyntax.Signature; if (type is PinnedType) { - writer.Write("pinned "); - ((PinnedType)type).ElementType.WriteTo(writer, onlyName, shortName); + ((PinnedType)type).ElementType.WriteTo(writer, syntaxForElementTypes); + writer.Write(" pinned"); } else if (type is ArrayType) { ArrayType at = (ArrayType)type; - at.ElementType.WriteTo(writer, onlyName, shortName); + at.ElementType.WriteTo(writer, syntaxForElementTypes); writer.Write('['); writer.Write(string.Join(", ", at.Dimensions)); writer.Write(']'); @@ -129,49 +242,57 @@ namespace ICSharpCode.Decompiler.Disassembler writer.Write('!'); if (((GenericParameter)type).Owner.GenericParameterType == GenericParameterType.Method) writer.Write('!'); - writer.Write(type.Name); + if (string.IsNullOrEmpty(type.Name) || type.Name[0] == '!' || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) + writer.Write(((GenericParameter)type).Position.ToString()); + else + writer.Write(Escape(type.Name)); } else if (type is ByReferenceType) { - ((ByReferenceType)type).ElementType.WriteTo(writer, onlyName, shortName); + ((ByReferenceType)type).ElementType.WriteTo(writer, syntaxForElementTypes); writer.Write('&'); } else if (type is PointerType) { - ((PointerType)type).ElementType.WriteTo(writer, onlyName, shortName); + ((PointerType)type).ElementType.WriteTo(writer, syntaxForElementTypes); writer.Write('*'); } else if (type is GenericInstanceType) { - type.GetElementType().WriteTo(writer, onlyName, shortName); + type.GetElementType().WriteTo(writer, syntaxForElementTypes); writer.Write('<'); var arguments = ((GenericInstanceType)type).GenericArguments; for (int i = 0; i < arguments.Count; i++) { if (i > 0) writer.Write(", "); - arguments[i].WriteTo(writer, onlyName, shortName); + arguments[i].WriteTo(writer, syntaxForElementTypes); } writer.Write('>'); } else if (type is OptionalModifierType) { - writer.Write("modopt("); - ((OptionalModifierType)type).ModifierType.WriteTo(writer, true, shortName); + ((OptionalModifierType)type).ElementType.WriteTo(writer, syntax); + writer.Write(" modopt("); + ((OptionalModifierType)type).ModifierType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write(") "); - ((OptionalModifierType)type).ElementType.WriteTo(writer, onlyName, shortName); } else if (type is RequiredModifierType) { - writer.Write("modreq("); - ((RequiredModifierType)type).ModifierType.WriteTo(writer, true, shortName); + ((RequiredModifierType)type).ElementType.WriteTo(writer, syntax); + writer.Write(" modreq("); + ((RequiredModifierType)type).ModifierType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write(") "); - ((RequiredModifierType)type).ElementType.WriteTo(writer, onlyName, shortName); } else { - string name = PrimitiveTypeName(type); - if (name != null) { + string name = PrimitiveTypeName(type.FullName); + if (syntax == ILNameSyntax.ShortTypeName) { + if (name != null) + writer.Write(name); + else + writer.WriteReference(Escape(type.Name), type); + } else if ((syntax == ILNameSyntax.Signature || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) && name != null) { writer.Write(name); } else { - if (!onlyName) + if (syntax == ILNameSyntax.Signature || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) writer.Write(type.IsValueType ? "valuetype " : "class "); if (type.DeclaringType != null) { - type.DeclaringType.WriteTo(writer, true, shortName); + type.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write('/'); writer.WriteReference(Escape(type.Name), type); } else { - if (!type.IsDefinition && type.Scope != null && !shortName && !(type is TypeSpecification)) + if (!type.IsDefinition && type.Scope != null && !(type is TypeSpecification)) writer.Write("[{0}]", Escape(type.Scope.Name)); - writer.WriteReference(shortName ? type.Name : type.FullName, type); + writer.WriteReference(Escape(type.FullName), type); } } } @@ -196,7 +317,19 @@ namespace ICSharpCode.Decompiler.Disassembler VariableReference variableRef = operand as VariableReference; if (variableRef != null) { - writer.WriteReference(variableRef.Index.ToString(), variableRef); + if (string.IsNullOrEmpty(variableRef.Name)) + writer.WriteReference(variableRef.Index.ToString(), variableRef); + else + writer.WriteReference(Escape(variableRef.Name), variableRef); + return; + } + + ParameterReference paramRef = operand as ParameterReference; + if (paramRef != null) { + if (string.IsNullOrEmpty(paramRef.Name)) + writer.WriteReference(paramRef.Index.ToString(), paramRef); + else + writer.WriteReference(Escape(paramRef.Name), paramRef); return; } @@ -208,7 +341,7 @@ namespace ICSharpCode.Decompiler.Disassembler TypeReference typeRef = operand as TypeReference; if (typeRef != null) { - typeRef.WriteTo(writer); + typeRef.WriteTo(writer, ILNameSyntax.TypeName); return; } @@ -220,17 +353,52 @@ namespace ICSharpCode.Decompiler.Disassembler string s = operand as string; if (s != null) { - writer.Write("\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""); - return; + writer.Write("\"" + NRefactory.CSharp.OutputVisitor.ConvertString(s) + "\""); + } else if (operand is char) { + writer.Write(((int)(char)operand).ToString()); + } else if (operand is float) { + float val = (float)operand; + if (val == 0) { + writer.Write("0.0"); + } else if (float.IsInfinity(val) || float.IsNaN(val)) { + byte[] data = BitConverter.GetBytes(val); + writer.Write('('); + for (int i = 0; i < data.Length; i++) { + if (i > 0) + writer.Write(' '); + writer.Write(data[i].ToString("X2")); + } + writer.Write(')'); + } else { + writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); + } + } else if (operand is double) { + double val = (double)operand; + if (val == 0) { + writer.Write("0.0"); + } else if (double.IsInfinity(val) || double.IsNaN(val)) { + byte[] data = BitConverter.GetBytes(val); + writer.Write('('); + for (int i = 0; i < data.Length; i++) { + if (i > 0) + writer.Write(' '); + writer.Write(data[i].ToString("X2")); + } + writer.Write(')'); + } else { + writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); + } + } else if (operand is bool) { + writer.Write((bool)operand ? "true" : "false"); + } else { + s = ToInvariantCultureString(operand); + writer.Write(s); } - - s = ToInvariantCultureString(operand); - writer.Write(s); } - public static string PrimitiveTypeName(this TypeReference type) + public static string PrimitiveTypeName(string fullName) { - switch (type.FullName) { + switch (fullName) { case "System.SByte": return "int8"; case "System.Int16": @@ -261,6 +429,8 @@ namespace ICSharpCode.Decompiler.Disassembler return "char"; case "System.Object": return "object"; + case "System.IntPtr": + return "native int"; default: return null; } diff --git a/ICSharpCode.Decompiler/Disassembler/ILStructure.cs b/ICSharpCode.Decompiler/Disassembler/ILStructure.cs index 5fc29a9bd..4c681e3c1 100644 --- a/ICSharpCode.Decompiler/Disassembler/ILStructure.cs +++ b/ICSharpCode.Decompiler/Disassembler/ILStructure.cs @@ -88,8 +88,10 @@ namespace ICSharpCode.Decompiler.Disassembler : this(ILStructureType.Root, 0, body.CodeSize) { // Build the tree of exception structures: - foreach (ExceptionHandler eh in body.ExceptionHandlers) { - AddNestedStructure(new ILStructure(ILStructureType.Try, eh.TryStart.Offset, eh.TryEnd.Offset, eh)); + for (int i = 0; i < body.ExceptionHandlers.Count; i++) { + ExceptionHandler eh = body.ExceptionHandlers[i]; + if (!body.ExceptionHandlers.Take(i).Any(oldEh => oldEh.TryStart == eh.TryStart && oldEh.TryEnd == eh.TryEnd)) + AddNestedStructure(new ILStructure(ILStructureType.Try, eh.TryStart.Offset, eh.TryEnd.Offset, eh)); if (eh.HandlerType == ExceptionHandlerType.Filter) AddNestedStructure(new ILStructure(ILStructureType.Filter, eh.FilterStart.Offset, eh.HandlerStart.Offset, eh)); AddNestedStructure(new ILStructure(ILStructureType.Handler, eh.HandlerStart.Offset, eh.HandlerEnd == null ? body.CodeSize : eh.HandlerEnd.Offset, eh)); diff --git a/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs index ef2f8a980..2fcde2335 100644 --- a/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs @@ -52,9 +52,6 @@ namespace ICSharpCode.Decompiler.Disassembler // start writing IL code MethodDefinition method = body.Method; output.WriteLine("// Method begins at RVA 0x{0:x4}", method.RVA); - if (method.HasOverrides) - foreach (var methodOverride in method.Overrides) - output.WriteLine(".override {0}::{1}", methodOverride.DeclaringType.FullName, methodOverride.Name); output.WriteLine("// Code size {0} (0x{0:x})", body.CodeSize); output.WriteLine(".maxstack {0}", body.MaxStackSize); if (method.DeclaringType.Module.Assembly.EntryPoint == method) @@ -69,8 +66,12 @@ namespace ICSharpCode.Decompiler.Disassembler foreach (var v in method.Body.Variables) { output.WriteDefinition("[" + v.Index + "] ", v); v.VariableType.WriteTo(output); - output.Write(' '); - output.Write(DisassemblerHelpers.Escape(v.Name)); + if (!string.IsNullOrEmpty(v.Name)) { + output.Write(' '); + output.Write(DisassemblerHelpers.Escape(v.Name)); + } + if (v.Index + 1 < method.Body.Variables.Count) + output.Write(','); output.WriteLine(); } output.Unindent(); @@ -80,29 +81,49 @@ namespace ICSharpCode.Decompiler.Disassembler if (detectControlStructure && body.Instructions.Count > 0) { Instruction inst = body.Instructions[0]; - WriteStructureBody(new ILStructure(body), ref inst, methodMapping, method.Body.CodeSize); + HashSet branchTargets = GetBranchTargets(body.Instructions); + WriteStructureBody(new ILStructure(body), branchTargets, ref inst, methodMapping, method.Body.CodeSize); } else { foreach (var inst in method.Body.Instructions) { inst.WriteTo(output); - // add IL code mappings - used in debugger - methodMapping.MemberCodeMappings.Add( - new SourceCodeMapping() { - SourceCodeLine = output.CurrentLine, - ILInstructionOffset = new ILRange { From = inst.Offset, To = inst.Next == null ? method.Body.CodeSize : inst.Next.Offset }, - MemberMapping = methodMapping - }); + if (methodMapping != null) { + // add IL code mappings - used in debugger + methodMapping.MemberCodeMappings.Add( + new SourceCodeMapping() { + SourceCodeLine = output.CurrentLine, + ILInstructionOffset = new ILRange { From = inst.Offset, To = inst.Next == null ? method.Body.CodeSize : inst.Next.Offset }, + MemberMapping = methodMapping + }); + } output.WriteLine(); } - output.WriteLine(); - foreach (var eh in method.Body.ExceptionHandlers) { - eh.WriteTo(output); + if (method.Body.HasExceptionHandlers) { output.WriteLine(); + foreach (var eh in method.Body.ExceptionHandlers) { + eh.WriteTo(output); + output.WriteLine(); + } } } } + HashSet GetBranchTargets(IEnumerable instructions) + { + HashSet branchTargets = new HashSet(); + foreach (var inst in instructions) { + Instruction target = inst.Operand as Instruction; + if (target != null) + branchTargets.Add(target.Offset); + Instruction[] targets = inst.Operand as Instruction[]; + if (targets != null) + foreach (Instruction t in targets) + branchTargets.Add(t.Offset); + } + return branchTargets; + } + void WriteStructureHeader(ILStructure s) { switch (s.Type) { @@ -116,7 +137,8 @@ namespace ICSharpCode.Decompiler.Disassembler output.WriteLine(); break; case ILStructureType.Try: - output.WriteLine(".try {"); + output.WriteLine(".try"); + output.WriteLine("{"); break; case ILStructureType.Handler: switch (s.ExceptionHandler.HandlerType) { @@ -125,22 +147,24 @@ namespace ICSharpCode.Decompiler.Disassembler output.Write("catch"); if (s.ExceptionHandler.CatchType != null) { output.Write(' '); - s.ExceptionHandler.CatchType.WriteTo(output); + s.ExceptionHandler.CatchType.WriteTo(output, ILNameSyntax.TypeName); } - output.WriteLine(" {"); + output.WriteLine(); break; case Mono.Cecil.Cil.ExceptionHandlerType.Finally: - output.WriteLine("finally {"); + output.WriteLine("finally"); break; case Mono.Cecil.Cil.ExceptionHandlerType.Fault: - output.WriteLine("fault {"); + output.WriteLine("fault"); break; default: throw new NotSupportedException(); } + output.WriteLine("{"); break; case ILStructureType.Filter: - output.WriteLine("filter {"); + output.WriteLine("filter"); + output.WriteLine("{"); break; default: throw new NotSupportedException(); @@ -148,17 +172,22 @@ namespace ICSharpCode.Decompiler.Disassembler output.Indent(); } - void WriteStructureBody(ILStructure s, ref Instruction inst, MemberMapping currentMethodMapping, int codeSize) + void WriteStructureBody(ILStructure s, HashSet branchTargets, ref Instruction inst, MemberMapping currentMethodMapping, int codeSize) { + bool isFirstInstructionInStructure = true; + bool prevInstructionWasBranch = false; int childIndex = 0; while (inst != null && inst.Offset < s.EndOffset) { int offset = inst.Offset; if (childIndex < s.Children.Count && s.Children[childIndex].StartOffset <= offset && offset < s.Children[childIndex].EndOffset) { ILStructure child = s.Children[childIndex++]; WriteStructureHeader(child); - WriteStructureBody(child, ref inst, currentMethodMapping, codeSize); + WriteStructureBody(child, branchTargets, ref inst, currentMethodMapping, codeSize); WriteStructureFooter(child); } else { + if (!isFirstInstructionInStructure && (prevInstructionWasBranch || branchTargets.Contains(offset))) { + output.WriteLine(); // put an empty line after branches, and in front of branch targets + } inst.WriteTo(output); // add IL code mappings - used in debugger @@ -172,8 +201,15 @@ namespace ICSharpCode.Decompiler.Disassembler } output.WriteLine(); + + prevInstructionWasBranch = inst.OpCode.FlowControl == FlowControl.Branch + || inst.OpCode.FlowControl == FlowControl.Cond_Branch + || inst.OpCode.FlowControl == FlowControl.Return + || inst.OpCode.FlowControl == FlowControl.Throw; + inst = inst.Next; } + isFirstInstructionInStructure = false; } } diff --git a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs index 5231b78c1..4e144924e 100644 --- a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs +++ b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs @@ -18,8 +18,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; using System.Threading; - using Mono.Cecil; using Mono.Collections.Generic; @@ -32,7 +34,6 @@ namespace ICSharpCode.Decompiler.Disassembler { ITextOutput output; CancellationToken cancellationToken; - bool detectControlStructure; bool isInType; // whether we are currently disassembling a whole type (-> defaultCollapsed for foldings) MethodBodyDisassembler methodBodyDisassembler; @@ -42,23 +43,24 @@ namespace ICSharpCode.Decompiler.Disassembler throw new ArgumentNullException("output"); this.output = output; this.cancellationToken = cancellationToken; - this.detectControlStructure = detectControlStructure; this.methodBodyDisassembler = new MethodBodyDisassembler(output, detectControlStructure, cancellationToken); } #region Disassemble Method EnumNameCollection methodAttributeFlags = new EnumNameCollection() { - { MethodAttributes.Static, "static" }, { MethodAttributes.Final, "final" }, - { MethodAttributes.Virtual, "virtual" }, { MethodAttributes.HideBySig, "hidebysig" }, - { MethodAttributes.Abstract, "abstract" }, { MethodAttributes.SpecialName, "specialname" }, - { MethodAttributes.PInvokeImpl, "pinvokeimpl" }, + { MethodAttributes.PInvokeImpl, null }, // handled separately { MethodAttributes.UnmanagedExport, "export" }, { MethodAttributes.RTSpecialName, "rtspecialname" }, - { MethodAttributes.RequireSecObject, "requiresecobj" }, - { MethodAttributes.NewSlot, "newslot" } + { MethodAttributes.RequireSecObject, "reqsecobj" }, + { MethodAttributes.NewSlot, "newslot" }, + { MethodAttributes.CheckAccessOnOverride, "strict" }, + { MethodAttributes.Abstract, "abstract" }, + { MethodAttributes.Virtual, "virtual" }, + { MethodAttributes.Static, "static" }, + { MethodAttributes.HasSecurity, null }, // ?? also invisible in ILDasm }; EnumNameCollection methodVisibility = new EnumNameCollection() { @@ -76,7 +78,7 @@ namespace ICSharpCode.Decompiler.Disassembler { MethodCallingConvention.ThisCall, "unmanaged thiscall" }, { MethodCallingConvention.FastCall, "unmanaged fastcall" }, { MethodCallingConvention.VarArg, "vararg" }, - { MethodCallingConvention.Generic, "generic" }, + { MethodCallingConvention.Generic, null }, }; EnumNameCollection methodCodeType = new EnumNameCollection() { @@ -90,6 +92,9 @@ namespace ICSharpCode.Decompiler.Disassembler { MethodImplAttributes.Synchronized, "synchronized" }, { MethodImplAttributes.NoInlining, "noinlining" }, { MethodImplAttributes.NoOptimization, "nooptimization" }, + { MethodImplAttributes.PreserveSig, "preservesig" }, + { MethodImplAttributes.InternalCall, "internalcall" }, + { MethodImplAttributes.ForwardRef, "forwardref" }, }; public void DisassembleMethod(MethodDefinition method) @@ -108,12 +113,53 @@ namespace ICSharpCode.Decompiler.Disassembler //emit flags WriteEnum(method.Attributes & MethodAttributes.MemberAccessMask, methodVisibility); WriteFlags(method.Attributes & ~MethodAttributes.MemberAccessMask, methodAttributeFlags); + if(method.IsCompilerControlled) output.Write("privatescope "); + + if ((method.Attributes & MethodAttributes.PInvokeImpl) == MethodAttributes.PInvokeImpl) { + output.Write("pinvokeimpl"); + if (method.HasPInvokeInfo) { + PInvokeInfo info = method.PInvokeInfo; + output.Write("(\"" + NRefactory.CSharp.OutputVisitor.ConvertString(info.Module.Name) + "\""); + + if (!string.IsNullOrEmpty(info.EntryPoint) && info.EntryPoint != method.Name) + output.Write(" as \"" + NRefactory.CSharp.OutputVisitor.ConvertString(info.EntryPoint) + "\""); + + if (info.IsNoMangle) + output.Write(" nomangle"); + + if (info.IsCharSetAnsi) + output.Write(" ansi"); + else if (info.IsCharSetAuto) + output.Write(" autochar"); + else if (info.IsCharSetUnicode) + output.Write(" unicode"); + + if (info.SupportsLastError) + output.Write(" lasterr"); + + if (info.IsCallConvCdecl) + output.Write(" cdecl"); + else if (info.IsCallConvFastcall) + output.Write(" fastcall"); + else if (info.IsCallConvStdCall) + output.Write(" stdcall"); + else if (info.IsCallConvThiscall) + output.Write(" thiscall"); + else if (info.IsCallConvWinapi) + output.Write(" winapi"); + + output.Write(')'); + } + output.Write(' '); + } output.WriteLine(); output.Indent(); - - if (method.HasThis) + if (method.ExplicitThis) { + output.Write("instance explicit "); + } else if (method.HasThis) { output.Write("instance "); + } //call convention WriteEnum(method.CallingConvention & (MethodCallingConvention)0x1f, callingConvention); @@ -122,7 +168,16 @@ namespace ICSharpCode.Decompiler.Disassembler //return type method.ReturnType.WriteTo(output); output.Write(' '); - output.Write(DisassemblerHelpers.Escape(method.Name)); + if (method.MethodReturnType.HasMarshalInfo) { + WriteMarshalInfo(method.MethodReturnType.MarshalInfo); + } + + if (method.IsCompilerControlled) { + output.Write(DisassemblerHelpers.Escape(method.Name + "$PST" + method.MetadataToken.ToInt32().ToString("X8"))); + } else { + output.Write(DisassemblerHelpers.Escape(method.Name)); + } + WriteTypeParameters(output, method); //( params ) @@ -143,34 +198,454 @@ namespace ICSharpCode.Decompiler.Disassembler WriteFlags(method.ImplAttributes & ~(MethodImplAttributes.CodeTypeMask | MethodImplAttributes.ManagedMask), methodImpl); output.Unindent(); - if (method.HasBody || method.HasCustomAttributes) { - OpenBlock(defaultCollapsed: isInType); - WriteAttributes(method.CustomAttributes); - - if (method.HasBody) { - // create IL code mappings - used in debugger - MemberMapping methodMapping = method.CreateCodeMapping(this.CodeMappings); - methodBodyDisassembler.Disassemble(method.Body, methodMapping); + OpenBlock(defaultCollapsed: isInType); + WriteAttributes(method.CustomAttributes); + if (method.HasOverrides) { + foreach (var methodOverride in method.Overrides) { + output.Write(".override method "); + methodOverride.WriteTo(output); + output.WriteLine(); + } + } + foreach (var p in method.Parameters) { + WriteParameterAttributes(p); + } + WriteSecurityDeclarations(method); + + if (method.HasBody) { + // create IL code mappings - used in debugger + MemberMapping methodMapping = method.CreateCodeMapping(this.CodeMappings); + methodBodyDisassembler.Disassemble(method.Body, methodMapping); + } + + CloseBlock("end of method " + DisassemblerHelpers.Escape(method.DeclaringType.Name) + "::" + DisassemblerHelpers.Escape(method.Name)); + } + + #region Write Security Declarations + void WriteSecurityDeclarations(ISecurityDeclarationProvider secDeclProvider) + { + if (!secDeclProvider.HasSecurityDeclarations) + return; + foreach (var secdecl in secDeclProvider.SecurityDeclarations) { + output.Write(".permissionset "); + switch (secdecl.Action) { + case SecurityAction.Request: + output.Write("request"); + break; + case SecurityAction.Demand: + output.Write("demand"); + break; + case SecurityAction.Assert: + output.Write("assert"); + break; + case SecurityAction.Deny: + output.Write("deny"); + break; + case SecurityAction.PermitOnly: + output.Write("permitonly"); + break; + case SecurityAction.LinkDemand: + output.Write("linkcheck"); + break; + case SecurityAction.InheritDemand: + output.Write("inheritcheck"); + break; + case SecurityAction.RequestMinimum: + output.Write("reqmin"); + break; + case SecurityAction.RequestOptional: + output.Write("reqopt"); + break; + case SecurityAction.RequestRefuse: + output.Write("reqrefuse"); + break; + case SecurityAction.PreJitGrant: + output.Write("prejitgrant"); + break; + case SecurityAction.PreJitDeny: + output.Write("prejitdeny"); + break; + case SecurityAction.NonCasDemand: + output.Write("noncasdemand"); + break; + case SecurityAction.NonCasLinkDemand: + output.Write("noncaslinkdemand"); + break; + case SecurityAction.NonCasInheritance: + output.Write("noncasinheritance"); + break; + default: + output.Write(secdecl.Action.ToString()); + break; + } + output.WriteLine(" = {"); + output.Indent(); + for (int i = 0; i < secdecl.SecurityAttributes.Count; i++) { + SecurityAttribute sa = secdecl.SecurityAttributes[i]; + if (sa.AttributeType.Scope == sa.AttributeType.Module) { + output.Write("class "); + output.Write(DisassemblerHelpers.Escape(GetAssemblyQualifiedName(sa.AttributeType))); + } else { + sa.AttributeType.WriteTo(output, ILNameSyntax.TypeName); + } + output.Write(" = {"); + if (sa.HasFields || sa.HasProperties) { + output.WriteLine(); + output.Indent(); + + foreach (CustomAttributeNamedArgument na in sa.Fields) { + output.Write("field "); + WriteSecurityDeclarationArgument(na); + output.WriteLine(); + } + + foreach (CustomAttributeNamedArgument na in sa.Properties) { + output.Write("property "); + WriteSecurityDeclarationArgument(na); + output.WriteLine(); + } + + output.Unindent(); + } + output.Write('}'); + + if (i + 1< secdecl.SecurityAttributes.Count) + output.Write(','); + output.WriteLine(); + } + output.Unindent(); + output.WriteLine("}"); + } + } + + void WriteSecurityDeclarationArgument(CustomAttributeNamedArgument na) + { + TypeReference type = na.Argument.Type; + if (type.MetadataType == MetadataType.Class || type.MetadataType == MetadataType.ValueType) { + output.Write("enum "); + if (type.Scope != type.Module) { + output.Write("class "); + output.Write(DisassemblerHelpers.Escape(GetAssemblyQualifiedName(type))); + } else { + type.WriteTo(output, ILNameSyntax.TypeName); } - - CloseBlock("End of method " + method.DeclaringType.Name + "." + method.Name); } else { - output.WriteLine(); + type.WriteTo(output); + } + output.Write(' '); + output.Write(DisassemblerHelpers.Escape(na.Name)); + output.Write(" = "); + if (na.Argument.Value is string) { + // secdecls use special syntax for strings + output.Write("string('{0}')", NRefactory.CSharp.OutputVisitor.ConvertString((string)na.Argument.Value).Replace("'", "\'")); + } else { + WriteConstant(na.Argument.Value); + } + } + + string GetAssemblyQualifiedName(TypeReference type) + { + AssemblyNameReference anr = type.Scope as AssemblyNameReference; + if (anr == null) { + ModuleDefinition md = type.Scope as ModuleDefinition; + if (md != null) { + anr = md.Assembly.Name; + } + } + if (anr != null) { + return type.FullName + ", " + anr.FullName; + } else { + return type.FullName; } } + #endregion + + #region WriteMarshalInfo + void WriteMarshalInfo(MarshalInfo marshalInfo) + { + output.Write("marshal("); + WriteNativeType(marshalInfo.NativeType, marshalInfo); + output.Write(") "); + } + + void WriteNativeType(NativeType nativeType, MarshalInfo marshalInfo = null) + { + switch (nativeType) { + case NativeType.None: + break; + case NativeType.Boolean: + output.Write("bool"); + break; + case NativeType.I1: + output.Write("int8"); + break; + case NativeType.U1: + output.Write("unsigned int8"); + break; + case NativeType.I2: + output.Write("int16"); + break; + case NativeType.U2: + output.Write("unsigned int16"); + break; + case NativeType.I4: + output.Write("int32"); + break; + case NativeType.U4: + output.Write("unsigned int32"); + break; + case NativeType.I8: + output.Write("int64"); + break; + case NativeType.U8: + output.Write("unsigned int64"); + break; + case NativeType.R4: + output.Write("float32"); + break; + case NativeType.R8: + output.Write("float64"); + break; + case NativeType.LPStr: + output.Write("lpstr"); + break; + case NativeType.Int: + output.Write("int"); + break; + case NativeType.UInt: + output.Write("unsigned int"); + break; + case NativeType.Func: + goto default; // ?? + case NativeType.Array: + ArrayMarshalInfo ami = (ArrayMarshalInfo)marshalInfo; + if (ami == null) + goto default; + if (ami.ElementType != NativeType.Max) + WriteNativeType(ami.ElementType); + output.Write('['); + if (ami.SizeParameterMultiplier == 0) { + output.Write(ami.Size.ToString()); + } else { + if (ami.Size >= 0) + output.Write(ami.Size.ToString()); + output.Write(" + "); + output.Write(ami.SizeParameterIndex.ToString()); + } + output.Write(']'); + break; + case NativeType.Currency: + output.Write("currency"); + break; + case NativeType.BStr: + output.Write("bstr"); + break; + case NativeType.LPWStr: + output.Write("lpwstr"); + break; + case NativeType.LPTStr: + output.Write("lptstr"); + break; + case NativeType.FixedSysString: + output.Write("fixed sysstring[{0}]", ((FixedSysStringMarshalInfo)marshalInfo).Size); + break; + case NativeType.IUnknown: + output.Write("iunknown"); + break; + case NativeType.IDispatch: + output.Write("idispatch"); + break; + case NativeType.Struct: + output.Write("struct"); + break; + case NativeType.IntF: + output.Write("interface"); + break; + case NativeType.SafeArray: + output.Write("safearray "); + SafeArrayMarshalInfo sami = marshalInfo as SafeArrayMarshalInfo; + if (sami != null) { + switch (sami.ElementType) { + case VariantType.None: + break; + case VariantType.I2: + output.Write("int16"); + break; + case VariantType.I4: + output.Write("int32"); + break; + case VariantType.R4: + output.Write("float32"); + break; + case VariantType.R8: + output.Write("float64"); + break; + case VariantType.CY: + output.Write("currency"); + break; + case VariantType.Date: + output.Write("date"); + break; + case VariantType.BStr: + output.Write("bstr"); + break; + case VariantType.Dispatch: + output.Write("idispatch"); + break; + case VariantType.Error: + output.Write("error"); + break; + case VariantType.Bool: + output.Write("bool"); + break; + case VariantType.Variant: + output.Write("variant"); + break; + case VariantType.Unknown: + output.Write("iunknown"); + break; + case VariantType.Decimal: + output.Write("decimal"); + break; + case VariantType.I1: + output.Write("int8"); + break; + case VariantType.UI1: + output.Write("unsigned int8"); + break; + case VariantType.UI2: + output.Write("unsigned int16"); + break; + case VariantType.UI4: + output.Write("unsigned int32"); + break; + case VariantType.Int: + output.Write("int"); + break; + case VariantType.UInt: + output.Write("unsigned int"); + break; + default: + output.Write(sami.ElementType.ToString()); + break; + } + } + break; + case NativeType.FixedArray: + output.Write("fixed array"); + FixedArrayMarshalInfo fami = marshalInfo as FixedArrayMarshalInfo; + if (fami != null) { + output.Write("[{0}]", fami.Size); + if (fami.ElementType != NativeType.None) { + output.Write(' '); + WriteNativeType(fami.ElementType); + } + } + break; + case NativeType.ByValStr: + output.Write("byvalstr"); + break; + case NativeType.ANSIBStr: + output.Write("ansi bstr"); + break; + case NativeType.TBStr: + output.Write("tbstr"); + break; + case NativeType.VariantBool: + output.Write("variant bool"); + break; + case NativeType.ASAny: + output.Write("as any"); + break; + case NativeType.LPStruct: + output.Write("lpstruct"); + break; + case NativeType.CustomMarshaler: + CustomMarshalInfo cmi = marshalInfo as CustomMarshalInfo; + if (cmi == null) + goto default; + output.Write("custom(\"{0}\", \"{1}\"", + NRefactory.CSharp.OutputVisitor.ConvertString(cmi.ManagedType.FullName), + NRefactory.CSharp.OutputVisitor.ConvertString(cmi.Cookie)); + if (cmi.Guid != Guid.Empty || !string.IsNullOrEmpty(cmi.UnmanagedType)) { + output.Write(", \"{0}\", \"{1}\"", cmi.Guid.ToString(), NRefactory.CSharp.OutputVisitor.ConvertString(cmi.UnmanagedType)); + } + output.Write(')'); + break; + case NativeType.Error: + output.Write("error"); + break; + default: + output.Write(nativeType.ToString()); + break; + } + } + #endregion void WriteParameters(Collection parameters) { for (int i = 0; i < parameters.Count; i++) { var p = parameters[i]; + if (p.IsIn) + output.Write("[in] "); + if (p.IsOut) + output.Write("[out] "); + if (p.IsOptional) + output.Write("[opt] "); p.ParameterType.WriteTo(output); output.Write(' '); + if (p.HasMarshalInfo) { + WriteMarshalInfo(p.MarshalInfo); + } output.WriteDefinition(DisassemblerHelpers.Escape(p.Name), p); if (i < parameters.Count - 1) output.Write(','); output.WriteLine(); } } + + bool HasParameterAttributes(ParameterDefinition p) + { + return p.HasConstant || p.HasCustomAttributes; + } + + void WriteParameterAttributes(ParameterDefinition p) + { + if (!HasParameterAttributes(p)) + return; + output.Write(".param [{0}]", p.Index + 1); + if (p.HasConstant) { + output.Write(" = "); + WriteConstant(p.Constant); + } + output.WriteLine(); + WriteAttributes(p.CustomAttributes); + } + + void WriteConstant(object constant) + { + if (constant == null) { + output.Write("nullref"); + } else { + string typeName = DisassemblerHelpers.PrimitiveTypeName(constant.GetType().FullName); + if (typeName != null && typeName != "string") { + output.Write(typeName); + output.Write('('); + float? cf = constant as float?; + double? cd = constant as double?; + if (cf.HasValue && (float.IsNaN(cf.Value) || float.IsInfinity(cf.Value))) { + output.Write("0x{0:x8}", BitConverter.ToInt32(BitConverter.GetBytes(cf.Value), 0)); + } else if (cd.HasValue && (double.IsNaN(cd.Value) || double.IsInfinity(cd.Value))) { + output.Write("0x{0:x16}", BitConverter.DoubleToInt64Bits(cd.Value)); + } else { + DisassemblerHelpers.WriteOperand(output, constant); + } + output.Write(')'); + } else { + DisassemblerHelpers.WriteOperand(output, constant); + } + } + } #endregion #region Disassemble Field @@ -196,20 +671,26 @@ namespace ICSharpCode.Decompiler.Disassembler { output.WriteDefinition(".field ", field); WriteEnum(field.Attributes & FieldAttributes.FieldAccessMask, fieldVisibility); - WriteFlags(field.Attributes & ~(FieldAttributes.FieldAccessMask | FieldAttributes.HasDefault), fieldAttributes); + const FieldAttributes hasXAttributes = FieldAttributes.HasDefault | FieldAttributes.HasFieldMarshal | FieldAttributes.HasFieldRVA; + WriteFlags(field.Attributes & ~(FieldAttributes.FieldAccessMask | hasXAttributes), fieldAttributes); + if (field.HasMarshalInfo) { + WriteMarshalInfo(field.MarshalInfo); + } field.FieldType.WriteTo(output); output.Write(' '); output.Write(DisassemblerHelpers.Escape(field.Name)); + if ((field.Attributes & FieldAttributes.HasFieldRVA) == FieldAttributes.HasFieldRVA) { + output.Write(" at I_{0:x8}", field.RVA); + } if (field.HasConstant) { output.Write(" = "); - DisassemblerHelpers.WriteOperand(output, field.Constant); + WriteConstant(field.Constant); } + output.WriteLine(); if (field.HasCustomAttributes) { - OpenBlock(false); + output.MarkFoldStart(); WriteAttributes(field.CustomAttributes); - CloseBlock(); - } else { - output.WriteLine(); + output.MarkFoldEnd(); } } #endregion @@ -225,15 +706,27 @@ namespace ICSharpCode.Decompiler.Disassembler { output.WriteDefinition(".property ", property); WriteFlags(property.Attributes, propertyAttributes); + if (property.HasThis) + output.Write("instance "); property.PropertyType.WriteTo(output); output.Write(' '); output.Write(DisassemblerHelpers.Escape(property.Name)); + + output.Write("("); + if (property.HasParameters) { + output.WriteLine(); + output.Indent(); + WriteParameters(property.Parameters); + output.Unindent(); + } + output.Write(")"); + OpenBlock(false); WriteAttributes(property.CustomAttributes); WriteNestedMethod(".get", property.GetMethod); WriteNestedMethod(".set", property.SetMethod); foreach (var method in property.OtherMethods) { - WriteNestedMethod(".method", method); + WriteNestedMethod(".other", method); } CloseBlock(); } @@ -242,16 +735,10 @@ namespace ICSharpCode.Decompiler.Disassembler { if (method == null) return; - if (detectControlStructure) { - output.WriteDefinition(keyword, method); - output.Write(' '); - DisassembleMethodInternal(method); - } else { - output.Write(keyword); - output.Write(' '); - method.WriteTo(output); - output.WriteLine(); - } + output.Write(keyword); + output.Write(' '); + method.WriteTo(output); + output.WriteLine(); } #endregion @@ -265,16 +752,16 @@ namespace ICSharpCode.Decompiler.Disassembler { output.WriteDefinition(".event ", ev); WriteFlags(ev.Attributes, eventAttributes); - ev.EventType.WriteTo(output); + ev.EventType.WriteTo(output, ILNameSyntax.TypeName); output.Write(' '); output.Write(DisassemblerHelpers.Escape(ev.Name)); OpenBlock(false); WriteAttributes(ev.CustomAttributes); - WriteNestedMethod(".add", ev.AddMethod); - WriteNestedMethod(".remove", ev.RemoveMethod); - WriteNestedMethod(".invoke", ev.InvokeMethod); + WriteNestedMethod(".addon", ev.AddMethod); + WriteNestedMethod(".removeon", ev.RemoveMethod); + WriteNestedMethod(".fire", ev.InvokeMethod); foreach (var method in ev.OtherMethods) { - WriteNestedMethod(".method", method); + WriteNestedMethod(".other", method); } CloseBlock(); } @@ -331,7 +818,7 @@ namespace ICSharpCode.Decompiler.Disassembler const TypeAttributes masks = TypeAttributes.ClassSemanticMask | TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.StringFormatMask; WriteFlags(type.Attributes & ~masks, typeAttributes); - output.Write(DisassemblerHelpers.Escape(type.Name)); + output.Write(DisassemblerHelpers.Escape(type.DeclaringType != null ? type.Name : type.FullName)); WriteTypeParameters(output, type); output.MarkFoldStart(defaultCollapsed: isInType); output.WriteLine(); @@ -339,7 +826,7 @@ namespace ICSharpCode.Decompiler.Disassembler if (type.BaseType != null) { output.Indent(); output.Write("extends "); - type.BaseType.WriteTo(output, true); + type.BaseType.WriteTo(output, ILNameSyntax.TypeName); output.WriteLine(); output.Unindent(); } @@ -352,9 +839,7 @@ namespace ICSharpCode.Decompiler.Disassembler output.Write("implements "); else output.Write(" "); - if (type.Interfaces[index].Namespace != null) - output.Write("{0}.", type.Interfaces[index].Namespace); - output.Write(type.Interfaces[index].Name); + type.Interfaces[index].WriteTo(output, ILNameSyntax.TypeName); } output.WriteLine(); output.Unindent(); @@ -365,6 +850,7 @@ namespace ICSharpCode.Decompiler.Disassembler bool oldIsInType = isInType; isInType = true; WriteAttributes(type.CustomAttributes); + WriteSecurityDeclarations(type); if (type.HasLayoutInfo) { output.WriteLine(".pack {0}", type.PackingSize); output.WriteLine(".size {0}", type.ClassSize); @@ -387,13 +873,13 @@ namespace ICSharpCode.Decompiler.Disassembler } output.WriteLine(); } - if (type.HasProperties) { - output.WriteLine("// Properties"); - foreach (var prop in type.Properties) { + if (type.HasMethods) { + output.WriteLine("// Methods"); + foreach (var m in type.Methods) { cancellationToken.ThrowIfCancellationRequested(); - DisassembleProperty(prop); + DisassembleMethod(m); + output.WriteLine(); } - output.WriteLine(); } if (type.HasEvents) { output.WriteLine("// Events"); @@ -404,18 +890,15 @@ namespace ICSharpCode.Decompiler.Disassembler } output.WriteLine(); } - if (type.HasMethods) { - output.WriteLine("// Methods"); - var accessorMethods = type.GetAccessorMethods(); - foreach (var m in type.Methods) { + if (type.HasProperties) { + output.WriteLine("// Properties"); + foreach (var prop in type.Properties) { cancellationToken.ThrowIfCancellationRequested(); - if (!(detectControlStructure && accessorMethods.Contains(m))) { - DisassembleMethod(m); - output.WriteLine(); - } + DisassembleProperty(prop); } + output.WriteLine(); } - CloseBlock("End of class " + type.FullName); + CloseBlock("end of class " + (type.DeclaringType != null ? type.Name : type.FullName)); isInType = oldIsInType; } @@ -432,18 +915,18 @@ namespace ICSharpCode.Decompiler.Disassembler } else if (gp.HasNotNullableValueTypeConstraint) { output.Write("valuetype "); } + if (gp.HasDefaultConstructorConstraint) { + output.Write(".ctor "); + } if (gp.HasConstraints) { output.Write('('); for (int j = 0; j < gp.Constraints.Count; j++) { if (j > 0) output.Write(", "); - gp.Constraints[j].WriteTo(output, true); + gp.Constraints[j].WriteTo(output, ILNameSyntax.TypeName); } output.Write(") "); } - if (gp.HasDefaultConstructorConstraint) { - output.Write(".ctor "); - } if (gp.IsContravariant) { output.Write('-'); } else if (gp.IsCovariant) { @@ -586,9 +1069,12 @@ namespace ICSharpCode.Decompiler.Disassembler { output.Write(".assembly " + DisassemblerHelpers.Escape(asm.Name.Name)); OpenBlock(false); - Version v = asm.Name.Version; - if (v != null) { - output.WriteLine(".ver {0}:{1}:{2}:{3}", v.Major, v.Minor, v.Build, v.Revision); + WriteAttributes(asm.CustomAttributes); + WriteSecurityDeclarations(asm); + if (asm.Name.PublicKey != null && asm.Name.PublicKey.Length > 0) { + output.Write(".publickey = "); + WriteBlob(asm.Name.PublicKey); + output.WriteLine(); } if (asm.Name.HashAlgorithm != AssemblyHashAlgorithm.None) { output.Write(".hash algorithm 0x{0:x8}", (int)asm.Name.HashAlgorithm); @@ -596,15 +1082,66 @@ namespace ICSharpCode.Decompiler.Disassembler output.Write(" // SHA1"); output.WriteLine(); } - if (asm.Name.PublicKey != null && asm.Name.PublicKey.Length > 0) { - output.Write(".publickey = "); - WriteBlob(asm.Name.PublicKey); - output.WriteLine(); + Version v = asm.Name.Version; + if (v != null) { + output.WriteLine(".ver {0}:{1}:{2}:{3}", v.Major, v.Minor, v.Build, v.Revision); } - WriteAttributes(asm.CustomAttributes); CloseBlock(); } + public void WriteAssemblyReferences(ModuleDefinition module) + { + foreach (var mref in module.ModuleReferences) { + output.WriteLine(".module extern {0}", DisassemblerHelpers.Escape(mref.Name)); + } + foreach (var aref in module.AssemblyReferences) { + output.Write(".assembly extern {0}", DisassemblerHelpers.Escape(aref.Name)); + OpenBlock(false); + if (aref.PublicKeyToken != null) { + output.Write(".publickeytoken = "); + WriteBlob(aref.PublicKeyToken); + output.WriteLine(); + } + if (aref.Version != null) { + output.WriteLine(".ver {0}:{1}:{2}:{3}", aref.Version.Major, aref.Version.Minor, aref.Version.Build, aref.Version.Revision); + } + CloseBlock(); + } + } + + public void WriteModuleHeader(ModuleDefinition module) + { + if (module.HasExportedTypes) { + foreach (ExportedType exportedType in module.ExportedTypes) { + output.Write(".class extern "); + if (exportedType.IsForwarder) + output.Write("forwarder "); + output.Write(exportedType.DeclaringType != null ? exportedType.Name : exportedType.FullName); + OpenBlock(false); + if (exportedType.DeclaringType != null) + output.WriteLine(".class extern {0}", DisassemblerHelpers.Escape(exportedType.DeclaringType.FullName)); + else + output.WriteLine(".assembly extern {0}", DisassemblerHelpers.Escape(exportedType.Scope.Name)); + CloseBlock(); + } + } + + output.WriteLine(".module {0}", module.Name); + output.WriteLine("// MVID: {0}", module.Mvid.ToString("B").ToUpperInvariant()); + // TODO: imagebase, file alignment, stackreserve, subsystem + output.WriteLine(".corflags 0x{0:x} // {1}", module.Attributes, module.Attributes.ToString()); + + WriteAttributes(module.CustomAttributes); + } + + public void WriteModuleContents(ModuleDefinition module) + { + foreach (TypeDefinition td in module.Types) { + DisassembleType(td); + output.WriteLine(); + } + } + /// public Tuple> CodeMappings { get; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 00640306b..1d15d9628 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -61,6 +61,7 @@ + diff --git a/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs index b1d883019..351c28428 100644 --- a/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs +++ b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs @@ -91,7 +91,7 @@ namespace ICSharpCode.Decompiler.ILAst } } - var defaultCase = ilSwitch.CaseBlocks.Where(cb => cb.Values == null).SingleOrDefault(); + var defaultCase = ilSwitch.CaseBlocks.SingleOrDefault(cb => cb.Values == null); // If there is no default block, remove empty case blocks if (defaultCase == null || (defaultCase.Body.Count == 1 && defaultCase.Body.Single().Match(ILCode.LoopOrSwitchBreak))) { ilSwitch.CaseBlocks.RemoveAll(b => b.Body.Count == 1 && b.Body.Single().Match(ILCode.LoopOrSwitchBreak)); @@ -140,14 +140,14 @@ namespace ICSharpCode.Decompiler.ILAst return true; } - ILNode breakBlock = GetParents(gotoExpr).Where(n => n is ILWhileLoop || n is ILSwitch).FirstOrDefault(); + ILNode breakBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop || n is ILSwitch); if (breakBlock != null && target == Exit(breakBlock, new HashSet() { gotoExpr })) { gotoExpr.Code = ILCode.LoopOrSwitchBreak; gotoExpr.Operand = null; return true; } - ILNode continueBlock = GetParents(gotoExpr).Where(n => n is ILWhileLoop).FirstOrDefault(); + ILNode continueBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop); if (continueBlock != null && target == Enter(continueBlock, new HashSet() { gotoExpr })) { gotoExpr.Code = ILCode.LoopContinue; gotoExpr.Operand = null; @@ -209,10 +209,10 @@ namespace ICSharpCode.Decompiler.ILAst } else if (expr.Code == ILCode.Nop) { return Exit(expr, visitedNodes); } else if (expr.Code == ILCode.LoopOrSwitchBreak) { - ILNode breakBlock = GetParents(expr).Where(n => n is ILWhileLoop || n is ILSwitch).First(); + ILNode breakBlock = GetParents(expr).First(n => n is ILWhileLoop || n is ILSwitch); return Exit(breakBlock, new HashSet() { expr }); } else if (expr.Code == ILCode.LoopContinue) { - ILNode continueBlock = GetParents(expr).Where(n => n is ILWhileLoop).First(); + ILNode continueBlock = GetParents(expr).First(n => n is ILWhileLoop); return Enter(continueBlock, new HashSet() { expr }); } else { return expr; diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 9c845293b..aed211f56 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -455,7 +455,7 @@ namespace ICSharpCode.Decompiler.ILAst if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one - var loadedBy = locVars.Select(locVar => reachableBody.SelectMany(bc => bc.StackBefore).Where(s => s.LoadFrom == locVar).Single()).ToList(); + var loadedBy = locVars.Select(locVar => reachableBody.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.PushedBy.Length == 1 && slot.PushedBy[0] == byteCode)) { @@ -572,7 +572,7 @@ namespace ICSharpCode.Decompiler.ILAst Loads = new List() { load } }); } else if (storedBy.Length == 1) { - VariableInfo newVar = newVars.Where(v => v.Stores.Contains(storedBy[0])).Single(); + VariableInfo newVar = newVars.Single(v => v.Stores.Contains(storedBy[0])); newVar.Loads.Add(load); } else { List mergeVars = newVars.Where(v => v.Stores.Union(storedBy).Any()).ToList(); diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 5268b9519..3e553db6e 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -45,6 +45,7 @@ namespace ICSharpCode.Decompiler.ILAst SimplifyLogicNot, TransformDecimalCtorToConstant, SimplifyLdObjAndStObj, + SimplifyCustomShortCircuit, TransformArrayInitializers, TransformObjectInitializers, SimplifyNullableOperators, @@ -141,6 +142,9 @@ namespace ICSharpCode.Decompiler.ILAst if (abortBeforeStep == ILAstOptimizationStep.SimplifyLdObjAndStObj) return; modified |= block.RunOptimization(SimplifyLdObjAndStObj); + if (abortBeforeStep == ILAstOptimizationStep.SimplifyCustomShortCircuit) return; + modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyCustomShortCircuit); + if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; modified |= block.RunOptimization(TransformArrayInitializers); @@ -319,6 +323,8 @@ namespace ICSharpCode.Decompiler.ILAst /// Converts call and callvirt instructions that read/write properties into CallGetter/CallSetter instructions. /// /// CallGetter/CallSetter is used to allow the ILAst to represent "while ((SomeProperty = value) != null)". + /// + /// Also simplifies 'newobj(SomeDelegate, target, ldvirtftn(F, target))' to 'newobj(SomeDelegate, target, ldvirtftn(F))' /// void IntroducePropertyAccessInstructions(ILNode node) { @@ -376,6 +382,19 @@ namespace ICSharpCode.Decompiler.ILAst expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallSetter : ILCode.CallvirtSetter; } } + } else if (expr.Code == ILCode.Newobj && expr.Arguments.Count == 2) { + // Might be 'newobj(SomeDelegate, target, ldvirtftn(F, target))'. + ILVariable target; + if (expr.Arguments[0].Match(ILCode.Ldloc, out target) + && expr.Arguments[1].Code == ILCode.Ldvirtftn + && expr.Arguments[1].Arguments.Count == 1 + && expr.Arguments[1].Arguments[0].MatchLdloc(target)) + { + // Remove the 'target' argument from the ldvirtftn instruction. + // It's not needed in the translation to C#, and needs to be eliminated so that the target expression + // can be inlined. + expr.Arguments[1].Arguments.Clear(); + } } } @@ -409,7 +428,7 @@ namespace ICSharpCode.Decompiler.ILAst lastNode.IsUnconditionalControlFlow()) { // Try to reuse the label - ILLabel label = currNode is ILLabel ? ((ILLabel)currNode) : new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; + ILLabel label = currNode as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++).ToString() }; // Terminate the last block if (!lastNode.IsUnconditionalControlFlow()) { diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index 1d366c06c..f86225fb8 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -141,6 +141,10 @@ namespace ICSharpCode.Decompiler.ILAst { output.Write("catch "); output.WriteReference(ExceptionType.FullName, ExceptionType); + if (ExceptionVariable != null) { + output.Write(' '); + output.Write(ExceptionVariable.Name); + } output.WriteLine(" {"); output.Indent(); base.WriteTo(output); @@ -378,10 +382,10 @@ namespace ICSharpCode.Decompiler.ILAst output.Write(((ILVariable)Operand).Name); if (this.InferredType != null) { output.Write(':'); - this.InferredType.WriteTo(output, true, true); + this.InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); if (this.ExpectedType != null && this.ExpectedType.FullName != this.InferredType.FullName) { output.Write("[exp:"); - this.ExpectedType.WriteTo(output, true, true); + this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); output.Write(']'); } } @@ -399,15 +403,15 @@ namespace ICSharpCode.Decompiler.ILAst output.Write(Code.GetName()); if (this.InferredType != null) { output.Write(':'); - this.InferredType.WriteTo(output, true, true); + this.InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); if (this.ExpectedType != null && this.ExpectedType.FullName != this.InferredType.FullName) { output.Write("[exp:"); - this.ExpectedType.WriteTo(output, true, true); + this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); output.Write(']'); } } else if (this.ExpectedType != null) { output.Write("[exp:"); - this.ExpectedType.WriteTo(output, true, true); + this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); output.Write(']'); } output.Write('('); @@ -425,13 +429,13 @@ namespace ICSharpCode.Decompiler.ILAst } else if (Operand is MethodReference) { MethodReference method = (MethodReference)Operand; if (method.DeclaringType != null) { - method.DeclaringType.WriteTo(output, true, true); + method.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); output.Write("::"); } output.WriteReference(method.Name, method); } else if (Operand is FieldReference) { FieldReference field = (FieldReference)Operand; - field.DeclaringType.WriteTo(output, true, true); + field.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); output.Write("::"); output.WriteReference(field.Name, field); } else { diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index 76fde12bf..d839f41ef 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -47,7 +47,13 @@ namespace ICSharpCode.Decompiler.ILAst numLdloca.Clear(); // Analyse the whole method - foreach(ILExpression expr in method.GetSelfAndChildrenRecursive()) { + AnalyzeNode(method); + } + + void AnalyzeNode(ILNode node) + { + ILExpression expr = node as ILExpression; + if (expr != null) { ILVariable locVar = expr.Operand as ILVariable; if (locVar != null) { if (expr.Code == ILCode.Stloc) { @@ -60,6 +66,16 @@ namespace ICSharpCode.Decompiler.ILAst throw new NotSupportedException(expr.Code.ToString()); } } + foreach (ILExpression child in expr.Arguments) + AnalyzeNode(child); + } else { + var catchBlock = node as ILTryCatchBlock.CatchBlock; + if (catchBlock != null && catchBlock.ExceptionVariable != null) { + numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + 1; + } + + foreach (ILNode child in node.GetChildren()) + AnalyzeNode(child); } } @@ -76,6 +92,20 @@ namespace ICSharpCode.Decompiler.ILAst { bool modified = false; List body = block.Body; + if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) { + ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable; + if (v != null && v.IsGenerated) { + if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) { + ILVariable v2; + ILExpression ldException; + if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) { + body.RemoveAt(0); + ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2; + modified = true; + } + } + } + } for(int i = 0; i < body.Count - 1;) { ILVariable locVar; ILExpression expr; diff --git a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs index cd5656c9d..e5409a2e5 100644 --- a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs +++ b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.ILAst { Dictionary labelToCfNode = new Dictionary(); - DecompilerContext context; + readonly DecompilerContext context; uint nextLabelIndex = 0; @@ -286,7 +286,7 @@ namespace ICSharpCode.Decompiler.ILAst ILLabel condLabel = caseLabels[i]; // Find or create new case block - ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.Where(b => b.EntryGoto.Operand == condLabel).FirstOrDefault(); + ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); if (caseBlock == null) { caseBlock = new ILSwitch.CaseBlock() { Values = new List(), diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs index c6d227ca3..be0a13711 100644 --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -90,6 +90,10 @@ namespace ICSharpCode.Decompiler.ILAst expr.Code = ILCode.Stobj; expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand)); modified = true; + } else if (expr.Code == ILCode.Cpobj) { + expr.Code = ILCode.Stobj; + expr.Arguments[1] = new ILExpression(ILCode.Ldobj, expr.Operand, expr.Arguments[1]); + modified = true; } ILExpression arg, arg2; TypeReference type; @@ -440,6 +444,7 @@ namespace ICSharpCode.Decompiler.ILAst #endregion #region IntroducePostIncrement + bool IntroducePostIncrement(List body, ILExpression expr, int pos) { bool modified = IntroducePostIncrementForVariables(body, expr, pos); @@ -452,7 +457,7 @@ namespace ICSharpCode.Decompiler.ILAst } return modified; } - + bool IntroducePostIncrementForVariables(List body, ILExpression expr, int pos) { // Works for variables and static fields/properties @@ -465,19 +470,50 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression exprInit; if (!(expr.Match(ILCode.Stloc, out exprVar, out exprInit) && exprVar.IsGenerated)) return false; - if (!(exprInit.Code == ILCode.Ldloc || exprInit.Code == ILCode.Ldsfld || (exprInit.Code == ILCode.CallGetter && exprInit.Arguments.Count == 0))) - return false; + //The next expression ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; if (nextExpr == null) return false; - if (exprInit.Code == ILCode.CallGetter) { - if (!(nextExpr.Code == ILCode.CallSetter && IsGetterSetterPair(exprInit.Operand, nextExpr.Operand))) - return false; - } else { - if (!(nextExpr.Code == (exprInit.Code == ILCode.Ldloc ? ILCode.Stloc : ILCode.Stsfld) && nextExpr.Operand == exprInit.Operand)) + + ILCode loadInstruction = exprInit.Code; + ILCode storeInstruction = nextExpr.Code; + bool recombineVariable = false; + + // We only recognise local variables, static fields, and static getters with no arguments + switch (loadInstruction) { + case ILCode.Ldloc: + //Must be a matching store type + if (storeInstruction != ILCode.Stloc) + return false; + ILVariable loadVar = (ILVariable)exprInit.Operand; + ILVariable storeVar = (ILVariable)nextExpr.Operand; + if (loadVar != storeVar) { + if (loadVar.OriginalVariable != null && loadVar.OriginalVariable == storeVar.OriginalVariable) + recombineVariable = true; + else + return false; + } + break; + case ILCode.Ldsfld: + if (storeInstruction != ILCode.Stsfld) + return false; + if (exprInit.Operand != nextExpr.Operand) + return false; + break; + case ILCode.CallGetter: + // non-static getters would have the 'this' argument + if (exprInit.Arguments.Count != 0) + return false; + if (storeInstruction != ILCode.CallSetter) + return false; + if (!IsGetterSetterPair(exprInit.Operand, nextExpr.Operand)) + return false; + break; + default: return false; } + ILExpression addExpr = nextExpr.Arguments[0]; int incrementAmount; @@ -485,12 +521,23 @@ namespace ICSharpCode.Decompiler.ILAst if (!(incrementAmount != 0 && addExpr.Arguments[0].MatchLdloc(exprVar))) return false; - if (exprInit.Code == ILCode.Ldloc) - exprInit.Code = ILCode.Ldloca; - else if (exprInit.Code == ILCode.CallGetter) - exprInit = new ILExpression(ILCode.AddressOf, null, exprInit); - else - exprInit.Code = ILCode.Ldsflda; + if (recombineVariable) { + // Split local variable, unsplit these two instances + foreach (var ilExpression in method.GetSelfAndChildrenRecursive(expression => expression.Operand == nextExpr.Operand)) + ilExpression.Operand = exprInit.Operand; + } + + switch (loadInstruction) { + case ILCode.Ldloc: + exprInit.Code = ILCode.Ldloca; + break; + case ILCode.Ldsfld: + exprInit.Code = ILCode.Ldsflda; + break; + case ILCode.CallGetter: + exprInit = new ILExpression(ILCode.AddressOf, null, exprInit); + break; + } expr.Arguments[0] = new ILExpression(incrementCode, incrementAmount, exprInit); body.RemoveAt(pos + 1); // TODO ILRanges return true; diff --git a/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs index e477dbff5..319e18bbb 100644 --- a/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs +++ b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs @@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.ILAst newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, condExpr, trueExpr); } } else { - // Ternary operator tends to create long complicated return statements + // Ternary operator tends to create long complicated return statements if (opCode == ILCode.Ret) return false; @@ -156,7 +156,7 @@ namespace ICSharpCode.Decompiler.ILAst // ... // v = NullCoalescing(ldloc(leftVar), rightExpr) // br(endBBLabel) - + ILVariable v, v2; ILExpression leftExpr, leftExpr2; ILVariable leftVar; @@ -165,7 +165,7 @@ namespace ICSharpCode.Decompiler.ILAst ILBasicBlock rightBB; ILExpression rightExpr; if (head.Body.Count >= 3 && - head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out v, out leftExpr) && + head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out v, out leftExpr) && leftExpr.Match(ILCode.Ldloc, out leftVar) && head.MatchLastAndBr(ILCode.Brtrue, out endBBLabel, out leftExpr2, out rightBBLabel) && leftExpr2.MatchLdloc(leftVar) && @@ -234,6 +234,107 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + public bool SimplifyCustomShortCircuit(List body, ILBasicBlock head, int pos) + { + Debug.Assert(body.Contains(head)); + + // --- looking for the following pattern --- + // stloc(targetVar, leftVar) + // brtrue(exitLabel, call(op_False, leftVar) + // br(followingBlock) + // + // FollowingBlock: + // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression)) + // br(exitLabel) + // --- + + if (head.Body.Count < 3) + return false; + + // looking for: + // stloc(targetVar, leftVar) + ILVariable targetVar; + ILExpression targetVarInitExpr; + if (!head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out targetVar, out targetVarInitExpr)) + return false; + + ILVariable leftVar; + if (!targetVarInitExpr.Match(ILCode.Ldloc, out leftVar)) + return false; + + // looking for: + // brtrue(exitLabel, call(op_False, leftVar) + // br(followingBlock) + ILExpression callExpr; + ILLabel exitLabel; + ILLabel followingBlock; + if(!head.MatchLastAndBr(ILCode.Brtrue, out exitLabel, out callExpr, out followingBlock)) + return false; + + if (labelGlobalRefCount[followingBlock] > 1) + return false; + + MethodReference opFalse; + ILExpression opFalseArg; + if (!callExpr.Match(ILCode.Call, out opFalse, out opFalseArg)) + return false; + + // ignore operators other than op_False and op_True + if (opFalse.Name != "op_False" && opFalse.Name != "op_True") + return false; + + if (!opFalseArg.MatchLdloc(leftVar)) + return false; + + ILBasicBlock followingBasicBlock = labelToBasicBlock[followingBlock]; + + // FollowingBlock: + // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression)) + // br(exitLabel) + ILVariable _targetVar; + ILExpression opBitwiseCallExpr; + ILLabel _exitLabel; + if (!followingBasicBlock.MatchSingleAndBr(ILCode.Stloc, out _targetVar, out opBitwiseCallExpr, out _exitLabel)) + return false; + + if (_targetVar != targetVar || exitLabel != _exitLabel) + return false; + + MethodReference opBitwise; + ILExpression leftVarExpression; + ILExpression rightExpression; + if (!opBitwiseCallExpr.Match(ILCode.Call, out opBitwise, out leftVarExpression, out rightExpression)) + return false; + + if (!opFalseArg.MatchLdloc(leftVarExpression.Operand as ILVariable)) + return false; + + // ignore operators other than op_BitwiseAnd and op_BitwiseOr + if (opBitwise.Name != "op_BitwiseAnd" && opBitwise.Name != "op_BitwiseOr") + return false; + + // insert: + // stloc(targetVar, LogicAnd(C::op_BitwiseAnd, leftVar, rightExpression) + // br(exitLabel) + ILCode op = opBitwise.Name == "op_BitwiseAnd" ? ILCode.LogicAnd : ILCode.LogicOr; + + if (op == ILCode.LogicAnd && opFalse.Name != "op_False") + return false; + + if (op == ILCode.LogicOr && opFalse.Name != "op_True") + return false; + + ILExpression shortCircuitExpr = MakeLeftAssociativeShortCircuit(op, opFalseArg, rightExpression); + shortCircuitExpr.Operand = opBitwise; + + head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br); + head.Body.Add(new ILExpression(ILCode.Stloc, targetVar, shortCircuitExpr)); + head.Body.Add(new ILExpression(ILCode.Br, exitLabel)); + body.Remove(followingBasicBlock); + + return true; + } + ILExpression MakeLeftAssociativeShortCircuit(ILCode code, ILExpression left, ILExpression right) { // Assuming that the inputs are already left associative diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 9474725d6..d9261fcc3 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -267,6 +267,10 @@ namespace ICSharpCode.Decompiler.ILAst return typeSystem.Boolean; case ILCode.LogicAnd: case ILCode.LogicOr: + // if Operand is set the logic and/or expression is a custom operator + // we can deal with it the same as a normal invocation. + if (expr.Operand != null) + goto case ILCode.Call; if (forceInferChildren) { InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean); InferTypeForExpression(expr.Arguments[1], typeSystem.Boolean); diff --git a/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs b/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs new file mode 100644 index 000000000..30a94a6e6 --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs @@ -0,0 +1,75 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; + +public static class CustomShortCircuitOperators +{ + private class B + { + public static bool operator true(CustomShortCircuitOperators.B x) + { + return true; + } + + public static bool operator false(CustomShortCircuitOperators.B x) + { + return false; + } + } + + private class C : CustomShortCircuitOperators.B + { + public static CustomShortCircuitOperators.C operator &(CustomShortCircuitOperators.C x, CustomShortCircuitOperators.C y) + { + return null; + } + + public static CustomShortCircuitOperators.C operator |(CustomShortCircuitOperators.C x, CustomShortCircuitOperators.C y) + { + return null; + } + + public static bool operator !(CustomShortCircuitOperators.C x) + { + return false; + } + + private static void Main() + { + CustomShortCircuitOperators.C c = new CustomShortCircuitOperators.C(); + CustomShortCircuitOperators.C c2 = new CustomShortCircuitOperators.C(); + CustomShortCircuitOperators.C c3 = c && c2; + CustomShortCircuitOperators.C c4 = c || c2; + Console.WriteLine(c3.ToString()); + Console.WriteLine(c4.ToString()); + } + + private static void Test2() + { + CustomShortCircuitOperators.C c = new CustomShortCircuitOperators.C(); + if (c && c) + { + Console.WriteLine(c.ToString()); + } + + if (!(c && c)) + { + Console.WriteLine(c.ToString()); + } + } + + private static void Test3() + { + CustomShortCircuitOperators.C c = new CustomShortCircuitOperators.C(); + if (c) + { + Console.WriteLine(c.ToString()); + } + if (!c) + { + Console.WriteLine(c.ToString()); + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/Tests/ExceptionHandling.cs b/ICSharpCode.Decompiler/Tests/ExceptionHandling.cs index 5fdc27dc5..7f6e506ee 100644 --- a/ICSharpCode.Decompiler/Tests/ExceptionHandling.cs +++ b/ICSharpCode.Decompiler/Tests/ExceptionHandling.cs @@ -2,6 +2,7 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Threading; public class ExceptionHandling { @@ -55,13 +56,38 @@ public class ExceptionHandling { Console.WriteLine(ex.Message); } - catch (Exception ex) + catch (Exception ex2) { - Console.WriteLine(ex.Message); + Console.WriteLine(ex2.Message); } catch { Console.WriteLine("other"); } } + + public void NoUsingStatementBecauseTheVariableIsAssignedTo() + { + CancellationTokenSource cancellationTokenSource = null; + try + { + cancellationTokenSource = new CancellationTokenSource(); + } + finally + { + if (cancellationTokenSource != null) + { + cancellationTokenSource.Dispose(); + } + } + } + + public void UsingStatementThatChangesTheVariable() + { + CancellationTokenSource cancellationTokenSource = null; + using (cancellationTokenSource) + { + cancellationTokenSource = new CancellationTokenSource(); + } + } } diff --git a/ICSharpCode.Decompiler/Tests/Generics.cs b/ICSharpCode.Decompiler/Tests/Generics.cs index 53cd5f623..e5b6e2eb7 100644 --- a/ICSharpCode.Decompiler/Tests/Generics.cs +++ b/ICSharpCode.Decompiler/Tests/Generics.cs @@ -60,6 +60,12 @@ public static class Generics } } + private static Type type1 = typeof(List<>); + private static Type type2 = typeof(Generics.MyArray<>); + private static Type type3 = typeof(List<>.Enumerator); + private static Type type4 = typeof(Generics.MyArray<>.NestedClass<>); + private static Type type5 = typeof(List[]); + public static void MethodWithConstraint() where T : class, S where S : ICloneable, new() { } diff --git a/ICSharpCode.Decompiler/Tests/Helpers/RemoveCompilerAttribute.cs b/ICSharpCode.Decompiler/Tests/Helpers/RemoveCompilerAttribute.cs index 0076eb2ba..251916557 100644 --- a/ICSharpCode.Decompiler/Tests/Helpers/RemoveCompilerAttribute.cs +++ b/ICSharpCode.Decompiler/Tests/Helpers/RemoveCompilerAttribute.cs @@ -15,7 +15,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var section = (AttributeSection)attribute.Parent; SimpleType type = attribute.Type as SimpleType; if (section.AttributeTarget == "assembly" && - (type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility")) + (type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "AssemblyVersion")) { attribute.Remove(); if (section.Attributes.Count == 0) diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index b4906c60f..33227a313 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -57,8 +57,10 @@ + + diff --git a/ICSharpCode.Decompiler/Tests/IncrementDecrement.cs b/ICSharpCode.Decompiler/Tests/IncrementDecrement.cs index 02a72190f..12c47186d 100644 --- a/ICSharpCode.Decompiler/Tests/IncrementDecrement.cs +++ b/ICSharpCode.Decompiler/Tests/IncrementDecrement.cs @@ -176,6 +176,12 @@ public class IncrementDecrement return i++ + j; } + public void PostIncrementInlineLocalVariable(Func f) + { + int num = 0; + f(num++); + } + public int PostIncrementArrayElement(int[] array, int pos) { return array[pos]--; diff --git a/ICSharpCode.Decompiler/Tests/PInvoke.cs b/ICSharpCode.Decompiler/Tests/PInvoke.cs new file mode 100644 index 000000000..fe9b9b5bc --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/PInvoke.cs @@ -0,0 +1,87 @@ +// 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.Runtime.InteropServices; + +// P/Invoke and marshalling attribute tests +public class PInvoke +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 2)] + public struct MarshalAsTest + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public uint[] FixedArray; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.Bool)] + public int[] FixedBoolArray; + + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] + public string[] SafeBStrArray; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] + public string FixedString; + } + + [StructLayout(LayoutKind.Explicit)] + public struct Rect + { + [FieldOffset(0)] + public int left; + [FieldOffset(4)] + public int top; + [FieldOffset(8)] + public int right; + [FieldOffset(12)] + public int bottom; + } + + public static decimal MarshalAttributesOnPropertyAccessors + { + [return: MarshalAs(UnmanagedType.Currency)] + get + { + return 0m; + } + [param: MarshalAs(UnmanagedType.Currency)] + set + { + } + } + + [DllImport("xyz.dll", CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool Method([MarshalAs(UnmanagedType.LPStr)] string input); + + [DllImport("xyz.dll")] + private static extern void New1(int ElemCnt, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] int[] ar); + + [DllImport("xyz.dll")] + private static extern void New2([MarshalAs(UnmanagedType.LPArray, SizeConst = 128)] int[] ar); + + [DllImport("xyz.dll")] + private static extern void New3([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Bool, SizeConst = 64, SizeParamIndex = 1)] int[] ar); + + public void CustomMarshal1([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "MyCompany.MyMarshaler")] object o) + { + } + + public void CustomMarshal2([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "MyCompany.MyMarshaler", MarshalCookie = "Cookie")] object o) + { + } +} diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index 998c17370..a3100391c 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.Decompiler.Tests TestFile(@"..\..\Tests\DelegateConstruction.cs"); } - [Test, Ignore("arg-Variables in catch clauses")] + [Test] public void ExceptionHandling() { TestFile(@"..\..\Tests\ExceptionHandling.cs"); @@ -49,6 +49,12 @@ namespace ICSharpCode.Decompiler.Tests TestFile(@"..\..\Tests\Generics.cs"); } + [Test] + public void CustomShortCircuitOperators() + { + TestFile(@"..\..\Tests\CustomShortCircuitOperators.cs"); + } + [Test] public void IncrementDecrement() { @@ -73,6 +79,12 @@ namespace ICSharpCode.Decompiler.Tests TestFile(@"..\..\Tests\MultidimensionalArray.cs"); } + [Test] + public void PInvoke() + { + TestFile(@"..\..\Tests\PInvoke.cs"); + } + [Test] public void PropertiesAndEvents() { diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index c88f832dc..fd4fb4891 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.ILSpy output.AddVisualLineElementGenerator(new MyLinkElementGenerator("SharpDevelop", "http://www.icsharpcode.net/opensource/sd/")); output.AddVisualLineElementGenerator(new MyLinkElementGenerator("MIT License", "resource:license.txt")); output.AddVisualLineElementGenerator(new MyLinkElementGenerator("LGPL", "resource:LGPL.txt")); - textView.Show(output); + textView.ShowText(output); } sealed class MyLinkElementGenerator : LinkElementGenerator @@ -130,7 +130,7 @@ namespace ICSharpCode.ILSpy } catch (Exception ex) { AvalonEditTextOutput exceptionOutput = new AvalonEditTextOutput(); exceptionOutput.WriteLine(ex.ToString()); - textView.Show(exceptionOutput); + textView.ShowText(exceptionOutput); } }, TaskScheduler.FromCurrentSynchronizationContext()); }; diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index f0644d70a..0eab2937f 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -165,7 +165,7 @@ namespace ICSharpCode.ILSpy } } } - ILSpy.MainWindow.Instance.TextView.Show(output); + ILSpy.MainWindow.Instance.TextView.ShowText(output); } else { Process.Start(e.Uri.ToString()); } diff --git a/ILSpy/BamlDecompiler.cs b/ILSpy/BamlDecompiler.cs index c223dcb82..07c60b13e 100644 --- a/ILSpy/BamlDecompiler.cs +++ b/ILSpy/BamlDecompiler.cs @@ -401,7 +401,7 @@ namespace ICSharpCode.ILSpy.Baml } return output; }), - t => textView.Show(t.Result, highlighting) + t => textView.ShowNode(t.Result, this, highlighting) ); return true; } diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 50afed4ea..928e53a37 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -47,11 +47,11 @@ namespace ICSharpCode.ILSpy string name = "C#"; bool showAllMembers = false; Predicate transformAbortCondition = null; - + public CSharpLanguage() { } - + #if DEBUG internal static IEnumerable GetDebugLanguages() { @@ -72,27 +72,72 @@ namespace ICSharpCode.ILSpy }; } #endif - - public override string Name { + + public override string Name + { get { return name; } } - - public override string FileExtension { + + public override string FileExtension + { get { return ".cs"; } } - - public override string ProjectFileExtension { + + public override string ProjectFileExtension + { get { return ".csproj"; } } - + public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(method.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: method.DeclaringType, isSingleMember: true); - codeDomBuilder.AddMethod(method); - RunTransformsAndGenerateCode(codeDomBuilder, output, options); + if (method.IsConstructor && !method.IsStatic && !method.DeclaringType.IsValueType) { + // also fields and other ctors so that the field initializers can be shown as such + AddFieldsAndCtors(codeDomBuilder, method.DeclaringType, method.IsStatic); + RunTransformsAndGenerateCode(codeDomBuilder, output, options, new SelectCtorTransform(method)); + } else { + codeDomBuilder.AddMethod(method); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); + } } + class SelectCtorTransform : IAstTransform + { + readonly MethodDefinition ctorDef; + + public SelectCtorTransform(MethodDefinition ctorDef) + { + this.ctorDef = ctorDef; + } + + public void Run(AstNode compilationUnit) + { + ConstructorDeclaration ctorDecl = null; + foreach (var node in compilationUnit.Children) { + ConstructorDeclaration ctor = node as ConstructorDeclaration; + if (ctor != null) { + if (ctor.Annotation() == ctorDef) { + ctorDecl = ctor; + } else { + // remove other ctors + ctor.Remove(); + } + } + // Remove any fields without initializers + FieldDeclaration fd = node as FieldDeclaration; + if (fd != null && fd.Variables.All(v => v.Initializer.IsNull)) + fd.Remove(); + } + if (ctorDecl.Initializer.ConstructorInitializerType == ConstructorInitializerType.This) { + // remove all fields + foreach (var node in compilationUnit.Children) + if (node is FieldDeclaration) + node.Remove(); + } + } + } + public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(property.DeclaringType, includeNamespace: true)); @@ -100,15 +145,55 @@ namespace ICSharpCode.ILSpy codeDomBuilder.AddProperty(property); RunTransformsAndGenerateCode(codeDomBuilder, output, options); } - + public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(field.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: field.DeclaringType, isSingleMember: true); - codeDomBuilder.AddField(field); - RunTransformsAndGenerateCode(codeDomBuilder, output, options); + if (field.IsLiteral) { + codeDomBuilder.AddField(field); + } else { + // also decompile ctors so that the field initializer can be shown + AddFieldsAndCtors(codeDomBuilder, field.DeclaringType, field.IsStatic); + } + RunTransformsAndGenerateCode(codeDomBuilder, output, options, new SelectFieldTransform(field)); + } + + /// + /// Removes all top-level members except for the specified fields. + /// + sealed class SelectFieldTransform : IAstTransform + { + readonly FieldDefinition field; + + public SelectFieldTransform(FieldDefinition field) + { + this.field = field; + } + + public void Run(AstNode compilationUnit) + { + foreach (var child in compilationUnit.Children) { + if (child is AttributedNode) { + if (child.Annotation() != field) + child.Remove(); + } + } + } } + void AddFieldsAndCtors(AstBuilder codeDomBuilder, TypeDefinition declaringType, bool isStatic) + { + foreach (var field in declaringType.Fields) { + if (field.IsStatic == isStatic) + codeDomBuilder.AddField(field); + } + foreach (var ctor in declaringType.Methods) { + if (ctor.IsConstructor && ctor.IsStatic == isStatic) + codeDomBuilder.AddMethod(ctor); + } + } + public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(ev.DeclaringType, includeNamespace: true)); @@ -116,7 +201,7 @@ namespace ICSharpCode.ILSpy codeDomBuilder.AddEvent(ev); RunTransformsAndGenerateCode(codeDomBuilder, output, options); } - + public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: type); @@ -124,14 +209,18 @@ namespace ICSharpCode.ILSpy RunTransformsAndGenerateCode(codeDomBuilder, output, options); } - void RunTransformsAndGenerateCode(AstBuilder astBuilder, ITextOutput output, DecompilationOptions options) + void RunTransformsAndGenerateCode(AstBuilder astBuilder, ITextOutput output, DecompilationOptions options, IAstTransform additionalTransform = null) { astBuilder.RunTransformations(transformAbortCondition); - if (options.DecompilerSettings.ShowXmlDocumentation) + if (additionalTransform != null) { + additionalTransform.Run(astBuilder.CompilationUnit); + } + if (options.DecompilerSettings.ShowXmlDocumentation) { AddXmlDocTransform.Run(astBuilder.CompilationUnit); + } astBuilder.GenerateCode(output); } - + public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) { if (options.FullDecompilation && options.SaveAsProjectDirectory != null) { @@ -150,7 +239,7 @@ namespace ICSharpCode.ILSpy } } } - + #region WriteProjectFile void WriteProjectFile(TextWriter writer, IEnumerable> files, ModuleDefinition module) { @@ -178,20 +267,20 @@ namespace ICSharpCode.ILSpy w.WriteStartElement("Project", ns); w.WriteAttributeString("ToolsVersion", "4.0"); w.WriteAttributeString("DefaultTargets", "Build"); - + w.WriteStartElement("PropertyGroup"); w.WriteElementString("ProjectGuid", Guid.NewGuid().ToString().ToUpperInvariant()); - + w.WriteStartElement("Configuration"); w.WriteAttributeString("Condition", " '$(Configuration)' == '' "); w.WriteValue("Debug"); w.WriteEndElement(); // - + w.WriteStartElement("Platform"); w.WriteAttributeString("Condition", " '$(Platform)' == '' "); w.WriteValue(platformName); w.WriteEndElement(); // - + switch (module.Kind) { case ModuleKind.Windows: w.WriteElementString("OutputType", "WinExe"); @@ -203,7 +292,7 @@ namespace ICSharpCode.ILSpy w.WriteElementString("OutputType", "Library"); break; } - + w.WriteElementString("AssemblyName", module.Assembly.Name.Name); switch (module.Runtime) { case TargetRuntime.Net_1_0: @@ -222,14 +311,14 @@ namespace ICSharpCode.ILSpy break; } w.WriteElementString("WarningLevel", "4"); - + w.WriteEndElement(); // - + w.WriteStartElement("PropertyGroup"); // platform-specific w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' "); w.WriteElementString("PlatformTarget", platformName); w.WriteEndElement(); // (platform-specific) - + w.WriteStartElement("PropertyGroup"); // Debug w.WriteAttributeString("Condition", " '$(Configuration)' == 'Debug' "); w.WriteElementString("OutputPath", "bin\\Debug\\"); @@ -237,7 +326,7 @@ namespace ICSharpCode.ILSpy w.WriteElementString("DebugType", "full"); w.WriteElementString("Optimize", "false"); w.WriteEndElement(); // (Debug) - + w.WriteStartElement("PropertyGroup"); // Release w.WriteAttributeString("Condition", " '$(Configuration)' == 'Release' "); w.WriteElementString("OutputPath", "bin\\Release\\"); @@ -245,8 +334,8 @@ namespace ICSharpCode.ILSpy w.WriteElementString("DebugType", "pdbonly"); w.WriteElementString("Optimize", "true"); w.WriteEndElement(); // (Release) - - + + w.WriteStartElement("ItemGroup"); // References foreach (AssemblyNameReference r in module.AssemblyReferences) { if (r.Name != "mscorlib") { @@ -257,7 +346,7 @@ namespace ICSharpCode.ILSpy } } w.WriteEndElement(); // (References) - + foreach (IGrouping gr in (from f in files group f.Item2 by f.Item1 into g orderby g.Key select g)) { w.WriteStartElement("ItemGroup"); foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) { @@ -267,16 +356,16 @@ namespace ICSharpCode.ILSpy } w.WriteEndElement(); } - + w.WriteStartElement("Import"); w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"); w.WriteEndElement(); - + w.WriteEndDocument(); } } #endregion - + #region WriteCodeFilesInProject bool IncludeTypeWhenDecompilingProject(TypeDefinition type, DecompilationOptions options) { @@ -286,11 +375,11 @@ namespace ICSharpCode.ILSpy return false; return true; } - + IEnumerable> WriteCodeFilesInProject(AssemblyDefinition assembly, DecompilationOptions options, HashSet directories) { var files = assembly.MainModule.Types.Where(t => IncludeTypeWhenDecompilingProject(t, options)).GroupBy( - delegate (TypeDefinition type) { + delegate(TypeDefinition type) { string file = TextView.DecompilerTextView.CleanUpName(type.Name) + this.FileExtension; if (string.IsNullOrEmpty(type.Namespace)) { return file; @@ -305,7 +394,7 @@ namespace ICSharpCode.ILSpy Parallel.ForEach( files, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, - delegate (IGrouping file) { + delegate(IGrouping file) { using (StreamWriter w = new StreamWriter(Path.Combine(options.SaveAsProjectDirectory, file.Key))) { AstBuilder codeDomBuilder = CreateAstBuilder(options, currentModule: assembly.MainModule); foreach (TypeDefinition type in file) { @@ -319,7 +408,7 @@ namespace ICSharpCode.ILSpy return files.Select(f => Tuple.Create("Compile", f.Key)); } #endregion - + #region WriteResourceFilesInProject IEnumerable> WriteResourceFilesInProject(LoadedAssembly assembly, DecompilationOptions options, HashSet directories) { @@ -333,7 +422,8 @@ namespace ICSharpCode.ILSpy IEnumerable rs = null; try { rs = new ResourceSet(s).Cast(); - } catch (ArgumentException) { + } + catch (ArgumentException) { } if (rs != null && rs.All(e => e.Value is Stream)) { foreach (var pair in rs) { @@ -351,7 +441,8 @@ namespace ICSharpCode.ILSpy string xaml = null; try { xaml = decompiler.DecompileBaml(ms, assembly.FileName, new ConnectMethodDecompiler(assembly), new AssemblyResolver(assembly)); - } catch (XamlXmlWriterException) {} // ignore XAML writer exceptions + } + catch (XamlXmlWriterException) { } // ignore XAML writer exceptions if (xaml != null) { File.WriteAllText(Path.Combine(options.SaveAsProjectDirectory, Path.ChangeExtension(fileName, ".xaml")), xaml); yield return Tuple.Create("Page", Path.ChangeExtension(fileName, ".xaml")); @@ -372,12 +463,13 @@ namespace ICSharpCode.ILSpy } yield return Tuple.Create("EmbeddedResource", fileName); } - } finally { + } + finally { if (bamlDecompilerAppDomain != null) AppDomain.Unload(bamlDecompilerAppDomain); } } - + string GetFileNameForResource(string fullName, HashSet directories) { string[] splitName = fullName.Split('.'); @@ -393,7 +485,7 @@ namespace ICSharpCode.ILSpy return fileName; } #endregion - + AstBuilder CreateAstBuilder(DecompilationOptions options, ModuleDefinition currentModule = null, TypeDefinition currentType = null, bool isSingleMember = false) { if (currentModule == null) @@ -417,7 +509,7 @@ namespace ICSharpCode.ILSpy if (includeNamespace) options |= ConvertTypeOptions.IncludeNamespace; AstType astType = AstBuilder.ConvertType(type, typeAttributes, options); - + StringWriter w = new StringWriter(); if (type.IsByReference) { ParameterDefinition pd = typeAttributes as ParameterDefinition; @@ -425,11 +517,11 @@ namespace ICSharpCode.ILSpy w.Write("out "); else w.Write("ref "); - + if (astType is ComposedType && ((ComposedType)astType).PointerRank > 0) ((ComposedType)astType).PointerRank--; } - + astType.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingOptions()), null); return w.ToString(); } @@ -464,12 +556,20 @@ namespace ICSharpCode.ILSpy } else return property.Name; } - + public override bool ShowMember(MemberReference member) { return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings); } - + + public override MemberReference GetOriginalCodeLocation(MemberReference member) + { + if (showAllMembers || !DecompilerSettingsPanel.CurrentDecompilerSettings.AnonymousMethods) + return member; + else + return ICSharpCode.ILSpy.TreeNodes.Analyzer.Helpers.GetOriginalCodeLocation(member); + } + public override string GetTooltip(MemberReference member) { MethodDefinition md = member as MethodDefinition; @@ -490,12 +590,12 @@ namespace ICSharpCode.ILSpy b.RunTransformations(); foreach (var attribute in b.CompilationUnit.Descendants.OfType()) attribute.Remove(); - + StringWriter w = new StringWriter(); b.GenerateCode(new PlainTextOutput(w)); return Regex.Replace(w.ToString(), @"\s+", " ").TrimEnd(); } - + return base.GetTooltip(member); } } diff --git a/ILSpy/Commands.cs b/ILSpy/Commands.cs index 67775f43d..8d89e9afc 100644 --- a/ILSpy/Commands.cs +++ b/ILSpy/Commands.cs @@ -122,7 +122,7 @@ namespace ICSharpCode.ILSpy return output; } ), - task => MainWindow.Instance.TextView.Show(task.Result)); + task => MainWindow.Instance.TextView.ShowText(task.Result)); } } #endif diff --git a/ILSpy/ConnectMethodDecompiler.cs b/ILSpy/ConnectMethodDecompiler.cs index b5487f06a..7cb2c1760 100644 --- a/ILSpy/ConnectMethodDecompiler.cs +++ b/ILSpy/ConnectMethodDecompiler.cs @@ -1,5 +1,20 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) +// 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; diff --git a/ILSpy/DisplaySettings.cs b/ILSpy/DisplaySettings.cs new file mode 100644 index 000000000..569bc0509 --- /dev/null +++ b/ILSpy/DisplaySettings.cs @@ -0,0 +1,93 @@ +// 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.ComponentModel; +using System.Windows.Media; + +namespace ICSharpCode.ILSpy +{ + /// + /// Description of DisplaySettings. + /// + public class DisplaySettings : INotifyPropertyChanged + { + public DisplaySettings() + { + } + + #region INotifyPropertyChanged implementation + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (PropertyChanged != null) { + PropertyChanged(this, e); + } + } + + protected void OnPropertyChanged(string propertyName) + { + OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); + } + #endregion + + FontFamily selectedFont; + + public FontFamily SelectedFont { + get { return selectedFont; } + set { + if (selectedFont != value) { + selectedFont = value; + OnPropertyChanged("SelectedFont"); + } + } + } + + double selectedFontSize; + + public double SelectedFontSize { + get { return selectedFontSize; } + set { + if (selectedFontSize != value) { + selectedFontSize = value; + OnPropertyChanged("SelectedFontSize"); + } + } + } + + bool showLineNumbers; + + public bool ShowLineNumbers { + get { return showLineNumbers; } + set { + if (showLineNumbers != value) { + showLineNumbers = value; + OnPropertyChanged("ShowLineNumbers"); + } + } + } + + public void CopyValues(DisplaySettings s) + { + this.SelectedFont = s.selectedFont; + this.SelectedFontSize = s.selectedFontSize; + this.ShowLineNumbers = s.showLineNumbers; + } + } +} diff --git a/ILSpy/DisplaySettingsPanel.xaml b/ILSpy/DisplaySettingsPanel.xaml new file mode 100644 index 000000000..97131eca3 --- /dev/null +++ b/ILSpy/DisplaySettingsPanel.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + + + + + + + + Show line numbers + + + \ No newline at end of file diff --git a/ILSpy/DisplaySettingsPanel.xaml.cs b/ILSpy/DisplaySettingsPanel.xaml.cs new file mode 100644 index 000000000..b66bb316d --- /dev/null +++ b/ILSpy/DisplaySettingsPanel.xaml.cs @@ -0,0 +1,154 @@ +// 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.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using System.Xml.Linq; +using ICSharpCode.Decompiler; + +namespace ICSharpCode.ILSpy +{ + /// + /// Interaction logic for DisplaySettingsPanel.xaml + /// + [ExportOptionPage("Display")] + public partial class DisplaySettingsPanel : UserControl, IOptionPage + { + public DisplaySettingsPanel() + { + InitializeComponent(); + + Task task = new Task(FontLoader); + task.Start(); + task.ContinueWith( + delegate(Task continuation) { + App.Current.Dispatcher.Invoke( + DispatcherPriority.Normal, + (Action)( + () => { + fontSelector.ItemsSource = task.Result; + if (continuation.Exception != null) { + foreach (var ex in continuation.Exception.InnerExceptions) { + MessageBox.Show(ex.ToString()); + } + } + }) + ); + } + ); + } + + public void Load(ILSpySettings settings) + { + this.DataContext = LoadDisplaySettings(settings); + } + + static DisplaySettings currentDisplaySettings; + + public static DisplaySettings CurrentDisplaySettings { + get { + return currentDisplaySettings ?? (currentDisplaySettings = LoadDisplaySettings(ILSpySettings.Load())); + } + } + + static bool IsSymbolFont(FontFamily fontFamily) + { + foreach (var tf in fontFamily.GetTypefaces()) { + GlyphTypeface glyph; + try { + if (tf.TryGetGlyphTypeface(out glyph)) + return glyph.Symbol; + } catch (Exception) { + return true; + } + } + return false; + } + + static FontFamily[] FontLoader() + { + return Fonts.SystemFontFamilies + .Where(ff => !IsSymbolFont(ff)) + .OrderBy(ff => ff.Source) + .ToArray(); + } + + public static DisplaySettings LoadDisplaySettings(ILSpySettings settings) + { + XElement e = settings["DisplaySettings"]; + DisplaySettings s = new DisplaySettings(); + s.SelectedFont = new FontFamily((string)e.Attribute("Font") ?? "Consolas"); + s.SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3; + s.ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false; + + return s; + } + + public void Save(XElement root) + { + DisplaySettings s = (DisplaySettings)this.DataContext; + + currentDisplaySettings.CopyValues(s); + + XElement section = new XElement("DisplaySettings"); + section.SetAttributeValue("Font", s.SelectedFont.Source); + section.SetAttributeValue("FontSize", s.SelectedFontSize); + section.SetAttributeValue("ShowLineNumbers", s.ShowLineNumbers); + + XElement existingElement = root.Element("DisplaySettings"); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + root.Add(section); + } + } + + public class FontSizeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is double) { + return Math.Round((double)value / 4 * 3); + } + + throw new NotImplementedException(); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is string) { + double d; + if (double.TryParse((string)value, out d)) + return d * 4 / 3; + return 11 * 4 / 3; + } + + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ILSpy/ILAstLanguage.cs b/ILSpy/ILAstLanguage.cs index 84348a4de..a462c2f21 100644 --- a/ILSpy/ILAstLanguage.cs +++ b/ILSpy/ILAstLanguage.cs @@ -68,7 +68,7 @@ namespace ICSharpCode.ILSpy output.Write(" : "); if (v.IsPinned) output.Write("pinned "); - v.Type.WriteTo(output, true, true); + v.Type.WriteTo(output, ILNameSyntax.ShortTypeName); } output.WriteLine(); } @@ -100,7 +100,7 @@ namespace ICSharpCode.ILSpy public override string TypeToString(TypeReference t, bool includeNamespace, ICustomAttributeProvider attributeProvider) { PlainTextOutput output = new PlainTextOutput(); - t.WriteTo(output, true, shortName: !includeNamespace); + t.WriteTo(output, includeNamespace ? ILNameSyntax.TypeName : ILNameSyntax.ShortTypeName); return output.ToString(); } } diff --git a/ILSpy/ILLanguage.cs b/ILSpy/ILLanguage.cs index e77497fa5..7578f107f 100644 --- a/ILSpy/ILLanguage.cs +++ b/ILSpy/ILLanguage.cs @@ -62,12 +62,38 @@ namespace ICSharpCode.ILSpy public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleProperty(property); + ReflectionDisassembler rd = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + rd.DisassembleProperty(property); + if (property.GetMethod != null) { + output.WriteLine(); + rd.DisassembleMethod(property.GetMethod); + } + if (property.SetMethod != null) { + output.WriteLine(); + rd.DisassembleMethod(property.SetMethod); + } + foreach (var m in property.OtherMethods) { + output.WriteLine(); + rd.DisassembleMethod(m); + } } public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) { - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleEvent(ev); + ReflectionDisassembler rd = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + rd.DisassembleEvent(ev); + if (ev.AddMethod != null) { + output.WriteLine(); + rd.DisassembleMethod(ev.AddMethod); + } + if (ev.RemoveMethod != null) { + output.WriteLine(); + rd.DisassembleMethod(ev.RemoveMethod); + } + foreach (var m in ev.OtherMethods) { + output.WriteLine(); + rd.DisassembleMethod(m); + } } public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) @@ -85,13 +111,23 @@ namespace ICSharpCode.ILSpy output.WriteLine("// " + assembly.FileName); output.WriteLine(); - new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).WriteAssemblyHeader(assembly.AssemblyDefinition); + ReflectionDisassembler rd = new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken); + if (options.FullDecompilation) + rd.WriteAssemblyReferences(assembly.AssemblyDefinition.MainModule); + rd.WriteAssemblyHeader(assembly.AssemblyDefinition); + output.WriteLine(); + rd.WriteModuleHeader(assembly.AssemblyDefinition.MainModule); + if (options.FullDecompilation) { + output.WriteLine(); + output.WriteLine(); + rd.WriteModuleContents(assembly.AssemblyDefinition.MainModule); + } } public override string TypeToString(TypeReference t, bool includeNamespace, ICustomAttributeProvider attributeProvider) { PlainTextOutput output = new PlainTextOutput(); - t.WriteTo(output, true, shortName: !includeNamespace); + t.WriteTo(output, includeNamespace ? ILNameSyntax.TypeName : ILNameSyntax.ShortTypeName); return output.ToString(); } } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index e14465833..0bda1e382 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -102,6 +102,7 @@ DecompilerSettingsPanel.xaml Code + @@ -138,22 +139,27 @@ Code + + DisplaySettingsPanel.xaml + Code + - + + + - @@ -230,6 +236,7 @@ SearchPane.cs + DecompilerTextView.cs @@ -237,6 +244,7 @@ + @@ -304,6 +312,5 @@ ICSharpCode.TreeView - \ No newline at end of file diff --git a/ILSpy/ILSpySettings.cs b/ILSpy/ILSpySettings.cs index 7d0fd19cb..053bee7ce 100644 --- a/ILSpy/ILSpySettings.cs +++ b/ILSpy/ILSpySettings.cs @@ -19,6 +19,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Xml; using System.Xml.Linq; @@ -58,7 +59,7 @@ namespace ICSharpCode.ILSpy { using (new MutexProtector(ConfigFileMutex)) { try { - XDocument doc = XDocument.Load(GetConfigFile()); + XDocument doc = LoadWithoutCheckingCharacters(GetConfigFile()); return new ILSpySettings(doc.Root); } catch (IOException) { return new ILSpySettings(); @@ -68,6 +69,15 @@ namespace ICSharpCode.ILSpy } } + static XDocument LoadWithoutCheckingCharacters(string fileName) + { + // XDocument.Load(fileName) validates that no invalid characters appear (not even in escaped form), + // but we need those characters for some obfuscated assemblies. + using (XmlTextReader r = new XmlTextReader(fileName)) { + return XDocument.Load(r); + } + } + /// /// Saves a setting section. /// @@ -94,7 +104,7 @@ namespace ICSharpCode.ILSpy string config = GetConfigFile(); XDocument doc; try { - doc = XDocument.Load(config); + doc = LoadWithoutCheckingCharacters(config); } catch (IOException) { // ensure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(config)); @@ -104,7 +114,12 @@ namespace ICSharpCode.ILSpy } doc.Root.SetAttributeValue("version", RevisionClass.Major + "." + RevisionClass.Minor + "." + RevisionClass.Build + "." + RevisionClass.Revision); action(doc.Root); - doc.Save(config); + // We can't use XDocument.Save(filename) because that checks for invalid characters, but those can appear + // in obfuscated assemblies. + using (XmlTextWriter writer = new XmlTextWriter(config, Encoding.UTF8)) { + writer.Formatting = Formatting.Indented; + doc.Save(writer); + } } } diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs index 361f6a5a8..8755e772e 100644 --- a/ILSpy/Images/Images.cs +++ b/ILSpy/Images/Images.cs @@ -65,6 +65,7 @@ namespace ICSharpCode.ILSpy public static readonly BitmapImage Interface = LoadBitmap("Interface"); public static readonly BitmapImage Delegate = LoadBitmap("Delegate"); public static readonly BitmapImage Enum = LoadBitmap("Enum"); + public static readonly BitmapImage StaticClass = LoadBitmap("StaticClass"); public static readonly BitmapImage Field = LoadBitmap("Field"); @@ -133,6 +134,7 @@ namespace ICSharpCode.ILSpy PreloadPublicIconToCache(TypeIcon.Struct, Images.Struct); PreloadPublicIconToCache(TypeIcon.Interface, Images.Interface); PreloadPublicIconToCache(TypeIcon.Delegate, Images.Delegate); + PreloadPublicIconToCache(TypeIcon.StaticClass, Images.StaticClass); } protected override ImageSource GetBaseImage(TypeIcon icon) @@ -154,6 +156,9 @@ namespace ICSharpCode.ILSpy case TypeIcon.Delegate: baseImage = Images.Delegate; break; + case TypeIcon.StaticClass: + baseImage = Images.StaticClass; + break; default: throw new NotSupportedException(); } diff --git a/ILSpy/Images/StaticClass.png b/ILSpy/Images/StaticClass.png new file mode 100644 index 000000000..af9ebd06a Binary files /dev/null and b/ILSpy/Images/StaticClass.png differ diff --git a/ILSpy/Images/TypeIcon.cs b/ILSpy/Images/TypeIcon.cs index 2e5f3f498..7bbb0d08a 100644 --- a/ILSpy/Images/TypeIcon.cs +++ b/ILSpy/Images/TypeIcon.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.ILSpy Enum, Struct, Interface, - Delegate + Delegate, + StaticClass } } diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index 539de51c8..c29818621 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -35,66 +35,69 @@ namespace ICSharpCode.ILSpy /// Gets the name of the language (as shown in the UI) /// public abstract string Name { get; } - + /// /// Gets the file extension used by source code files in this language. /// public abstract string FileExtension { get; } - - public virtual string ProjectFileExtension { + + public virtual string ProjectFileExtension + { get { return null; } } - + /// /// Gets the syntax highlighting used for this language. /// - public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { - get { + public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting + { + get + { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension); } } - + public virtual void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(method.DeclaringType, true) + "." + method.Name); } - + public virtual void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(property.DeclaringType, true) + "." + property.Name); } - + public virtual void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(field.DeclaringType, true) + "." + field.Name); } - + public virtual void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(ev.DeclaringType, true) + "." + ev.Name); } - + public virtual void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, TypeToString(type, true)); } - + public virtual void DecompileNamespace(string nameSpace, IEnumerable types, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, nameSpace); } - + public virtual void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) { WriteCommentLine(output, assembly.FileName); WriteCommentLine(output, assembly.AssemblyDefinition.FullName); } - + public virtual void WriteCommentLine(ITextOutput output, string comment) { output.WriteLine("// " + comment); } - + /// /// Converts a type reference into a string. This method is used by the member tree node for parameter and return types. /// @@ -105,7 +108,7 @@ namespace ICSharpCode.ILSpy else return type.Name; } - + /// /// Converts a member signature to a string. /// This is used for displaying the tooltip on a member reference. @@ -117,14 +120,14 @@ namespace ICSharpCode.ILSpy else return member.ToString(); } - + public virtual string FormatPropertyName(PropertyDefinition property, bool? isIndexer = null) { if (property == null) throw new ArgumentNullException("property"); return property.Name; } - + /// /// Used for WPF keyboard navigation. /// @@ -132,39 +135,49 @@ namespace ICSharpCode.ILSpy { return Name; } - + public virtual bool ShowMember(MemberReference member) { return true; } + + /// + /// Used by the analyzer to map compiler generated code back to the original code's location + /// + public virtual MemberReference GetOriginalCodeLocation(MemberReference member) + { + return member; + } } - + public static class Languages { static ReadOnlyCollection allLanguages; - + /// /// A list of all languages. /// - public static ReadOnlyCollection AllLanguages { - get { + public static ReadOnlyCollection AllLanguages + { + get + { return allLanguages; } } - - + + internal static void Initialize(CompositionContainer composition) { List languages = new List(); languages.AddRange(composition.GetExportedValues()); languages.Add(new ILLanguage(true)); - #if DEBUG +#if DEBUG languages.AddRange(ILAstLanguage.GetDebugLanguages()); languages.AddRange(CSharpLanguage.GetDebugLanguages()); - #endif +#endif allLanguages = languages.AsReadOnly(); } - + /// /// Gets a language using its name. /// If the language is not found, C# is returned instead. diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 262365ed0..4425555af 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -242,7 +242,7 @@ namespace ICSharpCode.ILSpy if (!found) { AvalonEditTextOutput output = new AvalonEditTextOutput(); output.Write("Cannot find " + args.NavigateTo); - decompilerTextView.Show(output); + decompilerTextView.ShowText(output); } } commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore @@ -511,8 +511,12 @@ namespace ICSharpCode.ILSpy if (ignoreDecompilationRequests) return; - if (recordHistory) - history.Record(new NavigationState(treeView.SelectedItems.OfType(), decompilerTextView.GetState())); + if (recordHistory) { + var dtState = decompilerTextView.GetState(); + if(dtState != null) + history.UpdateCurrent(new NavigationState(dtState)); + history.Record(new NavigationState(treeView.SelectedItems.OfType())); + } if (treeView.SelectedItems.Count == 1) { ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; @@ -586,8 +590,9 @@ namespace ICSharpCode.ILSpy void NavigateHistory(bool forward) { - var combinedState = new NavigationState(treeView.SelectedItems.OfType(), decompilerTextView.GetState()); - history.Record(combinedState, replace: true, clearForward: false); + var dtState = decompilerTextView.GetState(); + if(dtState != null) + history.UpdateCurrent(new NavigationState(dtState)); var newState = forward ? history.GoForward() : history.GoBack(); ignoreDecompilationRequests = true; diff --git a/ILSpy/NavigationHistory.cs b/ILSpy/NavigationHistory.cs index 0fb1bed05..c5983a96d 100644 --- a/ILSpy/NavigationHistory.cs +++ b/ILSpy/NavigationHistory.cs @@ -71,13 +71,18 @@ namespace ICSharpCode.ILSpy back.Clear(); forward.Clear(); } + + public void UpdateCurrent(T node) + { + current = node; + } - public void Record(T node, bool replace = false, bool clearForward = true) + public void Record(T node) { var navigationTime = DateTime.Now; var period = navigationTime - lastNavigationTime; - if (period.TotalSeconds < NavigationSecondsBeforeNewEntry || replace) { + if (period.TotalSeconds < NavigationSecondsBeforeNewEntry) { current = node; } else { if (current != null) @@ -88,8 +93,7 @@ namespace ICSharpCode.ILSpy current = node; } - if (clearForward) - forward.Clear(); + forward.Clear(); lastNavigationTime = navigationTime; } diff --git a/ILSpy/NavigationState.cs b/ILSpy/NavigationState.cs index 7a86ebec5..123c0b642 100644 --- a/ILSpy/NavigationState.cs +++ b/ILSpy/NavigationState.cs @@ -32,12 +32,18 @@ namespace ICSharpCode.ILSpy public IEnumerable TreeNodes { get { return treeNodes; } } public DecompilerTextViewState ViewState { get; private set; } - public NavigationState(IEnumerable treeNodes, DecompilerTextViewState viewState) + public NavigationState(DecompilerTextViewState viewState) { - this.treeNodes = new HashSet(treeNodes); + this.treeNodes = new HashSet(viewState.DecompiledNodes); ViewState = viewState; } + public NavigationState(IEnumerable treeNodes) + { + this.treeNodes = new HashSet(treeNodes); + } + + public bool Equals(NavigationState other) { // TODO: should this care about the view state as well? diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 1241037b1..024994462 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -29,6 +29,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; @@ -60,6 +61,7 @@ namespace ICSharpCode.ILSpy.TextView readonly UIElementGenerator uiElementGenerator; List activeCustomElementGenerators = new List(); FoldingManager foldingManager; + ILSpyTreeNode[] decompiledNodes; DefinitionLookup definitionLookup; CancellationTokenSource currentCancellationTokenSource; @@ -85,6 +87,9 @@ namespace ICSharpCode.ILSpy.TextView textEditor.Options.RequireControlModifierForHyperlinkClick = false; textEditor.TextArea.TextView.MouseHover += TextViewMouseHover; textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped; + textEditor.SetBinding(TextEditor.FontFamilyProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFont") }); + textEditor.SetBinding(TextEditor.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") }); + textEditor.SetBinding(TextEditor.ShowLineNumbersProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("ShowLineNumbers") }); } #endregion @@ -207,11 +212,21 @@ namespace ICSharpCode.ILSpy.TextView #endregion #region ShowOutput + public void ShowText(AvalonEditTextOutput textOutput) + { + ShowNodes(textOutput, null); + } + + public void ShowNode(AvalonEditTextOutput textOutput, ILSpyTreeNode node, IHighlightingDefinition highlighting = null) + { + ShowNodes(textOutput, new[] { node }, highlighting); + } + /// /// Shows the given output in the text view. /// Cancels any currently running decompilation tasks. /// - public void Show(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null) + public void ShowNodes(AvalonEditTextOutput textOutput, ILSpyTreeNode[] nodes, IHighlightingDefinition highlighting = null) { // Cancel the decompilation task: if (currentCancellationTokenSource != null) { @@ -220,6 +235,7 @@ namespace ICSharpCode.ILSpy.TextView } this.nextDecompilationRun = null; // remove scheduled decompilation run ShowOutput(textOutput, highlighting); + decompiledNodes = nodes; } /// @@ -340,6 +356,7 @@ namespace ICSharpCode.ILSpy.TextView } ShowOutput(output); } + decompiledNodes = context.TreeNodes; }); } @@ -513,6 +530,7 @@ namespace ICSharpCode.ILSpy.TextView output.WriteLine(ex.ToString()); ShowOutput(output); } + decompiledNodes = context.TreeNodes; }); } @@ -575,11 +593,15 @@ namespace ICSharpCode.ILSpy.TextView public DecompilerTextViewState GetState() { + if (decompiledNodes == null) + return null; + var state = new DecompilerTextViewState(); if (foldingManager != null) state.SaveFoldingsState(foldingManager.AllFoldings); state.VerticalOffset = textEditor.VerticalOffset; state.HorizontalOffset = textEditor.HorizontalOffset; + state.DecompiledNodes = decompiledNodes; return state; } } @@ -590,16 +612,17 @@ namespace ICSharpCode.ILSpy.TextView private int FoldingsChecksum; public double VerticalOffset; public double HorizontalOffset; + public ILSpyTreeNode[] DecompiledNodes; public void SaveFoldingsState(IEnumerable foldings) { ExpandedFoldings = foldings.Where(f => !f.IsFolded).Select(f => Tuple.Create(f.StartOffset, f.EndOffset)).ToList(); - FoldingsChecksum = foldings.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b); + FoldingsChecksum = unchecked(foldings.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b)); } internal void RestoreFoldings(List list) { - var checksum = list.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b); + var checksum = unchecked(list.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b)); if (FoldingsChecksum == checksum) foreach (var folding in list) folding.DefaultClosed = !ExpandedFoldings.Any(f => f.Item1 == folding.StartOffset && f.Item2 == folding.EndOffset); diff --git a/ILSpy/TextView/ILAsm-Mode.xshd b/ILSpy/TextView/ILAsm-Mode.xshd index 5f582d37c..76fbc1afa 100644 --- a/ILSpy/TextView/ILAsm-Mode.xshd +++ b/ILSpy/TextView/ILAsm-Mode.xshd @@ -348,6 +348,7 @@ tls true false + strict .class diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorTreeNode.cs similarity index 50% rename from ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs rename to ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorTreeNode.cs index d350147f1..041e4ef86 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorsTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventAccessorTreeNode.cs @@ -21,50 +21,19 @@ using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - internal sealed class AnalyzedEventAccessorsTreeNode : AnalyzerTreeNode + internal class AnalyzedEventAccessorTreeNode : AnalyzedMethodTreeNode { - public AnalyzedEventAccessorsTreeNode(EventDefinition analyzedEvent) - { - if (analyzedEvent == null) - throw new ArgumentNullException("analyzedEvent"); - - if (analyzedEvent.AddMethod != null) - this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.AddMethod, "add")); - if (analyzedEvent.RemoveMethod != null) - this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.RemoveMethod, "remove")); - foreach (var accessor in analyzedEvent.OtherMethods) - this.Children.Add(new AnalyzedEventAccessorTreeNode(accessor, null)); - } + private string name; - public override object Icon + public AnalyzedEventAccessorTreeNode(MethodDefinition analyzedMethod, string name) + : base(analyzedMethod) { - get { return Images.Search; } + this.name = name; } public override object Text { - get { return "Accessors"; } - } - - public static bool CanShow(EventDefinition property) - { - return !MainWindow.Instance.CurrentLanguage.ShowMember(property.AddMethod ?? property.RemoveMethod); - } - - internal class AnalyzedEventAccessorTreeNode : AnalyzedMethodTreeNode - { - private string name; - - public AnalyzedEventAccessorTreeNode(MethodDefinition analyzedMethod, string name) - : base(analyzedMethod) - { - this.name = name; - } - - public override object Text - { - get { return name ?? base.Text; } - } + get { return name ?? base.Text; } } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs index 113ffd029..549152bb7 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventOverridesTreeNode.cs @@ -68,36 +68,25 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); - } - - private IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) - { - assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); + ScopedWhereUsedAnalyzer analyzer; - // use parallelism only on the assembly level (avoid locks within Cecil) - return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + analyzer = new ScopedWhereUsedAnalyzer(analyzedEvent, FindReferencesInType); + return analyzer.PerformAnalysis(ct); } - private IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + private IEnumerable FindReferencesInType(TypeDefinition type) { - string asmName = asm.AssemblyDefinition.Name.Name; string name = analyzedEvent.Name; string declTypeName = analyzedEvent.DeclaringType.FullName; - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { - ct.ThrowIfCancellationRequested(); - - if (!TypesHierarchyHelpers.IsBaseType(analyzedEvent.DeclaringType, type, resolveTypeArguments: false)) - continue; - foreach (EventDefinition eventDef in type.Events) { - ct.ThrowIfCancellationRequested(); + if (!TypesHierarchyHelpers.IsBaseType(analyzedEvent.DeclaringType, type, resolveTypeArguments: false)) + yield break; - if (TypesHierarchyHelpers.IsBaseEvent(analyzedEvent, eventDef)) { - MethodDefinition anyAccessor = eventDef.AddMethod ?? eventDef.RemoveMethod; - bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; - yield return new AnalyzedEventTreeNode(eventDef, hidesParent ? "(hides) " : ""); - } + foreach (EventDefinition eventDef in type.Events) { + if (TypesHierarchyHelpers.IsBaseEvent(analyzedEvent, eventDef)) { + MethodDefinition anyAccessor = eventDef.AddMethod ?? eventDef.RemoveMethod; + bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; + yield return new AnalyzedEventTreeNode(eventDef, hidesParent ? "(hides) " : ""); } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs index 3ebf2d23b..89903abd4 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedEventTreeNode.cs @@ -57,8 +57,13 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer protected override void LoadChildren() { - if (AnalyzedEventAccessorsTreeNode.CanShow(analyzedEvent)) - this.Children.Add(new AnalyzedEventAccessorsTreeNode(analyzedEvent)); + if (analyzedEvent.AddMethod != null) + this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.AddMethod, "add")); + if (analyzedEvent.RemoveMethod != null) + this.Children.Add(new AnalyzedEventAccessorTreeNode(analyzedEvent.RemoveMethod, "remove")); + foreach (var accessor in analyzedEvent.OtherMethods) + this.Children.Add(new AnalyzedEventAccessorTreeNode(accessor, null)); + if (AnalyzedEventOverridesTreeNode.CanShow(analyzedEvent)) this.Children.Add(new AnalyzedEventOverridesTreeNode(analyzedEvent)); if (AnalyzedInterfaceEventImplementedByTreeNode.CanShow(analyzedEvent)) @@ -75,12 +80,12 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer public static bool CanShow(MemberReference member) { - var property = member as EventDefinition; - if (property == null) + var eventDef = member as EventDefinition; + if (eventDef == null) return false; - return AnalyzedEventAccessorsTreeNode.CanShow(property) - || AnalyzedEventOverridesTreeNode.CanShow(property); + return !MainWindow.Instance.CurrentLanguage.ShowMember(eventDef.AddMethod ?? eventDef.RemoveMethod) + || AnalyzedEventOverridesTreeNode.CanShow(eventDef); } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessTreeNode.cs index c18b62d0f..850ab3141 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedFieldAccessTreeNode.cs @@ -22,6 +22,7 @@ using System.Threading; using ICSharpCode.TreeView; using Mono.Cecil; using Mono.Cecil.Cil; +using System.Collections; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { @@ -30,6 +31,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private readonly bool showWrites; // true: show writes; false: show read access private readonly FieldDefinition analyzedField; private readonly ThreadingSupport threading; + private Lazy foundMethods; + private object hashLock = new object(); public AnalyzedFieldAccessTreeNode(FieldDefinition analyzedField, bool showWrites) { @@ -68,8 +71,14 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - var analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedField, FindReferencesInType); - return analyzer.PerformAnalysis(ct); + foundMethods = new Lazy(LazyThreadSafetyMode.ExecutionAndPublication); + + var analyzer = new ScopedWhereUsedAnalyzer(analyzedField, FindReferencesInType); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + foundMethods = null; } private IEnumerable FindReferencesInType(TypeDefinition type) @@ -95,8 +104,12 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer method.Body = null; - if (found) - yield return new AnalyzedMethodTreeNode(method); + if (found) { + MethodDefinition codeLocation = this.Language.GetOriginalCodeLocation(method) as MethodDefinition; + if (codeLocation != null && !HasAlreadyBeenFound(codeLocation)) { + yield return new AnalyzedMethodTreeNode(codeLocation); + } + } } } @@ -116,5 +129,18 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer return false; } } + + private bool HasAlreadyBeenFound(MethodDefinition method) + { + Hashtable hashtable = foundMethods.Value; + lock (hashLock) { + if (hashtable.Contains(method)) { + return true; + } else { + hashtable.Add(method, null); + return false; + } + } + } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceEventImplementedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceEventImplementedByTreeNode.cs index c17f3ec36..09cab0bf3 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceEventImplementedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceEventImplementedByTreeNode.cs @@ -69,8 +69,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedMethod, FindReferencesInType); + ScopedWhereUsedAnalyzer analyzer; + analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceMethodImplementedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceMethodImplementedByTreeNode.cs index 6ce86fc8b..960f48f89 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceMethodImplementedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfaceMethodImplementedByTreeNode.cs @@ -67,8 +67,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedMethod, FindReferencesInType); + ScopedWhereUsedAnalyzer analyzer; + analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfacePropertyImplementedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfacePropertyImplementedByTreeNode.cs index 06a4fb961..35d9bc3d0 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedInterfacePropertyImplementedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedInterfacePropertyImplementedByTreeNode.cs @@ -69,8 +69,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedMethod, FindReferencesInType); + ScopedWhereUsedAnalyzer analyzer; + analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodOverridesTreeNode.cs index f8f891d2d..05a3f5d6a 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodOverridesTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodOverridesTreeNode.cs @@ -73,45 +73,32 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); - } - - private IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) - { - assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); + ScopedWhereUsedAnalyzer analyzer; - // use parallelism only on the assembly level (avoid locks within Cecil) - return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); + return analyzer.PerformAnalysis(ct); } - private IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + private IEnumerable FindReferencesInType(TypeDefinition type) { - string asmName = asm.AssemblyDefinition.Name.Name; - string name = analyzedMethod.Name; - string declTypeName = analyzedMethod.DeclaringType.FullName; - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { - ct.ThrowIfCancellationRequested(); - SharpTreeNode newNode = null; - try { - if (!TypesHierarchyHelpers.IsBaseType(analyzedMethod.DeclaringType, type, resolveTypeArguments: false)) - continue; - - foreach (MethodDefinition method in type.Methods) { - ct.ThrowIfCancellationRequested(); - - if (TypesHierarchyHelpers.IsBaseMethod(analyzedMethod, method)) { - bool hidesParent = !method.IsVirtual ^ method.IsNewSlot; - newNode = new AnalyzedMethodTreeNode(method, hidesParent ? "(hides) " : ""); - } + SharpTreeNode newNode = null; + try { + if (!TypesHierarchyHelpers.IsBaseType(analyzedMethod.DeclaringType, type, resolveTypeArguments: false)) + yield break; + + foreach (MethodDefinition method in type.Methods) { + if (TypesHierarchyHelpers.IsBaseMethod(analyzedMethod, method)) { + bool hidesParent = !method.IsVirtual ^ method.IsNewSlot; + newNode = new AnalyzedMethodTreeNode(method, hidesParent ? "(hides) " : ""); } } - catch (ReferenceResolvingException) { - // ignore this type definition. maybe add a notification about such cases. - } - - if (newNode != null) - yield return newNode; } + catch (ReferenceResolvingException) { + // ignore this type definition. maybe add a notification about such cases. + } + + if (newNode != null) + yield return newNode; } public static bool CanShow(MethodDefinition method) diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 2ecc053f8..ad7654eae 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -58,9 +58,15 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { if (analyzedMethod.HasBody) this.Children.Add(new AnalyzedMethodUsesTreeNode(analyzedMethod)); - this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); + + if (analyzedMethod.IsVirtual && !(analyzedMethod.IsNewSlot && analyzedMethod.IsFinal)) + this.Children.Add(new AnalyzedVirtualMethodUsedByTreeNode(analyzedMethod)); + else + this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); + if (AnalyzedMethodOverridesTreeNode.CanShow(analyzedMethod)) this.Children.Add(new AnalyzedMethodOverridesTreeNode(analyzedMethod)); + if (AnalyzedInterfaceMethodImplementedByTreeNode.CanShow(analyzedMethod)) this.Children.Add(new AnalyzedInterfaceMethodImplementedByTreeNode(analyzedMethod)); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs index 2a7cf784f..f3f9504e6 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using ICSharpCode.TreeView; @@ -29,6 +30,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { private readonly MethodDefinition analyzedMethod; private readonly ThreadingSupport threading; + private ConcurrentDictionary foundMethods; public AnalyzedMethodUsedByTreeNode(MethodDefinition analyzedMethod) { @@ -66,10 +68,14 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; + foundMethods = new ConcurrentDictionary(); - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedMethod, FindReferencesInType); - return analyzer.PerformAnalysis(ct); + var analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + foundMethods = null; } private IEnumerable FindReferencesInType(TypeDefinition type) @@ -81,8 +87,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer continue; foreach (Instruction instr in method.Body.Instructions) { MethodReference mr = instr.Operand as MethodReference; - if (mr != null && - mr.Name == name && + if (mr != null && mr.Name == name && Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == analyzedMethod) { found = true; @@ -92,9 +97,18 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer method.Body = null; - if (found) - yield return new AnalyzedMethodTreeNode(method); + if (found) { + MethodDefinition codeLocation = this.Language.GetOriginalCodeLocation(method) as MethodDefinition; + if (codeLocation != null && !HasAlreadyBeenFound(codeLocation)) { + yield return new AnalyzedMethodTreeNode(codeLocation); + } + } } } + + private bool HasAlreadyBeenFound(MethodDefinition method) + { + return !foundMethods.TryAdd(method, 0); + } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorsTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorTreeNode.cs similarity index 50% rename from ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorsTreeNode.cs rename to ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorTreeNode.cs index 621417d2b..e9b898cba 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorsTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyAccessorTreeNode.cs @@ -21,50 +21,19 @@ using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - internal sealed class AnalyzedPropertyAccessorsTreeNode : AnalyzerTreeNode + internal class AnalyzedPropertyAccessorTreeNode : AnalyzedMethodTreeNode { - public AnalyzedPropertyAccessorsTreeNode(PropertyDefinition analyzedProperty) - { - if (analyzedProperty == null) - throw new ArgumentNullException("analyzedProperty"); - - if (analyzedProperty.GetMethod != null) - this.Children.Add(new AnalyzedPropertyAccessorTreeNode(analyzedProperty.GetMethod, "get")); - if (analyzedProperty.SetMethod != null) - this.Children.Add(new AnalyzedPropertyAccessorTreeNode(analyzedProperty.SetMethod, "set")); - foreach (var accessor in analyzedProperty.OtherMethods) - this.Children.Add(new AnalyzedPropertyAccessorTreeNode(accessor, null)); - } + private readonly string name; - public override object Icon + public AnalyzedPropertyAccessorTreeNode(MethodDefinition analyzedMethod, string name) + : base(analyzedMethod) { - get { return Images.Search; } + this.name = name; } public override object Text { - get { return "Accessors"; } - } - - public static bool CanShow(PropertyDefinition property) - { - return !MainWindow.Instance.CurrentLanguage.ShowMember(property.GetMethod ?? property.SetMethod); - } - - private class AnalyzedPropertyAccessorTreeNode : AnalyzedMethodTreeNode - { - private readonly string name; - - public AnalyzedPropertyAccessorTreeNode(MethodDefinition analyzedMethod, string name) - : base(analyzedMethod) - { - this.name = name; - } - - public override object Text - { - get { return name ?? base.Text; } - } + get { return name ?? base.Text; } } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs index 440a54f6e..6a6ea0f32 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyOverridesTreeNode.cs @@ -69,45 +69,27 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - return FindReferences(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), ct); - } - - private IEnumerable FindReferences(IEnumerable assemblies, CancellationToken ct) - { - assemblies = assemblies.Where(asm => asm.AssemblyDefinition != null); + ScopedWhereUsedAnalyzer analyzer; - // use parallelism only on the assembly level (avoid locks within Cecil) - return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + analyzer = new ScopedWhereUsedAnalyzer(analyzedProperty, FindReferencesInType); + return analyzer.PerformAnalysis(ct); } - private IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + private IEnumerable FindReferencesInType(TypeDefinition type) { - string asmName = asm.AssemblyDefinition.Name.Name; string name = analyzedProperty.Name; string declTypeName = analyzedProperty.DeclaringType.FullName; - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { - ct.ThrowIfCancellationRequested(); - SharpTreeNode newNode = null; - try { - if (!TypesHierarchyHelpers.IsBaseType(analyzedProperty.DeclaringType, type, resolveTypeArguments: false)) - continue; + if (!TypesHierarchyHelpers.IsBaseType(analyzedProperty.DeclaringType, type, resolveTypeArguments: false)) + yield break; - foreach (PropertyDefinition property in type.Properties) { - ct.ThrowIfCancellationRequested(); + foreach (PropertyDefinition property in type.Properties) { - if (TypesHierarchyHelpers.IsBaseProperty(analyzedProperty, property)) { - MethodDefinition anyAccessor = property.GetMethod ?? property.SetMethod; - bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; - newNode = new AnalyzedPropertyTreeNode(property, hidesParent ? "(hides) " : ""); - } - } - } - catch (ReferenceResolvingException) { - // ignore this type definition. + if (TypesHierarchyHelpers.IsBaseProperty(analyzedProperty, property)) { + MethodDefinition anyAccessor = property.GetMethod ?? property.SetMethod; + bool hidesParent = !anyAccessor.IsVirtual ^ anyAccessor.IsNewSlot; + yield return new AnalyzedPropertyTreeNode(property, hidesParent ? "(hides) " : ""); } - if (newNode != null) - yield return newNode; } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyTreeNode.cs index bba0b3362..b08127b52 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedPropertyTreeNode.cs @@ -60,8 +60,13 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer protected override void LoadChildren() { - if (AnalyzedPropertyAccessorsTreeNode.CanShow(analyzedProperty)) - this.Children.Add(new AnalyzedPropertyAccessorsTreeNode(analyzedProperty)); + if (analyzedProperty.GetMethod != null) + this.Children.Add(new AnalyzedPropertyAccessorTreeNode(analyzedProperty.GetMethod, "get")); + if (analyzedProperty.SetMethod != null) + this.Children.Add(new AnalyzedPropertyAccessorTreeNode(analyzedProperty.SetMethod, "set")); + foreach (var accessor in analyzedProperty.OtherMethods) + this.Children.Add(new AnalyzedPropertyAccessorTreeNode(accessor, null)); + if (AnalyzedPropertyOverridesTreeNode.CanShow(analyzedProperty)) this.Children.Add(new AnalyzedPropertyOverridesTreeNode(analyzedProperty)); if (AnalyzedInterfacePropertyImplementedByTreeNode.CanShow(analyzedProperty)) @@ -82,8 +87,8 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer if (property == null) return false; - return AnalyzedPropertyAccessorsTreeNode.CanShow(property) - || AnalyzedPropertyOverridesTreeNode.CanShow(property); + return !MainWindow.Instance.CurrentLanguage.ShowMember(property.GetMethod ?? property.SetMethod) + || AnalyzedPropertyOverridesTreeNode.CanShow(property); } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExposedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExposedByTreeNode.cs index 00e09e37d..cd14c0575 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExposedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExposedByTreeNode.cs @@ -65,9 +65,9 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; + ScopedWhereUsedAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedType, FindReferencesInType); + analyzer = new ScopedWhereUsedAnalyzer(analyzedType, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExtensionMethodsTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExtensionMethodsTreeNode.cs index c88ece234..3bd42506c 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExtensionMethodsTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeExtensionMethodsTreeNode.cs @@ -66,9 +66,9 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; + ScopedWhereUsedAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedType, FindReferencesInType); + analyzer = new ScopedWhereUsedAnalyzer(analyzedType, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeInstantiationsTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeInstantiationsTreeNode.cs index f11fca495..0c2d9465a 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedTypeInstantiationsTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedTypeInstantiationsTreeNode.cs @@ -71,9 +71,9 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private IEnumerable FetchChildren(CancellationToken ct) { - ScopedWhereUsedScopeAnalyzer analyzer; + ScopedWhereUsedAnalyzer analyzer; - analyzer = new ScopedWhereUsedScopeAnalyzer(analyzedType, FindReferencesInType); + analyzer = new ScopedWhereUsedAnalyzer(analyzedType, FindReferencesInType); return analyzer.PerformAnalysis(ct); } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs new file mode 100644 index 000000000..993ef36d7 --- /dev/null +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedVirtualMethodUsedByTreeNode.cs @@ -0,0 +1,151 @@ +// 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.Linq; +using System.Threading; +using ICSharpCode.TreeView; +using Mono.Cecil; +using Mono.Cecil.Cil; +using ICSharpCode.Decompiler.Ast; + +namespace ICSharpCode.ILSpy.TreeNodes.Analyzer +{ + internal sealed class AnalyzedVirtualMethodUsedByTreeNode : AnalyzerTreeNode + { + private readonly MethodDefinition analyzedMethod; + private readonly ThreadingSupport threading; + private ConcurrentDictionary foundMethods; + private MethodDefinition baseMethod; + private List possibleTypes; + + public AnalyzedVirtualMethodUsedByTreeNode(MethodDefinition analyzedMethod) + { + if (analyzedMethod == null) + throw new ArgumentNullException("analyzedMethod"); + + this.analyzedMethod = analyzedMethod; + this.threading = new ThreadingSupport(); + this.LazyLoading = true; + } + + public override object Text + { + get { return "Used By"; } + } + + public override object Icon + { + get { return Images.Search; } + } + + protected override void LoadChildren() + { + threading.LoadChildren(this, FetchChildren); + } + + protected override void OnCollapsing() + { + if (threading.IsRunning) { + this.LazyLoading = true; + threading.Cancel(); + this.Children.Clear(); + } + } + + private IEnumerable FetchChildren(CancellationToken ct) + { + InitializeAnalyzer(); + + var analyzer = new ScopedWhereUsedAnalyzer(analyzedMethod, FindReferencesInType); + foreach (var child in analyzer.PerformAnalysis(ct)) { + yield return child; + } + + ReleaseAnalyzer(); + } + + private void InitializeAnalyzer() + { + foundMethods = new ConcurrentDictionary(); + + var BaseMethods = TypesHierarchyHelpers.FindBaseMethods(analyzedMethod).ToArray(); + if (BaseMethods.Length > 0) { + baseMethod = BaseMethods[BaseMethods.Length - 1]; + } + + possibleTypes = new List(); + + TypeReference type = analyzedMethod.DeclaringType.BaseType; + while (type !=null) + { + possibleTypes.Add(type); + type = type.Resolve().BaseType; + } + } + + private void ReleaseAnalyzer() + { + foundMethods = null; + baseMethod = null; + } + + private IEnumerable FindReferencesInType(TypeDefinition type) + { + string name = analyzedMethod.Name; + foreach (MethodDefinition method in type.Methods) { + bool found = false; + string prefix = string.Empty; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null && mr.Name == name) { + // explicit call to the requested method + if (Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == analyzedMethod) { + found = true; + prefix = "(as base) "; + break; + } + // virtual call to base method + if (instr.OpCode.Code == Code.Callvirt && Helpers.IsReferencedBy(baseMethod.DeclaringType, mr.DeclaringType) && mr.Resolve() == baseMethod) { + found = true; + break; + } + } + } + + method.Body = null; + + if (found) { + MethodDefinition codeLocation = this.Language.GetOriginalCodeLocation(method) as MethodDefinition; + if (codeLocation != null && !HasAlreadyBeenFound(codeLocation)) { + yield return new AnalyzedMethodTreeNode(codeLocation, prefix); + } + } + } + } + + private bool HasAlreadyBeenFound(MethodDefinition method) + { + return !foundMethods.TryAdd(method, 0); + } + } +} diff --git a/ILSpy/TreeNodes/Analyzer/Helpers.cs b/ILSpy/TreeNodes/Analyzer/Helpers.cs index 778996a72..bef4e486a 100644 --- a/ILSpy/TreeNodes/Analyzer/Helpers.cs +++ b/ILSpy/TreeNodes/Analyzer/Helpers.cs @@ -20,7 +20,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using ICSharpCode.Decompiler; using Mono.Cecil; +using ICSharpCode.Decompiler.ILAst; +using Mono.Cecil.Cil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { @@ -50,5 +53,65 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer return true; } + + public static MemberReference GetOriginalCodeLocation(MemberReference member) + { + if (member is MethodDefinition) + return GetOriginalCodeLocation((MethodDefinition)member); + return member; + } + + public static MethodDefinition GetOriginalCodeLocation(MethodDefinition method) + { + if (method.IsCompilerGenerated()) { + return FindMethodUsageInType(method.DeclaringType, method) ?? method; + } + + var typeUsage = GetOriginalCodeLocation(method.DeclaringType, method); + + return typeUsage ?? method; + } + public static MethodDefinition GetOriginalCodeLocation(TypeDefinition type, MethodDefinition method) + { + if (type != null && type.DeclaringType != null && type.IsCompilerGenerated()) { + MethodDefinition constructor = GetTypeConstructor(type); + return FindMethodUsageInType(type.DeclaringType, constructor); + } + return null; + } + + private static MethodDefinition GetTypeConstructor(TypeDefinition type) + { + foreach (MethodDefinition method in type.Methods) { + if (method.Name == ".ctor") + return method; + } + return null; + } + + private static MethodDefinition FindMethodUsageInType(TypeDefinition type, MethodDefinition analyzedMethod) + { + string name = analyzedMethod.Name; + foreach (MethodDefinition method in type.Methods) { + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + MethodReference mr = instr.Operand as MethodReference; + if (mr != null && mr.Name == name && + Helpers.IsReferencedBy(analyzedMethod.DeclaringType, mr.DeclaringType) && + mr.Resolve() == analyzedMethod) { + found = true; + break; + } + } + + method.Body = null; + + if (found) + return method; + } + return null; + } } } diff --git a/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs b/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs index 48eb21bc5..2840901a9 100644 --- a/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs +++ b/ILSpy/TreeNodes/Analyzer/ScopedWhereUsedAnalyzer.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer /// /// Determines the accessibility domain of a member for where-used analysis. /// - internal class ScopedWhereUsedScopeAnalyzer + internal class ScopedWhereUsedAnalyzer { private AssemblyDefinition assemblyScope; private TypeDefinition typeScope; @@ -37,40 +37,36 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer private Accessibility typeAccessibility = Accessibility.Public; private Func> typeAnalysisFunction; - public ScopedWhereUsedScopeAnalyzer(TypeDefinition type, Func> typeAnalysisFunction) + public ScopedWhereUsedAnalyzer(TypeDefinition type, Func> typeAnalysisFunction) { this.typeScope = type; this.assemblyScope = type.Module.Assembly; this.typeAnalysisFunction = typeAnalysisFunction; } - public ScopedWhereUsedScopeAnalyzer(MethodDefinition method, Func> typeAnalysisFunction) + public ScopedWhereUsedAnalyzer(MethodDefinition method, Func> typeAnalysisFunction) : this(method.DeclaringType, typeAnalysisFunction) { - switch (method.Attributes & MethodAttributes.MemberAccessMask) { - case MethodAttributes.Private: - default: - memberAccessibility = Accessibility.Private; - break; - case MethodAttributes.FamANDAssem: - memberAccessibility = Accessibility.FamilyAndInternal; - break; - case MethodAttributes.Family: - memberAccessibility = Accessibility.Family; - break; - case MethodAttributes.Assembly: - memberAccessibility = Accessibility.Internal; - break; - case MethodAttributes.FamORAssem: - memberAccessibility = Accessibility.FamilyOrInternal; - break; - case MethodAttributes.Public: - memberAccessibility = Accessibility.Public; - break; - } + this.memberAccessibility = GetMethodAccessibility(method); + } + + public ScopedWhereUsedAnalyzer(PropertyDefinition property, Func> typeAnalysisFunction) + : this(property.DeclaringType, typeAnalysisFunction) + { + Accessibility getterAccessibility = (property.GetMethod == null) ? Accessibility.Private : GetMethodAccessibility(property.GetMethod); + Accessibility setterAccessibility = (property.SetMethod == null) ? Accessibility.Private : GetMethodAccessibility(property.SetMethod); + this.memberAccessibility = (Accessibility)Math.Max((int)getterAccessibility, (int)setterAccessibility); } - public ScopedWhereUsedScopeAnalyzer(FieldDefinition field, Func> typeAnalysisFunction) + public ScopedWhereUsedAnalyzer(EventDefinition eventDef, Func> typeAnalysisFunction) + : this(eventDef.DeclaringType, typeAnalysisFunction) + { + // we only have to check the accessibility of the the get method + // [CLS Rule 30: The accessibility of an event and of its accessors shall be identical.] + this.memberAccessibility = GetMethodAccessibility(eventDef.AddMethod); + } + + public ScopedWhereUsedAnalyzer(FieldDefinition field, Func> typeAnalysisFunction) : this(field.DeclaringType, typeAnalysisFunction) { switch (field.Attributes & FieldAttributes.FieldAccessMask) { @@ -96,6 +92,33 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer } } + private Accessibility GetMethodAccessibility(MethodDefinition method) + { + Accessibility accessibility; + switch (method.Attributes & MethodAttributes.MemberAccessMask) { + case MethodAttributes.Private: + default: + accessibility = Accessibility.Private; + break; + case MethodAttributes.FamANDAssem: + accessibility = Accessibility.FamilyAndInternal; + break; + case MethodAttributes.Family: + accessibility = Accessibility.Family; + break; + case MethodAttributes.Assembly: + accessibility = Accessibility.Internal; + break; + case MethodAttributes.FamORAssem: + accessibility = Accessibility.FamilyOrInternal; + break; + case MethodAttributes.Public: + accessibility = Accessibility.Public; + break; + } + return accessibility; + } + public IEnumerable PerformAnalysis(CancellationToken ct) { if (memberAccessibility == Accessibility.Private) { @@ -231,7 +254,7 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer break; } } - if (found) + if (found && AssemblyReferencesScopeType(assembly.AssemblyDefinition)) yield return assembly.AssemblyDefinition; } } @@ -255,12 +278,24 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer foreach (var assembly in assemblies) { ct.ThrowIfCancellationRequested(); - if (friendAssemblies.Contains(assembly.ShortName)) { + if (friendAssemblies.Contains(assembly.ShortName) && AssemblyReferencesScopeType(assembly.AssemblyDefinition)) { yield return assembly.AssemblyDefinition; } } } } } + + private bool AssemblyReferencesScopeType(AssemblyDefinition asm) + { + bool hasRef = false; + foreach (var typeref in asm.MainModule.GetTypeReferences()) { + if (typeref.Name == typeScope.Name && typeref.Namespace == typeScope.Namespace) { + hasRef = true; + break; + } + } + return hasRef; + } } } diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 73dbb38da..864759152 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -134,6 +134,10 @@ namespace ICSharpCode.ILSpy.TreeNodes this.Children.Add(ns); } } + + public override bool CanExpandRecursively { + get { return true; } + } public TypeTreeNode FindTypeNode(TypeDefinition def) { diff --git a/ILSpy/TreeNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceEntryNode.cs index 787c82dc3..43638064d 100644 --- a/ILSpy/TreeNodes/ResourceEntryNode.cs +++ b/ILSpy/TreeNodes/ResourceEntryNode.cs @@ -144,7 +144,7 @@ namespace ICSharpCode.ILSpy.TreeNodes output.AddUIElement(() => new Image { Source = image }); output.WriteLine(); output.AddButton(Images.Save, "Save", delegate { Save(null); }); - textView.Show(output, null); + textView.ShowNode(output, this, null); return true; } catch (Exception) { diff --git a/ILSpy/TreeNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceTreeNode.cs index 3b277a983..5e79845ba 100644 --- a/ILSpy/TreeNodes/ResourceTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceTreeNode.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.ILSpy.TreeNodes ext = ".xml"; else ext = Path.GetExtension(DecompilerTextView.CleanUpName(er.Name)); - textView.Show(output, HighlightingManager.Instance.GetDefinitionByExtension(ext)); + textView.ShowNode(output, this, HighlightingManager.Instance.GetDefinitionByExtension(ext)); return true; } } diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs index 23bce5df7..85568708a 100644 --- a/ILSpy/TreeNodes/TypeTreeNode.cs +++ b/ILSpy/TreeNodes/TypeTreeNode.cs @@ -117,6 +117,10 @@ namespace ICSharpCode.ILSpy.TreeNodes } } + public override bool CanExpandRecursively { + get { return true; } + } + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) { language.DecompileType(type, output, options); @@ -146,8 +150,10 @@ namespace ICSharpCode.ILSpy.TreeNodes } else { if (type.IsInterface) return TypeIcon.Interface; - else if (type.BaseType != null && type.BaseType.FullName == typeof(MulticastDelegate).FullName) + else if (IsDelegate(type)) return TypeIcon.Delegate; + else if (IsStaticClass(type)) + return TypeIcon.StaticClass; else return TypeIcon.Class; } @@ -178,6 +184,17 @@ namespace ICSharpCode.ILSpy.TreeNodes } return overlay; } + + private static bool IsDelegate(TypeDefinition type) + { + return type.BaseType != null && type.BaseType.FullName == typeof(MulticastDelegate).FullName; + } + + private static bool IsStaticClass(TypeDefinition type) + { + return type.IsSealed && type.IsAbstract; + } + #endregion MemberReference IMemberTreeNode.Member { diff --git a/ILSpy/TreeNodes/XamlResourceNode.cs b/ILSpy/TreeNodes/XamlResourceNode.cs index 9e42fb431..0ed226a1d 100644 --- a/ILSpy/TreeNodes/XamlResourceNode.cs +++ b/ILSpy/TreeNodes/XamlResourceNode.cs @@ -74,7 +74,7 @@ namespace ICSharpCode.ILSpy.Xaml } return output; }), - t => textView.Show(t.Result, highlighting) + t => textView.ShowNode(t.Result, this, highlighting) ); return true; } diff --git a/ILSpy/XmlDoc/XmlDocKeyProvider.cs b/ILSpy/XmlDoc/XmlDocKeyProvider.cs index 7e9960067..fc503c115 100644 --- a/ILSpy/XmlDoc/XmlDocKeyProvider.cs +++ b/ILSpy/XmlDoc/XmlDocKeyProvider.cs @@ -79,6 +79,10 @@ namespace ICSharpCode.ILSpy.XmlDoc static void AppendTypeName(StringBuilder b, TypeReference type) { + if (type == null) { + // could happen when a TypeSpecification has no ElementType; e.g. function pointers in C++/CLI assemblies + return; + } if (type is GenericInstanceType) { GenericInstanceType giType = (GenericInstanceType)type; if (type.DeclaringType != null) { diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index d4cc6e1c5..452ca686a 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -167,12 +167,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// This method can be used to restrict the analysis to only a part of the method. /// Only the control flow paths that are fully contained within the selected part will be analyzed. /// - /// Both 'start' and 'end' are inclusive. - public void SetAnalyzedRange(Statement start, Statement end) + /// By default, both 'start' and 'end' are inclusive. + public void SetAnalyzedRange(Statement start, Statement end, bool startInclusive = true, bool endInclusive = true) { - Debug.Assert(beginNodeDict.ContainsKey(start) && endNodeDict.ContainsKey(end)); - int startIndex = beginNodeDict[start].Index; - int endIndex = endNodeDict[end].Index; + var dictForStart = startInclusive ? beginNodeDict : endNodeDict; + var dictForEnd = endInclusive ? endNodeDict : beginNodeDict; + Debug.Assert(dictForStart.ContainsKey(start) && dictForEnd.ContainsKey(end)); + int startIndex = dictForStart[start].Index; + int endIndex = dictForEnd[end].Index; if (startIndex > endIndex) throw new ArgumentException("The start statement must be lexically preceding the end statement"); this.analyzedRangeStart = startIndex; diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs index b888cdea2..813f47213 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/ComposedType.cs @@ -52,7 +52,7 @@ namespace ICSharpCode.NRefactory.CSharp public int PointerRank { get { - return GetChildrenByRole(PointerRole).Count(); + return GetChildrenByRole(PointerRole).Count; } set { if (value < 0) @@ -141,7 +141,7 @@ namespace ICSharpCode.NRefactory.CSharp } public int Dimensions { - get { return 1 + GetChildrenByRole(Roles.Comma).Count(); } + get { return 1 + GetChildrenByRole(Roles.Comma).Count; } set { int d = this.Dimensions; while (d > value) { diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Identifier.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Identifier.cs index 70f1ddca4..6db26e523 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Identifier.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Identifier.cs @@ -96,7 +96,7 @@ namespace ICSharpCode.NRefactory.CSharp { if (name == null) throw new ArgumentNullException("name"); - IsVerbatim = name.StartsWith ("@"); + IsVerbatim = name.Length > 0 && name[0] == '@'; this.Name = IsVerbatim ? name.Substring (1) : name; this.startLocation = location; } diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index 1c770b634..da524257d 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -941,7 +941,11 @@ namespace ICSharpCode.NRefactory.CSharp return ConvertChar(ch); } - static string ConvertChar(char ch) + /// + /// Gets the escape sequence for the specified character. + /// + /// This method does not convert ' or ". + public static string ConvertChar(char ch) { switch (ch) { case '\\': @@ -963,7 +967,9 @@ namespace ICSharpCode.NRefactory.CSharp case '\v': return "\\v"; default: - if (char.IsControl(ch) || char.IsSurrogate(ch)) { + if (char.IsControl(ch) || char.IsSurrogate(ch) || + // print all uncommon white spaces as numbers + (char.IsWhiteSpace(ch) && ch != ' ')) { return "\\u" + ((int)ch).ToString("x4"); } else { return ch.ToString(); @@ -971,7 +977,10 @@ namespace ICSharpCode.NRefactory.CSharp } } - static string ConvertString(string str) + /// + /// Converts special characters to escape sequences within the given string. + /// + public static string ConvertString(string str) { StringBuilder sb = new StringBuilder(); foreach (char ch in str) { @@ -2145,7 +2154,9 @@ namespace ICSharpCode.NRefactory.CSharp // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. formatter.Space(); } + formatter.StartNode(comment); formatter.WriteComment(comment.CommentType, comment.Content); + formatter.EndNode(comment); lastWritten = LastWritten.Whitespace; return null; } diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs index 66865d53c..756adce40 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Parser/TypeSystemConvertVisitor.cs @@ -263,7 +263,7 @@ namespace ICSharpCode.NRefactory.CSharp #region Fields public override IEntity VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) { - bool isSingleField = fieldDeclaration.Variables.Count() == 1; + bool isSingleField = fieldDeclaration.Variables.Count == 1; Modifiers modifiers = fieldDeclaration.Modifiers; DefaultField field = null; foreach (VariableInitializer vi in fieldDeclaration.Variables) { @@ -476,7 +476,7 @@ namespace ICSharpCode.NRefactory.CSharp #region Events public override IEntity VisitEventDeclaration(EventDeclaration eventDeclaration, object data) { - bool isSingleEvent = eventDeclaration.Variables.Count() == 1; + bool isSingleEvent = eventDeclaration.Variables.Count == 1; Modifiers modifiers = eventDeclaration.Modifiers; DefaultEvent ev = null; foreach (VariableInitializer vi in eventDeclaration.Variables) { diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs index 3bcea943b..162f5c2bf 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs @@ -241,7 +241,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver ResolveResult VisitFieldOrEventDeclaration(AttributedNode fieldOrEventDeclaration) { - int initializerCount = fieldOrEventDeclaration.GetChildrenByRole(FieldDeclaration.Roles.Variable).Count(); + int initializerCount = fieldOrEventDeclaration.GetChildrenByRole(FieldDeclaration.Roles.Variable).Count; ResolveResult result = null; for (AstNode node = fieldOrEventDeclaration.FirstChild; node != null; node = node.NextSibling) { if (node.Role == FieldDeclaration.Roles.Variable) { @@ -939,7 +939,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver firstInitializer != null ? firstInitializer.Initializer : null, false); - int initializerCount = variableDeclarationStatement.Variables.Count(); + int initializerCount = variableDeclarationStatement.Variables.Count; ResolveResult result = null; for (AstNode node = variableDeclarationStatement.FirstChild; node != null; node = node.NextSibling) { if (node.Role == FieldDeclaration.Roles.Variable) { diff --git a/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs b/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs index 315f212eb..70d88aa16 100644 --- a/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs +++ b/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs @@ -120,7 +120,7 @@ namespace ICSharpCode.NRefactory.Documentation static string GetRedirectionTarget(string target) { - string programFilesDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + string programFilesDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); programFilesDir = AppendDirectorySeparator(programFilesDir); string corSysDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); diff --git a/SharpTreeView/SharpTreeNode.cs b/SharpTreeView/SharpTreeNode.cs index 3de3c9f9f..ae0a0e646 100644 --- a/SharpTreeView/SharpTreeNode.cs +++ b/SharpTreeView/SharpTreeNode.cs @@ -267,12 +267,26 @@ namespace ICSharpCode.TreeView lazyLoading = value; if (lazyLoading) { IsExpanded = false; + if (canExpandRecursively) { + canExpandRecursively = false; + RaisePropertyChanged("CanExpandRecursively"); + } } RaisePropertyChanged("LazyLoading"); RaisePropertyChanged("ShowExpander"); } } + bool canExpandRecursively = true; + + /// + /// Gets whether this node can be expanded recursively. + /// If not overridden, this property returns false if the node is using lazy-loading, and true otherwise. + /// + public virtual bool CanExpandRecursively { + get { return canExpandRecursively; } + } + public virtual bool ShowIcon { get { return Icon != null; } diff --git a/SharpTreeView/SharpTreeView.cs b/SharpTreeView/SharpTreeView.cs index d9409b79b..f2a66c797 100644 --- a/SharpTreeView/SharpTreeView.cs +++ b/SharpTreeView/SharpTreeView.cs @@ -227,11 +227,28 @@ namespace ICSharpCode.TreeView e.Handled = true; } break; + case Key.Multiply: + if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { + container.Node.IsExpanded = true; + ExpandRecursively(container.Node); + e.Handled = true; + } + break; } if (!e.Handled) base.OnKeyDown(e); } + void ExpandRecursively(SharpTreeNode node) + { + if (node.CanExpandRecursively) { + node.IsExpanded = true; + foreach (SharpTreeNode child in node.Children) { + ExpandRecursively(child); + } + } + } + /// /// Scrolls the specified node in view and sets keyboard focus on it. ///