I propose restricting pointer conversions in Swift to the C interop use case.
Constrain Pointer Conversion Feature to C-interoperability
- Proposal: SE-NNNN Constrain Pointer Conversion Feature to C-interoperability
- Author: Guillaume Lessard
- Review Manager: TBD
- Status: pending
- Implementation: pending
- Bugs: rdar://92429037
- Previous Revision:
Introduction
Currently, a Swift function with a parameter type of UnsafePointer<T>
can be called with an Array<T>
or an inout T
argument. These values are then viewed as an UnsafePointer<T>
from within the function body. This has been the case since the beginning of Swift in order to facilitate interoperability with C and Objective-C.
We believe that this feature is too broad, since it works for any function parameter of type UnsafePointer<T>
. By restricting this feature to the use cases of C, C++ and Objective-C interopability, we would improve the safety of Swift code, removing many opportunities for inadvertent bounds overruns.
Motivation
Swift allows pointer conversions for any function parameter of type UnsafePointer<T>
, including functions written purely in Swift. This leads to the surprising possibility that a called function can access out-of-bounds memory with a called site that hasn't obviously used an unsafe construct. A common case is a simple re-implementation of C's strlen()
:
func arrlen(_ p: UnsafePointer<CChar>) -> Int {
var i = 0
while p[i] != 0 { i += 1 }
return i
}
var array: [CChar] = [1, 2, 3]
arrlen(array) // probably not 3
This code will read beyond array
's heap storage if none of its elements equals 0
.
There have been well-documented program bugs and memory corruption issues due to to unintended use of this feature. While the actual type of the parameter contains the word Unsafe
, this is still unexpected: Unsafe
in a type name means it has manual memory management, not unchecked memory access.
There has been an initial attempt to restrict pointer conversion with the @_nonEphemeral
parameter attribute. The intent of the @_nonEphemeral
attribute is to warn against inadvertent pointer escapes in Swift code. The attribute must be present for the warning to appear. Unfortunately, this doesn't do enough to prevent inadvertent out-of-bounds memory access.
Proposed solution
We should restrict the pointer conversion feature to only be available for arguments to C, C++ and Objective-C functions. This would prevent inadvertent conversions from arrays to single pointers on one hand, and it would completely prevent the kind of pointer escapes the @_nonEphemeral
attribute is meant to warn against.
Pointer conversion should be usable transitively when Swift code implements a shim that calls out to C. We therefore should also add an attribute @forwardedToC
(name to be discussed) that enables pointer conversion for Swift function parameters when it is present.
Detailed design
The newly restricted pointer conversion feature would work just as it already does when applied to an argument to a C function call:
- Arguments of type
[T]
can be converted toUnsafePointer<T>
orUnsafeRawPointer
parameters. - Mutable arguments of type
[T]
orinout T
can be converted toUnsafeMutablePointer<T>
orUnsafeMutableRawPointer
parameters. This requires using the&
sigil. - Arguments of type
String
can be converted to parameters of typesUnsafePointer<UInt8>
,UnsafePointer<Int8>
orUnsafeRawPointer
.
In order to support shims written in Swift that forward their pointer parameters to an underlying C library, we will add a parameter attribute @forwardedToC
(naming suggestion welcome). This parameter will be valid only for Unsafe[Mutable]Pointer<T>
and Unsaf[Mutable]RawPointer
parameters. A swift method overriding an Objective-C method with an UnsafePointer<T>
parameter must use the @forwardedToC
attribute.
Aspirational feature: The @forwardedToC
attribute will only be valid when a Swift function forwards the marked parameter to either (a) a C function, or (b) another Swift function that has a compatible parameter marked with @forwardedToC
. Trying to use the attribute on an invalid parameter declaration will be a compiler error.
Variables of function type (e.g. var f: (UnsafePointer<Int>, Int) -> Int
) do not distinguish whether they store a Swift function or a C function pointer. As such, function calls made through stored variables will not allow pointer conversion, and will require using the withUnsafePointer(to:)
or withUnsafeBytes(of:)
functions, their mutable counterparts or their Array equivalents.
Source compatibility
This is a source-breaking change and must occur in conjunction with a new language mode.
ABI stability
This proposal does not affect ABI.
Implications on adoption
This feature will automatically be turned on in a new language mode.
Alternatives considered
We could elect to leave the pointer conversion as is, and add overloads as a "picket fence" around problematic Swift functions. See Swift#42002 for an example of what this alternative looks like. In our opinion this is not a sustainable alternative in the long run.
We could allow pointer conversion for function calls made through variables ("function pointers" in C parlance). This would make the change smaller than proposed here, but it would be too easy to bypass the safety added by this proposal. We could also give the compiler the ability to distinguish Swift closures from C function pointers, but that would be a substantial source break, and generally prevent substituting Swift functions with C functions.
Acknowledgments
John McCall previously initiated a discussion where larger changes to pointer conversions were contemplated, in "Revisiting the pointer conversions". From another angle, pointer conversions were made more lenient in order to better match C semantics in SE-0324. By making implicit pointer conversions usable in a larger range of situations, it has had the side effect of uncovering many unsafe misuses of pointer conversion.