Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ jobs:
contents: read
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
with:
with_release_mode_testing: true
with_windows: true
with_musl: true
with_android: true
with_wasm: true
with_linting: true
enable_all_traits: true
secrets: inherit
Expand All @@ -39,6 +41,13 @@ jobs:
uses: vapor/ci/.github/workflows/submit-deps.yml@main
secrets: inherit

foundation-linking:
uses: vapor/ci/.github/workflows/check-foundation-linking.yml@main
permissions:
contents: read
with:
swift_image: swift:6.3-noble

# integration-check:
# runs-on: ubuntu-latest
# container: swift:noble
Expand Down
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ let package = Package(

var swiftSettings: [SwiftSetting] {
[
.treatAllWarnings(as: .error),
.strictMemorySafety(),
.enableExperimentalFeature("SuppressedAssociatedTypesWithDefaults"),
.enableExperimentalFeature("LifetimeDependence"),
.enableExperimentalFeature("Lifetimes"),
.enableUpcomingFeature("LifetimeDependence"),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
Expand Down
10 changes: 5 additions & 5 deletions Sources/ConsoleKit/Terminal/ANSI.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#if canImport(Darwin)
import Darwin.C
#elseif canImport(Glibc)
@preconcurrency import Glibc
@unsafe @preconcurrency import Glibc
#elseif canImport(Musl)
@preconcurrency import Musl
@unsafe @preconcurrency import Musl
#elseif canImport(Android)
@preconcurrency import Android
@unsafe @preconcurrency import Android
#elseif os(WASI)
import WASILibc
#elseif os(Windows)
Expand Down Expand Up @@ -61,9 +61,9 @@ extension Terminal {
// fdopen() on stdout is fast; also the returned file MUST NOT be fclose()d
// This avoids concurrency complaints due to accessing global `stdout`.
#if os(Windows)
fflush(_fdopen(_fileno(stdout), "w+"))
unsafe fflush(_fdopen(_fileno(stdout), "w+"))
#else
fflush(fdopen(STDOUT_FILENO, "w+"))
unsafe fflush(fdopen(STDOUT_FILENO, "w+"))
#endif
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/ConsoleKit/Terminal/Console.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#if canImport(Darwin)
import Darwin.C
#elseif canImport(Glibc)
@preconcurrency import Glibc
@unsafe @preconcurrency import Glibc
#elseif canImport(Musl)
@preconcurrency import Musl
@unsafe @preconcurrency import Musl
#elseif canImport(Android)
@preconcurrency import Android
@unsafe @preconcurrency import Android
#elseif os(WASI)
import WASILibc
#elseif os(Windows)
Expand Down Expand Up @@ -111,7 +111,7 @@ extension Console {
// Xcode output does not support ANSI commands
return false
#elseif os(Windows)
return _isatty(_fileno(stdout)) > 0
return unsafe _isatty(_fileno(stdout)) > 0
#else
// If STDOUT is not an interactive terminal then omit ANSI commands
return isatty(STDOUT_FILENO) > 0
Expand Down
35 changes: 19 additions & 16 deletions Sources/ConsoleKit/Terminal/Terminal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
#if canImport(Darwin)
import Darwin.C
#elseif canImport(Glibc)
@preconcurrency import Glibc
@unsafe @preconcurrency import Glibc
#elseif canImport(Musl)
@preconcurrency import Musl
@unsafe @preconcurrency import Musl
#elseif canImport(Android)
@preconcurrency import Android
@unsafe @preconcurrency import Android
#elseif os(WASI)
import WASILibc
#elseif os(Windows)
Expand Down Expand Up @@ -66,26 +66,26 @@ public final class Terminal: Console, Sendable {
// swift-format-ignore
func plat_readpassphrase(into buf: UnsafeMutableBufferPointer<Int8>) -> Int {
#if canImport(Darwin)
let rpp = readpassphrase
let rpp = unsafe readpassphrase
#else
let rpp = linux_readpassphrase
let rpp = unsafe linux_readpassphrase
let RPP_REQUIRE_TTY = 0 as Int32
#endif

while rpp("", buf.baseAddress!, buf.count, RPP_REQUIRE_TTY) == nil {
while unsafe rpp("", buf.baseAddress!, buf.count, RPP_REQUIRE_TTY) == nil {
guard errno == EINTR else { return 0 }
}
return strlen(buf.baseAddress!)
return unsafe strlen(buf.baseAddress!)
}
// swift-format-ignore
func readpassphrase_str() -> String {
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
return .init(unsafeUninitializedCapacity: 1024) {
$0.withMemoryRebound(to: Int8.self) { plat_readpassphrase(into: $0) }
return unsafe .init(unsafeUninitializedCapacity: 1024) {
unsafe $0.withMemoryRebound(to: Int8.self) { unsafe plat_readpassphrase(into: $0) }
}
} else {
return .init(
decoding: [Int8](unsafeUninitializedCapacity: 1024) { $1 = plat_readpassphrase(into: $0) }.map(UInt8.init),
return unsafe .init(
decoding: [Int8](unsafeUninitializedCapacity: 1024) { $1 = unsafe plat_readpassphrase(into: $0) }.map(UInt8.init),
as: UTF8.self
)
}
Expand Down Expand Up @@ -140,16 +140,16 @@ public final class Terminal: Console, Sendable {
output = text.description
}
Swift.print(output, terminator: newLine ? "\n" : "")
fflush(stdout)
unsafe fflush(stdout)
}

/// See ``Console``
public func report(error: String, newLine: Bool) {
for c in (newLine ? "\(error)\n" : error).utf8 {
#if os(Windows)
_putc_nolock(CInt(c), stderr)
unsafe _putc_nolock(CInt(c), stderr)
#else
putc_unlocked(CInt(c), stderr)
unsafe putc_unlocked(CInt(c), stderr)
#endif
}
}
Expand All @@ -158,11 +158,14 @@ public final class Terminal: Console, Sendable {
public var size: (width: Int, height: Int) {
#if os(Windows)
var csbi = CONSOLE_SCREEN_BUFFER_INFO()
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
unsafe GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
return (Int(csbi.dwSize.X), Int(csbi.dwSize.Y))
#elseif os(WASI)
// WASI has no `ioctl`/`TIOCGWINSZ`/`winsize`, so return `0, 0` as "unknown size".
return (0, 0)
#else
var w = winsize()
_ = ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w)
_ = unsafe ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w)
return (Int(w.ws_col), Int(w.ws_row))
#endif
}
Expand Down
66 changes: 33 additions & 33 deletions Sources/ConsoleKit/Terminal/readpassphrase_linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
@preconcurrency import Glibc
@unsafe @preconcurrency import Glibc
#elseif canImport(Android)
@preconcurrency import Android
@unsafe @preconcurrency import Android
#elseif canImport(Musl)
@preconcurrency import Musl
@unsafe @preconcurrency import Musl
#endif

/// This implementation of `readpassphrase()`, used only on Linux where it's extremely difficult to get at the `libbsd`
Expand Down Expand Up @@ -36,33 +36,33 @@ internal func linux_readpassphrase(
#endif

// Open /dev/tty
let fd = open("/dev/tty", O_RDWR)
let fd = unsafe open("/dev/tty", O_RDWR)
guard fd >= 0 else { return nil }
defer { close(fd) }

// Disable echo
var oterm = termios()
var term = termios()
guard tcgetattr(fd, &oterm) == 0 else { return nil }
guard unsafe tcgetattr(fd, &oterm) == 0 else { return nil }
term = oterm
if (flags & 0x1 /* RPP_ECHO_ON */) == 0 {
term.c_lflag &= tcflag_t(bitPattern: numericCast(~(ECHO | ECHONL)))
}
_ = tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) // libbsd ignores it if this calls fails, should we be doing the same?
_ = unsafe tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) // libbsd ignores it if this calls fails, should we be doing the same?

// Reset the signal counts and install a recovery handler onto a whole buncha signals
linux_readpassphrase_signos.reset()
unsafe linux_readpassphrase_signos.reset()
var sigrecovery = sigaction()
sigemptyset(&sigrecovery.sa_mask)
unsafe sigemptyset(&sigrecovery.sa_mask)
sigrecovery.sa_flags = 0
#if canImport(Darwin)
sigrecovery.__sigaction_u = .init(__sa_handler: { linux_readpassphrase_signos[$0] += 1 })
sigrecovery.__sigaction_u = .init(__sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 })
#elseif canImport(Glibc)
sigrecovery.__sigaction_handler = .init(sa_handler: { linux_readpassphrase_signos[$0] += 1 })
sigrecovery.__sigaction_handler = .init(sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 })
#elseif canImport(Musl)
sigrecovery.__sa_handler = .init(sa_handler: { linux_readpassphrase_signos[$0] += 1 })
sigrecovery.__sa_handler = .init(sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 })
#elseif os(Android)
sigrecovery.sa_handler = { linux_readpassphrase_signos[$0] += 1 }
sigrecovery.sa_handler = { unsafe linux_readpassphrase_signos[$0] += 1 }
#endif
let sigsaves = linux_readpassphrase_installHandlers(linux_readpassphrase_signals, &sigrecovery)

Expand All @@ -72,24 +72,24 @@ internal func linux_readpassphrase(
var save_errno = 0 as Int32
var c: Int8 = 0
while i < bufsiz - 1 && nr == 1 && c != 0x0a && c != 0x0d {
nr = read(fd, &c, 1)
nr = unsafe read(fd, &c, 1)
if nr == 1 {
buf[i] = c
unsafe buf[i] = c
i += 1
}
}
buf[i] = 0
unsafe buf[i] = 0
save_errno = errno // save off errno for later restoration after the state restoration stuff below
if (term.c_lflag & tcflag_t(bitPattern: numericCast(ECHO))) == 0 { write(fd, "\n", 1) }
if (term.c_lflag & tcflag_t(bitPattern: numericCast(ECHO))) == 0 { unsafe write(fd, "\n", 1) }

// Restore original terminal config
if memcmp(&term, &oterm, MemoryLayout<termios>.size) != 0 {
if unsafe memcmp(&term, &oterm, MemoryLayout<termios>.size) != 0 {
// I don't understand what this sequence accomplishes with respect to ignoring SIGTTOU.
let save_sigttou = linux_readpassphrase_signos[SIGTTOU]
while tcsetattr(fd, TCSAFLUSH | TCSASOFT, &oterm) == -1 && errno == EINTR && linux_readpassphrase_signos[SIGTTOU] == 0 {
let save_sigttou = unsafe linux_readpassphrase_signos[SIGTTOU]
while unsafe tcsetattr(fd, TCSAFLUSH | TCSASOFT, &oterm) == -1 && errno == EINTR && linux_readpassphrase_signos[SIGTTOU] == 0 {
continue
}
linux_readpassphrase_signos[SIGTTOU] = save_sigttou
unsafe linux_readpassphrase_signos[SIGTTOU] = save_sigttou
}

// Restore signal handlers
Expand All @@ -98,7 +98,7 @@ internal func linux_readpassphrase(
// libbsd closes the TTY fd here. Since we deferred the fd closure, we just hope the difference doesn't cause problems.

// Re-raise any signals we temporarily ignored, now that the old signal handlers are back in place.
for i in 0..<NSIG where linux_readpassphrase_signos[i] != 0 {
for i in 0..<NSIG where unsafe linux_readpassphrase_signos[i] != 0 {
kill(getpid(), i)
// libbsd restarts the entire readpassphrase() execution if the signal was SIGTSTP, SIGTTIN, or SIGTTOU. Using goto.
// It makes sense functionally, but it's more trouble than it's worth for now.
Expand All @@ -108,7 +108,7 @@ internal func linux_readpassphrase(
if save_errno != 0 {
errno = save_errno
}
return nr == -1 ? nil : buf
return unsafe nr == -1 ? nil : buf
}

// MARK: - Workaround for https://github.com/swiftlang/swift/issues/91387
Expand All @@ -121,7 +121,7 @@ internal func linux_readpassphrase_installHandlers(_ signals: [Int32], _ handler
var previous = sigaction()

for (i, signo) in signals.enumerated() {
sigaction(signo, &handler, &previous)
unsafe sigaction(signo, &handler, &previous)
saved[i] = previous
}
return saved
Expand All @@ -132,15 +132,15 @@ internal func linux_readpassphrase_restoreHandlers(_ signals: [Int32], _ saved:

for (i, signo) in signals.enumerated() {
var sa = saved[i]
sigaction(signo, &sa, nil)
unsafe sigaction(signo, &sa, nil)
}
}

/// Used for signal recovery by `linux_readpassphrase()`. This is `static volatile` storage in the original.
/// We must avoid any accesses into the Swift runtime in the signal handler, so this is manually allocated
/// storage rather than a simple array. It is never deallocated and will be considered a leak by memory
/// analysis tools.
private let linux_readpassphrase_signos: VeryUnsafeMutableSigAtomicBufferPointer = .init(capacity: NSIG)
private let linux_readpassphrase_signos: VeryUnsafeMutableSigAtomicBufferPointer = unsafe .init(capacity: NSIG)

/// A version of `UnsafeMutableBufferPointer` which avoids any references to the Swift runtime, including conformance to
/// `Collection` or `Sequence`, etc. Guaranteed to only ever allocate once. Provides a (typically global) "array" of
Expand All @@ -154,27 +154,27 @@ private let linux_readpassphrase_signos: VeryUnsafeMutableSigAtomicBufferPointer
/// deliberately. Swift has no other model for doing this kind of thing yet.
///
/// If you think you want to use this for something, you're wrong.
private struct VeryUnsafeMutableSigAtomicBufferPointer: @unchecked Sendable {
@unsafe private struct VeryUnsafeMutableSigAtomicBufferPointer: @unchecked Sendable {
let capacity: Int
let baseAddress: UnsafeMutablePointer<sig_atomic_t>

init<I: FixedWidthInteger & BinaryInteger>(capacity: I) {
self.capacity = Int(capacity)
self.baseAddress = .allocate(capacity: self.capacity)
unsafe self.capacity = Int(capacity)
unsafe self.baseAddress = .allocate(capacity: self.capacity)
}

subscript(_ index: Int) -> sig_atomic_t {
get { self.baseAddress.advanced(by: index).pointee }
nonmutating set { self.baseAddress.advanced(by: index).pointee = newValue }
get { unsafe self.baseAddress.advanced(by: index).pointee }
nonmutating set { unsafe self.baseAddress.advanced(by: index).pointee = newValue }
}

subscript(_ index: Int32) -> sig_atomic_t {
get { self[Int(index)] }
nonmutating set { self[Int(index)] = newValue }
get { unsafe self[Int(index)] }
nonmutating set { unsafe self[Int(index)] = newValue }
}

func reset() {
self.baseAddress.update(repeating: 0, count: self.capacity)
unsafe self.baseAddress.update(repeating: 0, count: self.capacity)
}
}
#endif
8 changes: 4 additions & 4 deletions Sources/ConsoleLogger/ANSIColor.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#if canImport(Darwin)
import Darwin.C
#elseif canImport(Glibc)
@preconcurrency import Glibc
@unsafe @preconcurrency import Glibc
#elseif canImport(Musl)
@preconcurrency import Musl
@unsafe @preconcurrency import Musl
#elseif canImport(Android)
@preconcurrency import Android
@unsafe @preconcurrency import Android
#elseif os(WASI)
import WASILibc
#elseif os(Windows)
Expand All @@ -32,7 +32,7 @@ private var supportsANSICommands: Bool {
// Xcode output does not support ANSI commands
return false
#elseif os(Windows)
return _isatty(_fileno(stdout)) > 0
return unsafe _isatty(_fileno(stdout)) > 0
#else
// If STDOUT is not an interactive terminal then omit ANSI commands
return isatty(STDOUT_FILENO) > 0
Expand Down
Loading
Loading