cecil
cecil copied to clipboard
Override method resolution does not find base method with generic parameter
Mono.Cecil.Rocks.MethodDefinitionRocks.GetOriginalBaseMethod() doesn't find the base method of an overridden method if the method has a generic parameter. Here's the offending code:
class MyCollection : Collection<string>
{
// base: InsertItem(int, T)
protected override void InsertItem(int index, string item) { }
}
Since Collection<T>.InsertItem has T as its second parameter, and MyCollection.InsertItem has a string parameter there, the two parameter lists are not considered equal in Mono.Cecil.MetadataResolver.AreSame(TypeReference, TypeReference) where the etype is compared.
Since one etype says "Var" and the other "String", would it be okay to accept the difference if one is Var? Or would that be too broad?
The same happens for the return type instead of method parameters.
class BaseClass<T> {
public virtual T GetValue() { return default(T); }
}
class SubClass : BaseClass<int> {
public override int GetValue() { return 1; }
}
T and int are different here so the base method for SubClass.GetValue is not found.
Running it through a larger application, I see it happening only for type parameters that are now filled with an actual type. Here's my suggested fix:
if (a.etype == b.etype ||
a.etype != Metadata.ElementType.Var)
{
// existing checks for etype, GenericParameter, TypeSpecification and so on, until scope
}
I see. Yep, that definitely needs fixing.
The fix is not to change MetadataResolver.AreSame, are Var and String are well, not the same :smile_cat:.
Rather, GetOriginalMethod should check that the method is an instantiation of the base method.
This is really bugging me again now. This very InsertItem override method. I have just upgraded from 0.9.5 to 0.9.6 and now it's broken when it wasn't before. I'm using the hack I've described above. Not sure yet why it has worked and doesn't anymore now. Any news on this? It's simply breaking the assemblies I'm working with and I need to find a solution for that. How would this "GetOriginalMethod should check..." look like?
I noticed that there is such a thing as explicit method overrides in CIL and it should be usable to find a base method in case this is set. I'm going to do some tests but it seems only possible with C++/CLI not C#. If present, I believe the method.Overrides collection should be checked as well. (I've written some code for that.) But that's probably another issue. And unfortunately, the C# compiler (VS 2010) doesn't set the .override directive in the assembly for overridden methods (of the same name) so I can't use it here. Would be nice and simple. :-)
I'm currently in the process of fixing this issue. I can already resolve methods if they are using a single level of GenericParameters. Still trying to get the second level right, like T1 resolving to IEnumerator<T2>. My brain is aching. It's full of generic parameters and generic arguments.
To make this generics resolution truly recursive, I've written a method that only does that resolving of GenericParameters using a list of GenericArguments of some DeclaringType. Unfortunately I can't just replace the GenericParameter I find with its corresponding GenericArgument in the same structure because this affects the entire assembly. There's no Clone method so I can't easily make a copy of the TypeReference.
Maybe extending the AreSame methods for recursive generics resolution would be a better way. Each time an AreSame method gets a GenericParameter and the other part is not, it could look it up to a concrete type and continue the comparison with that instead. It can't always resolve everything in advance because sometimes it gets two GenericParameters and they are the same already.
This is really a major issue here. My fix has worked for a long time, but recently I discovered that certain source assemblies contain structures that it can't handle. I basically need to find the method of a type that implements an interface method. And that is currently impossible with Cecil. Does anybody else do such things? Does anybody use it at all?
Hi all, Any news?
I've also faced with generic params/arg mess. Now doing it other way around: Find interface from type's interfaces, resolve interface, find method from interface, manually construct method reference using params from resolved iface and args from unresolved one. This way you don't need to find exact method implementation from type, if you execute constructed method reference call against original type - it will work.
Running into this issue I believe.
I'm trying to modify the following:
class A : IEnumerable<B> { IEnumerator <B> IEnumerable<B>.GetEnumerator() { ... } }
To become:
class A : IEnumerable<C> { IEnumerator <C> IEnumerable<C>.GetEnumerator() { ... } }
The normal methods, return types, inheritance chain of base types all get modified fine, but when I modify the Overrides of the GetEnumerator method, they don't seem to alter the name of the overriding method itself, so peverify complains it can't find the token.
Reflector shows the modified method on class A as:
.method private hidebysig newslot virtual final instance class [mscorlib]System.Collections.Generic.IEnumerator``1<C> global::System.Collections.Generic.IEnumerable<B>.GetEnumerator() cil managed { .override [mscorlib]System.Collections.Generic.IEnumerable``1<C>::GetEnumerator .maxstack 8 L_0000: ldarg.0 L_0001: newobj instance void A/MyEnumerator::.ctor(class A) L_0006: ret }
Notice that class C appears everywhere, including in the override but the method name still shows class B in the generic.
I take care to use MakeGenericInstanceType and then find the method on the new type, import references, and even use the MakeHostInstanceGeneric() fix I found online, but in this particular case it doesn't seem to correctly change the override in the final output, despite everything looking right while stepping through with the debugger.
peverify reports: A::global::System.Collections.Generic.IEnumerable<B>.GetEnumerator][offset 0x00000001] Unable to resolve token.