swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.49k stars 10.35k forks source link

CommandLine.arguments and CommandLine.argc should be deprecated and replaced with immutable equivalents. #66213

Closed Lukasa closed 5 months ago

Lukasa commented 1 year ago

CommandLine.arguments and CommandLine.argc are both declared static var. This is a real footgun: they're unsafe to use in a concurrency context because they may be mutated from any thread at any time. There's no real value in these being mutable so I think it is largely an oversight. We should consider deprecating these and replacing them with thread-safe equivalents.

grynspan commented 1 year ago

I wonder if we could maybe get away with just removing the setters? Hopefully nobody's relying on them…

CodPro-Sui commented 1 year ago
import Foundation

let commandLineArguments = CommandLine.arguments

// Access command-line arguments as an array of strings
for argument in commandLineArguments {
    print("Argument: \(argument)")
}

// Access the number of command-line arguments
let argumentCount = CommandLine.argc
print("Argument count: \(argumentCount)")
grynspan commented 11 months ago

Note argc is immutable. A workaround:

let commandLineArgs: [String] = UnsafeBufferPointer<UnsafeMutablePointer<CChar>?>(
  start: CommandLine.unsafeArgv,
  count: Int(CommandLine.argc)
).lazy
  .compactMap { $0 }
  .compactMap { String(validatingUTF8: $0) }
stevapple commented 11 months ago

I wonder if we could maybe get away with just removing the setters? Hopefully nobody's relying on them…

I don't think this can get through before Swift 6. Also, we cannot change it directly because of ABI stability.

grynspan commented 11 months ago

We could add an equivalent method (the method-ness can be used to indicate that the value is recomputed on each use?)

extension CommandLine {
  /// Get the command-line arguments passed to this process.
  ///
  /// - Returns: An array of command-line arguments.
  public static func arguments() -> [String] {
    UnsafeBufferPointer(start: unsafeArgv, count: Int(argc)).lazy
      .compactMap { $0 }
      .compactMap { String(validatingUTF8: $0) }
  }
}

We can trivially make such a thing back-deployable or transparent so that it can be used in older Darwin releases too.

gwynne commented 9 months ago

See also #69658

dnadoba commented 5 months ago

ProcessInfo.processInfo.arguments from Foundation is the current best workaround

dnadoba commented 5 months ago

This is actually already fixed by https://github.com/apple/swift/pull/72258

grynspan commented 5 months ago

Based on @tshortli's comment here, it sounds like there may be more work to do?

tshortli commented 5 months ago

I fixed the availability checker problems I was describing in the comments you linked to.

grynspan commented 5 months ago

Awesome :)