// ---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // ---------------------------------------------------------------------------- using System; using System.IO; using System.Xml; using System.Compiler; using System.Collections; using Microsoft.SpecSharp.Collections; public class Test{ public static bool oneLine = false; public static bool staticsOnly = false; public static bool verbose = false; public static bool outputXml = false; public static bool outputLogicalModulesOnly = false; public static int Main(string[] args){ string[]! nargs = (string[]!)args; bool needHelp = false; bool firstModuleOverridesStdLib = false; RefGraph rg = new RefGraph(); // Process flags first foreach (string! file in nargs) { if (file[0] == '/' || file[0] == '-') { switch (file[1]) { case '?': case 'h': case 'H': needHelp = true; break; case 'l': case 'L': oneLine = true; assert outputXml == false; break; case 'm': case 'M': outputLogicalModulesOnly = true; break; case 'n': case 'N': firstModuleOverridesStdLib = true; break; case 's': case 'S': staticsOnly = true; break; case 'v': case 'V': verbose = true; break; case 'x': case 'X': outputXml = true; assert oneLine == false; break; default: needHelp = true; break; } } } if (needHelp) { Usage(); return 0; } // Now process files foreach (string! file in nargs) { if (file[0] != '/' && file[0] != '-') { string fullpath = Path.GetFullPath(file); if (firstModuleOverridesStdLib) { string dir = Path.GetDirectoryName(fullpath); SystemTypes.Initialize(false, false); SystemTypes.Clear(); TargetPlatform.SetToV1_1(dir); SystemAssemblyLocation.Location = fullpath; SystemCompilerRuntimeAssemblyLocation.Location = Path.Combine(dir, "System.Compiler.Runtime.dll"); SystemTypes.Initialize(true, true); // So we don't try to override it again (once is enough) firstModuleOverridesStdLib = false; } if (verbose) { Console.WriteLine("Scanning: " + file); } Module m = Module.GetModule(file, true, true, true); rg.Visit(m); rg.FixNamespaceKeys(); } } if (Console.Out != null) { if (outputXml) { XmlDocument doc; doc = rg.DumpAllDependenciesToXml(); assert doc != null; doc.Save(Console.Out as TextWriter); } else { rg.Dump(Console.Out); } } return 0; } public static void Usage() { Console.Write( "Usage:\n" + " refgraph [options] \n" + "Options:\n" + " /l -- Format one-line output for findstr or grep.\n" + " /m -- Output template for assigning logical modules to\n" + " type declarations.\n" + " /n -- Uses the first assembly as stdlib instead of using\n" + " the standard library (mscorlib.dll).\n" + " /s -- Only consider static references (non instance based).\n" + " /v -- Output diagnostic messages.\n" + " /x -- Output type dependencies as xml.\n" + " /? -- Display this help screen.\n" + "Summary:\n" + " Dumps the graph of cross references for each type.\n" ); } } interface IDump { void Dump(TextWriter! tr); } enum DeclKind { Assembly, Namespace, Type, Method, Field } [Flags] enum DepKind { baseClass = 1, iface = 2, sig = 4, code = 8, attr = 16, }; class Dep { public Decl! decl; public DepKind kind; public Dep(Decl! decl, DepKind kind) { this.decl = decl; this.kind = kind; } [Microsoft.Contracts.Confined] public override string! ToString() { string blah = decl + " (" + KindString() + ")"; assert blah != null; return blah; } public string! KindString() { if (kind == DepKind.baseClass) return "base"; else return (string!) kind.ToString(); } public XmlElement! CreateXmlElement(XmlDocument! doc) { XmlElement elem = doc.CreateElement("Dep"); assert elem != null; Decl.DumpXmlRef(decl, "DepDeclId", elem); elem.SetAttribute("Kind", KindString()); return elem; } } // A decl can be a namespace, type, method, or field class Decl { // Name of the namespace, type, method, or field string! fullName; string! shortName; public int key; public bool hasFullInfo = false; public bool alreadyOutput = false; public static int largestKey = 0; // Only used by members public bool isStatic = false; // Only used by members string access = null; // Scope of this decl. For example, the scope of a method // or field decl is the enclosing type. The scope of a class // decl is a namespace or nested class decl. public Decl lexicalScope = null; public Decl containingAssembly = null; // Decls this decl depends on. For a method, the decls are // the return and parameter types. For a field, the decl is // the field type. For a class, the decl is the base type // and interface types. A namespace decl has no dependencies. public ArrayList/**/ dependencies = new ArrayList/**/(); // The kind of decl this is: namespace, type, method, or field public DeclKind kind; public Decl(string! fullName, string! shortName, int key, DeclKind kind) { this.fullName = fullName; this.shortName = shortName; this.key = key; this.kind = kind; if (key > largestKey) largestKey = key; } [Microsoft.Contracts.Confined] public override bool Equals(object rhs) { Decl declRhs = rhs as Decl; if (declRhs == null) return false; return Equals(declRhs); } [Microsoft.Contracts.Confined] public bool Equals(Decl rhs) { if (rhs == null) return false; return this.key == rhs.key; } [Microsoft.Contracts.Confined] public override string! ToString() { string blah = shortName + " (" + kind.ToString() + ")" + (hasFullInfo ? " FULL" : ""); assert blah != null; return blah; } public void Dump(TextWriter! o, int indent) { Indent(o, indent); o.WriteLine(ToString()); Indent(o, indent+1); o.Write("Scope: "); if (lexicalScope == null) o.WriteLine("(null)"); else { assert lexicalScope != null; o.WriteLine(lexicalScope); } if (containingAssembly != null) { Indent(o, indent+1); o.WriteLine("Assembly: " + containingAssembly); } assert dependencies != null; for (int i = 0; i < dependencies.Count; ++i) { Dep dep = dependencies[i] as Dep; assert dep != null; Indent(o, indent+1); o.WriteLine("Dependency: " + dep); } } public static void Indent(TextWriter! o, int indent) { for (int i = 0; i < indent; ++i) { o.Write(" "); } } public XmlElement! CreateXmlElement(XmlDocument! doc) { XmlElement elem = doc.CreateElement("Decl"); assert elem != null; elem.SetAttribute("FullName", fullName); elem.SetAttribute("Name", shortName); elem.SetAttribute("Kind", kind.ToString()); elem.SetAttribute("Id", key.ToString()); if (Test.outputLogicalModulesOnly) { elem.SetAttribute("LogicalModule", ""); return elem; } elem.SetAttribute("HasFullInfo", hasFullInfo.ToString()); DumpXmlRef(lexicalScope, "ScopeDeclId", elem); if (containingAssembly != null) { DumpXmlRef(containingAssembly, "AssemblyDeclId", elem); } if (access != null) { elem.SetAttribute("IsStatic", isStatic.ToString()); elem.SetAttribute("Access", access); } assert dependencies != null; for (int i = 0; i < dependencies.Count; ++i) { Dep dep = dependencies[i] as Dep; assert dep != null; XmlElement depElem = dep.CreateXmlElement(doc); elem.AppendChild(depElem); } return elem; } public static void DumpXmlRef(Decl decl, string! name, XmlElement! element) { if (decl == null) element.SetAttribute(name, ""); else element.SetAttribute(name, decl.key.ToString()); } public void AddDependency(Decl! decl, DepKind depKind) { assert dependencies != null; for (int i = 0; i < dependencies.Count; ++i) { Dep dep = dependencies[i] as Dep; assert dep != null; if (dep.decl.Equals(decl)) { dep.kind |= depKind; return; } } dependencies.Add(new Dep(decl, depKind)); } public void AddAttributeDecls(AttributeList attributes, Hashtable! decls, Hashtable! ns_decls) { assert dependencies != null; //return; // REMOVE ME!! Attributes should be considered part of the signature // Add the attribute dependencies if (attributes != null) { foreach (AttributeNode! attribute in attributes) { if (attribute.Type != null) { AddDependency( GetOrCreateTypeDecl( attribute.Type, decls, ns_decls, false), DepKind.attr ); } } } } public static Decl GetOrCreateAssemblyDecl( AssemblyNode! assembly, Hashtable! decls, bool hasFullInfo) { Decl declAssembly = decls[assembly.UniqueKey] as Decl; if (declAssembly != null) { assert declAssembly.kind == DeclKind.Assembly; if (!declAssembly.hasFullInfo && hasFullInfo) declAssembly.hasFullInfo = true; return declAssembly; } assert assembly.Name != null; declAssembly = new Decl(assembly.Name, assembly.Name, assembly.UniqueKey, DeclKind.Assembly); decls[assembly.UniqueKey] = declAssembly; return declAssembly; } public static Decl GetOrCreateNamespaceDecl(System.Compiler.Identifier! namespaceId, Hashtable! decls, bool hasFullInfo) { Decl declNamespace = decls[namespaceId.UniqueIdKey] as Decl; if (declNamespace != null) { assert declNamespace.kind == DeclKind.Namespace; if (!declNamespace.hasFullInfo && hasFullInfo) declNamespace.hasFullInfo = true; return declNamespace; } string name = namespaceId.Name; if (name == string.Empty) name = ""; declNamespace = new Decl(name, name, namespaceId.UniqueIdKey, DeclKind.Namespace); decls[namespaceId.UniqueIdKey] = declNamespace; return declNamespace; } public static Decl! GetOrCreateTypeDecl(TypeNode! typeNode, Hashtable! decls, Hashtable! ns_decls, bool hasFullInfo) { Decl declType = decls[typeNode.UniqueKey] as Decl; if (declType != null) { assert declType.kind == DeclKind.Type; if (!declType.hasFullInfo && hasFullInfo) declType.hasFullInfo = true; return declType; } assert typeNode.FullName != null; string fullName = RefGraph.GetFullNameWithoutQualifiers(typeNode.FullName); assert fullName != null; string shortName = typeNode.Name.Name; assert shortName != null; declType = new Decl(fullName, shortName, typeNode.UniqueKey, DeclKind.Type); decls[typeNode.UniqueKey] = declType; declType.hasFullInfo = hasFullInfo; assert declType.dependencies != null; if (typeNode.DeclaringModule != null) { assert typeNode.DeclaringModule.ContainingAssembly != null; // Set the assembly declType.containingAssembly = GetOrCreateAssemblyDecl(typeNode.DeclaringModule.ContainingAssembly, decls, hasFullInfo); } // Set the scope to be the nesting class if (typeNode.DeclaringType != null) { declType.lexicalScope = GetOrCreateTypeDecl(typeNode.DeclaringType, decls, ns_decls, hasFullInfo); } // Set the namespace scope else if (typeNode.Namespace != null) { declType.lexicalScope = GetOrCreateNamespaceDecl(typeNode.Namespace, ns_decls, hasFullInfo); assert declType.lexicalScope != null; declType.lexicalScope.containingAssembly = declType.containingAssembly; } // Add the base type dependency if (typeNode.BaseType != null) { declType.AddDependency( GetOrCreateTypeDecl(typeNode.BaseType, decls, ns_decls, false), DepKind.baseClass ); } // Add the base interface dependencies if (typeNode.Interfaces != null) { System.Compiler.InterfaceList.Enumerator enum1 = typeNode.Interfaces.GetEnumerator(); while (enum1.MoveNext()) { Interface! iface = enum1.Current; declType.AddDependency( GetOrCreateTypeDecl(iface, decls, ns_decls, false), DepKind.iface ); } } // Add the attribute dependencies declType.AddAttributeDecls(typeNode.Attributes, decls, ns_decls); return declType; } public static Decl! GetOrCreateMethodDecl(Method! method, Hashtable! decls, Hashtable! ns_decls) { Decl declMethod = decls[method.UniqueKey] as Decl; if (declMethod != null) { assert declMethod.kind == DeclKind.Method; return declMethod; } string shortMethodName = method.GetUnmangledNameWithoutTypeParameters(true); assert shortMethodName != null; assert method.DeclaringType != null; assert method.DeclaringType.FullName != null; string fullName = method.DeclaringType.Name.Name + "." + method.GetUnmangledNameWithoutTypeParameters(false); assert fullName != null; if (!fullName.EndsWith(")")) fullName += "()"; assert fullName != null; declMethod = new Decl(fullName, shortMethodName, method.UniqueKey, DeclKind.Method); decls[method.UniqueKey] = declMethod; declMethod.isStatic = method.IsStatic; if (method.IsPrivate) { declMethod.access = "private"; } else if (method.IsFamily || method.IsFamilyAndAssembly) { declMethod.access = "protected"; } else if (method.IsPublic || method.IsAssembly || method.IsFamilyOrAssembly || method.IsVisibleOutsideAssembly) { declMethod.access = "public"; } else if (method.IsCompilerControlled) { // For some reason, a method like // // public struct DebugService { // [CLSCompliant(false)] // public static unsafe void PrintBegin(out char * buffer, out int length) { ... } // // should have IsPublic set to 'true' but it doesn't. However, it only // appears to happen with members which are "compiler controlled" (does // this mean 'unsafe'?) declMethod.access = "public"; } else { assert System.Console.Error != null; System.Console.Error.WriteLine("Member has unknown access: " + fullName); declMethod.access = "public"; } assert declMethod.dependencies != null; if (method.ReturnType != null) { declMethod.AddDependency( GetOrCreateTypeDecl(method.ReturnType, decls, ns_decls, false), DepKind.sig ); } if (method.Parameters != null) { System.Compiler.ParameterList.Enumerator enum1 = method.Parameters.GetEnumerator(); while (enum1.MoveNext()) { Parameter! parameter = enum1.Current; assert parameter.Type != null; declMethod.AddDependency( GetOrCreateTypeDecl(parameter.Type, decls, ns_decls, false), DepKind.sig ); declMethod.AddAttributeDecls( parameter.Attributes, decls, ns_decls ); } } if (method.DeclaringType != null) { declMethod.lexicalScope = GetOrCreateTypeDecl(method.DeclaringType, decls, ns_decls, false); } declMethod.AddAttributeDecls(method.Attributes, decls, ns_decls); return declMethod; } public static Decl! GetOrCreateFieldDecl(Field! field, Hashtable! decls, Hashtable! ns_decls) { Decl declField = decls[field.UniqueKey] as Decl; if (declField != null) { assert declField.kind == DeclKind.Field; return declField; } assert field.Name != null; assert field.Name.Name != null; assert field.DeclaringType != null; assert field.DeclaringType.FullName != null; string fullName = field.DeclaringType.Name.Name + "." + field.Name.Name; assert fullName != null; declField = new Decl(fullName, field.Name.Name, field.UniqueKey, DeclKind.Field); decls[field.UniqueKey] = declField; declField.isStatic = field.IsStatic; if (field.IsPrivate) { declField.access = "private"; } else if (field.IsFamily || field.IsFamilyAndAssembly) { declField.access = "protected"; } else if (field.IsPublic || field.IsAssembly || field.IsFamilyOrAssembly) { declField.access = "public"; } else if (field.IsCompilerControlled) { declField.access = "public"; } else { assert System.Console.Error != null; System.Console.Error.WriteLine("Member has unknown access: " + fullName); declField.access = "public"; } assert declField.dependencies != null; if (field.Type != null) { declField.AddDependency( GetOrCreateTypeDecl(field.Type, decls, ns_decls, false), DepKind.sig ); } if (field.DeclaringType != null) { declField.lexicalScope = GetOrCreateTypeDecl(field.DeclaringType, decls, ns_decls, false); } declField.AddAttributeDecls(field.Attributes, decls, ns_decls); return declField; } } class RefGraph : StandardVisitor, IDump { Hashtable!/**/ decls = new Hashtable(); Hashtable!/**/ ns_decls = new Hashtable(); Set types = new Set(); /// types referenced in signatures of the type TrivialHashtable!/*>*/ inSignatures = new TrivialHashtable(); /// types referenced in code of the type TrivialHashtable!/*>*/ inCode = new TrivialHashtable(); Set currentSet; Statement currentStatement; Method currentMethod = null; public override Node Visit(Node node) { Statement stmt = node as Statement; if (stmt != null && stmt.SourceContext.Document != null) { this.currentStatement = stmt; } return base.Visit(node); } public override TypeNode VisitTypeNode(TypeNode typeNode) { if (typeNode == null) return null; // Add this type (and its namespace, base class, and base ifaces) // to the the map of decls //assert this.decls[typeNode.UniqueKey] == null; Decl.GetOrCreateTypeDecl(typeNode, decls, ns_decls, true); Set oldSet = this.currentSet; Set inSigSet= new Set(); this.currentSet = inSigSet; base.VisitTypeNode(typeNode); this.inSignatures[typeNode.UniqueKey] = this.currentSet; types.Add(typeNode); this.currentSet = oldSet; return typeNode; } public override TypeNode VisitTypeReference(TypeNode typeNode) { if (typeNode == null) return null; Decl.GetOrCreateTypeDecl(typeNode, decls, ns_decls, false); this.currentSet.Add(typeNode); return typeNode; } public override Field VisitField(Field field) { if (field == null) return null; //assert decls[field.UniqueKey] == null; Decl declField = Decl.GetOrCreateFieldDecl(field, decls, ns_decls); declField.hasFullInfo = true; // Note: The field initializer has already been placed into // the constructor. return field; } public override Method VisitMethod(Method method) { if (method == null) return null; //assert decls[method.UniqueKey] == null; Decl declMethod = Decl.GetOrCreateMethodDecl(method, decls, ns_decls); declMethod.hasFullInfo = true; this.VisitParameterList(method.Parameters); if (!Test.staticsOnly) { this.VisitTypeReference(method.ReturnType); } Set savedSet = this.currentSet; TypeNode declType = method.DeclaringType; if (declType != null) { this.currentSet = this.InCodeSet(declType); assert this.currentMethod == null; this.currentMethod = method; this.Visit(method.Body); assert this.currentMethod == method; this.currentMethod = null; this.SetInCodeSet(declType, this.currentSet); } this.currentSet = savedSet; return method; } public override Expression VisitBinaryExpression(BinaryExpression binexpr) { if (binexpr == null) return null; TypeNode cast_type = null; TypeNode from_type = null; switch (binexpr.NodeType) { case NodeType.Castclass: cast_type = (TypeNode!) ((Literal!) binexpr.Operand2).Value; from_type = (!)((!)binexpr.Operand1).Type; if (Test.verbose) { assert this.currentStatement != null; WriteSourceContext(this.currentStatement); Console.WriteLine("down cast: '{0}' -> '{1}'", from_type.FullName, cast_type.FullName); } break; case NodeType.Unbox: cast_type = (TypeNode!) ((Literal!) binexpr.Operand2).Value; from_type = (!)((!)binexpr.Operand1).Type; if (Test.verbose) { assert this.currentStatement != null; WriteSourceContext(this.currentStatement); Console.WriteLine("unbox: '{0}' -> '{1}'", from_type.FullName, cast_type.FullName); } break; } if (cast_type != null) { assert from_type != null; assert this.currentMethod != null; Decl declMember = this.decls[this.currentMethod.UniqueKey] as Decl; assert declMember != null; declMember.AddDependency(Decl.GetOrCreateTypeDecl(cast_type, decls, ns_decls, false), DepKind.code); declMember.AddDependency(Decl.GetOrCreateTypeDecl(from_type, decls, ns_decls, false), DepKind.code); } return binexpr; } private void WriteSourceContext(Node! node) { Document d = node.SourceContext.Document; if (d != null) { Console.Write("{0}:{1} ", d.Name, node.SourceContext.StartLine); } } public override Expression VisitParameter(Parameter parameter) { if (!Test.staticsOnly) { return base.VisitParameter(parameter); } return parameter; } public override Expression VisitMemberBinding(MemberBinding mb) { if (mb == null) return mb; base.VisitMemberBinding(mb); Member bm = mb.BoundMember; if (bm != null) { if (currentMethod != null) { Decl methodDecl = this.decls[currentMethod.UniqueKey] as Decl; assert methodDecl != null; Decl memberDecl = null; if (bm is Field) { Field field = bm as Field; assert field != null; memberDecl = Decl.GetOrCreateFieldDecl(field, decls, ns_decls); } else if (bm is Method) { Method method = bm as Method; assert method != null; memberDecl = Decl.GetOrCreateMethodDecl(method, decls, ns_decls); } assert memberDecl != null; methodDecl.AddDependency(memberDecl, DepKind.code); } if (!Test.staticsOnly || bm.IsStatic) { this.VisitTypeReference(bm.DeclaringType); } } return mb; } Set InSigSet(TypeNode! tn) { object o = this.inSignatures[tn.UniqueKey]; if (o == null) return new Set(); return (Set)o; } Set InCodeSet(TypeNode! tn) { object o = this.inCode[tn.UniqueKey]; if (o == null) return new Set(); return (Set)o; } void SetInCodeSet(TypeNode! tn, Set data) { this.inCode[tn.UniqueKey] = data; } public void FixNamespaceKeys() { assert this.ns_decls.Values != null; foreach (object val in this.ns_decls.Values) { Decl decl = val as Decl; assert decl != null; if (decl.kind == DeclKind.Namespace) { decl.key += Decl.largestKey; } } } public void Dump(TextWriter! wr) { assert !Test.outputXml; foreach (TypeNode! tn in this.types) { if (Test.oneLine) { wr.WriteLine(); wr.WriteLine("{0}: Type: {0}", tn.FullName); TypeNode bt = tn.BaseType; if (bt != null) { wr.WriteLine("{0}: Base type: {1}", tn.FullName, bt.FullName); } InterfaceList il = tn.Interfaces; if (il != null) { for (int i = 0; i < il.Count; i++) { Interface intf = il[i]; if (intf != null) { Console.WriteLine("{0}: Interface: {1}", tn.FullName, intf.FullName); } } } Set s = InSigSet(tn); foreach (TypeNode! stn in s) { wr.WriteLine("{0}: Referenced from signature: {1}", tn.FullName, stn.FullName); } Set cs = InCodeSet(tn); foreach (TypeNode! stn in cs) { wr.WriteLine("{0}: Referenced from code: {1}", tn.FullName, stn.FullName); } } else { wr.WriteLine("\n\nType: {0}", tn.FullName); wr.WriteLine("----------------------"); TypeNode bt = tn.BaseType; if (bt != null) { wr.WriteLine("\n Base type: {0}", bt.FullName); } InterfaceList il = tn.Interfaces; if (il != null) { wr.WriteLine("\n Interfaces"); for (int i = 0; i < il.Count; i++) { Interface intf = il[i]; if (intf != null) { Console.WriteLine(" {0}", intf.FullName); } } } Set s = InSigSet(tn); wr.WriteLine("\n Referenced from signatures"); foreach (TypeNode! stn in s) { wr.WriteLine(" {0}", stn.FullName); } Set cs = InCodeSet(tn); wr.WriteLine("\n Referenced from code"); foreach (TypeNode! stn in cs) { wr.WriteLine(" {0}", stn.FullName); } } } } public XmlDocument DumpAllDependenciesToXml() { XmlDocument doc = new XmlDocument(); assert doc != null; XmlElement rootElement = doc.CreateElement("MemberDependencies"); assert rootElement != null; doc.AppendChild(rootElement); assert this.decls.Values != null; foreach (object val in this.decls.Values) { Decl decl = val as Decl; assert decl != null; Output(doc, rootElement, decl); } return doc; } public void Output(XmlDocument! doc, XmlElement! parentElement, Decl !decl) { if (decl.alreadyOutput) return; decl.alreadyOutput = true; if (Test.outputLogicalModulesOnly && (decl.kind == DeclKind.Assembly || decl.kind == DeclKind.Namespace)) { return; } // First output containing assembly if (decl.containingAssembly != null) Output(doc, parentElement, decl.containingAssembly); // Then output lexical scope assembly if (decl.lexicalScope != null) Output(doc, parentElement, decl.lexicalScope); // Then output all the dependencies for (int i = 0; decl.dependencies != null && i < decl.dependencies.Count; ++i) { Dep dep = decl.dependencies[i] as Dep; assert dep != null; assert dep.decl != null; Output(doc, parentElement, dep.decl); } XmlElement elem = decl.CreateXmlElement(doc); parentElement.AppendChild(elem); } static string GetFullyQualifiedTypeName(TypeNode! node) { if (node.DeclaringModule != null) { assert node.DeclaringModule.ContainingAssembly != null; assert node.FullName != null; return GetFullNameWithoutQualifiers(node.FullName) + "," + node.DeclaringModule.ContainingAssembly.Name; } else { return GetFullNameWithoutQualifiers((string!) node.FullName); } } public static string GetFullNameWithoutQualifiers(string! name) { int firstSpace = name.IndexOf(' '); if (firstSpace > 0) { return name.Substring(firstSpace + 1); } else { return name; } } static void AddDependency(Hashtable! mapTypeDependency2Kind, TypeNode! node, string! kind) { string! fqTypeName = (string!) GetFullyQualifiedTypeName(node); string curKind = mapTypeDependency2Kind[fqTypeName] as string; if (curKind == null || curKind == string.Empty) { curKind = kind; } else if (curKind.IndexOf(kind) != -1) { return; // Already recorded this kind of dependency } else { curKind += ", " + kind; } mapTypeDependency2Kind[fqTypeName] = curKind; } }