From 001e386ad0a41c21bf0f8de2cd9ab80816653a02 Mon Sep 17 00:00:00 2001 From: Dmitry Belyaev Date: Tue, 19 Aug 2014 12:30:08 +1000 Subject: [PATCH] Remember objects are being printed This prevents stack overflow if an object being pretty printed has loops in its attributes. --- MiniMods/PrettyPrint/PrettyPrintMinimod.cs | 56 ++++++++++++++++--- .../PrettyPrint/PrettyPrintMinimodTests.cs | 51 +++++++++++++++++ 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/MiniMods/PrettyPrint/PrettyPrintMinimod.cs b/MiniMods/PrettyPrint/PrettyPrintMinimod.cs index 0fcfb73..c9be9df 100644 --- a/MiniMods/PrettyPrint/PrettyPrintMinimod.cs +++ b/MiniMods/PrettyPrint/PrettyPrintMinimod.cs @@ -14,6 +14,33 @@ namespace Minimod.PrettyPrint { + internal class FrameStack + { + private object head; + private FrameStack tail; + + public static FrameStack Empty = new FrameStack(); + + private FrameStack() {} + + public FrameStack(object obj, FrameStack rest) + { + head = obj; + tail = rest; + } + + public bool Contains(object obj) + { + var current = this; + while (current != Empty) + { + if (current.head == obj) return true; + current = current.tail; + } + return false; + } + } + /// ///

Minimod.PrettyPrint, Version 1.0.1, Copyright © Lars Corneliussen 2011

/// Creates nice textual representations of any objects. Mostly meant for debug/informational output. @@ -405,12 +432,22 @@ public static string PrettyPrint(this object anyObject, Type declaredType, Func< } public static string PrettyPrint(this object anyObject, Type declaredType, Settings settings) + { + return PrettyPrint(anyObject, declaredType, settings, FrameStack.Empty); + } + + internal static string PrettyPrint(object anyObject, Type declaredType, Settings settings, FrameStack printing) { if (anyObject == null) { return ""; } + if (printing.Contains(anyObject)) + { + return "<" + anyObject.GetType().GetPrettyName() + " - referenced earlier>"; + } + var formatter = settings.GetCustomFormatter(anyObject); if (formatter != null) { @@ -434,7 +471,7 @@ public static string PrettyPrint(this object anyObject, Type declaredType, Setti if (anyObject is IEnumerable) { - return enumerable(anyObject as IEnumerable, declaredType, settings); + return enumerable(anyObject as IEnumerable, declaredType, settings, new FrameStack(anyObject, printing)); } if (declaredType.IsEnum && Enum.IsDefined(declaredType, anyObject)) @@ -442,14 +479,14 @@ public static string PrettyPrint(this object anyObject, Type declaredType, Setti return String.Format("<{0}.{1} = {2}>", declaredType.Name, Enum.GetName(declaredType, anyObject), (int)anyObject); } - return GenericFormatter.Format(actualType, anyObject, settings); + return GenericFormatter.Format(actualType, anyObject, settings, new FrameStack(anyObject, printing)); } #endregion - private static string enumerable(IEnumerable objects, Type declaredType, Settings settings) + private static string enumerable(IEnumerable objects, Type declaredType, Settings settings, FrameStack printing) { - string[] items = objects.Cast().Select(_ => _.PrettyPrint(settings)).ToArray(); + string[] items = objects.Cast().Select(x => PrettyPrint(x, x.GetType(), settings, printing)).ToArray(); if (settings.PrefersMultiline ?? ( @@ -502,13 +539,18 @@ public class MemberDetails } public static string Format(Type actualType, object anyObject, Settings settings) + { + return Format(actualType, anyObject, settings, FrameStack.Empty); + } + + internal static string Format(Type actualType, object anyObject, Settings settings, FrameStack printing) { if (actualType == null) throw new ArgumentNullException("actualType"); if (anyObject == null) throw new ArgumentNullException("anyObject"); if (settings == null) throw new ArgumentNullException("settings"); var members = - findAndFormatMembers(anyObject, settings, actualType) + findAndFormatMembers(anyObject, settings, actualType, printing) .Where(m => m.Value != null || (settings.OmitsNullMembers ?? false)) .ToArray(); @@ -538,7 +580,7 @@ private static bool mayFormatKeyValuePairs(MemberDetails[] members, out string f } private static IEnumerable findAndFormatMembers(object anyObject, Settings settings, - Type actualType) + Type actualType, FrameStack printing) { var properties = from prop in actualType.GetMembers().OfType() @@ -581,7 +623,7 @@ from prop in actualType.GetMembers().OfType() { pretty = hasCustomFormatter ? propFormatter(m.value, m.name, anyObject, settings) - : m.value.PrettyPrint(m.type, settings); + : PrettyPrint(m.value, m.type, settings, printing); } else { diff --git a/MiniMods/PrettyPrint/PrettyPrintMinimodTests.cs b/MiniMods/PrettyPrint/PrettyPrintMinimodTests.cs index 92c32ab..465fa9c 100644 --- a/MiniMods/PrettyPrint/PrettyPrintMinimodTests.cs +++ b/MiniMods/PrettyPrint/PrettyPrintMinimodTests.cs @@ -27,6 +27,57 @@ public void Sample2_UglyArray() Console.WriteLine(value); Console.WriteLine(value.PrettyPrint()); } + + class A + { + public int i; + public A a; + } + + [Test] + public void PrettyPrintRecursiveTypes() + { + var tail = new A { i = 0 }; + var head = new A { a = tail, i = 1 }; + tail.a = head; + + Console.WriteLine(head.PrettyPrint()); + Console.WriteLine(tail.PrettyPrint()); + } + + [Test] + public void PrettyPrintDeepRecusion() + { + var tail = new A { i = 0 }; + var head = tail; + + for (var i = 1; i < 100; ++i) + { + head = new A { a = head, i = i }; + } + + tail.a = head; + + Console.WriteLine(head.PrettyPrint()); + Console.WriteLine(tail.PrettyPrint()); + } + + class B + { + public int i; + public IEnumerable bs; + } + + [Test] + public void PrettyPrintEnumerable() + { + var tail = new B { i = 0 }; + var head = new B { i = 1, bs = new B[] { tail } }; + tail.bs = new [] { head }; + + Console.WriteLine(head.PrettyPrint()); + Console.WriteLine(tail.PrettyPrint()); + } } [TestFixture]