Skip to content

Commit 0198e0f

Browse files
committed
[cxx-interop] Factor API classification and verification into requests.
1 parent 409ef81 commit 0198e0f

File tree

6 files changed

+366
-99
lines changed

6 files changed

+366
-99
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ namespace swift {
268268
/// Imports getters and setters as computed properties.
269269
bool CxxInteropGettersSettersAsProperties = false;
270270

271+
bool EnableUnsafeEagerCxxImporting = false;
272+
271273
/// On Darwin platforms, use the pre-stable ABI's mark bit for Swift
272274
/// classes instead of the stable ABI's bit. This is needed when
273275
/// targeting OSes prior to macOS 10.14.4 and iOS 12.2, where

include/swift/ClangImporter/ClangImporterRequests.h

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,126 @@ class ClangRecordMemberLookup
178178
evaluate(Evaluator &evaluator, ClangRecordMemberLookupDescriptor desc) const;
179179
};
180180

181+
enum class CxxRecordSemanticsKind {
182+
Trivial,
183+
Owned,
184+
Reference,
185+
// An API that has be annotated as explicitly unsafe, but still importable.
186+
// TODO: we should rename these APIs.
187+
ExplicitlyUnsafe,
188+
// A record that has custom lifetime operations which we cannot prove are safe.
189+
UnsafeLifetimeOperation,
190+
// A record that contains a pointer (aka non-trivial type).
191+
UnsafePointerMember
192+
};
193+
194+
struct CxxRecordSemanticsDescriptor final {
195+
const clang::CXXRecordDecl *decl;
196+
197+
CxxRecordSemanticsDescriptor(const clang::CXXRecordDecl *decl)
198+
: decl(decl) {}
199+
200+
friend llvm::hash_code
201+
hash_value(const CxxRecordSemanticsDescriptor &desc) {
202+
return llvm::hash_combine(desc.decl);
203+
}
204+
205+
friend bool operator==(const CxxRecordSemanticsDescriptor &lhs,
206+
const CxxRecordSemanticsDescriptor &rhs) {
207+
return lhs.decl == rhs.decl;
208+
}
209+
210+
friend bool operator!=(const CxxRecordSemanticsDescriptor &lhs,
211+
const CxxRecordSemanticsDescriptor &rhs) {
212+
return !(lhs == rhs);
213+
}
214+
};
215+
216+
void simple_display(llvm::raw_ostream &out,
217+
CxxRecordSemanticsDescriptor desc);
218+
SourceLoc
219+
extractNearestSourceLoc(CxxRecordSemanticsDescriptor desc);
220+
221+
/// What pattern does this C++ API fit? Uses attributes such as
222+
/// import_owned_value and import_as_reference to determine the pattern.
223+
class CxxRecordSemantics
224+
: public SimpleRequest<CxxRecordSemantics,
225+
CxxRecordSemanticsKind(CxxRecordSemanticsDescriptor),
226+
RequestFlags::Cached> {
227+
public:
228+
using SimpleRequest::SimpleRequest;
229+
230+
// Caching
231+
bool isCached() const { return true; }
232+
233+
// Source location
234+
SourceLoc getNearestLoc() const { return SourceLoc(); };
235+
236+
private:
237+
friend SimpleRequest;
238+
239+
// Evaluation.
240+
CxxRecordSemanticsKind
241+
evaluate(Evaluator &evaluator, CxxRecordSemanticsDescriptor) const;
242+
};
243+
244+
enum class CxxRecordUseKind {
245+
Global,
246+
CReturnType,
247+
CxxReturnType
248+
};
249+
250+
struct SafeUseOfCxxRecordDescriptor final {
251+
const clang::CXXRecordDecl *recordDecl;
252+
CxxRecordUseKind kind;
253+
254+
SafeUseOfCxxRecordDescriptor(const clang::CXXRecordDecl *recordDecl,
255+
CxxRecordUseKind kind)
256+
: recordDecl(recordDecl), kind(kind) {}
257+
258+
friend llvm::hash_code
259+
hash_value(const SafeUseOfCxxRecordDescriptor &desc) {
260+
return llvm::hash_combine(desc.kind, desc.recordDecl);
261+
}
262+
263+
friend bool operator==(const SafeUseOfCxxRecordDescriptor &lhs,
264+
const SafeUseOfCxxRecordDescriptor &rhs) {
265+
return lhs.kind == rhs.kind && lhs.recordDecl == rhs.recordDecl;
266+
}
267+
268+
friend bool operator!=(const SafeUseOfCxxRecordDescriptor &lhs,
269+
const SafeUseOfCxxRecordDescriptor &rhs) {
270+
return !(lhs == rhs);
271+
}
272+
};
273+
274+
void simple_display(llvm::raw_ostream &out,
275+
SafeUseOfCxxRecordDescriptor desc);
276+
SourceLoc
277+
extractNearestSourceLoc(SafeUseOfCxxRecordDescriptor desc);
278+
279+
class IsSafeUseOfCxxRecord
280+
: public SimpleRequest<IsSafeUseOfCxxRecord,
281+
bool(SafeUseOfCxxRecordDescriptor),
282+
RequestFlags::Cached> {
283+
public:
284+
using SimpleRequest::SimpleRequest;
285+
286+
// Caching
287+
bool isCached() const { return true; }
288+
289+
// Source location
290+
SourceLoc getNearestLoc() const { return SourceLoc(); };
291+
292+
private:
293+
friend SimpleRequest;
294+
295+
// Evaluation.
296+
bool
297+
evaluate(Evaluator &evaluator, SafeUseOfCxxRecordDescriptor desc) const;
298+
};
299+
300+
181301
#define SWIFT_TYPEID_ZONE ClangImporter
182302
#define SWIFT_TYPEID_HEADER "swift/ClangImporter/ClangImporterTypeIDZone.def"
183303
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/ClangImporter/ClangImporterTypeIDZone.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ SWIFT_REQUEST(ClangImporter, CXXNamespaceMemberLookup,
2424
SWIFT_REQUEST(ClangImporter, ClangRecordMemberLookup,
2525
Decl *(ClangRecordMemberLookupDescriptor), Uncached,
2626
NoLocationInfo)
27+
SWIFT_REQUEST(ClangImporter, CxxRecordSemantics,
28+
CxxRecordSemanticsKind(const clang::CXXRecordDecl *), Cached,
29+
NoLocationInfo)
30+
SWIFT_REQUEST(ClangImporter, IsSafeUseOfCxxRecord,
31+
bool(SafeUseOfCxxRecordDescriptor), Cached,
32+
NoLocationInfo)

lib/ClangImporter/ClangImporter.cpp

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5828,3 +5828,210 @@ void ClangImporter::diagnoseMemberValue(const DeclName &name,
58285828
}
58295829
}
58305830
}
5831+
5832+
static bool hasImportAsRefAttr(const clang::RecordDecl *decl) {
5833+
return decl->hasAttrs() && llvm::any_of(decl->getAttrs(), [](auto *attr) {
5834+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
5835+
return swiftAttr->getAttribute() == "import_foreign_reference";
5836+
return false;
5837+
});
5838+
}
5839+
5840+
static bool hasOwnedValueAttr(const clang::RecordDecl *decl) {
5841+
if (decl->getNameAsString() == "basic_string" ||
5842+
decl->getNameAsString() == "vector")
5843+
return true;
5844+
5845+
return decl->hasAttrs() && llvm::any_of(decl->getAttrs(), [](auto *attr) {
5846+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
5847+
return swiftAttr->getAttribute() == "import_owned_value";
5848+
return false;
5849+
});
5850+
}
5851+
5852+
static bool hasUnsafeAPIAttr(const clang::Decl *decl) {
5853+
return decl->hasAttrs() && llvm::any_of(decl->getAttrs(), [](auto *attr) {
5854+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
5855+
return swiftAttr->getAttribute() == "import_unsafe_api";
5856+
return false;
5857+
});
5858+
}
5859+
5860+
/// Recursively checks that there are no pointers in any fields or base classes.
5861+
/// Does not check C++ records with specific API annotations.
5862+
static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) {
5863+
auto checkType = [](clang::QualType t) {
5864+
if (t->isPointerType())
5865+
return false;
5866+
5867+
if (auto recordType = dyn_cast<clang::RecordType>(t)) {
5868+
if (auto cxxRecord =
5869+
dyn_cast<clang::CXXRecordDecl>(recordType->getDecl())) {
5870+
if (hasImportAsRefAttr(cxxRecord) || hasOwnedValueAttr(cxxRecord) ||
5871+
hasUnsafeAPIAttr(cxxRecord))
5872+
return true;
5873+
5874+
if (!hasPointerInSubobjects(cxxRecord))
5875+
return false;
5876+
}
5877+
}
5878+
5879+
return true;
5880+
};
5881+
5882+
for (auto field : decl->fields()) {
5883+
if (!checkType(field->getType()))
5884+
return false;
5885+
}
5886+
5887+
for (auto base : decl->bases()) {
5888+
if (!checkType(base.getType()))
5889+
return false;
5890+
}
5891+
5892+
return true;
5893+
}
5894+
5895+
/// Recursively checks that there are no user-provided copy constructors or
5896+
/// destructors in any fields or base classes.
5897+
/// Does not check C++ records with specific API annotations.
5898+
static bool isSufficientlyTrivial(const clang::CXXRecordDecl *decl) {
5899+
if (decl->hasUserDeclaredCopyConstructor() ||
5900+
decl->hasUserDeclaredCopyAssignment() ||
5901+
decl->hasUserDeclaredDestructor())
5902+
return false;
5903+
5904+
auto checkType = [](clang::QualType t) {
5905+
if (auto recordType = dyn_cast<clang::RecordType>(t)) {
5906+
if (auto cxxRecord =
5907+
dyn_cast<clang::CXXRecordDecl>(recordType->getDecl())) {
5908+
if (hasImportAsRefAttr(cxxRecord) || hasOwnedValueAttr(cxxRecord) ||
5909+
hasUnsafeAPIAttr(cxxRecord))
5910+
return true;
5911+
5912+
if (!isSufficientlyTrivial(cxxRecord))
5913+
return false;
5914+
}
5915+
}
5916+
5917+
return true;
5918+
};
5919+
5920+
for (auto field : decl->fields()) {
5921+
if (!checkType(field->getType()))
5922+
return false;
5923+
}
5924+
5925+
for (auto base : decl->bases()) {
5926+
if (!checkType(base.getType()))
5927+
return false;
5928+
}
5929+
5930+
return true;
5931+
}
5932+
5933+
/// Checks if a record provides the required value type lifetime operations
5934+
/// (copy and destroy).
5935+
static bool hasRequiredValueTypeOperations(const clang::CXXRecordDecl *decl) {
5936+
if (auto dtor = decl->getDestructor()) {
5937+
if (dtor->isDeleted() || dtor->getAccess() != clang::AS_public) {
5938+
return false;
5939+
}
5940+
}
5941+
5942+
// If we have no way of copying the type we can't import the class
5943+
// at all because we cannot express the correct semantics as a swift
5944+
// struct.
5945+
if (llvm::any_of(decl->ctors(), [](clang::CXXConstructorDecl *ctor) {
5946+
return ctor->isCopyConstructor() &&
5947+
(ctor->isDeleted() || ctor->getAccess() != clang::AS_public);
5948+
}))
5949+
return false;
5950+
5951+
return true;
5952+
}
5953+
5954+
CxxRecordSemanticsKind
5955+
CxxRecordSemantics::evaluate(Evaluator &evaluator,
5956+
CxxRecordSemanticsDescriptor desc) const {
5957+
const auto *decl = desc.decl;
5958+
5959+
// Hard-coded special cases from the standard library (this will go away once
5960+
// API notes support namespaces).
5961+
if (decl->getNameAsString() == "basic_string" ||
5962+
decl->getNameAsString() == "vector")
5963+
return CxxRecordSemanticsKind::Owned;
5964+
5965+
if (hasImportAsRefAttr(decl))
5966+
return CxxRecordSemanticsKind::Reference;
5967+
5968+
// TODO: diagnose if the decl also has any attrs.
5969+
if (hasRequiredValueTypeOperations(decl))
5970+
return CxxRecordSemanticsKind::UnsafeLifetimeOperation;
5971+
5972+
if (hasUnsafeAPIAttr(decl))
5973+
return CxxRecordSemanticsKind::ExplicitlyUnsafe;
5974+
5975+
if (hasOwnedValueAttr(decl))
5976+
return CxxRecordSemanticsKind::Owned;
5977+
5978+
if (!isSufficientlyTrivial(decl))
5979+
return CxxRecordSemanticsKind::UnsafeLifetimeOperation;
5980+
5981+
if (hasPointerInSubobjects(decl))
5982+
return CxxRecordSemanticsKind::UnsafePointerMember;
5983+
5984+
return CxxRecordSemanticsKind::Trivial;
5985+
}
5986+
5987+
// TODO: rename to "is acceptable use of cxx record" (as some unsafe uses are OK).
5988+
bool
5989+
IsSafeUseOfCxxRecord::evaluate(Evaluator &evaluator,
5990+
SafeUseOfCxxRecordDescriptor desc) const {
5991+
const clang::CXXRecordDecl *decl = desc.recordDecl;
5992+
5993+
auto semanticsKind = evaluateOrDefault(evaluator,
5994+
CxxRecordSemantics({decl}), {});
5995+
5996+
// Always unsafe.
5997+
if (semanticsKind == CxxRecordSemanticsKind::UnsafeLifetimeOperation)
5998+
return false;
5999+
6000+
// Always OK.
6001+
if (semanticsKind == CxxRecordSemanticsKind::Trivial ||
6002+
semanticsKind == CxxRecordSemanticsKind::Reference ||
6003+
semanticsKind == CxxRecordSemanticsKind::Owned ||
6004+
// Maybe in the future this should warn.
6005+
semanticsKind == CxxRecordSemanticsKind::ExplicitlyUnsafe)
6006+
return true;
6007+
6008+
assert(semanticsKind == CxxRecordSemanticsKind::UnsafePointerMember);
6009+
// We can't dis-allow *all* APIs that potentially return unsafe projections
6010+
// because this would break ObjC interop. So only do this when it's a known
6011+
// C++ API (maybe this could warn in a specific compiler mode, though).
6012+
return desc.kind != CxxRecordUseKind::CxxReturnType;
6013+
}
6014+
6015+
void swift::simple_display(llvm::raw_ostream &out,
6016+
CxxRecordSemanticsDescriptor desc) {
6017+
out << "Matching API semantics of C++ record '"
6018+
<< desc.decl->getNameAsString()
6019+
<< "'.\n";
6020+
}
6021+
6022+
SourceLoc
6023+
swift::extractNearestSourceLoc(CxxRecordSemanticsDescriptor desc) {
6024+
return SourceLoc();
6025+
}
6026+
6027+
void swift::simple_display(llvm::raw_ostream &out,
6028+
SafeUseOfCxxRecordDescriptor desc) {
6029+
out << "Checking if '"
6030+
<< desc.recordDecl->getNameAsString()
6031+
<< "' is safe to use in context.\n";
6032+
}
6033+
6034+
SourceLoc
6035+
swift::extractNearestSourceLoc(SafeUseOfCxxRecordDescriptor desc) {
6036+
return SourceLoc();
6037+
}

0 commit comments

Comments
 (0)