// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) using System; using System.Collections.Generic; using System.Reflection; using Debugger.AddIn.TreeModel; using Debugger.AddIn.Visualizers.Graph.Layout; using Debugger.AddIn.Visualizers.Utils; using Debugger.MetaData; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Ast; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Services; namespace Debugger.AddIn.Visualizers.Graph { // The object graph building starts with given expression and recursively // explores all its members. // // Important part of the algorithm is finding if we already have a node // for given value - to detect loops and shared references correctly. // This is done using the following algorithm: // // getNodeForValue(value) // get the hashCode for the value // find if there is already a node with this hashCode (in O(1)) // if not, we can be sure we have not seen this value yet // if yes, it might be different object with the same hashCode -> compare addresses // // 'different object with the same hashCode' are possible - my question on stackoverflow: // http://stackoverflow.com/questions/750947/-net-unique-object-identifier // // This way, the whole graph building is O(n) in the size of the resulting graph. // However, evals are still very expensive -> lazy evaluation of only values that are actually seen by user. /// /// Builds for given string expression. /// public class ObjectGraphBuilder { /// /// The underlying debugger service used for getting expression values. /// private WindowsDebugger debuggerService; private ObjectGraph resultGraph; /// /// Underlying object graph data struture. /// public ObjectGraph ResultGraph { get { return this.resultGraph; } } /// /// Given hash code, lookup already existing node(s) with this hash code. /// private Lookup objectNodesForHashCode = new Lookup(); /// /// Binding flags for getting member expressions. /// private readonly BindingFlags publicInstanceMemberFlags = BindingFlags.Public | BindingFlags.Instance; private readonly BindingFlags nonPublicInstanceMemberFlags = BindingFlags.NonPublic | BindingFlags.Instance; /// /// Creates ObjectGraphBuilder. /// /// Debugger service. public ObjectGraphBuilder(WindowsDebugger debuggerService) { this.debuggerService = debuggerService; } /// /// Builds full object graph for given string expression. /// /// Expression valid in the program being debugged (eg. variable name) /// Object graph public ObjectGraph BuildGraphForExpression(GraphExpression expression, ExpandedExpressions expandedNodes) { if (WindowsDebugger.CurrentStackFrame == null) { throw new DebuggerVisualizerException("Please use the visualizer when debugging."); } Value rootValue = expression.GetValue(); if (rootValue.IsNull) { throw new DebuggerVisualizerException(expression + " is null."); } return buildGraphForValue(rootValue.GetPermanentReference(WindowsDebugger.EvalThread), expression, expandedNodes); } private ObjectGraph buildGraphForValue(Value rootValue, GraphExpression rootExpression, ExpandedExpressions expandedNodes) { resultGraph = new ObjectGraph(); //resultGraph.Root = buildGraphRecursive(debuggerService.GetValueFromName(expression).GetPermanentReference(), expandedNodes); resultGraph.Root = createNewNode(rootValue, rootExpression); loadContent(resultGraph.Root); loadNeighborsRecursive(resultGraph.Root, expandedNodes); return resultGraph; } public ObjectGraphNode ObtainNodeForExpression(GraphExpression expr) { bool createdNewNode; // ignored (caller is not interested, otherwise he would use the other overload) return ObtainNodeForExpression(expr, out createdNewNode); } public ObjectGraphNode ObtainNodeForExpression(GraphExpression expr, out bool createdNewNode) { return ObtainNodeForValue(expr.GetValue().GetPermanentReference(WindowsDebugger.EvalThread), expr, out createdNewNode); } /// /// Returns node in the graph that represents given value, or returns new node if not found. /// /// Value for which to obtain the node/ /// True if new node was created, false if existing node was returned. public ObjectGraphNode ObtainNodeForValue(Value value, GraphExpression expression, out bool createdNew) { createdNew = false; ObjectGraphNode nodeForValue = getExistingNodeForValue(value); if (nodeForValue == null) { // if no node for memberValue exists, create it nodeForValue = createNewNode(value, expression); loadContent(nodeForValue); createdNew = true; } return nodeForValue; } /// /// Fills node Content property tree. /// private void loadContent(ObjectGraphNode thisNode) { var contentRoot = new ThisNode(); thisNode.Content = contentRoot; // Object graph visualizer: collection support temp disabled (porting to new NRefactory). /*DebugType collectionType; DebugType itemType; if (thisNode.PermanentReference.Type.ResolveIListImplementation(out collectionType, out itemType)) { //AddRawViewNode(contentRoot, thisNode); // it is an IList LoadNodeCollectionContent(contentRoot, thisNode.Expression, collectionType); } else if (thisNode.PermanentReference.Type.ResolveIEnumerableImplementation(out collectionType, out itemType)) { //AddRawViewNode(contentRoot, thisNode); // it is an IEnumerable DebugType debugListType; var debugListExpression = new GraphExpression( DebuggerHelpers.CreateDebugListExpression(thisNode.Expression.Expr, itemType, out debugListType), () => DebuggerHelpers.CreateListFromIEnumerable(thisNode.Expression.GetValue()) ); LoadNodeCollectionContent(contentRoot, debugListExpression, debugListType); } else*/ { // it is an object LoadNodeObjectContent(contentRoot, thisNode.Expression, thisNode.PermanentReference.Type); } } void AddRawViewNode(AbstractNode contentRoot, ObjectGraphNode thisNode) { var rawViewNode = new RawViewNode(); contentRoot.AddChild(rawViewNode); LoadNodeObjectContent(rawViewNode, thisNode.Expression, thisNode.PermanentReference.Type); } // Object graph visualizer: collection support temp disabled (porting to new NRefactory). /*void LoadNodeCollectionContent(AbstractNode node, GraphExpression thisObject, DebugType iListType) { var thisObjectAsIList = new GraphExpression(thisObject.Expr.CastToIList(), thisObject.GetValue); int listCount = thisObjectAsIList.GetValue().GetIListCount(); PropertyInfo indexerProp = iListType.GetProperty("Item"); var v = new List(); for (int i = 0; i < listCount; i++) { var itemExpr = new GraphExpression( thisObjectAsIList.Expr.AppendIndexer(i), () => thisObjectAsIList.GetValue().GetIListItem(i) // EXPR-EVAL, Does a 'cast' to IList ); PropertyNode itemNode = new PropertyNode( new ObjectGraphProperty { Name = "[" + i + "]", MemberInfo = indexerProp, Expression = itemExpr, Value = "", IsAtomic = true, TargetNode = null }); node.AddChild(itemNode); } }*/ void LoadNodeObjectContent(AbstractNode node, GraphExpression expression, DebugType type) { // base if (type.BaseType != null && type.BaseType.FullName != "System.Object") { var baseClassNode = new BaseClassNode(type.BaseType.FullName, type.BaseType.Name); node.AddChild(baseClassNode); LoadNodeObjectContent(baseClassNode, expression, (DebugType)type.BaseType); } // non-public members var nonPublicProperties = getProperties(expression, type, this.nonPublicInstanceMemberFlags); if (nonPublicProperties.Count > 0) { var nonPublicMembersNode = new NonPublicMembersNode(); node.AddChild(nonPublicMembersNode); foreach (var nonPublicProperty in nonPublicProperties) { nonPublicMembersNode.AddChild(new PropertyNode(nonPublicProperty)); } } // public members foreach (var property in getProperties(expression, type, this.publicInstanceMemberFlags)) { node.AddChild(new PropertyNode(property)); } } private List getProperties(GraphExpression expression, DebugType shownType, BindingFlags flags) { List propertyList = new List(); foreach (MemberInfo memberProp in shownType.GetFieldsAndNonIndexedProperties(flags)) { if (memberProp.Name.Contains("<")) { // skip backing fields continue; } if (memberProp.DeclaringType != shownType) { // skip properties declared in the base type continue; } // ObjectGraphProperty needs string representation to know whether it is expanded var propExpression = new GraphExpression( expression.Expr.AppendMemberReference((IDebugMemberInfo)memberProp), () => expression.GetValue().GetMemberValue(WindowsDebugger.EvalThread, memberProp) ); // Value, IsAtomic are lazy evaluated propertyList.Add(new ObjectGraphProperty { Name = memberProp.Name, Expression = propExpression, Value = "", MemberInfo = memberProp, IsAtomic = true, TargetNode = null }); } return propertyList.Sorted(ObjectPropertyComparer.Instance); } /// /// For each complex property of this node, creates a neighbor graph node if needed and connects /// it using to ObjectProperty.TargetNode. /// private void loadNeighborsRecursive(ObjectGraphNode thisNode, ExpandedExpressions expandedNodes) { // evaluate properties first in case property getters are changing some fields - the fields will then have correct values foreach(ObjectGraphProperty complexProperty in thisNode.PropertiesFirstThenFields) { ObjectGraphNode targetNode = null; // We are only evaluating expanded nodes here. // We have to do this to know the "shape" of the graph. // We do not evaluate atomic and non-expanded properties, those will be lazy evaluated when drawn. if (expandedNodes.IsExpanded(complexProperty.Expression.Expr)) { // if expanded, evaluate this property Value memberValue = complexProperty.Expression.GetValue(); if (memberValue.IsNull) { continue; } else { // if property value is not null, create neighbor memberValue = memberValue.GetPermanentReference(WindowsDebugger.EvalThread); bool createdNew; // get existing node (loop) or create new targetNode = ObtainNodeForValue(memberValue, complexProperty.Expression, out createdNew); if (createdNew) { // if member node is new, recursively build its subtree loadNeighborsRecursive(targetNode, expandedNodes); } } } // connect property to target ObjectGraphNode complexProperty.TargetNode = targetNode; } } /// /// Creates new node for the value. /// /// Value, has to be valid. /// New empty object node representing the value. private ObjectGraphNode createNewNode(Value permanentReference, GraphExpression expression) { if (permanentReference == null) throw new ArgumentNullException("permanentReference"); ObjectGraphNode newNode = new ObjectGraphNode(); if (permanentReference.Type != null) { newNode.TypeName = permanentReference.Type.FormatNameCSharp(); } newNode.HashCode = permanentReference.InvokeDefaultGetHashCode(); resultGraph.AddNode(newNode); // remember this node's hashcode for quick lookup objectNodesForHashCode.Add(newNode.HashCode, newNode); // permanent reference to the object this node represents is useful for graph building, // and matching nodes in animations newNode.PermanentReference = permanentReference; newNode.Expression = expression; return newNode; } /// /// Finds node that represents the same instance as given value. /// /// Valid value representing an instance. private ObjectGraphNode getExistingNodeForValue(Value value) { int objectHashCode = value.InvokeDefaultGetHashCode(); // are there any nodes with the same hash code? LookupValueCollection nodesWithSameHashCode = objectNodesForHashCode[objectHashCode]; if (nodesWithSameHashCode == null) { return null; } else { // if there is a node with same hash code, check if it has also the same address // (hash codes are not uniqe - http://stackoverflow.com/questions/750947/-net-unique-object-identifier) ulong objectAddress = value.GetObjectAddress(); ObjectGraphNode nodeWithSameAddress = nodesWithSameHashCode.Find( node => { return node.PermanentReference.GetObjectAddress() == objectAddress; } ); return nodeWithSameAddress; } } } }