Skip to content
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

Add _stdlib_random for more platforms #1

Merged
merged 8 commits into from
Feb 10, 2018
Merged
51 changes: 51 additions & 0 deletions stdlib/public/core/ClosedRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,57 @@ extension ClosedRange where Bound: Strideable, Bound.Stride : SignedInteger {
}
}

extension ClosedRange
where Bound : FixedWidthInteger,
Bound.Magnitude : UnsignedInteger {

/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@_inlineable
public func random(
using generator: RandomNumberGenerator = Random.default
) -> Element? {
let isLowerNegative = Bound.isSigned && lowerBound < 0
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
var delta: Bound.Magnitude
if isLowerNegative {
delta = sameSign
? lowerBound.magnitude - upperBound.magnitude
: lowerBound.magnitude + upperBound.magnitude
} else {
delta = upperBound.magnitude - lowerBound.magnitude
}
if delta == Bound.Magnitude.max {
return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude)
}
delta += 1
let randomMagnitude = generator.next(upperBound: delta)
if sameSign {
return lowerBound + Bound(randomMagnitude)
} else {
return Bound.isSigned && randomMagnitude <= upperBound.magnitude
? Bound(randomMagnitude)
: 0 - Bound(randomMagnitude - upperBound.magnitude)
}
}
}

extension ClosedRange {
@_inlineable
public func overlaps(_ other: ClosedRange<Bound>) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/Integers.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2344,7 +2344,7 @@ ${assignmentOperatorComment(x.operator, False)}
% end
}

% for Range in ['CountableRange', 'CountableClosedRange']:
% for Range in ['Range', 'ClosedRange']:

extension FixedWidthInteger
where Self.Stride : SignedInteger,
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/Random.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ extension RandomNumberGenerator {
/// Using the preferred way:
///
/// let random = UInt8.random(in: .min ... .max)
/// let randomToTen = UInt32.random(in: 0 ... 128)
/// let randomToTen = UInt32.random(in: 0 ..< 10)
///
/// - Note: The default implementation of randomness is cryptographically secure.
/// It utilizes arc4random(3) on newer versions of macOS, iOS, etc. On older
Expand Down
50 changes: 50 additions & 0 deletions stdlib/public/core/Range.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,56 @@ extension Range where Bound: Strideable, Bound.Stride : SignedInteger {
}
}

extension Range
where Bound : FixedWidthInteger,
Bound.Magnitude : UnsignedInteger {

/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@_inlineable
public func random(
using generator: RandomNumberGenerator = Random.default
) -> Element? {
guard lowerBound != upperBound else {
return nil
}
let isLowerNegative = Bound.isSigned && lowerBound < 0
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
let delta: Bound.Magnitude
if isLowerNegative {
delta = sameSign
? lowerBound.magnitude - upperBound.magnitude
: lowerBound.magnitude + upperBound.magnitude
} else {
delta = upperBound.magnitude - lowerBound.magnitude
}
let randomMagnitude = generator.next(upperBound: delta)
if sameSign {
return lowerBound + Bound(randomMagnitude)
} else {
return randomMagnitude < upperBound.magnitude
? Bound(randomMagnitude)
: -1 - Bound(randomMagnitude - upperBound.magnitude)
}
}
}

extension Range: RangeExpression {
@_inlineable // FIXME(sil-serialize-all)
public func relative<C: Collection>(to collection: C) -> Range<Bound>
Expand Down
125 changes: 101 additions & 24 deletions stdlib/public/stubs/LibcShims.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,34 @@
#if defined(__APPLE__)
#define _REENTRANT
#include <math.h>
#include <CoreFoundation/CoreFoundation.h>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this kosher for the standard library? We could do without the detailed error message, IMO, if that simplifies dependencies.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xwu This dependency already exists. But I could remove the SecCopyErrorMessageString call.

#include <Security/Security.h>
#endif

#if defined(__Fuchsia__)
#include <sys/random.h>
#endif

#if defined(__linux__)
#include <linux/version.h>
#include <sys/syscall.h>
#else
#define KERNEL_VERSION(...) 0
#endif

#include <random>
#include <type_traits>
#include <cmath>

#if defined(_WIN32)
#include <io.h>
#define WIN32_LEAN_AND_MEAN
#define WIN32_NO_STATUS
#include <Windows.h>
#undef WIN32_NO_STATUS
#include <Bcrypt.h>
#include <Ntdef.h>
#include <Ntstatus.h>
#else
#include <unistd.h>
#include <pthread.h>
Expand Down Expand Up @@ -332,44 +352,101 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound) {
}

