Skip to content

Commit 0e740f8

Browse files
author
Gabor Horvath
committed
[cxx-interop] Introduce a safe C++ interop mode
In this mode all C++ types are imported as unsafe by default. Users explicitly marking types are escapable or not escapable can make them imported as safe. In the future, we also want to import unannotated functions as unsafe and add more logic to infer types that are actually safe, like agregates of escapable types.
1 parent 4bb9a58 commit 0e740f8

File tree

7 files changed

+90
-2
lines changed

7 files changed

+90
-2
lines changed

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(AllowUnsafeAttribute, true)
408408
/// Warn on use of unsafe constructs.
409409
EXPERIMENTAL_FEATURE(WarnUnsafe, true)
410410

411+
/// Import unsafe C and C++ constructs as @unsafe.
412+
EXPERIMENTAL_FEATURE(SafeInterop, true)
413+
411414
// Enable values in generic signatures, e.g. <let N: Int>
412415
EXPERIMENTAL_FEATURE(ValueGenerics, true)
413416

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ static bool usesFeatureAllowUnsafeAttribute(Decl *decl) {
300300
}
301301

302302
UNINTERESTING_FEATURE(WarnUnsafe)
303+
UNINTERESTING_FEATURE(SafeInterop)
303304

304305
static bool usesFeatureValueGenerics(Decl *decl) {
305306
auto genericContext = decl->getAsGenericContext();

lib/ClangImporter/ClangImporter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7391,6 +7391,10 @@ bool importer::hasNonEscapableAttr(const clang::RecordDecl *decl) {
73917391
return hasSwiftAttribute(decl, "~Escapable");
73927392
}
73937393

7394+
bool importer::hasEscapableAttr(const clang::RecordDecl *decl) {
7395+
return hasSwiftAttribute(decl, "Escapable");
7396+
}
7397+
73947398
/// Recursively checks that there are no pointers in any fields or base classes.
73957399
/// Does not check C++ records with specific API annotations.
73967400
static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) {

lib/ClangImporter/ImportDecl.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "swift/AST/PrettyStackTrace.h"
3838
#include "swift/AST/ProtocolConformance.h"
3939
#include "swift/AST/Stmt.h"
40+
#include "swift/AST/Type.h"
4041
#include "swift/AST/TypeCheckRequests.h"
4142
#include "swift/AST/Types.h"
4243
#include "swift/Basic/Assertions.h"
@@ -8154,6 +8155,25 @@ bool swift::importer::isMutabilityAttr(const clang::SwiftAttrAttr *swiftAttr) {
81548155
swiftAttr->getAttribute() == "nonmutating";
81558156
}
81568157

8158+
static bool importAsUnsafe(const ASTContext &context,
8159+
const clang::RecordDecl *decl,
8160+
const Decl *MappedDecl) {
8161+
if (!context.LangOpts.hasFeature(Feature::SafeInterop) ||
8162+
!context.LangOpts.hasFeature(Feature::AllowUnsafeAttribute) || !decl)
8163+
return false;
8164+
8165+
if (const auto *classDecl = dyn_cast<ClassDecl>(MappedDecl)) {
8166+
if (classDecl->getForeignClassKind() == ClassDecl::ForeignKind::CFType)
8167+
return false;
8168+
}
8169+
8170+
// TODO: Add logic to cover structural rules.
8171+
// TODO: import shared reference types as safe.
8172+
8173+
return !importer::hasNonEscapableAttr(decl) &&
8174+
!importer::hasEscapableAttr(decl);
8175+
}
8176+
81578177
void
81588178
ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
81598179
auto ClangDecl =
@@ -8178,6 +8198,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
81788198
//
81798199
// __attribute__((swift_attr("attribute")))
81808200
//
8201+
bool seenUnsafe = false;
81818202
for (auto swiftAttr : ClangDecl->specific_attrs<clang::SwiftAttrAttr>()) {
81828203
// FIXME: Hard-code @MainActor and @UIActor, because we don't have a
81838204
// point at which to do name lookup for imported entities.
@@ -8287,8 +8308,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
82878308
if (swiftAttr->getAttribute() == "unsafe") {
82888309
if (!SwiftContext.LangOpts.hasFeature(Feature::AllowUnsafeAttribute))
82898310
continue;
8290-
auto attr = new (SwiftContext) UnsafeAttr(/*implicit=*/false);
8291-
MappedDecl->getAttrs().add(attr);
8311+
seenUnsafe = true;
82928312
continue;
82938313
}
82948314

@@ -8332,6 +8352,13 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
83328352
swiftAttr->getAttribute());
83338353
}
83348354
}
8355+
8356+
if (seenUnsafe ||
8357+
importAsUnsafe(SwiftContext, dyn_cast<clang::RecordDecl>(ClangDecl),
8358+
MappedDecl)) {
8359+
auto attr = new (SwiftContext) UnsafeAttr(/*implicit=*/!seenUnsafe);
8360+
MappedDecl->getAttrs().add(attr);
8361+
}
83358362
};
83368363
importAttrsFromDecl(ClangDecl);
83378364

