blob: 1a612a3c2da6bfd1f1323795c91274e746031fed [file] [log] [blame]
// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: objc_interop
// UNSUPPORTED: OS=tvos
// UNSUPPORTED: OS=watchos
import Foundation
@NSKeyedArchiverEncodeNonGenericSubclassesOnly
final class Foo<T: NSCoding>: NSObject, NSCoding {
var one, two: T
init(one: T, two: T) {
self.one = one
self.two = two
}
@objc required convenience init(coder: NSCoder) {
let one = coder.decodeObject(forKey: "one") as! T
let two = coder.decodeObject(forKey :"two") as! T
self.init(one: one, two: two)
}
@objc(encodeWithCoder:) func encode(with encoder: NSCoder) {
encoder.encode(one, forKey: "one")
encoder.encode(two, forKey: "two")
}
}
// FIXME: W* macro equivalents should be in the Darwin/Glibc overlay
func WIFEXITED(_ status: Int32) -> Bool {
return (status & 0o177) == 0
}
func WEXITSTATUS(_ status: Int32) -> Int32 {
return (status >> 8) & 0xFF
}
// FIXME: "environ" should be in the Darwin overlay too
@_silgen_name("_NSGetEnviron")
func _NSGetEnviron() -> UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>>
var environ: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> {
return _NSGetEnviron().pointee
}
func driver() {
// Create a pipe to connect the archiver to the unarchiver.
var pipes: [Int32] = [0, 0]
guard pipe(&pipes) == 0 else { fatalError("pipe failed") }
let pipeRead = pipes[0], pipeWrite = pipes[1]
var archiver: pid_t = 0, unarchiver: pid_t = 0
let envp = environ
do {
// Set up the archiver's stdout to feed into our pipe.
var archiverActions: posix_spawn_file_actions_t?
guard posix_spawn_file_actions_init(&archiverActions) == 0 else {
fatalError("posix_spawn_file_actions_init failed")
}
defer { posix_spawn_file_actions_destroy(&archiverActions) }
guard posix_spawn_file_actions_adddup2(&archiverActions,
pipeWrite,
STDOUT_FILENO) == 0
&& posix_spawn_file_actions_addclose(&archiverActions,
pipeRead) == 0
else {
fatalError("posix_spawn_file_actions_add failed")
}
// Spawn the archiver process.
let str: StaticString = "-archive"
let optStr = UnsafeMutableRawPointer(mutating: str.utf8Start).bindMemory(
to: CChar.self, capacity: str.utf8CodeUnitCount)
let archiverArgv: [UnsafeMutablePointer<Int8>?] = [
CommandLine.unsafeArgv[0], optStr, nil
]
guard posix_spawn(&archiver, CommandLine.unsafeArgv[0],
&archiverActions, nil,
archiverArgv, envp) == 0 else {
fatalError("posix_spawn failed")
}
}
do {
// Set up the unarchiver's stdin to read from our pipe.
var unarchiverActions: posix_spawn_file_actions_t?
guard posix_spawn_file_actions_init(&unarchiverActions) == 0 else {
fatalError("posix_spawn_file_actions_init failed")
}
defer { posix_spawn_file_actions_destroy(&unarchiverActions) }
guard posix_spawn_file_actions_adddup2(&unarchiverActions,
pipeRead,
STDIN_FILENO) == 0
&& posix_spawn_file_actions_addclose(&unarchiverActions,
pipeWrite) == 0
else {
fatalError("posix_spawn_file_actions_add failed")
}
// Spawn the unarchiver process.
let str = "-unarchive" as StaticString
let optStr = UnsafeMutableRawPointer(mutating: str.utf8Start).bindMemory(
to: CChar.self, capacity: str.utf8CodeUnitCount)
var unarchiver: pid_t = 0
let unarchiverArgv: [UnsafeMutablePointer<Int8>?] = [
CommandLine.unsafeArgv[0], optStr, nil
]
guard posix_spawn(&unarchiver, CommandLine.unsafeArgv[0],
&unarchiverActions, nil,
unarchiverArgv, envp) == 0 else {
fatalError("posix_spawn failed")
}
}
// Wash our hands of the pipe, now that the subprocesses have started.
close(pipeRead)
close(pipeWrite)
// Wait for the subprocesses to finish.
var waiting: Set<pid_t> = [archiver, unarchiver]
while !waiting.isEmpty {
var status: Int32 = 0
let pid = wait(&status)
if pid == -1 {
// If the error was EINTR, just wait again.
if errno == EINTR { continue }
// If we have no children to wait for, stop.
if errno == ECHILD { break }
fatalError("wait failed")
}
waiting.remove(pid)
// Ensure the process exited successfully.
guard WIFEXITED(status) && WEXITSTATUS(status) == 0 else {
fatalError("subprocess exited abnormally")
}
}
}
func archive() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(Foo<NSString>(one: "one", two: "two"), forKey: "strings")
archiver.encode(Foo<NSNumber>(one: 1, two: 2), forKey: "numbers")
archiver.finishEncoding()
// Output the archived data over stdout, which should be piped to stdin
// on the unarchiver process.
while true {
let status = write(STDOUT_FILENO, data.bytes, data.length)
if status == data.length { break }
if errno == EINTR { continue }
fatalError("write failed")
}
}
func unarchive() {
// FIXME: Pre-instantiate the generic classes that were archived, since
// the ObjC runtime doesn't know how.
NSStringFromClass(Foo<NSNumber>.self)
NSStringFromClass(Foo<NSString>.self)
// Read in the data from stdin, where the archiver process should have
// written it.
var rawData: [UInt8] = []
var buffer = [UInt8](repeating: 0, count: 4096)
while true {
let count = read(STDIN_FILENO, &buffer, 4096)
if count == 0 { break }
if count == -1 {
if errno == EINTR { continue }
fatalError("read failed")
}
rawData += buffer[0..<count]
}
// Feed it into an unarchiver.
let data = NSData(bytes: rawData, length: rawData.count)
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
guard let strings
= unarchiver.decodeObject(forKey: "strings") as? Foo<NSString> else {
fatalError("unable to unarchive Foo<NSString>")
}
guard let numbers
= unarchiver.decodeObject(forKey: "numbers") as? Foo<NSNumber> else {
fatalError("unable to unarchive Foo<NSNumber>")
}
// CHECK-LABEL: <_TtGC4main3FooCSo8NSString_: {{0x[0-9a-f]+}}> #0
// CHECK: one: one
// CHECK: two: two
// CHECK-LABEL: <_TtGC4main3FooCSo8NSNumber_: {{0x[0-9a-f]+}}> #0
// CHECK: one: 1
// CHECK: two: 2
dump(strings)
dump(numbers)
}
// Pick a mode based on the command-line arguments.
// The test launches as a "driver" which then respawns itself into reader
// and writer subprocesses.
if CommandLine.arguments.count < 2 {
driver()
} else if CommandLine.arguments[1] == "-archive" {
archive()
} else if CommandLine.arguments[1] == "-unarchive" {
unarchive()
} else {
fatalError("invalid commandline argument")
}