using System; using System.Collections.Generic; using System.Reflection; using System.IO; using System.Text; using UnityEditor.Build.Content; namespace UnityEditor.Build.Pipeline.Utilities.USerialize { // Uses reflection to recursively produce a text based dump of the types, names and values of all fields in an object and it's sub-objects. // Useful for visual verification or comparison of the data internal class DumpToText { // Custom code to dump a type to text that cannot be properly handled by the default reflection based code internal interface ICustomDumper { // Return the type that this custom dumper deals with Type GetType(); // Dump an object to text void CustomDumper(DumpToText dumpToText, object value); } Dictionary m_CustomDumpers = new Dictionary(); StringBuilder m_StringBuilder = new StringBuilder(); string m_Indent = ""; internal DumpToText() { } internal DumpToText(params ICustomDumper[] customDumpers) { if (customDumpers != null) Array.ForEach(customDumpers, (customDumper) => AddCustomDumper(customDumper)); } internal void Clear() { m_StringBuilder = new StringBuilder(); m_Indent = ""; } internal void AddCustomDumper(ICustomDumper customDumper) { m_CustomDumpers.Add(customDumper.GetType(), customDumper); } internal string SanitiseFieldName(string fieldName) { if ((fieldName[0] == '<') && fieldName.EndsWith(">k__BackingField")) fieldName = fieldName.Substring(1, fieldName.Length - 17); return fieldName; } internal static string SanitiseGenericName(string genericName) { int argSepPos = genericName.IndexOf('`'); return (argSepPos != -1) ? genericName.Substring(0, argSepPos) : genericName; } internal void Add(string label) { label = SanitiseFieldName(label); m_StringBuilder.AppendLine($"{m_Indent}{label}"); } internal void Add(string label, string value) { label = SanitiseFieldName(label); m_StringBuilder.AppendLine($"{m_Indent}{label}: {value}"); } internal void Add(string typename, string label, string value) { label = SanitiseFieldName(label); m_StringBuilder.AppendLine($"{m_Indent}{typename} {label} = {value}"); } internal void Indent() { m_Indent = m_Indent + " | "; } internal void Undent() { m_Indent = m_Indent.Substring(0, m_Indent.Length - 4); } // Generate a one line string describing a type. For normal types it's just the type name, for generic type it has the generic arguments included internal static string DescribeType(Type typeToDescribe) { if (!typeToDescribe.IsGenericType) return typeToDescribe.Name; Type[] genericArgs = typeToDescribe.GetGenericArguments(); string argString = $"{SanitiseGenericName(typeToDescribe.Name)}<"; if (genericArgs.Length > 0) { if (genericArgs[0].IsGenericType) argString += DescribeType(genericArgs[0]); else argString += genericArgs[0].Name; for (int argNum = 1; argNum < genericArgs.Length; argNum++) { if (genericArgs[argNum].IsGenericType) argString += ", " + DescribeType(genericArgs[argNum]); else argString += ", " + genericArgs[argNum].Name; } } return argString + ">"; } internal StringBuilder Dump(string label, object thingToDump) { if (thingToDump == null) { Add(label, ""); return m_StringBuilder; } Type thingType = thingToDump.GetType(); if ((thingToDump != null) && (m_CustomDumpers.TryGetValue(thingType, out ICustomDumper customDumper))) { Add(label); customDumper.CustomDumper(this, thingToDump); return m_StringBuilder; } Add(label); if (m_Indent.Length > 256) { m_StringBuilder.AppendLine("*** Indent depth exceeded, dump aborted ***"); return m_StringBuilder; } if (m_StringBuilder.Length > 32 * 1024 * 1024) { m_StringBuilder.AppendLine("*** StringBuilder length exceeded, dump aborted ***"); return m_StringBuilder; } Indent(); foreach (FieldInfo field in thingType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { if (typeof(Array).IsAssignableFrom(field.FieldType)) { // An array Array array = field.GetValue(thingToDump) as Array; if (array != null) { if (array.Rank != 1) throw new InvalidDataException($"Arrays of ranks other than 1 are not currently supported - array '{field.Name}' is rank {array.Rank})"); Type elementType = field.FieldType.GetElementType(); Add(field.FieldType.Name, field.Name, $"{DescribeType(elementType)}[{array.Length}]"); string name = SanitiseFieldName(field.Name); if (elementType == typeof(string)) { Indent(); for (int elementIndex = 0; elementIndex < array.Length; elementIndex++) { Add($"{name}[{elementIndex}]", "\"" + ((string)array.GetValue(elementIndex)) + "\""); } Undent(); } else if (elementType == typeof(byte)) DumpPrimitiveArray(name, array); else if (elementType == typeof(Type)) DumpSimpleObjectArray(name, array); else { // General array for (int elementIndex = 0; elementIndex < array.Length; elementIndex++) { Indent(); object element = array.GetValue(elementIndex); Dump($"{name}[{elementIndex}] ({DescribeType(element.GetType())})", element); Undent(); } } } else Add(field.FieldType.Name, field.Name, "null (array)"); } else if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(List<>))) { // A List<> System.Collections.IList list = field.GetValue(thingToDump) as System.Collections.IList; if (list != null) { string typeString = DescribeType(field.FieldType); Add(typeString, field.Name, $"({list.Count})"); string name = SanitiseFieldName(field.Name); for (int elementIndex = 0; elementIndex < list.Count; elementIndex++) { Indent(); object element = list[elementIndex]; Dump($"{name}[{elementIndex}] ({DescribeType(element.GetType())})", element); Undent(); } } else Add(field.FieldType.Name, field.Name, "null (List<>)"); } else if (field.FieldType.IsEnum) { Add("enum " + field.FieldType.Name, SanitiseFieldName(field.Name), Enum.GetName(field.FieldType, field.GetValue(thingToDump))); } else if (field.FieldType == typeof(String)) { object fieldValue = field.GetValue(thingToDump); if (fieldValue != null) Add(field.FieldType.Name, SanitiseFieldName(field.Name), "\"" + ((string)fieldValue) + "\""); else Add(field.FieldType.Name, SanitiseFieldName(field.Name), ""); } else if (field.FieldType == typeof(RuntimeTypeHandle)) { Add("RuntimeTypeHandle", field.Name, Type.GetTypeFromHandle((RuntimeTypeHandle)field.GetValue(thingToDump)).AssemblyQualifiedName); } else if (field.FieldType.IsClass) { if (String.Equals(field.FieldType.Name, "MonoCMethod")) // Don't recurse into 'MonoCMethod' as it can end up in a loop Add("class " + field.FieldType.Name, SanitiseFieldName(field.Name)); else Dump("class " + field.FieldType.Name + " " + SanitiseFieldName(field.Name), field.GetValue(thingToDump)); } else if (field.FieldType.IsValueType && (!field.FieldType.IsPrimitive)) { Dump("struct " + field.FieldType.Name + " " + SanitiseFieldName(field.Name), field.GetValue(thingToDump)); } else Add(field.FieldType.Name, SanitiseFieldName(field.Name), field.GetValue(thingToDump).ToString()); } Undent(); return m_StringBuilder; } void DumpPrimitiveArray(string name, Array array) { Indent(); for (int elementIndex = 0; elementIndex < array.Length; elementIndex++) { Add($"{name}[{elementIndex}]", ((PrimitiveType)array.GetValue(elementIndex)).ToString()); } Undent(); } void DumpSimpleObjectArray(string name, Array array) { Indent(); for (int elementIndex = 0; elementIndex < array.Length; elementIndex++) { PrimitiveType element = (PrimitiveType)array.GetValue(elementIndex); if (element != null) Add($"{name}[{elementIndex}]", element.ToString()); else Add($"{name}[{elementIndex}]", "null"); } Undent(); } } // Custom DumpToText support for BuildUsageTagSet that cannot be properly dumped by reflection alone class CustomDumper_BuildUsageTagSet : DumpToText.ICustomDumper { Type DumpToText.ICustomDumper.GetType() { return typeof(BuildUsageTagSet); } void DumpToText.ICustomDumper.CustomDumper(DumpToText dumpToText, object value) { BuildUsageTagSet buildUsageTagSet = (BuildUsageTagSet)value; ObjectIdentifier[] objectIdentifiers = buildUsageTagSet.GetObjectIdentifiers(); dumpToText.Indent(); if (objectIdentifiers != null) { dumpToText.Add("ObjectIdentifier[]", "objectIdentifiers", $"ObjectIdentifier[{objectIdentifiers.Length}]"); dumpToText.Indent(); for (int objectIdentifierIndex = 0; objectIdentifierIndex < objectIdentifiers.Length; objectIdentifierIndex++) { dumpToText.Add("ObjectIdentifier", $"[{objectIdentifierIndex}]"); dumpToText.Indent(); dumpToText.Dump("ObjectIdentifier", objectIdentifiers[objectIdentifierIndex]); dumpToText.Undent(); } dumpToText.Undent(); } else dumpToText.Add("ObjectIdentifier[]", "objectIdentifiers", "null"); dumpToText.Undent(); } } }