diff --git a/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs b/src/DelegateDecompiler.Tests/DecompilerTestsBase.cs index d0adaa8b..d62ce742 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,7 +38,7 @@ 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) @@ -53,7 +53,13 @@ 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))); + Assert.That(DebugView(decompiled.Body), Is.EqualTo(DebugView(expected1.Body)).Or.EqualTo(DebugView(expected2.Body))); + } + + protected static void AssertAreEqual(Expression expected, Expression actual) + { + Assert.AreEqual(expected.ToString(), actual.ToString()); + 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..e05b6938 --- /dev/null +++ b/src/DelegateDecompiler.Tests/Issue135.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +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, Ignore("Not fixed")] + public void Test1() + { + 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); + } + + [Test, Ignore("Not fixed")] + public void Test2() + { + 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); + } + + [Test] + public void Test3() + { + 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); + } + } +} \ 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/Processor.cs b/src/DelegateDecompiler/Processor.cs index 3c51f427..65387e02 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 = method.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;