Skip to content

Commit

Permalink
Prevent infinite loop when invoking delegate in Mock.Of setup expre…
Browse files Browse the repository at this point in the history
…ssion (#528)

* Add failing regression test
* Fix infinite loop by adding logic for InvocationExpression
* Update the changelog
  • Loading branch information
stakx authored Nov 21, 2017
1 parent cb83e88 commit 124ab78
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
* Prevent Moq from relying on a mock's implementation of `IEnumerable<T>` (@stakx, #510)
* Verification leaked internal `MockVerificationException` type; remove it (@stakx, #511)
* Custom matcher properties not printed correctly in error messages (@stakx, #517)
* Infinite loop when invoking delegate in `Mock.Of` setup expression (@stakx, #528)

#### Obsoleted

Expand Down
21 changes: 21 additions & 0 deletions Moq.Tests/Regressions/IssueReportsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,27 @@ public void strict_mock_accepts_null_as_nullable_guid_value()

#endregion // #184

#region 224

public class Issue224
{
[Fact]
public void Delegate_invocation_in_Mock_Of_does_not_cause_infinite_loop()
{
var infiniteLoopTimeout = TimeSpan.FromSeconds(5);

var timedOut = !Task.Run(() =>
{
var fn = Mock.Of<Func<int>>(f => f() == 42);
Assert.Equal(42, fn());
}).Wait(infiniteLoopTimeout);

Assert.False(timedOut);
}
}

#endregion

#region 239

public class Issue239
Expand Down
51 changes: 51 additions & 0 deletions Source/Linq/MockSetupsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,17 @@ private static Expression ConvertToSetup(Expression left, Expression right)

return ConvertToSetup(method.Object, method, right);

case ExpressionType.Invoke:
var invocation = (InvocationExpression)left;
if (invocation.Expression is ParameterExpression && typeof(Delegate).IsAssignableFrom(invocation.Expression.Type))
{
return ConvertToSetup(invocation, right);
}
else
{
break;
}

case ExpressionType.Convert:
var left1 = (UnaryExpression)left;
return ConvertToSetup(left1.Operand, Expression.Convert(right, left1.Operand.Type));
Expand All @@ -177,6 +188,46 @@ private static Expression ConvertToSetup(Expression left, Expression right)
return null;
}

private static Expression ConvertToSetup(InvocationExpression invocation, Expression right)
{
// transforms a delegate invocation expression such as `f(...) == x` (where `invocation` := `f(...)` and `right` := `x`)
// to `Mock.Get(f).Setup(f' => f'(...)).Returns(x) != null` (which in turn will get incorporated into a query
// `CreateMocks().First(f => ...)`.

var delegateParameter = invocation.Expression;

var mockGetMethod =
typeof(Mock)
.GetMethod("Get", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(delegateParameter.Type);

var mockGetCall = Expression.Call(mockGetMethod, delegateParameter);

var setupMethod =
typeof(Mock<>)
.MakeGenericType(delegateParameter.Type)
.GetMethods("Setup")
.Single(m => m.IsGenericMethod)
.MakeGenericMethod(right.Type);

var setupCall = Expression.Call(
mockGetCall,
setupMethod,
Expression.Lambda(invocation, invocation.Expression as ParameterExpression));

var returnsMethod =
typeof(IReturns<,>)
.MakeGenericType(delegateParameter.Type, right.Type)
.GetMethod("Returns", new[] { right.Type });

var returnsCall = Expression.Call(
setupCall,
returnsMethod,
right);

return Expression.NotEqual(returnsCall, Expression.Constant(null));
}

private static Expression ConvertToSetupProperty(Expression targetObject, Expression left, Expression right)
{
// TODO: throw if target is a static class?
Expand Down

0 comments on commit 124ab78

Please sign in to comment.