Skip to content

Workaround for difference in behavior between optimizer and runtime understanding of casts (2.2) #1990

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 3 commits into from
Apr 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2027,10 +2027,16 @@ checkDynamicCastFromOptional(OpaqueValue *dest,
_fail(src, srcType, targetType, flags);
return {false, nullptr};
}

// Get the destination payload type
const Metadata *targetPayloadType =
cast<EnumMetadata>(targetType)->getGenericArgs()[0];

// Inject the .None tag
swift_storeEnumTagSinglePayload(dest, payloadType, enumCase,
swift_storeEnumTagSinglePayload(dest, targetPayloadType, enumCase,
1 /*emptyCases=*/);
_succeed(dest, src, srcType, flags);

// We don't have to destroy the source, because it was nil.
return {true, nullptr};
}
// .Some
Expand All @@ -2054,6 +2060,13 @@ bool swift::swift_dynamicCast(OpaqueValue *dest,
switch (targetType->getKind()) {
// Handle wrapping an Optional target.
case MetadataKind::Optional: {
// If the source is an existential, attempt to cast it first without
// unwrapping the target. This handles an optional source wrapped within an
// existential that Optional conforms to (Any).
if (auto srcExistentialType = dyn_cast<ExistentialTypeMetadata>(srcType)) {
return _dynamicCastFromExistential(dest, src, srcExistentialType,
targetType, flags);
}
// Recursively cast into the layout compatible payload area.
const Metadata *payloadType =
cast<EnumMetadata>(targetType)->getGenericArgs()[0];
Expand Down
25 changes: 2 additions & 23 deletions test/1_stdlib/Casts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,9 @@ import ObjectiveC
let CastsTests = TestSuite("Casts")

// Test for SR-426: missing release for some types after failed conversion
class DeinitTester {
private let onDeinit: () -> ()

init(onDeinit: () -> ()) {
self.onDeinit = onDeinit
}
deinit {
onDeinit()
}
}

func testFailedTupleCast(onDeinit: () -> ()) {
// This function is to establish a scope for t to
// be deallocated at the end of.
let t: Any = (1, DeinitTester(onDeinit: onDeinit))
_ = t is Any.Type
}

CastsTests.test("No leak for failed tuple casts") {
var deinitRan = false
testFailedTupleCast {
deinitRan = true
}
expectTrue(deinitRan)
let t: Any = (1, LifetimeTracked(0))
expectFalse(t is Any.Type)
}

protocol P {}
Expand Down
91 changes: 74 additions & 17 deletions test/1_stdlib/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,6 @@ import SwiftPrivate
import ObjectiveC
#endif

class DeinitTester {
private let onDeinit: () -> ()

init(onDeinit: () -> ()) {
self.onDeinit = onDeinit
}
deinit {
onDeinit()
}
}

let OptionalTests = TestSuite("Optional")

protocol TestProtocol1 {}
Expand Down Expand Up @@ -93,6 +82,7 @@ OptionalTests.test("Equatable") {

struct X {}
class C {}
class D {}

class E : Equatable {}
func == (_: E, _: E) -> Bool { return true }
Expand Down Expand Up @@ -190,27 +180,67 @@ OptionalTests.test("flatMap") {
expectEmpty((3 as Int32?).flatMap(half))
}

// FIXME: @inline(never) does not inhibit specialization

@inline(never)
@_semantics("optimize.sil.never")
func anyToAny<T, U>(a: T, _ : U.Type) -> U {
return a as! U
}

@inline(never)
@_semantics("optimize.sil.never")
func anyToAnyIs<T, U>(a: T, _ : U.Type) -> Bool {
return a is U
}

@inline(never)
@_semantics("optimize.sil.never")
func anyToAnyIsOptional<T, U>(a: T?, _ : U.Type) -> Bool {
return a is U?
}

@inline(never)
@_semantics("optimize.sil.never")
func anyToAnyOrNil<T, U>(a: T, _ : U.Type) -> U? {
return a as? U
}

@inline(never)
@_semantics("optimize.sil.never")
func canGenericCast<T, U>(a: T, _ ty : U.Type) -> Bool {
return anyToAnyOrNil(a, ty) != nil
}

protocol TestExistential {}
extension Int : TestExistential {}

OptionalTests.test("Casting Optional") {
let x = C()
let sx: C? = x
let nx: C? = nil
expectTrue(anyToAny(x, Optional<C>.self)! === x)
expectTrue(anyToAnyIs(x, Optional<C>.self))
expectFalse(anyToAnyIs(x, Optional<D>.self))

expectTrue(anyToAny(sx, C.self) === x)
expectTrue(anyToAnyIs(sx, C.self))
expectFalse(anyToAnyIs(sx, D.self))

expectTrue(anyToAny(sx, Optional<C>.self)! === x)
expectTrue(anyToAnyIs(sx, Optional<C>.self))
expectTrue(anyToAnyIsOptional(sx, C.self))
expectFalse(anyToAnyIsOptional(sx, D.self))

expectTrue(anyToAny(nx, Optional<C>.self) == nil)
expectTrue(anyToAnyIs(nx, Optional<C>.self))

// You can cast a nil of any type to a nil of any other type
// successfully
expectTrue(anyToAnyIs(nx, Optional<D>.self))

expectTrue(anyToAnyIsOptional(nx, C.self))

expectTrue(anyToAnyOrNil(nx, C.self) == nil)

let i = Int.max
Expand All @@ -229,19 +259,46 @@ OptionalTests.test("Casting Optional") {
expectTrue(anyToAnyOrNil(ni, Int.self) == nil)

// Test for SR-459: Weakened optionals don't zero.
var deinitRan = false
do {
var t = DeinitTester { deinitRan = true }
_ = anyToAny(Optional(t), CustomDebugStringConvertible.self)
}
expectTrue(deinitRan)
var t = LifetimeTracked(0)
_ = anyToAny(Optional(t), CustomDebugStringConvertible.self)
expectTrue(anyToAnyIs(Optional(t), CustomDebugStringConvertible.self))

// Test for SR-912: Runtime exception casting an Any nil to an Optional.
let oi: Int? = nil
expectTrue(anyToAny(oi as Any, Optional<Int>.self) == nil)
expectTrue(anyToAnyIs(oi as Any, Optional<Int>.self))
// For good measure test an existential that Optional does not conform to.
expectTrue(anyToAny(3 as TestExistential, Optional<Int>.self) == 3)
// And a type that is not convertible to its target.
anyToAny(nx as Any, Optional<Int>.self)

// Double-wrapped optional
expectTrue(anyToAnyIsOptional(oi as Any, Int.self))

// And a type that is not convertible to its target.
expectTrue(anyToAny(nx as Any, Optional<Int>.self) == nil)
expectTrue(anyToAnyIs(nx as Any, Optional<Int>.self))
expectTrue(anyToAnyIsOptional(nx as Any, Int.self))

expectTrue(anyToAnyOrNil(sx as Any, Optional<Int>.self) == nil)
expectFalse(anyToAnyIs(sx as Any, Optional<Int>.self))
expectFalse(anyToAnyIsOptional(sx as Any, Int.self))

// OK to convert nil of any type to optional of any other type
expectTrue(anyToAnyIs(Optional<(String, String)>.None, Optional<Bool>.self))
expectTrue(anyToAnyIsOptional(Optional<(String, String)>.None, Bool.self))
}

OptionalTests.test("Casting Optional Traps") {
let nx: C? = nil
expectCrashLater()
anyToAny(nx, Int.self)
}
OptionalTests.test("Casting Optional Any Traps") {
let nx: X? = X()
expectCrashLater()
_blackHole(anyToAny(nx as Any, Optional<Int>.self))
}

class TestNoString {}
class TestString : CustomStringConvertible, CustomDebugStringConvertible {
Expand Down