mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Introduces the first use of `@_DebugDescription` in the standard library, applying it to `ObjectIdentifier`. In order to use the DebugDescription macro in the stdlib, the following changes are required: 1. Compilation must reference the just built macros (ie `libSwiftMacros.dylib`), not those from the SDK. This is addressed by adding an explicit `-external-plugin-path` flag that overrides the defaults generated by the compiler (which uses SDK paths, where the macro may or may not exist, and may not be the current version). 2. As DebugDescription uses `@_section`, compilation enables the `SymbolLinkageMarkers` feature. Note that the use of DebugDescription is conditionally enabled for the following reasons: 1. Use is disabled in freestanding builds, where the stdlib macros are not built. 2. Use is temporarily disabled in Linux builds due to a dynamic loader issue that needs further investigation The dynamic loader error causing issues with Linux CI is: > swift-plugin-server: error while loading shared libraries: libswiftGlibc.so: cannot open shared object file: No such file or directory This PR depended on #71639, #71588, and #71685.
369 lines
12 KiB
Swift
369 lines
12 KiB
Swift
//===--- DebuggerSupport.swift --------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 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
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import SwiftShims
|
|
|
|
// Macros are disabled when Swift is built without swift-syntax.
|
|
#if $Macros && hasAttribute(attached)
|
|
|
|
/// Converts description definitions to a debugger Type Summary.
|
|
///
|
|
/// This macro converts compatible description implementations written in Swift
|
|
/// to an LLDB format known as a Type Summary. A Type Summary is LLDB's
|
|
/// equivalent to `debugDescription`, with the distinction that it does not
|
|
/// execute code inside the debugged process. By avoiding code execution,
|
|
/// descriptions can be produced faster, without potential side effects, and
|
|
/// shown in situations where code execution is not performed, such as the
|
|
/// variable list of an IDE.
|
|
///
|
|
/// Consider this an example. This `Team` struct has a `debugDescription` which
|
|
/// summarizes some key details, such as the team's name. The debugger only
|
|
/// computes this string on demand - typically via the `po` command. By applying
|
|
/// the `DebugDescription` macro, a matching Type Summary is constructed. This
|
|
/// allows the user to show a string like "Rams [11-2]", without executing
|
|
/// `debugDescription`. This improves the usability, performance, and
|
|
/// reliability of the debugging experience.
|
|
///
|
|
/// @DebugDescription
|
|
/// struct Team: CustomDebugStringConvertible {
|
|
/// var name: String
|
|
/// var wins, losses: Int
|
|
///
|
|
/// var debugDescription: String {
|
|
/// "\(name) [\(wins)-\(losses)]"
|
|
/// }
|
|
/// }
|
|
///
|
|
/// The `DebugDescription` macro supports both `debugDescription`, `description`,
|
|
/// as well as a third option: a property named `_debugDescription`. The first
|
|
/// two are implemented when conforming to the `CustomDebugStringConvertible`
|
|
/// and `CustomStringConvertible` protocols. The additional `_debugDescription`
|
|
/// property is useful when both `debugDescription` and `description` are
|
|
/// implemented, but don't meet the requirements of the `DebugDescription`
|
|
/// macro. If `_debugDescription` is implemented, `DebugDescription` choose it
|
|
/// over `debugDescription` and `description`. Likewise, `debugDescription` is
|
|
/// preferred over `description`.
|
|
///
|
|
/// ### Description Requirements
|
|
///
|
|
/// The description implementation has the following requirements:
|
|
///
|
|
/// * The body of the description implementation must a single string
|
|
/// expression. String concatenation is not supported, use string interpolation
|
|
/// instead.
|
|
/// * String interpolation can reference stored properties only, functions calls
|
|
/// and other arbitrary computation are not supported. Of note, conditional
|
|
/// logic and computed properties are not supported.
|
|
/// * Overloaded string interpolation cannot be used.
|
|
@attached(memberAttribute)
|
|
public macro _DebugDescription() =
|
|
#externalMacro(module: "SwiftMacros", type: "DebugDescriptionMacro")
|
|
|
|
/// Internal-only macro. See `@_DebugDescription`.
|
|
@attached(peer, names: named(_lldb_summary))
|
|
public macro _DebugDescriptionProperty(_ debugIdentifier: String, _ computedProperties: [String]) =
|
|
#externalMacro(module: "SwiftMacros", type: "_DebugDescriptionPropertyMacro")
|
|
|
|
#endif
|
|
|
|
#if SWIFT_ENABLE_REFLECTION
|
|
|
|
@frozen // namespace
|
|
public enum _DebuggerSupport {
|
|
private enum CollectionStatus {
|
|
case notACollection
|
|
case collectionOfElements
|
|
case collectionOfPairs
|
|
case element
|
|
case pair
|
|
case elementOfPair
|
|
|
|
internal var isCollection: Bool {
|
|
return self != .notACollection
|
|
}
|
|
|
|
internal func getChildStatus(child: Mirror) -> CollectionStatus {
|
|
let disposition = child.displayStyle
|
|
|
|
if disposition == .collection { return .collectionOfElements }
|
|
if disposition == .dictionary { return .collectionOfPairs }
|
|
if disposition == .set { return .collectionOfElements }
|
|
|
|
if self == .collectionOfElements { return .element }
|
|
if self == .collectionOfPairs { return .pair }
|
|
if self == .pair { return .elementOfPair }
|
|
|
|
return .notACollection
|
|
}
|
|
}
|
|
|
|
private static func isClass(_ value: Any) -> Bool {
|
|
return type(of: value) is AnyClass
|
|
}
|
|
|
|
private static func checkValue<T>(
|
|
_ value: Any,
|
|
ifClass: (AnyObject) -> T,
|
|
otherwise: () -> T
|
|
) -> T {
|
|
if isClass(value) {
|
|
return ifClass(_unsafeDowncastToAnyObject(fromAny: value))
|
|
}
|
|
return otherwise()
|
|
}
|
|
|
|
private static func asObjectIdentifier(_ value: Any) -> ObjectIdentifier? {
|
|
return checkValue(value,
|
|
ifClass: { return ObjectIdentifier($0) },
|
|
otherwise: { return nil })
|
|
}
|
|
|
|
private static func asObjectAddress(_ value: Any) -> String {
|
|
let address = checkValue(value,
|
|
ifClass: { return unsafeBitCast($0, to: Int.self) },
|
|
otherwise: { return 0 })
|
|
return String(address, radix: 16, uppercase: false)
|
|
}
|
|
|
|
private static func asStringRepresentation(
|
|
value: Any?,
|
|
mirror: Mirror,
|
|
count: Int
|
|
) -> String? {
|
|
switch mirror.displayStyle {
|
|
case .optional? where count > 0:
|
|
return "\(mirror.subjectType)"
|
|
case .optional?:
|
|
return value.map(String.init(reflecting:))
|
|
case .collection?, .dictionary?, .set?, .tuple?:
|
|
return count == 1 ? "1 element" : "\(count) elements"
|
|
case .`struct`?, .`enum`?, nil:
|
|
switch value {
|
|
case let x as CustomDebugStringConvertible:
|
|
return x.debugDescription
|
|
case let x as CustomStringConvertible:
|
|
return x.description
|
|
case _ where count > 0:
|
|
return "\(mirror.subjectType)"
|
|
default:
|
|
return value.map(String.init(reflecting:))
|
|
}
|
|
case .`class`?:
|
|
switch value {
|
|
case let x as CustomDebugStringConvertible:
|
|
return x.debugDescription
|
|
case let x as CustomStringConvertible:
|
|
return x.description
|
|
case let x?:
|
|
// for a Class with no custom summary, mimic the Foundation default
|
|
return "<\(type(of: x)): 0x\(asObjectAddress(x))>"
|
|
default:
|
|
// but if I can't provide a value, just use the type anyway
|
|
return "\(mirror.subjectType)"
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func ivarCount(mirror: Mirror) -> Int {
|
|
let ivars = mirror.superclassMirror.map(ivarCount) ?? 0
|
|
return ivars + mirror.children.count
|
|
}
|
|
|
|
private static func shouldExpand(
|
|
mirror: Mirror,
|
|
collectionStatus: CollectionStatus,
|
|
isRoot: Bool
|
|
) -> Bool {
|
|
if isRoot || collectionStatus.isCollection { return true }
|
|
if !mirror.children.isEmpty { return true }
|
|
if mirror.displayStyle == .`class` { return true }
|
|
if let sc = mirror.superclassMirror { return ivarCount(mirror: sc) > 0 }
|
|
return true
|
|
}
|
|
|
|
private static func printForDebuggerImpl<StreamType: TextOutputStream>(
|
|
value: Any?,
|
|
mirror: Mirror,
|
|
name: String?,
|
|
indent: Int,
|
|
maxDepth: Int,
|
|
isRoot: Bool,
|
|
parentCollectionStatus: CollectionStatus,
|
|
refsAlreadySeen: inout Set<ObjectIdentifier>,
|
|
maxItemCounter: inout Int,
|
|
target: inout StreamType
|
|
) {
|
|
guard maxItemCounter > 0 else { return }
|
|
|
|
guard shouldExpand(mirror: mirror,
|
|
collectionStatus: parentCollectionStatus,
|
|
isRoot: isRoot)
|
|
else { return }
|
|
|
|
maxItemCounter -= 1
|
|
|
|
print(String(repeating: " ", count: indent), terminator: "", to: &target)
|
|
|
|
// do not expand classes with no custom Mirror
|
|
// yes, a type can lie and say it's a class when it's not since we only
|
|
// check the displayStyle - but then the type would have a custom Mirror
|
|
// anyway, so there's that...
|
|
let isNonClass = mirror.displayStyle != .`class`
|
|
let isCustomReflectable: Bool
|
|
if let value = value {
|
|
isCustomReflectable = value is CustomReflectable
|
|
} else {
|
|
isCustomReflectable = true
|
|
}
|
|
let willExpand = isNonClass || isCustomReflectable
|
|
|
|
let count = mirror.children.count
|
|
let bullet = isRoot && (count == 0 || !willExpand) ? ""
|
|
: count == 0 ? "- "
|
|
: maxDepth <= 0 ? "▹ " : "▿ "
|
|
print(bullet, terminator: "", to: &target)
|
|
|
|
let collectionStatus = parentCollectionStatus.getChildStatus(child: mirror)
|
|
|
|
if let name = name {
|
|
print("\(name) : ", terminator: "", to: &target)
|
|
}
|
|
|
|
if let str = asStringRepresentation(value: value, mirror: mirror, count: count) {
|
|
print(str, terminator: "", to: &target)
|
|
}
|
|
|
|
if (maxDepth <= 0) || !willExpand {
|
|
print("", to: &target)
|
|
return
|
|
}
|
|
|
|
if let valueIdentifier = value.flatMap(asObjectIdentifier) {
|
|
if refsAlreadySeen.contains(valueIdentifier) {
|
|
print(" { ... }", to: &target)
|
|
return
|
|
} else {
|
|
refsAlreadySeen.insert(valueIdentifier)
|
|
}
|
|
}
|
|
|
|
print("", to: &target)
|
|
|
|
var printedElements = 0
|
|
|
|
if let sc = mirror.superclassMirror {
|
|
printForDebuggerImpl(
|
|
value: nil,
|
|
mirror: sc,
|
|
name: "super",
|
|
indent: indent + 2,
|
|
maxDepth: maxDepth - 1,
|
|
isRoot: false,
|
|
parentCollectionStatus: .notACollection,
|
|
refsAlreadySeen: &refsAlreadySeen,
|
|
maxItemCounter: &maxItemCounter,
|
|
target: &target)
|
|
}
|
|
|
|
for (optionalName,child) in mirror.children {
|
|
let childName = optionalName ?? "\(printedElements)"
|
|
if maxItemCounter <= 0 {
|
|
print(String(repeating: " ", count: indent+4), terminator: "", to: &target)
|
|
let remainder = count - printedElements
|
|
print("(\(remainder)", terminator: "", to: &target)
|
|
if printedElements > 0 {
|
|
print(" more", terminator: "", to: &target)
|
|
}
|
|
print(remainder == 1 ? " child)" : " children)", to: &target)
|
|
return
|
|
}
|
|
|
|
printForDebuggerImpl(
|
|
value: child,
|
|
mirror: Mirror(reflecting: child),
|
|
name: childName,
|
|
indent: indent + 2,
|
|
maxDepth: maxDepth - 1,
|
|
isRoot: false,
|
|
parentCollectionStatus: collectionStatus,
|
|
refsAlreadySeen: &refsAlreadySeen,
|
|
maxItemCounter: &maxItemCounter,
|
|
target: &target)
|
|
printedElements += 1
|
|
}
|
|
}
|
|
|
|
public static func stringForPrintObject(_ value: Any) -> String {
|
|
var maxItemCounter = Int.max
|
|
var refs = Set<ObjectIdentifier>()
|
|
var target = ""
|
|
|
|
printForDebuggerImpl(
|
|
value: value,
|
|
mirror: Mirror(reflecting: value),
|
|
name: nil,
|
|
indent: 0,
|
|
maxDepth: maxItemCounter,
|
|
isRoot: true,
|
|
parentCollectionStatus: .notACollection,
|
|
refsAlreadySeen: &refs,
|
|
maxItemCounter: &maxItemCounter,
|
|
target: &target)
|
|
|
|
return target
|
|
}
|
|
}
|
|
|
|
public func _stringForPrintObject(_ value: Any) -> String {
|
|
return _DebuggerSupport.stringForPrintObject(value)
|
|
}
|
|
|
|
#endif // SWIFT_ENABLE_REFLECTION
|
|
|
|
public func _debuggerTestingCheckExpect(_: String, _: String) { }
|
|
|
|
@_alwaysEmitIntoClient @_transparent
|
|
internal func _withHeapObject<R>(
|
|
of object: AnyObject,
|
|
_ body: (UnsafeMutableRawPointer) -> R
|
|
) -> R {
|
|
defer { _fixLifetime(object) }
|
|
let unmanaged = Unmanaged.passUnretained(object)
|
|
return body(unmanaged.toOpaque())
|
|
}
|
|
|
|
@_extern(c, "swift_retainCount") @usableFromInline
|
|
internal func _swift_retainCount(_: UnsafeMutableRawPointer) -> Int
|
|
@_extern(c, "swift_unownedRetainCount") @usableFromInline
|
|
internal func _swift_unownedRetainCount(_: UnsafeMutableRawPointer) -> Int
|
|
@_extern(c, "swift_weakRetainCount") @usableFromInline
|
|
internal func _swift_weakRetainCount(_: UnsafeMutableRawPointer) -> Int
|
|
|
|
// Utilities to get refcount(s) of class objects.
|
|
@backDeployed(before: SwiftStdlib 5.11)
|
|
public func _getRetainCount(_ object: AnyObject) -> UInt {
|
|
let count = _withHeapObject(of: object) { _swift_retainCount($0) }
|
|
return UInt(bitPattern: count)
|
|
}
|
|
|
|
@backDeployed(before: SwiftStdlib 5.11)
|
|
public func _getUnownedRetainCount(_ object: AnyObject) -> UInt {
|
|
let count = _withHeapObject(of: object) { _swift_unownedRetainCount($0) }
|
|
return UInt(bitPattern: count)
|
|
}
|
|
|
|
@backDeployed(before: SwiftStdlib 5.11)
|
|
public func _getWeakRetainCount(_ object: AnyObject) -> UInt {
|
|
let count = _withHeapObject(of: object) { _swift_weakRetainCount($0) }
|
|
return UInt(bitPattern: count)
|
|
}
|