From a740ed73c84f8016f9469fb60ba2621c6694dc19 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Fri, 20 Feb 2026 01:57:34 +0100 Subject: [PATCH 01/15] Add base implementation --- src/alcotest-engine/core.ml | 9 +- src/alcotest-engine/dune | 17 ++ src/alcotest-engine/stack_error_reporter.c | 197 ++++++++++++++++++++ test/e2e/alcotest/stubs/dune | 32 ++++ test/e2e/alcotest/stubs/nullexception.ml | 18 ++ test/e2e/alcotest/stubs/nullexceptionstub.c | 17 ++ test/e2e/alcotest/stubs/stubs.expected | 0 7 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 src/alcotest-engine/stack_error_reporter.c create mode 100644 test/e2e/alcotest/stubs/dune create mode 100644 test/e2e/alcotest/stubs/nullexception.ml create mode 100644 test/e2e/alcotest/stubs/nullexceptionstub.c create mode 100644 test/e2e/alcotest/stubs/stubs.expected diff --git a/src/alcotest-engine/core.ml b/src/alcotest-engine/core.ml index 459686d0..5c7a8423 100644 --- a/src/alcotest-engine/core.ml +++ b/src/alcotest-engine/core.ml @@ -82,6 +82,11 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct stderr : Formatters.stderr; } + exception SegFault of string + let _ = Callback.register_exception "segfault exception" (SegFault "Caught segfault") + + external setup_stub_exception_handler: unit -> unit = "caml_setup_stub_exception_handler" + let gen_run_id = let random_state = lazy (Random.State.make_self_init ()) in let random_hex _ = @@ -443,7 +448,9 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct ?compact ?tail_errors ?quick_only ?show_errors ?json ?filter ?log_dir ?bail ?record_backtrace ?ci - let run = Config.User.kcreate run' + let run = + setup_stub_exception_handler (); + Config.User.kcreate run' end module V1 = struct diff --git a/src/alcotest-engine/dune b/src/alcotest-engine/dune index 5dbe572d..5593f3c4 100644 --- a/src/alcotest-engine/dune +++ b/src/alcotest-engine/dune @@ -15,9 +15,26 @@ %{target} "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (= %{os_type} Win32)) + (action + (write-file %{target} "-ldbghelp"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (<> %{os_type} Win32)) + (action + (write-file %{target} ""))) + (library (name alcotest_engine) (public_name alcotest.engine) + (foreign_stubs + (language c) + (names stack_error_reporter)) + (c_library_flags (:standard (:include c_library_flags.sexp))) (libraries alcotest.stdlib_ext fmt diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c new file mode 100644 index 00000000..6eac1310 --- /dev/null +++ b/src/alcotest-engine/stack_error_reporter.c @@ -0,0 +1,197 @@ +// caml headers +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef struct { + char *buffer; + size_t capacity; + size_t offset; +} StackTraceBuffer; + +void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { + if (sb->offset >= sb->capacity) + return; // Buffer full + + va_list args; + va_start(args, format); + + size_t remaining = sb->capacity - sb->offset; + int written = vsnprintf(sb->buffer + sb->offset, remaining, format, args); + + va_end(args); + + if (written > 0) { + if ((size_t)written < remaining) { + sb->offset += written; + } else { + // Truncated or filled exactly; ensure null termination at the end + sb->offset = sb->capacity - 1; + sb->buffer[sb->offset] = '\0'; + } + } +} + +#if defined(_WIN32) || defined(_WIN64) +#define PLATFORM_WINDOWS +#include +#include + +void create_stacktrace(StackTraceBuffer* pStackTraceBuffer) +{ + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT context; + STACKFRAME64 stack; + DWORD machine_type; + + RtlCaptureContext(&context); + + ZeroMemory(&stack, sizeof(STACKFRAME64)); + +#ifdef _M_IX86 + machine_type = IMAGE_FILE_MACHINE_I386; + stack.AddrPC.Offset = context.Eip; + stack.AddrFrame.Offset = context.Ebp; + stack.AddrStack.Offset = context.Esp; +#elif _M_X64 + machine_type = IMAGE_FILE_MACHINE_AMD64; + stack.AddrPC.Offset = context.Rip; + stack.AddrFrame.Offset = context.Rsp; + stack.AddrStack.Offset = context.Rsp; +#elif _M_ARM64 + machine_type = IMAGE_FILE_MACHINE_ARM64; + stack.AddrPC.Offset = context.Pc; + stack.AddrFrame.Offset = context.Fp; + stack.AddrStack.Offset = context.Sp; +#else +#error "Unsupported platform" +#endif + + stack.AddrPC.Mode = AddrModeFlat; + stack.AddrFrame.Mode = AddrModeFlat; + stack.AddrStack.Mode = AddrModeFlat; + + SymInitialize(process, NULL, TRUE); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + + append_to_buffer(pStackTraceBuffer, "Stack trace:\n"); + append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "Function", "Address", "Line"); + append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "--------", "-------", "----"); + + while (StackWalk64( + machine_type, + process, + thread, + &stack, + &context, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL)) { + if (stack.AddrPC.Offset == 0) + break; + + DWORD64 symbol_addr = stack.AddrPC.Offset; + DWORD64 displacement = 0; + char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; + SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + // Get line information + IMAGEHLP_LINE64 line = {0}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + DWORD line_displacement = 0; + BOOL has_line = SymGetLineFromAddr64(process, symbol_addr, &line_displacement, &line); + + char function_name[MAX_SYM_NAME] = "Unknown"; + if (SymFromAddr(process, symbol_addr, &displacement, symbol)) { + strncpy(function_name, symbol->Name, MAX_SYM_NAME - 1); + function_name[MAX_SYM_NAME - 1] = '\0'; // Ensure null termination + } + // Format line information + char line_info[256] = "Unknown"; + if (has_line) { + snprintf(line_info, sizeof(line_info), "%s:%lu", line.FileName, line.LineNumber); + } + + // Print with better alignment using format specifiers + append_to_buffer(pStackTraceBuffer, + " %-40.40s 0x%016llX %s\n", + function_name, + symbol_addr, + line_info); + append_to_buffer(pStackTraceBuffer, "\0"); + + } + + SymCleanup(process); +} + + +LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { + const DWORD exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + switch(exceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: + { + void* faulting_address = (void*)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; + StackTraceBuffer stack_trace_buffer; + const size_t buffer_size = 1024 * 10; + char *error_buffer = (char *)malloc(buffer_size); + if (error_buffer == NULL) + { + caml_failwith("Malloc failed to allocate memory for exception stack trace"); + return EXCEPTION_CONTINUE_SEARCH; + } + stack_trace_buffer.capacity = buffer_size; + stack_trace_buffer.offset = 0; + stack_trace_buffer.buffer = error_buffer; + stack_trace_buffer.buffer[0] = '0'; + create_stacktrace(&stack_trace_buffer); + + // printf("Printing stacktrace...\n"); + // printf("%s\n", stack_trace_buffer.buffer); + + caml_raise_with_string(*caml_named_value("segfault exception"), stack_trace_buffer.buffer); + free(stack_trace_buffer.buffer); + } + default: break; + } + return EXCEPTION_CONTINUE_SEARCH; +} +#else +#define PLATFORM_UNIX +#include + void unix_signal_handler(int sig, siginfo_t *si, void *unused) { + caml_failwith("Exception occured"); + _exit(1); + } +#endif + + +CAMLprim value caml_setup_stub_exception_handler() +{ + CAMLparam0(); +#ifdef PLATFORM_WINDOWS + AddVectoredExceptionHandler(1, windows_exception_handler); +#elif defined(PLATFORM_UNIX) + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = unix_signal_handler; + sigaction(SIGSEGV, &sa, NULL); +#endif + CAMLreturn(Val_unit); +} \ No newline at end of file diff --git a/test/e2e/alcotest/stubs/dune b/test/e2e/alcotest/stubs/dune new file mode 100644 index 00000000..9612575b --- /dev/null +++ b/test/e2e/alcotest/stubs/dune @@ -0,0 +1,32 @@ +(executable + (name nullexception) + (libraries alcotest) + (foreign_stubs + (language c) + (names nullexceptionstub)) + ) + +(rule + (target stubs.actual) + (action + (with-outputs-to + %{target} + (run %{dep:nullexception.exe} --color=auto)))) + +(rule + (alias run-stuff) + (action + (run %{dep:nullexception.exe} --color=auto))) + +(rule + (target stubs.processed) + (action + (with-outputs-to + %{target} + (run ../../strip_randomness.exe %{dep:stubs.actual})))) + +(rule + (alias runtest) + (package alcotest) + (action + (diff stubs.expected stubs.processed))) diff --git a/test/e2e/alcotest/stubs/nullexception.ml b/test/e2e/alcotest/stubs/nullexception.ml new file mode 100644 index 00000000..d81ad6dc --- /dev/null +++ b/test/e2e/alcotest/stubs/nullexception.ml @@ -0,0 +1,18 @@ +external segfault_call : unit -> unit = "caml_segfault_call" + +let () = + let open Alcotest in + let call_seg () = + try + segfault_call (); + fail "Should segfault" + with e -> + print_endline (Printexc.to_string e); + (check pass) "Exception during nullptr dereference should happen" () () + in + run __FILE__ + [ + ("segfault", [ test_case "nullexcept" `Quick (function _ -> call_seg ())]); + ] + + diff --git a/test/e2e/alcotest/stubs/nullexceptionstub.c b/test/e2e/alcotest/stubs/nullexceptionstub.c new file mode 100644 index 00000000..7b312aa9 --- /dev/null +++ b/test/e2e/alcotest/stubs/nullexceptionstub.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +value caml_segfault_call() +{ + CAMLparam0(); + int* someVal = NULL; + int a = *someVal; + CAMLreturn(Int_val(a)); +} \ No newline at end of file diff --git a/test/e2e/alcotest/stubs/stubs.expected b/test/e2e/alcotest/stubs/stubs.expected new file mode 100644 index 00000000..e69de29b From 77f7efdb0a37439d225c9c9ef6fde2e8fb930139 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Sat, 21 Feb 2026 17:30:50 +0100 Subject: [PATCH 02/15] Update stubs, test, export SegFault exception --- src/alcotest-engine/core.ml | 8 +++++--- src/alcotest-engine/core_intf.ml | 4 ++++ src/alcotest-engine/stack_error_reporter.c | 7 +++---- test/e2e/alcotest/stubs/nullexception.ml | 6 ++++-- test/e2e/alcotest/stubs/stubs.expected | 7 +++++++ 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/alcotest-engine/core.ml b/src/alcotest-engine/core.ml index 5c7a8423..bbce2300 100644 --- a/src/alcotest-engine/core.ml +++ b/src/alcotest-engine/core.ml @@ -20,6 +20,9 @@ open Model exception Check_error of unit Fmt.t exception Skip +exception SegFault of string + +let _ = Callback.register_exception "segfault exception" (SegFault "Caught segfault") let () = let print_error = @@ -59,6 +62,7 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct type speed_level = [ `Quick | `Slow ] exception Test_error + exception SegFault = SegFault type 'a test_case = string * speed_level * 'a run @@ -82,9 +86,6 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct stderr : Formatters.stderr; } - exception SegFault of string - let _ = Callback.register_exception "segfault exception" (SegFault "Caught segfault") - external setup_stub_exception_handler: unit -> unit = "caml_setup_stub_exception_handler" let gen_run_id = @@ -458,4 +459,5 @@ module V1 = struct module Make = Make exception Skip = Skip + exception SegFault = SegFault end diff --git a/src/alcotest-engine/core_intf.ml b/src/alcotest-engine/core_intf.ml index 4d602276..e22f9b84 100644 --- a/src/alcotest-engine/core_intf.ml +++ b/src/alcotest-engine/core_intf.ml @@ -36,6 +36,8 @@ module V1_types = struct exception Test_error (** The exception return by {!run} in case of errors. *) + + exception SegFault of string val test_case : string -> speed_level -> ('a -> return) -> 'a test_case (** [test_case n s f] is the test case [n] running at speed [s] using the @@ -111,6 +113,7 @@ end module type Core = sig exception Check_error of unit Fmt.t + exception SegFault of string module V1 : sig module type S = V1_types.S @@ -123,5 +126,6 @@ module type Core = sig Intended for use by the {!Alcotest_lwt} and {!Alcotest_async} backends. *) exception Skip + exception SegFault of string end end diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 6eac1310..6e2eb42d 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -43,6 +43,8 @@ void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { } } +static const char *CAML_ERROR_ID = "segfault exception"; + #if defined(_WIN32) || defined(_WIN64) #define PLATFORM_WINDOWS #include @@ -161,10 +163,7 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { stack_trace_buffer.buffer[0] = '0'; create_stacktrace(&stack_trace_buffer); - // printf("Printing stacktrace...\n"); - // printf("%s\n", stack_trace_buffer.buffer); - - caml_raise_with_string(*caml_named_value("segfault exception"), stack_trace_buffer.buffer); + caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), stack_trace_buffer.buffer); free(stack_trace_buffer.buffer); } default: break; diff --git a/test/e2e/alcotest/stubs/nullexception.ml b/test/e2e/alcotest/stubs/nullexception.ml index d81ad6dc..ca0c82ee 100644 --- a/test/e2e/alcotest/stubs/nullexception.ml +++ b/test/e2e/alcotest/stubs/nullexception.ml @@ -6,9 +6,11 @@ let () = try segfault_call (); fail "Should segfault" - with e -> - print_endline (Printexc.to_string e); + with + | SegFault _ -> (check pass) "Exception during nullptr dereference should happen" () () + | _ -> + fail "Should call segfault exception" in run __FILE__ [ diff --git a/test/e2e/alcotest/stubs/stubs.expected b/test/e2e/alcotest/stubs/stubs.expected index e69de29b..b8da5ef4 100644 --- a/test/e2e/alcotest/stubs/stubs.expected +++ b/test/e2e/alcotest/stubs/stubs.expected @@ -0,0 +1,7 @@ +Testing `test/e2e/alcotest/stubs/nullexception.ml'. +This run has ID `'. + + [OK] segfault 0 nullexcept. + +Full test results in `/_build/_tests/'. +Test Successful in s. 1 test run. From cdd6dbbea0cf2c5850d4ee2233bffe04b1ddd4a3 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Sat, 21 Feb 2026 22:05:44 +0100 Subject: [PATCH 03/15] add linux impls --- src/alcotest-engine/dune | 4 +- src/alcotest-engine/stack_error_reporter.c | 74 ++++++++++++++++------ 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/alcotest-engine/dune b/src/alcotest-engine/dune index 5593f3c4..de755b8c 100644 --- a/src/alcotest-engine/dune +++ b/src/alcotest-engine/dune @@ -20,13 +20,13 @@ (target c_library_flags.sexp) (enabled_if (= %{os_type} Win32)) (action - (write-file %{target} "-ldbghelp"))) + (write-file %{target} "(-ldbghelp)"))) (rule (target c_library_flags.sexp) (enabled_if (<> %{os_type} Win32)) (action - (write-file %{target} ""))) + (write-file %{target} "()"))) (library (name alcotest_engine) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 6e2eb42d..c1488092 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -9,17 +9,31 @@ #include #include -#include #include +#include #include #include +const size_t buffer_size = 1024 * 10; typedef struct { char *buffer; size_t capacity; size_t offset; } StackTraceBuffer; +bool finit_stack_trace_buffer(StackTraceBuffer* pStackTraceBuffer, size_t size) +{ + char *error_buffer = (char *)malloc(size); + if (error_buffer == NULL) { + return false; + } + pStackTraceBuffer->capacity = size; + pStackTraceBuffer->offset = 0; + pStackTraceBuffer->buffer = error_buffer; + pStackTraceBuffer->buffer[0] = '0'; + return true; +} + void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { if (sb->offset >= sb->capacity) return; // Buffer full @@ -47,6 +61,7 @@ static const char *CAML_ERROR_ID = "segfault exception"; #if defined(_WIN32) || defined(_WIN64) #define PLATFORM_WINDOWS +#include #include #include @@ -150,17 +165,11 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { { void* faulting_address = (void*)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; StackTraceBuffer stack_trace_buffer; - const size_t buffer_size = 1024 * 10; - char *error_buffer = (char *)malloc(buffer_size); - if (error_buffer == NULL) + if(!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { - caml_failwith("Malloc failed to allocate memory for exception stack trace"); - return EXCEPTION_CONTINUE_SEARCH; + caml_failwith("Can't create stack trace buffer"); + return EXCEPTION_CONTINUE_SEARCH; } - stack_trace_buffer.capacity = buffer_size; - stack_trace_buffer.offset = 0; - stack_trace_buffer.buffer = error_buffer; - stack_trace_buffer.buffer[0] = '0'; create_stacktrace(&stack_trace_buffer); caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), stack_trace_buffer.buffer); @@ -173,10 +182,39 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { #else #define PLATFORM_UNIX #include - void unix_signal_handler(int sig, siginfo_t *si, void *unused) { - caml_failwith("Exception occured"); - _exit(1); +#include +#include +#include + +void unix_signal_handler(int sig, siginfo_t *si, void *unused) { + + StackTraceBuffer stack_trace_buffer; + if (!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { + caml_failwith("Can't create stack trace buffer"); + return; + } + + void* trace[20]; + size_t trace_size = backtrace(trace, 20); + + if(trace_size == 0) + { + caml_failwith("Couldn't get backtrace"); + return; } + + append_to_buffer(&stack_trace_buffer, "Stack trace:\n"); + + char** pSymbols = backtrace_symbols(trace, trace_size); + for(int i = 0; i < trace_size; ++i) + { + append_to_buffer(&stack_trace_buffer, pSymbols[i]); + } + free(pSymbols); + + caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), + stack_trace_buffer.buffer); +} #endif @@ -186,11 +224,11 @@ CAMLprim value caml_setup_stub_exception_handler() #ifdef PLATFORM_WINDOWS AddVectoredExceptionHandler(1, windows_exception_handler); #elif defined(PLATFORM_UNIX) - struct sigaction sa; - sa.sa_flags = SA_SIGINFO; - sigemptyset(&sa.sa_mask); - sa.sa_sigaction = unix_signal_handler; - sigaction(SIGSEGV, &sa, NULL); + // struct sigaction sa; + // sa.sa_flags = SA_SIGINFO; + // sigemptyset(&sa.sa_mask); + // sa.sa_sigaction = unix_signal_handler; + // sigaction(SIGSEGV, &sa, NULL); #endif CAMLreturn(Val_unit); } \ No newline at end of file From 821ef8596f8ee49803f5ae01cab41ed1c12c93f8 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Sun, 22 Feb 2026 15:51:35 +0100 Subject: [PATCH 04/15] Update exit codes, add fail expected states --- src/alcotest-engine/stack_error_reporter.c | 16 +++++++++------- test/e2e/alcotest/stubs/dune | 13 +++++-------- test/e2e/alcotest/stubs/nullexception.ml | 7 ++++--- test/e2e/alcotest/stubs/stubs.expected | 14 ++++++++++++-- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index c1488092..da4772bf 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -174,6 +174,7 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), stack_trace_buffer.buffer); free(stack_trace_buffer.buffer); + ExitProcess(STATUS_ACCESS_VIOLATION); } default: break; } @@ -203,17 +204,18 @@ void unix_signal_handler(int sig, siginfo_t *si, void *unused) { return; } - append_to_buffer(&stack_trace_buffer, "Stack trace:\n"); + append_to_buffer(&stack_trace_buffer, "Caught Violation access, here's stack trace:\n"); char** pSymbols = backtrace_symbols(trace, trace_size); for(int i = 0; i < trace_size; ++i) { - append_to_buffer(&stack_trace_buffer, pSymbols[i]); + append_to_buffer(&stack_trace_buffer, "%s\n", pSymbols[i]); } free(pSymbols); caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), stack_trace_buffer.buffer); + exit(WEXITED); } #endif @@ -224,11 +226,11 @@ CAMLprim value caml_setup_stub_exception_handler() #ifdef PLATFORM_WINDOWS AddVectoredExceptionHandler(1, windows_exception_handler); #elif defined(PLATFORM_UNIX) - // struct sigaction sa; - // sa.sa_flags = SA_SIGINFO; - // sigemptyset(&sa.sa_mask); - // sa.sa_sigaction = unix_signal_handler; - // sigaction(SIGSEGV, &sa, NULL); + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = unix_signal_handler; + sigaction(SIGSEGV, &sa, NULL); #endif CAMLreturn(Val_unit); } \ No newline at end of file diff --git a/test/e2e/alcotest/stubs/dune b/test/e2e/alcotest/stubs/dune index 9612575b..e30a8087 100644 --- a/test/e2e/alcotest/stubs/dune +++ b/test/e2e/alcotest/stubs/dune @@ -9,14 +9,11 @@ (rule (target stubs.actual) (action - (with-outputs-to - %{target} - (run %{dep:nullexception.exe} --color=auto)))) - -(rule - (alias run-stuff) - (action - (run %{dep:nullexception.exe} --color=auto))) + (with-accepted-exit-codes + (or 1 2 124 125) + (with-outputs-to + %{target} + (run %{dep:nullexception.exe} --color=auto))))) (rule (target stubs.processed) diff --git a/test/e2e/alcotest/stubs/nullexception.ml b/test/e2e/alcotest/stubs/nullexception.ml index ca0c82ee..e384b0ca 100644 --- a/test/e2e/alcotest/stubs/nullexception.ml +++ b/test/e2e/alcotest/stubs/nullexception.ml @@ -1,16 +1,17 @@ external segfault_call : unit -> unit = "caml_segfault_call" +(* This test should fail *) let () = let open Alcotest in let call_seg () = try segfault_call (); - fail "Should segfault" + (check pass) "Should get segfault exception" () () with | SegFault _ -> - (check pass) "Exception during nullptr dereference should happen" () () + fail "Got segfault" | _ -> - fail "Should call segfault exception" + (check pass) "Should get segfault exception" () () in run __FILE__ [ diff --git a/test/e2e/alcotest/stubs/stubs.expected b/test/e2e/alcotest/stubs/stubs.expected index b8da5ef4..b8a94b51 100644 --- a/test/e2e/alcotest/stubs/stubs.expected +++ b/test/e2e/alcotest/stubs/stubs.expected @@ -1,7 +1,17 @@ Testing `test/e2e/alcotest/stubs/nullexception.ml'. This run has ID `'. - [OK] segfault 0 nullexcept. +> [FAIL] segfault 0 nullexcept. + +┌──────────────────────────────────────────────────────────────────────────────┐ +│ [FAIL] segfault 0 nullexcept. │ +└──────────────────────────────────────────────────────────────────────────────┘ +ASSERT Got segfault +FAIL Got segfault + + +Logs saved to `/_build/_tests//segfault.000.output'. + ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. -Test Successful in s. 1 test run. +1 failure! in s. 1 test run. From 87fcef5c50a06b3205908c6ffdeb900fb9de5b02 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Sun, 22 Feb 2026 15:58:12 +0100 Subject: [PATCH 05/15] Fix endlines --- test/e2e/alcotest/stubs/stubs.expected | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/e2e/alcotest/stubs/stubs.expected b/test/e2e/alcotest/stubs/stubs.expected index b8a94b51..a1d83bda 100644 --- a/test/e2e/alcotest/stubs/stubs.expected +++ b/test/e2e/alcotest/stubs/stubs.expected @@ -1,17 +1,17 @@ -Testing `test/e2e/alcotest/stubs/nullexception.ml'. -This run has ID `'. - -> [FAIL] segfault 0 nullexcept. - -┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] segfault 0 nullexcept. │ -└──────────────────────────────────────────────────────────────────────────────┘ -ASSERT Got segfault -FAIL Got segfault - - -Logs saved to `/_build/_tests//segfault.000.output'. - ────────────────────────────────────────────────────────────────────────────── - -Full test results in `/_build/_tests/'. -1 failure! in s. 1 test run. +Testing `test/e2e/alcotest/stubs/nullexception.ml'. +This run has ID `'. + +> [FAIL] segfault 0 nullexcept. + +┌──────────────────────────────────────────────────────────────────────────────┐ +│ [FAIL] segfault 0 nullexcept. │ +└──────────────────────────────────────────────────────────────────────────────┘ +ASSERT Got segfault +FAIL Got segfault + + +Logs saved to `/_build/_tests//segfault.000.output'. + ────────────────────────────────────────────────────────────────────────────── + +Full test results in `/_build/_tests/'. +1 failure! in s. 1 test run. From 588162df461eda74ae02d0194d84542e13f5091d Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Wed, 11 Mar 2026 23:18:33 +0100 Subject: [PATCH 06/15] Use clang-format, apply suggestions, credit author, add rule for js build --- src/alcotest-engine/dune | 3 +- src/alcotest-engine/stack_error_reporter.c | 241 ++++++++++---------- test/e2e/alcotest/stubs/nullexceptionstub.c | 2 +- 3 files changed, 119 insertions(+), 127 deletions(-) diff --git a/src/alcotest-engine/dune b/src/alcotest-engine/dune index de755b8c..d6fbd3a0 100644 --- a/src/alcotest-engine/dune +++ b/src/alcotest-engine/dune @@ -15,7 +15,6 @@ %{target} "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) - (rule (target c_library_flags.sexp) (enabled_if (= %{os_type} Win32)) @@ -24,7 +23,7 @@ (rule (target c_library_flags.sexp) - (enabled_if (<> %{os_type} Win32)) + (enabled_if (or (<> %{os_type} Win32) (= %{architecture} "js"))) (action (write-file %{target} "()"))) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index da4772bf..e80075fb 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -21,8 +21,8 @@ typedef struct { size_t offset; } StackTraceBuffer; -bool finit_stack_trace_buffer(StackTraceBuffer* pStackTraceBuffer, size_t size) -{ +static bool finit_stack_trace_buffer(StackTraceBuffer *pStackTraceBuffer, + size_t size) { char *error_buffer = (char *)malloc(size); if (error_buffer == NULL) { return false; @@ -30,11 +30,11 @@ bool finit_stack_trace_buffer(StackTraceBuffer* pStackTraceBuffer, size_t size) pStackTraceBuffer->capacity = size; pStackTraceBuffer->offset = 0; pStackTraceBuffer->buffer = error_buffer; - pStackTraceBuffer->buffer[0] = '0'; + pStackTraceBuffer->buffer[0] = '\0'; return true; } -void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { +static void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { if (sb->offset >= sb->capacity) return; // Buffer full @@ -59,135 +59,131 @@ void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { static const char *CAML_ERROR_ID = "segfault exception"; -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) #define PLATFORM_WINDOWS +#include #include #include -#include -void create_stacktrace(StackTraceBuffer* pStackTraceBuffer) -{ - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - CONTEXT context; - STACKFRAME64 stack; - DWORD machine_type; - - RtlCaptureContext(&context); - - ZeroMemory(&stack, sizeof(STACKFRAME64)); - -#ifdef _M_IX86 - machine_type = IMAGE_FILE_MACHINE_I386; - stack.AddrPC.Offset = context.Eip; - stack.AddrFrame.Offset = context.Ebp; - stack.AddrStack.Offset = context.Esp; -#elif _M_X64 - machine_type = IMAGE_FILE_MACHINE_AMD64; - stack.AddrPC.Offset = context.Rip; - stack.AddrFrame.Offset = context.Rsp; - stack.AddrStack.Offset = context.Rsp; -#elif _M_ARM64 - machine_type = IMAGE_FILE_MACHINE_ARM64; - stack.AddrPC.Offset = context.Pc; - stack.AddrFrame.Offset = context.Fp; - stack.AddrStack.Offset = context.Sp; +// Stacktrace collection inspired by +// https://smhk.net/note/2025/03/c-stack-trace-in-windows/ +static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT context; + STACKFRAME64 stack; + DWORD machine_type; + + RtlCaptureContext(&context); + + ZeroMemory(&stack, sizeof(STACKFRAME64)); + +#if defined(_M_IX86) || defined(__i386__) + machine_type = IMAGE_FILE_MACHINE_I386; + stack.AddrPC.Offset = context.Eip; + stack.AddrFrame.Offset = context.Ebp; + stack.AddrStack.Offset = context.Esp; +#elif defined(_M_X64) || defined(__x86_64__) + machine_type = IMAGE_FILE_MACHINE_AMD64; + stack.AddrPC.Offset = context.Rip; + stack.AddrFrame.Offset = context.Rsp; + stack.AddrStack.Offset = context.Rsp; +#elif defined(_M_ARM64) || defined(__aarch64__) + machine_type = IMAGE_FILE_MACHINE_ARM64; + stack.AddrPC.Offset = context.Pc; + stack.AddrFrame.Offset = context.Fp; + stack.AddrStack.Offset = context.Sp; #else #error "Unsupported platform" #endif - stack.AddrPC.Mode = AddrModeFlat; - stack.AddrFrame.Mode = AddrModeFlat; - stack.AddrStack.Mode = AddrModeFlat; - - SymInitialize(process, NULL, TRUE); - SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - - append_to_buffer(pStackTraceBuffer, "Stack trace:\n"); - append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "Function", "Address", "Line"); - append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "--------", "-------", "----"); - - while (StackWalk64( - machine_type, - process, - thread, - &stack, - &context, - NULL, - SymFunctionTableAccess64, - SymGetModuleBase64, - NULL)) { - if (stack.AddrPC.Offset == 0) - break; - - DWORD64 symbol_addr = stack.AddrPC.Offset; - DWORD64 displacement = 0; - char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; - SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - // Get line information - IMAGEHLP_LINE64 line = {0}; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - DWORD line_displacement = 0; - BOOL has_line = SymGetLineFromAddr64(process, symbol_addr, &line_displacement, &line); - - char function_name[MAX_SYM_NAME] = "Unknown"; - if (SymFromAddr(process, symbol_addr, &displacement, symbol)) { - strncpy(function_name, symbol->Name, MAX_SYM_NAME - 1); - function_name[MAX_SYM_NAME - 1] = '\0'; // Ensure null termination - } - // Format line information - char line_info[256] = "Unknown"; - if (has_line) { - snprintf(line_info, sizeof(line_info), "%s:%lu", line.FileName, line.LineNumber); - } - - // Print with better alignment using format specifiers - append_to_buffer(pStackTraceBuffer, - " %-40.40s 0x%016llX %s\n", - function_name, - symbol_addr, - line_info); - append_to_buffer(pStackTraceBuffer, "\0"); - + stack.AddrPC.Mode = AddrModeFlat; + stack.AddrFrame.Mode = AddrModeFlat; + stack.AddrStack.Mode = AddrModeFlat; + + SymInitialize(process, NULL, TRUE); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + + append_to_buffer(pStackTraceBuffer, "Stack trace:\n"); + append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "Function", + "Address", "Line"); + append_to_buffer(pStackTraceBuffer, " %-40s %-18s %s\n", "--------", + "-------", "----"); + + while (StackWalk64(machine_type, process, thread, &stack, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + if (stack.AddrPC.Offset == 0) + break; + + DWORD64 symbol_addr = stack.AddrPC.Offset; + DWORD64 displacement = 0; + _Alignas(SYMBOL_INFO *) + symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; + SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + // Get line information + IMAGEHLP_LINE64 line = {0}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + DWORD line_displacement = 0; + BOOL has_line = + SymGetLineFromAddr64(process, symbol_addr, &line_displacement, &line); + + char function_name[MAX_SYM_NAME] = "Unknown"; + if (SymFromAddr(process, symbol_addr, &displacement, symbol)) { + strncpy(function_name, symbol->Name, MAX_SYM_NAME - 1); + function_name[MAX_SYM_NAME - 1] = '\0'; // Ensure null termination + } + // Format line information + char line_info[256] = "Unknown"; + if (has_line) { + snprintf(line_info, sizeof(line_info), "%s:%lu", line.FileName, + line.LineNumber); } - SymCleanup(process); -} + // Print with better alignment using format specifiers + append_to_buffer(pStackTraceBuffer, " %-40.40s 0x%016llX %s\n", + function_name, symbol_addr, line_info); + append_to_buffer(pStackTraceBuffer, "\0"); + } + SymCleanup(process); +} LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { - const DWORD exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; - switch(exceptionCode) { - case EXCEPTION_ACCESS_VIOLATION: - { - void* faulting_address = (void*)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; - StackTraceBuffer stack_trace_buffer; - if(!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) - { - caml_failwith("Can't create stack trace buffer"); - return EXCEPTION_CONTINUE_SEARCH; - } - create_stacktrace(&stack_trace_buffer); - - caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), stack_trace_buffer.buffer); - free(stack_trace_buffer.buffer); - ExitProcess(STATUS_ACCESS_VIOLATION); - } - default: break; + const DWORD exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + switch (exceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: { + void *faulting_address = + (void *)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; + StackTraceBuffer stack_trace_buffer; + if (!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { + caml_failwith("Can't create stack trace buffer"); + return EXCEPTION_CONTINUE_SEARCH; } - return EXCEPTION_CONTINUE_SEARCH; + create_stacktrace(&stack_trace_buffer); + + caml_raise_with_string(*caml_named_value(CAML_ERROR_ID), + stack_trace_buffer.buffer); + free(stack_trace_buffer.buffer); + ExitProcess(STATUS_ACCESS_VIOLATION); + } + default: + break; + } + return EXCEPTION_CONTINUE_SEARCH; } #else #define PLATFORM_UNIX -#include #include +#include #include #include -void unix_signal_handler(int sig, siginfo_t *si, void *unused) { +#define STACK_TRACE_LENGTH 20 + +static void unix_signal_handler(int sig, siginfo_t *si, void *unused) { StackTraceBuffer stack_trace_buffer; if (!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { @@ -195,20 +191,19 @@ void unix_signal_handler(int sig, siginfo_t *si, void *unused) { return; } - void* trace[20]; - size_t trace_size = backtrace(trace, 20); + void *trace[STACK_TRACE_LENGTH]; + size_t trace_size = backtrace(trace, STACK_TRACE_LENGTH); - if(trace_size == 0) - { + if (trace_size == 0) { caml_failwith("Couldn't get backtrace"); return; } - append_to_buffer(&stack_trace_buffer, "Caught Violation access, here's stack trace:\n"); - - char** pSymbols = backtrace_symbols(trace, trace_size); - for(int i = 0; i < trace_size; ++i) - { + append_to_buffer(&stack_trace_buffer, + "Caught Violation access, here's stack trace:\n"); + + char **pSymbols = backtrace_symbols(trace, trace_size); + for (int i = 0; i < trace_size; ++i) { append_to_buffer(&stack_trace_buffer, "%s\n", pSymbols[i]); } free(pSymbols); @@ -219,9 +214,7 @@ void unix_signal_handler(int sig, siginfo_t *si, void *unused) { } #endif - -CAMLprim value caml_setup_stub_exception_handler() -{ +CAMLprim value caml_setup_stub_exception_handler(void) { CAMLparam0(); #ifdef PLATFORM_WINDOWS AddVectoredExceptionHandler(1, windows_exception_handler); diff --git a/test/e2e/alcotest/stubs/nullexceptionstub.c b/test/e2e/alcotest/stubs/nullexceptionstub.c index 7b312aa9..4ffda952 100644 --- a/test/e2e/alcotest/stubs/nullexceptionstub.c +++ b/test/e2e/alcotest/stubs/nullexceptionstub.c @@ -8,7 +8,7 @@ #include #include -value caml_segfault_call() +value caml_segfault_call(void) { CAMLparam0(); int* someVal = NULL; From dcda5279eda8a047a0b3e4606e0a542c96c8d1d9 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 00:11:41 +0100 Subject: [PATCH 07/15] Fix js build by adding stub --- src/alcotest-engine/dune | 93 +++++++++++---------- src/alcotest-engine/stack_error_reporter.js | 7 ++ 2 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 src/alcotest-engine/stack_error_reporter.js diff --git a/src/alcotest-engine/dune b/src/alcotest-engine/dune index d6fbd3a0..c79449e7 100644 --- a/src/alcotest-engine/dune +++ b/src/alcotest-engine/dune @@ -1,46 +1,47 @@ -(rule - (target callsite_loc.ml) - (deps callsite_loc.412.ml) - (enabled_if - (>= %{ocaml_version} 4.12.0)) - (action - (copy %{deps} %{target}))) - -(rule - (target callsite_loc.ml) - (enabled_if - (< %{ocaml_version} 4.12.0)) - (action - (write-file - %{target} - "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) - -(rule - (target c_library_flags.sexp) - (enabled_if (= %{os_type} Win32)) - (action - (write-file %{target} "(-ldbghelp)"))) - -(rule - (target c_library_flags.sexp) - (enabled_if (or (<> %{os_type} Win32) (= %{architecture} "js"))) - (action - (write-file %{target} "()"))) - -(library - (name alcotest_engine) - (public_name alcotest.engine) - (foreign_stubs - (language c) - (names stack_error_reporter)) - (c_library_flags (:standard (:include c_library_flags.sexp))) - (libraries - alcotest.stdlib_ext - fmt - astring - cmdliner - fmt.cli - re - stdlib-shims - uutf) - (preprocess future_syntax)) +(rule + (target callsite_loc.ml) + (deps callsite_loc.412.ml) + (enabled_if + (>= %{ocaml_version} 4.12.0)) + (action + (copy %{deps} %{target}))) + +(rule + (target callsite_loc.ml) + (enabled_if + (< %{ocaml_version} 4.12.0)) + (action + (write-file + %{target} + "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (= %{os_type} Win32)) + (action + (write-file %{target} "(-ldbghelp)"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (or (<> %{os_type} Win32) (= %{architecture} "js"))) + (action + (write-file %{target} "()"))) + +(library + (name alcotest_engine) + (public_name alcotest.engine) + (foreign_stubs + (language c) + (names stack_error_reporter)) + (c_library_flags (:standard (:include c_library_flags.sexp))) + (js_of_ocaml (javascript_files stack_error_reporter.js)) + (libraries + alcotest.stdlib_ext + fmt + astring + cmdliner + fmt.cli + re + stdlib-shims + uutf) + (preprocess future_syntax)) diff --git a/src/alcotest-engine/stack_error_reporter.js b/src/alcotest-engine/stack_error_reporter.js new file mode 100644 index 00000000..5b08982c --- /dev/null +++ b/src/alcotest-engine/stack_error_reporter.js @@ -0,0 +1,7 @@ +//Provides: caml_setup_stub_exception_handler +function caml_setup_stub_exception_handler(unit) +{ + // Intentionally left empty + // This function is mainly for C stubs and Segfault errors + return; +} \ No newline at end of file From 9a51e3c7f9b35d7049d31fb3bd07a8b623a2295e Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 00:24:26 +0100 Subject: [PATCH 08/15] Add accepted exit code --- test/e2e/alcotest/stubs/dune | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/alcotest/stubs/dune b/test/e2e/alcotest/stubs/dune index e30a8087..f08eb21c 100644 --- a/test/e2e/alcotest/stubs/dune +++ b/test/e2e/alcotest/stubs/dune @@ -10,7 +10,7 @@ (target stubs.actual) (action (with-accepted-exit-codes - (or 1 2 124 125) + (or 0 1 2 124 125) (with-outputs-to %{target} (run %{dep:nullexception.exe} --color=auto))))) From 8cc6d0c7ccc11c17aa58eedca960c17b009f2891 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 01:07:22 +0100 Subject: [PATCH 09/15] use preallocated static buffer --- src/alcotest-engine/stack_error_reporter.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index e80075fb..5acd096e 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -14,7 +14,9 @@ #include #include -const size_t buffer_size = 1024 * 10; +#define CRASH_BUFFER_SIZE 10240 +static char crash_buffer[CRASH_BUFFER_SIZE]; + typedef struct { char *buffer; size_t capacity; @@ -23,10 +25,8 @@ typedef struct { static bool finit_stack_trace_buffer(StackTraceBuffer *pStackTraceBuffer, size_t size) { - char *error_buffer = (char *)malloc(size); - if (error_buffer == NULL) { - return false; - } + char *error_buffer = crash_buffer; + pStackTraceBuffer->capacity = size; pStackTraceBuffer->offset = 0; pStackTraceBuffer->buffer = error_buffer; @@ -158,7 +158,7 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { void *faulting_address = (void *)pExceptionInfo->ExceptionRecord->ExceptionInformation[1]; StackTraceBuffer stack_trace_buffer; - if (!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { + if (!finit_stack_trace_buffer(&stack_trace_buffer, CRASH_BUFFER_SIZE)) { caml_failwith("Can't create stack trace buffer"); return EXCEPTION_CONTINUE_SEARCH; } @@ -186,7 +186,7 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { static void unix_signal_handler(int sig, siginfo_t *si, void *unused) { StackTraceBuffer stack_trace_buffer; - if (!finit_stack_trace_buffer(&stack_trace_buffer, buffer_size)) { + if (!finit_stack_trace_buffer(&stack_trace_buffer, CRASH_BUFFER_SIZE)) { caml_failwith("Can't create stack trace buffer"); return; } @@ -224,6 +224,7 @@ CAMLprim value caml_setup_stub_exception_handler(void) { sigemptyset(&sa.sa_mask); sa.sa_sigaction = unix_signal_handler; sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGBUS, &sa, NULL); // Catch SIGBUS as well for macOS #endif CAMLreturn(Val_unit); } \ No newline at end of file From ea752f080e25aee67d7841efc0f8348cbc6b0697 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 01:08:27 +0100 Subject: [PATCH 10/15] Update test and expected code --- test/e2e/alcotest/stubs/dune | 2 +- test/e2e/alcotest/stubs/nullexception.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/alcotest/stubs/dune b/test/e2e/alcotest/stubs/dune index f08eb21c..e30a8087 100644 --- a/test/e2e/alcotest/stubs/dune +++ b/test/e2e/alcotest/stubs/dune @@ -10,7 +10,7 @@ (target stubs.actual) (action (with-accepted-exit-codes - (or 0 1 2 124 125) + (or 1 2 124 125) (with-outputs-to %{target} (run %{dep:nullexception.exe} --color=auto))))) diff --git a/test/e2e/alcotest/stubs/nullexception.ml b/test/e2e/alcotest/stubs/nullexception.ml index e384b0ca..81e01609 100644 --- a/test/e2e/alcotest/stubs/nullexception.ml +++ b/test/e2e/alcotest/stubs/nullexception.ml @@ -11,7 +11,7 @@ let () = | SegFault _ -> fail "Got segfault" | _ -> - (check pass) "Should get segfault exception" () () + fail "Got uncategorized exception" in run __FILE__ [ From 65f1ee099d30209bef90885f23907e95f3fbf4fb Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 01:10:32 +0100 Subject: [PATCH 11/15] Use alternative stack during crash --- src/alcotest-engine/stack_error_reporter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 5acd096e..871b71b7 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -220,7 +220,7 @@ CAMLprim value caml_setup_stub_exception_handler(void) { AddVectoredExceptionHandler(1, windows_exception_handler); #elif defined(PLATFORM_UNIX) struct sigaction sa; - sa.sa_flags = SA_SIGINFO; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); sa.sa_sigaction = unix_signal_handler; sigaction(SIGSEGV, &sa, NULL); From 8c774cddd193a41369510f03672d6e8c81904f2c Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 20:51:13 +0100 Subject: [PATCH 12/15] Fix line endings, remove unnecessary header files, reformat --- src/alcotest-engine/dune | 94 ++++++++++----------- src/alcotest-engine/stack_error_reporter.c | 22 ++--- src/alcotest-engine/stack_error_reporter.js | 3 +- test/e2e/alcotest/stubs/nullexceptionstub.c | 22 ++--- 4 files changed, 62 insertions(+), 79 deletions(-) diff --git a/src/alcotest-engine/dune b/src/alcotest-engine/dune index c79449e7..583fc4a1 100644 --- a/src/alcotest-engine/dune +++ b/src/alcotest-engine/dune @@ -1,47 +1,47 @@ -(rule - (target callsite_loc.ml) - (deps callsite_loc.412.ml) - (enabled_if - (>= %{ocaml_version} 4.12.0)) - (action - (copy %{deps} %{target}))) - -(rule - (target callsite_loc.ml) - (enabled_if - (< %{ocaml_version} 4.12.0)) - (action - (write-file - %{target} - "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) - -(rule - (target c_library_flags.sexp) - (enabled_if (= %{os_type} Win32)) - (action - (write-file %{target} "(-ldbghelp)"))) - -(rule - (target c_library_flags.sexp) - (enabled_if (or (<> %{os_type} Win32) (= %{architecture} "js"))) - (action - (write-file %{target} "()"))) - -(library - (name alcotest_engine) - (public_name alcotest.engine) - (foreign_stubs - (language c) - (names stack_error_reporter)) - (c_library_flags (:standard (:include c_library_flags.sexp))) - (js_of_ocaml (javascript_files stack_error_reporter.js)) - (libraries - alcotest.stdlib_ext - fmt - astring - cmdliner - fmt.cli - re - stdlib-shims - uutf) - (preprocess future_syntax)) +(rule + (target callsite_loc.ml) + (deps callsite_loc.412.ml) + (enabled_if + (>= %{ocaml_version} 4.12.0)) + (action + (copy %{deps} %{target}))) + +(rule + (target callsite_loc.ml) + (enabled_if + (< %{ocaml_version} 4.12.0)) + (action + (write-file + %{target} + "let get ?__FUNCTION__ () =\n ignore __FUNCTION__;\n None\n"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (= %{os_type} Win32)) + (action + (write-file %{target} "(-ldbghelp)"))) + +(rule + (target c_library_flags.sexp) + (enabled_if (or (<> %{os_type} Win32) (= %{architecture} "js"))) + (action + (write-file %{target} "()"))) + +(library + (name alcotest_engine) + (public_name alcotest.engine) + (foreign_stubs + (language c) + (names stack_error_reporter)) + (c_library_flags (:standard (:include c_library_flags.sexp))) + (js_of_ocaml (javascript_files stack_error_reporter.js)) + (libraries + alcotest.stdlib_ext + fmt + astring + cmdliner + fmt.cli + re + stdlib-shims + uutf) + (preprocess future_syntax)) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 871b71b7..8a9c7854 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -1,18 +1,9 @@ // caml headers #include #include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #define CRASH_BUFFER_SIZE 10240 static char crash_buffer[CRASH_BUFFER_SIZE]; @@ -60,7 +51,6 @@ static void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { static const char *CAML_ERROR_ID = "segfault exception"; #if defined(_WIN32) -#define PLATFORM_WINDOWS #include #include #include @@ -117,7 +107,7 @@ static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { DWORD64 symbol_addr = stack.AddrPC.Offset; DWORD64 displacement = 0; - _Alignas(SYMBOL_INFO *) + alignas(SYMBOL_INFO *) symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); @@ -151,7 +141,8 @@ static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { SymCleanup(process); } -LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { +static LONG WINAPI +windows_exception_handler(const EXCEPTION_POINTERS *pExceptionInfo) { const DWORD exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; switch (exceptionCode) { case EXCEPTION_ACCESS_VIOLATION: { @@ -175,7 +166,6 @@ LONG WINAPI windows_exception_handler(PEXCEPTION_POINTERS pExceptionInfo) { return EXCEPTION_CONTINUE_SEARCH; } #else -#define PLATFORM_UNIX #include #include #include @@ -200,7 +190,7 @@ static void unix_signal_handler(int sig, siginfo_t *si, void *unused) { } append_to_buffer(&stack_trace_buffer, - "Caught Violation access, here's stack trace:\n"); + "Access violation caught, stacktrace:\n"); char **pSymbols = backtrace_symbols(trace, trace_size); for (int i = 0; i < trace_size; ++i) { @@ -216,9 +206,9 @@ static void unix_signal_handler(int sig, siginfo_t *si, void *unused) { CAMLprim value caml_setup_stub_exception_handler(void) { CAMLparam0(); -#ifdef PLATFORM_WINDOWS +#if defined(_WIN32) AddVectoredExceptionHandler(1, windows_exception_handler); -#elif defined(PLATFORM_UNIX) +#else struct sigaction sa; sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); diff --git a/src/alcotest-engine/stack_error_reporter.js b/src/alcotest-engine/stack_error_reporter.js index 5b08982c..1d258dd6 100644 --- a/src/alcotest-engine/stack_error_reporter.js +++ b/src/alcotest-engine/stack_error_reporter.js @@ -1,6 +1,5 @@ //Provides: caml_setup_stub_exception_handler -function caml_setup_stub_exception_handler(unit) -{ +function caml_setup_stub_exception_handler(unit) { // Intentionally left empty // This function is mainly for C stubs and Segfault errors return; diff --git a/test/e2e/alcotest/stubs/nullexceptionstub.c b/test/e2e/alcotest/stubs/nullexceptionstub.c index 4ffda952..d1bbc7a8 100644 --- a/test/e2e/alcotest/stubs/nullexceptionstub.c +++ b/test/e2e/alcotest/stubs/nullexceptionstub.c @@ -1,17 +1,11 @@ -#include -#include -#include -#include -#include -#include #include -#include -#include -value caml_segfault_call(void) -{ - CAMLparam0(); - int* someVal = NULL; - int a = *someVal; - CAMLreturn(Int_val(a)); +#include + +value caml_segfault_call(void) { + CAMLparam0(); + int *someVal = NULL; + int a = *someVal; + printf("%i\n", a); // printf to force Apple plat evalation + CAMLreturn(Int_val(a)); } \ No newline at end of file From fb6e903ed934babb8a6735242724c3f688e1cb13 Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 21:17:11 +0100 Subject: [PATCH 13/15] Fix windows build --- src/alcotest-engine/stack_error_reporter.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 8a9c7854..58f5ccf9 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -16,11 +16,9 @@ typedef struct { static bool finit_stack_trace_buffer(StackTraceBuffer *pStackTraceBuffer, size_t size) { - char *error_buffer = crash_buffer; - pStackTraceBuffer->capacity = size; pStackTraceBuffer->offset = 0; - pStackTraceBuffer->buffer = error_buffer; + pStackTraceBuffer->buffer = crash_buffer; pStackTraceBuffer->buffer[0] = '\0'; return true; } @@ -51,9 +49,9 @@ static void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { static const char *CAML_ERROR_ID = "segfault exception"; #if defined(_WIN32) +#include #include #include -#include // Stacktrace collection inspired by // https://smhk.net/note/2025/03/c-stack-trace-in-windows/ @@ -107,8 +105,7 @@ static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { DWORD64 symbol_addr = stack.AddrPC.Offset; DWORD64 displacement = 0; - alignas(SYMBOL_INFO *) - symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; + alignas(SYMBOL_INFO *) char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; @@ -142,7 +139,7 @@ static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { } static LONG WINAPI -windows_exception_handler(const EXCEPTION_POINTERS *pExceptionInfo) { +windows_exception_handler(EXCEPTION_POINTERS *pExceptionInfo) { const DWORD exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; switch (exceptionCode) { case EXCEPTION_ACCESS_VIOLATION: { From 697d0adba45fe6d05c6fd650035754057c52ad4f Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 21:21:35 +0100 Subject: [PATCH 14/15] Add formating, add headers --- src/alcotest-engine/stack_error_reporter.c | 8 +++++++- test/e2e/alcotest/stubs/nullexceptionstub.c | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/alcotest-engine/stack_error_reporter.c b/src/alcotest-engine/stack_error_reporter.c index 58f5ccf9..ce950dc8 100644 --- a/src/alcotest-engine/stack_error_reporter.c +++ b/src/alcotest-engine/stack_error_reporter.c @@ -2,7 +2,10 @@ #include #include #include +#include +#include +#include #include #define CRASH_BUFFER_SIZE 10240 @@ -49,9 +52,11 @@ static void append_to_buffer(StackTraceBuffer *sb, const char *format, ...) { static const char *CAML_ERROR_ID = "segfault exception"; #if defined(_WIN32) +// clang-format off #include #include #include +// clang-format on // Stacktrace collection inspired by // https://smhk.net/note/2025/03/c-stack-trace-in-windows/ @@ -105,7 +110,8 @@ static void create_stacktrace(StackTraceBuffer *pStackTraceBuffer) { DWORD64 symbol_addr = stack.AddrPC.Offset; DWORD64 displacement = 0; - alignas(SYMBOL_INFO *) char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; + alignas(SYMBOL_INFO *) char + symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0}; SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbol_buffer; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; diff --git a/test/e2e/alcotest/stubs/nullexceptionstub.c b/test/e2e/alcotest/stubs/nullexceptionstub.c index d1bbc7a8..f74c9686 100644 --- a/test/e2e/alcotest/stubs/nullexceptionstub.c +++ b/test/e2e/alcotest/stubs/nullexceptionstub.c @@ -1,3 +1,4 @@ +#include #include #include From 86c30479678704188720a25351efaae6697aa75d Mon Sep 17 00:00:00 2001 From: SturdyPose Date: Thu, 12 Mar 2026 21:50:36 +0100 Subject: [PATCH 15/15] Try to trigger segfault on mac --- test/e2e/alcotest/stubs/nullexceptionstub.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/e2e/alcotest/stubs/nullexceptionstub.c b/test/e2e/alcotest/stubs/nullexceptionstub.c index f74c9686..884c07eb 100644 --- a/test/e2e/alcotest/stubs/nullexceptionstub.c +++ b/test/e2e/alcotest/stubs/nullexceptionstub.c @@ -1,12 +1,17 @@ #include #include -#include +#if !defined(_WIN32) +#include +#endif value caml_segfault_call(void) { CAMLparam0(); - int *someVal = NULL; - int a = *someVal; - printf("%i\n", a); // printf to force Apple plat evalation - CAMLreturn(Int_val(a)); + volatile int *p = (volatile int *)0; + *p = 0xDEADBEEF; + #if !defined(_WIN32) + // in case mac won't call segfault + raise(SIGSEGV); + #endif + CAMLreturn(Int_val(0)); } \ No newline at end of file