diff --git a/Examples/math/main.swift b/Examples/math/main.swift index 5e40f684c..acbc500b2 100644 --- a/Examples/math/main.swift +++ b/Examples/math/main.swift @@ -21,11 +21,6 @@ struct Math: ParsableCommand { // Commands can define a version for automatic '--version' support. version: "1.0.0", - // Pass an array to `subcommands` to set up a nested tree of subcommands. - // With language support for type-level introspection, this could be - // provided by automatically finding nested `ParsableCommand` types. - subcommands: [Add.self, Multiply.self, Statistics.self], - // A default subcommand, when provided, is automatically selected if a // subcommand is not given on the command line. defaultSubcommand: Add.self) @@ -82,8 +77,7 @@ extension Math { // Command names are automatically generated from the type name // by default; you can specify an override here. commandName: "stats", - abstract: "Calculate descriptive statistics.", - subcommands: [Average.self, StandardDeviation.self, Quantiles.self]) + abstract: "Calculate descriptive statistics.") } } diff --git a/Package.swift b/Package.swift index a8caf6ccb..a29fe2a84 100644 --- a/Package.swift +++ b/Package.swift @@ -22,8 +22,14 @@ var package = Package( dependencies: [], targets: [ .target( - name: "ArgumentParser", + name: "_CRuntime", dependencies: []), + .target( + name: "_Runtime", + dependencies: ["_CRuntime"]), + .target( + name: "ArgumentParser", + dependencies: ["_Runtime"]), .target( name: "ArgumentParserTestHelpers", dependencies: ["ArgumentParser"]), diff --git a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift index 41516c6cc..0a9359b36 100644 --- a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift @@ -34,7 +34,7 @@ struct BashCompletionsGenerator { let subcommandArgument = isRootCommand ? "2" : "$(($1+1))" // Include 'help' in the list of subcommands for the root command. - var subcommands = type.configuration.subcommands + var subcommands = type.subcommands if !subcommands.isEmpty && isRootCommand { subcommands.append(HelpCommand.self) } diff --git a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift index 05a638916..922882dab 100644 --- a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift @@ -29,7 +29,7 @@ struct FishCompletionsGenerator { let type = commands.last! let isRootCommand = commands.count == 1 let programName = commandChain[0] - var subcommands = type.configuration.subcommands + var subcommands = type.subcommands if !subcommands.isEmpty { if isRootCommand { diff --git a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift index ee4c178ad..bd28c2d08 100644 --- a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift @@ -37,7 +37,7 @@ struct ZshCompletionsGenerator { var args = generateCompletionArguments(commands) - var subcommands = type.configuration.subcommands + var subcommands = type.subcommands var subcommandHandler = "" if !subcommands.isEmpty { args.append("'(-): :->command'") diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index ea3b8b3af..5dcbb1c78 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -39,7 +39,10 @@ public struct CommandConfiguration { public var shouldDisplay: Bool /// An array of the types that define subcommands for this command. - public var subcommands: [ParsableCommand.Type] + /// + /// If `nil`, the argument parser will automatically discover nested + /// types who conform to `ParsableCommand` and use them as subcommands. + public var subcommands: [ParsableCommand.Type]? /// The default command type to run if no subcommand is given. public var defaultSubcommand: ParsableCommand.Type? @@ -74,7 +77,7 @@ public struct CommandConfiguration { discussion: String = "", version: String = "", shouldDisplay: Bool = true, - subcommands: [ParsableCommand.Type] = [], + subcommands: [ParsableCommand.Type]? = nil, defaultSubcommand: ParsableCommand.Type? = nil, helpNames: NameSpecification? = nil ) { @@ -97,7 +100,7 @@ public struct CommandConfiguration { discussion: String = "", version: String = "", shouldDisplay: Bool = true, - subcommands: [ParsableCommand.Type] = [], + subcommands: [ParsableCommand.Type]? = nil, defaultSubcommand: ParsableCommand.Type? = nil, helpNames: NameSpecification? = nil ) { diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 1042105de..75a49d9c9 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -44,6 +44,10 @@ extension ParsableCommand { public mutating func run() throws { throw CleanExit.helpRequest(self) } + + static var subcommands: [ParsableCommand.Type] { + configuration.subcommands ?? discoverSubcommands(for: Self.self) + } } // MARK: - API diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 16d997a89..593bfbac5 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -117,7 +117,7 @@ internal struct HelpGenerator { } var usageString = UsageGenerator(toolName: toolName, definition: [currentArgSet]).synopsis - if !currentCommand.configuration.subcommands.isEmpty { + if !currentCommand.subcommands.isEmpty { if usageString.last != " " { usageString += " " } usageString += "" } @@ -202,9 +202,10 @@ internal struct HelpGenerator { } } - let configuration = commandStack.last!.configuration + let command = commandStack.last! + let configuration = command.configuration let subcommandElements: [Section.Element] = - configuration.subcommands.compactMap { command in + command.subcommands.compactMap { command in guard command.configuration.shouldDisplay else { return nil } var label = command._commandName if command == configuration.defaultSubcommand { diff --git a/Sources/ArgumentParser/Utilities/SubcommandDiscovery.swift b/Sources/ArgumentParser/Utilities/SubcommandDiscovery.swift new file mode 100644 index 000000000..b317bb8fb --- /dev/null +++ b/Sources/ArgumentParser/Utilities/SubcommandDiscovery.swift @@ -0,0 +1,106 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _Runtime +@_implementationOnly import Foundation + +let subcommandLock = NSLock() +var discoveredSubcommands: [UnsafeRawPointer: [ParsableCommand.Type]] = [:] + +// Use runtime metadata information to find nested subcommands automatically. +// When getting the subcommands for a ParsableCommand, we look at all types who +// conform to ParsableCommand within the same module. If we find one who +// is nested in the base command that we're looking at now, we add it as a +// subcommand. +// +// struct Base: ParsableCommand {} +// +// extension Base { +// // This is considered an automatic subcommand! +// struct Sub: ParsableCommand {} +// } +// +func discoverSubcommands(for type: Any.Type) -> [ParsableCommand.Type] { + // Make sure that only classes, structs, and enums are checked for. + guard let selfMetadata = metadata(for: type), + selfMetadata.kind != .existential else { + return [] + } + + subcommandLock.lock() + + defer { + subcommandLock.unlock() + } + + guard !discoveredSubcommands.keys.contains(selfMetadata.pointer) else { + return discoveredSubcommands[selfMetadata.pointer]! + } + + let module = getModuleDescriptor(from: selfMetadata.descriptor) + + let parsableCommand: ContextDescriptor + + // If the swift_getExistentialTypeConstraints function is available, use that. + // Otherwise, fallback to using an earlier existential metadata layout. + if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) { + // FIXME: Implement this function + fatalError("swift_getExistentialTypeConstraints not implemented") + } else { + let parsableCommandMetadata = metadata( + for: ParsableCommand.self + ) as! ExistentialMetadata + + parsableCommand = parsableCommandMetadata.protocols[0] + } + + // Grab all of the conformances to ParsableCommand in the same module that + // this ParsableCommand was defined in. + let conformances = _Runtime.getConformances(for: parsableCommand, in: module) + + // FIXME: Maybe we want to reserve some initial space here? + var subcommands: [ParsableCommand.Type] = [] + + for conformance in conformances { + // If we don't have a context descriptor, then an ObjC class somehow + // conformed to a Swift protocol (not sure that's possible). + guard let descriptor = conformance.contextDescriptor else { + continue + } + + // This is okay because modules can't conform to protocols, so the type + // being referenced here is at least a child deep in the declaration context + // tree. + let parent = descriptor.parent! + + // We're only interested in conformances where the parent is ourselves + // (the parent ParsableCommand). + guard parent == selfMetadata.descriptor else { + continue + } + + // If a subcommand is generic, we can't add it as a default because we have + // no idea what type substitution they want for the generic parameter. + guard !descriptor.flags.isGeneric else { + continue + } + + // We found a subcommand! Use the access function to get the metadata for + // it and add it to the list! + let subcommand = descriptor.accessor() as! ParsableCommand.Type + subcommands.append(subcommand) + } + + // Remember to cache the results! + discoveredSubcommands[selfMetadata.pointer] = subcommands + + return subcommands +} diff --git a/Sources/ArgumentParser/Utilities/Tree.swift b/Sources/ArgumentParser/Utilities/Tree.swift index fe40fc0a0..da0964ea6 100644 --- a/Sources/ArgumentParser/Utilities/Tree.swift +++ b/Sources/ArgumentParser/Utilities/Tree.swift @@ -90,7 +90,7 @@ extension Tree where Element == ParsableCommand.Type { convenience init(root command: ParsableCommand.Type) throws { self.init(command) - for subcommand in command.configuration.subcommands { + for subcommand in command.subcommands { if subcommand == command { throw InitializationError.recursiveSubcommand(subcommand) } diff --git a/Sources/_CRuntime/ImageInspection.c b/Sources/_CRuntime/ImageInspection.c new file mode 100644 index 000000000..348b0c275 --- /dev/null +++ b/Sources/_CRuntime/ImageInspection.c @@ -0,0 +1,68 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include "ImageInspection.h" + +//===----------------------------------------------------------------------===// +// MachO Image Inspection +//===----------------------------------------------------------------------===// + +#if defined(__MACH__) + +#include + +void _loadImageCallback(const struct mach_header *header, intptr_t size) { + lookupSection(header, "__TEXT", "__swift5_proto", + registerProtocolConformances); +} + +__attribute__((__constructor__)) +void loadImages() { + _dyld_register_func_for_add_image(_loadImageCallback); +} + +#endif // defined(__MACH__) + +//===----------------------------------------------------------------------===// +// ELF Image Inspection +//===----------------------------------------------------------------------===// + +#if defined(__ELF__) + +#define SWIFT_REGISTER_SECTION(name, handle) \ + handle(&__start_##name, &__stop_##name - &__start_##name); + +__attribute__((__constructor__)) +void loadImages() { + SWIFT_REGISTER_SECTION(swift5_protocol_conformances, + registerProtocolConformances) +} + +#undef SWIFT_REGISTER_SECTION + +#endif // defined(__ELF__) + +//===----------------------------------------------------------------------===// +// COFF Image Inspection +//===----------------------------------------------------------------------===// + +#if !defined(__MACH__) && !defined(__ELF__) + +#define SWIFT_REGISTER_SECTION(name, handle) \ + handle((const char *)&__start_##name, &__stop_##name - &__start_##name); + +void loadImages() { + SWIFT_REGISTER_SECTION(sw5prtc, registerProtocolConformances) +} + +#undef SWIFT_REGISTER_SECTION + +#endif // !defined(__MACH__) && !defined(__ELF__) diff --git a/Sources/_CRuntime/MetadataViews.c b/Sources/_CRuntime/MetadataViews.c new file mode 100644 index 000000000..68e3fb566 --- /dev/null +++ b/Sources/_CRuntime/MetadataViews.c @@ -0,0 +1,12 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include "MetadataViews.h" diff --git a/Sources/_CRuntime/PtrauthStrip.c b/Sources/_CRuntime/PtrauthStrip.c new file mode 100644 index 000000000..682fc1532 --- /dev/null +++ b/Sources/_CRuntime/PtrauthStrip.c @@ -0,0 +1,21 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include "PtrauthStrip.h" + +#if defined(__arm64e__) +#include + +const void *__ptrauth_strip_asda(const void *pointer) { + return ptrauth_strip(pointer, ptrauth_key_asda); +} + +#endif diff --git a/Sources/_CRuntime/include/ImageInspection.h b/Sources/_CRuntime/include/ImageInspection.h new file mode 100644 index 000000000..8fc9e6481 --- /dev/null +++ b/Sources/_CRuntime/include/ImageInspection.h @@ -0,0 +1,115 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef IMAGE_INSPECTION_H +#define IMAGE_INSPECTION_H + +#include + +extern void registerProtocolConformances(const char *section, size_t size); + +void loadImages(); + +//===----------------------------------------------------------------------===// +// MachO Image Inspection +//===----------------------------------------------------------------------===// + +#if defined(__MACH__) + +#include + +extern void lookupSection(const struct mach_header *header, const char *segment, + const char *section, + void (*registerFunc)(const char *, size_t)); + +#endif // defined(__MACH__) + +//===----------------------------------------------------------------------===// +// ELF Image Inspection +//===----------------------------------------------------------------------===// + +#if defined(__ELF__) + +// Create an empty section here so that we can get legitimate pointers to the +// actual start and stop of a specific section. + +#define DECLARE_SWIFT_SECTION(name) \ + __asm__("\t.section " #name ", \"a\"\n"); \ + __attribute__((__visibility__("hidden"), __aligned__(1))) extern const char __start_##name; \ + __attribute__((__visibility__("hidden"), __aligned__(1))) extern const char __stop_##name; + +#if defined(__cplusplus) +extern "C" { +#endif + +DECLARE_SWIFT_SECTION(swift5_protocol_conformances) + +#if defined(__cplusplus) +} // extern "C" +#endif + +#undef DECLARE_SWIFT_SECTION + +#endif // defined(__ELF__) + +//===----------------------------------------------------------------------===// +// COFF Image Inspection +//===----------------------------------------------------------------------===// + +#if !defined(__MACH__) && !defined(__ELF__) + +#include "stdint.h" + +#define PASTE_EXPANDED(a, b) a##b +#define PASTE(a, b) PASTE_EXPANDED(a, b) + +#define STRING_EXPANDED(string) #string +#define STRING(string) STRING_EXPANDED(string) + +#define C_LABEL(name) PASTE(__USER_LABEL_PREFIX__, name) + +#define PRAGMA(pragma) _Pragma(#pragma) + +#define DECLARE_SWIFT_SECTION(name) \ + PRAGMA(section("." #name "$A", long, read)) \ + __declspec(allocate("." #name "$A")) \ + __declspec(align(1)) \ + static uintptr_t __start_##name = 0; \ + \ + PRAGMA(section("." #name "$C", long, read)) \ + __declspec(allocate("." #name "$C")) \ + __declspec(align(1)) \ + static uintptr_t __stop_##name = 0; + +#if defined(__cplusplus) +extern "C" { +#endif + +DECLARE_SWIFT_SECTION(sw5prtc) + +#if defined(__cplusplus) +} +#endif + +#undef DECLARE_SWIFT_SECTION + +#pragma section(".CRT$XCIS", long, read) + +__declspec(allocate(".CRT$XCIS")) +#if defined(__cplusplus) +extern "C" +#endif +void (*pLoadImages)(void) = &loadImages; +#pragma comment(linker, "/include:" STRING(C_LABEL(pLoadImages))) + +#endif // !defined(__MACH__) && !defined(__ELF__) + +#endif /* IMAGE_INSPECTION_H */ diff --git a/Sources/_CRuntime/include/MetadataViews.h b/Sources/_CRuntime/include/MetadataViews.h new file mode 100644 index 000000000..6e3e4da16 --- /dev/null +++ b/Sources/_CRuntime/include/MetadataViews.h @@ -0,0 +1,84 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef METADATA_VIEWS_H +#define METADATA_VIEWS_H + +#include "stdint.h" + +// FIXME: Currently, the ClangImporter does not import types who have ptrauth +// decorators on member pointer types. This means that we could get rid of the +// whole ptrauth ceremony in Swift by stripping the asda portion and actually +// sign using the designated type descriptor key. + +struct ClassMetadata { + intptr_t kind; + const void *superclass; + uint32_t flags; + uint32_t instanceAddressPoint; + uint32_t instanceSize; + uint16_t instanceAlignmentMask; + uint16_t runtimeReserved; + uint32_t classSize; + uint32_t classAddressPoint; + const void *descriptor; +}; + +struct ClassMetadataObjC { + intptr_t kind; + const void *superclass; + intptr_t cacheData[2]; + const void *data; + uint32_t flags; + uint32_t instanceAddressPoint; + uint32_t instanceSize; + uint16_t instanceAlignmentMask; + uint16_t runtimeReserved; + uint32_t classSize; + uint32_t classAddressPoint; + const void *descriptor; +}; + +struct ConformanceDescriptor { + int32_t protocol; + int32_t typeReference; + int32_t witnessTablePattern; + uint32_t flags; +}; + +struct ContextDescriptor { + uint32_t flags; + int32_t parent; +}; + +struct EnumMetadata { + intptr_t kind; + const void *descriptor; +}; + +struct ExistentialMetadata { + intptr_t kind; + uint32_t flags; + uint32_t numProtocols; +}; + +struct StructMetadata { + intptr_t kind; + const void *descriptor; +}; + +struct TypeContextDescriptor { + struct ContextDescriptor base; + int32_t name; + int32_t accessor; +}; + +#endif /* METADATA_VIEWS_H */ diff --git a/Sources/_CRuntime/include/PtrauthStrip.h b/Sources/_CRuntime/include/PtrauthStrip.h new file mode 100644 index 000000000..5498dfadc --- /dev/null +++ b/Sources/_CRuntime/include/PtrauthStrip.h @@ -0,0 +1,19 @@ +//===--------------------------------------------------------------*- C -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef PTRAUTH_H +#define PTRAUTH_H + +#if defined(__arm64e__) +const void *__ptrauth_strip_asda(const void *pointer); +#endif + +#endif /* PTRAUTH_H */ diff --git a/Sources/_Runtime/Metadata/ClassMetadata.swift b/Sources/_Runtime/Metadata/ClassMetadata.swift new file mode 100644 index 000000000..d0d4837cf --- /dev/null +++ b/Sources/_Runtime/Metadata/ClassMetadata.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +public struct ClassMetadata: Metadata, PointerView { + // The ObjC specific properties of class metadata are always present on + // ObjC interopt enabled devices or platforms pre Swift 5.4. In 5.4, + // those were removed on platforms who do not provide ObjectiveC. + #if canImport(ObjectiveC) || swift(<5.4) + typealias View = _CRuntime.ClassMetadataObjC + #else + typealias View = _CRuntime.ClassMetadata + #endif + + public let pointer: UnsafeRawPointer + + var descriptor: ContextDescriptor { + // Type descriptors are signed on arm64e using pointer authentication. + #if _ptrauth(_arm64e) + let signed = __ptrauth_strip_asda(view.descriptor)! + return ContextDescriptor(pointer: signed) + #else + return ContextDescriptor(pointer: view.descriptor) + #endif + } +} diff --git a/Sources/_Runtime/Metadata/ConformanceDescriptor.swift b/Sources/_Runtime/Metadata/ConformanceDescriptor.swift new file mode 100644 index 000000000..786cf36fc --- /dev/null +++ b/Sources/_Runtime/Metadata/ConformanceDescriptor.swift @@ -0,0 +1,68 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +public struct ConformanceDescriptor: PointerView { + typealias View = _CRuntime.ConformanceDescriptor + + let pointer: UnsafeRawPointer + + public var `protocol`: ContextDescriptor { + let _protocol = RelativeIndirectablePointer<_CRuntime.ContextDescriptor>( + offset: view.protocol + ) + + return ContextDescriptor(pointer: _protocol.address(from: pointer)) + } + + public var contextDescriptor: ContextDescriptor? { + let start = pointer + MemoryLayout.size + let offset = start.load(as: Int32.self) + let address = start + Int(offset) + let _flags = Flags(value: view.flags) + + switch _flags.typeReferenceKind { + case .directTypeDescriptor: + return ContextDescriptor(pointer: address) + case .indirectTypeDescriptor: + let indirect = address.load(as: ContextDescriptor.self) + return indirect + default: + return nil + } + } +} + +extension ConformanceDescriptor { + public struct Flags { + let value: UInt32 + + var typeReferenceKind: TypeReferenceKind { + TypeReferenceKind(UInt16(value & (0x7 << 3)) >> 3) + } + } +} + +enum TypeReferenceKind: UInt16 { + case directTypeDescriptor = 0x0 + case indirectTypeDescriptor = 0x1 + case other = 0xFFFF + + init(_ int: UInt16) { + guard let kind = TypeReferenceKind(rawValue: int) else { + self = .other + return + } + + self = kind + } +} diff --git a/Sources/_Runtime/Metadata/ContextDescriptor.swift b/Sources/_Runtime/Metadata/ContextDescriptor.swift new file mode 100644 index 000000000..d34e7251e --- /dev/null +++ b/Sources/_Runtime/Metadata/ContextDescriptor.swift @@ -0,0 +1,86 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +// We really don't care about any of the various context descriptor properties, +// thus this design is a single abstraction for all context descriptors. + +public struct ContextDescriptor: PointerView { + typealias View = _CRuntime.ContextDescriptor + + let pointer: UnsafeRawPointer + + public var flags: Flags { + Flags(value: view.flags) + } + + public var parent: ContextDescriptor? { + let _parent = RelativeIndirectablePointer<_CRuntime.ContextDescriptor>( + offset: view.parent + ) + + guard _parent.offset != 0 else { + return nil + } + + let start = pointer + MemoryLayout.size + let address = _parent.address(from: start) + return ContextDescriptor(pointer: address) + } + + public var accessor: MetadataAccessFunction { + switch flags.kind { + case .class, .struct, .enum: + let typeDescriptor = pointer.load(as: _CRuntime.TypeContextDescriptor.self) + let start = pointer + MemoryLayout.size * 3 + let _accessor = RelativeDirectPointer( + offset: typeDescriptor.accessor + ) + let address = _accessor.address(from: start) + return MetadataAccessFunction(pointer: address) + default: + fatalError("Context descriptor kind: \(flags.kind), has no type accessor") + } + } +} + +extension ContextDescriptor: Equatable {} +extension ContextDescriptor: Hashable {} + +extension ContextDescriptor { + public struct Flags { + let value: UInt32 + + var kind: Kind { + let raw = UInt8(value & 0x1F) + + guard let kind = Kind(rawValue: raw) else { + return .other + } + + return kind + } + + public var isGeneric: Bool { + value & 0x80 != 0 + } + } + + enum Kind: UInt8 { + case module = 0x0 + case `protocol` = 0x3 + case `class` = 0x11 + case `struct` = 0x12 + case `enum` = 0x13 + case other = 0xFF + } +} diff --git a/Sources/_Runtime/Metadata/EnumMetadata.swift b/Sources/_Runtime/Metadata/EnumMetadata.swift new file mode 100644 index 000000000..067209591 --- /dev/null +++ b/Sources/_Runtime/Metadata/EnumMetadata.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +public struct EnumMetadata: Metadata, PointerView { + typealias View = _CRuntime.EnumMetadata + + public let pointer: UnsafeRawPointer + + var descriptor: ContextDescriptor { + // Type descriptors are signed on arm64e using pointer authentication. + #if _ptrauth(_arm64e) + let signed = __ptrauth_strip_asda(view.descriptor)! + return ContextDescriptor(pointer: signed) + #else + return ContextDescriptor(pointer: view.descriptor) + #endif + } +} diff --git a/Sources/_Runtime/Metadata/ExistentialMetadata.swift b/Sources/_Runtime/Metadata/ExistentialMetadata.swift new file mode 100644 index 000000000..a126bc8b9 --- /dev/null +++ b/Sources/_Runtime/Metadata/ExistentialMetadata.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +public struct ExistentialMetadata: Metadata, PointerView { + typealias View = _CRuntime.ExistentialMetadata + + public let pointer: UnsafeRawPointer + + var flags: ExistentialMetadata.Flags { + Flags(value: view.flags) + } + + public var protocols: UnsafeBufferPointer { + var start = trailing + + // The first trailing object is the superclass metadata, if there is any. + if flags.hasSuperclassConstraint { + start += MemoryLayout.size + } + + return UnsafeBufferPointer( + start: UnsafePointer(start._rawValue), + count: Int(view.numProtocols) + ) + } +} + +extension ExistentialMetadata { + struct Flags { + let value: UInt32 + + var hasSuperclassConstraint: Bool { + value & 0x40000000 != 0 + } + } +} diff --git a/Sources/_Runtime/Metadata/Metadata.swift b/Sources/_Runtime/Metadata/Metadata.swift new file mode 100644 index 000000000..4c779ce73 --- /dev/null +++ b/Sources/_Runtime/Metadata/Metadata.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +public protocol Metadata { + var pointer: UnsafeRawPointer { get } +} + +extension Metadata { + public var kind: MetadataKind { + MetadataKind(pointer) + } + + public var descriptor: ContextDescriptor { + switch self { + case let classMetadata as ClassMetadata: + return classMetadata.descriptor + case let enumMetadata as EnumMetadata: + return enumMetadata.descriptor + case let structMetadata as StructMetadata: + return structMetadata.descriptor + default: + fatalError("Metadata kind: \(kind), does not have a context descriptor.") + } + } +} + +public enum MetadataKind: Int { + case `class` = 0 + case `struct` = 0x200 + case `enum` = 0x201 + case optional = 0x202 + case existential = 0x303 + case other = 0xFFFF +} + +extension MetadataKind { + init(_ ptr: UnsafeRawPointer) { + let raw = ptr.load(as: Int.self) + + guard let kind = MetadataKind(rawValue: raw) else { + // If we're larger than 2047, then this is a class isa pointer for ObjC, + // otherwise we're some other kind of metadata (or unknown) that we don't + // care about for autodiscovery. + self = raw > 0x7FF ? .class : .other + return + } + + self = kind + } +} + +public func metadata(for type: Any.Type) -> Metadata? { + let ptr = unsafeBitCast(type, to: UnsafeRawPointer.self) + let kind = MetadataKind(ptr) + + switch kind { + case .class: + return ClassMetadata(pointer: ptr) + case .struct: + return StructMetadata(pointer: ptr) + case .enum, .optional: + return EnumMetadata(pointer: ptr) + case .existential: + return ExistentialMetadata(pointer: ptr) + default: + return nil + } +} diff --git a/Sources/_Runtime/Metadata/MetadataAccessFunction.swift b/Sources/_Runtime/Metadata/MetadataAccessFunction.swift new file mode 100644 index 000000000..7f3b52db8 --- /dev/null +++ b/Sources/_Runtime/Metadata/MetadataAccessFunction.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +public struct MetadataAccessFunction { + let pointer: UnsafeRawPointer + + public func callAsFunction() -> Any.Type { + // For the purposes of subcommand autodiscovery, we don't care about types + // who are generic or require witness tables, so using the simple access + // function is okay because we only ever call it on non-generic types. + let fn = unsafeBitCast( + pointer, + to: (@convention(thin) (Int) -> MetadataResponse).self + ) + + // We only care about complete blocking metadata, so 0 is correct here. + return fn(0).type + } +} + +struct MetadataResponse { + let type: Any.Type + + let state: Int +} diff --git a/Sources/_Runtime/Metadata/StructMetadata.swift b/Sources/_Runtime/Metadata/StructMetadata.swift new file mode 100644 index 000000000..f2dfd28b4 --- /dev/null +++ b/Sources/_Runtime/Metadata/StructMetadata.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime + +public struct StructMetadata: Metadata, PointerView { + typealias View = _CRuntime.StructMetadata + + public let pointer: UnsafeRawPointer + + var descriptor: ContextDescriptor { + // Type descriptors are signed on arm64e using pointer authentication. + #if _ptrauth(_arm64e) + let signed = __ptrauth_strip_asda(view.descriptor)! + return ContextDescriptor(pointer: signed) + #else + return ContextDescriptor(pointer: view.descriptor) + #endif + } +} diff --git a/Sources/_Runtime/Utilities/ImageInspection.swift b/Sources/_Runtime/Utilities/ImageInspection.swift new file mode 100644 index 000000000..a670a5ac7 --- /dev/null +++ b/Sources/_Runtime/Utilities/ImageInspection.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _CRuntime +@_implementationOnly import Foundation + +extension NSLock { + func withLock(_ closure: () throws -> T) rethrows -> T { + lock() + defer { unlock() } + return try closure() + } +} + +let conformanceLock = NSLock() +var conformances: + [ContextDescriptor: [ContextDescriptor: [ConformanceDescriptor]]] = [:] + +@_cdecl("registerProtocolConformances") +public func registerProtocolConformances( + _ section: UnsafeRawPointer, + size: Int +) { + // This section is a list of relative pointers. + let stride = stride( + from: section, + to: section + size, + by: MemoryLayout.stride + ) + + for start in stride { + let address = start.load( + as: RelativeDirectPointer<_CRuntime.ConformanceDescriptor>.self + ).address(from: start) + let conformance = ConformanceDescriptor(pointer: address) + + // If we don't have a context descriptor, then the conforming type is an + // ObjC class, and we don't care about those. + if let descriptor = conformance.contextDescriptor { + let module = getModuleDescriptor(from: descriptor) + + conformanceLock.withLock { + conformances[ + conformance.protocol, + default: [:] + ][ + module, + default: [] + ].append(conformance) + } + } + } +} + +// Helper to walk up the context descriptor parent chain to eventually get the +// module descriptor at the top. +public func getModuleDescriptor( + from descriptor: ContextDescriptor +) -> ContextDescriptor { + var parent = descriptor + + while let newParent = parent.parent { + parent = newParent + } + + return parent +} + +public func getConformances( + for protocol: ContextDescriptor, + in module: ContextDescriptor +) -> [ConformanceDescriptor] { + return conformanceLock.withLock { + guard let protoEntry = conformances[`protocol`] else { + return [] + } + + guard let moduleEntry = protoEntry[module] else { + return [] + } + + return moduleEntry + } +} + +//===----------------------------------------------------------------------===// +// MachO Image Inspection +//===----------------------------------------------------------------------===// + +#if canImport(MachO) + +import MachO + +#if arch(x86_64) || arch(arm64) +typealias mach_header_platform = mach_header_64 +#else +typealias mach_header_platform = mach_header +#endif + +@_cdecl("lookupSection") +public func lookupSection( + _ header: UnsafePointer?, + segment: UnsafePointer?, + section: UnsafePointer?, + do handler: @convention(c) (UnsafeRawPointer, Int) -> () +) { + guard let header = header else { + return + } + + var size: UInt = 0 + + let section = header.withMemoryRebound( + to: mach_header_platform.self, + capacity: 1 + ) { + getsectiondata($0, segment, section, &size) + } + + guard section != nil else { + return + } + + handler(section!, Int(size)) +} + +#endif diff --git a/Sources/_Runtime/Utilities/PointerView.swift b/Sources/_Runtime/Utilities/PointerView.swift new file mode 100644 index 000000000..53e47d53b --- /dev/null +++ b/Sources/_Runtime/Utilities/PointerView.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +protocol PointerView { + associatedtype View + + var pointer: UnsafeRawPointer { get } +} + +extension PointerView { + var view: View { + pointer.load(as: View.self) + } + + var trailing: UnsafeRawPointer { + pointer + MemoryLayout.size + } +} diff --git a/Sources/_Runtime/Utilities/RelativePointers.swift b/Sources/_Runtime/Utilities/RelativePointers.swift new file mode 100644 index 000000000..76f0cd384 --- /dev/null +++ b/Sources/_Runtime/Utilities/RelativePointers.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +struct RelativeDirectPointer { + let offset: Int32 + + func address(from pointer: UnsafeRawPointer) -> UnsafeRawPointer { + pointer + Int(offset) + } +} + +struct RelativeIndirectablePointer { + let offset: Int32 + + func address(from pointer: UnsafeRawPointer) -> UnsafeRawPointer { + let dest = pointer + Int(offset & ~1) + + // If our low bit is set, then it indicates that we're indirectly pointing + // at our desired object. Otherwise, it's directly in front of us. + if Int(offset) & 1 == 1 { + return dest.load(as: UnsafeRawPointer.self) + } else { + return dest + } + } +} diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index dd850252f..e450b01ec 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -614,7 +614,6 @@ extension DefaultsEndToEndTests { fileprivate struct Main: ParsableCommand { static var configuration = CommandConfiguration( - subcommands: [Sub.self], defaultSubcommand: Sub.self ) diff --git a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift index 1c5c520c5..191f348ae 100644 --- a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift @@ -19,9 +19,6 @@ final class NestedCommandEndToEndTests: XCTestCase { // MARK: Single value String fileprivate struct Foo: ParsableCommand { - static var configuration = - CommandConfiguration(subcommands: [Build.self, Package.self]) - @Flag(name: .short) var verbose: Bool = false @@ -33,9 +30,6 @@ fileprivate struct Foo: ParsableCommand { } struct Package: ParsableCommand { - static var configuration = - CommandConfiguration(subcommands: [Clean.self, Config.self]) - @Flag(name: .short) var force: Bool = false @@ -152,10 +146,6 @@ private struct UniqueOptions: ParsableArguments { } private struct Super: ParsableCommand { - static var configuration: CommandConfiguration { - .init(subcommands: [Sub1.self, Sub2.self]) - } - @OptionGroup() var options: Options struct Sub1: ParsableCommand { diff --git a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift index ed89a44d3..e15cccb57 100644 --- a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift @@ -155,8 +155,7 @@ struct BaseCommand: ParsableCommand { static let baseFlagValue = "base" static var configuration = CommandConfiguration( - commandName: "base", - subcommands: [SubCommand.self] + commandName: "base" ) @Option() @@ -174,8 +173,7 @@ extension BaseCommand { static let subFlagValue = "sub" static var configuration = CommandConfiguration( - commandName: "sub", - subcommands: [SubSubCommand.self] + commandName: "sub" ) @Option() @@ -239,8 +237,7 @@ extension SubcommandEndToEndTests { private struct A: ParsableCommand { static var configuration = CommandConfiguration( - version: "1.0.0", - subcommands: [HasVersionFlag.self, NoVersionFlag.self]) + version: "1.0.0") struct HasVersionFlag: ParsableCommand { @Flag var version: Bool = false @@ -261,3 +258,75 @@ extension SubcommandEndToEndTests { } } } + +// MARK: Subcommand Autodiscovery + +fileprivate struct Swift: ParsableCommand { + // Test Build and Package are treated as automatic subcommands. + + struct Build: ParsableCommand { + // Test that DontBuild is not picked up as a subcommand by explictly using + // an empty subcommand array. + + static var configuration = CommandConfiguration( + subcommands: [] + ) + + struct DontBuild: ParsableCommand {} + } + + struct Package: ParsableCommand { + // Test that Init is treated as a subcommand, but Destroy is not. + + static var configuration = CommandConfiguration( + subcommands: [Init.self] + ) + + struct Init: ParsableCommand {} + + struct Destroy: ParsableCommand {} + } +} + +extension SubcommandEndToEndTests { + func testAutodiscovery() throws { + let swiftHelp = Swift.helpMessage() + + AssertEqualStringsIgnoringTrailingWhitespace(""" + USAGE: swift + + OPTIONS: + -h, --help Show help information. + + SUBCOMMANDS: + package + build + + See 'swift help ' for detailed help. + """, swiftHelp) + + let buildHelp = Swift.Build.helpMessage() + + AssertEqualStringsIgnoringTrailingWhitespace(""" + USAGE: build + + OPTIONS: + -h, --help Show help information. + + """, buildHelp) + + let packageHelp = Swift.Package.helpMessage() + + AssertEqualStringsIgnoringTrailingWhitespace(""" + USAGE: package + + OPTIONS: + -h, --help Show help information. + + SUBCOMMANDS: + init + + See 'package help ' for detailed help. + """, packageHelp) + } +} diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift index 8d19a6d67..6a68e26d8 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift @@ -17,9 +17,6 @@ extension Package { } extension Package.Config { - public static var configuration = CommandConfiguration( - subcommands: [GetMirror.self, SetMirror.self, UnsetMirror.self]) - /// Print mirror configuration for the given package dependency struct GetMirror: ParsableCommand { @OptionGroup() diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift index ccdbc5a9b..55937777d 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift @@ -87,10 +87,7 @@ struct Options: ParsableArguments { var swiftCompilerFlags: [String] = [] } -struct Package: ParsableCommand { - static var configuration = CommandConfiguration( - subcommands: [Clean.self, Config.self, Describe.self, GenerateXcodeProject.self, Hidden.self]) -} +struct Package: ParsableCommand {} extension Package { struct Hidden: ParsableCommand { diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 1334b92b6..12138d054 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -264,7 +264,6 @@ extension HelpGenerationTests { @Argument var argument: String = "" } - static var configuration = CommandConfiguration(subcommands: [CommandWithVeryLongName.self,ShortCommand.self,AnotherCommandWithVeryLongName.self,AnotherCommand.self]) } func testHelpWithSubcommands() { @@ -512,8 +511,7 @@ extension HelpGenerationTests { extension HelpGenerationTests { private struct ParserBug: ParsableCommand { static let configuration = CommandConfiguration( - commandName: "parserBug", - subcommands: [Sub.self]) + commandName: "parserBug") struct CommonOptions: ParsableCommand { @Flag(help: "example flag")