diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs index da884aa92a19ce76eb4826182ad867e77b29675b..66d32a6c9347de93d6fa46305fc9c27dfce85fbb 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs @@ -1261,4 +1261,4 @@ private struct ILMarker public int AbsoluteOffset; } } -} +} \ No newline at end of file diff --git a/src/Compilers/Core/Portable/Serialization/ObjectReader.cs b/src/Compilers/Core/Portable/Serialization/ObjectReader.cs index b57a4e54027d083bb95af0f703908b2da77d8bc4..e4cafd01701865e542f5691625f61835eb393c0a 100644 --- a/src/Compilers/Core/Portable/Serialization/ObjectReader.cs +++ b/src/Compilers/Core/Portable/Serialization/ObjectReader.cs @@ -139,8 +139,15 @@ public object ReadValue() _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); - task.Wait(_cancellationToken); - value = task.Result; + + // We must not proceed until the additional task completes. After returning from a read, the underlying + // stream providing access to raw memory will be closed; if this occurs before the separate thread + // completes its read then an access violation can occur attempting to read from unmapped memory. + // + // CANCELLATION: If cancellation is required, DO NOT attempt to cancel the operation by cancelling this + // wait. Cancellation must only be implemented by modifying 'task' to cancel itself in a timely manner + // so the wait can complete. + value = task.GetAwaiter().GetResult(); } else { diff --git a/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs b/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs index 549be6c6a9c7eae3b697346b150efd4be1dea8a7..f56bf93935c5e84ead075a0bcc3c718ed6b392cb 100644 --- a/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs +++ b/src/Compilers/Core/Portable/Serialization/ObjectWriter.cs @@ -488,7 +488,15 @@ private void WriteArray(Array array) _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); - task.Wait(); + + // We must not proceed until the additional task completes. After returning from a write, the underlying + // stream providing access to raw memory will be closed; if this occurs before the separate thread + // completes its write then an access violation can occur attempting to write to unmapped memory. + // + // CANCELLATION: If cancellation is required, DO NOT attempt to cancel the operation by cancelling this + // wait. Cancellation must only be implemented by modifying 'task' to cancel itself in a timely manner + // so the wait can complete. + task.GetAwaiter().GetResult(); } else { @@ -748,7 +756,15 @@ private void WriteObject(object instance, IObjectWritable instanceAsWritableOpt) _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); - task.Wait(_cancellationToken); + + // We must not proceed until the additional task completes. After returning from a write, the underlying + // stream providing access to raw memory will be closed; if this occurs before the separate thread + // completes its write then an access violation can occur attempting to write to unmapped memory. + // + // CANCELLATION: If cancellation is required, DO NOT attempt to cancel the operation by cancelling this + // wait. Cancellation must only be implemented by modifying 'task' to cancel itself in a timely manner + // so the wait can complete. + task.GetAwaiter().GetResult(); } else { diff --git a/src/EditorFeatures/Test/Extensions/SourceTextContainerExtensionsTests.cs b/src/EditorFeatures/Test/Extensions/SourceTextContainerExtensionsTests.cs index d77b7371c84e2198bb55c696e3fcf35b11ed02a3..20ff526e2c246ceefc7991bb02f09fc5dadcf0b7 100644 --- a/src/EditorFeatures/Test/Extensions/SourceTextContainerExtensionsTests.cs +++ b/src/EditorFeatures/Test/Extensions/SourceTextContainerExtensionsTests.cs @@ -24,14 +24,17 @@ public void GetBufferTextFromNonTextContainerThrows() [Fact] public void GetBufferTextFromTextContainerDoesNotThrow() { - var textSnapshotMock = new Mock(); + var textImageMock = new Mock(); + var textSnapshotMock = new Mock(); var bufferMock = new Mock(); + + textSnapshotMock.Setup(s => s.TextImage).Returns(textImageMock.Object); bufferMock.SetupGet(x => x.CurrentSnapshot).Returns(textSnapshotMock.Object); bufferMock.SetupGet(x => x.Properties).Returns(new VisualStudio.Utilities.PropertyCollection()); - var textContainer = Microsoft.CodeAnalysis.Text.Extensions.TextBufferContainer.From(bufferMock.Object); + var textContainer = Text.Extensions.TextBufferContainer.From(bufferMock.Object); - Microsoft.CodeAnalysis.Text.Extensions.GetTextBuffer(textContainer); + Text.Extensions.GetTextBuffer(textContainer); } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index 1d6233f68c56fa703569470ee7e6e1276e74c875..e5b7568b14c46032a3625552c499a0f4c5f69ec4 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -109,7 +109,11 @@ private EditorTextFactoryService CreateMockTextFactoryService() { var text = reader.ReadToEnd(); - var mockSnapshot = new Mock(); + var mockImage = new Mock(); + mockImage.Setup(i => i.GetText(It.IsAny())).Returns(text); + + var mockSnapshot = new Mock(); + mockSnapshot.Setup(s => s.TextImage).Returns(mockImage.Object); mockSnapshot.Setup(s => s.GetText()).Returns(text); var mockTextBuffer = new Mock(); diff --git a/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs b/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs index f8c7668459e194f5b4d1158f84e5e8b8a2dd6623..8f41e3dae7d360ac636d2b2044c2b752f417c598 100644 --- a/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs +++ b/src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs @@ -10,7 +10,6 @@ using System.Threading; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; @@ -29,8 +28,8 @@ private class SnapshotSourceText : SourceText /// internal sealed class ClosedSnapshotSourceText : SnapshotSourceText { - public ClosedSnapshotSourceText(ITextSnapshot roslynSnapshot, Encoding encodingOpt) - : base(roslynSnapshot, encodingOpt, containerOpt: null) + public ClosedSnapshotSourceText(ITextImage textImage, Encoding encodingOpt) + : base(textImage, encodingOpt, containerOpt: null) { } } @@ -38,9 +37,10 @@ public ClosedSnapshotSourceText(ITextSnapshot roslynSnapshot, Encoding encodingO private static readonly Func s_textLog = (v1, v2) => string.Format("FullRange : from {0} to {1}", v1, v2); /// - /// The ITextSnapshot backing the SourceText instance + /// The backing the SourceText instance /// - protected readonly ITextSnapshot RoslynSnapshot; + public readonly ITextImage TextImage; + private readonly Encoding _encodingOpt; private readonly TextBufferContainer _containerOpt; private readonly int _reiteratedVersion; @@ -49,17 +49,17 @@ private SnapshotSourceText(ITextSnapshot editorSnapshot, Encoding encodingOpt) { Contract.ThrowIfNull(editorSnapshot); - this.RoslynSnapshot = TextBufferMapper.ToRoslyn(editorSnapshot); + this.TextImage = TextBufferMapper.RecordTextSnapshotAndGetImage(editorSnapshot); _containerOpt = TextBufferContainer.From(editorSnapshot.TextBuffer); _reiteratedVersion = editorSnapshot.Version.ReiteratedVersionNumber; _encodingOpt = encodingOpt; } - public SnapshotSourceText(ITextSnapshot roslynSnapshot, Encoding encodingOpt, TextBufferContainer containerOpt) + public SnapshotSourceText(ITextImage textImage, Encoding encodingOpt, TextBufferContainer containerOpt) { - Contract.ThrowIfNull(roslynSnapshot); + Contract.ThrowIfNull(textImage); - this.RoslynSnapshot = roslynSnapshot; + this.TextImage = textImage; _encodingOpt = encodingOpt; _containerOpt = containerOpt; } @@ -122,10 +122,8 @@ public override Encoding Encoding get { return _encodingOpt; } } - public ITextSnapshot EditorSnapshot - { - get { return TextBufferMapper.ToEditor(this.RoslynSnapshot); } - } + public ITextSnapshot TryFindEditorSnapshot() + => TextBufferMapper.TryFindEditorSnapshot(this.TextImage); protected static ITextBufferCloneService TextBufferFactory { @@ -154,14 +152,14 @@ public override int Length { get { - var res = this.RoslynSnapshot.Length; + var res = this.TextImage.Length; return res; } } public override char this[int position] { - get { return this.RoslynSnapshot[position]; } + get { return this.TextImage[position]; } } #region Lines @@ -181,21 +179,21 @@ public LineInfo(SnapshotSourceText text) public override int Count { - get { return _text.RoslynSnapshot.LineCount; } + get { return _text.TextImage.LineCount; } } public override TextLine this[int index] { get { - var line = _text.RoslynSnapshot.GetLineFromLineNumber(index); + var line = _text.TextImage.GetLineFromLineNumber(index); return TextLine.FromSpan(_text, TextSpan.FromBounds(line.Start, line.End)); } } public override int IndexOf(int position) { - return _text.RoslynSnapshot.GetLineNumberFromPosition(position); + return _text.TextImage.GetLineNumberFromPosition(position); } public override TextLine GetLineFromPosition(int position) @@ -205,7 +203,7 @@ public override TextLine GetLineFromPosition(int position) public override LinePosition GetLinePosition(int position) { - ITextSnapshotLine textLine = _text.RoslynSnapshot.GetLineFromPosition(position); + var textLine = _text.TextImage.GetLineFromPosition(position); return new LinePosition(textLine.LineNumber, position - textLine.Start); } } @@ -213,13 +211,13 @@ public override LinePosition GetLinePosition(int position) public override string ToString() { - return this.RoslynSnapshot.GetText(); + return this.TextImage.GetText(); } public override string ToString(TextSpan textSpan) { var editorSpan = new Span(textSpan.Start, textSpan.Length); - var res = this.RoslynSnapshot.GetText(editorSpan); + var res = this.TextImage.GetText(editorSpan); return res; } @@ -244,7 +242,7 @@ public override SourceText WithChanges(IEnumerable changes) } // otherwise, create a new cloned snapshot - var buffer = factory.Clone(RoslynSnapshot.GetFullSpan()); + var buffer = factory.Clone(TextImage); var baseSnapshot = buffer.CurrentSnapshot; // apply the change to the buffer @@ -258,7 +256,10 @@ public override SourceText WithChanges(IEnumerable changes) edit.Apply(); } - return new ChangedSourceText(this, baseSnapshot, buffer.CurrentSnapshot); + return new ChangedSourceText( + baseText: this, + baseSnapshot: ((ITextSnapshot2)baseSnapshot).TextImage, + currentSnapshot: ((ITextSnapshot2)buffer.CurrentSnapshot).TextImage); } /// @@ -267,9 +268,9 @@ public override SourceText WithChanges(IEnumerable changes) private class ChangedSourceText : SnapshotSourceText { private readonly SnapshotSourceText _baseText; - private readonly ITextSnapshot _baseSnapshot; + private readonly ITextImage _baseSnapshot; - public ChangedSourceText(SnapshotSourceText baseText, ITextSnapshot baseSnapshot, ITextSnapshot currentSnapshot) + public ChangedSourceText(SnapshotSourceText baseText, ITextImage baseSnapshot, ITextImage currentSnapshot) : base(currentSnapshot, baseText.Encoding, containerOpt: null) { _baseText = baseText; @@ -294,18 +295,18 @@ public override IReadOnlyList GetChangeRanges(SourceText oldTex return new[] { new TextChangeRange(new TextSpan(0, oldText.Length), this.Length) }; } - return GetChangeRanges(_baseSnapshot, _baseSnapshot.Length, this.RoslynSnapshot); + return GetChangeRanges(_baseSnapshot, _baseSnapshot.Length, this.TextImage); } } public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) { - this.RoslynSnapshot.CopyTo(sourceIndex, destination, destinationIndex, count); + this.TextImage.CopyTo(sourceIndex, destination, destinationIndex, count); } public override void Write(TextWriter textWriter, TextSpan span, CancellationToken cancellationToken) { - this.RoslynSnapshot.Write(textWriter, span.ToSpan()); + this.TextImage.Write(textWriter, span.ToSpan()); } #region GetChangeRangesImplementation @@ -334,36 +335,44 @@ public override IReadOnlyList GetChangeRanges(SourceText oldTex } } - var oldSnapshot = oldText.FindCorrespondingEditorTextSnapshot(); - var newSnapshot = this.FindCorrespondingEditorTextSnapshot(); + var oldSnapshot = oldText.TryFindCorrespondingEditorTextImage(); + var newSnapshot = this.TryFindCorrespondingEditorTextImage(); return GetChangeRanges(oldSnapshot, oldText.Length, newSnapshot); } - private IReadOnlyList GetChangeRanges(ITextSnapshot oldSnapshot, int oldTextLength, ITextSnapshot newSnapshot) + private IReadOnlyList GetChangeRanges(ITextImage oldImage, int oldTextLength, ITextImage newImage) { - if (oldSnapshot == null || - newSnapshot == null || - oldSnapshot.TextBuffer != newSnapshot.TextBuffer) + if (oldImage == null || + newImage == null || + oldImage.Version.Identifier != newImage.Version.Identifier) { // Claim its all changed Logger.Log(FunctionId.Workspace_SourceText_GetChangeRanges, "Invalid Snapshots"); - return ImmutableArray.Create(new TextChangeRange(new TextSpan(0, oldTextLength), this.Length)); + return ImmutableArray.Create(new TextChangeRange(new TextSpan(0, oldTextLength), this.Length)); } - else if (oldSnapshot.Version.ReiteratedVersionNumber == newSnapshot.Version.ReiteratedVersionNumber) + else if (AreSameReiteratedVersion(oldImage, newImage)) { // content of two snapshot must be same even if versions are different return TextChangeRange.NoChanges; } else { - return GetChangeRanges(oldSnapshot, newSnapshot, forward: oldSnapshot.Version.VersionNumber <= newSnapshot.Version.VersionNumber); + return GetChangeRanges(oldImage, newImage, forward: oldImage.Version.VersionNumber <= newImage.Version.VersionNumber); } } + private static bool AreSameReiteratedVersion(ITextImage oldImage, ITextImage newImage) + { + var oldSnapshot = TextBufferMapper.TryFindEditorSnapshot(oldImage); + var newSnapshot = TextBufferMapper.TryFindEditorSnapshot(newImage); + + return oldSnapshot != null && newSnapshot != null && oldSnapshot.Version.ReiteratedVersionNumber == newSnapshot.Version.ReiteratedVersionNumber; + } + private static readonly Func s_forwardTextChangeRange = c => CreateTextChangeRange(c, forward: true); private static readonly Func s_backwardTextChangeRange = c => CreateTextChangeRange(c, forward: false); - private IReadOnlyList GetChangeRanges(ITextSnapshot snapshot1, ITextSnapshot snapshot2, bool forward) + private IReadOnlyList GetChangeRanges(ITextImage snapshot1, ITextImage snapshot2, bool forward) { var oldSnapshot = forward ? snapshot1 : snapshot2; var newSnapshot = forward ? snapshot2 : snapshot1; @@ -399,7 +408,7 @@ private IReadOnlyList GetChangeRanges(ITextSnapshot snapshot1, } } - private TextChangeRange GetChangeRanges(ITextVersion oldVersion, ITextVersion newVersion, bool forward) + private TextChangeRange GetChangeRanges(ITextImageVersion oldVersion, ITextImageVersion newVersion, bool forward) { TextChangeRange? range = null; var iterator = GetMultipleVersionTextChanges(oldVersion, newVersion, forward); @@ -412,7 +421,8 @@ private TextChangeRange GetChangeRanges(ITextVersion oldVersion, ITextVersion ne return range.Value; } - private static IEnumerable> GetMultipleVersionTextChanges(ITextVersion oldVersion, ITextVersion newVersion, bool forward) + private static IEnumerable> GetMultipleVersionTextChanges( + ITextImageVersion oldVersion, ITextImageVersion newVersion, bool forward) { for (var version = oldVersion; version != newVersion; version = version.Next) { diff --git a/src/EditorFeatures/Text/Extensions.TextBufferContainer.cs b/src/EditorFeatures/Text/Extensions.TextBufferContainer.cs index e5d9e4601f66161a7ef3e9d6462c8b0c5783f861..3b95c6d9c7a6e6fce24f22881a1de8128e71d8fc 100644 --- a/src/EditorFeatures/Text/Extensions.TextBufferContainer.cs +++ b/src/EditorFeatures/Text/Extensions.TextBufferContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; @@ -30,7 +29,7 @@ private TextBufferContainer(ITextBuffer editorBuffer, Encoding encoding) Contract.ThrowIfNull(encoding); _weakEditorBuffer = new WeakReference(editorBuffer); - _currentText = new SnapshotSourceText(TextBufferMapper.ToRoslyn(editorBuffer.CurrentSnapshot), encoding, this); + _currentText = new SnapshotSourceText(TextBufferMapper.RecordTextSnapshotAndGetImage(editorBuffer.CurrentSnapshot), encoding, this); } /// @@ -54,13 +53,14 @@ private static TextBufferContainer CreateContainer(ITextBuffer editorBuffer) return new TextBufferContainer(editorBuffer, editorBuffer.GetEncodingOrUTF8()); } - public ITextBuffer EditorTextBuffer { get { return _weakEditorBuffer.GetTarget(); } } + public ITextBuffer TryFindEditorTextBuffer() + => _weakEditorBuffer.GetTarget(); public override SourceText CurrentText { get { - var editorBuffer = this.EditorTextBuffer; + var editorBuffer = this.TryFindEditorTextBuffer(); return editorBuffer != null ? editorBuffer.CurrentSnapshot.AsText() : _currentText; @@ -73,7 +73,7 @@ public override SourceText CurrentText { lock (_gate) { - var textBuffer = this.EditorTextBuffer; + var textBuffer = this.TryFindEditorTextBuffer(); if (this.EtextChanged == null && textBuffer != null) { textBuffer.ChangedHighPriority += this.OnTextContentChanged; @@ -89,7 +89,7 @@ public override SourceText CurrentText { this.EtextChanged -= value; - var textBuffer = this.EditorTextBuffer; + var textBuffer = this.TryFindEditorTextBuffer(); if (this.EtextChanged == null && textBuffer != null) { textBuffer.ChangedHighPriority -= this.OnTextContentChanged; @@ -120,4 +120,4 @@ private void OnTextContentChanged(object sender, TextContentChangedEventArgs arg public TextChangeEventArgs LastEventArgs { get; private set; } } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Text/Extensions.TextBufferMapper.cs b/src/EditorFeatures/Text/Extensions.TextBufferMapper.cs index 55142d87b2e62e0e8a12a9477af1c36a57b2f8e3..353232aa8642c1153b02ceeced6e2d6d48939790 100644 --- a/src/EditorFeatures/Text/Extensions.TextBufferMapper.cs +++ b/src/EditorFeatures/Text/Extensions.TextBufferMapper.cs @@ -2,8 +2,6 @@ using System; using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; @@ -16,48 +14,32 @@ public static partial class Extensions /// private static class TextBufferMapper { - private static readonly ConditionalWeakTable s_editorToRoslynSnapshotMap = new ConditionalWeakTable(); - private static readonly ConditionalWeakTable> s_roslynToEditorSnapshotMap = new ConditionalWeakTable>(); - private static readonly ConditionalWeakTable.CreateValueCallback s_createSnapshotCallback = CreateSnapshot; + private static readonly ConditionalWeakTable> s_roslynToEditorSnapshotMap = new ConditionalWeakTable>(); - public static ITextSnapshot ToRoslyn(ITextSnapshot editorSnapshot) + public static ITextImage RecordTextSnapshotAndGetImage(ITextSnapshot editorSnapshot) { Contract.ThrowIfNull(editorSnapshot); - var roslynSnapshot = s_editorToRoslynSnapshotMap.GetValue(editorSnapshot, s_createSnapshotCallback); - if (roslynSnapshot == null) + var textImage = ((ITextSnapshot2)editorSnapshot).TextImage; + Contract.ThrowIfNull(textImage); + + // If we're already in the map, there's nothing to update. Do a quick check + // to avoid two allocations per call to RecordTextSnapshotAndGetImage. + if (!s_roslynToEditorSnapshotMap.TryGetValue(textImage, out var weakReference) || + weakReference.GetTarget() != editorSnapshot) { - return editorSnapshot; + // put reverse entry that won't hold onto anything + s_roslynToEditorSnapshotMap.GetValue( + textImage, _ => new WeakReference(editorSnapshot)); } - return roslynSnapshot; - } - - private static ITextSnapshot CreateSnapshot(ITextSnapshot editorSnapshot) - { - var factory = TextBufferFactory; - - // We might not have a factory if there is no primary workspace (for example, under the unit test harness, - // or in CodeSense where they are just using the parsers by themselves). In that case, just use the editor - // snapshot as-is. - // - // Creating a buffer off a given snapshot should be cheap, so it should be okay to create a dummy buffer here - // just to host the snapshot we want. - var roslynSnapshot = factory != null - ? factory.Clone(editorSnapshot.GetFullSpan()).CurrentSnapshot - : editorSnapshot; - - // put reverse entry that won't hold onto anything - var weakEditorSnapshot = new WeakReference(editorSnapshot); - s_roslynToEditorSnapshotMap.GetValue(roslynSnapshot, _ => weakEditorSnapshot); - - return roslynSnapshot; + return textImage; } - public static ITextSnapshot ToEditor(ITextSnapshot roslynSnapshot) + public static ITextSnapshot TryFindEditorSnapshot(ITextImage textImage) { - Contract.ThrowIfNull(roslynSnapshot); - if (!s_roslynToEditorSnapshotMap.TryGetValue(roslynSnapshot, out var weakReference) || + Contract.ThrowIfNull(textImage); + if (!s_roslynToEditorSnapshotMap.TryGetValue(textImage, out var weakReference) || !weakReference.TryGetTarget(out var editorSnapshot)) { return null; @@ -82,4 +64,4 @@ private static ITextBufferCloneService TextBufferFactory } } } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Text/Extensions.cs b/src/EditorFeatures/Text/Extensions.cs index 461760eeb07b44080f677b41a943417466a7187e..d107677abe956ecc8ef900bf1f10977b1eff52de 100644 --- a/src/EditorFeatures/Text/Extensions.cs +++ b/src/EditorFeatures/Text/Extensions.cs @@ -18,7 +18,7 @@ public static ITextBuffer GetTextBuffer(this SourceTextContainer textContainer) => TryGetTextBuffer(textContainer) ?? throw new ArgumentException(TextEditorResources.textContainer_is_not_a_SourceTextContainer_that_was_created_from_an_ITextBuffer, nameof(textContainer)); public static ITextBuffer TryGetTextBuffer(this SourceTextContainer textContainer) - => (textContainer as TextBufferContainer)?.EditorTextBuffer; + => (textContainer as TextBufferContainer)?.TryFindEditorTextBuffer(); /// /// Returns the ITextSnapshot behind this SourceText, or null if it wasn't created from one. @@ -28,7 +28,10 @@ public static ITextBuffer TryGetTextBuffer(this SourceTextContainer textContaine /// /// The underlying ITextSnapshot. public static ITextSnapshot FindCorrespondingEditorTextSnapshot(this SourceText text) - => (text as SnapshotSourceText)?.EditorSnapshot; + => (text as SnapshotSourceText)?.TryFindEditorSnapshot(); + + internal static ITextImage TryFindCorrespondingEditorTextImage(this SourceText text) + => (text as SnapshotSourceText)?.TextImage; internal static TextLine AsTextLine(this ITextSnapshotLine line) => line.Snapshot.AsText().Lines[line.LineNumber]; @@ -37,7 +40,7 @@ public static SourceText AsText(this ITextSnapshot textSnapshot) => SnapshotSourceText.From(textSnapshot); internal static SourceText AsRoslynText(this ITextSnapshot textSnapshot, Encoding encoding) - => new SnapshotSourceText.ClosedSnapshotSourceText(textSnapshot, encoding); + => new SnapshotSourceText.ClosedSnapshotSourceText(((ITextSnapshot2)textSnapshot).TextImage, encoding); /// /// Gets the workspace corresponding to the text buffer. diff --git a/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/ITextBufferCloneService.cs b/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/ITextBufferCloneService.cs index 9ca95551d64d1a6aa74f759e0c2c476be8a4886a..7929a2898f74e366d2cfd903e1209d8f1cec9c2a 100644 --- a/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/ITextBufferCloneService.cs +++ b/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/ITextBufferCloneService.cs @@ -1,10 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.VisualStudio.Text; @@ -13,5 +8,6 @@ namespace Microsoft.CodeAnalysis.Text internal interface ITextBufferCloneService : IWorkspaceService { ITextBuffer Clone(SnapshotSpan span); + ITextBuffer Clone(ITextImage textImage); } -} +} \ No newline at end of file diff --git a/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/TextBufferCloneServiceFactory.cs b/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/TextBufferCloneServiceFactory.cs index 41a87d24d37d1e83db85068452567622ea616959..32e17c6e056ce2fa821401a990b9c534086c7c71 100644 --- a/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/TextBufferCloneServiceFactory.cs +++ b/src/EditorFeatures/Text/Implementation/TextBufferFactoryService/TextBufferCloneServiceFactory.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Text; @@ -23,7 +18,7 @@ internal class TextBufferCloneServiceFactory : IWorkspaceServiceFactory ITextBufferFactoryService textBufferFactoryService, IContentTypeRegistryService contentTypeRegistry) { - _singleton = new TextBufferCloneService((ITextBufferFactoryService2)textBufferFactoryService, contentTypeRegistry.UnknownContentType); + _singleton = new TextBufferCloneService((ITextBufferFactoryService3)textBufferFactoryService, contentTypeRegistry.UnknownContentType); } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) @@ -33,19 +28,20 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private class TextBufferCloneService : ITextBufferCloneService { - private readonly ITextBufferFactoryService2 _textBufferFactoryService; + private readonly ITextBufferFactoryService3 _textBufferFactoryService; private readonly IContentType _unknownContentType; - public TextBufferCloneService(ITextBufferFactoryService2 textBufferFactoryService, IContentType unknownContentType) + public TextBufferCloneService(ITextBufferFactoryService3 textBufferFactoryService, IContentType unknownContentType) { _textBufferFactoryService = textBufferFactoryService; _unknownContentType = unknownContentType; } public ITextBuffer Clone(SnapshotSpan span) - { - return _textBufferFactoryService.CreateTextBuffer(span, _unknownContentType); - } + => _textBufferFactoryService.CreateTextBuffer(span, _unknownContentType); + + public ITextBuffer Clone(ITextImage textImage) + => _textBufferFactoryService.CreateTextBuffer(textImage, _unknownContentType); } } -} +} \ No newline at end of file diff --git a/src/NuGet/BuildNuGets.csx b/src/NuGet/BuildNuGets.csx index 44bfa7d3ef385b46c0ad46759a36e19a9863a472..9c37504157cb5fcd51b88dd870bab3811941e65e 100644 --- a/src/NuGet/BuildNuGets.csx +++ b/src/NuGet/BuildNuGets.csx @@ -7,11 +7,12 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using System.Xml.Linq; -string usage = @"usage: BuildNuGets.csx []"; +string usage = @"usage: BuildNuGets.csx []"; -if (Args.Count < 3 || Args.Count > 4) +if (Args.Count < 4 || Args.Count > 5) { Console.WriteLine(usage); Environment.Exit(1); @@ -32,7 +33,19 @@ var BuildVersion = Args[1].Trim(); var BuildingReleaseNugets = IsReleaseVersion(BuildVersion); var NuspecDirPath = Path.Combine(SolutionRoot, "src/NuGet"); var OutDir = Path.GetFullPath(Args[2]).TrimEnd('\\'); -var NuspecNameFilter = Args.Count > 3 ? Args[3] : null; + +var CommitSha = Args[3].Replace("<", "").Replace(">", ""); +var CommitIsDeveloperBuild = CommitSha == ""; +if (!CommitIsDeveloperBuild && !Regex.IsMatch(CommitSha, "[A-Fa-f0-9]+")) +{ + Console.WriteLine("Invalid Git sha value: expected or a valid sha"); + Environment.Exit(1); +} +var CommitPathMessage = CommitIsDeveloperBuild + ? "This an unofficial build from a developer's machine" + : $"This package was built from the source at https://github.com/dotnet/roslyn/commit/{CommitSha}"; + +var NuspecNameFilter = Args.Count > 4 ? Args[4] : null; var LicenseUrlRedist = @"http://go.microsoft.com/fwlink/?LinkId=529443"; var LicenseUrlNonRedist = @"http://go.microsoft.com/fwlink/?LinkId=529444"; @@ -216,6 +229,7 @@ int PackFiles(string[] nuspecFiles, string licenseUrl) { "tags", Tags }, { "emptyDirPath", emptyDir }, { "additionalFilesPath", NuGetAdditionalFilesPath }, + { "commitPathMessage", CommitPathMessage }, { "srcDirPath", SrcDirPath } }; diff --git a/src/NuGet/Microsoft.CodeAnalysis.Build.Tasks.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Build.Tasks.nuspec index edb7eff46a0cdce375fddec3cfbb9fac79215e14..fbf3cb52db45e4660a3f6bd0c9299cae34d599ac 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Build.Tasks.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Build.Tasks.nuspec @@ -6,6 +6,8 @@ Contains the build task and targets used by MSBuild to run the C# and VB compilers. Supports using VBCSCompiler on Windows. + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.CSharp.CodeStyle.nuspec b/src/NuGet/Microsoft.CodeAnalysis.CSharp.CodeStyle.nuspec index 4a3618d07bf4369f81ae8dc0b2e474d8708e3e45..8caeb89137da587471659c5b7980ba07031ea2fe 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.CSharp.CodeStyle.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.CSharp.CodeStyle.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.CSharp.CodeStyle - + .NET Compiler Platform ("Roslyn") code style analyzers for C#. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Features.nuspec b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Features.nuspec index 5a588aefdb993d1b505015d2aad2be2dd429d20b..664108fa10c42b043fe978add35052b1f5d3a843 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Features.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Features.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.CSharp.Features - + .NET Compiler Platform ("Roslyn") support for creating C# editing experiences. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Scripting.nuspec b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Scripting.nuspec index 1fb7a32292f50fc21a9be3bb4ff83db0a8f51e9d..eb7b148cf5b9448b71635c938ff5183bcb83d968 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Scripting.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Scripting.nuspec @@ -3,10 +3,7 @@ Microsoft.CodeAnalysis.CSharp.Scripting Microsoft .NET Compiler Platform ("Roslyn") CSharp scripting package. - - Microsoft .NET Compiler Platform ("Roslyn") CSharp scripting package. - - + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Workspaces.nuspec b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Workspaces.nuspec index 961d25fc3b48096f8667555ee934ca6ac4132f8e..44a832397ce503a014dc01c815e704151797c772 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.CSharp.Workspaces.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.CSharp.Workspaces.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.CSharp.Workspaces - + .NET Compiler Platform ("Roslyn") support for analyzing C# projects and solutions. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.CSharp.nuspec b/src/NuGet/Microsoft.CodeAnalysis.CSharp.nuspec index 244c1e253dc39adb3fe7434a490429a73ff12486..d1034e394e39c35df816866d0c26578ece620660 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.CSharp.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.CSharp.nuspec @@ -2,11 +2,12 @@ Microsoft.CodeAnalysis.CSharp - + .NET Compiler Platform ("Roslyn") support for C#, Microsoft.CodeAnalysis.CSharp.dll. More details at https://aka.ms/roslyn-packages - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec index 596e7f707d23dfca4e4d42e8dee2bf48f620ca40..a3f8409a57cfc0a278762e3c35e85fba57727d1d 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Common.nuspec @@ -5,6 +5,8 @@ A shared package used by the Microsoft .NET Compiler Platform ("Roslyn"). A shared package used by the Microsoft .NET Compiler Platform ("Roslyn"). Do not install this package manually, it will be added as a prerequisite by other packages that require it. + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Compilers.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Compilers.nuspec index 9f5895fe18bad4d62653fcfc74f2fcfcde90fc23..d4534fd9852ec3005d21320c3240c0dd3d489583 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Compilers.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Compilers.nuspec @@ -7,6 +7,8 @@ .NET Compiler Platform ("Roslyn"). Install this package to get both C# and Visual Basic support. Install either of the dependencies directly to get one of the languages separately. More details at https://aka.ms/roslyn-packages + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Text.nuspec b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Text.nuspec index 7031596ce361ea0ee7c2aab873b2d8403068e40c..69b8d428b6c1eee4a982aed7c4936f165bf889fa 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Text.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.Text.nuspec @@ -2,11 +2,14 @@ Microsoft.CodeAnalysis.EditorFeatures.Text - + .NET Compiler Platform ("Roslyn") support for working with Visual Studio text buffers. + + + Supported Platforms: + - .NET Framework 4.6 -Supported Platforms: -- .NET Framework 4.6 + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.nuspec b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.nuspec index cb295a93ee9c9e3f883c1d54b25e7ca5fe78707f..97de80c0cde9c559ff9c4f194e01e15153029252 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.EditorFeatures.nuspec @@ -2,11 +2,14 @@ Microsoft.CodeAnalysis.EditorFeatures - + .NET Compiler Platform ("Roslyn") support for editor features inside the Visual Studio editor.. + + + Supported Platforms: + - .NET Framework 4.6 -Supported Platforms: -- .NET Framework 4.6 + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Features.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Features.nuspec index d95662b27cdecc09e7e2a50ef663d35bf44dac3f..2a04dbb50d97ca46b16b0613b3b7e3feb959066c 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Features.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Features.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.Features - + .NET Compiler Platform ("Roslyn") support for creating editing experiences. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Remote.Razor.ServiceHub.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Remote.Razor.ServiceHub.nuspec index 37f6aeadc0cf71b19a3df641d192552190b524de..bf2f8ffa4791ac92182d9cf0d3cbd477fd61532c 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Remote.Razor.ServiceHub.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Remote.Razor.ServiceHub.nuspec @@ -10,6 +10,8 @@ Supported Platforms: - .NET Framework 4.6 + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Remote.ServiceHub.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Remote.ServiceHub.nuspec index b9cf719a62df2b790a15c4a08f38fd1f78fe394c..c158e26d7cd1e920964177a748bfac08ce4982ae 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Remote.ServiceHub.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Remote.ServiceHub.nuspec @@ -10,6 +10,8 @@ Supported Platforms: - .NET Framework 4.6 + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Remote.Workspaces.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Remote.Workspaces.nuspec index 158ada81c9dd7ec7c2dbf35ddd2f43c6c47087c2..a6fc9c37b69add8e2c94ef1453956ba5e453ffe5 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Remote.Workspaces.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Remote.Workspaces.nuspec @@ -10,6 +10,8 @@ Supported Platforms: - .NET Framework 4.6 + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Scripting.Common.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Scripting.Common.nuspec index 2e61055828db78afd9bb76b8f8198af929fb0adc..e49d40d4041cf43fdd1eeec40b2a8cdeb214003b 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Scripting.Common.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Scripting.Common.nuspec @@ -5,6 +5,8 @@ Microsoft .NET Compiler Platform ("Roslyn") shared scripting package. Microsoft .NET Compiler Platform ("Roslyn") shared scripting package. Do not install this package manually, it will be added as a prerequisite by other packages that require it. + + $commitPathMessage$ en-US diff --git a/src/NuGet/Microsoft.CodeAnalysis.Scripting.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Scripting.nuspec index a653ebc47548d04c444e92cffc32c05a644e4492..eb9ad387920690d2a61193f1c6e3b0b8cfbf0f4a 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Scripting.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Scripting.nuspec @@ -5,6 +5,8 @@ Microsoft .NET Compiler Platform ("Roslyn") CSharp and VB scripting package. Microsoft .NET Compiler Platform ("Roslyn") CSharp and VB scripting package. + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.nuspec b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.nuspec index 14c4a8e33937624e6cf1ae9256aa00f4aa3a07be..ef0f6799c95f13a540854fd4f5850ff2f60db104 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.CodeStyle.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.VisualBasic.CodeStyle - + .NET Compiler Platform ("Roslyn") code style analyzers for Visual Basic. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Features.nuspec b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Features.nuspec index b6ebf2c9ed5ae92351d2156475346e251342ff7f..2634c48e994447a340ca2ebb9e80378a7527b2dd 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Features.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Features.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.VisualBasic.Features - + .NET Compiler Platform ("Roslyn") support for creating Visual Basic editing experiences. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Scripting.nuspec b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Scripting.nuspec index 0665ca37a77351e6bfc7cf4ec64a1cfeba534a69..dd385ee960aca80c16402609af40d17b32c53deb 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Scripting.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Scripting.nuspec @@ -5,6 +5,8 @@ Microsoft .NET Compiler Platform ("Roslyn") Visual Basic scripting package. Microsoft .NET Compiler Platform ("Roslyn") Visual Basic scripting package. + + $commitPathMessage$ en-US diff --git a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Workspaces.nuspec b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Workspaces.nuspec index 7814410ec9ee7a649cd2ffcdabfc2aaeca8c775d..8f01cb796f9ebb50caeef2a9a68ef97ed7cc97b6 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Workspaces.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.Workspaces.nuspec @@ -2,9 +2,10 @@ Microsoft.CodeAnalysis.VisualBasic.Workspaces - + .NET Compiler Platform ("Roslyn") support for analyzing Visual Basic projects and solutions. - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.nuspec b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.nuspec index a1607cba6bc8b147b7b26da40c1df4b502d117d7..2fc48064eea9b56747a6a6beae14fcff4f85a5d2 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.VisualBasic.nuspec @@ -2,11 +2,12 @@ Microsoft.CodeAnalysis.VisualBasic - + .NET Compiler Platform ("Roslyn") support for Visual Basic, Microsoft.CodeAnalysis.VisualBasic.dll. More details at https://aka.ms/roslyn-packages - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.Workspaces.Common.nuspec b/src/NuGet/Microsoft.CodeAnalysis.Workspaces.Common.nuspec index d00a5bce4d799d60cb126755d8efc7a47ad47c0c..1e3cba09cc498516f1f27c7e5dacea634abcf7ac 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.Workspaces.Common.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.Workspaces.Common.nuspec @@ -7,6 +7,8 @@ A shared package used by the .NET Compiler Platform ("Roslyn") including support for analyzing projects and solutions. Do not install this package manually, it will be added as a prerequisite by other packages that require it. + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.CodeAnalysis.nuspec b/src/NuGet/Microsoft.CodeAnalysis.nuspec index ad9c455ad807b3ff373ac05be1e70cd578daacfb..5c526a686b04ef13a9cfe52a48763b4fec11b447 100644 --- a/src/NuGet/Microsoft.CodeAnalysis.nuspec +++ b/src/NuGet/Microsoft.CodeAnalysis.nuspec @@ -12,6 +12,8 @@ - "Microsoft.CodeAnalysis.Compilers" (both compilers) - "Microsoft.CodeAnalysis.CSharp" (only the C# compiler) - "Microsoft.CodeAnalysis.VisualBasic (only the VB compiler) + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.NETCore.Compilers.nuspec b/src/NuGet/Microsoft.NETCore.Compilers.nuspec index e1f9ca0c5759859fb5dae6a035aea7df71176ca5..efe7434a1022f88d7928ca50443b99a2d5987bfa 100644 --- a/src/NuGet/Microsoft.NETCore.Compilers.nuspec +++ b/src/NuGet/Microsoft.NETCore.Compilers.nuspec @@ -2,9 +2,10 @@ Microsoft.NETCore.Compilers - + CoreCLR-compatible versions of the C# and VB compilers for use in MSBuild. - + + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.Net.CSharp.Interactive.netcore.nuspec b/src/NuGet/Microsoft.Net.CSharp.Interactive.netcore.nuspec index 77d27899321b398b30baefbf7449e3e11c2d88b6..14743e22e5a23fa244425128c334baeed4d9ae61 100644 --- a/src/NuGet/Microsoft.Net.CSharp.Interactive.netcore.nuspec +++ b/src/NuGet/Microsoft.Net.CSharp.Interactive.netcore.nuspec @@ -2,12 +2,13 @@ Microsoft.Net.CSharp.Interactive.netcore - + CoreCLR-compatible version of csi.exe. Supported Platforms: - .NET Core (NETCoreApp1.0) - + + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.Net.Compilers.netcore.nuspec b/src/NuGet/Microsoft.Net.Compilers.netcore.nuspec index 515b685fc371a72284ae2da7b545cdabd733f1b2..c6c2bc3f94d505a1de295a7a2c00d701d4be64c3 100644 --- a/src/NuGet/Microsoft.Net.Compilers.netcore.nuspec +++ b/src/NuGet/Microsoft.Net.Compilers.netcore.nuspec @@ -2,9 +2,10 @@ Microsoft.Net.Compilers.netcore - + CoreCLR-compatible versions of the C# and VB compilers. - + + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.Net.Compilers.nuspec b/src/NuGet/Microsoft.Net.Compilers.nuspec index c6b6d02a71fc9d9b30a39a57d3a84b79fb8ca252..8321c6247b429efee0a6664982fa58ef5c76e137 100644 --- a/src/NuGet/Microsoft.Net.Compilers.nuspec +++ b/src/NuGet/Microsoft.Net.Compilers.nuspec @@ -2,11 +2,12 @@ Microsoft.Net.Compilers - + .Net Compilers package. Referencing this package will cause the project to be built using the specific version of the C# and Visual Basic compilers contained in the package, as opposed to any system installed version. This package can be used to compile code targeting any platform, but can only be run using the desktop .NET 4.6+ Full Framework. - + + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.VisualStudio.IntegrationTest.Utilities.nuspec b/src/NuGet/Microsoft.VisualStudio.IntegrationTest.Utilities.nuspec index 224e88abc1e85055a2d4c85cc7a33b010311fd7d..bff72256b563b7e4424ef45bbca6d81404b33e0e 100644 --- a/src/NuGet/Microsoft.VisualStudio.IntegrationTest.Utilities.nuspec +++ b/src/NuGet/Microsoft.VisualStudio.IntegrationTest.Utilities.nuspec @@ -2,7 +2,8 @@ Microsoft.VisualStudio.IntegrationTest.Utilities - Utility methods used to run Visual Studio integration tests. + Utility methods used to run Visual Studio integration tests. + $commitPathMessage$ en-US true $version$ diff --git a/src/NuGet/Microsoft.VisualStudio.LanguageServices.Next.nuspec b/src/NuGet/Microsoft.VisualStudio.LanguageServices.Next.nuspec index 0d206e08985090bd6fe2d74ee67855134e93969e..592adfb3a9efecd0e199d65e2ddd4b3e222165fc 100644 --- a/src/NuGet/Microsoft.VisualStudio.LanguageServices.Next.nuspec +++ b/src/NuGet/Microsoft.VisualStudio.LanguageServices.Next.nuspec @@ -2,12 +2,13 @@ Microsoft.VisualStudio.LanguageServices.Next - + .NET Compiler Platform ("Roslyn") support for Visual Studio "15". Supported Platforms: - .NET Framework 4.6 - + + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient.nuspec b/src/NuGet/Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient.nuspec index 8eef70c5c35841c1ccc67cbe082a9278d77408e7..6c529e17b5c6dc52813f579d58f5396614b71d81 100644 --- a/src/NuGet/Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient.nuspec +++ b/src/NuGet/Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient.nuspec @@ -2,11 +2,14 @@ Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient - + .NET Compiler Platform ("Roslyn") support for Visual Studio "15". + + + Supported Platforms: + - .NET Framework 4.6 -Supported Platforms: -- .NET Framework 4.6 + $commitPathMessage$ diff --git a/src/NuGet/Microsoft.VisualStudio.LanguageServices.nuspec b/src/NuGet/Microsoft.VisualStudio.LanguageServices.nuspec index b9479010e609549d690c8dad6fa4afb82cc4c76e..025a295d3862d4eb8864c213da3c4a49a45f7e01 100644 --- a/src/NuGet/Microsoft.VisualStudio.LanguageServices.nuspec +++ b/src/NuGet/Microsoft.VisualStudio.LanguageServices.nuspec @@ -2,12 +2,13 @@ Microsoft.VisualStudio.LanguageServices - + .NET Compiler Platform ("Roslyn") support for Visual Studio. Supported Platforms: - .NET Framework 4.6 - + + $commitPathMessage$ diff --git a/src/NuGet/NuGet.proj b/src/NuGet/NuGet.proj index 2cf66880af289e0e57d3b14b2702752dc1b4b307..fba1be4c3d8f2e5a3964f6f02759078f9231b374 100644 --- a/src/NuGet/NuGet.proj +++ b/src/NuGet/NuGet.proj @@ -5,13 +5,13 @@ - + - + - + diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeTracker.cs index 19d8413d0a553f9c0175bbc4ad1a24261d7f804e..ef40a9fc74c1a3f85061f0e680dfd29a08c0172d 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeTracker.cs @@ -15,7 +15,7 @@ internal sealed class FileChangeTracker : IVsFileChangeEvents, IDisposable { private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Add | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Size); - private static readonly Lazy s_none = new Lazy(() => /* value doesn't matter*/ 42424242, LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy s_none = new Lazy(() => null, LazyThreadSafetyMode.ExecutionAndPublication); private readonly IVsFileChangeEx _fileChangeService; private readonly string _filePath; @@ -23,12 +23,30 @@ internal sealed class FileChangeTracker : IVsFileChangeEvents, IDisposable /// /// The cookie received from the IVsFileChangeEx interface that is watching for changes to - /// this file. + /// this file. This field may never be null, but might be a Lazy that has a value of null if + /// we either failed to subscribe over never have tried to subscribe. /// - private Lazy _fileChangeCookie; + private Lazy _fileChangeCookie; public event EventHandler UpdatedOnDisk; + /// + /// Operations on synchronize on a single lock within that service, so there's no point + /// in us trying to have multiple threads all trying to use it at the same time. When we queue a new background thread operation + /// we'll just do a continuation after the previous one. Any callers of will bypass that queue + /// and ensure it happens quickly. + /// + private static Task s_lastBackgroundTask = Task.CompletedTask; + + /// + /// The object to use as a monitor guarding . This lock is not strictly necessary, since we don't need + /// to ensure the background tasks happen entirely sequentially -- if we just removed the lock, and two subscriptions happened, we end up with + /// a 'branching' set of continuations, but that's fine since we're generally not running things in parallel. But it's easy to write, + /// and easy to delete if this lock has contention itself. Given we tend to call on the UI + /// thread, I don't expect to see contention. + /// + private static readonly object s_lastBackgroundTaskGate = new object(); + public FileChangeTracker(IVsFileChangeEx fileChangeService, string filePath) { _fileChangeService = fileChangeService; @@ -65,44 +83,81 @@ public void StartFileChangeListeningAsync() { if (_disposed) { - throw new ObjectDisposedException(typeof(FileChangeTracker).Name); + throw new ObjectDisposedException(nameof(FileChangeTracker)); } Contract.ThrowIfTrue(_fileChangeCookie != s_none); - _fileChangeCookie = new Lazy(() => + _fileChangeCookie = new Lazy(() => { - Marshal.ThrowExceptionForHR( - _fileChangeService.AdviseFileChange(_filePath, FileChangeFlags, this, out var newCookie)); - return newCookie; + try + { + Marshal.ThrowExceptionForHR( + _fileChangeService.AdviseFileChange(_filePath, FileChangeFlags, this, out var newCookie)); + return newCookie; + } + catch (Exception e) when (ShouldTrapException(e)) + { + return null; + } }, LazyThreadSafetyMode.ExecutionAndPublication); - // file change service is free-threaded. start running it in background right away - Task.Run(() => _fileChangeCookie.Value, CancellationToken.None); + lock (s_lastBackgroundTaskGate) + { + s_lastBackgroundTask = s_lastBackgroundTask.ContinueWith(_ => _fileChangeCookie.Value, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); + } + } + + private static bool ShouldTrapException(Exception e) + { + if (e is FileNotFoundException) + { + // The IVsFileChange implementation shouldn't ever be throwing exceptions like this, but it's a + // transient file system issue (perhaps the file being deleted while we're changing subscriptions) + // and so there's nothing better to do. We'll still non-fatal to track the rate this is happening + return FatalError.ReportWithoutCrash(e); + } + else if (e is PathTooLongException) + { + // Nothing better we can do. We won't be able to open this file either, and thus we'll do our usual + // reporting of unopenable/missing files to the output window as usual. + return true; + } + else + { + return false; + } } public void StopFileChangeListening() { if (_disposed) { - throw new ObjectDisposedException(typeof(FileChangeTracker).Name); + throw new ObjectDisposedException(nameof(FileChangeTracker)); } // there is a slight chance that we haven't subscribed to the service yet so we subscribe and unsubscribe // both here unnecessarily. but I believe that probably is a theoretical problem and never happen in real life. // and even if that happens, it will be just a perf hit - if (_fileChangeCookie != s_none) + if (_fileChangeCookie == s_none) { - var hr = _fileChangeService.UnadviseFileChange(_fileChangeCookie.Value); + return; + } + + var fileChangeCookie = _fileChangeCookie.Value; + _fileChangeCookie = s_none; - // Verify if the file still exists before reporting the unadvise failure. - // This is a workaround for VSO #248774 - if (hr != VSConstants.S_OK && File.Exists(_filePath)) + // We may have tried to subscribe but failed, so have to check a second time + if (fileChangeCookie.HasValue) + { + try + { + Marshal.ThrowExceptionForHR( + _fileChangeService.UnadviseFileChange(fileChangeCookie.Value)); + } + catch (Exception e) when (ShouldTrapException(e)) { - Marshal.ThrowExceptionForHR(hr); } - - _fileChangeCookie = s_none; } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs index 536f6bb852f564c18afc6752a087b033ec145ed3..29af6b55698ace7fb3f1610946fbf66d8bf2f9ee 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioAnalyzer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; @@ -31,7 +32,6 @@ public VisualStudioAnalyzer(string fullPath, IVsFileChangeEx fileChangeService, _tracker = new FileChangeTracker(fileChangeService, fullPath); _tracker.UpdatedOnDisk += OnUpdatedOnDisk; _tracker.StartFileChangeListeningAsync(); - _tracker.EnsureSubscription(); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; _projectId = projectId; _workspace = workspace; @@ -55,7 +55,9 @@ public AnalyzerReference GetReference() { if (File.Exists(_fullPath)) { - _analyzerReference = new AnalyzerFileReference(_fullPath, _loader); + // Pass down a custom loader that will ensure we are watching for file changes once we actually load the assembly. + var assemblyLoaderForFileTracker = new AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(this); + _analyzerReference = new AnalyzerFileReference(_fullPath, assemblyLoaderForFileTracker); ((AnalyzerFileReference)_analyzerReference).AnalyzerLoadFailed += OnAnalyzerLoadError; } else @@ -108,5 +110,30 @@ private void OnUpdatedOnDisk(object sender, EventArgs e) { UpdatedOnDisk?.Invoke(this, EventArgs.Empty); } + + /// + /// This custom loader just wraps an existing loader, but ensures that we start listening to the file + /// for changes once we've actually looked at the file. + /// + private class AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched : IAnalyzerAssemblyLoader + { + private readonly VisualStudioAnalyzer _analyzer; + + public AnalyzerAssemblyLoaderThatEnsuresFileBeingWatched(VisualStudioAnalyzer analyzer) + { + _analyzer = analyzer; + } + + public void AddDependencyLocation(string fullPath) + { + _analyzer._loader.AddDependencyLocation(fullPath); + } + + public Assembly LoadFromPath(string fullPath) + { + _analyzer._tracker.EnsureSubscription(); + return _analyzer._loader.LoadFromPath(fullPath); + } + } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.WorkspaceHostState.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.WorkspaceHostState.cs index eb06df0166551060e52513a8fd34d1f2ee08ce15..008601b0bd272e8e1eda8517cebc0dcbe851ecf2 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.WorkspaceHostState.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectTracker.WorkspaceHostState.cs @@ -139,7 +139,7 @@ internal void SolutionClosed() var solutionInfo = SolutionInfo.Create(id, version.Value, solutionFilePath, projects: projectInfos); - _tracker.NotifyWorkspaceHosts(host => host.OnSolutionAdded(solutionInfo)); + _workspaceHost.OnSolutionAdded(solutionInfo); _solutionAdded = true; } diff --git a/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.WorkspaceHost.cs b/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.WorkspaceHost.cs index 07dce0b1fc56eb35ec4d90af8d1b7b4fed6cf5b9..35a589526f430a0ed6dddc2a627be9bbd5d647c1 100644 --- a/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.WorkspaceHost.cs +++ b/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.WorkspaceHost.cs @@ -33,13 +33,6 @@ private class WorkspaceHost : ForegroundThreadAffinitizedObject, IVisualStudioWo _currentSolutionId = workspace.CurrentSolution.Id; } - public Task InitializeAsync() - { - // Ensure that we populate the remote service with the initial state of - // the workspace's solution. - return RegisterPrimarySolutionAsync(); - } - public void OnAfterWorkingFolderChange() { this.AssertIsForeground(); @@ -57,17 +50,18 @@ private async Task RegisterPrimarySolutionAsync() _currentSolutionId = _workspace.CurrentSolution.Id; var solutionId = _currentSolutionId; - using (var session = await _client.TryCreateSessionAsync(WellKnownRemoteHostServices.RemoteHostService, _workspace.CurrentSolution, CancellationToken.None).ConfigureAwait(false)) + using (var connection = await _client.TryCreateConnectionAsync(WellKnownRemoteHostServices.RemoteHostService, CancellationToken.None).ConfigureAwait(false)) { - if (session == null) + if (connection == null) { - // failed to create session. remote host might not responding or gone. + // failed to create connection. remote host might not responding or gone. return; } - await session.InvokeAsync(nameof(IRemoteHostService.RegisterPrimarySolutionId), solutionId).ConfigureAwait(false); + await connection.InvokeAsync( + nameof(IRemoteHostService.RegisterPrimarySolutionId), solutionId).ConfigureAwait(false); - await session.InvokeAsync( + await connection.InvokeAsync( nameof(IRemoteHostService.UpdateSolutionIdStorageLocation), solutionId, _workspace.DeferredState?.ProjectTracker.GetWorkingFolderPath(_workspace.CurrentSolution)).ConfigureAwait(false); } diff --git a/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.cs b/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.cs index e69e29b2928a29f728e486f3090c3d04eb15b3d7..8d37c280a457a6866deac4caa82b395953c28ce7 100644 --- a/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.cs +++ b/src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.cs @@ -73,28 +73,16 @@ private static async Task RegisterWorkspaceHostAsync(Workspace workspace, Remote return; } - // don't block UI thread while initialize workspace host - var host = new WorkspaceHost(vsWorkspace, client); - - // Initialize the remote side with whatever data we have currently for the workspace. - // As workspace changes happen, this host will get notified, and it can remote those - // changes appropriately over to the remote size. - await host.InitializeAsync().ConfigureAwait(false); - // RegisterWorkspaceHost is required to be called from UI thread so push the code // to UI thread to run. await Task.Factory.SafeStartNew(() => { - vsWorkspace.GetProjectTrackerAndInitializeIfNecessary(Shell.ServiceProvider.GlobalProvider).RegisterWorkspaceHost(host); - - // There may have been notifications fired by the workspace between the time we - // were created and now when we let it know about us. Because of that, we need - // to do another initialization pass to make sure all the current workpsace - // state is pushed over to the remote side. - // - // We can do this in a fire and forget manner. We don't want to block the UI - // thread while we're pushing this data over. - Task.Run(() => host.InitializeAsync()); + var projectTracker = vsWorkspace.GetProjectTrackerAndInitializeIfNecessary(Shell.ServiceProvider.GlobalProvider); + + var host = new WorkspaceHost(vsWorkspace, client); + + projectTracker.RegisterWorkspaceHost(host); + projectTracker.StartSendingEventsToWorkspaceHost(host); }, CancellationToken.None, ForegroundThreadAffinitizedObject.CurrentForegroundThreadData.TaskScheduler).ConfigureAwait(false); } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSendToInteractive.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSendToInteractive.cs index a1864f5fc5b320e731146a16b91fc43de64ca960..8b44b1dc995def95e8f72d4b6413c7d6d1957658 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSendToInteractive.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSendToInteractive.cs @@ -12,7 +12,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.CSharp [Collection(nameof(SharedIntegrationHostFixture))] public class CSharpSendToInteractive : AbstractInteractiveWindowTest { - private const string FileName = "test.cs"; + private const string FileName = "Program.cs"; public CSharpSendToInteractive(VisualStudioInstanceFactory instanceFactory) : base(instanceFactory) @@ -21,16 +21,16 @@ public CSharpSendToInteractive(VisualStudioInstanceFactory instanceFactory) var project = new Project(ProjectName); VisualStudio.SolutionExplorer.AddProject(project, WellKnownProjectTemplates.ConsoleApplication, Microsoft.CodeAnalysis.LanguageNames.CSharp); - VisualStudio.SolutionExplorer.AddFile( - project, + VisualStudio.SolutionExplorer.UpdateFile( + ProjectName, FileName, @"using System; namespace TestProj { - public class Program1 + public class Program { - public static void Main1(string[] args) + public static void Main(string[] args) { /* 1 */int x = 1;/* 2 */ @@ -51,7 +51,9 @@ public string M() return ""C.M()""; } } - }"); + } +", + open: true); VisualStudio.InteractiveWindow.SubmitText("using System;"); } @@ -214,7 +216,7 @@ public void AddAssemblyReferenceAndTypesToInteractive() VisualStudio.Workspace.WaitForAsyncOperations(FeatureAttribute.SolutionCrawler); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19441")] + [Fact] public void ResetInteractiveFromProjectAndVerify() { var assembly = new ProjectUtils.AssemblyReference("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs index 28402316668a57b187d8bd768b1fcd82f69a9b7b..2bee0960b4021e955c6fb2013beffa9c6e852cc4 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/SolutionExplorer_InProc.cs @@ -286,41 +286,42 @@ private string GetProjectTemplatePath(string projectTemplate, string languageNam } public void CleanUpOpenSolution() - { - var dte = GetDTE(); - dte.Documents.CloseAll(EnvDTE.vsSaveChanges.vsSaveChangesNo); - - if (dte.Solution != null) + => InvokeOnUIThread(() => { - var directoriesToDelete = new List(); + var dte = GetDTE(); + dte.Documents.CloseAll(EnvDTE.vsSaveChanges.vsSaveChangesNo); - // Save the full path to each project in the solution. This is so we can - // cleanup any folders after the solution is closed. - foreach (EnvDTE.Project project in dte.Solution.Projects) + if (dte.Solution != null) { - if (!string.IsNullOrEmpty(project.FullName)) + var directoriesToDelete = new List(); + + // Save the full path to each project in the solution. This is so we can + // cleanup any folders after the solution is closed. + foreach (EnvDTE.Project project in dte.Solution.Projects) { - directoriesToDelete.Add(Path.GetDirectoryName(project.FullName)); + if (!string.IsNullOrEmpty(project.FullName)) + { + directoriesToDelete.Add(Path.GetDirectoryName(project.FullName)); + } } - } - // Save the full path to the solution. This is so we can cleanup any folders after the solution is closed. - // The solution might be zero-impact and thus has no name, so deal with that - var solutionFullName = dte.Solution.FullName; + // Save the full path to the solution. This is so we can cleanup any folders after the solution is closed. + // The solution might be zero-impact and thus has no name, so deal with that + var solutionFullName = dte.Solution.FullName; - if (!string.IsNullOrEmpty(solutionFullName)) - { - directoriesToDelete.Add(Path.GetDirectoryName(solutionFullName)); - } + if (!string.IsNullOrEmpty(solutionFullName)) + { + directoriesToDelete.Add(Path.GetDirectoryName(solutionFullName)); + } - dte.Solution.Close(SaveFirst: false); + dte.Solution.Close(SaveFirst: false); - foreach (var directoryToDelete in directoriesToDelete) - { - IntegrationHelper.TryDeleteDirectoryRecursively(directoryToDelete); + foreach (var directoryToDelete in directoriesToDelete) + { + IntegrationHelper.TryDeleteDirectoryRecursively(directoryToDelete); + } } - } - } + }); private EnvDTE.Project GetProject(string nameOrFileName) => _solution.Projects.OfType().First(p diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs index 440cc284a9f9ecc3fde9c0fb5498efef80a48855..0a923d0efcafd178a62692c9bd50605d66e44080 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/SolutionExplorer_OutOfProc.cs @@ -90,6 +90,9 @@ public void OpenFileWithDesigner(ProjectUtils.Project project, string fileName) public void OpenFile(ProjectUtils.Project project, string fileName) => _inProc.OpenFile(project.Name, fileName); + public void UpdateFile(string projectName, string fileName, string contents, bool open = false) + => _inProc.UpdateFile(projectName, fileName, contents, open); + public void RenameFile(ProjectUtils.Project project, string oldFileName, string newFileName) => _inProc.RenameFile(project.Name, oldFileName, newFileName); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 17d79c3ea5a6d5a3f23c43dc01174e066dd5e756..7a51ce088f4a16e2c2e24fad59c7dcac360c2ec7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -1,12 +1,15 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -15,6 +18,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { using SymbolAndProjectIdSet = HashSet>; + using RelatedTypeCache = ConditionalWeakTable), AsyncLazy>>>; /// /// Provides helper methods for finding dependent types (derivations, implementations, @@ -39,17 +43,104 @@ internal static class DependentTypeFinder private static readonly ObjectPool s_setPool = new ObjectPool( () => new SymbolAndProjectIdSet(SymbolAndProjectIdComparer.SymbolEquivalenceInstance)); + // Caches from a types to their related types (in the context of a specific solution). + // Kept as a cache so that clients who make many calls into us won't end up computing + // the same data over and over again. Will be let go the moment the solution they're + // based off of is no longer alive. + // + // Importantly, the caches only store SymbolKeys and Ids. As such, they will not hold + // any Symbols or Compilations alive. + + private static readonly RelatedTypeCache s_typeToImmediatelyDerivedClassesMap = new RelatedTypeCache(); + private static readonly RelatedTypeCache s_typeToTransitivelyDerivedClassesMap = new RelatedTypeCache(); + private static readonly RelatedTypeCache s_typeToTransitivelyImplementingTypesMap = new RelatedTypeCache(); + private static readonly RelatedTypeCache s_typeToImmediatelyDerivedAndImplementingTypesMap = new RelatedTypeCache(); + + public static async Task>> FindTypesFromCacheOrComputeAsync( + INamedTypeSymbol type, + Solution solution, + IImmutableSet projects, + RelatedTypeCache cache, + Func>>> findAsync, + CancellationToken cancellationToken) + { + var dictionary = cache.GetOrCreateValue(solution); + + var result = default(ImmutableArray>); + + // Do a quick lookup first to avoid the allocation. If it fails, go through the + // slower allocating path. + var key = (type.GetSymbolKey(), projects); + if (!dictionary.TryGetValue(key, out var lazy)) + { + lazy = dictionary.GetOrAdd(key, + new AsyncLazy>( + async c => + { + // If we're the code that is actually computing the symbols, then just + // take our result and store it in the outer frame. That way the caller + // doesn't need to incur the cost of deserializing the symbol keys that + // we're create right below this. + result = await findAsync(c).ConfigureAwait(false); + return result.SelectAsArray(t => (t.Symbol.GetSymbolKey(), t.ProjectId)); + }, + cacheResult: true)); + } + + // If we were the caller that actually computed the symbols, then we can just return + // the values we got. + if (!result.IsDefault) + { + return result; + } + + // Otherwise, someone else computed the symbols and cached the results as symbol + // keys. Convert those symbol keys back to symbols and return. + var symbolKeys = await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); + var builder = ArrayBuilder>.GetInstance(); + + // Group by projectId so that we only process one project/compilation at a time. + // Also, process in dependency order so taht previous compilations are ready if + // they're referenced by later compilations. + var dependencyOrder = solution.GetProjectDependencyGraph() + .GetTopologicallySortedProjects() + .Select((id, index) => (id, index)) + .ToDictionary(t => t.id, t => t.index); + + var orderedGroups = symbolKeys.GroupBy(t => t.Item2).OrderBy(g => dependencyOrder[g.Key]); + foreach (var group in orderedGroups) + { + var project = solution.GetProject(group.Key); + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + foreach (var (symbolKey, _) in group) + { + var resolvedSymbol = symbolKey.Resolve(compilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (resolvedSymbol is INamedTypeSymbol namedType) + { + builder.Add(new SymbolAndProjectId(namedType, project.Id)); + } + } + } + + return builder.ToImmutableAndFree(); + } + /// /// Used for implementing the Inherited-By relation for progression. /// - internal static Task>> FindImmediatelyDerivedClassesAsync( + public static Task>> FindImmediatelyDerivedClassesAsync( INamedTypeSymbol type, Solution solution, CancellationToken cancellationToken) { - return FindDerivedClassesAsync( - SymbolAndProjectId.Create(type, projectId: null), solution, projects: null, - transitive: false, cancellationToken: cancellationToken); + return FindTypesFromCacheOrComputeAsync( + type, solution, projects: null, + cache: s_typeToImmediatelyDerivedClassesMap, + findAsync: c => FindDerivedClassesAsync( + SymbolAndProjectId.Create(type, projectId: null), solution, projects: null, + transitive: false, cancellationToken: c), + cancellationToken: cancellationToken); } /// @@ -61,9 +152,12 @@ internal static class DependentTypeFinder IImmutableSet projects, CancellationToken cancellationToken) { - return FindDerivedClassesAsync( - SymbolAndProjectId.Create(type, projectId: null), solution, projects, - transitive: true, cancellationToken: cancellationToken); + return FindTypesFromCacheOrComputeAsync( + type, solution, projects, s_typeToTransitivelyDerivedClassesMap, + c => FindDerivedClassesAsync( + SymbolAndProjectId.Create(type, projectId: null), solution, projects, + transitive: true, cancellationToken: c), + cancellationToken); } private static Task>> FindDerivedClassesAsync( @@ -96,7 +190,19 @@ internal static class DependentTypeFinder /// Implementation of for /// s /// - public static async Task>> FindTransitivelyImplementingTypesAsync( + public static Task>> FindTransitivelyImplementingTypesAsync( + INamedTypeSymbol type, + Solution solution, + IImmutableSet projects, + CancellationToken cancellationToken) + { + return FindTypesFromCacheOrComputeAsync( + type, solution, projects, s_typeToTransitivelyImplementingTypesMap, + c => FindTransitivelyImplementingTypesWorkerAsync(type, solution, projects, c), + cancellationToken); + } + + private static async Task>> FindTransitivelyImplementingTypesWorkerAsync( INamedTypeSymbol type, Solution solution, IImmutableSet projects, @@ -114,14 +220,18 @@ internal static class DependentTypeFinder /// /// Used for implementing the Inherited-By relation for progression. /// - internal static Task>> FindImmediatelyDerivedAndImplementingTypesAsync( + public static Task>> FindImmediatelyDerivedAndImplementingTypesAsync( INamedTypeSymbol type, Solution solution, CancellationToken cancellationToken) { - return FindDerivedAndImplementingTypesAsync( - SymbolAndProjectId.Create(type, projectId: null), solution, projects: null, - transitive: false, cancellationToken: cancellationToken); + return FindTypesFromCacheOrComputeAsync( + type, solution, projects: null, + cache: s_typeToImmediatelyDerivedAndImplementingTypesMap, + findAsync: c => FindDerivedAndImplementingTypesAsync( + SymbolAndProjectId.Create(type, projectId: null), solution, projects: null, + transitive: false, cancellationToken: c), + cancellationToken: cancellationToken); } private static Task>> FindDerivedAndImplementingTypesAsync( @@ -554,7 +664,11 @@ internal static class DependentTypeFinder var typesToSearchFor = CreateSymbolAndProjectIdSet(); typesToSearchFor.AddAll(sourceAndMetadataTypes); - var inheritanceQuery = new InheritanceQuery(sourceAndMetadataTypes); + var comparer = project.LanguageServices.GetService().StringComparer; + var inheritanceQuery = new InheritanceQuery(sourceAndMetadataTypes, comparer); + + var schedulerPair = new ConcurrentExclusiveSchedulerPair( + TaskScheduler.Default, maxConcurrencyLevel: Math.Max(1, Environment.ProcessorCount)); // As long as there are new types to search for, keep looping. while (typesToSearchFor.Count > 0) @@ -563,10 +677,14 @@ internal static class DependentTypeFinder inheritanceQuery.TypeNames.AddRange(typesToSearchFor.Select(c => c.Symbol.Name)); // Search all the documents of this project in parallel. - var tasks = project.Documents.Select(d => FindImmediatelyInheritingTypesInDocumentAsync( - d, typesToSearchFor, inheritanceQuery, - cachedModels, cachedInfos, - sourceTypeImmediatelyMatches, cancellationToken)).ToArray(); + var tasks = project.Documents.Select( + d => Task.Factory.StartNew( + () => FindImmediatelyInheritingTypesInDocumentAsync( + d, typesToSearchFor, inheritanceQuery, cachedModels, + cachedInfos, sourceTypeImmediatelyMatches, cancellationToken), + cancellationToken, + TaskCreationOptions.None, + schedulerPair.ConcurrentScheduler).Unwrap()).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); @@ -618,8 +736,8 @@ internal static class DependentTypeFinder Document document, SymbolAndProjectIdSet typesToSearchFor, InheritanceQuery inheritanceQuery, - ConcurrentSet cachedModels, - ConcurrentSet cachedInfos, + ConcurrentSet cachedModels, + ConcurrentSet cachedInfos, Func typeImmediatelyMatches, CancellationToken cancellationToken) { @@ -721,13 +839,13 @@ private class InheritanceQuery public readonly HashSet TypeNames; - public InheritanceQuery(SymbolAndProjectIdSet sourceAndMetadataTypes) + public InheritanceQuery(SymbolAndProjectIdSet sourceAndMetadataTypes, StringComparer comparer) { DerivesFromSystemObject = sourceAndMetadataTypes.Any(t => t.Symbol.SpecialType == SpecialType.System_Object); DerivesFromSystemValueType = sourceAndMetadataTypes.Any(t => t.Symbol.SpecialType == SpecialType.System_ValueType); DerivesFromSystemEnum = sourceAndMetadataTypes.Any(t => t.Symbol.SpecialType == SpecialType.System_Enum); DerivesFromSystemMulticastDelegate = sourceAndMetadataTypes.Any(t => t.Symbol.SpecialType == SpecialType.System_MulticastDelegate); - TypeNames = new HashSet(StringComparer.OrdinalIgnoreCase); + TypeNames = new HashSet(comparer); } }