|
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Activities.Debugger
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.SymbolStore;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime;
using System.Security;
using System.Security.Permissions;
using System.Activities.Debugger.Symbol;
// Manager for supporting debugging a state machine.
// The general usage is to call:
// - DefineState() for each state
// - Bake() once you've defined all the states you need to enter.
// - EnterState() / LeaveState() for each state.
// You can Define new states and bake them, such as if the script loads a new file.
// Baking is expensive, so it's best to define as many states in each batch.
[DebuggerNonUserCode]
// This class need not serialized.
[Fx.Tag.XamlVisible(false)]
public sealed class StateManager : IDisposable
{
static readonly Guid WorkflowLanguageGuid = new Guid("1F1149BB-9732-4EB8-9ED4-FA738768919C");
static readonly LocalsItemDescription[] debugInfoDescriptions = new LocalsItemDescription[] {
new LocalsItemDescription("debugInfo", typeof(DebugInfo))
};
static Type threadWorkerControllerType = typeof(ThreadWorkerController);
static MethodInfo islandWorkerMethodInfo = threadWorkerControllerType.GetMethod("IslandWorker", BindingFlags.Static | BindingFlags.Public);
internal const string MethodWithPrimingPrefix = "_";
List<LogicalThread> threads;
DynamicModuleManager dynamicModuleManager;
// Don't expose this, because that would expose the setters. Changing the properties
// after baking types has undefined semantics and would be confusing to the user.
Properties properties;
bool debugStartedAtRoot;
// Simple default constructor.
internal StateManager()
: this(new Properties(), true, null)
{
}
// Constructor.
// Properties must be set at creation time.
internal StateManager(Properties properties, bool debugStartedAtRoot, DynamicModuleManager dynamicModuleManager)
{
this.properties = properties;
this.debugStartedAtRoot = debugStartedAtRoot;
this.threads = new List<LogicalThread>();
if (dynamicModuleManager == null)
{
dynamicModuleManager = new DynamicModuleManager(this.properties.ModuleNamePrefix);
}
this.dynamicModuleManager = dynamicModuleManager;
}
internal Properties ManagerProperties
{
get { return this.properties; }
}
internal bool IsPriming
{
get;
set;
}
// Whether debugging is started at the root workflow (contrast to attaching in the middle
// of a running workflow.
internal bool DebugStartedAtRoot
{
get
{
return this.debugStartedAtRoot;
}
}
// Declare a new state associated with the given source location.
// States should have disjoint source locations.
// location is Source location associated with this state.
// This returns a state object, which can be passed to EnterState.
internal State DefineState(SourceLocation location)
{
return DefineState(location, string.Empty, null, 0);
}
internal State DefineState(SourceLocation location, string name)
{
return DefineState(location, name, null, 0);
}
internal State DefineState(SourceLocation location, string name, LocalsItemDescription[] earlyLocals, int numberOfEarlyLocals)
{
return this.dynamicModuleManager.DefineState(location, name, earlyLocals, numberOfEarlyLocals);
}
internal State DefineStateWithDebugInfo(SourceLocation location, string name)
{
return DefineState(location, name, debugInfoDescriptions, debugInfoDescriptions.Length);
}
// Bake all states using the default type namespace.
// States must be baked before calling EnterState().
internal void Bake()
{
Bake(this.properties.TypeNamePrefix, null);
}
// Bake all newly defined states. States must be baked before calling EnterState().
// typeName is the type name that the islands are contained in. This
// may show up on the callstack. If this is not unique, it will be appended with a unique
// identifier.
internal void Bake(string typeName, Dictionary<string, byte[]> checksumCache)
{
this.dynamicModuleManager.Bake(typeName, this.properties.TypeNamePrefix, checksumCache);
}
internal int CreateLogicalThread(string threadName)
{
int threadId = -1;
// Reuse thread if exists
// Start from 1 since main thread never disposed earlier.
for (int i = 1; i < this.threads.Count; ++i)
{
if (this.threads[i] == null)
{
this.threads[i] = new LogicalThread(i, threadName, this);
threadId = i;
break;
}
}
// If can't reuse old thread.
if (threadId < 0)
{
threadId = this.threads.Count;
this.threads.Add(new LogicalThread(threadId, threadName, this));
}
return threadId;
}
// Push the state onto the virtual callstack, with no locals.
// State is the state to push onto stack.
//internal void EnterState(int threadIndex, State state)
//{
// this.EnterState(threadIndex, state, null);
//}
// Enter a state and push it onto the 'virtual callstack'.
// If the user set a a breakpoint at the source location associated with
// this state, this call will hit that breakpoint.
// Call LeaveState when the interpretter is finished with this state.
//
// State is state to enter.
// "locals" is local variables (both early-bound and late-bound) associated with this state.
// Early-bound locals match by name with the set passed into DefineState.
// Late-bound will be displayed read-only to the user in the watch window.</param>
//
// EnterState can be called reentrantly. If code calls Enter(A); Enter(B); Enter(C);
// Then on the call to Enter(C), the virtual callstack will be A-->B-->C.
// Each call to Enter() will rebuild the virtual callstack.
//
internal void EnterState(int threadIndex, State state, IDictionary<string, object> locals)
{
this.EnterState(threadIndex, new VirtualStackFrame(state, locals));
}
// Enter a state and push it onto the 'virtual callstack'.
// Stackframe describing state to enter, along with the
// locals in that state.
internal void EnterState(int threadIndex, VirtualStackFrame stackFrame)
{
Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread");
Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null");
this.threads[threadIndex].EnterState(stackFrame);
}
// Pop the state most recently pushed by EnterState.
internal void LeaveState(int threadIndex, State state)
{
Fx.Assert(threadIndex < this.threads.Count, "Index out of range for thread");
Fx.Assert(this.threads[threadIndex] != null, "LogicalThread is null");
this.threads[threadIndex].LeaveState(state);
}
// Common helper to invoke an Stack frame.
// This handles marshaling the args.
// islandArguments - arbitrary argument passed ot the islands.
// [DebuggerHidden]
internal void InvokeWorker(object islandArguments, VirtualStackFrame stackFrame)
{
State state = stackFrame.State;
if (!state.DebuggingEnabled)
{
// We need to short circuit and call IslandWorker because that is what the generated code
// would have done, if we had generated it. This causes the thread to finish up.
ThreadWorkerController.IslandWorker((ThreadWorkerController)islandArguments);
return;
}
MethodInfo methodInfo = this.dynamicModuleManager.GetIsland(state, this.IsPriming);
IDictionary<string, object> allLocals = stackFrame.Locals;
// Package up the raw arguments array.
const int numberOfBaseArguments = 2;
int numberOfEarlyLocals = state.NumberOfEarlyLocals;
object[] arguments = new object[numberOfBaseArguments + numberOfEarlyLocals]; // +1 for IslandArguments and +1 for IsPriming
arguments[0] = this.IsPriming;
arguments[1] = islandArguments;
if (numberOfEarlyLocals > 0)
{
int i = numberOfBaseArguments;
foreach (LocalsItemDescription localsItemDescription in state.EarlyLocals)
{
string name = localsItemDescription.Name;
object value;
if (allLocals.TryGetValue(name, out value))
{
// We could assert that val.GetType() is assignable to localsItemDescription.Type.
// MethodInfo invoke will check this anyways; but we could check
// it and give a better error.
}
else
{
// Local not supplied in the array! Use a default.
value = Activator.CreateInstance(localsItemDescription.Type);
}
arguments[i] = value;
i++;
}
}
methodInfo.Invoke(null, arguments);
}
// Release any unmanaged resources.
// This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited.
public void Dispose()
{
ExitThreads();
}
internal void ExitThreads()
{
foreach (LogicalThread logicalThread in this.threads)
{
if (logicalThread != null)
{
logicalThread.Exit();
}
}
this.threads.Clear();
}
// Release any unmanaged resources.
// This may not necessarily unload islands or dynamic modules that were created until the calling appdomain has exited.
public void Exit(int threadIndex)
{
Fx.Assert(threadIndex >= 0, "Invalid thread index");
Fx.Assert(this.threads[threadIndex] != null, "Cannot dispose null LogicalThread");
LogicalThread thread = this.threads[threadIndex];
thread.Exit();
// Null the entry on the List for future reuse.
this.threads[threadIndex] = null;
}
// Property bag for Manager. These provide customization hooks.
// All properties have valid default values.
[DebuggerNonUserCode]
internal class Properties
{
public Properties() :
this("Locals", "Script", "States", "WorkflowDebuggerThread", true)
{
}
public Properties(string defaultLocalsName, string moduleNamePrefix, string typeNamePrefix, string auxiliaryThreadName, bool breakOnStartup)
{
this.DefaultLocalsName = defaultLocalsName;
this.ModuleNamePrefix = moduleNamePrefix;
this.TypeNamePrefix = typeNamePrefix;
this.AuxiliaryThreadName = auxiliaryThreadName;
this.BreakOnStartup = breakOnStartup;
}
public string DefaultLocalsName
{
get;
set;
}
// The name of the dynamic module (not including extension) that the states are emitted to.
// This may show up on the callstack.
// This is a prefix because there may be multiple modules for the islands.
public string ModuleNamePrefix
{
get;
set;
}
// Typename that states are created in.
// This is a prefix because there may be multiple Types for the islands
// (such as if islands are created lazily).
public string TypeNamePrefix
{
get;
set;
}
// If UseAuxiliaryThread is true, sets the friendly name of that thread as visible
// in the debugger's window.
public string AuxiliaryThreadName
{
get;
set;
}
// If true, the VM issues a Debugger.Break() before entering the first state.
// This can be useful for an F11 experience on startup to stop at the first state.
// If this is false, then the interpreter will run until it hits a breakpoint or some
// other stopping event.
public bool BreakOnStartup
{
get;
set;
}
}
[DebuggerNonUserCode]
class LogicalThread
{
int threadId;
Stack<VirtualStackFrame> callStack;
ThreadWorkerController controller;
public LogicalThread(int threadId, string threadName, StateManager stateManager)
{
this.threadId = threadId;
this.callStack = new Stack<VirtualStackFrame>();
this.controller = new ThreadWorkerController();
this.controller.Initialize(threadName + "." + threadId.ToString(CultureInfo.InvariantCulture), stateManager);
}
// Unwind call stack cleanly.
void UnwindCallStack()
{
while (this.callStack.Count > 0)
{ // LeaveState will do the popping.
this.LeaveState(this.callStack.Peek().State);
}
}
public void Exit()
{
this.UnwindCallStack();
this.controller.Exit();
}
// Enter a state and push it onto the 'virtual callstack'.
// Stackframe describing state to enter, along with the
// locals in that state.
public void EnterState(VirtualStackFrame stackFrame)
{
if (stackFrame != null && stackFrame.State != null)
{
this.callStack.Push(stackFrame);
this.controller.EnterState(stackFrame);
}
else
{ // signify "Uninstrumented call"
this.callStack.Push(null);
}
}
// Pop the state most recently pushed by EnterState.
[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters, Justification = "Revisit for bug 36860")]
public void LeaveState(State state)
{
if (this.callStack.Count > 0)
{
VirtualStackFrame stackFrame = this.callStack.Pop();
Fx.Assert(
(state == null && stackFrame == null) ||
(stackFrame != null && stackFrame.State == state),
"Unmatched LeaveState: " +
((state == null) ? "null" : state.Name) +
" with top stack frame: " +
((stackFrame == null || stackFrame.State == null) ? "null" : stackFrame.State.Name));
if (stackFrame != null) // Matches with an uninstrumented Activity.
{
this.controller.LeaveState();
}
}
else
{
Fx.Assert("Unmatched LeaveState: " + ((state != null) ? state.Name : "null"));
}
}
}
internal class DynamicModuleManager
{
// List of all state that have been created with DefineState.
List<State> states;
Dictionary<SourceLocation, State> stateMap = new Dictionary<SourceLocation, State>();
// Index into states array of the last set of states baked.
// So Bake() will build islands for each state
// { states[x], where indexLastBaked <= x < states.Length; }
int indexLastBaked;
// Mapping from State --> MethodInfo for that state.
// This gets populated as states get baked
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic assembly.")]
[SecurityCritical]
Dictionary<State, MethodInfo> islands;
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic assembly.")]
[SecurityCritical]
Dictionary<State, MethodInfo> islandsWithPriming;
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic assembly.")]
[SecurityCritical]
ModuleBuilder dynamicModule;
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic assembly.")]
[SecurityCritical]
Dictionary<string, ISymbolDocumentWriter> sourceDocuments;
[Fx.Tag.SecurityNote(Critical = "Accesses Critical members and calling Critical method InitDynamicModule.",
Safe = "We are only creating empty dictionaries, not populating them. And we are validating the provided moduleNamePrefix in partial trust.")]
[SecuritySafeCritical]
public DynamicModuleManager(string moduleNamePrefix)
{
this.states = new List<State>();
this.islands = new Dictionary<State, MethodInfo>();
this.islandsWithPriming = new Dictionary<State, MethodInfo>();
this.sourceDocuments = new Dictionary<string, ISymbolDocumentWriter>();
if (!PartialTrustHelpers.AppDomainFullyTrusted)
{
moduleNamePrefix = State.ValidateIdentifierString(moduleNamePrefix);
}
InitDynamicModule(moduleNamePrefix);
}
public State DefineState(SourceLocation location, string name, LocalsItemDescription[] earlyLocals, int numberOfEarlyLocals)
{
State state;
lock (this)
{
if (!this.stateMap.TryGetValue(location, out state))
{
lock (this)
{
state = new State(location, name, earlyLocals, numberOfEarlyLocals);
this.stateMap.Add(location, state);
this.states.Add(state);
}
}
}
return state;
}
// Bake all newly defined states. States must be baked before calling EnterState().
// typeName is the type name that the islands are contained in. This
// may show up on the callstack. If this is not unique, it will be appended with a unique
// identifier.
[Fx.Tag.SecurityNote(Critical = "Accesses Critical members and invoking Critical methods.",
Safe = "We validating the input strings - typeName and typeNamePrefix - and the checksum values in the checksumCache.")]
[SecuritySafeCritical]
public void Bake(string typeName, string typeNamePrefix, Dictionary<string, byte[]> checksumCache)
{
// In partial trust, validate the typeName and typeNamePrefix.
if (!PartialTrustHelpers.AppDomainFullyTrusted)
{
typeName = State.ValidateIdentifierString(typeName);
typeNamePrefix = State.ValidateIdentifierString(typeNamePrefix);
if (checksumCache != null)
{
bool nullifyChecksumCache = false;
foreach (KeyValuePair<string, byte[]> kvpair in checksumCache)
{
// We use an MD5 hash for the checksum, so the byte array should be 16 elements long.
if (!SymbolHelper.ValidateChecksum(kvpair.Value))
{
nullifyChecksumCache = true;
Trace.WriteLine(SR.DebugSymbolChecksumValueInvalid);
break;
}
}
// If we found an invalid checksum, just don't use the cache.
if (nullifyChecksumCache)
{
checksumCache = null;
}
}
}
lock (this)
{
if (this.indexLastBaked < this.states.Count) // there are newly created states.
{
// Ensure typename is unique. Append a number if needed.
int suffix = 1;
while (this.dynamicModule.GetType(typeName) != null)
{
typeName = typeNamePrefix + "_" + suffix.ToString(CultureInfo.InvariantCulture);
++suffix;
}
TypeBuilder typeBuilder = this.dynamicModule.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);
for (int i = indexLastBaked; i < this.states.Count; i++)
{
// Only create the island if debugging is enabled for the state.
if (this.states[i].DebuggingEnabled)
{
MethodBuilder methodBuilder = this.CreateIsland(typeBuilder, this.states[i], false, checksumCache);
Fx.Assert(methodBuilder != null, "CreateIsland call should have succeeded");
// Always generate method with priming, for the following scenario:
// 1. Start debugging a workflow inside VS, workflow debug session 1 starts (debugStartedAtRoot = true, instrumentation is done)
// 2. Workflow persisted, workflow debug session 1 ends
// 3. Workflow continued, workflow debug session 2 starts (debugStartedAtRoot = false, instrumentation is skipped because the static dynamicModuleManager is being reused and the instrumentation is done)
// 4. PrimeCallStack is called to rebuild the call stack
// 5. NullReferenceException will be thrown if MethodInfo with prime is not available
MethodBuilder methodBuilderWithPriming = this.CreateIsland(typeBuilder, this.states[i], true, checksumCache);
Fx.Assert(methodBuilderWithPriming != null, "CreateIsland call should have succeeded");
// Save information needed to call Type.GetMethod() later.
this.states[i].CacheMethodInfo(typeBuilder, methodBuilder.Name);
}
}
// Actual baking.
typeBuilder.CreateType();
// Calling Type.GetMethod() is slow (10,000 calls can take ~1 minute).
// So defer that to later.
this.indexLastBaked = this.states.Count;
}
}
}
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic module.")]
[SecurityCritical]
internal MethodBuilder CreateMethodBuilder(TypeBuilder typeBuilder, Type typeIslandArguments, State state, bool withPriming)
{
// create the method
string methodName = (state.Name != null ? state.Name : ("Line_" + state.Location.StartLine));
if (withPriming)
{
methodName = MethodWithPrimingPrefix + methodName;
}
// Parameters to the islands:
// 1. Args
// 2. IDict of late-bound locals.
// 3 ... N. list of early bound locals.
const int numberOfBaseArguments = 2;
IEnumerable<LocalsItemDescription> earlyLocals = state.EarlyLocals;
int numberOfEarlyLocals = state.NumberOfEarlyLocals;
Type[] parameterTypes = new Type[numberOfBaseArguments + numberOfEarlyLocals];
parameterTypes[0] = typeof(bool);
parameterTypes[1] = typeIslandArguments;
if (numberOfEarlyLocals > 0)
{
int i = numberOfBaseArguments;
foreach (LocalsItemDescription localsItemDescription in earlyLocals)
{
parameterTypes[i] = localsItemDescription.Type;
i++;
}
}
Type returnType = typeof(void);
MethodBuilder methodbuilder = typeBuilder.DefineMethod(
methodName,
MethodAttributes.Static | MethodAttributes.Public,
returnType, parameterTypes);
// Need to define parameter here, otherwise EE cannot get the correct IDebugContainerField
// for debugInfo.
methodbuilder.DefineParameter(1, ParameterAttributes.None, "isPriming");
methodbuilder.DefineParameter(2, ParameterAttributes.None, "typeIslandArguments");
// Define the parameter names
// Note that we can hide implementation-specific arguments from VS by not defining parameter
// info for them. Eg., the StepInTarget argument doesn't appear to show up in VS at all.
if (numberOfEarlyLocals > 0)
{
int i = numberOfBaseArguments + 1;
foreach (LocalsItemDescription localsItemDescription in earlyLocals)
{
methodbuilder.DefineParameter(i, ParameterAttributes.None, localsItemDescription.Name);
i++;
}
}
return methodbuilder;
}
[Fx.Tag.InheritThrows(From = "GetILGenerator", FromDeclaringType = typeof(MethodBuilder))]
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic module.")]
[SecurityCritical]
MethodBuilder CreateIsland(TypeBuilder typeBuilder, State state, bool withPrimingTest, Dictionary<string, byte[]>checksumCache)
{
MethodBuilder methodbuilder = this.CreateMethodBuilder(typeBuilder, threadWorkerControllerType, state, withPrimingTest);
ILGenerator ilGenerator = methodbuilder.GetILGenerator();
const int lineHidden = 0xFeeFee; // #line hidden directive
// Island:
// void MethodName(Manager m)
// {
// .line
// nop
// call Worker(m)
// ret;
// }
SourceLocation stateLocation = state.Location;
ISymbolDocumentWriter document = this.GetSourceDocument(stateLocation.FileName, stateLocation.Checksum, checksumCache);
Label islandWorkerLabel = ilGenerator.DefineLabel();
// Hide all the opcodes before the real source line.
// This is needed for Island which is called during priming (Attach to Process):
// It should skip the line directive during priming, thus it won't break at user's
// breakpoints at the beginning during priming the callstack.
if (withPrimingTest)
{
ilGenerator.MarkSequencePoint(document, lineHidden, 1, lineHidden, 100);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Brtrue, islandWorkerLabel);
}
// Emit sequence point before the IL instructions to map it to a source location.
ilGenerator.MarkSequencePoint(document, stateLocation.StartLine, stateLocation.StartColumn, stateLocation.EndLine, stateLocation.EndColumn);
ilGenerator.Emit(OpCodes.Nop);
ilGenerator.MarkLabel(islandWorkerLabel);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.EmitCall(OpCodes.Call, islandWorkerMethodInfo, null);
ilGenerator.Emit(OpCodes.Ret);
return methodbuilder;
}
[SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts, Justification = "The validations of the user input are done elsewhere.")]
[Fx.Tag.SecurityNote(Critical = "Because we Assert UnmanagedCode in order to be able to emit symbols.")]
[SecurityCritical]
void InitDynamicModule(string asmName)
{
// See http://blogs.msdn.com/Microsoft/archive/2005/02/03/366429.aspx for a simple example
// of debuggable reflection-emit.
Fx.Assert(dynamicModule == null, "can only be initialized once");
// create a dynamic assembly and module
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = asmName;
AssemblyBuilder assemblyBuilder;
// The temporary assembly needs to be Transparent.
ConstructorInfo transparentCtor =
typeof(SecurityTransparentAttribute).GetConstructor(
Type.EmptyTypes);
CustomAttributeBuilder transparent = new CustomAttributeBuilder(
transparentCtor,
new Object[] { });
assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, null, true, new CustomAttributeBuilder[] { transparent });
// Mark generated code as debuggable.
// See http://blogs.msdn.com/rmbyers/archive/2005/06/26/432922.aspx for explanation.
Type debuggableAttributeType = typeof(DebuggableAttribute);
ConstructorInfo constructorInfo = debuggableAttributeType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });
CustomAttributeBuilder builder = new CustomAttributeBuilder(constructorInfo, new object[] {
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.Default
});
assemblyBuilder.SetCustomAttribute(builder);
// We need UnmanagedCode permissions because we are asking for Symbols to be emitted.
// We are protecting the dynamicModule so that only Critical code modifies it.
PermissionSet unmanagedCodePermissionSet = new PermissionSet(PermissionState.None);
unmanagedCodePermissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode));
unmanagedCodePermissionSet.Assert();
try
{
dynamicModule = assemblyBuilder.DefineDynamicModule(asmName, true); // <-- pass 'true' to track debug info.
}
finally
{
CodeAccessPermission.RevertAssert();
}
}
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic module.",
Safe = "State validates its SecurityCritical members itself")]
[SecuritySafeCritical]
public MethodInfo GetIsland(State state, bool isPriming)
{
MethodInfo island = null;
if (isPriming)
{
lock (islandsWithPriming)
{
if (!islandsWithPriming.TryGetValue(state, out island))
{
island = state.GetMethodInfo(true);
islandsWithPriming[state] = island;
}
}
}
else
{
lock (islands)
{
if (!islands.TryGetValue(state, out island))
{
island = state.GetMethodInfo(false);
islands[state] = island;
}
}
}
return island;
}
// This method is only called from CreateIsland, which is only called from Bake.
// Bake does a "lock(this)" before calling CreateIsland, so access to the sourceDocuments
// dictionary is protected by that lock. If this changes, locking will need to be added
// to this method to protect the sourceDocuments dictionary.
[Fx.Tag.SecurityNote(Critical = "Used in generating the dynamic module.")]
[SecurityCritical]
private ISymbolDocumentWriter GetSourceDocument(string fileName, byte[] checksum, Dictionary<string, byte[]> checksumCache)
{
ISymbolDocumentWriter documentWriter;
string sourceDocKey = fileName + SymbolHelper.GetHexStringFromChecksum(checksum);
if (!this.sourceDocuments.TryGetValue(sourceDocKey, out documentWriter))
{
documentWriter =
dynamicModule.DefineDocument(
fileName,
StateManager.WorkflowLanguageGuid,
SymLanguageVendor.Microsoft,
SymDocumentType.Text);
this.sourceDocuments.Add(sourceDocKey, documentWriter);
byte[] checksumBytes;
if (checksumCache == null || !checksumCache.TryGetValue(fileName.ToUpperInvariant(), out checksumBytes))
{
checksumBytes = SymbolHelper.CalculateChecksum(fileName);
}
if (checksumBytes != null)
{
documentWriter.SetCheckSum(SymbolHelper.ChecksumProviderId, checksumBytes);
}
}
return documentWriter;
}
}
}
}
|