swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.27k stars 1.13k forks source link

[SR-7463] Hey look, another Data slice bug #3720

Open CharlesJS opened 6 years ago

CharlesJS commented 6 years ago
Previous ID SR-7463
Radar None
Original Reporter @CharlesJS
Type Bug

Attachment: Download

Environment Xcode 9.4 beta (9Q1004a) Apple Swift version 4.1.1 (swiftlang-902.0.50 clang-902.0.37.1) Target: x86_64-apple-darwin17.5.0
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 1 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: aa6490d568a7ecdb4f7b862155560005

Issue Description:

PRE-CREDITS TEASER:

PICARD: Mr. Data. What is the current status of your positronic network?

DATA: Captain, I am a slice of a Data struct, which was bridged from a dispatch_data_t from Objective-C, which was toll-free bridged to an NSData object, and then bridged to Swift.

PICARD: Mr. Data, what is the size of your contents?

DATA: 360 bytes, sir.

PICARD: Mr. Data. Can you enumerate your contents?

DATA: ...

PICARD: Mr. Data. Your contents please?

DATA: ...

PICARD:


This may come as a shock, but it turns out there's a bug involving slicing Data structs that have been bridged from Objective-C. In other news, water has been discovered in the ocean. Dun dun dunnnnnnn!

What's going on this time is that a slice of a Data object will sometimes fail to do anything when you call enumerateBytes() on it. I haven't figured out the general rule for what will trigger this, but this particular case does it for me every time:

I've attached a project, but here's the raw code:

DataMaker.m:

#import "DataMaker.h"
#include <dispatch/dispatch.h>

@implementation DataMaker

+ (void)makeMeAData:(void (^)(NSData *))handler {
    int fd = open("/dev/random", O_RDONLY);

    dispatch_io_t stream = dispatch_io_create(DISPATCH_IO_STREAM, fd, dispatch_get_main_queue(), ^(__unused int error) { close(fd); });

    __block dispatch_data_t data = dispatch_data_create("", 0, nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);

    dispatch_io_read(stream, 0, 5792, dispatch_get_main_queue(), ^(bool done, dispatch_data_t eachData, __unused int error) {
        data = dispatch_data_create_concat(data, eachData);

        if (done) {
            close(fd);
            handler((NSData *)data);
        }
    });
}

@end

main.swift:

import Foundation

DataMaker.makeMeAData { data in
    let slice = data!.suffix(360)

    print("Enumerating a slice of \(slice.count) bytes")

    slice.enumerateBytes { buf, _, _ in
        print("here's a chunk of \(buf.count) bytes")
    }

    print("Done!")

    exit(0)
}

And the output:

Enumerating a slice of 360 bytes
Done!
Program ended with exit code: 0

As you see, the contents of the slice are not actually enumerated.

belkadan commented 6 years ago

:-( Thanks for finding these, Charles.

cc @phausler

CharlesJS commented 6 years ago

I still think that DataSlice should be a separate type, with a DataProtocol combining them (as well as DispatchData), like we have for String and Substring, and that we could solve the "nobody uses it" problem by just bridging Objective-C NSData method arguments to DataProtocol instead of Data, kind of like how a bunch of APIs that used to take String have gradually been changed to take StringProtocol instead. It would lead to a much simpler implementation, and probably have a lot fewer weird problems like this. But as long as the system remains the way it is, I'll keep looking for corner cases that break it.