|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests
{
using static ActiveStatementTestHelpers;
[UseExportProvider]
public class EditSessionActiveStatementsTests : TestBase
{
private static readonly TestComposition s_composition = FeaturesTestCompositions.Features.AddParts(typeof(NoCompilationLanguageService));
private static EditSession CreateEditSession(
Solution solution,
ImmutableArray<ManagedActiveStatementDebugInfo> activeStatements,
ImmutableDictionary<ManagedMethodId, ImmutableArray<NonRemappableRegion>> nonRemappableRegions = null,
CommittedSolution.DocumentState initialState = CommittedSolution.DocumentState.MatchesBuildOutput)
{
var mockDebuggerService = new MockManagedEditAndContinueDebuggerService()
{
GetActiveStatementsImpl = () => activeStatements,
};
var mockCompilationOutputsProvider = new Func<Project, CompilationOutputs>(_ => new MockCompilationOutputs(Guid.NewGuid()));
var debuggingSession = new DebuggingSession(
new DebuggingSessionId(1),
solution,
mockDebuggerService,
mockCompilationOutputsProvider,
NullPdbMatchingSourceTextProvider.Instance,
initialDocumentStates: [],
reportDiagnostics: true);
if (initialState != CommittedSolution.DocumentState.None)
{
EditAndContinueTestVerifier.SetDocumentsState(debuggingSession, solution, initialState);
}
debuggingSession.RestartEditSession(nonRemappableRegions ?? ImmutableDictionary<ManagedMethodId, ImmutableArray<NonRemappableRegion>>.Empty, inBreakState: true);
return debuggingSession.EditSession;
}
private static async Task<Solution> AddDefaultTestSolutionAsync(TestWorkspace workspace, string[] markedSources)
{
var solution = workspace.CurrentSolution;
var project = solution
.AddProject("proj", "proj", LanguageNames.CSharp)
.WithMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Standard));
solution = project.Solution;
for (var i = 0; i < markedSources.Length; i++)
{
var name = $"test{i + 1}.cs";
var text = SourceText.From(SourceMarkers.Clear(markedSources[i]), Encoding.UTF8);
var id = DocumentId.CreateNewId(project.Id, name);
solution = solution.AddDocument(id, name, text, filePath: Path.Combine(TempRoot.Root, name));
}
await workspace.ChangeSolutionAsync(solution);
return solution;
}
[Fact]
public async Task BaseActiveStatementsAndExceptionRegions1()
{
var markedSources = new[]
{
@"class Test1
{
static void M1()
{
try { } finally { <AS:1>F1();</AS:1> }
}
static void F1()
{
<AS:0>Console.WriteLine(1);</AS:0>
}
}",
@"class Test2
{
static void M2()
{
try
{
try
{
<AS:3>F2();</AS:3>
}
catch (Exception1 e1)
{
}
}
catch (Exception2 e2)
{
}
}
static void F2()
{
<AS:2>Test1.M1()</AS:2>
}
static void Main()
{
try { <AS:4>M2();</AS:4> } finally { }
}
}
"
};
var module1 = new Guid("11111111-1111-1111-1111-111111111111");
var module2 = new Guid("22222222-2222-2222-2222-222222222222");
var module3 = new Guid("33333333-3333-3333-3333-333333333333");
var module4 = new Guid("44444444-4444-4444-4444-444444444444");
var activeStatements = GetActiveStatementDebugInfosCSharp(
markedSources,
methodRowIds: [1, 2, 3, 4, 5],
ilOffsets: [1, 1, 1, 2, 3],
modules: [module1, module1, module2, module2, module2]);
// add an extra active statement that has no location, it should be ignored:
activeStatements = activeStatements.Add(
new ManagedActiveStatementDebugInfo(
new ManagedInstructionId(new ManagedMethodId(module: Guid.NewGuid(), token: 0x06000005, version: 1), ilOffset: 10),
documentName: null,
sourceSpan: default,
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame));
// add an extra active statement from project not belonging to the solution, it should be ignored:
activeStatements = activeStatements.Add(
new ManagedActiveStatementDebugInfo(
new ManagedInstructionId(new ManagedMethodId(module: module3, token: 0x06000005, version: 1), ilOffset: 10),
"NonRoslynDocument.mcpp",
new SourceSpan(1, 1, 1, 10),
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame));
// Add an extra active statement from language that doesn't support Roslyn EnC should be ignored:
// See https://github.com/dotnet/roslyn/issues/24408 for test scenario.
activeStatements = activeStatements.Add(
new ManagedActiveStatementDebugInfo(
new ManagedInstructionId(new ManagedMethodId(module: module4, token: 0x06000005, version: 1), ilOffset: 10),
"a.dummy",
new SourceSpan(2, 1, 2, 10),
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame));
using var workspace = new TestWorkspace(composition: s_composition);
var solution = await AddDefaultTestSolutionAsync(workspace, markedSources);
var projectId = solution.ProjectIds.Single();
var dummyProject = solution.AddProject("dummy_proj", "dummy_proj", NoCompilationConstants.LanguageName);
solution = dummyProject.Solution.AddDocument(DocumentId.CreateNewId(dummyProject.Id, NoCompilationConstants.LanguageName), "a.dummy", "");
var project = solution.GetProject(projectId);
var document1 = project.Documents.Single(d => d.Name == "test1.cs");
var document2 = project.Documents.Single(d => d.Name == "test2.cs");
var editSession = CreateEditSession(solution, activeStatements);
var baseActiveStatementsMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
// Active Statements
var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray();
AssertEx.Equal(
[
$"0: {document1.FilePath}: (9,14)-(9,35) flags=[LeafFrame, MethodUpToDate] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0001",
$"1: {document1.FilePath}: (4,32)-(4,37) flags=[MethodUpToDate, NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0001",
$"2: {document2.FilePath}: (21,14)-(21,24) flags=[MethodUpToDate, NonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000003 v1 IL_0001", // [|Test1.M1()|] in F2
$"3: {document2.FilePath}: (8,20)-(8,25) flags=[MethodUpToDate, NonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000004 v1 IL_0002", // [|F2();|] in M2
$"4: {document2.FilePath}: (26,20)-(26,25) flags=[MethodUpToDate, NonLeafFrame] mvid=22222222-2222-2222-2222-222222222222 0x06000005 v1 IL_0003", // [|M2();|] in Main
$"5: NonRoslynDocument.mcpp: (1,1)-(1,10) flags=[MethodUpToDate, NonLeafFrame] mvid={module3} 0x06000005 v1 IL_000A",
$"6: a.dummy: (2,1)-(2,10) flags=[MethodUpToDate, NonLeafFrame] mvid={module4} 0x06000005 v1 IL_000A"
], statements.Select(InspectActiveStatementAndInstruction));
// Active Statements per document
Assert.Equal(4, baseActiveStatementsMap.DocumentPathMap.Count);
AssertEx.Equal(
[
$"1: {document1.FilePath}: (4,32)-(4,37) flags=[MethodUpToDate, NonLeafFrame]",
$"0: {document1.FilePath}: (9,14)-(9,35) flags=[LeafFrame, MethodUpToDate]"
], baseActiveStatementsMap.DocumentPathMap[document1.FilePath].Select(InspectActiveStatement));
AssertEx.Equal(
[
$"3: {document2.FilePath}: (8,20)-(8,25) flags=[MethodUpToDate, NonLeafFrame]", // [|F2();|] in M2
$"2: {document2.FilePath}: (21,14)-(21,24) flags=[MethodUpToDate, NonLeafFrame]", // [|Test1.M1()|] in F2
$"4: {document2.FilePath}: (26,20)-(26,25) flags=[MethodUpToDate, NonLeafFrame]" // [|M2();|] in Main
], baseActiveStatementsMap.DocumentPathMap[document2.FilePath].Select(InspectActiveStatement));
AssertEx.Equal(
[
$"5: NonRoslynDocument.mcpp: (1,1)-(1,10) flags=[MethodUpToDate, NonLeafFrame]",
], baseActiveStatementsMap.DocumentPathMap["NonRoslynDocument.mcpp"].Select(InspectActiveStatement));
AssertEx.Equal(
[
$"6: a.dummy: (2,1)-(2,10) flags=[MethodUpToDate, NonLeafFrame]",
], baseActiveStatementsMap.DocumentPathMap["a.dummy"].Select(InspectActiveStatement));
// Exception Regions
var analyzer = solution.GetProject(projectId).Services.GetRequiredService<IEditAndContinueAnalyzer>();
var oldActiveStatements1 = await baseActiveStatementsMap.GetOldActiveStatementsAsync(analyzer, document1, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(
[
$"[{document1.FilePath}: (4,8)-(4,46)]",
"[]",
], oldActiveStatements1.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans) + "]"));
var oldActiveStatements2 = await baseActiveStatementsMap.GetOldActiveStatementsAsync(analyzer, document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(
[
$"[{document2.FilePath}: (14,8)-(16,9), {document2.FilePath}: (10,10)-(12,11)]",
"[]",
$"[{document2.FilePath}: (26,35)-(26,46)]",
], oldActiveStatements2.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans) + "]"));
// GetActiveStatementAndExceptionRegionSpans
// Assume 2 updates in Document2:
// Test2.M2: adding a line in front of try-catch.
// Test2.F2: moving the entire method 2 lines down.
var newActiveStatementsInChangedDocuments = ImmutableArray.Create(
new DocumentActiveStatementChanges(
oldSpans: oldActiveStatements2,
newStatements: ImmutableArray.Create(
statements[3].WithFileSpan(statements[3].FileSpan.AddLineDelta(+1)),
statements[2].WithFileSpan(statements[2].FileSpan.AddLineDelta(+2)),
statements[4]),
newExceptionRegions: ImmutableArray.Create(
oldActiveStatements2[0].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(+1)),
oldActiveStatements2[1].ExceptionRegions.Spans,
oldActiveStatements2[2].ExceptionRegions.Spans)));
EditSession.GetActiveStatementAndExceptionRegionSpans(
module2,
baseActiveStatementsMap,
updatedMethodTokens: ImmutableArray.Create(0x06000004), // contains only recompiled methods in the project we are interested in (module2)
previousNonRemappableRegions: ImmutableDictionary<ManagedMethodId, ImmutableArray<NonRemappableRegion>>.Empty,
newActiveStatementsInChangedDocuments,
out var activeStatementsInUpdatedMethods,
out var nonRemappableRegions,
out var exceptionRegionUpdates);
AssertEx.Equal(
[
$"0x06000004 v1 | AS {document2.FilePath}: (8,20)-(8,25) => (9,20)-(9,25)",
$"0x06000004 v1 | ER {document2.FilePath}: (14,8)-(16,9) => (15,8)-(17,9)",
$"0x06000004 v1 | ER {document2.FilePath}: (10,10)-(12,11) => (11,10)-(13,11)",
$"0x06000003 v1 | AS {document2.FilePath}: (21,14)-(21,24) => (21,14)-(21,24)",
$"0x06000005 v1 | AS {document2.FilePath}: (26,20)-(26,25) => (26,20)-(26,25)"
], nonRemappableRegions.Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}"));
AssertEx.Equal(
[
$"0x06000004 v1 | (15,8)-(17,9) Delta=-1",
$"0x06000004 v1 | (11,10)-(13,11) Delta=-1"
], exceptionRegionUpdates.Select(InspectExceptionRegionUpdate));
AssertEx.Equal(
[
$"0x06000004 v1 IL_0002: (9,20)-(9,25)"
], activeStatementsInUpdatedMethods.Select(InspectActiveStatementUpdate));
}
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24439")]
public async Task BaseActiveStatementsAndExceptionRegions2()
{
var baseSource =
@"class Test
{
static void F1()
{
try
{
<AS:0>F2();</AS:0>
}
catch (Exception) {
Console.WriteLine(1);
Console.WriteLine(2);
Console.WriteLine(3);
}
/*insert1[1]*/
}
static void F2()
{
<AS:1>throw new Exception();</AS:1>
}
}";
var updatedSource = Update(baseSource, marker: "1");
var module1 = new Guid("11111111-1111-1111-1111-111111111111");
var baseText = SourceText.From(baseSource);
var updatedText = SourceText.From(updatedSource);
var baseActiveStatementInfos = GetActiveStatementDebugInfosCSharp(
[baseSource],
modules: [module1, module1],
methodVersions: [1, 1],
flags:
[
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, // F1
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, // F2
]);
using var workspace = new TestWorkspace(composition: s_composition);
var solution = await AddDefaultTestSolutionAsync(workspace, [baseSource]);
var project = solution.Projects.Single();
var document = project.Documents.Single();
var editSession = CreateEditSession(solution, baseActiveStatementInfos);
var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
// Active Statements
var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray();
AssertEx.Equal(
[
$"0: {document.FilePath}: (6,18)-(6,23) flags=[MethodUpToDate, NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v1 IL_0000 '<AS:0>F2();</AS:0>'",
$"1: {document.FilePath}: (18,14)-(18,36) flags=[LeafFrame, MethodUpToDate] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v1 IL_0000 '<AS:1>throw new Exception();</AS:1>'"
], baseActiveStatements.Select(s => InspectActiveStatementAndInstruction(s, baseText)));
// Exception Regions
var analyzer = solution.GetProject(project.Id).Services.GetRequiredService<IEditAndContinueAnalyzer>();
var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false);
// Note that the spans correspond to the base snapshot (V2).
AssertEx.Equal(
[
$"[{document.FilePath}: (8,8)-(12,9) 'catch (Exception) {{']",
"[]",
], oldActiveStatements.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans.Select(span => $"{span} '{GetFirstLineText(span.Span, baseText)}'")) + "]"));
// GetActiveStatementAndExceptionRegionSpans
var newActiveStatementsInChangedDocuments = ImmutableArray.Create(
new DocumentActiveStatementChanges(
oldSpans: oldActiveStatements,
newStatements: ImmutableArray.Create(
baseActiveStatements[0],
baseActiveStatements[1].WithFileSpan(baseActiveStatements[1].FileSpan.AddLineDelta(+1))),
newExceptionRegions: ImmutableArray.Create(
oldActiveStatements[0].ExceptionRegions.Spans,
oldActiveStatements[1].ExceptionRegions.Spans))
);
EditSession.GetActiveStatementAndExceptionRegionSpans(
module1,
baseActiveStatementMap,
updatedMethodTokens: ImmutableArray.Create(0x06000001), // F1
previousNonRemappableRegions: ImmutableDictionary<ManagedMethodId, ImmutableArray<NonRemappableRegion>>.Empty,
newActiveStatementsInChangedDocuments,
out var activeStatementsInUpdatedMethods,
out var nonRemappableRegions,
out var exceptionRegionUpdates);
AssertEx.Equal(
[
$"0x06000001 v1 | AS {document.FilePath}: (6,18)-(6,23) => (6,18)-(6,23)",
$"0x06000001 v1 | ER {document.FilePath}: (8,8)-(12,9) => (8,8)-(12,9)",
$"0x06000002 v1 | AS {document.FilePath}: (18,14)-(18,36) => (18,14)-(18,36)",
], nonRemappableRegions.OrderBy(r => r.Region.OldSpan.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}"));
AssertEx.Equal(
[
"0x06000001 v1 | (8,8)-(12,9) Delta=0",
], exceptionRegionUpdates.Select(InspectExceptionRegionUpdate));
AssertEx.Equal(
[
"0x06000001 v1 IL_0000: (6,18)-(6,23) '<AS:0>F2();</AS:0>'"
], activeStatementsInUpdatedMethods.Select(update => $"{InspectActiveStatementUpdate(update)} '{GetFirstLineText(update.NewSpan.ToLinePositionSpan(), updatedText)}'"));
}
[Fact]
public async Task BaseActiveStatementsAndExceptionRegions_WithInitialNonRemappableRegions()
{
var markedSourceV1 =
@"class Test
{
static void F1()
{
try
{
<AS:0>M();</AS:0>
}
<ER:0.0>catch
{
}</ER:0.0>
}
static void F2()
{ /*delete2
*/try
{
}
<ER:1.0>catch
{
<AS:1>M();</AS:1>
}</ER:1.0>/*insert2[1]*/
}
static void F3()
{
try
{
try
{ /*delete1
*/<AS:2>M();</AS:2>/*insert1[3]*/
}
<ER:2.0>finally
{
}</ER:2.0>
}
<ER:2.1>catch
{
}</ER:2.1>
/*delete1
*/ }
static void F4()
{ /*insert1[1]*//*insert2[2]*/
try
{
try
{
}
<ER:3.0>catch
{
<AS:3>M();</AS:3>
}</ER:3.0>
}
<ER:3.1>catch
{
}</ER:3.1>
}
}";
var markedSourceV2 = Update(markedSourceV1, marker: "1");
var markedSourceV3 = Update(markedSourceV2, marker: "2");
var module1 = new Guid("11111111-1111-1111-1111-111111111111");
var sourceTextV1 = SourceText.From(markedSourceV1);
var sourceTextV2 = SourceText.From(markedSourceV2);
var sourceTextV3 = SourceText.From(markedSourceV3);
var activeStatementsPreRemap = GetActiveStatementDebugInfosCSharp([markedSourceV1],
modules: [module1, module1, module1, module1],
methodVersions: [2, 2, 1, 1], // method F3 and F4 were not remapped
flags:
[
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, // F1
ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, // F2
ActiveStatementFlags.None | ActiveStatementFlags.NonLeafFrame, // F3
ActiveStatementFlags.None | ActiveStatementFlags.NonLeafFrame, // F4
]);
var exceptionSpans = SourceMarkers.GetExceptionRegions(markedSourceV1);
var filePath = activeStatementsPreRemap[0].DocumentName;
var spanPreRemap2 = new SourceFileSpan(filePath, activeStatementsPreRemap[2].SourceSpan.ToLinePositionSpan());
var erPreRemap20 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][0]));
var erPreRemap21 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[2][1]));
var spanPreRemap3 = new SourceFileSpan(filePath, activeStatementsPreRemap[3].SourceSpan.ToLinePositionSpan());
var erPreRemap30 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][0]));
var erPreRemap31 = new SourceFileSpan(filePath, sourceTextV1.Lines.GetLinePositionSpan(exceptionSpans[3][1]));
// Assume that the following edits have been made to F3 and F4 and set up non-remappable regions mapping
// from the pre-remap spans of AS:2 and AS:3 to their current location.
var initialNonRemappableRegions = new Dictionary<ManagedMethodId, ImmutableArray<NonRemappableRegion>>
{
{ new ManagedMethodId(module1, 0x06000003, 1), ImmutableArray.Create(
// move AS:2 one line up:
new NonRemappableRegion(spanPreRemap2, spanPreRemap2.AddLineDelta(-1), isExceptionRegion: false),
// move ER:2.0 and ER:2.1 two lines down:
new NonRemappableRegion(erPreRemap20, erPreRemap20.AddLineDelta(+2), isExceptionRegion: true),
new NonRemappableRegion(erPreRemap21, erPreRemap21.AddLineDelta(+2), isExceptionRegion: true)) },
{ new ManagedMethodId(module1, 0x06000004, 1), ImmutableArray.Create(
// move AS:3 one line down:
new NonRemappableRegion(spanPreRemap3, spanPreRemap3.AddLineDelta(+1), isExceptionRegion: false),
// move ER:3.0 and ER:3.1 one line down:
new NonRemappableRegion(erPreRemap30, erPreRemap30.AddLineDelta(+1), isExceptionRegion: true),
new NonRemappableRegion(erPreRemap31, erPreRemap31.AddLineDelta(+1), isExceptionRegion: true)) }
}.ToImmutableDictionary();
using var workspace = new TestWorkspace(composition: s_composition);
var solution = await AddDefaultTestSolutionAsync(workspace, [markedSourceV2]);
var project = solution.Projects.Single();
var document = project.Documents.Single();
var editSession = CreateEditSession(solution, activeStatementsPreRemap, initialNonRemappableRegions);
var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
// Active Statements
var baseActiveStatements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray();
// Note that the spans of AS:2 and AS:3 correspond to the base snapshot (V2).
AssertEx.Equal(
[
$"0: {document.FilePath}: (6,18)-(6,22) flags=[MethodUpToDate, NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000001 v2 IL_0000 '<AS:0>M();</AS:0>'",
$"1: {document.FilePath}: (20,18)-(20,22) flags=[MethodUpToDate, NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000002 v2 IL_0000 '<AS:1>M();</AS:1>'",
$"2: {document.FilePath}: (29,22)-(29,26) flags=[NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000003 v1 IL_0000 '{{ <AS:2>M();</AS:2>'",
$"3: {document.FilePath}: (53,22)-(53,26) flags=[NonLeafFrame] mvid=11111111-1111-1111-1111-111111111111 0x06000004 v1 IL_0000 '<AS:3>M();</AS:3>'"
], baseActiveStatements.Select(s => InspectActiveStatementAndInstruction(s, sourceTextV2)));
// Exception Regions
var analyzer = solution.GetProject(project.Id).Services.GetRequiredService<IEditAndContinueAnalyzer>();
var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false);
// Note that the spans correspond to the base snapshot (V2).
AssertEx.Equal(
[
$"[{document.FilePath}: (8,16)-(10,9) '<ER:0.0>catch']",
$"[{document.FilePath}: (18,16)-(21,9) '<ER:1.0>catch']",
$"[{document.FilePath}: (38,16)-(40,9) '<ER:2.1>catch', {document.FilePath}: (34,20)-(36,13) '<ER:2.0>finally']",
$"[{document.FilePath}: (56,16)-(58,9) '<ER:3.1>catch', {document.FilePath}: (51,20)-(54,13) '<ER:3.0>catch']",
], oldActiveStatements.Select(s => "[" + string.Join(", ", s.ExceptionRegions.Spans.Select(span => $"{span} '{GetFirstLineText(span.Span, sourceTextV2)}'")) + "]"));
// GetActiveStatementAndExceptionRegionSpans
// Assume 2 more updates:
// F2: Move 'try' one line up (a new non-remappable entries will be added)
// F4: Insert 2 new lines before the first 'try' (an existing non-remappable entries will be updated)
var newActiveStatementsInChangedDocuments = ImmutableArray.Create(
new DocumentActiveStatementChanges(
oldSpans: oldActiveStatements,
newStatements: ImmutableArray.Create(
baseActiveStatements[0],
baseActiveStatements[1].WithFileSpan(baseActiveStatements[1].FileSpan.AddLineDelta(-1)),
baseActiveStatements[2],
baseActiveStatements[3].WithFileSpan(baseActiveStatements[3].FileSpan.AddLineDelta(+2))),
newExceptionRegions: ImmutableArray.Create(
oldActiveStatements[0].ExceptionRegions.Spans,
oldActiveStatements[1].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(-1)),
oldActiveStatements[2].ExceptionRegions.Spans,
oldActiveStatements[3].ExceptionRegions.Spans.SelectAsArray(es => es.AddLineDelta(+2)))));
EditSession.GetActiveStatementAndExceptionRegionSpans(
module1,
baseActiveStatementMap,
updatedMethodTokens: ImmutableArray.Create(0x06000002, 0x06000004), // F2, F4
initialNonRemappableRegions,
newActiveStatementsInChangedDocuments,
out var activeStatementsInUpdatedMethods,
out var nonRemappableRegions,
out var exceptionRegionUpdates);
// Note: Since no method have been remapped yet all the following spans are in their pre-remap locations:
AssertEx.Equal(
[
$"0x06000001 v2 | AS {document.FilePath}: (6,18)-(6,22) => (6,18)-(6,22)",
$"0x06000002 v2 | ER {document.FilePath}: (18,16)-(21,9) => (17,16)-(20,9)",
$"0x06000002 v2 | AS {document.FilePath}: (20,18)-(20,22) => (19,18)-(19,22)",
$"0x06000003 v1 | AS {document.FilePath}: (30,22)-(30,26) => (29,22)-(29,26)", // AS:2 moved -1 in first edit, 0 in second
$"0x06000003 v1 | ER {document.FilePath}: (32,20)-(34,13) => (34,20)-(36,13)", // ER:2.0 moved +2 in first edit, 0 in second
$"0x06000003 v1 | ER {document.FilePath}: (36,16)-(38,9) => (38,16)-(40,9)", // ER:2.0 moved +2 in first edit, 0 in second
$"0x06000004 v1 | ER {document.FilePath}: (50,20)-(53,13) => (53,20)-(56,13)", // ER:3.0 moved +1 in first edit, +2 in second
$"0x06000004 v1 | AS {document.FilePath}: (52,22)-(52,26) => (55,22)-(55,26)", // AS:3 moved +1 in first edit, +2 in second
$"0x06000004 v1 | ER {document.FilePath}: (55,16)-(57,9) => (58,16)-(60,9)", // ER:3.1 moved +1 in first edit, +2 in second
], nonRemappableRegions.OrderBy(r => r.Region.OldSpan.Span.Start.Line).Select(r => $"{r.Method.GetDebuggerDisplay()} | {r.Region.GetDebuggerDisplay()}"));
AssertEx.Equal(
[
$"0x06000002 v2 | (17,16)-(20,9) Delta=1",
$"0x06000003 v1 | (34,20)-(36,13) Delta=-2",
$"0x06000003 v1 | (38,16)-(40,9) Delta=-2",
$"0x06000004 v1 | (53,20)-(56,13) Delta=-3",
$"0x06000004 v1 | (58,16)-(60,9) Delta=-3",
], exceptionRegionUpdates.OrderBy(r => r.NewSpan.StartLine).Select(InspectExceptionRegionUpdate));
AssertEx.Equal(
[
$"0x06000002 v2 IL_0000: (19,18)-(19,22) '<AS:1>M();</AS:1>'",
$"0x06000004 v1 IL_0000: (55,22)-(55,26) '<AS:3>M();</AS:3>'"
], activeStatementsInUpdatedMethods.Select(update => $"{InspectActiveStatementUpdate(update)} '{GetFirstLineText(update.NewSpan.ToLinePositionSpan(), sourceTextV3)}'"));
}
[Fact]
public async Task BaseActiveStatementsAndExceptionRegions_Recursion()
{
var markedSources = new[]
{
@"class C
{
static void M()
{
try
{
<AS:1>M();</AS:1>
}
catch (Exception e)
{
}
}
static void F()
{
<AS:0>M();</AS:0>
}
}"
};
var thread1 = Guid.NewGuid();
var thread2 = Guid.NewGuid();
// Thread1 stack trace: F (AS:0), M (AS:1 leaf)
// Thread2 stack trace: F (AS:0), M (AS:1), M (AS:1 leaf)
var activeStatements = GetActiveStatementDebugInfosCSharp(
markedSources,
methodRowIds: [1, 2],
ilOffsets: [1, 1],
flags:
[
ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.NonUserCode | ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.MethodUpToDate,
ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.LeafFrame | ActiveStatementFlags.MethodUpToDate
]);
using var workspace = new TestWorkspace(composition: s_composition);
var solution = await AddDefaultTestSolutionAsync(workspace, markedSources);
var project = solution.Projects.Single();
var document = project.Documents.Single();
var editSession = CreateEditSession(solution, activeStatements);
var baseActiveStatementMap = await editSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
// Active Statements
Assert.Equal(1, baseActiveStatementMap.DocumentPathMap.Count);
AssertEx.Equal(
[
$"1: {document.FilePath}: (6,18)-(6,22) flags=[LeafFrame, MethodUpToDate, NonLeafFrame]",
$"0: {document.FilePath}: (15,14)-(15,18) flags=[PartiallyExecuted, NonUserCode, MethodUpToDate, NonLeafFrame]",
], baseActiveStatementMap.DocumentPathMap[document.FilePath].Select(InspectActiveStatement));
Assert.Equal(2, baseActiveStatementMap.InstructionMap.Count);
var statements = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.InstructionId.Method.Token).ToArray();
var s = statements[0];
Assert.Equal(0x06000001, s.InstructionId.Method.Token);
Assert.Equal(document.FilePath, s.FilePath);
Assert.True(s.IsNonLeaf);
s = statements[1];
Assert.Equal(0x06000002, s.InstructionId.Method.Token);
Assert.Equal(document.FilePath, s.FilePath);
Assert.True(s.IsLeaf);
Assert.True(s.IsNonLeaf);
// Exception Regions
var analyzer = solution.GetProject(project.Id).Services.GetRequiredService<IEditAndContinueAnalyzer>();
var oldActiveStatements = await baseActiveStatementMap.GetOldActiveStatementsAsync(analyzer, document, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(
[
$"[{document.FilePath}: (8,8)-(10,9)]",
"[]"
], oldActiveStatements.Select(s => "[" + string.Join(",", s.ExceptionRegions.Spans) + "]"));
}
}
}
|