Skip to content

Commit

Permalink
[NativeAOT] Fix formatting or assertion failures and fail-fasts (#91300)
Browse files Browse the repository at this point in the history
Fix a long standing TODO. Formatting of assertion failures and fail-fasts in native AOT matches regular CoreCLR with this change.

```csharp
Debug.Assert(false, "Something is not right.");
```

Before:
```
Unhandled Exception: System.Diagnostics.DebugProvider+DebugAssertException: Something is not right.
   at System.Diagnostics.DebugProvider.Fail(String, String) + 0x49
   at System.Diagnostics.Debug.Fail(String, String) + 0x5a
   at System.Diagnostics.Debug.Assert(Boolean, String, String) + 0x2d
   at System.Diagnostics.Debug.Assert(Boolean, String) + 0x28
   at Program.<Main>$(String[] args) + 0x1d
   at test!<BaseAddress>+0x1d3a67
   at test!<BaseAddress>+0x1d3af5
```

After:
```
Process terminated. Assertion failed.
Something is not right.
   at System.Diagnostics.DebugProvider.Fail(String, String) + 0x49
   at System.Diagnostics.Debug.Fail(String, String) + 0x5a
   at System.Diagnostics.Debug.Assert(Boolean, String, String) + 0x2d
   at System.Diagnostics.Debug.Assert(Boolean, String) + 0x28
   at Program.<Main>$(String[] args) + 0x1d
   at repro!<BaseAddress>+0x1d51c7
   at repro!<BaseAddress>+0x1d5255
```

```csharp
Environment.FailFast("Fatal error!");
```

Before:
```
Process terminated. Fatal error!
```

After:
```
Process terminated. Fatal error!
   at System.RuntimeExceptionHelpers.FailFast(String, Exception, String, RhFailFastReason, IntPtr, IntPtr) + 0x335
   at System.Environment.FailFast(String) + 0x35
   at Program.<Main>$(String[] args) + 0x1b
   at repro!<BaseAddress>+0x1d40c7
   at repro!<BaseAddress>+0x1d4155
```
  • Loading branch information
jkotas authored Sep 5, 2023
1 parent fdb5b2a commit 6e48c37
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum RhFailFastReason
UnhandledException = 2, // "unhandled exception"
UnhandledExceptionFromPInvoke = 3, // "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition."
EnvironmentFailFast = 4,
AssertionFailure = 5,
}

internal static unsafe partial class EH
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,6 @@ public virtual void TryGetMethodBase(IntPtr methodStartAddress, out MethodBase m
}
}

public virtual bool OnContractFailure(string? stackTrace, ContractFailureKind contractFailureKind, string? displayMessage, string userMessage, string conditionText, Exception innerException)
{
Debug.WriteLine("Assertion failed: " + (displayMessage ?? ""));
if (Debugger.IsAttached)
Debugger.Break();
return false;
}

public static DeveloperExperience Default
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ private bool WriteHeader(RhFailFastReason reason, ulong crashingThreadId, string

CrashReason crashReason = reason switch
{
RhFailFastReason.AssertionFailure or
RhFailFastReason.EnvironmentFailFast => CrashReason.EnvironmentFailFast,
RhFailFastReason.InternalError => CrashReason.InternalFailFast,
RhFailFastReason.UnhandledException or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,16 @@ public static void Exit(int exitCode)
// to assign blame for crashes. Don't mess with this, such as by making it call
// another managed helper method, unless you consult with some CLR Watson experts.
[DoesNotReturn]
public static void FailFast(string message) =>
public static void FailFast(string? message) =>
RuntimeExceptionHelpers.FailFast(message);

[DoesNotReturn]
public static void FailFast(string message, Exception exception) =>
public static void FailFast(string? message, Exception? exception) =>
RuntimeExceptionHelpers.FailFast(message, exception);

internal static void FailFast(string message, Exception exception, string _ /*errorSource*/)
{
// TODO: errorSource originates from CoreCLR (See: https://github.com/dotnet/coreclr/pull/15895)
// For now, we ignore errorSource but we should distinguish the way FailFast prints exception message using errorSource
bool result = DeveloperExperience.Default.OnContractFailure(exception.StackTrace, ContractFailureKind.Assert, message, null, null, null);
if (!result)
{
RuntimeExceptionHelpers.FailFast(message, exception);
}
}
// Used by System.Diagnostics.Debug.Assert/Fail
internal static void FailFast(string? message, Exception? exception, string errorSource) =>
RuntimeExceptionHelpers.FailFast(message, exception, errorSource: errorSource, reason: RhFailFastReason.AssertionFailure);

private static int GetProcessorCount() => Runtime.RuntimeImports.RhGetProcessCpuCount();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,40 +118,21 @@ public class RuntimeExceptionHelpers
}
}

