diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b7c3b6dc2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +; Top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 4 + +[*.il] +indent_style = space +indent_size = 2 + +[*.csproj] +indent_style = space +indent_size = 2 +[*.config] +indent_style = space +indent_size = 2 +[*.vsixmanifest] +indent_style = space +indent_size = 2 +[*.vsct] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index b003bfeba..5ab4836f1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ obj/ _ReSharper*/ *.ReSharper *.patch -/ILSpy.sln.ide -/packages/* +/packages +*.ide/ diff --git a/ICSharpCode.Decompiler/Ast/AstBuilder.cs b/ICSharpCode.Decompiler/Ast/AstBuilder.cs index 11fb65fd0..3b5ac89f4 100644 --- a/ICSharpCode.Decompiler/Ast/AstBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstBuilder.cs @@ -160,7 +160,7 @@ namespace ICSharpCode.Decompiler.Ast RunTransformations(); syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); - var outputFormatter = new TextOutputFormatter(output) { FoldBraces = context.Settings.FoldBraces }; + var outputFormatter = new TextOutputFormatter(output, context) { FoldBraces = context.Settings.FoldBraces }; var formattingPolicy = context.Settings.CSharpFormattingOptions; syntaxTree.AcceptVisitor(new CSharpOutputVisitor(outputFormatter, formattingPolicy)); } @@ -321,11 +321,13 @@ namespace ICSharpCode.Decompiler.Ast if (typeDef.IsEnum) { long expectedEnumMemberValue = 0; bool forcePrintingInitializers = IsFlagsEnum(typeDef); + TypeCode baseType = TypeCode.Int32; foreach (FieldDefinition field in typeDef.Fields) { if (!field.IsStatic) { // the value__ field if (field.FieldType != typeDef.Module.TypeSystem.Int32) { astType.AddChild(ConvertType(field.FieldType), Roles.BaseType); + baseType = TypeAnalysis.GetTypeCode(field.FieldType); } } else { EnumMemberDeclaration enumMember = new EnumMemberDeclaration(); @@ -333,7 +335,7 @@ namespace ICSharpCode.Decompiler.Ast enumMember.Name = CleanName(field.Name); long memberValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, field.Constant, false); if (forcePrintingInitializers || memberValue != expectedEnumMemberValue) { - enumMember.AddChild(new PrimitiveExpression(field.Constant), EnumMemberDeclaration.InitializerRole); + enumMember.AddChild(new PrimitiveExpression(CSharpPrimitiveCast.Cast(baseType, field.Constant, false)), EnumMemberDeclaration.InitializerRole); } expectedEnumMemberValue = memberValue + 1; astType.AddChild(enumMember, Roles.TypeMemberRole); diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index 078fd4fa7..3180d3842 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -52,8 +52,8 @@ namespace ICSharpCode.Decompiler.Ast /// These are used to update the parameter names when the decompiler generates names for the parameters. /// Block for the method body public static BlockStatement CreateMethodBody(MethodDefinition methodDef, - DecompilerContext context, - IEnumerable parameters = null) + DecompilerContext context, + IEnumerable parameters = null) { MethodDefinition oldCurrentMethod = context.CurrentMethod; Debug.Assert(oldCurrentMethod == null || oldCurrentMethod == methodDef); @@ -103,8 +103,8 @@ namespace ICSharpCode.Decompiler.Ast if (parameters != null) { foreach (var pair in (from p in parameters - join v in astBuilder.Parameters on p.Annotation() equals v.OriginalParameter - select new { p, v.Name })) + join v in astBuilder.Parameters on p.Annotation() equals v.OriginalParameter + select new { p, v.Name })) { pair.p.Name = pair.Name; } @@ -204,7 +204,7 @@ namespace ICSharpCode.Decompiler.Ast tryCatchStmt.TryBlock = TransformBlock(tryCatchNode.TryBlock); foreach (var catchClause in tryCatchNode.CatchBlocks) { if (catchClause.ExceptionVariable == null - && (catchClause.ExceptionType == null || catchClause.ExceptionType.MetadataType == MetadataType.Object)) + && (catchClause.ExceptionType == null || catchClause.ExceptionType.MetadataType == MetadataType.Object)) { tryCatchStmt.CatchClauses.Add(new Ast.CatchClause { Body = TransformBlock(catchClause) }); } else { @@ -262,7 +262,7 @@ namespace ICSharpCode.Decompiler.Ast result = node; if (result != null) - result = result.WithAnnotation(new TypeInformation(expr.InferredType)); + result = result.WithAnnotation(new TypeInformation(expr.InferredType, expr.ExpectedType)); if (result != null) return result.WithAnnotation(ilRanges); @@ -291,16 +291,10 @@ namespace ICSharpCode.Decompiler.Ast { BinaryOperatorExpression boe; if (byteCode.InferredType is PointerType) { - if (byteCode.Arguments[0].ExpectedType is PointerType) { - arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); - boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); - boe.AddAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); - } else if (byteCode.Arguments[1].ExpectedType is PointerType) { - arg1 = DivideBySize(arg1, ((PointerType)byteCode.InferredType).ElementType); - boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); + boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); + if (byteCode.Arguments[0].ExpectedType is PointerType || + byteCode.Arguments[1].ExpectedType is PointerType) { boe.AddAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); - } else { - boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); } } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); @@ -314,12 +308,9 @@ namespace ICSharpCode.Decompiler.Ast { BinaryOperatorExpression boe; if (byteCode.InferredType is PointerType) { + boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); if (byteCode.Arguments[0].ExpectedType is PointerType) { - arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); - boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); boe.WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); - } else { - boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); } } else { boe = new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); @@ -460,12 +451,30 @@ namespace ICSharpCode.Decompiler.Ast // can also mean Inequality, when used with object references TypeReference arg1Type = byteCode.Arguments[0].InferredType; if (arg1Type != null && !arg1Type.IsValueType) goto case ILCode.Cne; + + // when comparing signed integral values using Cgt_Un with 0 + // the Ast should actually contain InEquality since "(uint)a > 0u" is identical to "a != 0" + if (arg1Type.IsSignedIntegralType()) + { + var p = arg2 as Ast.PrimitiveExpression; + if (p != null && p.Value.IsZero()) goto case ILCode.Cne; + } + goto case ILCode.Cgt; } case ILCode.Cle_Un: { // can also mean Equality, when used with object references TypeReference arg1Type = byteCode.Arguments[0].InferredType; if (arg1Type != null && !arg1Type.IsValueType) goto case ILCode.Ceq; + + // when comparing signed integral values using Cle_Un with 0 + // the Ast should actually contain Equality since "(uint)a <= 0u" is identical to "a == 0" + if (arg1Type.IsSignedIntegralType()) + { + var p = arg2 as Ast.PrimitiveExpression; + if (p != null && p.Value.IsZero()) goto case ILCode.Ceq; + } + goto case ILCode.Cle; } case ILCode.Cle: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.LessThanOrEqual, arg2); @@ -706,7 +715,7 @@ namespace ICSharpCode.Decompiler.Ast } return new StackAllocExpression { Type = AstBuilder.ConvertType(type), - CountExpression = DivideBySize(arg1, type) + CountExpression = arg1 }; } case ILCode.Mkrefany: @@ -902,45 +911,6 @@ namespace ICSharpCode.Decompiler.Ast } } - /// - /// Divides expr by the size of 'type'. - /// - Expression DivideBySize(Expression expr, TypeReference type) - { - CastExpression cast = expr as CastExpression; - if (cast != null && cast.Type is PrimitiveType && ((PrimitiveType)cast.Type).Keyword == "int") - expr = cast.Expression.Detach(); - - Expression sizeOfExpression; - switch (TypeAnalysis.GetInformationAmount(type)) { - case 1: - case 8: - sizeOfExpression = new PrimitiveExpression(1); - break; - case 16: - sizeOfExpression = new PrimitiveExpression(2); - break; - case 32: - sizeOfExpression = new PrimitiveExpression(4); - break; - case 64: - sizeOfExpression = new PrimitiveExpression(8); - break; - default: - sizeOfExpression = new SizeOfExpression { Type = AstBuilder.ConvertType(type) }; - break; - } - - BinaryOperatorExpression boe = expr as BinaryOperatorExpression; - if (boe != null && boe.Operator == BinaryOperatorType.Multiply && sizeOfExpression.IsMatch(boe.Right)) - return boe.Left.Detach(); - - if (sizeOfExpression.IsMatch(expr)) - return new PrimitiveExpression(1); - - return new BinaryOperatorExpression(expr, BinaryOperatorType.Divide, sizeOfExpression); - } - Expression MakeDefaultValue(TypeReference type) { TypeDefinition typeDef = type.Resolve(); diff --git a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs index 0f1889b48..e7af6d26e 100644 --- a/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs +++ b/ICSharpCode.Decompiler/Ast/TextOutputFormatter.cs @@ -30,6 +30,7 @@ namespace ICSharpCode.Decompiler.Ast public class TextOutputFormatter : IOutputFormatter { readonly ITextOutput output; + readonly DecompilerContext context; readonly Stack nodeStack = new Stack(); int braceLevelWithinType = -1; bool inDocumentationComment = false; @@ -38,11 +39,14 @@ namespace ICSharpCode.Decompiler.Ast public bool FoldBraces = false; - public TextOutputFormatter(ITextOutput output) + public TextOutputFormatter(ITextOutput output, DecompilerContext context) { if (output == null) throw new ArgumentNullException("output"); + if (context == null) + throw new ArgumentNullException("context"); this.output = output; + this.context = context; } public void WriteIdentifier(string identifier) @@ -89,6 +93,24 @@ namespace ICSharpCode.Decompiler.Ast { memberRef = node.Parent.Annotation(); } + if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && memberRef != null) { + var declaringType = memberRef.DeclaringType.Resolve(); + if (declaringType != null && declaringType.IsDelegate()) + return null; + } + return FilterMemberReference(memberRef); + } + + MemberReference FilterMemberReference(MemberReference memberRef) + { + if (memberRef == null) + return null; + + if (context.Settings.AutomaticEvents && memberRef is FieldDefinition) { + var field = (FieldDefinition)memberRef; + return field.DeclaringType.Events.FirstOrDefault(ev => ev.Name == field.Name) ?? memberRef; + } + return memberRef; } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index 0838c2489..d0768af64 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -144,6 +144,15 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName)) continue; } + IfElseStatement ies = stmt as IfElseStatement; + if (ies != null) { + foreach (var child in IfElseChainChildren(ies)) { + BlockStatement subBlock = child as BlockStatement; + if (subBlock != null) + DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); + } + continue; + } foreach (AstNode child in stmt.Children) { BlockStatement subBlock = child as BlockStatement; if (subBlock != null) { @@ -268,6 +277,15 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } } + + IfElseStatement ies = stmt as IfElseStatement; + if (ies != null) { + foreach (var child in IfElseChainChildren(ies)) { + if (!(child is BlockStatement) && UsesVariable(child, variableName)) + return false; + } + return true; + } // We can move the variable into a sub-block only if the variable is used in only that sub-block (and not in expressions such as the loop condition) for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) { @@ -285,6 +303,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } return true; } + + static IEnumerable IfElseChainChildren(IfElseStatement ies) + { + IfElseStatement prev; + do { + yield return ies.Condition; + yield return ies.TrueStatement; + prev = ies; + ies = ies.FalseStatement as IfElseStatement; + } while (ies != null); + if (!prev.FalseStatement.IsNull) + yield return prev.FalseStatement; + } static bool HasNestedBlocks(AstNode node) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DelegateConstruction.cs index ee03ce2b8..026288fd9 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DelegateConstruction.cs @@ -180,8 +180,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms foreach (AstNode node in body.Descendants) { if (node is ThisReferenceExpression) node.ReplaceWith(target.Clone()); - } + Expression replacement; if (isLambda) { LambdaExpression lambda = new LambdaExpression(); lambda.CopyAnnotationsFrom(ame); @@ -189,11 +189,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression; returnExpr.Remove(); lambda.Body = returnExpr; - objectCreateExpression.ReplaceWith(lambda); + replacement = lambda; } else { ame.Body = body; - objectCreateExpression.ReplaceWith(ame); + replacement = ame; + } + var expectedType = objectCreateExpression.Annotation().ExpectedType.Resolve(); + if (expectedType != null && !expectedType.IsDelegate()) { + var simplifiedDelegateCreation = (ObjectCreateExpression)objectCreateExpression.Clone(); + simplifiedDelegateCreation.Arguments.Clear(); + simplifiedDelegateCreation.Arguments.Add(replacement); + replacement = simplifiedDelegateCreation; } + objectCreateExpression.ReplaceWith(replacement); return true; } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index fd899430f..6d8608794 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -99,6 +99,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (result != null) return result; } + AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement); + if (simplifiedIfElse != null) + return simplifiedIfElse; return base.VisitIfElseStatement(ifElseStatement, data); } @@ -614,6 +617,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms static readonly AstNode lockTryCatchPattern = new TryCatchStatement { TryBlock = new BlockStatement { + new OptionalNode(new VariableDeclarationStatement()).ToStatement(), new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke( "Enter", new AnyNode("enter"), new DirectionExpression { @@ -626,21 +630,57 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms new IfElseStatement { Condition = new Backreference("flag"), TrueStatement = new BlockStatement { - new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new NamedNode("exit", new IdentifierExpression(Pattern.AnyString))) + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) } } }}; + + static readonly AstNode oldMonitorCallPattern = new ExpressionStatement( + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Enter", new AnyNode("enter")) + ); + + static readonly AstNode oldLockTryCatchPattern = new TryCatchStatement + { + TryBlock = new BlockStatement { + new Repeat(new AnyNode()).ToStatement() + }, + FinallyBlock = new BlockStatement { + new TypePattern(typeof(System.Threading.Monitor)).ToType().Invoke("Exit", new AnyNode("exit")) + } + }; + + bool AnalyzeLockV2(ExpressionStatement node, out Expression enter, out Expression exit) + { + enter = null; + exit = null; + Match m1 = oldMonitorCallPattern.Match(node); + if (!m1.Success) return false; + Match m2 = oldLockTryCatchPattern.Match(node.NextSibling); + if (!m2.Success) return false; + enter = m1.Get("enter").Single(); + exit = m2.Get("exit").Single(); + return true; + } + + bool AnalyzeLockV4(ExpressionStatement node, out Expression enter, out Expression exit) + { + enter = null; + exit = null; + Match m1 = lockFlagInitPattern.Match(node); + if (!m1.Success) return false; + Match m2 = lockTryCatchPattern.Match(node.NextSibling); + if (!m2.Success) return false; + enter = m2.Get("enter").Single(); + exit = m2.Get("exit").Single(); + return m1.Get("variable").Single().Identifier == m2.Get("flag").Single().Identifier; + } public LockStatement TransformLock(ExpressionStatement node) { - Match m1 = lockFlagInitPattern.Match(node); - if (!m1.Success) return null; - AstNode tryCatch = node.NextSibling; - Match m2 = lockTryCatchPattern.Match(tryCatch); - if (!m2.Success) return null; - if (m1.Get("variable").Single().Identifier == m2.Get("flag").Single().Identifier) { - Expression enter = m2.Get("enter").Single(); - IdentifierExpression exit = m2.Get("exit").Single(); + Expression enter, exit; + bool isV2 = AnalyzeLockV2(node, out enter, out exit); + if (isV2 || AnalyzeLockV4(node, out enter, out exit)) { + AstNode tryCatch = node.NextSibling; if (!exit.IsMatch(enter)) { // If exit and enter are not the same, then enter must be "exit = ..." AssignmentExpression assign = enter as AssignmentExpression; @@ -656,7 +696,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms LockStatement l = new LockStatement(); l.Expression = enter.Detach(); l.EmbeddedStatement = ((TryCatchStatement)tryCatch).TryBlock.Detach(); - ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); // Remove 'Enter()' call + if (!isV2) // Remove 'Enter()' call + ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); tryCatch.ReplaceWith(l); node.Remove(); // remove flag variable return l; @@ -1047,5 +1088,36 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return null; } #endregion + + #region Simplify cascading if-else-if statements + static readonly IfElseStatement cascadingIfElsePattern = new IfElseStatement + { + Condition = new AnyNode(), + TrueStatement = new AnyNode(), + FalseStatement = new BlockStatement { + Statements = { + new NamedNode( + "nestedIfStatement", + new IfElseStatement { + Condition = new AnyNode(), + TrueStatement = new AnyNode(), + FalseStatement = new OptionalNode(new AnyNode()) + } + ) + } + } + }; + + AstNode SimplifyCascadingIfElseStatements(IfElseStatement node) + { + Match m = cascadingIfElsePattern.Match(node); + if (m.Success) { + IfElseStatement elseIf = m.Get("nestedIfStatement").Single(); + node.FalseStatement = elseIf.Detach(); + } + + return null; + } + #endregion } } diff --git a/ICSharpCode.Decompiler/CecilExtensions.cs b/ICSharpCode.Decompiler/CecilExtensions.cs index 25faad4fe..76665d3f0 100644 --- a/ICSharpCode.Decompiler/CecilExtensions.cs +++ b/ICSharpCode.Decompiler/CecilExtensions.cs @@ -126,6 +126,41 @@ namespace ICSharpCode.Decompiler return false; return type.IsValueType || type.IsVoid(); } + + /// + /// checks if the given TypeReference is one of the following types: + /// [sbyte, short, int, long, IntPtr] + /// + public static bool IsSignedIntegralType(this TypeReference type) + { + return type.MetadataType == MetadataType.SByte || + type.MetadataType == MetadataType.Int16 || + type.MetadataType == MetadataType.Int32 || + type.MetadataType == MetadataType.Int64 || + type.MetadataType == MetadataType.IntPtr; + } + + /// + /// checks if the given value is a numeric zero-value. + /// NOTE that this only works for types: [sbyte, short, int, long, IntPtr, byte, ushort, uint, ulong, float, double and decimal] + /// + public static bool IsZero(this object value) + { + return value.Equals((sbyte)0) || + value.Equals((short)0) || + value.Equals(0) || + value.Equals(0L) || + value.Equals(IntPtr.Zero) || + value.Equals((byte)0) || + value.Equals((ushort)0) || + value.Equals(0u) || + value.Equals(0UL) || + value.Equals(0.0f) || + value.Equals(0.0) || + value.Equals((decimal)0); + + } + #endregion /// @@ -190,6 +225,7 @@ namespace ICSharpCode.Decompiler return null; } + [Obsolete("throwing exceptions is considered a bug")] public static TypeDefinition ResolveOrThrow(this TypeReference typeReference) { var resolved = typeReference.Resolve(); @@ -346,5 +382,16 @@ namespace ICSharpCode.Decompiler { return new ICSharpCode.NRefactory.TypeSystem.FullTypeName(typeDef.FullName); } + + public static bool IsDelegate(this TypeDefinition type) + { + if (type.BaseType != null && type.BaseType.Namespace == "System") { + if (type.BaseType.Name == "MulticastDelegate") + return true; + if (type.BaseType.Name == "Delegate" && type.Name != "MulticastDelegate") + return true; + } + return false; + } } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 6c8ccbe74..bd8543cf5 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.ILAst PropertyAccessInstructions, SplitToMovableBlocks, TypeInference, + HandlePointerArithmetic, SimplifyShortCircuit, SimplifyTernaryOperator, SimplifyNullCoalescing, @@ -125,6 +126,9 @@ namespace ICSharpCode.Decompiler.ILAst // Types are needed for the ternary operator optimization TypeAnalysis.Run(context, method); + if (abortBeforeStep == ILAstOptimizationStep.HandlePointerArithmetic) return; + HandlePointerArithmetic(method); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { bool modified; do { @@ -664,6 +668,107 @@ namespace ICSharpCode.Decompiler.ILAst return combinedVariable; }); } + + static void HandlePointerArithmetic(ILNode method) + { + foreach (ILExpression expr in method.GetSelfAndChildrenRecursive()) { + List args = expr.Arguments; + switch (expr.Code) { + case ILCode.Localloc: + args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType); + break; + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + if (expr.InferredType is PointerType) { + if (args[0].ExpectedType is PointerType) + args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType); + else if (args[1].ExpectedType is PointerType) + args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType); + } + break; + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + if (expr.InferredType is PointerType) { + if (args[0].ExpectedType is PointerType) + args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType); + } + break; + } + } + } + + static ILExpression UnwrapIntPtrCast(ILExpression expr) + { + if (expr.Code != ILCode.Conv_I && expr.Code != ILCode.Conv_U) + return expr; + + ILExpression arg = expr.Arguments[0]; + switch (arg.InferredType.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + case MetadataType.UInt16: + case MetadataType.Int16: + case MetadataType.UInt32: + case MetadataType.Int32: + case MetadataType.UInt64: + case MetadataType.Int64: + return arg; + } + + return expr; + } + + static ILExpression DivideBySize(ILExpression expr, TypeReference type) + { + expr = UnwrapIntPtrCast(expr); + + ILExpression sizeOfExpression; + switch (TypeAnalysis.GetInformationAmount(type)) { + case 1: + case 8: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 1); + break; + case 16: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 2); + break; + case 32: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 4); + break; + case 64: + sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 8); + break; + default: + sizeOfExpression = new ILExpression(ILCode.Sizeof, type); + break; + } + + if (expr.Code == ILCode.Mul || expr.Code == ILCode.Mul_Ovf || expr.Code == ILCode.Mul_Ovf_Un) { + ILExpression mulArg = expr.Arguments[1]; + if (mulArg.Code == sizeOfExpression.Code && sizeOfExpression.Operand.Equals(mulArg.Operand)) + return UnwrapIntPtrCast(expr.Arguments[0]); + } + + if (expr.Code == sizeOfExpression.Code) { + if (sizeOfExpression.Operand.Equals(expr.Operand)) + return new ILExpression(ILCode.Ldc_I4, 1); + + if (expr.Code == ILCode.Ldc_I4) { + int offsetInBytes = (int)expr.Operand; + int elementSize = (int)sizeOfExpression.Operand; + int offsetInElements = offsetInBytes / elementSize; + + // ensure integer division + if (offsetInElements * elementSize == offsetInBytes) { + expr.Operand = offsetInElements; + return expr; + } + } + } + + return new ILExpression(ILCode.Div_Un, null, expr, sizeOfExpression); + } public static void ReplaceVariables(ILNode node, Func variableMapping) { diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 243897e36..1931d390d 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -450,7 +450,7 @@ namespace ICSharpCode.Decompiler.ILAst return (TypeReference)expr.Operand; case ILCode.Localloc: if (forceInferChildren) { - InferTypeForExpression(expr.Arguments[0], typeSystem.Int32); + InferTypeForExpression(expr.Arguments[0], null); } if (expectedType is PointerType) return expectedType; @@ -1013,7 +1013,7 @@ namespace ICSharpCode.Decompiler.ILAst TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); if (leftPreferred is PointerType) { left.InferredType = left.ExpectedType = leftPreferred; - InferTypeForExpression(right, typeSystem.IntPtr); + InferTypeForExpression(right, null); return leftPreferred; } if (IsEnum(leftPreferred)) { @@ -1024,7 +1024,7 @@ namespace ICSharpCode.Decompiler.ILAst } TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType); if (rightPreferred is PointerType) { - InferTypeForExpression(left, typeSystem.IntPtr); + InferTypeForExpression(left, null); right.InferredType = right.ExpectedType = rightPreferred; return rightPreferred; } @@ -1044,7 +1044,7 @@ namespace ICSharpCode.Decompiler.ILAst TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); if (leftPreferred is PointerType) { left.InferredType = left.ExpectedType = leftPreferred; - InferTypeForExpression(right, typeSystem.IntPtr); + InferTypeForExpression(right, null); return leftPreferred; } if (IsEnum(leftPreferred)) { diff --git a/ICSharpCode.Decompiler/Properties/AssemblyInfo.template.cs b/ICSharpCode.Decompiler/Properties/AssemblyInfo.template.cs index 059625750..e45f93319 100644 --- a/ICSharpCode.Decompiler/Properties/AssemblyInfo.template.cs +++ b/ICSharpCode.Decompiler/Properties/AssemblyInfo.template.cs @@ -12,7 +12,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("IL decompiler engine")] [assembly: AssemblyCompany("ic#code")] [assembly: AssemblyProduct("ILSpy")] -[assembly: AssemblyCopyright("Copyright 2011 AlphaSierraPapa for the SharpDevelop Team")] +[assembly: AssemblyCopyright("Copyright 2011-2015 AlphaSierraPapa for the SharpDevelop Team")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/ICSharpCode.Decompiler/Tests/CodeSampleFileParser.cs b/ICSharpCode.Decompiler/Tests/CodeSampleFileParser.cs index 552d092ee..5cb49aad7 100644 --- a/ICSharpCode.Decompiler/Tests/CodeSampleFileParser.cs +++ b/ICSharpCode.Decompiler/Tests/CodeSampleFileParser.cs @@ -87,7 +87,8 @@ namespace ICSharpCode.Decompiler.Tests { if(String.IsNullOrWhiteSpace(s)) return true; - return s.Trim().StartsWith("//"); + s = s.Trim(); + return s.StartsWith("//") || s.StartsWith("#"); // Also ignore #pragmas for warning suppression } public static string ConcatLines(IEnumerable lines) diff --git a/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs b/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs index f51908659..5bd0ec516 100644 --- a/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs +++ b/ICSharpCode.Decompiler/Tests/CustomShortCircuitOperators.cs @@ -20,21 +20,19 @@ using System; public static class CustomShortCircuitOperators { - private class B + // TODO: Restore base class after https://roslyn.codeplex.com/workitem/358 is fixed. + private class C { - public static bool operator true(CustomShortCircuitOperators.B x) + public static bool operator true(CustomShortCircuitOperators.C x) { return true; } - public static bool operator false(CustomShortCircuitOperators.B x) + public static bool operator false(CustomShortCircuitOperators.C x) { return false; } - } - private class C : CustomShortCircuitOperators.B - { public static CustomShortCircuitOperators.C operator &(CustomShortCircuitOperators.C x, CustomShortCircuitOperators.C y) { return null; diff --git a/ICSharpCode.Decompiler/Tests/DecompilerTestBase.cs b/ICSharpCode.Decompiler/Tests/DecompilerTestBase.cs index 8205042a9..659bb2be6 100644 --- a/ICSharpCode.Decompiler/Tests/DecompilerTestBase.cs +++ b/ICSharpCode.Decompiler/Tests/DecompilerTestBase.cs @@ -35,10 +35,8 @@ namespace ICSharpCode.Decompiler.Tests { protected static void ValidateFileRoundtrip(string samplesFileName) { - var lines = File.ReadAllLines(Path.Combine(@"..\..\Tests", samplesFileName)); - var testCode = RemoveIgnorableLines(lines); - var decompiledTestCode = RoundtripCode(testCode); - CodeAssert.AreEqual(testCode, decompiledTestCode); + var fullPath = Path.Combine(@"..\..\Tests", samplesFileName); + AssertRoundtripCode(fullPath); } static string RemoveIgnorableLines(IEnumerable lines) @@ -46,29 +44,27 @@ namespace ICSharpCode.Decompiler.Tests return CodeSampleFileParser.ConcatLines(lines.Where(l => !CodeSampleFileParser.IsCommentOrBlank(l))); } - /// - /// Compiles and decompiles a source code. - /// - /// The source code to copile. - /// The decompilation result of compiled source code. - static string RoundtripCode(string code) + protected static void AssertRoundtripCode(string fileName, bool optimize = false, bool useDebug = false, int compilerVersion = 4) { - DecompilerSettings settings = new DecompilerSettings(); - settings.FullyQualifyAmbiguousTypeNames = false; - AssemblyDefinition assembly = Compile(code); - AstBuilder decompiler = new AstBuilder(new DecompilerContext(assembly.MainModule) { Settings = settings }); + var code = RemoveIgnorableLines(File.ReadLines(fileName)); + AssemblyDefinition assembly = CompileLegacy(code, optimize, useDebug, compilerVersion); + + AstBuilder decompiler = new AstBuilder(new DecompilerContext(assembly.MainModule)); decompiler.AddAssembly(assembly); new Helpers.RemoveCompilerAttribute().Run(decompiler.SyntaxTree); + StringWriter output = new StringWriter(); decompiler.GenerateCode(new PlainTextOutput(output)); - return output.ToString(); + CodeAssert.AreEqual(code, output.ToString()); } - static AssemblyDefinition Compile(string code) + protected static AssemblyDefinition CompileLegacy(string code, bool optimize, bool useDebug, int compilerVersion) { - CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary { { "CompilerVersion", "v4.0" } }); + CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary { { "CompilerVersion", "v" + new Version(compilerVersion, 0) } }); CompilerParameters options = new CompilerParameters(); - options.ReferencedAssemblies.Add("System.Core.dll"); + options.CompilerOptions = "/unsafe /o" + (optimize ? "+" : "-") + (useDebug ? " /debug" : ""); + if (compilerVersion >= 4) + options.ReferencedAssemblies.Add("System.Core.dll"); CompilerResults results = provider.CompileAssemblyFromSource(options, code); try { diff --git a/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs b/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs index 0a82fb82a..3e5a42ff4 100644 --- a/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs +++ b/ICSharpCode.Decompiler/Tests/Helpers/CodeAssert.cs @@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers private static IEnumerable NormalizeAndSplitCode(string input) { - return input.Split(new[] { "\r\n", "\n\r", "\n", "\r" }, StringSplitOptions.None); + return input.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 072c2efdc..ad7c18b66 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -107,7 +107,6 @@ - diff --git a/ICSharpCode.Decompiler/Tests/Lock.cs b/ICSharpCode.Decompiler/Tests/Lock.cs new file mode 100644 index 000000000..da5a59c74 --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/Lock.cs @@ -0,0 +1,38 @@ +// Copyright (c) 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; + +public class Lock +{ + public void LockThis() + { + lock (this) + { + Console.WriteLine(); + } + } + + public void LockOnType() + { + lock (typeof(Lock)) + { + Console.WriteLine(); + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs index 22458bcfc..d8857c30a 100644 --- a/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs +++ b/ICSharpCode.Decompiler/Tests/Types/S_TypeMemberDeclarations.cs @@ -664,7 +664,9 @@ namespace HideMembers3 } public class J2 : J { +#pragma warning disable 0108 // Deliberate bad code for test case public int get_P; +#pragma warning restore 0108 } } //$$ HideMembers4 diff --git a/ICSharpCode.Decompiler/Tests/UnsafeCode.cs b/ICSharpCode.Decompiler/Tests/UnsafeCode.cs index 66fb29529..1a3bd6a0f 100644 --- a/ICSharpCode.Decompiler/Tests/UnsafeCode.cs +++ b/ICSharpCode.Decompiler/Tests/UnsafeCode.cs @@ -122,7 +122,22 @@ public class UnsafeCode } return this.PointerReferenceExpression((double*)ptr); } - + + public unsafe int* PointerArithmetic(int* p) + { + return p + 2; + } + + public unsafe byte* PointerArithmetic2(long* p, int y, int x) + { + return (byte*)p + (y * x); + } + + public unsafe long* PointerArithmetic3(long* p) + { + return (long*)((byte*)p + 3); + } + unsafe ~UnsafeCode() { this.PassPointerAsRefParameter(this.NullPointer); diff --git a/ICSharpCode.Decompiler/Tests/ValueTypes.cs b/ICSharpCode.Decompiler/Tests/ValueTypes.cs index b3aa64478..1493cff4c 100644 --- a/ICSharpCode.Decompiler/Tests/ValueTypes.cs +++ b/ICSharpCode.Decompiler/Tests/ValueTypes.cs @@ -168,4 +168,21 @@ public static class ValueTypes Console.WriteLine("true"); } } + + public static void CompareNotEqual0IsReallyNotEqual(IComparable a) + { + if (a.CompareTo(0) != 0) + { + Console.WriteLine("true"); + } + } + + public static void CompareEqual0IsReallyEqual(IComparable a) + { + if (a.CompareTo(0) == 0) + { + Console.WriteLine("true"); + } + } + } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypesHierarchyHelpers.cs b/ICSharpCode.Decompiler/TypeSystem/TypesHierarchyHelpers.cs index 8de45cca5..f0f5ece53 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypesHierarchyHelpers.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypesHierarchyHelpers.cs @@ -32,9 +32,11 @@ namespace ICSharpCode.Decompiler.Ast if (resolveTypeArguments) return BaseTypes(derivedType).Any(t => t.Item == baseType); else { - var comparableBaseType = baseType.ResolveOrThrow(); + var comparableBaseType = baseType.Resolve(); + if (comparableBaseType == null) + return false; while (derivedType.BaseType != null) { - var resolvedBaseType = derivedType.BaseType.ResolveOrThrow(); + var resolvedBaseType = derivedType.BaseType.Resolve(); if (resolvedBaseType == null) return false; if (comparableBaseType == resolvedBaseType) @@ -185,24 +187,32 @@ namespace ICSharpCode.Decompiler.Ast if (derivedType == null) throw new ArgumentNullException("derivedType"); - var visibility = IsVisibleFromDerived(baseMember); - if (visibility.HasValue) - return visibility.Value; + MethodAttributes attrs = GetAccessAttributes(baseMember) & MethodAttributes.MemberAccessMask; + if (attrs == MethodAttributes.Private) + return false; if (baseMember.DeclaringType.Module == derivedType.Module) return true; - // TODO: Check also InternalsVisibleToAttribute. - return false; - } - private static bool? IsVisibleFromDerived(IMemberDefinition member) - { - MethodAttributes attrs = GetAccessAttributes(member) & MethodAttributes.MemberAccessMask; - if (attrs == MethodAttributes.Private) + if (attrs == MethodAttributes.Assembly || attrs == MethodAttributes.FamANDAssem) { + var derivedTypeAsm = derivedType.Module.Assembly; + var asm = baseMember.DeclaringType.Module.Assembly; + + if (asm.HasCustomAttributes) { + var attributes = asm.CustomAttributes + .Where(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.InternalsVisibleToAttribute"); + foreach (var attribute in attributes) { + string assemblyName = attribute.ConstructorArguments[0].Value as string; + assemblyName = assemblyName.Split(',')[0]; // strip off any public key info + if (assemblyName == derivedTypeAsm.Name.Name) + return true; + } + } + return false; - if (attrs == MethodAttributes.Assembly || attrs == MethodAttributes.FamANDAssem) - return null; - return true; + } + + return true; } private static MethodAttributes GetAccessAttributes(IMemberDefinition member) diff --git a/ILSpy.AddIn/GlobalSuppressions.cs b/ILSpy.AddIn/GlobalSuppressions.cs new file mode 100644 index 000000000..a893f9d25 --- /dev/null +++ b/ILSpy.AddIn/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. Project-level +// suppressions either have no target or are given a specific target +// and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click "In Project +// Suppression File". You do not need to add suppressions to this +// file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] diff --git a/ILSpy.AddIn/Guids.cs b/ILSpy.AddIn/Guids.cs new file mode 100644 index 000000000..bbca1aa1a --- /dev/null +++ b/ILSpy.AddIn/Guids.cs @@ -0,0 +1,14 @@ +// Guids.cs +// MUST match guids.h +using System; + +namespace ICSharpCode.ILSpy.AddIn +{ + static class GuidList + { + public const string guidILSpyAddInPkgString = "a9120dbe-164a-4891-842f-fb7829273838"; + public const string guidILSpyAddInCmdSetString = "85ddb8ca-a842-4b1c-ba1a-94141fdf19d0"; + + public static readonly Guid guidILSpyAddInCmdSet = new Guid(guidILSpyAddInCmdSetString); + }; +} \ No newline at end of file diff --git a/ILSpy.AddIn/ILSpy-Large.ico b/ILSpy.AddIn/ILSpy-Large.ico new file mode 100644 index 000000000..14a26f95a Binary files /dev/null and b/ILSpy.AddIn/ILSpy-Large.ico differ diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj new file mode 100644 index 000000000..7c56424cb --- /dev/null +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -0,0 +1,246 @@ + + + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\VSSDK\Microsoft.VsSDK.targets + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets + + + + + $(VisualStudioVersion) + + + + Debug + AnyCPU + 2.0 + {9D7BE6C0-B7B3-4A50-A54E-18A2D84A3384} + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + ICSharpCode.ILSpy.AddIn + ILSpy.AddIn + True + Key.snk + v4.0 + + + + Program + $(DevEnvDir)\devenv.exe + /rootSuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + ..\packages\VSSDK.DTE.7.0.3\lib\net20\envdte.dll + False + False + + + + False + ..\packages\VSSDK.OLE.Interop.7.0.3\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll + + + False + ..\packages\VSSDK.Shell.10.10.0.3\lib\net40\Microsoft.VisualStudio.Shell.10.0.dll + + + False + ..\packages\VSSDK.Shell.Immutable.10.10.0.3\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll + + + False + ..\packages\VSSDK.Shell.Interop.7.0.3\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll + + + False + ..\packages\VSSDK.Shell.Interop.8.8.0.3\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll + + + + False + ..\packages\VSSDK.Shell.Interop.9.9.0.3\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll + + + False + ..\packages\VSSDK.TextManager.Interop.7.0.3\lib\net20\Microsoft.VisualStudio.TextManager.Interop.dll + + + False + ..\packages\VSSDK.TextManager.Interop.8.8.0.3\lib\net20\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + + + + + ..\packages\VSSDK.DTE.7.0.3\lib\net20\stdole.dll + False + False + + + + + + + + + + + + + {26AD1324-4B7C-44BC-84F8-B86AED45729F} + 10 + 0 + 0 + primary + False + False + + + {1A31287A-4D7D-413E-8E32-3B374931BD89} + 8 + 0 + 0 + primary + False + False + + + {2CE2370E-D744-4936-A090-3FFFE667B0E1} + 9 + 0 + 0 + primary + False + False + + + + + + True + True + Resources.resx + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + true + VSPackage + + + + + + Designer + + + + + + + + Menus.ctmenu + Designer + + + + + + + + Always + true + + + Always + true + + + + + + {6c55b776-26d4-4db3-a6ab-87e783b2f3d1} + ICSharpCode.AvalonEdit + + + {984cc812-9470-4a13-aff9-cc44068d666c} + ICSharpCode.Decompiler + + + {a6bad2ba-76ba-461c-8b6d-418607591247} + ILSpy.BamlDecompiler + + + {1e85eff9-e370-4683-83e4-8a3d063ff791} + ILSpy + + + {d68133bd-1e63-496e-9ede-4fbdbf77b486} + Mono.Cecil + + + {63e6915c-7ea4-4d76-ab28-0d7191eea626} + Mono.Cecil.Pdb + + + {53dca265-3c3c-42f9-b647-f72ba678122b} + ICSharpCode.NRefactory.CSharp + + + {7b82b671-419f-45f4-b778-d9286f996efa} + ICSharpCode.NRefactory.VB + + + {3b2a5653-ec97-4001-bb9b-d90f1af2c371} + ICSharpCode.NRefactory + + + {dde2a481-8271-4eac-a330-8fa6a38d13d1} + ICSharpCode.TreeView + + + + true + + + + + \ No newline at end of file diff --git a/ILSpy.AddIn/ILSpyAddIn.vsct b/ILSpy.AddIn/ILSpyAddIn.vsct new file mode 100644 index 000000000..69af34274 --- /dev/null +++ b/ILSpy.AddIn/ILSpyAddIn.vsct @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ILSpy.AddIn/ILSpyAddInPackage.cs b/ILSpy.AddIn/ILSpyAddInPackage.cs new file mode 100644 index 000000000..de2ddd160 --- /dev/null +++ b/ILSpy.AddIn/ILSpyAddInPackage.cs @@ -0,0 +1,167 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.InteropServices; +using System.ComponentModel.Design; +using Microsoft.Win32; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; +using System.Reflection; +using System.IO; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.AddIn +{ + /// + /// This is the class that implements the package exposed by this assembly. + /// + /// The minimum requirement for a class to be considered a valid package for Visual Studio + /// is to implement the IVsPackage interface and register itself with the shell. + /// This package uses the helper classes defined inside the Managed Package Framework (MPF) + /// to do it: it derives from the Package class that provides the implementation of the + /// IVsPackage interface and uses the registration attributes defined in the framework to + /// register itself and its components with the shell. + /// + // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is + // a package. + [PackageRegistration(UseManagedResourcesOnly = true)] + // This attribute is used to register the information needed to show this package + // in the Help/About dialog of Visual Studio. + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] + // This attribute is needed to let the shell know that this package exposes some menus. + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(GuidList.guidILSpyAddInPkgString)] + [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string)] + public sealed class ILSpyAddInPackage : Package + { + /// + /// Default constructor of the package. + /// Inside this method you can place any initialization code that does not require + /// any Visual Studio service because at this point the package object is created but + /// not sited yet inside Visual Studio environment. The place to do all the other + /// initialization is the Initialize method. + /// + public ILSpyAddInPackage() + { + Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString())); + } + + + + ///////////////////////////////////////////////////////////////////////////// + // Overridden Package Implementation + #region Package Members + + /// + /// Initialization of the package; this method is called right after the package is sited, so this is the place + /// where you can put all the initialization code that rely on services provided by VisualStudio. + /// + protected override void Initialize() + { + Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString())); + base.Initialize(); + + // Add our command handlers for menu (commands must exist in the .vsct file) + OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + if (null != mcs) { + // Create the command for the menu item. + CommandID menuCommandID = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenReferenceInILSpy); + MenuCommand menuItem = new MenuCommand(OpenReferenceInILSpyCallback, menuCommandID); + mcs.AddCommand(menuItem); + + // Create the command for the menu item. + CommandID menuCommandID2 = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenProjectOutputInILSpy); + MenuCommand menuItem2 = new MenuCommand(OpenProjectOutputInILSpyCallback, menuCommandID2); + mcs.AddCommand(menuItem2); + + // Create the command for the menu item. + CommandID menuCommandID3 = new CommandID(GuidList.guidILSpyAddInCmdSet, (int)PkgCmdIDList.cmdidOpenILSpy); + MenuCommand menuItem3 = new MenuCommand(OpenILSpyCallback, menuCommandID3); + mcs.AddCommand(menuItem3); + } + } + #endregion + + /// + /// This function is the callback used to execute a command when the a menu item is clicked. + /// See the Initialize method to see how the menu item is associated to this function using + /// the OleMenuCommandService service and the MenuCommand class. + /// + private void OpenReferenceInILSpyCallback(object sender, EventArgs e) + { + var explorer = ((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ToolWindows.SolutionExplorer; + var items =(object[]) explorer.SelectedItems; + + foreach (EnvDTE.UIHierarchyItem item in items) { + dynamic reference = item.Object; + string path = null; + if (reference.PublicKeyToken != "") { + var token = Utils.HexStringToBytes(reference.PublicKeyToken); + path = GacInterop.FindAssemblyInNetGac(new AssemblyNameReference(reference.Identity, new Version(reference.Version)) { PublicKeyToken = token }); + } + if (path == null) + path = reference.Path; + OpenAssemblyInILSpy(path); + } + } + + private void OpenProjectOutputInILSpyCallback(object sender, EventArgs e) + { + var explorer = ((EnvDTE80.DTE2)GetGlobalService(typeof(EnvDTE.DTE))).ToolWindows.SolutionExplorer; + var items = (object[])explorer.SelectedItems; + + foreach (EnvDTE.UIHierarchyItem item in items) { + EnvDTE.Project project = (EnvDTE.Project)item.Object; + EnvDTE.Configuration config = project.ConfigurationManager.ActiveConfiguration; + string projectPath = Path.GetDirectoryName(project.FileName); + string outputPath = config.Properties.Item("OutputPath").Value.ToString(); + string assemblyFileName = project.Properties.Item("OutputFileName").Value.ToString(); + OpenAssemblyInILSpy(Path.Combine(projectPath, outputPath, assemblyFileName)); + } + } + + private void OpenILSpyCallback(object sender, EventArgs e) + { + Process.Start(GetILSpyPath()); + } + + private string GetILSpyPath() + { + var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location); + return Path.Combine(basePath, "ILSpy.exe"); + } + + private void OpenAssemblyInILSpy(string assemblyFileName) + { + if (!File.Exists(assemblyFileName)) { + ShowMessage("Could not find assembly '{0}', please ensure the project and all references were built correctly!", assemblyFileName); + return; + } + Process.Start(GetILSpyPath(), Utils.ArgumentArrayToCommandLine(assemblyFileName)); + } + + private void ShowMessage(string format, params object[] items) + { + IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); + Guid clsid = Guid.Empty; + int result; + Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure( + uiShell.ShowMessageBox( + 0, + ref clsid, + "ILSpy.AddIn", + string.Format(CultureInfo.CurrentCulture, format, items), + string.Empty, + 0, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, + OLEMSGICON.OLEMSGICON_INFO, + 0, // false + out result + ) + ); + } + } +} \ No newline at end of file diff --git a/ILSpy.AddIn/Key.snk b/ILSpy.AddIn/Key.snk new file mode 100644 index 000000000..727377dec Binary files /dev/null and b/ILSpy.AddIn/Key.snk differ diff --git a/ILSpy.AddIn/PkgCmdID.cs b/ILSpy.AddIn/PkgCmdID.cs new file mode 100644 index 000000000..1cc38a3fd --- /dev/null +++ b/ILSpy.AddIn/PkgCmdID.cs @@ -0,0 +1,13 @@ +// PkgCmdID.cs +// MUST match PkgCmdID.h +using System; + +namespace ICSharpCode.ILSpy.AddIn +{ + static class PkgCmdIDList + { + public const uint cmdidOpenILSpy = 0x100; + public const uint cmdidOpenReferenceInILSpy = 0x200; + public const uint cmdidOpenProjectOutputInILSpy = 0x300; + }; +} \ No newline at end of file diff --git a/ILSpy.AddIn/Properties/AssemblyInfo.cs b/ILSpy.AddIn/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..037d95e27 --- /dev/null +++ b/ILSpy.AddIn/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ILSpy.AddIn")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("IC#Code")] +[assembly: AssemblyProduct("ILSpy.AddIn")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en-US")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: InternalsVisibleTo("ILSpy.AddIn_IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100653c4a319be4f524972c3c5bba5fd243330f8e900287d9022d7821a63fd0086fd3801e3683dbe9897f2ecc44727023e9b40adcf180730af70c81c54476b3e5ba8b0f07f5132b2c3cc54347a2c1a9d64ebaaaf3cbffc1a18c427981e2a51d53d5ab02536b7550e732f795121c38a0abfdb38596353525d034baf9e6f1fd8ac4ac")] +[assembly: InternalsVisibleTo("ILSpy.AddIn_UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100653c4a319be4f524972c3c5bba5fd243330f8e900287d9022d7821a63fd0086fd3801e3683dbe9897f2ecc44727023e9b40adcf180730af70c81c54476b3e5ba8b0f07f5132b2c3cc54347a2c1a9d64ebaaaf3cbffc1a18c427981e2a51d53d5ab02536b7550e732f795121c38a0abfdb38596353525d034baf9e6f1fd8ac4ac")] diff --git a/ILSpy.AddIn/Resources.Designer.cs b/ILSpy.AddIn/Resources.Designer.cs new file mode 100644 index 000000000..646632f04 --- /dev/null +++ b/ILSpy.AddIn/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.35317 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ICSharpCode.ILSpy.AddIn { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ICSharpCode.ILSpy.AddIn.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/ILSpy.AddIn/Resources.resx b/ILSpy.AddIn/Resources.resx new file mode 100644 index 000000000..891c592b4 --- /dev/null +++ b/ILSpy.AddIn/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ILSpy.AddIn/Resources/Images.png b/ILSpy.AddIn/Resources/Images.png new file mode 100644 index 000000000..4a217fa71 Binary files /dev/null and b/ILSpy.AddIn/Resources/Images.png differ diff --git a/ILSpy.AddIn/Resources/Package.ico b/ILSpy.AddIn/Resources/Package.ico new file mode 100644 index 000000000..900abec81 Binary files /dev/null and b/ILSpy.AddIn/Resources/Package.ico differ diff --git a/ILSpy.AddIn/Utils.cs b/ILSpy.AddIn/Utils.cs new file mode 100644 index 000000000..ce850fcfe --- /dev/null +++ b/ILSpy.AddIn/Utils.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.ILSpy.AddIn +{ + class Utils + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] + [DllImport("kernel32.dll")] + static extern IntPtr LocalFree(IntPtr hMem); + + #region CommandLine <-> Argument Array + /// + /// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules. + /// + /// + /// Command line parsing rules: + /// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument. + /// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. + /// - n backslashes not followed by a quotation mark simply produce n backslashes. + /// + public static unsafe string[] CommandLineToArgumentArray(string commandLine) + { + if (string.IsNullOrEmpty(commandLine)) + return new string[0]; + int numberOfArgs; + char** arr = CommandLineToArgvW(commandLine, out numberOfArgs); + if (arr == null) + throw new Win32Exception(); + try { + string[] result = new string[numberOfArgs]; + for (int i = 0; i < numberOfArgs; i++) { + result[i] = new string(arr[i]); + } + return result; + } finally { + // Free memory obtained by CommandLineToArgW. + LocalFree(new IntPtr(arr)); + } + } + + static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' }; + + /// + /// Escapes a set of arguments according to the CommandLineToArgvW rules. + /// + /// + /// Command line parsing rules: + /// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument. + /// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. + /// - n backslashes not followed by a quotation mark simply produce n backslashes. + /// + public static string ArgumentArrayToCommandLine(params string[] arguments) + { + if (arguments == null) + return null; + StringBuilder b = new StringBuilder(); + for (int i = 0; i < arguments.Length; i++) { + if (i > 0) + b.Append(' '); + AppendArgument(b, arguments[i]); + } + return b.ToString(); + } + + static void AppendArgument(StringBuilder b, string arg) + { + if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0) { + b.Append(arg); + } else { + b.Append('"'); + for (int j = 0; ; j++) { + int backslashCount = 0; + while (j < arg.Length && arg[j] == '\\') { + backslashCount++; + j++; + } + if (j == arg.Length) { + b.Append('\\', backslashCount * 2); + break; + } else if (arg[j] == '"') { + b.Append('\\', backslashCount * 2 + 1); + b.Append('"'); + } else { + b.Append('\\', backslashCount); + b.Append(arg[j]); + } + } + b.Append('"'); + } + } + #endregion + + public static byte[] HexStringToBytes(string hex) + { + var result = new byte[hex.Length / 2]; + for (int i = 0; i < hex.Length / 2; i++) { + result[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return result; + } + } +} diff --git a/ILSpy.AddIn/VSPackage.resx b/ILSpy.AddIn/VSPackage.resx new file mode 100644 index 000000000..345019f09 --- /dev/null +++ b/ILSpy.AddIn/VSPackage.resx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ILSpy.AddIn + + + Integration of the ILSpy Decompiler into Visual Studio. + + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/ILSpy.AddIn/license.txt b/ILSpy.AddIn/license.txt new file mode 100644 index 000000000..0bfb45649 --- /dev/null +++ b/ILSpy.AddIn/license.txt @@ -0,0 +1,12 @@ +The following MIT license applies to ILSpy, NRefactory and ICSharpCode.Decompiler. Mono.Cecil also uses the MIT license (Copyright JB Evain). AvalonEdit and SharpTreeView use LGPL, which can be found in the LGPL.txt file. ILSpy.BamlDecompiler uses the MS-PL, which can be found in the MS-PL.txt file. + + +MIT license: + +Copyright (c) 2011-2014 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. \ No newline at end of file diff --git a/ILSpy.AddIn/packages.config b/ILSpy.AddIn/packages.config new file mode 100644 index 000000000..14a098365 --- /dev/null +++ b/ILSpy.AddIn/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy.AddIn/source.extension.vsixmanifest b/ILSpy.AddIn/source.extension.vsixmanifest new file mode 100644 index 000000000..7be4f368e --- /dev/null +++ b/ILSpy.AddIn/source.extension.vsixmanifest @@ -0,0 +1,32 @@ + + + + ILSpy + IC#Code + 1.2 + Integrates the ILSpy decompiler into Visual Studio. + ILSpy-Large.ico + license.txt + + http://ilspy.net + 1033 + + + + Pro + + + Pro + + + Pro + + + Pro + + + + + |%CurrentProject%;PkgdefProjectOutputGroup| + + diff --git a/ILSpy.BamlDecompiler/CecilTypeResolver.cs b/ILSpy.BamlDecompiler/CecilTypeResolver.cs index 9de73931a..ba9299fd6 100644 --- a/ILSpy.BamlDecompiler/CecilTypeResolver.cs +++ b/ILSpy.BamlDecompiler/CecilTypeResolver.cs @@ -39,12 +39,13 @@ namespace ILSpy.BamlDecompiler public IType GetTypeByAssemblyQualifiedName(string name) { - int comma = name.IndexOf(','); + int bracket = name.LastIndexOf(']'); + int comma = bracket > -1 ? name.IndexOf(',', bracket) : name.IndexOf(','); if (comma == -1) throw new ArgumentException("invalid name"); - - string fullName = name.Substring(0, comma); + + string fullName = bracket > -1 ? name.Substring(0, name.IndexOf('[')) : name.Substring(0, comma); string assemblyName = name.Substring(comma + 1).Trim(); var type = thisAssembly.MainModule.GetType(fullName); diff --git a/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/TypeDeclaration.cs b/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/TypeDeclaration.cs index 95ae027ab..6454fe1ed 100644 --- a/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/TypeDeclaration.cs +++ b/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/TypeDeclaration.cs @@ -131,7 +131,8 @@ namespace Ricciolo.StylesExplorer.MarkupReflection void ParseName(string assemblyQualifiedName, out string name, out string @namespace, out string assembly) { - int commaSeparator = assemblyQualifiedName.IndexOf(", "); + int bracket = assemblyQualifiedName.LastIndexOf(']'); + int commaSeparator = bracket > -1 ? assemblyQualifiedName.IndexOf(", ", bracket) : assemblyQualifiedName.IndexOf(", "); assembly = ""; if (commaSeparator >= 0) { assembly = assemblyQualifiedName.Substring(commaSeparator + 2); diff --git a/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/XmlBamlReader.cs b/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/XmlBamlReader.cs index 9676243c9..b80d2ae5b 100644 --- a/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/XmlBamlReader.cs +++ b/ILSpy.BamlDecompiler/Ricciolo.StylesExplorer.MarkupReflection/XmlBamlReader.cs @@ -868,11 +868,7 @@ namespace Ricciolo.StylesExplorer.MarkupReflection switch (x) { case 0x25a: // StaticExtension - object resource = this.GetResourceName(valueIdentifier); - if (resource is ResourceName) - value = this.GetStaticExtension(((ResourceName)resource).Name); - else if (resource is PropertyDeclaration) - value = this.GetStaticExtension(FormatPropertyDeclaration(((PropertyDeclaration)resource), true, false, false)); + value = this.GetStaticExtension(this.GetResourceName(valueIdentifier)); break; case 0x25b: // StaticResource case 0xbd: // DynamicResource @@ -883,7 +879,7 @@ namespace Ricciolo.StylesExplorer.MarkupReflection else if (isStaticType) { TypeDeclaration extensionDeclaration = this.GetTypeDeclaration(extensionIdentifier); - value = GetExtension(extensionDeclaration, GetStaticExtension(GetResourceName(valueIdentifier).ToString())); + value = GetExtension(extensionDeclaration, GetStaticExtension(GetResourceName(valueIdentifier))); } else { @@ -912,7 +908,7 @@ namespace Ricciolo.StylesExplorer.MarkupReflection short identifier = reader.ReadInt16(); string text = reader.ReadString(); - EnqueueProperty(identifier, text); + EnqueueProperty(identifier, EscapeCurlyBraces(text)); } void ReadPropertyWithConverter() @@ -921,7 +917,16 @@ namespace Ricciolo.StylesExplorer.MarkupReflection string text = reader.ReadString(); reader.ReadInt16(); - EnqueueProperty(identifier, text); + EnqueueProperty(identifier, EscapeCurlyBraces(text)); + } + + string EscapeCurlyBraces(string text) + { + if (!text.StartsWith("{", StringComparison.OrdinalIgnoreCase)) + return text; + if (text.StartsWith("{}", StringComparison.OrdinalIgnoreCase)) + return text; + return "{}" + text; } bool HaveSeenNestedElement() @@ -1316,7 +1321,7 @@ namespace Ricciolo.StylesExplorer.MarkupReflection { reader.ReadInt16(); - // Non serve aprire niente, è il default + // Non serve aprire niente, ?il default } static void ReadConstructorParametersStart() @@ -1449,15 +1454,7 @@ namespace Ricciolo.StylesExplorer.MarkupReflection if (isValueType) resource = GetTypeExtension(typeIdentifier); else if (isStaticType) { - object name = GetResourceName(typeIdentifier); - if (name == null) - resource = null; - else if (name is ResourceName) - resource = GetStaticExtension(((ResourceName)name).Name); - else if (name is PropertyDeclaration) - resource = GetStaticExtension(FormatPropertyDeclaration(((PropertyDeclaration)name), true, false, false)); - else - throw new InvalidOperationException("Invalid resource: " + name.GetType()); + resource = GetStaticExtension(GetResourceName(typeIdentifier)); } else { resource = this.stringTable[typeIdentifier]; } @@ -1473,8 +1470,18 @@ namespace Ricciolo.StylesExplorer.MarkupReflection return String.Format("{{TemplateBinding {0}}}", FormatPropertyDeclaration(propertyDeclaration, true, false, false)); } - string GetStaticExtension(string name) + string GetStaticExtension(object resource) { + if (resource == null) + return null; + string name; + if (resource is ResourceName) + name = ((ResourceName)resource).Name; + else if (resource is PropertyDeclaration) + name = this.FormatPropertyDeclaration(((PropertyDeclaration)resource), true, false, false); + else + throw new InvalidOperationException("Invalid resource: " + resource.GetType()); + string prefix = this.LookupPrefix(XmlPIMapping.XamlNamespace, false); if (String.IsNullOrEmpty(prefix)) return String.Format("{{Static {0}}}", name); @@ -1588,7 +1595,8 @@ namespace Ricciolo.StylesExplorer.MarkupReflection string fullName = reader.ReadString(); assemblyId = (short)(assemblyId & 0xfff); TypeDeclaration declaration; - int length = fullName.LastIndexOf('.'); + int bracket = fullName.IndexOf('['); + int length = bracket > -1 ? fullName.LastIndexOf('.', bracket) : fullName.LastIndexOf('.'); if (length != -1) { string name = fullName.Substring(length + 1); diff --git a/ILSpy.BamlDecompiler/Tests/Cases/CustomControl.cs b/ILSpy.BamlDecompiler/Tests/Cases/CustomControl.cs index f85c51a4e..e48b20d8a 100644 --- a/ILSpy.BamlDecompiler/Tests/Cases/CustomControl.cs +++ b/ILSpy.BamlDecompiler/Tests/Cases/CustomControl.cs @@ -10,6 +10,8 @@ namespace ILSpy.BamlDecompiler.Tests.Cases { public class CustomControl : ContentControl { + public static string SimpleProperty = "Hi!"; + public static readonly DependencyProperty CustomNameProperty = DependencyProperty.RegisterAttached("CustomName", typeof(string), typeof(CustomControl)); public static string GetCustomName(DependencyObject target) diff --git a/ILSpy.BamlDecompiler/Tests/Cases/EscapeSequence.xaml b/ILSpy.BamlDecompiler/Tests/Cases/EscapeSequence.xaml new file mode 100644 index 000000000..6487f47a8 --- /dev/null +++ b/ILSpy.BamlDecompiler/Tests/Cases/EscapeSequence.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ILSpy.BamlDecompiler/Tests/Cases/MarkupExtension.xaml b/ILSpy.BamlDecompiler/Tests/Cases/MarkupExtension.xaml index 5073d3e29..565fb2e1c 100644 --- a/ILSpy.BamlDecompiler/Tests/Cases/MarkupExtension.xaml +++ b/ILSpy.BamlDecompiler/Tests/Cases/MarkupExtension.xaml @@ -3,6 +3,6 @@