Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate API and add NFW telemetry for it #70365

Merged
merged 3 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

Expand Down Expand Up @@ -182,6 +183,7 @@ public new SyntaxTriviaList GetTrailingTrivia()
/// <summary>
/// Deserialize a syntax node from the byte stream.
/// </summary>
[Obsolete(SerializationDeprecationException.Text, error: false)]
public static SyntaxNode DeserializeFrom(Stream stream, CancellationToken cancellationToken = default)
{
if (stream == null)
Expand All @@ -194,6 +196,8 @@ public static SyntaxNode DeserializeFrom(Stream stream, CancellationToken cancel
throw new InvalidOperationException(CodeAnalysisResources.TheStreamCannotBeReadFrom);
}

// Report NFW to see if this is being used in the wild.
FatalError.ReportNonFatalError(new SerializationDeprecationException());
using var reader = ObjectReader.TryGetReader(stream, leaveOpen: true, cancellationToken);

if (reader == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using Roslyn.Test.Utilities;
using Xunit;

#pragma warning disable CS0618 // Type or member is obsolete
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonmalinowski the presumption here is that we do not actual send NFWs for compiler unit tests.


namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class SerializationTests
Expand Down Expand Up @@ -305,3 +307,5 @@ public class C
}
}
}

#pragma warning restore CS0618 // Type or member is obsolete
82 changes: 50 additions & 32 deletions src/Compilers/Core/Portable/InternalUtilities/FatalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public OperationCanceledIgnoringCallerTokenException(Exception innerException)
internal static class FatalError
{
public delegate void ErrorReporterHandler(Exception exception, ErrorSeverity severity, bool forceDump);

private static ErrorReporterHandler? s_handler;
private static ErrorReporterHandler? s_nonFatalHandler;

#pragma warning disable IDE0052 // Remove unread private members - We want to hold onto last exception to make investigation easier
private static Exception? s_reportedException;
Expand All @@ -37,21 +39,15 @@ internal static class FatalError
/// <summary>
/// Set by the host to handle an error report; this may crash the process or report telemetry.
/// </summary>
[DisallowNull]
public static ErrorReporterHandler? Handler
/// <param name="nonFatalHandler">A handler that will not crash the process when called. Used when calling <see
/// cref="ReportNonFatalError(Exception, ErrorSeverity, bool)"/></param>
public static void SetHandlers(ErrorReporterHandler handler, ErrorReporterHandler? nonFatalHandler)
{
get
{
return s_handler;
}

set
if (s_handler != handler)
{
if (s_handler != value)
{
Debug.Assert(s_handler == null, "Handler already set");
s_handler = value;
}
Debug.Assert(s_handler == null, "Handler already set");
s_handler = handler;
s_nonFatalHandler = nonFatalHandler;
}
}

Expand All @@ -72,25 +68,31 @@ public static void OverwriteHandler(ErrorReporterHandler? value)
/// This file is in linked into multiple layers, but we want to ensure that all layers have the same copy.
/// This lets us copy the handler in this instance into the same in another instance.
/// </remarks>
public static void CopyHandlerTo(Assembly assembly)
public static void CopyHandlersTo(Assembly assembly)
{
var targetType = assembly.GetType(typeof(FatalError).FullName!, throwOnError: true)!;
var targetHandlerProperty = targetType.GetProperty(nameof(FatalError.Handler), BindingFlags.Static | BindingFlags.Public)!;
if (Handler is not null)
{
// We need to convert the delegate type to the type in the linked copy since they won't have identity.
var convertedDelegate = Delegate.CreateDelegate(targetHandlerProperty.PropertyType, Handler.Target, method: Handler.Method);
targetHandlerProperty.SetValue(obj: null, value: convertedDelegate);
}
else
copyHandlerTo(assembly, s_handler, nameof(s_handler));
copyHandlerTo(assembly, s_nonFatalHandler, nameof(s_nonFatalHandler));

static void copyHandlerTo(Assembly assembly, ErrorReporterHandler? handler, string handlerName)
{
targetHandlerProperty.SetValue(obj: null, value: null);
var targetType = assembly.GetType(typeof(FatalError).FullName!, throwOnError: true)!;
var targetHandlerProperty = targetType.GetProperty(handlerName, BindingFlags.Static | BindingFlags.NonPublic)!;
if (handler is not null)
{
// We need to convert the delegate type to the type in the linked copy since they won't have identity.
var convertedDelegate = Delegate.CreateDelegate(targetHandlerProperty.PropertyType, handler.Target, handler.Method);
targetHandlerProperty.SetValue(obj: null, value: convertedDelegate);
}
else
{
targetHandlerProperty.SetValue(obj: null, value: null);
}
}
}

/// <summary>
/// Use in an exception filter to report an error without catching the exception.
/// The error is reported by calling <see cref="Handler"/>.
/// The error is reported by calling <see cref="s_handler"/>.
/// </summary>
/// <returns><see langword="false"/> to avoid catching the exception.</returns>
[DebuggerHidden]
Expand All @@ -101,7 +103,7 @@ public static bool ReportAndPropagate(Exception exception, ErrorSeverity severit
}

/// <summary>
/// Use in an exception filter to report an error (by calling <see cref="Handler"/>), unless the
/// Use in an exception filter to report an error (by calling <see cref="s_handler"/>), unless the
/// operation has been cancelled. The exception is never caught.
/// </summary>
/// <returns><see langword="false"/> to avoid catching the exception.</returns>
Expand All @@ -117,7 +119,7 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, ErrorSe
}