#if defined(__APPLE__)
#include <Security/Security.h>

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) {
Copy link

@xwu xwu Feb 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why isn't this conditional written in the form of a macro? And why not just stick with SecRandomCopyBytes for all Apple platforms?

(I recognize that this isn't being changed here, but I thought I'd ask.)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea so Greg Parker brought this up as well in the actual pr, but I know a few on the mailing list had said that the Darwin solution should use arc4random. In addition to those comments, the whole family of arc4random is advertised as being always successful on the mac man pages man arc4random, so removing the possibility of error in something I'm in favor of. Although, I do see using one solution for all versions as an easier implementation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why isn't this conditional written in the form of a macro?

@xwu The original was using macros. I suggested __builtin_available because it should be a runtime conditional. AFAIK, until the stdlib is a system library, MAC_OS_X_VERSION_MIN_REQUIRED will always be MAC_OS_X_VERSION_10_9 (see supported target platforms).

arc4random_buf(buf, nbytes);
}else {
} else {
OSStatus status = SecRandomCopyBytes(kSecRandomDefault, nbytes, buf);
if (status != errSecSuccess) {
fatalError(0, "Fatal error: SecRandomCopyBytes failed with error %d\n", status);
char message[256] = "";
if (CFStringRef cfString {SecCopyErrorMessageString(status, nullptr)}) {
CFStringGetCString(cfString, message, sizeof(message),
kCFStringEncodingUTF8);
CFRelease(cfString);
}
fatalError(0, "'%s' error %d: %s\n", __func__, status, message);
}
}
}
#elif defined(__linux__)
#include <linux/version.h>
#include <sys/syscall.h>

#elif defined(__BIONIC__) || defined(__CYGWIN__) || defined(__FreeBSD__)

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
auto _read = [&]() -> __swift_ssize_t {
return syscall(SYS_getrandom, buf, bytes, 0);
};
#else
auto _read = [&]() -> __swift_ssize_t {
static const int fd = _stdlib_open("/dev/urandom", O_RDONLY);
if (fd < 0) {
fatalError(0, "Unable to open '/dev/urandom'\n");
arc4random_buf(buf, nbytes);
}

#elif defined(__Fuchsia__)

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
while (nbytes > 0) {
constexpr __swift_size_t max_nbytes = 256;
__swift_size_t actual_nbytes = (nbytes < max_nbytes ?
nbytes : max_nbytes);
if (0 != getentropy(buf, actual_nbytes)) {
fatalError(0, "'%s' error %d: %s\n", __func__, errno, strerror(errno));
}
return _stdlib_read(fd, buf, bytes);
};
#endif
buf = static_cast<uint8_t *>(buf) + actual_nbytes;
nbytes -= actual_nbytes;
}
}

#elif defined(__linux__) && LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
while (nbytes > 0) {
auto result = _read();
if (result < 1) {
if (errno == EINTR) { continue; }
fatalError(0, "Unable to read '/dev/urandom'\n");
__swift_ssize_t actual_nbytes = syscall(SYS_getrandom, buf, nbytes, 0);
if (actual_nbytes < 1) {
if (errno == EINTR) {
continue;
} else {
fatalError(0, "'%s' error %d: %s\n", __func__, errno, strerror(errno));
}
}
buf = static_cast<uint8_t *>(buf) + result;
nbytes -= result;
buf = static_cast<uint8_t *>(buf) + actual_nbytes;
nbytes -= actual_nbytes;
}
}

#elif defined(_WIN32)

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
// TODO: Bcrypt.dll and/or Bcrypt.lib
NTSTATUS status = BCryptGenRandom(nullptr,
static_cast<PUCHAR>(buf),
static_cast<ULONG>(nbytes),
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (!NT_SUCCESS(status)) {
// TODO: FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, ...);
fatalError(0, "'%s' error %#.8x\n", __func__, status);
}
}

#else // Haiku, Linux(<3.17), etc.

SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
static const int fd = _stdlib_open("/dev/urandom", O_RDONLY, 0);
if (fd < 0) {
fatalError(0, "'%s' error %d: %s\n", __func__, errno, strerror(errno));
}
while (nbytes > 0) {
__swift_ssize_t actual_nbytes = _stdlib_read(fd, buf, nbytes);
if (actual_nbytes < 1) {
if (errno == EINTR) {
continue;
} else {
fatalError(0, "'%s' error %d: %s\n", __func__, errno, strerror(errno));
}
}
buf = static_cast<uint8_t *>(buf) + actual_nbytes;
nbytes -= actual_nbytes;
}
}

#endif