diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a865dfa9b..544634162 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,7 +94,9 @@ jobs: uses: actions/upload-artifact@v3 with: name: c3-windows-${{ matrix.build_type }} - path: build\${{ matrix.build_type }}\c3c.exe + path: | + build\${{ matrix.build_type }}\c3c.exe + build\${{ matrix.build_type }}\c3c_rt build-msys2-mingw: runs-on: windows-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 37066d6d8..5ee625040 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,12 +116,14 @@ if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") if(CMAKE_BUILD_TYPE STREQUAL "Debug") message("Loading Windows LLVM debug libraries, this may take a while...") FetchContent_MakeAvailable(LLVM_Windows_debug) - set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_debug_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH}) + set(llvm_dir ${llvm_windows_debug_SOURCE_DIR}) else() message("Loading Windows LLVM libraries, this may take a while...") FetchContent_MakeAvailable(LLVM_Windows) - set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_windows_SOURCE_DIR} ${CMAKE_SYSTEM_PREFIX_PATH}) + set(llvm_dir ${llvm_windows_SOURCE_DIR}) endif() + set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH}) + find_package(LLVM REQUIRED CONFIG) find_package(LLD REQUIRED CONFIG) else() @@ -146,6 +148,9 @@ else() message(STATUS "LLVM was not built with RTTI") endif() +string(REPLACE "." ";" VERSION_LIST ${LLVM_PACKAGE_VERSION}) +list(GET VERSION_LIST 0 LLVM_MAJOR_VERSION) + include_directories(${LLVM_INCLUDE_DIRS}) link_directories(${LLVM_LIBRARY_DIRS}) add_definitions(${LLVM_DEFINITIONS}) @@ -210,30 +215,30 @@ if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR})) file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR}) endif() -if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16) - find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH) - set(lld_libs - ${LLD_COFF} - ${LLD_COMMON} - ${LLD_WASM} - ${LLD_MINGW} - ${LLD_ELF} - ${LLD_MACHO} - ) -else() - set(lld_libs - ${LLD_COFF} - ${LLD_COMMON} - ${LLD_WASM} - ${LLD_MINGW} - ${LLD_ELF} - ${LLD_MACHO} - ) -endif() +find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH) +set(lld_libs + ${LLD_COFF} + ${LLD_COMMON} + ${LLD_WASM} + ${LLD_MINGW} + ${LLD_ELF} + ${LLD_MACHO} + ) if (APPLE) set(lld_libs ${lld_libs} xar) -endif () + find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIRS}/clang/${LLVM_MAJOR_VERSION}/lib/darwin") + find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIRS}/clang/${LLVM_MAJOR_VERSION}/lib/darwin") + find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIRS}/clang/${LLVM_MAJOR_VERSION}/lib/darwin") + find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIRS}/clang/${LLVM_MAJOR_VERSION}/lib/darwin") + set(sanitizer_runtime_libraries + ${RT_ASAN_DYNAMIC} + ${RT_TSAN_DYNAMIC} + # Unused + # ${RT_UBSAN_DYNAMIC} + # ${RT_LSAN_DYNAMIC} + ) +endif() message(STATUS "linking to llvm libs ${lld_libs}") message(STATUS "Found lld libs ${lld_libs}") @@ -424,6 +429,12 @@ if(MSVC) target_compile_options(tilde-backend PUBLIC /MT) endif() endif() + set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows) + set(sanitizer_runtime_libraries + ${clang_lib_dir}/clang_rt.asan-x86_64.lib + ${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib + ${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll + ${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib) else() message(STATUS "using gcc/clang warning switches") target_link_options(c3c PRIVATE -pthread) @@ -434,8 +445,25 @@ else() -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) endif() - install(TARGETS c3c DESTINATION bin) install(DIRECTORY lib/ DESTINATION lib/c3) +if (DEFINED sanitizer_runtime_libraries) + add_custom_command(TARGET c3c POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $/c3c_rt + COMMAND "${CMAKE_COMMAND}" -E make_directory $/c3c_rt + COMMAND "${CMAKE_COMMAND}" -E copy ${sanitizer_runtime_libraries} $/c3c_rt + VERBATIM + COMMENT "Copying sanitizer runtime libraries to output directory") + + if (APPLE) + # Change LC_ID_DYLIB to be rpath-based instead of having an absolute path + add_custom_command(TARGET c3c POST_BUILD + COMMAND find $/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $ + VERBATIM) + endif() + + install(DIRECTORY $/c3c_rt/ DESTINATION bin/c3c_rt) +endif() + feature_summary(WHAT ALL) diff --git a/lib/std/collections/list.c3 b/lib/std/collections/list.c3 index ca228408a..f32fdc1f3 100644 --- a/lib/std/collections/list.c3 +++ b/lib/std/collections/list.c3 @@ -27,20 +27,9 @@ fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = a { self.allocator = allocator; self.size = 0; - if (initial_capacity > 0) - { - initial_capacity = math::next_power_of_2(initial_capacity); - $if type_is_overaligned(): - self.entries = allocator::malloc_aligned(allocator, Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!; - $else - self.entries = allocator::malloc(allocator, Type.sizeof * initial_capacity); - $endif - } - else - { - self.entries = null; - } - self.capacity = initial_capacity; + self.capacity = 0; + self.entries = null; + self.reserve(initial_capacity); return self; } @@ -81,14 +70,14 @@ fn List* List.temp_init_with_array(&self, Type[] values) } /** - * @require self.size == 0 "The List must be empty" + * @require self.capacity == 0 "The List must not be allocated" **/ fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap()) { self.allocator = allocator; - self.size = types.len; self.capacity = types.len; self.entries = types.ptr; + self.set_size(types.len); } fn usz! List.to_format(&self, Formatter* formatter) @dynamic @@ -124,18 +113,19 @@ fn String List.to_tstring(&self) fn void List.push(&self, Type element) @inline { self.reserve(1); - self.entries[self.size++] = element; + self.entries[self.set_size(self.size + 1)] = element; } fn Type! List.pop(&self) { if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; - return self.entries[--self.size]; + defer self.set_size(self.size - 1); + return self.entries[self.size - 1]; } fn void List.clear(&self) { - self.size = 0; + self.set_size(0); } /** @@ -153,7 +143,8 @@ fn Type! List.pop_first(&self) **/ fn void List.remove_at(&self, usz index) { - if (!--self.size || index == self.size) return; + self.set_size(self.size - 1); + if (!self.size || index == self.size) return; self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size]; } @@ -161,9 +152,10 @@ fn void List.add_all(&self, List* other_list) { if (!other_list.size) return; self.reserve(other_list.size); + usz index = self.set_size(self.size + other_list.size); foreach (&value : other_list) { - self.entries[self.size++] = *value; + self.entries[index++] = *value; } } @@ -216,8 +208,8 @@ fn void List.add_array(&self, Type[] array) { if (!array.len) return; self.reserve(array.len); - self.entries[self.size : array.len] = array[..]; - self.size += array.len; + usz index = self.set_size(self.size + array.len); + self.entries[index : array.len] = array[..]; } fn void List.push_front(&self, Type type) @inline @@ -235,7 +227,7 @@ fn void List.insert_at(&self, usz index, Type type) { self.entries[i] = self.entries[i - 1]; } - self.size++; + self.set_size(self.size + 1); self.entries[index] = type; } @@ -250,7 +242,7 @@ fn void List.set_at(&self, usz index, Type type) fn void! List.remove_last(&self) @maydiscard { if (!self.size) return IteratorResult.NO_MORE_ELEMENT?; - self.size--; + self.set_size(self.size - 1); } fn void! List.remove_first(&self) @maydiscard @@ -293,11 +285,14 @@ fn Type List.get(&self, usz index) @inline fn void List.free(&self) { - if (!self.allocator) return; + if (!self.allocator || !self.capacity) return; + + self.pre_free(); // Remove sanitizer annotation + $if type_is_overaligned(): - allocator::free_aligned(self.allocator, self.entries); + allocator::free_aligned(self.allocator, self.entries); $else - allocator::free(self.allocator, self.entries); + allocator::free(self.allocator, self.entries); $endif; self.capacity = 0; self.size = 0; @@ -329,11 +324,21 @@ fn usz List.retain_if(&self, ElementPredicate selection) fn usz List.remove_using_test(&self, ElementTest filter, any context) { + usz old_size = self.size; + defer { + if (old_size != self.size) self._update_size_change(old_size, self.size); + } return list_common::list_remove_using_test(self, filter, false, context); } + + fn usz List.retain_using_test(&self, ElementTest filter, any context) { + usz old_size = self.size; + defer { + if (old_size != self.size) self._update_size_change(old_size, self.size); + } return list_common::list_remove_using_test(self, filter, true, context); } @@ -342,13 +347,18 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local if (!min_capacity) return; if (self.capacity >= min_capacity) return; if (!self.allocator) self.allocator = allocator::heap(); + + self.pre_free(); // Remove sanitizer annotation + min_capacity = math::next_power_of_2(min_capacity); $if type_is_overaligned(): - self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof)!!; + self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof)!!; $else - self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity); + self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity); $endif; self.capacity = min_capacity; + + self.post_alloc(); // Add sanitizer annotation } macro Type List.@item_at(&self, usz index) @operator([]) @@ -377,6 +387,39 @@ fn void List.reserve(&self, usz added) self.ensure_capacity(new_capacity); } +fn void List._update_size_change(&self,usz old_size, usz new_size) +{ + if (old_size == new_size) return; + sanitizer::annotate_contiguous_container(self.entries, + &self.entries[self.capacity], + &self.entries[old_size], + &self.entries[new_size]); +} +/** + * @require new_size == 0 || self.capacity != 0 + **/ +fn usz List.set_size(&self, usz new_size) @inline @private +{ + usz old_size = self.size; + self._update_size_change(old_size, new_size); + self.size = new_size; + return old_size; +} + +macro void List.pre_free(&self) @private +{ + if (!self.capacity) return; + self._update_size_change(self.size, self.capacity); +} + +/** + * @require self.capacity + **/ +macro void List.post_alloc(&self) @private +{ + self._update_size_change(self.capacity, self.size); +} + // Functions for equatable types @@ -443,7 +486,6 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) { return @ok(self.remove_at(self.index_of(value))); } - /** * @param [&inout] self "The list to remove elements from" * @param value "The value to remove" @@ -451,6 +493,10 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) **/ fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) { + usz old_size = self.size; + defer { + if (old_size != self.size) self._update_size_change(old_size, self.size); + } return list_common::list_remove_item(self, value); } @@ -459,6 +505,10 @@ fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE) { if (!other_list.size) return; + usz old_size = self.size; + defer { + if (old_size != self.size) self._update_size_change(old_size, self.size); + } foreach (v : other_list) self.remove_item(v); } @@ -475,6 +525,10 @@ fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER) fn usz List.compact(&self) @if(ELEMENT_IS_POINTER) { + usz old_size = self.size; + defer { + if (old_size != self.size) self._update_size_change(old_size, self.size); + } return list_common::list_compact(self); } diff --git a/lib/std/core/allocators/temp_allocator.c3 b/lib/std/core/allocators/temp_allocator.c3 index eee505931..9d211a5cd 100644 --- a/lib/std/core/allocators/temp_allocator.c3 +++ b/lib/std/core/allocators/temp_allocator.c3 @@ -60,21 +60,12 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic if (old_pointer + old_size == &self.data[self.used]) { self.used -= old_size; + asan::poison_memory_region(&self.data[self.used], old_size); } } fn void TempAllocator.reset(&self, usz mark) @dynamic { TempAllocatorPage *last_page = self.last_page; - $if env::COMPILER_SAFE_MODE: - if (!last_page) - { - usz cleaned = self.used - mark; - if (cleaned > 0) - { - self.data[mark : cleaned] = 0xAA; - } - } - $endif while (last_page && last_page.mark > mark) { TempAllocatorPage *to_free = last_page; @@ -82,6 +73,19 @@ fn void TempAllocator.reset(&self, usz mark) @dynamic self._free_page(to_free)!!; } self.last_page = last_page; + $if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER: + if (!last_page) + { + usz cleaned = self.used - mark; + if (cleaned > 0) + { + $if env::COMPILER_SAFE_MODE: + self.data[mark : cleaned] = 0xAA; + $endif + asan::poison_memory_region(&self.data[mark], cleaned); + } + } + $endif self.used = mark; } @@ -147,9 +151,10 @@ fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al } usz new_usage = (usz)(mem - start_mem) + size; - // Arena alignment, simple! + // Arena allocation, simple! if (new_usage <= self.capacity) { + asan::unpoison_memory_region(starting_ptr, new_usage - self.used); TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof; chunk_start.size = size; self.used = new_usage; diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index bb506f6c6..73d0617fc 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -19,6 +19,8 @@ fault SearchResult { MISSING } **/ fault CastResult { TYPE_MISMATCH } +def VoidFn = fn void(); + /** * Stores a variable on the stack, then restores it at the end of the * macro scope. diff --git a/lib/std/core/env.c3 b/lib/std/core/env.c3 index 7d4deb9b8..05b32da08 100644 --- a/lib/std/core/env.c3 +++ b/lib/std/core/env.c3 @@ -148,6 +148,10 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD; const bool NETBSD = LIBC && OS_TYPE == NETBSD; const bool WASI = LIBC && OS_TYPE == WASI; const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64; +const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER; +const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER; +const bool THREAD_SANITIZER = $$THREAD_SANITIZER; +const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER; macro bool os_is_darwin() @const { diff --git a/lib/std/core/sanitizer/asan.c3 b/lib/std/core/sanitizer/asan.c3 new file mode 100644 index 000000000..118235d38 --- /dev/null +++ b/lib/std/core/sanitizer/asan.c3 @@ -0,0 +1,127 @@ +// Add this to your code to suppress leak detection or set other default options +// fn ZString __asan_default_options() @export("__asan_default_options") @if(env::ADDRESS_SANITIZER) +// { +// return "detect_leaks=0"; +// } + +// Add this to break on error +// asan::set_error_report_callback(fn (ZString err) +// { +// breakpoint(); +// }); + +module std::core::sanitizer::asan; + +def ErrorCallback = fn void (ZString); + +/** + * Marks a memory region ([addr, addr+size)) as unaddressable. + * + * This memory must be previously allocated by your program. Instrumented + * code is forbidden from accessing addresses in this region until it is + * unpoisoned. This function is not guaranteed to poison the entire region - + * it could poison only a subregion of [addr, addr+size) due to ASan + * alignment restrictions. + * + * NOTE This function is not thread-safe because no two threads can poison or + * unpoison memory in the same memory region simultaneously. + * + * @param addr "Start of memory region." + * @param size "Size of memory region." + **/ +macro poison_memory_region(void* addr, usz size) +{ + $if env::ADDRESS_SANITIZER: + __asan_poison_memory_region(addr, size); + $endif +} + +/** + * Marks a memory region ([addr, addr+size)) as addressable. + * + * This memory must be previously allocated by your program. Accessing + * addresses in this region is allowed until this region is poisoned again. + * This function could unpoison a super-region of [addr, addr+size) due + * to ASan alignment restrictions. + * + * NOTE This function is not thread-safe because no two threads can + * poison or unpoison memory in the same memory region simultaneously. + * + * @param addr "Start of memory region." + * @param size "Size of memory region." + **/ +macro unpoison_memory_region(void* addr, usz size) +{ + $if env::ADDRESS_SANITIZER: + __asan_unpoison_memory_region(addr, size); + $endif +} + +/** + * Checks if an address is poisoned. + * @return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false." + * @param addr "Address to check." + **/ +macro bool address_is_poisoned(void* addr) +{ + $if env::ADDRESS_SANITIZER: + return (bool)__asan_address_is_poisoned(addr); + $else + return false; + $endif +} + +/** + * Checks if a region is poisoned. + * + * If at least one byte in [beg, beg+size) is poisoned, returns the + * address of the first such byte. Otherwise returns 0. + * + * @param beg "Start of memory region." + * @param size "Start of memory region." + * @return "Address of first poisoned byte." + **/ +macro void* region_is_poisoned(void* beg, usz size) +{ + $if env::ADDRESS_SANITIZER: + return __asan_region_is_poisoned(addr); + $else + return null; + $endif +} + +/** + * Sets the callback function to be called during ASan error reporting. + **/ +fn void set_error_report_callback(ErrorCallback callback) +{ + $if env::ADDRESS_SANITIZER: + __asan_set_error_report_callback(callback); + $endif +} + +module std::core::sanitizer::asan @if(env::ADDRESS_SANITIZER); + +extern fn void __asan_poison_memory_region(void* addr, usz size); +extern fn void __asan_unpoison_memory_region(void* addr, usz size); +extern fn CInt __asan_address_is_poisoned(void* addr); +extern fn void* __asan_region_is_poisoned(void* beg, usz size); +extern fn void __asan_describe_address(void* addr); +extern fn CInt __asan_report_present(); +extern fn void* __asan_get_report_pc(); +extern fn void* __asan_get_report_bp(); +extern fn void* __asan_get_report_sp(); +extern fn void* __asan_get_report_address(); +extern fn CInt __asan_get_report_access_type(); +extern fn usz __asan_get_report_access_size(); +extern fn ZString __asan_get_report_description(); +extern fn ZString __asan_locate_address(void* addr, char* name, usz name_size, void** region_address, usz* region_size); +extern fn usz __asan_get_alloc_stack(void* addr, void** trace, usz size, CInt* thread_id); +extern fn usz __asan_get_free_stack(void* addr, void** trace, usz size, CInt* thread_id); +extern fn void __asan_get_shadow_mapping(usz* shadow_scale, usz* shadow_offset); +extern fn void __asan_set_error_report_callback(ErrorCallback callback); +extern fn void __asan_print_accumulated_stats(); +extern fn void* __asan_get_current_fake_stack(); +extern fn void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg, void** end); +extern fn void __asan_handle_no_return(); +extern fn CInt __asan_update_allocation_context(void* addr); diff --git a/lib/std/core/sanitizer/sanitizer.c3 b/lib/std/core/sanitizer/sanitizer.c3 new file mode 100644 index 000000000..6e253868b --- /dev/null +++ b/lib/std/core/sanitizer/sanitizer.c3 @@ -0,0 +1,80 @@ +module std::core::sanitizer; + +macro void annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid) +{ + $if env::ADDRESS_SANITIZER: + __sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid); + $endif +} + +macro void annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end, void* old_container_beg, void* old_container_end, void* new_container_beg, void* new_container_end) +{ + $if env::ADDRESS_SANITIZER: + __sanitizer_annotate_double_ended_contiguous_container(storage_beg, storage_end, old_container_beg, old_container_end, new_container_beg, new_container_end); + $endif +} + +macro void print_stack_trace() +{ + $if env::ADDRESS_SANITIZER: + __sanitizer_print_stack_trace(); + $endif +} + +fn void set_death_callback(VoidFn callback) +{ + $if env::ANY_SANITIZER: + __sanitizer_set_death_callback(callback); + $endif +} + +module std::core::sanitizer @if (env::ANY_SANITIZER); + +struct __Sanitizer_sandbox_arguments +{ + CInt coverage_sandboxed; + iptr coverage_fd; + CUInt coverage_max_block_size; +} + +extern fn void __sanitizer_set_report_path(ZString path); +extern fn void __sanitizer_set_report_fd(void* fd); +extern fn ZString __sanitizer_get_report_path(); +extern fn void __sanitizer_sandbox_on_notify(__Sanitizer_sandbox_arguments* args); +extern fn void __sanitizer_report_error_summary(ZString error_summary); +extern fn ushort __sanitizer_unaligned_load16(void* p); +extern fn uint __sanitizer_unaligned_load32(void* p); +extern fn ulong __sanitizer_unaligned_load64(void* p); +extern fn void __sanitizer_unaligned_store16(void* p, ushort x); +extern fn void __sanitizer_unaligned_store32(void* p, uint x); +extern fn void __sanitizer_unaligned_store64(void* p, ulong x); +extern fn CInt __sanitizer_acquire_crash_state(); +extern fn void __sanitizer_annotate_contiguous_container(void* beg, void* end, void* old_mid, void* new_mid); +extern fn void __sanitizer_annotate_double_ended_contiguous_container(void* storage_beg, void* storage_end, + void* old_container_beg, void* old_container_end, + void* new_container_beg, void* new_container_end); +extern fn CInt __sanitizer_verify_contiguous_container(void* beg, void* mid, void* end); +extern fn CInt __sanitizer_verify_double_ended_contiguous_container( + void* storage_beg, void* container_beg, + void* container_end, void* storage_end); +extern fn void* __sanitizer_contiguous_container_find_bad_address(void* beg, void* mid, void* end); +extern fn void* __sanitizer_double_ended_contiguous_container_find_bad_address( + void* storage_beg, void* container_beg, + void* container_end, void* storage_end); + +extern fn void __sanitizer_print_stack_trace(); +extern fn void __sanitizer_symbolize_pc(void* pc, ZString fmt, char* out_buf, usz out_buf_size); +extern fn void __sanitizer_symbolize_global(void* data_ptr, ZString fmt, char* out_buf, usz out_buf_size); +extern fn void __sanitizer_set_death_callback(VoidFn callback); +extern fn void __sanitizer_weak_hook_memcmp(void* called_pc, void* s1, void* s2, usz n, CInt result); +extern fn void __sanitizer_weak_hook_strncmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result); +extern fn void __sanitizer_weak_hook_strncasecmp(void* called_pc, ZString s1, ZString s2, usz n, CInt result); +extern fn void __sanitizer_weak_hook_strcmp(void* called_pc, ZString s1, ZString s2, CInt result); +extern fn void __sanitizer_weak_hook_strcasecmp(void* called_pc, ZString s1, ZString s2, CInt result); +extern fn void __sanitizer_weak_hook_strstr(void* called_pc, ZString s1, ZString s2, char* result); +extern fn void __sanitizer_weak_hook_strcasestr(void* called_pc, ZString s1, ZString s2, char* result); +extern fn void __sanitizer_weak_hook_memmem(void* called_pc, void* s1, usz len1, void* s2, usz len2, void* result); +extern fn void __sanitizer_print_memory_profile(usz top_percent, usz max_number_of_contexts); +extern fn void __sanitizer_start_switch_fiber(void** fake_stack_save, void* bottom, usz size); +extern fn void __sanitizer_finish_switch_fiber(void* fake_stack_save, void** bottom_old, usz* size_old); +extern fn CInt __sanitizer_get_module_and_offset_for_pc(void* pc, char* module_path, usz module_path_len, void** pc_offset); diff --git a/lib/std/core/sanitizer/tsan.c3 b/lib/std/core/sanitizer/tsan.c3 new file mode 100644 index 000000000..b9bf3da7b --- /dev/null +++ b/lib/std/core/sanitizer/tsan.c3 @@ -0,0 +1,39 @@ +module std::core::sanitizer::tsan; + +distinct MutexFlags = inline CUInt; + +const MutexFlags MUTEX_LINKER_INIT = 1 << 0; +const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1; +const MutexFlags MUTEX_READ_REENTRANT = 1 << 2; +const MutexFlags MUTEX_NOT_STATIC = 1 << 8; +const MutexFlags MUTEX_READ_LOCK = 1 << 3; +const MutexFlags MUTEX_TRY_LOCK = 1 << 4; +const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5; +const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6; +const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7; +const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK; +const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED; + +macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif } +macro void mutex_destroy(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_destroy(addr, flags); $endif } +macro void mutex_pre_lock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_lock(addr, flags); $endif } +macro void mutex_post_lock(void* addr, MutexFlags flags, CInt recursion) { $if env::THREAD_SANITIZER: __tsan_mutex_post_lock(addr, flags, recursion); $endif } +macro CInt mutex_pre_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: return __tsan_mutex_pre_unlock(addr, flags); $else return 0; $endif } +macro void mutex_post_unlock(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_unlock(addr, flags); $endif } +macro void mutex_pre_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_signal(addr, flags); $endif } +macro void mutex_post_signal(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_signal(addr, flags); $endif } +macro void mutex_pre_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_pre_divert(addr, flags); $endif } +macro void mutex_post_divert(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_post_divert(addr, flags); $endif } + +module std::core::sanitizer::tsan @if(env::THREAD_SANITIZER) @private; + +extern fn void __tsan_mutex_create(void* addr, CUInt flags); +extern fn void __tsan_mutex_destroy(void* addr, CUInt flags); +extern fn void __tsan_mutex_pre_lock(void* addr, CUInt flags); +extern fn void __tsan_mutex_post_lock(void* addr, CUInt flags, CInt recursion); +extern fn CInt __tsan_mutex_pre_unlock(void* addr, CUInt flags); +extern fn void __tsan_mutex_post_unlock(void* addr, CUInt flags); +extern fn void __tsan_mutex_pre_signal(void* addr, CUInt flags); +extern fn void __tsan_mutex_post_signal(void* addr, CUInt flags); +extern fn void __tsan_mutex_pre_divert(void* addr, CUInt flags); +extern fn void __tsan_mutex_post_divert(void* addr, CUInt flags); diff --git a/src/build/build.h b/src/build/build.h index 053ed5ddb..6b84d4731 100644 --- a/src/build/build.h +++ b/src/build/build.h @@ -291,7 +291,9 @@ typedef enum WIN_CRT_DEFAULT = -1, WIN_CRT_NONE = 0, WIN_CRT_DYNAMIC = 1, - WIN_CRT_STATIC = 2, + WIN_CRT_DYNAMIC_DEBUG = 2, + WIN_CRT_STATIC = 3, + WIN_CRT_STATIC_DEBUG = 4, } WinCrtLinking; typedef enum @@ -351,6 +353,15 @@ typedef enum ARCH_OS_TARGET_LAST = WINDOWS_X64 } ArchOsTarget; +typedef enum +{ + SANITIZE_NOT_SET = -1, + SANITIZE_NONE, + SANITIZE_ADDRESS, + SANITIZE_MEMORY, + SANITIZE_THREAD, +} SanitizeMode; + #define ANY_WINDOWS_ARCH_OS WINDOWS_AARCH64: case WINDOWS_X64: case MINGW_X64 typedef enum @@ -484,6 +495,7 @@ typedef struct BuildOptions_ SizeOptimizationLevel optsize; RiscvFloatCapability riscv_float_capability; MemoryEnvironment memory_environment; + SanitizeMode sanitize_mode; bool print_keywords; bool print_attributes; bool print_builtins; @@ -616,8 +628,11 @@ typedef struct StructReturn x86_struct_return : 3; X86VectorCapability x86_vector_capability : 4; RiscvFloatCapability riscv_float_capability : 4; - bool trap_on_wrap : 1; Win64Simd pass_win64_simd_as_arrays : 3; + bool trap_on_wrap : 1; + bool sanitize_address : 1; + bool sanitize_memory : 1; + bool sanitize_thread : 1; FpOpt fp_math; SafetyLevel safe_mode; PanicLevel panic_level; diff --git a/src/build/build_internal.h b/src/build/build_internal.h index 959a5e2c4..fb8bc4d07 100644 --- a/src/build/build_internal.h +++ b/src/build/build_internal.h @@ -26,10 +26,12 @@ static const char *memory_environment[6] = { [MEMORY_ENV_NONE] = "none", }; -static const char *wincrt_linking[3] = { +static const char *wincrt_linking[5] = { [WIN_CRT_NONE] = "none", [WIN_CRT_DYNAMIC] = "dynamic", + [WIN_CRT_DYNAMIC_DEBUG] = "dynamic-debug", [WIN_CRT_STATIC] = "static", + [WIN_CRT_STATIC_DEBUG] = "static-debug", }; static const char *vector_conv[2] = { @@ -99,6 +101,13 @@ static const char *reloc_models[5] = { [RELOC_BIG_PIE] = "PIE", }; +static const char *sanitize_modes[4] = { + [SANITIZE_NONE] = "none", + [SANITIZE_ADDRESS] = "address", + [SANITIZE_MEMORY] = "memory", + [SANITIZE_THREAD] = "thread", +}; + Project *project_load(void); BuildTarget *project_select_target(Project *project, const char *optional_target); void update_feature_flags(const char ***flags, const char ***removed_flag, const char *arg, bool add); diff --git a/src/build/build_options.c b/src/build/build_options.c index 5481f4509..587481955 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -181,7 +181,7 @@ static void usage(void) PRINTF(" --print-input - Print inputted C3 files to stdout."); PRINTF(""); PRINTF(" --winsdk - Set the directory for Windows system library files for cross compilation."); - PRINTF(" --wincrt=