提交 27b4c380 编写于 作者: H Heejae Chang

made diagnostic engine to verify span given to diagnostic from analyzers. and...

made diagnostic engine to verify span given to diagnostic from analyzers. and added explicit check in tagger for out of range
上级 d60c6829
......@@ -897,7 +897,7 @@ public void TestReportingDiagnosticWithInvalidLocation()
var treeInAnotherCompilation = anotherCompilation.SyntaxTrees.Single();
string message = new ArgumentException(
string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, AnalyzerWithInvalidDiagnosticLocation.Descriptor.Id, treeInAnotherCompilation.FilePath), "diagnostic").Message;
string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, AnalyzerWithInvalidDiagnosticLocation.Descriptor.Id, treeInAnotherCompilation.GetRoot().Location.SourceSpan, treeInAnotherCompilation.FilePath), "diagnostic").Message;
compilation.VerifyDiagnostics();
......@@ -1902,7 +1902,7 @@ public class RegularClass
var tree = CSharpSyntaxTree.ParseText(source, path: "Source.cs");
var compilation = CreateCompilationWithMscorlib45(new[] { tree });
compilation.VerifyDiagnostics();
var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer(GeneratedCodeAnalysisFlags.None) };
compilation.VerifyAnalyzerDiagnostics(analyzers, null, null, true,
Diagnostic("GeneratedCodeAnalyzerWarning", "}").WithArguments("Source.cs").WithLocation(11, 1),
......
......@@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class CodeAnalysisResources {
......@@ -740,7 +740,7 @@ internal class CodeAnalysisResources {
}
/// <summary>
/// Looks up a localized string similar to Reported diagnostic &apos;{0}&apos; has a source location in file &apos;{1}&apos;, which is not part of the compilation being analyzed..
/// Looks up a localized string similar to Reported diagnostic &apos;{0}&apos; has a source location {1} in file &apos;{2}&apos;, which is not part of the compilation being analyzed..
/// </summary>
internal static string InvalidDiagnosticLocationReported {
get {
......@@ -1334,7 +1334,7 @@ internal class CodeAnalysisResources {
}
/// <summary>
/// Looks up a localized string similar to Windows PDB writer doesn&apos;t support deterministic compilation: &apos;{0}&apos;.
/// Looks up a localized string similar to Windows PDB writer doesn&apos;t support SourceLink feature: &apos;{0}&apos;.
/// </summary>
internal static string SymWriterDoesNotSupportSourceLink {
get {
......@@ -1343,7 +1343,7 @@ internal class CodeAnalysisResources {
}
/// <summary>
/// Looks up a localized string similar to Windows PDB writer is not available -- could not find Microsoft.DiaSymReader.Native.{0}.dll.
/// Looks up a localized string similar to Windows PDB writer doesn&apos;t support deterministic compilation: &apos;{0}&apos;.
/// </summary>
internal static string SymWriterNotDeterministic {
get {
......
......@@ -450,7 +450,7 @@
<value>Reported diagnostic has an ID '{0}', which is not a valid identifier.</value>
</data>
<data name="InvalidDiagnosticLocationReported" xml:space="preserve">
<value>Reported diagnostic '{0}' has a source location in file '{1}', which is not part of the compilation being analyzed.</value>
<value>Reported diagnostic '{0}' has a source location {1} in file '{2}', which is not part of the compilation being analyzed.</value>
</data>
<data name="The_type_0_is_not_understood_by_the_serialization_binder" xml:space="preserve">
<value>The type '{0}' is not understood by the serialization binder.</value>
......
......@@ -73,7 +73,7 @@ internal static void VerifyDiagnosticLocationsInCompilation(Diagnostic diagnosti
}
}
}
internal static void VerifyIOperationFeatureFlag(bool isIOperationFeatureEnabled)
{
if (!isIOperationFeatureEnabled)
......@@ -84,10 +84,15 @@ internal static void VerifyIOperationFeatureFlag(bool isIOperationFeatureEnabled
private static void VerifyDiagnosticLocationInCompilation(string id, Location location, Compilation compilation)
{
if (location.IsInSource && !compilation.ContainsSyntaxTree(location.SourceTree))
if (!location.IsInSource)
{
return;
}
if (!compilation.ContainsSyntaxTree(location.SourceTree) || location.SourceTree.Length < location.SourceSpan.End)
{
// Disallow diagnostics with source locations outside this compilation.
throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, id, location.SourceTree.FilePath), "diagnostic");
throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
}
}
......
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
......@@ -312,5 +313,60 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
}
}
}
[Fact]
public async Task TestDiagnosticSpan()
{
var source = @"// empty code";
var ideAnalyzer = new InvalidSpanDocumentAnalyzer();
using (var ideEngineWorkspace = TestWorkspace.CreateCSharp(source))
{
var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
await Assert.ThrowsAsync<ArgumentException>(() =>
DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(ideAnalyzer, ideEngineDocument, new Text.TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length)));
}
var analyzer = new InvalidSpanAnalyzer();
using (var compilerEngineWorkspace = TestWorkspace.CreateCSharp(source))
{
var compilerEngineCompilation = (CSharpCompilation)compilerEngineWorkspace.CurrentSolution.Projects.Single().GetCompilationAsync().Result;
Assert.Throws<ArgumentException>(() => compilerEngineCompilation.GetAnalyzerDiagnostics(new[] { analyzer }));
}
}
private class InvalidSpanAnalyzer : DiagnosticAnalyzer
{
public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
=> context.RegisterSyntaxTreeAction(Analyze);
private void Analyze(SyntaxTreeAnalysisContext context)
=> context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(context.Tree, TextSpan.FromBounds(1000, 2000))));
}
private class InvalidSpanDocumentAnalyzer : DocumentDiagnosticAnalyzer
{
public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Descriptor);
public override Task<ImmutableArray<Diagnostic>> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken)
{
return Task.FromResult(ImmutableArray.Create(Diagnostic.Create(Descriptor, Location.Create(context.Tree, TextSpan.FromBounds(1000, 2000)))));
}
public override Task<ImmutableArray<Diagnostic>> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken)
{
return Task.FromResult<ImmutableArray<Diagnostic>.Empty>();
}
}
}
}
......@@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
......@@ -63,7 +64,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I
// We act as a source of events ourselves. When the diagnostics service tells
// us about new diagnostics, we'll use that to kick of the asynchronous tagging
// work.
var eventSource = TaggerEventSources.Compose(
TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.Medium),
this);
......@@ -83,69 +84,79 @@ protected override Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSn
private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag)
{
if (!_owner.IsEnabled)
try
{
return;
}
if (!_owner.IsEnabled)
{
return;
}
var document = spanToTag.Document;
if (document == null)
{
return;
}
var document = spanToTag.Document;
if (document == null)
{
return;
}
// See if we've marked any spans as those we want to suppress diagnostics for.
// This can happen for buffers used in the preview workspace where some feature
// is generating code that it doesn't want errors shown for.
var buffer = spanToTag.SnapshotSpan.Snapshot.TextBuffer;
var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
// Producing tags is simple. We just grab the diagnostics we were already told about,
// and we convert them to tag spans.
object id;
ImmutableArray<DiagnosticData> diagnostics;
SourceText sourceText;
ITextSnapshot editorSnapshot;
lock (_gate)
{
id = _latestId;
diagnostics = _latestDiagnostics;
sourceText = _latestSourceText;
editorSnapshot = _latestEditorSnapshot;
}
// See if we've marked any spans as those we want to suppress diagnostics for.
// This can happen for buffers used in the preview workspace where some feature
// is generating code that it doesn't want errors shown for.
var buffer = spanToTag.SnapshotSpan.Snapshot.TextBuffer;
var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
// Producing tags is simple. We just grab the diagnostics we were already told about,
// and we convert them to tag spans.
object id;
ImmutableArray<DiagnosticData> diagnostics;
SourceText sourceText;
ITextSnapshot editorSnapshot;
lock (_gate)
{
id = _latestId;
diagnostics = _latestDiagnostics;
sourceText = _latestSourceText;
editorSnapshot = _latestEditorSnapshot;
}
if (sourceText == null || editorSnapshot == null)
{
return;
}
if (sourceText == null || editorSnapshot == null)
{
return;
}
var project = document.Project;
var project = document.Project;
var requestedSnapshot = spanToTag.SnapshotSpan.Snapshot;
var requestedSpan = spanToTag.SnapshotSpan;
var isLiveUpdate = id is ISupportLiveUpdate;
var requestedSnapshot = spanToTag.SnapshotSpan.Snapshot;
var requestedSpan = spanToTag.SnapshotSpan;
var isLiveUpdate = id is ISupportLiveUpdate;
foreach (var diagnosticData in diagnostics)
{
if (_owner.IncludeDiagnostic(diagnosticData))
foreach (var diagnosticData in diagnostics)
{
var actualSpan = diagnosticData
.GetExistingOrCalculatedTextSpan(sourceText)
.ToSnapshotSpan(editorSnapshot)
.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
if (actualSpan.IntersectsWith(requestedSpan) &&
!IsSuppressed(suppressedDiagnosticsSpans, actualSpan))
if (_owner.IncludeDiagnostic(diagnosticData))
{
var tagSpan = _owner.CreateTagSpan(isLiveUpdate, actualSpan, diagnosticData);
if (tagSpan != null)
var actualSpan = diagnosticData
.GetExistingOrCalculatedTextSpan(sourceText)
.ToSnapshotSpan(editorSnapshot)
.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
if (actualSpan.IntersectsWith(requestedSpan) &&
!IsSuppressed(suppressedDiagnosticsSpans, actualSpan))
{
context.AddTag(tagSpan);
var tagSpan = _owner.CreateTagSpan(isLiveUpdate, actualSpan, diagnosticData);
if (tagSpan != null)
{
context.AddTag(tagSpan);
}
}
}
}
}
catch (ArgumentOutOfRangeException ex) when (FatalError.ReportWithoutCrash(ex))
{
// https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false
// explicitly report NFW to find out what is causing us for out of range.
// stop crashing on such occations
return;
}
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
......
......@@ -377,7 +377,7 @@ public IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(Document targetDocu
try
{
var diagnostics = await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false);
var diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty();
// Apply filtering from compilation options (source suppressions, ruleset, etc.)
if (compilationOpt != null)
......@@ -385,6 +385,8 @@ public IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(Document targetDocu
diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt).ToImmutableArrayOrEmpty();
}
await VerifyDiagnosticLocationsAsync(diagnostics, project, cancellationToken).ConfigureAwait(false);
return diagnostics;
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
......@@ -423,6 +425,8 @@ public IEnumerable<DiagnosticData> ConvertToLocalDiagnostics(Document targetDocu
return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt);
}
await VerifyDiagnosticLocationsAsync(diagnostics, document.Project, cancellationToken).ConfigureAwait(false);
return diagnostics;
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
......@@ -584,6 +588,83 @@ private IEnumerable<DiagnosticData> ConvertToLocalDiagnosticsWithCompilation(Doc
}
}
private static async Task VerifyDiagnosticLocationsAsync(ImmutableArray<Diagnostic> diagnostics, Project project, CancellationToken cancellationToken)
{
foreach (var diagnostic in diagnostics)
{
await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false);
if (diagnostic.AdditionalLocations != null)
{
foreach (var location in diagnostic.AdditionalLocations)
{
await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false);
}
}
}
}
private static async Task VerifyDiagnosticLocationAsync(string id, Location location, Project project, CancellationToken cancellationToken)
{
switch (location.Kind)
{
case LocationKind.None:
case LocationKind.MetadataFile:
case LocationKind.XmlFile:
// ignore these kinds
break;
case LocationKind.SourceFile:
{
if (project.GetDocument(location.SourceTree) == null)
{
// Disallow diagnostics with source locations outside this project.
throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
}
if (location.SourceTree.Length < location.SourceSpan.End)
{
// Disallow diagnostics with source locations outside this project.
throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
}
}
break;
case LocationKind.ExternalFile:
{
var document = TryGetDocumentWithFilePath(project, location.GetLineSpan().Path);
if (document == null)
{
// this is not a roslyn file. we don't care about this file.
return;
}
// we do this for every diagnostic, so this can be really expansive but we dont have any other way.
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
if (text.Length < location.SourceSpan.End)
{
// Disallow diagnostics with locations outside this project.
throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
}
}
break;
default:
throw ExceptionUtilities.Unreachable;
}
}
private static Document TryGetDocumentWithFilePath(Project project, string path)
{
foreach (var documentId in project.Solution.GetDocumentIdsWithFilePath(path))
{
if (documentId.ProjectId == project.Id)
{
return project.GetDocument(documentId);
}
}
return null;
}
private static void GetLogFunctionIdAndTitle(AnalysisKind kind, out FunctionId functionId, out string title)
{
switch (kind)
......
......@@ -2574,6 +2574,16 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Reported diagnostic &apos;{0}&apos; has a source location {1} in file &apos;{2}&apos;, which is not part of the compilation being analyzed..
/// </summary>
internal static string Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_compilation_being_analyzed {
get {
return ResourceManager.GetString("Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_" +
"compilation_being_analyzed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reported diagnostic with ID &apos;{0}&apos; is not supported by the analyzer..
/// </summary>
......
......@@ -1247,4 +1247,7 @@ This version used in: {2}</value>
<data name="default_expression_can_be_simplified" xml:space="preserve">
<value>'default' expression can be simplified</value>
</data>
<data name="Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_not_part_of_the_compilation_being_analyzed" xml:space="preserve">
<value>Reported diagnostic '{0}' has a source location {1} in file '{2}', which is not part of the compilation being analyzed.</value>
</data>
</root>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册