// 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Debugger.AddIn.Visualizers;
using Debugger.AddIn.Visualizers.Utils;
using Debugger.MetaData;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Debugging;
using ICSharpCode.SharpDevelop.Gui.Pads;
using ICSharpCode.SharpDevelop.Services;
namespace Debugger.AddIn.TreeModel
{
///
/// Tree node which represents debuggee's .
/// The node stores a lambda which can be used to reobtain the value
/// at any time (possibly even after some stepping).
///
///
/// The general rule is that getting a value or getting children will
/// either succeed or result in .
///
public class ValueNode: TreeNode
{
Func getValue;
Action setValue;
Value cachedValue;
Debugger.Process cachedValueProcess;
object cachedValueDebuggeeState;
string fullValue;
GetValueException error;
public string FullText {
get { return this.Value; }
}
public ValueNode(string imageName, string name, Func getValue, Action setValue = null)
: base(imageName, name, string.Empty, string.Empty, null)
{
if (getValue == null)
throw new ArgumentNullException("getValue");
this.getValue = getValue;
this.setValue = setValue;
GetValueAndUpdateUI();
}
///
/// Get the value of the node and cache it as long-lived reference.
/// We assume that the user will need this value a lot.
///
public Value GetValue()
{
// The value still survives across debuggee state, but we want a fresh one for the UI
if (cachedValue == null || cachedValueProcess.DebuggeeState != cachedValueDebuggeeState)
{
Stopwatch watch = new Stopwatch();
watch.Start();
cachedValue = this.getValue().GetPermanentReference();
cachedValueProcess = cachedValue.Process;
cachedValueDebuggeeState = cachedValue.Process.DebuggeeState;
LoggingService.InfoFormatted("Evaluated node '{0}' in {1} ms (result cached for future use)", this.Name, watch.ElapsedMilliseconds);
}
return cachedValue;
}
public void SetValue(Value value)
{
if (setValue == null)
throw new DebuggerException("Setting of value is not supported for this node");
try
{
this.setValue(value);
}
catch(GetValueException e)
{
MessageService.ShowMessage(e.Message, "${res:MainWindow.Windows.Debug.LocalVariables.CannotSetValue.Title}");
}
}
///
/// Get the value of the node and update the UI text fields.
///
void GetValueAndUpdateUI()
{
try {
Stopwatch watch = new Stopwatch();
watch.Start();
// Do not keep permanent reference
Value val = this.getValue();
// Note that the child collections are lazy-evaluated
if (val.IsNull) {
this.GetChildren = null;
} else if (val.Type.IsPrimitive || val.Type.FullName == typeof(string).FullName) { // Must be before IsClass
this.GetChildren = null;
} else if (val.Type.IsArray) { // Must be before IsClass
var dims = val.ArrayDimensions; // Eval now
if (dims.TotalElementCount > 0) {
this.GetChildren = () => GetArrayChildren(dims, dims);
}
} else if (val.Type.IsClass || val.Type.IsValueType) {
if (val.Type.FullNameWithoutGenericArguments == typeof(List<>).FullName) {
if ((int)val.GetMemberValue("_size").PrimitiveValue > 0)
this.GetChildren = () => GetIListChildren(this.GetValue);
} else {
this.GetChildren = () => GetObjectChildren(val.Type);
}
} else if (val.Type.IsPointer) {
if (val.Dereference() != null) {
this.GetChildren = () => new[] { new ValueNode("Icons.16x16.Local", "*" + this.Name, () => GetValue().Dereference()) };
}
}
// Do last since it may expire the object
if (val.IsNull) {
fullValue = "null";
} else if (val.Type.IsInteger) {
var i = val.PrimitiveValue;
if (DebuggingOptions.Instance.ShowIntegersAs == ShowIntegersAs.Decimal) {
fullValue = i.ToString();
} else {
string hex = string.Format("0x{0:X4}", i);
if (hex.Length > 6 ) hex = string.Format("0x{0:X8}", i);
if (hex.Length > 10) hex = string.Format("0x{0:X16}", i);
if (DebuggingOptions.Instance.ShowIntegersAs == ShowIntegersAs.Hexadecimal) {
fullValue = hex;
} else {
fullValue = string.Format("{0} ({1})", i, hex);
}
}
} else if (val.Type.IsPointer) {
fullValue = String.Format("0x{0:X}", val.PointerAddress);
} else if (val.Type.FullName == typeof(string).FullName) {
fullValue = '"' + val.InvokeToString().Replace("\n", "\\n").Replace("\t", "\\t").Replace("\r", "\\r").Replace("\0", "\\0").Replace("\b", "\\b").Replace("\a", "\\a").Replace("\f", "\\f").Replace("\v", "\\v").Replace("\"", "\\\"") + '"';
} else if (val.Type.FullName == typeof(char).FullName) {
fullValue = "'" + val.InvokeToString().Replace("\n", "\\n").Replace("\t", "\\t").Replace("\r", "\\r").Replace("\0", "\\0").Replace("\b", "\\b").Replace("\a", "\\a").Replace("\f", "\\f").Replace("\v", "\\v").Replace("\"", "\\\"") + "'";
} else if ((val.Type.IsClass || val.Type.IsValueType)) {
fullValue = val.InvokeToString();
} else {
fullValue = val.AsString();
}
this.error = null;
this.Value = (fullValue.Length > 256) ? fullValue.Substring(0, 256) + "..." : fullValue;
this.Type = val.Type.Name;
if (!val.IsNull) {
this.VisualizerCommands = VisualizerDescriptors.GetAllDescriptors()
.Where(descriptor => descriptor.IsVisualizerAvailable(val.Type))
.Select(descriptor => descriptor.CreateVisualizerCommand(this.Name, this.GetValue))
.ToList();
}
LoggingService.InfoFormatted("Evaluated node '{0}' in {1} ms", this.Name, watch.ElapsedMilliseconds);
} catch (GetValueException e) {
error = e;
this.Value = e.Message;
this.Type = string.Empty;
this.GetChildren = null;
this.VisualizerCommands = null;
return;
}
}
// public ContextMenuStrip GetContextMenu()
// {
// if (this.Error != null) return GetErrorContextMenu();
//
// ContextMenuStrip menu = new ContextMenuStrip();
//
// ToolStripMenuItem copyItem;
// copyItem = new ToolStripMenuItem();
// copyItem.Text = ResourceService.GetString("MainWindow.Windows.Debug.LocalVariables.CopyToClipboard");
// copyItem.Checked = false;
// copyItem.Click += delegate {
// ClipboardWrapper.SetText(fullText);
// };
// ToolStripMenuItem hexView;
// hexView = new ToolStripMenuItem();
// hexView.Text = ResourceService.GetString("MainWindow.Windows.Debug.LocalVariables.ShowInHexadecimal");
// hexView.Checked = DebuggingOptions.Instance.ShowValuesInHexadecimal;
// hexView.Click += delegate {
// // refresh all pads that use ValueNode for display
// DebuggingOptions.Instance.ShowValuesInHexadecimal = !DebuggingOptions.Instance.ShowValuesInHexadecimal;
// // always check if instance is null, might be null if pad is not opened
// if (LocalVarPad.Instance != null)
// LocalVarPad.Instance.RefreshPad();
// if (WatchPad.Instance != null)
// WatchPad.Instance.RefreshPad();
// };
// menu.Items.AddRange(new ToolStripItem[] {
// copyItem,
// //hexView
// });
//
// return menu;
// }
ContextMenuStrip GetErrorContextMenu()
{
ContextMenuStrip menu = new ContextMenuStrip();
ToolStripMenuItem showError = new ToolStripMenuItem();
showError.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.LocalVariables.ShowFullError}");
showError.Click += delegate { MessageService.ShowException(error, null); };
menu.Items.Add(showError);
return menu;
}
public static string GetImageForMember(IDebugMemberInfo memberInfo)
{
string name = string.Empty;
if (memberInfo.IsPublic) {
} else if (memberInfo.IsAssembly) {
name += "Internal";
} else if (memberInfo.IsFamily) {
name += "Protected";
} else if (memberInfo.IsPrivate) {
name += "Private";
}
if (memberInfo is FieldInfo) {
name += "Field";
} else if (memberInfo is PropertyInfo) {
name += "Property";
} else if (memberInfo is MethodInfo) {
name += "Method";
} else {
throw new DebuggerException("Unknown member type " + memberInfo.GetType().FullName);
}
return "Icons.16x16." + name;
}
///
/// The root of any node evaluation is valid stack frame.
///
static StackFrame GetCurrentStackFrame()
{
var debugger = DebuggerService.CurrentDebugger as WindowsDebugger;
if (debugger == null || debugger.DebuggedProcess == null)
throw new GetValueException("Debugger is not running");
if (debugger.DebuggedProcess.IsRunning)
throw new GetValueException("Process is not paused");
if (debugger.DebuggedProcess.SelectedStackFrame == null)
throw new GetValueException("No stack frame selected");
return debugger.DebuggedProcess.SelectedStackFrame;
}
public static IEnumerable GetLocalVariables()
{
var stackFrame = GetCurrentStackFrame();
foreach(DebugParameterInfo par in stackFrame.MethodInfo.GetParameters()) {
var parCopy = par;
yield return new ValueNode("Icons.16x16.Parameter", par.Name, () => parCopy.GetValue(GetCurrentStackFrame()));
}
if (stackFrame.HasSymbols) {
foreach(DebugLocalVariableInfo locVar in stackFrame.MethodInfo.GetLocalVariables(stackFrame.IP)) {
var locVarCopy = locVar;
yield return new ValueNode("Icons.16x16.Local", locVar.Name, () => locVarCopy.GetValue(GetCurrentStackFrame()));
}
} else {
WindowsDebugger debugger = (WindowsDebugger)DebuggerService.CurrentDebugger;
if (debugger.debuggerDecompilerService != null) {
int typeToken = stackFrame.MethodInfo.DeclaringType.MetadataToken;
int methodToken = stackFrame.MethodInfo.MetadataToken;
foreach (var localVar in debugger.debuggerDecompilerService.GetLocalVariables(typeToken, methodToken)) {
int index = ((int[])debugger.debuggerDecompilerService.GetLocalVariableIndex(typeToken, methodToken, localVar))[0];
yield return new ValueNode("Icons.16x16.Local", localVar, () => {
var newStackFrame = GetCurrentStackFrame();
if (newStackFrame.MethodInfo != stackFrame.MethodInfo)
throw new GetValueException("Expected stack frame: " + stackFrame.MethodInfo.ToString());
return newStackFrame.GetLocalVariableValue((uint)index);
});
}
}
}
}
IEnumerable GetObjectChildren(DebugType shownType)
{
MemberInfo[] publicStatic = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
MemberInfo[] publicInstance = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
MemberInfo[] nonPublicStatic = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly);
MemberInfo[] nonPublicInstance = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
DebugType baseType = (DebugType)shownType.BaseType;
if (baseType != null) {
yield return new TreeNode(
"Icons.16x16.Class",
StringParser.Parse("${res:MainWindow.Windows.Debug.LocalVariables.BaseClass}"),
baseType.Name,
baseType.FullName,
baseType.FullName == "System.Object" ? (Func>) null : () => GetObjectChildren(baseType)
);
}
if (nonPublicInstance.Length > 0) {
yield return new TreeNode(
StringParser.Parse("${res:MainWindow.Windows.Debug.LocalVariables.NonPublicMembers}"),
() => GetMembers(nonPublicInstance)
);
}
if (publicStatic.Length > 0 || nonPublicStatic.Length > 0) {
yield return new TreeNode(
StringParser.Parse("${res:MainWindow.Windows.Debug.LocalVariables.StaticMembers}"),
() => {
var children = GetMembers(publicStatic).ToList();
if (nonPublicStatic.Length > 0) {
children.Insert(0, new TreeNode(
StringParser.Parse("${res:MainWindow.Windows.Debug.LocalVariables.NonPublicStaticMembers}"),
() => GetMembers(nonPublicStatic)
));
}
return children;
}
);
}
if (shownType.GetInterface(typeof(IList).FullName) != null) {
yield return new TreeNode(
"IList",
() => GetIListChildren(GetValue)
);
} else {
DebugType iEnumerableType, itemType;
if (shownType.ResolveIEnumerableImplementation(out iEnumerableType, out itemType)) {
yield return new TreeNode(
null,
"IEnumerable",
"Expanding will enumerate the IEnumerable",
string.Empty,
() => GetIListChildren(() => DebuggerHelpers.CreateListFromIEnumeralbe(GetValue()))
);
}
}
foreach(TreeNode node in GetMembers(publicInstance)) {
yield return node;
}
}
IEnumerable GetMembers(MemberInfo[] members)
{
foreach(MemberInfo memberInfo in members.OrderBy(m => m.Name)) {
var memberInfoCopy = memberInfo;
string imageName = GetImageForMember((IDebugMemberInfo)memberInfo);
yield return new ValueNode(imageName, memberInfo.Name, () => GetValue().GetMemberValue(memberInfoCopy));
}
}
static IEnumerable GetIListChildren(Func getValue)
{
Value list;
PropertyInfo itemProp;
int count = 0;
try {
// TODO: We want new list on reeval
// We need the list to survive generation of index via Eval
list = getValue().GetPermanentReference();
DebugType iListType = (DebugType)list.Type.GetInterface(typeof(IList).FullName);
itemProp = iListType.GetProperty("Item");
// Do not get string representation since it can be printed in hex
count = (int)list.GetPropertyValue(iListType.GetProperty("Count")).PrimitiveValue;
} catch (GetValueException e) {
return new [] { new TreeNode(null, "(error)", e.Message, string.Empty, null) };
}
if (count == 0) {
return new [] { new TreeNode("(empty)", null) };
} else {
return Enumerable.Range(0, count).Select(i => new ValueNode("Icons.16x16.Field", "[" + i + "]", () => list.GetPropertyValue(itemProp, Eval.CreateValue(list.AppDomain, i))));
}
}
TreeNode GetArraySubsetNode(ArrayDimensions bounds, ArrayDimensions originalBounds)
{
StringBuilder name = new StringBuilder();
bool isFirst = true;
name.Append("[");
for(int i = 0; i < bounds.Count; i++) {
if (!isFirst) name.Append(", ");
isFirst = false;
ArrayDimension dim = bounds[i];
ArrayDimension originalDim = originalBounds[i];
if (dim.Count == 0) {
name.Append("-");
} else if (dim.Count == 1) {
name.Append(dim.LowerBound.ToString());
} else if (dim.Equals(originalDim)) {
name.Append("*");
} else {
name.Append(dim.LowerBound);
name.Append("..");
name.Append(dim.UpperBound);
}
}
name.Append("]");
return new TreeNode(name.ToString(), () => GetArrayChildren(bounds, originalBounds));
}
IEnumerable GetArrayChildren(ArrayDimensions bounds, ArrayDimensions originalBounds)
{
const int MaxElementCount = 1000;
if (bounds.TotalElementCount == 0)
{
yield return new TreeNode("(empty)", null);
yield break;
}
// The whole array is small - just add all elements as childs
if (bounds.TotalElementCount <= MaxElementCount * 2) {
foreach(int[] indices in bounds.Indices) {
StringBuilder sb = new StringBuilder(indices.Length * 4);
sb.Append("[");
bool isFirst = true;
foreach(int index in indices) {
if (!isFirst) sb.Append(", ");
sb.Append(index.ToString());
isFirst = false;
}
sb.Append("]");
int[] indicesCopy = indices;
yield return new ValueNode("Icons.16x16.Field", sb.ToString(), () => GetValue().GetArrayElement(indicesCopy));
}
yield break;
}
// Find a dimension of size at least 2
int splitDimensionIndex = bounds.Count - 1;
for(int i = 0; i < bounds.Count; i++) {
if (bounds[i].Count > 1) {
splitDimensionIndex = i;
break;
}
}
ArrayDimension splitDim = bounds[splitDimensionIndex];
// Split the dimension
int elementsPerSegment = 1;
while (splitDim.Count > elementsPerSegment * MaxElementCount) {
elementsPerSegment *= MaxElementCount;
}
for(int i = splitDim.LowerBound; i <= splitDim.UpperBound; i += elementsPerSegment) {
List newDims = new List(bounds);
newDims[splitDimensionIndex] = new ArrayDimension(i, Math.Min(i + elementsPerSegment - 1, splitDim.UpperBound));
yield return GetArraySubsetNode(new ArrayDimensions(newDims), originalBounds);
}
yield break;
}
}
}