2017-09-22 2 views
3

Während der Arbeit mit CharacterSet bin ich auf ein interessantes Problem gestoßen. Von dem, was ich bis jetzt gesammelt habe, basiert CharacterSet basiert auf UnicodeScalar; Sie können es mit Skalaren initialisieren und prüfen, ob ein Skalar in der Menge enthalten ist. Das Abfragen der Menge, um herauszufinden, ob sie eine Character enthält, deren Glyphe aus mehreren Unicode-Skalarwerten bestehen könnte, macht keinen Sinn.Überprüfung von CharacterSet für einzelne UnicodeScalar ergibt seltsames Verhalten

Mein Problem liegt, wenn ich mit dem Emoji, das ist ein einzelner Unicode-Skalarwert (128518 in Dezimal) testen. Da dies eine einzelne Unicode-Skalarwert ist, würde ich gedacht, es würde funktionieren, und hier sind die Ergebnisse:

"" == 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 

Wie Sie sehen können, arbeitet die Überprüfung, ob die CharacterSet einen einzelnen Skalarwert enthält (möglicherweise aufgrund eines Optimierung), aber unter anderen Umständen funktioniert es nicht wie erwartet.

ich keine Informationen über die untere Ebene Umsetzung von CharacterSet oder ob es funktioniert in einer bestimmten Codierung (dh UTF-16 wie NSString), aber wie die API viel mit UnicodeScalar beschäftigt Ich bin überrascht, es Fehler wie diese finden kann und ich bin mir nicht sicher, warum es passiert oder wie man weiter nachforscht.

Kann jemand etwas Licht auf warum dies sein?

+0

Scheint, einige Fehler von Foundation (oder Swift Standard Library) zu sein. Der 'supersetD'-Fall gibt mit Xcode 9 'true' zurück, so dass der Fehler von 'union (_ :)' in den neuesten SDKs behoben zu sein scheint. Problemumgehung: 'CharacterSet (charactersIn:" ") .union (Zeichensatz (ZeichenIn:" A "))'. – OOPer

+0

Und es wird seltsamer: https://pastebin.com/zCrM1XUL.Ich habe etwas gegraben und Sie könnten '_CFCharacterSetIsLongCharacterMember' in 'CFCharacterSet.c' suchen, das ist was die contains-Methode tut (und ich bin mir ziemlich sicher, dass ich nicht viel davon verstehe). https://github.com/apple/swift-corelibs-foundation/blob/d95964015bed377baa99c3612281afa11bf06990/CoreFoundation/Stringsubproj/CFCharacterSet.c#L1716 – nyg

+0

@nyg Ich verbrachte eine Menge Zeit herauszufinden, was alles, also wenn Sie Bin neugierig, sieh dir meine Antwort unten an. –

Antwort

7

Der Quellcode zu CharacterSetis available, actually. Die Quelle für contains ist:

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

es also im Grunde nur Anrufe durch CFCharacterSetIsLongCharacterMember. Der Quellcode für diese is also available, although only for Yosemite (die Versionen für El Cap und Sierra sagen beide "Coming Soon"). Der Yosemite-Code schien jedoch mit dem übereinzustimmen, was ich bei der Demontage von Sierra gesehen hatte. Wie auch immer, der Code dafür sieht so aus:

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

So können wir folgen und herausfinden, was los ist. Leider müssen wir unsere x86_64 Assembly Skills aus dem Weg räumen. Aber fürchte dich nicht, denn ich habe das schon für dich getan, denn anscheinend mache ich das an einem Freitagabend zum Spaß.

Eine hilfreiche Sache zu haben, ist die Datenstruktur:

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

Wir müssen wissen, was zum Teufel CFRuntimeBase ist auch:

typedef struct __CFRuntimeBase { 
    uintptr_t _cfisa; 
    uint8_t _cfinfo[4]; 
#if __LP64__ 
    uint32_t _rc; 
#endif 
} CFRuntimeBase; 

