blob: ef0a7961916ece6bfa047cef8a400110324fdde7 [file] [log] [blame]
//===----------------- OSLogStringTypes.swift -----------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
// This file defines extensions for interpolating strings into an OSLogMesage.
// It defines `appendInterpolation` function for String type. It also defines
// extensions for serializing strings into the argument buffer passed to
// os_log ABIs. Note that os_log requires passing a stable pointer to an
// interpolated string.
//
// The `appendInterpolation` function defined in this file accept privacy and
// alignment options along with the interpolated expression as shown below:
//
// 1. "\(x, privacy: .private, align: .right\)"
// 2. "\(x, align: .right(columns: 10)\)"
extension OSLogInterpolation {
/// Defines interpolation for expressions of type String.
///
/// Do not call this function directly. It will be called automatically when interpolating
/// a value of type `String` in the string interpolations passed to the log APIs.
///
/// - Parameters:
/// - argumentString: The interpolated expression of type String, which is autoclosured.
/// - align: Left or right alignment with the minimum number of columns as
/// defined by the type `OSLogStringAlignment`.
/// - privacy: A privacy qualifier which is either private or public.
/// It is auto-inferred by default.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
@_semantics("oslog.requires_constant_arguments")
public mutating func appendInterpolation(
_ argumentString: @autoclosure @escaping () -> String,
align: OSLogStringAlignment = .none,
privacy: OSLogPrivacy = .auto
) {
guard argumentCount < maxOSLogArgumentCount else { return }
formatString += getStringFormatSpecifier(align, privacy)
// If minimum column width is specified, append this value first. Note that the
// format specifier would use a '*' for width e.g. %*s.
if let minColumns = align.minimumColumnWidth {
appendAlignmentArgument(minColumns)
}
// If the privacy has a mask, append the mask argument, which is a constant payload.
// Note that this should come after the width but before the precision.
if privacy.hasMask {
appendMaskArgument(privacy)
}
// Append the string argument.
addStringHeaders(privacy)
arguments.append(argumentString)
argumentCount += 1
stringArgumentCount += 1
}
/// Update preamble and append argument headers based on the parameters of
/// the interpolation.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
internal mutating func addStringHeaders(_ privacy: OSLogPrivacy) {
// Append argument header.
let header = getArgumentHeader(privacy: privacy, type: .string)
arguments.append(header)
// Append number of bytes needed to serialize the argument.
let byteCount = pointerSizeInBytes()
arguments.append(UInt8(byteCount))
// Increment total byte size by the number of bytes needed for this
// argument, which is the sum of the byte size of the argument and
// two bytes needed for the headers.
totalBytesForSerializingArguments += byteCount + 2
preamble = getUpdatedPreamble(privacy: privacy, isScalar: false)
}
/// Construct an os_log format specifier from the given parameters.
/// This function must be constant evaluable and all its arguments
/// must be known at compile time.
@inlinable
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func getStringFormatSpecifier(
_ align: OSLogStringAlignment,
_ privacy: OSLogPrivacy
) -> String {
var specifier = "%"
if let privacySpecifier = privacy.privacySpecifier {
specifier += "{"
specifier += privacySpecifier
specifier += "}"
}
if case .start = align.anchor {
specifier += "-"
}
if let _ = align.minimumColumnWidth {
specifier += "*"
}
specifier += "s"
return specifier
}
}
extension OSLogArguments {
/// Append an (autoclosured) interpolated expression of String type, passed to
/// `OSLogMessage.appendInterpolation`, to the array of closures tracked
/// by this instance.
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
internal mutating func append(_ value: @escaping () -> String) {
argumentClosures.append({ (position, _, stringArgumentOwners) in
serialize(
value(),
at: &position,
storingStringOwnersIn: &stringArgumentOwners)
})
}
}
/// Return the byte size of a pointer as strings are passed to the C os_log ABIs by
/// a stable pointer to its UTF8 bytes. Since pointers do not have a public
/// bitWidth property, and since MemoryLayout is not supported by the constant
/// evaluator, this function returns the byte size of Int, which must equal the
/// word length of the target architecture and hence the pointer size.
/// This function must be constant evaluable. Note that it is marked transparent
/// instead of @inline(__always) as it is used in optimize(none) functions.
@_transparent
@_alwaysEmitIntoClient
internal func pointerSizeInBytes() -> Int {
return Int.bitWidth &>> logBitsPerByte
}
/// Serialize a stable pointer to the string `stringValue` at the buffer location
/// pointed to by `bufferPosition`.
@_alwaysEmitIntoClient
@inline(__always)
internal func serialize(
_ stringValue: String,
at bufferPosition: inout UnsafeMutablePointer<UInt8>,
storingStringOwnersIn stringArgumentOwners: inout ObjectStorage<Any>
) {
let stringPointer =
getNullTerminatedUTF8Pointer(
stringValue,
storingStringOwnersIn: &stringArgumentOwners)
let byteCount = pointerSizeInBytes()
let dest =
UnsafeMutableRawBufferPointer(start: bufferPosition, count: byteCount)
withUnsafeBytes(of: stringPointer) { dest.copyMemory(from: $0) }
bufferPosition += byteCount
}
/// Return a pointer that points to a contiguous sequence of null-terminated,
/// UTF8 charcters. If necessary, extends the lifetime of `stringValue` by
/// using `stringArgumentOwners`.
@_alwaysEmitIntoClient
@inline(never)
internal func getNullTerminatedUTF8Pointer(
_ stringValue: String,
storingStringOwnersIn stringArgumentOwners: inout ObjectStorage<Any>
) -> UnsafeRawPointer {
let (optStorage, bytePointer, _, _, _):
(AnyObject?, UnsafeRawPointer, Int, Bool, Bool) =
stringValue._deconstructUTF8(scratch: nil)
if let storage = optStorage {
initializeAndAdvance(&stringArgumentOwners, to: storage)
} else {
initializeAndAdvance(&stringArgumentOwners, to: stringValue._guts)
}
return bytePointer
}