源代码CharacterSet
is available, actually.的来源contains
是:
fileprivate func contains(_ member: Unicode.Scalar) -> Bool {
switch _backing {
case .immutable(let cs):
return CFCharacterSetIsLongCharacterMember(cs, member.value)
case .mutable(let cs):
return CFCharacterSetIsLongCharacterMember(cs, member.value)
}
}
因此,它基本上只是调用,直至CFCharacterSetIsLongCharacterMember
。该is also available, although only for Yosemite的源代码(El Cap和Sierra的版本都表示“即将推出”)。然而,优胜美地的代码似乎与我在Sierra上的反汇编中看到的相符。无论如何,代码如下所示:
Boolean CFCharacterSetIsLongCharacterMember(CFCharacterSetRef theSet, UTF32Char theChar) {
CFIndex length;
UInt32 plane = (theChar >> 16);
Boolean isAnnexInverted = false;
Boolean isInverted;
Boolean result = false;
CF_OBJC_FUNCDISPATCHV(__kCFCharacterSetTypeID, Boolean, (NSCharacterSet *)theSet, longCharacterIsMember:(UTF32Char)theChar);
__CFGenericValidateType(theSet, __kCFCharacterSetTypeID);
if (plane) {
CFCharacterSetRef annexPlane;
if (__CFCSetIsBuiltin(theSet)) {
isInverted = __CFCSetIsInverted(theSet);
return (CFUniCharIsMemberOf(theChar, __CFCSetBuiltinType(theSet)) ? !isInverted : isInverted);
}
isAnnexInverted = __CFCSetAnnexIsInverted(theSet);
if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) {
if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) {
isInverted = __CFCSetIsInverted(theSet);
length = __CFCSetRangeLength(theSet);
return (length && __CFCSetRangeFirstChar(theSet) <= theChar && theChar < __CFCSetRangeFirstChar(theSet) + length ? !isInverted : isInverted);
} else {
return (isAnnexInverted ? true : false);
}
} else {
theSet = annexPlane;
theChar &= 0xFFFF;
}
}
isInverted = __CFCSetIsInverted(theSet);
switch (__CFCSetClassType(theSet)) {
case __kCFCharSetClassBuiltin:
result = (CFUniCharIsMemberOf(theChar, __CFCSetBuiltinType(theSet)) ? !isInverted : isInverted);
break;
case __kCFCharSetClassRange:
length = __CFCSetRangeLength(theSet);
result = (length && __CFCSetRangeFirstChar(theSet) <= theChar && theChar < __CFCSetRangeFirstChar(theSet) + length ? !isInverted : isInverted);
break;
case __kCFCharSetClassString:
result = ((length = __CFCSetStringLength(theSet)) ? (__CFCSetBsearchUniChar(__CFCSetStringBuffer(theSet), length, theChar) ? !isInverted : isInverted) : isInverted);
break;
case __kCFCharSetClassBitmap:
result = (__CFCSetCompactBitmapBits(theSet) ? (__CFCSetIsMemberBitmap(__CFCSetBitmapBits(theSet), theChar) ? true : false) : isInverted);
break;
case __kCFCharSetClassCompactBitmap:
result = (__CFCSetCompactBitmapBits(theSet) ? (__CFCSetIsMemberInCompactBitmap(__CFCSetCompactBitmapBits(theSet), theChar) ? true : false) : isInverted);
break;
default:
CFAssert1(0, __kCFLogAssertion, "%s: Internal inconsistency error: unknown character set type", __PRETTY_FUNCTION__); // We should never come here
return false; // To make compiler happy
}
return (result ? !isAnnexInverted : isAnnexInverted);
}
因此,我们可以跟进,并找出发生了什么事。不幸的是,我们必须破解我们的x86_64组装技能才能做到这一点。但是不要害怕,因为我已经为你做了这件事,因为显然这是我在周五晚上为了好玩而做的。
一个有用的事情已经是数据结构:
struct __CFCharacterSet {
CFRuntimeBase _base;
CFHashCode _hashValue;
union {
struct {
CFIndex _type;
} _builtin;
struct {
UInt32 _firstChar;
CFIndex _length;
} _range;
struct {
UniChar *_buffer;
CFIndex _length;
} _string;
struct {
uint8_t *_bits;
} _bitmap;
struct {
uint8_t *_cBits;
} _compactBitmap;
} _variants;
CFCharSetAnnexStruct *_annex;
};
我们需要知道到底什么CFRuntimeBase
是,太:
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
你猜怎么着!还有一些我们需要的常量。
enum {
__kCFCharSetClassTypeMask = 0x0070,
__kCFCharSetClassBuiltin = 0x0000,
__kCFCharSetClassRange = 0x0010,
__kCFCharSetClassString = 0x0020,
__kCFCharSetClassBitmap = 0x0030,
__kCFCharSetClassSet = 0x0040,
__kCFCharSetClassCompactBitmap = 0x0040,
// irrelevant stuff redacted
};
然后我们就可以在CFCharacterSetIsLongCharacterMember
突破和日志结构:
supersetA.contains(UnicodeScalar(128518)!)
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 90190000 02000000 00000000 00000000 06f60100 00000000 01000000 00000000 00000000 00000000>
基于上面的结构,我们可以计算出这是什么字符集而成的。在这种情况下,相关部分将是从CFRuntimeBase
开始的cfinfo
的第一个字节,它们是字节9-12。这个的第一个字节0x90
包含字符集的类型信息。它需要AND
编辑与__kCFCharSetClassTypeMask
,这得到我们0x10
,这是__kCFCharSetClassRange
。
对于此行:
supersetB.contains(UnicodeScalar(128518)!)
的结构是:
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 a0190000 02000000 00000000 00000000 9066f000 01000000 02000000 00000000 00000000 00000000>
这次字节9是0xa0
,其AND
ED与掩模是0x20
,__kCFCharSetClassString
。
此时,Monty Python演员阵容尖叫着“Get On With It!”,所以让我们来看看CFCharacterSetIsLongCharacterMember
的来源,看看发生了什么。
跳过过去所有的CF_OBJC_FUNCDISPATCHV
废话,我们得到这一行:
if (plane) {
这显然计算结果为真在这两种情况下。下一个测试:
if (__CFCSetIsBuiltin(theSet)) {
此计算为在这两种情况下错误的,因为无论是类型__kCFCharSetClassBuiltin
,所以我们跳过这个块。
isAnnexInverted = __CFCSetAnnexIsInverted(theSet);
在这两种情况下,_annex
指针为空(见在结构的端部的所有零),所以这是false
。
本次测试将是true
出于同样的原因:
if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) {
带着我们:
if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) {
的__CFCSetHasNonBMPPlane
宏检查_annex
,所以这是假的。当然,表情符号不在BMP平面中,所以这实际上对于两个个案都是错误的,即使是返回正确结果的情况也是如此。
__CFCSetIsRange
检查我们的类型是否为__kCFCharSetClassRange
,这是第一次。所以这是我们的分歧点。这样做的第二次调用,产生不正确的结果,返回到下一行:
return (isAnnexInverted ? true : false);
而且由于附件是NULL
,造成isAnnexInverted
是假的,这个返回false。
至于如何解决它......好吧,我不能。但现在我们知道它为什么发生了。据我所知,主要的问题是在创建字符集时_annex
字段未被填充,并且由于附件似乎用于跟踪非BMP平面中的字符,我认为它应该对于两个字符集都存在。顺便说一下,如果您决定使用file one(我会将其归档于CoreFoundation,因为这是实际问题所在),此信息可能对您的错误报告有所帮助。
似乎是基金会(或Swift标准库)的一些错误。 'supersetD'的情况在Xcode 9中返回'true',所以'union(_ :)'的错误似乎在最新的SDK中得到修复。解决方法:'CharacterSet(charactersIn:“”).union(CharacterSet(charactersIn:“A”))''。 – OOPer
它变得更加怪异:https://pastebin.com/zCrM1XUL。我做了一些挖掘,你可能想看看'CFCharacterSet.c'中的'_CFCharacterSetIsLongCharacterMember',这就是contains方法的作用(我确信我不太了解它)。 https://github.com/apple/swift-corelibs-foundation/blob/d95964015bed377baa99c3612281afa11bf06990/CoreFoundation/String.subproj/CFCharacterSet.c#L1716 – nyg
@nyg我花了大把的时间搞清楚它是什么,所以如果你我很好奇,看看下面的答案。 –