From d139c72fc24fdb574861ac22fc11b91de944b2a4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 21 Dec 2025 11:53:44 +0000 Subject: [PATCH 1/8] Make `using` as a statement an opt-in with `#+feature using-stmt` --- core/compress/zlib/zlib.odin | 2 +- core/image/netpbm/netpbm.odin | 34 ++++++++++++++++----------------- core/image/png/png.odin | 2 +- examples/demo/demo.odin | 1 + src/build_settings.cpp | 5 +++++ src/check_stmt.cpp | 8 +++++--- src/check_type.cpp | 11 +++++++---- src/parser.cpp | 2 ++ vendor/fontstash/fontstash.odin | 2 +- 9 files changed, 39 insertions(+), 28 deletions(-) diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index b9189c1f1..e484a4958 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -1,4 +1,4 @@ -#+vet !using-param +#+feature using-stmt package compress_zlib /* diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index 25e0228b5..6a30d135a 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -1,4 +1,3 @@ -#+vet !using-stmt package netpbm import "core:bytes" @@ -73,8 +72,7 @@ save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context } } - // using info so we can just talk about the header - using info + header := &info.header // validation if header.format in (PBM + PGM + Formats{.Pf}) && img.channels != 1 \ @@ -664,14 +662,14 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for ASCII :: Formats{.P1, .P2, .P3} */ - using res.header + h := &res.header - width = img.width - height = img.height - channels = img.channels - depth = img.depth - maxval = 255 if img.depth == 8 else 65535 - little_endian = true if ODIN_ENDIAN == .Little else false + h.width = img.width + h.height = img.height + h.channels = img.channels + h.depth = img.depth + h.maxval = 255 if img.depth == 8 else 65535 + h.little_endian = ODIN_ENDIAN == .Little // Assume we'll find a suitable format ok = true @@ -680,37 +678,37 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for case 1: // Must be Portable Float Map if img.depth == 32 { - format = .Pf + h.format = .Pf return } if force_black_and_white { // Portable Bit Map - format = .P4 if prefer_binary else .P1 - maxval = 1 + h.format = .P4 if prefer_binary else .P1 + h.maxval = 1 return } else { // Portable Gray Map - format = .P5 if prefer_binary else .P2 + h.format = .P5 if prefer_binary else .P2 return } case 3: // Must be Portable Float Map if img.depth == 32 { - format = .PF + h.format = .PF return } // Portable Pixel Map - format = .P6 if prefer_binary else .P3 + h.format = .P6 if prefer_binary else .P3 return case: // Portable Arbitrary Map if img.depth == 8 || img.depth == 16 { - format = .P7 - scale = pfm_scale + h.format = .P7 + h.scale = pfm_scale return } } diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 1a7240bad..0170d3168 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1,4 +1,4 @@ -#+vet !using-stmt +#+feature using-stmt package png /* diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 1ea06d096..d4f0d9d4b 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1,5 +1,6 @@ #+vet !using-stmt !using-param #+feature dynamic-literals +#+feature using-stmt package main import "core:fmt" diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 7160f3721..12631c403 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -360,6 +360,8 @@ enum OptInFeatureFlags : u64 { OptInFeatureFlag_IntegerDivisionByZero_Self = 1u<<4, OptInFeatureFlag_IntegerDivisionByZero_AllBits = 1u<<5, + OptInFeatureFlag_UsingStmt = 1u<<6, + OptInFeatureFlag_IntegerDivisionByZero_ALL = OptInFeatureFlag_IntegerDivisionByZero_Trap| OptInFeatureFlag_IntegerDivisionByZero_Zero| @@ -384,6 +386,9 @@ u64 get_feature_flag_from_name(String const &name) { if (name == "integer-division-by-zero:all-bits") { return OptInFeatureFlag_IntegerDivisionByZero_AllBits; } + if (name == "using-stmt") { + return OptInFeatureFlag_UsingStmt; + } if (name == "global-context") { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 835f0162a..26860db4f 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2943,10 +2943,12 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) error(us->token, "Empty 'using' list"); return; } - if (check_vet_flags(node) & VetFlag_UsingStmt) { + + u64 feature_flags = check_feature_flags(ctx, node); + if ((feature_flags & OptInFeatureFlag_UsingStmt) == 0) { ERROR_BLOCK(); - error(node, "'using' as a statement is not allowed when '-vet' or '-vet-using' is applied"); - error_line("\t'using' is considered bad practice to use as a statement outside of immediate refactoring\n"); + error(node, "'using' has been disallowed as it is considered bad practice to use as a statement outside of immediate refactoring"); + error_line("\tIt you do require it for refactoring purposes or legacy code, it can be enabled on a per-file basis with '#+feature using-stmt'\n"); } for (Ast *expr : us->list) { diff --git a/src/check_type.cpp b/src/check_type.cpp index af07efd8f..2452cc6d0 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1830,11 +1830,14 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para Type *specialization = nullptr; bool is_using = (p->flags&FieldFlag_using) != 0; - if ((check_vet_flags(param) & VetFlag_UsingParam) && is_using) { - ERROR_BLOCK(); - error(param, "'using' on a procedure parameter is not allowed when '-vet' or '-vet-using-param' is applied"); - error_line("\t'using' is considered bad practice to use as a statement/procedure parameter outside of immediate refactoring\n"); + + u64 feature_flags = check_feature_flags(ctx, param); + + if (is_using && (feature_flags & OptInFeatureFlag_UsingStmt) == 0) { + ERROR_BLOCK(); + error(param, "'using' has been disallowed as it is considered bad practice to use as a statement/procedure parameter outside of immediate refactoring"); + error_line("\tIt you do require it for refactoring purposes or legacy code, it can be enabled on a per-file basis with '#+feature using-stmt'\n"); } if (type_expr == nullptr) { diff --git a/src/parser.cpp b/src/parser.cpp index 06703d643..e27e184d0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6449,6 +6449,8 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { syntax_error(token_for_pos, "Invalid feature flag name: %.*s", LIT(p)); error_line("\tExpected one of the following\n"); error_line("\tdynamic-literals\n"); + error_line("\tglobal-context\n"); + error_line("\tusing-stmt\n"); error_line("\tinteger-division-by-zero:trap\n"); error_line("\tinteger-division-by-zero:zero\n"); error_line("\tinteger-division-by-zero:self\n"); diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 772e379f0..33e7b68de 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,5 +1,5 @@ // An Odin-native source port of [[ Fontstash ; https://github.com/memononen/fontstash ]]. -#+vet !using-param +#+feature using-stmt package fontstash import "base:runtime" From 8b745c3909a3482aebe27998d8b870286e448e35 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 21 Dec 2025 13:14:38 +0000 Subject: [PATCH 2/8] Remove `using` use in thread_other.odin --- core/thread/thread_other.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index dde2a8e48..878119cbd 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -37,7 +37,7 @@ _destroy :: proc(thread: ^Thread) { unimplemented("core:thread procedure not supported on target") } -_terminate :: proc(using thread : ^Thread, exit_code: int) { +_terminate :: proc(thread : ^Thread, exit_code: int) { unimplemented("core:thread procedure not supported on target") } From c9fff456cd19bf92c72a96bdbc98062c3530dc57 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Tue, 23 Dec 2025 02:44:36 +1000 Subject: [PATCH 3/8] [`core:testing`] Use Windows API for SIG handling --- core/testing/signal_handler_libc.odin | 21 +- core/testing/signal_handler_windows.odin | 240 ++++++++++++++++++++++- 2 files changed, 246 insertions(+), 15 deletions(-) diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index a1441f29d..74608bb48 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -1,5 +1,5 @@ #+private -#+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package testing /* @@ -8,6 +8,7 @@ package testing List of contributors: Feoramund: Total rewrite. + blob1807: Windows Win32 API rewrite. */ import "base:intrinsics" @@ -24,18 +25,12 @@ import "core:terminal/ansi" @(private="file") stop_test_passed: libc.sig_atomic_t @(private="file") stop_test_alert: libc.sig_atomic_t -when ODIN_ARCH == .i386 && ODIN_OS == .Windows { - // Thread-local storage is problematic on Windows i386 - @(private="file") - local_test_index: libc.sig_atomic_t - @(private="file") - local_test_index_set: bool -} else { - @(private="file", thread_local) - local_test_index: libc.sig_atomic_t - @(private="file", thread_local) - local_test_index_set: bool -} + +@(private="file", thread_local) +local_test_index: libc.sig_atomic_t +@(private="file", thread_local) +local_test_index_set: bool + // Windows does not appear to have a SIGTRAP, so this is defined here, instead // of in the libc package, just so there's no confusion about it being diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin index 74ebe2998..cde0cad9d 100644 --- a/core/testing/signal_handler_windows.odin +++ b/core/testing/signal_handler_windows.odin @@ -1,6 +1,242 @@ #+private +#+build windows package testing -__setup_signal_handler :: proc() {} +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's license. + + List of contributors: + Feoramund: Total rewrite. + blob1807: Windows Win32 API rewrite. + +*/ + +import "base:runtime" +import "base:intrinsics" + +import "core:os" +import "core:sync" +import "core:c/libc" +import "core:terminal/ansi" + +import win32 "core:sys/windows" + + +@(private="file") stop_runner_flag: int + +@(private="file") stop_test_gate: sync.Mutex +@(private="file") stop_test_index: int +@(private="file") stop_test_signal: Exception_Code +@(private="file") stop_test_passed: bool +@(private="file") stop_test_alert: int + + +when ODIN_ARCH == .i386 { + // Thread-local storage is problematic on Windows i386 + @(private="file") + local_test_index: int + @(private="file") + local_test_index_set: bool +} else { + @(private="file", thread_local) + local_test_index: int + @(private="file", thread_local) + local_test_index_set: bool +} + + +@(private="file") +Exception_Code :: enum win32.DWORD { + Datatype_Misalignment = win32.EXCEPTION_DATATYPE_MISALIGNMENT, + Breakpoint = win32.EXCEPTION_BREAKPOINT, + Single_Step = win32.EXCEPTION_SINGLE_STEP, + Access_Violation = win32.EXCEPTION_ACCESS_VIOLATION, + In_Page_Error = win32.EXCEPTION_IN_PAGE_ERROR, + Illegal_Instruction = win32.EXCEPTION_ILLEGAL_INSTRUCTION, + Noncontinuable_Exception = win32.EXCEPTION_NONCONTINUABLE_EXCEPTION, + Invaild_Disposition = win32.EXCEPTION_INVALID_DISPOSITION, + Array_Bounds_Exceeded = win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, + FLT_Denormal_Operand = win32.EXCEPTION_FLT_DENORMAL_OPERAND, + FLT_Divide_By_Zero = win32.EXCEPTION_FLT_DIVIDE_BY_ZERO, + FLT_Inexact_Result = win32.EXCEPTION_FLT_INEXACT_RESULT, + FLT_Invalid_Operation = win32.EXCEPTION_FLT_INVALID_OPERATION, + FLT_Overflow = win32.EXCEPTION_FLT_OVERFLOW, + FLT_Stack_Check = win32.EXCEPTION_FLT_STACK_CHECK, + FLT_Underflow = win32.EXCEPTION_FLT_UNDERFLOW, + INT_Divide_By_Zero = win32.EXCEPTION_INT_DIVIDE_BY_ZERO, + INT_Overflow = win32.EXCEPTION_INT_OVERFLOW, + PRIV_Instruction = win32.EXCEPTION_PRIV_INSTRUCTION, + Stack_Overflow = win32.EXCEPTION_STACK_OVERFLOW, +} + + +@(private="file") +stop_runner_callback :: proc "system" (ctrl_type: win32.DWORD) -> win32.BOOL { + if ctrl_type == win32.CTRL_C_EVENT { + prev := intrinsics.atomic_add(&stop_runner_flag, 1) + + // If the flag was already set (if this is the second signal sent for example), + // consider this a forced (not graceful) exit. + if prev > 0 { + os.exit(1) + } + // Say we've hanndled the signal. + return true + } + + // This will also get called for other events which we don't handle for. + // Instead we pass it on to the next handler. + return false +} + +@(private) +stop_test_callback :: proc "system" (info: ^win32.EXCEPTION_POINTERS) -> win32.LONG { + if !local_test_index_set { + // We're a thread created by a test thread. + // + // There's nothing we can do to inform the test runner about who + // signalled, so hopefully the test will handle their own sub-threads. + return win32.EXCEPTION_CONTINUE_SEARCH + } + + context = runtime.default_context() + code := Exception_Code(info.ExceptionRecord.ExceptionCode) + + if local_test_index == -1 { + // We're the test runner, and we ourselves have caught a signal from + // which there is no recovery. + // + // The most we can do now is make sure the user's cursor is visible, + // nuke the entire processs, and hope a useful core dump survives. + if !global_ansi_disabled { + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + os.write_string(os.stdout, show_cursor) + os.flush(os.stdout) + } + + // This is an attempt at being compliant by avoiding printf. + expbuf: [8]byte + expstr: string + { + expnum := cast(int)code + i := len(expbuf) - 2 + for expnum > 0 { + m := expnum % 10 + expnum /= 10 + expbuf[i] = cast(u8)('0' + m) + i -= 1 + } + expstr = cast(string)expbuf[1 + i:len(expbuf) - 1] + } + + advisory_a := ` +The test runner's main thread has caught an unrecoverable error (signal ` + advisory_b := `) and will now forcibly terminate. +This is a dire bug and should be reported to the Odin developers. +` + os.write_string(os.stderr, advisory_a) + os.write_string(os.stderr, expstr) + os.write_string(os.stderr, advisory_b) + os.flush(os.stderr) + + win32.TerminateProcess(win32.GetCurrentProcess(), 1) + } + + if sync.mutex_guard(&stop_test_gate) { + intrinsics.atomic_store(&stop_test_index, local_test_index) + intrinsics.atomic_store(&stop_test_signal, code) + passed: bool + check_passing: { + if location := local_test_assertion_raised.location; location != {} { + for i in 0.. bool { + return intrinsics.atomic_load(&stop_runner_flag) == 1 +} + +@(private="file") +unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) { + if ok { + sync.mutex_unlock(&stop_test_gate) + } +} + +@(deferred_out=unlock_stop_test_gate) +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + if intrinsics.atomic_load(&stop_test_alert) == 1 { + intrinsics.atomic_store(&stop_test_alert, 0) + + test_index = intrinsics.atomic_load(&stop_test_index) + if intrinsics.atomic_load(&stop_test_passed) { + reason = .Successful_Stop + } else { + #partial switch intrinsics.atomic_load(&stop_test_signal) { + case .Illegal_Instruction: reason = .Illegal_Instruction + case .Access_Violation: reason = .Segmentation_Fault + case .Breakpoint, .Single_Step: reason = .Unhandled_Trap + + case .FLT_Denormal_Operand ..= .INT_Overflow: + reason = .Arithmetic_Error + } + } + ok = true + } + + return +} -_test_thread_cancel :: proc "contextless" () {} From 56194732a8b73412970cc4a6dd41c7792db77832 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:45:51 +1000 Subject: [PATCH 4/8] Remove unneeded `Exception_Code` enum --- core/testing/signal_handler_windows.odin | 49 ++++++------------------ 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin index cde0cad9d..2942b5bd2 100644 --- a/core/testing/signal_handler_windows.odin +++ b/core/testing/signal_handler_windows.odin @@ -9,7 +9,6 @@ package testing List of contributors: Feoramund: Total rewrite. blob1807: Windows Win32 API rewrite. - */ import "base:runtime" @@ -27,7 +26,7 @@ import win32 "core:sys/windows" @(private="file") stop_test_gate: sync.Mutex @(private="file") stop_test_index: int -@(private="file") stop_test_signal: Exception_Code +@(private="file") stop_test_signal: win32.DWORD @(private="file") stop_test_passed: bool @(private="file") stop_test_alert: int @@ -46,31 +45,6 @@ when ODIN_ARCH == .i386 { } -@(private="file") -Exception_Code :: enum win32.DWORD { - Datatype_Misalignment = win32.EXCEPTION_DATATYPE_MISALIGNMENT, - Breakpoint = win32.EXCEPTION_BREAKPOINT, - Single_Step = win32.EXCEPTION_SINGLE_STEP, - Access_Violation = win32.EXCEPTION_ACCESS_VIOLATION, - In_Page_Error = win32.EXCEPTION_IN_PAGE_ERROR, - Illegal_Instruction = win32.EXCEPTION_ILLEGAL_INSTRUCTION, - Noncontinuable_Exception = win32.EXCEPTION_NONCONTINUABLE_EXCEPTION, - Invaild_Disposition = win32.EXCEPTION_INVALID_DISPOSITION, - Array_Bounds_Exceeded = win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, - FLT_Denormal_Operand = win32.EXCEPTION_FLT_DENORMAL_OPERAND, - FLT_Divide_By_Zero = win32.EXCEPTION_FLT_DIVIDE_BY_ZERO, - FLT_Inexact_Result = win32.EXCEPTION_FLT_INEXACT_RESULT, - FLT_Invalid_Operation = win32.EXCEPTION_FLT_INVALID_OPERATION, - FLT_Overflow = win32.EXCEPTION_FLT_OVERFLOW, - FLT_Stack_Check = win32.EXCEPTION_FLT_STACK_CHECK, - FLT_Underflow = win32.EXCEPTION_FLT_UNDERFLOW, - INT_Divide_By_Zero = win32.EXCEPTION_INT_DIVIDE_BY_ZERO, - INT_Overflow = win32.EXCEPTION_INT_OVERFLOW, - PRIV_Instruction = win32.EXCEPTION_PRIV_INSTRUCTION, - Stack_Overflow = win32.EXCEPTION_STACK_OVERFLOW, -} - - @(private="file") stop_runner_callback :: proc "system" (ctrl_type: win32.DWORD) -> win32.BOOL { if ctrl_type == win32.CTRL_C_EVENT { @@ -101,7 +75,7 @@ stop_test_callback :: proc "system" (info: ^win32.EXCEPTION_POINTERS) -> win32.L } context = runtime.default_context() - code := Exception_Code(info.ExceptionRecord.ExceptionCode) + code := info.ExceptionRecord.ExceptionCode if local_test_index == -1 { // We're the test runner, and we ourselves have caught a signal from @@ -166,11 +140,11 @@ This is a dire bug and should be reported to the Odin developers. } signal := local_test_expected_failures.signal switch signal { - case libc.SIGILL: passed = code == .Illegal_Instruction - case libc.SIGSEGV: passed = code == .Access_Violation + case libc.SIGILL: passed = code == win32.EXCEPTION_ILLEGAL_INSTRUCTION + case libc.SIGSEGV: passed = code == win32.EXCEPTION_ACCESS_VIOLATION case libc.SIGFPE: - #partial switch code { - case .FLT_Denormal_Operand ..= .INT_Overflow: + switch code { + case win32.EXCEPTION_FLT_DENORMAL_OPERAND ..= win32.EXCEPTION_INT_OVERFLOW: passed = true } } @@ -225,12 +199,13 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) if intrinsics.atomic_load(&stop_test_passed) { reason = .Successful_Stop } else { - #partial switch intrinsics.atomic_load(&stop_test_signal) { - case .Illegal_Instruction: reason = .Illegal_Instruction - case .Access_Violation: reason = .Segmentation_Fault - case .Breakpoint, .Single_Step: reason = .Unhandled_Trap + switch intrinsics.atomic_load(&stop_test_signal) { + case win32.EXCEPTION_ILLEGAL_INSTRUCTION: reason = .Illegal_Instruction + case win32.EXCEPTION_ACCESS_VIOLATION: reason = .Segmentation_Fault + case win32.EXCEPTION_BREAKPOINT: reason = .Unhandled_Trap + case win32.EXCEPTION_SINGLE_STEP: reason = .Unhandled_Trap - case .FLT_Denormal_Operand ..= .INT_Overflow: + case win32.EXCEPTION_FLT_DENORMAL_OPERAND ..= win32.EXCEPTION_INT_OVERFLOW: reason = .Arithmetic_Error } } From 4eeb509fe8f1750be784786d4ac6470d6591fd6e Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:17:43 +1000 Subject: [PATCH 5/8] Fix an OOB & format exception code as hex Fix an OOB caused when the test runner catches the exception & format exception codes as upper case hex. --- core/testing/signal_handler_windows.odin | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin index 2942b5bd2..8843dde92 100644 --- a/core/testing/signal_handler_windows.odin +++ b/core/testing/signal_handler_windows.odin @@ -17,6 +17,7 @@ import "base:intrinsics" import "core:os" import "core:sync" import "core:c/libc" +import "core:strconv" import "core:terminal/ansi" import win32 "core:sys/windows" @@ -89,23 +90,16 @@ stop_test_callback :: proc "system" (info: ^win32.EXCEPTION_POINTERS) -> win32.L os.flush(os.stdout) } - // This is an attempt at being compliant by avoiding printf. expbuf: [8]byte - expstr: string - { - expnum := cast(int)code - i := len(expbuf) - 2 - for expnum > 0 { - m := expnum % 10 - expnum /= 10 - expbuf[i] = cast(u8)('0' + m) - i -= 1 + expstr := strconv.write_uint(expbuf[:], cast(u64)code, 16) + for &c in expbuf { + if 'a' <= c && c <= 'f' { + c -= 32 } - expstr = cast(string)expbuf[1 + i:len(expbuf) - 1] } advisory_a := ` -The test runner's main thread has caught an unrecoverable error (signal ` +The test runner's main thread has caught an unrecoverable error (exception 0x` advisory_b := `) and will now forcibly terminate. This is a dire bug and should be reported to the Odin developers. ` @@ -155,7 +149,7 @@ This is a dire bug and should be reported to the Odin developers. } // Pass on the exeption to the next handler. As we don't wont to recover from it. - // This will allow debuggers handle it properly. + // This also allows debuggers handle it properly. return win32.EXCEPTION_CONTINUE_SEARCH } From 13faedaf4ce3a24ed92917d44244f226ff3fed57 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Wed, 24 Dec 2025 00:39:21 +1000 Subject: [PATCH 6/8] Try to ensure the runner's handler gets called first --- core/testing/signal_handler_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin index 8843dde92..ef4a9d75d 100644 --- a/core/testing/signal_handler_windows.odin +++ b/core/testing/signal_handler_windows.odin @@ -165,7 +165,7 @@ _setup_signal_handler :: proc() { // - Asserts and panics; // - Arithmetic errors; and // - Segmentation faults (illegal memory access). - win32.AddVectoredExceptionHandler(0, stop_test_callback) + win32.AddVectoredExceptionHandler(1, stop_test_callback) } _setup_task_signal_handler :: proc(test_index: int) { From 569da5a1cf346056fd09fdabea567d2dd1ee8534 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Wed, 24 Dec 2025 01:08:52 +1000 Subject: [PATCH 7/8] Revert "Try to ensure the runner's handler gets called first" This reverts commit 13faedaf4ce3a24ed92917d44244f226ff3fed57. As it's causing issues with ASAN, which adds it's own `ExceptionHandler` as the first. Making ours first messes with it. Causing an Access Violation when trying to setup `context` in our handler. --- core/testing/signal_handler_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin index ef4a9d75d..8843dde92 100644 --- a/core/testing/signal_handler_windows.odin +++ b/core/testing/signal_handler_windows.odin @@ -165,7 +165,7 @@ _setup_signal_handler :: proc() { // - Asserts and panics; // - Arithmetic errors; and // - Segmentation faults (illegal memory access). - win32.AddVectoredExceptionHandler(1, stop_test_callback) + win32.AddVectoredExceptionHandler(0, stop_test_callback) } _setup_task_signal_handler :: proc(test_index: int) { From 808c14f60f3753e9030de8a8d9086dacb341c414 Mon Sep 17 00:00:00 2001 From: bplu4t2f Date: Sat, 3 Jan 2026 12:07:47 +0100 Subject: [PATCH 8/8] Add more win32 bindings --- core/sys/windows/gdi32.odin | 11 +++++-- core/sys/windows/user32.odin | 12 +++++++ core/sys/windows/ux_theme.odin | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 756ec5011..ad2243b56 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -34,7 +34,6 @@ foreign gdi32 { SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- GetDCBrushColor :: proc(hdc: HDC) -> COLORREF --- PatBlt :: proc(hdc: HDC, x, y, w, h: INT, rop: DWORD) -> BOOL --- - Rectangle :: proc(hdc: HDC, left, top, right, bottom: INT) -> BOOL --- CreateFontW :: proc(cHeight, cWidth, cEscapement, cOrientation, cWeight: INT, bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, iClipPrecision, iQuality, iPitchAndFamily: DWORD, pszFaceName: LPCWSTR) -> HFONT --- CreateFontIndirectW :: proc(lplf: ^LOGFONTW) -> HFONT --- @@ -70,12 +69,20 @@ foreign gdi32 { RealizePalette :: proc(hdc: HDC) -> UINT --- SetTextColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- - RoundRect :: proc(hdc: HDC, left: INT, top: INT, right: INT, bottom: INT, width: INT, height: INT) -> BOOL --- SetPixel :: proc(hdc: HDC, x: INT, y: INT, color: COLORREF) -> COLORREF --- GdiTransparentBlt :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, crTransparent: UINT) -> BOOL --- GdiGradientFill :: proc(hdc: HDC, pVertex: PTRIVERTEX, nVertex: ULONG, pMesh: PVOID, nCount: ULONG, ulMode: ULONG) -> BOOL --- GdiAlphaBlend :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, ftn: BLENDFUNCTION) -> BOOL --- + + // Filled Shape Functions + Rectangle :: proc(hdc: HDC, left, top, right, bottom: c_int) -> BOOL --- + Ellipse :: proc(hdc: HDC, left, top, right, bottom: c_int) -> BOOL --- + RoundRect :: proc(hdc: HDC, left, top, right, bottom, width, height: c_int) -> BOOL --- + Pie :: proc(hdc: HDC, left, right, top, bottom, xr1, yr1, xr2, yr2: c_int) -> BOOL --- + Chord :: proc(hdc: HDC, x1, y1, x2, y2, x3, y3, x4, y4: c_int) -> BOOL --- + Polygon :: proc(hdc: HDC, apt: [^]POINT, cpt: c_int) -> BOOL --- + PolyPolygon :: proc(hdc: HDC, apt: [^]POINT, asz: [^]c_int, csz: c_int) -> BOOL --- } @(require_results) diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index cd3efdeb7..580c09b45 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -58,6 +58,7 @@ foreign user32 { IsZoomed :: proc(hwnd: HWND) -> BOOL --- BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- GetTopWindow :: proc(hWnd: HWND) -> HWND --- + GetWindow :: proc(hwnd: HWND, uCmd: UINT) -> HWND --- SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- GetForegroundWindow :: proc() -> HWND --- GetDesktopWindow :: proc() -> HWND --- @@ -279,6 +280,7 @@ foreign user32 { FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> c_int --- FrameRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> c_int --- + InvertRect :: proc(hDC: HDC, lprc: ^RECT) -> BOOL --- EqualRect :: proc(lprc1, lprc2: ^RECT) -> BOOL --- OffsetRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- InflateRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- @@ -939,3 +941,13 @@ ESB_DISABLE_UP :: 0x0001 ESB_DISABLE_DOWN :: 0x0002 ESB_DISABLE_LTUP :: ESB_DISABLE_LEFT ESB_DISABLE_RTDN :: ESB_DISABLE_RIGHT + +// Command constants for GetWindow +GW_HWNDFIRST :: 0 +GW_HWNDLAST :: 1 +GW_HWNDNEXT :: 2 +GW_HWNDPREV :: 3 +GW_OWNER :: 4 +GW_CHILD :: 5 +GW_ENABLEDPOPUP :: 6 +GW_MAX :: 6 diff --git a/core/sys/windows/ux_theme.odin b/core/sys/windows/ux_theme.odin index 4ce126c73..e19800246 100644 --- a/core/sys/windows/ux_theme.odin +++ b/core/sys/windows/ux_theme.odin @@ -14,5 +14,62 @@ PMARGINS :: ^MARGINS @(default_calling_convention="system") foreign uxtheme { IsThemeActive :: proc() -> BOOL --- + GetWindowTheme :: proc(hwnd: HWND) -> HTHEME --- SetWindowTheme :: proc(hWnd: HWND, pszSubAppName, pszSubIdList: LPCWSTR) -> HRESULT --- + + // Buffered painting and buffered animation + BufferedPaintInit :: proc() -> HRESULT --- + BufferedPaintUnInit :: proc() -> HRESULT --- + + BeginBufferedPaint :: proc(hdcTarget: HDC, prcTarget: ^RECT, dwFormat: BP_BUFFERFORMAT, pPaintParams: ^BP_PAINTPARAMS, phdc: ^HDC) -> HPAINTBUFFER --- + EndBufferedPaint :: proc(hBufferedPaint: HPAINTBUFFER, fUpdateTarget: BOOL) -> HRESULT --- + + GetBufferedPaintTargetRect :: proc(hBufferedPaint: HPAINTBUFFER, prc: ^RECT) -> HRESULT --- + GetBufferedPaintTargetDC :: proc(hBufferedPaint: HPAINTBUFFER) -> HDC --- + GetBufferedPaintDC :: proc(hBufferedPaint: HPAINTBUFFER) -> HDC --- + GetBufferedPaintBits :: proc(hBufferedPaint, ppbBuffer: ^[^]RGBQUAD, pcxRow: ^c_int) -> HRESULT --- + + BufferedPaintClear :: proc(hBufferedPaint: HPAINTBUFFER, prc: ^RECT) -> HRESULT --- + BufferedPaintSetAlpha :: proc(hBufferedPaint: HPAINTBUFFER, prc: ^RECT, alpha: BYTE) -> HRESULT --- + + BufferedPaintStopAllAnimations :: proc(hwnd: HWND) -> HRESULT --- + BeginBufferedAnimation :: proc(hwnd: HWND, hdcTarget: HDC, prcTarget: ^RECT, dwFormat: BP_BUFFERFORMAT, pPaintParams: ^BP_PAINTPARAMS, pAnimationParams: ^BP_ANIMATIONPARAMS, phdcFrom: ^HDC, phdcTo: ^HDC) -> HANIMATIONBUFFER --- + BufferedPaintRenderAnimation :: proc(hwnd: HWND, hdcTarget: HDC) -> BOOL --- +} + +HTHEME :: distinct HANDLE +HPAINTBUFFER :: distinct HANDLE +HANIMATIONBUFFER :: distinct HANDLE + +BP_BUFFERFORMAT :: enum c_int { + BPBF_COMPATIBLEBITMAP, + BPBF_DIB, + BPBF_TOPDOWNDIB, + BPBF_TOPDOWNMONODIB, +} + +BP_ANIMATIONSTYLE :: enum c_int { + BPAS_NONE, + BPAS_LINEAR, + BPAS_CUBIC, + BPAS_SINE, +} + +// Constants for BP_PAINTPARAMS.dwFlags +BPPF_ERASE :: 0x0001 +BPPF_NOCLIP :: 0x0002 +BPPF_NONCLIENT :: 0x0004 + +BP_ANIMATIONPARAMS :: struct { + cbSize: DWORD, + dwFlags: DWORD, + style: BP_ANIMATIONSTYLE, + dwDuration: DWORD, +} + +BP_PAINTPARAMS :: struct { + cbSize: DWORD, + dwFlags: DWORD, + prcExclude: ^RECT, + pBlendFunction: ^BLENDFUNCTION, }