// // verifier.cs: compares two assemblies and reports differences. // // Author: // Sergey Chaban (serge@wildwestsoftware.com) // // (C) Sergey Chaban (serge@wildwestsoftware.com) // using System; using System.IO; using System.Collections; using System.Reflection; namespace Mono.Verifier { //////////////////////////////// // Collections //////////////////////////////// public abstract class MemberCollection : IEnumerable { public delegate MemberInfo [] InfoQuery (Type type, BindingFlags bindings); public delegate bool MemberComparer (MemberInfo mi1, MemberInfo mi2); protected SortedList list; protected MemberComparer comparer; protected BindingFlags bindings; protected MemberCollection (Type type, InfoQuery query, MemberComparer comparer, BindingFlags bindings) { if (query == null) throw new NullReferenceException ("Invalid query delegate."); if (comparer == null) throw new NullReferenceException ("Invalid comparer."); this.comparer = comparer; this.bindings = bindings; this.list = new SortedList (); MemberInfo [] data = query (type, bindings); foreach (MemberInfo info in data) { this.list [info.Name] = info; } } public MemberInfo this [string name] { get { return list [name] as MemberInfo; } } public override int GetHashCode () { return list.GetHashCode (); } public override bool Equals (object o) { bool res = (o is MemberCollection); if (res) { MemberCollection another = o as MemberCollection; IEnumerator it = GetEnumerator (); while (it.MoveNext () && res) { MemberInfo inf1 = it.Current as MemberInfo; MemberInfo inf2 = another [inf1.Name]; res &= comparer (inf1, inf2); } } return res; } public static bool operator == (MemberCollection c1, MemberCollection c2) { return c1.Equals (c2); } public static bool operator != (MemberCollection c1, MemberCollection c2) { return !(c1 == c2); } public IEnumerator GetEnumerator() { return new Iterator (this); } internal class Iterator : IEnumerator { private MemberCollection host; private int pos; internal Iterator (MemberCollection host) { this.host=host; this.Reset (); } /// public object Current { get { if (host != null && pos >=0 && pos < host.list.Count) { return host.list.GetByIndex (pos); } else { return null; } } } /// public bool MoveNext () { if (host!=null) { return (++pos) < host.list.Count; } else { return false; } } /// public void Reset () { this.pos = -1; } } } //--- Method collections /// /// Abstract collection of class' methods. /// public abstract class MethodCollectionBase : MemberCollection { protected MethodCollectionBase (Type type, BindingFlags bindings) : base (type, new InfoQuery (Query), new MemberComparer (Comparer), bindings) { } private static MemberInfo [] Query (Type type, BindingFlags bindings) { // returns MethodInfo [] return type.GetMethods (bindings); } private static bool Comparer (MemberInfo mi1, MemberInfo mi2) { bool res = false; if (mi1 is MethodInfo && (mi2 == null || mi2 is MethodInfo)) { MethodInfo inf1 = mi1 as MethodInfo; MethodInfo inf2 = mi2 as MethodInfo; res = Compare.Methods (inf1, inf2); } else { Verifier.log.Write ("internal-error", "Wrong comparer arguments.", ImportanceLevel.HIGH); } return res; } } /// /// Collection of public instance methods of a class. /// public class PublicMethods : MethodCollectionBase { public PublicMethods (Type type) : base (type, BindingFlags.Public | BindingFlags.Instance) { } } /// /// Collection of public static methods of a class. /// public class PublicStaticMethods : MethodCollectionBase { public PublicStaticMethods (Type type) : base (type, BindingFlags.Public | BindingFlags.Static) { } } /// /// Collection of non-public instance methods of a class. /// public class NonPublicMethods : MethodCollectionBase { public NonPublicMethods (Type type) : base (type, BindingFlags.NonPublic | BindingFlags.Instance) { } } /// /// Collection of non-public static methods of a class. /// public class NonPublicStaticMethods : MethodCollectionBase { public NonPublicStaticMethods (Type type) : base (type, BindingFlags.NonPublic | BindingFlags.Static) { } } //--- Field collections public abstract class FieldCollectionBase : MemberCollection { protected FieldCollectionBase (Type type, BindingFlags bindings) : base (type, new InfoQuery (Query), new MemberComparer (Comparer), bindings) { } private static MemberInfo [] Query (Type type, BindingFlags bindings) { // returns FieldInfo [] return type.GetFields (bindings); } private static bool Comparer (MemberInfo mi1, MemberInfo mi2) { bool res = false; if (mi1 is FieldInfo && (mi2 == null || mi2 is FieldInfo)) { FieldInfo inf1 = mi1 as FieldInfo; FieldInfo inf2 = mi2 as FieldInfo; res = Compare.Fields (inf1, inf2); } else { Verifier.log.Write ("internal-error", "Wrong comparer arguments.", ImportanceLevel.HIGH); } return res; } } public class PublicFields : FieldCollectionBase { public PublicFields (Type type) : base (type, BindingFlags.Public | BindingFlags.Instance) { } } public class PublicStaticFields : FieldCollectionBase { public PublicStaticFields (Type type) : base (type, BindingFlags.Public | BindingFlags.Static) { } } public class NonPublicFields : FieldCollectionBase { public NonPublicFields (Type type) : base (type, BindingFlags.NonPublic | BindingFlags.Instance) { } } public class NonPublicStaticFields : FieldCollectionBase { public NonPublicStaticFields (Type type) : base (type, BindingFlags.NonPublic | BindingFlags.Static) { } } public abstract class AbstractTypeStuff { public readonly Type type; public AbstractTypeStuff (Type type) { if (type == null) throw new NullReferenceException ("Invalid type."); this.type = type; } public override int GetHashCode () { return type.GetHashCode (); } public static bool operator == (AbstractTypeStuff t1, AbstractTypeStuff t2) { if ((t1 as object) == null) { if ((t2 as object) == null) return true; return false; } return t1.Equals (t2); } public static bool operator != (AbstractTypeStuff t1, AbstractTypeStuff t2) { return !(t1 == t2); } public override bool Equals (object o) { return (o is AbstractTypeStuff && CompareTypes (o as AbstractTypeStuff)); } protected virtual bool CompareTypes (AbstractTypeStuff that) { Verifier.Log.Write ("info", "Comparing types.", ImportanceLevel.LOW); bool res; res = Compare.Types (this.type, that.type); return res; } } /// /// Represents a class. /// public class ClassStuff : AbstractTypeStuff { public PublicMethods publicMethods; public PublicStaticMethods publicStaticMethods; public NonPublicMethods nonpublicMethods; public NonPublicStaticMethods nonpublicStaticMethods; public PublicFields publicFields; public PublicStaticFields publicStaticFields; public NonPublicFields nonpublicFields; public NonPublicStaticFields nonpublicStaticFields; public ClassStuff (Type type) : base (type) { publicMethods = new PublicMethods (type); publicStaticMethods = new PublicStaticMethods (type); nonpublicMethods = new NonPublicMethods (type); nonpublicStaticMethods = new NonPublicStaticMethods (type); publicFields = new PublicFields (type); publicStaticFields = new PublicStaticFields (type); nonpublicFields = new NonPublicFields (type); nonpublicStaticFields = new NonPublicStaticFields (type); } public override int GetHashCode () { return base.GetHashCode (); } private bool CompareMethods (ClassStuff that) { bool res = true; bool ok; Verifier.Log.Write ("info", "Comparing public instance methods.", ImportanceLevel.LOW); ok = (this.publicMethods == that.publicMethods); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing public static methods.", ImportanceLevel.LOW); ok = (this.publicStaticMethods == that.publicStaticMethods); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing non-public instance methods.", ImportanceLevel.LOW); ok = (this.nonpublicMethods == that.nonpublicMethods); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing non-public static methods.", ImportanceLevel.LOW); ok = (this.nonpublicStaticMethods == that.nonpublicStaticMethods); res &= ok; if (!ok && Verifier.stopOnError) return res; return res; } private bool CompareFields (ClassStuff that) { bool res = true; bool ok; Verifier.Log.Write ("info", "Comparing public instance fields.", ImportanceLevel.LOW); ok = (this.publicFields == that.publicFields); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing public static fields.", ImportanceLevel.LOW); ok = (this.publicStaticFields == that.publicStaticFields); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing non-public instance fields.", ImportanceLevel.LOW); ok = (this.nonpublicFields == that.nonpublicFields); res &= ok; if (!ok && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing non-public static fields.", ImportanceLevel.LOW); ok = (this.nonpublicStaticFields == that.nonpublicStaticFields); res &= ok; if (!ok && Verifier.stopOnError) return res; return res; } public override bool Equals (object o) { bool res = (o is ClassStuff); if (res) { ClassStuff that = o as ClassStuff; res &= this.CompareTypes (that); if (!res && Verifier.stopOnError) return res; res &= this.CompareMethods (that); if (!res && Verifier.stopOnError) return res; res &= this.CompareFields (that); if (!res && Verifier.stopOnError) return res; } return res; } } /// /// Represents an interface. /// public class InterfaceStuff : AbstractTypeStuff { public PublicMethods publicMethods; public InterfaceStuff (Type type) : base (type) { publicMethods = new PublicMethods (type); } public override int GetHashCode () { return base.GetHashCode (); } public override bool Equals (object o) { bool res = (o is InterfaceStuff); if (res) { bool ok; InterfaceStuff that = o as InterfaceStuff; res = this.CompareTypes (that); if (!res && Verifier.stopOnError) return res; Verifier.Log.Write ("info", "Comparing interface methods.", ImportanceLevel.LOW); ok = (this.publicMethods == that.publicMethods); res &= ok; if (!ok && Verifier.stopOnError) return res; } return res; } } /// /// Represents an enumeration. /// public class EnumStuff : AbstractTypeStuff { //public FieldInfo [] members; public string baseType; public Hashtable enumTable; public bool isFlags; public EnumStuff (Type type) : base (type) { //members = type.GetFields (BindingFlags.Public | BindingFlags.Static); Array values = Enum.GetValues (type); Array names = Enum.GetNames (type); baseType = Enum.GetUnderlyingType (type).Name; enumTable = new Hashtable (); object [] attrs = type.GetCustomAttributes (false); isFlags = (attrs != null && attrs.Length > 0); if (isFlags) { foreach (object attr in attrs) { isFlags |= (attr is FlagsAttribute); } } int indx = 0; foreach (string id in names) { enumTable [id] = Convert.ToInt64(values.GetValue(indx) as Enum); ++indx; } } public override int GetHashCode () { return base.GetHashCode (); } public override bool Equals (object o) { bool res = (o is EnumStuff); bool ok; if (res) { EnumStuff that = o as EnumStuff; ok = this.CompareTypes (that); res &= ok; if (!ok && Verifier.stopOnError) return res; ok = (this.baseType == that.baseType); res &= ok; if (!ok) { Verifier.log.Write ("error", String.Format ("Underlying types mismatch [{0}, {1}].", this.baseType, that.baseType), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } Verifier.Log.Write ("info", "Comparing [Flags] attribute."); ok = !(this.isFlags ^ that.isFlags); res &= ok; if (!ok) { Verifier.log.Write ("error", String.Format ("[Flags] attribute mismatch ({0} : {1}).", this.isFlags ? "Yes" : "No", that.isFlags ? "Yes" : "No"), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } Verifier.Log.Write ("info", "Comparing enum values."); ICollection names = enumTable.Keys; foreach (string id in names) { ok = that.enumTable.ContainsKey (id); res &= ok; if (!ok) { Verifier.log.Write ("error", String.Format("{0} absent in enumeration.", id), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } if (ok) { long val1 = (long) this.enumTable [id]; long val2 = (long) that.enumTable [id]; ok = (val1 == val2); res &= ok; if (!ok) { Verifier.log.Write ("error", String.Format ("Enum values mismatch [{0}: {1} != {2}].", id, val1, val2), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } } } } return res; } } public sealed class TypeArray { public static readonly TypeArray empty = new TypeArray (Type.EmptyTypes); public Type [] types; public TypeArray (Type [] types) { this.types = new Type [types.Length]; for (int i = 0; i < types.Length; i++) { this.types.SetValue (types.GetValue (i), i); } } } public class AssemblyLoader { public delegate void Hook (TypeArray assemblyTypes); private static Hashtable cache; private Hook hook; static AssemblyLoader () { cache = new Hashtable (11); } public AssemblyLoader (Hook hook) { if (hook == null) throw new NullReferenceException ("Invalid loader hook."); this.hook = hook; } public bool LoadFrom (string assemblyName) { bool res = false; try { TypeArray types = TypeArray.empty; lock (cache) { if (cache.Contains (assemblyName)) { types = (cache [assemblyName] as TypeArray); if (types == null) types = TypeArray.empty; } else { Assembly asm = Assembly.LoadFrom (assemblyName); Type [] allTypes = asm.GetTypes (); if (allTypes == null) allTypes = Type.EmptyTypes; types = new TypeArray (allTypes); cache [assemblyName] = types; } } hook (types); res = true; } catch (ReflectionTypeLoadException rtle) { // FIXME: Should we try to recover? Use loaded portion of types. Type [] loaded = rtle.Types; for (int i = 0, xCnt = 0; i < loaded.Length; i++) { if (loaded [i] == null) { Verifier.log.Write ("fatal error", String.Format ("Unable to load {0}, reason - {1}", loaded [i], rtle.LoaderExceptions [xCnt++]), ImportanceLevel.LOW); } } } catch (FileNotFoundException fnfe) { Verifier.log.Write ("fatal error", fnfe.ToString (), ImportanceLevel.LOW); } catch (Exception x) { Verifier.log.Write ("fatal error", x.ToString (), ImportanceLevel.LOW); } return res; } } public abstract class AbstractTypeCollection : SortedList { private AssemblyLoader loader; public AbstractTypeCollection () { loader = new AssemblyLoader (new AssemblyLoader.Hook (LoaderHook)); } public AbstractTypeCollection (string assemblyName) : this () { LoadFrom (assemblyName); } public abstract void LoaderHook (TypeArray types); public bool LoadFrom (string assemblyName) { return loader.LoadFrom (assemblyName); } } public class ClassCollection : AbstractTypeCollection { public ClassCollection () : base () { } public ClassCollection (string assemblyName) : base (assemblyName) { } public override void LoaderHook (TypeArray types) { foreach (Type type in types.types) { if (type.IsClass) { this [type.FullName] = new ClassStuff (type); } } } } public class InterfaceCollection : AbstractTypeCollection { public InterfaceCollection () : base () { } public InterfaceCollection (string assemblyName) : base (assemblyName) { } public override void LoaderHook (TypeArray types) { foreach (Type type in types.types) { if (type.IsInterface) { this [type.FullName] = new InterfaceStuff (type); } } } } public class EnumCollection : AbstractTypeCollection { public EnumCollection () : base () { } public EnumCollection (string assemblyName) : base (assemblyName) { } public override void LoaderHook (TypeArray types) { foreach (Type type in types.types) { if (type.IsEnum) { this [type.FullName] = new EnumStuff (type); } } } } public class AssemblyStuff { public string name; public bool valid; public ClassCollection classes; public InterfaceCollection interfaces; public EnumCollection enums; protected delegate bool Comparer (AssemblyStuff asm1, AssemblyStuff asm2); private static ArrayList comparers; static AssemblyStuff () { comparers = new ArrayList (); comparers.Add (new Comparer (CompareNumClasses)); comparers.Add (new Comparer (CompareNumInterfaces)); comparers.Add (new Comparer (CompareClasses)); comparers.Add (new Comparer (CompareInterfaces)); comparers.Add (new Comparer (CompareEnums)); } protected static bool CompareNumClasses (AssemblyStuff asm1, AssemblyStuff asm2) { bool res = (asm1.classes.Count == asm2.classes.Count); if (!res) Verifier.Log.Write ("error", "Number of classes mismatch.", ImportanceLevel.MEDIUM); return res; } protected static bool CompareNumInterfaces (AssemblyStuff asm1, AssemblyStuff asm2) { bool res = (asm1.interfaces.Count == asm2.interfaces.Count); if (!res) Verifier.Log.Write ("error", "Number of interfaces mismatch.", ImportanceLevel.MEDIUM); return res; } protected static bool CompareClasses (AssemblyStuff asm1, AssemblyStuff asm2) { bool res = true; Verifier.Log.Write ("info", "Comparing classes."); foreach (DictionaryEntry c in asm1.classes) { string className = c.Key as string; if (Verifier.Excluded.Contains (className)) { Verifier.Log.Write ("info", String.Format ("Ignoring class {0}.", className), ImportanceLevel.MEDIUM); continue; } Verifier.Log.Write ("class", className); ClassStuff class1 = c.Value as ClassStuff; ClassStuff class2 = asm2.classes [className] as ClassStuff; if (class2 == null) { Verifier.Log.Write ("error", String.Format ("There is no such class in {0}", asm2.name)); res = false; if (Verifier.stopOnError || !Verifier.ignoreMissingTypes) return res; continue; } res &= (class1 == class2); if (!res && Verifier.stopOnError) return res; } return res; } protected static bool CompareInterfaces (AssemblyStuff asm1, AssemblyStuff asm2) { bool res = true; Verifier.Log.Write ("info", "Comparing interfaces."); foreach (DictionaryEntry ifc in asm1.interfaces) { string ifcName = ifc.Key as string; Verifier.Log.Write ("interface", ifcName); InterfaceStuff ifc1 = ifc.Value as InterfaceStuff; InterfaceStuff ifc2 = asm2.interfaces [ifcName] as InterfaceStuff; if (ifc2 == null) { Verifier.Log.Write ("error", String.Format ("There is no such interface in {0}", asm2.name)); res = false; if (Verifier.stopOnError || !Verifier.ignoreMissingTypes) return res; continue; } res &= (ifc1 == ifc2); if (!res && Verifier.stopOnError) return res; } return res; } protected static bool CompareEnums (AssemblyStuff asm1, AssemblyStuff asm2) { bool res = true; Verifier.Log.Write ("info", "Comparing enums."); foreach (DictionaryEntry e in asm1.enums) { string enumName = e.Key as string; Verifier.Log.Write ("enum", enumName); EnumStuff e1 = e.Value as EnumStuff; EnumStuff e2 = asm2.enums [enumName] as EnumStuff; if (e2 == null) { Verifier.Log.Write ("error", String.Format ("There is no such enum in {0}", asm2.name)); res = false; if (Verifier.stopOnError || !Verifier.ignoreMissingTypes) return res; continue; } res &= (e1 == e2); if (!res && Verifier.stopOnError) return res; } return res; } public AssemblyStuff (string assemblyName) { this.name = assemblyName; valid = false; } public bool Load () { bool res = true; bool ok; classes = new ClassCollection (); ok = classes.LoadFrom (name); res &= ok; if (!ok) Verifier.log.Write ("error", String.Format ("Unable to load classes from {0}.", name), ImportanceLevel.HIGH); interfaces = new InterfaceCollection (); ok = interfaces.LoadFrom (name); res &= ok; if (!ok) Verifier.log.Write ("error", String.Format ("Unable to load interfaces from {0}.", name), ImportanceLevel.HIGH); enums = new EnumCollection (); ok = enums.LoadFrom (name); res &= ok; if (!ok) Verifier.log.Write ("error", String.Format ("Unable to load enums from {0}.", name), ImportanceLevel.HIGH); valid = res; return res; } public override bool Equals (object o) { bool res = (o is AssemblyStuff); if (res) { AssemblyStuff that = o as AssemblyStuff; IEnumerator it = comparers.GetEnumerator (); while ((res || !Verifier.stopOnError) && it.MoveNext ()) { Comparer compare = it.Current as Comparer; res &= compare (this, that); } } return res; } public static bool operator == (AssemblyStuff asm1, AssemblyStuff asm2) { return asm1.Equals (asm2); } public static bool operator != (AssemblyStuff asm1, AssemblyStuff asm2) { return !(asm1 == asm2); } public override int GetHashCode () { return classes.GetHashCode () ^ interfaces.GetHashCode (); } public override string ToString () { string res; if (valid) { res = String.Format ("Asssembly {0}, valid, {1} classes, {2} interfaces, {3} enums.", name, classes.Count, interfaces.Count, enums.Count); } else { res = String.Format ("Asssembly {0}, invalid.", name); } return res; } } //////////////////////////////// // Compare //////////////////////////////// public sealed class Compare { private Compare () { } public static bool Parameters (ParameterInfo[] params1, ParameterInfo[] params2) { bool res = true; if (params1.Length != params2.Length) { Verifier.Log.Write ("Parameter count mismatch."); return false; } int count = params1.Length; for (int i = 0; i < count && res; i++) { if (params1 [i].Name != params2 [i].Name) { Verifier.Log.Write ("error", String.Format ("Parameters names mismatch {0}, {1}.", params1 [i].Name, params2 [i].Name)); res = false; if (Verifier.stopOnError) break; } Verifier.Log.Write ("parameter", params1 [i].Name); if (!Compare.Types (params1 [i].ParameterType, params2 [i].ParameterType)) { Verifier.Log.Write ("error", String.Format ("Parameters types mismatch {0}, {1}.", params1 [i].ParameterType, params2 [i].ParameterType)); res = false; if (Verifier.stopOnError) break; } if (Verifier.checkOptionalFlags) { if (params1 [i].IsIn != params2 [i].IsIn) { Verifier.Log.Write ("error", "[in] mismatch."); res = false; if (Verifier.stopOnError) break; } if (params1 [i].IsOut != params2 [i].IsOut) { Verifier.Log.Write ("error", "[out] mismatch."); res = false; if (Verifier.stopOnError) break; } if (params1 [i].IsRetval != params2 [i].IsRetval) { Verifier.Log.Write ("error", "[ref] mismatch."); res = false; if (Verifier.stopOnError) break; } if (params1 [i].IsOptional != params2 [i].IsOptional) { Verifier.Log.Write ("error", "Optional flag mismatch."); res = false; if (Verifier.stopOnError) break; } } // checkOptionalFlags } return res; } public static bool Methods (MethodInfo mi1, MethodInfo mi2) { if (mi2 == null) { Verifier.Log.Write ("error", String.Format ("There is no such method {0}.", mi1.Name), ImportanceLevel.MEDIUM); return false; } Verifier.Log.Flush (); Verifier.Log.Write ("method", String.Format ("{0}.", mi1.Name)); bool res = true; bool ok; string expected; ok = Compare.Types (mi1.ReturnType, mi2.ReturnType); res &= ok; if (!ok) { Verifier.Log.Write ("error", "Return types mismatch.", ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsAbstract == mi2.IsAbstract); res &= ok; if (!ok) { expected = (mi1.IsAbstract) ? "abstract" : "non-abstract"; Verifier.Log.Write ("error", String.Format ("Expected to be {0}.", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsVirtual == mi2.IsVirtual); res &= ok; if (!ok) { expected = (mi1.IsVirtual) ? "virtual" : "non-virtual"; Verifier.Log.Write ("error", String.Format ("Expected to be {0}.", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsFinal == mi2.IsFinal); res &= ok; if (!ok) { expected = (mi1.IsFinal) ? "final" : "overridable"; Verifier.Log.Write ("error", String.Format ("Expected to be {0}.", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } // compare access modifiers ok = (mi1.IsPrivate == mi2.IsPrivate); res &= ok; if (!ok) { expected = (mi1.IsPublic) ? "public" : "private"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsFamily == mi2.IsFamily); res &= ok; if (!ok) { expected = (mi1.IsFamily) ? "protected" : "!protected"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsAssembly == mi2.IsAssembly); res &= ok; if (!ok) { expected = (mi1.IsAssembly) ? "internal" : "!internal"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (mi1.IsStatic == mi2.IsStatic); res &= ok; if (!ok) { expected = (mi1.IsStatic) ? "static" : "instance"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } // parameters ok = Compare.Parameters (mi1.GetParameters (), mi2.GetParameters ()); res &= ok; if (!ok && Verifier.stopOnError) return res; ok = (mi1.CallingConvention == mi2.CallingConvention); res &= ok; if (!ok) { Verifier.Log.Write ("error", "Calling conventions mismatch.", ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } return res; } public static bool Fields (FieldInfo fi1, FieldInfo fi2) { if (fi2 == null) { Verifier.Log.Write ("error", String.Format ("There is no such field {0}.", fi1.Name), ImportanceLevel.MEDIUM); return false; } bool res = true; bool ok; string expected; Verifier.Log.Write ("field", String.Format ("{0}.", fi1.Name)); ok = (fi1.IsPrivate == fi2.IsPrivate); res &= ok; if (!ok) { expected = (fi1.IsPublic) ? "public" : "private"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (fi1.IsFamily == fi2.IsFamily); res &= ok; if (!ok) { expected = (fi1.IsFamily) ? "protected" : "!protected"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (fi1.IsAssembly == fi2.IsAssembly); res &= ok; if (!ok) { expected = (fi1.IsAssembly) ? "internal" : "!internal"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (fi1.IsInitOnly == fi2.IsInitOnly); res &= ok; if (!ok) { expected = (fi1.IsInitOnly) ? "readonly" : "!readonly"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } ok = (fi1.IsStatic == fi2.IsStatic); res &= ok; if (!ok) { expected = (fi1.IsStatic) ? "static" : "instance"; Verifier.Log.Write ("error", String.Format ("Accessibility levels mismatch (expected [{0}]).", expected), ImportanceLevel.MEDIUM); if (Verifier.stopOnError) return res; } return res; } public static bool Types (Type type1, Type type2) { // NOTE: // simply calling type1.Equals (type2) won't work, // types are in different assemblies hence they have // different (fully-qualified) names. int eqFlags = 0; eqFlags |= (type1.IsAbstract == type2.IsAbstract) ? 0 : 0x001; eqFlags |= (type1.IsClass == type2.IsClass) ? 0 : 0x002; eqFlags |= (type1.IsValueType == type2.IsValueType) ? 0 : 0x004; eqFlags |= (type1.IsPublic == type2.IsPublic) ? 0 : 0x008; eqFlags |= (type1.IsSealed == type2.IsSealed) ? 0 : 0x010; eqFlags |= (type1.IsEnum == type2.IsEnum) ? 0 : 0x020; eqFlags |= (type1.IsPointer == type2.IsPointer) ? 0 : 0x040; eqFlags |= (type1.IsPrimitive == type2.IsPrimitive) ? 0 : 0x080; bool res = (eqFlags == 0); if (!res) { // TODO: convert flags into descriptive message. Verifier.Log.Write ("error", "Types mismatch (0x" + eqFlags.ToString("X") + ").", ImportanceLevel.HIGH); } bool ok; ok = (type1.Attributes & TypeAttributes.BeforeFieldInit) == (type2.Attributes & TypeAttributes.BeforeFieldInit); if (!ok) { Verifier.Log.Write ("error", "Types attributes mismatch: BeforeFieldInit.", ImportanceLevel.HIGH); } res &= ok; ok = (type1.Attributes & TypeAttributes.ExplicitLayout) == (type2.Attributes & TypeAttributes.ExplicitLayout); if (!ok) { Verifier.Log.Write ("error", "Types attributes mismatch: ExplicitLayout.", ImportanceLevel.HIGH); } res &= ok; ok = (type1.Attributes & TypeAttributes.SequentialLayout) == (type2.Attributes & TypeAttributes.SequentialLayout); if (!ok) { Verifier.Log.Write ("error", "Types attributes mismatch: SequentialLayout.", ImportanceLevel.HIGH); } res &= ok; ok = (type1.Attributes & TypeAttributes.Serializable) == (type2.Attributes & TypeAttributes.Serializable); if (!ok) { Verifier.Log.Write ("error", "Types attributes mismatch: Serializable.", ImportanceLevel.HIGH); } res &= ok; return res; } } //////////////////////////////// // Log //////////////////////////////// public enum ImportanceLevel : int { LOW = 0, MEDIUM, HIGH } public interface ILogger { void Write (string tag, string msg, ImportanceLevel importance); void Write (string msg, ImportanceLevel level); void Write (string tag, string msg); void Write (string msg); ImportanceLevel DefaultImportance {get; set;} void Flush (); void Close (); } public abstract class AbstractLogger : ILogger { private ImportanceLevel defImportance = ImportanceLevel.MEDIUM; public abstract void Write (string tag, string msg, ImportanceLevel importance); public abstract void Write (string msg, ImportanceLevel level); public virtual void Write (string tag, string msg) { Write (tag, msg, DefaultImportance); } public virtual void Write (string msg) { Write (msg, DefaultImportance); } public virtual ImportanceLevel DefaultImportance { get { return defImportance; } set { defImportance = value < ImportanceLevel.LOW ? ImportanceLevel.LOW : value > ImportanceLevel.HIGH ? ImportanceLevel.HIGH : value; } } public abstract void Flush (); public abstract void Close (); } public class TextLogger : AbstractLogger { private TextWriter writer; public TextLogger (TextWriter writer) { if (writer == null) throw new NullReferenceException (); this.writer = writer; } private void DoWrite (string tag, string msg) { if (tag != null && tag.Length > 0) { writer.WriteLine ("[{0}]\t{1}", tag, msg); } else { writer.WriteLine ("\t\t" + msg); } } public override void Write (string tag, string msg, ImportanceLevel importance) { int v = Log.VerboseLevel; switch (v) { case 0 : break; case 1 : if (importance >= ImportanceLevel.HIGH) { DoWrite (tag, msg); } break; case 2 : if (importance >= ImportanceLevel.MEDIUM) { DoWrite (tag, msg); } break; case 3 : DoWrite (tag, msg); break; default: break; } } public override void Write (string msg, ImportanceLevel importance) { Write (null, msg, importance); } public override void Flush () { Console.Out.Flush (); } public override void Close () { if (writer != Console.Out && writer != Console.Error) { writer.Close (); } } } public sealed class Log { private static int verbose = 3; private ArrayList consumers; public Log (bool useDefault) { consumers = new ArrayList (); if (useDefault) AddConsumer (new TextLogger (Console.Out)); } public Log () : this (true) { } public static int VerboseLevel { get { return verbose; } set { verbose = (value < 0) ? 0 : (value > 3) ? 3 : value; } } public void AddConsumer (ILogger consumer) { consumers.Add (consumer); } public void Write (string tag, string msg, ImportanceLevel importance) { foreach (ILogger logger in consumers) { if (tag == null || tag == "") { logger.Write (msg, importance); } else { logger.Write (tag, msg, importance); } } } public void Write (string msg, ImportanceLevel importance) { Write (null, msg, importance); } public void Write (string tag, string msg) { foreach (ILogger logger in consumers) { if (tag == null || tag == "") { logger.Write (msg); } else { logger.Write (tag, msg); } } } public void Write (string msg) { Write (null, msg); } public void Flush () { foreach (ILogger logger in consumers) { logger.Flush (); } } public void Close () { foreach (ILogger logger in consumers) { logger.Flush (); logger.Close (); } } } //////////////////////////////// // Main //////////////////////////////// public class Verifier { public static readonly Log log = new Log (); public static bool stopOnError = false; public static bool ignoreMissingTypes = true; public static bool checkOptionalFlags = true; private static readonly IList excluded; static Verifier () { excluded = new ArrayList (); excluded.Add (""); } private Verifier () { } public static Log Log { get { return log; } } public static IList Excluded { get { return excluded; } } public static void Main (String [] args) { if (args.Length < 2) { Console.WriteLine ("Usage: verifier assembly1 assembly2"); } else { string name1 = args [0]; string name2 = args [1]; bool ok = false; AssemblyStuff asm1 = new AssemblyStuff (name1); AssemblyStuff asm2 = new AssemblyStuff (name2); ok = asm1.Load (); if (!ok) { Console.WriteLine ("Unable to load assembly {0}.", name1); Environment.Exit (-1); } ok = asm2.Load (); if (!ok) { Console.WriteLine ("Unable to load assembly {0}.", name2); Environment.Exit (-1); } try { ok = (asm1 == asm2); } catch { ok = false; } finally { Log.Close (); } if (!ok) { Console.WriteLine ("--- not equal"); Environment.Exit (-1); } } } } }