//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 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 // //===----------------------------------------------------------------------===// import SwiftShims /// Effectively an untyped NSString that doesn't require foundation. @usableFromInline internal typealias _CocoaString = AnyObject #if _runtime(_ObjC) // Swift's String bridges NSString via this protocol and these // variables, allowing the core stdlib to remain decoupled from // Foundation. @objc private protocol _StringSelectorHolder : _NSCopying { @objc var length: Int { get } @objc var hash: UInt { get } @objc(characterAtIndex:) func character(at offset: Int) -> UInt16 @objc(getCharacters:range:) func getCharacters( _ buffer: UnsafeMutablePointer, range aRange: _SwiftNSRange ) @objc(_fastCStringContents:) func _fastCStringContents( _ requiresNulTermination: Int8 ) -> UnsafePointer? @objc(_fastCharacterContents) func _fastCharacterContents() -> UnsafePointer? @objc(getBytes:maxLength:usedLength:encoding:options:range:remainingRange:) func getBytes(_ buffer: UnsafeMutableRawPointer?, maxLength maxBufferCount: Int, usedLength usedBufferCount: UnsafeMutablePointer?, encoding: UInt, options: UInt, range: _SwiftNSRange, remaining leftover: UnsafeMutablePointer<_SwiftNSRange>?) -> Int8 @objc(compare:options:range:locale:) func compare(_ string: _CocoaString, options: UInt, range: _SwiftNSRange, locale: AnyObject?) -> Int } /* Passing a _CocoaString through _objc() lets you call ObjC methods that the compiler doesn't know about, via the protocol above. In order to get good performance, you need a double indirection like this: func a -> _objc -> func a' because any refcounting @_effects on 'a' will be lost when _objc breaks ARC's knowledge that the _CocoaString and _StringSelectorHolder are the same object. */ @inline(__always) private func _objc(_ str: _CocoaString) -> _StringSelectorHolder { return unsafeBitCast(str, to: _StringSelectorHolder.self) } @_effects(releasenone) private func _copyNSString(_ str: _StringSelectorHolder) -> _CocoaString { return str.copy(with: nil) } @usableFromInline // @testable @_effects(releasenone) internal func _stdlib_binary_CFStringCreateCopy( _ source: _CocoaString ) -> _CocoaString { return _copyNSString(_objc(source)) } @_effects(readonly) private func _NSStringLen(_ str: _StringSelectorHolder) -> Int { return str.length } @usableFromInline // @testable @_effects(readonly) internal func _stdlib_binary_CFStringGetLength( _ source: _CocoaString ) -> Int { return _NSStringLen(_objc(source)) } @_effects(readonly) private func _NSStringCharactersPtr(_ str: _StringSelectorHolder) -> UnsafeMutablePointer? { return UnsafeMutablePointer(mutating: str._fastCharacterContents()) } @usableFromInline // @testable @_effects(readonly) internal func _stdlib_binary_CFStringGetCharactersPtr( _ source: _CocoaString ) -> UnsafeMutablePointer? { return _NSStringCharactersPtr(_objc(source)) } @_effects(releasenone) private func _NSStringGetCharacters( from source: _StringSelectorHolder, range: Range, into destination: UnsafeMutablePointer ) { source.getCharacters(destination, range: _SwiftNSRange( location: range.startIndex, length: range.count) ) } /// Copies a slice of a _CocoaString into contiguous storage of sufficient /// capacity. @_effects(releasenone) internal func _cocoaStringCopyCharacters( from source: _CocoaString, range: Range, into destination: UnsafeMutablePointer ) { _NSStringGetCharacters(from: _objc(source), range: range, into: destination) } @_effects(readonly) private func _NSStringGetCharacter( _ target: _StringSelectorHolder, _ position: Int ) -> UTF16.CodeUnit { return target.character(at: position) } @_effects(readonly) internal func _cocoaStringSubscript( _ target: _CocoaString, _ position: Int ) -> UTF16.CodeUnit { return _NSStringGetCharacter(_objc(target), position) } @_effects(releasenone) private func _NSStringCopyUTF8( _ o: _StringSelectorHolder, into bufPtr: UnsafeMutableBufferPointer ) -> Int? { let ptr = bufPtr.baseAddress._unsafelyUnwrappedUnchecked let len = o.length var remainingRange = _SwiftNSRange(location: 0, length: 0) var usedLen = 0 let success = 0 != o.getBytes( ptr, maxLength: bufPtr.count, usedLength: &usedLen, encoding: _cocoaUTF8Encoding, options: 0, range: _SwiftNSRange(location: 0, length: len), remaining: &remainingRange ) if success && remainingRange.length == 0 { return usedLen } return nil } @_effects(releasenone) internal func _cocoaStringCopyUTF8( _ target: _CocoaString, into bufPtr: UnsafeMutableBufferPointer ) -> Int? { return _NSStringCopyUTF8(_objc(target), into: bufPtr) } @_effects(readonly) private func _NSStringUTF8Count( _ o: _StringSelectorHolder, range: Range ) -> Int? { var remainingRange = _SwiftNSRange(location: 0, length: 0) var usedLen = 0 let success = 0 != o.getBytes( UnsafeMutablePointer(Builtin.inttoptr_Word(0._builtinWordValue)), maxLength: 0, usedLength: &usedLen, encoding: _cocoaUTF8Encoding, options: 0, range: _SwiftNSRange(location: range.startIndex, length: range.count), remaining: &remainingRange ) if success && remainingRange.length == 0 { return usedLen } return nil } @_effects(readonly) internal func _cocoaStringUTF8Count( _ target: _CocoaString, range: Range ) -> Int? { if range.isEmpty { return 0 } return _NSStringUTF8Count(_objc(target), range: range) } @_effects(readonly) private func _NSStringCompare( _ o: _StringSelectorHolder, _ other: _CocoaString ) -> Int { let range = _SwiftNSRange(location: 0, length: o.length) let options = UInt(2) /* NSLiteralSearch*/ return o.compare(other, options: options, range: range, locale: nil) } @_effects(readonly) internal func _cocoaStringCompare( _ string: _CocoaString, _ other: _CocoaString ) -> Int { return _NSStringCompare(_objc(string), other) } @_effects(readonly) internal func _cocoaHashString( _ string: _CocoaString ) -> UInt { return _swift_stdlib_CFStringHashNSString(string) } @_effects(readonly) internal func _cocoaHashASCIIBytes( _ bytes: UnsafePointer, length: Int ) -> UInt { return _swift_stdlib_CFStringHashCString(bytes, length) } // These "trampolines" are effectively objc_msgSend_super. // They bypass our implementations to use NSString's. @_effects(readonly) internal func _cocoaCStringUsingEncodingTrampoline( _ string: _CocoaString, _ encoding: UInt ) -> UnsafePointer? { return _swift_stdlib_NSStringCStringUsingEncodingTrampoline(string, encoding) } @_effects(releasenone) internal func _cocoaGetCStringTrampoline( _ string: _CocoaString, _ buffer: UnsafeMutablePointer, _ maxLength: Int, _ encoding: UInt ) -> Int8 { return Int8(_swift_stdlib_NSStringGetCStringTrampoline( string, buffer, maxLength, encoding)) } // // Conversion from NSString to Swift's native representation. // private var kCFStringEncodingASCII: _swift_shims_CFStringEncoding { @inline(__always) get { return 0x0600 } } private var kCFStringEncodingUTF8: _swift_shims_CFStringEncoding { @inline(__always) get { return 0x8000100 } } internal enum _KnownCocoaString { case storage case shared case cocoa #if !(arch(i386) || arch(arm)) case tagged #endif @inline(__always) init(_ str: _CocoaString) { #if !(arch(i386) || arch(arm)) if _isObjCTaggedPointer(str) { self = .tagged return } #endif switch unsafeBitCast(_swift_classOfObjCHeapObject(str), to: UInt.self) { case unsafeBitCast(__StringStorage.self, to: UInt.self): self = .storage case unsafeBitCast(__SharedStringStorage.self, to: UInt.self): self = .shared default: self = .cocoa } } } #if !(arch(i386) || arch(arm)) // Resiliently write a tagged _CocoaString's contents into a buffer. // TODO: move this to the Foundation overlay and reimplement it with // _NSTaggedPointerStringGetBytes @_effects(releasenone) // @opaque internal func _bridgeTagged( _ cocoa: _CocoaString, intoUTF8 bufPtr: UnsafeMutableBufferPointer ) -> Int? { _internalInvariant(_isObjCTaggedPointer(cocoa)) return _cocoaStringCopyUTF8(cocoa, into: bufPtr) } #endif @_effects(readonly) private func _NSStringASCIIPointer(_ str: _StringSelectorHolder) -> UnsafePointer? { // TODO(String bridging): Is there a better interface here? Ideally we'd be // able to ask for UTF8 rather than just ASCII return str._fastCStringContents(0)?._asUInt8 } @_effects(readonly) // @opaque internal func _cocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer? { return _NSStringASCIIPointer(_objc(str)) } private enum CocoaStringPointer { case ascii(UnsafePointer) case utf8(UnsafePointer) case utf16(UnsafePointer) case none } @_effects(readonly) private func _getCocoaStringPointer( _ cfImmutableValue: _CocoaString ) -> CocoaStringPointer { if let asciiPtr = _cocoaASCIIPointer(cfImmutableValue) { // NOTE: CFStringGetCStringPointer means ASCII return .ascii(asciiPtr) } if let utf16Ptr = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue) { return .utf16(utf16Ptr) } return .none } @usableFromInline @_effects(releasenone) // @opaque internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts { switch _KnownCocoaString(cocoaString) { case .storage: return _unsafeUncheckedDowncast( cocoaString, to: __StringStorage.self).asString._guts case .shared: return _unsafeUncheckedDowncast( cocoaString, to: __SharedStringStorage.self).asString._guts #if !(arch(i386) || arch(arm)) case .tagged: return _StringGuts(_SmallString(taggedCocoa: cocoaString)) #endif case .cocoa: // "Copy" it into a value to be sure nobody will modify behind // our backs. In practice, when value is already immutable, this // just does a retain. // // TODO: Only in certain circumstances should we emit this call: // 1) If it's immutable, just retain it. // 2) If it's mutable with no associated information, then a copy must // happen; might as well eagerly bridge it in. // 3) If it's mutable with associated information, must make the call let immutableCopy = _stdlib_binary_CFStringCreateCopy(cocoaString) as AnyObject #if !(arch(i386) || arch(arm)) if _isObjCTaggedPointer(immutableCopy) { return _StringGuts(_SmallString(taggedCocoa: immutableCopy)) } #endif let (fastUTF8, isASCII): (Bool, Bool) switch _getCocoaStringPointer(immutableCopy) { case .ascii(_): (fastUTF8, isASCII) = (true, true) case .utf8(_): (fastUTF8, isASCII) = (true, false) default: (fastUTF8, isASCII) = (false, false) } let length = _stdlib_binary_CFStringGetLength(immutableCopy) return _StringGuts( cocoa: immutableCopy, providesFastUTF8: fastUTF8, isASCII: isASCII, length: length) } } extension String { public // SPI(Foundation) init(_cocoaString: AnyObject) { self._guts = _bridgeCocoaString(_cocoaString) } } @_effects(releasenone) private func _createCFString( _ ptr: UnsafePointer, _ count: Int, _ encoding: UInt32 ) -> AnyObject { return _swift_stdlib_CFStringCreateWithBytes( nil, //ignored in the shim for perf reasons ptr, count, kCFStringEncodingUTF8, 0 ) as AnyObject } extension String { @_effects(releasenone) public // SPI(Foundation) func _bridgeToObjectiveCImpl() -> AnyObject { // Smol ASCII a) may bridge to tagged pointers, b) can't contain a BOM if _guts.isSmallASCII { return _guts.asSmall.withUTF8 { bufPtr in return _createCFString( bufPtr.baseAddress._unsafelyUnwrappedUnchecked, bufPtr.count, kCFStringEncodingUTF8 ) } } if _guts.isSmall { // We can't form a tagged pointer String, so grow to a non-small String, // and bridge that instead. Also avoids CF deleting any BOM that may be // present var copy = self copy._guts.grow(_SmallString.capacity + 1) _internalInvariant(!copy._guts.isSmall) return copy._bridgeToObjectiveCImpl() } if _guts._object.isImmortal { // TODO: We'd rather emit a valid ObjC object statically than create a // shared string class instance. let gutsCountAndFlags = _guts._object._countAndFlags return __SharedStringStorage( immortal: _guts._object.fastUTF8.baseAddress!, countAndFlags: _StringObject.CountAndFlags( sharedCount: _guts.count, isASCII: gutsCountAndFlags.isASCII)) } _internalInvariant(_guts._object.hasObjCBridgeableObject, "Unknown non-bridgeable object case") return _guts._object.objCBridgeableObject } } // At runtime, this class is derived from `__SwiftNativeNSStringBase`, // which is derived from `NSString`. // // The @_swift_native_objc_runtime_base attribute // This allows us to subclass an Objective-C class and use the fast Swift // memory allocator. @objc @_swift_native_objc_runtime_base(__SwiftNativeNSStringBase) class __SwiftNativeNSString { @objc internal init() {} deinit {} } // Called by the SwiftObject implementation to get the description of a value // as an NSString. @_silgen_name("swift_stdlib_getDescription") public func _getDescription(_ x: T) -> AnyObject { return String(reflecting: x)._bridgeToObjectiveCImpl() } #else // !_runtime(_ObjC) internal class __SwiftNativeNSString { internal init() {} deinit {} } #endif // Special-case Index <-> Offset converters for bridging and use in accelerating // the UTF16View in general. extension StringProtocol { @_specialize(where Self == String) @_specialize(where Self == Substring) public // SPI(Foundation) func _toUTF16Offset(_ idx: Index) -> Int { return self.utf16.distance(from: self.utf16.startIndex, to: idx) } @_specialize(where Self == String) @_specialize(where Self == Substring) public // SPI(Foundation) func _toUTF16Index(_ offset: Int) -> Index { return self.utf16.index(self.utf16.startIndex, offsetBy: offset) } @_specialize(where Self == String) @_specialize(where Self == Substring) public // SPI(Foundation) func _toUTF16Offsets(_ indices: Range) -> Range { let lowerbound = _toUTF16Offset(indices.lowerBound) let length = self.utf16.distance( from: indices.lowerBound, to: indices.upperBound) return Range( uncheckedBounds: (lower: lowerbound, upper: lowerbound + length)) } @_specialize(where Self == String) @_specialize(where Self == Substring) public // SPI(Foundation) func _toUTF16Indices(_ range: Range) -> Range { let lowerbound = _toUTF16Index(range.lowerBound) let upperbound = _toUTF16Index(range.lowerBound + range.count) return Range(uncheckedBounds: (lower: lowerbound, upper: upperbound)) } } extension String { public // @testable / @benchmarkable func _copyUTF16CodeUnits( into buffer: UnsafeMutableBufferPointer, range: Range ) { _internalInvariant(buffer.count >= range.count) let indexRange = self._toUTF16Indices(range) self.utf16._nativeCopy(into: buffer, alignedRange: indexRange) } }