1578 lines
51 KiB
C#
1578 lines
51 KiB
C#
//
|
|
// ObjectValueAdaptor.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.Linq;
|
|
using System.Text;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
|
|
using Mono.Debugging.Client;
|
|
using Mono.Debugging.Backend;
|
|
|
|
namespace Mono.Debugging.Evaluation
|
|
{
|
|
public abstract class ObjectValueAdaptor: IDisposable
|
|
{
|
|
readonly Dictionary<string, TypeDisplayData> typeDisplayData = new Dictionary<string, TypeDisplayData> ();
|
|
|
|
// Time to wait while evaluating before switching to async mode
|
|
public int DefaultEvaluationWaitTime { get; set; }
|
|
|
|
public event EventHandler<BusyStateEventArgs> BusyStateChanged;
|
|
|
|
public DebuggerSession DebuggerSession {
|
|
get { return asyncEvaluationTracker.Session; }
|
|
set { asyncEvaluationTracker.Session = value; }
|
|
}
|
|
|
|
static readonly Dictionary<string, string> CSharpTypeNames = new Dictionary<string, string> ();
|
|
readonly AsyncEvaluationTracker asyncEvaluationTracker = new AsyncEvaluationTracker ();
|
|
readonly AsyncOperationManager asyncOperationManager = new AsyncOperationManager ();
|
|
|
|
static ObjectValueAdaptor ()
|
|
{
|
|
CSharpTypeNames["System.Void"] = "void";
|
|
CSharpTypeNames["System.Object"] = "object";
|
|
CSharpTypeNames["System.Boolean"] = "bool";
|
|
CSharpTypeNames["System.Byte"] = "byte";
|
|
CSharpTypeNames["System.SByte"] = "sbyte";
|
|
CSharpTypeNames["System.Char"] = "char";
|
|
CSharpTypeNames["System.Enum"] = "enum";
|
|
CSharpTypeNames["System.Int16"] = "short";
|
|
CSharpTypeNames["System.Int32"] = "int";
|
|
CSharpTypeNames["System.Int64"] = "long";
|
|
CSharpTypeNames["System.UInt16"] = "ushort";
|
|
CSharpTypeNames["System.UInt32"] = "uint";
|
|
CSharpTypeNames["System.UInt64"] = "ulong";
|
|
CSharpTypeNames["System.Single"] = "float";
|
|
CSharpTypeNames["System.Double"] = "double";
|
|
CSharpTypeNames["System.Decimal"] = "decimal";
|
|
CSharpTypeNames["System.String"] = "string";
|
|
}
|
|
|
|
protected ObjectValueAdaptor ()
|
|
{
|
|
DefaultEvaluationWaitTime = 100;
|
|
|
|
asyncOperationManager.BusyStateChanged += (sender, e) => OnBusyStateChanged (e);
|
|
asyncEvaluationTracker.WaitTime = DefaultEvaluationWaitTime;
|
|
}
|
|
|
|
public void Dispose ()
|
|
{
|
|
asyncEvaluationTracker.Dispose ();
|
|
asyncOperationManager.Dispose ();
|
|
}
|
|
|
|
public ObjectValue CreateObjectValue (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags)
|
|
{
|
|
try {
|
|
return CreateObjectValueImpl (ctx, source, path, obj, flags);
|
|
} catch (EvaluatorAbortedException ex) {
|
|
return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags);
|
|
} catch (EvaluatorException ex) {
|
|
return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags);
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
return ObjectValue.CreateFatalError (path.LastName, ex.Message, flags);
|
|
}
|
|
}
|
|
|
|
public virtual string GetDisplayTypeName (string typeName)
|
|
{
|
|
return GetDisplayTypeName (typeName.Replace ('+', '.'), 0, typeName.Length);
|
|
}
|
|
|
|
public string GetDisplayTypeName (EvaluationContext ctx, object type)
|
|
{
|
|
return GetDisplayTypeName (GetTypeName (ctx, type));
|
|
}
|
|
|
|
string GetDisplayTypeName (string typeName, int startIndex, int endIndex)
|
|
{
|
|
// Note: '[' denotes the start of an array
|
|
// '`' denotes a generic type
|
|
// ',' denotes the start of the assembly name
|
|
int tokenIndex = typeName.IndexOfAny (new [] { '[', '`', ',' }, startIndex, endIndex - startIndex);
|
|
List<string> genericArgs = null;
|
|
string array = string.Empty;
|
|
int genericEndIndex = -1;
|
|
int typeEndIndex;
|
|
|
|
retry:
|
|
if (tokenIndex == -1) // Simple type
|
|
return GetShortTypeName (typeName.Substring (startIndex, endIndex - startIndex));
|
|
|
|
if (typeName[tokenIndex] == ',') // Simple type with an assembly name
|
|
return GetShortTypeName (typeName.Substring (startIndex, tokenIndex - startIndex));
|
|
|
|
// save the index of the end of the type name
|
|
typeEndIndex = tokenIndex;
|
|
|
|
// decode generic args first, if this is a generic type
|
|
if (typeName[tokenIndex] == '`') {
|
|
genericEndIndex = typeName.IndexOf ('[', tokenIndex, endIndex - tokenIndex);
|
|
if (genericEndIndex == -1) {
|
|
// Mono's compiler seems to generate non-generic types with '`'s in the name
|
|
// e.g. __EventHandler`1_FileCopyEventArgs_DelegateFactory_2
|
|
tokenIndex = typeName.IndexOfAny (new [] { '[', ',' }, tokenIndex, endIndex - tokenIndex);
|
|
goto retry;
|
|
}
|
|
|
|
tokenIndex = genericEndIndex;
|
|
genericArgs = GetGenericArguments (typeName, ref tokenIndex, endIndex);
|
|
}
|
|
|
|
// decode array rank info
|
|
while (tokenIndex < endIndex && typeName[tokenIndex] == '[') {
|
|
int arrayEndIndex = typeName.IndexOf (']', tokenIndex, endIndex - tokenIndex);
|
|
if (arrayEndIndex == -1)
|
|
break;
|
|
arrayEndIndex++;
|
|
array += typeName.Substring (tokenIndex, arrayEndIndex - tokenIndex);
|
|
tokenIndex = arrayEndIndex;
|
|
}
|
|
|
|
string name = typeName.Substring (startIndex, typeEndIndex - startIndex);
|
|
|
|
if (genericArgs == null)
|
|
return GetShortTypeName (name) + array;
|
|
|
|
// Use the prettier name for nullable types
|
|
if (name == "System.Nullable" && genericArgs.Count == 1)
|
|
return genericArgs[0] + "?" + array;
|
|
|
|
// Insert the generic arguments next to each type.
|
|
// for example: Foo`1+Bar`1[System.Int32,System.String]
|
|
// is converted to: Foo<int>.Bar<string>
|
|
var builder = new StringBuilder (name);
|
|
int i = typeEndIndex + 1;
|
|
int genericIndex = 0;
|
|
int argCount, next;
|
|
|
|
while (i < genericEndIndex) {
|
|
// decode the argument count
|
|
argCount = 0;
|
|
while (i < genericEndIndex && char.IsDigit (typeName[i])) {
|
|
argCount = (argCount * 10) + (typeName[i] - '0');
|
|
i++;
|
|
}
|
|
|
|
// insert the argument types
|
|
builder.Append ('<');
|
|
while (argCount > 0 && genericIndex < genericArgs.Count) {
|
|
builder.Append (genericArgs[genericIndex++]);
|
|
if (--argCount > 0)
|
|
builder.Append (',');
|
|
}
|
|
builder.Append ('>');
|
|
|
|
// Find the end of the next generic type component
|
|
if ((next = typeName.IndexOf ('`', i, genericEndIndex - i)) == -1)
|
|
next = genericEndIndex;
|
|
|
|
// Append the next generic type component
|
|
builder.Append (typeName, i, next - i);
|
|
|
|
i = next + 1;
|
|
}
|
|
|
|
return builder + array;
|
|
}
|
|
|
|
List<string> GetGenericArguments (string typeName, ref int i, int endIndex)
|
|
{
|
|
// Get a list of the generic arguments.
|
|
// When returning, i points to the next char after the closing ']'
|
|
var genericArgs = new List<string> ();
|
|
|
|
i++;
|
|
|
|
while (i < endIndex && typeName [i] != ']') {
|
|
int pend = FindTypeEnd (typeName, i, endIndex);
|
|
bool escaped = typeName [i] == '[';
|
|
|
|
genericArgs.Add (GetDisplayTypeName (typeName, escaped ? i + 1 : i, escaped ? pend - 1 : pend));
|
|
i = pend;
|
|
|
|
if (i < endIndex && typeName[i] == ',')
|
|
i++;
|
|
}
|
|
|
|
i++;
|
|
|
|
return genericArgs;
|
|
}
|
|
|
|
static int FindTypeEnd (string typeName, int startIndex, int endIndex)
|
|
{
|
|
int i = startIndex;
|
|
int brackets = 0;
|
|
|
|
while (i < endIndex) {
|
|
char c = typeName[i];
|
|
|
|
if (c == '[') {
|
|
brackets++;
|
|
} else if (c == ']') {
|
|
if (brackets <= 0)
|
|
return i;
|
|
|
|
brackets--;
|
|
} else if (c == ',' && brackets == 0) {
|
|
return i;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
public static string GetCSharpTypeName (string typeName)
|
|
{
|
|
int star = typeName.IndexOf ('*');
|
|
string name, ptr, csharp;
|
|
|
|
if (star != -1) {
|
|
name = typeName.Substring (0, star);
|
|
ptr = typeName.Substring (star);
|
|
} else {
|
|
ptr = string.Empty;
|
|
name = typeName;
|
|
}
|
|
|
|
if (CSharpTypeNames.TryGetValue (name, out csharp))
|
|
return csharp + ptr;
|
|
|
|
return typeName;
|
|
}
|
|
|
|
public virtual string GetShortTypeName (string typeName)
|
|
{
|
|
return GetCSharpTypeName (typeName);
|
|
}
|
|
|
|
public virtual void OnBusyStateChanged (BusyStateEventArgs e)
|
|
{
|
|
EventHandler<BusyStateEventArgs> evnt = BusyStateChanged;
|
|
if (evnt != null)
|
|
evnt (this, e);
|
|
}
|
|
|
|
public abstract ICollectionAdaptor CreateArrayAdaptor (EvaluationContext ctx, object arr);
|
|
public abstract IStringAdaptor CreateStringAdaptor (EvaluationContext ctx, object str);
|
|
|
|
public abstract bool IsNull (EvaluationContext ctx, object val);
|
|
public abstract bool IsPrimitive (EvaluationContext ctx, object val);
|
|
public abstract bool IsPointer (EvaluationContext ctx, object val);
|
|
public abstract bool IsString (EvaluationContext ctx, object val);
|
|
public abstract bool IsArray (EvaluationContext ctx, object val);
|
|
public abstract bool IsEnum (EvaluationContext ctx, object val);
|
|
public abstract bool IsValueType (object type);
|
|
public virtual bool IsPrimitiveType (object type) { throw new NotImplementedException (); }
|
|
public abstract bool IsClass (EvaluationContext ctx, object type);
|
|
public abstract object TryCast (EvaluationContext ctx, object val, object type);
|
|
|
|
public abstract object GetValueType (EvaluationContext ctx, object val);
|
|
public abstract string GetTypeName (EvaluationContext ctx, object type);
|
|
public abstract object[] GetTypeArgs (EvaluationContext ctx, object type);
|
|
public abstract object GetBaseType (EvaluationContext ctx, object type);
|
|
|
|
public virtual bool IsDelayedType (EvaluationContext ctx, object type)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public virtual bool IsGenericType (EvaluationContext ctx, object type)
|
|
{
|
|
return type != null && GetTypeName (ctx, type).IndexOf ('`') != -1;
|
|
}
|
|
|
|
public virtual IEnumerable<object> GetGenericTypeArguments (EvaluationContext ctx, object type)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
public virtual bool IsNullableType (EvaluationContext ctx, object type)
|
|
{
|
|
return type != null && GetTypeName (ctx, type).StartsWith ("System.Nullable`1", StringComparison.Ordinal);
|
|
}
|
|
|
|
public virtual bool NullableHasValue (EvaluationContext ctx, object type, object obj)
|
|
{
|
|
ValueReference hasValue = GetMember (ctx, type, obj, "HasValue");
|
|
|
|
return (bool) hasValue.ObjectValue;
|
|
}
|
|
|
|
public virtual ValueReference NullableGetValue (EvaluationContext ctx, object type, object obj)
|
|
{
|
|
return GetMember (ctx, type, obj, "Value");
|
|
}
|
|
|
|
public virtual bool IsFlagsEnumType (EvaluationContext ctx, object type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual bool IsSafeToInvokeMethod (EvaluationContext ctx, object method, object obj)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual IEnumerable<EnumMember> GetEnumMembers (EvaluationContext ctx, object type)
|
|
{
|
|
object longType = GetType (ctx, "System.Int64");
|
|
var tref = new TypeValueReference (ctx, type);
|
|
|
|
foreach (var cr in tref.GetChildReferences (ctx.Options)) {
|
|
var c = TryCast (ctx, cr.Value, longType);
|
|
if (c == null)
|
|
continue;
|
|
|
|
long val = (long) TargetObjectToObject (ctx, c);
|
|
var em = new EnumMember { Name = cr.Name, Value = val };
|
|
|
|
yield return em;
|
|
}
|
|
}
|
|
|
|
public object GetBaseType (EvaluationContext ctx, object type, bool includeObjectClass)
|
|
{
|
|
object bt = GetBaseType (ctx, type);
|
|
string tn = bt != null ? GetTypeName (ctx, bt) : null;
|
|
|
|
if (!includeObjectClass && bt != null && (tn == "System.Object" || tn == "System.ValueType"))
|
|
return null;
|
|
if (tn == "System.Enum")
|
|
return GetMembers (ctx, type, null, BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public).FirstOrDefault ()?.Type;
|
|
|
|
return bt;
|
|
}
|
|
|
|
public virtual bool IsClassInstance (EvaluationContext ctx, object val)
|
|
{
|
|
return IsClass (ctx, GetValueType (ctx, val));
|
|
}
|
|
|
|
public virtual bool IsExternalType (EvaluationContext ctx, object type)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public virtual bool IsPublic (EvaluationContext ctx, object type)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public object GetType (EvaluationContext ctx, string name)
|
|
{
|
|
return GetType (ctx, name, null);
|
|
}
|
|
|
|
public abstract object GetType (EvaluationContext ctx, string name, object[] typeArgs);
|
|
|
|
public virtual string GetValueTypeName (EvaluationContext ctx, object val)
|
|
{
|
|
return GetTypeName (ctx, GetValueType (ctx, val));
|
|
}
|
|
|
|
public virtual object CreateTypeObject (EvaluationContext ctx, object type)
|
|
{
|
|
return default (object);
|
|
}
|
|
|
|
public virtual bool IsTypeLoaded (EvaluationContext ctx, string typeName)
|
|
{
|
|
var type = GetType (ctx, typeName);
|
|
|
|
return type != null && IsTypeLoaded (ctx, type);
|
|
}
|
|
|
|
public virtual bool IsTypeLoaded (EvaluationContext ctx, object type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual object ForceLoadType (EvaluationContext ctx, string typeName)
|
|
{
|
|
var type = GetType (ctx, typeName);
|
|
|
|
if (type == null || IsTypeLoaded (ctx, type))
|
|
return type;
|
|
|
|
return ForceLoadType (ctx, type) ? type : null;
|
|
}
|
|
|
|
public virtual bool ForceLoadType (EvaluationContext ctx, object type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public abstract object CreateValue (EvaluationContext ctx, object value);
|
|
|
|
public abstract object CreateValue (EvaluationContext ctx, object type, params object[] args);
|
|
|
|
public abstract object CreateNullValue (EvaluationContext ctx, object type);
|
|
|
|
public virtual object GetBaseValue (EvaluationContext ctx, object val)
|
|
{
|
|
return val;
|
|
}
|
|
|
|
public virtual object CreateDelayedLambdaValue (EvaluationContext ctx, string expression, Tuple<string, object>[] localVariables)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public virtual string[] GetImportedNamespaces (EvaluationContext ctx)
|
|
{
|
|
return new string[0];
|
|
}
|
|
|
|
public virtual void GetNamespaceContents (EvaluationContext ctx, string namspace, out string[] childNamespaces, out string[] childTypes)
|
|
{
|
|
childTypes = childNamespaces = new string[0];
|
|
}
|
|
|
|
protected virtual ObjectValue CreateObjectValueImpl (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags)
|
|
{
|
|
object type = obj != null ? GetValueType (ctx, obj) : null;
|
|
string typeName = type != null ? GetTypeName (ctx, type) : "";
|
|
|
|
if (obj == null || IsNull (ctx, obj))
|
|
return ObjectValue.CreateNullObject (source, path, GetDisplayTypeName (typeName), flags);
|
|
|
|
if (IsPrimitive (ctx, obj) || IsEnum (ctx,obj))
|
|
return ObjectValue.CreatePrimitive (source, path, GetDisplayTypeName (typeName), ctx.Evaluator.TargetObjectToExpression (ctx, obj), flags);
|
|
|
|
if (IsArray (ctx, obj))
|
|
return ObjectValue.CreateObject (source, path, GetDisplayTypeName (typeName), ctx.Evaluator.TargetObjectToExpression (ctx, obj), flags, null);
|
|
|
|
EvaluationResult tvalue = null;
|
|
TypeDisplayData tdata = null;
|
|
string tname;
|
|
|
|
if (IsNullableType (ctx, type)) {
|
|
if (NullableHasValue (ctx, type, obj)) {
|
|
ValueReference value = NullableGetValue (ctx, type, obj);
|
|
|
|
tdata = GetTypeDisplayData (ctx, value.Type);
|
|
obj = value.Value;
|
|
} else {
|
|
tdata = GetTypeDisplayData (ctx, type);
|
|
tvalue = new EvaluationResult ("null");
|
|
}
|
|
|
|
tname = GetDisplayTypeName (typeName);
|
|
} else {
|
|
tdata = GetTypeDisplayData (ctx, type);
|
|
|
|
if (!string.IsNullOrEmpty (tdata.TypeDisplayString) && ctx.Options.AllowDisplayStringEvaluation) {
|
|
try {
|
|
tname = EvaluateDisplayString (ctx, obj, tdata.TypeDisplayString);
|
|
} catch (MissingMemberException) {
|
|
// missing property or otherwise malformed DebuggerDisplay string
|
|
tname = GetDisplayTypeName (typeName);
|
|
}
|
|
} else {
|
|
tname = GetDisplayTypeName (typeName);
|
|
}
|
|
}
|
|
|
|
if (tvalue == null) {
|
|
if (!string.IsNullOrEmpty (tdata.ValueDisplayString) && ctx.Options.AllowDisplayStringEvaluation) {
|
|
try {
|
|
tvalue = new EvaluationResult (EvaluateDisplayString (ctx, obj, tdata.ValueDisplayString));
|
|
} catch (MissingMemberException) {
|
|
// missing property or otherwise malformed DebuggerDisplay string
|
|
tvalue = ctx.Evaluator.TargetObjectToExpression (ctx, obj);
|
|
}
|
|
} else {
|
|
tvalue = ctx.Evaluator.TargetObjectToExpression (ctx, obj);
|
|
}
|
|
}
|
|
|
|
ObjectValue oval = ObjectValue.CreateObject (source, path, tname, tvalue, flags, null);
|
|
if (!string.IsNullOrEmpty (tdata.NameDisplayString) && ctx.Options.AllowDisplayStringEvaluation) {
|
|
try {
|
|
oval.Name = EvaluateDisplayString (ctx, obj, tdata.NameDisplayString);
|
|
} catch (MissingMemberException) {
|
|
// missing property or otherwise malformed DebuggerDisplay string
|
|
}
|
|
}
|
|
|
|
return oval;
|
|
}
|
|
|
|
public ObjectValue[] GetObjectValueChildren (EvaluationContext ctx, IObjectSource objectSource, object obj, int firstItemIndex, int count)
|
|
{
|
|
return GetObjectValueChildren (ctx, objectSource, GetValueType (ctx, obj), obj, firstItemIndex, count, true);
|
|
}
|
|
|
|
public virtual ObjectValue[] GetObjectValueChildren (EvaluationContext ctx, IObjectSource objectSource, object type, object obj, int firstItemIndex, int count, bool dereferenceProxy)
|
|
{
|
|
if (obj is EvaluationResult)
|
|
return new ObjectValue[0];
|
|
|
|
if (IsArray (ctx, obj)) {
|
|
var agroup = new ArrayElementGroup (ctx, CreateArrayAdaptor (ctx, obj));
|
|
return agroup.GetChildren (ctx.Options);
|
|
}
|
|
|
|
if (IsPrimitive (ctx, obj))
|
|
return new ObjectValue[0];
|
|
|
|
if (IsNullableType (ctx, type)) {
|
|
if (NullableHasValue (ctx, type, obj)) {
|
|
ValueReference value = NullableGetValue (ctx, type, obj);
|
|
|
|
return GetObjectValueChildren (ctx, objectSource, value.Type, value.Value, firstItemIndex, count, dereferenceProxy);
|
|
}
|
|
|
|
return new ObjectValue[0];
|
|
}
|
|
|
|
bool showRawView = false;
|
|
|
|
// If there is a proxy, it has to show the members of the proxy
|
|
object proxy = obj;
|
|
if (dereferenceProxy) {
|
|
proxy = GetProxyObject (ctx, obj);
|
|
if (proxy != obj) {
|
|
type = GetValueType (ctx, proxy);
|
|
showRawView = true;
|
|
}
|
|
}
|
|
|
|
TypeDisplayData tdata = GetTypeDisplayData (ctx, type);
|
|
bool groupPrivateMembers = ctx.Options.GroupPrivateMembers || IsExternalType (ctx, type);
|
|
|
|
var values = new List<ObjectValue> ();
|
|
BindingFlags flattenFlag = ctx.Options.FlattenHierarchy ? (BindingFlags)0 : BindingFlags.DeclaredOnly;
|
|
BindingFlags nonPublicFlag = !(groupPrivateMembers || showRawView) ? BindingFlags.NonPublic : (BindingFlags) 0;
|
|
BindingFlags staticFlag = ctx.Options.GroupStaticMembers ? (BindingFlags)0 : BindingFlags.Static;
|
|
BindingFlags access = BindingFlags.Public | BindingFlags.Instance | flattenFlag | nonPublicFlag | staticFlag;
|
|
|
|
// Load all members to a list before creating the object values,
|
|
// to avoid problems with objects being invalidated due to evaluations in the target,
|
|
var list = new List<ValueReference> ();
|
|
list.AddRange (GetMembersSorted (ctx, objectSource, type, proxy, access));
|
|
|
|
// Some implementations of DebuggerProxies(showRawView==true) only have private members
|
|
if (showRawView && list.Count == 0) {
|
|
list.AddRange (GetMembersSorted (ctx, objectSource, type, proxy, access | BindingFlags.NonPublic));
|
|
}
|
|
var names = new ObjectValueNameTracker (ctx);
|
|
object tdataType = type;
|
|
|
|
foreach (ValueReference val in list) {
|
|
try {
|
|
object decType = val.DeclaringType;
|
|
if (decType != null && decType != tdataType) {
|
|
tdataType = decType;
|
|
tdata = GetTypeDisplayData (ctx, decType);
|
|
}
|
|
|
|
DebuggerBrowsableState state = tdata.GetMemberBrowsableState (val.Name);
|
|
if (state == DebuggerBrowsableState.Never)
|
|
continue;
|
|
|
|
if (state == DebuggerBrowsableState.RootHidden && dereferenceProxy) {
|
|
object ob = val.Value;
|
|
if (ob != null) {
|
|
values.Clear ();
|
|
values.AddRange (GetObjectValueChildren (ctx, val, ob, -1, -1));
|
|
showRawView = true;
|
|
break;
|
|
}
|
|
} else {
|
|
ObjectValue oval = val.CreateObjectValue (true);
|
|
names.Disambiguate (val, oval);
|
|
values.Add (oval);
|
|
}
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
values.Add (ObjectValue.CreateError (null, new ObjectPath (val.Name), GetDisplayTypeName (GetTypeName (ctx, val.Type)), ex.Message, val.Flags));
|
|
}
|
|
}
|
|
|
|
if (showRawView) {
|
|
values.Add (RawViewSource.CreateRawView (ctx, objectSource, obj));
|
|
} else {
|
|
if (IsArray (ctx, proxy)) {
|
|
var col = CreateArrayAdaptor (ctx, proxy);
|
|
var agroup = new ArrayElementGroup (ctx, col);
|
|
var val = ObjectValue.CreateObject (null, new ObjectPath ("Raw View"), "", "", ObjectValueFlags.ReadOnly, values.ToArray ());
|
|
|
|
values = new List<ObjectValue> ();
|
|
values.Add (val);
|
|
values.AddRange (agroup.GetChildren (ctx.Options));
|
|
} else {
|
|
if (ctx.Options.GroupStaticMembers && HasMembers (ctx, type, proxy, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | flattenFlag)) {
|
|
access = BindingFlags.Static | BindingFlags.Public | flattenFlag | nonPublicFlag;
|
|
values.Add (FilteredMembersSource.CreateStaticsNode (ctx, objectSource, type, proxy, access));
|
|
}
|
|
|
|
if (groupPrivateMembers && HasMembers (ctx, type, proxy, BindingFlags.Instance | BindingFlags.NonPublic | flattenFlag | staticFlag))
|
|
values.Add (FilteredMembersSource.CreateNonPublicsNode (ctx, objectSource, type, proxy, BindingFlags.Instance | BindingFlags.NonPublic | flattenFlag | staticFlag));
|
|
|
|
if (!ctx.Options.FlattenHierarchy) {
|
|
object baseType = GetBaseType (ctx, type, false);
|
|
if (baseType != null)
|
|
values.Insert (0, BaseTypeViewSource.CreateBaseTypeView (ctx, objectSource, baseType, proxy));
|
|
}
|
|
|
|
if (ctx.SupportIEnumerable) {
|
|
var iEnumerableType = GetImplementedInterfaces (ctx, type).FirstOrDefault ((interfaceType) => {
|
|
string interfaceName = GetTypeName (ctx, interfaceType);
|
|
if (interfaceName == "System.Collections.IEnumerable")
|
|
return true;
|
|
if (interfaceName == "System.Collections.Generic.IEnumerable`1")
|
|
return true;
|
|
return false;
|
|
});
|
|
if (iEnumerableType != null)
|
|
values.Add (ObjectValue.CreatePrimitive (new EnumerableSource (proxy, iEnumerableType, ctx), new ObjectPath ("IEnumerator"), "", new EvaluationResult (""), ObjectValueFlags.ReadOnly | ObjectValueFlags.Object | ObjectValueFlags.Group | ObjectValueFlags.IEnumerable));
|
|
}
|
|
}
|
|
}
|
|
|
|
return values.ToArray ();
|
|
}
|
|
|
|
public ObjectValue[] GetExpressionValuesAsync (EvaluationContext ctx, string[] expressions)
|
|
{
|
|
var values = new ObjectValue[expressions.Length];
|
|
|
|
for (int n = 0; n < values.Length; n++) {
|
|
string exp = expressions[n];
|
|
|
|
// This is a workaround to a bug in mono 2.0. That mono version fails to compile
|
|
// an anonymous method here
|
|
var edata = new ExpData (ctx, exp, this);
|
|
values[n] = asyncEvaluationTracker.Run (exp, ObjectValueFlags.Literal, edata.Run);
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
class ExpData
|
|
{
|
|
readonly ObjectValueAdaptor adaptor;
|
|
readonly EvaluationContext ctx;
|
|
readonly string exp;
|
|
|
|
public ExpData (EvaluationContext ctx, string exp, ObjectValueAdaptor adaptor)
|
|
{
|
|
this.ctx = ctx;
|
|
this.exp = exp;
|
|
this.adaptor = adaptor;
|
|
}
|
|
|
|
public ObjectValue Run ()
|
|
{
|
|
return adaptor.GetExpressionValue (ctx, exp);
|
|
}
|
|
}
|
|
|
|
public virtual ValueReference GetIndexerReference (EvaluationContext ctx, object target, object[] indices)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public virtual ValueReference GetIndexerReference (EvaluationContext ctx, object target, object type, object[] indices)
|
|
{
|
|
return GetIndexerReference (ctx, target, indices);
|
|
}
|
|
|
|
public ValueReference GetLocalVariable (EvaluationContext ctx, string name)
|
|
{
|
|
return OnGetLocalVariable (ctx, name);
|
|
}
|
|
|
|
protected virtual ValueReference OnGetLocalVariable (EvaluationContext ctx, string name)
|
|
{
|
|
ValueReference best = null;
|
|
foreach (ValueReference var in GetLocalVariables (ctx)) {
|
|
if (var.Name == name)
|
|
return var;
|
|
if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase))
|
|
best = var;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
public virtual ValueReference GetParameter (EvaluationContext ctx, string name)
|
|
{
|
|
return OnGetParameter (ctx, name);
|
|
}
|
|
|
|
protected virtual ValueReference OnGetParameter (EvaluationContext ctx, string name)
|
|
{
|
|
ValueReference best = null;
|
|
foreach (ValueReference var in GetParameters (ctx)) {
|
|
if (var.Name == name)
|
|
return var;
|
|
if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase))
|
|
best = var;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
public IEnumerable<ValueReference> GetLocalVariables (EvaluationContext ctx)
|
|
{
|
|
return OnGetLocalVariables (ctx);
|
|
}
|
|
|
|
public ValueReference GetThisReference (EvaluationContext ctx)
|
|
{
|
|
return OnGetThisReference (ctx);
|
|
}
|
|
|
|
public IEnumerable<ValueReference> GetParameters (EvaluationContext ctx)
|
|
{
|
|
return OnGetParameters (ctx);
|
|
}
|
|
|
|
protected virtual IEnumerable<ValueReference> OnGetLocalVariables (EvaluationContext ctx)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
protected virtual IEnumerable<ValueReference> OnGetParameters (EvaluationContext ctx)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
protected virtual ValueReference OnGetThisReference (EvaluationContext ctx)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public virtual ValueReference GetCurrentException (EvaluationContext ctx)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public virtual object GetEnclosingType (EvaluationContext ctx)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
protected virtual CompletionData GetMemberCompletionData (EvaluationContext ctx, ValueReference vr)
|
|
{
|
|
var data = new CompletionData ();
|
|
|
|
foreach (var cv in vr.GetChildReferences (ctx.Options))
|
|
data.Items.Add (new CompletionItem (cv.Name, cv.Flags));
|
|
|
|
data.ExpressionLength = 0;
|
|
|
|
return data;
|
|
}
|
|
|
|
public virtual CompletionData GetExpressionCompletionData (EvaluationContext ctx, string expr)
|
|
{
|
|
if (expr == null)
|
|
return null;
|
|
|
|
int dot = expr.LastIndexOf ('.');
|
|
|
|
if (dot != -1) {
|
|
try {
|
|
var vr = ctx.Evaluator.Evaluate (ctx, expr.Substring (0, dot), null);
|
|
if (vr != null) {
|
|
var completionData = GetMemberCompletionData (ctx, vr);
|
|
completionData.ExpressionLength = expr.Length - dot - 1;
|
|
return completionData;
|
|
}
|
|
|
|
// FIXME: handle types and namespaces...
|
|
} catch (EvaluatorException) {
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool lastWastLetter = false;
|
|
int i = expr.Length - 1;
|
|
|
|
while (i >= 0) {
|
|
char c = expr[i--];
|
|
if (!char.IsLetterOrDigit (c) && c != '_')
|
|
break;
|
|
|
|
lastWastLetter = !char.IsDigit (c);
|
|
}
|
|
|
|
if (lastWastLetter || expr.Length == 0) {
|
|
var data = new CompletionData ();
|
|
data.ExpressionLength = expr.Length - (i + 1);
|
|
|
|
// Local variables
|
|
|
|
foreach (var vc in GetLocalVariables (ctx)) {
|
|
data.Items.Add (new CompletionItem (vc.Name, vc.Flags));
|
|
}
|
|
|
|
// Parameters
|
|
|
|
foreach (var vc in GetParameters (ctx)) {
|
|
data.Items.Add (new CompletionItem (vc.Name, vc.Flags));
|
|
}
|
|
|
|
// Members
|
|
|
|
ValueReference thisobj = GetThisReference (ctx);
|
|
|
|
if (thisobj != null)
|
|
data.Items.Add (new CompletionItem ("this", ObjectValueFlags.Field | ObjectValueFlags.ReadOnly));
|
|
|
|
object type = GetEnclosingType (ctx);
|
|
|
|
foreach (var vc in GetMembers (ctx, null, type, thisobj != null ? thisobj.Value : null)) {
|
|
data.Items.Add (new CompletionItem (vc.Name, vc.Flags));
|
|
}
|
|
|
|
if (data.Items.Count > 0)
|
|
return data;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public IEnumerable<ValueReference> GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co)
|
|
{
|
|
foreach (ValueReference val in GetMembers (ctx, objectSource, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) {
|
|
val.ParentSource = objectSource;
|
|
yield return val;
|
|
}
|
|
}
|
|
|
|
public ValueReference GetMember (EvaluationContext ctx, IObjectSource objectSource, object co, string name)
|
|
{
|
|
return GetMember (ctx, objectSource, GetValueType (ctx, co), co, name);
|
|
}
|
|
|
|
protected virtual ValueReference OnGetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name)
|
|
{
|
|
return GetMember (ctx, t, co, name);
|
|
}
|
|
|
|
public ValueReference GetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name)
|
|
{
|
|
ValueReference m = OnGetMember (ctx, objectSource, t, co, name);
|
|
if (m != null)
|
|
m.ParentSource = objectSource;
|
|
return m;
|
|
}
|
|
|
|
protected virtual ValueReference GetMember (EvaluationContext ctx, object t, object co, string name)
|
|
{
|
|
ValueReference best = null;
|
|
foreach (ValueReference var in GetMembers (ctx, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) {
|
|
if (var.Name == name)
|
|
return var;
|
|
if (!ctx.Evaluator.CaseSensitive && var.Name.Equals (name, StringComparison.CurrentCultureIgnoreCase))
|
|
best = var;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
internal IEnumerable<ValueReference> GetMembersSorted (EvaluationContext ctx, IObjectSource objectSource, object t, object co)
|
|
{
|
|
return GetMembersSorted (ctx, objectSource, t, co, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
|
}
|
|
|
|
internal IEnumerable<ValueReference> GetMembersSorted (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags)
|
|
{
|
|
var list = new List<ValueReference> ();
|
|
|
|
foreach (var vr in GetMembers (ctx, objectSource, t, co, bindingFlags)) {
|
|
vr.ParentSource = objectSource;
|
|
list.Add (vr);
|
|
}
|
|
|
|
list.Sort ((v1, v2) => string.Compare (v1.Name, v2.Name, StringComparison.Ordinal));
|
|
|
|
return list;
|
|
}
|
|
|
|
public bool HasMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags)
|
|
{
|
|
return GetMembers (ctx, t, co, bindingFlags).Any ();
|
|
}
|
|
|
|
public bool HasMember (EvaluationContext ctx, object type, string memberName)
|
|
{
|
|
return HasMember (ctx, type, memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
|
}
|
|
|
|
public abstract bool HasMember (EvaluationContext ctx, object type, string memberName, BindingFlags bindingFlags);
|
|
|
|
/// <summary>
|
|
/// Returns all members of a type. The following binding flags have to be honored:
|
|
/// BindingFlags.Static, BindingFlags.Instance, BindingFlags.Public, BindingFlags.NonPublic, BindingFlags.DeclareOnly
|
|
/// </summary>
|
|
protected abstract IEnumerable<ValueReference> GetMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags);
|
|
|
|
/// <summary>
|
|
/// Returns all members of a type. The following binding flags have to be honored:
|
|
/// BindingFlags.Static, BindingFlags.Instance, BindingFlags.Public, BindingFlags.NonPublic, BindingFlags.DeclareOnly
|
|
/// </summary>
|
|
protected virtual IEnumerable<ValueReference> GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags)
|
|
{
|
|
return GetMembers (ctx, t, co, bindingFlags);
|
|
}
|
|
|
|
public virtual IEnumerable<object> GetNestedTypes (EvaluationContext ctx, object type)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
public virtual IEnumerable<object> GetImplementedInterfaces (EvaluationContext ctx, object type)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
public virtual object GetParentType (EvaluationContext ctx, object type)
|
|
{
|
|
var tt = type as Type;
|
|
|
|
if (tt != null)
|
|
return tt.DeclaringType;
|
|
|
|
var name = GetTypeName (ctx, type);
|
|
int plus = name.LastIndexOf ('+');
|
|
|
|
return plus != -1 ? GetType (ctx, name.Substring (0, plus)) : null;
|
|
}
|
|
|
|
public virtual object CreateArray (EvaluationContext ctx, object type, object[] values)
|
|
{
|
|
var arrType = GetType (ctx, "System.Collections.ArrayList");
|
|
var arrayList = CreateValue (ctx, arrType, new object[0]);
|
|
object[] objTypes = { GetType (ctx, "System.Object") };
|
|
|
|
foreach (object value in values)
|
|
RuntimeInvoke (ctx, arrType, arrayList, "Add", objTypes, new [] { value });
|
|
|
|
var typof = CreateTypeObject (ctx, type);
|
|
objTypes = new [] { GetType (ctx, "System.Type") };
|
|
|
|
return RuntimeInvoke (ctx, arrType, arrayList, "ToArray", objTypes, new [] { typof });
|
|
}
|
|
|
|
public virtual object CreateArray (EvaluationContext ctx, object type, int [] lengths)
|
|
{
|
|
if (lengths.Length > 3) {
|
|
throw new NotSupportedException ("Arrays with more than 3 demensions are not supported.");
|
|
}
|
|
var arrType = GetType (ctx, "System.Array");
|
|
var intType = GetType (ctx, "System.Int32");
|
|
var typeType = GetType (ctx, "System.Type");
|
|
var arguments = new object [lengths.Length + 1];
|
|
var argTypes = new object [lengths.Length + 1];
|
|
arguments [0] = CreateTypeObject (ctx, type);
|
|
argTypes [0] = typeType;
|
|
for (int i = 0; i < lengths.Length; i++) {
|
|
arguments [i + 1] = FromRawValue (ctx, lengths [i]);
|
|
argTypes [i + 1] = intType;
|
|
}
|
|
|
|
return RuntimeInvoke (ctx, arrType, null, "CreateInstance", argTypes, arguments);
|
|
}
|
|
|
|
public virtual object ToRawValue (EvaluationContext ctx, IObjectSource source, object obj)
|
|
{
|
|
if (IsEnum (ctx, obj)) {
|
|
var longType = GetType (ctx, "System.Int64");
|
|
var c = Cast (ctx, obj, longType);
|
|
|
|
return TargetObjectToObject (ctx, c);
|
|
}
|
|
|
|
if (ctx.Options.ChunkRawStrings && IsString (ctx, obj)) {
|
|
var adaptor = CreateStringAdaptor (ctx, obj);
|
|
return new RawValueString (new RemoteRawValueString (adaptor, obj));
|
|
}
|
|
|
|
if (IsPrimitive (ctx, obj))
|
|
return TargetObjectToObject (ctx, obj);
|
|
|
|
if (IsArray (ctx, obj)) {
|
|
var adaptor = CreateArrayAdaptor (ctx, obj);
|
|
return new RawValueArray (new RemoteRawValueArray (ctx, source, adaptor, obj));
|
|
}
|
|
|
|
return new RawValue (new RemoteRawValue (ctx, source, obj));
|
|
}
|
|
|
|
public virtual object FromRawValue (EvaluationContext ctx, object obj)
|
|
{
|
|
var rawValue = obj as RawValue;
|
|
if (rawValue != null) {
|
|
var val = rawValue.Source as RemoteRawValue;
|
|
if (val == null)
|
|
throw new InvalidOperationException ("Unknown RawValue source: " + rawValue.Source);
|
|
|
|
return val.TargetObject;
|
|
}
|
|
|
|
var rawArray = obj as RawValueArray;
|
|
if (rawArray != null) {
|
|
var val = rawArray.Source as RemoteRawValueArray;
|
|
if (val == null)
|
|
throw new InvalidOperationException ("Unknown RawValueArray source: " + rawArray.Source);
|
|
|
|
return val.TargetObject;
|
|
}
|
|
|
|
var rawString = obj as RawValueString;
|
|
if (rawString != null) {
|
|
var val = rawString.Source as RemoteRawValueString;
|
|
if (val == null)
|
|
throw new InvalidOperationException ("Unknown RawValueString source: " + rawString.Source);
|
|
|
|
return val.TargetObject;
|
|
}
|
|
|
|
var array = obj as Array;
|
|
if (array != null) {
|
|
if (obj.GetType ().GetElementType () == typeof (RawValue))
|
|
throw new NotSupportedException ();
|
|
|
|
var elemType = GetType (ctx, obj.GetType ().GetElementType ().FullName);
|
|
if (elemType == null)
|
|
throw new EvaluatorException ("Unknown target type: {0}", obj.GetType ().GetElementType ().FullName);
|
|
|
|
var values = new object [array.Length];
|
|
for (int n = 0; n < values.Length; n++)
|
|
values[n] = FromRawValue (ctx, array.GetValue (n));
|
|
|
|
return CreateArray (ctx, elemType, values);
|
|
}
|
|
|
|
return CreateValue (ctx, obj);
|
|
}
|
|
|
|
public virtual object TargetObjectToObject (EvaluationContext ctx, object obj)
|
|
{
|
|
if (IsNull (ctx, obj))
|
|
return null;
|
|
|
|
if (IsArray (ctx, obj)) {
|
|
ICollectionAdaptor adaptor = CreateArrayAdaptor (ctx, obj);
|
|
string ename = GetDisplayTypeName (GetTypeName (ctx, adaptor.ElementType));
|
|
int[] dims = adaptor.GetDimensions ();
|
|
var tn = new StringBuilder ("[");
|
|
|
|
for (int n = 0; n < dims.Length; n++) {
|
|
if (n > 0)
|
|
tn.Append (',');
|
|
tn.Append (dims[n]);
|
|
}
|
|
|
|
tn.Append ("]");
|
|
|
|
int i = ename.LastIndexOf ('>');
|
|
if (i == -1)
|
|
i = 0;
|
|
|
|
i = ename.IndexOf ('[', i);
|
|
|
|
if (i != -1)
|
|
return new EvaluationResult ("{" + ename.Substring (0, i) + tn + ename.Substring (i) + "}");
|
|
|
|
return new EvaluationResult ("{" + ename + tn + "}");
|
|
}
|
|
|
|
object type = GetValueType (ctx, obj);
|
|
string typeName = GetTypeName (ctx, type);
|
|
if (IsEnum (ctx, obj)) {
|
|
object longType = GetType (ctx, "System.Int64");
|
|
object c = Cast (ctx, obj, longType);
|
|
long val = (long) TargetObjectToObject (ctx, c);
|
|
long rest = val;
|
|
string composed = string.Empty;
|
|
string composedDisplay = string.Empty;
|
|
|
|
foreach (var em in GetEnumMembers (ctx, type)) {
|
|
if (em.Value == val)
|
|
return new EvaluationResult (typeName + "." + em.Name, em.Name);
|
|
|
|
if (em.Value != 0 && (rest & em.Value) == em.Value) {
|
|
rest &= ~em.Value;
|
|
if (composed.Length > 0) {
|
|
composed += " | ";
|
|
composedDisplay += " | ";
|
|
}
|
|
composed += typeName + "." + em.Name;
|
|
composedDisplay += em.Name;
|
|
}
|
|
}
|
|
|
|
if (IsFlagsEnumType (ctx, type) && rest == 0 && composed.Length > 0)
|
|
return new EvaluationResult (composed, composedDisplay);
|
|
|
|
return new EvaluationResult (val.ToString ());
|
|
}
|
|
|
|
if (typeName == "System.Decimal") {
|
|
string res = CallToString (ctx, obj);
|
|
// This returns the decimal formatted using the current culture. It has to be converted to invariant culture.
|
|
decimal dec = decimal.Parse (res);
|
|
res = dec.ToString (System.Globalization.CultureInfo.InvariantCulture);
|
|
return new EvaluationResult (res);
|
|
}
|
|
|
|
if (typeName == "System.nfloat" || typeName == "System.nint") {
|
|
return TargetObjectToObject (ctx, GetMembersSorted (ctx, null, type, obj, BindingFlags.Instance | BindingFlags.NonPublic).Single ().Value);
|
|
}
|
|
|
|
if (IsClassInstance (ctx, obj)) {
|
|
TypeDisplayData tdata = GetTypeDisplayData (ctx, GetValueType (ctx, obj));
|
|
if (!string.IsNullOrEmpty (tdata.ValueDisplayString) && ctx.Options.AllowDisplayStringEvaluation) {
|
|
try {
|
|
return new EvaluationResult (EvaluateDisplayString (ctx, obj, tdata.ValueDisplayString));
|
|
} catch (MissingMemberException) {
|
|
// missing property or otherwise malformed DebuggerDisplay string
|
|
}
|
|
}
|
|
|
|
// Return the type name
|
|
if (ctx.Options.AllowToStringCalls) {
|
|
try {
|
|
return new EvaluationResult ("{" + CallToString (ctx, obj) + "}");
|
|
} catch (TimeOutException) {
|
|
// ToString() timed out, fall back to default behavior.
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty (tdata.TypeDisplayString) && ctx.Options.AllowDisplayStringEvaluation) {
|
|
try {
|
|
return new EvaluationResult ("{" + EvaluateDisplayString (ctx, obj, tdata.TypeDisplayString) + "}");
|
|
} catch (MissingMemberException) {
|
|
// missing property or otherwise malformed DebuggerDisplay string
|
|
}
|
|
}
|
|
|
|
return new EvaluationResult ("{" + GetDisplayTypeName (GetValueTypeName (ctx, obj)) + "}");
|
|
}
|
|
|
|
return new EvaluationResult ("{" + CallToString (ctx, obj) + "}");
|
|
}
|
|
|
|
public object Convert (EvaluationContext ctx, object obj, object targetType)
|
|
{
|
|
if (obj == null)
|
|
return null;
|
|
|
|
object res = TryConvert (ctx, obj, targetType);
|
|
if (res != null)
|
|
return res;
|
|
|
|
throw new EvaluatorException ("Can't convert an object of type '{0}' to type '{1}'", GetValueTypeName (ctx, obj), GetTypeName (ctx, targetType));
|
|
}
|
|
|
|
public virtual object TryConvert (EvaluationContext ctx, object obj, object targetType)
|
|
{
|
|
return TryCast (ctx, obj, targetType);
|
|
}
|
|
|
|
public virtual object Cast (EvaluationContext ctx, object obj, object targetType)
|
|
{
|
|
if (obj == null)
|
|
return null;
|
|
|
|
object res = TryCast (ctx, obj, targetType);
|
|
if (res != null)
|
|
return res;
|
|
|
|
throw new EvaluatorException ("Can't cast an object of type '{0}' to type '{1}'", GetValueTypeName (ctx, obj), GetTypeName (ctx, targetType));
|
|
}
|
|
|
|
public virtual string CallToString (EvaluationContext ctx, object obj)
|
|
{
|
|
return GetValueTypeName (ctx, obj);
|
|
}
|
|
|
|
// FIXME: next time we can break ABI/API, make this abstract
|
|
protected virtual object GetBaseTypeWithAttribute (EvaluationContext ctx, object type, object attrType)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public object GetProxyObject (EvaluationContext ctx, object obj)
|
|
{
|
|
TypeDisplayData data = GetTypeDisplayData (ctx, GetValueType (ctx, obj));
|
|
if (string.IsNullOrEmpty (data.ProxyType) || !ctx.Options.AllowDebuggerProxy)
|
|
return obj;
|
|
|
|
string proxyType = data.ProxyType;
|
|
object[] typeArgs = null;
|
|
|
|
int index = proxyType.IndexOf ('`');
|
|
if (index != -1) {
|
|
// The proxy type is an uninstantiated generic type.
|
|
// The number of type args of the proxy must match the args of the target object
|
|
int startIndex = index + 1;
|
|
int endIndex = index + 1;
|
|
|
|
while (endIndex < proxyType.Length && char.IsDigit (proxyType[endIndex]))
|
|
endIndex++;
|
|
|
|
var attrType = GetType (ctx, "System.Diagnostics.DebuggerTypeProxyAttribute");
|
|
int num = int.Parse (proxyType.Substring (startIndex, endIndex - startIndex));
|
|
var proxiedType = GetBaseTypeWithAttribute (ctx, GetValueType (ctx, obj), attrType);
|
|
|
|
if (proxiedType == null || !IsGenericType (ctx, proxiedType))
|
|
return obj;
|
|
|
|
typeArgs = GetTypeArgs (ctx, proxiedType);
|
|
if (typeArgs.Length != num)
|
|
return obj;
|
|
|
|
if (endIndex < proxyType.Length) {
|
|
// chop off the []'d list of generic type arguments
|
|
proxyType = proxyType.Substring (0, endIndex);
|
|
}
|
|
}
|
|
|
|
object ttype = GetType (ctx, proxyType, typeArgs);
|
|
if (ttype == null) {
|
|
// the proxy type string might be in the form: "Namespace.TypeName, Assembly...", chop off the ", Assembly..." bit.
|
|
if ((index = proxyType.IndexOf (',')) != -1)
|
|
ttype = GetType (ctx, proxyType.Substring (0, index).Trim (), typeArgs);
|
|
}
|
|
if (ttype == null)
|
|
throw new EvaluatorException ("Unknown type '{0}'", data.ProxyType);
|
|
|
|
try {
|
|
object val = CreateValue (ctx, ttype, obj);
|
|
return val ?? obj;
|
|
} catch (EvaluatorException) {
|
|
// probably couldn't find the .ctor for the proxy type because the linker stripped it out
|
|
return obj;
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
public TypeDisplayData GetTypeDisplayData (EvaluationContext ctx, object type)
|
|
{
|
|
if (!IsClass (ctx, type))
|
|
return TypeDisplayData.Default;
|
|
|
|
TypeDisplayData td;
|
|
string tname = GetTypeName (ctx, type);
|
|
if (typeDisplayData.TryGetValue (tname, out td))
|
|
return td;
|
|
|
|
try {
|
|
td = OnGetTypeDisplayData (ctx, type);
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
}
|
|
|
|
if (td == null)
|
|
typeDisplayData[tname] = td = TypeDisplayData.Default;
|
|
else
|
|
typeDisplayData[tname] = td;
|
|
|
|
return td;
|
|
}
|
|
|
|
protected virtual TypeDisplayData OnGetTypeDisplayData (EvaluationContext ctx, object type)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
static bool IsQuoted (string str)
|
|
{
|
|
return str.Length >= 2 && str[0] == '"' && str[str.Length - 1] == '"';
|
|
}
|
|
|
|
public string EvaluateDisplayString (EvaluationContext ctx, object obj, string expr)
|
|
{
|
|
var display = new StringBuilder ();
|
|
int i = expr.IndexOf ('{');
|
|
int last = 0;
|
|
|
|
while (i != -1 && i < expr.Length) {
|
|
display.Append (expr, last, i - last);
|
|
i++;
|
|
|
|
int j = expr.IndexOf ('}', i);
|
|
if (j == -1)
|
|
return expr;
|
|
|
|
string memberExpr = expr.Substring (i, j - i).Trim ();
|
|
if (memberExpr.Length == 0)
|
|
return expr;
|
|
|
|
int comma = memberExpr.LastIndexOf (',');
|
|
bool noquotes = false;
|
|
if (comma != -1) {
|
|
var option = memberExpr.Substring (comma + 1).Trim ();
|
|
memberExpr = memberExpr.Substring (0, comma).Trim ();
|
|
noquotes |= option == "nq";
|
|
}
|
|
|
|
var props = memberExpr.Split (new [] { '.' });
|
|
object val = obj;
|
|
|
|
for (int k = 0; k < props.Length; k++) {
|
|
var member = GetMember (ctx, null, GetValueType (ctx, val), val, props [k]);
|
|
if (member != null) {
|
|
val = member.Value;
|
|
} else {
|
|
var methodName = props [k].TrimEnd ('(', ')', ' ');
|
|
if (HasMethod (ctx, GetValueType (ctx, val), methodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) {
|
|
val = RuntimeInvoke (ctx, GetValueType (ctx, val), val, methodName, new object[0], new object[0]);
|
|
} else {
|
|
val = null;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (val != null) {
|
|
var str = ctx.Evaluator.TargetObjectToString (ctx, val);
|
|
if (str == null)
|
|
display.Append ("null");
|
|
else if (noquotes && IsQuoted (str))
|
|
display.Append (str, 1, str.Length - 2);
|
|
else
|
|
display.Append (str);
|
|
} else {
|
|
throw new MissingMemberException (GetValueTypeName (ctx, obj), memberExpr);
|
|
}
|
|
|
|
last = j + 1;
|
|
i = expr.IndexOf ('{', last);
|
|
}
|
|
|
|
display.Append (expr, last, expr.Length - last);
|
|
|
|
return display.ToString ();
|
|
}
|
|
|
|
public void AsyncExecute (AsyncOperation operation, int timeout)
|
|
{
|
|
asyncOperationManager.Invoke (operation, timeout);
|
|
}
|
|
|
|
public ObjectValue CreateObjectValueAsync (string name, ObjectValueFlags flags, ObjectEvaluatorDelegate evaluator)
|
|
{
|
|
return asyncEvaluationTracker.Run (name, flags, evaluator);
|
|
}
|
|
|
|
public bool IsEvaluating {
|
|
get { return asyncEvaluationTracker.IsEvaluating; }
|
|
}
|
|
|
|
public void CancelAsyncOperations ( )
|
|
{
|
|
asyncEvaluationTracker.Stop ();
|
|
asyncOperationManager.AbortAll ();
|
|
asyncEvaluationTracker.WaitForStopped ();
|
|
}
|
|
|
|
public ObjectValue GetExpressionValue (EvaluationContext ctx, string exp)
|
|
{
|
|
try {
|
|
var var = ctx.Evaluator.Evaluate (ctx, exp);
|
|
|
|
if (var == null)
|
|
return ObjectValue.CreateUnknown (exp);
|
|
|
|
var value = var.CreateObjectValue (ctx.Options);
|
|
value.Name = exp;
|
|
return value;
|
|
} catch (ImplicitEvaluationDisabledException) {
|
|
return ObjectValue.CreateImplicitNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ObjectValueFlags.None);
|
|
} catch (NotSupportedExpressionException ex) {
|
|
return ObjectValue.CreateNotSupported (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None);
|
|
} catch (EvaluatorException ex) {
|
|
return ObjectValue.CreateError (ctx.ExpressionValueSource, new ObjectPath (exp), "", ex.Message, ObjectValueFlags.None);
|
|
} catch (Exception ex) {
|
|
ctx.WriteDebuggerError (ex);
|
|
return ObjectValue.CreateUnknown (exp);
|
|
}
|
|
}
|
|
|
|
public virtual bool HasMethodWithParamLength (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags, int paramLength)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool HasMethod (EvaluationContext ctx, object targetType, string methodName)
|
|
{
|
|
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static;
|
|
|
|
if (!ctx.Evaluator.CaseSensitive)
|
|
flags |= BindingFlags.IgnoreCase;
|
|
|
|
return HasMethod (ctx, targetType, methodName, null, null, flags);
|
|
}
|
|
|
|
public bool HasMethod (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags)
|
|
{
|
|
return HasMethod (ctx, targetType, methodName, null, null, flags);
|
|
}
|
|
|
|
// argTypes can be null, meaning that it has to return true if there is any method with that name
|
|
// flags will only contain Static or Instance flags
|
|
public bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] argTypes, BindingFlags flags)
|
|
{
|
|
return HasMethod (ctx, targetType, methodName, null, argTypes, flags);
|
|
}
|
|
|
|
// argTypes can be null, meaning that it has to return true if there is any method with that name
|
|
// flags will only contain Static or Instance flags
|
|
public abstract bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags);
|
|
|
|
// outarg `untyped lambda`
|
|
// if one of argtypes is untyped lambda, this will resolve its type.
|
|
public virtual bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags, out Tuple<int, object>[] resolvedLambdaTypes)
|
|
{
|
|
resolvedLambdaTypes = null;
|
|
return HasMethod (ctx, targetType, methodName, genericTypeArgs, argTypes, flags);
|
|
}
|
|
|
|
public object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] argTypes, object[] argValues)
|
|
{
|
|
return RuntimeInvoke (ctx, targetType, target, methodName, null, argTypes, argValues);
|
|
}
|
|
|
|
public virtual object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues, out object[] outArgs){
|
|
outArgs = null;
|
|
return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues);
|
|
}
|
|
|
|
public abstract object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues);
|
|
|
|
public virtual ValidationResult ValidateExpression (EvaluationContext ctx, string expression)
|
|
{
|
|
return ctx.Evaluator.ValidateExpression (ctx, expression);
|
|
}
|
|
}
|
|
|
|
public class TypeDisplayData
|
|
{
|
|
public string ProxyType { get; internal set; }
|
|
public string ValueDisplayString { get; internal set; }
|
|
public string TypeDisplayString { get; internal set; }
|
|
public string NameDisplayString { get; internal set; }
|
|
public bool IsCompilerGenerated { get; internal set; }
|
|
|
|
public bool IsProxyType {
|
|
get { return ProxyType != null; }
|
|
}
|
|
|
|
public static readonly TypeDisplayData Default = new TypeDisplayData (null, null, null, null, false, null);
|
|
|
|
public Dictionary<string, DebuggerBrowsableState> MemberData { get; internal set; }
|
|
|
|
public TypeDisplayData (string proxyType, string valueDisplayString, string typeDisplayString,
|
|
string nameDisplayString, bool isCompilerGenerated, Dictionary<string, DebuggerBrowsableState> memberData)
|
|
{
|
|
ProxyType = proxyType;
|
|
ValueDisplayString = valueDisplayString;
|
|
TypeDisplayString = typeDisplayString;
|
|
NameDisplayString = nameDisplayString;
|
|
IsCompilerGenerated = isCompilerGenerated;
|
|
MemberData = memberData;
|
|
}
|
|
|
|
public DebuggerBrowsableState GetMemberBrowsableState (string name)
|
|
{
|
|
if (MemberData == null)
|
|
return DebuggerBrowsableState.Collapsed;
|
|
|
|
DebuggerBrowsableState state;
|
|
if (!MemberData.TryGetValue (name, out state))
|
|
state = DebuggerBrowsableState.Collapsed;
|
|
|
|
return state;
|
|
}
|
|
}
|
|
|
|
class ObjectValueNameTracker
|
|
{
|
|
readonly Dictionary<string,KeyValuePair<ObjectValue, ValueReference>> names = new Dictionary<string,KeyValuePair<ObjectValue, ValueReference>> ();
|
|
readonly EvaluationContext ctx;
|
|
|
|
public ObjectValueNameTracker (EvaluationContext ctx)
|
|
{
|
|
this.ctx = ctx;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disambiguate the ObjectValue's name (in the case where the property name also exists in a base class).
|
|
/// </summary>
|
|
/// <param name='val'>
|
|
/// The ValueReference.
|
|
/// </param>
|
|
/// <param name='oval'>
|
|
/// The ObjectValue.
|
|
/// </param>
|
|
public void Disambiguate (ValueReference val, ObjectValue oval)
|
|
{
|
|
KeyValuePair<ObjectValue, ValueReference> other;
|
|
|
|
if (names.TryGetValue (oval.Name, out other)) {
|
|
object tn = val.DeclaringType;
|
|
|
|
if (tn != null)
|
|
oval.Name += " (" + ctx.Adapter.GetDisplayTypeName (ctx, tn) + ")";
|
|
if (!other.Key.Name.EndsWith (")", StringComparison.Ordinal)) {
|
|
tn = other.Value.DeclaringType;
|
|
if (tn != null)
|
|
other.Key.Name += " (" + ctx.Adapter.GetDisplayTypeName (ctx, tn) + ")";
|
|
}
|
|
}
|
|
|
|
names [oval.Name] = new KeyValuePair<ObjectValue, ValueReference> (oval, val);
|
|
}
|
|
}
|
|
|
|
public struct EnumMember
|
|
{
|
|
public string Name { get; set; }
|
|
public long Value { get; set; }
|
|
}
|
|
}
|