mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The compiler stubs for testing the OSLog implementation are in need of an update. This change updates the stubs to be consistent with the current OSLog implementation, updates the existing tests, and adds new tests for String and metatype interpolations. rdar://69719729
359 lines
12 KiB
Swift
359 lines
12 KiB
Swift
//===----------------- OSLogMessage.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 contains data structures and helper functions that are used by
|
|
// the new OS log APIs.
|
|
|
|
import ObjectiveC
|
|
|
|
/// Maximum number of arguments i.e., interpolated expressions that can
|
|
/// be used in the string interpolations passed to the log APIs.
|
|
/// This limit is imposed by the logging system.
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
public var maxOSLogArgumentCount: UInt8 { return 48 }
|
|
|
|
// Note that this is marked transparent instead of @inline(__always) as it is
|
|
// used in optimize(none) functions.
|
|
@_transparent
|
|
@_alwaysEmitIntoClient
|
|
internal var logBitsPerByte: Int { return 3 }
|
|
|
|
/// Represents a string interpolation passed to the log APIs.
|
|
///
|
|
/// This type converts (through its methods) the given string interpolation into
|
|
/// a C-style format string and a sequence of arguments.
|
|
///
|
|
/// - Warning: Do not explicitly refer to this type. It will be implicitly created
|
|
/// by the compiler when you pass a string interpolation to the log APIs.
|
|
@frozen
|
|
public struct OSLogInterpolation : StringInterpolationProtocol {
|
|
/// A format string constructed from the given string interpolation to be
|
|
/// passed to the os_log ABI.
|
|
@usableFromInline
|
|
internal var formatString: String
|
|
|
|
/// A representation of a sequence of arguments that must be serialized
|
|
/// to a byte buffer and passed to the os_log ABI. Each argument, which is
|
|
/// an (autoclosured) expressions that is interpolated, is prepended with a
|
|
/// two byte header. The first header byte consists of a four bit flag and
|
|
/// a four bit type. The second header byte has the size of the argument in
|
|
/// bytes. This is schematically illustrated below.
|
|
/// ----------------------------
|
|
/// | 4-bit type | 4-bit flag |
|
|
/// ----------------------------
|
|
/// | 1st argument size in bytes|
|
|
/// ----------------------------
|
|
/// | 1st argument bytes |
|
|
/// ----------------------------
|
|
/// | 4-bit type | 4-bit flag |
|
|
/// -----------------------------
|
|
/// | 2nd argument size in bytes|
|
|
/// ----------------------------
|
|
/// | 2nd argument bytes |
|
|
/// ----------------------------
|
|
/// ...
|
|
@usableFromInline
|
|
internal var arguments: OSLogArguments
|
|
|
|
/// The possible values for the argument type, as defined by the os_log ABI,
|
|
/// which occupies four most significant bits of the first byte of the
|
|
/// argument header. The rawValue of this enum must be constant evaluable.
|
|
/// (Note that an auto-generated rawValue is not constant evaluable because
|
|
/// it cannot be annotated so.)
|
|
@usableFromInline
|
|
internal enum ArgumentType {
|
|
case scalar, count, string, pointer, object, mask
|
|
|
|
@inlinable
|
|
internal var rawValue: UInt8 {
|
|
switch self {
|
|
case .scalar:
|
|
return 0
|
|
case .count:
|
|
return 1
|
|
case .string:
|
|
return 2
|
|
case .pointer:
|
|
return 3
|
|
case .mask:
|
|
return 7
|
|
default: //.object
|
|
return 4
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The first summary byte in the byte buffer passed to the os_log ABI that
|
|
/// summarizes the privacy and nature of the arguments.
|
|
@usableFromInline
|
|
internal var preamble: UInt8
|
|
|
|
/// Denotes the bit that indicates whether there is private argument.
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
internal var privateBitMask: UInt8 { 0x1 }
|
|
|
|
/// Denotes the bit that indicates whether there is non-scalar argument:
|
|
/// String, NSObject or Pointer.
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
internal var nonScalarBitMask: UInt8 { 0x2 }
|
|
|
|
/// The second summary byte that denotes the number of arguments, which is
|
|
/// also the number of interpolated expressions. This will be determined
|
|
/// on the fly in order to support concatenation and interpolation of
|
|
/// instances of `OSLogMessage`.
|
|
@usableFromInline
|
|
internal var argumentCount: UInt8
|
|
|
|
/// Sum total of all the bytes (including header bytes) needed for
|
|
/// serializing the arguments.
|
|
@usableFromInline
|
|
internal var totalBytesForSerializingArguments: Int
|
|
|
|
/// The number of arguments that are Strings. This count is used to create
|
|
/// auxiliary storage meant for extending the lifetime of the string arguments
|
|
/// until the log call completes.
|
|
@usableFromInline
|
|
internal var stringArgumentCount: Int
|
|
|
|
/// The number of arguments that are NSObjects. This count is used to create
|
|
/// auxiliary storage meant for extending the lifetime of the NSObject
|
|
/// arguments until the log call completes.
|
|
@usableFromInline
|
|
internal var objectArgumentCount: Int
|
|
|
|
// Some methods defined below are marked @_optimize(none) to prevent inlining
|
|
// of string internals (such as String._StringGuts) which will interfere with
|
|
// constant evaluation and folding. Note that these methods will be inlined,
|
|
// constant evaluated/folded and optimized in the context of a caller.
|
|
|
|
@_semantics("oslog.interpolation.init")
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
public init(literalCapacity: Int, interpolationCount: Int) {
|
|
// Since the format string and the arguments array are fully constructed
|
|
// at compile time, the parameters are ignored.
|
|
formatString = ""
|
|
arguments = OSLogArguments()
|
|
preamble = 0
|
|
argumentCount = 0
|
|
totalBytesForSerializingArguments = 0
|
|
stringArgumentCount = 0
|
|
objectArgumentCount = 0
|
|
}
|
|
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
public mutating func appendLiteral(_ literal: String) {
|
|
formatString += literal.percentEscapedString
|
|
}
|
|
|
|
/// `appendInterpolation` conformances will be added by extensions to this type.
|
|
|
|
/// Compute a byte-sized argument header consisting of flag and type.
|
|
/// Flag and type take up the least and most significant four bits
|
|
/// of the header byte, respectively.
|
|
/// This function should be constant evaluable.
|
|
@inlinable
|
|
@_semantics("constant_evaluable")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
internal func getArgumentHeader(
|
|
privacy: OSLogPrivacy,
|
|
type: ArgumentType
|
|
) -> UInt8 {
|
|
return (type.rawValue &<< 4) | privacy.argumentFlag
|
|
}
|
|
|
|
/// Compute the new preamble based whether the current argument is private
|
|
/// or not. This function must be constant evaluable.
|
|
@inlinable
|
|
@_semantics("constant_evaluable")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
internal func getUpdatedPreamble(
|
|
privacy: OSLogPrivacy,
|
|
isScalar: Bool
|
|
) -> UInt8 {
|
|
var preamble = self.preamble
|
|
if privacy.isAtleastPrivate {
|
|
preamble |= privateBitMask
|
|
}
|
|
if !isScalar || privacy.hasMask {
|
|
preamble |= nonScalarBitMask
|
|
}
|
|
return preamble
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
/// Replace all percents "%" in the string by "%%" so that the string can be
|
|
/// interpreted as a C format string. This function is constant evaluable
|
|
/// and its semantics is modeled within the evaluator.
|
|
@inlinable
|
|
internal var percentEscapedString: String {
|
|
@_semantics("string.escapePercent.get")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
get {
|
|
return self
|
|
.split(separator: "%", omittingEmptySubsequences: false)
|
|
.joined(separator: "%%")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a message passed to the log APIs. This type should be created
|
|
/// from a string interpolation or a string literal.
|
|
///
|
|
/// Do not explicitly refer to this type. It will be implicitly created
|
|
/// by the compiler when you pass a string interpolation to the log APIs.
|
|
@frozen
|
|
public struct OSLogMessage :
|
|
ExpressibleByStringInterpolation, ExpressibleByStringLiteral
|
|
{
|
|
public let interpolation: OSLogInterpolation
|
|
|
|
@inlinable
|
|
@_optimize(none)
|
|
@_semantics("oslog.message.init_interpolation")
|
|
@_semantics("constant_evaluable")
|
|
public init(stringInterpolation: OSLogInterpolation) {
|
|
self.interpolation = stringInterpolation
|
|
}
|
|
|
|
@inlinable
|
|
@_optimize(none)
|
|
@_semantics("oslog.message.init_stringliteral")
|
|
@_semantics("constant_evaluable")
|
|
public init(stringLiteral value: String) {
|
|
var s = OSLogInterpolation(literalCapacity: 1, interpolationCount: 0)
|
|
s.appendLiteral(value)
|
|
self.interpolation = s
|
|
}
|
|
|
|
/// The byte size of the buffer that will be passed to the logging system.
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
public var bufferSize: Int {
|
|
// The two additional bytes is for the preamble and argument count.
|
|
return interpolation.totalBytesForSerializingArguments + 2
|
|
}
|
|
}
|
|
|
|
@usableFromInline
|
|
internal typealias ByteBufferPointer = UnsafeMutablePointer<UInt8>
|
|
@usableFromInline
|
|
internal typealias ObjectStorage<T> = UnsafeMutablePointer<T>?
|
|
@usableFromInline
|
|
internal typealias ArgumentClosures =
|
|
[(inout ByteBufferPointer,
|
|
inout ObjectStorage<NSObject>,
|
|
inout ObjectStorage<Any>) -> ()]
|
|
|
|
/// A representation of a sequence of arguments and headers (of possibly
|
|
/// different types) that have to be serialized to a byte buffer. The arguments
|
|
/// are captured within closures and stored in an array. The closures accept an
|
|
/// instance of `OSLogByteBufferBuilder`, and when invoked, serialize the
|
|
/// argument using the passed `OSLogByteBufferBuilder` instance.
|
|
@frozen
|
|
@usableFromInline
|
|
internal struct OSLogArguments {
|
|
/// An array of closures that captures arguments of possibly different types.
|
|
/// Each closure accepts a pointer into a byte buffer and serializes the
|
|
/// captured arguments at the pointed location. The closures also accept an
|
|
/// array of AnyObject to store references to auxiliary storage created during
|
|
/// serialization.
|
|
@usableFromInline
|
|
internal var argumentClosures: ArgumentClosures
|
|
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
internal init() {
|
|
argumentClosures = []
|
|
}
|
|
|
|
/// Append a byte-sized header, constructed by
|
|
/// `OSLogMessage.appendInterpolation`, to the tracked array of closures.
|
|
@_semantics("constant_evaluable")
|
|
@inlinable
|
|
@_optimize(none)
|
|
internal mutating func append(_ header: UInt8) {
|
|
argumentClosures.append({ (position, _, _) in
|
|
serialize(header, at: &position)
|
|
})
|
|
}
|
|
|
|
/// `append` for other types must be implemented by extensions.
|
|
}
|
|
|
|
/// Serialize a UInt8 value at the buffer location pointed to by `bufferPosition`,
|
|
/// and increment the `bufferPosition` with the byte size of the serialized value.
|
|
@_alwaysEmitIntoClient
|
|
@inline(__always)
|
|
internal func serialize(
|
|
_ value: UInt8,
|
|
at bufferPosition: inout ByteBufferPointer)
|
|
{
|
|
bufferPosition[0] = value
|
|
bufferPosition += 1
|
|
}
|
|
|
|
// The following code defines helper functions for creating and maintaining
|
|
// a buffer for holding a fixed number for instances of a type T. Such buffers
|
|
// are used to hold onto NSObjects and Strings that are interpolated in the log
|
|
// message until the end of the log call.
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(__always)
|
|
internal func createStorage<T>(
|
|
capacity: Int,
|
|
type: T.Type
|
|
) -> ObjectStorage<T> {
|
|
return
|
|
capacity == 0 ?
|
|
nil :
|
|
UnsafeMutablePointer<T>.allocate(capacity: capacity)
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(__always)
|
|
internal func initializeAndAdvance<T>(
|
|
_ storageOpt: inout ObjectStorage<T>,
|
|
to value: T
|
|
) {
|
|
// This if statement should get optimized away.
|
|
if let storage = storageOpt {
|
|
storage.initialize(to: value)
|
|
storageOpt = storage.advanced(by: 1)
|
|
}
|
|
}
|
|
|
|
@_alwaysEmitIntoClient
|
|
@inline(__always)
|
|
internal func destroyStorage<T>(_ storageOpt: ObjectStorage<T>, count: Int) {
|
|
// This if statement should get optimized away.
|
|
if let storage = storageOpt {
|
|
storage.deinitialize(count: count)
|
|
storage.deallocate()
|
|
}
|
|
}
|