Skip to content

Commit f08868f

Browse files
author
Gabor Horvath
committed
[cxx-interop] Lifetime dependence on a class is unsafe
Swift cannot guarantee exclusivity of a class. On the other hand, lifetimebound annotations on the C++ side do not guarantee exclusivity. To resolve this issue, we ignore lifetime dependency annotations that introduce exclusive dependence on classes and import functions with ignored annotations as unsafe. Currently, Swift has no language feature to support these scenarios but it might get new features in the future to help people work around this problem. rdar://153747746
1 parent d388da7 commit f08868f

File tree

4 files changed

+106
-21
lines changed

4 files changed

+106
-21
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "clang/AST/Decl.h"
6464
#include "clang/AST/DeclCXX.h"
6565
#include "clang/AST/DeclObjCCommon.h"
66+
#include "clang/AST/DeclTemplate.h"
6667
#include "clang/AST/Expr.h"
6768
#include "clang/AST/PrettyPrinter.h"
6869
#include "clang/AST/RecordLayout.h"
@@ -4194,6 +4195,20 @@ namespace {
41944195
return false;
41954196
}
41964197

4198+
static bool canTypeMutateBuffer(clang::QualType ty) {
4199+
// FIXME: better way to detect if a type can mutate the underlying buffer.
4200+
if (const auto *rd = ty->getAsRecordDecl()) {
4201+
if (rd->isInStdNamespace() && rd->getName() == "span")
4202+
return !cast<clang::ClassTemplateSpecializationDecl>(rd)
4203+
->getTemplateArgs()
4204+
.get(0)
4205+
.getAsType()
4206+
.isConstQualified();
4207+
}
4208+
// Conservatively assume most types can mutate the underlying buffer.
4209+
return true;
4210+
}
4211+
41974212
void addLifetimeDependencies(const clang::FunctionDecl *decl,
41984213
AbstractFunctionDecl *result) {
41994214
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate)
@@ -4205,12 +4220,23 @@ namespace {
42054220
!isa<clang::CXXMethodDecl, clang::ObjCMethodDecl>(decl))
42064221
return;
42074222

4223+
bool hasSkippedLifetimeAnnotation = false;
42084224
auto isEscapable = [this](clang::QualType ty) {
42094225
return evaluateOrDefault(
42104226
Impl.SwiftContext.evaluator,
42114227
ClangTypeEscapability({ty.getTypePtr(), &Impl}),
42124228
CxxEscapability::Unknown) != CxxEscapability::NonEscapable;
42134229
};
4230+
auto importedAsClass = [this](clang::QualType ty, bool forSelf) {
4231+
if (!forSelf) {
4232+
if (ty->getPointeeType().isNull())
4233+
return false;
4234+
ty = ty->getPointeeType();
4235+
}
4236+
if (const auto *rd = ty->getAsRecordDecl())
4237+
return recordHasReferenceSemantics(rd);
4238+
return false;
4239+
};
42144240

42154241
auto swiftParams = result->getParameters();
42164242
bool hasSelf =
@@ -4237,6 +4263,7 @@ namespace {
42374263
}
42384264

