Skip to content

[cxx-interop] Flip the switch: only import safe APIs. #59624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions SwiftCompilerSources/Sources/Basic/SourceLoc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct SourceLoc {
guard bridged.isValid() else {
return nil
}
self.locationInFile = bridged.getOpaquePointerValue().assumingMemoryBound(to: UInt8.self)
self.locationInFile = bridged.__getOpaquePointerValueUnsafe().assumingMemoryBound(to: UInt8.self)
}

public var bridged: swift.SourceLoc {
Expand Down Expand Up @@ -57,7 +57,7 @@ public struct CharSourceRange {
}

public init?(bridged: swift.CharSourceRange) {
guard let start = SourceLoc(bridged: bridged.getStart()) else {
guard let start = SourceLoc(bridged: bridged.__getStartUnsafe()) else {
return nil
}
self.init(start: start, byteLength: bridged.getByteLength())
Expand Down
2 changes: 1 addition & 1 deletion SwiftCompilerSources/Sources/Basic/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ extension String {
/// Underscored to avoid name collision with the std overlay.
/// To be replaced with an overlay call once the CI uses SDKs built with Swift 5.8.
public init(_cxxString s: std.string) {
self.init(cString: s.c_str())
self.init(cString: s.__c_strUnsafe())
withExtendedLifetime(s) {}
}
}
Expand Down
37 changes: 37 additions & 0 deletions docs/CppInteroperability/UserManual.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# C++ Interop User Manual
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is user manual lacks introduction and basic context to be able to understand the sections below. Is it meant to be a manual for forward interop, or bidirectional whole interop? How is the user supposed to read it and understand what categories are without some sort of basic introduction.


The following document explains how C++ APIs are imported into Swift and is targeted at users of C++ interoperability. Hopefully this document will help you understand why the compiler cannot import various APIs and help you update these APIs to be useable from Swift. First, the document will lay out some API patterns and definitions, then it will discuss how the Swift compiler decides if an API should be usable in Swift.

## Reference Types

Reference types have reference semantics and object identity. A reference type is a pointer (or “reference”) to some object which means there is a layer of indirection. When a reference type is copied, the pointer’s value is copied rather than the object’s storage. This means reference types can be used to represent non-copyable types in C++. Any C++ APIs that use reference types must have at least one layer of indirection to the type (a pointer or reference). Currently reference types must be immortal (never deallocated) or have manually managed lifetimes. You can specify a type has reference semantics by using the `import_reference` swift attribute.

## Owned Types

Owned types “own” some storage which can be copied and destroyed. An owned type must be copyable and destructible. The copy constructor must copy any storage that is owned by the type and the destructor must destroy that storage. Copies and destroys must balance out and these operations must not have side effects. Examples of owned types include `std::vector` and `std::string`. The Swift compiler will assume that any types which do not contain pointers are owned types. You can also specify a type is an owned type by using the `import_owned` swift attribute.

## Iterator Types

An iterator represents a point in a range. Iterator types must provide a comparison operator and increment operator (increment or ++ operators are imported as a member called successor). Iterators can be returned by `begin` and `end` methods to form a range which will automatically be imported as a Sequence in Swift. Iterators can often be inferred by the compiler using the C++ iterator traits, but you can also specify a type is an iterator by using the `import_iterator` swift attribute.

## Trivial Types

Trivial types can be copied by copying the bits of a value of the trivial type and do not need any special destruction logic. Trivial types are inferred by the compiler and cannot be specified using an attribute. Trivial types own their storage, so rules below that apply to owned types also apply to trivial types (specifically regarding projections). Pointers are not trivial types. When Objective-C++ mode is enabled, C++ types that hold Objective-C classes are still considered trivial, even though they technically violate the above contract.

## Unsafe Types

All types which do not fall into one of the above categories are considered unsafe. Any unsafe API, including unsafe types that are copyable and destructible may be imported using the `import_unsafe` swift attribute. There are two kinds of unsafe types: **unsafe pointer types** and **un-importable types**.

**Unsafe Pointer Types:** are types which contain an un-owned pointer. This type is assumed to represent an unsafe memory projection when used as a return type for the method of an owned type.

**Un-importable Types:** are types that are not copyable or destructible. These types cannot be represented in Swift, so they cannot be imported (even if they are marked with the `import_unsafe` attribute).

## API rules

The Swift compiler enforces certain API rules, not to ensure a completely safe C++ API interface, but to prevent especially unsafe patterns that will likely lead to bugs. Many of these unsafe patterns stem from the subtly different lifetime semantics in C++ and Swift and aim to prevent memory projections of owned types. The currently enforced rules are as follows:

* Un-importable types are not allowed to be used in any contexts.
* Unsafe pointer types are not allowed to represent unsafe memory projections from owned types. Note: unsafe pointer types *are* allowed to represent memory projections from reference types. Note: the Swift compiler assumes that global and static functions do not return projections.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you clarify that unsafe pointers are allowed to represent memory projections from immortal reference types here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have the idea of immortal vs other foreign reference types. Above it clarifies that foreign reference types must be immortal or manually managed, so I think it's fine. This states our current support, not our future plans.

* Iterators are not allowed to represent unsafe memory projections from owned types. Iterators may be used safely through the `CxxIterator` and `CxxSequence` protocols which iterators and ranges automatically conform to.
* Members of an unsafe type that is explicitly imported using the `import_unsafe` swift attribute are still subject to the above rules. Each individual unsafe member must also have the `import_unsafe` swift attribute to be usable.

28 changes: 27 additions & 1 deletion include/swift/AST/DiagnosticsClangImporter.def
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ WARNING(too_many_class_template_instantiations, none,
"template instantiation for '%0' not imported: too many instantiations",
(StringRef))

WARNING(api_pattern_attr_ignored, none,
"'%0' swift attribute ignored on type '%1': type is not copyable or destructible",
(StringRef, StringRef))

NOTE(macro_not_imported_unsupported_operator, none, "operator not supported in macro arithmetic", ())
NOTE(macro_not_imported_unsupported_named_operator, none, "operator '%0' not supported in macro arithmetic", (StringRef))
NOTE(macro_not_imported_invalid_string_literal, none, "invalid string literal", ())
Expand All @@ -143,8 +147,30 @@ NOTE(return_type_not_imported, none, "return type not imported", ())
NOTE(parameter_type_not_imported, none, "parameter %0 not imported", (const clang::NamedDecl*))
NOTE(incomplete_interface, none, "interface %0 is incomplete", (const clang::NamedDecl*))
NOTE(incomplete_protocol, none, "protocol %0 is incomplete", (const clang::NamedDecl*))
NOTE(unsupported_builtin_type, none, "built-in type '%0' not supported", (StringRef))
NOTE(incomplete_record, none, "record '%0' is not defined (incomplete)", (StringRef))
NOTE(record_over_aligned, none, "record '%0' is over aligned", (StringRef))
NOTE(record_non_trivial_copy_destroy, none, "record '%0' is not trivial to copy/destroy", (StringRef))
NOTE(record_is_dependent, none, "record '%0' is dependent", (StringRef))
NOTE(record_parent_unimportable, none, "record %0's parent is not importable", (StringRef))
NOTE(reference_passed_by_value, none, "function uses foreign reference type "
"'%0' as a value in %1 types which breaks "
"'import_reference' contract (outlined in "
"C++ Interop User Manual).",
(StringRef, StringRef))
NOTE(record_not_automatically_importable, none, "record '%0' is not "
"automatically importable: %1. "
"Refer to the C++ Interop User "
"Manual to classify this type.",
(StringRef, StringRef))
NOTE(projection_not_imported, none, "C++ method '%0' that returns unsafe "
"projection of type '%1' not imported",
(StringRef, StringRef))
NOTE(dont_use_iterator_api, none, "C++ method '%0' that returns an unsafe "
"iterator not imported: use Swift Sequence "
"APIs instead",
(StringRef))

NOTE(unsupported_builtin_type, none, "built-in type '%0' not supported", (StringRef))
NOTE(record_field_not_imported, none, "field %0 not imported", (const clang::NamedDecl*))
NOTE(invoked_func_not_imported, none, "function %0 not imported", (const clang::NamedDecl*))
NOTE(record_method_not_imported, none, "method %0 not imported", (const clang::NamedDecl*))
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Basic/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@

// Tells Swift's ClangImporter to import a C++ type as a foreign reference type.
#if __has_attribute(swift_attr)
#define SWIFT_IMPORT_REFERENCE __attribute__((swift_attr("import_as_ref")))
#define SWIFT_IMPORT_REFERENCE __attribute__((swift_attr("import_reference")))
#else
#define SWIFT_IMPORT_REFERENCE
#endif
Expand Down
107 changes: 107 additions & 0 deletions include/swift/ClangImporter/ClangImporterRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,113 @@ class ClangRecordMemberLookup
evaluate(Evaluator &evaluator, ClangRecordMemberLookupDescriptor desc) const;
};

enum class CxxRecordSemanticsKind {
Trivial,
Owned,
Reference,
Iterator,
// An API that has be annotated as explicitly unsafe, but still importable.
// TODO: we should rename these APIs.
ExplicitlyUnsafe,
// A record that is either not copyable or not destructible.
MissingLifetimeOperation,
// A record that contains a pointer (aka non-trivial type).
UnsafePointerMember
};

struct CxxRecordSemanticsDescriptor final {
const clang::CXXRecordDecl *decl;
ASTContext &ctx;

CxxRecordSemanticsDescriptor(const clang::CXXRecordDecl *decl,
ASTContext &ctx)
: decl(decl), ctx(ctx) {}

friend llvm::hash_code hash_value(const CxxRecordSemanticsDescriptor &desc) {
return llvm::hash_combine(desc.decl);
}

friend bool operator==(const CxxRecordSemanticsDescriptor &lhs,
const CxxRecordSemanticsDescriptor &rhs) {
return lhs.decl == rhs.decl;
}

friend bool operator!=(const CxxRecordSemanticsDescriptor &lhs,
const CxxRecordSemanticsDescriptor &rhs) {
return !(lhs == rhs);
}
};

void simple_display(llvm::raw_ostream &out, CxxRecordSemanticsDescriptor desc);
SourceLoc extractNearestSourceLoc(CxxRecordSemanticsDescriptor desc);

/// What pattern does this C++ API fit? Uses attributes such as
/// import_owned and import_as_reference to determine the pattern.
class CxxRecordSemantics
: public SimpleRequest<CxxRecordSemantics,
CxxRecordSemanticsKind(CxxRecordSemanticsDescriptor),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

// Caching
bool isCached() const { return true; }

// Source location
SourceLoc getNearestLoc() const { return SourceLoc(); };

private:
friend SimpleRequest;

// Evaluation.
CxxRecordSemanticsKind evaluate(Evaluator &evaluator,
CxxRecordSemanticsDescriptor) const;
};

struct SafeUseOfCxxDeclDescriptor final {
const clang::Decl *decl;
ASTContext &ctx;

SafeUseOfCxxDeclDescriptor(const clang::Decl *decl, ASTContext &ctx)
: decl(decl), ctx(ctx) {}

friend llvm::hash_code hash_value(const SafeUseOfCxxDeclDescriptor &desc) {
return llvm::hash_combine(desc.decl);
}

friend bool operator==(const SafeUseOfCxxDeclDescriptor &lhs,
const SafeUseOfCxxDeclDescriptor &rhs) {
return lhs.decl == rhs.decl;
}

friend bool operator!=(const SafeUseOfCxxDeclDescriptor &lhs,
const SafeUseOfCxxDeclDescriptor &rhs) {
return !(lhs == rhs);
}
};

void simple_display(llvm::raw_ostream &out, SafeUseOfCxxDeclDescriptor desc);
SourceLoc extractNearestSourceLoc(SafeUseOfCxxDeclDescriptor desc);

class IsSafeUseOfCxxDecl
: public SimpleRequest<IsSafeUseOfCxxDecl, bool(SafeUseOfCxxDeclDescriptor),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

// Caching
bool isCached() const { return true; }

// Source location
SourceLoc getNearestLoc() const { return SourceLoc(); };

private:
friend SimpleRequest;

// Evaluation.
bool evaluate(Evaluator &evaluator, SafeUseOfCxxDeclDescriptor desc) const;
};

#define SWIFT_TYPEID_ZONE ClangImporter
#define SWIFT_TYPEID_HEADER "swift/ClangImporter/ClangImporterTypeIDZone.def"
#include "swift/Basic/DefineTypeIDZone.h"
Expand Down
6 changes: 6 additions & 0 deletions include/swift/ClangImporter/ClangImporterTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ SWIFT_REQUEST(ClangImporter, CXXNamespaceMemberLookup,
SWIFT_REQUEST(ClangImporter, ClangRecordMemberLookup,
Decl *(ClangRecordMemberLookupDescriptor), Uncached,
NoLocationInfo)
SWIFT_REQUEST(ClangImporter, CxxRecordSemantics,
CxxRecordSemanticsKind(const clang::CXXRecordDecl *), Cached,
NoLocationInfo)
SWIFT_REQUEST(ClangImporter, IsSafeUseOfCxxDecl,
bool(SafeUseOfCxxRecordDescriptor), Cached,
NoLocationInfo)
Loading