lib/ClangImporter/ImporterImpl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,8 @@ bool hasUnsafeAPIAttr(const clang::Decl *decl);
20312031

20322032
bool hasNonEscapableAttr(const clang::RecordDecl *decl);
20332033

2034+
bool hasEscapableAttr(const clang::RecordDecl *decl);
2035+
20342036
bool isViewType(const clang::CXXRecordDecl *decl);
20352037

20362038
} // end namespace importer

lib/ClangImporter/SwiftBridging/swift/bridging

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@
164164
#define SWIFT_NONESCAPABLE \
165165
__attribute__((swift_attr("~Escapable")))
166166

167+
/// Specifies that a specific c++ type such class or struct should be imported
168+
/// as a escapable Swift value. While this matches the default behavior,
169+
/// in safe mode interop mode it ensures that the type is not marked as
170+
/// unsafe.
171+
#define SWIFT_ESCAPABLE \
172+
__attribute__((swift_attr("Escapable")))
173+
167174
/// Specifies that the return value is passed as owned for C++ functions and
168175
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
169176
#define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained")))
@@ -187,6 +194,7 @@
187194
#define SWIFT_UNCHECKED_SENDABLE
188195
#define SWIFT_NONCOPYABLE
189196
#define SWIFT_NONESCAPABLE
197+
#define SWIFT_ESCAPABLE
190198
#define SWIFT_RETURNS_RETAINED
191199
#define SWIFT_RETURNS_UNRETAINED
192200

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
// RUN: rm -rf %t
3+
// RUN: split-file %s %t
4+
// RUN: %target-swift-frontend -typecheck -verify -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t/Inputs %t/test.swift -enable-experimental-feature AllowUnsafeAttribute -enable-experimental-feature WarnUnsafe -enable-experimental-feature NonescapableTypes -enable-experimental-feature SafeInterop -cxx-interoperability-mode=default -diagnostic-style llvm 2>&1
5+
6+
// REQUIRES: objc_interop
7+
8+
//--- Inputs/module.modulemap
9+
module Test {
10+
header "nonescapable.h"
11+
requires cplusplus
12+
}
13+
14+
//--- Inputs/nonescapable.h
15+
#include "swift/bridging"
16+
17+
struct SWIFT_NONESCAPABLE View {
18+
__attribute__((swift_attr("@lifetime(immortal)")))
19+
View() : member(nullptr) {}
20+
__attribute__((swift_attr("@lifetime(p)")))
21+
View(const int *p [[clang::lifetimebound]]) : member(p) {}
22+
View(const View&) = default;
23+
private:
24+
const int *member;
25+
};
26+
27+
struct SWIFT_ESCAPABLE Owner {};
28+
29+
struct Unannotated {};
30+
31+
//--- test.swift
32+
33+
import Test
34+
import CoreFoundation
35+
36+
func useUnsafeParam(x: Unannotated) { // expected-warning{{reference to unsafe struct 'Unannotated'}}
37+
}
38+
39+
func useSafeParams(x: Owner, y: View) {
40+
}
41+
42+
func useCfType(x: CFArray) {
43+
}

0 commit comments

Comments
 (0)