You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
13 KiB
327 lines
13 KiB
// 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. |
|
|
|
/// <summary> |
|
/// Builds <see cref="ObjectGraph" /> for given string expression. |
|
/// </summary> |
|
public class ObjectGraphBuilder |
|
{ |
|
/// <summary> |
|
/// The underlying debugger service used for getting expression values. |
|
/// </summary> |
|
private WindowsDebugger debuggerService; |
|
|
|
private ObjectGraph resultGraph; |
|
/// <summary> |
|
/// Underlying object graph data struture. |
|
/// </summary> |
|
public ObjectGraph ResultGraph { get { return this.resultGraph; } } |
|
|
|
/// <summary> |
|
/// Given hash code, lookup already existing node(s) with this hash code. |
|
/// </summary> |
|
private Lookup<int, ObjectGraphNode> objectNodesForHashCode = new Lookup<int, ObjectGraphNode>(); |
|
|
|
/// <summary> |
|
/// Binding flags for getting member expressions. |
|
/// </summary> |
|
private readonly BindingFlags publicInstanceMemberFlags = |
|
BindingFlags.Public | BindingFlags.Instance; |
|
|
|
private readonly BindingFlags nonPublicInstanceMemberFlags = |
|
BindingFlags.NonPublic | BindingFlags.Instance; |
|
|
|
/// <summary> |
|
/// Creates ObjectGraphBuilder. |
|
/// </summary> |
|
/// <param name="debuggerService">Debugger service.</param> |
|
public ObjectGraphBuilder(WindowsDebugger debuggerService) |
|
{ |
|
this.debuggerService = debuggerService; |
|
} |
|
|
|
/// <summary> |
|
/// Builds full object graph for given string expression. |
|
/// </summary> |
|
/// <param name="expression">Expression valid in the program being debugged (eg. variable name)</param> |
|
/// <returns>Object graph</returns> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Returns node in the graph that represents given value, or returns new node if not found. |
|
/// </summary> |
|
/// <param name="value">Value for which to obtain the node/</param> |
|
/// <param name="createdNew">True if new node was created, false if existing node was returned.</param> |
|
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; |
|
} |
|
|
|
/// <summary> |
|
/// Fills node Content property tree. |
|
/// </summary> |
|
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<String>(); |
|
|
|
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<ObjectGraphProperty> getProperties(GraphExpression expression, DebugType shownType, BindingFlags flags) |
|
{ |
|
List<ObjectGraphProperty> propertyList = new List<ObjectGraphProperty>(); |
|
|
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// For each complex property of this node, creates a neighbor graph node if needed and connects |
|
/// it using to ObjectProperty.TargetNode. |
|
/// </summary> |
|
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; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Creates new node for the value. |
|
/// </summary> |
|
/// <param name="permanentReference">Value, has to be valid.</param> |
|
/// <returns>New empty object node representing the value.</returns> |
|
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; |
|
} |
|
|
|
/// <summary> |
|
/// Finds node that represents the same instance as given value. |
|
/// </summary> |
|
/// <param name="value">Valid value representing an instance.</param> |
|
private ObjectGraphNode getExistingNodeForValue(Value value) |
|
{ |
|
int objectHashCode = value.InvokeDefaultGetHashCode(); |
|
// are there any nodes with the same hash code? |
|
LookupValueCollection<ObjectGraphNode> 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; |
|
} |
|
} |
|
} |
|
}
|
|
|