diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index e0aefd41f..b07ccb74c 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; - +using ICSharpCode.Decompiler.Ast.Transforms; using ICSharpCode.Decompiler.ILAst; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.Utils; @@ -183,17 +183,17 @@ namespace ICSharpCode.Decompiler.Ast yield return tryCatchStmt; } else if (node is ILFixedStatement) { ILFixedStatement fixedNode = (ILFixedStatement)node; - ILVariable v; - ILExpression init; - if (!fixedNode.Initializer.Match(ILCode.Stloc, out v, out init)) - throw new InvalidOperationException("Fixed initializer must be an assignment to a local variable"); FixedStatement fixedStatement = new FixedStatement(); - fixedStatement.Type = AstBuilder.ConvertType(v.Type); - fixedStatement.Variables.Add( - new VariableInitializer { - Name = v.Name, - Initializer = (Expression)TransformExpression(init) - }.WithAnnotation(v)); + foreach (ILExpression initializer in fixedNode.Initializers) { + Debug.Assert(initializer.Code == ILCode.Stloc); + ILVariable v = (ILVariable)initializer.Operand; + fixedStatement.Variables.Add( + new VariableInitializer { + Name = v.Name, + Initializer = (Expression)TransformExpression(initializer.Arguments[0]) + }.WithAnnotation(v)); + } + fixedStatement.Type = AstBuilder.ConvertType(((ILVariable)fixedNode.Initializers[0].Operand).Type); fixedStatement.EmbeddedStatement = TransformBlock(fixedNode.BodyBlock); yield return fixedStatement; } else if (node is ILBlock) { @@ -240,9 +240,36 @@ namespace ICSharpCode.Decompiler.Ast switch(byteCode.Code) { #region Arithmetic - case ILCode.Add: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); - case ILCode.Add_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); - case ILCode.Add_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + { + if (byteCode.InferredType is PointerType) { + if (byteCode.Arguments[0].ExpectedType is PointerType) { + arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); + return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2) + .WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); + } else if (byteCode.Arguments[1].ExpectedType is PointerType) { + arg1 = DivideBySize(arg1, ((PointerType)byteCode.InferredType).ElementType); + return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2) + .WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); + } + } + return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Add, arg2); + } + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + { + if (byteCode.InferredType is PointerType) { + if (byteCode.Arguments[0].ExpectedType is PointerType) { + arg2 = DivideBySize(arg2, ((PointerType)byteCode.InferredType).ElementType); + return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2) + .WithAnnotation(IntroduceUnsafeModifier.PointerArithmeticAnnotation); + } + } + return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); + } case ILCode.Div: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); case ILCode.Div_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Divide, arg2); case ILCode.Mul: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2); @@ -250,9 +277,6 @@ namespace ICSharpCode.Decompiler.Ast case ILCode.Mul_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Multiply, arg2); case ILCode.Rem: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); case ILCode.Rem_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Modulus, arg2); - case ILCode.Sub: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); - case ILCode.Sub_Ovf: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); - case ILCode.Sub_Ovf_Un: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.Subtract, arg2); case ILCode.And: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseAnd, arg2); case ILCode.Or: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.BitwiseOr, arg2); case ILCode.Xor: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.ExclusiveOr, arg2); @@ -487,7 +511,20 @@ namespace ICSharpCode.Decompiler.Ast return InlineAssembly(byteCode, args); } case ILCode.Leave: return new GotoStatement() { Label = ((ILLabel)operand).Name }; - case ILCode.Localloc: return InlineAssembly(byteCode, args); + case ILCode.Localloc: + { + PointerType ptrType = byteCode.InferredType as PointerType; + TypeReference type; + if (ptrType != null) { + type = ptrType.ElementType; + } else { + type = typeSystem.Byte; + } + return new StackAllocExpression { + Type = AstBuilder.ConvertType(type), + CountExpression = DivideBySize(arg1, type) + }; + } case ILCode.Mkrefany: return InlineAssembly(byteCode, args); case ILCode.Newobj: { Cecil.TypeReference declaringType = ((MethodReference)operand).DeclaringType; @@ -557,6 +594,45 @@ 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.Match(boe.Right) != null) + return boe.Left.Detach(); + + if (sizeOfExpression.Match(expr) != null) + 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/DeclareVariableInSmallestScope.cs b/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs index 6f38542f6..f609c2414 100644 --- a/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs +++ b/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs @@ -55,8 +55,12 @@ namespace ICSharpCode.Decompiler.Ast return node; FixedStatement fixedStatement = node as FixedStatement; - if (fixedStatement != null && fixedStatement.Variables.Single().Name == name) - return null; // no need to introduce the variable here + if (fixedStatement != null) { + foreach (VariableInitializer v in fixedStatement.Variables) { + if (v.Name == name) + return null; // no need to introduce the variable here + } + } AstNode withinCurrent = FindInsertPos(node.FirstChild, name, allowPassIntoLoops); if (withinCurrent != null) { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs index fd716eb00..958b97c65 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/IntroduceUnsafeModifier.cs @@ -8,6 +8,10 @@ namespace ICSharpCode.Decompiler.Ast.Transforms { public class IntroduceUnsafeModifier : DepthFirstAstVisitor, IAstTransform { + public static readonly object PointerArithmeticAnnotation = new PointerArithmetic(); + + sealed class PointerArithmetic {} + public void Run(AstNode compilationUnit) { compilationUnit.AcceptVisitor(this, null); @@ -42,17 +46,23 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) { - base.VisitUnaryOperatorExpression(unaryOperatorExpression, data); + bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression, data); if (unaryOperatorExpression.Operator == UnaryOperatorType.Dereference) { BinaryOperatorExpression bop = unaryOperatorExpression.Expression as BinaryOperatorExpression; - if (bop != null && bop.Operator == BinaryOperatorType.Add) { - // TODO: transform "*(ptr + int)" to "ptr[int]" + if (bop != null && bop.Operator == BinaryOperatorType.Add && bop.Annotation() != null) { + // transform "*(ptr + int)" to "ptr[int]" + IndexerExpression indexer = new IndexerExpression(); + indexer.Target = bop.Left.Detach(); + indexer.Arguments.Add(bop.Right.Detach()); + indexer.CopyAnnotationsFrom(unaryOperatorExpression); + indexer.CopyAnnotationsFrom(bop); + unaryOperatorExpression.ReplaceWith(indexer); } return true; } else if (unaryOperatorExpression.Operator == UnaryOperatorType.AddressOf) { return true; } else { - return false; + return result; } } @@ -71,5 +81,11 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } return result; } + + public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data) + { + base.VisitStackAllocExpression(stackAllocExpression, data); + return true; + } } } diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index dfafb45bf..c28dbc8df 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -528,13 +528,13 @@ namespace ICSharpCode.Decompiler.ILAst public class ILFixedStatement : ILNode { - public ILExpression Initializer; + public List Initializers = new List(); public ILBlock BodyBlock; public override IEnumerable GetChildren() { - if (this.Initializer != null) - yield return this.Initializer; + foreach (ILExpression initializer in this.Initializers) + yield return initializer; if (this.BodyBlock != null) yield return this.BodyBlock; } @@ -542,8 +542,11 @@ namespace ICSharpCode.Decompiler.ILAst public override void WriteTo(ITextOutput output) { output.Write("fixed ("); - if (this.Initializer != null) - this.Initializer.WriteTo(output); + for (int i = 0; i < this.Initializers.Count; i++) { + if (i > 0) + output.Write(", "); + this.Initializers[i].WriteTo(output); + } output.WriteLine(") {"); output.Indent(); this.BodyBlock.WriteTo(output); diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index b79287bd6..00978db58 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -275,7 +275,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression copiedExpr; if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr) && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 - && CanPerformCopyPropagation(copiedExpr)) + && CanPerformCopyPropagation(copiedExpr, v)) { // un-inline the arguments of the ldArg instruction ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count]; @@ -307,7 +307,7 @@ namespace ICSharpCode.Decompiler.ILAst } } - bool CanPerformCopyPropagation(ILExpression expr) + bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable) { switch (expr.Code) { case ILCode.Ldloca: @@ -318,9 +318,15 @@ namespace ICSharpCode.Decompiler.ILAst // so they can be safely copied. return true; case ILCode.Ldloc: - // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) ILVariable v = (ILVariable)expr.Operand; - return v.IsParameter && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0; + if (v.IsParameter) { + // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) + return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0; + } else { + // Variables are be copied only if both they and the target copy variable are generated, + // and if the variable has only a single assignment + return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1; + } default: return false; } diff --git a/ICSharpCode.Decompiler/ILAst/PatternMatching.cs b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs index c646ca418..747d3780a 100644 --- a/ICSharpCode.Decompiler/ILAst/PatternMatching.cs +++ b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs @@ -20,9 +20,8 @@ namespace ICSharpCode.Decompiler.ILAst public static bool Match(this ILNode node, ILCode code, out T operand) { ILExpression expr = node as ILExpression; - if (expr != null && expr.Prefixes == null && expr.Code == code) { + if (expr != null && expr.Prefixes == null && expr.Code == code && expr.Arguments.Count == 0) { operand = (T)expr.Operand; - Debug.Assert(expr.Arguments.Count == 0); return true; } operand = default(T); @@ -108,5 +107,11 @@ namespace ICSharpCode.Decompiler.ILAst ILVariable v; return node.Match(ILCode.Ldloc, out v) && v.IsParameter && v.OriginalParameter.Index == -1; } + + public static bool MatchLdloc(this ILNode node, ILVariable expectedVar) + { + ILVariable v; + return node.Match(ILCode.Ldloc, out v) && v == expectedVar; + } } } diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs index 8fbd47c4d..d895ba03c 100644 --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -2,8 +2,10 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using ICSharpCode.NRefactory.Utils; using Mono.Cecil; @@ -297,30 +299,46 @@ namespace ICSharpCode.Decompiler.ILAst #region IntroduceFixedStatements void IntroduceFixedStatements(ILBlock block, ref int i) { - // stloc(pinned_Var, conv.u(ldc.i4(0))) ILExpression initValue; ILVariable pinnedVar; - if (!MatchFixedInitializer(block, i, out pinnedVar, out initValue)) + int initEndPos; + if (!MatchFixedInitializer(block, i, out pinnedVar, out initValue, out initEndPos)) return; - // find initialization of v: + + ILFixedStatement fixedStmt = block.Body.ElementAtOrDefault(initEndPos) as ILFixedStatement; + if (fixedStmt != null) { + ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression; + if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) { + // we found a second initializer for the existing fixed statement + fixedStmt.Initializers.Insert(0, initValue); + block.Body.RemoveRange(i, initEndPos - i); + fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1); + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); + return; + } + } + + // find where pinnedVar is reset to 0: int j; - for (j = i + 1; j < block.Body.Count; j++) { + for (j = initEndPos; j < block.Body.Count; j++) { ILVariable v2; ILExpression storedVal; + // stloc(pinned_Var, conv.u(ldc.i4(0))) if (block.Body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) { if (IsNullOrZero(storedVal)) { - // Create fixed statement from i to j - ILFixedStatement stmt = new ILFixedStatement(); - stmt.Initializer = initValue; - stmt.BodyBlock = new ILBlock(block.Body.GetRange(i + 1, j - i - 1)); // from i+1 to j-1 (inclusive) - block.Body.RemoveRange(i + 1, j - i); // from j+1 to i (inclusive) - block.Body[i] = stmt; - if (pinnedVar.Type.IsByReference) - pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); break; } } } + // Create fixed statement from i to j + fixedStmt = new ILFixedStatement(); + fixedStmt.Initializers.Add(initValue); + fixedStmt.BodyBlock = new ILBlock(block.Body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive) + block.Body.RemoveRange(i + 1, Math.Min(j, block.Body.Count - 1) - i); // from i+1 to j (inclusive) + block.Body[i] = fixedStmt; + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); } bool IsNullOrZero(ILExpression expr) @@ -330,11 +348,13 @@ namespace ICSharpCode.Decompiler.ILAst return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull; } - bool MatchFixedInitializer(ILBlock block, int i, out ILVariable pinnedVar, out ILExpression initValue) + bool MatchFixedInitializer(ILBlock block, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos) { - if (block.Body[i].Match(ILCode.Stloc, out pinnedVar, out initValue)) { + if (block.Body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) { initValue = (ILExpression)block.Body[i]; - return pinnedVar.IsPinned; + nextPos = i + 1; + HandleStringFixing(pinnedVar, block.Body, ref nextPos, ref initValue); + return true; } ILCondition ifStmt = block.Body[i] as ILCondition; ILExpression arrayLoadingExpr; @@ -345,23 +365,28 @@ namespace ICSharpCode.Decompiler.ILAst && ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue) && pinnedVar.IsPinned && IsNullOrZero(trueValue)) { - ILVariable stlocVar; - ILExpression falseValue; - if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 - && ifStmt.FalseBlock.Body[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) - { - ILVariable loadedVariable; - if (falseValue.Code == ILCode.Ldelema - && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable - && IsNullOrZero(falseValue.Arguments[1])) + if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) { + ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0]; + ILVariable stlocVar; + ILExpression falseValue; + if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0 + && fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) { - initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); - return true; + ILVariable loadedVariable; + if (falseValue.Code == ILCode.Ldelema + && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable + && IsNullOrZero(falseValue.Arguments[1])) + { + initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); + nextPos = i + 1; + return true; + } } } } } initValue = null; + nextPos = -1; return false; } @@ -392,6 +417,54 @@ namespace ICSharpCode.Decompiler.ILAst else return expr; } + + bool HandleStringFixing(ILVariable pinnedVar, List body, ref int pos, ref ILExpression fixedStmtInitializer) + { + // fixed (stloc(pinnedVar, ldloc(text))) { + // var1 = var2 = conv.i(ldloc(pinnedVar)) + // if (logicnot(logicnot(var1))) { + // var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData)) + // } + // stloc(ptrVar, var2) + // ... + + if (pos >= body.Count) + return false; + + ILVariable var1, var2; + ILExpression varAssignment, ptrInitialization; + if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization))) + return false; + if (!(var1.IsGenerated && var2.IsGenerated)) + return false; + if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U) + ptrInitialization = ptrInitialization.Arguments[0]; + if (!ptrInitialization.MatchLdloc(pinnedVar)) + return false; + + ILCondition ifStmt = body[pos + 1] as ILCondition; + if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0))) + return false; + if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1)) + return false; + ILVariable assignedVar; + ILExpression assignedExpr; + if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add)) + return false; + MethodReference calledMethod; + if (!(assignedExpr.Arguments[0].MatchLdloc(var1) && assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod))) + return false; + if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers")) + return false; + + ILVariable pointerVar; + if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) { + pos += 3; + fixedStmtInitializer.Operand = pointerVar; + return true; + } + return false; + } #endregion } } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 397e5e485..ddeea40cc 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -26,22 +26,54 @@ namespace ICSharpCode.Decompiler.ILAst ta.module = context.CurrentMethod.Module; ta.typeSystem = ta.module.TypeSystem; ta.method = method; - ta.InferTypes(method); - ta.InferRemainingStores(); - // Now that stores were inferred, we can infer the remaining instructions that depended on those stored - // (but which didn't provide an expected type for the store) - // For example, this is necessary to make a switch() over a generated variable work correctly. - ta.InferTypes(method); + ta.CreateDependencyGraph(method); + ta.IdentifySingleLoadVariables(); + ta.RunInference(); + } + + sealed class ExpressionToInfer + { + public ILExpression Expression; + + public bool Done; + + /// + /// Set for assignment expressions that should wait until the variable type is available + /// from the context where the variable is used. + /// + public ILVariable DependsOnSingleLoad; + + /// + /// The list variables that are read by this expression. + /// + public List Dependencies = new List(); + + public override string ToString() + { + if (Done) + return "[Done] " + Expression.ToString(); + else + return Expression.ToString(); + } + } DecompilerContext context; TypeSystem typeSystem; ILBlock method; ModuleDefinition module; - List storedToGeneratedVariables = new List(); - HashSet inferredVariables = new HashSet(); + List allExpressions = new List(); + DefaultDictionary> assignmentExpressions = new DefaultDictionary>(_ => new List()); + HashSet singleLoadVariables = new HashSet(); - void InferTypes(ILNode node) + #region CreateDependencyGraph + /// + /// Creates the "ExpressionToInfer" instances (=nodes in dependency graph) + /// + /// + /// We are using a dependency graph to ensure that expressions are analyzed in the correct order. + /// + void CreateDependencyGraph(ILNode node) { ILCondition cond = node as ILCondition; if (cond != null) { @@ -51,59 +83,142 @@ namespace ICSharpCode.Decompiler.ILAst if (loop != null && loop.Condition != null) { loop.Condition.ExpectedType = typeSystem.Boolean; } + ILTryCatchBlock.CatchBlock catchBlock = node as ILTryCatchBlock.CatchBlock; + if (catchBlock != null && catchBlock.ExceptionVariable != null && catchBlock.ExceptionType != null && catchBlock.ExceptionVariable.Type == null) { + catchBlock.ExceptionVariable.Type = catchBlock.ExceptionType; + } ILExpression expr = node as ILExpression; if (expr != null) { - foreach (ILExpression store in expr.GetSelfAndChildrenRecursive(e => e.Code == ILCode.Stloc)) { - ILVariable v = (ILVariable)store.Operand; - if (v.IsGenerated && v.Type == null && !inferredVariables.Contains(v) && HasSingleLoad(v)) { - // Don't deal with this node or its children yet, - // wait for the expected type to be inferred first. - // This happens with the arg_... variables introduced by the ILAst - we skip inferring the whole statement, - // and first infer the statement that reads from the arg_... variable. - // The ldloc inference will write the expected type to the variable, and the next InferRemainingStores() pass - // will then infer this statement with the correct expected type. - storedToGeneratedVariables.Add(expr); - // However, it is possible that this statement both writes to and reads from the variable (think inlined assignments). - if (expr.GetSelfAndChildrenRecursive(e => e.Code == ILCode.Ldlen && e.Operand == v).Any()) { - // In this case, we analyze it now anyways, and will re-evaluate it later - break; - } else { - return; - } + ExpressionToInfer expressionToInfer = new ExpressionToInfer(); + expressionToInfer.Expression = expr; + allExpressions.Add(expressionToInfer); + FindNestedAssignments(expr, expressionToInfer); + + if (expr.Code == ILCode.Stloc && ((ILVariable)expr.Operand).Type == null) + assignmentExpressions[(ILVariable)expr.Operand].Add(expressionToInfer); + return; + } + foreach (ILNode child in node.GetChildren()) { + CreateDependencyGraph(child); + } + } + + void FindNestedAssignments(ILExpression expr, ExpressionToInfer parent) + { + foreach (ILExpression arg in expr.Arguments) { + if (arg.Code == ILCode.Stloc) { + ExpressionToInfer expressionToInfer = new ExpressionToInfer(); + expressionToInfer.Expression = arg; + allExpressions.Add(expressionToInfer); + FindNestedAssignments(arg, expressionToInfer); + ILVariable v = (ILVariable)arg.Operand; + if (v.Type == null) { + assignmentExpressions[v].Add(expressionToInfer); + // the instruction that consumes the stloc result is handled as if it was reading the variable + parent.Dependencies.Add(v); + } + } else { + ILVariable v; + if (arg.Match(ILCode.Ldloc, out v) && v.Type == null) { + parent.Dependencies.Add(v); } + FindNestedAssignments(arg, parent); } - bool anyArgumentIsMissingType = expr.Arguments.Any(a => a.InferredType == null); - if (expr.InferredType == null || anyArgumentIsMissingType) - expr.InferredType = InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingType); } - foreach (ILNode child in node.GetChildren()) { - InferTypes(child); + } + #endregion + + void IdentifySingleLoadVariables() + { + // Find all variables that are assigned to exactly a single time: + var q = from expr in allExpressions + from v in expr.Dependencies + group expr by v; + foreach (var g in q.ToArray()) { + ILVariable v = g.Key; + if (g.Count() == 1 && g.Single().Expression.GetSelfAndChildrenRecursive().Count(e => e.Operand == v) == 1) { + singleLoadVariables.Add(v); + // Mark the assignments as dependent on the type from the single load: + foreach (var assignment in assignmentExpressions[v]) { + assignment.DependsOnSingleLoad = v; + } + } } } - bool HasSingleLoad(ILVariable v) + void RunInference() { - int loads = 0; - foreach (ILExpression expr in method.GetSelfAndChildrenRecursive()) { - if (expr.Operand == v) { - if (expr.Code == ILCode.Ldloc) - loads++; - else if (expr.Code != ILCode.Stloc) - return false; + int numberOfExpressionsAlreadyInferred = 0; + // Two flags that allow resolving cycles: + bool ignoreSingleLoadDependencies = false; + bool assignVariableTypesBasedOnPartialInformation = false; + while (numberOfExpressionsAlreadyInferred < allExpressions.Count) { + int oldCount = numberOfExpressionsAlreadyInferred; + foreach (ExpressionToInfer expr in allExpressions) { + if (!expr.Done && expr.Dependencies.TrueForAll(v => v.Type != null || singleLoadVariables.Contains(v)) + && (expr.DependsOnSingleLoad == null || expr.DependsOnSingleLoad.Type != null || ignoreSingleLoadDependencies)) + { + RunInference(expr.Expression); + expr.Done = true; + numberOfExpressionsAlreadyInferred++; + } + } + if (numberOfExpressionsAlreadyInferred == oldCount) { + if (ignoreSingleLoadDependencies) { + if (assignVariableTypesBasedOnPartialInformation) + throw new InvalidOperationException("Could not infer any expression"); + else + assignVariableTypesBasedOnPartialInformation = true; + } else { + // We have a cyclic dependency; we'll try if we can resolve it by ignoring single-load dependencies. + // This can happen if the variable was not actually assigned an expected type by the single-load instruction. + ignoreSingleLoadDependencies = true; + continue; + } + } else { + assignVariableTypesBasedOnPartialInformation = false; + ignoreSingleLoadDependencies = false; + } + // Now infer types for variables: + foreach (var pair in assignmentExpressions) { + ILVariable v = pair.Key; + if (v.Type == null && (assignVariableTypesBasedOnPartialInformation ? pair.Value.Any(e => e.Done) : pair.Value.All(e => e.Done))) { + TypeReference inferredType = null; + foreach (ExpressionToInfer expr in pair.Value) { + Debug.Assert(expr.Expression.Code == ILCode.Stloc); + ILExpression assignedValue = expr.Expression.Arguments.Single(); + if (assignedValue.InferredType != null) { + if (inferredType == null) { + inferredType = assignedValue.InferredType; + } else { + // pick the common base type + inferredType = TypeWithMoreInformation(inferredType, assignedValue.InferredType); + } + } + } + if (inferredType == null) + inferredType = typeSystem.Object; + v.Type = inferredType; + // Assign inferred type to all the assignments (in case they used different inferred types): + foreach (ExpressionToInfer expr in pair.Value) { + expr.Expression.InferredType = inferredType; + // re-infer if the expected type has changed + InferTypeForExpression(expr.Expression.Arguments.Single(), inferredType); + } + } } } - return loads == 1; } - void InferRemainingStores() + void RunInference(ILExpression expr) { - while (storedToGeneratedVariables.Count > 0) { - List stored = storedToGeneratedVariables; - storedToGeneratedVariables = new List(); - foreach (ILExpression expr in stored) - InferTypes(expr); - if (!(storedToGeneratedVariables.Count < stored.Count)) - throw new InvalidOperationException("Infinite loop in type analysis detected."); + bool anyArgumentIsMissingExpectedType = expr.Arguments.Any(a => a.ExpectedType == null); + if (expr.InferredType == null || anyArgumentIsMissingExpectedType) + InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingExpectedType); + foreach (var arg in expr.Arguments) { + if (arg.Code != ILCode.Stloc) { + RunInference(arg); + } } } @@ -118,7 +233,8 @@ namespace ICSharpCode.Decompiler.ILAst { if (expectedType != null && expr.ExpectedType != expectedType) { expr.ExpectedType = expectedType; - forceInferChildren = true; + if (expr.Code != ILCode.Stloc) // stloc is special case and never gets re-evaluated + forceInferChildren = true; } if (forceInferChildren || expr.InferredType == null) expr.InferredType = DoInferTypeForExpression(expr, expectedType, forceInferChildren); @@ -159,22 +275,17 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Stloc: { ILVariable v = (ILVariable)expr.Operand; - if (forceInferChildren || v.Type == null) { - TypeReference t = InferTypeForExpression(expr.Arguments.Single(), ((ILVariable)expr.Operand).Type); - if (v.Type == null) - v.Type = t; + if (forceInferChildren) { + // do not use 'expectedType' in here! + InferTypeForExpression(expr.Arguments.Single(), v.Type); } return v.Type; } case ILCode.Ldloc: { ILVariable v = (ILVariable)expr.Operand; - if (v.Type == null) { + if (v.Type == null && singleLoadVariables.Contains(v)) { v.Type = expectedType; - // Mark the variable as inferred. This is necessary because expectedType might be null - // (e.g. the only use of an arg_*-Variable is a pop statement), - // so we can't tell from v.Type whether it was already inferred. - inferredVariables.Add(v); } return v.Type; } @@ -197,7 +308,7 @@ namespace ICSharpCode.Decompiler.ILAst else InferTypeForExpression(expr.Arguments[i], method.DeclaringType); } else { - InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1: i].ParameterType, method)); + InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1 : i].ParameterType, method)); } } } @@ -292,8 +403,9 @@ namespace ICSharpCode.Decompiler.ILAst if (elementType != null) { // An integer can be stored in any other integer of the same size. int infoAmount = GetInformationAmount(elementType); - if (infoAmount == 1) infoAmount = 8; - if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null) + if (infoAmount == 1 && GetInformationAmount(operandType) == 8) + operandType = elementType; + else if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null) operandType = elementType; } if (forceInferChildren) { @@ -325,20 +437,26 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Neg: return InferTypeForExpression(expr.Arguments.Single(), expectedType); case ILCode.Add: + return InferArgumentsInAddition(expr, null, expectedType); case ILCode.Sub: + return InferArgumentsInSubtraction(expr, null, expectedType); case ILCode.Mul: case ILCode.Or: case ILCode.And: case ILCode.Xor: return InferArgumentsInBinaryOperator(expr, null, expectedType); case ILCode.Add_Ovf: + return InferArgumentsInAddition(expr, true, expectedType); case ILCode.Sub_Ovf: + return InferArgumentsInSubtraction(expr, true, expectedType); case ILCode.Mul_Ovf: case ILCode.Div: case ILCode.Rem: return InferArgumentsInBinaryOperator(expr, true, expectedType); case ILCode.Add_Ovf_Un: + return InferArgumentsInAddition(expr, false, expectedType); case ILCode.Sub_Ovf_Un: + return InferArgumentsInSubtraction(expr, false, expectedType); case ILCode.Mul_Ovf_Un: case ILCode.Div_Un: case ILCode.Rem_Un: @@ -481,11 +599,11 @@ namespace ICSharpCode.Decompiler.ILAst case ILCode.Conv_I: case ILCode.Conv_Ovf_I: case ILCode.Conv_Ovf_I_Un: - return HandleConversion(nativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr); + return HandleConversion(NativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr); case ILCode.Conv_U: case ILCode.Conv_Ovf_U: case ILCode.Conv_Ovf_U_Un: - return HandleConversion(nativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr); + return HandleConversion(NativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr); case ILCode.Conv_R4: return typeSystem.Single; case ILCode.Conv_R8: @@ -559,17 +677,17 @@ namespace ICSharpCode.Decompiler.ILAst TypeReference HandleConversion(int targetBitSize, bool targetSigned, ILExpression arg, TypeReference expectedType, TypeReference targetType) { - if (targetBitSize >= nativeInt && expectedType is PointerType) { + if (targetBitSize >= NativeInt && expectedType is PointerType) { InferTypeForExpression(arg, expectedType); return expectedType; } TypeReference argType = InferTypeForExpression(arg, null); - if (targetBitSize >= nativeInt && argType is ByReferenceType) { + if (targetBitSize >= NativeInt && argType is ByReferenceType) { // conv instructions on managed references mean that the GC should stop tracking them, so they become pointers: PointerType ptrType = new PointerType(((ByReferenceType)argType).ElementType); InferTypeForExpression(arg, ptrType); return ptrType; - } else if (targetBitSize >= nativeInt && argType is PointerType) { + } else if (targetBitSize >= NativeInt && argType is PointerType) { return argType; } return (GetInformationAmount(expectedType) == targetBitSize && IsSigned(expectedType) == targetSigned) ? expectedType : targetType; @@ -679,19 +797,82 @@ namespace ICSharpCode.Decompiler.ILAst } } + TypeReference InferArgumentsInAddition(ILExpression expr, bool? isSigned, TypeReference expectedType) + { + ILExpression left = expr.Arguments[0]; + ILExpression right = expr.Arguments[1]; + TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); + if (leftPreferred is PointerType) { + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, typeSystem.IntPtr); + return leftPreferred; + } else { + TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType); + if (rightPreferred is PointerType) { + InferTypeForExpression(left, typeSystem.IntPtr); + right.InferredType = right.ExpectedType = rightPreferred; + return rightPreferred; + } else if (leftPreferred == rightPreferred) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else if (rightPreferred == DoInferTypeForExpression(left, rightPreferred)) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred; + } else if (leftPreferred == DoInferTypeForExpression(right, leftPreferred)) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else { + left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred); + left.InferredType = DoInferTypeForExpression(left, left.ExpectedType); + right.InferredType = DoInferTypeForExpression(right, right.ExpectedType); + return left.ExpectedType; + } + } + } + + TypeReference InferArgumentsInSubtraction(ILExpression expr, bool? isSigned, TypeReference expectedType) + { + ILExpression left = expr.Arguments[0]; + ILExpression right = expr.Arguments[1]; + TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType); + if (leftPreferred is PointerType) { + left.InferredType = left.ExpectedType = leftPreferred; + InferTypeForExpression(right, typeSystem.IntPtr); + return leftPreferred; + } else { + TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType); + if (leftPreferred == rightPreferred) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else if (rightPreferred == DoInferTypeForExpression(left, rightPreferred)) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred; + } else if (leftPreferred == DoInferTypeForExpression(right, leftPreferred)) { + return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred; + } else { + left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred); + left.InferredType = DoInferTypeForExpression(left, left.ExpectedType); + right.InferredType = DoInferTypeForExpression(right, right.ExpectedType); + return left.ExpectedType; + } + } + } + TypeReference TypeWithMoreInformation(TypeReference leftPreferred, TypeReference rightPreferred) { int left = GetInformationAmount(leftPreferred); int right = GetInformationAmount(rightPreferred); - if (left < right) + if (left < right) { return rightPreferred; - else + } else if (left > right) { + return leftPreferred; + } else { + // TODO return leftPreferred; + } } - const int nativeInt = 33; // treat native int as between int32 and int64 + /// + /// Information amount used for IntPtr. + /// + public const int NativeInt = 33; // treat native int as between int32 and int64 - static int GetInformationAmount(TypeReference type) + public static int GetInformationAmount(TypeReference type) { if (type == null) return 0; @@ -725,7 +906,7 @@ namespace ICSharpCode.Decompiler.ILAst return 64; case MetadataType.IntPtr: case MetadataType.UIntPtr: - return nativeInt; + return NativeInt; default: return 100; // we consider structs/objects to have more information than any primitives } diff --git a/ICSharpCode.Decompiler/Tests/UnsafeCode.cs b/ICSharpCode.Decompiler/Tests/UnsafeCode.cs index 9fc546161..03b8b6ab5 100644 --- a/ICSharpCode.Decompiler/Tests/UnsafeCode.cs +++ b/ICSharpCode.Decompiler/Tests/UnsafeCode.cs @@ -56,4 +56,22 @@ public class UnsafeCode { return d->ToString(); } + + public unsafe void FixMultipleStrings(string text) + { + fixed (char* c = text, d = Environment.UserName, e = text) { + *c = 'c'; + *d = 'd'; + *e = 'e'; + } + } + + public unsafe string StackAlloc(int count) + { + char* a = stackalloc char[count]; + for (int i = 0; i < count; i++) { + a[i] = (char)i; + } + return PointerReferenceExpression((double*)a); + } } diff --git a/ILSpy/Commands.cs b/ILSpy/Commands.cs index 462fa1602..da2a1a8de 100644 --- a/ILSpy/Commands.cs +++ b/ILSpy/Commands.cs @@ -56,19 +56,9 @@ namespace ICSharpCode.ILSpy } [ExportMainMenuCommand(Menu = "_File", Header = "_Save Code...", MenuIcon = "Images/SaveFile.png", MenuCategory = "Save", MenuOrder = 0)] - sealed class SaveCommand : SimpleCommand + sealed class SaveCommand : CommandWrapper { - public override void Execute(object parameter) - { - MainWindow mainWindow = MainWindow.Instance; - if (mainWindow.SelectedNodes.Count() == 1) { - if (mainWindow.SelectedNodes.Single().Save(mainWindow.TextView)) - return; - } - mainWindow.TextView.SaveToDisk(mainWindow.CurrentLanguage, - mainWindow.SelectedNodes, - new DecompilationOptions() { FullDecompilation = true }); - } + public SaveCommand() : base(ApplicationCommands.Save) {} } class CommandWrapper : ICommand diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 2949d12f2..d90f4d4cd 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -22,6 +22,9 @@ +