Hi all -
If someone is interested, here (at the end of this posting) is the
code I plan to use in my call graph machine. It is a modification of
Mono.Linker.Steps.TypeMapStep, to which Jb pointed me.
The changes from the original code are:
(a) It's an abstract class that has an abstract OnFoundMatch(MethodDef
@base, MethodDef @override). Therefore, a bunch of methods is now non-
static.
(b) If there are more interfaces with a matching method,
TypeMapStep.GetBaseMethodInInterfaceHierarchy returns the method of
only the first interface. With a call graph, we need all matching
methods in all base interfaces. Example:
interface I1 { void M(); }
interface I2 { void M(); }
class C : I1, I2 { public void M() {} }
In this code, we want to know that C.M() implements I1.M() as well as
I2.M().
Therefore, GetBaseMethodInInterfaceHierarchy() now returns an
IEnumerable<MethodDefinition>; and calls to it now iterate over the
result.
(c) I also want to get all potential calls from "mixing in interface
method implementations", so I need also these "strange
implementations." Here is an example of valid C# code:
interface I1 { void M(); }
class C { public void M() {} }
class CDerived : C {}
Here, I1.M() is implemented by C.M() in CDerived, although I1 and C do
not have any direct knowledge of each other. To find all these cases,
I added the following code to MapVirtualMethods:
// Go to each base class ...
for (var @base = GetBaseType(type); @base != null; @base =
GetBaseType(@base)) {
foreach (MethodDefinition method in @base.Methods) {
if (!method.IsVirtual)
continue;
// ... and map its methods to the methods of all
the current(!) type's interfaces.
MapVirtualInterfaceMethod(type, method);
}
}
To do this, I gave MapVirtualInterfaceMethod an additional parameter,
namely the type from which it should take the implementation method;
the single-parameter version of GetBaseMethodInInterfaceHierarchy
became useless after this. In addition, the guard at the top of
MapVirtualMethods which returns immediately if no methods are present
is removed so that the new for loop runs also in this case (which is
e.g. necessary for the given example).
----------
The code below does not handle everything perfectly - and moreover, I
have not tested it except for some demo cases. Momentarily, the idea
is not to miss any potential overrides; but maybe accept a few wrong
ones. Here are two examples where the code gives a "false positive":
a.
interface I { void M(); }
abstract class C {
public virtual void M() { }
}
abstract class D : C { }
abstract class E : D {
public new virtual void M() { }
}
class F : E, I { }
Here, C.M() cannot implement I.M() if we make the "closed-world
assumption" that we know all classes in the universe (which I assume
with tools that look "downwards" in the inheritance tree). The
MethodMatcher, yet, says that (a) C.M() implements I.M(); and (b) that
E.M() overrides C.M(). Precise tracking of hidings is, I think,
necessary to find out which methods are NOT overrides or implements in
such cases.
b. An interesting piece of code in C#:
interface I<T> {
T M(T p);
}
abstract class C<T> : I<T> {
public abstract T M(T p);
}
abstract class D<T> : C<T>, I<int> {
public override T M(T p) { return p; }
public int M(int p) { return 10; }
}
class E : D<int> { }
Now, we can create an E and call M(5) on it. The interesting question
then is: Which M() is called? ;-) ...
... there is a general rule somewhere that spelt-out parameter types
are a better match than generic parameters. Therefore, the second
method (where int is spelt out) is called. I found no way to call the
first implementation of M in class D via an object of class E - is
there one?
On the other hand, the current MethodMatcher says that also the first
implementation implementents I<T>'s M() method - which is therefore
"somewhat true" (leaving it out gives a compile time error) and
"somewhat false" (a call to I<T> will never call that implementation
[it appears]).
(With two interfaces, that sort of thing is NOT allowed:
interface I<T> {
T M(T p);
}
abstract class D<T> : I<T>, I<int> {
public T M(T p) { return p; }
public int M(int p) { return 10; }
}
class E : D<int> { }
yields (in Visual Studio 2010 beta) Error: 'D<T>' cannot implement
both 'I<int>' and 'I<T>' because they may unify for some type
parameter substitutions. This does not even work if one specifies
"where T : class" which would guarantee that T can never be int.)
Ok - so much for the moment. Below is the code.
Regards
Harald M.
----------
using System.Collections.Generic;
using Mono.Cecil;
/// <summary>
/// Resolution of overrides and implements of methods.
/// Copied and modified from Mono's Mono.Linker.Steps.TypeMapStep
by Jb Evain ([email protected])
/// </summary>
public abstract class MethodMatcher {
protected abstract void OnFoundMatch(MethodDefinition @base,
MethodDefinition @override);
public void ProcessAssembly(AssemblyDefinition assembly) {
foreach (TypeDefinition type in assembly.MainModule.Types)
MapType(type);
}
void MapType(TypeDefinition type) {
MapVirtualMethods(type);
}
void MapVirtualMethods(TypeDefinition type) {
foreach (MethodDefinition method in type.Methods) {
if (!method.IsVirtual)
continue;
MapVirtualMethod(method);
if (method.HasOverrides)
MapOverrides(method);
}
// Go to each base class ...
for (var @base = GetBaseType(type); @base != null; @base =
GetBaseType(@base)) {
foreach (MethodDefinition method in @base.Methods) {
if (!method.IsVirtual)
continue;
// ... and map its methods to the methods of all
the current(!) type's interfaces.
MapVirtualInterfaceMethod(type, method);
}
}
}
void MapVirtualMethod(MethodDefinition method) {
MapVirtualBaseMethod(method);
MapVirtualInterfaceMethod(method.DeclaringType, method);
}
void MapVirtualBaseMethod(MethodDefinition method) {
MethodDefinition @base = GetBaseMethodInTypeHierarchy
(method);
if (@base == null)
return;
AnnotateMethods(@base, method);
}
void MapVirtualInterfaceMethod(TypeDefinition type,
MethodDefinition method) {
foreach (var @base in GetBaseMethodInInterfaceHierarchy
(type, method))
AnnotateMethods(@base, method);
}
void MapOverrides(MethodDefinition method) {
foreach (MethodReference overrideRef in method.Overrides)
{
MethodDefinition @override = overrideRef.Resolve();
if (@override == null)
continue;
AnnotateMethods(@override, method);
}
}
void AnnotateMethods(MethodDefinition @base, MethodDefinition
@override) {
OnFoundMatch(@base, @override);
}
static MethodDefinition GetBaseMethodInTypeHierarchy
(MethodDefinition method) {
TypeDefinition @base = GetBaseType(method.DeclaringType);
while (@base != null) {
MethodDefinition baseMethod = TryMatchMethod(@base,
method);
if (baseMethod != null)
return baseMethod;
@base = GetBaseType(@base);
}
return null;
}
static IEnumerable<MethodDefinition>
GetBaseMethodInInterfaceHierarchy(TypeDefinition type,
MethodDefinition method) {
foreach (TypeReference interfaceRef in type.Interfaces) {
TypeDefinition @interface = interfaceRef.Resolve();
if (@interface == null)
continue;
MethodDefinition baseMethod = TryMatchMethod
(@interface, method);
if (baseMethod != null)
yield return baseMethod;
foreach (var md in GetBaseMethodInInterfaceHierarchy
(@interface, method))
yield return md;
}
}
static MethodDefinition TryMatchMethod(TypeDefinition type,
MethodDefinition method) {
if (!type.HasMethods)
return null;
foreach (MethodDefinition candidate in type.Methods)
if (MethodMatch(candidate, method))
return candidate;
return null;
}
static bool MethodMatch(MethodDefinition candidate,
MethodDefinition method) {
if (!candidate.IsVirtual)
return false;
if (candidate.Name != method.Name)
return false;
if (!TypeMatch(candidate.ReturnType.ReturnType,
method.ReturnType.ReturnType))
return false;
if (candidate.Parameters.Count != method.Parameters.Count)
return false;
for (int i = 0; i < candidate.Parameters.Count; i++)
if (!TypeMatch(candidate.Parameters[i].ParameterType,
method.Parameters[i].ParameterType))
return false;
return true;
}
static bool TypeMatch(ModType a, ModType b) {
if (!TypeMatch(a.ModifierType, b.ModifierType))
return false;
return TypeMatch(a.ElementType, b.ElementType);
}
static bool TypeMatch(TypeSpecification a, TypeSpecification
b) {
if (a is GenericInstanceType)
return TypeMatch((GenericInstanceType)a,
(GenericInstanceType)b);
if (a is ModType)
return TypeMatch((ModType)a, (ModType)b);
return TypeMatch(a.ElementType, b.ElementType);
}
static bool TypeMatch(GenericInstanceType a,
GenericInstanceType b) {
if (!TypeMatch(a.ElementType, b.ElementType))
return false;
if (a.GenericArguments.Count != b.GenericArguments.Count)
return false;
if (a.GenericArguments.Count == 0)
return true;
for (int i = 0; i < a.GenericArguments.Count; i++)
if (!TypeMatch(a.GenericArguments[i],
b.GenericArguments[i]))
return false;
return true;
}
static bool TypeMatch(TypeReference a, TypeReference b) {
if (a is GenericParameter)
return true;
if (a is TypeSpecification || b is TypeSpecification) {
if (a.GetType() != b.GetType())
return false;
return TypeMatch((TypeSpecification)a,
(TypeSpecification)b);
}
return a.FullName == b.FullName;
}
static TypeDefinition GetBaseType(TypeDefinition type) {
if (type == null || type.BaseType == null)
return null;
return type.BaseType.Resolve();
}
}
--
--
mono-cecil