Foq icon indicating copy to clipboard operation
Foq copied to clipboard

Unit type return results in InvalidProgramException

Open drivehappy opened this issue 6 years ago • 3 comments

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 avatar Oct 10 '17 16:10 drivehappy

@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 😃

m4rs-mt avatar May 03 '21 14:05 m4rs-mt

@drivehappy, any thoughts about @m4rs-mt's approach?

stackedsax avatar May 11 '21 23:05 stackedsax

@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.

saul avatar May 24 '21 08:05 saul