Und erraten, was! Es gibt auch einige Konstanten, die wir brauchen.

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

Wir können dann auf CFCharacterSetIsLongCharacterMember brechen und die Struktur log:

supersetA.contains(UnicodeScalar(128518)!) 

(lldb) po [NSData dataWithBytes:$rdi length:48] 
<21b3d2ad ffff1d00 90190000 02000000 00000000 00000000 06f60100 00000000 01000000 00000000 00000000 00000000> 

oben auf den structs Basierend, können wir herausfinden, was dieser Zeichensatz aus ist. In diesem Fall ist der relevante Teil das erste Byte von cfinfo von CFRuntimeBase, die Bytes 9-12 sind. Das erste Byte davon, 0x90 enthält die Typinformation für den Zeichensatz. Es muss AND Ed mit __kCFCharSetClassTypeMask sein, die uns 0x10 erhält, die __kCFCharSetClassRange ist.

Für diese Zeile:

supersetB.contains(UnicodeScalar(128518)!) 

die Struktur ist:

(lldb) po [NSData dataWithBytes:$rdi length:48] 
<21b3d2ad ffff1d00 a0190000 02000000 00000000 00000000 9066f000 01000000 02000000 00000000 00000000 00000000> 

Dieses Mal Byte 9 0xa0 ist, die AND ed mit der Maske 0x20 ist, __kCFCharSetClassString.

An diesem Punkt schreien die Monty Python Besetzung "Get On With It!", Also lasst uns durch die Quelle gehen für CFCharacterSetIsLongCharacterMember und sehen, was vor sich geht.

Skipping vorbei an all den CF_OBJC_FUNCDISPATCHV Mist, kommen wir zu dieser Zeile:

if (plane) { 

Dies wertet offensichtlich auf true in beiden Fällen. Nächster Test:

if (__CFCSetIsBuiltin(theSet)) { 

Dieses Ergebnis falsch in beiden Fällen, da weder Typ __kCFCharSetClassBuiltin war, so dass wir diesen Block überspringen.

isAnnexInverted = __CFCSetAnnexIsInverted(theSet); 

In beiden Fällen waren die _annex Zeiger null (alle Nullen am Ende der Struktur sehen), so ist dies false.

Dieser Test wird true aus dem gleichen Grund sein:

if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) { 

unter uns:

if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) { 

Die __CFCSetHasNonBMPPlane Makro überprüft _annex, so dass falsch ist. Das Emoji ist natürlich nicht in der BMP-Ebene, so scheint dies tatsächlich falsch für beide Fälle, auch die, die das richtige Ergebnis zurückgegeben wurde.

__CFCSetIsRange prüft, ob unser Typ __kCFCharSetClassRange ist, der nur das erste Mal wahr ist. Das ist unser Punkt der Divergenz. Der zweite Aufruf von dieser, die das falsche Ergebnis erzeugt, kehrt in der nächsten Zeile:

return (isAnnexInverted ? true : false); 

Und da die Anlage ist NULL, was isAnnexInverted als falsch, dies gibt false zurück.

Wie, um es zu beheben ... nun, kann ich nicht. Aber jetzt wissen wir, warum es passiert ist. Von dem, was ich sagen kann, ist das Hauptproblem, dass das Feld _annex nicht gefüllt wird, wenn der Zeichensatz erstellt wird, und da der Anhang verwendet wird, um die Zeichen in Nicht-BMP-Ebenen zu verfolgen, denke ich, dass es sollte für beide Zeichensätze vorhanden sein. Übrigens werden diese Informationen wahrscheinlich in einem Fehlerbericht hilfreich sein, wenn Sie sich dazu entscheiden, file one (ich würde es gegen CoreFoundation, da das ist, wo das eigentliche Problem ist).

+0

Sie mein Freund ... sind eine Legende! Du bist tief in das gegangen! Ich werde den Fehlerbericht abgefeuert bekommen! –