//===----------------------------------------------------------------------===// // // 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 @objc(newTaggedNSStringWithASCIIBytes_:length_:) func createTaggedString(bytes: UnsafePointer, count: Int) -> AnyObject? } /* 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 { if let len = getConstantTaggedCocoaContents(source)?.utf16Length { return len } return _NSStringLen(_objc(source)) } @_effects(readonly) internal func _isNSString(_ str:AnyObject) -> Bool { if getConstantTaggedCocoaContents(str) != nil { return true } return _swift_stdlib_isNSString(str) != 0 } @_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: UnsafeMutableRawBufferPointer ) -> 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: UnsafeMutableRawBufferPointer ) -> 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( UnsafeMutableRawPointer(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) || arch(arm64_32) || arch(wasm32)) case tagged #endif #if arch(arm64) case constantTagged #endif @inline(__always) init(_ str: _CocoaString) { #if !(arch(i386) || arch(arm) || arch(arm64_32)) if _isObjCTaggedPointer(str) { #if arch(arm64) if let _ = getConstantTaggedCocoaContents(str) { self = .constantTagged } else { self = .tagged } #else self = .tagged #endif 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) || arch(arm64_32)) // 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: UnsafeMutableRawBufferPointer ) -> 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 //TODO(String bridging): Unconditionally asking for nul-terminated contents is // overly conservative and hurts perf with some NSStrings return str._fastCStringContents(1)?._asUInt8 } @_effects(readonly) // @opaque private func _withCocoaASCIIPointer( _ str: _CocoaString, requireStableAddress: Bool, work: (UnsafePointer) -> R? ) -> R? { #if !(arch(i386) || arch(arm) || arch(arm64_32)) if _isObjCTaggedPointer(str) { if let ptr = getConstantTaggedCocoaContents(str)?.asciiContentsPointer { return work(ptr) } if requireStableAddress { return nil // tagged pointer strings don't support _fastCStringContents } let tmp = _StringGuts(_SmallString(taggedCocoa: str)) return tmp.withFastUTF8 { work($0.baseAddress._unsafelyUnwrappedUnchecked) } } #endif defer { _fixLifetime(str) } if let ptr = _NSStringASCIIPointer(_objc(str)) { return work(ptr) } return nil } @_effects(readonly) // @opaque internal func withCocoaASCIIPointer( _ str: _CocoaString, work: (UnsafePointer) -> R? ) -> R? { return _withCocoaASCIIPointer(str, requireStableAddress: false, work: work) } @_effects(readonly) internal func stableCocoaASCIIPointer(_ str: _CocoaString) -> UnsafePointer? { return _withCocoaASCIIPointer(str, requireStableAddress: true, work: { $0 }) } private enum CocoaStringPointer { case ascii(UnsafePointer) case utf8(UnsafePointer) case utf16(UnsafePointer) case none } @_effects(readonly) private func _getCocoaStringPointer( _ cfImmutableValue: _CocoaString ) -> CocoaStringPointer { if let ascii = stableCocoaASCIIPointer(cfImmutableValue) { return .ascii(ascii) } // We could ask for UTF16 here via _stdlib_binary_CFStringGetCharactersPtr, // but we currently have no use for it return .none } #if arch(arm64) //11000000..payload..111 private var constantTagMask:UInt { 0b1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0111 } private var expectedConstantTagValue:UInt { 0b1100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0111 } #endif @inline(__always) private func formConstantTaggedCocoaString( untaggedCocoa: _CocoaString ) -> AnyObject? { #if !arch(arm64) return nil #else let constantPtr:UnsafeRawPointer = Builtin.reinterpretCast(untaggedCocoa) // Check if what we're pointing to is actually a valid tagged constant guard _swift_stdlib_dyld_is_objc_constant_string(constantPtr) == 1 else { return nil } let retaggedPointer = UInt(bitPattern: constantPtr) | expectedConstantTagValue return unsafeBitCast(retaggedPointer, to: AnyObject.self) #endif } @inline(__always) private func getConstantTaggedCocoaContents(_ cocoaString: _CocoaString) -> (utf16Length: Int, asciiContentsPointer: UnsafePointer, untaggedCocoa: _CocoaString)? { #if !arch(arm64) return nil #else guard _isObjCTaggedPointer(cocoaString) else { return nil } let taggedValue = unsafeBitCast(cocoaString, to: UInt.self) guard taggedValue & constantTagMask == expectedConstantTagValue else { return nil } let payloadMask = ~constantTagMask let payload = taggedValue & payloadMask let ivarPointer = UnsafePointer<_swift_shims_builtin_CFString>( bitPattern: payload )! guard _swift_stdlib_dyld_is_objc_constant_string( UnsafeRawPointer(ivarPointer) ) == 1 else { return nil } let length = ivarPointer.pointee.length let isUTF16Mask:UInt = 0x0000_0000_0000_0004 //CFStringFlags bit 4: isUnicode let isASCII = ivarPointer.pointee.flags & isUTF16Mask == 0 _precondition(isASCII) // we don't currently support non-ASCII here let contentsPtr = ivarPointer.pointee.str return ( utf16Length: Int(length), asciiContentsPointer: contentsPtr, untaggedCocoa: Builtin.reinterpretCast(ivarPointer) ) #endif } @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) || arch(arm64_32)) case .tagged: return _StringGuts(_SmallString(taggedCocoa: cocoaString)) #if arch(arm64) case .constantTagged: let taggedContents = getConstantTaggedCocoaContents(cocoaString)! return _StringGuts( cocoa: taggedContents.untaggedCocoa, providesFastUTF8: false, //TODO: if contentsPtr is UTF8 compatible, use it isASCII: true, length: taggedContents.utf16Length ) #endif #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) #if !(arch(i386) || arch(arm) || arch(arm64_32)) 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 { @_spi(Foundation) public init(_cocoaString: AnyObject) { self._guts = _bridgeCocoaString(_cocoaString) } } @_effects(releasenone) private func _createNSString( _ receiver: _StringSelectorHolder, _ ptr: UnsafePointer, _ count: Int, _ encoding: UInt32 ) -> AnyObject? { return receiver.createTaggedString(bytes: ptr, count: count) } @_effects(releasenone) private func _createCFString( _ ptr: UnsafePointer, _ count: Int, _ encoding: UInt32 ) -> AnyObject? { return _createNSString( unsafeBitCast(__StringStorage.self as AnyClass, to: _StringSelectorHolder.self), ptr, count, encoding ) } extension String { @_effects(releasenone) public // SPI(Foundation) func _bridgeToObjectiveCImpl() -> AnyObject { _connectOrphanedFoundationSubclassesIfNeeded() // Smol ASCII a) may bridge to tagged pointers, b) can't contain a BOM if _guts.isSmallASCII { let maybeTagged = _guts.asSmall.withUTF8 { bufPtr in return _createCFString( bufPtr.baseAddress._unsafelyUnwrappedUnchecked, bufPtr.count, kCFStringEncodingUTF8 ) } if let tagged = maybeTagged { return tagged } } 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 // TODO: small capacity minimum is lifted, just need to make native 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") let result = _guts._object.objCBridgeableObject return formConstantTaggedCocoaString(untaggedCocoa: result) ?? result } } // Note: This function is not intended to be called from Swift. The // availability information here is perfunctory; this function isn't considered // part of the Stdlib's Swift ABI. @available(SwiftStdlib 5.2, *) @_cdecl("_SwiftCreateBridgedString") @usableFromInline internal func _SwiftCreateBridgedString_DoNotCall( bytes: UnsafePointer, length: Int, encoding: _swift_shims_CFStringEncoding ) -> Unmanaged { let bufPtr = UnsafeBufferPointer(start: bytes, count: length) let str:String switch encoding { case kCFStringEncodingUTF8: str = String(decoding: bufPtr, as: Unicode.UTF8.self) case kCFStringEncodingASCII: str = String(decoding: bufPtr, as: Unicode.ASCII.self) default: fatalError("Unsupported encoding in shim") } return Unmanaged.passRetained(str._bridgeToObjectiveCImpl()) } // 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) @_spi(Foundation) public 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() } @_silgen_name("swift_stdlib_NSStringFromUTF8") @usableFromInline //this makes the symbol available to the runtime :( @available(SwiftStdlib 5.2, *) internal func _NSStringFromUTF8(_ s: UnsafePointer, _ len: Int) -> AnyObject { return String( decoding: UnsafeBufferPointer(start: s, count: len), as: UTF8.self )._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) } }