pascalabcnet/VisualPascalABCNETLinux/MonoDebugging/Client/ObjectValue.cs

883 lines
24 KiB
C#
Raw Normal View History

2023-04-10 22:04:27 +03:00
// ObjectValue.cs
//
// Authors: Lluis Sanchez Gual <lluis@novell.com>
// Jeffrey Stedfast <jeff@xamarin.com>
//
// Copyright (c) 2008 Novell, Inc (http://www.novell.com)
// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using Mono.Debugging.Backend;
namespace Mono.Debugging.Client
{
[Serializable]
public class ObjectValue
{
ObjectPath path;
int arrayCount = -1;
bool isNull;
string name;
string value;
string typeName;
string displayValue;
string childSelector;
ObjectValueFlags flags;
IObjectValueSource source;
IObjectValueUpdater updater;
List<ObjectValue> children;
ManualResetEvent evaluatedEvent;
[NonSerialized]
readonly object mutex = new object ();
[NonSerialized]
UpdateCallback updateCallback;
[NonSerialized]
EventHandler valueChanged;
[NonSerialized]
StackFrame parentFrame;
static ObjectValue Create (IObjectValueSource source, ObjectPath path, string typeName)
{
var val = new ObjectValue ();
val.typeName = typeName;
val.source = source;
val.path = path;
return val;
}
public static ObjectValue CreateObject (IObjectValueSource source, ObjectPath path, string typeName, string value, ObjectValueFlags flags, ObjectValue[] children)
{
return CreateObject (source, path, typeName, new EvaluationResult (value), flags, children);
}
public static ObjectValue CreateObject (IObjectValueSource source, ObjectPath path, string typeName, EvaluationResult value, ObjectValueFlags flags, ObjectValue[] children)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.Object;
val.displayValue = value.DisplayValue;
val.value = value.Value;
if (children != null) {
val.children = new List<ObjectValue> ();
val.children.AddRange (children);
}
return val;
}
public static ObjectValue CreateNullObject (IObjectValueSource source, string name, string typeName, ObjectValueFlags flags)
{
return CreateNullObject (source, new ObjectPath (name), typeName, flags);
}
public static ObjectValue CreateNullObject (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.Object;
val.value = "(null)";
val.isNull = true;
return val;
}
public static ObjectValue CreatePrimitive (IObjectValueSource source, ObjectPath path, string typeName, EvaluationResult value, ObjectValueFlags flags)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.Primitive;
val.displayValue = value.DisplayValue;
val.value = value.Value;
return val;
}
public static ObjectValue CreateArray (IObjectValueSource source, ObjectPath path, string typeName, int arrayCount, ObjectValueFlags flags, ObjectValue[] children)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.Array;
val.value = "[" + arrayCount + "]";
val.arrayCount = arrayCount;
if (children != null && children.Length > 0) {
val.children = new List<ObjectValue> ();
val.children.AddRange (children);
}
return val;
}
public static ObjectValue CreateUnknown (IObjectValueSource source, ObjectPath path, string typeName)
{
var val = Create (source, path, typeName);
val.flags = ObjectValueFlags.Unknown | ObjectValueFlags.ReadOnly;
return val;
}
public static ObjectValue CreateUnknown (string name)
{
return CreateUnknown (null, new ObjectPath (name), "");
}
public static ObjectValue CreateError (IObjectValueSource source, ObjectPath path, string typeName, string value, ObjectValueFlags flags)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.Error;
val.value = value;
return val;
}
public static ObjectValue CreateImplicitNotSupported (IObjectValueSource source, ObjectPath path, string typeName, ObjectValueFlags flags)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.ImplicitNotSupported;
val.value = "Implicit evaluation is disabled";
return val;
}
public static ObjectValue CreateNotSupported (IObjectValueSource source, ObjectPath path, string typeName, string message, ObjectValueFlags flags)
{
var val = Create (source, path, typeName);
val.flags = flags | ObjectValueFlags.NotSupported;
val.value = message;
return val;
}
public static ObjectValue CreateFatalError (string name, string message, ObjectValueFlags flags)
{
var val = new ObjectValue ();
val.flags = flags | ObjectValueFlags.Error;
val.value = message;
val.name = name;
return val;
}
public static ObjectValue CreateEvaluating (IObjectValueUpdater updater, ObjectPath path, ObjectValueFlags flags)
{
var val = Create (null, path, null);
val.flags = flags | ObjectValueFlags.Evaluating;
val.updater = updater;
return val;
}
public static ObjectValue CreateShowMore ()
{
return new ObjectValue () {
flags = ObjectValueFlags.IEnumerable,
name = ""
};
}
public DebuggerSession DebuggerSession {
get {
return parentFrame?.DebuggerSession;
}
}
/// <summary>
/// Gets the flags of the value
/// </summary>
public ObjectValueFlags Flags {
get { return flags; }
}
/// <summary>
/// Name of the value (for example, the property name)
/// </summary>
public string Name {
get { return name ?? path[path.Length - 1]; }
set { name = value; }
}
/// <summary>
/// Gets or sets the value of the object
/// </summary>
/// <value>
/// The value.
/// </value>
/// <exception cref='InvalidOperationException'>
/// Is thrown when trying to set a value on a read-only ObjectValue
/// </exception>
/// <remarks>
/// This value is a string representation of the ObjectValue. The content depends on several evaluation
/// options. For example, if ToString calls are enabled, this value will be the result of calling
/// ToString.
/// If the object is a primitive type, in general the Value will be an expression that represents the
/// value in the target language. For example, when debugging C#, if the property is an string, the value
/// will include the quotation marks and chars like '\' will be properly escaped.
/// If you need to get the real CLR value of the object, use GetRawValue.
/// </remarks>
public virtual string Value {
get {
return value;
}
set {
if (IsReadOnly || source == null)
throw new InvalidOperationException ("Value is not editable");
var res = source.SetValue (path, value, null);
if (res != null) {
this.value = res.Value;
displayValue = res.DisplayValue;
isNull = value == null;
}
}
}
/// <summary>
/// Gets or sets the display value of this object
/// </summary>
/// <remarks>
/// This method returns a string to be used when showing the value of this object.
/// In most cases, the Value and DisplayValue properties return the same text, but there are some cases
/// in which DisplayValue may return a more convenient textual representation of the value, which
/// may not be a valid target language expression.
/// For example in C#, an enum Value includes the full enum type name (e.g. "Gtk.ResponseType.OK"),
/// while DisplayValue only has the enum value name ("OK").
/// </remarks>
public string DisplayValue {
get { return displayValue ?? Value; }
set { displayValue = value; }
}
/// <summary>
/// Sets the value of this object, using the default evaluation options
/// </summary>
public void SetValue (string value)
{
SetValue (value, parentFrame.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Sets the value of this object, using the specified evaluation options
/// </summary>
/// <param name='value'>
/// The value
/// </param>
/// <param name='options'>
/// The options
/// </param>
/// <exception cref='InvalidOperationException'>
/// Is thrown if the value is read-only
/// </exception>
public void SetValue (string value, EvaluationOptions options)
{
if (IsReadOnly || source == null)
throw new InvalidOperationException ("Value is not editable");
var res = source.SetValue (path, value, options);
if (res != null) {
this.value = res.Value;
displayValue = res.DisplayValue;
}
}
/// <summary>
/// Gets the raw value of this object
/// </summary>
/// <returns>
/// The raw value.
/// </returns>
/// <remarks>
/// This method can be used to get the CLR value of the object. For example, if this ObjectValue is
/// a property of type String, this method will return the System.String value of the property.
/// If this ObjectValue refers to an object instead of a primitive value, then a RawValue object
/// will be returned. RawValue can be used to get and set members of an object, and to call methods.
/// If this ObjectValue refers to an array, then a RawValueArray object will be returned.
/// </remarks>
public object GetRawValue ()
{
var ops = parentFrame.DebuggerSession.EvaluationOptions.Clone ();
ops.EllipsizeStrings = false;
return GetRawValue (ops);
}
/// <summary>
/// Gets the raw value of this object
/// </summary>
/// <param name='options'>
/// The evaluation options
/// </param>
/// <returns>
/// The raw value.
/// </returns>
/// <remarks>
/// This method can be used to get the CLR value of the object. For example, if this ObjectValue is
/// a property of type String, this method will return the System.String value of the property.
/// If this ObjectValue refers to an object instead of a primitive value, then a RawValue object
/// will be returned. RawValue can be used to get and set members of an object, and to call methods.
/// If this ObjectValue refers to an array, then a RawValueArray object will be returned.
/// </remarks>
public object GetRawValue (EvaluationOptions options)
{
if (source == null && (IsEvaluating || IsEvaluatingGroup)) {
if (!WaitHandle.WaitOne (options.EvaluationTimeout)) {
throw new Evaluation.TimeOutException ();
}
}
var res = source.GetRawValue (path, options);
if (res is IRawObject raw)
raw.Connect (parentFrame.DebuggerSession, options);
return res;
}
/// <summary>
/// Sets the raw value of this object
/// </summary>
/// <param name='value'>
/// The value
/// </param>
/// <remarks>
/// The provided value can be a primitive type, a RawValue object or a RawValueArray object.
/// </remarks>
public void SetRawValue (object value)
{
SetRawValue (value, parentFrame.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Sets the raw value of this object
/// </summary>
/// <param name='value'>
/// The value
/// </param>
/// <param name='options'>
/// The evaluation options
/// </param>
/// <remarks>
/// The provided value can be a primitive type, a RawValue object or a RawValueArray object.
/// </remarks>
public void SetRawValue (object value, EvaluationOptions options)
{
if (source == null && (IsEvaluating || IsEvaluatingGroup)) {
if (!WaitHandle.WaitOne (options.EvaluationTimeout)) {
throw new Evaluation.TimeOutException ();
}
}
source.SetRawValue (path, value, options);
}
/// <summary>
/// Full name of the type of the object
/// </summary>
public string TypeName {
get { return typeName; }
set { typeName = value; }
}
/// <summary>
/// Gets or sets the child selector.
/// </summary>
/// <remarks>
/// The child selector is an expression which can be concatenated to a parent expression to get this child.
/// For example, if this object is a reference to a field named 'foo' of an object, the child
/// selector is '.foo'.
/// </remarks>
public string ChildSelector {
get {
if (childSelector != null)
return childSelector;
if ((flags & ObjectValueFlags.ArrayElement) != 0)
return Name;
return "." + Name;
}
set { childSelector = value; }
}
/// <summary>
/// Gets a value indicating whether this object has children.
/// </summary>
/// <value>
/// <c>true</c> if this instance has children; otherwise, <c>false</c>.
/// </value>
public bool HasChildren {
get {
if (isNull)
return false;
if (IsEvaluating)
return false;
if (children != null)
return children.Count > 0;
if (source == null)
return false;
if (IsArray)
return arrayCount > 0;
if (IsObject)
return true;
return false;
}
}
/// <summary>
/// Gets a child value
/// </summary>
/// <returns>
/// The child.
/// </returns>
/// <param name='name'>
/// Name of the member
/// </param>
/// <remarks>
/// This method can be used to get a member of an object (such as a field or property)
/// </remarks>
public ObjectValue GetChild (string name)
{
return GetChild (name, parentFrame?.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Gets a child value
/// </summary>
/// <returns>
/// The child.
/// </returns>
/// <param name='name'>
/// Name of the member
/// </param>
/// <param name='options'>
/// Options to be used to evaluate the child
/// </param>
/// <remarks>
/// This method can be used to get a member of an object (such as a field or property)
/// </remarks>
public ObjectValue GetChild (string name, EvaluationOptions options)
{
if (IsArray)
throw new InvalidOperationException ("Object is an array.");
if (IsEvaluating)
return null;
if (children == null) {
children = new List<ObjectValue> ();
if (source != null) {
try {
var cs = source.GetChildren (path, -1, -1, options);
ConnectCallbacks (parentFrame, cs);
children.AddRange (cs);
} catch (Exception ex) {
children = null;
return CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly);
}
}
}
foreach (var child in children) {
if (child.Name == name)
return child;
}
return null;
}
/// <summary>
/// Gets all children of the object
/// </summary>
/// <returns>
/// An array of all child values
/// </returns>
public ObjectValue[] GetAllChildren ()
{
return GetAllChildren (parentFrame.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Gets all children of the object
/// </summary>
/// <returns>
/// An array of all child values
/// </returns>
/// <param name='options'>
/// Options to be used to evaluate the children
/// </param>
public ObjectValue[] GetAllChildren (EvaluationOptions options)
{
if (IsEvaluating)
return new ObjectValue[0];
if (IsArray) {
GetArrayItem (arrayCount - 1);
return children.ToArray ();
}
if (children == null) {
children = new List<ObjectValue> ();
if (source != null) {
try {
var cs = source.GetChildren (path, -1, -1, options);
ConnectCallbacks (parentFrame, cs);
children.AddRange (cs);
} catch (Exception ex) {
if (parentFrame != null)
parentFrame.DebuggerSession.OnDebuggerOutput (true, ex.ToString ());
children.Add (CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly));
}
}
}
return children.ToArray ();
}
public ObjectValue[] GetRangeOfChildren (int index, int count)
{
return GetRangeOfChildren (index, count, parentFrame.DebuggerSession.EvaluationOptions);
}
public ObjectValue[] GetRangeOfChildren (int index, int count, EvaluationOptions options)
{
if (IsEvaluating)
return new ObjectValue[0];
if (IsArray) {
GetArrayItem (arrayCount - 1);
if (index >= ArrayCount)
return new ObjectValue[0];
return children.Skip (index).Take (Math.Min (count, ArrayCount - index)).ToArray ();
}
if (children == null) {
children = new List<ObjectValue> ();
}
if (children.Count < index + count) {
if (source != null) {
try {
ObjectValue[] cs = source.GetChildren (path, children.Count, index + count, options);
ConnectCallbacks (parentFrame, cs);
children.AddRange (cs);
} catch (Exception ex) {
if (parentFrame != null)
parentFrame.DebuggerSession.OnDebuggerOutput (true, ex.ToString ());
children.Add (CreateFatalError ("", ex.Message, ObjectValueFlags.ReadOnly));
}
}
}
if (index >= children.Count)
return new ObjectValue[0];
return children.Skip (index).Take (Math.Min (count, children.Count - index)).ToArray ();
}
/// <summary>
/// Gets an item of an array
/// </summary>
/// <returns>
/// The array item.
/// </returns>
/// <param name='index'>
/// Item index
/// </param>
/// <exception cref='InvalidOperationException'>
/// Is thrown if this object is not an array (IsArray returns false)
/// </exception>
public ObjectValue GetArrayItem (int index)
{
return GetArrayItem (index, parentFrame.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Gets an item of an array
/// </summary>
/// <returns>
/// The array item.
/// </returns>
/// <param name='index'>
/// Item index
/// </param>
/// <param name='options'>
/// Options to be used to evaluate the item
/// </param>
/// <exception cref='InvalidOperationException'>
/// Is thrown if this object is not an array (IsArray returns false)
/// </exception>
public ObjectValue GetArrayItem (int index, EvaluationOptions options)
{
if (!IsArray)
throw new InvalidOperationException ("Object is not an array.");
if (index >= arrayCount || index < 0 || IsEvaluating)
throw new IndexOutOfRangeException ();
if (children == null)
children = new List<ObjectValue> ();
if (index >= children.Count) {
int nc = (index + 50);
if (nc > arrayCount)
nc = arrayCount;
nc = nc - children.Count;
try {
var items = source.GetChildren (path, children.Count, nc, options);
ConnectCallbacks (parentFrame, items);
children.AddRange (items);
} catch (Exception ex) {
return CreateFatalError ("", ex.Message, ObjectValueFlags.ArrayElement | ObjectValueFlags.ReadOnly);
}
}
return children [index];
}
/// <summary>
/// Gets the number of items of an array
/// </summary>
/// <exception cref='InvalidOperationException'>
/// Is thrown if this object is not an array (IsArray returns false)
/// </exception>
public int ArrayCount {
get {
if (!IsArray)
throw new InvalidOperationException ("Object is not an array.");
if (IsEvaluating)
return 0;
return arrayCount;
}
}
public bool IsNull {
get { return isNull; }
}
public bool IsReadOnly {
get { return HasFlag (ObjectValueFlags.ReadOnly); }
}
public bool IsArray {
get { return HasFlag (ObjectValueFlags.Array); }
}
public bool IsObject {
get { return HasFlag (ObjectValueFlags.Object); }
}
public bool IsPrimitive {
get { return HasFlag (ObjectValueFlags.Primitive); }
}
public bool IsUnknown {
get { return HasFlag (ObjectValueFlags.Unknown); }
}
public bool IsNotSupported {
get { return HasFlag (ObjectValueFlags.NotSupported); }
}
public bool IsImplicitNotSupported {
get { return HasFlag (ObjectValueFlags.ImplicitNotSupported); }
}
public bool IsError {
get { return HasFlag (ObjectValueFlags.Error); }
}
public bool IsEvaluating {
get { return HasFlag (ObjectValueFlags.Evaluating); }
}
public bool IsEvaluatingGroup {
get { return HasFlag (ObjectValueFlags.EvaluatingGroup); }
}
public bool CanRefresh {
get { return source != null && !HasFlag (ObjectValueFlags.NoRefresh); }
}
public bool HasFlag (ObjectValueFlags flag)
{
return (flags & flag) != 0;
}
public event EventHandler ValueChanged {
add {
lock (mutex) {
valueChanged += value;
if (!IsEvaluating)
value (this, EventArgs.Empty);
}
}
remove {
lock (mutex) {
valueChanged -= value;
}
}
}
/// <summary>
/// Refreshes the value of this object
/// </summary>
/// <remarks>
/// This method can be called to get a more up-to-date value for this object.
/// </remarks>
public void Refresh ()
{
Refresh (parentFrame.DebuggerSession.EvaluationOptions);
}
/// <summary>
/// Refreshes the value of this object
/// </summary>
/// <remarks>
/// This method can be called to get a more up-to-date value for this object.
/// </remarks>
public void Refresh (EvaluationOptions options)
{
if (!CanRefresh)
return;
var val = source.GetValue (path, options);
UpdateFrom (val, true);
}
/// <summary>
/// Gets a wait handle which can be used to wait for the evaluation of this object to end
/// </summary>
/// <value>
/// The wait handle.
/// </value>
public WaitHandle WaitHandle {
get {
lock (mutex) {
if (evaluatedEvent == null)
evaluatedEvent = new ManualResetEvent (!IsEvaluating);
return evaluatedEvent;
}
}
}
internal IObjectValueUpdater Updater {
get { return updater; }
}
internal void UpdateFrom (ObjectValue val, bool notify)
{
lock (mutex) {
arrayCount = val.arrayCount;
if (val.name != null)
name = val.name;
value = val.value;
displayValue = val.displayValue;
typeName = val.typeName;
flags = val.flags;
source = val.source;
children = val.children;
path = val.path;
updater = val.updater;
ConnectCallbacks (parentFrame, this);
if (evaluatedEvent != null)
evaluatedEvent.Set ();
if (notify && valueChanged != null)
valueChanged (this, EventArgs.Empty);
}
}
internal UpdateCallback GetUpdateCallback ()
{
if (IsEvaluating) {
if (updateCallback == null)
updateCallback = new UpdateCallback (new UpdateCallbackProxy (this), path);
return updateCallback;
}
return null;
}
~ObjectValue ()
{
#if !NETCOREAPP
if (updateCallback != null)
System.Runtime.Remoting.RemotingServices.Disconnect ((UpdateCallbackProxy)updateCallback.Callback);
#endif
}
internal static void ConnectCallbacks (StackFrame parentFrame, params ObjectValue[] values)
{
Dictionary<IObjectValueUpdater, List<UpdateCallback>> callbacks = null;
var valueList = new List<ObjectValue> (values);
for (int n = 0; n < valueList.Count; n++) {
var val = valueList [n];
val.source = parentFrame.DebuggerSession.WrapDebuggerObject (val.source);
val.updater = parentFrame.DebuggerSession.WrapDebuggerObject (val.updater);
val.parentFrame = parentFrame;
var cb = val.GetUpdateCallback ();
if (cb != null) {
if (callbacks == null)
callbacks = new Dictionary<IObjectValueUpdater, List<UpdateCallback>> ();
List<UpdateCallback> list;
if (!callbacks.TryGetValue (val.Updater, out list)) {
list = new List<UpdateCallback> ();
callbacks [val.Updater] = list;
}
list.Add (cb);
}
if (val.children != null)
valueList.AddRange (val.children);
}
if (callbacks != null) {
// Do the callback connection in a background thread
ThreadPool.QueueUserWorkItem (delegate {
foreach (KeyValuePair<IObjectValueUpdater, List<UpdateCallback>> cbs in callbacks) {
cbs.Key.RegisterUpdateCallbacks (cbs.Value.ToArray ());
}
});
}
}
}
class UpdateCallbackProxy: MarshalByRefObject, IObjectValueUpdateCallback
{
readonly WeakReference valRef;
public void UpdateValue (ObjectValue newValue)
{
var val = valRef.Target as ObjectValue;
if (val != null)
val.UpdateFrom (newValue, true);
}
public UpdateCallbackProxy (ObjectValue val)
{
valRef = new WeakReference (val);
}
}
}