Swift 1.2: Calls to getopt_long only parse long arguments successfully 1% of the time

Originator:apsharp
Number:rdar://19873041 Date Originated:18-Feb-2015 09:57 PM
Status:Closed Resolved:Behaves correctly
Product:Developer Tools Product Version:Swift 1.2
Classification:Other Bug Reproducible:Always
 
Summary:
I'm trying to implement command line argument parsing in Swift by wrapping the standard getopt_long API, rather than reinvent the wheel. I found that I was able to successfully parse short arguments (e.g., "-v"), but was unable to successfully parse the long form (e.g., "--verbose"). However I observed that very occasionally it would succeed.

Steps to Reproduce:
1. Write this in main.swift:
import Darwin

let long_options = [
	option(name: "verbose", has_arg: no_argument, flag: nil, val: Int32(UnicodeScalar("v").value))
]

while let ord = .Some(getopt_long(Process.argc, Process.unsafeArgv, "v", long_options, nil)) where ord != -1,
      let c = .Some(UnicodeScalar(UInt32(ord))) {
	switch c {
		case "v":
			println("successfully parsed 'verbose'")
		default:
			break
	}
}

2. Compile: xcrun -sdk macosx swiftc main.swift

3. Run: ./main --verbose

Expected Results:
The program would print "successfully parsed 'verbose'". It actually does succeed occasionally, but only about 5 or 10 times in 1000 (see Notes).

Actual Results:
The program prints "main: unrecognized option `--verbose'", most of the time.

Regression:
N/A

Notes:
I've attached a sample project that runs this same test 1000 times, and counts how many times it succeeds. It's usually about 5 to 10 times in 1000. To run the test, just unzip the folder, cd into the directory, and run "rake". For example:

$ rake
xcrun -sdk macosx swiftc main.swift -o parse-verbose-flag
Running './parse-verbose-flag --verbose' 1000 times
Successfully parsed on 8 out of 1000 tests

Comments

My response, marking the issue as resolved

Thanks for the tip!

This one works with an array of long options:


import Darwin

func withCStrings(strings: [String], body: [UnsafePointer] -> Result) -> Result? {
  if let string = strings.first {
    return string.withCString { cstring in
      withCStrings(Array(dropFirst(strings))) { other_cstrings in
        body([cstring] + other_cstrings)
      } ?? body([cstring])
    }
  } else {
    return nil
  }
}

withCStrings(["verbose", "edit"]) { cstrings -> () in
  let long_options = cstrings.map {
    option(name: $0, has_arg: no_argument, flag: nil, val: Int32($0.memory))
  }

let short_option_chars = lazy(cstrings).map { Character(UnicodeScalar(UInt8($0.memory))) }
  let short_options = String(short_option_chars)

while let ord = Optional(getopt_long(Process.argc, Process.unsafeArgv, short_options, long_options, nil)) where ord != -1,
  let c = Optional(UnicodeScalar(UInt32(ord))) {
    switch c {
    case "v":
      println("successfully parsed 'verbose'")
    case "e":
      println("successfully parsed 'edit'")
    default:
      break;
    }
  }
}

This issue has been verified as resolved and can be closed.

Response from Apple

This issue behaves as intended based on the following:

The problem is that the lifetime of the implicitly-created char* is only as long as the call, i.e. the constructor of the "option" instance, which is intended behavior. The compiler could be smarter about this, someday, potentially. Here's one that works:


import Darwin

"verbose".withCString {
  (verbose: UnsafePointer)->() in
  let long_options = [
    option(name: verbose, has_arg: no_argument, flag: nil, val: Int32(UnicodeScalar("v").value))
  ]

while let ord? = Optional(getopt_long(Process.argc, Process.unsafeArgv, "v", long_options, nil)) where ord != -1,
  let c? = Optional(UnicodeScalar(UInt32(ord))) {
    switch c {
    case "v":
      println("successfully parsed 'verbose'")
    default:
      break
    }
  }
}

Please update your bug report to let us know if this is still an issue for you.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!