mirror of https://github.com/icsharpcode/ILSpy.git
443 lines
12 KiB
443 lines
12 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
using System; |
|
using System.Collections.Generic; |
|
using System.ComponentModel; |
|
using System.Globalization; |
|
using System.Reflection; |
|
using System.Runtime.InteropServices; |
|
using System.Windows.Media; |
|
|
|
using Debugger; |
|
using Debugger.MetaData; |
|
using Decompiler; |
|
using ICSharpCode.NRefactory.Ast; |
|
using ICSharpCode.NRefactory.CSharp; |
|
using ILSpy.Debugger.Services; |
|
using ILSpy.Debugger.Services.Debugger; |
|
|
|
namespace ILSpy.Debugger.Models.TreeModel |
|
{ |
|
public class ExpressionNode: TreeNode, ISetText, INotifyPropertyChanged |
|
{ |
|
bool evaluated; |
|
|
|
Expression expression; |
|
bool canSetText; |
|
GetValueException error; |
|
string fullText; |
|
|
|
public bool Evaluated { |
|
get { return evaluated; } |
|
set { evaluated = value; } |
|
} |
|
|
|
public Expression Expression { |
|
get { return expression; } |
|
} |
|
|
|
public override bool CanSetText { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return canSetText; |
|
} |
|
} |
|
|
|
public GetValueException Error { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return error; |
|
} |
|
} |
|
|
|
public string FullText { |
|
get { return fullText; } |
|
} |
|
|
|
public override string Text { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return base.Text; |
|
} |
|
set { |
|
if (value != base.Text) { |
|
base.Text = value; |
|
NotifyPropertyChanged("Text"); |
|
} |
|
} |
|
} |
|
|
|
public override string FullName { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
|
|
return this.expression.PrettyPrint() ?? Name.Trim(); |
|
} |
|
} |
|
|
|
public override string Type { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return base.Type; |
|
} |
|
} |
|
|
|
public override IEnumerable<TreeNode> ChildNodes { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return base.ChildNodes; |
|
} |
|
} |
|
|
|
public override bool HasChildNodes { |
|
get { |
|
if (!evaluated) EvaluateExpression(); |
|
return base.HasChildNodes; |
|
} |
|
} |
|
|
|
/// <summary> Used to determine available VisualizerCommands </summary> |
|
private DebugType expressionType; |
|
/// <summary> Used to determine available VisualizerCommands </summary> |
|
private bool valueIsNull = true; |
|
|
|
private IEnumerable<IVisualizerCommand> visualizerCommands; |
|
public override IEnumerable<IVisualizerCommand> VisualizerCommands { |
|
get { |
|
if (visualizerCommands == null) { |
|
visualizerCommands = getAvailableVisualizerCommands(); |
|
} |
|
return visualizerCommands; |
|
} |
|
} |
|
|
|
private IEnumerable<IVisualizerCommand> getAvailableVisualizerCommands() |
|
{ |
|
if (!evaluated) EvaluateExpression(); |
|
|
|
if (this.expressionType == null) { |
|
// no visualizers if EvaluateExpression failed |
|
yield break; |
|
} |
|
if (this.valueIsNull) { |
|
// no visualizers if evaluated value is null |
|
yield break; |
|
} |
|
if (this.expressionType.IsPrimitive || this.expressionType.IsSystemDotObject() || this.expressionType.IsEnum()) { |
|
// no visualizers for primitive types |
|
yield break; |
|
} |
|
|
|
yield break; |
|
// foreach (var descriptor in VisualizerDescriptors.GetAllDescriptors()) { |
|
// if (descriptor.IsVisualizerAvailable(this.expressionType)) { |
|
// yield return descriptor.CreateVisualizerCommand(this.Expression); |
|
// } |
|
// } |
|
} |
|
|
|
public ExpressionNode(ImageSource image, string name, Expression expression) |
|
{ |
|
this.ImageSource = image; |
|
this.Name = name; |
|
this.expression = expression; |
|
} |
|
|
|
void EvaluateExpression() |
|
{ |
|
evaluated = true; |
|
|
|
Value val; |
|
try { |
|
var frame = WindowsDebugger.DebuggedProcess.SelectedThread.MostRecentStackFrame; |
|
int token = frame.MethodInfo.MetadataToken; |
|
// get the target name |
|
int index = Name.IndexOf('.'); |
|
string targetName = Name; |
|
if (index != -1) { |
|
targetName = Name.Substring(0, index); |
|
} |
|
|
|
List<ILVariable> list; |
|
if (ILAstBuilder.MemberLocalVariables.TryGetValue(token, out list)) { |
|
var variable = list.Find(v => v.Name == targetName); |
|
if (variable != null) { |
|
if (expression is MemberReferenceExpression) { |
|
var memberExpression = (MemberReferenceExpression)expression; |
|
memberExpression.Target.AddAnnotation(new int[] { variable.OriginalVariable.Index }); |
|
} else { |
|
expression.AddAnnotation(new int[] { variable.OriginalVariable.Index }); |
|
} |
|
} |
|
} |
|
|
|
val = expression.Evaluate(WindowsDebugger.DebuggedProcess); |
|
} catch (GetValueException e) { |
|
error = e; |
|
this.Text = e.Message; |
|
return; |
|
} |
|
|
|
this.canSetText = val.Type.IsPrimitive; |
|
|
|
this.expressionType = val.Type; |
|
this.Type = val.Type.Name; |
|
this.valueIsNull = val.IsNull; |
|
|
|
// Note that these return enumerators so they are lazy-evaluated |
|
if (val.IsNull) { |
|
} else if (val.Type.IsPrimitive || val.Type.FullName == typeof(string).FullName) { // Must be before IsClass |
|
} else if (val.Type.IsArray) { // Must be before IsClass |
|
if (val.ArrayLength > 0) |
|
this.ChildNodes = Utils.LazyGetChildNodesOfArray(this.Expression, val.ArrayDimensions); |
|
} else if (val.Type.IsClass || val.Type.IsValueType) { |
|
if (val.Type.FullNameWithoutGenericArguments == typeof(List<>).FullName) { |
|
if ((int)val.GetMemberValue("_size").PrimitiveValue > 0) |
|
this.ChildNodes = Utils.LazyGetItemsOfIList(this.expression); |
|
} else { |
|
this.ChildNodes = Utils.LazyGetChildNodesOfObject(this.Expression, val.Type); |
|
} |
|
} else if (val.Type.IsPointer) { |
|
Value deRef = val.Dereference(); |
|
if (deRef != null) { |
|
this.ChildNodes = new ExpressionNode [] { new ExpressionNode(this.ImageSource, "*" + this.Name, this.Expression.AppendDereference()) }; |
|
} |
|
} |
|
|
|
// if (DebuggingOptions.Instance.ICorDebugVisualizerEnabled) { |
|
// TreeNode info = ICorDebug.GetDebugInfoRoot(val.AppDomain, val.CorValue); |
|
// this.ChildNodes = Utils.PrependNode(info, this.ChildNodes); |
|
// } |
|
|
|
// Do last since it may expire the object |
|
if (val.Type.IsInteger) { |
|
fullText = FormatInteger(val.PrimitiveValue); |
|
} else if (val.Type.IsPointer) { |
|
fullText = String.Format("0x{0:X}", val.PointerAddress); |
|
} else if ((val.Type.FullName == typeof(string).FullName || |
|
val.Type.FullName == typeof(char).FullName) && !val.IsNull) { |
|
try { |
|
fullText = '"' + Escape(val.InvokeToString()) + '"'; |
|
} catch (GetValueException e) { |
|
error = e; |
|
fullText = e.Message; |
|
return; |
|
} |
|
} else if ((val.Type.IsClass || val.Type.IsValueType) && !val.IsNull) { |
|
try { |
|
fullText = val.InvokeToString(); |
|
} catch (GetValueException e) { |
|
error = e; |
|
fullText = e.Message; |
|
return; |
|
} |
|
} else { |
|
fullText = val.AsString(); |
|
} |
|
|
|
this.Text = (fullText.Length > 256) ? fullText.Substring(0, 256) + "..." : fullText; |
|
} |
|
|
|
string Escape(string source) |
|
{ |
|
return source.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("\"", "\\\""); |
|
} |
|
|
|
string FormatInteger(object i) |
|
{ |
|
// if (DebuggingOptions.Instance.ShowIntegersAs == ShowIntegersAs.Decimal) |
|
if (true) |
|
return i.ToString(); |
|
|
|
// string hex = null; |
|
// for(int len = 1;; len *= 2) { |
|
// hex = string.Format("{0:X" + len + "}", i); |
|
// if (hex.Length == len) |
|
// break; |
|
// } |
|
// |
|
// if (true) { |
|
// return "0x" + hex; |
|
// } else { |
|
// if (ShowAsHex(i)) { |
|
// return String.Format("{0} (0x{1})", i, hex); |
|
// } else { |
|
// return i.ToString(); |
|
// } |
|
// } |
|
} |
|
|
|
bool ShowAsHex(object i) |
|
{ |
|
ulong val; |
|
if (i is sbyte || i is short || i is int || i is long) { |
|
unchecked { val = (ulong)Convert.ToInt64(i); } |
|
if (val > (ulong)long.MaxValue) |
|
val = ~val + 1; |
|
} else { |
|
val = Convert.ToUInt64(i); |
|
} |
|
if (val >= 0x10000) |
|
return true; |
|
|
|
int ones = 0; // How many 1s there is |
|
int runs = 0; // How many runs of 1s there is |
|
int size = 0; // Size of the integer in bits |
|
while(val != 0) { // There is at least one 1 |
|
while((val & 1) == 0) { // Skip 0s |
|
val = val >> 1; |
|
size++; |
|
} |
|
while((val & 1) == 1) { // Skip 1s |
|
val = val >> 1; |
|
size++; |
|
ones++; |
|
} |
|
runs++; |
|
} |
|
|
|
return size >= 7 && runs <= (size + 7) / 8; |
|
} |
|
|
|
public override bool SetText(string newText) |
|
{ |
|
string fullName = FullName; |
|
|
|
Value val = null; |
|
try { |
|
val = this.Expression.Evaluate(WindowsDebugger.DebuggedProcess); |
|
if (val.Type.IsInteger && newText.StartsWith("0x")) { |
|
try { |
|
val.PrimitiveValue = long.Parse(newText.Substring(2), NumberStyles.HexNumber); |
|
} catch (FormatException) { |
|
throw new NotSupportedException(); |
|
} catch (OverflowException) { |
|
throw new NotSupportedException(); |
|
} |
|
} else { |
|
val.PrimitiveValue = newText; |
|
} |
|
this.Text = newText; |
|
return true; |
|
} catch (NotSupportedException) { |
|
string format = "Can not convert {0} to {1}"; |
|
string msg = string.Format(format, newText, val.Type.PrimitiveType); |
|
System.Windows.MessageBox.Show(msg); |
|
} catch (COMException) { |
|
// COMException (0x80131330): Cannot perfrom SetValue on non-leaf frames. |
|
// Happens if trying to set value after exception is breaked |
|
System.Windows.MessageBox.Show("UnknownError"); |
|
} |
|
return false; |
|
} |
|
|
|
public static ImageSource GetImageForThis(out string imageName) |
|
{ |
|
imageName = "Icons.16x16.Parameter"; |
|
return ImageService.GetImage(imageName); |
|
} |
|
|
|
public static ImageSource GetImageForParameter(out string imageName) |
|
{ |
|
imageName = "Icons.16x16.Parameter"; |
|
return ImageService.GetImage(imageName); |
|
} |
|
|
|
public static ImageSource GetImageForLocalVariable(out string imageName) |
|
{ |
|
imageName = "Icons.16x16.Local"; |
|
return ImageService.GetImage(imageName); |
|
} |
|
|
|
public static ImageSource GetImageForArrayIndexer(out string imageName) |
|
{ |
|
imageName = "Icons.16x16.Field"; |
|
return ImageService.GetImage(imageName); |
|
} |
|
|
|
public static ImageSource GetImageForMember(IDebugMemberInfo memberInfo, out string imageName) |
|
{ |
|
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); |
|
} |
|
|
|
imageName = "Icons.16x16." + name; |
|
return ImageService.GetImage(imageName); |
|
} |
|
|
|
// 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; |
|
// } |
|
|
|
public static WindowsDebugger WindowsDebugger { |
|
get { |
|
return (WindowsDebugger)DebuggerService.CurrentDebugger; |
|
} |
|
} |
|
|
|
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; |
|
|
|
private void NotifyPropertyChanged(string info) |
|
{ |
|
if (PropertyChanged != null) |
|
{ |
|
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(info)); |
|
} |
|
} |
|
} |
|
}
|
|
|