//===----------------- 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 @usableFromInline internal typealias ObjectStorage = UnsafeMutablePointer? @usableFromInline internal typealias ArgumentClosures = [(inout ByteBufferPointer, inout ObjectStorage, inout ObjectStorage) -> ()] /// 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( capacity: Int, type: T.Type ) -> ObjectStorage { return capacity == 0 ? nil : UnsafeMutablePointer.allocate(capacity: capacity) } @_alwaysEmitIntoClient @inline(__always) internal func initializeAndAdvance( _ storageOpt: inout ObjectStorage, 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(_ storageOpt: ObjectStorage, count: Int) { // This if statement should get optimized away. if let storage = storageOpt { storage.deinitialize(count: count) storage.deallocate() } }