private static string GetStringForFailFastReason(RhFailFastReason reason)
{
switch (reason)
private static string GetStringForFailFastReason(RhFailFastReason reason) => reason switch
{
case RhFailFastReason.InternalError:
return "Runtime internal error";
case RhFailFastReason.UnhandledException:
return "Unhandled exception: a managed exception was not handled before reaching unmanaged code";
case RhFailFastReason.UnhandledExceptionFromPInvoke:
return "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition";
case RhFailFastReason.EnvironmentFailFast:
return "Environment.FailFast was called";
default:
return "Unknown reason.";
}
}

[DoesNotReturn]
public static void FailFast(string message)
{
FailFast(message, null, RhFailFastReason.EnvironmentFailFast, IntPtr.Zero, IntPtr.Zero);
}

[DoesNotReturn]
public static void FailFast(string message, Exception? exception)
{
FailFast(message, exception, RhFailFastReason.EnvironmentFailFast, IntPtr.Zero, IntPtr.Zero);
}
RhFailFastReason.InternalError => "Runtime internal error",
RhFailFastReason.UnhandledException => "Unhandled exception: a managed exception was not handled before reaching unmanaged code",
RhFailFastReason.UnhandledExceptionFromPInvoke => "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition",
RhFailFastReason.EnvironmentFailFast => "Environment.FailFast was called",
RhFailFastReason.AssertionFailure => "Assertion failure",
_ => "Unknown reason."
};

// Used to report exceptions that *logically* go unhandled in the Fx code. For example, an
// exception that escapes from a ThreadPool workitem, or from a void-returning async method.
public static void ReportUnhandledException(Exception exception)
{
FailFast(GetStringForFailFastReason(RhFailFastReason.UnhandledException), exception, RhFailFastReason.UnhandledException, IntPtr.Zero, IntPtr.Zero);
FailFast(exception: exception, reason: RhFailFastReason.UnhandledException);
}

// This is the classlib-provided fail-fast function that will be invoked whenever the runtime
Expand All @@ -167,7 +148,7 @@ public static void RuntimeFailFast(RhFailFastReason reason, Exception? exception
// back into the dispatcher.
try
{
FailFast(message: null, exception, reason, pExAddress, pExContext);
FailFast(exception: exception, reason: reason, pExAddress: pExAddress, pExContext: pExContext);
}
catch
{
Expand Down Expand Up @@ -199,7 +180,9 @@ internal unsafe struct EXCEPTION_RECORD
private static ulong s_crashingThreadId;

[DoesNotReturn]
internal static unsafe void FailFast(string? message, Exception? exception, RhFailFastReason reason, IntPtr pExAddress, IntPtr pExContext)
internal static unsafe void FailFast(string? message = null, Exception? exception = null, string? errorSource = null,
RhFailFastReason reason = RhFailFastReason.EnvironmentFailFast,
IntPtr pExAddress = 0, IntPtr pExContext = 0)
{
IntPtr triageBufferAddress = IntPtr.Zero;
int triageBufferSize = 0;
Expand All @@ -209,33 +192,43 @@ internal static unsafe void FailFast(string? message, Exception? exception, RhFa
ulong previousThreadId = Interlocked.CompareExchange(ref s_crashingThreadId, currentThreadId, 0);
if (previousThreadId == 0)
{
message ??= GetStringForFailFastReason(reason);

CrashInfo crashInfo = new();
crashInfo.Open(reason, s_crashingThreadId, message);
crashInfo.Open(reason, s_crashingThreadId, message ?? GetStringForFailFastReason(reason));

bool minimalFailFast = (exception == PreallocatedOutOfMemoryException.Instance);
if (!minimalFailFast)
{
string prefix;
string outputMessage;
if (exception != null)
Internal.Console.Error.Write(((exception == null) || (reason is RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure)) ?
"Process terminated. " : "Unhandled exception. ");

if (errorSource != null)
{
Internal.Console.Error.Write(errorSource);
Internal.Console.Error.WriteLine();
}

if (message != null)
{
prefix = "Unhandled exception. ";
outputMessage = exception.ToString();
Internal.Console.Error.Write(message);
Internal.Console.Error.WriteLine();
}
else

if (errorSource == null && message == null && (exception == null || reason is RhFailFastReason.EnvironmentFailFast))
{
Internal.Console.Error.Write(GetStringForFailFastReason(reason));
Internal.Console.Error.WriteLine();
}

if (reason is RhFailFastReason.EnvironmentFailFast)
{
prefix = "Process terminated. ";
outputMessage = message;
Internal.Console.Error.Write(new StackTrace().ToString());
}

Internal.Console.Error.Write(prefix);
if (outputMessage != null)
if ((exception != null) && (reason is not RhFailFastReason.AssertionFailure))
{
Internal.Console.Error.Write(outputMessage);
Internal.Console.Error.Write(exception.ToString());
Internal.Console.Error.WriteLine();
}
Internal.Console.Error.Write(Environment.NewLine);

if (exception != null)
{
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Private.CoreLib/src/Internal/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ public static void WriteLine(string? s) =>

public static void WriteLine() =>
Write(Environment.NewLineConst);

public static partial class Error
{
public static void WriteLine() =>
Write(Environment.NewLineConst);
}
}
}

0 comments on commit 6e48c37

Please sign in to comment.