2017-09-22 9 views
3

CharacterSetで作業している間に、面白い問題が発生しました。私が今までに集めたものはCharacterSetUnicodeScalarに基づいています。スカラで初期化し、スカラがセット内に含まれているかどうかを確認できます。セットに問い合わせてCharacterが含まれているかどうかを調べるには、グリフがいくつかのユニコードスカラー値で構成されている可能性があります。UnicodeScalarのCharacterSetを確認すると、奇妙な動作が発生する

私の問題は、単一のUnicodeスカラー値(10進数で128518)である絵文字でテストするときです。これは、私はそれが仕事だろうと思っているだろう、単一のUnicodeスカラー値であり、ここでは結果されると:あなたが見ることができるようにCharacterSetがのために、単一のスカラー値(おそらくが含まれている場合、チェックが

"" == UnicodeScalar(128518)! // true 

// A few variations to show exactly what is being set up 
let supersetA = CharacterSet(charactersIn: "") 
let supersetB = CharacterSet(charactersIn: "A") 
let supersetC = CharacterSet(charactersIn: UnicodeScalar(128518)!...UnicodeScalar(128518)!) 
let supersetD = CharacterSet(charactersIn: UnicodeScalar(65)...UnicodeScalar(65)).union(CharacterSet(charactersIn: UnicodeScalar(128518)!...UnicodeScalar(128518)!)) 

supersetA.contains(UnicodeScalar(128518)!) // true 
supersetB.contains(UnicodeScalar(128518)!) // false 
supersetC.contains(UnicodeScalar(128518)!) // true 
supersetD.contains(UnicodeScalar(128518)!) // false 

の作品他の状況では期待どおりに動作しません。

私はCharacterSetまたはそれが(NSStringのようなつまりUTF-16)特定のエンコーディングで動作するかどうかの低レベルの実装に関するあらゆる情報を見つけることはできませんが、APIはUnicodeScalarで多くのことを扱うように私はそれがこのように失敗しています驚いています私はなぜそれが起こっているのか、さらに調査する方法については不明です。

これがなぜ起こっているのか誰にも分かりますか?

+0

Foundation(またはSwift Standard Library)のバグのようです。 'supersetD'の場合はXcode 9で' true'を返すので、 'union(_ :)'のバグは最新のSDKで修正されているようです。回避策: 'CharacterSet(charactersIn:" ")).union(CharacterSet(charactersIn:" A "))'。 – OOPer

+0

そしてそれはより奇妙になります:https://pastebin.com/zCrM1XUL。私はいくつかの掘り下げを行い、 'CFCharacterSet.c'の中の' _CFCharacterSetIsLongCharacterMember'を見たいと思うかもしれません。それはcontainsメソッドがしていることです(そして私はそれを理解していません)。 https://github.com/apple/swift-corelibs-foundation/blob/d95964015bed377baa99c3612281afa11bf06990/CoreFoundation/String.subproj/CFCharacterSet.c#L1716 – nyg

+0

@nygそれはすべてのことを考え出す時間を費やしました。好奇心が強い、以下の私の答えを見てください。 –

答えて

7

CharacterSetis 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の両方のバージョンは「Coming Soon」と呼ばれています)。しかし、ヨセミテのコードは、私がシエラの分解で見ていたものと一致するように見えました。とにかく、そのコードは次のようになります:

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; 

そして、何を推測

ておくと便利なことは、データ構造です!必要な定数もいくつかあります。私たちは、その後、CFCharacterSetIsLongCharacterMemberでブレークし、構造を記録することができ

enum { 
     __kCFCharSetClassTypeMask = 0x0070, 
      __kCFCharSetClassBuiltin = 0x0000, 
      __kCFCharSetClassRange = 0x0010, 
      __kCFCharSetClassString = 0x0020, 
      __kCFCharSetClassBitmap = 0x0030, 
      __kCFCharSetClassSet = 0x0040, 
      __kCFCharSetClassCompactBitmap = 0x0040, 
    // irrelevant stuff redacted 
}; 

上記の構造体に基づいて
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は、マスクとAND EDは0x20__kCFCharSetClassStringある、0xa0あります。

この時点で、Monty Pythonのキャストは「Get On With It!」と叫んでいるので、CFCharacterSetIsLongCharacterMemberのソースを見て、何が起こっているのかを見てみましょう。すべてCF_OBJC_FUNCDISPATCHVがらくたを過ぎてスキップ

が、我々はこの行を取得:

if (plane) { 

これは明らかに両方のケースでtrueに評価されます。次のテスト:

if (__CFCSetIsBuiltin(theSet)) { 

どちらのタイプが__kCFCharSetClassBuiltinだったので、これは、両方のケースではfalseと評価され、私たちは、そのブロックをスキップします。両方の場合において

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); 

と別館がisAnnexInvertedが偽であることを引き起こして、NULLであることから、これはfalseを返します。

それを修正する方法は...まあ、できません。しかし今、私たちはそれがなぜ起こったのか知っています。私が知ることから、主な問題は、キャラクタセットが作成されたときに_annexフィールドが埋まっていないことです。また、別館が非BMPプレーンのキャラクタを追跡するために使用されているように見えます。両方の文字セットに存在する必要があります。ちなみに、この情報はおそらくfile oneに決定したら、バグレポートに役立ちます(実際の問​​題があるので、CoreFoundationに対して提出します)。

+0

あなたは私の友人です...伝説です!あなたはそれに深く関わった!私はバグ報告を解雇するでしょう! –

関連する問題