From 542983327f61706b2ed07583601861846361364b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:35:11 -0800 Subject: [PATCH] Revert "Report StackOverflowException on NativeAOT on Linux (#94485)" (#95415) This reverts commit 261668fef9cb3c61c9b809b0a715ee873358a9d9. We're hitting seg faults instead of null reference exceptions in tests after this change. Reverting to make sure this caused the issue, and then investigate. --- .../nativeaot/Runtime/inc/CommonTypes.h | 1 - src/coreclr/nativeaot/Runtime/thread.cpp | 104 +------- src/coreclr/nativeaot/Runtime/thread.h | 12 +- .../Runtime/unix/HardwareExceptions.cpp | 43 ++-- .../nativeaot/Runtime/unix/UnixSignals.cpp | 4 +- .../nativeaot/Runtime/unix/UnixSignals.h | 2 +- .../Common/CoreCLRTestLibrary/Utilities.cs | 2 +- .../exceptions/stackoverflow/stackoverflow.cs | 5 +- .../stackoverflow/stackoverflow.csproj | 14 ++ .../stackoverflow/stackoverflow3.cs | 8 +- .../stackoverflow/stackoverflow3.csproj | 15 ++ .../stackoverflow/stackoverflowtester.cs | 223 ++++++++---------- .../stackoverflow/stackoverflowtester.csproj | 8 +- src/tests/issues.targets | 6 +- 14 files changed, 159 insertions(+), 288 deletions(-) create mode 100644 src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj create mode 100644 src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj diff --git a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h index bd0675971c0a8..fde2d9247e07b 100644 --- a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h +++ b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h @@ -4,7 +4,6 @@ #ifndef __COMMON_TYPES_H__ #define __COMMON_TYPES_H__ -#include #include #include #include diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 8972cd46ac9cb..4c924524fd4d7 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -5,7 +5,6 @@ #include "CommonTypes.h" #include "CommonMacros.h" #include "daccess.h" -#include "CommonMacros.inl" #include "PalRedhawkCommon.h" #include "PalRedhawk.h" #include "rhassert.h" @@ -28,11 +27,6 @@ #include "RhConfig.h" #include "RhVolatile.h" -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) -#include -#include -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - #ifndef DACCESS_COMPILE EXTERN_C NATIVEAOT_API void* REDHAWK_CALLCONV RhpHandleAlloc(void* pObject, int type); @@ -287,9 +281,6 @@ void Thread::Construct() if (StressLog::StressLogOn(~0u, 0)) m_pThreadStressLog = StressLog::CreateThreadStressLog(this); #endif // STRESS_LOG -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - EnsureSignalAlternateStack(); -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) // Everything else should be initialized to 0 via the static initialization of tls_CurrentThread. @@ -306,89 +297,6 @@ void Thread::Construct() ASSERT(m_interruptedContext == NULL); } -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) -void Thread::FreeSignalAlternateStack() -{ - void *altstack = m_alternateStack; - m_alternateStack = nullptr; - - if (altstack != nullptr) - { - stack_t ss, oss; - // The man page for sigaltstack says that when the ss.ss_flags is set to SS_DISABLE, - // all other ss fields are ignored. However, MUSL implementation checks that the - // ss_size is >= MINSIGSTKSZ even in this case. - ss.ss_size = MINSIGSTKSZ; - ss.ss_flags = SS_DISABLE; - ss.ss_sp = NULL; - int st = sigaltstack(&ss, &oss); - if ((st == 0) && (oss.ss_flags != SS_DISABLE)) - { - // Make sure this altstack is this PAL's before freeing. - if (oss.ss_sp == altstack) - { - int st = munmap(oss.ss_sp, oss.ss_size); - _ASSERTE(st == 0); - } - } - } -} - -bool Thread::EnsureSignalAlternateStack() -{ - int st = 0; - - stack_t oss; - - // Query the current alternate signal stack - st = sigaltstack(NULL, &oss); - if ((st == 0) && (oss.ss_flags == SS_DISABLE)) - { - // There is no alternate stack for SIGSEGV handling installed yet so allocate one - - // We include the size of the SignalHandlerWorkerReturnPoint in the alternate stack size since the - // context contained in it is large and the SIGSTKSZ was not sufficient on ARM64 during testing. - int altStackSize = SIGSTKSZ; -#ifdef HAS_ADDRESS_SANITIZER - // Asan also uses alternate stack so we increase its size on the SIGSTKSZ * 4 that enough for asan - // (see kAltStackSize in compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cc) - altStackSize += SIGSTKSZ * 4; -#endif - altStackSize = ALIGN_UP(altStackSize, PalOsPageSize()); - int flags = MAP_ANONYMOUS | MAP_PRIVATE; -#ifdef MAP_STACK - flags |= MAP_STACK; -#endif - void* altStack = mmap(NULL, altStackSize, PROT_READ | PROT_WRITE, flags, -1, 0); - if (altStack != MAP_FAILED) - { - // create a guard page for the alternate stack - st = mprotect(altStack, PalOsPageSize(), PROT_NONE); - if (st == 0) - { - stack_t ss; - ss.ss_sp = (char*)altStack; - ss.ss_size = altStackSize; - ss.ss_flags = 0; - st = sigaltstack(&ss, NULL); - } - - if (st == 0) - { - m_alternateStack = altStack; - } - else - { - int st2 = munmap(altStack, altStackSize); - _ASSERTE(st2 == 0); - } - } - } - - return (st == 0); -} -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - bool Thread::IsInitialized() { return (m_ThreadStateFlags != TSF_Unknown); @@ -453,10 +361,6 @@ void Thread::Destroy() } #endif //FEATURE_SUSPEND_REDIRECTION -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - FreeSignalAlternateStack(); -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - ASSERT(m_pGCFrameRegistrations == NULL); } @@ -962,19 +866,19 @@ void Thread::Unhijack() } // This unhijack routine is called to undo a hijack, that is potentially on a different thread. -// +// // Although there are many code sequences (here and in asm) to // perform an unhijack operation, they will never execute concurrently: -// +// // - A thread may unhijack itself at any time so long as it does that from unmanaged code while in coop mode. // This ensures that coop thread can access its stack synchronously. // Unhijacking from unmanaged code ensures that another thread will not attempt to hijack it, // since we only hijack threads that are executing managed code. -// +// // - A GC thread may access a thread asynchronously, including unhijacking it. // Asynchronously accessed thread must be in preemptive mode and should not // access the managed portion of its stack. -// +// // - A thread that owns the suspension can access another thread as long as the other thread is // in preemptive mode or suspended in managed code. // Either way the other thread cannot be accessing its hijack. diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index aa3f370a8643d..7aa473eb6763d 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -94,7 +94,7 @@ struct ThreadBuffer HANDLE m_hPalThread; // WARNING: this may legitimately be INVALID_HANDLE_VALUE void ** m_ppvHijackedReturnAddressLocation; void * m_pvHijackedReturnAddress; - uintptr_t m_uHijackedReturnValueFlags; + uintptr_t m_uHijackedReturnValueFlags; PTR_ExInfo m_pExInfoStackHead; Object* m_threadAbortException; // ThreadAbortException instance -set only during thread abort Object* m_pThreadLocalStatics; @@ -112,9 +112,6 @@ struct ThreadBuffer #ifdef FEATURE_GC_STRESS uint32_t m_uRand; // current per-thread random number #endif // FEATURE_GC_STRESS -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - void * m_alternateStack; // ptr to alternate signal stack -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) }; struct ReversePInvokeFrame @@ -155,7 +152,7 @@ class Thread : private ThreadBuffer // For suspension APCs it is mostly harmless, but wasteful and in extreme // cases may force the target thread into stack oveflow. // We use this flag to avoid sending another APC when one is still going through. - // + // // On Unix this is an optimization to not queue up more signals when one is // still being processed. }; @@ -315,11 +312,6 @@ class Thread : private ThreadBuffer bool IsActivationPending(); void SetActivationPending(bool isPending); - -#if defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) - bool EnsureSignalAlternateStack(); - void FreeSignalAlternateStack(); -#endif // defined(TARGET_UNIX) && !HAVE_MACH_EXCEPTIONS && !defined(HOST_TVOS) }; #ifndef __GCENV_BASE_INCLUDED__ diff --git a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp index 99682c36362f2..03ee5d514c3dc 100644 --- a/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp @@ -11,9 +11,6 @@ #include "HardwareExceptions.h" #include "UnixSignals.h" #include "PalCreateDump.h" -#include "thread.h" -#include "threadstore.h" -#include #if defined(HOST_APPLE) #include @@ -548,17 +545,6 @@ bool HardwareExceptionHandler(int code, siginfo_t *siginfo, void *context, void* // Handler for the SIGSEGV signal void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context) { - // First check if we have a stack overflow - size_t sp = ((UNIX_CONTEXT *)context)->GetSp(); - size_t failureAddress = (size_t)siginfo->si_addr; - - // If the failure address is at most one page above or below the stack pointer, - // we have a stack overflow. - if ((failureAddress - (sp - PalOsPageSize())) < (size_t)PalOsPageSize() * 2) - { - PalPrintFatalError("\nProcess is terminating due to StackOverflowException.\n"); - RhFailFast(); - } bool isHandled = HardwareExceptionHandler(code, siginfo, context, siginfo->si_addr); if (isHandled) { @@ -603,8 +589,7 @@ void SIGFPEHandler(int code, siginfo_t *siginfo, void *context) // Initialize hardware exception handling bool InitializeHardwareExceptionHandling() { - // Run SIGSEGV handler on separate stack so we can handle stack overflow. Otherwise, the current (invalid) stack is used and another segfault is raised. - if (!AddSignalHandler(SIGSEGV, SIGSEGVHandler, &g_previousSIGSEGV, SA_ONSTACK)) + if (!AddSignalHandler(SIGSEGV, SIGSEGVHandler, &g_previousSIGSEGV)) { return false; } @@ -616,23 +601,23 @@ bool InitializeHardwareExceptionHandling() #if defined(HOST_APPLE) #ifndef HOST_TVOS // task_set_exception_ports is not supported on tvOS - // LLDB installs task-wide Mach exception handlers. XNU dispatches Mach - // exceptions first to any registered "activation" handler and then to - // any registered task handler before dispatching the exception to a - // host-wide Mach exception handler that does translation to POSIX - // signals. This makes it impossible to use LLDB with implicit null + // LLDB installs task-wide Mach exception handlers. XNU dispatches Mach + // exceptions first to any registered "activation" handler and then to + // any registered task handler before dispatching the exception to a + // host-wide Mach exception handler that does translation to POSIX + // signals. This makes it impossible to use LLDB with implicit null // checks in NativeAOT; continuing execution after LLDB traps an // EXC_BAD_ACCESS will result in LLDB's EXC_BAD_ACCESS handler being // invoked again. This also interferes with the translation of SIGFPEs // to .NET-level ArithmeticExceptions. Work around this here by - // installing a no-op task-wide Mach exception handler for - // EXC_BAD_ACCESS and EXC_ARITHMETIC. - kern_return_t kr = task_set_exception_ports( - mach_task_self(), - EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, /* SIGSEGV, SIGFPE */ - MACH_PORT_NULL, - EXCEPTION_STATE_IDENTITY, - MACHINE_THREAD_STATE); + // installing a no-op task-wide Mach exception handler for + // EXC_BAD_ACCESS and EXC_ARITHMETIC. + kern_return_t kr = task_set_exception_ports( + mach_task_self(), + EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, /* SIGSEGV, SIGFPE */ + MACH_PORT_NULL, + EXCEPTION_STATE_IDENTITY, + MACHINE_THREAD_STATE); ASSERT(kr == KERN_SUCCESS); #endif #endif diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp index 813c6704b6213..33852920653ff 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.cpp @@ -9,11 +9,11 @@ #include "UnixSignals.h" // Add handler for hardware exception signal -bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction, int additionalFlags) +bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction) { struct sigaction newAction; - newAction.sa_flags = SA_RESTART | additionalFlags; + newAction.sa_flags = SA_RESTART; newAction.sa_handler = NULL; newAction.sa_sigaction = handler; newAction.sa_flags |= SA_SIGINFO; diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h index 9980ffb5df9e5..60e08d461e383 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h +++ b/src/coreclr/nativeaot/Runtime/unix/UnixSignals.h @@ -14,7 +14,7 @@ typedef void (*SignalHandler)(int code, siginfo_t* siginfo, void* context); -bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction, int additionalFlags = 0); +bool AddSignalHandler(int signal, SignalHandler handler, struct sigaction* previousAction); void RestoreSignalHandler(int signal_id, struct sigaction* previousAction); #endif // __UNIX_SIGNALS_H__ diff --git a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs index 20b299b814fce..c298a46abca87 100644 --- a/src/tests/Common/CoreCLRTestLibrary/Utilities.cs +++ b/src/tests/Common/CoreCLRTestLibrary/Utilities.cs @@ -94,7 +94,7 @@ public static bool IsWindowsIoTCore public static bool IsMonoRuntime => Type.GetType("Mono.RuntimeStructs") != null; public static bool IsNotMonoRuntime => !IsMonoRuntime; - public static bool IsNativeAot => IsSingleFile && IsNotMonoRuntime && !IsReflectionEmitSupported; + public static bool IsNativeAot => IsNotMonoRuntime && !IsReflectionEmitSupported; public static bool HasAssemblyFiles => !string.IsNullOrEmpty(typeof(Utilities).Assembly.Location); public static bool IsSingleFile => !HasAssemblyFiles; diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs index 64aafb1238a47..8249f4bbb44ac 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.cs @@ -65,8 +65,7 @@ struct LargeStruct65536 LargeStruct4096 se; LargeStruct4096 sf; } - - internal class StackOverflow + class Program { [MethodImpl(MethodImplOptions.NoInlining)] static void InfiniteRecursionA() @@ -135,7 +134,7 @@ static void SecondaryThreadsTest(bool smallframe) } } - internal static void Run(string[] args) + static void Main(string[] args) { bool smallframe = (args[0] == "smallframe"); if (args[1] == "secondary") diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj new file mode 100644 index 0000000000000..e75283a0f2d0f --- /dev/null +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow.csproj @@ -0,0 +1,14 @@ + + + + true + false + BuildOnly + + false + + + + + + diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs index 62f6afd06fd62..c833bae8afc15 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.cs @@ -2,16 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -namespace TestStackOverflow +namespace TestStackOverflow3 { - internal class StackOverflow3 + class Program { private const int MAX_RECURSIVE_CALLS = 1000000; static int ctr = 0; - public static void Run() + public static void Main() { - StackOverflow3 ex = new StackOverflow3(); + Program ex = new Program(); ex.Execute(); } diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj new file mode 100644 index 0000000000000..d805f0b4c7ecb --- /dev/null +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflow3.csproj @@ -0,0 +1,15 @@ + + + + true + false + + false + true + BuildOnly + + + + + + diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs index 6e97fd7bb5516..07f5e03e7cb62 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.IO; -using System.Linq; using System.Text; using Xunit; @@ -14,72 +11,18 @@ namespace TestStackOverflow { public class Program { - const string EnableMiniDumpEnvVar = "DOTNET_DbgEnableMiniDump"; - - const string MainSignature = ".Main(System.String[])"; - - public static int Main(string[] args) - { - if (args.Length > 0) - { - string testName = args[0]; - string[] testArgs = Array.Empty(); - switch (testName) - { - case "stackoverflow": - Assert.True(args.Length >= 2); - testArgs = args[1..].ToArray(); - StackOverflow.Run(testArgs); - break; - case "stackoverflow3": - StackOverflow3.Run(); - break; - default: - throw new InvalidOperationException($"Invalid arguments to 'stackoverflowtester' process. Test '{testName}' does not exist"); - } - throw new InvalidOperationException($"Invalid arguments to 'stackoverflowtester' process. Test '{testName}' with arguments '{testArgs}' should have thrown an exception."); - } - - int exitCode = 101; - try - { - TestStackOverflowSmallFrameMainThread(); - exitCode++; - TestStackOverflowLargeFrameMainThread(); - exitCode++; - TestStackOverflowSmallFrameSecondaryThread(); - exitCode++; - TestStackOverflowLargeFrameSecondaryThread(); - exitCode++; - TestStackOverflow3(); - } - catch (Exception e) - { - Console.WriteLine(e.ToString()); - return exitCode; - } - - return 100; - } - - [UnconditionalSuppressMessage("SingleFile", "IL3000", Justification = "We want an empty string for location if the test is running as single file")] - static bool TestStackOverflow(string testName, string testArgs, out string[] stderrLines, out bool checkStackFrame) + static void TestStackOverflow(string testName, string testArgs, out List stderrLines) { + Console.WriteLine($"Running {testName} test({testArgs})"); List lines = new List(); - string thisAssemblyPath = typeof(Program).Assembly.Location; - bool isSingleFile = TestLibrary.Utilities.IsSingleFile; - Process testProcess = new Process(); - // Always use whatever runner started this test, or the exe if we're running single file / NativeAOT - testProcess.StartInfo.FileName = Environment.ProcessPath; - // We want the path to this assembly in CoreCLR and the empty string for single file / NativeAOT - testProcess.StartInfo.Arguments = $"{typeof(Program).Assembly.Location} {testName} {testArgs}"; - Console.WriteLine($"Running {testName} {testArgs}"); - testProcess.StartInfo.Environment.Add(EnableMiniDumpEnvVar, "0"); + + testProcess.StartInfo.FileName = Path.Combine(Environment.GetEnvironmentVariable("CORE_ROOT"), "corerun"); + testProcess.StartInfo.Arguments = $"{Path.Combine(Directory.GetCurrentDirectory(), "..", testName, $"{testName}.dll")} {testArgs}"; testProcess.StartInfo.UseShellExecute = false; testProcess.StartInfo.RedirectStandardError = true; - testProcess.ErrorDataReceived += (sender, line) => + testProcess.ErrorDataReceived += (sender, line) => { Console.WriteLine($"\"{line.Data}\""); if (!string.IsNullOrEmpty(line.Data)) @@ -93,15 +36,12 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std testProcess.WaitForExit(); testProcess.CancelErrorRead(); - stderrLines = lines.ToArray(); - - // NativeAOT doesn't provide a stack trace on stack overflow - checkStackFrame = !isSingleFile; + stderrLines = lines; int[] expectedExitCodes; if ((Environment.OSVersion.Platform == PlatformID.Unix) || (Environment.OSVersion.Platform == PlatformID.MacOSX)) { - expectedExitCodes = new int[] { 128 + 6 }; + expectedExitCodes = new int[] { 128 + 6}; } else { @@ -112,121 +52,148 @@ static bool TestStackOverflow(string testName, string testArgs, out string[] std { string separator = string.Empty; StringBuilder expectedListBuilder = new StringBuilder(); - Array.ForEach(expectedExitCodes, code => - { + Array.ForEach(expectedExitCodes, code => { expectedListBuilder.Append($"{separator}0x{code:X8}"); separator = " or "; }); throw new Exception($"Exit code: 0x{testProcess.ExitCode:X8}, expected {expectedListBuilder.ToString()}"); } - string expectedMessage; - if (isSingleFile) + if (lines[0] != "Stack overflow.") { - expectedMessage = "Process is terminating due to StackOverflowException."; + throw new Exception("Missing \"Stack overflow.\" at the first line"); } - else + } + + [Fact] + public static void TestStackOverflowSmallFrameMainThread() + { + TestStackOverflow("stackoverflow", "smallframe main", out List lines); + + if (!lines[lines.Count - 1].EndsWith(".Main(System.String[])")) { - expectedMessage = "Stack overflow."; + throw new Exception("Missing \"Main\" method frame at the last line"); } - if (lines.Count > 0 && lines[0] == expectedMessage) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) { - return true; + throw new Exception("Missing \"Test\" method frame"); } - throw new Exception($"Missing \"{expectedMessage}\" at the first line of stderr"); - } + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + { + throw new Exception("Missing \"InfiniteRecursionA\" method frame"); + } - public static void AssertStackFramePresent(string stackFrame, ReadOnlySpan lines) - { - foreach(var line in lines) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) + { + throw new Exception("Missing \"InfiniteRecursionB\" method frame"); + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) { - if (line.EndsWith(stackFrame)) - return; + throw new Exception("Missing \"InfiniteRecursionC\" method frame"); } - throw new Exception($"Missing \"{stackFrame}\" from stack trace"); } - public static void TestStackOverflowSmallFrameMainThread() + [Fact] + public static void TestStackOverflowLargeFrameMainThread() { - TestStackOverflow("stackoverflow", "smallframe main", out string[] lines, out bool checkStackFrame); + TestStackOverflow("stackoverflow", "largeframe main", out List lines); - if (!checkStackFrame) + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow.Program.Main(System.String[])")) { - return; + throw new Exception("Missing \"Main\" method frame at the last line"); } - AssertStackFramePresent(MainSignature, lines[(lines.Length-1)..]); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC()", lines); - } + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.Test(Boolean)"))) + { + throw new Exception("Missing \"Test\" method frame"); + } - public static void TestStackOverflowLargeFrameMainThread() - { - TestStackOverflow("stackoverflow", "largeframe main", out string[] lines, out bool checkStackFrame); + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + { + throw new Exception("Missing \"InfiniteRecursionA2\" method frame"); + } - if (!checkStackFrame) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB2()"))) { - return; + throw new Exception("Missing \"InfiniteRecursionB2\" method frame"); } - if (!lines[lines.Length - 1].EndsWith(MainSignature)) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC2()"))) { - throw new Exception("Missing \"Main\" method frame at the last line"); + throw new Exception("Missing \"InfiniteRecursionC2\" method frame"); } - AssertStackFramePresent(MainSignature, lines[(lines.Length-1)..]); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Run(System.String[])", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB2()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC2()", lines); } + [Fact] public static void TestStackOverflowSmallFrameSecondaryThread() { - TestStackOverflow("stackoverflow", "smallframe secondary", out string[] lines, out bool checkStackFrame); + TestStackOverflow("stackoverflow", "smallframe secondary", out List lines); + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) + { + throw new Exception("Missing \"TestStackOverflow.Program.Test\" method frame"); + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA()"))) + { + throw new Exception("Missing \"InfiniteRecursionA\" method frame"); + } - if (!checkStackFrame) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionB()"))) { - return; + throw new Exception("Missing \"InfiniteRecursionB\" method frame"); } - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC()", lines); + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionC()"))) + { + throw new Exception("Missing \"InfiniteRecursionC\" method frame"); + } } + [Fact] public static void TestStackOverflowLargeFrameSecondaryThread() { - TestStackOverflow("stackoverflow", "largeframe secondary", out string[] lines, out bool checkStackFrame); + TestStackOverflow("stackoverflow", "largeframe secondary", out List lines); + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.Test(Boolean)"))) + { + throw new Exception("Missing \"TestStackOverflow.Program.Test\" method frame"); + } + + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow.Program.InfiniteRecursionA2()"))) + { + throw new Exception("Missing \"InfiniteRecursionA2\" method frame"); + } - if (!checkStackFrame) + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionB2()"))) { - return; + throw new Exception("Missing \"InfiniteRecursionB2\" method frame"); } - AssertStackFramePresent("at TestStackOverflow.StackOverflow.Test(Boolean)", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionA2()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionB2()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow.InfiniteRecursionC2()", lines); + if (!lines.Exists(elem => elem.EndsWith("TestStackOverflow.Program.InfiniteRecursionC2()"))) + { + throw new Exception("Missing \"InfiniteRecursionC2\" method frame"); + } } + [Fact] public static void TestStackOverflow3() { - TestStackOverflow("stackoverflow3", "", out string[] lines, out bool checkStackFrame); + TestStackOverflow("stackoverflow3", "", out List lines); + + if (!lines[lines.Count - 1].EndsWith("at TestStackOverflow3.Program.Main()")) + { + throw new Exception("Missing \"Main\" method frame at the last line"); + } - if (!checkStackFrame) + if (!lines.Exists(elem => elem.EndsWith("at TestStackOverflow3.Program.Execute(System.String)"))) { - return; + throw new Exception("Missing \"Execute\" method frame"); } - AssertStackFramePresent(MainSignature, lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Run()", lines); - AssertStackFramePresent("at TestStackOverflow.StackOverflow3.Execute(System.String)", lines); } } } diff --git a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj index 402a9c65f3fdd..eb0ae0ea4cd17 100644 --- a/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj +++ b/src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.csproj @@ -5,15 +5,9 @@ false true - true - false - - - - - + diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 1b2a852bba493..18f5307158708 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -89,8 +89,7 @@ - - + https://github.com/dotnet/runtime/issues/46175 @@ -731,6 +730,9 @@ https://github.com/dotnet/runtimelab/issues/155: Reflection.Emit + + Specific to CoreCLR + No crossgen folder under Core_Root