Files
swift-mirror/stdlib/public/core/DebuggerSupport.swift
Yuta Saito 7df19b86ea [stdlib] Fix calling convention mismatch for debugger utility functions (#69352)
This is the 3rd attempt to fix the mismatch, where the actual definition
(e.g. `swift_retainCount`) are defined with C calling-convention and the
callers wrongly expect Swift calling-convention.

The 1st fix broke ABI compatibility by introducing new symbol references
from app-side without any availability checks.
The 2nd fix broke lldb's retain counting feature due to new x-ref to
Clang module in serialized function body by `@_alwaysEmitIntoClient`.

This attemps to avoid introducing serialized x-ref to Clang module by
using new `@_extern(c)` attribute.

Resolves rdar://113910821

Co-authored-by: Karoy Lorentey <klorentey@apple.com>
2023-11-08 10:45:28 -08:00

306 lines
9.2 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
#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)
}