blob: e4eac6de3dc499e1a451f9201372a6ea39310b87 [file] [log] [blame]
//===--- 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
//
//===----------------------------------------------------------------------===//
public enum _DebuggerSupport {
@_versioned // FIXME(sil-serialize-all)
internal enum CollectionStatus {
case NotACollection
case CollectionOfElements
case CollectionOfPairs
case Element
case Pair
case ElementOfPair
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal var isCollection: Bool {
return self != .NotACollection
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal func getChildStatus(child: Mirror) -> CollectionStatus {
let disposition = child.displayStyle ?? .struct
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
}
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func isClass(_ value: Any) -> Bool {
if let _ = type(of: value) as? AnyClass {
return true
}
return false
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func checkValue<T>(
_ value: Any,
ifClass: (AnyObject) -> T,
otherwise: () -> T
) -> T {
if isClass(value) {
return ifClass(_unsafeDowncastToAnyObject(fromAny: value))
}
return otherwise()
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func asObjectIdentifier(_ value: Any) -> ObjectIdentifier? {
return checkValue(value,
ifClass: { return ObjectIdentifier($0) },
otherwise: { return nil })
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func asNumericValue(_ value: Any) -> Int {
return checkValue(value,
ifClass: { return unsafeBitCast($0, to: Int.self) },
otherwise: { return 0 })
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func asStringRepresentation(
value: Any?,
mirror: Mirror,
count: Int
) -> String? {
let ds = mirror.displayStyle ?? .`struct`
switch ds {
case .optional:
if count > 0 {
return "\(mirror.subjectType)"
}
else {
if let x = value {
return String(reflecting: x)
}
}
case .collection:
fallthrough
case .dictionary:
fallthrough
case .set:
fallthrough
case .tuple:
if count == 1 {
return "1 element"
} else {
return "\(count) elements"
}
case .`struct`:
fallthrough
case .`enum`:
if let x = value {
if let cdsc = (x as? CustomDebugStringConvertible) {
return cdsc.debugDescription
}
if let csc = (x as? CustomStringConvertible) {
return csc.description
}
}
if count > 0 {
return "\(mirror.subjectType)"
}
case .`class`:
if let x = value {
if let cdsc = (x as? CustomDebugStringConvertible) {
return cdsc.debugDescription
}
if let csc = (x as? CustomStringConvertible) {
return csc.description
}
// for a Class with no custom summary, mimic the Foundation default
return "<\(type(of: x)): 0x\(String(asNumericValue(x), radix: 16, uppercase: false))>"
} else {
// but if I can't provide a value, just use the type anyway
return "\(mirror.subjectType)"
}
}
if let x = value {
return String(reflecting: x)
}
return nil
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func ivarCount(mirror: Mirror) -> Int {
let count = Int(mirror.children.count)
if let sc = mirror.superclassMirror {
return ivarCount(mirror: sc) + count
} else {
return count
}
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal static func shouldExpand(
mirror: Mirror,
collectionStatus: CollectionStatus,
isRoot: Bool
) -> Bool {
if isRoot || collectionStatus.isCollection { return true }
let count = Int(mirror.children.count)
if count > 0 { return true }
if let sc = mirror.superclassMirror {
return ivarCount(mirror: sc) > 0
} else {
return true
}
}
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
internal 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,
targetStream: inout StreamType
) {
if maxItemCounter <= 0 {
return
}
if !shouldExpand(mirror: mirror,
collectionStatus: parentCollectionStatus,
isRoot: isRoot) {
return
}
maxItemCounter -= 1
for _ in 0..<indent {
print(" ", terminator: "", to: &targetStream)
}
// 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...
var willExpand = true
if let ds = mirror.displayStyle {
if ds == .`class` {
if let x = value {
if !(x is CustomReflectable) {
willExpand = false
}
}
}
}
let count = Int(mirror.children.count)
let bullet = isRoot && (count == 0 || !willExpand) ? ""
: count == 0 ? "- "
: maxDepth <= 0 ? "▹ " : "▿ "
print("\(bullet)", terminator: "", to: &targetStream)
let collectionStatus = parentCollectionStatus.getChildStatus(child: mirror)
if let nam = name {
print("\(nam) : ", terminator: "", to: &targetStream)
}
if let str = asStringRepresentation(value: value, mirror: mirror, count: count) {
print("\(str)", terminator: "", to: &targetStream)
}
if (maxDepth <= 0) || !willExpand {
print("", to: &targetStream)
return
}
if let x = value {
if let valueIdentifier = asObjectIdentifier(x) {
if refsAlreadySeen.contains(valueIdentifier) {
print(" { ... }", to: &targetStream)
return
} else {
refsAlreadySeen.insert(valueIdentifier)
}
}
}
print("", to: &targetStream)
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,
targetStream: &targetStream)
}
for (optionalName,child) in mirror.children {
let childName = optionalName ?? "\(printedElements)"
if maxItemCounter <= 0 {
for _ in 0..<(indent+4) {
print(" ", terminator: "", to: &targetStream)
}
let remainder = count - printedElements
print("(\(remainder)", terminator: "", to: &targetStream)
if printedElements > 0 {
print(" more", terminator: "", to: &targetStream)
}
if remainder == 1 {
print(" child)", to: &targetStream)
} else {
print(" children)", to: &targetStream)
}
return
}
printForDebuggerImpl(
value: child,
mirror: Mirror(reflecting: child),
name: childName,
indent: indent + 2,
maxDepth: maxDepth - 1,
isRoot: false,
parentCollectionStatus: collectionStatus,
refsAlreadySeen: &refsAlreadySeen,
maxItemCounter: &maxItemCounter,
targetStream: &targetStream)
printedElements += 1
}
}
// LLDB uses this function in expressions, and if it is inlined the resulting
// LLVM IR is enormous. As a result, to improve LLDB performance we are not
// making it @_inlineable.
public static func stringForPrintObject(_ value: Any) -> String {
var maxItemCounter = Int.max
var refs = Set<ObjectIdentifier>()
var targetStream = ""
printForDebuggerImpl(
value: value,
mirror: Mirror(reflecting: value),
name: nil,
indent: 0,
maxDepth: maxItemCounter,
isRoot: true,
parentCollectionStatus: .NotACollection,
refsAlreadySeen: &refs,
maxItemCounter: &maxItemCounter,
targetStream: &targetStream)
return targetStream
}
}