dotnet / linker

388 stars 126 forks source link

Add test case for #3112 with pseudo-circular reference when base provides iface method #3120

Closed jtschuster closed 1 year ago

jtschuster commented 1 year ago

Adds a minimal repro for #3112.

The scenario looks like this:

Types; Base type B has method M. Derived type D1 derives from B, and implements interface I1 with methods from B. Derived type D2 derives from B, and implements interface I2 with methods from B.

Assembly A1 has B. Assembly A2 has D1. Assembly A3 has I1 and D2. I2 can be in any assembly.

In the trimmer: D1 is marked instantiated. -> B is marked instantiated (as a base type) -> D1's interfaces are marked, and the implementation methods are marked -> B.M() is marked -> I.M() is added as a base method for B.M()

Later, B.M() is passed to IsMethodNeededByInstantiatedTypeDueToPreservedScope() -> Base methods of B.M() are iterated over -> I.M() is one of the base methods, and is passed to IsMethodNeedeByTypeDueToPreservedScope() -> Base methods of I.M() are required, triggering the entire assembly A3 to be mapped. -> D2 is mapped -> I2.M() is added as a base method of B.M() -- While base methods of B.M() is being iterated over

There is a psuedo-circular assembly reference with the way we build out TypeMapInfo A1 (psuedo-)refers to A3 (when A2 is referenced) because B.M() impls I1.M() A3 refers to A1 because D2 derives from B.

In main the same exact situation doesn't cause issues, and https://github.com/dotnet/linker/pull/3094 fixes this repro for .Net 7, but I don't think a similar situation is guaranteed to never happen. Since base methods can provide interface members, the list of base methods of a type is dependent on which assemblies have been processed by TypeMapInfo at the time GetBaseMethods is called.

jtschuster commented 1 year ago

I assume you tested that this breaks the compiler with shipping 7.0.

Yes, it crashes the release/7.0 linker.