* Refactor Bincompat
Organize everything around internal functions that test for
a particular OS version.
Correctly handle cases where we don't know the version of the app.
Make all bincompat functions consistently return `true` for the
legacy semantics, `false` for new semantics. Consistently name
them all to reflect this.
* Conditionalize the support for SR-14635
SR-14635 pointed out a hole in the updated dynamic casting logic
that allowed certain casts that should have been illegal.
In particular, when casting certain types to Obj-C protocols,
the Swift value gets boxed; we would permit the cast to succeed
whenever the resulting box satisfied the protocol. For example,
this allowed any Swift value to be cast to `NSCopying` regardless of
whether or not it implemented the required `copy(with:)` method.
This was fixed in #37683 to reject such casts but of course some folks were
depending on this behavior to pass Swift data into Obj-C functions.
(The properly supported approach for passing arbitrary Swift data into
Obj-C functions is to cast the Swift value to `AnyObject`.)
This change makes that new behavior conditional. For now,
the legacy semantics are enabled on Apple platforms and the
new semantics are in use everywhere else. This will allow
us to gradually enable enforcement of the new behavior over
time.
* Just skip this test on Apple platforms, since it is inconsistently implemented there (and is therefore not really testable)
On Windows, std::max_align_t is only 8-byte aligned, but Swift assumes 16-byte alignment. MaximumAlignment is our notion of the maximum alignment of a type, so use that instead.
* SR-14635: Casts to NSCopying should not always succeed
The runtime dynamic casting logic explores a variety of strategies for
each cast request. One of the last options is to wrap the source
in a `__SwiftValue` box so it can bridge to Obj-C. The previous
code was overly aggressive about such boxing; it performed the boxing
for any source type and only checked to verify that the `__SwiftValue`
box itself was compatible with the destination.
Among other oddities, this results in the behavior discussed
in SR-14635, where any Swift or Obj-C type will always successfully cast
to NSCopying because `__SwiftValue` is compatible with NSCopying.
This is actually two subtly different issues:
* Class types should not be subject to `__SwiftValue` boxing at all.
Casting class types to class existentials is already handled elsewhere,
so this function should just reject any source with class type.
* Non-class types should be boxed only when being assigned to
an AnyObject (an "unconstrained class existential"). If
the class existential has constraints, it is by definition
a class-constrained existential which should not receive
any non-class object.
To solve these, this PR disables `__SwiftValue` boxing in two cases:
1. If the source is a class (reference) type.
2. If the destination has constraints
Resolves SR-14635
Resolves rdar://78224322
* Avoid boxing class metatypes on Darwin
But continue boxing
* Non-class metatypes on all platforms
* All metatypes on non-Darwin platforms
Obj-C interop requires that we do not box class metatypes;
those must be usable as simple pointers when passed to Obj-C.
But no other metatype is object-compatible, so we have to
continue boxing everything else.
* Split out ObjC-specific test cases
Take the existing CompatibilityOverride mechanism and generalize it so it can be used in both the runtime and Concurrency libraries. The mechanism is preprocessor-heavy, so this requires some tricks. Use the SWIFT_TARGET_LIBRARY_NAME define to distinguish the libraries, and use a different .def file and mach-o section name accordingly.
We want the global/main executor functions to be a little more flexible. Instead of using the override mechanism, we expose function pointers that can be set by the compatibility library, or by any other code that wants to use a custom implementation.
rdar://73726764
The new cast logic checks and aborts if a non-nullable pointer has a null value
at runtime. However, because this was tolerated by the old casting logic, some
apps inadvertently rely on being able to cast such null references.
This change adds specific checks for null pointers in compatibility mode and
handles them similarly to the previous casting logic:
* Casting to Obj-C or CF type: casting a null pointer succeeds
* Casting to class-constrained existential: casting a null pointer succeeds
* Casting to a Swift class type: cast fails without crashing
* Bridging from Obj-C class to Swift struct type: cast fails without crashing
This also adds a guard to avoid trying to lookup the dynamic type of a null
class reference.
In non-compatibility mode, all of the above cause an immediate runtime crash.
The changes here are only intended to support existing binaries running against
new Swift runtimes.
Resolves rdar://72323929
This restores the earlier behavior of Optionals cast to
AnyHashable, so that [String?:Any] dictionaries cast
to [AnyHashable:Any] can be indexed by plain String
keys.
This is a little problematic because it's not consistent with the
compiler-optimized casts.
But the ability to index such dictionaries by plain String
keys seems important to preserve. SR-9047 will expand
Optional/AnyHashable interoperability so that such
indexing works without this special case.
An earlier proposal would link-check this, changing the behavior
depending on the caller. But it's not really workable to
change the behavior seen by intermediate frameworks depending
on the app they're being called by.
As part of making casting more consistent, the behavior of Optional -> AnyHashable casts
was changed in some cases. This PR provides a hook for re-enabling the old behavior
in certain contexts.
Background: Most of the time, casts from String? to AnyHashable get optimized
to just injects the String? into the AnyHashable, so the following
has long been true and remains true in Swift 5.4:
```
let s = "abc"
let o: String? = s
// Next test is true because s is promoted to Optional<String>
print(s == o)
// Next test is false: Optional<String> and String are different types
print(s as AnyHashable == o as AnyHashable)
```
But when casts ended up going through the runtime, Swift 5.3 behaved
differently, as you could see by casting a dictionary with `String?` keys (in
the generic array code, key and value casts always use the runtime logic). In
the following code, both print statements behave differently in Swift 5.4 than
before:
```
let a: [String?:String] = ["Foo":"Bar"]
let b = a as [AnyHashable:Any]
print(b["Foo"] == "Bar") // Works before Swift 5.4
print(b["Foo" as String?] == "Bar") // Works in Swift 5.4 and later
```
Old behavior: The `String?` keys would get unwrapped to `String` before being injected into AnyHashable. This allows the first to work but strangely breaks the second.
New behavior: The `String?` keys do not get unwrapped. This breaks the first but makes the second work.
TODO: The long-term goal, of course, is for `AnyHashable("Foo" as String?)` to
test equal to `AnyHashable("Foo")` (and hash the same, of course). In that
case, all of the tests above will succeed.
Resolves rdar://73301155
Background: We've noticed a lot of problems from Obj-C APIs that returned null
even though they were declared to never do so. These mismatches subvert Swift's
type system and can lead to hard-to-diagnose crashes much later in the program.
This fatal error was introduced into the primary casting function to help catch
such problems closer to the point where they occur so developers could more
easily identify and fix them.
However, there's been some concern about what this means for old binaries, so
we're considering a check here that would allow the old behavior in certain
cases yet to be determined. This PR adds the framework for such a check.
Resolves rdar://72323929
* Dynamic Casting: Properly unwrap existential metatype sources
Existential metatypes are really just existentials that hold metatypes. As
such, they should be handled in the general casting logic in much the same way
as regular existentials: They should generally be ignored by most casting logic,
and unwrapped as necessary at the top level.
In particular, the previous code would fail to correctly handle the following
cast from an existential metatype (`AnyObject.Type`) to an existential
(`AnyObject`):
```
class C {}
let a = C.self as AnyObject.Type
let b = a as! AnyObject
```
With the old code, `b` above would hold a reference to a `__SwiftValue` box
containing the type reference. The correct result would simply store the type
reference directly in `b`. These two are only really distinguishable in that
the correct form permits `a === b` to return `true`.
Fixes rdar://70582753
Note: This is not yet fully supported on Linux. Basically, metatypes on Linux are not currently
fully compatible with reference-counted class pointers, which prevents us from
fully supporting metatype operations on Linux that we support on macOS.
* Dynamic Cast Rework: Runtime
This is a completely refactored version of the core swift_dynamicCast
runtime method.
This fixes a number of bugs, especially in the handling of multiply-wrapped
types such as Optional within Any. The result should be much closer to the
behavior specified by `docs/DynamicCasting.md`.
Most of the type-specific logic is simply copied over from the
earlier implementation, but the overall structure has been changed
to be uniformly recursive. In particular, this provides uniform
handling of Optional, existentials, Any and other common "box"
types along all paths. The consistent structure should also be
easier to update in the future with new general types.
Benchmarking does not show any noticable performance implications.
**Temporarily**, the old implementation is still available. Setting the
environment variable `SWIFT_OLD_DYNAMIC_CAST_RUNTIME` before launching a program
will use the old runtime implementation. This is only to facilitate testing;
once the new implementation is stable, I expect to completely remove the old
implementation.