Skip to content

Fix bit shift math in CFCharacterSet equality code #936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2017

Conversation

mark1smi
Copy link
Contributor

@mark1smi mark1smi commented Apr 6, 2017

The code was originally shifting a 32-bit value by 32 bits in
some cases, which is undefined behavior. The intent was that
all bits would be shifted out, leaving a value of 0 to perform
computation with. We now treat the value as 64-bits for the shift
and re-cast to 32-bits for the computation.

The code was originally shifting a 32-bit value by 32 bits in
some cases, which is undefined behavior. The intent was that
all bits would be shifted out, leaving a value of 0 to perform
computation with. We now treat the value as 64-bits for the shift
and re-cast to 32-bits for the computation.
@mark1smi
Copy link
Contributor Author

mark1smi commented Apr 6, 2017

The code below exercises the code paths in fast path comparison __CFCSetIsBitmapEqualToRange between character sets with an internal representation of __kCFCharSetClassRange and __kCFCharSetClassBitmap. This code fails prior to the fix.

Interestingly, this bug also symptomizes in shipping OS X and iOS.

    CFCharacterSetRef rangeSet, bitmapSet;

    for (CFIndex startOffset = 0; startOffset < 32; startOffset++) {
        for (CFIndex endOffset = 0; endOffset < 32; endOffset++) {
            rangeSet = CFCharacterSetCreateWithCharactersInRange(NULL, CFRangeMake(1 + startOffset, 100 + endOffset));

            CFDataRef data = CFCharacterSetCreateBitmapRepresentation(NULL, rangeSet);
            bitmapSet = CFCharacterSetCreateWithBitmapRepresentation(NULL, data);
            CFRelease(data);

            XCTAssertTrue(CFEqual(rangeSet, bitmapSet));

            CFRelease(rangeSet);
            CFRelease(bitmapSet);
        }
    }

    for (CFIndex startOffset = 0; startOffset < 32; startOffset++) {
        for (CFIndex endOffset = 0; endOffset < 32; endOffset++) {
            rangeSet = CFCharacterSetCreateWithCharactersInRange(NULL, CFRangeMake(startOffset, 0 + endOffset));

            CFDataRef data = CFCharacterSetCreateBitmapRepresentation(NULL, rangeSet);
            bitmapSet = CFCharacterSetCreateWithBitmapRepresentation(NULL, data);
            CFRelease(data);

            XCTAssertTrue(CFEqual(rangeSet, bitmapSet));

            CFRelease(rangeSet);
            CFRelease(bitmapSet);
        }
    }

@mark1smi
Copy link
Contributor Author

mark1smi commented Apr 6, 2017

cc: @modocache

@pushkarnk
Copy link
Member

@mark1smi Good catch. Will it be possible to include a unit test for this?

@mark1smi
Copy link
Contributor Author

mark1smi commented Apr 7, 2017

@pushkarnk Thanks. I provided a unit test in the comment to exercise the C API, but didn't find anywhere in the project where C unit tests run. I spent a number of hours attempting to provide a test in NSCharacterSet.swift, but encountered two problems:

  1. It was unclear to me how much the swift tests are used because they would not link (I submitted pull request Update symbols to match new swift mangling #937 to address that)
  2. After fixing the link problem, it appears that something is fundamentally broken with NSCharacterSet in swift, such that you can't even compare two NSCharacterSets (infinite recursion with NSCharacterSet swift's isEqual sending its _cfObject down to CFEqual, and CFEqual sending back up via CFTYPE_SWIFT_FUNCDISPATCH1(Boolean, cf1, NSObject.isEqual, (CFSwiftRef)cf2); ). You'll notice that there are no enabled tests in TestNSCharacterSet.swift that compare two NSCharacterSets!

For reference, the swift code for the unit test would look like:

    func test_RangeCompareWithBitmap() {
        for startOffset: Int in 0..<32 {
            for endOffset: Int in 0..<32 {
                //let rangeSet = CharacterSet(Range:(1 + startOffset, 100 + endOffset))
                let rangeSet = CharacterSet(charactersIn: Range<UnicodeScalar> (uncheckedBounds:(UnicodeScalar(1 + startOffset)!, UnicodeScalar(100 + endOffset)!)));
                let bitmap = rangeSet.bitmapRepresentation
                let bitmapSet = CharacterSet(bitmapRepresentation: bitmap)
                XCTAssertEqual(rangeSet, bitmapSet)
            }
        }

        for startOffset: Int in 0..<32 {
            for endOffset: Int in 0..<32 {
                let rangeSet = CharacterSet(charactersIn: Range<UnicodeScalar> (uncheckedBounds:(UnicodeScalar(startOffset)!, UnicodeScalar(endOffset)!)));
                let bitmap = rangeSet.bitmapRepresentation
                let bitmapSet = CharacterSet(bitmapRepresentation: bitmap)
                XCTAssertEqual(rangeSet, bitmapSet)
            }
        }
    }

I could add that as a disabled test to the diff if you would like, but it felt to me a strange way of testing the C API.

@parkera
Copy link
Contributor

parkera commented Apr 7, 2017

We know that NSCharacterSet is basically un-subclassable, which is why we switched the implementation over to has-a CFCharacterSet instead. That is likely the issue you ran into with it in Swift.

@parkera
Copy link
Contributor

parkera commented Apr 7, 2017

I'll run our internal CF tests against your change to verify this. (rdar://31503541)

@parkera
Copy link
Contributor

parkera commented Apr 7, 2017

@swift-ci test and merge

@swift-ci swift-ci merged commit d959640 into swiftlang:master Apr 7, 2017
@mark1smi mark1smi deleted the CFCharacterSet_bitmath branch April 7, 2017 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants