Foq
Foq copied to clipboard
Unit type return results in InvalidProgramException
Description
Building a mock for a member that returns a unit type results in a System.InvalidProgramException: "Common Language Runtime detected an invalid program."
Repro steps
Compile and run the following program:
open Foq
type IMyInterface =
abstract member foo: int -> unit
let mockMyInterface = Mock<IMyInterface>.With(fun mock ->
<@ mock.foo(any()) --> () @>
)
[<EntryPoint>]
let main args =
mockMyInterface.foo(5)
0
Expected behavior
The mock should successfully call foo(5) without error.
Actual behavior
An exception of System.InvalidProgramException is thrown.
Known workarounds
It seems if I build the mock out via the Setup/Create approach this works correctly:
let mockMyInterface =
Mock<IMyInterface>()
.Setup(fun x -> <@ x.foo() @>)
.Returns(())
.Create()
The documentation is sparse, so I'm unsure of the difference between the two (I opted for the .With() for brevity).
Related information
- Windows 10 x64
- For 1.7.2
- .NET 4.5/4.7 Framework causes this exception. Mono 5.x fails with a stacktrace and the message
unknown type 0x01 in type_to_load_membase
@drivehappy Thank you for your very detailed problem report and steps to reproduce this problem. I took a closer look at the problem myself and was able to isolate it @ptrelford. It is caused by loading a return variable of type void
and issuing multiple Ret
statements (both problems are directly related). The given code causes the following statements to be emitted (for foo
):
...
// Part1
OpCodes.Ldarg_0
OpCodes.Ldfld, returnValuesField
OpCodes.Ldc_I4, returnValuesIndex
OpCodes.Ldelem_Ref
OpCodes.Unbox_Any, returnType
OpCodes.Ret
// Part2
OpCodes.Ret
The first instruction stream (Part1
) is generated by iterating over all overloads
https://github.com/fsprojects/Foq/blob/master/Foq/Foq.fs#L573. Then, a valid overload is detected to generate code https://github.com/fsprojects/Foq/blob/master/Foq/Foq.fs#L576, which in turn triggers code generation for a ReturnValue(value, returnType)
(https://github.com/fsprojects/Foq/blob/master/Foq/Foq.fs#L223). I think this piece of code contains the "actual issue", as it does not differentiate between a void
return and the return of a single value. However, I could be wrong, as the surrounding of this code could compensate for this problem in most cases (except this one).
Additionally, we pass the check https://github.com/fsprojects/Foq/blob/master/Foq/Foq.fs#L580 if the function has a unit
or void
return (which I believe is correct). Depending on how we solve this problem in the first place, this could already avoid issuing duplicate return statements.
I can also prepare a PR if we decide on a possible way to fix this 😃
@drivehappy, any thoughts about @m4rs-mt's approach?
@m4rs-mt
I can also prepare a PR if we decide on a possible way to fix this 😃
If you have a solution please raise a PR (preferably with a test that asserts it fixes this issue). I'm more than happy to review and merge.