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

Remove places where the compiler is swallowing exceptions #58870

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11722,7 +11722,13 @@ public void LoadinganalyzerNetStandard13()
at TestAnalyzer.get_SupportedDiagnostics()
at Microsoft.CodeAnalysis.Diagnostics.AnalyzerManager.AnalyzerExecutionContext.<>c__DisplayClass20_0.<ComputeDiagnosticDescriptors>b__0(Object _)
at Microsoft.CodeAnalysis.Diagnostics.AnalyzerExecutor.ExecuteAndCatchIfThrows_NoLock[TArg](DiagnosticAnalyzer analyzer, Action`1 analyze, TArg argument, Nullable`1 info)
-----", outputWithoutPaths);
-----
Analyzer 'TestAnalyzer' threw the following exception:
'System.NotImplementedException: 28
at TestAnalyzer.get_SupportedDiagnostics()
at Microsoft.CodeAnalysis.Diagnostics.AnalyzerExecutor.CreateDisablingMessage(DiagnosticAnalyzer analyzer, String analyzerName)
-----
'.", outputWithoutPaths);

Assert.Equal(0, result.ExitCode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,16 @@ public class C2
public async Task AnalyzerExceptionFromSupportedDiagnosticsCall()
{
var exception = new Exception();
const string AnalyzerName = "Microsoft.CodeAnalysis.UnitTests.Diagnostics.SuppressMessageAttributeTests+ThrowExceptionFromSupportedDiagnostics";

var diagnostic = Diagnostic("AD0001", null)
.WithArguments(
"Microsoft.CodeAnalysis.UnitTests.Diagnostics.SuppressMessageAttributeTests+ThrowExceptionFromSupportedDiagnostics",
AnalyzerName,
"System.Exception",
exception.Message,
(IFormattable)$@"{new LazyToString(() => exception.ToString().Substring(0, exception.ToString().IndexOf("---")))}-----")
(IFormattable)$@"{new LazyToString(() =>
exception.ToString().Substring(0, exception.ToString().IndexOf("---")) + "-----" + Environment.NewLine + Environment.NewLine +
string.Format(CodeAnalysisResources.CompilerAnalyzerThrowsDescription, AnalyzerName, exception.ToString() + Environment.NewLine + "-----" + Environment.NewLine))}")
.WithLocation(1, 1);

await VerifyCSharpAsync("public class C { }",
Expand Down
14 changes: 1 addition & 13 deletions src/Compilers/Core/Portable/Compilation/SemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,7 @@ public SyntaxTree SyntaxTree
/// <returns></returns>
public IOperation? GetOperation(SyntaxNode node, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
return GetOperationCore(node, cancellationToken);
}
#pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375
catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e, cancellationToken))
#pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete
{
// Log a Non-fatal-watson and then ignore the crash in the attempt of getting operation
Debug.Assert(false, "\n" + e.ToString());
}

return null;
return GetOperationCore(node, cancellationToken);
}

