// // mono-api-info.cs - Dumps public assembly information to an xml file. // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // Copyright (C) 2003-2008 Novell, Inc (http://www.novell.com) // using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Xml; using Mono.Cecil; using Mono.Cecil.Cil; using System.IO; namespace CorCompare { public class Driver { public static int Main (string [] args) { bool showHelp = false; AbiMode = false; FollowForwarders = false; var acoll = new AssemblyCollection (); var options = new Mono.Options.OptionSet { "usage: mono-api-info [OPTIONS+] ASSEMBLY+", "", "Expose IL structure of CLR assemblies as XML.", "", "Available Options:", { "abi", "Generate ABI, not API; contains only classes with instance fields which are not [NonSerialized].", v => AbiMode = v != null }, { "f|follow-forwarders", "Follow type forwarders.", v => FollowForwarders = v != null }, { "d|L|lib|search-directory=", "Check for assembly references in {DIRECTORY}.", v => TypeHelper.Resolver.AddSearchDirectory (v) }, { "r=", "Read and register the file {ASSEMBLY}, and add the directory containing ASSEMBLY to the search path.", v => TypeHelper.Resolver.ResolveFile (v) }, { "h|?|help", "Show this message and exit.", v => showHelp = v != null }, }; var asms = options.Parse (args); if (showHelp || asms.Count == 0) { options.WriteOptionDescriptions (Console.Out); Console.WriteLine (); return showHelp? 0 :1; } string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); string pf = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"assembly\GAC\MSDATASRC\7.0.3300.0__b03f5f7f11d50a3a")); foreach (string arg in asms) { acoll.Add (arg); if (arg.Contains ("v3.0")) { TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"Microsoft.NET\Framework\v2.0.50727")); } else if (arg.Contains ("v3.5")) { TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"Microsoft.NET\Framework\v2.0.50727")); TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"Microsoft.NET\Framework\v3.0\Windows Communication Foundation")); } else if (arg.Contains ("v4.0")) { if (arg.Contains ("Silverlight")) { TypeHelper.Resolver.AddSearchDirectory (Path.Combine (pf, @"Microsoft Silverlight\4.0.51204.0")); } else { TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"Microsoft.NET\Framework\v4.0.30319")); TypeHelper.Resolver.AddSearchDirectory (Path.Combine (windir, @"Microsoft.NET\Framework\v4.0.30319\WPF")); } } else { TypeHelper.Resolver.AddSearchDirectory (Path.GetDirectoryName (arg)); } } XmlDocument doc = new XmlDocument (); acoll.Document = doc; acoll.DoOutput (); var writer = new WellFormedXmlWriter (new XmlTextWriter (Console.Out) { Formatting = Formatting.Indented }); XmlNode decl = doc.CreateXmlDeclaration ("1.0", "utf-8", null); doc.InsertBefore (decl, doc.DocumentElement); doc.WriteTo (writer); return 0; } internal static bool AbiMode { get; private set; } internal static bool FollowForwarders { get; private set; } } public class Utils { public static string CleanupTypeName (TypeReference type) { return CleanupTypeName (type.FullName); } public static string CleanupTypeName (string t) { return t.Replace ('<', '[').Replace ('>', ']').Replace ('/', '+'); } } class AssemblyCollection { XmlDocument document; List assemblies = new List (); public AssemblyCollection () { } public bool Add (string name) { AssemblyDefinition ass = LoadAssembly (name); if (ass == null) { Console.Error.WriteLine ("Cannot load assembly file " + name); return false; } assemblies.Add (ass); return true; } public void DoOutput () { if (document == null) throw new InvalidOperationException ("Document not set"); XmlNode nassemblies = document.CreateElement ("assemblies", null); document.AppendChild (nassemblies); foreach (AssemblyDefinition a in assemblies) { AssemblyData data = new AssemblyData (document, nassemblies, a); data.DoOutput (); } } public XmlDocument Document { set { document = value; } } AssemblyDefinition LoadAssembly (string assembly) { try { if (File.Exists (assembly)) return TypeHelper.Resolver.ResolveFile (assembly); return TypeHelper.Resolver.Resolve (assembly); } catch (Exception e) { Console.WriteLine (e); return null; } } } abstract class BaseData { protected XmlDocument document; protected XmlNode parent; protected BaseData (XmlDocument doc, XmlNode parent) { this.document = doc; this.parent = parent; } public abstract void DoOutput (); protected void AddAttribute (XmlNode node, string name, string value) { XmlAttribute attr = document.CreateAttribute (name); attr.Value = value; node.Attributes.Append (attr); } } class TypeForwardedToData : BaseData { AssemblyDefinition ass; public TypeForwardedToData (XmlDocument document, XmlNode parent, AssemblyDefinition ass) : base (document, parent) { this.ass = ass; } public override void DoOutput () { XmlNode natts = parent.SelectSingleNode("attributes"); if (natts == null) { natts = document.CreateElement ("attributes", null); parent.AppendChild (natts); } foreach (ExportedType type in ass.MainModule.ExportedTypes) { if (((uint)type.Attributes & 0x200000u) == 0) continue; XmlNode node = document.CreateElement ("attribute"); AddAttribute (node, "name", typeof (TypeForwardedToAttribute).FullName); XmlNode properties = node.AppendChild (document.CreateElement ("properties")); XmlNode property = properties.AppendChild (document.CreateElement ("property")); AddAttribute (property, "name", "Destination"); AddAttribute (property, "value", Utils.CleanupTypeName (type.FullName)); natts.AppendChild (node); } } public static void OutputForwarders (XmlDocument document, XmlNode parent, AssemblyDefinition ass) { TypeForwardedToData tftd = new TypeForwardedToData (document, parent, ass); tftd.DoOutput (); } } class AssemblyData : BaseData { AssemblyDefinition ass; public AssemblyData (XmlDocument document, XmlNode parent, AssemblyDefinition ass) : base (document, parent) { this.ass = ass; } public override void DoOutput () { if (document == null) throw new InvalidOperationException ("Document not set"); XmlNode nassembly = document.CreateElement ("assembly", null); AssemblyNameDefinition aname = ass.Name; AddAttribute (nassembly, "name", aname.Name); AddAttribute (nassembly, "version", aname.Version.ToString ()); parent.AppendChild (nassembly); if (!Driver.FollowForwarders) { TypeForwardedToData.OutputForwarders (document, nassembly, ass); } AttributeData.OutputAttributes (document, nassembly, ass); var types = new List (); if (ass.MainModule.Types != null) { types.AddRange (ass.MainModule.Types); } if (Driver.FollowForwarders && ass.MainModule.ExportedTypes != null) { foreach (var t in ass.MainModule.ExportedTypes) { var forwarded = t.Resolve (); if (forwarded == null) { throw new Exception ("Could not resolve forwarded type " + t.FullName + " in " + ass.Name); } types.Add (forwarded); } } if (types.Count == 0) { return; } types.Sort (TypeReferenceComparer.Default); XmlNode nss = document.CreateElement ("namespaces", null); nassembly.AppendChild (nss); string current_namespace = "$%&$&"; XmlNode ns = null; XmlNode classes = null; foreach (TypeDefinition t in types) { if (string.IsNullOrEmpty (t.Namespace)) continue; if (!Driver.AbiMode && ((t.Attributes & TypeAttributes.VisibilityMask) != TypeAttributes.Public)) continue; if (t.DeclaringType != null) continue; // enforce !nested if (t.Namespace != current_namespace) { current_namespace = t.Namespace; ns = document.CreateElement ("namespace", null); AddAttribute (ns, "name", current_namespace); nss.AppendChild (ns); classes = document.CreateElement ("classes", null); ns.AppendChild (classes); } TypeData bd = new TypeData (document, classes, t); bd.DoOutput (); } } } abstract class MemberData : BaseData { MemberReference [] members; public MemberData (XmlDocument document, XmlNode parent, MemberReference [] members) : base (document, parent) { this.members = members; } public override void DoOutput () { XmlNode mclass = document.CreateElement (ParentTag, null); parent.AppendChild (mclass); foreach (MemberReference member in members) { XmlNode mnode = document.CreateElement (Tag, null); mclass.AppendChild (mnode); AddAttribute (mnode, "name", GetName (member)); if (!NoMemberAttributes) AddAttribute (mnode, "attrib", GetMemberAttributes (member)); AttributeData.OutputAttributes (document, mnode, (ICustomAttributeProvider) member); AddExtraData (mnode, member); } } protected virtual void AddExtraData (XmlNode p, MemberReference memberDefenition) { } protected virtual string GetName (MemberReference memberDefenition) { return "NoNAME"; } protected virtual string GetMemberAttributes (MemberReference memberDefenition) { return null; } public virtual bool NoMemberAttributes { get { return false; } set {} } public virtual string ParentTag { get { return "NoPARENTTAG"; } } public virtual string Tag { get { return "NoTAG"; } } public static void OutputGenericParameters (XmlDocument document, XmlNode nclass, IGenericParameterProvider provider) { if (provider.GenericParameters.Count == 0) return; var gparameters = provider.GenericParameters; XmlElement ngeneric = document.CreateElement ("generic-parameters"); nclass.AppendChild (ngeneric); foreach (GenericParameter gp in gparameters) { XmlElement nparam = document.CreateElement ("generic-parameter"); nparam.SetAttribute ("name", gp.Name); nparam.SetAttribute ("attributes", ((int) gp.Attributes).ToString ()); AttributeData.OutputAttributes (document, nparam, gp); ngeneric.AppendChild (nparam); var constraints = gp.Constraints; if (constraints.Count == 0) continue; XmlElement nconstraint = document.CreateElement ("generic-parameter-constraints"); foreach (TypeReference constraint in constraints) { XmlElement ncons = document.CreateElement ("generic-parameter-constraint"); ncons.SetAttribute ("name", Utils.CleanupTypeName (constraint)); nconstraint.AppendChild (ncons); } nparam.AppendChild (nconstraint); } } } class TypeData : MemberData { TypeDefinition type; public TypeData (XmlDocument document, XmlNode parent, TypeDefinition type) : base (document, parent, null) { this.type = type; } public override void DoOutput () { if (document == null) throw new InvalidOperationException ("Document not set"); XmlNode nclass = document.CreateElement ("class", null); AddAttribute (nclass, "name", type.Name); string classType = GetClassType (type); AddAttribute (nclass, "type", classType); if (type.BaseType != null) AddAttribute (nclass, "base", Utils.CleanupTypeName (type.BaseType)); if (type.IsSealed) AddAttribute (nclass, "sealed", "true"); if (type.IsAbstract) AddAttribute (nclass, "abstract", "true"); if ( (type.Attributes & TypeAttributes.Serializable) != 0 || type.IsEnum) AddAttribute (nclass, "serializable", "true"); string charSet = GetCharSet (type); AddAttribute (nclass, "charset", charSet); string layout = GetLayout (type); if (layout != null) AddAttribute (nclass, "layout", layout); if (type.PackingSize >= 0) { AddAttribute (nclass, "pack", type.PackingSize.ToString ()); } if (type.ClassSize >= 0) { AddAttribute (nclass, "size", type.ClassSize.ToString ()); } parent.AppendChild (nclass); AttributeData.OutputAttributes (document, nclass, type); XmlNode ifaces = null; foreach (TypeReference iface in TypeHelper.GetInterfaces (type).OrderBy (s => s.FullName)) { if (!TypeHelper.IsPublic (iface)) // we're only interested in public interfaces continue; if (ifaces == null) { ifaces = document.CreateElement ("interfaces", null); nclass.AppendChild (ifaces); } XmlNode iface_node = document.CreateElement ("interface", null); AddAttribute (iface_node, "name", Utils.CleanupTypeName (iface)); ifaces.AppendChild (iface_node); } MemberData.OutputGenericParameters (document, nclass, type); ArrayList members = new ArrayList (); FieldDefinition [] fields = GetFields (type); if (fields.Length > 0) { Array.Sort (fields, MemberReferenceComparer.Default); FieldData fd = new FieldData (document, nclass, fields); members.Add (fd); } if (type.IsEnum) { var value_type = GetEnumValueField (type); if (value_type == null) throw new NotSupportedException (); AddAttribute (nclass, "enumtype", Utils.CleanupTypeName (value_type.FieldType)); } if (!Driver.AbiMode) { MethodDefinition [] ctors = GetConstructors (type); if (ctors.Length > 0) { Array.Sort (ctors, MethodDefinitionComparer.Default); members.Add (new ConstructorData (document, nclass, ctors)); } PropertyDefinition[] properties = GetProperties (type); if (properties.Length > 0) { Array.Sort (properties, PropertyDefinitionComparer.Default); members.Add (new PropertyData (document, nclass, properties)); } EventDefinition [] events = GetEvents (type); if (events.Length > 0) { Array.Sort (events, MemberReferenceComparer.Default); members.Add (new EventData (document, nclass, events)); } MethodDefinition [] methods = GetMethods (type); if (methods.Length > 0) { Array.Sort (methods, MethodDefinitionComparer.Default); members.Add (new MethodData (document, nclass, methods)); } } foreach (MemberData md in members) md.DoOutput (); var nested = type.NestedTypes; //remove non public(familiy) and nested in second degree for (int i = nested.Count - 1; i >= 0; i--) { TypeDefinition t = nested [i]; if ((t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic || (t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily || (t.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem) { // public if (t.DeclaringType == type) continue; // not nested of nested } nested.RemoveAt (i); } if (nested.Count > 0) { var nestedArray = nested.ToArray (); Array.Sort (nestedArray, TypeReferenceComparer.Default); XmlNode classes = document.CreateElement ("classes", null); nclass.AppendChild (classes); foreach (TypeDefinition t in nestedArray) { TypeData td = new TypeData (document, classes, t); td.DoOutput (); } } } static FieldReference GetEnumValueField (TypeDefinition type) { foreach (FieldDefinition field in type.Fields) if (field.IsSpecialName && field.Name == "value__") return field; return null; } protected override string GetMemberAttributes (MemberReference member) { if (member != type) throw new InvalidOperationException ("odd"); return ((int) type.Attributes).ToString (CultureInfo.InvariantCulture); } public static bool MustDocumentMethod (MethodDefinition method) { // All other methods MethodAttributes maskedAccess = method.Attributes & MethodAttributes.MemberAccessMask; return maskedAccess == MethodAttributes.Public || maskedAccess == MethodAttributes.Family || maskedAccess == MethodAttributes.FamORAssem; } static string GetClassType (TypeDefinition t) { if (t.IsEnum) return "enum"; if (t.IsValueType) return "struct"; if (t.IsInterface) return "interface"; if (TypeHelper.IsDelegate(t)) return "delegate"; if (t.IsPointer) return "pointer"; return "class"; } static string GetCharSet (TypeDefinition type) { TypeAttributes maskedStringFormat = type.Attributes & TypeAttributes.StringFormatMask; if (maskedStringFormat == TypeAttributes.AnsiClass) return CharSet.Ansi.ToString (); if (maskedStringFormat == TypeAttributes.AutoClass) return CharSet.Auto.ToString (); if (maskedStringFormat == TypeAttributes.UnicodeClass) return CharSet.Unicode.ToString (); return CharSet.None.ToString (); } static string GetLayout (TypeDefinition type) { TypeAttributes maskedLayout = type.Attributes & TypeAttributes.LayoutMask; if (maskedLayout == TypeAttributes.AutoLayout) return LayoutKind.Auto.ToString (); if (maskedLayout == TypeAttributes.ExplicitLayout) return LayoutKind.Explicit.ToString (); if (maskedLayout == TypeAttributes.SequentialLayout) return LayoutKind.Sequential.ToString (); return null; } FieldDefinition [] GetFields (TypeDefinition type) { ArrayList list = new ArrayList (); var fields = type.Fields; foreach (FieldDefinition field in fields) { if (field.IsSpecialName) continue; if (Driver.AbiMode && field.IsStatic) continue; // we're only interested in public or protected members FieldAttributes maskedVisibility = (field.Attributes & FieldAttributes.FieldAccessMask); if (Driver.AbiMode && !field.IsNotSerialized) { list.Add (field); } else { if (maskedVisibility == FieldAttributes.Public || maskedVisibility == FieldAttributes.Family || maskedVisibility == FieldAttributes.FamORAssem) { list.Add (field); } } } return (FieldDefinition []) list.ToArray (typeof (FieldDefinition)); } internal static PropertyDefinition [] GetProperties (TypeDefinition type) { ArrayList list = new ArrayList (); var properties = type.Properties;//type.GetProperties (flags); foreach (PropertyDefinition property in properties) { MethodDefinition getMethod = property.GetMethod; MethodDefinition setMethod = property.SetMethod; bool hasGetter = (getMethod != null) && MustDocumentMethod (getMethod); bool hasSetter = (setMethod != null) && MustDocumentMethod (setMethod); // if neither the getter or setter should be documented, then // skip the property if (hasGetter || hasSetter) { list.Add (property); } } return (PropertyDefinition []) list.ToArray (typeof (PropertyDefinition)); } private MethodDefinition[] GetMethods (TypeDefinition type) { ArrayList list = new ArrayList (); var methods = type.Methods;//type.GetMethods (flags); foreach (MethodDefinition method in methods) { if (method.IsSpecialName && !method.Name.StartsWith ("op_")) continue; // we're only interested in public or protected members if (!MustDocumentMethod(method)) continue; if (IsFinalizer (method)) { string name = method.DeclaringType.Name; int arity = name.IndexOf ('`'); if (arity > 0) name = name.Substring (0, arity); method.Name = "~" + name; } list.Add (method); } return (MethodDefinition []) list.ToArray (typeof (MethodDefinition)); } static bool IsFinalizer (MethodDefinition method) { if (method.Name != "Finalize") return false; if (!method.IsVirtual) return false; if (method.Parameters.Count != 0) return false; return true; } private MethodDefinition [] GetConstructors (TypeDefinition type) { ArrayList list = new ArrayList (); var ctors = type.Methods.Where (m => m.IsConstructor);//type.GetConstructors (flags); foreach (MethodDefinition constructor in ctors) { // we're only interested in public or protected members if (!MustDocumentMethod(constructor)) continue; list.Add (constructor); } return (MethodDefinition []) list.ToArray (typeof (MethodDefinition)); } private EventDefinition[] GetEvents (TypeDefinition type) { ArrayList list = new ArrayList (); var events = type.Events;//type.GetEvents (flags); foreach (EventDefinition eventDef in events) { MethodDefinition addMethod = eventDef.AddMethod;//eventInfo.GetAddMethod (true); if (addMethod == null || !MustDocumentMethod (addMethod)) continue; list.Add (eventDef); } return (EventDefinition []) list.ToArray (typeof (EventDefinition)); } } class FieldData : MemberData { public FieldData (XmlDocument document, XmlNode parent, FieldDefinition [] members) : base (document, parent, members) { } protected override string GetName (MemberReference memberDefenition) { FieldDefinition field = (FieldDefinition) memberDefenition; return field.Name; } protected override string GetMemberAttributes (MemberReference memberDefenition) { FieldDefinition field = (FieldDefinition) memberDefenition; return ((int) field.Attributes).ToString (CultureInfo.InvariantCulture); } protected override void AddExtraData (XmlNode p, MemberReference memberDefenition) { base.AddExtraData (p, memberDefenition); FieldDefinition field = (FieldDefinition) memberDefenition; AddAttribute (p, "fieldtype", Utils.CleanupTypeName (field.FieldType)); if (field.IsLiteral) { object value = field.Constant;//object value = field.GetValue (null); string stringValue = null; //if (value is Enum) { // // FIXME: when Mono bug #60090 has been // // fixed, we should just be able to use // // Convert.ToString // stringValue = ((Enum) value).ToString ("D", CultureInfo.InvariantCulture); //} //else { stringValue = Convert.ToString (value, CultureInfo.InvariantCulture); //} if (stringValue != null) AddAttribute (p, "value", stringValue); } } public override string ParentTag { get { return "fields"; } } public override string Tag { get { return "field"; } } } class PropertyData : MemberData { public PropertyData (XmlDocument document, XmlNode parent, PropertyDefinition [] members) : base (document, parent, members) { } protected override string GetName (MemberReference memberDefenition) { PropertyDefinition prop = (PropertyDefinition) memberDefenition; return prop.Name; } protected override void AddExtraData (XmlNode p, MemberReference memberDefenition) { base.AddExtraData (p, memberDefenition); PropertyDefinition prop = (PropertyDefinition) memberDefenition; AddAttribute (p, "ptype", Utils.CleanupTypeName (prop.PropertyType)); MethodDefinition _get = prop.GetMethod; MethodDefinition _set = prop.SetMethod; bool haveGet = (_get != null && TypeData.MustDocumentMethod(_get)); bool haveSet = (_set != null && TypeData.MustDocumentMethod(_set)); MethodDefinition [] methods; if (haveGet && haveSet) { methods = new MethodDefinition [] { _get, _set }; } else if (haveGet) { methods = new MethodDefinition [] { _get }; } else if (haveSet) { methods = new MethodDefinition [] { _set }; } else { //odd return; } if (haveGet || _set.Parameters.Count > 1) { string parms = Parameters.GetSignature (methods [0].Parameters); if (!string.IsNullOrEmpty (parms)) AddAttribute (p, "params", parms); } MethodData data = new MethodData (document, p, methods); //data.NoMemberAttributes = true; data.DoOutput (); } protected override string GetMemberAttributes (MemberReference memberDefenition) { PropertyDefinition prop = (PropertyDefinition) memberDefenition; return ((int) prop.Attributes).ToString (CultureInfo.InvariantCulture); } public override string ParentTag { get { return "properties"; } } public override string Tag { get { return "property"; } } } class EventData : MemberData { public EventData (XmlDocument document, XmlNode parent, EventDefinition [] members) : base (document, parent, members) { } protected override string GetName (MemberReference memberDefenition) { EventDefinition evt = (EventDefinition) memberDefenition; return evt.Name; } protected override string GetMemberAttributes (MemberReference memberDefenition) { EventDefinition evt = (EventDefinition) memberDefenition; return ((int) evt.Attributes).ToString (CultureInfo.InvariantCulture); } protected override void AddExtraData (XmlNode p, MemberReference memberDefenition) { base.AddExtraData (p, memberDefenition); EventDefinition evt = (EventDefinition) memberDefenition; AddAttribute (p, "eventtype", Utils.CleanupTypeName (evt.EventType)); } public override string ParentTag { get { return "events"; } } public override string Tag { get { return "event"; } } } class MethodData : MemberData { bool noAtts; public MethodData (XmlDocument document, XmlNode parent, MethodDefinition [] members) : base (document, parent, members) { } protected override string GetName (MemberReference memberDefenition) { MethodDefinition method = (MethodDefinition) memberDefenition; string name = method.Name; string parms = Parameters.GetSignature (method.Parameters); return string.Format ("{0}({1})", name, parms); } protected override string GetMemberAttributes (MemberReference memberDefenition) { MethodDefinition method = (MethodDefinition) memberDefenition; return ((int)( method.Attributes)).ToString (CultureInfo.InvariantCulture); } protected override void AddExtraData (XmlNode p, MemberReference memberDefenition) { base.AddExtraData (p, memberDefenition); if (!(memberDefenition is MethodDefinition)) return; MethodDefinition mbase = (MethodDefinition) memberDefenition; ParameterData parms = new ParameterData (document, p, mbase.Parameters); parms.DoOutput (); if (mbase.IsAbstract) AddAttribute (p, "abstract", "true"); if (mbase.IsVirtual) AddAttribute (p, "virtual", "true"); if (mbase.IsFinal && mbase.IsVirtual && mbase.IsReuseSlot) AddAttribute (p, "sealed", "true"); if (mbase.IsStatic) AddAttribute (p, "static", "true"); string rettype = Utils.CleanupTypeName (mbase.MethodReturnType.ReturnType); if (rettype != "System.Void" || !mbase.IsConstructor) AddAttribute (p, "returntype", (rettype)); AttributeData.OutputAttributes (document, p, mbase.MethodReturnType); MemberData.OutputGenericParameters (document, p, mbase); } public override bool NoMemberAttributes { get { return noAtts; } set { noAtts = value; } } public override string ParentTag { get { return "methods"; } } public override string Tag { get { return "method"; } } } class ConstructorData : MethodData { public ConstructorData (XmlDocument document, XmlNode parent, MethodDefinition [] members) : base (document, parent, members) { } public override string ParentTag { get { return "constructors"; } } public override string Tag { get { return "constructor"; } } } class ParameterData : BaseData { private IList parameters; public ParameterData (XmlDocument document, XmlNode parent, IList parameters) : base (document, parent) { this.parameters = parameters; } public override void DoOutput () { XmlNode parametersNode = document.CreateElement ("parameters"); parent.AppendChild (parametersNode); foreach (ParameterDefinition parameter in parameters) { XmlNode paramNode = document.CreateElement ("parameter"); parametersNode.AppendChild (paramNode); AddAttribute (paramNode, "name", parameter.Name); AddAttribute (paramNode, "position", parameter.Method.Parameters.IndexOf(parameter).ToString(CultureInfo.InvariantCulture)); AddAttribute (paramNode, "attrib", ((int) parameter.Attributes).ToString()); string direction = "in"; if (parameter.ParameterType is ByReferenceType) direction = parameter.IsOut ? "out" : "ref"; TypeReference t = parameter.ParameterType; AddAttribute (paramNode, "type", Utils.CleanupTypeName (t)); if (parameter.IsOptional) { AddAttribute (paramNode, "optional", "true"); if (parameter.HasConstant) AddAttribute (paramNode, "defaultValue", parameter.Constant == null ? "NULL" : parameter.Constant.ToString ()); } if (direction != "in") AddAttribute (paramNode, "direction", direction); AttributeData.OutputAttributes (document, paramNode, parameter); } } } class AttributeData : BaseData { IList atts; AttributeData (XmlDocument doc, XmlNode parent, IList attributes) : base (doc, parent) { atts = attributes; } public override void DoOutput () { if (document == null) throw new InvalidOperationException ("Document not set"); if (atts == null || atts.Count == 0) return; XmlNode natts = parent.SelectSingleNode("attributes"); if (natts == null) { natts = document.CreateElement ("attributes", null); parent.AppendChild (natts); } foreach (var att in atts.OrderBy ((a) => a.Constructor.DeclaringType.FullName)) { string attName = Utils.CleanupTypeName (att.Constructor.DeclaringType); if (SkipAttribute (att)) continue; XmlNode node = document.CreateElement ("attribute"); AddAttribute (node, "name", attName); XmlNode properties = null; Dictionary attribute_mapping = CreateAttributeMapping (att); foreach (string name in attribute_mapping.Keys) { if (name == "TypeId") continue; if (properties == null) { properties = node.AppendChild (document.CreateElement ("properties")); } object o = attribute_mapping [name]; XmlNode n = properties.AppendChild (document.CreateElement ("property")); AddAttribute (n, "name", name); if (o == null) { AddAttribute (n, "value", "null"); continue; } string value = o.ToString (); if (attName.EndsWith ("GuidAttribute")) value = value.ToUpper (); AddAttribute (n, "value", value); } natts.AppendChild (node); } } static Dictionary CreateAttributeMapping (CustomAttribute attribute) { var mapping = new Dictionary (); PopulateMapping (mapping, attribute); var constructor = attribute.Constructor.Resolve (); if (constructor == null || !constructor.HasParameters) return mapping; PopulateMapping (mapping, constructor, attribute); return mapping; } static void PopulateMapping (Dictionary mapping, CustomAttribute attribute) { if (!attribute.HasProperties) return; foreach (var named_argument in attribute.Properties) { var name = named_argument.Name; var arg = named_argument.Argument; if (arg.Value is CustomAttributeArgument) arg = (CustomAttributeArgument) arg.Value; mapping.Add (name, GetArgumentValue (arg.Type, arg.Value)); } } static Dictionary CreateArgumentFieldMapping (MethodDefinition constructor) { Dictionary field_mapping = new Dictionary (); int? argument = null; foreach (Instruction instruction in constructor.Body.Instructions) { switch (instruction.OpCode.Code) { case Code.Ldarg_1: argument = 1; break; case Code.Ldarg_2: argument = 2; break; case Code.Ldarg_3: argument = 3; break; case Code.Ldarg: case Code.Ldarg_S: argument = ((ParameterDefinition) instruction.Operand).Index + 1; break; case Code.Stfld: FieldReference field = (FieldReference) instruction.Operand; if (field.DeclaringType.FullName != constructor.DeclaringType.FullName) continue; if (!argument.HasValue) break; if (!field_mapping.ContainsKey (field)) field_mapping.Add (field, (int) argument - 1); argument = null; break; } } return field_mapping; } static Dictionary CreatePropertyFieldMapping (TypeDefinition type) { Dictionary property_mapping = new Dictionary (); foreach (PropertyDefinition property in type.Properties) { if (property.GetMethod == null) continue; if (!property.GetMethod.HasBody) continue; foreach (Instruction instruction in property.GetMethod.Body.Instructions) { if (instruction.OpCode.Code != Code.Ldfld) continue; FieldReference field = (FieldReference) instruction.Operand; if (field.DeclaringType.FullName != type.FullName) continue; property_mapping.Add (property, field); break; } } return property_mapping; } static void PopulateMapping (Dictionary mapping, MethodDefinition constructor, CustomAttribute attribute) { if (!constructor.HasBody) return; // Custom handling for attributes with arguments which cannot be easily extracted var ca = attribute.ConstructorArguments; switch (constructor.DeclaringType.FullName) { case "System.Runtime.CompilerServices.DecimalConstantAttribute": var dca = constructor.Parameters[2].ParameterType == constructor.Module.TypeSystem.Int32 ? new DecimalConstantAttribute ((byte) ca[0].Value, (byte) ca[1].Value, (int) ca[2].Value, (int) ca[3].Value, (int) ca[4].Value) : new DecimalConstantAttribute ((byte) ca[0].Value, (byte) ca[1].Value, (uint) ca[2].Value, (uint) ca[3].Value, (uint) ca[4].Value); mapping.Add ("Value", dca.Value); return; case "System.ComponentModel.BindableAttribute": if (ca.Count != 1) break; if (constructor.Parameters[0].ParameterType == constructor.Module.TypeSystem.Boolean) { mapping.Add ("Bindable", ca[0].Value); } else { throw new NotImplementedException (); } return; } var field_mapping = CreateArgumentFieldMapping (constructor); var property_mapping = CreatePropertyFieldMapping ((TypeDefinition) constructor.DeclaringType); foreach (var pair in property_mapping) { int argument; if (!field_mapping.TryGetValue (pair.Value, out argument)) continue; var ca_arg = ca [argument]; if (ca_arg.Value is CustomAttributeArgument) ca_arg = (CustomAttributeArgument) ca_arg.Value; mapping.Add (pair.Key.Name, GetArgumentValue (ca_arg.Type, ca_arg.Value)); } } static object GetArgumentValue (TypeReference reference, object value) { var type = reference.Resolve (); if (type == null) return value; if (type.IsEnum) { if (IsFlaggedEnum (type)) return GetFlaggedEnumValue (type, value); return GetEnumValue (type, value); } return value; } static bool IsFlaggedEnum (TypeDefinition type) { if (!type.IsEnum) return false; if (!type.HasCustomAttributes) return false; foreach (CustomAttribute attribute in type.CustomAttributes) if (attribute.Constructor.DeclaringType.FullName == "System.FlagsAttribute") return true; return false; } static object GetFlaggedEnumValue (TypeDefinition type, object value) { if (value is ulong) return GetFlaggedEnumValue (type, (ulong)value); long flags = Convert.ToInt64 (value); var signature = new StringBuilder (); for (int i = type.Fields.Count - 1; i >= 0; i--) { FieldDefinition field = type.Fields [i]; if (!field.HasConstant) continue; long flag = Convert.ToInt64 (field.Constant); if (flag == 0) continue; if ((flags & flag) == flag) { if (signature.Length != 0) signature.Append (", "); signature.Append (field.Name); flags -= flag; } } return signature.ToString (); } static object GetFlaggedEnumValue (TypeDefinition type, ulong flags) { var signature = new StringBuilder (); for (int i = type.Fields.Count - 1; i >= 0; i--) { FieldDefinition field = type.Fields [i]; if (!field.HasConstant) continue; ulong flag = Convert.ToUInt64 (field.Constant); if (flag == 0) continue; if ((flags & flag) == flag) { if (signature.Length != 0) signature.Append (", "); signature.Append (field.Name); flags -= flag; } } return signature.ToString (); } static object GetEnumValue (TypeDefinition type, object value) { foreach (FieldDefinition field in type.Fields) { if (!field.HasConstant) continue; if (Comparer.Default.Compare (field.Constant, value) == 0) return field.Name; } return value; } static bool SkipAttribute (CustomAttribute attribute) { var type_name = Utils.CleanupTypeName (attribute.Constructor.DeclaringType); return !TypeHelper.IsPublic (attribute) || type_name.EndsWith ("TODOAttribute"); } public static void OutputAttributes (XmlDocument doc, XmlNode parent, ICustomAttributeProvider provider) { if (!provider.HasCustomAttributes) return; AttributeData ad = new AttributeData (doc, parent, provider.CustomAttributes); ad.DoOutput (); } } static class Parameters { public static string GetSignature (IList infos) { if (infos == null || infos.Count == 0) return string.Empty; var signature = new StringBuilder (); for (int i = 0; i < infos.Count; i++) { if (i > 0) signature.Append (", "); ParameterDefinition info = infos [i]; string modifier; if ((info.Attributes & ParameterAttributes.In) != 0) modifier = "in"; else if ((info.Attributes & ParameterAttributes.Out) != 0) modifier = "out"; else modifier = string.Empty; if (modifier.Length > 0) { signature.Append (modifier); signature.Append (" "); } signature.Append (Utils.CleanupTypeName (info.ParameterType)); } return signature.ToString (); } } class TypeReferenceComparer : IComparer { public static TypeReferenceComparer Default = new TypeReferenceComparer (); public int Compare (TypeReference a, TypeReference b) { int result = String.Compare (a.Namespace, b.Namespace, StringComparison.Ordinal); if (result != 0) return result; return String.Compare (a.Name, b.Name, StringComparison.Ordinal); } } class MemberReferenceComparer : IComparer { public static MemberReferenceComparer Default = new MemberReferenceComparer (); public int Compare (object a, object b) { MemberReference ma = (MemberReference) a; MemberReference mb = (MemberReference) b; return String.Compare (ma.Name, mb.Name, StringComparison.Ordinal); } } class PropertyDefinitionComparer : IComparer { public static PropertyDefinitionComparer Default = new PropertyDefinitionComparer (); public int Compare (PropertyDefinition ma, PropertyDefinition mb) { int res = String.Compare (ma.Name, mb.Name); if (res != 0) return res; if (!ma.HasParameters && !mb.HasParameters) return 0; if (!ma.HasParameters) return -1; if (!mb.HasParameters) return 1; return MethodDefinitionComparer.Compare (ma.Parameters, mb.Parameters); } } class MethodDefinitionComparer : IComparer { public static MethodDefinitionComparer Default = new MethodDefinitionComparer (); public int Compare (object a, object b) { MethodDefinition ma = (MethodDefinition) a; MethodDefinition mb = (MethodDefinition) b; int res = String.Compare (ma.Name, mb.Name); if (res != 0) return res; if (!ma.HasParameters && !mb.HasParameters) return 0; if (!ma.HasParameters) return -1; if (!mb.HasParameters) return 1; res = Compare (ma.Parameters, mb.Parameters); if (res != 0) return res; // operators can differ by only return type return string.CompareOrdinal (ma.ReturnType.FullName, mb.ReturnType.FullName); } public static int Compare (IList pia, IList pib) { var res = pia.Count - pib.Count; if (res != 0) return res; string siga = Parameters.GetSignature (pia); string sigb = Parameters.GetSignature (pib); return String.Compare (siga, sigb); } } }