Skip to content

Commit e0dd80f

Browse files
committed
Handle long tagged NSStrings
1 parent 1aa0fb8 commit e0dd80f

File tree

6 files changed

+137
-11
lines changed

6 files changed

+137
-11
lines changed

stdlib/public/core/SmallString.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,22 @@ extension _SmallString {
353353
//
354354
@_effects(readonly) // @opaque
355355
@usableFromInline // testable
356-
internal init(taggedCocoa cocoa: AnyObject) {
356+
internal init?(taggedCocoa cocoa: AnyObject) {
357357
self.init()
358+
var success = true
358359
self.withMutableCapacity {
359-
let len = _bridgeTagged(cocoa, intoUTF8: $0)
360-
_internalInvariant(len != nil && len! <= _SmallString.capacity,
361-
"Internal invariant violated: large tagged NSStrings")
362-
return len._unsafelyUnwrappedUnchecked
360+
/*
361+
For regular NSTaggedPointerStrings we will always succeed here, but
362+
tagged NSLocalizedStrings may not fit in a SmallString
363+
*/
364+
if let len = _bridgeTagged(cocoa, intoUTF8: $0) {
365+
return len
366+
}
367+
success = false
368+
return 0
369+
}
370+
if !success {
371+
return nil
363372
}
364373
self._invariantCheck()
365374
}

stdlib/public/core/StringBridge.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,9 @@ internal enum _KnownCocoaString {
335335
#if !(arch(i386) || arch(arm) || arch(arm64_32))
336336

337337
// Resiliently write a tagged _CocoaString's contents into a buffer.
338-
// TODO: move this to the Foundation overlay and reimplement it with
339-
// _NSTaggedPointerStringGetBytes
338+
// The Foundation overlay takes care of bridging tagged pointer strings before
339+
// they reach us, but this may still be called by older code, or by strings
340+
// entering our domain via the arguments to -isEqual:, etc...
340341
@_effects(releasenone) // @opaque
341342
internal func _bridgeTagged(
342343
_ cocoa: _CocoaString,
@@ -370,8 +371,11 @@ private func _withCocoaASCIIPointer<R>(
370371
if requireStableAddress {
371372
return nil // tagged pointer strings don't support _fastCStringContents
372373
}
373-
let tmp = _StringGuts(_SmallString(taggedCocoa: str))
374-
return tmp.withFastUTF8 { work($0.baseAddress._unsafelyUnwrappedUnchecked) }
374+
if let smol = _SmallString(taggedCocoa: str) {
375+
return _StringGuts(smol).withFastUTF8 {
376+
work($0.baseAddress._unsafelyUnwrappedUnchecked)
377+
}
378+
}
375379
}
376380
#endif
377381
defer { _fixLifetime(str) }
@@ -503,7 +507,11 @@ internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
503507
cocoaString, to: __SharedStringStorage.self).asString._guts
504508
#if !(arch(i386) || arch(arm) || arch(arm64_32))
505509
case .tagged:
506-
return _StringGuts(_SmallString(taggedCocoa: cocoaString))
510+
// Foundation should be taking care of tagged pointer strings before they
511+
// reach here, so the only ones reaching this point should be back deployed,
512+
// which will never have tagged pointer strings that aren't small, hence
513+
// the force unwrap here.
514+
return _StringGuts(_SmallString(taggedCocoa: cocoaString)!)
507515
#if arch(arm64)
508516
case .constantTagged:
509517
let taggedContents = getConstantTaggedCocoaContents(cocoaString)!
@@ -530,7 +538,11 @@ internal func _bridgeCocoaString(_ cocoaString: _CocoaString) -> _StringGuts {
530538

531539
#if !(arch(i386) || arch(arm) || arch(arm64_32))
532540
if _isObjCTaggedPointer(immutableCopy) {
533-
return _StringGuts(_SmallString(taggedCocoa: immutableCopy))
541+
// Copying a tagged pointer can produce a tagged pointer, but only if it's
542+
// small enough to definitely fit in a _SmallString
543+
return _StringGuts(
544+
_SmallString(taggedCocoa: immutableCopy).unsafelyUnwrapped
545+
)
534546
}
535547
#endif
536548

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <objc/objc.h>
2+
#import <objc/NSObject.h>
3+
4+
5+
@interface NSSlowTaggedLocalizedString : NSObject
6+
7+
+ (instancetype) createTestString;
8+
+ (void) setContents: (const char *)newContents;
9+
10+
@end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#import <string.h>
2+
#import "NSSlowTaggedLocalizedString.h"
3+
#import <dlfcn.h>
4+
5+
@implementation NSSlowTaggedLocalizedString
6+
7+
uintptr_t *obfuscator;
8+
void (*_objc_registerTaggedPointerClass)(uint16_t tag, Class cls);
9+
10+
+ (instancetype) createTestString {
11+
#if __LP64__
12+
if (obfuscator == NULL) {
13+
obfuscator = dlsym(RTLD_DEFAULT, "objc_debug_taggedpointer_obfuscator");
14+
*obfuscator = 0;
15+
_objc_registerTaggedPointerClass = dlsym(RTLD_DEFAULT, "_objc_registerTaggedPointerClass");
16+
(*_objc_registerTaggedPointerClass)(0, self); //0 would be unsafe if we loaded Foundation, but we aren't doing that
17+
}
18+
#if __x86_64__
19+
return (id)(void *)(uintptr_t)1; //x86_64 uses the LSB as the tag bit, and we want tag 0
20+
#else
21+
return (id)(void *)(uintptr_t)(1llu << 63); //MSB everywhere else
22+
#endif
23+
#else
24+
return nil;
25+
#endif
26+
}
27+
28+
static const char *contents = NULL;
29+
30+
+ (void) setContents: (const char *)newContents {
31+
const char *oldContents = contents;
32+
contents = strdup(newContents);
33+
free((void *)oldContents);
34+
}
35+
36+
- (const char *)_fastCStringContents:(BOOL)nullTerminationRequired {
37+
return contents;
38+
}
39+
40+
- (uint64_t)length {
41+
return strlen(contents);
42+
}
43+
44+
- (id)copyWithZone:(id)unused {
45+
return self;
46+
}
47+
48+
- (uint16_t)characterAtIndex:(NSUInteger)index {
49+
if (index >= [self length]) {
50+
//throw the appropriate exception
51+
abort();
52+
}
53+
return (uint16_t)contents[index];
54+
}
55+
56+
- (void *) _fastCharacterContents {
57+
return nil;
58+
}
59+
60+
@end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module NSSlowTaggedLocalizedString {
2+
header "NSSlowTaggedLocalizedString.h"
3+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: mkdir -p %t
2+
// RUN: %target-clang -fobjc-arc %S/Inputs/NSSlowTaggedLocalizedString/NSSlowTaggedLocalizedString.m -fno-objc-arc -c -o %t/NSSlowTaggedLocalizedString.o
3+
// RUN: %target-build-swift -Xfrontend -disable-access-control -I %S/Inputs/NSSlowTaggedLocalizedString/ %t/NSSlowTaggedLocalizedString.o %s -o %t/a.out
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: objc_interop
9+
10+
import NSSlowTaggedLocalizedString
11+
import Swift
12+
13+
import StdlibUnittest
14+
15+
let longTaggedTests = TestSuite("NonContiguousTaggedStrings")
16+
17+
longTaggedTests.test("EqualLongTagged") {
18+
var native = "Send Message to different Team"
19+
let longTagged = NSSlowTaggedLocalizedString.createTestString()
20+
NSSlowTaggedLocalizedString.setContents(&native)
21+
defer {
22+
NSSlowTaggedLocalizedString.setContents(nil)
23+
}
24+
let reverseBridged = native._guts._object.objCBridgeableObject
25+
expectEqual(
26+
reverseBridged.isEqual(to: longTagged),
27+
longTagged.isEqual(to: reverseBridged)
28+
)
29+
}
30+
31+
runAllTests()
32+

0 commit comments

Comments
 (0)