/// <summary>
/// <para>Use in an exception filter to report an error (by calling <see cref="Handler"/>), unless the
/// <para>Use in an exception filter to report an error (by calling <see cref="s_handler"/>), unless the
/// operation has been cancelled at the request of <paramref name="contextCancellationToken"/>. The exception is
/// never caught.</para>
///
Expand Down Expand Up @@ -152,7 +154,7 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, Cancell

/// <summary>
/// Report an error.
/// Calls <see cref="Handler"/> and doesn't pass the exception through (the method returns true).
/// Calls <see cref="s_handler"/> and doesn't pass the exception through (the method returns true).
/// This is generally expected to be used within an exception filter as that allows us to
/// capture data at the point the exception is thrown rather than when it is handled.
/// However, it can also be used outside of an exception filter. If the exception has not
Expand All @@ -175,7 +177,7 @@ public static bool ReportWithDumpAndCatch(Exception exception, ErrorSeverity sev
}

/// <summary>
/// Use in an exception filter to report an error (by calling <see cref="Handler"/>) and catch
/// Use in an exception filter to report an error (by calling <see cref="s_handler"/>) and catch
/// the exception, unless the operation was cancelled.
/// </summary>
/// <returns><see langword="true"/> to catch the exception if the error was reported; otherwise,
Expand All @@ -192,7 +194,7 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, ErrorSeveri
}

/// <summary>
/// <para>Use in an exception filter to report an error (by calling <see cref="Handler"/>) and
/// <para>Use in an exception filter to report an error (by calling <see cref="s_handler"/>) and
/// catch the exception, unless the operation was cancelled at the request of
/// <paramref name="contextCancellationToken"/>.</para>
///
Expand Down Expand Up @@ -230,12 +232,28 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, Cancellatio
// into many assemblies and finding the right type can be much harder.
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Report(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized, bool forceDump = false)
{
ReportException(exception, severity, forceDump, s_handler);
}

/// <summary>
/// Used to report a non-fatal-watson (when possible) to report an exception. The exception is not caught. Does
/// nothing if no non-fatal error handler is registered. See the second argument to <see
/// cref="SetHandlers(ErrorReporterHandler, ErrorReporterHandler?)"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ReportNonFatalError(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized, bool forceDump = false)
{
ReportException(exception, severity, forceDump, s_nonFatalHandler);
}