protected abstract IOperation? GetOperationCore(SyntaxNode node, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2697,12 +2697,8 @@ ImmutableArray<IOperation> getOperationsToAnalyzeWithStackGuard(ImmutableArray<I
{
return GetOperationsToAnalyze(operationBlocksToAnalyze);
}
#pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375
catch (Exception ex) when (ex is InsufficientExecutionStackException || FatalError.ReportIfNonFatalAndCatchUnlessCanceled(ex, cancellationToken))
#pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete
catch (Exception ex) when (ex is InsufficientExecutionStackException)
{
// the exception filter will short-circuit if `ex` is `InsufficientExecutionStackException` (from OperationWalker)
// and no non-fatal-watson will be logged as a result.
var diagnostic = AnalyzerExecutor.CreateDriverExceptionDiagnostic(ex);
var analyzer = this.Analyzers[0];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer
var analyzerName = analyzer.ToString();
var title = CodeAnalysisResources.CompilerAnalyzerFailure;
var messageFormat = CodeAnalysisResources.CompilerAnalyzerThrows;
var contextInformation = string.Join(Environment.NewLine, CreateDiagnosticDescription(info, e), CreateDisablingMessage(analyzer)).Trim();
var contextInformation = string.Join(Environment.NewLine, CreateDiagnosticDescription(info, e), CreateDisablingMessage(analyzer, analyzerName)).Trim();
var messageArguments = new[] { analyzerName, e.GetType().ToString(), e.Message, contextInformation };
var description = string.Format(CodeAnalysisResources.CompilerAnalyzerThrowsDescription, analyzerName, CreateDiagnosticDescription(info, e));
var descriptor = GetAnalyzerExceptionDiagnosticDescriptor(AnalyzerExceptionDiagnosticId, title, description, messageFormat);
Expand All @@ -1647,21 +1647,23 @@ private static string CreateDiagnosticDescription(AnalysisContextInfo? info, Exc
string.Format(CodeAnalysisResources.ExceptionContext, info?.GetContext()), e.CreateDiagnosticDescription());
}

private static string CreateDisablingMessage(DiagnosticAnalyzer analyzer)
private static string CreateDisablingMessage(DiagnosticAnalyzer analyzer, string analyzerName)
{
var diagnosticIds = ImmutableSortedSet<string>.Empty.WithComparer(StringComparer.OrdinalIgnoreCase);
try
{
foreach (var diagnostic in analyzer.SupportedDiagnostics)
{
diagnosticIds = diagnosticIds.Add(diagnostic.Id);
// If a null diagnostic is returned, we would have already reported that to the user earlier; we can just skip this.
if (diagnostic != null)
{
diagnosticIds = diagnosticIds.Add(diagnostic.Id);
}
}
}
#pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375
catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e))
#pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete
catch (Exception ex)
{
// Intentionally empty
return string.Format(CodeAnalysisResources.CompilerAnalyzerThrowsDescription, analyzerName, ex.CreateDiagnosticDescription());
}

if (diagnosticIds.IsEmpty)
Expand Down
100 changes: 10 additions & 90 deletions src/Compilers/Core/Portable/InternalUtilities/FatalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ public static ErrorReporterHandler? Handler
}
}

public static bool HandlerIsNonFatal { get; set; }

/// <summary>
/// Same as setting the Handler property except that it avoids the assert. This is useful in
/// test code which needs to verify the handler is called in specific cases and will continually
Expand Down Expand Up @@ -85,9 +83,6 @@ public static void CopyHandlerTo(Assembly assembly)
{
targetHandlerProperty.SetValue(obj: null, value: null);
}

var targetIsNonFatalProperty = targetType.GetProperty(nameof(HandlerIsNonFatal), BindingFlags.Static | BindingFlags.Public)!;
targetIsNonFatalProperty.SetValue(obj: null, value: HandlerIsNonFatal);
}

#endif
Expand Down Expand Up @@ -149,6 +144,11 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, Cancell
return ReportAndPropagate(exception, severity);
}

// Since the command line compiler has no way to catch exceptions, report them, and march on, we
// simply don't offer such a mechanism here to avoid accidental swallowing of exceptions.

#if !COMPILERCORE