42394265
auto retType = decl->getReturnType();
4266+
bool retMayMutateBuffer = canTypeMutateBuffer(retType);
42404267
auto warnForEscapableReturnType = [&] {
42414268
if (isEscapableAnnotatedType(retType.getTypePtr())) {
42424269
Impl.addImportDiagnostic(
@@ -4252,8 +4279,11 @@ namespace {
42524279
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
42534280
SmallBitVector paramHasAnnotation(dependencyVecSize);
42544281
std::map<unsigned, SmallBitVector> inheritedArgDependences;
4255-
auto processLifetimeBound = [&](unsigned idx, clang::QualType ty) {
4282+
auto processLifetimeBound = [&](unsigned idx, clang::QualType ty,
4283+
bool forSelf = false) {
42564284
warnForEscapableReturnType();
4285+
if (retMayMutateBuffer && importedAsClass(ty, forSelf))
4286+
hasSkippedLifetimeAnnotation = true;
42574287
paramHasAnnotation[idx] = true;
42584288
if (isEscapable(ty))
42594289
scopedLifetimeParamIndicesForReturn[idx] = true;
@@ -4302,7 +4332,8 @@ namespace {
43024332
if (getImplicitObjectParamAnnotation<clang::LifetimeBoundAttr>(decl))
43034333
processLifetimeBound(
43044334
result->getSelfIndex(),
4305-
cast<clang::CXXMethodDecl>(decl)->getThisType()->getPointeeType());
4335+
cast<clang::CXXMethodDecl>(decl)->getThisType()->getPointeeType(),
4336+
/*forSelf=*/true);
43064337
if (auto attr =
43074338
getImplicitObjectParamAnnotation<clang::LifetimeCaptureByAttr>(
43084339
decl))
@@ -4352,16 +4383,21 @@ namespace {
43524383
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
43534384
}
43544385

4355-
for (auto [idx, param] : llvm::enumerate(decl->parameters())) {
4356-
if (isEscapable(param->getType()))
4357-
continue;
4358-
if (param->hasAttr<clang::NoEscapeAttr>() || paramHasAnnotation[idx])
4359-
continue;
4360-
// We have a nonescapable parameter that does not have its lifetime
4361-
// annotated nor is it marked noescape.
4386+
if (hasSkippedLifetimeAnnotation) {
43624387
auto attr = new (ASTContext) UnsafeAttr(/*implicit=*/true);
43634388
result->getAttrs().add(attr);
4364-
break;
4389+
} else {
4390+
for (auto [idx, param] : llvm::enumerate(decl->parameters())) {
4391+
if (isEscapable(param->getType()))
4392+
continue;
4393+
if (param->hasAttr<clang::NoEscapeAttr>() || paramHasAnnotation[idx])
4394+
continue;
4395+
// We have a nonescapable parameter that does not have its lifetime
4396+
// annotated nor is it marked noescape.
4397+
auto attr = new (ASTContext) UnsafeAttr(/*implicit=*/true);
4398+
result->getAttrs().add(attr);
4399+
break;
4400+
}
43654401
}
43664402

43674403
Impl.diagnoseTargetDirectly(decl);
@@ -9546,13 +9582,22 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) {
95469582
SIW_DBG(" Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n");
95479583
attachMacro = true;
95489584
}
9585+
auto requiresExclusiveClassDependency = [](ParamDecl *fromParam,
9586+
clang::QualType toType) {
9587+
return fromParam->getInterfaceType()->isAnyClassReferenceType() &&
9588+
SwiftDeclConverter::canTypeMutateBuffer(toType);
9589+
};
95499590
bool returnHasLifetimeInfo = false;
95509591
if (SwiftDeclConverter::getImplicitObjectParamAnnotation<
95519592
clang::LifetimeBoundAttr>(ClangDecl)) {
95529593
SIW_DBG(" Found lifetimebound attribute on implicit 'this'\n");
9553-
printer.printLifetimeboundReturn(SwiftifyInfoPrinter::SELF_PARAM_INDEX,
9554-
true);
9555-
returnHasLifetimeInfo = true;
9594+
if (!requiresExclusiveClassDependency(
9595+
MappedDecl->getImplicitSelfDecl(/*createIfNeeded*/ true),
9596+
ClangDecl->getReturnType())) {
9597+
printer.printLifetimeboundReturn(SwiftifyInfoPrinter::SELF_PARAM_INDEX,
9598+
true);
9599+
returnHasLifetimeInfo = true;
9600+
}
95569601
}
95579602

95589603
bool isClangInstanceMethod =
@@ -9609,14 +9654,18 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) {
96099654
if (clangParam->hasAttr<clang::LifetimeBoundAttr>()) {
96109655
SIW_DBG(" Found lifetimebound attribute on parameter '"
96119656
<< *clangParam << "'\n");
9612-
// If this parameter has bounds info we will tranform it into a Span,
9613-
// so then it will no longer be Escapable.
9614-
bool willBeEscapable = swiftParamTy->isEscapable() &&
9615-
(!paramHasBoundsInfo ||
9616-
mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX);
9617-
printer.printLifetimeboundReturn(mappedIndex, willBeEscapable);
9618-
paramHasLifetimeInfo = true;
9619-
returnHasLifetimeInfo = true;
9657+
if (!requiresExclusiveClassDependency(swiftParam,
9658+
ClangDecl->getReturnType())) {
9659+
// If this parameter has bounds info we will tranform it into a Span,
9660+
// so then it will no longer be Escapable.
9661+
bool willBeEscapable =
9662+
swiftParamTy->isEscapable() &&
9663+
(!paramHasBoundsInfo ||
9664+
mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX);
9665+
printer.printLifetimeboundReturn(mappedIndex, willBeEscapable);
9666+
paramHasLifetimeInfo = true;
9667+
returnHasLifetimeInfo = true;
9668+
}
96209669
}
96219670
if (paramIsStdSpan && paramHasLifetimeInfo) {
96229671
SIW_DBG(" Found both std::span and lifetime info "

test/Interop/Cxx/class/safe-interop-mode.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,14 @@ View safeFunc(View v1 [[clang::noescape]], View v2 [[clang::lifetimebound]]);
6464
void unsafeFunc(View v1 [[clang::noescape]], View v2);
6565

6666
class SharedObject {
67+
public:
68+
View getView() const [[clang::lifetimebound]];
6769
private:
6870
int *p;
6971
} SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject);
7072

73+
View getViewFromSharedObject(SharedObject* p [[clang::lifetimebound]]);
74+
7175
inline void retainSharedObject(SharedObject *) {}
7276
inline void releaseSharedObject(SharedObject *) {}
7377

@@ -161,6 +165,10 @@ func useUnsafeLifetimeAnnotated(v: View) {
161165
func useSharedReference(frt: SharedObject, x: OwnedData) {
162166
let _ = frt
163167
x.takeSharedObject(frt)
168+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
169+
let _ = frt.getView() // expected-note{{reference to unsafe instance method 'getView()'}}
170+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
171+
let _ = getViewFromSharedObject(frt) // expected-note{{reference to unsafe global function 'getViewFromSharedObject'}}
164172
}
165173

166174
@available(SwiftStdlib 5.8, *)

test/Interop/Cxx/stdlib/Inputs/std-span.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ inline SpanOfInt initSpan(int arr[], size_t size) {
6565
struct DependsOnSelf {
6666
std::vector<int> v;
6767
__attribute__((swift_name("get()")))
68+
__attribute__((swift_attr("@safe")))
6869
ConstSpanOfInt get() const [[clang::lifetimebound]] { return ConstSpanOfInt(v.data(), v.size()); }
6970
};
7071

@@ -181,4 +182,20 @@ inline void mutableKeyword(SpanOfInt copy [[clang::noescape]]) {}
181182
inline void spanWithoutTypeAlias(std::span<const int> s [[clang::noescape]]) {}
182183
inline void mutableSpanWithoutTypeAlias(std::span<int> s [[clang::noescape]]) {}
183184

185+
#define IMMORTAL_FRT \
186+
__attribute__((swift_attr("import_reference"))) \
187+
__attribute__((swift_attr("retain:immortal"))) \
188+
__attribute__((swift_attr("release:immortal")))
189+
190+
struct IMMORTAL_FRT DependsOnSelfFRT {
191+
std::vector<int> v;
192+
__attribute__((swift_name("get()"))) ConstSpanOfInt get() const
193+
[[clang::lifetimebound]] {
194+
return ConstSpanOfInt(v.data(), v.size());
195+
}
196+
SpanOfInt getMutable() [[clang::lifetimebound]] {
197+
return SpanOfInt(v.data(), v.size());
198+
}
199+
};
200+
184201
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_SPAN_H

test/Interop/Cxx/stdlib/std-span-interface.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ import CxxStdlib
3737
// CHECK-NEXT: mutating func otherTemplatedType2(_ copy: ConstSpanOfInt, _: UnsafeMutablePointer<S<CInt>>!)
3838
// CHECK-NEXT: }
3939

40+
// CHECK: class DependsOnSelfFRT {
41+
// CHECK-NEXT: init()
42+
// CHECK-NEXT: /// This is an auto-generated wrapper for safer interop
43+
// CHECK-NEXT: @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
44+
// CHECK-NEXT: @_lifetime(borrow self)
45+
// CHECK-NEXT: @_alwaysEmitIntoClient @_disfavoredOverload public borrowing func get() -> Span<CInt>
46+
// CHECK-NEXT: borrowing func get() -> ConstSpanOfInt
47+
// CHECK-NEXT: borrowing func {{(__)?}}getMutable{{(Unsafe)?}}() -> SpanOfInt
48+
// CHECK-NEXT: var v: std.{{.*}}vector<CInt, std.{{.*}}allocator<CInt>>
49+
// CHECK-NEXT: }
50+
4051
// CHECK: /// This is an auto-generated wrapper for safer interop
4152
// CHECK-NEXT: @available(visionOS 1.0, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
4253
// CHECK-NEXT: @_lifetime(s: copy s)

0 commit comments

Comments
 (0)