diff --git a/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs b/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs index d0adaa8b..b9411c4b 100644 --- a/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs +++ b/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs @@ -7,7 +7,7 @@ namespace DelegateDecompiler.Tests { public class DecompilerTestsBase { - private static readonly Func debugView = BuildDebugView(); + static readonly Func DebugView = BuildDebugView(); private static Func BuildDebugView() { @@ -25,7 +25,7 @@ protected static void Test(Expression expected, T compiled) var y = decompiled.Body.ToString(); Console.WriteLine(y); Assert.AreEqual(x, y); - Assert.AreEqual(debugView(expected.Body), debugView(decompiled.Body)); + Assert.AreEqual(DebugView(expected.Body), DebugView(decompiled.Body)); } protected static void Test(Expression expected, MethodInfo compiled) @@ -38,10 +38,10 @@ protected static void Test(Expression expected, MethodInfo compiled) var y = decompiled.Body.ToString(); Console.WriteLine(y); Assert.AreEqual(x, y); - Assert.AreEqual(debugView(expected.Body), debugView(decompiled.Body)); + Assert.AreEqual(DebugView(expected.Body), DebugView(decompiled.Body)); } - protected static void Test(Expression expected1, Expression expected2, T compiled) + protected static void Test(Expression expected1, Expression expected2, T compiled, bool compareDebugView = true) { //Double cast required as we can not convert T to Delegate directly var decompiled = ((Delegate) ((object) compiled)).Decompile(); @@ -53,7 +53,15 @@ protected static void Test(Expression expected1, Expression expected2, var y = decompiled.Body.ToString(); Console.WriteLine(y); Assert.That(y, Is.EqualTo(x1).Or.EqualTo(x2)); - Assert.That(debugView(decompiled.Body), Is.EqualTo(debugView(expected1.Body)).Or.EqualTo(debugView(expected2.Body))); + if (compareDebugView) + Assert.That(DebugView(decompiled.Body), Is.EqualTo(DebugView(expected1.Body)).Or.EqualTo(DebugView(expected2.Body))); + } + + protected static void AssertAreEqual(Expression expected, Expression actual, bool compareDebugView = true) + { + Assert.AreEqual(expected.ToString(), actual.ToString()); + if (compareDebugView) + Assert.AreEqual(DebugView(expected), DebugView(actual)); } } } diff --git a/src/DelegateDecompiler.Tests/Issue135.cs b/src/DelegateDecompiler.Tests/Issue135.cs new file mode 100644 index 00000000..63b3ecb8 --- /dev/null +++ b/src/DelegateDecompiler.Tests/Issue135.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; + +namespace DelegateDecompiler.Tests +{ + [TestFixture] + public class Issue135 : DecompilerTestsBase + { + public class Post { + public bool IsActive { get; set; } + } + + public class Blog + { + public bool HasBar { get; } + public bool HasBaz { get; } + + public IEnumerable Posts { get; } + + + [Decompile] + public bool HasFoo + { + get { return (HasBar || HasBaz) && Posts.Any(x => x.IsActive); } + } + + [Decompile] + public bool HasFoo2 + { + get { return (HasBar && Posts.Any(x => x.IsActive)) || (HasBaz && Posts.Any(x => x.IsActive)); } + } + + [Decompile] + public bool HasFoo3 + { + get { return Posts.Any(x => x.IsActive) && (HasBar || HasBaz); } + } + } + + [Test] + public void Test1() + { + Expression> expected1 = b => + (b.HasBar || b.HasBaz) && b.Posts.Any(x => x.IsActive); + + Expression> expected2 = b => + b.HasBar ? b.Posts.Any(x => x.IsActive) : b.HasBaz && b.Posts.Any(x => x.IsActive); + + Func actual = b => + (b.HasBar || b.HasBaz) && b.Posts.Any(x => x.IsActive); + + Test(expected1, expected2, actual, false); + } + + [Test] + public void Test2() + { + Expression> expected1 = b => + b.HasBar && b.Posts.Any(x => x.IsActive) || b.HasBaz && b.Posts.Any(x => x.IsActive); + + Expression> expected2 = b => + b.HasBar + ? b.Posts.Any(x => x.IsActive) || b.HasBaz && b.Posts.Any(x => x.IsActive) + : b.HasBaz && b.Posts.Any(x => x.IsActive); + + Func actual = b => + b.HasBar && b.Posts.Any(x => x.IsActive) || b.HasBaz && b.Posts.Any(x => x.IsActive); + + Test(expected1, expected2, actual, false); + } + + [Test] + public void Test3() + { + Expression> expected = b => + b.Posts.Any(x => x.IsActive) && (b.HasBar || b.HasBaz); + + Func actual = b => + b.Posts.Any(x => x.IsActive) && (b.HasBar || b.HasBaz); + + Test(expected, actual); + } + + [Test, Ignore("Not fixed yet.")] + public void TestQueryable1() + { + var blogs = new[] {new Blog()}.AsQueryable(); + + var expected = ( + from b in blogs + where (b.HasBar || b.HasBaz) && b.Posts.Any(x => x.IsActive) + select b); + + var actual = ( + from b in blogs + where b.HasFoo + select b).Decompile(); + + AssertAreEqual(expected.Expression, actual.Expression, compareDebugView: false); + } + + [Test, Ignore("Not fixed yet.")] + public void TestQueryable2() + { + var blogs = new[] {new Blog()}.AsQueryable(); + + var expected = ( + from b in blogs + where (b.HasBar && b.Posts.Any(x => x.IsActive)) || (b.HasBaz && b.Posts.Any(x => x.IsActive)) + select b); + + var actual = ( + from b in blogs + where b.HasFoo2 + select b).Decompile(); + + AssertAreEqual(expected.Expression, actual.Expression, compareDebugView: false); + } + + [Test] + public void TestQueryable3() + { + var blogs = new[] {new Blog()}.AsQueryable(); + + var expected = ( + from b in blogs + where b.Posts.Any(x => x.IsActive) && (b.HasBar || b.HasBaz) + select b); + + var actual = ( + from b in blogs + where b.HasFoo3 + select b).Decompile(); + + AssertAreEqual(expected.Expression, actual.Expression, compareDebugView: false); + } + } +} \ No newline at end of file diff --git a/src/DelegateDecompiler.Tests/QueryableExtensionsTests.cs b/src/DelegateDecompiler.Tests/QueryableExtensionsTests.cs index 9392b06b..a4dda8ad 100644 --- a/src/DelegateDecompiler.Tests/QueryableExtensionsTests.cs +++ b/src/DelegateDecompiler.Tests/QueryableExtensionsTests.cs @@ -5,7 +5,7 @@ namespace DelegateDecompiler.Tests { [TestFixture] - public class QueryableExtensionsTests + public class QueryableExtensionsTests : DecompilerTestsBase { [Test] public void InlinePropertyWithoutAttribute() @@ -20,7 +20,7 @@ public void InlinePropertyWithoutAttribute() where employee.FullNameWithoutAttribute.Computed() == "Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -36,7 +36,7 @@ public void InlineProperty() where employee.FullName == "Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -52,7 +52,7 @@ public void ConcatNonStringInlineProperty() where employee.FromTo == "0-100" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -70,7 +70,7 @@ public void InlinePropertyOrderBy() orderby employee.FullName select employee); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -88,7 +88,7 @@ public void InlinePropertyOrderByThenBy() orderby employee.FullName select employee).ThenBy(x => x.IsActive); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -104,7 +104,7 @@ where true where employee.IsActive select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -120,7 +120,7 @@ public void TestLdflda() where employee.Count == 0 select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -136,7 +136,7 @@ public void InlineTooDeepProperty() where employee.TooDeepName == "Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -154,7 +154,7 @@ public void InlinePropertyWithVariableClosure() select employee).Decompile(); Console.WriteLine(expected); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -170,7 +170,7 @@ public void InlineMethod() where employee.FullNameMethod() == "Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -186,7 +186,7 @@ public void InlineMethodWithArg() where employee.FullNameMethod("Mr ") == "Mr Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -202,7 +202,7 @@ public void InlineMethodWithTwoArgs() where employee.FullNameMethod("Mr ", " Jr.") == "Mr Test User Jr." select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test, Ignore("Minor differences")] @@ -218,7 +218,7 @@ public void Issue39() where employee.Test select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -230,7 +230,7 @@ public void Issue58() var actual = employees.AsQueryable().Where(_ => _.ComplexProperty == 1).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -246,7 +246,7 @@ public void InlineExtensionMethod() where employee.FullName().Computed() == "Test User" select employee).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -264,7 +264,7 @@ where employee.FullName().Computed() == "Test User" orderby employee.FullName ().Computed() select employee); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -282,7 +282,7 @@ where employee.FullName().Computed() == "Test User" orderby employee.FullName().Computed() select employee).ThenBy(x => x.IsActive); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -298,7 +298,7 @@ public void InlinePropertyNullableShortColeasce1() where employee.TheBad > (short)0 select employee); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test] @@ -314,7 +314,7 @@ public void InlinePropertyNullableShortColeasce2() where (employee.MyField.HasValue ? (short)0 : (short)1) > 0 select employee); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } [Test, Ignore("Minor differences")] @@ -328,7 +328,7 @@ public void Issue78() var actual = employees.AsQueryable().Select(e => e.TotalHoursDb).Decompile(); - Assert.AreEqual(expected.Expression.ToString(), actual.Expression.ToString()); + AssertAreEqual(expected.Expression, actual.Expression); } } } diff --git a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs index f02a1c9a..badf08c5 100644 --- a/src/DelegateDecompiler/OptimizeExpressionVisitor.cs +++ b/src/DelegateDecompiler/OptimizeExpressionVisitor.cs @@ -101,10 +101,13 @@ protected override Expression VisitConditional(ConditionalExpression node) if (test.NodeType == ExpressionType.Not) { + var testOperand = ((UnaryExpression) test).Operand; if (ifTrueConstant?.Value as bool? == false) { - return Expression.AndAlso(((UnaryExpression) test).Operand, ifFalse); + return Expression.AndAlso(testOperand, ifFalse); } + + return Visit(node.Update(testOperand, ifFalse, ifTrue)); } return node.Update(test, ifTrue, ifFalse); diff --git a/src/DelegateDecompiler/Processor.cs b/src/DelegateDecompiler/Processor.cs index 3c51f427..fa3763c7 100644 --- a/src/DelegateDecompiler/Processor.cs +++ b/src/DelegateDecompiler/Processor.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -89,6 +90,10 @@ public Expression Final() static readonly MethodInfo StringConcat = typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }); + //TODO: Move to ProcessorState?? + static readonly ConcurrentDictionary AnonymousDelegatesCache = + new ConcurrentDictionary(); + public static Expression Process(VariableInfo[] locals, IList
args, Instruction instruction, Type returnType) { Processor processor = new Processor(); @@ -383,22 +388,7 @@ Expression Process() else if (state.Instruction.OpCode == OpCodes.Ldftn) { var method = (MethodInfo) state.Instruction.Operand; - var decompile = method.Decompile(); - - var obj = state.Stack.Pop(); - if (!method.IsStatic) - { - var expressions = new Dictionary - { - {decompile.Parameters[0], obj} - }; - - var body = new ReplaceExpressionVisitor(expressions).Visit(decompile.Body); - body = TransparentIdentifierRemovingExpressionVisitor.RemoveTransparentIdentifiers(body); - decompile = Expression.Lambda(body, decompile.Parameters.Skip(1)); - } - - state.Stack.Push(decompile); + state.Stack.Push(DecompileLambdaExpression(method, () => state.Stack.Pop())); state.Instruction = state.Instruction.Next; } else if (state.Instruction.OpCode == OpCodes.Bgt || @@ -777,6 +767,30 @@ Expression Process() return state == null ? Expression.Empty() : state.Final(); } + static LambdaExpression DecompileLambdaExpression(MethodInfo method, Func @this) + { + if (method.IsStatic) + { + return AnonymousDelegatesCache.GetOrAdd(method, m => m.Decompile()); + } + + //Should always call. + var expression = @this(); + return AnonymousDelegatesCache.GetOrAdd(method, m => + { + var decompiled = m.Decompile(); + + var expressions = new Dictionary + { + {decompiled.Parameters[0], expression} + }; + + var body = new ReplaceExpressionVisitor(expressions).Visit(decompiled.Body); + body = TransparentIdentifierRemovingExpressionVisitor.RemoveTransparentIdentifiers(body); + return Expression.Lambda(body, decompiled.Parameters.Skip(1)); + }); + } + static object GetRuntimeHandle(object operand) { var fieldInfo = operand as FieldInfo;