未验证 提交 5f86d25c 编写于 作者: T Tomáš Matoušek 提交者: GitHub

EnC: Tweak handling of out-of-sync documents to work around source file...

EnC: Tweak handling of out-of-sync documents to work around source file content inconsistencies (#40947)

* Do not block in presence of out-of-sync documents.

Instead, ignore any changes made to these documents while debugging until their content matches the source used to build the baseline DLL.

* Only check output PDB, not debugger SymReader, for document checksums.
Turns out SymReader does not support reading document checksums once EnC changes have been applied.

Better handle errors that might occur when validating checksums. Previously some of the errors were not reported as diagnostics.

We previously blocked EnC when we observed a source file that is out-of-sync (i.e. its current content does not match the checksum in the originally built PDB). We can however just ignore these files and report a warning that changes made to this file won’t be applied until the file content is reverted back to the state it was when the PDB was built (the file transitions to “matching” state). Once a file is in matching state it can’t change back to another state. We know that we have not applied any change to the code that was compiled from the file because we ignored the file while it was in out-of-sync state. Therefore we know that any changes made from now on can be safely applied to the debuggee.

If we can’t determine whether a file matches or not due to error reading the PDB or the source file content we can treat it similarly to out-of-sync file. That is, ignore any changes until we are able to confirm the file matches. That can happen if, e.g. the PDB is temporarily locked by another process and unlocked later.

Simplify implementation of GetStatusAsync.

Fixes VSO [1051496](https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1051496) ([VS feedback](https://developercommunity.visualstudio.com/content/problem/880533/edits-were-made-which-cannot-be-compiled-stop-debu.html))
上级 71028213
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
......@@ -332,12 +333,14 @@ public static PathKind GetPathKind(string path)
return PathKind.Relative;
}
#nullable enable
/// <summary>
/// True if the path is an absolute path (rooted to drive or network share)
/// </summary>
public static bool IsAbsolute(string path)
public static bool IsAbsolute([NotNullWhen(true)]string? path)
{
if (string.IsNullOrEmpty(path))
if (RoslynString.IsNullOrEmpty(path))
{
return false;
}
......@@ -361,6 +364,8 @@ public static bool IsAbsolute(string path)
IsDirectorySeparator(path[1]);
}
#nullable restore
/// <summary>
/// Returns true if given path is absolute and starts with a drive specification ("C:\").
/// </summary>
......
......@@ -88,7 +88,7 @@ private EditAndContinueWorkspaceService CreateEditAndContinueService(Workspace w
_mockDebugeeModuleMetadataProvider,
reportTelemetry: data => EditAndContinueWorkspaceService.LogDebuggingSessionTelemetry(data, (id, message) => _telemetryLog.Add($"{id}: {message.GetMessage()}"), () => ++_telemetryId));
private DebuggingSession StartDebuggingSession(EditAndContinueWorkspaceService service, CommittedSolution.DocumentState initialState = CommittedSolution.DocumentState.MatchesDebuggee)
private DebuggingSession StartDebuggingSession(EditAndContinueWorkspaceService service, CommittedSolution.DocumentState initialState = CommittedSolution.DocumentState.MatchesBuildOutput)
{
service.StartDebuggingSession();
var session = service.Test_GetDebuggingSession();
......@@ -280,8 +280,7 @@ public async Task RunMode_DesignTimeOnlyDocument()
Assert.Empty(diagnostics);
// validate solution update status and emit - changes made in design-time-only documents are ignored:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndDebuggingSession();
VerifyReanalyzeInvocation(workspace, null, ImmutableArray<DocumentId>.Empty, false);
......@@ -319,8 +318,9 @@ public async Task RunMode_ProjectNotBuilt()
}
[Fact]
public async Task RunMode_ErrorReadingFile()
public async Task RunMode_ErrorReadingModuleFile()
{
// empty module file will cause read error:
var moduleFile = Temp.CreateFile();
using (var workspace = TestWorkspace.CreateCSharp("class C1 { void M() { System.Console.WriteLine(1); } }"))
......@@ -347,8 +347,7 @@ public async Task RunMode_ErrorReadingFile()
Assert.Empty(diagnostics);
// validate solution update status and emit - changes made during run mode are ignored:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -386,7 +385,7 @@ public async Task RunMode_DocumentOutOfSync()
Assert.Empty(diagnostics);
// the document is now in-sync (a file watcher observed a change and updated the status):
debuggingSession.LastCommittedSolution.Test_SetDocumentState(document2.Id, CommittedSolution.DocumentState.MatchesDebuggee);
debuggingSession.LastCommittedSolution.Test_SetDocumentState(document2.Id, CommittedSolution.DocumentState.MatchesBuildOutput);
diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(new[] { "ENC1003" }, diagnostics.Select(d => d.Id));
......@@ -428,9 +427,7 @@ public async Task RunMode_FileAdded()
var diagnostics2 = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(new[] { "ENC1003" }, diagnostics2.Select(d => d.Id));
// validate solution update status and emit - changes made during run mode are ignored:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndDebuggingSession();
VerifyReanalyzeInvocation(workspace, null, ImmutableArray.Create(document2.Id), false);
......@@ -455,8 +452,7 @@ public async Task RunMode_Diagnostics()
var service = CreateEditAndContinueService(workspace);
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
StartDebuggingSession(service);
......@@ -465,16 +461,14 @@ public async Task RunMode_Diagnostics()
var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, CancellationToken.None).ConfigureAwait(false);
Assert.Empty(diagnostics);
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
// change the source:
workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }"));
var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single();
// validate solution update status and emit - changes made during run mode are ignored:
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -521,8 +515,7 @@ public async Task RunMode_DifferentDocumentWithSameContent()
Assert.Empty(diagnostics2);
// validate solution update status and emit - changes made during run mode are ignored:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndDebuggingSession();
......@@ -556,8 +549,7 @@ public async Task BreakMode_ProjectThatDoesNotSupportEnC()
var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single();
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -597,8 +589,7 @@ public async Task BreakMode_DesignTimeOnlyDocument_Dynamic()
workspace.ChangeDocument(document1.Id, SourceText.From("class E {}"));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -658,8 +649,7 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad)
Assert.Empty(await service.GetDocumentDiagnosticsAsync(documentC2, CancellationToken.None).ConfigureAwait(false));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -670,8 +660,7 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad)
LoadLibraryToDebuggee(moduleInfo);
// validate solution update status and emit:
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -683,8 +672,9 @@ public async Task BreakMode_DesignTimeOnlyDocument_Wpf(bool delayLoad)
}
[Fact]
public async Task BreakMode_ErrorReadingFile()
public async Task BreakMode_ErrorReadingModuleFile()
{
// module file is empty, which will cause a read error:
var moduleFile = Temp.CreateFile();
using (var workspace = TestWorkspace.CreateCSharp("class C1 { void M() { System.Console.WriteLine(1); } }"))
......@@ -706,9 +696,7 @@ public async Task BreakMode_ErrorReadingFile()
var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
Assert.Empty(diagnostics);
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(0, _emitDiagnosticsClearedCount);
......@@ -742,6 +730,161 @@ public async Task BreakMode_ErrorReadingFile()
}
}
[Fact]
public async Task BreakMode_ErrorReadingPdbFile()
{
var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
var dir = Temp.CreateDirectory();
var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1);
using var workspace = new TestWorkspace();
var document1 = workspace.CurrentSolution.
AddProject("test", "test", LanguageNames.CSharp).
AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path);
var project = document1.Project;
workspace.ChangeSolution(project.Solution);
var (_, moduleId) = EmitAndLoadLibraryToDebuggee(source1, project.Id, sourceFilePath: sourceFile.Path);
_mockCompilationOutputsService.Outputs[project.Id] = new MockCompilationOutputs(moduleId)
{
OpenPdbStreamImpl = () =>
{
throw new IOException("Error");
}
};
var service = CreateEditAndContinueService(workspace);
StartDebuggingSession(service, initialState: CommittedSolution.DocumentState.None);
service.StartEditSession();
// change the source:
workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8));
var document2 = workspace.CurrentSolution.GetDocument(document1.Id);
// error not reported here since it might be intermittent and will be reported if the issue persist when applying the update:
var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
Assert.Empty(diagnostics);
// an error occured so we need to call update to determine whether we have changes to apply or not:
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(0, _emitDiagnosticsClearedCount);
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
Assert.Empty(deltas);
Assert.Equal(1, _emitDiagnosticsClearedCount);
var eventArgs = _emitDiagnosticsUpdated.Single();
Assert.Null(eventArgs.DocumentId);
Assert.Equal(project.Id, eventArgs.ProjectId);
AssertEx.Equal(new[] { "Warning ENC1006: " + string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path) },
eventArgs.Diagnostics.Select(d => $"{d.Severity} {d.Id}: {d.Message}"));
_emitDiagnosticsUpdated.Clear();
_emitDiagnosticsClearedCount = 0;
service.EndEditSession();
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(0, _emitDiagnosticsClearedCount);
service.EndDebuggingSession();
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(1, _emitDiagnosticsClearedCount);
AssertEx.Equal(new[]
{
"Debugging_EncSession: SessionId=1|SessionCount=0|EmptySessionCount=1"
}, _telemetryLog);
}
[Fact]
public async Task BreakMode_ErrorReadingSourceFile()
{
var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
var dir = Temp.CreateDirectory();
var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1);
using var workspace = new TestWorkspace();
var document1 = workspace.CurrentSolution.
AddProject("test", "test", LanguageNames.CSharp).
AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path);
var project = document1.Project;
workspace.ChangeSolution(project.Solution);
var (_, moduleId) = EmitAndLoadLibraryToDebuggee(source1, project.Id, sourceFilePath: sourceFile.Path);
var service = CreateEditAndContinueService(workspace);
StartDebuggingSession(service, initialState: CommittedSolution.DocumentState.None);
service.StartEditSession();
// change the source:
workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", Encoding.UTF8));
var document2 = workspace.CurrentSolution.GetDocument(document1.Id);
using var fileLock = File.Open(sourceFile.Path, FileMode.Open, FileAccess.Read, FileShare.None);
// error not reported here since it might be intermittent and will be reported if the issue persist when applying the update:
var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
Assert.Empty(diagnostics);
// an error occured so we need to call update to determine whether we have changes to apply or not:
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(0, _emitDiagnosticsClearedCount);
// try apply changes:
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
Assert.Empty(deltas);
Assert.Equal(1, _emitDiagnosticsClearedCount);
var eventArgs = _emitDiagnosticsUpdated.Single();
Assert.Null(eventArgs.DocumentId);
Assert.Equal(project.Id, eventArgs.ProjectId);
AssertEx.Equal(new[] { "Warning ENC1006: " + string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path) },
eventArgs.Diagnostics.Select(d => $"{d.Severity} {d.Id}: {d.Message}"));
_emitDiagnosticsUpdated.Clear();
_emitDiagnosticsClearedCount = 0;
fileLock.Dispose();
// try apply changes again:
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
Assert.NotEmpty(deltas);
Assert.Equal(1, _emitDiagnosticsClearedCount);
Assert.Empty(_emitDiagnosticsUpdated);
_emitDiagnosticsClearedCount = 0;
service.EndEditSession();
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(0, _emitDiagnosticsClearedCount);
service.EndDebuggingSession();
Assert.Empty(_emitDiagnosticsUpdated);
Assert.Equal(1, _emitDiagnosticsClearedCount);
AssertEx.Equal(new[]
{
"Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0",
"Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0"
}, _telemetryLog);
}
[Fact]
public async Task BreakMode_FileAdded()
{
......@@ -775,9 +918,7 @@ public async Task BreakMode_FileAdded()
var diagnostics2 = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(new[] { "ENC2123" }, diagnostics2.Select(d => d.Id));
// validate solution update status and emit - changes made during run mode are ignored:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
service.EndDebuggingSession();
......@@ -824,7 +965,7 @@ void M()
"[136..137): " + expectedMessage,
};
string inspectDiagnostic(Diagnostic d)
static string inspectDiagnostic(Diagnostic d)
=> $"{d.Location.SourceSpan}: {d.Id}: {d.GetMessage()}";
using (var workspace = TestWorkspace.CreateCSharp(source1))
......@@ -871,8 +1012,7 @@ string inspectDiagnostic(Diagnostic d)
AssertEx.Equal(expectedDiagnostics, diagnostics3.Select(inspectDiagnostic));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -930,8 +1070,7 @@ public async Task BreakMode_Encodings()
await debuggingSession.LastCommittedSolution.OnSourceFileUpdatedAsync(documentId, debuggingSession.CancellationToken).ConfigureAwait(false);
// EnC service queries for a document, which triggers read of the source file from disk.
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
service.EndDebuggingSession();
......@@ -964,8 +1103,7 @@ public async Task BreakMode_RudeEdits()
diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}"));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -991,60 +1129,76 @@ public async Task BreakMode_RudeEdits()
[Fact]
public async Task BreakMode_RudeEdits_DocumentOutOfSync()
{
var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }";
var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
using var workspace = TestWorkspace.CreateCSharp(source1);
var dir = Temp.CreateDirectory();
var sourceFile = dir.CreateFile("a.cs");
var project = workspace.CurrentSolution.Projects.Single();
var (_, moduleId) = EmitAndLoadLibraryToDebuggee(source1, project.Id);
var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single();
using var workspace = new TestWorkspace();
var service = CreateEditAndContinueService(workspace);
var project = workspace.CurrentSolution.
AddProject("test", "test", LanguageNames.CSharp).
AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40));
var debuggingSession = StartDebuggingSession(service);
debuggingSession.LastCommittedSolution.Test_SetDocumentState(document1.Id, CommittedSolution.DocumentState.OutOfSync);
workspace.ChangeSolution(project.Solution);
// compile with source0:
var (_, moduleId) = EmitAndLoadLibraryToDebuggee(source0, project.Id, sourceFilePath: sourceFile.Path);
// update the file with source1 before session starts:
sourceFile.WriteAllText(source1);
// source1 is reflected in workspace before session starts:
var document1 = project.AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8), filePath: sourceFile.Path);
workspace.ChangeSolution(document1.Project.Solution);
var service = CreateEditAndContinueService(workspace);
var debuggingSession = StartDebuggingSession(service, initialState: CommittedSolution.DocumentState.None);
service.StartEditSession();
VerifyReanalyzeInvocation(workspace, null, ImmutableArray<DocumentId>.Empty, false);
// change the source (rude edit):
workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void RenamedMethod() { System.Console.WriteLine(1); } }"));
var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single();
var document2 = workspace.CurrentSolution.GetDocument(document1.Id);
// no Rude Edits, since the document is out-of-sync
var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
Assert.Empty(diagnostics);
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
// since the document is out-of-sync we need to call update to determine whether we have changes to apply or not:
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
Assert.Empty(deltas);
AssertEx.Equal(
new[] { "ENC1005: " + string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, "test1.cs") },
new[] { "ENC1005: " + string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path) },
_emitDiagnosticsUpdated.Single().Diagnostics.Select(d => $"{d.Id}: {d.Message}"));
_emitDiagnosticsUpdated.Clear();
_emitDiagnosticsClearedCount = 0;
// the document is now in-sync (a file watcher observed a change and updated the status):
debuggingSession.LastCommittedSolution.Test_SetDocumentState(document1.Id, CommittedSolution.DocumentState.MatchesDebuggee);
// update the file to match the build:
sourceFile.WriteAllText(source0);
// we do not reload the content of out-of-sync file for analyzer query:
diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(
new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) },
diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
Assert.Empty(diagnostics);
// validate solution update status and emit:
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
// debugger query will trigger reload of out-of-sync file content:
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
// now we see the rude edit:
diagnostics = await service.GetDocumentDiagnosticsAsync(document2, CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(new[] { "ENC0020" }, diagnostics.Select(d => d.Id));
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
Assert.Empty(deltas);
Assert.Empty(_emitDiagnosticsUpdated);
service.EndEditSession();
VerifyReanalyzeInvocation(workspace, null, ImmutableArray.Create(document2.Id), false);
......@@ -1100,8 +1254,7 @@ public async Task BreakMode_RudeEdits_DocumentWithoutSequencePoints()
diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -1148,8 +1301,7 @@ public async Task BreakMode_RudeEdits_DelayLoadedModule()
new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) },
diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -1164,8 +1316,7 @@ public async Task BreakMode_RudeEdits_DelayLoadedModule()
new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) },
diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -1202,8 +1353,7 @@ public async Task BreakMode_SyntaxError()
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -1253,8 +1403,7 @@ public async Task BreakMode_SemanticError()
// The EnC analyzer does not check for and block on all semantic errors as they are already reported by diagnostic analyzer.
// Blocking update on semantic errors would be possible, but the status check is only an optimization to avoid emitting.
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
......@@ -1310,16 +1459,13 @@ public async Task BreakMode_FileStatus_CompilationError()
workspace.ChangeDocument(documentC.Id, SourceText.From("class C { void M() { "));
// Common.cs is included in projects B and C. Both of these projects must have no errors, otherwise update is blocked.
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: "Common.cs", CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: "Common.cs", CancellationToken.None).ConfigureAwait(false));
// No changes in project containing file B.cs.
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: "B.cs", CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: "B.cs", CancellationToken.None).ConfigureAwait(false));
// All projects must have no errors.
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
service.EndDebuggingSession();
......@@ -1353,8 +1499,7 @@ public async Task BreakMode_ValidSignificantChange_EmitError()
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
AssertEx.Equal(new[] { "CS8055" }, _emitDiagnosticsUpdated.Single().Diagnostics.Select(d => d.Id));
......@@ -1378,8 +1523,7 @@ public async Task BreakMode_ValidSignificantChange_EmitError()
Assert.Empty(editSession.DebuggingSession.GetBaselineModuleReaders());
// solution update status after discarding an update (still has update ready):
var commitedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, commitedUpdateSolutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
Assert.Empty(_emitDiagnosticsUpdated);
......@@ -1455,10 +1599,9 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b
}
// EnC service queries for a document, which triggers read of the source file from disk.
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
service.CommitSolutionUpdate();
......@@ -1470,17 +1613,17 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b
workspace.ChangeDocument(documentId, CreateSourceTextFromFile(sourceFile.Path));
var document3 = workspace.CurrentSolution.Projects.Single().Documents.Single();
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
var hasChanges = await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
if (saveDocument)
{
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(hasChanges);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
}
else
{
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(hasChanges);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
}
......@@ -1491,9 +1634,9 @@ public async Task BreakMode_ValidSignificantChange_ApplyBeforeFileWatcherEvent(b
[Fact]
public async Task BreakMode_ValidSignificantChange_FileUpdateBeforeDebuggingSessionStarts()
{
// workspace: --V0--------------V2-------|--------V3---------------V1--------------|
// file system: --V0---------V1-----V2-----|---------------------------V1------------|
// \--build--/ ^save F5 ^ ^F10 (blocked) ^save F10 (ok)
// workspace: --V0--------------V2-------|--------V3------------------V1--------------|
// file system: --V0---------V1-----V2-----|------------------------------V1------------|
// \--build--/ ^save F5 ^ ^F10 (no change) ^save F10 (ok)
// file watcher: no-op
var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
......@@ -1533,11 +1676,11 @@ public async Task BreakMode_ValidSignificantChange_FileUpdateBeforeDebuggingSess
var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, CancellationToken.None).ConfigureAwait(false);
AssertEx.Empty(diagnostics);
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
// since the document is out-of-sync we need to call update to determine whether we have changes to apply or not:
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatus);
Assert.Equal(SolutionUpdateStatus.Blocked, solutionStatusEmit);
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
AssertEx.Equal(
new[] { "ENC1005: " + string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path) },
......@@ -1556,11 +1699,10 @@ public async Task BreakMode_ValidSignificantChange_FileUpdateBeforeDebuggingSess
Assert.Equal(CommittedSolution.DocumentState.OutOfSync, state);
sourceFile.WriteAllText(source1);
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
// the content actually hasn't changed:
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
service.EndEditSession();
......@@ -1603,8 +1745,7 @@ public async Task BreakMode_ValidSignificantChange_DocumentOutOfSync(bool delayL
VerifyReanalyzeInvocation(workspace, null, ImmutableArray<DocumentId>.Empty, false);
// no changes have been made to the project
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -1623,8 +1764,7 @@ public async Task BreakMode_ValidSignificantChange_DocumentOutOfSync(bool delayL
Assert.Empty(diagnostics);
// the content of the file is now exactly the same as the compiled document, so there is no change to be applied:
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
(solutionStatusEmit, _) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, solutionStatusEmit);
......@@ -1675,8 +1815,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful(bool commitUpd
AssertEx.Empty(diagnostics1);
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
AssertEx.Empty(emitDiagnosticsUpdated);
......@@ -1718,8 +1857,8 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful(bool commitUpd
Assert.Same(newBaseline, editSession.DebuggingSession.Test_GetProjectEmitBaseline(project.Id));
// solution update status after committing an update:
var commitedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, commitedUpdateSolutionStatus);
var commitedUpdateSolutionStatus = await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.False(commitedUpdateSolutionStatus);
}
else
{
......@@ -1729,8 +1868,8 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful(bool commitUpd
Assert.Null(service.Test_GetPendingSolutionUpdate());
// solution update status after committing an update:
var discardedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, discardedUpdateSolutionStatus);
var discardedUpdateSolutionStatus = await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.True(discardedUpdateSolutionStatus);
}
service.EndEditSession();
......@@ -1804,8 +1943,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred
var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single();
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
......@@ -1851,8 +1989,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred
Assert.Same(newBaseline, editSession.DebuggingSession.Test_GetProjectEmitBaseline(project.Id));
// solution update status after committing an update:
var commitedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, commitedUpdateSolutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
......@@ -1952,8 +2089,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
workspace.ChangeDocument(projectB.Documents.Single().Id, SourceText.From(source2, Encoding.UTF8));
// validate solution update status and emit:
var solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
var (solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
......@@ -1995,8 +2131,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
Assert.Same(newBaselineB1, editSession.DebuggingSession.Test_GetProjectEmitBaseline(projectB.Id));
// solution update status after committing an update:
var commitedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, commitedUpdateSolutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
service.StartEditSession();
......@@ -2010,8 +2145,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
workspace.ChangeDocument(projectB.Documents.Single().Id, SourceText.From(source3, Encoding.UTF8));
// validate solution update status and emit:
solutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatus);
Assert.True(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
(solutionStatusEmit, deltas) = await service.EmitSolutionUpdateAsync(CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.Ready, solutionStatusEmit);
......@@ -2052,8 +2186,7 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule()
Assert.Same(newBaselineB2, editSession.DebuggingSession.Test_GetProjectEmitBaseline(projectB.Id));
// solution update status after committing an update:
commitedUpdateSolutionStatus = await service.GetSolutionUpdateStatusAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false);
Assert.Equal(SolutionUpdateStatus.None, commitedUpdateSolutionStatus);
Assert.False(await service.HasChangesAsync(sourceFilePath: null, CancellationToken.None).ConfigureAwait(false));
service.EndEditSession();
......
......@@ -97,7 +97,7 @@ private sealed class Validator
ImmutableArray<ActiveStatementDebugInfo> activeStatements,
ImmutableDictionary<ActiveMethodId, ImmutableArray<NonRemappableRegion>> nonRemappableRegions = null,
Func<Solution, Solution> adjustSolution = null,
CommittedSolution.DocumentState initialState = CommittedSolution.DocumentState.MatchesDebuggee)
CommittedSolution.DocumentState initialState = CommittedSolution.DocumentState.MatchesBuildOutput)
{
var exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(
TestExportProvider.MinimumCatalogWithCSharpAndVisualBasic.WithPart(typeof(CSharpEditAndContinueAnalyzer)).WithPart(typeof(DummyLanguageService)));
......@@ -544,7 +544,7 @@ static void M()
}, baseExceptionRegions.Select(r => r.Spans.IsDefault ? "out-of-sync" : "[" + string.Join(",", r.Spans) + "]"));
// document got synchronized:
validator.EditSession.DebuggingSession.LastCommittedSolution.Test_SetDocumentState(docs[0], CommittedSolution.DocumentState.MatchesDebuggee);
validator.EditSession.DebuggingSession.LastCommittedSolution.Test_SetDocumentState(docs[0], CommittedSolution.DocumentState.MatchesBuildOutput);
baseExceptionRegions = await validator.EditSession.GetBaseActiveExceptionRegionsAsync(CancellationToken.None).ConfigureAwait(false);
......
......@@ -34,41 +34,31 @@ internal enum DocumentState
{
None = 0,
/// <summary>
/// The current document content matches the content the built module was compiled with.
/// The document content is matched with the build output instead of the loaded module
/// since the module hasn't been loaded yet.
///
/// This document state may change to <see cref="OutOfSync"/>, <see cref="MatchesDebuggee"/>,
/// or <see cref="DesignTimeOnly"/> or once the module has been loaded.
/// </summary>
MatchesBuildOutput = 1,
/// <summary>
/// The current document content does not match the content the module was compiled with.
/// This document state may change to <see cref="MatchesDebuggee"/> or <see cref="DesignTimeOnly"/>.
/// This document state may change to <see cref="MatchesBuildOutput"/> or <see cref="DesignTimeOnly"/>.
/// </summary>
OutOfSync = 2,
OutOfSync = 1,
/// <summary>
/// The current document content matches the content the loaded module was compiled with.
/// This is a final state. Once a document is in this state it won't switch to a different one.
/// It hasn't been possible to determine whether the current document content does matches the content
/// the module was compiled with due to error while reading the PDB or the source file.
/// This document state may change to <see cref="MatchesBuildOutput"/> or <see cref="DesignTimeOnly"/>.
/// </summary>
MatchesDebuggee = 3,
Indeterminate = 2,
/// <summary>
/// The document is not compiled into the module. It's only included in the project
/// to support design-time features such as completion, etc.
/// This is a final state. Once a document is in this state it won't switch to a different one.
/// </summary>
DesignTimeOnly = 4,
}
DesignTimeOnly = 3,
private enum SourceHashOrigin
{
None = 0,
LoadedPdb = 1,
BuiltPdb = 2
/// <summary>
/// The current document content matches the content the built module was compiled with.
/// This is a final state. Once a document is in this state it won't switch to a different one.
/// </summary>
MatchesBuildOutput = 4
}
/// <summary>
......@@ -93,8 +83,8 @@ private enum SourceHashOrigin
/// from which the assembly is built. These documents won't have a record in the PDB and will be tracked as
/// <see cref="DocumentState.DesignTimeOnly"/>.
///
/// A document state can only change from <see cref="DocumentState.OutOfSync"/> to <see cref="DocumentState.MatchesDebuggee"/>.
/// Once a document state is <see cref="DocumentState.MatchesDebuggee"/> or <see cref="DocumentState.DesignTimeOnly"/>
/// A document state can only change from <see cref="DocumentState.OutOfSync"/> to <see cref="DocumentState.MatchesBuildOutput"/>.
/// Once a document state is <see cref="DocumentState.MatchesBuildOutput"/> or <see cref="DocumentState.DesignTimeOnly"/>
/// it will never change.
/// </summary>
private readonly Dictionary<DocumentId, DocumentState> _documentState;
......@@ -148,7 +138,6 @@ public Task OnSourceFileUpdatedAsync(DocumentId documentId, CancellationToken ca
public async Task<(Document? Document, DocumentState State)> GetDocumentAndStateAsync(DocumentId documentId, CancellationToken cancellationToken, bool reloadOutOfSyncDocument = false)
{
Document? document;
var matchLoadedModulesOnly = false;
lock (_guard)
{
......@@ -158,29 +147,16 @@ public async Task<(Document? Document, DocumentState State)> GetDocumentAndState
return (null, DocumentState.None);
}
if (document.FilePath == null)
{
return (null, DocumentState.DesignTimeOnly);
}
if (_documentState.TryGetValue(documentId, out var documentState))
{
switch (documentState)
{
case DocumentState.MatchesDebuggee:
case DocumentState.MatchesBuildOutput:
return (document, documentState);
case DocumentState.DesignTimeOnly:
return (null, documentState);
case DocumentState.MatchesBuildOutput:
// Module might have been loaded since the last time we checked,
// let's check whether that is so and the document now matches the debuggee.
// Do not try to read the information from on-disk module again.
// CONSIDER: Reusing the state until we receive module load event.
matchLoadedModulesOnly = true;
break;
case DocumentState.OutOfSync:
if (reloadOutOfSyncDocument)
{
......@@ -189,23 +165,33 @@ public async Task<(Document? Document, DocumentState State)> GetDocumentAndState
return (null, documentState);
case DocumentState.Indeterminate:
// Previous attempt resulted in a read error. Try again.
break;
case DocumentState.None:
throw ExceptionUtilities.Unreachable;
}
}
if (!PathUtilities.IsAbsolute(document.FilePath))
{
return (null, DocumentState.DesignTimeOnly);
}
}
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var (matchingSourceText, checksumOrigin, isDocumentMissing) = await TryGetPdbMatchingSourceTextAsync(
document.FilePath, sourceText.Encoding, document.Project.Id, matchLoadedModulesOnly, cancellationToken).ConfigureAwait(false);
var (matchingSourceText, pdbHasDocument) = await Task.Run(
() => TryGetPdbMatchingSourceText(document.FilePath, sourceText.Encoding, document.Project.Id),
cancellationToken).ConfigureAwait(false);
lock (_guard)
{
// only listed document states can be changed:
if (_documentState.TryGetValue(documentId, out var documentState) &&
documentState != DocumentState.OutOfSync &&
documentState != DocumentState.MatchesBuildOutput)
documentState != DocumentState.Indeterminate)
{
return (document, documentState);
}
......@@ -213,24 +199,15 @@ public async Task<(Document? Document, DocumentState State)> GetDocumentAndState
DocumentState newState;
Document? matchingDocument;
if (checksumOrigin == SourceHashOrigin.None)
{
// We know the document matches the build output and the module is still not loaded.
if (matchLoadedModulesOnly)
if (pdbHasDocument == null)
{
return (document, DocumentState.MatchesBuildOutput);
// Unable to determine due to error reading the PDB or the source file.
return (document, DocumentState.Indeterminate);
}
// PDB for the module not found (neither loaded nor in built outputs):
Debug.Assert(isDocumentMissing);
return (null, DocumentState.DesignTimeOnly);
}
if (isDocumentMissing)
if (pdbHasDocument == false)
{
// Source file is not listed in the PDB. This may happen for a couple of reasons:
// The library wasn't built with that source file - the file has been added before debugging session started but after build captured it.
// This is the case for WPF .g.i.cs files.
// Source file is not listed in the PDB (e.g. WPF .g.i.cs files).
matchingDocument = null;
newState = DocumentState.DesignTimeOnly;
}
......@@ -246,7 +223,7 @@ public async Task<(Document? Document, DocumentState State)> GetDocumentAndState
matchingDocument = _solution.GetDocument(documentId);
}
newState = (checksumOrigin == SourceHashOrigin.LoadedPdb) ? DocumentState.MatchesDebuggee : DocumentState.MatchesBuildOutput;
newState = DocumentState.MatchesBuildOutput;
}
else
{
......@@ -259,42 +236,20 @@ public async Task<(Document? Document, DocumentState State)> GetDocumentAndState
}
}
public void CommitSolution(Solution solution, ImmutableArray<Document> updatedDocuments)
public void CommitSolution(Solution solution)
{
lock (_guard)
{
// Changes in the updated documents has just been applied to the debuggee process.
// Therefore, these documents now match exactly the state of the debuggee.
foreach (var document in updatedDocuments)
{
// Changes in design-time-only documents should have been ignored.
Debug.Assert(_documentState[document.Id] != DocumentState.DesignTimeOnly);
_documentState[document.Id] = DocumentState.MatchesDebuggee;
Debug.Assert(document.Project.Solution == solution);
}
_solution = solution;
}
}
private async Task<(SourceText? Source, SourceHashOrigin ChecksumOrigin, bool IsDocumentMissing)> TryGetPdbMatchingSourceTextAsync(
string sourceFilePath,
Encoding? encoding,
ProjectId projectId,
bool matchLoadedModulesOnly,
CancellationToken cancellationToken)
{
var (symChecksum, algorithm, origin) = await TryReadSourceFileChecksumFromPdb(sourceFilePath, projectId, matchLoadedModulesOnly, cancellationToken).ConfigureAwait(false);
if (symChecksum.IsDefault)
private (SourceText? Source, bool? HasDocument) TryGetPdbMatchingSourceText(string sourceFilePath, Encoding? encoding, ProjectId projectId)
{
return (Source: null, origin, IsDocumentMissing: true);
}
if (!PathUtilities.IsAbsolute(sourceFilePath))
bool? hasDocument = TryReadSourceFileChecksumFromPdb(sourceFilePath, projectId, out var symChecksum, out var algorithm);
if (hasDocument != true)
{
EditAndContinueWorkspaceService.Log.Write("Error calculating checksum for source file '{0}': path not absolute", sourceFilePath);
return (Source: null, origin, IsDocumentMissing: false);
return (Source: null, hasDocument);
}
try
......@@ -306,82 +261,36 @@ public void CommitSolution(Solution solution, ImmutableArray<Document> updatedDo
// might end up updating the committed solution with a document that has a different encoding than
// the one that's in the workspace, resulting in false document changes when we compare the two.
var sourceText = SourceText.From(fileStream, encoding, checksumAlgorithm: algorithm);
var fileChecksum = sourceText.GetChecksum();
return (sourceText.GetChecksum().SequenceEqual(symChecksum) ? sourceText : null, origin, IsDocumentMissing: false);
}
catch (Exception e)
if (fileChecksum.SequenceEqual(symChecksum))
{
EditAndContinueWorkspaceService.Log.Write("Error calculating checksum for source file '{0}': '{1}'", sourceFilePath, e.Message);
return (Source: null, origin, IsDocumentMissing: false);
}
return (sourceText, hasDocument);
}
private async Task<(ImmutableArray<byte> Checksum, SourceHashAlgorithm Algorithm, SourceHashOrigin Origin)> TryReadSourceFileChecksumFromPdb(string sourceFilePath, ProjectId projectId, bool matchLoadedModulesOnly, CancellationToken cancellationToken)
{
try
{
var (mvid, mvidError) = await _debuggingSession.GetProjectModuleIdAsync(projectId, cancellationToken).ConfigureAwait(false);
if (mvid == Guid.Empty)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match PDB: can't read MVID ('{1}')", sourceFilePath, mvidError);
return default;
EditAndContinueWorkspaceService.Log.Write("Checksum differs for source file '{0}'", sourceFilePath);
return (Source: null, hasDocument);
}
// Dispatch to a background thread - reading symbols from debuggee requires MTA thread.
var (checksum, algorithmId, origin) = (Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA) ?
await Task.Factory.StartNew(ReadChecksum, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default).ConfigureAwait(false) :
ReadChecksum();
if (checksum.IsDefault)
catch (Exception e)
{
return (default, default, origin);
EditAndContinueWorkspaceService.Log.Write("Error calculating checksum for source file '{0}': '{1}'", sourceFilePath, e.Message);
return (Source: null, HasDocument: null);
}
var algorithm = SourceHashAlgorithms.GetSourceHashAlgorithm(algorithmId);
if (algorithm == SourceHashAlgorithm.None)
{
// unknown algorithm:
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match PDB: unknown checksum alg", sourceFilePath);
return (default, default, origin);
}
return (checksum, algorithm, origin);
(ImmutableArray<byte> Checksum, Guid AlgorithmId, SourceHashOrigin Origin) ReadChecksum()
{
try
/// <summary>
/// Returns true if the PDB contains a document record for given <paramref name="sourceFilePath"/>,
/// in which case <paramref name="checksum"/> and <paramref name="algorithm"/> contain its checksum.
/// False if the document is not found in the PDB.
/// Null if it can't be determined because the PDB is not available or an error occured while reading the PDB.
/// </summary>
private bool? TryReadSourceFileChecksumFromPdb(string sourceFilePath, ProjectId projectId, out ImmutableArray<byte> checksum, out SourceHashAlgorithm algorithm)
{
// first try to check against loaded module
cancellationToken.ThrowIfCancellationRequested();
checksum = default;
algorithm = default;
var moduleInfo = _debuggingSession.DebugeeModuleMetadataProvider.TryGetBaselineModuleInfo(mvid);
if (moduleInfo != null)
{
try
{
if (EditAndContinueMethodDebugInfoReader.TryGetDocumentChecksum(moduleInfo.SymReader, sourceFilePath, out var checksum, out var algorithmId))
{
return (checksum, algorithmId, SourceHashOrigin.LoadedPdb);
}
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match loaded PDB: no SymDocument", sourceFilePath);
}
catch (Exception e)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match loaded PDB: error reading symbols: {1}", sourceFilePath, e.Message);
}
return (default, default, SourceHashOrigin.LoadedPdb);
}
if (matchLoadedModulesOnly)
{
return (default, default, SourceHashOrigin.None);
}
// if the module is not loaded check against build output:
cancellationToken.ThrowIfCancellationRequested();
var compilationOutputs = _debuggingSession.CompilationOutputsProvider.GetCompilationOutputs(projectId);
DebugInformationReaderProvider? debugInfoReaderProvider;
......@@ -391,46 +300,49 @@ private async Task<(ImmutableArray<byte> Checksum, SourceHashAlgorithm Algorithm
}
catch (Exception e)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: error opening PDB: {1}", sourceFilePath, e.Message);
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: error opening PDB '{1}': {2}", sourceFilePath, compilationOutputs.PdbDisplayPath, e.Message);
debugInfoReaderProvider = null;
}
if (debugInfoReaderProvider == null)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: PDB not found", sourceFilePath);
return (default, default, SourceHashOrigin.None);
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: PDB '{1}' not found", sourceFilePath, compilationOutputs.PdbDisplayPath);
return null;
}
try
{
var debugInfoReader = debugInfoReaderProvider.CreateEditAndContinueMethodDebugInfoReader();
if (debugInfoReader.TryGetDocumentChecksum(sourceFilePath, out var checksum, out var algorithmId))
if (!debugInfoReader.TryGetDocumentChecksum(sourceFilePath, out checksum, out var algorithmId))
{
return (checksum, algorithmId, SourceHashOrigin.BuiltPdb);
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: no document", sourceFilePath);
return false;
}
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: no SymDocument", sourceFilePath);
return (default, default, SourceHashOrigin.BuiltPdb);
}
catch (Exception e)
algorithm = SourceHashAlgorithms.GetSourceHashAlgorithm(algorithmId);
if (algorithm == SourceHashAlgorithm.None)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: error reading symbols: {1}", sourceFilePath, e.Message);
// This can only happen if the PDB was post-processed by a misbehaving tool.
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match PDB: unknown checksum alg", sourceFilePath);
}
return (default, default, SourceHashOrigin.BuiltPdb);
return true;
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
catch (Exception e)
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match PDB: unexpected exception: {1}", sourceFilePath, e.Message);
return default;
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match output PDB: error reading symbols: {1}", sourceFilePath, e.Message);
}
finally
{
debugInfoReaderProvider.Dispose();
}
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
{
EditAndContinueWorkspaceService.Log.Write("Source '{0}' doesn't match PDB: unexpected exception: {1}", sourceFilePath, e.Message);
return default;
}
return null;
}
}
}
......@@ -186,7 +186,7 @@ public void CommitSolutionUpdate(PendingSolutionUpdate update)
}
}
LastCommittedSolution.CommitSolution(update.Solution, update.ChangedDocuments);
LastCommittedSolution.CommitSolution(update.Solution);
}
/// <summary>
......
......@@ -154,6 +154,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di
AddGeneralDiagnostic(EditAndContinueErrorCode.ChangesDisallowedWhileStoppedAtException, nameof(FeaturesResources.ChangesDisallowedWhileStoppedAtException));
AddGeneralDiagnostic(EditAndContinueErrorCode.ChangesNotAppliedWhileRunning, nameof(FeaturesResources.ChangesNotAppliedWhileRunning), DiagnosticSeverity.Warning);
AddGeneralDiagnostic(EditAndContinueErrorCode.DocumentIsOutOfSyncWithDebuggee, nameof(FeaturesResources.DocumentIsOutOfSyncWithDebuggee), DiagnosticSeverity.Warning);
AddGeneralDiagnostic(EditAndContinueErrorCode.UnableToReadSourceFileOrPdb, nameof(FeaturesResources.UnableToReadSourceFileOrPdb), DiagnosticSeverity.Warning);
s_descriptors = builder.ToImmutable();
}
......
......@@ -9,5 +9,6 @@ internal enum EditAndContinueErrorCode
ChangesNotAppliedWhileRunning = 3,
ChangesDisallowedWhileStoppedAtException = 4,
DocumentIsOutOfSyncWithDebuggee = 5,
UnableToReadSourceFileOrPdb = 6,
}
}
......@@ -205,6 +205,7 @@ public async Task<ImmutableArray<Diagnostic>> GetDocumentDiagnosticsAsync(Docume
var (oldDocument, oldDocumentState) = await debuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(document.Id, cancellationToken).ConfigureAwait(false);
if (oldDocumentState == CommittedSolution.DocumentState.OutOfSync ||
oldDocumentState == CommittedSolution.DocumentState.Indeterminate ||
oldDocumentState == CommittedSolution.DocumentState.DesignTimeOnly)
{
// Do not report diagnostics for existing out-of-sync documents or design-time-only documents.
......@@ -396,7 +397,7 @@ private void ClearReportedRunModeDiagnostics()
/// but does not provide a definitive answer. Only <see cref="EmitSolutionUpdateAsync"/> can definitively determine whether
/// the update is valid or not.
/// </returns>
public Task<SolutionUpdateStatus> GetSolutionUpdateStatusAsync(string sourceFilePath, CancellationToken cancellationToken)
public Task<bool> HasChangesAsync(string? sourceFilePath, CancellationToken cancellationToken)
{
// GetStatusAsync is called outside of edit session when the debugger is determining
// whether a source file checksum matches the one in PDB.
......@@ -404,10 +405,10 @@ public Task<SolutionUpdateStatus> GetSolutionUpdateStatusAsync(string sourceFile
var editSession = _editSession;
if (editSession == null)
{
return Task.FromResult(SolutionUpdateStatus.None);
return Task.FromResult(false);
}
return editSession.GetSolutionUpdateStatusAsync(_workspace.CurrentSolution, sourceFilePath, cancellationToken);
return editSession.HasChangesAsync(_workspace.CurrentSolution, sourceFilePath, cancellationToken);
}
public async Task<(SolutionUpdateStatus Summary, ImmutableArray<Deltas> Deltas)> EmitSolutionUpdateAsync(CancellationToken cancellationToken)
......@@ -428,8 +429,7 @@ public async Task<(SolutionUpdateStatus Summary, ImmutableArray<Deltas> Deltas)>
solution,
solutionUpdate.EmitBaselines,
solutionUpdate.Deltas,
solutionUpdate.ModuleReaders,
solutionUpdate.ChangedDocuments));
solutionUpdate.ModuleReaders));
// commit/discard was not called:
Contract.ThrowIfFalse(previousPendingUpdate == null);
......
......@@ -307,11 +307,34 @@ internal async Task<ImmutableArray<ActiveStatementExceptionRegions>> GetBaseActi
}
}
private async Task<(ImmutableArray<(Document Document, AsyncLazy<DocumentAnalysisResults> Results)>, ImmutableArray<Diagnostic> Diagnostics)> GetChangedDocumentsAnalysesAsync(
Project baseProject, Project project, CancellationToken cancellationToken)
private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolution baseSolution, Project project, ArrayBuilder<Document> changedDocuments, ArrayBuilder<Document> addedDocuments, CancellationToken cancellationToken)
{
var changedDocuments = ArrayBuilder<(Document? Old, Document New)>.GetInstance();
var outOfSyncDiagnostics = ArrayBuilder<Diagnostic>.GetInstance();
changedDocuments.Clear();
addedDocuments.Clear();
if (!EditAndContinueWorkspaceService.SupportsEditAndContinue(project))
{
return;
}
var baseProject = baseSolution.GetProject(project.Id);
if (baseProject == project)
{
return;
}
// When debugging session is started some projects might not have been loaded to the workspace yet.
// We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied
// and will result in source mismatch when the user steps into them.
//
// TODO (https://github.com/dotnet/roslyn/issues/1204):
// hook up the debugger reported error, check that the project has not been loaded and report a better error.
// Here, we assume these projects are not modified.
if (baseProject == null)
{
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not loaded", project.Id.DebugName, project.Id);
return;
}
var changes = project.GetChanges(baseProject);
foreach (var documentId in changes.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true))
......@@ -338,48 +361,70 @@ internal async Task<ImmutableArray<ActiveStatementExceptionRegions>> GetBaseActi
continue;
}
var (oldDocument, oldDocumentState) = await DebuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(documentId, cancellationToken, reloadOutOfSyncDocument: true).ConfigureAwait(false);
changedDocuments.Add(document);
}
foreach (var documentId in changes.GetAddedDocuments())
{
var document = project.GetDocument(documentId)!;
if (EditAndContinueWorkspaceService.IsDesignTimeOnlyDocument(document))
{
continue;
}
addedDocuments.Add(document);
}
}
private async Task<(ImmutableArray<(Document Document, AsyncLazy<DocumentAnalysisResults> Results)>, ImmutableArray<Diagnostic> DocumentDiagnostics)> AnalyzeDocumentsAsync(
ArrayBuilder<Document> changedDocuments, ArrayBuilder<Document> addedDocuments, CancellationToken cancellationToken)
{
var documentDiagnostics = ArrayBuilder<Diagnostic>.GetInstance();
var builder = ArrayBuilder<(Document? Old, Document New)>.GetInstance();
foreach (var document in changedDocuments)
{
var (oldDocument, oldDocumentState) = await DebuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(document.Id, cancellationToken, reloadOutOfSyncDocument: true).ConfigureAwait(false);
switch (oldDocumentState)
{
case CommittedSolution.DocumentState.DesignTimeOnly:
continue;
case CommittedSolution.DocumentState.Indeterminate:
case CommittedSolution.DocumentState.OutOfSync:
var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.DocumentIsOutOfSyncWithDebuggee);
outOfSyncDiagnostics.Add(Diagnostic.Create(descriptor, Location.Create(document.FilePath!, textSpan: default, lineSpan: default), new[] { document.FilePath }));
var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor((oldDocumentState == CommittedSolution.DocumentState.Indeterminate) ?
EditAndContinueErrorCode.UnableToReadSourceFileOrPdb : EditAndContinueErrorCode.DocumentIsOutOfSyncWithDebuggee);
documentDiagnostics.Add(Diagnostic.Create(descriptor, Location.Create(document.FilePath!, textSpan: default, lineSpan: default), new[] { document.FilePath }));
continue;
default:
case CommittedSolution.DocumentState.MatchesBuildOutput:
// Include the document regardless of whether the module it was built into has been loaded or not.
// If the module has been built it might get loaded later during the debugging session,
// at which point we apply all changes that have been made to the project so far.
changedDocuments.Add((oldDocument, document));
builder.Add((oldDocument, document));
break;
default:
throw ExceptionUtilities.UnexpectedValue(oldDocumentState);
}
}
foreach (var documentId in changes.GetAddedDocuments())
{
var document = project.GetDocument(documentId)!;
if (EditAndContinueWorkspaceService.IsDesignTimeOnlyDocument(document))
foreach (var document in addedDocuments)
{
continue;
}
changedDocuments.Add((null, document));
builder.Add((null, document));
}
var result = ImmutableArray<(Document, AsyncLazy<DocumentAnalysisResults>)>.Empty;
if (changedDocuments.Count != 0)
if (builder.Count != 0)
{
lock (_analysesGuard)
{
result = changedDocuments.SelectAsArray(change => (change.New, GetDocumentAnalysisNoLock(change.Old, change.New)));
result = builder.SelectAsArray(change => (change.New, GetDocumentAnalysisNoLock(change.Old, change.New)));
}
}
changedDocuments.Free();
return (result, outOfSyncDiagnostics.ToImmutableAndFree());
builder.Free();
return (result, documentDiagnostics.ToImmutableAndFree());
}
public AsyncLazy<DocumentAnalysisResults> GetDocumentAnalysis(Document? baseDocument, Document document)
......@@ -449,108 +494,78 @@ internal void TrackDocumentWithReportedDiagnostics(DocumentId documentId)
}
/// <summary>
/// Determines the status of projects containing given <paramref name="sourceFilePath"/> or the entire solution if <paramref name="sourceFilePath"/> is null.
/// Determines whether projects contain any changes that might need to be applied.
/// Checks only projects containing a given <paramref name="sourceFilePath"/> or all projects of the solution if <paramref name="sourceFilePath"/> is null.
/// Invoked by the debugger on every step. It is critical for stepping performance that this method returns as fast as possible in absence of changes.
/// </summary>
public async Task<SolutionUpdateStatus> GetSolutionUpdateStatusAsync(Solution solution, string sourceFilePath, CancellationToken cancellationToken)
public async Task<bool> HasChangesAsync(Solution solution, string? sourceFilePath, CancellationToken cancellationToken)
{
try
{
if (_changesApplied)
{
return SolutionUpdateStatus.None;
return false;
}
if (DebuggingSession.LastCommittedSolution.HasNoChanges(solution))
var baseSolution = DebuggingSession.LastCommittedSolution;
if (baseSolution.HasNoChanges(solution))
{
return SolutionUpdateStatus.None;
return false;
}
var projects = (sourceFilePath == null) ? solution.Projects :
from documentId in solution.GetDocumentIdsWithFilePath(sourceFilePath)
select solution.GetDocument(documentId)!.Project;
bool anyChanges = false;
using var changedDocumentsDisposer = ArrayBuilder<Document>.GetInstance(out var changedDocuments);
using var addedDocumentsDisposer = ArrayBuilder<Document>.GetInstance(out var addedDocuments);
foreach (var project in projects)
{
if (!EditAndContinueWorkspaceService.SupportsEditAndContinue(project))
await PopulateChangedAndAddedDocumentsAsync(baseSolution, project, changedDocuments, addedDocuments, cancellationToken).ConfigureAwait(false);
if (changedDocuments.IsEmpty() && addedDocuments.IsEmpty())
{
continue;
}
var baseProject = DebuggingSession.LastCommittedSolution.GetProject(project.Id);
if (baseProject == project)
// Check MVID before analyzing documents as the analysis needs to read the PDB which will likely fail if we can't even read the MVID.
var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(project.Id, cancellationToken).ConfigureAwait(false);
if (mvidReadError != null)
{
continue;
// Can't read MVID. This might be an intermittent failure, so don't report it here.
// Report the project as containing changes, so that we proceed to EmitSolutionUpdateAsync where we report the error if it still persists.
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not built", project.Id.DebugName, project.Id);
return true;
}
// When debugging session is started some projects might not have been loaded to the workspace yet.
// We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied
// and will result in source mismatch when the user steps into them.
//
// TODO (https://github.com/dotnet/roslyn/issues/1204):
// hook up the debugger reported error, check that the project has not been loaded and report a better error.
// Here, we assume these projects are not modified.
if (baseProject == null)
if (mvid == Guid.Empty)
{
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not loaded", project.Id.DebugName, project.Id);
// Project not built. We ignore any changes made in its sources.
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not built", project.Id.DebugName, project.Id);
continue;
}
var (changedDocumentAnalyses, diagnostics) = await GetChangedDocumentsAnalysesAsync(baseProject, project, cancellationToken).ConfigureAwait(false);
if (diagnostics.Any())
var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(changedDocuments, addedDocuments, cancellationToken).ConfigureAwait(false);
if (documentDiagnostics.Any())
{
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: out-of-sync documents present (diagnostic: '{2}')",
project.Id.DebugName, project.Id, diagnostics[0]);
return SolutionUpdateStatus.Blocked;
}
project.Id.DebugName, project.Id, documentDiagnostics[0]);
if (changedDocumentAnalyses.Length == 0)
{
continue;
// Although we do not apply changes in out-of-sync/indeterminate documents we report that changes are present,
// so that the debugger triggers emit of updates. There we check if these documents are still in a bad state and report warnings
// that any changes in such documents are not applied.
return true;
}
var projectSummary = await GetProjectAnalysisSymmaryAsync(changedDocumentAnalyses, cancellationToken).ConfigureAwait(false);
if (projectSummary == ProjectAnalysisSummary.ValidChanges)
{
var (mvid, _) = await DebuggingSession.GetProjectModuleIdAsync(baseProject.Id, cancellationToken).ConfigureAwait(false);
if (mvid == Guid.Empty)
{
// project not built
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: project not built", project.Id.DebugName, project.Id);
continue;
}
if (!GetModuleDiagnostics(mvid, project.Name).IsEmpty)
if (projectSummary != ProjectAnalysisSummary.NoChanges)
{
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: module blocking EnC", project.Id.DebugName, project.Id);
return SolutionUpdateStatus.Blocked;
}
}
EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: {2}", project.Id.DebugName, project.Id, projectSummary);
switch (projectSummary)
{
case ProjectAnalysisSummary.NoChanges:
continue;
case ProjectAnalysisSummary.CompilationErrors:
case ProjectAnalysisSummary.RudeEdits:
return SolutionUpdateStatus.Blocked;
case ProjectAnalysisSummary.ValidChanges:
case ProjectAnalysisSummary.ValidInsignificantChanges:
anyChanges = true;
continue;
default:
throw ExceptionUtilities.UnexpectedValue(projectSummary);
return true;
}
}
return anyChanges ? SolutionUpdateStatus.Ready : SolutionUpdateStatus.None;
return false;
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(e))
{
......@@ -672,39 +687,40 @@ internal ImmutableArray<LocationlessDiagnostic> GetDebugeeStateDiagnostics()
public async Task<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution, CancellationToken cancellationToken)
{
var deltas = ArrayBuilder<Deltas>.GetInstance();
var emitBaselines = ArrayBuilder<(ProjectId, EmitBaseline)>.GetInstance();
var readers = ArrayBuilder<IDisposable>.GetInstance();
var diagnostics = ArrayBuilder<(ProjectId, ImmutableArray<Diagnostic>)>.GetInstance();
var changedDocuments = ArrayBuilder<Document>.GetInstance();
try
{
bool isBlocked = false;
using var deltasDisposer = ArrayBuilder<Deltas>.GetInstance(out var deltas);
using var emitBaselinesDisposer = ArrayBuilder<(ProjectId, EmitBaseline)>.GetInstance(out var emitBaselines);
using var readersDisposer = ArrayBuilder<IDisposable>.GetInstance(out var readers);
using var diagnosticsDisposer = ArrayBuilder<(ProjectId, ImmutableArray<Diagnostic>)>.GetInstance(out var diagnostics);
using var changedDocumentsDisposer = ArrayBuilder<Document>.GetInstance(out var changedDocuments);
using var addedDocumentsDisposer = ArrayBuilder<Document>.GetInstance(out var addedDocuments);
var baseSolution = DebuggingSession.LastCommittedSolution;
bool isBlocked = false;
foreach (var project in solution.Projects)
{
if (!EditAndContinueWorkspaceService.SupportsEditAndContinue(project))
await PopulateChangedAndAddedDocumentsAsync(baseSolution, project, changedDocuments, addedDocuments, cancellationToken).ConfigureAwait(false);
if (changedDocuments.IsEmpty() && addedDocuments.IsEmpty())
{
continue;
}
var baseProject = DebuggingSession.LastCommittedSolution.GetProject(project.Id);
// TODO (https://github.com/dotnet/roslyn/issues/1204):
// When debugging session is started some projects might not have been loaded to the workspace yet.
// We capture the base solution. Edits in files that are in projects that haven't been loaded won't be applied
// and will result in source mismatch when the user steps into them.
// TODO: hook up the debugger reported error, check that the project has not been loaded and report a better error.
// Here, we assume these projects are not modified.
if (baseProject == null)
var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(project.Id, cancellationToken).ConfigureAwait(false);
if (mvidReadError != null)
{
EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}' [0x{1:X8}]: project not loaded", project.Id.DebugName, project.Id);
// The error hasn't been reported by GetDocumentDiagnosticsAsync since it might have been intermittent.
// The MVID is required for emit so we consider the error permanent and report it here.
// Bail before analyzing documents as the analysis needs to read the PDB which will likely fail if we can't even read the MVID.
diagnostics.Add((project.Id, ImmutableArray.Create(mvidReadError)));
Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.ValidChanges, ImmutableArray.Create(mvidReadError.Descriptor.Id));
isBlocked = true;
continue;
}
var (mvid, mvidReadError) = await DebuggingSession.GetProjectModuleIdAsync(project.Id, cancellationToken).ConfigureAwait(false);
if (mvid == Guid.Empty && mvidReadError == null)
if (mvid == Guid.Empty)
{
EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}' [0x{1:X8}]: project not built", project.Id.DebugName, project.Id);
continue;
......@@ -724,16 +740,14 @@ public async Task<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution, Can
// e.g. the binary was built with an overload C.M(object), but a generator updated class C to also contain C.M(string),
// which change we have not observed yet. Then call-sites of C.M in a changed document observed by the analysis will be seen as C.M(object)
// instead of the true C.M(string).
var (changedDocumentAnalyses, outOfSyncDiagnostics) = await GetChangedDocumentsAnalysesAsync(baseProject, project, cancellationToken).ConfigureAwait(false);
if (outOfSyncDiagnostics.Any())
var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(changedDocuments, addedDocuments, cancellationToken).ConfigureAwait(false);
if (documentDiagnostics.Any())
{
// The error hasn't been reported by GetDocumentDiagnosticsAsync since out-of-sync documents are likely to be synchronized
// before the changes are attempted to be applied. If they are not the project changes can't be applied.
diagnostics.Add((project.Id, outOfSyncDiagnostics));
Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.RudeEdits, outOfSyncDiagnostics);
isBlocked = true;
continue;
// The diagnostic hasn't been reported by GetDocumentDiagnosticsAsync since out-of-sync documents are likely to be synchronized
// before the changes are attempted to be applied. If we still have any out-of-sync documents we report warnings and ignore changes in them.
// If in future the file is updated so that its content matches the PDB checksum, the document transitions to a matching state,
// and we consider any further changes to it for application.
diagnostics.Add((project.Id, documentDiagnostics));
}
var projectSummary = await GetProjectAnalysisSymmaryAsync(changedDocumentAnalyses, cancellationToken).ConfigureAwait(false);
......@@ -750,17 +764,6 @@ public async Task<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution, Can
continue;
}
if (mvidReadError != null)
{
// The error hasn't been reported by GetDocumentDiagnosticsAsync since it might have been intermittent.
// The MVID is required for emit so we consider the error permanent and report it here.
diagnostics.Add((project.Id, ImmutableArray.Create(mvidReadError)));
Telemetry.LogProjectAnalysisSummary(projectSummary, ImmutableArray.Create(mvidReadError.Descriptor.Id));
isBlocked = true;
continue;
}
var moduleDiagnostics = GetModuleDiagnostics(mvid, project.Name);
if (!moduleDiagnostics.IsEmpty)
{
......@@ -803,11 +806,6 @@ public async Task<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution, Can
Emit();
}
if (!isBlocked)
{
changedDocuments.AddRange(changedDocumentAnalyses.Select(a => a.Document));
}
void Emit()
{
Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA, "SymReader requires MTA");
......@@ -903,27 +901,20 @@ void Emit()
if (isBlocked)
{
deltas.Free();
emitBaselines.Free();
foreach (var reader in readers)
{
reader.Dispose();
}
readers.Free();
changedDocuments.Free();
return SolutionUpdate.Blocked(diagnostics.ToImmutableAndFree());
return SolutionUpdate.Blocked(diagnostics.ToImmutable());
}
return new SolutionUpdate(
(deltas.Count > 0) ? SolutionUpdateStatus.Ready : SolutionUpdateStatus.None,
deltas.ToImmutableAndFree(),
readers.ToImmutableAndFree(),
emitBaselines.ToImmutableAndFree(),
changedDocuments.ToImmutableAndFree(),
diagnostics.ToImmutableAndFree());
deltas.ToImmutable(),
readers.ToImmutable(),
emitBaselines.ToImmutable(),
diagnostics.ToImmutable());
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceledAndPropagate(e))
{
......
......@@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue
internal interface IEditAndContinueWorkspaceService : IWorkspaceService
{
Task<ImmutableArray<Diagnostic>> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken);
Task<SolutionUpdateStatus> GetSolutionUpdateStatusAsync(string sourceFilePath, CancellationToken cancellationToken);
Task<bool> HasChangesAsync(string sourceFilePath, CancellationToken cancellationToken);
Task<(SolutionUpdateStatus Summary, ImmutableArray<Deltas> Deltas)> EmitSolutionUpdateAsync(CancellationToken cancellationToken);
void CommitSolutionUpdate();
......
......@@ -11,20 +11,17 @@ internal sealed class PendingSolutionUpdate
public readonly ImmutableArray<(ProjectId ProjectId, EmitBaseline Baseline)> EmitBaselines;
public readonly ImmutableArray<Deltas> Deltas;
public readonly ImmutableArray<IDisposable> ModuleReaders;
public readonly ImmutableArray<Document> ChangedDocuments;
public PendingSolutionUpdate(
Solution solution,
ImmutableArray<(ProjectId ProjectId, EmitBaseline Baseline)> emitBaselines,
ImmutableArray<Deltas> deltas,
ImmutableArray<IDisposable> moduleReaders,
ImmutableArray<Document> changedDocuments)
ImmutableArray<IDisposable> moduleReaders)
{
Solution = solution;
EmitBaselines = emitBaselines;
Deltas = deltas;
ModuleReaders = moduleReaders;
ChangedDocuments = changedDocuments;
}
}
}
......@@ -12,21 +12,18 @@ namespace Microsoft.CodeAnalysis.EditAndContinue
public readonly ImmutableArray<IDisposable> ModuleReaders;
public readonly ImmutableArray<(ProjectId ProjectId, EmitBaseline Baseline)> EmitBaselines;
public readonly ImmutableArray<(ProjectId ProjectId, ImmutableArray<Diagnostic> Diagnostic)> Diagnostics;
public readonly ImmutableArray<Document> ChangedDocuments;
public SolutionUpdate(
SolutionUpdateStatus summary,
ImmutableArray<Deltas> deltas,
ImmutableArray<IDisposable> moduleReaders,
ImmutableArray<(ProjectId, EmitBaseline)> emitBaselines,
ImmutableArray<Document> changedDocuments,
ImmutableArray<(ProjectId ProjectId, ImmutableArray<Diagnostic> Diagnostics)> diagnostics)
{
Summary = summary;
Deltas = deltas;
EmitBaselines = emitBaselines;
ModuleReaders = moduleReaders;
ChangedDocuments = changedDocuments;
Diagnostics = diagnostics;
}
......@@ -38,7 +35,6 @@ public static SolutionUpdate Blocked()
ImmutableArray<Deltas>.Empty,
ImmutableArray<IDisposable>.Empty,
ImmutableArray<(ProjectId, EmitBaseline)>.Empty,
ImmutableArray<Document>.Empty,
diagnostics);
}
}
......@@ -1347,7 +1347,7 @@ internal class FeaturesResources {
}
/// <summary>
/// Looks up a localized string similar to The current content of source file &apos;{0}&apos; does not match the built source. The debug session can&apos;t continue until the content of the source file is restored..
/// Looks up a localized string similar to The current content of source file &apos;{0}&apos; does not match the built source. Any changes made to this file while debugging won&apos;t be applied until its content matches the built source..
/// </summary>
internal static string DocumentIsOutOfSyncWithDebuggee {
get {
......@@ -4056,6 +4056,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Unable to read source file &apos;{0}&apos; or the PDB built for the containing project. Any changes made to this file while debugging won&apos;t be applied until its content matches the built source..
/// </summary>
internal static string UnableToReadSourceFileOrPdb {
get {
return ResourceManager.GetString("UnableToReadSourceFileOrPdb", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unexpected interface member kind: {0}.
/// </summary>
......
......@@ -1681,7 +1681,10 @@ This version used in: {2}</value>
<value>Changes made in project '{0}' will not be applied while the application is running</value>
</data>
<data name="DocumentIsOutOfSyncWithDebuggee" xml:space="preserve">
<value>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</value>
<value>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</value>
</data>
<data name="UnableToReadSourceFileOrPdb" xml:space="preserve">
<value>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</value>
</data>
<data name="ChangesDisallowedWhileStoppedAtException" xml:space="preserve">
<value>Changes are not allowed while stopped at exception</value>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Aktuální obsah zdrojového souboru {0} se neshoduje se sestaveným zdrojem. Relace ladění nemůže pokračovat, dokud se obsah zdrojového souboru neobnoví.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Aktuální obsah zdrojového souboru {0} se neshoduje se sestaveným zdrojem. Relace ladění nemůže pokračovat, dokud se obsah zdrojového souboru neobnoví.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Nepotřebné přiřazení hodnoty</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Der aktuelle Inhalt der Quelldatei "{0}" stimmt nicht mit der erstellten Quelle überein. Die Debugsitzung kann erst fortgesetzt werden, wenn der Inhalt der Quelldatei wiederhergestellt wurde.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Der aktuelle Inhalt der Quelldatei "{0}" stimmt nicht mit der erstellten Quelle überein. Die Debugsitzung kann erst fortgesetzt werden, wenn der Inhalt der Quelldatei wiederhergestellt wurde.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Unnötige Zuweisung eines Werts.</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">El contenido actual del archivo de código fuente "{0}" no coincide con el del origen compilado. La sesión de depuración no puede continuar hasta que se restaure el contenido del archivo de código fuente.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">El contenido actual del archivo de código fuente "{0}" no coincide con el del origen compilado. La sesión de depuración no puede continuar hasta que se restaure el contenido del archivo de código fuente.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Asignación innecesaria de un valor</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Le contenu actuel du fichier source « {0} » ne correspond pas à la source générée. La session de débogage ne peut pas continuer tant que le contenu du fichier source n'est pas restauré.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Le contenu actuel du fichier source « {0} » ne correspond pas à la source générée. La session de débogage ne peut pas continuer tant que le contenu du fichier source n'est pas restauré.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Assignation inutile d'une valeur</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Il contenuto corrente del file di origine '{0}' non corrisponde all'origine compilata. La sessione di debug non può continuare finché non viene ripristinato il contenuto del file di origine.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Il contenuto corrente del file di origine '{0}' non corrisponde all'origine compilata. La sessione di debug non può continuare finché non viene ripristinato il contenuto del file di origine.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Assegnazione non necessaria di un valore</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">ソース ファイル '{0}' の現在の内容はビルドされたソースと一致しません。ソース ファイルの内容が復元されるまで、デバッグ セッションを続行できません。</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">ソース ファイル '{0}' の現在の内容はビルドされたソースと一致しません。ソース ファイルの内容が復元されるまで、デバッグ セッションを続行できません。</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">値の不必要な代入</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">소스 파일 '{0}'의 현재 콘텐츠가 빌드된 소스와 일치하지 않습니다. 소스 파일의 콘텐츠가 복원될 때까지 디버그 세션을 계속할 수 없습니다.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">소스 파일 '{0}'의 현재 콘텐츠가 빌드된 소스와 일치하지 않습니다. 소스 파일의 콘텐츠가 복원될 때까지 디버그 세션을 계속할 수 없습니다.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">불필요한 값 할당</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Bieżąca zawartość pliku źródłowego „{0}” nie pasuje do skompilowanego źródła. Nie można kontynuować sesji debugowania do czasu przywrócenia zawartości pliku źródłowego.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Bieżąca zawartość pliku źródłowego „{0}” nie pasuje do skompilowanego źródła. Nie można kontynuować sesji debugowania do czasu przywrócenia zawartości pliku źródłowego.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Niepotrzebne przypisanie wartości</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">O conteúdo atual do arquivo de origem '{0}' não corresponde à fonte compilada. A sessão de depuração não pode continuar até que o conteúdo do arquivo de origem seja restaurado.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">O conteúdo atual do arquivo de origem '{0}' não corresponde à fonte compilada. A sessão de depuração não pode continuar até que o conteúdo do arquivo de origem seja restaurado.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Atribuição desnecessária de um valor</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">Текущее содержимое исходного файла "{0}" не соответствует созданному источнику. Сеанс отладки не может быть продолжен, пока не будет восстановлено содержимое исходного файла.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">Текущее содержимое исходного файла "{0}" не соответствует созданному источнику. Сеанс отладки не может быть продолжен, пока не будет восстановлено содержимое исходного файла.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Ненужное присваивание значения</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">'{0}' kaynak dosyasının geçerli içeriği, oluşturulan kaynakla eşleşmiyor. Hata ayıklama oturumu, kaynak dosyanın içeriği geri yüklenene kadar devam edemez.</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">'{0}' kaynak dosyasının geçerli içeriği, oluşturulan kaynakla eşleşmiyor. Hata ayıklama oturumu, kaynak dosyanın içeriği geri yüklenene kadar devam edemez.</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">Bir değerin gereksiz ataması</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">源文件 "{0}" 的当前内容与生成的源不匹配。除非还原源文件的内容,否则调试会话将无法继续。</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">源文件 "{0}" 的当前内容与生成的源不匹配。除非还原源文件的内容,否则调试会话将无法继续。</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">不需要赋值</target>
......
......@@ -198,8 +198,8 @@
<note />
</trans-unit>
<trans-unit id="DocumentIsOutOfSyncWithDebuggee">
<source>The current content of source file '{0}' does not match the built source. The debug session can't continue until the content of the source file is restored.</source>
<target state="translated">來源檔案 '{0}' 的目前內容與建立的來源不符。在還原來源檔案的內容之前,無法繼續進行偵錯工作階段。</target>
<source>The current content of source file '{0}' does not match the built source. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="needs-review-translation">來源檔案 '{0}' 的目前內容與建立的來源不符。在還原來源檔案的內容之前,無法繼續進行偵錯工作階段。</target>
<note />
</trans-unit>
<trans-unit id="EditAndContinue">
......@@ -572,6 +572,11 @@
<target state="new">The selection contains a local function call without its declaration.</target>
<note />
</trans-unit>
<trans-unit id="UnableToReadSourceFileOrPdb">
<source>Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</source>
<target state="new">Unable to read source file '{0}' or the PDB built for the containing project. Any changes made to this file while debugging won't be applied until its content matches the built source.</target>
<note />
</trans-unit>
<trans-unit id="Unnecessary_assignment_of_a_value">
<source>Unnecessary assignment of a value</source>
<target state="translated">指派了不必要的值</target>
......
......@@ -50,6 +50,8 @@
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\FatalError.cs" Link="Utilities\FatalError.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\FileNameUtilities.cs" Link="Utilities\FileNameUtilities.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\PlatformInformation.cs" Link="Utilities\PlatformInformation.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\Debug.cs" Link="Utilities\Debug.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\RoslynString.cs" Link="Utilities\RoslynString.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\Hash.cs" Link="Utilities\Hash.cs" />
<Compile Include="..\..\Compilers\Core\Portable\FileSystem\FileUtilities.cs" Link="Utilities\FileUtilities.cs" />
<Compile Include="..\..\Compilers\Core\Portable\FileSystem\PathUtilities.cs" Link="Utilities\PathUtilities.cs" />
......
......@@ -33,12 +33,20 @@ public Task<ManagedModuleUpdateStatus> GetStatusAsync(CancellationToken cancella
/// Returns the state of the changes made to the source.
/// The EnC manager calls this to determine whether there are any changes to the source
/// and if so whether there are any rude edits.
///
/// TODO: Future work in the debugger https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1051385 will replace this with bool HasChangesAsync.
/// The debugger currently uses <see cref="SolutionUpdateStatus.Ready"/> as a signal to trigger emit of updates
/// (i.e. to call <see cref="GetManagedModuleUpdatesAsync(CancellationToken)"/>).
/// When <see cref="SolutionUpdateStatus.Blocked"/> is returned updates are not emitted.
/// Since <see cref="GetManagedModuleUpdatesAsync(CancellationToken)"/> already handles all validation and error reporting
/// we either return <see cref="SolutionUpdateStatus.None"/> if there are no changes or <see cref="SolutionUpdateStatus.Ready"/> if there are any changes.
/// </summary>
public async Task<ManagedModuleUpdateStatus> GetStatusAsync(string sourceFilePath, CancellationToken cancellationToken)
{
try
{
return (await _encService.GetSolutionUpdateStatusAsync(sourceFilePath, cancellationToken).ConfigureAwait(false)).ToModuleUpdateStatus();
return (await _encService.HasChangesAsync(sourceFilePath, cancellationToken).ConfigureAwait(false)) ?
ManagedModuleUpdateStatus.Ready : ManagedModuleUpdateStatus.None;
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册