diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml index 3f8172e9a575..8e5db39a5fa1 100644 --- a/.github/workflows/mingw-w64.yml +++ b/.github/workflows/mingw-w64.yml @@ -80,7 +80,7 @@ jobs: shell: msys2 {0} run: | mkdir bin - cc crystal.obj -o bin/crystal.exe \ + cc crystal.obj -o bin/crystal.exe -municode \ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \ $(llvm-config --libs --system-libs --ldflags) \ -lole32 -lWS2_32 -Wl,--stack,0x800000 diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 65f7ed72a453..8bb7349318c6 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -87,6 +87,8 @@ describe Socket, tags: "network" do expect_raises(IO::TimeoutError) { server.accept } expect_raises(IO::TimeoutError) { server.accept? } + ensure + server.try &.close end it "sends messages" do diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 6b349072294d..a84a6adebc74 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -28,6 +28,8 @@ describe UDPSocket, tags: "network" do socket = UDPSocket.new(family) socket.bind(address, 0) socket.local_address.address.should eq address + ensure + socket.try &.close end it "sends and receives messages" do diff --git a/src/compiler/crystal/loader/mingw.cr b/src/compiler/crystal/loader/mingw.cr index 677f564cec16..2c557a893640 100644 --- a/src/compiler/crystal/loader/mingw.cr +++ b/src/compiler/crystal/loader/mingw.cr @@ -7,6 +7,8 @@ require "crystal/system/win32/library_archive" # The core implementation is derived from the MSVC loader. Main deviations are: # # - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s; +# - `.parse` automatically inserts a C runtime library if `-mcrtdll` isn't +# supplied; # - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a` # for DLL import libraries, `.a` for other libraries; # - `.default_search_paths` relies solely on `.cc_each_library_path`. @@ -28,6 +30,11 @@ class Crystal::Loader file_paths = [] of String extra_search_paths = [] of String + # note that `msvcrt` is a default runtime chosen at MinGW-w64 build time, + # `ucrt` is always UCRT (even in a MINGW64 environment), and + # `msvcrt-os` is always MSVCRT (even in a UCRT64 environment) + crt_dll = "msvcrt" + OptionParser.parse(args.dup) do |parser| parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory| extra_search_paths << directory @@ -39,17 +46,21 @@ class Crystal::Loader raise LoadError.new "static libraries are not supported by Crystal's runtime loader" end parser.unknown_args do |args, after_dash| - file_paths.concat args + file_paths.concat args.reject(&.starts_with?("-mcrtdll=")) end parser.invalid_option do |arg| - unless arg.starts_with?("-Wl,") + if crt_dll_arg = arg.lchop?("-mcrtdll=") + # the GCC spec is `%{!mcrtdll=*:-lmsvcrt} %{mcrtdll=*:-l%*}` + crt_dll = crt_dll_arg + elsif !arg.starts_with?("-Wl,") raise LoadError.new "Not a recognized linker flag: #{arg}" end end end search_paths = extra_search_paths + search_paths + libnames << crt_dll begin loader = new(search_paths) diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index caad6748229f..2120bfc06bfc 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -2,14 +2,12 @@ require "c/stringapiset" require "c/winnls" require "c/stdlib" -{% begin %} - # we have both `main` and `wmain`, so we must choose an unambiguous entry point +# we have both `main` and `wmain`, so we must choose an unambiguous entry point +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] - {% if flag?(:msvc) %} - @[Link(ldflags: "/ENTRY:wmainCRTStartup")] - {% elsif flag?(:gnu) && !flag?(:interpreted) %} - @[Link(ldflags: "-municode")] - {% end %} + @[Link(ldflags: "/ENTRY:wmainCRTStartup")] +{% elsif flag?(:gnu) && !flag?(:interpreted) %} + @[Link(ldflags: "-municode")] {% end %} lib LibCrystalMain end diff --git a/src/dir/glob.cr b/src/dir/glob.cr index cd45f0a03baf..2fc8d988c20a 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -37,6 +37,10 @@ class Dir # Returns an array of all files that match against any of *patterns*. # + # ``` + # Dir.glob "path/to/folder/*.txt" # Returns all files in the target folder that end in ".txt". + # Dir.glob "path/to/folder/**/*" # Returns all files in the target folder and its subfolders. + # ``` # The pattern syntax is similar to shell filename globbing, see `File.match?` for details. # # NOTE: Path separator in patterns needs to be always `/`. The returned file names use system-specific path separators. diff --git a/src/empty.cr b/src/empty.cr index 204e30da48c0..cb79610a5be3 100644 --- a/src/empty.cr +++ b/src/empty.cr @@ -1,6 +1,6 @@ require "primitives" -{% if flag?(:win32) %} +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup` {% end %} lib LibCrystalMain diff --git a/src/gc/none.cr b/src/gc/none.cr index ce84027e6e69..651027266e5b 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -1,5 +1,6 @@ {% if flag?(:win32) %} require "c/process" + require "c/heapapi" {% end %} require "crystal/tracing" @@ -11,21 +12,42 @@ module GC # :nodoc: def self.malloc(size : LibC::SizeT) : Void* Crystal.trace :gc, "malloc", size: size - # libc malloc is not guaranteed to return cleared memory, so we need to - # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678 - LibC.malloc(size).tap(&.clear) + + {% if flag?(:win32) %} + LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size) + {% else %} + # libc malloc is not guaranteed to return cleared memory, so we need to + # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678 + LibC.malloc(size).tap(&.clear) + {% end %} end # :nodoc: def self.malloc_atomic(size : LibC::SizeT) : Void* Crystal.trace :gc, "malloc", size: size, atomic: 1 - LibC.malloc(size) + + {% if flag?(:win32) %} + LibC.HeapAlloc(LibC.GetProcessHeap, 0, size) + {% else %} + LibC.malloc(size) + {% end %} end # :nodoc: def self.realloc(pointer : Void*, size : LibC::SizeT) : Void* Crystal.trace :gc, "realloc", size: size - LibC.realloc(pointer, size) + + {% if flag?(:win32) %} + # realloc with a null pointer should behave like plain malloc, but Win32 + # doesn't do that + if pointer + LibC.HeapReAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, pointer, size) + else + LibC.HeapAlloc(LibC.GetProcessHeap, LibC::HEAP_ZERO_MEMORY, size) + end + {% else %} + LibC.realloc(pointer, size) + {% end %} end def self.collect @@ -39,7 +61,12 @@ module GC def self.free(pointer : Void*) : Nil Crystal.trace :gc, "free" - LibC.free(pointer) + + {% if flag?(:win32) %} + LibC.HeapFree(LibC.GetProcessHeap, 0, pointer) + {% else %} + LibC.free(pointer) + {% end %} end def self.is_heap_ptr(pointer : Void*) : Bool diff --git a/src/levenshtein.cr b/src/levenshtein.cr index e890d59c90ef..01ad1bc40784 100644 --- a/src/levenshtein.cr +++ b/src/levenshtein.cr @@ -139,7 +139,7 @@ module Levenshtein # end # best_match # => "ello" # ``` - def self.find(name, tolerance = nil, &) + def self.find(name, tolerance = nil, &) : String? Finder.find(name, tolerance) do |sn| yield sn end @@ -154,7 +154,7 @@ module Levenshtein # Levenshtein.find("hello", ["hullo", "hel", "hall", "hell"], 2) # => "hullo" # Levenshtein.find("hello", ["hurlo", "hel", "hall"], 1) # => nil # ``` - def self.find(name, all_names, tolerance = nil) + def self.find(name, all_names, tolerance = nil) : String? Finder.find(name, all_names, tolerance) end end diff --git a/src/lib_c.cr b/src/lib_c.cr index 0bd8d2c2cc35..7bc94a34f53e 100644 --- a/src/lib_c.cr +++ b/src/lib_c.cr @@ -1,4 +1,4 @@ -{% if flag?(:win32) %} +{% if flag?(:msvc) %} @[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})] {% end %} lib LibC diff --git a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr index 1738cf774cac..8db5152585bc 100644 --- a/src/lib_c/x86_64-windows-msvc/c/heapapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/heapapi.cr @@ -1,6 +1,8 @@ require "c/winnt" lib LibC + HEAP_ZERO_MEMORY = 0x00000008 + fun GetProcessHeap : HANDLE fun HeapAlloc(hHeap : HANDLE, dwFlags : DWORD, dwBytes : SizeT) : Void* fun HeapReAlloc(hHeap : HANDLE, dwFlags : DWORD, lpMem : Void*, dwBytes : SizeT) : Void* diff --git a/src/string.cr b/src/string.cr index 09272c80eb45..273472d34517 100644 --- a/src/string.cr +++ b/src/string.cr @@ -1798,11 +1798,7 @@ class String def rchop? : String? return if empty? - if to_unsafe[bytesize - 1] < 0x80 || single_byte_optimizable? - return unsafe_byte_slice_string(0, bytesize - 1) - end - - self[0, size - 1] + unsafe_byte_slice_string(0, Char::Reader.new(at_end: self).pos, @length > 0 ? @length - 1 : 0) end # Returns a new `String` with *suffix* removed from the end of the string if possible, else returns `nil`.