|
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Debugger
{
using System;
using System.Activities.XamlIntegration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime;
using System.Windows.Markup;
using System.Xaml;
// Class to communicate with Workflow's Expression Evaluation.
// The methods of this class get invoked thru reflection by Visual Studio, so this needs to be public
// to allow partial trust to work.
public class DebugInfo
{
ActivityInstance activityInstance;
LocalInfo[] locals;
LocalInfo[] arguments;
Dictionary<string, LocalInfo> cachedLocalInfos;
internal DebugInfo(ActivityInstance activityInstance)
{
Fx.Assert(activityInstance != null, "activityInstance cannot be null");
this.activityInstance = activityInstance;
}
internal object EvaluateExpression(string expressionString)
{
// This is to shortcircuit a common case where
// internally, vsdebugger calls our EE with literal "0"
int intResult;
if (int.TryParse(expressionString, out intResult))
{
return intResult;
}
// At refresh, Expression Evaluator may re-evaluate locals/arguments.
// To speed up the process, locals/arguments are cached.
LocalInfo cachedLocalInfo = null;
if (this.cachedLocalInfos != null && this.cachedLocalInfos.TryGetValue(expressionString, out cachedLocalInfo))
{
return cachedLocalInfo;
}
Activity activity = activityInstance.Activity;
LocationReferenceEnvironment locationReferenceEnvironment = activity.PublicEnvironment;
// a temporary context for EvaluateExpression
// We're not using the executor so it's ok that it's null
CodeActivityContext context = new CodeActivityContext(activityInstance, null);
object result;
try
{
// First try as R-Value
if (!TryEvaluateExpression(expressionString, null, locationReferenceEnvironment, context, out result))
{
return SR.DebugInfoCannotEvaluateExpression(expressionString);
}
}
catch (Exception ex)
{
// ---- all exceptions, this exception is generated by user typing input in either
// Watch window or Immediate windows. Exception should not affect the current runtime.
// Except for fatal exception.
if (Fx.IsFatal(ex))
{
throw;
}
context.Dispose();
return SR.DebugInfoCannotEvaluateExpression(expressionString);
}
// Now try expression as an L-Value if possible.
try
{
object resultLocation;
if (TryEvaluateExpression(expressionString, result.GetType(), locationReferenceEnvironment, context, out resultLocation))
{
LocalInfo localInfo = new LocalInfo()
{
Name = expressionString,
Location = resultLocation as Location
};
this.cachedLocalInfos[expressionString] = localInfo;
return localInfo;
}
}
catch (Exception ex)
{
// ---- all exceptions, this exception is generated by user typing input in either
// Watch window or Immediate windows. Exception should not affect the current runtime.
// Except for fatal exception.
if (Fx.IsFatal(ex))
{
throw;
}
}
finally
{
context.Dispose();
}
return result;
}
static bool TryEvaluateExpression(
string expressionString,
Type locationValueType, // Non null for Reference type (location)
LocationReferenceEnvironment locationReferenceEnvironment,
CodeActivityContext context,
out object result)
{
expressionString = string.Format(CultureInfo.InvariantCulture, "[{0}]", expressionString);
Type activityType;
if (locationValueType != null)
{
activityType = typeof(Activity<>).MakeGenericType(typeof(Location<>).MakeGenericType(locationValueType));
}
else
{
activityType = typeof(Activity<object>);
}
// General expression.
ActivityWithResultConverter converter = new ActivityWithResultConverter(activityType);
ActivityWithResult expression = converter.ConvertFromString(
new TypeDescriptorContext { LocationReferenceEnvironment = locationReferenceEnvironment },
expressionString) as ActivityWithResult;
if (locationValueType != null)
{
Type locationHelperType = typeof(LocationHelper<>).MakeGenericType(locationValueType);
LocationHelper helper = (LocationHelper)Activator.CreateInstance(locationHelperType);
return helper.TryGetValue(expression, locationReferenceEnvironment, context, out result);
}
else
{
return TryEvaluateExpression(expression, locationReferenceEnvironment, context, out result);
}
}
static bool TryEvaluateExpression(
ActivityWithResult element,
LocationReferenceEnvironment locationReferenceEnvironment,
CodeActivityContext context,
out object result)
{
// value is some expression type and needs to be opened
context.Reinitialize(context.CurrentInstance, context.CurrentExecutor, element, context.CurrentInstance.InternalId);
if (element != null && !element.IsRuntimeReady)
{
WorkflowInspectionServices.CacheMetadata(element, locationReferenceEnvironment);
}
if (element == null || !element.IsFastPath)
{
result = SR.DebugInfoNotSkipArgumentResolution;
return false;
}
result = element.InternalExecuteInResolutionContextUntyped(context);
return true;
}
internal LocalInfo[] GetArguments()
{
if (this.arguments == null || this.arguments.Length == 0)
{
this.arguments =
activityInstance.Activity.RuntimeArguments.Select(argument =>
new LocalInfo
{
Name = argument.Name,
Location = argument.InternalGetLocation(activityInstance.Environment)
}).ToArray();
if (this.arguments.Length > 0)
{
this.CacheLocalInfos(this.arguments);
}
}
return this.arguments;
}
internal LocalInfo[] GetLocals()
{
if (this.locals == null || this.locals.Length == 0)
{
Activity activity = activityInstance.Activity;
List<Variable> allVariables = new List<Variable>();
List<RuntimeArgument> allArguments = new List<RuntimeArgument>();
List<DelegateArgument> allDelegateArguments = new List<DelegateArgument>();
HashSet<string> existingNames = new HashSet<string>();
while (activity != null)
{
allVariables.AddRange(RemoveHiddenVariables(existingNames, activity.RuntimeVariables));
allVariables.AddRange(RemoveHiddenVariables(existingNames, activity.ImplementationVariables));
if (activity.HandlerOf != null)
{
allDelegateArguments.AddRange(RemoveHiddenDelegateArguments(existingNames,
activity.HandlerOf.RuntimeDelegateArguments.Select(delegateArgument =>
delegateArgument.BoundArgument)));
}
allArguments.AddRange(RemoveHiddenArguments(existingNames, activity.RuntimeArguments));
activity = activity.Parent;
}
this.locals =
new LocalInfo[] {
new LocalInfo
{
Name = "this",
Type = "System.Activities.ActivityInstance",
Value = activityInstance
}
}
.Concat(allVariables.Select(variable =>
new LocalInfo
{
Name = variable.Name,
Location = variable.InternalGetLocation(activityInstance.Environment)
})
.Concat(allArguments.Select(argument =>
new LocalInfo
{
Name = argument.Name,
Location = argument.InternalGetLocation(activityInstance.Environment)
}))
.Concat(allDelegateArguments.Select(argument =>
new LocalInfo
{
Name = argument.Name,
Location = argument.InternalGetLocation(activityInstance.Environment)
}))
.OrderBy(info => info.Name))
.ToArray();
if (this.locals.Length > 0)
{
this.CacheLocalInfos(this.locals);
}
}
return this.locals;
}
// Remove ancestor's variables that are hidden because the same name already define in the current scope.
// This will also update existingNames to include ancestor variable names that are retained.
static List<Variable> RemoveHiddenVariables(HashSet<string> existingNames, IEnumerable<Variable> ancestorVariables)
{
List<Variable> cleanUpList = new List<Variable>();
foreach (Variable variable in ancestorVariables)
{
if (variable.Name == null)
{
continue;
}
if (!(variable.Name.StartsWith("_", StringComparison.Ordinal) || // private variables that should be hidden
existingNames.Contains(variable.Name))) // variable name already exists in current scope
{
cleanUpList.Add(variable);
existingNames.Add(variable.Name);
}
}
return cleanUpList;
}
// Remove ancestor's arguments that are hidden because the same name already define in the current scope.
// This will also update existingNames to include ancestor delegate argument names that are retained.
static List<DelegateArgument> RemoveHiddenDelegateArguments(HashSet<string> existingNames, IEnumerable<DelegateArgument> ancestorDelegateArguments)
{
List<DelegateArgument> cleanUpList = new List<DelegateArgument>();
foreach (DelegateArgument delegateArgument in ancestorDelegateArguments)
{
if (delegateArgument != null && delegateArgument.Name != null)
{
if (!existingNames.Contains(delegateArgument.Name)) // variable name already exists in current scope
{
cleanUpList.Add(delegateArgument);
existingNames.Add(delegateArgument.Name);
}
}
}
return cleanUpList;
}
// Remove ancestor's arguments that are hidden because the same name already define in the current scope.
// This will also update existingNames to include ancestor argument names that are retained.
static List<RuntimeArgument> RemoveHiddenArguments(HashSet<string> existingNames, IList<RuntimeArgument> ancestorArguments)
{
List<RuntimeArgument> cleanUpList = new List<RuntimeArgument>(ancestorArguments.Count);
foreach (RuntimeArgument argument in ancestorArguments)
{
if (!existingNames.Contains(argument.Name))
{
cleanUpList.Add(argument);
existingNames.Add(argument.Name);
}
}
return cleanUpList;
}
internal bool SetValueAsString(Location location, string value, string stringRadix)
{
bool succeed = true;
try
{
value = value.Trim();
Type t = location.LocationType;
if (t == typeof(string) && value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal))
{
location.Value = RemoveQuotes(value);
}
else if (t == typeof(bool))
{
location.Value = Convert.ToBoolean(value, CultureInfo.InvariantCulture);
}
else if (t == typeof(sbyte))
{
location.Value = Convert.ToSByte(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(char))
{
char ch;
succeed = ConvertToChar(value, Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture), out ch);
if (succeed)
{
location.Value = ch;
}
}
else if (t == typeof(Int16))
{
location.Value = Convert.ToInt16(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(Int32))
{
location.Value = Convert.ToInt32(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(Int64))
{
location.Value = Convert.ToInt64(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(byte))
{
location.Value = Convert.ToByte(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(UInt16))
{
location.Value = Convert.ToUInt16(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(UInt32))
{
location.Value = Convert.ToUInt32(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(UInt64))
{
location.Value = Convert.ToUInt64(RemoveHexadecimalPrefix(value), Convert.ToInt32(stringRadix, CultureInfo.InvariantCulture));
}
else if (t == typeof(Single))
{
if (!value.Contains(","))
{
location.Value = Convert.ToSingle(value, CultureInfo.InvariantCulture);
}
else
{
succeed = false;
}
}
else if (t == typeof(Double))
{
if (!value.Contains(","))
{
location.Value = Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
else
{
succeed = false;
}
}
else if (t == typeof(Decimal))
{
value = value.TrimEnd();
if (value.EndsWith("M", StringComparison.OrdinalIgnoreCase) || // suffix for Decimal format in C#
value.EndsWith("D", StringComparison.OrdinalIgnoreCase)) // suffix for Decimal format in VB
{
value = value.Substring(0, value.Length - 1); // remove the suffix
}
if (value.Contains(","))
{
succeed = false;
}
else
{
location.Value = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
}
}
else if (t == typeof(DateTime))
{
location.Value = Convert.ToDateTime(value, CultureInfo.CurrentCulture);
}
else if (t.IsEnum)
{
location.Value = Enum.Parse(t, value, true);
}
}
catch (InvalidCastException)
{
succeed = false;
}
catch (OverflowException)
{
succeed = false;
}
catch (FormatException)
{
succeed = false;
}
catch (ArgumentOutOfRangeException)
{
succeed = false;
}
return succeed;
}
// Cache local infos for faster evaluation.
void CacheLocalInfos(LocalInfo[] localInfos)
{
if (this.cachedLocalInfos == null)
{
this.cachedLocalInfos = new Dictionary<string, LocalInfo>(StringComparer.OrdinalIgnoreCase);
}
foreach (LocalInfo localInfo in localInfos)
{
this.cachedLocalInfos[localInfo.Name] = localInfo;
}
}
static string RemoveHexadecimalPrefix(string stringValue)
{
stringValue = stringValue.Trim().ToUpperInvariant();
if (stringValue.StartsWith("0X", StringComparison.Ordinal))
{
stringValue = stringValue.Substring(2);
}
return stringValue;
}
static string RemoveQuotes(string stringValue)
{
if (stringValue.StartsWith("\"", StringComparison.Ordinal))
{
stringValue = stringValue.Substring(1);
}
if (stringValue.EndsWith("\"", StringComparison.Ordinal))
{
stringValue = stringValue.Substring(0, stringValue.Length - 1);
}
return stringValue;
}
static bool ConvertToChar(string stringValue, int radix, out char ch)
{
bool succeed = false;
ch = '\0'; // null character
try
{
if ((stringValue[0] == '\'') || (stringValue[0] == '"')) // VB Char is using double quote
{
if (stringValue[1] == '\\')
{
switch (stringValue[2])
{
case '\'': // single quote
case 'a':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
if (stringValue[3] == stringValue[0]) // matched single/double quote
{
ch = stringValue[2];
}
succeed = true;
break;
}
}
else if (stringValue[2] == stringValue[0]) // matched single/double quote
{
ch = stringValue[1];
succeed = true;
}
}
else
{ // It can be either an digit , e.g. 65, or 65'A'.
// For the second case, we ignore the char.
int endIndex = stringValue.IndexOf('\'');
if (endIndex < 0)
{
endIndex = stringValue.IndexOf('"');
}
if (endIndex > 0)
{
stringValue = stringValue.Substring(0, endIndex);
}
ch = (char)Convert.ToUInt16(RemoveHexadecimalPrefix(stringValue), radix);
succeed = true;
}
}
catch (IndexOutOfRangeException)
{ // Invalid character length
succeed = false;
}
return succeed;
}
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.NestedTypesShouldNotBeVisible, Justification = "Needed for partial trust.")]
internal class LocalInfo
{
string type;
object valueField;
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotDeclareVisibleInstanceFields, Justification = "Needed for partial trust.")]
public string Name;
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotDeclareVisibleInstanceFields, Justification = "Needed for partial trust.")]
public Location Location;
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode, Justification = "Called by Expression Evaluator")]
public object Value
{
get
{
if (this.Location != null)
{
return this.Location.Value;
}
return this.valueField;
}
set
{
this.valueField = value;
}
}
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode, Justification = "Called by Expression Evaluator")]
public string Type
{
get
{
if (this.Location != null)
{
return this.Location.LocationType.Name;
}
return this.type;
}
set
{
this.type = value;
}
}
}
class TypeDescriptorContext : ITypeDescriptorContext, IXamlNamespaceResolver, INameScope, INamespacePrefixLookup
{
public LocationReferenceEnvironment LocationReferenceEnvironment;
public IContainer Container
{
get { throw FxTrace.Exception.AsError(new NotImplementedException()); }
}
public object Instance
{
get { throw FxTrace.Exception.AsError(new NotImplementedException()); }
}
public PropertyDescriptor PropertyDescriptor
{
get { throw FxTrace.Exception.AsError(new NotImplementedException()); }
}
public void OnComponentChanged()
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public bool OnComponentChanging()
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public object GetService(Type serviceType)
{
if (serviceType.IsAssignableFrom(typeof(TypeDescriptorContext)))
{
return this;
}
return null;
}
public string GetNamespace(string prefix)
{
if (string.IsNullOrEmpty(prefix))
{
return String.Empty;
}
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public object FindName(string name)
{
LocationReference result;
if (LocationReferenceEnvironment.TryGetLocationReference(name, out result))
{
return result;
}
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.VariableOrArgumentDoesNotExist(name)));
}
public void RegisterName(string name, object scopedElement)
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public void UnregisterName(string name)
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public string LookupPrefix(string name)
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
public IEnumerable<NamespaceDeclaration> GetNamespacePrefixes()
{
return Enumerable.Empty<NamespaceDeclaration>();
}
}
// to perform the generics dance around Locations we need these helpers
abstract class LocationHelper
{
public abstract bool TryGetValue(Activity expression, LocationReferenceEnvironment locationReferenceEnvironment, CodeActivityContext context, out object result);
}
class LocationHelper<TLocationValue> : LocationHelper
{
public override bool TryGetValue(Activity expression, LocationReferenceEnvironment locationReferenceEnvironment, CodeActivityContext context, out object result)
{
Activity<Location<TLocationValue>> activity = expression as Activity<Location<TLocationValue>>;
result = null;
if (activity != null)
{
context.Reinitialize(context.CurrentInstance, context.CurrentExecutor, expression, context.CurrentInstance.InternalId);
if (activity != null && !activity.IsRuntimeReady)
{
WorkflowInspectionServices.CacheMetadata(activity, locationReferenceEnvironment);
}
if (activity.IsFastPath)
{
result = activity.InternalExecuteInResolutionContext(context);
return true;
}
}
return false;
}
}
}
}
|