blob: 9aff68ade3bdc3c2988e1a4c5cbcaa9f36527b4d [file] [log] [blame]
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -swift-version 5 -DPTR_SIZE_%target-ptrsize -o %t/OSLogExecutionTest
// RUN: %target-codesign %t/OSLogExecutionTest
// RUN: %target-run %t/OSLogExecutionTest
//
// RUN: %target-build-swift %s -O -swift-version 5 -DPTR_SIZE_%target-ptrsize -o %t/OSLogExecutionTest
// RUN: %target-codesign %t/OSLogExecutionTest
// RUN: %target-run %t/OSLogExecutionTest
// REQUIRES: executable_test
// REQUIRES: foundation
//
// REQUIRES: VENDOR=apple
// Run-time tests for testing the correctness of the optimizations that optimize the
// construction of the format string and the byte buffer from a string interpolation.
// The tests here are run with -Onone (which includes only mandatory optimizations)
// and also with full optimizations -O.
import OSLogTestHelper
import StdlibUnittest
import Foundation
defer { runAllTests() }
internal var InterpolationTestSuite = TestSuite("OSLogInterpolationTest")
internal let bitsPerByte = 8
/// A struct that provides methods for checking whether a given byte buffer
/// conforms to the format expected by the os_log ABI. This struct acts as
/// a specification of the byte buffer format.
internal struct OSLogBufferChecker {
internal let buffer: UnsafeBufferPointer<UInt8>
internal init(_ byteBuffer: UnsafeBufferPointer<UInt8>) {
buffer = byteBuffer
}
/// Bit mask for setting bits in the peamble. The bits denoted by the bit
/// mask indicate whether there is an argument that is private, and whether
/// there is an argument that is non-scalar: String, NSObject or Pointer.
internal enum PreambleBitMask: UInt8 {
case privateBitMask = 0x1
case nonScalarBitMask = 0x2
}
/// Check the summary bytes of the byte buffer.
/// - Parameters:
/// - argumentCount: number of arguments expected to be in the buffer.
/// - hasPrivate: true iff there exists a private argument
/// - hasNonScalar: true iff there exists a non-scalar argument
internal func checkSummaryBytes(
argumentCount: UInt8,
hasPrivate: Bool,
hasNonScalar: Bool
) {
let preamble = buffer[0]
let privateBit = preamble & PreambleBitMask.privateBitMask.rawValue
expectEqual(hasPrivate, privateBit != 0)
let nonScalarBit = preamble & PreambleBitMask.nonScalarBitMask.rawValue
expectEqual(hasNonScalar, nonScalarBit != 0)
expectEqual(argumentCount, buffer[1])
}
/// The possible values for the argument flag as defined by the os_log ABI.
/// This occupies four least significant bits of the first byte of the
/// argument header. Two least significant bits are used to indicate privacy
/// and the other two bits are reserved.
internal enum ArgumentFlag: UInt8 {
case autoFlag = 0
case privateFlag = 0x1
case publicFlag = 0x2
}
/// The possible values for the argument type as defined by the os_log ABI.
/// This occupies four most significant bits of the first byte of the
/// argument header.
internal enum ArgumentType: UInt8 {
case scalar = 0, count = 1, string = 2, pointer = 3, object = 4, mask = 7
// TODO: include wide string and errno here if needed.
}
/// Check the encoding of an argument headers in the byte buffer starting from
/// the `startIndex` and return argument bytes.
private func checkArgumentHeadersAndGetBytes(
startIndex: Int,
size: UInt8,
flag: ArgumentFlag,
type: ArgumentType
) -> [UInt8] {
let argumentHeader = buffer[startIndex]
expectEqual((type.rawValue << 4) | flag.rawValue, argumentHeader)
expectEqual(size, buffer[startIndex + 1])
// Argument data starts after the two header bytes.
let argumentBytes: [UInt8] =
(0..<Int(size)).reduce(into: []) { (acc, index) in
acc.append(buffer[startIndex + 2 + index])
}
return argumentBytes
}
/// Check whether the bytes starting from `startIndex` contain the encoding for a
/// numerical value.
internal func checkNumeric<T>(
startIndex: Int,
flag: ArgumentFlag,
type: ArgumentType,
expectedValue: T
) where T : Numeric {
let byteSize = UInt8(MemoryLayout<T>.size)
let argumentBytes =
checkArgumentHeadersAndGetBytes(
startIndex: startIndex,
size: byteSize,
flag: flag,
type: type)
withUnsafeBytes(of: expectedValue) { expectedBytes in
for i in 0..<Int(byteSize) {
expectEqual(
expectedBytes[i],
argumentBytes[i],
"mismatch at byte number \(i) "
+ "of the expected value \(expectedValue)")
}
}
}
/// Check whether the bytes starting from `startIndex` contain the encoding for an Int.
internal func checkInt<T>(
startIndex: Int,
flag: ArgumentFlag,
expectedInt: T
) where T : FixedWidthInteger {
checkNumeric(
startIndex: startIndex,
flag: flag,
type: .scalar,
expectedValue: expectedInt)
}
/// Check whether the bytes starting from `startIndex` contain the encoding for a count.
internal func checkCount(
startIndex: Int,
expectedCount: Int,
precision: Bool
) {
checkNumeric(
startIndex: startIndex,
flag: .autoFlag,
type: precision ? .count : .scalar,
expectedValue: CInt(expectedCount))
}
/// Check whether the bytes starting from `startIndex` contain the encoding
/// for a string.
internal func checkString(
startIndex: Int,
flag: ArgumentFlag,
expectedString: String
) {
let pointerSize = UInt8(MemoryLayout<UnsafePointer<Int8>>.size)
let argumentBytes =
checkArgumentHeadersAndGetBytes(
startIndex: startIndex,
size: pointerSize,
flag: flag,
type: .string)
// Read the pointer to a string stored in the buffer and compare it with
// the expected string using `strcmp`. Note that it is important we use a
// C function here to compare the string as it more closely represents
// the C os_log functions.
var stringAddress: Int = 0
// Copy the bytes of the address byte by byte. Note that
// RawPointer.load(fromByteOffset:,_) function cannot be used here as the
// address: `buffer + offset` is not aligned for reading an Int.
for i in 0..<Int(pointerSize) {
stringAddress |= Int(argumentBytes[i]) << (8 * i)
}
let bufferDataPointer = UnsafePointer<Int8>(bitPattern: stringAddress)
expectedString.withCString {
let compareResult = strcmp($0, bufferDataPointer)
expectEqual(0, compareResult, "strcmp returned \(compareResult)")
}
}
/// Check whether the bytes starting from `startIndex` contain the encoding
/// for an NSObject.
internal func checkNSObject(
startIndex: Int,
flag: ArgumentFlag,
expectedObject: NSObject
) {
let pointerSize = UInt8(MemoryLayout<UnsafePointer<Int8>>.size)
let argumentBytes =
checkArgumentHeadersAndGetBytes(
startIndex: startIndex,
size: pointerSize,
flag: flag,
type: .object)
// Convert data to a pointer and check if the addresses stored in the
// pointer and the one in the buffer match.
let objectAddress =
Unmanaged
.passUnretained(expectedObject)
.toOpaque()
withUnsafeBytes(of: objectAddress) { expectedBytes in
for i in 0..<Int(pointerSize) {
// Argument data starts after the two header bytes.
expectEqual(
expectedBytes[i],
argumentBytes[i],
"mismatch at byte number \(i) "
+ "of the expected object address \(objectAddress)")
}
}
}
internal func checkDouble(
startIndex: Int,
flag: ArgumentFlag,
expectedValue: Double
) {
let byteSize: UInt8 = 8
let argumentBytes =
checkArgumentHeadersAndGetBytes(
startIndex: startIndex,
size: byteSize,
flag: flag,
type: .scalar)
withUnsafeBytes(of: expectedValue) { expectedBytes in
for i in 0..<Int(byteSize) {
expectEqual(
expectedBytes[i],
argumentBytes[i],
"mismatch at byte number \(i) "
+ "of the expected value \(expectedValue)")
}
}
}
/// Check the given assertions on the arguments stored in the byte buffer.
/// - Parameters:
/// - assertions: one assertion for each argument stored in the byte buffer.
internal func checkArguments(_ assertions: [(Int) -> Void]) {
var currentArgumentIndex = 2
for assertion in assertions {
expectTrue(currentArgumentIndex < buffer.count)
assertion(currentArgumentIndex)
// Advance the index to the next argument by adding the size of the
// current argument and the two bytes of headers.
currentArgumentIndex += 2 + Int(buffer[currentArgumentIndex + 1])
}
}
/// Check a sequence of assertions on the arguments stored in the byte buffer.
internal func checkArguments(_ assertions: (Int) -> Void ...) {
checkArguments(assertions)
}
}
InterpolationTestSuite.test("integer literal") {
_osLogTestHelper(
"An integer literal \(10)",
assertion: { (formatString, buffer) in
expectEqual(
"An integer literal %ld",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(startIndex: $0, flag: .autoFlag, expectedInt: 10)
})
})
}
InterpolationTestSuite.test("integer with formatting") {
_osLogTestHelper(
"Minimum integer value: \(UInt.max, format: .hex)",
assertion: { (formatString, buffer) in
expectEqual(
"Minimum integer value: %lx",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: UInt.max)
})
})
}
InterpolationTestSuite.test("integer with privacy and formatting") {
let addr: UInt = 0x7afebabe
_osLogTestHelper(
"Access to invalid address: \(addr, format: .hex, privacy: .private)",
assertion: { (formatString, buffer) in
expectEqual(
"Access to invalid address: %{private}lx",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .privateFlag,
expectedInt: addr)
})
})
}
InterpolationTestSuite.test("test multiple arguments") {
let filePerms: UInt = 0o777
let pid = 122225
let privateID = 0x79abcdef
_osLogTestHelper(
"""
Access prevented: process \(pid, privacy: .public) initiated by \
user: \(privateID, privacy: .private) attempted resetting \
permissions to \(filePerms, format: .octal)
""",
assertion: { (formatString, buffer) in
expectEqual(
"""
Access prevented: process %{public}ld initiated by \
user: %{private}ld attempted resetting permissions \
to %lo
""",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 3,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments(
{
bufferChecker.checkInt(
startIndex: $0,
flag: .publicFlag,
expectedInt: pid)
},
{
bufferChecker.checkInt(
startIndex: $0,
flag: .privateFlag,
expectedInt: privateID)
},
{
bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: filePerms)
})
})
}
InterpolationTestSuite.test("interpolation of too many arguments") {
// The following string interpolation has 49 interpolated values. Only 48
// of these must be present in the generated format string and byte buffer.
_osLogTestHelper(
"""
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \(1) \
\(1) \(1) \(1) \(1) \(1) \(1) \(1)
""",
assertion: { (formatString, buffer) in
expectEqual(
String(
repeating: "%ld ",
count: Int(maxOSLogArgumentCount)),
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: maxOSLogArgumentCount,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments(
Array(
repeating: {
bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: 1) },
count: Int(maxOSLogArgumentCount))
)
})
}
InterpolationTestSuite.test("string interpolations with percents") {
_osLogTestHelper(
"a = (c % d)%%",
assertion: { (formatString, buffer) in
expectEqual("a = (c %% d)%%%%", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 0,
hasPrivate: false,
hasNonScalar: false)
})
}
InterpolationTestSuite.test("integer types") {
_osLogTestHelper("Int32 max: \(Int32.max)") {
(formatString, buffer) in
expectEqual("Int32 max: %d", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 1,
hasPrivate: false,
hasNonScalar: false)
bufferChecker.checkArguments({
bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: Int32.max)
})
}
}
InterpolationTestSuite.test("string arguments") {
let small = "a"
let large = "this is a large string"
_osLogTestHelper(
"small: \(small, privacy: .public) large: \(large)") {
(formatString, buffer) in
expectEqual("small: %{public}s large: %s", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 2,
hasPrivate: false,
hasNonScalar: true
)
bufferChecker.checkArguments({
bufferChecker.checkString(
startIndex: $0,
flag: .publicFlag,
expectedString: small)
},
{ bufferChecker.checkString(
startIndex: $0,
flag: .autoFlag,
expectedString: large)
})
}
}
InterpolationTestSuite.test("dynamic strings") {
let concatString = "hello" + " - " + "world"
let interpolatedString = "\(31) trillion digits of pi are known so far"
_osLogTestHelper(
"""
concat: \(concatString, privacy: .public) \
interpolated: \(interpolatedString, privacy: .private)
""") { (formatString, buffer) in
expectEqual("concat: %{public}s interpolated: %{private}s", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 2,
hasPrivate: true,
hasNonScalar: true
)
bufferChecker.checkArguments({
bufferChecker.checkString(
startIndex: $0,
flag: .publicFlag,
expectedString: concatString)
},
{ bufferChecker.checkString(
startIndex: $0,
flag: .privateFlag,
expectedString: interpolatedString)
})
}
}
InterpolationTestSuite.test("NSObject") {
let nsArray: NSArray = [0, 1, 2]
let nsDictionary: NSDictionary = [1 : ""]
_osLogTestHelper(
"""
NSArray: \(nsArray, privacy: .public) \
NSDictionary: \(nsDictionary)
""") { (formatString, buffer) in
expectEqual("NSArray: %{public}@ NSDictionary: %@", formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 2,
hasPrivate: false,
hasNonScalar: true
)
bufferChecker.checkArguments({
bufferChecker.checkNSObject(
startIndex: $0,
flag: .publicFlag,
expectedObject: nsArray)
},
{ bufferChecker.checkNSObject(
startIndex: $0,
flag: .autoFlag,
expectedObject: nsDictionary)
})
}
}
// A generic function.
func toString<T>(_ subject: T?) -> String {
return ""
}
protocol TestProto {
}
InterpolationTestSuite.test("Interpolation of complex expressions") {
class TestClass<T: TestProto>: NSObject {
func testFunction() {
// The following call should not crash.
_osLogTestHelper("A complex expression \(toString(self))") {
(formatString, _) in
expectEqual("A complex expression %s", formatString)
}
}
}
class B : TestProto { }
TestClass<B>().testFunction()
}
InterpolationTestSuite.test("Include prefix formatting option") {
let unsignedValue: UInt = 0o171
_osLogTestHelper(
"Octal with prefix: \(unsignedValue, format: .octal(includePrefix: true))") {
(formatString, buffer) in
expectEqual("Octal with prefix: 0o%lo", formatString)
}
}
InterpolationTestSuite.test("Hex with uppercase formatting option") {
let unsignedValue: UInt = 0xcafebabe
_osLogTestHelper(
"Hex with uppercase: \(unsignedValue, format: .hex(uppercase: true))") {
(formatString, buffer) in
expectEqual("Hex with uppercase: %lX", formatString)
}
}
InterpolationTestSuite.test("Integer with explicit positive sign") {
let posValue = Int.max
_osLogTestHelper(
"\(posValue, format: .decimal(explicitPositiveSign: true))") {
(formatString, buffer) in
expectEqual("%+ld", formatString)
}
}
InterpolationTestSuite.test("Unsigned integer with explicit positive sign") {
let posValue = UInt.max
_osLogTestHelper(
"\(posValue, format: .decimal(explicitPositiveSign: true))") {
(formatString, buffer) in
expectEqual("+%lu", formatString)
}
}
InterpolationTestSuite.test("Integer formatting with precision") {
let intValue = 1200
_osLogTestHelper(
"\(intValue, format: .decimal(minDigits: 10))") {
(formatString, buffer) in
expectEqual("%.*ld", formatString)
// The buffer should contain two arguments: precision and the actual argument.
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 2,
hasPrivate: false,
hasNonScalar: false
)
bufferChecker.checkArguments(
{ bufferChecker.checkCount(
startIndex: $0,
expectedCount: 10,
precision: true)
},
{ bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: intValue)
})
}
}
InterpolationTestSuite.test("Integer formatting with alignment") {
let intValue = 10
_osLogTestHelper(
"""
\(intValue, align: .right(columns: 10)) \
\(intValue, align: .left(columns: 5), privacy: .private)
""") {
(formatString, buffer) in
expectEqual("%*ld %{private}-*ld", formatString)
}
}
InterpolationTestSuite.test("Integer formatting with precision and alignment") {
let intValue = 1200
_osLogTestHelper(
"\(intValue, format: .decimal(minDigits: 10), align: .left(columns: 7))") {
(formatString, buffer) in
expectEqual("%-*.*ld", formatString)
// The buffer must contain three arguments: width, precision, and the argument.
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 3,
hasPrivate: false,
hasNonScalar: false
)
bufferChecker.checkArguments(
{ bufferChecker.checkCount(
startIndex: $0,
expectedCount: 7,
precision: false)
},
{ bufferChecker.checkCount(
startIndex: $0,
expectedCount: 10,
precision: true)
},
{ bufferChecker.checkInt(
startIndex: $0,
flag: .autoFlag,
expectedInt: intValue)
})
}
}
InterpolationTestSuite.test("String with alignment") {
let smallString = "a" + "b"
let concatString = "hello" + " - " + "world"
_osLogTestHelper(
"""
\(smallString, align: .right(columns: 10)) \
\(concatString, align: .left(columns: 7), privacy: .public)
""") {
(formatString, buffer) in
expectEqual("%*s %{public}-*s", formatString)
}
}
InterpolationTestSuite.test("Floats and Doubles") {
let x = 1.2 + 0.5
let pi: Double = 3.141593
let floatPi: Float = 3.141593
_osLogTestHelper(
"""
A double value: \(x, privacy: .private) \
pi as double: \(pi), pi as float: \(floatPi)
""") {
(formatString, buffer) in
expectEqual(
"""
A double value: %{private}f pi as double: %f, \
pi as float: %f
""",
formatString)
let bufferChecker = OSLogBufferChecker(buffer)
bufferChecker.checkSummaryBytes(
argumentCount: 3,
hasPrivate: true,
hasNonScalar: false)
bufferChecker.checkArguments(
{ bufferChecker.checkDouble(
startIndex: $0,
flag: .privateFlag,
expectedValue: x)
},
{ bufferChecker.checkDouble(
startIndex: $0,
flag: .autoFlag,
expectedValue: pi)
},
{ bufferChecker.checkDouble(
startIndex: $0,
flag: .autoFlag,
expectedValue: Double(floatPi))
})
}
}