Skip to content

Commit 711428d

Browse files
committed
[Swiftify] Support __sized_by on byte-sized pointee types
Previously we would emit a macro that would error on expansion when trying to add a safe wrapper to a function with __sized_by on a type that mapped to UnsafePointer<T> instead of UnsafeRawPointer or OpaquePointer. __sized_by is acceptable when used on byte-sized pointee types, so this adds machinery in the macro expansion to support that. Meanwhile on the ClangImporter side, we add a check so that __sized_by on pointee types with a size is ignored if that size is larger than 1 byte. When _SwiftifyImport applies .sizedBy to a pointer of type UnsafePointer<T> it will still map it to a RawSpan/UnsafeRawBufferPointer in the safe overload. The assumption is that any API using __sized_by is dealing with raw bytes, so raw pointers are a better Swift abstraction than UnsafePointer<CChar> etc. It also lets the user avoid doing a scary pointer cast from some potentially larger-than-byte-sized pointer to a byte-sized pointer. Casts to RawPointers are generally safer and more ergonomic. rdar://150966684 rdar://150966021
1 parent 375164f commit 711428d

File tree

9 files changed

+265
-19
lines changed

9 files changed

+265
-19
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9145,16 +9145,38 @@ struct CountedByExpressionValidator
91459145
};
91469146
} // namespace
91479147

