Skip to content

Commit 90a5e49

Browse files
authored
Merge pull request #82725 from Xazax-hun/dependence-on-sharedobj-is-unsafe
[cxx-interop] Lifetime dependence on a class is unsafe
2 parents bf07c4b + f08868f commit 90a5e49

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);
@@ -9549,13 +9585,22 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) {
95499585
SIW_DBG(" Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n");
95509586
attachMacro = true;
95519587
}
9588+
auto requiresExclusiveClassDependency = [](ParamDecl *fromParam,
9589+
clang::QualType toType) {
9590+
return fromParam->getInterfaceType()->isAnyClassReferenceType() &&
9591+
SwiftDeclConverter::canTypeMutateBuffer(toType);
9592+
};
95529593
bool returnHasLifetimeInfo = false;
95539594
if (SwiftDeclConverter::getImplicitObjectParamAnnotation<
95549595
clang::LifetimeBoundAttr>(ClangDecl)) {
95559596
SIW_DBG(" Found lifetimebound attribute on implicit 'this'\n");
9556-
printer.printLifetimeboundReturn(SwiftifyInfoPrinter::SELF_PARAM_INDEX,
9557-
true);
9558-
returnHasLifetimeInfo = true;
9597+
if (!requiresExclusiveClassDependency(
9598+
MappedDecl->getImplicitSelfDecl(/*createIfNeeded*/ true),
9599+
ClangDecl->getReturnType())) {
9600+
printer.printLifetimeboundReturn(SwiftifyInfoPrinter::SELF_PARAM_INDEX,
9601+
true);
9602+
returnHasLifetimeInfo = true;
9603+
}
95599604
}
95609605

95619606
bool isClangInstanceMethod =
@@ -9612,14 +9657,18 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) {
96129657
if (clangParam->hasAttr<clang::LifetimeBoundAttr>()) {
96139658
SIW_DBG(" Found lifetimebound attribute on parameter '"
96149659
<< *clangParam << "'\n");
9615-
// If this parameter has bounds info we will tranform it into a Span,
9616-
// so then it will no longer be Escapable.
9617-
bool willBeEscapable = swiftParamTy->isEscapable() &&
9618-
(!paramHasBoundsInfo ||
9619-
mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX);
9620-
printer.printLifetimeboundReturn(mappedIndex, willBeEscapable);
9621-
paramHasLifetimeInfo = true;
9622-
returnHasLifetimeInfo = true;
9660+
if (!requiresExclusiveClassDependency(swiftParam,
9661+
ClangDecl->getReturnType())) {
9662+
// If this parameter has bounds info we will tranform it into a Span,
9663+
// so then it will no longer be Escapable.
9664+
bool willBeEscapable =
9665+
swiftParamTy->isEscapable() &&
9666+
(!paramHasBoundsInfo ||
9667+
mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX);
9668+
printer.printLifetimeboundReturn(mappedIndex, willBeEscapable);
9669+
paramHasLifetimeInfo = true;
9670+
returnHasLifetimeInfo = true;
9671+
}
96239672
}
96249673
if (paramIsStdSpan && paramHasLifetimeInfo) {
96259674
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
@@ -38,6 +38,17 @@ import CxxStdlib
3838
// CHECK-NEXT: mutating func otherTemplatedType2(_ copy: ConstSpanOfInt, _: UnsafeMutablePointer<S<CInt>>!)
3939
// CHECK-NEXT: }
4040

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

0 commit comments

Comments
 (0)