412 lines
10 KiB
V
412 lines
10 KiB
V
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
@[has_globals]
|
|
module builtin
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
// Cast the V callback to the Windows SDK callback type. Clang 20 treats the
|
|
// otherwise-compatible struct pointer mismatch as a hard error for v_win.c.
|
|
#define v_set_unhandled_exception_filter(handler) SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)(handler))
|
|
|
|
// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
|
// See https://www.codeproject.com/KB/string/cppstringguide1.aspx
|
|
pub type C.BOOL = int
|
|
pub type C.LONG = int
|
|
|
|
pub type C.HINSTANCE = voidptr
|
|
|
|
pub type C.HICON = voidptr
|
|
|
|
pub type C.HCURSOR = voidptr
|
|
|
|
pub type C.HBRUSH = voidptr
|
|
|
|
pub type C.HWND = voidptr
|
|
|
|
pub type C.HGLOBAL = voidptr
|
|
|
|
pub type C.HANDLE = voidptr
|
|
|
|
pub type C.LRESULT = voidptr
|
|
|
|
pub type C.CHAR = char
|
|
|
|
pub type C.TCHAR = u16 // It is u8 if UNICODE is not defined, but for V programs it always is
|
|
|
|
pub type C.WCHAR = u16
|
|
|
|
pub type C.LPSTR = &char
|
|
|
|
pub type C.LPWSTR = &C.WCHAR
|
|
|
|
pub type C.LPTSTR = &C.TCHAR
|
|
|
|
pub type C.LPCTSTR = &C.TCHAR
|
|
|
|
fn C.WriteConsoleW(voidptr, &u16, u32, &u32, voidptr) bool
|
|
fn C.WriteFile(voidptr, &u8, u32, &u32, voidptr) bool
|
|
fn C.ExitProcess(u32)
|
|
fn C.GetProcessHeap() voidptr
|
|
fn C.HeapAlloc(voidptr, u32, usize) voidptr
|
|
fn C.HeapFree(voidptr, u32, voidptr) bool
|
|
|
|
fn C._setmode(int, int) int
|
|
|
|
// set_stream_binary_mode disables CRT newline translation for redirected stdio streams.
|
|
fn set_stream_binary_mode(stream &C.FILE) {
|
|
fd := C._fileno(stream)
|
|
if fd >= 0 {
|
|
C._setmode(fd, C._O_BINARY)
|
|
}
|
|
}
|
|
|
|
fn is_terminal(fd int) int {
|
|
mut mode := u32(0)
|
|
$if v2_native_windows_pe_minimal ? {
|
|
if fd != 0 && fd != 1 && fd != 2 {
|
|
return 0
|
|
}
|
|
handle_id := if fd == 0 {
|
|
std_input_handle
|
|
} else if fd == 2 {
|
|
std_error_handle
|
|
} else {
|
|
std_output_handle
|
|
}
|
|
handle := C.GetStdHandle(handle_id)
|
|
if isnil(handle) || handle == voidptr(-1) {
|
|
return 0
|
|
}
|
|
if !C.GetConsoleMode(handle, &mode) {
|
|
return 0
|
|
}
|
|
return int(mode)
|
|
} $else {
|
|
osfh := voidptr(C._get_osfhandle(fd))
|
|
C.GetConsoleMode(osfh, voidptr(&mode))
|
|
return int(mode)
|
|
}
|
|
}
|
|
|
|
// GetStdHandle takes DWORD values. Microsoft defines these as ((DWORD)-N),
|
|
// so spell the rollover values explicitly for native backends.
|
|
const std_input_handle = u32(0xfffffff6)
|
|
const std_output_handle = u32(0xfffffff5)
|
|
const std_error_handle = u32(0xfffffff4)
|
|
const enable_processed_output = 1
|
|
const enable_wrap_at_eol_output = 2
|
|
const evable_virtual_terminal_processing = 4
|
|
|
|
// Write UTF-8 directly as UTF-16 for console hosts instead of changing the console code page.
|
|
@[manualfree]
|
|
fn write_buf_to_console(fd int, buf &u8, buf_len int) bool {
|
|
if buf_len <= 0 || is_terminal(fd) <= 0 {
|
|
return false
|
|
}
|
|
mut console_handle := C.GetStdHandle(std_output_handle)
|
|
if fd == 2 {
|
|
console_handle = C.GetStdHandle(std_error_handle)
|
|
}
|
|
if isnil(console_handle) || console_handle == voidptr(-1) {
|
|
return false
|
|
}
|
|
unsafe {
|
|
wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
|
|
if wide_len <= 0 {
|
|
return false
|
|
}
|
|
mut wide_buf := &u16(malloc_noscan((wide_len + 1) * int(sizeof(u16))))
|
|
if isnil(wide_buf) {
|
|
return false
|
|
}
|
|
defer {
|
|
free(wide_buf)
|
|
}
|
|
converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
|
|
if converted <= 0 {
|
|
return false
|
|
}
|
|
wide_buf[converted] = 0
|
|
mut remaining_chars := converted
|
|
mut wide_ptr := wide_buf
|
|
for remaining_chars > 0 {
|
|
mut chars_written := u32(0)
|
|
if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
|
|
|| chars_written == 0 {
|
|
return false
|
|
}
|
|
wide_ptr += int(chars_written)
|
|
remaining_chars -= int(chars_written)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
@[manualfree]
|
|
fn write_buf_to_console_kernel32(fd int, buf &u8, buf_len int) bool {
|
|
if buf_len <= 0 || (fd != 1 && fd != 2) {
|
|
return false
|
|
}
|
|
console_handle := C.GetStdHandle(if fd == 2 { std_error_handle } else { std_output_handle })
|
|
if isnil(console_handle) || console_handle == voidptr(-1) {
|
|
return false
|
|
}
|
|
mut mode := u32(0)
|
|
if !C.GetConsoleMode(console_handle, &mode) {
|
|
return false
|
|
}
|
|
unsafe {
|
|
wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
|
|
if wide_len <= 0 {
|
|
return false
|
|
}
|
|
heap := C.GetProcessHeap()
|
|
if isnil(heap) {
|
|
return false
|
|
}
|
|
wide_size := usize((wide_len + 1) * int(sizeof(u16)))
|
|
mut wide_buf := &u16(C.HeapAlloc(heap, 0, wide_size))
|
|
if isnil(wide_buf) {
|
|
return false
|
|
}
|
|
defer {
|
|
C.HeapFree(heap, 0, wide_buf)
|
|
}
|
|
converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
|
|
if converted <= 0 {
|
|
return false
|
|
}
|
|
wide_buf[converted] = 0
|
|
mut remaining_chars := converted
|
|
mut wide_ptr := wide_buf
|
|
for remaining_chars > 0 {
|
|
mut chars_written := u32(0)
|
|
if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
|
|
|| chars_written == 0 {
|
|
return false
|
|
}
|
|
wide_ptr += int(chars_written)
|
|
remaining_chars -= int(chars_written)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
fn write_buf_to_fd_kernel32(fd int, buf &u8, buf_len int) bool {
|
|
return write_buf_to_fd_kernel32_status(fd, buf, buf_len) == 0
|
|
}
|
|
|
|
fn write_buf_to_fd_kernel32_status(fd int, buf &u8, buf_len int) int {
|
|
if buf_len <= 0 {
|
|
return 0
|
|
}
|
|
if fd != 1 && fd != 2 {
|
|
return 1
|
|
}
|
|
if write_buf_to_console_kernel32(fd, buf, buf_len) {
|
|
return 0
|
|
}
|
|
handle_id := if fd == 2 { std_error_handle } else { std_output_handle }
|
|
handle := C.GetStdHandle(handle_id)
|
|
if isnil(handle) || handle == voidptr(-1) {
|
|
return 2
|
|
}
|
|
mut ptr := unsafe { buf }
|
|
mut remaining_bytes := buf_len
|
|
unsafe {
|
|
for remaining_bytes > 0 {
|
|
chunk := if remaining_bytes > int(0x7fffffff) {
|
|
int(0x7fffffff)
|
|
} else {
|
|
remaining_bytes
|
|
}
|
|
mut written := u32(0)
|
|
if !C.WriteFile(handle, ptr, u32(chunk), &written, nil) {
|
|
return 3
|
|
}
|
|
if written == 0 {
|
|
return 4
|
|
}
|
|
ptr += int(written)
|
|
remaining_bytes -= int(written)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
@[manualfree]
|
|
fn write_buf_to_fd_windows_non_minimal(fd int, buf &u8, buf_len int) {
|
|
if buf_len <= 0 {
|
|
return
|
|
}
|
|
mut ptr := unsafe { buf }
|
|
mut remaining_bytes := isize(buf_len)
|
|
if write_buf_to_console(fd, ptr, int(remaining_bytes)) {
|
|
return
|
|
}
|
|
mut stream := voidptr(C.stdout)
|
|
if fd == 2 {
|
|
stream = voidptr(C.stderr)
|
|
}
|
|
mut x := isize(0)
|
|
unsafe {
|
|
for remaining_bytes > 0 {
|
|
x = isize(C.fwrite(ptr, 1, remaining_bytes, stream))
|
|
if x <= 0 {
|
|
// GUI programs on Windows may not have a writable stdout/stderr stream.
|
|
break
|
|
}
|
|
ptr += x
|
|
remaining_bytes -= x
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_buf_to_fd_kernel32_or_exit(fd int, buf &u8, buf_len int) {
|
|
write_status := write_buf_to_fd_kernel32_status(fd, buf, buf_len)
|
|
if write_status != 0 {
|
|
C.ExitProcess(u32(220 + write_status))
|
|
}
|
|
}
|
|
|
|
@[markused]
|
|
fn builtin_init() {
|
|
$if v2_native_windows_pe_minimal ? {
|
|
return
|
|
} $else {
|
|
$if gcboehm ? {
|
|
$if !gc_warn_on_stderr ? {
|
|
gc_set_warn_proc(internal_gc_warn_proc_none)
|
|
}
|
|
}
|
|
set_stream_binary_mode(C.stdout)
|
|
set_stream_binary_mode(C.stderr)
|
|
if is_terminal(1) > 0 {
|
|
C.SetConsoleMode(C.GetStdHandle(std_output_handle),
|
|
enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
|
|
C.SetConsoleMode(C.GetStdHandle(std_error_handle),
|
|
enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
|
|
unsafe {
|
|
set_stream_unbuffered(C.stdout)
|
|
set_stream_unbuffered(C.stderr)
|
|
}
|
|
}
|
|
$if !no_backtrace ? {
|
|
add_unhandled_exception_handler()
|
|
}
|
|
// On windows, the default buffering is block based (~4096bytes), which interferes badly with non cmd shells
|
|
// It is much better to have it off by default instead.
|
|
unbuffer_stdout()
|
|
}
|
|
}
|
|
|
|
// TODO: copypaste from os
|
|
// we want to be able to use this here without having to `import os`
|
|
struct ExceptionRecord {
|
|
pub:
|
|
// status_ constants
|
|
code u32
|
|
flags u32
|
|
record &ExceptionRecord = unsafe { nil }
|
|
address voidptr
|
|
param_count u32
|
|
// params []voidptr
|
|
}
|
|
|
|
struct ContextRecord {
|
|
// TODO
|
|
}
|
|
|
|
struct ExceptionPointers {
|
|
pub:
|
|
exception_record &ExceptionRecord = unsafe { nil }
|
|
context_record &ContextRecord = unsafe { nil }
|
|
}
|
|
|
|
@[callconv: stdcall]
|
|
type TopLevelExceptionFilter = fn (&ExceptionPointers) C.LONG
|
|
|
|
fn C.SetUnhandledExceptionFilter(TopLevelExceptionFilter) voidptr
|
|
fn C.v_set_unhandled_exception_filter(TopLevelExceptionFilter) voidptr
|
|
|
|
@[callconv: stdcall]
|
|
fn unhandled_exception_handler(e &ExceptionPointers) C.LONG {
|
|
match e.exception_record.code {
|
|
// These are 'used' by the backtrace printer
|
|
// so we dont want to catch them...
|
|
0x4001000A, 0x40010006, 0x406D1388, 0xE06D7363 {
|
|
return 0
|
|
}
|
|
else {
|
|
eprintln('Unhandled Exception 0x' + ptr_str(e.exception_record.code) + ' at ' +
|
|
ptr_str(e.exception_record.address))
|
|
flush_stdout()
|
|
flush_stderr()
|
|
print_backtrace_skipping_top_frames(5)
|
|
flush_stdout()
|
|
flush_stderr()
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
fn add_unhandled_exception_handler() {
|
|
// A vectored handler also sees first-chance exceptions that Windows APIs may
|
|
// handle internally, which can lead to false-positive "Unhandled Exception"
|
|
// reports. Register a top-level filter instead.
|
|
C.v_set_unhandled_exception_filter(unhandled_exception_handler)
|
|
}
|
|
|
|
fn C.IsDebuggerPresent() bool
|
|
|
|
fn C.__debugbreak()
|
|
|
|
fn break_if_debugger_attached() {
|
|
$if tinyc {
|
|
unsafe {
|
|
mut ptr := &voidptr(0)
|
|
*ptr = nil
|
|
_ = ptr
|
|
}
|
|
} $else {
|
|
if C.IsDebuggerPresent() {
|
|
C.__debugbreak()
|
|
}
|
|
}
|
|
}
|
|
|
|
const format_message_allocate_buffer = 0x00000100
|
|
const format_message_argument_array = 0x00002000
|
|
const format_message_from_hmodule = 0x00000800
|
|
const format_message_from_string = 0x00000400
|
|
const format_message_from_system = 0x00001000
|
|
const format_message_ignore_inserts = 0x00000200
|
|
|
|
// return an error message generated from WinAPI's `LastError`
|
|
pub fn winapi_lasterr_str() string {
|
|
err_msg_id := C.GetLastError()
|
|
if err_msg_id == 8 {
|
|
// handle this case special since `FormatMessageW()` might not work anymore
|
|
return 'insufficient memory'
|
|
}
|
|
mut msgbuf := &u16(unsafe { nil })
|
|
res := C.FormatMessageW(format_message_allocate_buffer | format_message_from_system | format_message_ignore_inserts,
|
|
0, err_msg_id, 0, voidptr(&msgbuf), 0, 0)
|
|
err_msg := if res == 0 {
|
|
'Win-API error ${err_msg_id}'
|
|
} else {
|
|
unsafe { string_from_wide(msgbuf) }
|
|
}
|
|
return err_msg
|
|
}
|
|
|
|
// panic with an error message generated from WinAPI's `LastError`
|
|
@[noreturn]
|
|
pub fn panic_lasterr(base string) {
|
|
panic(base + winapi_lasterr_str())
|
|
}
|