9148-
static bool SwiftifiableCAT(const clang::CountAttributedType *CAT) {
9149-
return CAT && CountedByExpressionValidator().Visit(CAT->getCountExpr());
9148+
// don't try to transform any Swift types that _SwiftifyImport doesn't know how to handle
9149+
static bool SwiftifiableCountedByPointerType(Type swiftType) {
9150+
Type nonnullType = swiftType->lookThroughSingleOptionalType();
9151+
PointerTypeKind PTK;
9152+
return nonnullType->getAnyPointerElementType(PTK) &&
9153+
(PTK == PTK_UnsafePointer || PTK == PTK_UnsafeMutablePointer);
91509154
}
9151-
9152-
static bool SwiftifiablePointerType(Type swiftType) {
9153-
// don't try to transform any Swift types that _SwiftifyImport doesn't know how to handle
9155+
static bool SwiftifiableSizedByPointerType(const clang::ASTContext &ctx,
9156+
Type swiftType,
9157+
const clang::CountAttributedType *CAT) {
91549158
Type nonnullType = swiftType->lookThroughSingleOptionalType();
9159+
if (nonnullType->isOpaquePointer())
9160+
return true;
91559161
PointerTypeKind PTK;
9156-
return nonnullType->isOpaquePointer() ||
9157-
(nonnullType->getAnyPointerElementType(PTK) && PTK != PTK_AutoreleasingUnsafeMutablePointer);
9162+
if (!nonnullType->getAnyPointerElementType(PTK))
9163+
return false;
9164+
if (PTK == PTK_UnsafeRawPointer || PTK == PTK_UnsafeMutableRawPointer)
9165+
return true;
9166+
if (PTK != PTK_UnsafePointer && PTK != PTK_UnsafeMutablePointer)
9167+
return false;
9168+
// We have a pointer to a type with a size. Verify that it is char-sized.
9169+
auto PtrT = CAT->getAs<clang::PointerType>();
9170+
auto PointeeT = PtrT->getPointeeType();
9171+
return ctx.getTypeSizeInChars(PointeeT).isOne();
9172+
}
9173+
static bool SwiftifiableCAT(const clang::ASTContext &ctx,
9174+
const clang::CountAttributedType *CAT,
9175+
Type swiftType) {
9176+
return CAT && CountedByExpressionValidator().Visit(CAT->getCountExpr()) &&
9177+
(CAT->isCountInBytes() ?
9178+
SwiftifiableSizedByPointerType(ctx, swiftType, CAT)
9179+
: SwiftifiableCountedByPointerType(swiftType));
91589180
}
91599181

91609182
void ClangImporter::Implementation::swiftify(FuncDecl *MappedDecl) {
@@ -9194,7 +9216,7 @@ void ClangImporter::Implementation::swiftify(FuncDecl *MappedDecl) {
91949216
bool returnIsStdSpan = registerStdSpanTypeMapping(
91959217
swiftReturnTy, ClangDecl->getReturnType());
91969218
auto *CAT = ClangDecl->getReturnType()->getAs<clang::CountAttributedType>();
9197-
if (SwiftifiableCAT(CAT) && SwiftifiablePointerType(swiftReturnTy)) {
9219+
if (SwiftifiableCAT(getClangASTContext(), CAT, swiftReturnTy)) {
91989220
printer.printCountedBy(CAT, SwiftifyInfoPrinter::RETURN_VALUE_INDEX);
91999221
attachMacro = true;
92009222
}
@@ -9211,7 +9233,7 @@ void ClangImporter::Implementation::swiftify(FuncDecl *MappedDecl) {
92119233
Type swiftParamTy = swiftParam->getInterfaceType();
92129234
bool paramHasBoundsInfo = false;
92139235
auto *CAT = clangParamTy->getAs<clang::CountAttributedType>();
9214-
if (SwiftifiableCAT(CAT) && SwiftifiablePointerType(swiftParamTy)) {
9236+
if (SwiftifiableCAT(getClangASTContext(), CAT, swiftParamTy)) {
92159237
printer.printCountedBy(CAT, index);
92169238
attachMacro = paramHasBoundsInfo = true;
92179239
}

lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,7 @@ func transformType(
329329
let text = name.text
330330
let isRaw = isRawPointerType(text: text)
331331
if isRaw && !isSizedBy {
332-
throw DiagnosticError("raw pointers only supported for SizedBy", node: name)
333-
}
334-
if !isRaw && isSizedBy {
335-
throw DiagnosticError("SizedBy only supported for raw pointers", node: name)
332+
throw DiagnosticError("void pointers not supported for countedBy", node: name)
336333
}
337334

338335
guard let kind: Mutability = getPointerMutability(text: text) else {
@@ -375,6 +372,33 @@ func isMutablePointerType(_ type: TypeSyntax) -> Bool {
375372
}
376373
}
377374

375+
func getPointeeType(_ type: TypeSyntax) -> TypeSyntax? {
376+
if let optType = type.as(OptionalTypeSyntax.self) {
377+
return getPointeeType(optType.wrappedType)
378+
}
379+
if let impOptType = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
380+
return getPointeeType(impOptType.wrappedType)
381+
}
382+
if let attrType = type.as(AttributedTypeSyntax.self) {
383+
return getPointeeType(attrType.baseType)
384+
}
385+
386+
guard let idType = type.as(IdentifierTypeSyntax.self) else {
387+
return nil
388+
}
389+
let text = idType.name.text
390+
if text != "UnsafePointer" && text != "UnsafeMutablePointer" {
391+
return nil
392+
}
393+
guard let x = idType.genericArgumentClause else {
394+
return nil
395+
}
396+
guard let y = x.arguments.first else {
397+
return nil
398+
}
399+
return y.argument.as(TypeSyntax.self)
400+
}
401+
378402
protocol BoundsCheckedThunkBuilder {
379403
func buildFunctionCall(_ pointerArgs: [Int: ExprSyntax]) throws -> ExprSyntax
380404
// buildBasicBoundsChecks creates a variable with the same name as the parameter it replaced,
@@ -648,6 +672,7 @@ extension PointerBoundsThunkBuilder {
648672
return try transformType(oldType, generateSpan, isSizedBy, isParameter)
649673
}
650674
}
675+
651676
var countLabel: String {
652677
return isSizedBy && generateSpan ? "byteCount" : "count"
653678
}
@@ -826,7 +851,7 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
826851
var args = argOverrides
827852
let argExpr = ExprSyntax("\(unwrappedName).baseAddress")
828853
assert(args[index] == nil)
829-
args[index] = try castPointerToOpaquePointer(unwrapIfNonnullable(argExpr))
854+
args[index] = try castPointerToTargetType(unwrapIfNonnullable(argExpr))
830855
let call = try base.buildFunctionCall(args)
831856
let ptrRef = unwrapIfNullable("\(name)")
832857

@@ -871,11 +896,16 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
871896
return type
872897
}
873898

874-
func castPointerToOpaquePointer(_ baseAddress: ExprSyntax) throws -> ExprSyntax {
899+
func castPointerToTargetType(_ baseAddress: ExprSyntax) throws -> ExprSyntax {
875900
let type = peelOptionalType(getParam(signature, index).type)
876901
if type.canRepresentBasicType(type: OpaquePointer.self) {
877902
return ExprSyntax("OpaquePointer(\(baseAddress))")
878903
}
904+
if isSizedBy {
905+
if let pointeeType = getPointeeType(type) {
906+
return "\(baseAddress).assumingMemoryBound(to: \(pointeeType).self)"
907+
}
908+
}
879909
return baseAddress
880910
}
881911

@@ -907,7 +937,7 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
907937
return unwrappedCall
908938
}
909939

910-
args[index] = try castPointerToOpaquePointer(getPointerArg())
940+
args[index] = try castPointerToTargetType(getPointerArg())
911941
return try base.buildFunctionCall(args)
912942
}
913943
}

test/Interop/C/swiftify-import/Inputs/sized-by-lifetimebound.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#pragma once
22

3+
#include <stdint.h>
4+
5+
#ifndef __sized_by
36
#define __sized_by(x) __attribute__((__sized_by__(x)))
7+
#endif
48
#define __lifetimebound __attribute__((lifetimebound))
59

610
const void * __sized_by(len) simple(int len, int len2, const void * __sized_by(len2) __lifetimebound p);
@@ -18,4 +22,10 @@ const void * __sized_by(len) _Nullable nullable(int len, int len2, const void *
1822
typedef struct foo opaque_t;
1923
opaque_t * __sized_by(len) opaque(int len, int len2, opaque_t * __sized_by(len2) __lifetimebound p);
2024

21-
const void * __sized_by(len) nonsizedLifetime(int len, const void * __lifetimebound p);
25+
const void * __sized_by(len) nonsizedLifetime(int len, const void * __lifetimebound p);
26+
27+
uint8_t *__sized_by(size) bytesized(int size, const uint8_t *__sized_by(size) __lifetimebound);
28+
29+
char *__sized_by(size) charsized(char *__sized_by(size) __lifetimebound, int size);
30+
31+
const uint16_t *__sized_by(size) doublebytesized(uint16_t *__sized_by(size) __lifetimebound, int size);

test/Interop/C/swiftify-import/Inputs/sized-by-noescape.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include <stdint.h>
4+
35
#define __sized_by(x) __attribute__((__sized_by__(x)))
46
#define __noescape __attribute__((noescape))
57

@@ -23,3 +25,8 @@ const void * __sized_by(len) __noescape _Nonnull returnPointer(int len);
2325
typedef struct foo opaque_t;
2426
void opaque(int len, opaque_t * __sized_by(len) __noescape p);
2527

28+
void bytesized(int size, const uint8_t *__sized_by(size) __noescape);
29+
30+
void charsized(char *__sized_by(size) __noescape, int size);
31+
32+
void doublebytesized(uint16_t *__sized_by(size) __noescape, int size);

test/Interop/C/swiftify-import/Inputs/sized-by.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include <stdint.h>
4+
35
#define __sized_by(x) __attribute__((__sized_by__(x)))
46

57
void simple(int len, void * __sized_by(len) p);
@@ -21,3 +23,9 @@ void * __sized_by(len) returnPointer(int len);
2123

2224
typedef struct foo opaque_t;
2325
void opaque(int len, opaque_t * __sized_by(len) p);
26+
27+
void charsized(char *__sized_by(size), int size);
28+
29+
uint8_t *__sized_by(size) bytesized(int size);
30+
31+
void doublebytesized(uint16_t *__sized_by(size), int size);

test/Interop/C/swiftify-import/sized-by-lifetimebound.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ import SizedByLifetimeboundClang
1313

1414
// CHECK: /// This is an auto-generated wrapper for safer interop
1515
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
16+
// CHECK-NEXT: @lifetime(copy _bytesized_param1)
17+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func bytesized(_ _bytesized_param1: RawSpan) -> MutableRawSpan
18+
19+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
20+
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
21+
// CHECK-NEXT: @lifetime(copy _charsized_param0)
22+
// CHECK-NEXT: @lifetime(_charsized_param0: copy _charsized_param0)
23+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func charsized(_ _charsized_param0: inout MutableRawSpan) -> MutableRawSpan
24+
25+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
26+
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
1627
// CHECK-NEXT: @lifetime(copy p)
1728
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func complexExpr(_ len: Int32, _ offset: Int32, _ p: RawSpan) -> RawSpan
1829

@@ -93,3 +104,16 @@ public func callSimple(_ p: RawSpan) {
93104
public func callNonsizedLifetime(_ p: UnsafeRawPointer) {
94105
let _: RawSpan = nonsizedLifetime(73, p)
95106
}
107+
108+
@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
109+
@inlinable
110+
public func callBytesized(_ p: RawSpan) {
111+
let _: MutableRawSpan = bytesized(p)
112+
}
113+
114+
@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
115+
@inlinable
116+
@lifetime(p: copy p)
117+
public func callCharsized(_ p: inout MutableRawSpan) {
118+
let _: MutableRawSpan = charsized(&p)
119+
}

test/Interop/C/swiftify-import/sized-by-noescape.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ import SizedByNoEscapeClang
1212

1313
// CHECK: /// This is an auto-generated wrapper for safer interop
1414
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
15+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func bytesized(_ _bytesized_param1: RawSpan)
16+
17+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
18+
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
19+
// CHECK-NEXT: @lifetime(_charsized_param0: copy _charsized_param0)
20+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func charsized(_ _charsized_param0: inout MutableRawSpan)
21+
22+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
23+
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
1524
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func complexExpr(_ len: Int{{.*}}, _ offset: Int{{.*}}, _ p: RawSpan)
1625

1726
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
@@ -93,3 +102,16 @@ public func callSimple(_ p: RawSpan) {
93102
public func callSwiftAttr(_ p: RawSpan) {
94103
swiftAttr(p)
95104
}
105+
106+
@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
107+
@inlinable
108+
public func callBytesized(_ p: RawSpan) {
109+
bytesized(p)
110+
}
111+
112+
@available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
113+
@inlinable
114+
@lifetime(p: copy p)
115+
public func callCharsized(_ p: inout MutableRawSpan) {
116+
charsized(&p)
117+
}

test/Interop/C/swiftify-import/sized-by.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
// RUN: %target-swift-ide-test -print-module -module-to-print=SizedByClang -plugin-path %swift-plugin-dir -I %S/Inputs -source-filename=x -enable-experimental-feature SafeInteropWrappers | %FileCheck %s
44

55
// swift-ide-test doesn't currently typecheck the macro expansions, so run the compiler as well
6-
// RUN: %empty-directory(%t)
7-
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/SizedBy.swiftmodule -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
6+
// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs -enable-experimental-feature SafeInteropWrappers %s
87

98
// Check that ClangImporter correctly infers and expands @_SwiftifyImport macros for functions with __sized_by parameters.
109
import SizedByClang
1110

1211

1312
// CHECK: /// This is an auto-generated wrapper for safer interop
13+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func bytesized(_ size: Int{{.*}}) -> UnsafeMutableRawBufferPointer
14+
15+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
16+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func charsized(_ _charsized_param0: UnsafeMutableRawBufferPointer)
17+
18+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
1419
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public func complexExpr(_ len: Int{{.*}}, _ offset: Int{{.*}}, _ p: UnsafeMutableRawBufferPointer)
1520

1621
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
@@ -82,3 +87,13 @@ public func callSimple(_ p: UnsafeMutableRawBufferPointer) {
8287
public func callSwiftAttr(_ p: UnsafeMutableRawBufferPointer) {
8388
swiftAttr(p)
8489
}
90+
91+
@inlinable
92+
public func callCharsized(_ p: UnsafeMutableRawBufferPointer) {
93+
charsized(p)
94+
}
95+
96+
@inlinable
97+
public func callBytesized() {
98+
let _: UnsafeMutableRawBufferPointer = bytesized(37)
99+
}

0 commit comments

Comments
 (0)