This project is read-only.

Resolving Metadata Tokens to IPrimarySourceLocation in CCI?

Topics: PDB Reader, PE reader
Jul 17, 2012 at 12:36 PM

Hi!

I was wondering what the best way to resolve a metadata token (as retrieved via reflection) in CCI to a source location.

The use case is that I have a file containing information about types/members (and each of those includes a metadata token). 

I know that it is possible to use a method metadata token to retrieve the source location as shown in this thread: http://ccimetadata.codeplex.com/discussions/361167 but that doesn't fulfill my use case 100%.

I would either need a way to retrieve a source location for ANY metadata token (mainly type/property/event additionally to method) or a way to access the method metadata tokens of a type (ie. first method), property (ie. get accessor method), event (ie. add accessor method).

I think the second approach is more realistic since the metadata tokens for properties/events are not included in a PDB file as far as I know and therefore can't be retrieved just using PdbReader. 

So basically the question is: can I directly resolve a metadata token using PeReader or do I have to traverse a whole assembly till I find the member/type with the right token?

Also on a sidenote: is there any specific reason why you don't make the TokenValue property publicly accessible? To get this from CCI I know have to use reflection (which is quite slow) or a modified CCI version with the InternalsVisibleTo attribute set (which I find imposes quite a burden on my project since I have to maintain my own CCI version just for accessing one member).

Thanks in advance and best regards,

Chris "Calavera" Schuster

Jul 17, 2012 at 6:45 PM

There are two interfaces that help with this:

  /// <summary>
  /// Implemented by metadata objects that have been obtained from a CLR PE file.
  /// </summary>
  public interface IMetadataObjectWithToken {
    /// <summary>
    /// The most significant byte identifies a metadata table, using the values specified by ECMA-335.
    /// The least significant three bytes represent the row number in the table, with the first row being numbered as one.
    /// If, for some implementation reason, a metadata object implements this interface but was not obtained from a metadata table
    /// (for example it might be an array type reference that only occurs in a signature blob), the value is UInt32.MaxValue.
    /// </summary>
    uint TokenValue { get; }
  }

  /// <summary>
  /// Implemented by methods that can turn tokens into metadata objects. For example, a method definition implemented
  /// by a metadata reader might implement this interface.
  /// </summary>
  public interface ITokenDecoder : IMethodDefinition {
    /// <summary>
    /// Returns a metadata model object that corresponds to this token value.
    /// If no such object can be found then the result is null.
    /// </summary>
    object/*?*/ GetObjectForToken(uint token);
  }

You can cast any definition coming from PEReader to the first interface, but only method definitions to the second (this should be fixed one day).

These interfaces are not required to be implemented by any IDefinition, since object models can come from sources other than a metadata reader, in which case tokens are not meaningful. 


Jul 18, 2012 at 12:23 PM

So basically there is no way to get the IDefinition for a given metadata token other than looping through all definitions and comparing the tokens?

Additionally I think it would be probably a good idea to adapt the documentation comments to reflect which type of metadata token a method accepts. The wording "Returns a metadata model object that corresponds to this token value." leads me to believe that I can pass ANY kind of metadata token in and get a useful result.

A further problem (in my opinion) is that the methods that take such a method token as parameter often return null if the metadata token was of the wrong type. That in addition to the aforementioned unclear documentation comment creates the illusion that I did everything right and CCI just wasn't able to find something for the given metadata token when in reality I passed in a wrong value. Normally this kind of error results in an ArgumentException and I think it would be good if CCI also handled those errors like that (if possible).

Thanks for the quick answer and best regards!

Jul 18, 2012 at 7:48 PM

You currently have to traverse the OM until you find a method body. Once you have that, the ITokenDecoder interface you can obtain from it (via a downcast to the interface) should be able to decode any token that comes from the module in which the method is defined.

The CCI interface design avoids the use of exceptions as an error reporting mechanism. Most methods never return null, so if a method is annotated and documented to return null, the caller is obligated to check for the null result and do something about it. Much like you would have to write a catch clause to deal with the exception.

Every design takes getting used to and has its pros and cons. For better or for worse, however, chaning this design is no longer an option.

Jul 19, 2012 at 1:19 PM

I submitted a patch that contains the implementation I now use to resolve metadata tokens (it has the id 12602).

I tried to only add functionality and don't change existing one to allow for maximum compatibility.