private static void ReportException(Exception exception, ErrorSeverity severity, bool forceDump, ErrorReporterHandler? handler)
{
// hold onto last exception to make investigation easier
s_reportedException = exception;
s_reportedExceptionMessage = exception.ToString();

if (s_handler == null)
if (handler == null)
{
return;
}
Expand All @@ -256,7 +274,7 @@ private static void Report(Exception exception, ErrorSeverity severity = ErrorSe
exception.Data[s_reportedMarker] = s_reportedMarker;
}

s_handler(exception, severity, forceDump);
handler(exception, severity, forceDump);
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/Compilers/Core/Portable/Syntax/SyntaxNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

Expand Down Expand Up @@ -1391,6 +1392,7 @@ public bool IsEquivalentTo(SyntaxNode node, bool topLevel = false)
/// Serializes the node to the given <paramref name="stream"/>.
/// Leaves the <paramref name="stream"/> open for further writes.
/// </summary>
[Obsolete(SerializationDeprecationException.Text, error: false)]
public virtual void SerializeTo(Stream stream, CancellationToken cancellationToken = default)
{
if (stream == null)
Expand All @@ -1403,10 +1405,26 @@ public virtual void SerializeTo(Stream stream, CancellationToken cancellationTok
throw new InvalidOperationException(CodeAnalysisResources.TheStreamCannotBeWrittenTo);
}

// Report NFW to see if this is being used in the wild.
FatalError.ReportNonFatalError(new SerializationDeprecationException());
using var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken);
writer.WriteValue(Green);
}

/// <summary>
/// Specialized exception subtype to make it easier to search telemetry streams for this specific case.
/// </summary>
private protected sealed class SerializationDeprecationException : Exception
{
public const string Text = "Syntax serialization support is deprecated and will be removed in a future version of this API";

public SerializationDeprecationException()
: base(Text)
{

}
}

#region Core Methods

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ internal static ICompilerServerHost CreateCompilerServerHost(ICompilerServerLogg
}

compilerServerHost.Logger.Log("Keep alive timeout is: {0} milliseconds.", keepAlive?.TotalMilliseconds ?? 0);
FatalError.Handler = FailFast.Handler;
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);

