diff --git a/changelog/druntime.segfault-message.dd b/changelog/druntime.segfault-message.dd new file mode 100644 index 0000000000..8c300b2d65 --- /dev/null +++ b/changelog/druntime.segfault-message.dd @@ -0,0 +1,65 @@ +New segfault handler showing backtraces for null access / call stack overflow on linux + +While buffer overflows are usually caught by array bounds checks, there are still other situations where a segmentation fault occurs in D programs: + +- `null` pointer dereference +- Corrupted or dangling pointer dereference in `@system` code +- Call stack overflow (infinite recursion) + +These result in an uninformative runtime error such as: + +$(CONSOLE +[1] 37856 segmentation fault (core dumped) ./app +) + +In order to find the cause of the error, the program needs to be run again in a debugger like gdb. + +There is the `registerMemoryErrorHandler` function in `etc.linux.memoryerror`, which catches `SIGSEGV` signals and transforms them into a thrown `InvalidPointerError`, providing a better message. +However, it doesn't work on call stack overflow, because it uses stack memory itself, so the segfault handler segfaults. +It also relies on inline assembly, limiting it to the x86 architecture. + +A new function `registerMemoryAssertHandler` has been introduced, which does handle stack overflow by setting up an [altstack](https://man7.org/linux/man-pages/man2/sigaltstack.2.html). +It uses `assert(0)` instead of throwing an `Error` object, so the result corresponds to the chosen `-checkaction=[D|C|halt|context]` setting. + +Example: + +--- +void main() +{ + version (linux) + { + import etc.linux.memoryerror; + registerMemoryAssertHandler(); + } + int* p = null; + int* q = cast(int*) 0xDEADBEEF; + + // int a = *p; // segmentation fault: null pointer read/write operation + // int b = *q; // segmentation fault: invalid pointer read/write operation + recurse(); // segmentation fault: call stack overflow +} + +void recurse() +{ + recurse(); +} +--- + +Output with `dmd -g -run app.d`: + +$(CONSOLE +core.exception.AssertError@src/etc/linux/memoryerror.d(82): segmentation fault: call stack overflow +$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH)$(NDASH) +src/core/exception.d:587 onAssertErrorMsg [0x58e270d2802d] +src/core/exception.d:803 _d_assert_msg [0x58e270d1fb64] +src/etc/linux/memoryerror.d:82 _d_handleSignalAssert [0x58e270d1f48d] +??:? [0x7004139e876f] +./app.d:16 void scratch.recurse() [0x58e270d1d757] +./app.d:18 void scratch.recurse() [0x58e270d1d75c] +./app.d:18 void scratch.recurse() [0x58e270d1d75c] +./app.d:18 void scratch.recurse() [0x58e270d1d75c] +./app.d:18 void scratch.recurse() [0x58e270d1d75c] +... +... +... +) diff --git a/druntime/src/etc/linux/memoryerror.d b/druntime/src/etc/linux/memoryerror.d index 0bf54b3787..31e1d83796 100644 --- a/druntime/src/etc/linux/memoryerror.d +++ b/druntime/src/etc/linux/memoryerror.d @@ -1,9 +1,5 @@ /** - * Handle page protection errors using D errors (exceptions). $(D NullPointerError) is - * thrown when dereferencing null pointers. A system-dependent error is thrown in other - * cases. - * - * Note: Only x86 and x86_64 are supported for now. + * Handle page protection errors using D errors (exceptions) or asserts. * * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). @@ -14,313 +10,444 @@ module etc.linux.memoryerror; -version (CRuntime_Glibc) +version (linux) { - version (X86) - version = MemoryErrorSupported; - version (X86_64) - version = MemoryErrorSupported; + version (DigitalMars) + { + version (CRuntime_Glibc) + { + version (X86) + version = MemoryErrorSupported; + else version (X86_64) + version = MemoryErrorSupported; + } + } } -version (MemoryErrorSupported): -@system: +version (linux) +{ + version (X86) + version = MemoryAssertSupported; + else version (X86_64) + version = MemoryAssertSupported; + else version (ARM) + version = MemoryAssertSupported; + else version (AArch64) + version = MemoryAssertSupported; + else version (PPC64) + version = MemoryAssertSupported; +} + +version (MemoryErrorSupported) + version = AnySupported; +else version (MemoryErrorSupported) + version = AnySupported; + +version (AnySupported): import core.sys.posix.signal : SA_SIGINFO, sigaction, sigaction_t, siginfo_t, SIGSEGV; import ucontext = core.sys.posix.ucontext; -// Register and unregister memory error handler. - -bool registerMemoryErrorHandler() nothrow +version (MemoryAssertSupported) { - sigaction_t action; - action.sa_sigaction = &handleSignal; - action.sa_flags = SA_SIGINFO; - - auto oldptr = &old_sigaction; - - return !sigaction(SIGSEGV, &action, oldptr); + import core.sys.posix.signal : SA_ONSTACK, sigaltstack, SIGSTKSZ, stack_t; } -bool deregisterMemoryErrorHandler() nothrow -{ - auto oldptr = &old_sigaction; - - return !sigaction(SIGSEGV, oldptr, null); -} - -/** - * Thrown on POSIX systems when a SIGSEGV signal is received. - */ -class InvalidPointerError : Error -{ - this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow - { - super("", file, line, next); - } - - this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow - { - super("", file, line, next); - } -} - -/** - * Thrown on null pointer dereferences. - */ -class NullPointerError : InvalidPointerError -{ - this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow - { - super(file, line, next); - } - - this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow - { - super(file, line, next); - } -} - -unittest -{ - int* getNull() { return null; } - - assert(registerMemoryErrorHandler()); - - bool b; - - try - { - *getNull() = 42; - } - catch (NullPointerError) - { - b = true; - } - - assert(b); - - b = false; - - try - { - *getNull() = 42; - } - catch (InvalidPointerError) - { - b = true; - } - - assert(b); - - assert(deregisterMemoryErrorHandler()); -} - -// Signal handler space. - -private: - -__gshared sigaction_t old_sigaction; - -alias typeof(ucontext.ucontext_t.init.uc_mcontext.gregs[0]) RegType; - -version (X86_64) -{ - static RegType savedRDI, savedRSI; - - extern(C) - void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow - { - auto context = cast(ucontext.ucontext_t*)contextPtr; - - // Save registers into global thread local, to allow recovery. - savedRDI = context.uc_mcontext.gregs[ucontext.REG_RDI]; - savedRSI = context.uc_mcontext.gregs[ucontext.REG_RSI]; - - // Hijack current context so we call our handler. - auto rip = context.uc_mcontext.gregs[ucontext.REG_RIP]; - auto addr = cast(RegType) info.si_addr; - context.uc_mcontext.gregs[ucontext.REG_RDI] = addr; - context.uc_mcontext.gregs[ucontext.REG_RSI] = rip; - context.uc_mcontext.gregs[ucontext.REG_RIP] = cast(RegType) ((rip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); - } - - // All handler functions must be called with faulting address in RDI and original RIP in RSI. - - // This function is called when the segfault's cause is to call an invalid function pointer. - void sigsegvCodeHandler() - { - asm - { - naked; - - // Handle the stack for an invalid function call (segfault at RIP). - // With the return pointer, the stack is now alligned. - push RBP; - mov RBP, RSP; - - jmp sigsegvDataHandler; - } - } - - void sigsegvDataHandler() - { - asm - { - naked; - - push RSI; // return address (original RIP). - push RBP; // old RBP - mov RBP, RSP; - - pushfq; // Save flags. - push RAX; // RAX, RCX, RDX, and R8 to R11 are trash registers and must be preserved as local variables. - push RCX; - push RDX; - push R8; - push R9; - push R10; - push R11; // With 10 pushes, the stack is still aligned. - - // Parameter address is already set as RAX. - call sigsegvUserspaceProcess; - - // Restore RDI and RSI values. - call restoreRDI; - push RAX; // RDI is in RAX. It is pushed and will be poped back to RDI. - - call restoreRSI; - mov RSI, RAX; - - pop RDI; - - // Restore trash registers value. - pop R11; - pop R10; - pop R9; - pop R8; - pop RDX; - pop RCX; - pop RAX; - popfq; // Restore flags. - - // Return - pop RBP; - ret; - } - } - - // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. - RegType restoreRDI() - { - return savedRDI; - } - - RegType restoreRSI() - { - return savedRSI; - } -} -else version (X86) -{ - static RegType savedEAX, savedEDX; - - extern(C) - void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow - { - auto context = cast(ucontext.ucontext_t*)contextPtr; - - // Save registers into global thread local, to allow recovery. - savedEAX = context.uc_mcontext.gregs[ucontext.REG_EAX]; - savedEDX = context.uc_mcontext.gregs[ucontext.REG_EDX]; - - // Hijack current context so we call our handler. - auto eip = context.uc_mcontext.gregs[ucontext.REG_EIP]; - auto addr = cast(RegType) info.si_addr; - context.uc_mcontext.gregs[ucontext.REG_EAX] = addr; - context.uc_mcontext.gregs[ucontext.REG_EDX] = eip; - context.uc_mcontext.gregs[ucontext.REG_EIP] = cast(RegType) ((eip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); - } - - // All handler functions must be called with faulting address in EAX and original EIP in EDX. - - // This function is called when the segfault's cause is to call an invalid function pointer. - void sigsegvCodeHandler() - { - asm - { - naked; - - // Handle the stack for an invalid function call (segfault at EIP). - // 4 bytes are used for function pointer; We need 12 byte to keep stack aligned. - sub ESP, 12; - mov 8[ESP], EBP; - mov EBP, ESP; - - jmp sigsegvDataHandler; - } - } - - void sigsegvDataHandler() - { - asm - { - naked; - - // We jump directly here if we are in a valid function call case. - push EDX; // return address (original EIP). - push EBP; // old EBP - mov EBP, ESP; - - pushfd; // Save flags. - push ECX; // ECX is a trash register and must be preserved as local variable. - // 4 pushes have been done. The stack is aligned. - - // Parameter address is already set as EAX. - call sigsegvUserspaceProcess; - - // Restore register values and return. - call restoreRegisters; - - pop ECX; - popfd; // Restore flags. - - // Return - pop EBP; - ret; - } - } - - // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. - RegType[2] restoreRegisters() - { - RegType[2] restore; - restore[0] = savedEAX; - restore[1] = savedEDX; - - return restore; - } -} -else -{ - static assert(false, "Unsupported architecture."); -} - -// This should be calculated by druntime. -// TODO: Add a core.memory function for this. -enum PAGE_SIZE = 4096; +@system: // The first 64Kb are reserved for detecting null pointer dereferences. -enum MEMORY_RESERVED_FOR_NULL_DEREFERENCE = 4096 * 16; +// TODO: this is a platform-specific assumption, can be made more robust +private enum size_t MEMORY_RESERVED_FOR_NULL_DEREFERENCE = 4096 * 16; -// User space handler -void sigsegvUserspaceProcess(void* address) +version (MemoryErrorSupported) { - // SEGV_MAPERR, SEGV_ACCERR. - // The first page is protected to detect null dereferences. - if ((cast(size_t) address) < MEMORY_RESERVED_FOR_NULL_DEREFERENCE) + /** + * Register memory error handler, store the old handler. + * + * `NullPointerError` is thrown when dereferencing null pointers. + * A generic `InvalidPointerError` error is thrown in other cases. + * + * Returns: whether the registration was successful + * + * Limitations: Only x86 and x86_64 are supported for now. + */ + bool registerMemoryErrorHandler() nothrow { - throw new NullPointerError(); + sigaction_t action; + action.sa_sigaction = &handleSignal; + action.sa_flags = SA_SIGINFO; + + auto oldptr = &oldSigactionMemoryError; + + return !sigaction(SIGSEGV, &action, oldptr); } - throw new InvalidPointerError(); + /** + * Revert the memory error handler back to the one from before calling `registerMemoryErrorHandler()`. + * + * Returns: whether the registration of the old handler was successful + */ + bool deregisterMemoryErrorHandler() nothrow + { + auto oldptr = &oldSigactionMemoryError; + + return !sigaction(SIGSEGV, oldptr, null); + } + + /** + * Thrown on POSIX systems when a SIGSEGV signal is received. + */ + class InvalidPointerError : Error + { + this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow + { + super("", file, line, next); + } + + this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow + { + super("", file, line, next); + } + } + + /** + * Thrown on null pointer dereferences. + */ + class NullPointerError : InvalidPointerError + { + this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow + { + super(file, line, next); + } + + this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow + { + super(file, line, next); + } + } + + unittest + { + int* getNull() { return null; } + + assert(registerMemoryErrorHandler()); + + bool b; + + try + { + *getNull() = 42; + } + catch (NullPointerError) + { + b = true; + } + + assert(b); + + b = false; + + try + { + *getNull() = 42; + } + catch (InvalidPointerError) + { + b = true; + } + + assert(b); + + assert(deregisterMemoryErrorHandler()); + } + + // Signal handler space. + + private: + + __gshared sigaction_t oldSigactionMemoryError; + + alias RegType = typeof(ucontext.ucontext_t.init.uc_mcontext.gregs[0]); + + version (X86_64) + { + static RegType savedRDI, savedRSI; + + extern(C) + void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow + { + auto context = cast(ucontext.ucontext_t*)contextPtr; + + // Save registers into global thread local, to allow recovery. + savedRDI = context.uc_mcontext.gregs[ucontext.REG_RDI]; + savedRSI = context.uc_mcontext.gregs[ucontext.REG_RSI]; + + // Hijack current context so we call our handler. + auto rip = context.uc_mcontext.gregs[ucontext.REG_RIP]; + auto addr = cast(RegType) info.si_addr; + context.uc_mcontext.gregs[ucontext.REG_RDI] = addr; + context.uc_mcontext.gregs[ucontext.REG_RSI] = rip; + context.uc_mcontext.gregs[ucontext.REG_RIP] = cast(RegType) ((rip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); + } + + // All handler functions must be called with faulting address in RDI and original RIP in RSI. + + // This function is called when the segfault's cause is to call an invalid function pointer. + void sigsegvCodeHandler() + { + asm + { + naked; + + // Handle the stack for an invalid function call (segfault at RIP). + // With the return pointer, the stack is now alligned. + push RBP; + mov RBP, RSP; + + jmp sigsegvDataHandler; + } + } + + void sigsegvDataHandler() + { + asm + { + naked; + + push RSI; // return address (original RIP). + push RBP; // old RBP + mov RBP, RSP; + + pushfq; // Save flags. + push RAX; // RAX, RCX, RDX, and R8 to R11 are trash registers and must be preserved as local variables. + push RCX; + push RDX; + push R8; + push R9; + push R10; + push R11; // With 10 pushes, the stack is still aligned. + + // Parameter address is already set as RAX. + call sigsegvUserspaceProcess; + + // Restore RDI and RSI values. + call restoreRDI; + push RAX; // RDI is in RAX. It is pushed and will be poped back to RDI. + + call restoreRSI; + mov RSI, RAX; + + pop RDI; + + // Restore trash registers value. + pop R11; + pop R10; + pop R9; + pop R8; + pop RDX; + pop RCX; + pop RAX; + popfq; // Restore flags. + + // Return + pop RBP; + ret; + } + } + + // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. + RegType restoreRDI() + { + return savedRDI; + } + + RegType restoreRSI() + { + return savedRSI; + } + } + else version (X86) + { + static RegType savedEAX, savedEDX; + + extern(C) + void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow + { + auto context = cast(ucontext.ucontext_t*)contextPtr; + + // Save registers into global thread local, to allow recovery. + savedEAX = context.uc_mcontext.gregs[ucontext.REG_EAX]; + savedEDX = context.uc_mcontext.gregs[ucontext.REG_EDX]; + + // Hijack current context so we call our handler. + auto eip = context.uc_mcontext.gregs[ucontext.REG_EIP]; + auto addr = cast(RegType) info.si_addr; + context.uc_mcontext.gregs[ucontext.REG_EAX] = addr; + context.uc_mcontext.gregs[ucontext.REG_EDX] = eip; + context.uc_mcontext.gregs[ucontext.REG_EIP] = cast(RegType) ((eip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); + } + + // All handler functions must be called with faulting address in EAX and original EIP in EDX. + + // This function is called when the segfault's cause is to call an invalid function pointer. + void sigsegvCodeHandler() + { + asm + { + naked; + + // Handle the stack for an invalid function call (segfault at EIP). + // 4 bytes are used for function pointer; We need 12 byte to keep stack aligned. + sub ESP, 12; + mov [ESP + 8], EBP; + mov EBP, ESP; + + jmp sigsegvDataHandler; + } + } + + void sigsegvDataHandler() + { + asm + { + naked; + + // We jump directly here if we are in a valid function call case. + push EDX; // return address (original EIP). + push EBP; // old EBP + mov EBP, ESP; + + pushfd; // Save flags. + push ECX; // ECX is a trash register and must be preserved as local variable. + // 4 pushes have been done. The stack is aligned. + + // Parameter address is already set as EAX. + call sigsegvUserspaceProcess; + + // Restore register values and return. + call restoreRegisters; + + pop ECX; + popfd; // Restore flags. + + // Return + pop EBP; + ret; + } + } + + // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. + RegType[2] restoreRegisters() + { + RegType[2] restore; + restore[0] = savedEAX; + restore[1] = savedEDX; + + return restore; + } + } + else + { + static assert(false, "Unsupported architecture."); + } + + // User space handler + void sigsegvUserspaceProcess(void* address) + { + // SEGV_MAPERR, SEGV_ACCERR. + // The first page is protected to detect null dereferences. + if ((cast(size_t) address) < MEMORY_RESERVED_FOR_NULL_DEREFERENCE) + { + throw new NullPointerError(); + } + + throw new InvalidPointerError(); + } +} + +version (MemoryAssertSupported) +{ + private __gshared sigaction_t oldSigactionMemoryAssert; // sigaction before calling `registerMemoryAssertHandler` + + /** + * Registers a signal handler for SIGSEGV that turns them into an assertion failure, + * providing a more descriptive error message and stack trace if the program is + * compiled with debug info and D assertions (as opposed to C assertions). + * + * Differences with the `registerMemoryErrorHandler` version are: + * - The handler is registered with SA_ONSTACK, so it can handle stack overflows. + * - It uses `assert(0)` instead of `throw new Error` and doesn't support catching the error. + * - This is a template so that the -check and -checkaction flags of the compiled program are used, + * instead of the ones used for compiling druntime. + * + * Returns: whether the registration was successful + */ + bool registerMemoryAssertHandler()() + { + nothrow @nogc extern(C) + void _d_handleSignalAssert(int signum, siginfo_t* info, void* contextPtr) + { + // Guess the reason for the segfault by seeing if the faulting address + // is close to the stack pointer or the null pointer. + + const void* segfaultingPtr = info.si_addr; + + auto context = cast(ucontext.ucontext_t*) contextPtr; + version (X86_64) + const stackPtr = cast(void*) context.uc_mcontext.gregs[ucontext.REG_RSP]; + else version (X86) + const stackPtr = cast(void*) context.uc_mcontext.gregs[ucontext.REG_ESP]; + else version (ARM) + const stackPtr = cast(void*) context.uc_mcontext.arm_sp; + else version (AArch64) + const stackPtr = cast(void*) context.uc_mcontext.sp; + else version (PPC64) + const stackPtr = cast(void*) context.uc_mcontext.regs.gpr[1]; + else + static assert(false, "Unsupported architecture."); // TODO: other architectures + auto distanceToStack = cast(ptrdiff_t) (stackPtr - segfaultingPtr); + if (distanceToStack < 0) + distanceToStack = -distanceToStack; + + if (stackPtr && distanceToStack <= 4096) + assert(false, "segmentation fault: call stack overflow"); + else if (cast(size_t) segfaultingPtr < MEMORY_RESERVED_FOR_NULL_DEREFERENCE) + assert(false, "segmentation fault: null pointer read/write operation"); + else + assert(false, "segmentation fault: invalid pointer read/write operation"); + } + + sigaction_t action; + action.sa_sigaction = &_d_handleSignalAssert; + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + + // Set up alternate stack, because segfaults can be caused by stack overflow, + // in which case the stack is already exhausted + __gshared ubyte[SIGSTKSZ] altStack; + stack_t ss; + ss.ss_sp = altStack.ptr; + ss.ss_size = altStack.length; + ss.ss_flags = 0; + if (sigaltstack(&ss, null) == -1) + return false; + + return !sigaction(SIGSEGV, &action, &oldSigactionMemoryAssert); + } + + /** + * Revert the memory error handler back to the one from before calling `registerMemoryAssertHandler()`. + * + * Returns: whether the registration of the old handler was successful + */ + bool deregisterMemoryAssertHandler() + { + return !sigaction(SIGSEGV, &oldSigactionMemoryAssert, null); + } + + unittest + { + // Testing actual memory errors is done in the test suite + assert(registerMemoryAssertHandler()); + assert(deregisterMemoryAssertHandler()); + } } diff --git a/druntime/src/rt/dmain2.d b/druntime/src/rt/dmain2.d index b487fd9a37..0d79e5a4ee 100644 --- a/druntime/src/rt/dmain2.d +++ b/druntime/src/rt/dmain2.d @@ -460,6 +460,13 @@ private extern (C) int _d_run_main2(char[][] args, size_t totalArgsLength, MainF useExceptionTrap = false; } + version (none) + { + // Causes test failures related to Fibers, not enabled by default yet + import etc.linux.memoryerror; + cast(void) registerMemoryAssertHandler(); + } + void tryExec(scope void delegate() dg) { if (useExceptionTrap) diff --git a/druntime/test/exceptions/Makefile b/druntime/test/exceptions/Makefile index 9fa4af26f9..01fa0f9170 100644 --- a/druntime/test/exceptions/Makefile +++ b/druntime/test/exceptions/Makefile @@ -12,7 +12,8 @@ SED:=sed GDB:=gdb ifeq ($(OS),linux) - TESTS+=line_trace line_trace_21656 long_backtrace_trunc rt_trap_exceptions cpp_demangle + TESTS+=line_trace line_trace_21656 long_backtrace_trunc rt_trap_exceptions cpp_demangle \ + memoryerror_null_read memoryerror_null_write memoryerror_null_call memoryerror_stackoverflow line_trace_dflags:=-L--export-dynamic endif @@ -88,6 +89,11 @@ $(ROOT)/rt_trap_exceptions.done: stderr_exp2="src/rt_trap_exceptions.d:8 main" $(ROOT)/assert_fail.done: stderr_exp="success." $(ROOT)/cpp_demangle.done: stderr_exp="thrower(int)" $(ROOT)/message_with_null.done: stderr_exp=" world" +$(ROOT)/memoryerror_null_read.done: stderr_exp="segmentation fault: null pointer read/write operation" +$(ROOT)/memoryerror_null_write.done: stderr_exp="segmentation fault: null pointer read/write operation" +$(ROOT)/memoryerror_null_call.done: stderr_exp="segmentation fault: null pointer read/write operation" +$(ROOT)/memoryerror_null_call.done: stderr_exp2="uncaught exception reached top of stack" +$(ROOT)/memoryerror_stackoverflow.done: stderr_exp="segmentation fault: call stack overflow" $(ROOT)/%.done: $(ROOT)/%$(DOTEXE) @echo Testing $* diff --git a/druntime/test/exceptions/src/memoryerror_null_call.d b/druntime/test/exceptions/src/memoryerror_null_call.d new file mode 100644 index 0000000000..3f1224be1c --- /dev/null +++ b/druntime/test/exceptions/src/memoryerror_null_call.d @@ -0,0 +1,9 @@ +import etc.linux.memoryerror; + +void function() foo = null; + +void main() +{ + registerMemoryAssertHandler; + foo(); +} diff --git a/druntime/test/exceptions/src/memoryerror_null_read.d b/druntime/test/exceptions/src/memoryerror_null_read.d new file mode 100644 index 0000000000..8c4ca4876c --- /dev/null +++ b/druntime/test/exceptions/src/memoryerror_null_read.d @@ -0,0 +1,9 @@ +import etc.linux.memoryerror; + +int* x = null; + +void main() +{ + registerMemoryAssertHandler; + *x = 3; +} diff --git a/druntime/test/exceptions/src/memoryerror_null_write.d b/druntime/test/exceptions/src/memoryerror_null_write.d new file mode 100644 index 0000000000..e45b7c6c9d --- /dev/null +++ b/druntime/test/exceptions/src/memoryerror_null_write.d @@ -0,0 +1,9 @@ +import etc.linux.memoryerror; + +int* x = null; + +int main() +{ + registerMemoryAssertHandler; + return *x; +} diff --git a/druntime/test/exceptions/src/memoryerror_stackoverflow.d b/druntime/test/exceptions/src/memoryerror_stackoverflow.d new file mode 100644 index 0000000000..f54d78d26a --- /dev/null +++ b/druntime/test/exceptions/src/memoryerror_stackoverflow.d @@ -0,0 +1,22 @@ +import etc.linux.memoryerror; + +pragma(inline, false): + +void f(ref ubyte[1024] buf) +{ + ubyte[1024] cpy = buf; + g(cpy); +} + +void g(ref ubyte[1024] buf) +{ + ubyte[1024] cpy = buf; + f(cpy); +} + +void main() +{ + registerMemoryAssertHandler; + ubyte[1024] buf; + f(buf); +}