diff --git a/ICSharpCode.Decompiler/Ast/Annotations.cs b/ICSharpCode.Decompiler/Ast/Annotations.cs index 257f889c0..7b22dd013 100644 --- a/ICSharpCode.Decompiler/Ast/Annotations.cs +++ b/ICSharpCode.Decompiler/Ast/Annotations.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; using Mono.Cecil; namespace ICSharpCode.Decompiler.Ast @@ -12,4 +16,35 @@ namespace ICSharpCode.Decompiler.Ast this.InferredType = inferredType; } } + + public class LdTokenAnnotation {} + + /// + /// Annotation that is applied to the body expression of an Expression.Lambda() call. + /// + public class ParameterDeclarationAnnotation + { + public readonly List Parameters = new List(); + + public ParameterDeclarationAnnotation(ILExpression expr) + { + Debug.Assert(expr.Code == ILCode.ExpressionTreeParameterDeclarations); + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + ILExpression p = expr.Arguments[i]; + // p looks like this: + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + ILVariable v = (ILVariable)p.Operand; + TypeReference typeRef = (TypeReference)p.Arguments[0].Arguments[0].Arguments[0].Operand; + string name = (string)p.Arguments[0].Arguments[1].Operand; + Parameters.Add(new ParameterDeclaration(AstBuilder.ConvertType(typeRef), name).WithAnnotation(v)); + } + } + } + + /// + /// Annotation that is applied to a LambdaExpression that was produced by an expression tree. + /// + public class ExpressionTreeLambdaAnnotation + { + } } diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index f40d5d154..178b7311d 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -370,9 +370,6 @@ namespace ICSharpCode.Decompiler.Ast // change "new (int[,])[10] to new int[10][,]" ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers); ace.Initializer = new ArrayInitializerExpression(); - var first = ace.AdditionalArraySpecifiers.First(); - first.Remove(); - ace.Arguments.AddRange(Enumerable.Repeat(0, first.Dimensions).Select(i => new EmptyExpression())); } var newArgs = new List(); foreach (var arrayDimension in arrayType.Dimensions.Skip(1).Reverse()) @@ -461,9 +458,9 @@ namespace ICSharpCode.Decompiler.Ast goto case ILCode.Cle; } case ILCode.Cle: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.LessThanOrEqual, arg2); - case ILCode.Cge_Un: + case ILCode.Cge_Un: case ILCode.Cge: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.GreaterThanOrEqual, arg2); - case ILCode.Clt_Un: + case ILCode.Clt_Un: case ILCode.Clt: return new Ast.BinaryOperatorExpression(arg1, BinaryOperatorType.LessThan, arg2); #endregion #region Logical @@ -661,7 +658,20 @@ namespace ICSharpCode.Decompiler.Ast if (operand is Cecil.TypeReference) { return AstBuilder.CreateTypeOfExpression((TypeReference)operand).Member("TypeHandle"); } else { - return InlineAssembly(byteCode, args); + var referencedEntity = new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand)).WithAnnotation(byteCode.Operand); + string loadName; + string handleName; + if (operand is Cecil.FieldReference) { + loadName = "fieldof"; + handleName = "FieldHandle"; + } else if (operand is Cecil.MethodReference) { + loadName = "methodof"; + handleName = "MethodHandle"; + } else { + loadName = "ldtoken"; + handleName = "Handle"; + } + return new IdentifierExpression(loadName).Invoke(referencedEntity).WithAnnotation(new LdTokenAnnotation()).Member(handleName); } case ILCode.Leave: return new GotoStatement() { Label = ((ILLabel)operand).Name }; case ILCode.Localloc: @@ -718,22 +728,7 @@ namespace ICSharpCode.Decompiler.Ast MethodDefinition ctor = ((MethodReference)operand).Resolve(); if (methodDef != null) { AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); - bool allNamesCanBeInferred = true; - for (int i = 0; i < args.Count; i++) { - string inferredName; - if (args[i] is IdentifierExpression) - inferredName = ((IdentifierExpression)args[i]).Identifier; - else if (args[i] is MemberReferenceExpression) - inferredName = ((MemberReferenceExpression)args[i]).MemberName; - else - inferredName = null; - - if (inferredName != ctor.Parameters[i].Name) { - allNamesCanBeInferred = false; - break; - } - } - if (allNamesCanBeInferred) { + if (CanInferAnonymousTypePropertyNamesFromArguments(args, ctor.Parameters)) { atce.Initializers.AddRange(args); } else { for (int i = 0; i < args.Count; i++) { @@ -829,13 +824,34 @@ namespace ICSharpCode.Decompiler.Ast return arg1.WithAnnotation(PushNegation.LiftedOperatorAnnotation); case ILCode.AddressOf: return MakeRef(arg1); + case ILCode.ExpressionTreeParameterDeclarations: + args[args.Count - 1].AddAnnotation(new ParameterDeclarationAnnotation(byteCode)); + return args[args.Count - 1]; case ILCode.NullableOf: - case ILCode.ValueOf: return arg1; + case ILCode.ValueOf: return arg1; default: throw new Exception("Unknown OpCode: " + byteCode.Code); } } + internal static bool CanInferAnonymousTypePropertyNamesFromArguments(IList args, IList parameters) + { + for (int i = 0; i < args.Count; i++) { + string inferredName; + if (args[i] is IdentifierExpression) + inferredName = ((IdentifierExpression)args[i]).Identifier; + else if (args[i] is MemberReferenceExpression) + inferredName = ((MemberReferenceExpression)args[i]).MemberName; + else + inferredName = null; + + if (inferredName != parameters[i].Name) { + return false; + } + } + return true; + } + static readonly AstNode objectInitializerPattern = new AssignmentExpression( new MemberReferenceExpression { Target = new InitializedObjectExpression() diff --git a/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs b/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs new file mode 100644 index 000000000..9c1d80197 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/CustomPatterns.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Reflection; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + sealed class TypePattern : Pattern + { + readonly string ns; + readonly string name; + + public TypePattern(Type type) + { + this.ns = type.Namespace; + this.name = type.Name; + } + + public override bool DoMatch(INode other, Match match) + { + ComposedType ct = other as ComposedType; + AstType o; + if (ct != null && !ct.HasNullableSpecifier && ct.PointerRank == 0 && !ct.ArraySpecifiers.Any()) { + // Special case: ILSpy sometimes produces a ComposedType but then removed all array specifiers + // from it. In that case, we need to look at the base type for the annotations. + o = ct.BaseType; + } else { + o = other as AstType; + if (o == null) + return false; + } + TypeReference tr = o.Annotation(); + return tr != null && tr.Namespace == ns && tr.Name == name; + } + + public override string ToString() + { + return name; + } + } + + sealed class LdTokenPattern : Pattern + { + AnyNode childNode; + + public LdTokenPattern(string groupName) + { + this.childNode = new AnyNode(groupName); + } + + public override bool DoMatch(INode other, Match match) + { + InvocationExpression ie = other as InvocationExpression; + if (ie != null && ie.Annotation() != null && ie.Arguments.Count == 1) { + return childNode.DoMatch(ie.Arguments.Single(), match); + } + return false; + } + + public override string ToString() + { + return "ldtoken(...)"; + } + } + + /// + /// typeof-Pattern that applies on the expanded form of typeof (prior to ReplaceMethodCallsWithOperators) + /// + sealed class TypeOfPattern : Pattern + { + INode childNode; + + public TypeOfPattern(string groupName) + { + childNode = new TypePattern(typeof(Type)).ToType().Invoke( + "GetTypeFromHandle", new TypeOfExpression(new AnyNode(groupName)).Member("TypeHandle")); + } + + public override bool DoMatch(INode other, Match match) + { + return childNode.DoMatch(other, match); + } + + public override string ToString() + { + return "typeof(...)"; + } + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 17cca7650..9a0c7edb9 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -209,6 +209,18 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return true; } + public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data) + { + if (context.Settings.ExpressionTrees && ExpressionTreeConverter.CouldBeExpressionTree(invocationExpression)) { + Expression converted = ExpressionTreeConverter.TryConvert(context, invocationExpression); + if (converted != null) { + invocationExpression.ReplaceWith(converted); + return converted.AcceptVisitor(this, data); + } + } + return base.VisitInvocationExpression(invocationExpression, data); + } + #region Track current variables public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) { diff --git a/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs b/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs new file mode 100644 index 000000000..adedbf744 --- /dev/null +++ b/ICSharpCode.Decompiler/Ast/Transforms/ExpressionTreeConverter.cs @@ -0,0 +1,420 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using ICSharpCode.Decompiler.ILAst; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.PatternMatching; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.Ast.Transforms +{ + public class ExpressionTreeConverter + { + #region static TryConvert method + public static bool CouldBeExpressionTree(InvocationExpression expr) + { + if (expr != null && expr.Arguments.Count == 2) { + MethodReference mr = expr.Annotation(); + return mr != null && mr.Name == "Lambda" && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression"; + } + return false; + } + + public static Expression TryConvert(DecompilerContext context, Expression expr) + { + Expression converted = new ExpressionTreeConverter(context).Convert(expr); + if (converted != null) { + converted.AddAnnotation(new ExpressionTreeLambdaAnnotation()); + } + return converted; + } + #endregion + + readonly DecompilerContext context; + Stack activeLambdas = new Stack(); + + private ExpressionTreeConverter(DecompilerContext context) + { + this.context = context; + } + + #region Main Convert method + Expression Convert(Expression expr) + { + InvocationExpression invocation = expr as InvocationExpression; + if (invocation != null) { + MethodReference mr = invocation.Annotation(); + if (mr != null && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression") { + switch (mr.Name) { + case "Add": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Add, false); + case "AddChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Add, true); + case "AddAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, false); + case "AddAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Add, true); + case "And": + return ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseAnd); + case "AndAlso": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalAnd); + case "AndAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseAnd); + case "ArrayAccess": + case "ArrayIndex": + case "ArrayLength": + return NotImplemented(invocation); + case "Assign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Assign); + case "Call": + return NotImplemented(invocation); + case "Coalesce": + return ConvertBinaryOperator(invocation, BinaryOperatorType.NullCoalescing); + case "Condition": + return NotImplemented(invocation); + case "Constant": + if (invocation.Arguments.Count >= 1) + return invocation.Arguments.First().Clone(); + else + return NotSupported(expr); + case "Convert": + case "ConvertChecked": + case "Default": + return NotImplemented(invocation); + case "Divide": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Divide); + case "DivideAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Divide); + case "Equal": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Equality); + case "ExclusiveOr": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ExclusiveOr); + case "ExclusiveOrAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ExclusiveOr); + case "Field": + return ConvertField(invocation); + case "GreaterThan": + return ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThan); + case "GreaterThanOrEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.GreaterThanOrEqual); + case "Lambda": + return ConvertLambda(invocation); + case "LeftShift": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftLeft); + case "LeftShiftAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftLeft); + case "LessThan": + return ConvertBinaryOperator(invocation, BinaryOperatorType.LessThan); + case "LessThanOrEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.LessThanOrEqual); + case "Modulo": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Modulus); + case "ModuloAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Modulus); + case "Multiply": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, false); + case "MultiplyChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Multiply, true); + case "MultiplyAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, false); + case "MultiplyAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Multiply, true); + case "Negate": + case "NegateChecked": + return NotImplemented(invocation); + case "New": + return ConvertNewObject(invocation); + case "Not": + return NotImplemented(invocation); + case "NotEqual": + return ConvertBinaryOperator(invocation, BinaryOperatorType.InEquality); + case "OnesComplement": + return NotImplemented(invocation); + case "Or": + return ConvertBinaryOperator(invocation, BinaryOperatorType.BitwiseOr); + case "OrAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.BitwiseOr); + case "OrElse": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ConditionalOr); + case "Quote": + return NotImplemented(invocation); + case "RightShift": + return ConvertBinaryOperator(invocation, BinaryOperatorType.ShiftRight); + case "RightShiftAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.ShiftRight); + case "Subtract": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, false); + case "SubtractChecked": + return ConvertBinaryOperator(invocation, BinaryOperatorType.Subtract, true); + case "SubtractAssign": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, false); + case "SubtractAssignChecked": + return ConvertAssignmentOperator(invocation, AssignmentOperatorType.Subtract, true); + case "TypeAs": + return ConvertTypeAs(invocation); + case "TypeIs": + return ConvertTypeIs(invocation); + } + } + } + IdentifierExpression ident = expr as IdentifierExpression; + if (ident != null) { + ILVariable v = ident.Annotation(); + if (v != null) { + foreach (LambdaExpression lambda in activeLambdas) { + foreach (ParameterDeclaration p in lambda.Parameters) { + if (p.Annotation() == v) + return new IdentifierExpression(p.Name).WithAnnotation(v); + } + } + } + } + return NotSupported(expr); + } + + Expression NotSupported(Expression expr) + { + Debug.WriteLine("Expression Tree Conversion Failed: '" + expr + "' is not supported"); + return null; + } + + Expression NotImplemented(Expression expr) + { + return new IdentifierExpression("NotImplemented").Invoke(expr.Clone()); + } + #endregion + + #region Convert Lambda + static readonly Expression emptyArrayPattern = new ArrayCreateExpression { + Type = new AnyNode(), + Arguments = { new PrimitiveExpression(0) } + }; + + Expression ConvertLambda(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + LambdaExpression lambda = new LambdaExpression(); + Expression body = invocation.Arguments.First(); + ArrayCreateExpression parameterArray = invocation.Arguments.Last() as ArrayCreateExpression; + if (parameterArray == null) + return NotSupported(invocation); + + var annotation = body.Annotation(); + if (annotation != null) { + lambda.Parameters.AddRange(annotation.Parameters); + } else { + // No parameter declaration annotation found. + if (!emptyArrayPattern.IsMatch(parameterArray)) + return null; + } + + activeLambdas.Push(lambda); + Expression convertedBody = Convert(body); + activeLambdas.Pop(); + if (convertedBody == null) + return null; + lambda.Body = convertedBody; + return lambda; + } + #endregion + + #region Convert Field + static readonly Expression getFieldFromHandlePattern = + new TypePattern(typeof(FieldInfo)).ToType().Invoke("GetFieldFromHandle", new LdTokenPattern("field").ToExpression().Member("FieldHandle")); + + Expression ConvertField(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Expression fieldInfoExpr = invocation.Arguments.ElementAt(1); + Match m = getFieldFromHandlePattern.Match(fieldInfoExpr); + if (!m.Success) + return NotSupported(invocation); + + FieldReference fr = m.Get("field").Single().Annotation(); + if (fr == null) + return null; + + Expression target = Convert(invocation.Arguments.ElementAt(0)); + if (target == null) + return null; + + return target.Member(fr.Name).WithAnnotation(fr); + } + #endregion + + #region Convert Binary Operator + Expression ConvertBinaryOperator(InvocationExpression invocation, BinaryOperatorType op, bool? isChecked = null) + { + if (invocation.Arguments.Count != 2) + return NotSupported(invocation); + + Expression left = Convert(invocation.Arguments.ElementAt(0)); + if (left == null) + return null; + Expression right = Convert(invocation.Arguments.ElementAt(1)); + if (right == null) + return null; + + BinaryOperatorExpression boe = new BinaryOperatorExpression(left, op, right); + if (isChecked != null) + boe.AddAnnotation(isChecked.Value ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); + return boe; + } + + Expression ConvertAssignmentOperator(InvocationExpression invocation, AssignmentOperatorType op, bool? isChecked = null) + { + return NotImplemented(invocation); + } + #endregion + + #region Convert New Object + static readonly Expression newObjectCtorPattern = new TypePattern(typeof(MethodBase)).ToType().Invoke + ( + "GetMethodFromHandle", + new LdTokenPattern("ctor").ToExpression().Member("MethodHandle"), + new OptionalNode(new TypeOfExpression(new AnyNode("declaringType")).Member("TypeHandle")) + ).CastTo(new TypePattern(typeof(ConstructorInfo))); + + Expression ConvertNewObject(InvocationExpression invocation) + { + if (invocation.Arguments.Count < 1 || invocation.Arguments.Count > 3) + return NotSupported(invocation); + + Match m = newObjectCtorPattern.Match(invocation.Arguments.First()); + if (!m.Success) + return NotSupported(invocation); + + MethodReference ctor = m.Get("ctor").Single().Annotation(); + if (ctor == null) + return null; + + TypeReference declaringType; + if (m.Has("declaringType")) { + declaringType = m.Get("declaringType").Single().Annotation(); + } else { + declaringType = ctor.DeclaringType; + } + if (declaringType == null) + return null; + + ObjectCreateExpression oce = new ObjectCreateExpression(AstBuilder.ConvertType(declaringType)); + if (invocation.Arguments.Count >= 2) { + IList arguments = ConvertExpressionsArray(invocation.Arguments.ElementAtOrDefault(1)); + if (arguments == null) + return null; + oce.Arguments.AddRange(arguments); + } + if (invocation.Arguments.Count >= 3 && declaringType.IsAnonymousType()) { + MethodDefinition resolvedCtor = ctor.Resolve(); + if (resolvedCtor == null || resolvedCtor.Parameters.Count != oce.Arguments.Count) + return null; + AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); + var arguments = oce.Arguments.ToArray(); + if (AstMethodBodyBuilder.CanInferAnonymousTypePropertyNamesFromArguments(arguments, resolvedCtor.Parameters)) { + oce.Arguments.MoveTo(atce.Initializers); + } else { + for (int i = 0; i < resolvedCtor.Parameters.Count; i++) { + atce.Initializers.Add( + new NamedExpression { + Identifier = resolvedCtor.Parameters[i].Name, + Expression = arguments[i].Detach() + }); + } + } + return atce; + } + + return oce; + } + #endregion + + #region ConvertExpressionsArray + static readonly Pattern expressionArrayPattern = new Choice { + new ArrayCreateExpression { + Type = new TypePattern(typeof(System.Linq.Expressions.Expression)), + Arguments = { new PrimitiveExpression(0) } + }, + new ArrayCreateExpression { + Type = new TypePattern(typeof(System.Linq.Expressions.Expression)), + AdditionalArraySpecifiers = { new ArraySpecifier() }, + Initializer = new ArrayInitializerExpression { + Elements = { new Repeat(new AnyNode("elements")) } + } + } + }; + + IList ConvertExpressionsArray(Expression arrayExpression) + { + Match m = expressionArrayPattern.Match(arrayExpression); + if (m.Success) { + List result = new List(); + foreach (Expression expr in m.Get("elements")) { + Expression converted = Convert(expr); + if (converted == null) + return null; + result.Add(converted); + } + return result; + } + return null; + } + #endregion + + #region Convert TypeAs/TypeIs + static readonly TypeOfPattern typeOfPattern = new TypeOfPattern("type"); + + Expression ConvertTypeAs(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return null; + Match m = typeOfPattern.Match(invocation.Arguments.ElementAt(1)); + if (m.Success) { + Expression converted = Convert(invocation.Arguments.First()); + if (converted != null) + return new AsExpression(converted, m.Get("type").Single().Clone()); + } + return null; + } + + Expression ConvertTypeIs(InvocationExpression invocation) + { + if (invocation.Arguments.Count != 2) + return null; + Match m = typeOfPattern.Match(invocation.Arguments.ElementAt(1)); + if (m.Success) { + Expression converted = Convert(invocation.Arguments.First()); + if (converted != null) { + return new IsExpression { + Expression = converted, + Type = m.Get("type").Single().Clone() + }; + } + } + return null; + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index 8a19eca2a..10d09ba82 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -227,7 +227,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms Name = variableName, Initializer = m1.Get("initializer").Single().Detach() }.CopyAnnotationsFrom(node.Expression) - .WithAnnotation(m1.Get("variable").Single().Annotation()) + .WithAnnotation(m1.Get("variable").Single().Annotation()) } }.CopyAnnotationsFrom(node); } else { @@ -985,28 +985,5 @@ namespace ICSharpCode.Decompiler.Ast.Transforms return null; } #endregion - - #region Pattern Matching Helpers - sealed class TypePattern : Pattern - { - readonly string ns; - readonly string name; - - public TypePattern(Type type) - { - this.ns = type.Namespace; - this.name = type.Name; - } - - public override bool DoMatch(INode other, Match match) - { - AstNode o = other as AstNode; - if (o == null) - return false; - TypeReference tr = o.Annotation(); - return tr != null && tr.Namespace == ns && tr.Name == name; - } - } - #endregion } } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index ddd0b038f..05a598520 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -41,6 +41,21 @@ namespace ICSharpCode.Decompiler } } + bool expressionTrees = true; + + /// + /// Decompile expression trees. + /// + public bool ExpressionTrees { + get { return expressionTrees; } + set { + if (expressionTrees != value) { + expressionTrees = value; + OnPropertyChanged("ExpressionTrees"); + } + } + } + bool yieldReturn = true; /// diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a717d07f0..8a497a8b3 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -61,9 +61,11 @@ + + diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 464df9521..710ad85ce 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -53,6 +53,7 @@ namespace ICSharpCode.Decompiler.ILAst TransformObjectInitializers, MakeAssignmentExpression, IntroducePostIncrement, + InlineExpressionTreeParameterDeclarations, InlineVariables2, FindLoops, FindConditions, @@ -171,6 +172,11 @@ namespace ICSharpCode.Decompiler.ILAst if (abortBeforeStep == ILAstOptimizationStep.IntroducePostIncrement) return; modified |= block.RunOptimization(IntroducePostIncrement); + if (abortBeforeStep == ILAstOptimizationStep.InlineExpressionTreeParameterDeclarations) return; + if (context.Settings.ExpressionTrees) { + modified |= block.RunOptimization(InlineExpressionTreeParameterDeclarations); + } + if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return; modified |= new ILInlining(method).InlineAllInBlock(block); new ILInlining(method).CopyPropagation(); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 475420e46..cbf548698 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -328,6 +328,12 @@ namespace ICSharpCode.Decompiler.ILAst /// becomes "NullableOf(add(valueof(...), ...))" /// NullableOf, + /// + /// Declares parameters that are used in an expression tree. + /// The last child of this node is the call constructing the expression tree, all other children are the + /// assignments to the ParameterExpression variables. + /// + ExpressionTreeParameterDeclarations } public static class ILCodeUtil diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs index d32a4926c..858ba9a52 100644 --- a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -953,17 +953,17 @@ namespace ICSharpCode.Decompiler.ILAst var a = expr.Arguments[0]; ILCode c; switch (a.Code) { - case ILCode.Ceq: c = ILCode.Cne; break; - case ILCode.Cne: c = ILCode.Ceq; break; - case ILCode.Cgt: c = ILCode.Cle; break; - case ILCode.Cgt_Un: c = ILCode.Cle_Un; break; - case ILCode.Cge: c = ILCode.Clt; break; - case ILCode.Cge_Un: c = ILCode.Clt_Un; break; - case ILCode.Clt: c = ILCode.Cge; break; - case ILCode.Clt_Un: c = ILCode.Cge_Un; break; - case ILCode.Cle: c = ILCode.Cgt; break; - case ILCode.Cle_Un: c = ILCode.Cgt_Un; break; - default: return false; + case ILCode.Ceq: c = ILCode.Cne; break; + case ILCode.Cne: c = ILCode.Ceq; break; + case ILCode.Cgt: c = ILCode.Cle; break; + case ILCode.Cgt_Un: c = ILCode.Cle_Un; break; + case ILCode.Cge: c = ILCode.Clt; break; + case ILCode.Cge_Un: c = ILCode.Clt_Un; break; + case ILCode.Clt: c = ILCode.Cge; break; + case ILCode.Clt_Un: c = ILCode.Cge_Un; break; + case ILCode.Cle: c = ILCode.Cgt; break; + case ILCode.Cle_Un: c = ILCode.Cgt_Un; break; + default: return false; } a.Code = c; a.ILRanges.AddRange(expr.ILRanges); @@ -982,20 +982,20 @@ namespace ICSharpCode.Decompiler.ILAst static void SimplifyShiftOperators(ILExpression expr, ref bool modified) { - for (int i = 0; i < expr.Arguments.Count; i++) + for (int i = 0; i < expr.Arguments.Count; i++) SimplifyShiftOperators(expr.Arguments[i], ref modified); - if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un) + if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un) return; var a = expr.Arguments[1]; - if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null) + if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null) return; int mask; switch (expr.InferredType.MetadataType) { case MetadataType.Int32: - case MetadataType.UInt32: mask = 31; break; + case MetadataType.UInt32: mask = 31; break; case MetadataType.Int64: - case MetadataType.UInt64: mask = 63; break; - default: return; + case MetadataType.UInt64: mask = 63; break; + default: return; } if ((int)a.Arguments[1].Operand != mask) return; var res = a.Arguments[0]; @@ -1005,5 +1005,81 @@ namespace ICSharpCode.Decompiler.ILAst modified = true; } #endregion + + #region InlineExpressionTreeParameterDeclarations + bool InlineExpressionTreeParameterDeclarations(List body, ILExpression expr, int pos) + { + // When there is a Expression.Lambda() call, and the parameters are declared in the + // IL statement immediately prior to the one containing the Lambda() call, + // using this code for the3 declaration: + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + // and the variables v are assigned only once (in that statements), and read only in a Expression::Lambda + // call that immediately follows the assignment statements, then we will inline those assignments + // into the Lambda call using ILCode.ExpressionTreeParameterDeclarations. + + // This is sufficient to allow inlining over the expression tree construction. The remaining translation + // of expression trees into C# will be performed by a C# AST transformer. + + for (int i = expr.Arguments.Count - 1; i >= 0; i--) { + if (InlineExpressionTreeParameterDeclarations(body, expr.Arguments[i], pos)) + return true; + } + + MethodReference mr; + ILExpression lambdaBodyExpr, parameterArray; + if (!(expr.Match(ILCode.Call, out mr, out lambdaBodyExpr, out parameterArray) && mr.Name == "Lambda")) + return false; + if (!(parameterArray.Code == ILCode.InitArray && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + int firstParameterPos = pos - parameterArray.Arguments.Count; + if (firstParameterPos < 0) + return false; + + ILExpression[] parameterInitExpressions = new ILExpression[parameterArray.Arguments.Count + 1]; + for (int i = 0; i < parameterArray.Arguments.Count; i++) { + parameterInitExpressions[i] = body[firstParameterPos + i] as ILExpression; + if (!MatchParameterVariableAssignment(parameterInitExpressions[i])) + return false; + ILVariable v = (ILVariable)parameterInitExpressions[i].Operand; + if (!parameterArray.Arguments[i].MatchLdloc(v)) + return false; + // TODO: validate that the variable is only used here and within 'body' + } + + parameterInitExpressions[parameterInitExpressions.Length - 1] = lambdaBodyExpr; + Debug.Assert(expr.Arguments[0] == lambdaBodyExpr); + expr.Arguments[0] = new ILExpression(ILCode.ExpressionTreeParameterDeclarations, null, parameterInitExpressions); + + body.RemoveRange(firstParameterPos, parameterArray.Arguments.Count); + + return true; + } + + bool MatchParameterVariableAssignment(ILExpression expr) + { + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + ILVariable v; + ILExpression init; + if (!expr.Match(ILCode.Stloc, out v, out init)) + return false; + if (v.IsGenerated || v.IsParameter || v.IsPinned) + return false; + if (v.Type == null || v.Type.FullName != "System.Linq.Expressions.ParameterExpression") + return false; + MethodReference parameterMethod; + ILExpression typeArg, nameArg; + if (!init.Match(ILCode.Call, out parameterMethod, out typeArg, out nameArg)) + return false; + if (!(parameterMethod.Name == "Parameter" && parameterMethod.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + MethodReference getTypeFromHandle; + ILExpression typeToken; + if (!typeArg.Match(ILCode.Call, out getTypeFromHandle, out typeToken)) + return false; + if (!(getTypeFromHandle.Name == "GetTypeFromHandle" && getTypeFromHandle.DeclaringType.FullName == "System.Type")) + return false; + return typeToken.Code == ILCode.Ldtoken && nameArg.Code == ILCode.Ldstr; + } + #endregion } } diff --git a/ICSharpCode.Decompiler/Tests/ExpressionTrees.cs b/ICSharpCode.Decompiler/Tests/ExpressionTrees.cs new file mode 100644 index 000000000..e52eb4d0a --- /dev/null +++ b/ICSharpCode.Decompiler/Tests/ExpressionTrees.cs @@ -0,0 +1,345 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Xml; + +namespace ICSharpCode.Decompiler.Tests +{ + public class ExpressionTrees + { + int field; + + static object ToCode(object x, Expression> expr) + { + return expr; + } + + static object ToCode(object x, Expression> expr) + { + return expr; + } + + static object X() + { + return null; + } + + public void Parameter(bool a) + { + ToCode(X(), () => a); + } + + public void LocalVariable() + { + bool a = true; + ToCode(X(), () => a); + } + + public void LambdaParameter() + { + ToCode(X(), (bool a) => a); + } + + public void AddOperator(int x) + { + ToCode(X(), () => 1 + x + 2); + } + + public void AnonymousClasses() + { + ToCode(X(), () => new { X = 3, A = "a" }); + } + + public void ArrayIndex() + { + ToCode(X(), () => new[] { 3, 4, 5 }[0 + (int)(DateTime.Now.Ticks % 3)]); + } + + public void ArrayLengthAndDoubles() + { + ToCode(X(), () => new[] { 1.0, 2.01, 3.5 }.Concat(new[] { 1.0, 2.0 }).ToArray().Length); + } + + public void AsOperator() + { + ToCode(X(), () => new object() as string); + } + + public void ComplexGenericName() + { + ToCode(X(), () => ((Func)(x => x > 0))(0)); + } + + public void DefaultValue() + { + ToCode(X(), () => new TimeSpan(1, 2, 3) == default(TimeSpan)); + } + + public void EnumConstant() + { + ToCode(X(), () => new object().Equals(MidpointRounding.ToEven)); + } + + public void IndexerAccess() + { + var dict = Enumerable.Range(1, 20).ToDictionary(n => n.ToString()); + ToCode(X(), () => dict["3"] == 3); + } + + public void IsOperator() + { + ToCode(X(), () => new object() is string); + } + + public void ListInitializer() + { + ToCode(X(), () => new Dictionary { { 1, 1 }, { 2, 2 }, { 3, 4 } }.Count == 3); + } + + public void ListInitializer2() + { + ToCode(X(), () => new List(50) { 1, 2, 3 }.Count == 3); + } + + public void ListInitializer3() + { + ToCode(X(), () => new List { 1, 2, 3 }.Count == 3); + } + + public void LiteralCharAndProperty() + { + ToCode(X(), () => new string(' ', 3).Length == 1); + } + + public void CharNoCast() + { + ToCode(X(), () => "abc"[1] == 'b'); + } + + public void StringsImplicitCast() + { + int i = 1; + string x = "X"; + ToCode(X(), () => (("a\n\\b" ?? x) + x).Length == 2 ? false : true && (1m + -i > 0 || false)); + } + + public void NotImplicitCast() + { + byte z = 42; + ToCode(X(), () => ~z == 0); + } + + public void MembersBuiltin() + { + ToCode(X(), () => 1.23m.ToString()); + ToCode(X(), () => AttributeTargets.All.HasFlag((Enum)AttributeTargets.Assembly)); + ToCode(X(), () => "abc".Length == 3); + ToCode(X(), () => 'a'.CompareTo('b') < 0); + } + + public void MembersDefault() + { + ToCode(X(), () => default(DateTime).Ticks == 0); + ToCode(X(), () => default(int[]).Length == 0); + ToCode(X(), () => default(Type).IsLayoutSequential); + ToCode(X(), () => default(List).Count); + ToCode(X(), () => default(int[]).Clone() == null); + ToCode(X(), () => default(Type).IsInstanceOfType(new object())); + ToCode(X(), () => default(List).AsReadOnly()); + } + + public void DoAssert() + { + field = 37; + ToCode(X(), () => field != C()); + ToCode(X(), () => !ReferenceEquals(this, new ExpressionTrees())); + ToCode(X(), () => MyEquals(this) && !MyEquals(default(ExpressionTrees))); + } + + int C() + { + return field + 5; + } + + bool MyEquals(ExpressionTrees other) + { + return other != null && field == other.field; + } + + public void MethodGroupAsExtensionMethod() + { + ToCode(X(), () => (Func)new[] { 2000, 2004, 2008, 2012 }.Any); + } + + public void MethodGroupConstant() + { + ToCode(X(), () => Array.TrueForAll(new[] { 2000, 2004, 2008, 2012 }, DateTime.IsLeapYear)); + + HashSet set = new HashSet(); + ToCode(X(), () => new[] { 2000, 2004, 2008, 2012 }.All(set.Add)); + + Func, bool> sink = f => f(null, null); + ToCode(X(), () => sink(int.Equals)); + } + + public void MultipleCasts() + { + ToCode(X(), () => 1 == (int)(object)1); + } + + public void MultipleDots() + { + ToCode(X(), () => 3.ToString().ToString().Length > 0); + } + + public void NestedLambda() + { + Func, int> call = f => f(); + //no params + ToCode(X(), () => call(() => 42)); + //one param + ToCode(X(), () => new[] { 37, 42 }.Select(x => x * 2)); + //two params + ToCode(X(), () => new[] { 37, 42 }.Select((x, i) => x * 2)); + } + + public void CurriedLambda() + { + ToCode>>(X(), a => b => c => a + b + c); + } + + bool Fizz(Func a) + { + return a(42); + } + + bool Buzz(Func a) + { + return a(42); + } + + bool Fizz(Func a) + { + return a("42"); + } + + public void NestedLambda2() + { + ToCode(X(), () => Fizz(x => x == "a")); + ToCode(X(), () => Fizz(x => x == 37)); + + ToCode(X(), () => Fizz((int x) => true)); + ToCode(X(), () => Buzz(x => true)); + } + + public void NewArrayAndExtensionMethod() + { + ToCode(X(), () => new[] { 1.0, 2.01, 3.5 }.SequenceEqual(new[] { 1.0, 2.01, 3.5 })); + } + + public void NewMultiDimArray() + { + ToCode(X(), () => new int[3, 4].Length == 1); + } + + public void NewObject() + { + ToCode(X(), () => new object() != new object()); + } + + public void NotOperator() + { + bool x = true; + int y = 3; + byte z = 42; + ToCode(X(), () => ~(int)z == 0); + ToCode(X(), () => ~y == 0); + ToCode(X(), () => !x); + } + + public void ObjectInitializers() + { + XmlReaderSettings s = new XmlReaderSettings { + CloseInput = false, + CheckCharacters = false + }; + ToCode(X(), () => new XmlReaderSettings { CloseInput = s.CloseInput, CheckCharacters = s.CheckCharacters }.Equals(s)); + } + + public void Quoted() + { + ToCode(X(), () => (Expression>)((n, s) => s + n.ToString()) != null); + } + + public void Quoted2() + { + ToCode(X(), () => ToCode(X(), () => true).Equals(null)); + } + + public void QuotedWithAnonymous() + { + ToCode(X(), () => new[] { new { X = "a", Y = "b" } }.Select(o => o.X + o.Y).Single()); + } + + public void StaticCall() + { + ToCode(X(), () => Equals(3, 0)); + } + + public void ThisCall() + { + ToCode(X(), () => !Equals(3)); + } + + public void ThisExplicit() + { + ToCode(X(), () => object.Equals(this, 3)); + } + + public void TypedConstant() + { + ToCode(X(), () => new[] { typeof(int), typeof(string) }); + } + + public void StaticCallImplicitCast() + { + ToCode(X(), () => Equals(3, 0)); + } + + public void StaticMembers() + { + ToCode(X(), () => (DateTime.Now > DateTime.Now + TimeSpan.FromMilliseconds(10.001)).ToString() == "False"); + } + + public void Strings() + { + int i = 1; + string x = "X"; + ToCode(X(), () => (("a\n\\b" ?? x) + x).Length == 2 ? false : true && (1m + (decimal)-i > 0m || false)); + } + + public void StringAccessor() + { + ToCode(X(), () => (int)"abc"[1] == 98); + } + } +} diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index 9b3457dd4..0244a2d23 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -14,7 +14,7 @@ 4 false False - 67,169,1058,728 + 67,169,1058,728,1720 x86 @@ -64,6 +64,7 @@ + diff --git a/ICSharpCode.Decompiler/Tests/InitializerTests.cs b/ICSharpCode.Decompiler/Tests/InitializerTests.cs index 8240a6aef..2f17c2976 100644 --- a/ICSharpCode.Decompiler/Tests/InitializerTests.cs +++ b/ICSharpCode.Decompiler/Tests/InitializerTests.cs @@ -534,7 +534,7 @@ public class InitializerTests public int[,] MultidimensionalInit() { - return new int[, ] + return new int[,] { { @@ -655,7 +655,7 @@ public class InitializerTests { return new int[][,] { - new int[, ] + new int[,] { { @@ -687,7 +687,7 @@ public class InitializerTests } }, - new int[, ] + new int[,] { { @@ -719,7 +719,7 @@ public class InitializerTests } }, - new int[, ] + new int[,] { { @@ -750,7 +750,7 @@ public class InitializerTests 0 } }, - new int[, ] + new int[,] { { @@ -789,7 +789,7 @@ public class InitializerTests { return new int[][,,] { - new int[, , ] + new int[,,] { { { @@ -827,7 +827,7 @@ public class InitializerTests } }, - new int[, , ] + new int[,,] { { { diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index 4d278e0e9..a4feebca0 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -52,6 +52,12 @@ namespace ICSharpCode.Decompiler.Tests TestFile(@"..\..\Tests\DelegateConstruction.cs"); } + [Test, Ignore("Not yet implemented")] + public void ExpressionTrees() + { + TestFile(@"..\..\Tests\ExpressionTrees.cs"); + } + [Test] public void ExceptionHandling() { diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 91237ddf8..336e8aa49 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -52,6 +52,7 @@ namespace ICSharpCode.ILSpy } static readonly Uri UpdateUrl = new Uri("http://www.ilspy.net/updates.xml"); + const string band = "beta"; static AvailableVersionInfo latestAvailableVersion; @@ -193,7 +194,7 @@ namespace ICSharpCode.ILSpy try { XDocument doc = XDocument.Load(new MemoryStream(e.Result)); var bands = doc.Root.Elements("band"); - var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == "stable") ?? bands.First(); + var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First(); Version version = new Version((string)currentBand.Element("latestVersion")); string url = (string)currentBand.Element("downloadUrl"); if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal)))