var dispatcher = new ServerDispatcher(compilerServerHost, clientConnectionHost, listener);
dispatcher.ListenAndDispatchConnections(keepAlive, cancellationToken);
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/Shared/Csc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal Csc(string responseFile, BuildPaths buildPaths, string[] args, IAnalyze

internal static int Run(string[] args, BuildPaths buildPaths, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader)
{
FatalError.Handler = FailFast.Handler;
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);

var responseFile = Path.Combine(buildPaths.ClientDirectory, CSharpCompiler.ResponseFileName);
var compiler = new Csc(responseFile, buildPaths, args, analyzerLoader);
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/Shared/Vbc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal Vbc(string responseFile, BuildPaths buildPaths, string[] args, IAnalyze

internal static int Run(string[] args, BuildPaths buildPaths, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader)
{
FatalError.Handler = FailFast.Handler;
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);

var responseFile = Path.Combine(buildPaths.ClientDirectory, VisualBasicCompiler.ResponseFileName);
var compiler = new Vbc(responseFile, buildPaths, args, analyzerLoader);
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/Test/Utilities/CSharp/CSharpTestSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ private CSharpTestSource(object value)

private static void CheckSerializable(SyntaxTree tree)
{
#pragma warning disable CS0618 // Type or member is obsolete
using var stream = new MemoryStream();
var root = tree.GetRoot();
root.SerializeTo(stream);
stream.Position = 0;

// verify absence of exception:
_ = CSharpSyntaxNode.DeserializeFrom(stream);
#pragma warning restore CS0618 // Type or member is obsolete
}

public SyntaxTree[] GetSyntaxTrees(CSharpParseOptions parseOptions, string sourceFileName = "")
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/Test/Utilities/VisualBasic/BasicTestSource.vb
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ Public Structure BasicTestSource
End Function

Private Shared Sub CheckSerializable(tree As SyntaxTree)
#Disable Warning BC40000 ' Type or member is obsolete
Using stream = New MemoryStream()
Dim root = tree.GetRoot()
root.SerializeTo(stream)
stream.Position = 0

' verify absence of exception
VisualBasicSyntaxNode.DeserializeFrom(stream)
#Enable Warning BC40000 ' Type or member is obsolete
End Using
End Sub

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Reflection
Imports System.Threading
Imports Microsoft.CodeAnalysis.ErrorReporting
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Expand Down Expand Up @@ -125,6 +126,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
''' <summary>
''' Deserialize a syntax node from a byte stream.
''' </summary>
<Obsolete(SerializationDeprecationException.Text, False)>
Public Shared Function DeserializeFrom(stream As IO.Stream, Optional cancellationToken As CancellationToken = Nothing) As SyntaxNode
If stream Is Nothing Then
Throw New ArgumentNullException(NameOf(stream))
Expand All @@ -134,6 +136,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Throw New InvalidOperationException(CodeAnalysisResources.TheStreamCannotBeReadFrom)
End If

' Report NFW to see if this Is being used in the wild.
FatalError.ReportNonFatalError(New SerializationDeprecationException())

Using reader = ObjectReader.TryGetReader(stream, leaveOpen:=True, cancellationToken:=cancellationToken)
If reader Is Nothing Then
Throw New ArgumentException(CodeAnalysisResources.Stream_contains_invalid_data, NameOf(stream))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Test.Utilities
Imports Xunit

Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests
#Disable Warning BC40000 ' Type or member is obsolete

Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests
Public Class SerializationTests

Private Sub RoundTrip(text As String, Optional expectRecursive As Boolean = True)
Expand Down Expand Up @@ -461,3 +462,5 @@ End Module
End Sub
End Class
End Namespace

#Enable Warning BC40000 ' Type or member is obsolete
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public abstract class ResultProvider : IDkmClrResultProvider

static ResultProvider()
{
FatalError.Handler = FailFast.Handler;
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);
}

internal ResultProvider(IDkmClrFormatter2 formatter2, IDkmClrFullNameProvider fullNameProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ public static void Initialize(ITelemetryReporter? reporter, string? telemetryLev
{
Contract.ThrowIfTrue(_instance is not null);

FatalError.Handler = ReportFault;
FatalError.CopyHandlerTo(typeof(Compilation).Assembly);
FatalError.ErrorReporterHandler handler = ReportFault;
FatalError.SetHandlers(handler, nonFatalHandler: handler);
FatalError.CopyHandlersTo(typeof(Compilation).Assembly);

if (reporter is not null && telemetryLevel is not null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Interactive/HostProcess/InteractiveHostEntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static class InteractiveHostEntryPoint
{
private static async Task<int> Main(string[] args)
{
FatalError.Handler = FailFast.Handler;
FatalError.SetHandlers(FailFast.Handler, nonFatalHandler: null);

// Disables Windows Error Reporting for the process, so that the process fails fast.
SetErrorMode(GetErrorMode() | ErrorMode.SEM_FAILCRITICALERRORS | ErrorMode.SEM_NOOPENFILEERRORBOX | ErrorMode.SEM_NOGPFAULTERRORBOX);
Expand Down
4 changes: 4 additions & 0 deletions src/Tools/IdeCoreBenchmarks/SerializationBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

#pragma warning disable CS0618 // Type or member is obsolete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these you could also the class itself as [Obsolete] which might make it clearer this obsolete in it's own right.


namespace IdeCoreBenchmarks
{
[MemoryDiagnoser]
Expand Down Expand Up @@ -96,3 +98,5 @@ public void DeserializeSyntaxNode()
}
}
}

#pragma warning restore CS0618 // Type or member is obsolete