Skip to content

Instantly share code, notes, and snippets.

@glessard
Last active August 23, 2023 22:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glessard/ba1468bdc09d51ce5253cd7043ad3a78 to your computer and use it in GitHub Desktop.
Save glessard/ba1468bdc09d51ce5253cd7043ad3a78 to your computer and use it in GitHub Desktop.
Swift Pitch: Restrict Pointer Conversions to C Interop

Constrain Pointer Conversion Feature to C-interoperability

  • Proposal: SE-NNNN Constrain Pointer Conversion Feature to C-interoperability
  • Author: Guillaume Lessard, Alejandro Alonso
  • 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> argument, while parameters of type UnsafeMutablePointer<T> can be called with a mutable Array<T> or an inout T argument. From within the function body, these values can then be operated on via a pointer value. 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 that typed as a pointer. By restricting this feature to the use cases of C, C++ and Objective-C interopability, we could 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 within a call 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 propose to 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, C++ ,or Objective-C. We therefore also propose to 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 to UnsafePointer<T> or UnsafeRawPointer parameters.
  • Mutable arguments of type [T] or inout T can be converted to UnsafeMutablePointer<T> or UnsafeMutableRawPointer parameters. This requires using the & sigil.
  • Arguments of type String can be converted to parameters of types UnsafePointer<UInt8>, UnsafePointer<Int8> or UnsafeRawPointer.

In order to support Swift code that forwards pointer parameters to an underlying C, C++, or Objective-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, not implemented yet: 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.

Not implemented yet: Variables of function types (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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment