Skip to content

Commit 8cccbe0

Browse files
committed
[Sema] Infer @_nonEphemeral for various parameters
These include memberwise initializers for pointer properties and enum case constructors for pointer payloads. In both cases, the pointer is being immediately escaped, so don't allow the user to pass a temporary pointer value.
1 parent 1bc56dc commit 8cccbe0

File tree

8 files changed

+155
-3
lines changed

8 files changed

+155
-3
lines changed

include/swift/AST/Decl.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5339,7 +5339,33 @@ class ParamDecl : public VarDecl {
53395339

53405340
/// Does this parameter reject temporary pointer conversions?
53415341
bool isNonEphemeral() const {
5342-
return getAttrs().hasAttribute<NonEphemeralAttr>();
5342+
if (getAttrs().hasAttribute<NonEphemeralAttr>())
5343+
return true;
5344+
5345+
// Only pointer parameters can be non-ephemeral.
5346+
auto ty = getInterfaceType();
5347+
if (!ty->lookThroughSingleOptionalType()->getAnyPointerElementType())
5348+
return false;
5349+
5350+
// Enum element pointer parameters are always non-ephemeral.
5351+
auto *parentDecl = getDeclContext()->getAsDecl();
5352+
if (parentDecl && isa<EnumElementDecl>(parentDecl))
5353+
return true;
5354+
5355+
return false;
5356+
}
5357+
5358+
/// Attempt to apply an implicit `@_nonEphemeral` attribute to this parameter.
5359+
void setNonEphemeralIfPossible() {
5360+
// Don't apply the attribute if this isn't a pointer param.
5361+
auto type = getInterfaceType();
5362+
if (!type->lookThroughSingleOptionalType()->getAnyPointerElementType())
5363+
return;
5364+
5365+
if (!getAttrs().hasAttribute<NonEphemeralAttr>()) {
5366+
auto &ctx = getASTContext();
5367+
getAttrs().add(new (ctx) NonEphemeralAttr(/*IsImplicit*/ true));
5368+
}
53435369
}
53445370

53455371
/// Remove the type of this varargs element designator, without the array

lib/ClangImporter/ImportDecl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,10 @@ createValueConstructor(ClangImporter::Implementation &Impl,
14241424
param->setSpecifier(ParamSpecifier::Default);
14251425
param->setInterfaceType(var->getInterfaceType());
14261426
Impl.recordImplicitUnwrapForDecl(param, var->isImplicitlyUnwrappedOptional());
1427+
1428+
// Don't allow the parameter to accept temporary pointer conversions.
1429+
param->setNonEphemeralIfPossible();
1430+
14271431
valueParameters.push_back(param);
14281432
}
14291433

lib/Sema/CodeSynthesis.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl,
240240
arg->setSpecifier(ParamSpecifier::Default);
241241
arg->setInterfaceType(varInterfaceType);
242242
arg->setImplicit();
243-
243+
244+
// Don't allow the parameter to accept temporary pointer conversions.
245+
arg->setNonEphemeralIfPossible();
246+
244247
maybeAddMemberwiseDefaultArg(arg, var, defaultInits, params.size(), ctx);
245248

246249
params.push_back(arg);

test/IDE/Inputs/custom-modules/InferImportAsMember.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,12 @@ extern void IAMClassInvert(IAMOtherName iamOtherName);
113113
// Test collision where we can see the getter, but not setter
114114
extern float IAMStruct1GetCollisionNonProperty(struct IAMStruct1, int);
115115

116+
/// Struct with pointer properties and a synthesized memberwise init.
117+
struct IAMPointerStruct {
118+
double *ptr1;
119+
double *ptr2;
120+
};
121+
122+
extern struct IAMPointerStruct IAMPointerStructCreateOther(double *ptr);
123+
116124
#endif // INFER_IMPORT_AS_MEMBER_H

test/IDE/infer_import_as_member.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -import-objc-header %S/Inputs/custom-modules/CollisionImportAsMember.h -I %t -I %S/Inputs/custom-modules -print-module -source-filename %s -module-to-print=InferImportAsMember -always-argument-labels -enable-infer-import-as-member -skip-unavailable > %t.printed.A.txt
2-
// RUN: %target-swift-frontend -typecheck -import-objc-header %S/Inputs/custom-modules/CollisionImportAsMember.h -I %t -I %S/Inputs/custom-modules %s -enable-infer-import-as-member -verify
2+
// RUN: %target-swift-frontend -typecheck -import-objc-header %S/Inputs/custom-modules/CollisionImportAsMember.h -I %t -I %S/Inputs/custom-modules %s -enable-infer-import-as-member -verify -diagnose-invalid-ephemeralness-as-error
33
// RUN: %FileCheck %s -check-prefix=PRINT -strict-whitespace < %t.printed.A.txt
44

55
// REQUIRES: objc_interop
@@ -111,3 +111,27 @@ let _ = mine.getCollisionNonProperty(1)
111111
// PRINT-NEXT: init!(i i: Double)
112112
// PRINT-NEXT: class func invert(_ iamOtherName: IAMOtherName!)
113113
// PRINT-NEXT: }
114+
//
115+
// PRINT-LABEL: struct IAMPointerStruct {
116+
// PRINT-NEXT: var ptr1: UnsafeMutablePointer<Double>!
117+
// PRINT-NEXT: var ptr2: UnsafeMutablePointer<Double>!
118+
// PRINT-NEXT: init()
119+
// PRINT-NEXT: init(ptr1 ptr1: UnsafeMutablePointer<Double>!, ptr2 ptr2: UnsafeMutablePointer<Double>!)
120+
// PRINT-NEXT: }
121+
// PRINT-NEXT: extension IAMPointerStruct {
122+
// PRINT-NEXT: init(otherPtr ptr: UnsafeMutablePointer<Double>!)
123+
// PRINT-NEXT: }
124+
125+
func testNonEphemeralInitParams(x: Double) {
126+
var x = x
127+
128+
_ = IAMPointerStruct(ptr1: &x, ptr2: &x)
129+
// expected-error@-1 {{cannot use inout expression here; argument #1 must be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
130+
// expected-note@-2 {{implicit argument conversion from 'Double' to 'UnsafeMutablePointer<Double>?' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
131+
// expected-note@-3 {{use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
132+
// expected-error@-4 {{cannot use inout expression here; argument #2 must be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
133+
// expected-note@-5 {{implicit argument conversion from 'Double' to 'UnsafeMutablePointer<Double>?' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
134+
// expected-note@-6 {{use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
135+
136+
_ = IAMPointerStruct(otherPtr: &x) // Okay.
137+
}

test/IDE/newtype.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,23 @@ func testFixit() {
234234
let _ = NSMyContextName
235235
// expected-error@-1{{'NSMyContextName' has been renamed to 'NSSomeContext.Name.myContextName'}} {{10-25=NSSomeContext.Name.myContextName}}
236236
}
237+
238+
func testNonEphemeralInitParams(x: OpaquePointer) {
239+
var x = x
240+
241+
_ = TRefRef(&x) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'init(_:)'}}
242+
// expected-note@-1 {{implicit argument conversion from 'OpaquePointer' to 'UnsafeMutablePointer<OpaquePointer>' produces a pointer valid only for the duration of the call}}
243+
// expected-note@-2 {{use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
244+
245+
_ = TRefRef(rawValue: &x) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'init(rawValue:)'}}
246+
// expected-note@-1 {{implicit argument conversion from 'OpaquePointer' to 'UnsafeMutablePointer<OpaquePointer>' produces a pointer valid only for the duration of the call}}
247+
// expected-note@-2 {{use 'withUnsafeMutablePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
248+
249+
_ = ConstTRefRef(&x) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'init(_:)'}}
250+
// expected-note@-1 {{implicit argument conversion from 'OpaquePointer' to 'UnsafePointer<OpaquePointer>' produces a pointer valid only for the duration of the call}}
251+
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
252+
253+
_ = ConstTRefRef(rawValue: &x) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'init(rawValue:)'}}
254+
// expected-note@-1 {{implicit argument conversion from 'OpaquePointer' to 'UnsafePointer<OpaquePointer>' produces a pointer valid only for the duration of the call}}
255+
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
256+
}

test/Sema/diag_non_ephemeral.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,39 @@ func testNonEphemeralInOperators() {
435435
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
436436
}
437437

438+
struct S3 {
439+
var ptr1: UnsafeMutableRawPointer
440+
lazy var ptr2 = UnsafeMutableRawPointer(&topLevelS)
441+
}
442+
443+
enum E {
444+
case mutableRaw(UnsafeMutableRawPointer)
445+
case const(UnsafePointer<Int8>)
446+
}
447+
448+
func testNonEphemeralInMemberwiseInits() {
449+
var local = 0
450+
451+
_ = S3(ptr1: &topLevelS, ptr2: &local) // expected-error {{cannot use inout expression here; argument #2 must be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
452+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer?' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
453+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
454+
455+
_ = S3.init(ptr1: &local, ptr2: &topLevelS) // expected-error {{cannot use inout expression here; argument #1 must be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
456+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
457+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
458+
459+
_ = E.mutableRaw(&local) // expected-error {{cannot use inout expression here; argument #1 must be a pointer that outlives the call to 'mutableRaw'}}
460+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer' produces a pointer valid only for the duration of the call to 'mutableRaw'}}
461+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
462+
463+
_ = E.const([1, 2, 3]) // expected-error {{cannot pass '[Int8]' to parameter; argument #1 must be a pointer that outlives the call to 'const'}}
464+
// expected-note@-1 {{implicit argument conversion from '[Int8]' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'const'}}
465+
// expected-note@-2 {{use the 'withUnsafeBufferPointer' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope}}
466+
467+
_ = E.const("hello") // expected-error {{cannot pass 'String' to parameter; argument #1 must be a pointer that outlives the call to 'const'}}
468+
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'const'}}
469+
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
470+
}
438471

439472
// Allow the stripping of @_nonEphemeral. This is unfortunate, but ensures we don't force the user to write things
440473
// like `func higherOrder(_ fn: (@_nonEphemeral UnsafeMutableRawPointer) -> Void) {}`, given that the attribute is non-user-facing.

test/Sema/diag_non_ephemeral_warning.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,37 @@ func testNonEphemeralInOperators() {
436436
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer' produces a pointer valid only for the duration of the call to '^^^'}}
437437
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
438438
}
439+
440+
struct S3 {
441+
var ptr1: UnsafeMutableRawPointer
442+
lazy var ptr2 = UnsafeMutableRawPointer(&topLevelS)
443+
}
444+
445+
enum E {
446+
case mutableRaw(UnsafeMutableRawPointer)
447+
case const(UnsafePointer<Int8>)
448+
}
449+
450+
func testNonEphemeralInMemberwiseInits() {
451+
var local = 0
452+
453+
_ = S3(ptr1: &topLevelS, ptr2: &local) // expected-warning {{inout expression creates a temporary pointer, but argument #2 should be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
454+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer?' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
455+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
456+
457+
_ = S3.init(ptr1: &local, ptr2: &topLevelS) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'init(ptr1:ptr2:)'}}
458+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer' produces a pointer valid only for the duration of the call to 'init(ptr1:ptr2:)'}}
459+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
460+
461+
_ = E.mutableRaw(&local) // expected-warning {{inout expression creates a temporary pointer, but argument #1 should be a pointer that outlives the call to 'mutableRaw'}}
462+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafeMutableRawPointer' produces a pointer valid only for the duration of the call to 'mutableRaw'}}
463+
// expected-note@-2 {{use 'withUnsafeMutableBytes' in order to explicitly convert argument to buffer pointer valid for a defined scope}}
464+
465+
_ = E.const([1, 2, 3]) // expected-warning {{passing '[Int8]' to parameter, but argument #1 should be a pointer that outlives the call to 'const'}}
466+
// expected-note@-1 {{implicit argument conversion from '[Int8]' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'const'}}
467+
// expected-note@-2 {{use the 'withUnsafeBufferPointer' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope}}
468+
469+
_ = E.const("hello") // expected-warning {{passing 'String' to parameter, but argument #1 should be a pointer that outlives the call to 'const'}}
470+
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'const'}}
471+
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
472+
}

0 commit comments

Comments
 (0)