Can't save assembly with calls to non-public members
SvenGroot opened this issue · 8 comments
I was trying to generate a dynamic assembly containing a type that inherits from another type whose constructor is protected. This fails with an exception in AssemblyMetadata.GetMethodOrConstructorSignature
because that method only considers public methods and constructors. The fix is to pass the appropriate flags to GetMethods/GetConstructors. That would probably be BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static
for GetMethods and BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
for GetConstructors.
Hi @SvenGroot, Thanks a lot for reporting this issue! The problem is probably in AssemblyMetadata.Methods.cs
line 64 (with probably a similar issue for methods 2 lines above). I will try to find the time to push a fix. PR are welcome as well.
I was trying to add a unit test for this so I could make a pr but if the base class is also in the generated assembly it seems to hit some other bugs.
If the base class is in a referenced assembly (as is the case in my scenario), the change as you described is sufficient, but there's no easy way to add a test for that it seems.
I have a minimal code sample that seems to trigger the problem:
public class ClassWithProtectedCtor<T>
{
protected ClassWithProtectedCtor(int foo) { }
}
public class ClassCallingProtectedCtor : ClassWithProtectedCtor<int>
{
public ClassCallingProtectedCtor(int foo) : base(foo) { }
}
Making the protected
constructor generic appears to be necessary to get ILPack to fail, however, merely adding the extra binding options (as discussed above) does not address the problem.
Update: redefining AssemblyMetadata.TryGetConstructorDefinition
with the following seems to improve the situation:
public bool TryGetConstructorDefinition(ConstructorInfo ctor, out MethodBaseDefinitionMetadata metadata)
{
if (ctor.DeclaringType.IsConstructedGenericType)
{
// HACK: [vermorel] Unclear how to get the original constructor from the open type
// See https://stackoverflow.com/questions/43850948/with-constructorinfo-from-a-constructed-generic-type-how-to-i-get-the-matchin
var open = ctor.DeclaringType.GetGenericTypeDefinition().GetConstructors(AllMethods);
foreach (var ci in open)
{
if (ci.MetadataToken == ctor.MetadataToken)
{
ctor = ci;
break;
}
}
}
return _ctorDefHandles.TryGetValue(ctor, out metadata);
}
But I am not getting another error Metadata table GenericParam not sorted.
Yes, that is what I was referring to. In my scenario, ClassWithProtectedCtor<T>
would be defined in an external assembly that is referenced by the generated assembly, in which case you run into the issue I described above.
If ClassWithProtectedCtor<T>
is in the generated assembly (as it is in your sample), you run into a bunch of other issues, which you're discovering.
The fix to my issue is very simple, but harder to test because it would require adding another class library for the generated assembly to reference. The fix to the other issues seems to be more involved.
@SvenGroot My latest commit might have solved your problem. Let me know.
@vermorel Sorry, no, I still hit the same problem. Remember that in my case the generic type I'm inheriting from is defined in an external assembly, so it's hitting a slightly different path than the test. You still need to make the change I originally suggested.
This is the call stack of my failure, in case that helps:
Message:
System.InvalidOperationException : Sequence contains no matching element
Stack Trace:
ThrowHelper.ThrowNoMatchException()
Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
AssemblyMetadata.GetMethodOrConstructorSignature(MethodBase methodBase) line 64
AssemblyMetadata.ResolveConstructorReference(ConstructorInfo ctor) line 41
AssemblyMetadata.GetConstructorHandle(ConstructorInfo ctor) line 21
MethodBodyWriter.Write(IAssemblyMetadata metadata, IReadOnlyList`1 il) line 106
MethodBodyStreamWriter.AddMethodBody(MethodBase methodBase, StandaloneSignatureHandle localVariablesSignature) line 46
AssemblyGenerator.CreateConstructor(ConstructorInfo ctor) line 40
AssemblyGenerator.CreateConstructors(IEnumerable`1 constructors) line 59
AssemblyGenerator.CreateType(Type type, List`1 genericParams) line 158
AssemblyGenerator.CreateTypes(IEnumerable`1 types, List`1 genericParams) line 31
AssemblyGenerator.CreateModules(IEnumerable`1 moduleInfo) line 25
AssemblyGenerator.GenerateAssemblyBytes(Assembly assembly) line 112
AssemblyGenerator.GenerateAssembly(Assembly assembly, String path) line 143
Thanks, that last change has fixed my issue!
Now, if you could release an updated package on NuGet with these fixes, I would be eternally grateful. 😊
The Nuget 0.1.3
is become visible soon. Thanks for your kind word.