/// <summary>
/// Report an error.
/// Calls <see cref="Handler"/> and doesn't pass the exception through (the method returns true).
Expand All @@ -160,24 +160,14 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, Cancell
/// </summary>
/// <returns>True to catch the exception.</returns>
[DebuggerHidden]
#if COMPILERCORE
private
#else
public
#endif
static bool ReportAndCatch(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
public static bool ReportAndCatch(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
Report(exception, severity);
return true;
}

[DebuggerHidden]
#if COMPILERCORE
private
#else
public
#endif
static bool ReportWithDumpAndCatch(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
public static bool ReportWithDumpAndCatch(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
Report(exception, severity, forceDump: true);
return true;
Expand All @@ -190,42 +180,13 @@ static bool ReportWithDumpAndCatch(Exception exception, ErrorSeverity severity =
/// <returns><see langword="true"/> to catch the exception if the error was reported; otherwise,
/// <see langword="false"/> to propagate the exception if the operation was cancelled.</returns>
[DebuggerHidden]
#if COMPILERCORE
private
#else
public
#endif
static bool ReportAndCatchUnlessCanceled(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
if (exception is OperationCanceledException)
{
return false;
}

return ReportAndCatch(exception, severity);
}

/// <summary>
/// Use in an exception filter to report an error (by calling <see cref="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,
/// <see langword="false"/> to propagate the exception if the operation was cancelled.</returns>
// This is only a temporary shim; the removal is tracked by https://github.com/dotnet/roslyn/issues/58375.
[DebuggerHidden]
[Obsolete("This is only to support places the compiler is catching exceptions on the command line; do not use in new code.")]
public static bool ReportIfNonFatalAndCatchUnlessCanceled(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
public static bool ReportAndCatchUnlessCanceled(Exception exception, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
if (exception is OperationCanceledException)
{
return false;
}

if (!HandlerIsNonFatal)
{
return true;
}

return ReportAndCatch(exception, severity);
}

Expand All @@ -249,12 +210,7 @@ public static bool ReportIfNonFatalAndCatchUnlessCanceled(Exception exception, E
/// <returns><see langword="true"/> to catch the exception if the error was reported; otherwise,
/// <see langword="false"/> to propagate the exception if the operation was cancelled.</returns>
[DebuggerHidden]
#if COMPILERCORE
private
#else
public
#endif
static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized)
public static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken))
{
Expand All @@ -264,43 +220,7 @@ static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken
return ReportAndCatch(exception, severity);
}

/// <summary>
/// <para>Use in an exception filter to report an error (by calling <see cref="Handler"/>) and
/// catch the exception, unless the operation was cancelled at the request of
/// <paramref name="contextCancellationToken"/>.</para>
///
/// <para>Cancellable operations are only expected to throw <see cref="OperationCanceledException"/> if the
/// applicable <paramref name="contextCancellationToken"/> indicates cancellation is requested by setting
/// <see cref="CancellationToken.IsCancellationRequested"/>. Unexpected cancellation, i.e. an
/// <see cref="OperationCanceledException"/> which occurs without <paramref name="contextCancellationToken"/>
/// requesting cancellation, is treated as an error by this method.</para>
///
/// <para>This method does not require <see cref="OperationCanceledException.CancellationToken"/> to match
/// <paramref name="contextCancellationToken"/>, provided cancellation is expected per the previous
/// paragraph.</para>
/// </summary>
/// <param name="contextCancellationToken">A <see cref="CancellationToken"/> which will have
/// <see cref="CancellationToken.IsCancellationRequested"/> set if cancellation is expected.</param>
/// <returns><see langword="true"/> to catch the exception if the error was reported; otherwise,
/// <see langword="false"/> to propagate the exception if the operation was cancelled.</returns>
// This is only a temporary shim; the removal is tracked by https://github.com/dotnet/roslyn/issues/58375.
[DebuggerHidden]
[Obsolete("This is only to support places the compiler is catching and swallowing exceptions on the command line; do not use in new code.")]
public static bool ReportIfNonFatalAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized)
{
if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken))
{
return false;
}

if (!HandlerIsNonFatal)
{
// We'll catch the exception, but we won't report anything.
return true;
}

return ReportAndCatch(exception, severity);
}
#endif

private static readonly object s_reportedMarker = new();

Expand Down
18 changes: 3 additions & 15 deletions src/Compilers/Core/Portable/Operations/ControlFlowGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,9 @@ internal static ControlFlowGraph CreateCore(IOperation operation, string argumen
throw new ArgumentException(CodeAnalysisResources.OperationHasNullSemanticModel, argumentNameForException);
}

try
{
ControlFlowGraph controlFlowGraph = ControlFlowGraphBuilder.Create(operation);
Debug.Assert(controlFlowGraph.OriginalOperation == operation);
return controlFlowGraph;
}
#pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375
catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e, cancellationToken))
#pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete
{
// Log a Non-fatal-watson and then ignore the crash in the attempt of getting flow graph.
Debug.Assert(false, "\n" + e.ToString());
}

return null;
ControlFlowGraph controlFlowGraph = ControlFlowGraphBuilder.Create(operation);
Debug.Assert(controlFlowGraph.OriginalOperation == operation);
return controlFlowGraph;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ internal static class FaultReporter
public static void InitializeFatalErrorHandlers()
{
FatalError.Handler = static (exception, severity, forceDump) => ReportFault(exception, ConvertSeverity(severity), forceDump);
FatalError.HandlerIsNonFatal = true;
FatalError.CopyHandlerTo(typeof(Compilation).Assembly);
}

Expand Down