// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Debugger;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.Symbols;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue
{
[Export(typeof(IActiveStatementProvider)), Shared]
internal sealed partial class VisualStudioActiveStatementProvider : IActiveStatementProvider
{
///
/// Retrieves active statements from the debuggee process.
/// Shall only be called while in debug mode.
/// Can be invoked on any thread.
///
public Task> GetActiveStatementsAsync(CancellationToken cancellationToken)
{
using (DebuggerComponent.ManagedEditAndContinueService())
{
// TODO: return empty outside of debug session.
// https://github.com/dotnet/roslyn/issues/24325
var completion = new TaskCompletionSource>();
var builders = default(ArrayBuilder>);
int pendingRuntimes = 0;
int runtimeCount = 0;
// No exception should be thrown in case of errors on the debugger side.
// The debugger is responsible to provide telemetry for error cases.
// The callback should not be called, but it's there to guarantee that the task completes and a hang is avoided.
var workList = DkmWorkList.Create(_ => { completion.TrySetResult(ImmutableArray.Empty); });
void CancelWork()
{
if (builders != null)
{
FreeBuilders(builders);
builders = null;
workList.Cancel(blockOnCompletion: false);
// make sure we cancel with the token we received from the caller:
completion.TrySetCanceled(cancellationToken);
}
}
foreach (var process in DkmProcess.GetProcesses())
{
foreach (var runtimeInstance in process.GetRuntimeInstances())
{
if (runtimeInstance.TagValue == DkmRuntimeInstance.Tag.ClrRuntimeInstance)
{
var clrRuntimeInstance = (DkmClrRuntimeInstance)runtimeInstance;
int runtimeIndex = runtimeCount;
runtimeCount++;
clrRuntimeInstance.GetActiveStatements(workList, activeStatementsResult =>
{
try
{
if (cancellationToken.IsCancellationRequested)
{
CancelWork();
return;
}
var localBuilders = builders;
if (localBuilders == null) // e.g. cancelled
{
return;
}
if (activeStatementsResult.ErrorCode != 0)
{
localBuilders[runtimeIndex] = ArrayBuilder.GetInstance(0);
// the last active statement of the last runtime has been processed:
if (Interlocked.Decrement(ref pendingRuntimes) == 0)
{
completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
}
return;
}
// group active statement by instruction and aggregate flags and threads:
var instructionMap = PooledDictionary Threads, int Index, ActiveStatementFlags Flags)>.GetInstance();
GroupActiveStatementsByInstructionId(instructionMap, activeStatementsResult.ActiveStatements);
int pendingStatements = instructionMap.Count;
localBuilders[runtimeIndex] = ArrayBuilder.GetInstance(pendingStatements);
localBuilders[runtimeIndex].Count = pendingStatements;
if (instructionMap.Count == 0)
{
if (Interlocked.Decrement(ref pendingRuntimes) == 0)
{
completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
}
return;
}
foreach (var (instructionId, (symbol, threads, index, flags)) in instructionMap)
{
var immutableThreads = threads.ToImmutableAndFree();
symbol.GetSourcePosition(workList, DkmSourcePositionFlags.None, InspectionSession: null, sourcePositionResult =>
{
try
{
if (cancellationToken.IsCancellationRequested)
{
CancelWork();
return;
}
DkmSourcePosition position;
string documentNameOpt;
LinePositionSpan span;
if (sourcePositionResult.ErrorCode == 0 && (position = sourcePositionResult.SourcePosition) != null)
{
documentNameOpt = position.DocumentName;
span = ToLinePositionSpan(position.TextSpan);
}
else
{
// The debugger can't determine source location for the active statement.
// The PDB might not be available or the statement is in a method that doesn't have debug information.
documentNameOpt = null;
span = default;
}
localBuilders[runtimeIndex][index] = new ActiveStatementDebugInfo(
instructionId,
documentNameOpt,
span,
immutableThreads,
flags);
// the last active statement of the current runtime has been processed:
if (Interlocked.Decrement(ref pendingStatements) == 0)
{
// the last active statement of the last runtime has been processed:
if (Interlocked.Decrement(ref pendingRuntimes) == 0)
{
completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
}
}
}
catch (Exception e)
{
completion.TrySetException(e);
}
});
}
instructionMap.Free();
}
catch (Exception e)
{
completion.TrySetException(e);
}
});
}
}
}
pendingRuntimes = runtimeCount;
builders = ArrayBuilder>.GetInstance(runtimeCount);
builders.Count = runtimeCount;
// Start execution of the Concord work items.
workList.BeginExecution();
return completion.Task;
}
}
private static void FreeBuilders(ArrayBuilder> builders)
{
foreach (var builderArray in builders)
{
builderArray?.Free();
}
builders.Free();
}
}
}