diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs index a522a67b0..50eaa15b2 100644 --- a/ICSharpCode.Decompiler/CSharp/Annotations.cs +++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs @@ -89,5 +89,10 @@ namespace ICSharpCode.Decompiler.CSharp var rr = node.Annotation(); return rr != null ? rr.GetSymbol() : null; } + + public static ResolveResult GetResolveResult(this AstNode node) + { + return node.Annotation() ?? ErrorResolveResult.UnknownError; + } } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 4022e1f14..fe8df8578 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -106,10 +106,10 @@ namespace ICSharpCode.Decompiler.CSharp return expr.WithRR(new ResolveResult(variable.Type)); } - ExpressionWithResolveResult ConvertField(IField field) + ExpressionWithResolveResult ConvertField(IField field, ILInstruction target = null) { - Expression expr = new IdentifierExpression(field.Name); - return expr.WithRR(new ResolveResult(field.ReturnType)); + return new MemberReferenceExpression(TranslateTarget(field, target, true), field.Name) + .WithRR(new ResolveResult(field.ReturnType)); } TranslatedExpression IsType(IsInst inst) @@ -132,6 +132,19 @@ namespace ICSharpCode.Decompiler.CSharp { return HandleCallInstruction(inst); } + + protected internal override TranslatedExpression VisitNewArr(NewArr inst) + { + var arg = Translate(inst.Size); + return new ArrayCreateExpression { + Type = ConvertType(inst.Type), + Arguments = { + arg + } + } + .WithILInstruction(inst) + .WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, inst.Type, 1), new [] { arg.ResolveResult }, new ResolveResult[0])); + } protected internal override TranslatedExpression VisitLdcI4(LdcI4 inst) { @@ -384,26 +397,52 @@ namespace ICSharpCode.Decompiler.CSharp { return HandleCallInstruction(inst); } + + static bool IsDelegateConstruction(CallInstruction inst) + { + return inst.Arguments.Count == 2 + && (inst.Arguments[1].OpCode == OpCode.LdFtn + || inst.Arguments[1].OpCode == OpCode.LdVirtFtn) + && inst.Method.DeclaringType.Kind == TypeKind.Delegate; + } + + TranslatedExpression HandleDelegateConstruction(CallInstruction inst) + { + var func = (LdFtn)inst.Arguments[1]; + var target = TranslateTarget(func.Method, inst.Arguments[0], func.OpCode == OpCode.LdFtn); + return new MemberReferenceExpression(target, func.Method.Name) + .WithILInstruction(inst) + .WithRR(new MemberResolveResult(target.ResolveResult, func.Method)); + } + + TranslatedExpression TranslateTarget(IMember member, ILInstruction target, bool nonVirtualInvocation) + { + if (!member.IsStatic) { + if (nonVirtualInvocation && target.MatchLdThis() && member.DeclaringTypeDefinition != resolver.CurrentTypeDefinition) { + return new BaseReferenceExpression() + .WithILInstruction(target) + .WithRR(new ThisResolveResult(member.DeclaringType, nonVirtualInvocation)); + } else { + return Translate(target).ConvertTo(member.DeclaringType, this); + } + } else { + return new TypeReferenceExpression(ConvertType(member.DeclaringType)) + .WithoutILInstruction() + .WithRR(new TypeResolveResult(member.DeclaringType)); + } + } TranslatedExpression HandleCallInstruction(CallInstruction inst) { // Used for Call, CallVirt and NewObj TranslatedExpression target; if (inst.OpCode == OpCode.NewObj) { - target = default(TranslatedExpression); // no target - } else if (!inst.Method.IsStatic) { - var argInstruction = inst.Arguments[0]; - if (inst.OpCode == OpCode.Call && argInstruction.MatchLdThis()) { - target = new BaseReferenceExpression() - .WithILInstruction(argInstruction) - .WithRR(new ThisResolveResult(inst.Method.DeclaringType, causesNonVirtualInvocation: true)); - } else { - target = Translate(argInstruction).ConvertTo(inst.Method.DeclaringType, this); + if (IsDelegateConstruction(inst)) { + return HandleDelegateConstruction(inst); } + target = default(TranslatedExpression); // no target } else { - target = new TypeReferenceExpression(ConvertType(inst.Method.DeclaringType)) - .WithoutILInstruction() - .WithRR(new TypeResolveResult(inst.Method.DeclaringType)); + target = TranslateTarget(inst.Method, inst.Arguments.FirstOrDefault(), inst.OpCode == OpCode.Call); } var arguments = inst.Arguments.SelectArray(Translate); @@ -414,6 +453,10 @@ namespace ICSharpCode.Decompiler.CSharp for (int i = firstParamIndex; i < arguments.Length; i++) { var parameter = inst.Method.Parameters[i - firstParamIndex]; arguments[i] = arguments[i].ConvertTo(parameter.Type, this); + + if (parameter.IsOut && arguments[i].Expression is DirectionExpression) { + ((DirectionExpression)arguments[i].Expression).FieldDirection = FieldDirection.Out; + } } var argumentResolveResults = arguments.Skip(firstParamIndex).Select(arg => arg.ResolveResult).ToList(); @@ -535,12 +578,12 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitLdFld(LdFld inst) { - return ConvertField(inst.Field).WithILInstruction(inst); + return ConvertField(inst.Field, inst.Target).WithILInstruction(inst); } protected internal override TranslatedExpression VisitStFld(StFld inst) { - return Assignment(ConvertField(inst.Field).WithoutILInstruction(), Translate(inst.Value)).WithILInstruction(inst); + return Assignment(ConvertField(inst.Field, inst.Target).WithoutILInstruction(), Translate(inst.Value)).WithILInstruction(inst); } protected internal override TranslatedExpression VisitLdsFld(LdsFld inst) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 0a951d144..9755b9c7c 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -17,14 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System.Diagnostics; +using ICSharpCode.NRefactory.Utils; using ICSharpCode.Decompiler.IL; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.TypeSystem; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ICSharpCode.Decompiler.CSharp { @@ -73,6 +72,30 @@ namespace ICSharpCode.Decompiler.CSharp var falseStatement = inst.FalseInst.OpCode == OpCode.Nop ? null : Convert(inst.FalseInst); return new IfElseStatement(condition, trueStatement, falseStatement); } + + CaseLabel CreateTypedCaseLabel(long i, IType type) + { + Expression value; + if (type.IsKnownType(KnownTypeCode.Boolean)) { + value = new PrimitiveExpression(i != 0); + } else { + value = new PrimitiveExpression(CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, true)); + } + return new CaseLabel(value); + } + + protected internal override Statement VisitSwitchInstruction(SwitchInstruction inst) + { + var value = exprBuilder.Translate(inst.Value); + var stmt = new SwitchStatement() { Expression = value }; + foreach (var section in inst.Sections) { + var astSection = new ICSharpCode.NRefactory.CSharp.SwitchSection(); + astSection.CaseLabels.AddRange(section.Labels.Range().Select(i => CreateTypedCaseLabel(i, value.Type))); + astSection.Statements.Add(Convert(section.Body)); + stmt.SwitchSections.Add(astSection); + } + return stmt; + } /// Target block that a 'continue;' statement would jump to Block continueTarget; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index ee53dfd3a..01628408a 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using ICSharpCode.NRefactory.PatternMatching; +using ICSharpCode.NRefactory.TypeSystem; using Mono.Cecil; using Ast = ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp; @@ -40,23 +41,25 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms }, MemberName = "TypeHandle" }; - + + TransformContext context; + public override void VisitInvocationExpression(InvocationExpression invocationExpression) { base.VisitInvocationExpression(invocationExpression); ProcessInvocationExpression(invocationExpression); } - internal static void ProcessInvocationExpression(InvocationExpression invocationExpression) + void ProcessInvocationExpression(InvocationExpression invocationExpression) { - MethodReference methodRef = invocationExpression.Annotation(); - if (methodRef == null) + var method = invocationExpression.GetSymbol() as IMethod; + if (method == null) return; var arguments = invocationExpression.Arguments.ToArray(); // Reduce "String.Concat(a, b)" to "a + b" - if (methodRef.Name == "Concat" && methodRef.DeclaringType.FullName == "System.String" && arguments.Length >= 2) - { + if (method.Name == "Concat" && method.DeclaringType.FullName == "System.String" && arguments.Length >= 2 + && arguments.All(a => a.GetResolveResult().Type.IsKnownType(KnownTypeCode.String))) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression Expression expr = arguments[0]; for (int i = 1; i < arguments.Length; i++) { @@ -66,7 +69,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return; } - switch (methodRef.FullName) { + switch (method.FullName) { case "System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)": if (arguments.Length == 1) { if (typeHandleOnTypeOfPattern.IsMatch(arguments[0])) { @@ -94,7 +97,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms FieldReference field = oldArg.Annotation(); if (field != null) { AstType declaringType = ((TypeOfExpression)mre2.Target).Type.Detach(); - oldArg.ReplaceWith(declaringType.Member(field.Name).WithAnnotation(field)); + oldArg.ReplaceWith(declaringType.Member(field.Name).CopyAnnotationsFrom(oldArg)); invocationExpression.ReplaceWith(mre1.Target); return; } @@ -104,35 +107,35 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms break; } - BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(methodRef.Name); + BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name); if (bop != null && arguments.Length == 2) { invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression invocationExpression.ReplaceWith( - new BinaryOperatorExpression(arguments[0], bop.Value, arguments[1]).WithAnnotation(methodRef) + new BinaryOperatorExpression(arguments[0], bop.Value, arguments[1]).CopyAnnotationsFrom(invocationExpression) ); return; } - UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(methodRef.Name); + UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(method.Name); if (uop != null && arguments.Length == 1) { arguments[0].Remove(); // detach argument invocationExpression.ReplaceWith( - new UnaryOperatorExpression(uop.Value, arguments[0]).WithAnnotation(methodRef) + new UnaryOperatorExpression(uop.Value, arguments[0]).CopyAnnotationsFrom(invocationExpression) ); return; } - if (methodRef.Name == "op_Explicit" && arguments.Length == 1) { + if (method.Name == "op_Explicit" && arguments.Length == 1) { arguments[0].Remove(); // detach argument invocationExpression.ReplaceWith( - arguments[0].CastTo(ConvertType(methodRef.ReturnType, methodRef.MethodReturnType)) - .WithAnnotation(methodRef) + arguments[0].CastTo(context.TypeSystemAstBuilder.ConvertType(method.ReturnType)) + .CopyAnnotationsFrom(invocationExpression) ); return; } - if (methodRef.Name == "op_Implicit" && arguments.Length == 1) { + if (method.Name == "op_Implicit" && arguments.Length == 1) { invocationExpression.ReplaceWith(arguments[0]); return; } - if (methodRef.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == Roles.Condition) { + if (method.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == Roles.Condition) { invocationExpression.ReplaceWith(arguments[0]); return; } @@ -322,11 +325,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms new TypePattern(typeof(MethodInfo)), new TypePattern(typeof(ConstructorInfo)) }); - - static AstType ConvertType(TypeReference parameterType, object ctx) - { - return new SimpleType(parameterType.Name); - } public override void VisitCastExpression(CastExpression castExpression) { @@ -334,19 +332,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms // Handle methodof Match m = getMethodOrConstructorFromHandlePattern.Match(castExpression); if (m.Success) { - MethodReference method = m.Get("method").Single().Annotation(); - if (m.Has("declaringType")) { + IMethod method = m.Get("method").Single().GetSymbol() as IMethod; + if (m.Has("declaringType") && method != null) { Expression newNode = m.Get("declaringType").Single().Detach().Member(method.Name); - newNode = newNode.Invoke(method.Parameters.Select(p => new TypeReferenceExpression(ConvertType(p.ParameterType, p)))); - newNode.AddAnnotation(method); + newNode = newNode.Invoke(method.Parameters.Select(p => new TypeReferenceExpression(context.TypeSystemAstBuilder.ConvertType(p.Type)))); m.Get("method").Single().ReplaceWith(newNode); } - castExpression.ReplaceWith(m.Get("ldtokenNode").Single()); + castExpression.ReplaceWith(m.Get("ldtokenNode").Single().CopyAnnotationsFrom(castExpression)); } } void IAstTransform.Run(AstNode node, TransformContext context) { + this.context = context; node.AcceptVisitor(this); } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 10bf9a44c..c43a6b503 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -100,6 +100,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index f000785d6..edcadc30e 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL /// void Warn(string message) { - Debug.Fail(message); + //Debug.Fail(message); } void ReadInstructions(Dictionary> outputStacks, CancellationToken cancellationToken) @@ -583,7 +583,7 @@ namespace ICSharpCode.Decompiler.IL case ILOpCode.Sub_Ovf_Un: return BinaryNumeric(OpCode.Sub, true, Sign.Unsigned); case ILOpCode.Switch: - throw new NotImplementedException(); + return DecodeSwitch(); case ILOpCode.Xor: return BinaryNumeric(OpCode.BitXor); case ILOpCode.Box: @@ -656,7 +656,7 @@ namespace ICSharpCode.Decompiler.IL case ILOpCode.Mkrefany: throw new NotImplementedException(); case ILOpCode.Newarr: - throw new NotImplementedException(); + return new NewArr(ReadAndDecodeTypeReference(), Pop()); case ILOpCode.Refanytype: throw new NotImplementedException(); case ILOpCode.Refanyval: @@ -666,15 +666,23 @@ namespace ICSharpCode.Decompiler.IL case ILOpCode.Sizeof: return new SizeOf(ReadAndDecodeTypeReference()); case ILOpCode.Stelem: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: ReadAndDecodeTypeReference()); case ILOpCode.Stelem_I1: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.SByte)); case ILOpCode.Stelem_I2: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Int16)); case ILOpCode.Stelem_I4: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Int32)); case ILOpCode.Stelem_I8: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Int64)); case ILOpCode.Stelem_R4: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Single)); case ILOpCode.Stelem_R8: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Double)); case ILOpCode.Stelem_I: + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.IntPtr)); case ILOpCode.Stelem_Ref: - throw new NotImplementedException(); + return StElem(value: Pop(), index: Pop(), array: Pop(), type: compilation.FindType(KnownTypeCode.Object)); case ILOpCode.Stobj: return new Void(new StObj(value: Pop(), target: Pop(), type: ReadAndDecodeTypeReference())); case ILOpCode.Throw: @@ -747,6 +755,11 @@ namespace ICSharpCode.Decompiler.IL { return new LdObj(new LdElema(array, index, type), type); } + + private ILInstruction StElem(ILInstruction array, ILInstruction index, ILInstruction value, IType type) + { + return new Void(new StObj(new LdElema(array, index, type), value, type)); + } private ILInstruction DecodeConstrainedCall() { @@ -897,6 +910,24 @@ namespace ICSharpCode.Decompiler.IL branchStackDict[targetILOffset] = stack.ToImmutableArray(); } } + + ILInstruction DecodeSwitch() + { + uint length = reader.ReadUInt32(); + int baseOffset = 4 * (int)length + reader.Position; + var instr = new SwitchInstruction(Pop()); + + for (uint i = 0; i < length; i++) { + var section = new SwitchSection(); + section.Labels = new LongSet(i); + int target = baseOffset + reader.ReadInt32(); + MarkBranchTarget(target); + section.Body = new Branch(target); + instr.Sections.Add(section); + } + + return instr; + } ILInstruction BinaryNumeric(OpCode opCode, bool checkForOverflow = false, Sign sign = Sign.None) { diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index c21af53c3..b09ab5cf4 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -70,6 +70,10 @@ namespace ICSharpCode.Decompiler.IL Leave, /// If statement / conditional expression. if (condition) trueExpr else falseExpr IfInstruction, + /// Switch statement + SwitchInstruction, + /// Switch section within a switch statement + SwitchSection, /// Try-catch statement. TryCatch, /// Catch handler within a try-catch statement. @@ -158,6 +162,8 @@ namespace ICSharpCode.Decompiler.IL UnboxAny, /// Creates an object instance and calls the constructor. NewObj, + /// Creates an array instance. + NewArr, /// Initializes the value at an address. InitObj, /// Returns the default value for a type. @@ -777,6 +783,71 @@ namespace ICSharpCode.Decompiler.IL } } + /// Switch statement + public sealed partial class SwitchInstruction : ILInstruction + { + public override StackType ResultType { get { return StackType.Void; } } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitSwitchInstruction(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitSwitchInstruction(this); + } + } + + /// Switch section within a switch statement + public sealed partial class SwitchSection : ILInstruction + { + ILInstruction body; + public ILInstruction Body { + get { return this.body; } + set { + ValidateChild(value); + SetChildInstruction(ref this.body, value, 0); + } + } + protected sealed override int GetChildCount() + { + return 1; + } + protected sealed override ILInstruction GetChild(int index) + { + switch (index) { + case 0: + return this.body; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override void SetChild(int index, ILInstruction value) + { + switch (index) { + case 0: + this.Body = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + public sealed override ILInstruction Clone() + { + var clone = (SwitchSection)ShallowClone(); + clone.Body = this.body.Clone(); + return clone; + } + public override StackType ResultType { get { return StackType.Void; } } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitSwitchSection(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitSwitchSection(this); + } + } + /// Try-catch statement. public sealed partial class TryCatch : TryInstruction { @@ -2320,6 +2391,87 @@ namespace ICSharpCode.Decompiler.IL } } + /// Creates an array instance. + public sealed partial class NewArr : ILInstruction + { + public NewArr(IType type, ILInstruction size) : base(OpCode.NewArr) + { + this.type = type; + this.Size = size; + } + readonly IType type; + /// Returns the type operand. + public IType Type { get { return type; } } + ILInstruction size; + public ILInstruction Size { + get { return this.size; } + set { + ValidateArgument(value); + SetChildInstruction(ref this.size, value, 0); + } + } + protected sealed override int GetChildCount() + { + return 1; + } + protected sealed override ILInstruction GetChild(int index) + { + switch (index) { + case 0: + return this.size; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override void SetChild(int index, ILInstruction value) + { + switch (index) { + case 0: + this.Size = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + public sealed override ILInstruction Clone() + { + var clone = (NewArr)ShallowClone(); + clone.Size = this.size.Clone(); + return clone; + } + internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context) + { + this.Size = this.size.Inline(flagsBefore, context); + return this; + } + internal sealed override void TransformStackIntoVariables(TransformStackIntoVariablesState state) + { + Size.TransformStackIntoVariables(state); + } + public override StackType ResultType { get { return StackType.O; } } + protected override InstructionFlags ComputeFlags() + { + return size.Flags | InstructionFlags.MayThrow; + } + public override void WriteTo(ITextOutput output) + { + output.Write(OpCode); + output.Write(' '); + Disassembler.DisassemblerHelpers.WriteOperand(output, type); + output.Write('('); + this.size.WriteTo(output); + output.Write(')'); + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitNewArr(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitNewArr(this); + } + } + /// Initializes the value at an address. public sealed partial class InitObj : UnaryInstruction { @@ -2718,6 +2870,14 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitSwitchInstruction(SwitchInstruction inst) + { + Default(inst); + } + protected internal virtual void VisitSwitchSection(SwitchSection inst) + { + Default(inst); + } protected internal virtual void VisitTryCatch(TryCatch inst) { Default(inst); @@ -2894,6 +3054,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitNewArr(NewArr inst) + { + Default(inst); + } protected internal virtual void VisitInitObj(InitObj inst) { Default(inst); @@ -3016,6 +3180,14 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitSwitchInstruction(SwitchInstruction inst) + { + return Default(inst); + } + protected internal virtual T VisitSwitchSection(SwitchSection inst) + { + return Default(inst); + } protected internal virtual T VisitTryCatch(TryCatch inst) { return Default(inst); @@ -3192,6 +3364,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitNewArr(NewArr inst) + { + return Default(inst); + } protected internal virtual T VisitInitObj(InitObj inst) { return Default(inst); @@ -3298,6 +3474,8 @@ namespace ICSharpCode.Decompiler.IL "br", "leave", "if", + "switch", + "switch.section", "try.catch", "try.catch.handler", "try.finally", @@ -3342,6 +3520,7 @@ namespace ICSharpCode.Decompiler.IL "unbox", "unbox.any", "newobj", + "newarr", "initobj", "default.value", "throw", diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 054b50d2e..da19f1a82 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -70,6 +70,11 @@ new ChildInfo("trueInst"), new ChildInfo("falseInst"), }), CustomConstructor, CustomComputeFlags, CustomWriteTo), + new OpCode("switch", "Switch statement", + CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void")), + new OpCode("switch.section", "Switch section within a switch statement", + CustomClassName("SwitchSection"), CustomChildren(new [] { new ChildInfo("body") }), + CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void")), new OpCode("try.catch", "Try-catch statement.", BaseClass("TryInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo), new OpCode("try.catch.handler", "Catch handler within a try-catch statement.", @@ -170,6 +175,8 @@ Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("type.GetStackType()")), new OpCode("newobj", "Creates an object instance and calls the constructor.", CustomClassName("NewObj"), Call, ResultType("O")), + new OpCode("newarr", "Creates an array instance.", + CustomClassName("NewArr"), HasTypeOperand, CustomArguments("size"), MayThrow, ResultType("O")), new OpCode("initobj", "Initializes the value at an address.", CustomClassName("InitObj"), Unary, HasTypeOperand, VoidResult), new OpCode("default.value", "Returns the default value for a type.", @@ -185,7 +192,7 @@ CustomClassName("LdLen"), CustomArguments("array"), MayThrow, ResultType("I")), new OpCode("ldelema", "Load address of array element.", CustomClassName("LdElema"), CustomArguments("array", "index"), HasTypeOperand, - MayThrow, ResultType("Ref"), SupportsReadonlyPrefix), + MayThrow, ResultType("Ref"), SupportsReadonlyPrefix) }; #> using System; diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs new file mode 100644 index 000000000..7a5c6da94 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -0,0 +1,158 @@ +// Copyright (c) 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. + +using System; +using System.Linq; + +namespace ICSharpCode.Decompiler.IL +{ + /// + /// Description of SwitchInstruction. + /// + partial class SwitchInstruction + { + public SwitchInstruction(ILInstruction value) + : base(OpCode.SwitchInstruction) + { + this.Value = value; + this.Sections = new InstructionCollection(this, 1); + } + + ILInstruction value; + public ILInstruction Value { + get { return this.value; } + set { + ValidateChild(value); + SetChildInstruction(ref this.value, value, 0); + } + } + + public readonly InstructionCollection Sections; + + internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context) + { + Value = value.Inline(flagsBefore, context); + return this; + } + + protected override InstructionFlags ComputeFlags() + { + var sectionFlags = InstructionFlags.None; + foreach (var section in Sections) { + sectionFlags = IfInstruction.CombineFlags(sectionFlags, section.Flags); + } + return value.Flags | sectionFlags; + } + + public override void WriteTo(ITextOutput output) + { + output.Write("switch ("); + value.WriteTo(output); + output.WriteLine(") {"); + output.Indent(); + foreach (var section in this.Sections) { + section.WriteTo(output); + output.WriteLine(); + } + output.Unindent(); + output.Write('}'); + } + + protected override int GetChildCount() + { + return 1 + Sections.Count; + } + + protected override ILInstruction GetChild(int index) + { + if (index == 0) + return value; + return Sections[index - 1]; + } + + protected override void SetChild(int index, ILInstruction value) + { + if (index == 0) + Value = value; + else + Sections[index - 1] = (SwitchSection)value; + } + + internal override void TransformStackIntoVariables(TransformStackIntoVariablesState state) + { + Value.TransformStackIntoVariables(state); + var stateAfterExpression = state.Variables.Clone(); + for (int i = 0; i < Sections.Count; i++) { + state.Variables = stateAfterExpression.Clone(); + Sections[i] = (SwitchSection)Sections[i].Inline(InstructionFlags.None, state); + Sections[i].TransformStackIntoVariables(state); + if (!Sections[i].HasFlag(InstructionFlags.EndPointUnreachable)) { + state.MergeVariables(state.Variables, stateAfterExpression); + } + } + state.Variables = stateAfterExpression; + } + + public override ILInstruction Clone() + { + var clone = new SwitchInstruction(value.Clone()); + clone.ILRange = this.ILRange; + clone.Value = value.Clone(); + clone.Sections.AddRange(this.Sections.Select(h => (SwitchSection)h.Clone())); + return clone; + } + } + + partial class SwitchSection + { + public SwitchSection() + : base(OpCode.SwitchSection) + { + + } + + public LongSet Labels { get; set; } + + internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context) + { + // Inlining into switch sections would be madness. + // To keep phase-1 execution semantics consistent with inlining, there's a + // phase-1-boundary around every switch section. + return this; + } + + protected override InstructionFlags ComputeFlags() + { + return Block.Phase1Boundary(body.Flags); + } + + public override void WriteTo(ITextOutput output) + { + output.Write("case "); + output.Write(Labels.ToString()); + output.Write(": "); + + body.WriteTo(output); + } + + internal override void TransformStackIntoVariables(TransformStackIntoVariablesState state) + { + body.TransformStackIntoVariables(state); + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs index 2e276ef10..b32d1a37e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs @@ -129,17 +129,17 @@ namespace ICSharpCode.Decompiler.IL } else if (inst.ResultType == StackType.Void && stack.Count > 0) { // For void instructions on non-empty stack, we can create a new inline block (or add to an existing one) // This works even when inst involves Peek. - ILInstruction headInst = stack.Pop(); - Block inlineBlock = headInst as Block; - if (inlineBlock == null || inlineBlock.FinalInstruction.OpCode != OpCode.Pop) { - inlineBlock = new Block { - Instructions = { headInst }, - ILRange = new Interval(headInst.ILRange.Start, headInst.ILRange.Start), - FinalInstruction = new Pop(headInst.ResultType) - }; - } - inlineBlock.Instructions.Add(inst); - inst = inlineBlock; +// ILInstruction headInst = stack.Pop(); +// Block inlineBlock = headInst as Block; +// if (inlineBlock == null || inlineBlock.FinalInstruction.OpCode != OpCode.Pop) { +// inlineBlock = new Block { +// Instructions = { headInst }, +// ILRange = new Interval(headInst.ILRange.Start, headInst.ILRange.Start), +// FinalInstruction = new Pop(headInst.ResultType) +// }; +// } +// inlineBlock.Instructions.Add(inst); +// inst = inlineBlock; } if (inst.HasFlag(InstructionFlags.MayPeek)) { // Prevent instruction from being inlined if it was peeked at. diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 26f2cb620..0ab6646fa 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -107,15 +107,13 @@ + - - - - + \ No newline at end of file diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Switch.cs b/ICSharpCode.Decompiler/Tests/TestCases/Switch.cs new file mode 100644 index 000000000..14907cd8f --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/TestCases/Switch.cs @@ -0,0 +1,101 @@ +// 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 static class Switch +{ + public static void Main() + { + string[] args = { "First case", "Else" }; + TestCase(ShortSwitchOverString, args); + } + + static void TestCase(Func target, params string[] args) + { + foreach (var arg in args) { + Console.WriteLine(target(arg)); + } + } + + public static string ShortSwitchOverString(string text) + { + switch (text) { + case "First case": + return "Text"; + default: + return "Default"; + } + } + + public static string SwitchOverString1(string text) + { + switch (text) { + case "First case": + return "Text1"; + case "Second case": + case "2nd case": + return "Text2"; + case "Third case": + return "Text3"; + case "Fourth case": + return "Text4"; + case "Fifth case": + return "Text5"; + case "Sixth case": + return "Text6"; + case null: + return null; + default: + return "Default"; + } + } + + public static string SwitchOverString2() + { + switch (Environment.UserName) { + case "First case": + return "Text1"; + case "Second case": + return "Text2"; + case "Third case": + return "Text3"; + case "Fourth case": + return "Text4"; + case "Fifth case": + return "Text5"; + case "Sixth case": + return "Text6"; + default: + return "Default"; + } + } + + public static string SwitchOverBool(bool b) + { + switch (b) { + case true: + return bool.TrueString; + case false: + return bool.FalseString; + default: + return null; + } + } +} + diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index d674643d6..85b3d5954 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -52,6 +52,12 @@ namespace ICSharpCode.Decompiler.Tests TestCompileDecompileCompileOutputAll("PropertiesAndEvents.cs"); } + [Test] + public void Switch() + { + TestCompileDecompileCompileOutputAll("Switch.cs"); + } + void TestCompileDecompileCompileOutputAll(string testFileName) { TestCompileDecompileCompileOutput(testFileName, CompilerOptions.None); diff --git a/ICSharpCode.Decompiler/Util/Interval.cs b/ICSharpCode.Decompiler/Util/Interval.cs index 6b9437a76..4666552cd 100644 --- a/ICSharpCode.Decompiler/Util/Interval.cs +++ b/ICSharpCode.Decompiler/Util/Interval.cs @@ -140,12 +140,18 @@ namespace ICSharpCode.Decompiler return !(lhs == rhs); } #endregion + + public IEnumerable Range() + { + for (long i = Start; i < End; i++) + yield return i; + } } /// /// An immutable set of longs, that is implemented as a list of intervals. /// - struct LongSet + public struct LongSet { public readonly ImmutableArray Intervals; @@ -154,6 +160,11 @@ namespace ICSharpCode.Decompiler this.Intervals = intervals; } + public LongSet(long value) + : this(ImmutableArray.Create(new LongInterval(value, unchecked(value + 1)))) + { + } + public bool IsEmpty { get { return Intervals.IsDefaultOrEmpty; } @@ -184,6 +195,11 @@ namespace ICSharpCode.Decompiler return min; } + public IEnumerable Range() + { + return Intervals.SelectMany(i => i.Range()); + } + public override string ToString() { return string.Join(",", Intervals);