2733 lines
88 KiB
C#
2733 lines
88 KiB
C#
//
|
|
// SoftDebuggerAdaptor.cs
|
|
//
|
|
// Authors: Lluis Sanchez Gual <lluis@novell.com>
|
|
// Jeffrey Stedfast <jeff@xamarin.com>
|
|
//
|
|
// Copyright (c) 2009 Novell, Inc (http://www.novell.com)
|
|
// Copyright (c) 2011,2012 Xamain 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.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
using Mono.Debugger.Soft;
|
|
using Mono.Debugging.Backend;
|
|
using Mono.Debugging.Evaluation;
|
|
using Mono.Debugging.Client;
|
|
|
|
|
|
namespace Mono.Debugging.Soft
|
|
{
|
|
public class SoftDebuggerAdaptor : ObjectValueAdaptor
|
|
{
|
|
static readonly Dictionary<Type, OpCode> convertOps = new Dictionary<Type, OpCode> ();
|
|
delegate object TypeCastDelegate (object value);
|
|
|
|
static SoftDebuggerAdaptor ()
|
|
{
|
|
convertOps.Add (typeof (double), OpCodes.Conv_R8);
|
|
convertOps.Add (typeof (float), OpCodes.Conv_R4);
|
|
convertOps.Add (typeof (ulong), OpCodes.Conv_U8);
|
|
convertOps.Add (typeof (uint), OpCodes.Conv_U4);
|
|
convertOps.Add (typeof (ushort), OpCodes.Conv_U2);
|
|
convertOps.Add (typeof (char), OpCodes.Conv_U2);
|
|
convertOps.Add (typeof (byte), OpCodes.Conv_U1);
|
|
convertOps.Add (typeof (long), OpCodes.Conv_I8);
|
|
convertOps.Add (typeof (int), OpCodes.Conv_I4);
|
|
convertOps.Add (typeof (short), OpCodes.Conv_I2);
|
|
convertOps.Add (typeof (sbyte), OpCodes.Conv_I1);
|
|
}
|
|
|
|
public SoftDebuggerAdaptor ()
|
|
{
|
|
}
|
|
|
|
public SoftDebuggerSession Session {
|
|
get { return (SoftDebuggerSession)DebuggerSession; }
|
|
set { DebuggerSession = value; }
|
|
}
|
|
|
|
//static string GetPrettyMethodName (EvaluationContext ctx, MethodMirror method)
|
|
//{
|
|
// var name = new System.Text.StringBuilder ();
|
|
|
|
// name.Append (ctx.Adapter.GetDisplayTypeName (method.ReturnType.FullName));
|
|
// name.Append (" ");
|
|
// name.Append (ctx.Adapter.GetDisplayTypeName (method.DeclaringType.FullName));
|
|
// name.Append (".");
|
|
// name.Append (method.Name);
|
|
|
|
// if (method.VirtualMachine.Version.AtLeast (2, 12)) {
|
|
// if (method.IsGenericMethodDefinition || method.IsGenericMethod) {
|
|
// name.Append ("<");
|
|
// if (method.VirtualMachine.Version.AtLeast (2, 15)) {
|
|
// var argTypes = method.GetGenericArguments ();
|
|
// for (int i = 0; i < argTypes.Length; i++) {
|
|
// if (i != 0)
|
|
// name.Append (", ");
|
|
// name.Append (ctx.Adapter.GetDisplayTypeName (argTypes[i].FullName));
|
|
// }
|
|
// }
|
|
// name.Append (">");
|
|
// }
|
|
// }
|
|
|
|
// name.Append (" (");
|
|
// var @params = method.GetParameters ();
|
|
// for (int i = 0; i < @params.Length; i++) {
|
|
// if (i != 0)
|
|
// name.Append (", ");
|
|
// if (@params[i].Attributes.HasFlag (ParameterAttributes.Out)) {
|
|
// if (@params[i].Attributes.HasFlag (ParameterAttributes.In))
|
|
// name.Append ("ref ");
|
|
// else
|
|
// name.Append ("out ");
|
|
// }
|
|
// name.Append (ctx.Adapter.GetDisplayTypeName (@params[i].ParameterType.FullName));
|
|
// name.Append (" ");
|
|
// name.Append (@params[i].Name);
|
|
// }
|
|
// name.Append (")");
|
|
|
|
// return name.ToString ();
|
|
//}
|
|
|
|
string InvokeToString (SoftEvaluationContext ctx, MethodMirror method, object obj)
|
|
{
|
|
try {
|
|
var result = ctx.RuntimeInvoke (method, obj, new Value[0]);
|
|
var str = result as StringMirror;
|
|
if (str != null)
|
|
{
|
|
return MirrorStringToString(ctx, str);
|
|
}
|
|
|
|
|
|
return null;
|
|
} catch {
|
|
return GetDisplayTypeName (GetValueTypeName (ctx, obj));
|
|
}
|
|
}
|
|
|
|
public override string CallToString (EvaluationContext ctx, object obj)
|
|
{
|
|
if (obj == null)
|
|
return null;
|
|
var str = obj as StringMirror;
|
|
if (str != null)
|
|
{
|
|
return str.Value;
|
|
}
|
|
|
|
var em = obj as EnumMirror;
|
|
if (em != null)
|
|
return em.StringValue;
|
|
|
|
var primitive = obj as PrimitiveValue;
|
|
if (primitive != null)
|
|
return primitive.Value.ToString ();
|
|
var pointer = obj as PointerValue;
|
|
if (pointer != null)
|
|
return string.Format ("0x{0:x}", pointer.Address);
|
|
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
var sm = obj as StructMirror;
|
|
var om = obj as ObjectMirror;
|
|
|
|
if (sm != null && sm.Type.IsPrimitive) {
|
|
// Boxed primitive
|
|
if (sm.Fields.Length > 0 && (sm.Fields[0] is PrimitiveValue))
|
|
return ((PrimitiveValue) sm.Fields[0]).Value.ToString ();
|
|
} else if (om != null && cx.Options.AllowTargetInvoke) {
|
|
var method = OverloadResolve (cx, om.Type, "ToString", null, new ArgumentType[0], true, false, false);
|
|
if (method != null && method.DeclaringType.FullName != "System.Object")
|
|
return InvokeToString (cx, method, obj);
|
|
} else if (sm != null && cx.Options.AllowTargetInvoke) {
|
|
var method = OverloadResolve (cx, sm.Type, "ToString", null, new ArgumentType [0], true, false, false);
|
|
if (method != null && method.DeclaringType.FullName != "System.ValueType")
|
|
return InvokeToString (cx, method, obj);
|
|
}
|
|
|
|
return GetDisplayTypeName (GetValueTypeName (ctx, obj));
|
|
}
|
|
|
|
public override object TryConvert (EvaluationContext ctx, object obj, object targetType)
|
|
{
|
|
var res = TryCast (ctx, obj, targetType);
|
|
|
|
if (res != null || obj == null)
|
|
return res;
|
|
|
|
var otype = GetValueType (ctx, obj) as Type;
|
|
|
|
if (otype != null) {
|
|
var tm = targetType as TypeMirror;
|
|
if (tm != null)
|
|
targetType = Type.GetType (tm.FullName, false);
|
|
|
|
var tt = targetType as Type;
|
|
if (tt != null) {
|
|
try {
|
|
var primitive = obj as PrimitiveValue;
|
|
if (primitive != null)
|
|
obj = primitive.Value;
|
|
|
|
res = System.Convert.ChangeType (obj, tt);
|
|
return CreateValue (ctx, res);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static readonly Dictionary<string, TypeCastDelegate> typeCastDelegatesCache = new Dictionary<string, TypeCastDelegate> ();
|
|
|
|
static TypeCastDelegate GenerateTypeCastDelegate (string methodName, Type fromType, Type toType)
|
|
{
|
|
lock(typeCastDelegatesCache) {
|
|
if (typeCastDelegatesCache.TryGetValue (methodName, out var cached))
|
|
return cached;
|
|
|
|
var argTypes = new [] { typeof (object) };
|
|
var method = new DynamicMethod (methodName, typeof (object), argTypes, true);
|
|
var il = method.GetILGenerator ();
|
|
ConstructorInfo ctorInfo;
|
|
System.Reflection.MethodInfo methodInfo;
|
|
|
|
il.Emit (OpCodes.Ldarg_0);
|
|
il.Emit (OpCodes.Unbox_Any, fromType);
|
|
|
|
if (fromType.IsSubclassOf (typeof (Nullable))) {
|
|
var propInfo = fromType.GetProperty ("Value");
|
|
methodInfo = propInfo.GetGetMethod ();
|
|
|
|
il.Emit (OpCodes.Stloc_0);
|
|
il.Emit (OpCodes.Ldloca_S);
|
|
il.Emit (OpCodes.Call, methodInfo);
|
|
|
|
fromType = methodInfo.ReturnType;
|
|
}
|
|
|
|
if (!convertOps.TryGetValue (toType, out var conv)) {
|
|
argTypes = new [] { fromType };
|
|
|
|
if (toType == typeof (string)) {
|
|
methodInfo = fromType.GetMethod ("ToString", new Type [0]);
|
|
il.Emit (OpCodes.Call, methodInfo);
|
|
} else if ((methodInfo = toType.GetMethod ("op_Explicit", argTypes)) != null) {
|
|
il.Emit (OpCodes.Call, methodInfo);
|
|
} else if ((methodInfo = toType.GetMethod ("op_Implicit", argTypes)) != null) {
|
|
il.Emit (OpCodes.Call, methodInfo);
|
|
} else if ((ctorInfo = toType.GetConstructor (argTypes)) != null) {
|
|
il.Emit (OpCodes.Call, ctorInfo);
|
|
} else {
|
|
// No idea what else to try...
|
|
throw new InvalidCastException ();
|
|
}
|
|
} else {
|
|
il.Emit (conv);
|
|
}
|
|
|
|
il.Emit (OpCodes.Box, toType);
|
|
il.Emit (OpCodes.Ret);
|
|
cached = (TypeCastDelegate)method.CreateDelegate (typeof (TypeCastDelegate));
|
|
typeCastDelegatesCache [methodName] = cached;
|
|
|
|
return cached;
|
|
}
|
|
}
|
|
|
|
static object DynamicCast (object value, Type target)
|
|
{
|
|
var methodName = string.Format ("CastFrom{0}To{1}", value.GetType ().Name, target.Name);
|
|
var method = GenerateTypeCastDelegate (methodName, value.GetType (), target);
|
|
|
|
return method.Invoke (value);
|
|
}
|
|
|
|
static bool CanForceCast (EvaluationContext ctx, ArgumentType fromType, TypeMirror toType)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
MethodMirror method;
|
|
|
|
if (CanCast (ctx, fromType, toType))
|
|
return true;
|
|
|
|
// check for explicit cast operators in the target type
|
|
method = OverloadResolve (cx, toType, "op_Explicit", null, new [] { fromType }, false, true, false, false);
|
|
if (method != null)
|
|
return true;
|
|
|
|
// check for explicit cast operators on the source type
|
|
method = OverloadResolve (cx, fromType.Type, "op_Explicit", null, toType, new [] { fromType }, false, true, false, false);
|
|
if (method != null)
|
|
return true;
|
|
|
|
method = OverloadResolve (cx, toType, ".ctor", null, new [] { fromType }, true, false, false, false);
|
|
if (method != null)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool CanCast (EvaluationContext ctx, ArgumentType fromType, TypeMirror toType)
|
|
{
|
|
var cx = (SoftEvaluationContext)ctx;
|
|
MethodMirror method;
|
|
|
|
// check for implicit cast operators in the target type
|
|
method = OverloadResolve (cx, toType, "op_Implicit", null, new [] { fromType }, false, true, false, false);
|
|
if (method != null)
|
|
return true;
|
|
|
|
// check for implicit cast operators on the source type
|
|
method = OverloadResolve (cx, fromType.Type, "op_Implicit", null, toType, new [] { fromType }, false, true, false, false);
|
|
if (method != null)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
object TryForceCast (EvaluationContext ctx, Value value, TypeMirror fromType, TypeMirror toType)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
MethodMirror method;
|
|
|
|
// check for explicit and implicit cast operators in the target type
|
|
method = OverloadResolve (cx, toType, "op_Explicit", null, new [] { new ArgumentType { Type = fromType } }, false, true, false, false);
|
|
if (method != null)
|
|
return cx.RuntimeInvoke (method, toType, new [] { value });
|
|
|
|
method = OverloadResolve (cx, toType, "op_Implicit", null, new [] { new ArgumentType { Type = fromType } }, false, true, false, false);
|
|
if (method != null)
|
|
return cx.RuntimeInvoke (method, toType, new [] { value });
|
|
|
|
// check for explicit and implicit cast operators on the source type
|
|
method = OverloadResolve (cx, fromType, "op_Explicit", null, toType, new [] { new ArgumentType { Type = fromType } }, false, true, false, false);
|
|
if (method != null)
|
|
return cx.RuntimeInvoke (method, fromType, new [] { value });
|
|
|
|
method = OverloadResolve (cx, fromType, "op_Implicit", null, toType, new [] { new ArgumentType { Type = fromType } }, false, true, false, false);
|
|
if (method != null)
|
|
return cx.RuntimeInvoke (method, fromType, new [] { value });
|
|
|
|
// Finally, try a ctor...
|
|
try {
|
|
return CreateValue (ctx, toType, value);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public override object TryCast (EvaluationContext ctx, object val, object type)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
var toType = type as TypeMirror;
|
|
TypeMirror fromType;
|
|
|
|
if (val == null)
|
|
return null;
|
|
|
|
if (val is DelayedLambdaValue)
|
|
return null;
|
|
|
|
var valueType = GetValueType (ctx, val);
|
|
|
|
fromType = valueType as TypeMirror;
|
|
|
|
// If we are trying to cast into non-primitive/enum value(e.g. System.nint)
|
|
// that class might have implicit operator and this must be handled via TypeMirrors
|
|
if (toType != null && !toType.IsPrimitive && !toType.IsEnum)
|
|
fromType = ToTypeMirror (ctx, valueType);
|
|
|
|
if (fromType != null) {
|
|
// Try casting the primitive type of the enum
|
|
var em = val as EnumMirror;
|
|
if (em != null)
|
|
return TryCast (ctx, CreateValue (ctx, em.Value), type);
|
|
|
|
if (toType != null && toType.IsAssignableFrom(fromType))
|
|
return val;
|
|
|
|
if (toType == null)
|
|
return null;
|
|
|
|
MethodMirror method;
|
|
|
|
if (fromType.IsGenericType && fromType.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal)) {
|
|
method = OverloadResolve (cx, fromType, "get_Value", null, new ArgumentType[0], true, false, false);
|
|
if (method != null) {
|
|
val = cx.RuntimeInvoke (method, val, new Value[0]);
|
|
return TryCast (ctx, val, type);
|
|
}
|
|
}
|
|
|
|
|
|
if (toType.IsGenericType && toType.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal))
|
|
{
|
|
var primitiveVal = val as PrimitiveValue;
|
|
if (primitiveVal != null && primitiveVal.Value == null) {
|
|
val = CreateValue (ctx, toType, new object [0]);
|
|
} else {
|
|
val = CreateValue (ctx, toType, val);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
return TryForceCast (ctx, (Value) val, fromType, toType);
|
|
}
|
|
|
|
var vtype = valueType as Type;
|
|
|
|
if (vtype != null) {
|
|
if (toType != null) {
|
|
if (toType.IsEnum) {
|
|
var casted = TryCast (ctx, val, toType.EnumUnderlyingType) as PrimitiveValue;
|
|
|
|
return casted != null ? cx.Session.VirtualMachine.CreateEnumMirror (toType, casted) : null;
|
|
}
|
|
|
|
type = Type.GetType (toType.FullName, false);
|
|
}
|
|
|
|
var tt = type as Type;
|
|
if (tt != null) {
|
|
if (tt.IsAssignableFrom (vtype))
|
|
return val;
|
|
|
|
try {
|
|
if (tt.IsPrimitive || tt == typeof (string)) {
|
|
var primitive = val as PrimitiveValue;
|
|
if (primitive != null)
|
|
val = primitive.Value;
|
|
|
|
if (val == null)
|
|
return null;
|
|
|
|
return CreateValue (ctx, DynamicCast (val, tt));
|
|
}
|
|
|
|
fromType = (TypeMirror) ForceLoadType (ctx, ((Type) valueType).FullName);
|
|
if (toType == null)
|
|
toType = (TypeMirror) ForceLoadType (ctx, tt.FullName);
|
|
|
|
return TryForceCast (ctx, (Value) val, fromType, toType);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override IStringAdaptor CreateStringAdaptor (EvaluationContext ctx, object str)
|
|
{
|
|
return new StringAdaptor ((StringMirror) str);
|
|
}
|
|
|
|
public override ICollectionAdaptor CreateArrayAdaptor (EvaluationContext ctx, object arr)
|
|
{
|
|
return new ArrayAdaptor ((ArrayMirror) arr);
|
|
}
|
|
|
|
public override object CreateNullValue (EvaluationContext ctx, object type)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
return new PrimitiveValue (cx.Session.VirtualMachine, null);
|
|
}
|
|
|
|
public override object CreateTypeObject (EvaluationContext ctx, object type)
|
|
{
|
|
return ((TypeMirror) type).GetTypeObject ();
|
|
}
|
|
|
|
public override object CreateValue (EvaluationContext ctx, object type, params object[] argValues)
|
|
{
|
|
ctx.AssertTargetInvokeAllowed ();
|
|
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
var tm = (TypeMirror) type;
|
|
|
|
var types = new ArgumentType [argValues.Length];
|
|
var values = new Value[argValues.Length];
|
|
|
|
for (int n = 0; n < argValues.Length; n++) {
|
|
types[n] = ToArgumentType (ctx, GetValueType (ctx, argValues[n]));
|
|
}
|
|
|
|
var method = OverloadResolve (cx, tm, ".ctor", null, types, true, false, false);
|
|
|
|
if (method != null) {
|
|
var mparams = method.GetParameters ();
|
|
|
|
for (int n = 0; n < argValues.Length; n++) {
|
|
var param_type = mparams [n].ParameterType;
|
|
|
|
if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && param_type.IsGenericType) {
|
|
/* TODO: Add genericTypeArgs and handle this
|
|
bool throwCastException = true;
|
|
|
|
if (method.VirtualMachine.Version.AtLeast (2, 15)) {
|
|
var args = param_type.GetGenericArguments ();
|
|
|
|
if (args.Length == genericTypes.Length) {
|
|
var real_type = soft.Adapter.GetType (soft, param_type.GetGenericTypeDefinition ().FullName, genericTypes);
|
|
|
|
values [n] = (Value)TryCast (soft, (Value)argValues [n], real_type);
|
|
if (!(values [n] == null && argValues [n] != null && !soft.Adapter.IsNull (soft, argValues [n])))
|
|
throwCastException = false;
|
|
}
|
|
}
|
|
|
|
if (throwCastException) {
|
|
string fromType = !IsGeneratedType (types [n]) ? soft.Adapter.GetDisplayTypeName (soft, types [n]) : types [n].FullName;
|
|
string toType = soft.Adapter.GetDisplayTypeName (soft, param_type);
|
|
|
|
throw new EvaluatorException ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", n, fromType, toType);
|
|
}*/
|
|
} else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && CanForceCast (ctx, types [n], param_type)) {
|
|
values [n] = (Value)TryCast (ctx, argValues [n], param_type);
|
|
} else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && CanDoPrimaryCast (types [n], param_type)) {
|
|
values [n] = (Value)TryConvert (ctx, argValues [n], param_type);
|
|
} else {
|
|
values [n] = (Value)argValues [n];
|
|
}
|
|
}
|
|
|
|
lock(method.VirtualMachine) {
|
|
return tm.NewInstance (cx.Thread, method, values);
|
|
}
|
|
}
|
|
|
|
if (argValues.Length == 0 && tm.VirtualMachine.Version.AtLeast (2, 31))
|
|
return tm.NewInstance ();
|
|
|
|
var typeName = ctx.Adapter.GetDisplayTypeName (ctx, type);
|
|
|
|
throw new EvaluatorException ("Constructor not found for type `{0}'.", typeName);
|
|
}
|
|
|
|
public override object CreateValue (EvaluationContext ctx, object value)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
var str = value as string;
|
|
if (str != null)
|
|
return cx.Domain.CreateString (str);
|
|
|
|
if (value is decimal) {
|
|
var bits = decimal.GetBits ((decimal)value);
|
|
|
|
return CreateValue (ctx, ToTypeMirror (ctx, typeof (decimal)), CreateValue (ctx, bits [0]), CreateValue (ctx, bits [1]), CreateValue (ctx, bits [2]), CreateValue (ctx, (bits [3] & unchecked((int)0x80000000)) != 0), CreateValue (ctx, (byte)(bits [3] >> 16)));
|
|
}
|
|
|
|
return cx.Session.VirtualMachine.CreateValue (value);
|
|
}
|
|
|
|
public object CreateByteArray (EvaluationContext ctx, byte [] bytes)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
if (Session.VirtualMachine.Version.AtLeast (2, 52))
|
|
return cx.Domain.CreateByteArray (bytes);
|
|
|
|
var arrayType = GetType (ctx, "System.Array");
|
|
var int32Type = GetType (ctx, "System.Int32");
|
|
var typeType = GetType (ctx, "System.Type");
|
|
var stringType = GetType (ctx, "System.String");
|
|
var byteTypeValue = RuntimeInvoke (ctx, typeType, null, "GetType", new object [] { stringType }, new object [] { CreateValue (ctx, "System.Byte") });
|
|
//var byteType = ctx.Adapter.GetType (ctx, "System.Byte");
|
|
var n = CreateValue (ctx, bytes.Length);
|
|
var args = new object [] { byteTypeValue, n };
|
|
var arr = RuntimeInvoke (ctx, arrayType, null, "CreateInstance", new object [] { typeType, int32Type }, args);
|
|
if (arr is ArrayMirror) {
|
|
var arrm = arr as ArrayMirror;
|
|
arrm.SetByteValues (bytes);
|
|
return arr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override object GetBaseValue (EvaluationContext ctx, object val)
|
|
{
|
|
return val;
|
|
}
|
|
|
|
public override bool NullableHasValue (EvaluationContext ctx, object type, object obj)
|
|
{
|
|
var hasValue = GetMember (ctx, type, obj, "hasValue") ?? GetMember (ctx, type, obj, "has_value");
|
|
|
|
return (bool) hasValue.ObjectValue;
|
|
}
|
|
|
|
public override ValueReference NullableGetValue (EvaluationContext ctx, object type, object obj)
|
|
{
|
|
return GetMember (ctx, type, obj, "value");
|
|
}
|
|
|
|
public override object GetEnclosingType (EvaluationContext ctx)
|
|
{
|
|
return ((SoftEvaluationContext) ctx).Frame.Method.DeclaringType;
|
|
}
|
|
|
|
public override string[] GetImportedNamespaces (EvaluationContext ctx)
|
|
{
|
|
var namespaces = new HashSet<string> ();
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
foreach (var type in cx.Session.GetAllTypes ())
|
|
namespaces.Add (type.Namespace);
|
|
|
|
var nss = new string [namespaces.Count];
|
|
namespaces.CopyTo (nss);
|
|
|
|
return nss;
|
|
}
|
|
|
|
public override ValueReference GetIndexerReference (EvaluationContext ctx, object target, object type, object [] indices)
|
|
{
|
|
var values = new Value [indices.Length];
|
|
var types = new ArgumentType [indices.Length];
|
|
for (int n = 0; n < indices.Length; n++) {
|
|
types[n] = ToArgumentType (ctx, GetValueType (ctx, indices[n]));
|
|
values[n] = (Value) indices[n];
|
|
}
|
|
|
|
var candidates = new List<MethodMirror> ();
|
|
var props = new List<PropertyInfoMirror> ();
|
|
var mirType = type as TypeMirror;
|
|
while (mirType != null) {
|
|
foreach (var prop in mirType.GetProperties ()) {
|
|
var met = prop.GetGetMethod (true);
|
|
if (met != null &&
|
|
!met.IsStatic &&
|
|
met.GetParameters ().Length > 0 &&
|
|
!(met.IsPrivate && met.IsVirtual)) {//Don't use explicit interface implementation
|
|
candidates.Add (met);
|
|
props.Add (prop);
|
|
}
|
|
}
|
|
mirType = mirType.BaseType;
|
|
}
|
|
|
|
var idx = OverloadResolve ((SoftEvaluationContext) ctx, mirType, null, null, null, types, candidates, true);
|
|
int i = candidates.IndexOf (idx);
|
|
|
|
var getter = props[i].GetGetMethod (true);
|
|
|
|
return getter != null ? new PropertyValueReference (ctx, props[i], target, null, getter, values) : null;
|
|
}
|
|
|
|
public override ValueReference GetIndexerReference (EvaluationContext ctx, object target, object[] indices)
|
|
{
|
|
object valueType = GetValueType (ctx, target);
|
|
var targetType = valueType as TypeMirror;
|
|
|
|
if (targetType == null) {
|
|
var tt = valueType as Type;
|
|
|
|
if (tt == null)
|
|
return null;
|
|
|
|
targetType = (TypeMirror) ForceLoadType (ctx, tt.FullName);
|
|
}
|
|
|
|
return GetIndexerReference (ctx, target, targetType, indices);
|
|
}
|
|
|
|
static bool InGeneratedClosureOrIteratorType (EvaluationContext ctx)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
if (cx.Frame.Method.IsStatic)
|
|
return false;
|
|
|
|
var tm = cx.Frame.Method.DeclaringType;
|
|
|
|
return IsGeneratedType (tm);
|
|
}
|
|
|
|
static bool IsLocalFunction(EvaluationContext ctx)
|
|
{
|
|
return ((SoftEvaluationContext)ctx).Frame.Method.Name.IndexOf (">g__", StringComparison.Ordinal) > 0;
|
|
}
|
|
|
|
internal static bool IsGeneratedType (TypeMirror tm)
|
|
{
|
|
//
|
|
// This should cover all C# generated special containers
|
|
// - anonymous methods
|
|
// - lambdas
|
|
// - iterators
|
|
// - async methods
|
|
//
|
|
// which allow stepping into
|
|
//
|
|
|
|
// Note: mcs uses the form <${NAME}>c__${KIND}${NUMBER} where the leading '<' seems to have been dropped in 3.4.x
|
|
// csc uses the form <${NAME}>d__${NUMBER}
|
|
// roslyn uses the form <${NAME}>d
|
|
|
|
return tm.Name.IndexOf (">c__", StringComparison.Ordinal) > 0 || tm.Name.IndexOf (">d", StringComparison.Ordinal) > 0;
|
|
}
|
|
|
|
internal static string GetNameFromGeneratedType (TypeMirror tm)
|
|
{
|
|
return tm.Name.Substring (1, tm.Name.IndexOf ('>') - 1);
|
|
}
|
|
|
|
static bool IsHoistedThisReference (FieldInfoMirror field)
|
|
{
|
|
// mcs is "<>f__this" or "$this" (if in an async compiler generated type)
|
|
// csc is "<>4__this"
|
|
return field.Name == "$this" ||
|
|
(field.Name.StartsWith ("<>", StringComparison.Ordinal) &&
|
|
field.Name.EndsWith ("__this", StringComparison.Ordinal));
|
|
}
|
|
|
|
static bool IsClosureReferenceField (FieldInfoMirror field)
|
|
{
|
|
// mcs is "$locvar"
|
|
// old mcs is "<>f__ref"
|
|
// csc is "CS$<>"
|
|
// roslyn is "<>8__"
|
|
return field.Name.StartsWith ("CS$<>", StringComparison.Ordinal) ||
|
|
field.Name.StartsWith ("<>f__ref", StringComparison.Ordinal) ||
|
|
field.Name.StartsWith ("$locvar", StringComparison.Ordinal) ||
|
|
field.Name.StartsWith ("<>8__", StringComparison.Ordinal);
|
|
}
|
|
|
|
static bool IsClosureReferenceLocal (LocalVariable local)
|
|
{
|
|
if (local.Name == null)
|
|
return false;
|
|
|
|
// mcs is "$locvar" or starts with '<'
|
|
// csc is "CS$<>"
|
|
return local.Name.Length == 0 || local.Name[0] == '<' || local.Name.StartsWith ("$locvar", StringComparison.Ordinal) ||
|
|
local.Name.StartsWith ("CS$<>", StringComparison.Ordinal);
|
|
}
|
|
|
|
static bool IsGeneratedTemporaryLocal (LocalVariable local)
|
|
{
|
|
// csc uses CS$ prefix for temporary variables and <>t__ prefix for async task-related state variables
|
|
return local.Name != null && (local.Name.StartsWith ("CS$", StringComparison.Ordinal) || local.Name.StartsWith ("<>t__", StringComparison.Ordinal));
|
|
}
|
|
|
|
Dictionary<MethodMirror, PortablePdbData.SoftScope []> methodScopeCache = new Dictionary<MethodMirror, PortablePdbData.SoftScope []> ();
|
|
|
|
string GetHoistedIteratorLocalName (FieldInfoMirror field, SoftEvaluationContext cx)
|
|
{
|
|
//mcs captured args, of form <$>name
|
|
if (field.Name.StartsWith ("<$>", StringComparison.Ordinal)) {
|
|
return field.Name.Substring (3);
|
|
}
|
|
|
|
// csc, mcs locals of form <name>__#, where # represents index of scope
|
|
// roslyn locals of form <name>5__#, where # represents index of scope
|
|
if (field.Name [0] == '<') {
|
|
int suffixLength = 3;
|
|
var i = field.Name.IndexOf (">__", StringComparison.Ordinal);
|
|
if (i == -1) {
|
|
suffixLength = 4;
|
|
i = field.Name.IndexOf (">5__", StringComparison.Ordinal);
|
|
}
|
|
|
|
if (i != -1 && field.VirtualMachine.Version.AtLeast (2, 43)) {
|
|
if (int.TryParse (field.Name.Substring (i + suffixLength), out int scopeIndex) && scopeIndex > 0) {//0 means whole method scope
|
|
PortablePdbData.SoftScope [] scopes = null;
|
|
|
|
scopeIndex--;//Scope index is 1 based(not zero)
|
|
|
|
if (cx.Frame.Method != null && !methodScopeCache.TryGetValue (cx.Frame.Method, out scopes)) {
|
|
scopes = cx.Session.GetPdbData (cx.Frame.Method)?.GetHoistedScopes (cx.Frame.Method);
|
|
if (scopes == null || scopes.Length == 0) {
|
|
// If hoisted scopes are empty use normal scopes
|
|
scopes = cx.Frame.Method.GetScopes ().Select (s => new PortablePdbData.SoftScope { LiveRangeStart = s.LiveRangeStart, LiveRangeEnd = s.LiveRangeEnd }).ToArray ();
|
|
DebuggerLoggingService.LogMessage ("PDB data not found for frame: {0}", cx.Frame);
|
|
}
|
|
|
|
methodScopeCache [cx.Frame.Method] = scopes;
|
|
}
|
|
|
|
if (scopes != null && scopeIndex < scopes.Length) {
|
|
var scope = scopes [scopeIndex];
|
|
if (scope.LiveRangeStart > cx.Frame.Location.ILOffset || scope.LiveRangeEnd < cx.Frame.Location.ILOffset)
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
i = field.Name.IndexOf ('>');
|
|
if (i > 1) {
|
|
return field.Name.Substring (1, i - 1);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
IEnumerable<ValueReference> GetHoistedLocalVariables (SoftEvaluationContext cx, ValueReference vthis, HashSet<FieldInfoMirror> alreadyVisited = null)
|
|
{
|
|
if (vthis == null)
|
|
return new ValueReference [0];
|
|
|
|
object val;
|
|
try {
|
|
val = vthis.Value;
|
|
} catch (InvalidStackFrameException) {
|
|
return new ValueReference [0];
|
|
} catch (AbsentInformationException) {
|
|
return new ValueReference [0];
|
|
} catch (EvaluatorException ex) when (ex.InnerException is AbsentInformationException) {
|
|
return new ValueReference [0];
|
|
}
|
|
|
|
if (IsNull (cx, val))
|
|
return new ValueReference [0];
|
|
|
|
var tm = (TypeMirror) vthis.Type;
|
|
var isIterator = IsGeneratedType (tm);
|
|
|
|
var list = new List<ValueReference> ();
|
|
var type = (TypeMirror) vthis.Type;
|
|
|
|
foreach (var field in type.GetFields ()) {
|
|
if (IsHoistedThisReference (field))
|
|
continue;
|
|
|
|
if (IsClosureReferenceField (field)) {
|
|
alreadyVisited = alreadyVisited ?? new HashSet<FieldInfoMirror> ();
|
|
if (alreadyVisited.Contains (field))
|
|
continue;
|
|
alreadyVisited.Add (field);
|
|
list.AddRange (GetHoistedLocalVariables (cx, new FieldValueReference (cx, field, val, type), alreadyVisited));
|
|
continue;
|
|
}
|
|
|
|
if (field.Name[0] == '<') {
|
|
if (isIterator) {
|
|
var name = GetHoistedIteratorLocalName (field, cx);
|
|
|
|
if (!string.IsNullOrEmpty (name))
|
|
list.Add (new FieldValueReference (cx, field, val, type, name, ObjectValueFlags.Variable) {
|
|
ParentSource = vthis
|
|
});
|
|
}
|
|
} else if (!field.Name.Contains ("$")) {
|
|
list.Add (new FieldValueReference (cx, field, val, type, field.Name, ObjectValueFlags.Variable) {
|
|
ParentSource = vthis
|
|
});
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
ValueReference GetHoistedThisReference (SoftEvaluationContext cx)
|
|
{
|
|
try {
|
|
var val = cx.Frame.GetThis ();
|
|
var valueType = GetValueType (cx, val);
|
|
var type = valueType as TypeMirror;
|
|
|
|
if (type == null) {
|
|
var tt = valueType as Type;
|
|
|
|
if (tt == null)
|
|
return null;
|
|
|
|
type = (TypeMirror) ForceLoadType (cx, tt.FullName);
|
|
}
|
|
|
|
return GetHoistedThisReference (cx, type, val);
|
|
} catch (InvalidStackFrameException) {
|
|
} catch (AbsentInformationException) {
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ValueReference GetHoistedThisReference (SoftEvaluationContext cx, TypeMirror type, object val, HashSet<FieldInfoMirror> alreadyVisited = null)
|
|
{
|
|
foreach (var field in type.GetFields ()) {
|
|
if (IsHoistedThisReference (field))
|
|
return new FieldValueReference (cx, field, val, type, "this", ObjectValueFlags.Literal);
|
|
|
|
if (IsClosureReferenceField (field)) {
|
|
alreadyVisited = alreadyVisited ?? new HashSet<FieldInfoMirror> ();
|
|
if (alreadyVisited.Contains (field))
|
|
continue;
|
|
alreadyVisited.Add (field);
|
|
var fieldRef = new FieldValueReference (cx, field, val, type);
|
|
var thisRef = GetHoistedThisReference (cx, field.FieldType, fieldRef.Value, alreadyVisited);
|
|
if (thisRef != null)
|
|
return thisRef;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// if the local does not have a name, constructs one from the index
|
|
static string GetLocalName (SoftEvaluationContext cx, LocalVariable local)
|
|
{
|
|
if (!string.IsNullOrEmpty (local.Name) || cx.SourceCodeAvailable)
|
|
return local.Name;
|
|
|
|
return "loc" + local.Index;
|
|
}
|
|
|
|
protected override ValueReference OnGetLocalVariable (EvaluationContext ctx, string name)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
if (InGeneratedClosureOrIteratorType (cx) || IsLocalFunction(cx))
|
|
return FindByName (OnGetLocalVariables (cx), v => v.Name, name, ctx.CaseSensitive);
|
|
|
|
try {
|
|
LocalVariable local = null;
|
|
|
|
if (!cx.SourceCodeAvailable) {
|
|
if (name.StartsWith ("loc", StringComparison.Ordinal)) {
|
|
int idx;
|
|
|
|
if (int.TryParse (name.Substring (3), out idx))
|
|
local = cx.Frame.Method.GetLocals ().FirstOrDefault (loc => loc.Index == idx);
|
|
}
|
|
} else {
|
|
local = ctx.CaseSensitive
|
|
? cx.Frame.GetVisibleVariableByName (name)
|
|
: FindByName (cx.Frame.GetVisibleVariables (), v => v.Name, name, false);
|
|
}
|
|
|
|
if (local != null)
|
|
return new VariableValueReference (ctx, GetLocalName (cx, local), local);
|
|
|
|
return FindByName (OnGetLocalVariables (ctx), v => v.Name, name, ctx.CaseSensitive);
|
|
} catch (InvalidStackFrameException) {
|
|
return null;
|
|
} catch (AbsentInformationException) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected override IEnumerable<ValueReference> OnGetLocalVariables (EvaluationContext ctx)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
if (InGeneratedClosureOrIteratorType (cx)) {
|
|
var vthis = GetThisReference (cx);
|
|
return GetHoistedLocalVariables (cx, vthis).Union (GetLocalVariables (cx));
|
|
}
|
|
|
|
if (IsLocalFunction (cx)) {
|
|
var vthis = GetClosureReference (cx);
|
|
// if there's no closure reference then it didn't capture anything
|
|
if (vthis != null) {
|
|
return GetHoistedLocalVariables (cx, vthis).Union (GetLocalVariables (cx));
|
|
}
|
|
}
|
|
|
|
return GetLocalVariables (cx);
|
|
}
|
|
|
|
static VariableValueReference GetClosureReference (SoftEvaluationContext cx)
|
|
{
|
|
foreach (var local in cx.Frame.Method.GetLocals ()) {
|
|
if (IsClosureReferenceLocal (local)) {
|
|
return new VariableValueReference (cx, local.Name, local);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
IEnumerable<ValueReference> GetLocalVariables (SoftEvaluationContext cx)
|
|
{
|
|
LocalVariable[] locals;
|
|
|
|
try {
|
|
locals = cx.Frame.GetVisibleVariables ().Where (x => !x.IsArg && ((IsClosureReferenceLocal (x) && IsGeneratedType (x.Type)) || !IsGeneratedTemporaryLocal (x))).ToArray ();
|
|
} catch (InvalidStackFrameException) {
|
|
yield break;
|
|
} catch (AbsentInformationException) {
|
|
yield break;
|
|
}
|
|
|
|
if (locals.Length == 0)
|
|
yield break;
|
|
|
|
var batch = new LocalVariableBatch (locals);
|
|
|
|
for (int i = 0; i < locals.Length; i++) {
|
|
if (IsClosureReferenceLocal (locals[i]) && IsGeneratedType (locals[i].Type)) {
|
|
foreach (var gv in GetHoistedLocalVariables (cx, new VariableValueReference (cx, locals[i].Name, locals[i], batch))) {
|
|
yield return gv;
|
|
}
|
|
} else if (!IsGeneratedTemporaryLocal (locals[i])) {
|
|
yield return new VariableValueReference (cx, GetLocalName (cx, locals[i]), locals[i], batch);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override bool HasMember (EvaluationContext ctx, object type, string memberName, BindingFlags bindingFlags)
|
|
{
|
|
var tm = (TypeMirror) type;
|
|
|
|
while (tm != null) {
|
|
var field = FindByName (tm.GetFields (), f => f.Name, memberName, ctx.CaseSensitive);
|
|
|
|
if (field != null)
|
|
return true;
|
|
|
|
var prop = FindByName (tm.GetProperties (), p => p.Name, memberName, ctx.CaseSensitive);
|
|
|
|
if (prop != null) {
|
|
var getter = prop.GetGetMethod (bindingFlags.HasFlag (BindingFlags.NonPublic));
|
|
if (getter != null)
|
|
return true;
|
|
}
|
|
|
|
if (bindingFlags.HasFlag (BindingFlags.DeclaredOnly))
|
|
break;
|
|
|
|
tm = tm.BaseType;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool IsAnonymousType (TypeMirror type)
|
|
{
|
|
return type.Name.StartsWith ("<>__AnonType", StringComparison.Ordinal);
|
|
}
|
|
|
|
protected override ValueReference GetMember (EvaluationContext ctx, object t, object co, string name)
|
|
{
|
|
return OnGetMember (ctx, null, t, co, name);
|
|
}
|
|
|
|
protected override ValueReference OnGetMember (EvaluationContext ctx, IObjectSource objectSource, object t, object co, string name)
|
|
{
|
|
var type = t as TypeMirror;
|
|
var tupleNames = GetTupleElementNames (objectSource, ctx);
|
|
while (type != null) {
|
|
var field = FindByName (type.GetFields (), f => MapTupleName(f.Name, tupleNames), name, ctx.CaseSensitive);
|
|
|
|
if (field != null && (field.IsStatic || co != null))
|
|
return new FieldValueReference (ctx, field, co, type);
|
|
|
|
var prop = FindByName (type.GetProperties (), p => p.Name, name, ctx.CaseSensitive);
|
|
|
|
if (prop != null && (IsStatic (prop) || co != null)) {
|
|
var getter = prop.GetGetMethod (true);
|
|
// Optimization: if the property has a CompilerGenerated backing field, use that instead.
|
|
// This way we avoid overhead of invoking methods on the debugee when the value is requested.
|
|
//But also check that this method is not virtual, because in that case we need to call getter to invoke override
|
|
if (!getter.IsVirtual) {
|
|
var cgFieldName = string.Format ("<{0}>{1}", prop.Name, IsAnonymousType (type) ? "" : "k__BackingField");
|
|
if ((field = FindByName (type.GetFields (), f => f.Name, cgFieldName, true)) != null && IsCompilerGenerated (field))
|
|
return new FieldValueReference (ctx, field, co, type, prop.Name, ObjectValueFlags.Property);
|
|
}
|
|
// Backing field not available, so do things the old fashioned way.
|
|
return getter != null ? new PropertyValueReference (ctx, prop, co, type, getter, null) : null;
|
|
}
|
|
if (type.IsInterface) {
|
|
foreach (var inteface in type.GetInterfaces ()) {
|
|
var result = GetMember (ctx, inteface, co, name);
|
|
if (result != null)
|
|
return result;
|
|
}
|
|
//foreach above recursively checked all "base" interfaces
|
|
//nothing was found, quit, otherwise we would loop forever
|
|
return null;
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static string MapTupleName (string name, string [] tupleNames)
|
|
{
|
|
if (tupleNames != null &&
|
|
name.Length > 4 &&
|
|
name.StartsWith ("Item", StringComparison.Ordinal) &&
|
|
int.TryParse (name.Substring (4), out var tupleIndex) &&
|
|
tupleNames.Length >= tupleIndex &&
|
|
tupleNames [tupleIndex - 1] != null)
|
|
return tupleNames [tupleIndex - 1];
|
|
|
|
return name;
|
|
}
|
|
|
|
static bool IsCompilerGenerated (FieldInfoMirror field)
|
|
{
|
|
var attrs = field.GetCustomAttributes (true);
|
|
var generated = GetAttribute<CompilerGeneratedAttribute> (attrs);
|
|
|
|
return generated != null;
|
|
}
|
|
|
|
static bool IsStatic (PropertyInfoMirror prop)
|
|
{
|
|
var met = prop.GetGetMethod (true) ?? prop.GetSetMethod (true);
|
|
|
|
return met.IsStatic;
|
|
}
|
|
|
|
static T FindByName<T> (IEnumerable<T> items, Func<T,string> getName, string name, bool caseSensitive)
|
|
{
|
|
var best = default(T);
|
|
|
|
foreach (var item in items) {
|
|
var itemName = getName (item);
|
|
|
|
if (itemName == name)
|
|
return item;
|
|
|
|
if (!caseSensitive && itemName.Equals (name, StringComparison.CurrentCultureIgnoreCase))
|
|
best = item;
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
protected override IEnumerable<ValueReference> GetMembers (EvaluationContext ctx, object t, object co, BindingFlags bindingFlags)
|
|
{
|
|
return GetMembers (ctx, null, t, co, bindingFlags);
|
|
}
|
|
|
|
string[] GetTupleElementNames(IObjectSource source, EvaluationContext ctx)
|
|
{
|
|
if (source is FieldValueReference)
|
|
{
|
|
FieldValueReference field = source as FieldValueReference;
|
|
if ((field.Type as TypeMirror)?.Name?.StartsWith("ValueTuple`", StringComparison.Ordinal) != true)
|
|
return null;
|
|
return field.GetTupleElementNames();
|
|
}
|
|
if (source is PropertyValueReference)
|
|
{
|
|
PropertyValueReference prop = source as PropertyValueReference;
|
|
if ((prop.Type as TypeMirror)?.Name?.StartsWith("ValueTuple`", StringComparison.Ordinal) != true)
|
|
return null;
|
|
return prop.GetTupleElementNames();
|
|
}
|
|
if (source is VariableValueReference)
|
|
{
|
|
VariableValueReference variable = source as VariableValueReference;
|
|
if ((variable.Type as TypeMirror)?.Name?.StartsWith("ValueTuple`", StringComparison.Ordinal) != true)
|
|
return null;
|
|
return variable.GetTupleElementNames((SoftEvaluationContext)ctx);
|
|
}
|
|
return null;
|
|
|
|
}
|
|
|
|
protected override IEnumerable<ValueReference> GetMembers (EvaluationContext ctx, IObjectSource objectSource, object t, object co, BindingFlags bindingFlags)
|
|
{
|
|
var subProps = new Dictionary<string, PropertyInfoMirror> ();
|
|
var type = t as TypeMirror;
|
|
TypeMirror realType = null;
|
|
|
|
if (co != null && (bindingFlags & BindingFlags.Instance) != 0)
|
|
realType = GetValueType (ctx, co) as TypeMirror;
|
|
|
|
// First of all, get a list of properties overriden in sub-types
|
|
while (realType != null && realType != type) {
|
|
foreach (var prop in realType.GetProperties (bindingFlags | BindingFlags.DeclaredOnly)) {
|
|
var met = prop.GetGetMethod (true);
|
|
|
|
if (met == null || met.GetParameters ().Length != 0 || met.IsAbstract || !met.IsVirtual || met.IsStatic)
|
|
continue;
|
|
|
|
if (met.IsPublic && ((bindingFlags & BindingFlags.Public) == 0))
|
|
continue;
|
|
|
|
if (!met.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0))
|
|
continue;
|
|
|
|
subProps [prop.Name] = prop;
|
|
}
|
|
|
|
realType = realType.BaseType;
|
|
}
|
|
|
|
var tupleNames = GetTupleElementNames (objectSource, ctx);
|
|
bool hasExplicitInterface = false;
|
|
while (type != null) {
|
|
var fieldsBatch = new FieldReferenceBatch (co);
|
|
foreach (var field in type.GetFields ()) {
|
|
if (field.IsStatic && ((bindingFlags & BindingFlags.Static) == 0))
|
|
continue;
|
|
|
|
if (!field.IsStatic && ((bindingFlags & BindingFlags.Instance) == 0))
|
|
continue;
|
|
|
|
if (field.IsPublic && ((bindingFlags & BindingFlags.Public) == 0))
|
|
continue;
|
|
|
|
if (!field.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0))
|
|
continue;
|
|
if (field.IsStatic) {
|
|
yield return new FieldValueReference (ctx, field, co, type);
|
|
} else {
|
|
fieldsBatch.Add (field);
|
|
yield return new FieldValueReference (ctx, field, co, type, MapTupleName (field.Name, tupleNames), ObjectValueFlags.Field, fieldsBatch);
|
|
}
|
|
}
|
|
|
|
foreach (var prop in type.GetProperties (bindingFlags)) {
|
|
var getter = prop.GetGetMethod (true);
|
|
|
|
if (getter == null || getter.GetParameters ().Length != 0 || getter.IsAbstract)
|
|
continue;
|
|
|
|
if (getter.IsStatic && ((bindingFlags & BindingFlags.Static) == 0))
|
|
continue;
|
|
|
|
if (!getter.IsStatic && ((bindingFlags & BindingFlags.Instance) == 0))
|
|
continue;
|
|
|
|
if (getter.IsPublic && ((bindingFlags & BindingFlags.Public) == 0))
|
|
continue;
|
|
|
|
//This is only possible in case of explicitly implemented interface property, which we handle later
|
|
if (getter.IsVirtual && getter.IsPrivate) {
|
|
hasExplicitInterface = true;
|
|
continue;
|
|
}
|
|
|
|
if (!getter.IsPublic && ((bindingFlags & BindingFlags.NonPublic) == 0))
|
|
continue;
|
|
|
|
// If a property is overriden, return the override instead of the base property
|
|
PropertyInfoMirror overridden;
|
|
if (getter.IsVirtual && subProps.TryGetValue (prop.Name, out overridden)) {
|
|
getter = overridden.GetGetMethod (true);
|
|
if (getter == null)
|
|
continue;
|
|
|
|
yield return new PropertyValueReference (ctx, overridden, co, overridden.DeclaringType, getter, null);
|
|
} else {
|
|
yield return new PropertyValueReference (ctx, prop, co, type, getter, null);
|
|
}
|
|
}
|
|
|
|
if ((bindingFlags & BindingFlags.DeclaredOnly) != 0)
|
|
break;
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
type = t as TypeMirror;
|
|
if (type == null ||
|
|
!hasExplicitInterface ||
|
|
(bindingFlags & BindingFlags.Instance) == 0 ||
|
|
(bindingFlags & BindingFlags.Public) == 0) {
|
|
yield break;
|
|
}
|
|
if (Session.VirtualMachine.Version.AtLeast (2, 11)) {
|
|
var interfaces = type.GetInterfaces ();
|
|
foreach (var intr in interfaces) {
|
|
var map = type.GetInterfaceMap (intr);
|
|
foreach (var prop in intr.GetProperties (bindingFlags)) {
|
|
var getter = prop.GetGetMethod (true);
|
|
if (getter == null || getter.GetParameters ().Length != 0)
|
|
continue;
|
|
var implementationGetter = map.TargetMethods [Array.IndexOf (map.InterfaceMethods, getter)];
|
|
//We are only intersted into private(explicit) implementations because public ones are already handled before
|
|
if (implementationGetter.IsPublic)
|
|
continue;
|
|
yield return new PropertyValueReference (ctx, prop, co, type, getter, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool IsIEnumerable (TypeMirror type)
|
|
{
|
|
if (!type.IsInterface)
|
|
return false;
|
|
|
|
if (type.Namespace == "System.Collections" && type.Name == "IEnumerable")
|
|
return true;
|
|
|
|
if (type.Namespace == "System.Collections.Generic" && type.Name == "IEnumerable`1")
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static ObjectValueFlags GetFlags (MethodMirror method)
|
|
{
|
|
var flags = ObjectValueFlags.Method;
|
|
|
|
if (method.IsStatic)
|
|
flags |= ObjectValueFlags.Global;
|
|
|
|
if (method.IsPublic)
|
|
flags |= ObjectValueFlags.Public;
|
|
else if (method.IsPrivate)
|
|
flags |= ObjectValueFlags.Private;
|
|
else if (method.IsFamily)
|
|
flags |= ObjectValueFlags.Protected;
|
|
else if (method.IsFamilyAndAssembly)
|
|
flags |= ObjectValueFlags.Internal;
|
|
else if (method.IsFamilyOrAssembly)
|
|
flags |= ObjectValueFlags.InternalProtected;
|
|
|
|
return flags;
|
|
}
|
|
|
|
protected override CompletionData GetMemberCompletionData (EvaluationContext ctx, ValueReference vr)
|
|
{
|
|
var properties = new HashSet<string> ();
|
|
var methods = new HashSet<string> ();
|
|
var fields = new HashSet<string> ();
|
|
var data = new CompletionData ();
|
|
var type = vr.Type as TypeMirror;
|
|
bool isEnumerable = false;
|
|
|
|
while (type != null) {
|
|
if (!isEnumerable && IsIEnumerable (type))
|
|
isEnumerable = true;
|
|
|
|
bool isExternal = Session.IsExternalCode (type);
|
|
|
|
foreach (var field in type.GetFields ()) {
|
|
if (field.IsStatic || field.IsSpecialName || (isExternal && !field.IsPublic) ||
|
|
IsClosureReferenceField (field) || IsCompilerGenerated (field))
|
|
continue;
|
|
|
|
if (fields.Add (field.Name))
|
|
data.Items.Add (new CompletionItem (field.Name, FieldValueReference.GetFlags (field)));
|
|
}
|
|
|
|
foreach (var property in type.GetProperties ()) {
|
|
var getter = property.GetGetMethod (true);
|
|
|
|
if (getter == null || getter.IsStatic || (isExternal && !getter.IsPublic))
|
|
continue;
|
|
|
|
if (properties.Add (property.Name))
|
|
data.Items.Add (new CompletionItem (property.Name, PropertyValueReference.GetFlags (property, getter)));
|
|
}
|
|
|
|
foreach (var method in type.GetMethods ()) {
|
|
if (method.IsStatic || method.IsConstructor || method.IsSpecialName || (isExternal && !method.IsPublic))
|
|
continue;
|
|
|
|
if (methods.Add (method.Name))
|
|
data.Items.Add (new CompletionItem (method.Name, GetFlags (method)));
|
|
}
|
|
|
|
if (type.BaseType == null && type.FullName != "System.Object")
|
|
type = ctx.Adapter.GetType (ctx, "System.Object") as TypeMirror;
|
|
else
|
|
type = type.BaseType;
|
|
}
|
|
|
|
type = (TypeMirror) vr.Type;
|
|
foreach (var iface in type.GetInterfaces ()) {
|
|
if (!isEnumerable && IsIEnumerable (iface))
|
|
isEnumerable = true;
|
|
}
|
|
|
|
if (isEnumerable) {
|
|
// Look for LINQ extension methods...
|
|
var linq = ctx.Adapter.GetType (ctx, "System.Linq.Enumerable") as TypeMirror;
|
|
if (linq != null) {
|
|
foreach (var method in linq.GetMethods ()) {
|
|
if (!method.IsStatic || method.IsConstructor || method.IsSpecialName || !method.IsPublic)
|
|
continue;
|
|
|
|
if (methods.Add (method.Name))
|
|
data.Items.Add (new CompletionItem (method.Name, ObjectValueFlags.Method | ObjectValueFlags.Public));
|
|
}
|
|
}
|
|
}
|
|
|
|
data.ExpressionLength = 0;
|
|
|
|
return data;
|
|
}
|
|
|
|
public override void GetNamespaceContents (EvaluationContext ctx, string namspace, out string[] childNamespaces, out string[] childTypes)
|
|
{
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
var types = new HashSet<string> ();
|
|
var namespaces = new HashSet<string> ();
|
|
var namspacePrefix = namspace.Length > 0 ? namspace + "." : "";
|
|
|
|
foreach (var type in soft.Session.GetAllTypes ()) {
|
|
if (type.Namespace == namspace || type.Namespace.StartsWith (namspacePrefix, StringComparison.InvariantCulture)) {
|
|
namespaces.Add (type.Namespace);
|
|
types.Add (type.FullName);
|
|
}
|
|
}
|
|
|
|
childNamespaces = new string [namespaces.Count];
|
|
namespaces.CopyTo (childNamespaces);
|
|
|
|
childTypes = new string [types.Count];
|
|
types.CopyTo (childTypes);
|
|
}
|
|
|
|
protected override ObjectValue CreateObjectValueImpl (EvaluationContext ctx, IObjectValueSource source, ObjectPath path, object obj, ObjectValueFlags flags)
|
|
{
|
|
try {
|
|
return base.CreateObjectValueImpl (ctx, source, path, obj, flags);
|
|
}
|
|
catch (NotSupportedException e) {
|
|
throw new EvaluatorException ("Evaluation failed: {0}", e.Message);
|
|
}
|
|
}
|
|
|
|
protected override IEnumerable<ValueReference> OnGetParameters (EvaluationContext ctx)
|
|
{
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
LocalVariable[] locals;
|
|
|
|
try {
|
|
locals = soft.Frame.Method.GetLocals ().Where (x => x.IsArg && !IsClosureReferenceLocal (x)).ToArray ();
|
|
} catch (InvalidStackFrameException) {
|
|
yield break;
|
|
} catch (AbsentInformationException) {
|
|
yield break;
|
|
}
|
|
|
|
if (locals.Length == 0)
|
|
yield break;
|
|
|
|
var batch = new LocalVariableBatch (locals);
|
|
|
|
for (int i = 0; i < locals.Length; i++) {
|
|
var name = !string.IsNullOrEmpty (locals[i].Name) ? locals[i].Name : "arg" + locals[i].Index;
|
|
yield return new VariableValueReference (ctx, name, locals[i], batch);
|
|
}
|
|
}
|
|
|
|
protected override ValueReference OnGetThisReference (EvaluationContext ctx)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
if (InGeneratedClosureOrIteratorType (cx))
|
|
return GetHoistedThisReference (cx);
|
|
|
|
return GetThisReference (cx);
|
|
}
|
|
|
|
static ValueReference GetThisReference (SoftEvaluationContext ctx)
|
|
{
|
|
return ctx.Frame.Method.IsStatic ? null : new ThisValueReference (ctx, ctx.Frame);
|
|
}
|
|
|
|
public override ValueReference GetCurrentException (EvaluationContext ctx)
|
|
{
|
|
try {
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
var exc = cx.Session.GetExceptionObject (cx.Thread);
|
|
|
|
return exc != null ? LiteralValueReference.CreateTargetObjectLiteral (ctx, ctx.Options.CurrentExceptionTag, exc) : null;
|
|
} catch (AbsentInformationException) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public override bool IsGenericType (EvaluationContext ctx, object type)
|
|
{
|
|
return type != null && ((TypeMirror) type).IsGenericType;
|
|
}
|
|
|
|
public override IEnumerable<object> GetGenericTypeArguments (EvaluationContext ctx, object type)
|
|
{
|
|
return ((TypeMirror)type).GetGenericArguments ();
|
|
}
|
|
|
|
public override object[] GetTypeArgs (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = (TypeMirror) type;
|
|
|
|
if (tm.VirtualMachine.Version.AtLeast (2, 15))
|
|
return tm.GetGenericArguments ();
|
|
|
|
// fall back to parsing them from the from the FullName
|
|
var names = new List<string> ();
|
|
var typeName = tm.FullName;
|
|
int i = typeName.IndexOf ('`');
|
|
|
|
if (i != -1) {
|
|
i = typeName.IndexOf ('[', i);
|
|
if (i == -1)
|
|
return new object [0];
|
|
int si = ++i;
|
|
int nt = 0;
|
|
for (; i < typeName.Length && (nt > 0 || typeName[i] != ']'); i++) {
|
|
if (typeName[i] == '[')
|
|
nt++;
|
|
else if (typeName[i] == ']')
|
|
nt--;
|
|
else if (typeName[i] == ',' && nt == 0) {
|
|
names.Add (typeName.Substring (si, i - si));
|
|
si = i + 1;
|
|
}
|
|
}
|
|
names.Add (typeName.Substring (si, i - si));
|
|
var types = new object [names.Count];
|
|
for (int n=0; n<names.Count; n++) {
|
|
string tn = names [n];
|
|
if (tn.StartsWith ("[", StringComparison.Ordinal))
|
|
tn = tn.Substring (1, tn.Length - 2);
|
|
types [n] = GetType (ctx, tn);
|
|
if (types [n] == null)
|
|
return new object [0];
|
|
}
|
|
return types;
|
|
}
|
|
|
|
return new object [0];
|
|
}
|
|
|
|
public override object GetType (EvaluationContext ctx, string name, object[] typeArgs)
|
|
{
|
|
var cx = (SoftEvaluationContext) ctx;
|
|
|
|
int i = name.IndexOf (',');
|
|
if (i != -1) {
|
|
// Find first comma outside brackets
|
|
int nest = 0;
|
|
for (int n = 0; n < name.Length; n++) {
|
|
char c = name [n];
|
|
if (c == '[')
|
|
nest++;
|
|
else if (c == ']')
|
|
nest--;
|
|
else if (c == ',' && nest == 0) {
|
|
name = name.Substring (0, n).Trim ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeArgs != null && typeArgs.Length > 0) {
|
|
string args = "";
|
|
|
|
foreach (var argType in typeArgs) {
|
|
if (args.Length > 0)
|
|
args += ",";
|
|
|
|
string tn;
|
|
var atm = argType as TypeMirror;
|
|
if (atm == null) {
|
|
var att = (Type) argType;
|
|
|
|
tn = att.FullName + ", " + att.Assembly.GetName ();
|
|
} else {
|
|
tn = atm.FullName + ", " + atm.Assembly.GetName ();
|
|
}
|
|
|
|
if (tn.IndexOf (',') != -1)
|
|
tn = "[" + tn + "]";
|
|
|
|
args += tn;
|
|
}
|
|
|
|
name += "[" +args + "]";
|
|
}
|
|
|
|
var tm = cx.Session.GetType (name);
|
|
if (tm != null)
|
|
return tm;
|
|
|
|
foreach (var asm in cx.Domain.GetAssemblies ()) {
|
|
tm = asm.GetType (name, false, false);
|
|
if (tm != null)
|
|
return tm;
|
|
}
|
|
|
|
var method = cx.Frame.Method;
|
|
if (method.IsGenericMethod && !method.IsGenericMethodDefinition) {
|
|
var definition = method.GetGenericMethodDefinition ();
|
|
var names = definition.GetGenericArguments ();
|
|
var types = method.GetGenericArguments ();
|
|
|
|
for (i = 0; i < names.Length; i++) {
|
|
if (names[i].FullName == name)
|
|
return types[i];
|
|
}
|
|
}
|
|
|
|
var declaringType = method.DeclaringType;
|
|
if (declaringType.IsGenericType && !declaringType.IsGenericTypeDefinition) {
|
|
var definition = declaringType.GetGenericTypeDefinition ();
|
|
var types = declaringType.GetGenericArguments ();
|
|
var names = definition.GetGenericArguments ();
|
|
|
|
for (i = 0; i < names.Length; i++) {
|
|
if (names[i].FullName == name)
|
|
return types[i];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected override object GetBaseTypeWithAttribute (EvaluationContext ctx, object type, object attrType)
|
|
{
|
|
var atm = attrType as TypeMirror;
|
|
var tm = type as TypeMirror;
|
|
|
|
while (tm != null && atm != null) {
|
|
if (tm.GetCustomAttributes (atm, false).Any ())
|
|
return tm;
|
|
|
|
tm = tm.BaseType;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override object GetParentType (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
if (tm != null) {
|
|
int plus = tm.FullName.LastIndexOf ('+');
|
|
|
|
return plus != -1 ? GetType (ctx, tm.FullName.Substring (0, plus)) : null;
|
|
}
|
|
|
|
return ((Type) type).DeclaringType;
|
|
}
|
|
|
|
public override IEnumerable<object> GetNestedTypes (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = (TypeMirror) type;
|
|
|
|
foreach (var nested in tm.GetNestedTypes ())
|
|
if (!IsGeneratedType (nested))
|
|
yield return nested;
|
|
}
|
|
|
|
public override IEnumerable<object> GetImplementedInterfaces (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = (TypeMirror) type;
|
|
|
|
foreach (var nested in tm.GetInterfaces ())
|
|
yield return nested;
|
|
}
|
|
|
|
public override string GetTypeName (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror ?? (type as ArgumentType)?.Type;
|
|
|
|
if (tm != null) {
|
|
if (IsGeneratedType (tm)) {
|
|
// Return the name of the container-type.
|
|
return tm.FullName.Substring (0, tm.FullName.LastIndexOf ('+'));
|
|
}
|
|
|
|
return tm.FullName;
|
|
}
|
|
|
|
return ((Type) type).FullName;
|
|
}
|
|
|
|
public override object GetValueType (EvaluationContext ctx, object val)
|
|
{
|
|
if (val == null)
|
|
return typeof (object);
|
|
if (val is ArrayMirror)
|
|
return ((ArrayMirror) val).Type;
|
|
if (val is ObjectMirror)
|
|
return ((ObjectMirror) val).Type;
|
|
if (val is EnumMirror)
|
|
return ((EnumMirror) val).Type;
|
|
if (val is StructMirror)
|
|
return ((StructMirror) val).Type;
|
|
if (val is PointerValue)
|
|
return ((PointerValue) val).Type;
|
|
if (val is DelayedLambdaValue)
|
|
return ((DelayedLambdaValue)val).DelayedType;
|
|
if (val is PrimitiveValue) {
|
|
var pv = (PrimitiveValue) val;
|
|
if (pv.Value == null)
|
|
return typeof (object);
|
|
|
|
return pv.Value.GetType ();
|
|
}
|
|
|
|
throw new NotSupportedException (val.GetType ().FullName);
|
|
}
|
|
|
|
public override object GetBaseType (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
return tm != null ? tm.BaseType : null;
|
|
}
|
|
|
|
public override bool HasMethodWithParamLength (EvaluationContext ctx, object targetType, string methodName, BindingFlags flags, int paramLength)
|
|
{
|
|
var soft = (SoftEvaluationContext)ctx;
|
|
var currentType = ToTypeMirror (ctx, targetType);
|
|
var allowInstance = (flags & BindingFlags.Instance) != 0;
|
|
var allowStatic = (flags & BindingFlags.Static) != 0;
|
|
var onlyPublic = (flags & BindingFlags.Public) != 0;
|
|
|
|
while (currentType != null) {
|
|
var methods = GetMethodsByName (soft, currentType, methodName);
|
|
|
|
foreach (var method in methods) {
|
|
var parms = method.GetParameters ();
|
|
if (parms.Length == paramLength && ((method.IsStatic && allowStatic) || (!method.IsStatic && allowInstance))) {
|
|
if (onlyPublic && !method.IsPublic)
|
|
continue;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (methodName == ".ctor")
|
|
break;
|
|
|
|
if (currentType.BaseType == null && currentType.FullName != "System.Object")
|
|
currentType = soft.Adapter.GetType (soft, "System.Object") as TypeMirror;
|
|
else
|
|
currentType = currentType.BaseType;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object [] genericTypeArgs, object [] argTypes, BindingFlags flags)
|
|
{
|
|
return HasMethod (ctx, targetType, methodName, genericTypeArgs, argTypes, flags, out _);
|
|
}
|
|
|
|
public override bool HasMethod (EvaluationContext ctx, object targetType, string methodName, object[] genericTypeArgs, object[] argTypes, BindingFlags flags, out Tuple<int, object>[] resolvedLambdaTypes)
|
|
{
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
var tm = ToTypeMirror (ctx, targetType);
|
|
ArgumentType[] typeArgs = null;
|
|
ArgumentType [] types = null;
|
|
var hasDelayed = false;
|
|
resolvedLambdaTypes = null;
|
|
|
|
if (genericTypeArgs != null) {
|
|
typeArgs = new ArgumentType [genericTypeArgs.Length];
|
|
for (int n = 0; n < genericTypeArgs.Length; n++)
|
|
typeArgs[n] = ToArgumentType (ctx, genericTypeArgs[n]);
|
|
}
|
|
|
|
if (argTypes != null) {
|
|
types = new ArgumentType [argTypes.Length];
|
|
for (int n = 0; n < argTypes.Length; n++) {
|
|
var argType = ToArgumentType (ctx, argTypes[n]);
|
|
types[n] = argType;
|
|
hasDelayed |= argType.IsDelayed;
|
|
}
|
|
}
|
|
|
|
var allowInstance = (flags & BindingFlags.Instance) != 0;
|
|
var allowStatic = (flags & BindingFlags.Static) != 0;
|
|
var throwIfNotFound = false;
|
|
var tryCasting = true;
|
|
MethodMirror method;
|
|
|
|
if (hasDelayed) {
|
|
var candidates = OverloadResolveMulti (soft, tm, methodName, typeArgs, null, types, allowInstance, allowStatic, throwIfNotFound, tryCasting);
|
|
method = null;
|
|
} else {
|
|
method = OverloadResolve (soft, tm, methodName, typeArgs, types, allowInstance, allowStatic, throwIfNotFound, tryCasting);
|
|
}
|
|
|
|
return method != null;
|
|
}
|
|
|
|
// Returns the best method from `candidates' using lambda types in
|
|
// `argTypes'. Non lambda types in argtypes will be ignored. We make sure
|
|
// lamdas have its exected type by compiling them. If a matched method is
|
|
// found, return an array of tuples of (argument index, resolved argument
|
|
// type for lambda) through 'resolved'.
|
|
|
|
public override bool IsExternalType (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
|
|
return tm == null || ((SoftEvaluationContext)ctx).Session.IsExternalCode (tm);
|
|
}
|
|
|
|
public override bool IsString (EvaluationContext ctx, object val)
|
|
{
|
|
return val is StringMirror;
|
|
}
|
|
|
|
public override bool IsArray (EvaluationContext ctx, object val)
|
|
{
|
|
return val is ArrayMirror;
|
|
}
|
|
|
|
public override bool IsValueType (object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
return tm != null && tm.IsValueType;
|
|
}
|
|
|
|
public override bool IsPrimitiveType (object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
if (tm == null)
|
|
return false;
|
|
|
|
if (tm.IsPrimitive)
|
|
return true;
|
|
|
|
if (tm.IsValueType && tm.Namespace == "System" && (tm.Name == "nfloat" || tm.Name == "nint"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public override bool IsClass (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
return tm != null && (tm.IsClass || tm.IsValueType) && !tm.IsPrimitive;
|
|
}
|
|
|
|
public override bool IsNull (EvaluationContext ctx, object val)
|
|
{
|
|
return val == null || ((val is PrimitiveValue) && ((PrimitiveValue)val).Value == null) || ((val is PointerValue) && ((PointerValue)val).Address == 0);
|
|
}
|
|
|
|
public override bool IsPrimitive (EvaluationContext ctx, object val)
|
|
{
|
|
if (val is PrimitiveValue || val is StringMirror || val is PointerValue)
|
|
return true;
|
|
var sm = val as StructMirror;
|
|
if (sm == null)
|
|
return false;
|
|
if (sm.Type.IsPrimitive)
|
|
return true;
|
|
if (sm.Type.Namespace == "System" && (sm.Type.Name == "nfloat" || sm.Type.Name == "nint"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public override bool IsPointer (EvaluationContext ctx, object val)
|
|
{
|
|
return val is PointerValue;
|
|
}
|
|
|
|
public override bool IsEnum (EvaluationContext ctx, object val)
|
|
{
|
|
return val is EnumMirror;
|
|
}
|
|
|
|
public override bool IsDelayedType (EvaluationContext ctx, object type)
|
|
{
|
|
return type is DelayedLambdaType;
|
|
}
|
|
|
|
public override bool IsPublic (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = ToTypeMirror (ctx, type);
|
|
|
|
return tm != null && tm.IsPublic;
|
|
}
|
|
|
|
protected override TypeDisplayData OnGetTypeDisplayData (EvaluationContext ctx, object type)
|
|
{
|
|
Dictionary<string, DebuggerBrowsableState> memberData = null;
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
bool isCompilerGenerated = false;
|
|
string displayValue = null;
|
|
string displayName = null;
|
|
string displayType = null;
|
|
string proxyType = null;
|
|
|
|
try {
|
|
var tm = (TypeMirror) type;
|
|
|
|
foreach (var attr in tm.GetCustomAttributes (true)) {
|
|
var attrName = attr.Constructor.DeclaringType.FullName;
|
|
switch (attrName) {
|
|
case "System.Diagnostics.DebuggerDisplayAttribute":
|
|
var display = BuildAttribute<DebuggerDisplayAttribute> (attr);
|
|
displayValue = display.Value;
|
|
displayName = display.Name;
|
|
displayType = display.Type;
|
|
break;
|
|
case "System.Diagnostics.DebuggerTypeProxyAttribute":
|
|
var proxy = BuildAttribute<DebuggerTypeProxyAttribute> (attr);
|
|
proxyType = proxy.ProxyTypeName;
|
|
if (!string.IsNullOrEmpty (proxyType))
|
|
ForceLoadType (soft, proxyType);
|
|
break;
|
|
case "System.Runtime.CompilerServices.CompilerGeneratedAttribute":
|
|
isCompilerGenerated = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var field in tm.GetFields ()) {
|
|
var attrs = field.GetCustomAttributes (true);
|
|
var browsable = GetAttribute <DebuggerBrowsableAttribute> (attrs);
|
|
|
|
if (browsable == null) {
|
|
var generated = GetAttribute<CompilerGeneratedAttribute> (attrs);
|
|
if (generated != null)
|
|
browsable = new DebuggerBrowsableAttribute (DebuggerBrowsableState.Never);
|
|
}
|
|
|
|
if (browsable != null) {
|
|
if (memberData == null)
|
|
memberData = new Dictionary<string, DebuggerBrowsableState> ();
|
|
memberData [field.Name] = browsable.State;
|
|
}
|
|
}
|
|
|
|
foreach (var property in tm.GetProperties ()) {
|
|
var browsable = GetAttribute <DebuggerBrowsableAttribute> (property.GetCustomAttributes (true));
|
|
if (browsable != null) {
|
|
if (memberData == null)
|
|
memberData = new Dictionary<string, DebuggerBrowsableState> ();
|
|
memberData [property.Name] = browsable.State;
|
|
}
|
|
}
|
|
} catch (Exception ex) {
|
|
soft.Session.WriteDebuggerOutput (true, ex.ToString ());
|
|
}
|
|
|
|
return new TypeDisplayData (proxyType, displayValue, displayType, displayName, isCompilerGenerated, memberData);
|
|
}
|
|
|
|
static T GetAttribute<T> (CustomAttributeDataMirror[] attrs)
|
|
{
|
|
foreach (var attr in attrs) {
|
|
if (attr.Constructor.DeclaringType.FullName == typeof (T).FullName)
|
|
return BuildAttribute<T> (attr);
|
|
}
|
|
|
|
return default (T);
|
|
}
|
|
|
|
public override bool IsTypeLoaded (EvaluationContext ctx, string typeName)
|
|
{
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
|
|
return soft.Session.GetType (typeName) != null;
|
|
}
|
|
|
|
public override bool IsTypeLoaded (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = (TypeMirror) type;
|
|
|
|
if (tm.VirtualMachine.Version.AtLeast (2, 23))
|
|
return tm.IsInitialized;
|
|
|
|
return IsTypeLoaded (ctx, tm.FullName);
|
|
}
|
|
|
|
public override bool ForceLoadType (EvaluationContext ctx, object type)
|
|
{
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
var tm = (TypeMirror) type;
|
|
|
|
if (!tm.VirtualMachine.Version.AtLeast (2, 23))
|
|
return IsTypeLoaded (ctx, tm.FullName);
|
|
|
|
if (tm.IsInitialized)
|
|
return true;
|
|
|
|
if (!tm.Attributes.HasFlag (TypeAttributes.BeforeFieldInit))
|
|
return false;
|
|
|
|
var cctor = OverloadResolve (soft, tm, ".cctor", null, new ArgumentType[0], false, true, false);
|
|
if (cctor == null)
|
|
return true;
|
|
|
|
try {
|
|
lock (cctor.VirtualMachine) {
|
|
tm.InvokeMethod(soft.Thread, cctor, new Value [0], InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded);
|
|
}
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
soft.Session.StackVersion++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static T BuildAttribute<T> (CustomAttributeDataMirror attr)
|
|
{
|
|
var args = new List<object> ();
|
|
var type = typeof (T);
|
|
|
|
foreach (var arg in attr.ConstructorArguments) {
|
|
object val = arg.Value;
|
|
|
|
if (val is TypeMirror) {
|
|
// The debugger attributes that take a type as parameter of the constructor have
|
|
// a corresponding constructor overload that takes a type name. We'll use that
|
|
// constructor because we can't load target types in the debugger process.
|
|
// So what we do here is convert the Type to a String.
|
|
var tm = (TypeMirror) val;
|
|
// Workaround for older Mono runtime that doesn't support generics, simply use ICollection viewer instead generic version
|
|
if (!tm.VirtualMachine.Version.AtLeast (2, 15) && tm.FullName.StartsWith ("System.Collections.Generic.CollectionDebuggerView`", StringComparison.Ordinal))
|
|
val = "System.Collections.CollectionDebuggerView, " + tm.Assembly.ManifestModule.Name;
|
|
else
|
|
val = tm.FullName + ", " + tm.Assembly.ManifestModule.Name;
|
|
} else if (val is EnumMirror) {
|
|
var em = (EnumMirror) val;
|
|
|
|
if (type == typeof (DebuggerBrowsableAttribute))
|
|
val = (DebuggerBrowsableState) em.Value;
|
|
else
|
|
val = em.Value;
|
|
}
|
|
|
|
args.Add (val);
|
|
}
|
|
|
|
var attribute = Activator.CreateInstance (type, args.ToArray ());
|
|
|
|
foreach (var arg in attr.NamedArguments) {
|
|
object val = arg.TypedValue.Value;
|
|
string postFix = "";
|
|
|
|
if (arg.TypedValue.ArgumentType == typeof (Type))
|
|
postFix = "TypeName";
|
|
if (arg.Field != null)
|
|
type.GetField (arg.Field.Name + postFix).SetValue (attribute, val);
|
|
else if (arg.Property != null)
|
|
type.GetProperty (arg.Property.Name + postFix).SetValue (attribute, val, null);
|
|
}
|
|
|
|
return (T) attribute;
|
|
}
|
|
|
|
public class ArgumentType
|
|
{
|
|
TypeMirror type;
|
|
DelayedLambdaType delayedType;
|
|
public bool RepresentsNull { get; internal set; }
|
|
public bool IsDelayed { get; internal set; }
|
|
public TypeMirror Type {
|
|
get {
|
|
if (IsDelayed)
|
|
throw new NotSupportedException();
|
|
return type;
|
|
}
|
|
internal set {
|
|
type = value;
|
|
}
|
|
}
|
|
public DelayedLambdaType DelayedType {
|
|
get {
|
|
if (!IsDelayed)
|
|
throw new NotSupportedException();
|
|
return delayedType;
|
|
}
|
|
internal set {
|
|
delayedType = value;
|
|
}
|
|
}
|
|
|
|
public static implicit operator TypeMirror (ArgumentType d)
|
|
{
|
|
return d.Type;
|
|
}
|
|
|
|
public static implicit operator ArgumentType (TypeMirror d)
|
|
{
|
|
return new ArgumentType () { Type = d };
|
|
}
|
|
}
|
|
|
|
ArgumentType ToArgumentType (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
var isDelayed = ctx.Adapter.IsDelayedType (ctx, type);
|
|
|
|
if (isDelayed) {
|
|
return new ArgumentType {
|
|
IsDelayed = true,
|
|
DelayedType = type as DelayedLambdaType
|
|
};
|
|
}
|
|
|
|
return new ArgumentType {
|
|
Type = tm ?? (TypeMirror)GetType (ctx, ((Type)type).FullName),
|
|
// GetValueType method always returns `typeof (object)` when value is `null`
|
|
// We need to store this information because later in process this information is lost
|
|
// when needed by OverloadResolve when trying to determine correct method overload
|
|
RepresentsNull = (type as Type) == typeof (object)
|
|
};
|
|
}
|
|
|
|
TypeMirror ToTypeMirror (EvaluationContext ctx, object type)
|
|
{
|
|
var tm = type as TypeMirror;
|
|
|
|
return tm ?? (TypeMirror) GetType (ctx, ((Type) type).FullName);
|
|
}
|
|
|
|
public override object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues)
|
|
{
|
|
object[] outArgs;
|
|
return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues, false, out outArgs);
|
|
}
|
|
|
|
public override object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues, out object[] outArgs)
|
|
{
|
|
return RuntimeInvoke (ctx, targetType, target, methodName, genericTypeArgs, argTypes, argValues, true, out outArgs);
|
|
}
|
|
|
|
private object RuntimeInvoke (EvaluationContext ctx, object targetType, object target, string methodName, object[] genericTypeArgs, object[] argTypes, object[] argValues,bool enableOutArgs, out object[] outArgs)
|
|
{
|
|
var type = ToTypeMirror (ctx, targetType);
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
|
|
soft.AssertTargetInvokeAllowed ();
|
|
|
|
var genericTypes = new ArgumentType [genericTypeArgs != null ? genericTypeArgs.Length : 0];
|
|
for (int n = 0; n < genericTypes.Length; n++)
|
|
genericTypes[n] = ToArgumentType (soft, genericTypeArgs[n]);
|
|
|
|
var types = new ArgumentType [argTypes.Length];
|
|
for (int n = 0; n < argTypes.Length; n++)
|
|
types[n] = ToArgumentType (soft, argTypes[n]);
|
|
|
|
var method = OverloadResolve (soft, type, methodName, genericTypes, types, target != null, target == null, true);
|
|
var mparams = method.GetParameters ();
|
|
var values = new Value [argValues.Length];
|
|
|
|
for (int n = 0; n < argValues.Length; n++) {
|
|
var param_type = mparams [n].ParameterType;
|
|
if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && param_type.IsGenericType &&
|
|
!(param_type.IsGenericType && param_type.FullName.StartsWith ("System.Nullable`1", StringComparison.Ordinal))) {
|
|
var throwCastException = true;
|
|
|
|
if (method.VirtualMachine.Version.AtLeast (2, 15)) {
|
|
var args = param_type.GetGenericArguments ();
|
|
|
|
if (args.Length == genericTypes.Length) {
|
|
var real_type = soft.Adapter.GetType (soft, param_type.GetGenericTypeDefinition ().FullName, genericTypes);
|
|
|
|
values [n] = (Value)TryCast (soft, (Value)argValues [n], real_type);
|
|
if (!(values [n] == null && argValues [n] != null && !soft.Adapter.IsNull (soft, argValues [n])))
|
|
throwCastException = false;
|
|
}
|
|
}
|
|
|
|
if (throwCastException) {
|
|
var fromType = !IsGeneratedType (types [n].Type) ? soft.Adapter.GetDisplayTypeName (soft, types [n]) : types [n].Type.FullName;
|
|
var toType = soft.Adapter.GetDisplayTypeName (soft, param_type);
|
|
|
|
throw new EvaluatorException ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", n, fromType, toType);
|
|
}
|
|
} else if (argValues[n] is DelayedLambdaValue) {
|
|
outArgs = null;
|
|
return null;
|
|
/*string error;
|
|
var val = CompileAndLoadLambdaValue (soft, (DelayedLambdaValue) argValues[n], param_type, out error);
|
|
values[n] = (Value) val;
|
|
|
|
if (error != null)
|
|
throw new EvaluatorException ("Invalid arguments for method `{0}': Argument {1}: {2}", methodName, n, error);*/
|
|
} else if (param_type.FullName != types [n].Type.FullName && !param_type.IsAssignableFrom (types [n].Type) && !types[n].RepresentsNull && CanForceCast (ctx, types [n], param_type)) {
|
|
values [n] = (Value)TryCast (ctx, argValues [n], param_type);
|
|
} else {
|
|
values [n] = (Value)argValues [n];
|
|
}
|
|
}
|
|
if (enableOutArgs) {
|
|
Value[] outArgsValue;
|
|
var result = soft.RuntimeInvoke (method, target ?? targetType, values, out outArgsValue);
|
|
outArgs = outArgsValue;
|
|
return result;
|
|
} else {
|
|
outArgs = null;
|
|
return soft.RuntimeInvoke (method, target ?? targetType, values);
|
|
}
|
|
}
|
|
|
|
static ArgumentType[] ResolveGenericTypeArguments (MethodMirror method, ArgumentType[] argTypes)
|
|
{
|
|
var genericArgs = method.GetGenericArguments ();
|
|
var types = new ArgumentType[genericArgs.Length];
|
|
var parameters = method.GetParameters ();
|
|
var names = new List<string> ();
|
|
|
|
// map the generic argument type names
|
|
foreach (var arg in genericArgs)
|
|
names.Add (arg.Name);
|
|
|
|
// map parameter types to generic argument types...
|
|
for (int i = 0; i < argTypes.Length && i < parameters.Length; i++) {
|
|
if (argTypes[i].IsDelayed)
|
|
continue;
|
|
|
|
var paramType = parameters[i].ParameterType;
|
|
var isArray = paramType.IsArray;
|
|
if (isArray)
|
|
paramType = paramType.GetElementType ();
|
|
|
|
int index = names.IndexOf (paramType.Name);
|
|
|
|
if (index != -1 && types[index] == null) {
|
|
var argType = argTypes[i].Type;
|
|
if (isArray && argType.IsArray)
|
|
argType = argType.GetElementType ();
|
|
|
|
types[index] = argType;
|
|
}
|
|
}
|
|
|
|
// make sure we have all the generic argument types...
|
|
for (int i = 0; i < types.Length; i++) {
|
|
if (types[i] == null)
|
|
return null;
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
static MethodMirror PickFirstCandidate (MethodMirror [] methods)
|
|
{
|
|
if (methods == null || methods.Length == 0)
|
|
return null;
|
|
|
|
if (methods.Length == 1)
|
|
return methods [0];
|
|
|
|
// If there is an ambiguous match, just pick the first match. If the user was expecting
|
|
// something else, he can provide more specific arguments
|
|
|
|
//if (!throwIfNotFound)
|
|
// return null;
|
|
//if (methodName != null)
|
|
// throw new EvaluatorException ("Ambiguous method `{0}'; need to use full name", methodName);
|
|
//else
|
|
// throw new EvaluatorException ("Ambiguous arguments for indexer.", methodName);
|
|
|
|
return methods [0];
|
|
}
|
|
|
|
public static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, ArgumentType[] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting = true)
|
|
{
|
|
return OverloadResolve (ctx, type, methodName, genericTypeArgs, null, argTypes, allowInstance, allowStatic, throwIfNotFound, tryCasting);
|
|
}
|
|
|
|
public static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType [] genericTypeArgs, TypeMirror returnType, ArgumentType [] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting)
|
|
{
|
|
var results = OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, allowInstance, allowStatic, throwIfNotFound, tryCasting: tryCasting);
|
|
return PickFirstCandidate (results);
|
|
}
|
|
|
|
public static MethodMirror[] GetMethodsByName (SoftEvaluationContext ctx, TypeMirror type, string methodName)
|
|
{
|
|
const BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
|
MethodMirror[] methods = null;
|
|
var cache = ctx.Session.OverloadResolveCache;
|
|
|
|
if (ctx.CaseSensitive) {
|
|
lock (cache) {
|
|
cache.TryGetValue (Tuple.Create (type, methodName), out methods);
|
|
}
|
|
}
|
|
|
|
if (methods == null) {
|
|
if (type.VirtualMachine.Version.AtLeast (2, 7)) {
|
|
methods = type.GetMethodsByNameFlags (methodName, flag, !ctx.CaseSensitive);
|
|
} else {
|
|
methods = type.GetMethods ();
|
|
}
|
|
|
|
if (ctx.CaseSensitive) {
|
|
lock (cache) {
|
|
cache [Tuple.Create (type, methodName)] = methods;
|
|
}
|
|
}
|
|
}
|
|
return methods;
|
|
}
|
|
|
|
|
|
public static MethodMirror[] OverloadResolveMulti (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType [] genericTypeArgs, TypeMirror returnType, ArgumentType [] argTypes, bool allowInstance, bool allowStatic, bool throwIfNotFound, bool tryCasting)
|
|
{
|
|
var candidates = new List<MethodMirror> ();
|
|
var currentType = type;
|
|
|
|
while (currentType != null) {
|
|
MethodMirror [] methods = GetMethodsByName(ctx, currentType, methodName);
|
|
|
|
foreach (var method in methods) {
|
|
if (method.Name == methodName || (!ctx.CaseSensitive && method.Name.Equals (methodName, StringComparison.CurrentCultureIgnoreCase))) {
|
|
MethodMirror actualMethod;
|
|
|
|
if (argTypes != null && method.VirtualMachine.Version.AtLeast (2, 24) && method.IsGenericMethod) {
|
|
var generic = method.GetGenericMethodDefinition ();
|
|
ArgumentType [] typeArgs;
|
|
|
|
//Console.WriteLine ("Attempting to resolve generic type args for: {0}", GetPrettyMethodName (ctx, generic));
|
|
|
|
if ((genericTypeArgs == null || genericTypeArgs.Length == 0))
|
|
typeArgs = ResolveGenericTypeArguments (generic, argTypes);
|
|
else
|
|
typeArgs = genericTypeArgs;
|
|
|
|
if (typeArgs == null || typeArgs.Length != generic.GetGenericArguments ().Length) {
|
|
//Console.WriteLine ("Failed to resolve generic method argument types...");
|
|
continue;
|
|
}
|
|
|
|
actualMethod = generic.MakeGenericMethod (typeArgs.Select (t => t.Type).ToArray ());
|
|
//Console.WriteLine ("Resolve generic method to: {0}", GetPrettyMethodName (ctx, actualMethod));
|
|
} else {
|
|
actualMethod = method;
|
|
}
|
|
|
|
var parms = actualMethod.GetParameters ();
|
|
if (argTypes == null || parms.Length == argTypes.Length && ((actualMethod.IsStatic && allowStatic) || (!actualMethod.IsStatic && allowInstance)))
|
|
candidates.Add (actualMethod);
|
|
}
|
|
}
|
|
|
|
if (argTypes == null && candidates.Count > 0)
|
|
break; // when argTypes is null, we are just looking for *any* match (not a specific match)
|
|
|
|
if (methodName == ".ctor")
|
|
break; // Can't create objects using constructor from base classes
|
|
|
|
// Make sure that we always pull in at least System.Object methods (this is mostly needed for cases where 'type' was an interface)
|
|
if (currentType.BaseType == null && currentType.FullName != "System.Object")
|
|
currentType = ctx.Adapter.GetType (ctx, "System.Object") as TypeMirror;
|
|
else
|
|
currentType = currentType.BaseType;
|
|
}
|
|
|
|
return OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, candidates, throwIfNotFound, tryCasting);
|
|
}
|
|
|
|
static bool IsApplicable (SoftEvaluationContext ctx, MethodMirror method, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType [] types, out string error, out int matchCount, bool tryCasting = true)
|
|
{
|
|
var mparams = method.GetParameters ();
|
|
matchCount = 0;
|
|
|
|
for (int i = 0; i < types.Length; i++) {
|
|
var param_type = mparams[i].ParameterType;
|
|
|
|
if (types [i].RepresentsNull && !SoftEvaluationContext.IsValueTypeOrPrimitive (param_type))
|
|
continue;
|
|
|
|
if (types[i].IsDelayed) {
|
|
var lambdaType = types[i].DelayedType;
|
|
if (lambdaType.IsAcceptableType (param_type)) {
|
|
matchCount++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (param_type.FullName == types[i].Type.FullName) {
|
|
matchCount++;
|
|
continue;
|
|
}
|
|
|
|
if (param_type.IsAssignableFrom (types[i].Type))
|
|
continue;
|
|
|
|
if (param_type.IsGenericType) {
|
|
if (genericTypeArgs != null && method.VirtualMachine.Version.AtLeast (2, 12)) {
|
|
// FIXME: how can we make this more definitive?
|
|
if (param_type.GetGenericArguments ().Length == genericTypeArgs.Length)
|
|
continue;
|
|
} else {
|
|
// no way to check... assume it'll work?
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (tryCasting && CanCast (ctx, types [i], param_type))
|
|
continue;
|
|
|
|
if (CanDoPrimaryCast(types[i].Type, param_type))
|
|
continue;
|
|
|
|
var fromType = !IsGeneratedType (types[i].Type) ? ctx.Adapter.GetDisplayTypeName (ctx, types[i]) : types[i].Type.FullName;
|
|
var toType = ctx.Adapter.GetDisplayTypeName (ctx, param_type);
|
|
|
|
error = string.Format ("Argument {0}: Cannot implicitly convert `{1}' to `{2}'", i, fromType, toType);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (returnType != null && returnType != method.ReturnType) {
|
|
var actual = ctx.Adapter.GetDisplayTypeName (ctx, method.ReturnType);
|
|
var expected = ctx.Adapter.GetDisplayTypeName (ctx, returnType);
|
|
|
|
error = string.Format ("Return types do not match: `{0}' vs `{1}'", expected, actual);
|
|
|
|
return false;
|
|
}
|
|
|
|
error = null;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool CanDoPrimaryCast (TypeMirror fromType, TypeMirror toType)
|
|
{
|
|
var name = toType.CSharpName;
|
|
switch (fromType.CSharpName) {
|
|
case "sbyte": return name == "short" || name == "int" || name == "long" || name == "float" || name == "double" || name == "decimal";
|
|
case "byte": return name == "short" || name == "ushort" || name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal";
|
|
case "short": return name == "int" || name == "long" || name == "float" || name == "double" || name == "decimal";
|
|
case "ushort": return name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal";
|
|
case "int": return name == "long" || name == "float" || name == "double" || name == "decimal";
|
|
case "uint": return name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal";
|
|
case "long": return name == "float" || name == "double" || name == "decimal";
|
|
case "char": return name == "ushort" || name == "int" || name == "uint" || name == "long" || name == "ulong" || name == "float" || name == "double" || name == "decimal";
|
|
case "float": return name == "double";
|
|
case "ulong": return name == "float" || name == "double" || name == "decimal";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static MethodMirror OverloadResolve (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType[] argTypes, List<MethodMirror> candidates, bool throwIfNotFound, bool tryCasting = true)
|
|
{
|
|
var results = OverloadResolveMulti (ctx, type, methodName, genericTypeArgs, returnType, argTypes, candidates, throwIfNotFound, tryCasting: tryCasting);
|
|
return PickFirstCandidate (results);
|
|
}
|
|
|
|
static MethodMirror[] OverloadResolveMulti (SoftEvaluationContext ctx, TypeMirror type, string methodName, ArgumentType[] genericTypeArgs, TypeMirror returnType, ArgumentType[] argTypes, List<MethodMirror> candidates, bool throwIfNotFound, bool tryCasting = true)
|
|
{
|
|
if (candidates.Count == 0) {
|
|
if (throwIfNotFound) {
|
|
var typeName = ctx.Adapter.GetDisplayTypeName (ctx, type);
|
|
|
|
if (methodName == null)
|
|
throw new EvaluatorException ("Indexer not found in type `{0}'.", typeName);
|
|
|
|
if (genericTypeArgs != null && genericTypeArgs.Length > 0) {
|
|
var types = string.Join (", ", genericTypeArgs.Select (t => ctx.Adapter.GetDisplayTypeName (ctx, t)));
|
|
|
|
throw new EvaluatorException ("Method `{0}<{1}>' not found in type `{2}'.", methodName, types, typeName);
|
|
}
|
|
|
|
throw new EvaluatorException ("Method `{0}' not found in type `{1}'.", methodName, typeName);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (argTypes == null) {
|
|
// This is just a probe to see if the type contains *any* methods of the given name
|
|
return new MethodMirror[] { candidates [0] };
|
|
}
|
|
|
|
if (candidates.Count == 1) {
|
|
if (IsApplicable (ctx, candidates [0], genericTypeArgs, returnType, argTypes, out var error, out var matchCount, tryCasting))
|
|
return new MethodMirror [] { candidates [0] };
|
|
|
|
if (throwIfNotFound)
|
|
throw new EvaluatorException ("Invalid arguments for method `{0}': {1}", methodName, error);
|
|
|
|
return null;
|
|
}
|
|
|
|
// Ok, now we need to find exact matches.
|
|
var bestCandidates = new List<MethodMirror>();
|
|
int bestCount = -1;
|
|
|
|
foreach (var method in candidates) {
|
|
string error;
|
|
int matchCount;
|
|
|
|
if (!IsApplicable (ctx, method, genericTypeArgs, returnType, argTypes, out error, out matchCount, tryCasting))
|
|
continue;
|
|
|
|
if (matchCount == bestCount) {
|
|
bestCandidates.Add (method);
|
|
} else if (matchCount > bestCount) {
|
|
bestCandidates = new List<MethodMirror> { method };
|
|
bestCount = matchCount;
|
|
}
|
|
}
|
|
|
|
if (bestCandidates.Count == 0) {
|
|
if (!throwIfNotFound)
|
|
return null;
|
|
|
|
if (methodName != null)
|
|
throw new EvaluatorException ("Invalid arguments for method `{0}'.", methodName);
|
|
|
|
throw new EvaluatorException ("Invalid arguments for indexer.");
|
|
}
|
|
return bestCandidates.ToArray ();
|
|
}
|
|
|
|
public override object TargetObjectToObject (EvaluationContext ctx, object obj)
|
|
{
|
|
if (obj is StringMirror) {
|
|
return MirrorStringToString (ctx, (StringMirror)obj);
|
|
}
|
|
|
|
if (obj is PrimitiveValue)
|
|
return ((PrimitiveValue)obj).Value;
|
|
|
|
if (obj is PointerValue)
|
|
return new EvaluationResult ("0x" + ((PointerValue)obj).Address.ToString ("x"));
|
|
|
|
if (obj is StructMirror) {
|
|
var sm = (StructMirror) obj;
|
|
|
|
if (sm.Type.IsPrimitive) {
|
|
// Boxed primitive
|
|
if (sm.Type.FullName == "System.IntPtr")
|
|
return new EvaluationResult ("0x" + ((long)((PointerValue)sm.Fields [0]).Address).ToString ("x"));
|
|
if (sm.Fields.Length > 0 && (sm.Fields[0] is PrimitiveValue))
|
|
return ((PrimitiveValue)sm.Fields[0]).Value;
|
|
} else if (sm.Type.FullName == "System.Decimal") {
|
|
var soft = (SoftEvaluationContext) ctx;
|
|
|
|
var method = OverloadResolve (soft, sm.Type, "GetBits", null, new [] { new ArgumentType { Type = sm.Type, RepresentsNull = false } }, false, true, false);
|
|
|
|
if (method != null) {
|
|
ArrayMirror array;
|
|
|
|
try {
|
|
lock (method.VirtualMachine) {
|
|
array = sm.Type.InvokeMethod(soft.Thread, method, new [] { sm }, InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded) as ArrayMirror;
|
|
}
|
|
} catch {
|
|
array = null;
|
|
} finally {
|
|
soft.Session.StackVersion++;
|
|
}
|
|
|
|
if (array != null) {
|
|
var bits = new int[4];
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
bits[i] = (int) TargetObjectToObject (ctx, array[i]);
|
|
|
|
return new decimal (bits);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return base.TargetObjectToObject (ctx, obj);
|
|
}
|
|
|
|
static string MirrorStringToString (EvaluationContext ctx, StringMirror mirror)
|
|
{
|
|
string str;
|
|
|
|
if (ctx.Options.EllipsizeStrings) {
|
|
if (mirror.VirtualMachine.Version.AtLeast (2, 10)) {
|
|
int length = mirror.Length;
|
|
|
|
if (length > ctx.Options.EllipsizedLength)
|
|
str = new string (mirror.GetChars (0, ctx.Options.EllipsizedLength)) + EvaluationOptions.Ellipsis;
|
|
else
|
|
str = mirror.Value;
|
|
} else {
|
|
str = mirror.Value;
|
|
if (str.Length > ctx.Options.EllipsizedLength)
|
|
str = str.Substring (0, ctx.Options.EllipsizedLength) + EvaluationOptions.Ellipsis;
|
|
}
|
|
} else {
|
|
str = mirror.Value;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
}
|
|
|
|
class MethodCall: AsyncOperation
|
|
{
|
|
readonly InvokeOptions options = InvokeOptions.DisableBreakpoints;
|
|
|
|
readonly ManualResetEvent shutdownEvent = new ManualResetEvent (false);
|
|
readonly MethodMirror function;
|
|
readonly Value[] args;
|
|
readonly object obj;
|
|
IAsyncResult handle;
|
|
Exception exception;
|
|
InvokeResult result;
|
|
|
|
public MethodCall (SoftEvaluationContext ctx, MethodMirror function, object obj, Value[] args, bool enableOutArgs) : base (ctx)
|
|
{
|
|
this.function = function;
|
|
this.obj = obj;
|
|
this.args = args;
|
|
if (enableOutArgs) {
|
|
this.options |= InvokeOptions.ReturnOutArgs;
|
|
}
|
|
if (function.VirtualMachine.Version.AtLeast (2, 40)) {
|
|
this.options |= InvokeOptions.Virtual;
|
|
}
|
|
}
|
|
|
|
public override string Description {
|
|
get {
|
|
return function.DeclaringType.FullName + "." + function.Name;
|
|
}
|
|
}
|
|
|
|
public override void Invoke ()
|
|
{
|
|
var ctx = (SoftEvaluationContext) EvaluationContext;
|
|
|
|
try {
|
|
if (obj is ObjectMirror)
|
|
handle = ((ObjectMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null);
|
|
else if (obj is TypeMirror)
|
|
handle = ((TypeMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null);
|
|
else if (obj is StructMirror)
|
|
handle = ((StructMirror)obj).BeginInvokeMethod (ctx.Thread, function, args, options | InvokeOptions.ReturnOutThis, null, null);
|
|
else if (obj is PrimitiveValue)
|
|
handle = ((PrimitiveValue)obj).BeginInvokeMethod (ctx.Thread, function, args, options, null, null);
|
|
else
|
|
throw new ArgumentException ("Soft debugger method calls cannot be invoked on objects of type " + obj.GetType ().Name);
|
|
} catch (InvocationException ex) {
|
|
ctx.Session.StackVersion++;
|
|
exception = ex;
|
|
} catch (Exception ex) {
|
|
ctx.Session.StackVersion++;
|
|
DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex);
|
|
exception = ex;
|
|
}
|
|
}
|
|
|
|
public override void Abort ()
|
|
{
|
|
var ctx = (SoftEvaluationContext)EvaluationContext;
|
|
if (handle is IInvokeAsyncResult) {
|
|
var info = GetInfo ();
|
|
DebuggerLoggingService.LogMessage ("Aborting invocation of " + info);
|
|
((IInvokeAsyncResult) handle).Abort ();
|
|
ctx.Session.StackVersion++;
|
|
// Don't wait for the abort to finish. The engine will do it.
|
|
} else {
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
|
|
public override void Shutdown ()
|
|
{
|
|
shutdownEvent.Set ();
|
|
}
|
|
|
|
void EndInvoke ()
|
|
{
|
|
var ctx = (SoftEvaluationContext) EvaluationContext;
|
|
|
|
try {
|
|
if (obj is ObjectMirror)
|
|
result = ((ObjectMirror)obj).EndInvokeMethodWithResult (handle);
|
|
else if (obj is TypeMirror)
|
|
result = ((TypeMirror)obj).EndInvokeMethodWithResult (handle);
|
|
else if (obj is StructMirror)
|
|
result = ((StructMirror)obj).EndInvokeMethodWithResult (handle);
|
|
else
|
|
result = ((PrimitiveValue)obj).EndInvokeMethodWithResult (handle);
|
|
} catch (InvocationException ex) {
|
|
if (!Aborting && ex.Exception != null) {
|
|
string ename = ctx.Adapter.GetValueTypeName (ctx, ex.Exception);
|
|
var vref = ctx.Adapter.GetMember (ctx, null, ex.Exception, "Message");
|
|
|
|
exception = vref != null ? new Exception (ename + ": " + (string) vref.ObjectValue) : new Exception (ename);
|
|
return;
|
|
}
|
|
exception = ex;
|
|
} catch (Exception ex) {
|
|
DebuggerLoggingService.LogError ("Error in soft debugger method call thread on " + GetInfo (), ex);
|
|
exception = ex;
|
|
} finally {
|
|
ctx.Session.StackVersion++;
|
|
}
|
|
}
|
|
|
|
string GetInfo ()
|
|
{
|
|
try {
|
|
TypeMirror type = null;
|
|
if (obj is ObjectMirror)
|
|
type = ((ObjectMirror)obj).Type;
|
|
else if (obj is TypeMirror)
|
|
type = (TypeMirror)obj;
|
|
else if (obj is StructMirror)
|
|
type = ((StructMirror)obj).Type;
|
|
return string.Format ("method {0} on object {1}",
|
|
function == null? "[null]" : function.FullName,
|
|
type == null? "[null]" : type.FullName);
|
|
} catch (Exception ex) {
|
|
DebuggerLoggingService.LogError ("Error getting info for SDB MethodCall", ex);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public override bool WaitForCompleted (int timeout)
|
|
{
|
|
if (handle == null)
|
|
return true;
|
|
int res = WaitHandle.WaitAny (new WaitHandle[] { handle.AsyncWaitHandle, shutdownEvent }, timeout);
|
|
if (res == 0) {
|
|
EndInvoke ();
|
|
return true;
|
|
}
|
|
// Return true if shut down.
|
|
return res == 1;
|
|
}
|
|
|
|
public Value ReturnValue {
|
|
get {
|
|
if (exception != null)
|
|
throw new EvaluatorException (exception.Message);
|
|
return result.Result;
|
|
}
|
|
}
|
|
|
|
public Value[] OutArgs {
|
|
get {
|
|
if (exception != null)
|
|
throw new EvaluatorException (exception.Message);
|
|
return result.OutArgs;
|
|
}
|
|
}
|
|
}
|
|
}
|