Bug 49308 - [AppDomain] System.MissingMethodException because wrong assemblies are loaded into domain
Summary: [AppDomain] System.MissingMethodException because wrong assemblies are loaded...
Status: ASSIGNED
Alias: None
Product: Runtime
Classification: Mono
Component: General (show other bugs)
Version: master
Hardware: PC Mac OS
: --- normal
Target Milestone: Future Cycle (TBD)
Assignee: Aleksey Kliger
URL:
Depends on:
Blocks: 43352
  Show dependency tree
 
Reported: 2016-12-08 12:25 UTC by David Karlaš
Modified: 2017-10-13 13:59 UTC (History)
4 users (show)

See Also:
Tags: bugpool
Is this bug a regression?: ---
Last known good build:


Attachments
sample project (1.28 MB, application/zip)
2016-12-08 12:25 UTC, David Karlaš
Details

Description David Karlaš 2016-12-08 12:25:29 UTC
Created attachment 18827 [details]
sample project

Xamarin Studio+NUnit is using AppDomains to load .dlls and we always want to use our nunit.core.dll, nunit.utils.dll...
https://github.com/mono/monodevelop/blob/93c3aab/main/src/addins/MonoDevelop.UnitTesting.NUnit/NUnitRunner/NUnitTestRunner.cs#L53-L67

See attached project for reproduction of crash below. Same code works fine on .Net(it loads .dlls as expected from bin/Debug folder and not as Mono does from bin/copy folder.

```
Unhandled Exception:
System.MissingMethodException: Method 'NUnit.Core.TestSuiteBuilder.Build' not found.


Server stack trace: 
  at (wrapper remoting-invoke-with-check) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at (wrapper xdomain-dispatch) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (object,byte[]&,byte[]&,string,string[])

Exception rethrown at [0]: 
  at (wrapper xdomain-invoke) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at (wrapper remoting-invoke-with-check) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at MonoDevelop.UnitTesting.NUnit.External.RemoteNUnitTestRunner.GetTestInfo (System.String path) [0x00009] in <1ca860bd7d9040a0affd76b5bd2801c6>:0 
  at NUnitRunner.Application.Main (System.String[] args) [0x0005f] in <1ca860bd7d9040a0affd76b5bd2801c6>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.MissingMethodException: Method 'NUnit.Core.TestSuiteBuilder.Build' not found.


Server stack trace: 
  at (wrapper remoting-invoke-with-check) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at (wrapper xdomain-dispatch) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (object,byte[]&,byte[]&,string,string[])

Exception rethrown at [0]: 
  at (wrapper xdomain-invoke) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at (wrapper remoting-invoke-with-check) MonoDevelop.UnitTesting.NUnit.External.NUnitTestRunner:GetTestInfo (string,string[])
  at MonoDevelop.UnitTesting.NUnit.External.RemoteNUnitTestRunner.GetTestInfo (System.String path) [0x00009] in <1ca860bd7d9040a0affd76b5bd2801c6>:0 
  at NUnitRunner.Application.Main (System.String[] args) [0x0005f] in <1ca860bd7d9040a0affd76b5bd2801c6>:0 
```
Comment 1 Aleksey Kliger 2017-10-11 00:20:25 UTC
Here's what I know so far:

In Mono domain.SetData("a", new AddinRegistry()) will cause the runtime to try to load a reference to a nunit.util.dll in the new domain and since it wasn't loaded yet (because the call to SetData happens before the CreateInstanceFromAndUnwrap() of bin/Debug/NUnitRunner.exe ). 

As a result, Mono needs to probe for nunit.util.dll and it looks in the ApplicationBase of the new domain which is bin/copy/.  After that, the domain has bin/copy/nunit.{util,interfaces}.dll loaded first, followed by bin/Debug/nunit.{util,interfaces,core}.dll  And so then it tries to find a method in bin/Debug/nunit.core.dll's TestSuiteBuilder.Build that takes a bin/copy/nunit.interfaces.dll's TestPackage - and no such overload exists.

Next, I need to understand how .NET gets away without probing in bin/copy/ due to the SetData.

(As a workaround if the CreateInstanceFromAndUnwrap happens before the SetData, and make sure to preload nunit.core.dll from bin/Debug/, everything more or less works.)
Comment 2 Aleksey Kliger 2017-10-13 13:59:47 UTC
So the problem is indeed the line `domain.SetData ("a", new AddinRegistry ())` where `domain` is a newly-created domain.

The AddinRegistry object (which is a MarshalByRefObject) is created in the current domain and then we serialize the object reference and try to create a transparent proxy in the newly-created domain.

To do that, we deserialize the object reference and get its type using System.Runtime.RemotingServices.Unmarshal (ObjRef, bool) which is called from System.Runtime.Serialization.DoFixups ().

The Unmarshal() first tries to get the a Type for the serialized server type for the object reference - which means it tries to load the AddinRegistry type from nunit.utils assembly in the new domain using System.Type.GetType (string) (where the string is an assembly-qualified type name).  At that point we're in mono's normal assembly loading mechanism and we ask it for an assembly named "nunit.util" - so it consults the ApplicationBase (bin/copy) and we get the wrong one.

What happens on .NET is that after the SetData even though a proxy object is created, it does not, somehow, trigger a load of nunit.util in the new domain from either path (bin/Debug nor bin/copy).

I am frankly, baffled, at how .NET can do that.

Note You need to log in before you can comment on or make changes to this bug.