mirror of
https://github.com/apple/swift.git
synced 2026-10-08 12:50:50 +02:00
With ImportUnsafeCxxMethodsAsAlwaysUnsafe, a C++ method that would otherwise be renamed to '__<name>Unsafe' keeps its name and is imported '@unsafe(always)'. That was decided for every method the unsafe projection heuristic flags, including ones that were never renamed, such as methods with a custom Swift name. Those are now only '@unsafe', as without the feature, instead of breaking existing code that uses them.
1492 lines
59 KiB
C++
1492 lines
59 KiB
C++
#include "ClangDerivedConformances.h"
|
|
#include "CxxUnsafetyReason.h"
|
|
#include "ImporterImpl.h"
|
|
#include "swift/AST/DiagnosticsClangImporter.h"
|
|
#include "swift/AST/ParameterList.h"
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/ClangImporter/ClangImporter.h"
|
|
#include "swift/ClangImporter/ClangImporterRequests.h"
|
|
#include "clang/AST/Attr.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclObjC.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/ExprCXX.h"
|
|
#include "clang/AST/RecordLayout.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Basic/Specifiers.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include <algorithm>
|
|
|
|
using namespace swift;
|
|
using namespace importer;
|
|
|
|
bool importer::hasImportReferenceAttr(const clang::RecordDecl *decl) {
|
|
return hasSwiftAttribute(decl, {"import_reference"});
|
|
}
|
|
|
|
bool importer::hasSwiftAttributeOnAnyRedecl(const clang::RecordDecl *decl,
|
|
ArrayRef<StringRef> attrs) {
|
|
return llvm::any_of(decl->redecls(), [&](const clang::Decl *redecl) {
|
|
return hasSwiftAttribute(redecl, attrs);
|
|
});
|
|
}
|
|
|
|
bool importer::isForeignReferenceRecord(const clang::RecordDecl *decl,
|
|
Evaluator &eval) {
|
|
// Only ask the request once there is a definition. It is cached per
|
|
// declaration, so asking about a class template specialization that has not
|
|
// been instantiated yet would cache "not a reference" for good, even though
|
|
// instantiating it may reveal a reference base.
|
|
if (decl->getDefinition())
|
|
return evaluateOrDefault(eval, ForeignReferenceTypeInfoRequest({decl}),
|
|
ForeignReferenceTypeInfo())
|
|
.isReference();
|
|
|
|
// Without one, a direct annotation is all there is to go on.
|
|
return hasSwiftAttributeOnAnyRedecl(decl, {"import_reference"});
|
|
}
|
|
|
|
bool importer::hasImportAsOpaquePointerAttr(const clang::RecordDecl *decl) {
|
|
return llvm::any_of(decl->specific_attrs<clang::SwiftAttrAttr>(),
|
|
[](const clang::SwiftAttrAttr *swiftAttr) {
|
|
return swiftAttr->getAttribute() ==
|
|
"import_opaque_pointer";
|
|
});
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Direct view analysis
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
/// Whether \p type is "self-contained" for the purpose of direct-view
|
|
/// inference: it is escapable (SWIFT_ESCAPABLE), a foreign reference type
|
|
/// (SWIFT_SHARED_REFERENCE / SWIFT_IMMORTAL_REFERENCE), or a record explicitly
|
|
/// annotated SWIFT_SELF_CONTAINED (import_owned). A record explicitly marked
|
|
/// unsafe is never self-contained, which also excludes
|
|
/// SWIFT_UNSAFE_REFERENCE.
|
|
bool isSelfContainedForDirectView(const clang::Type *type, Evaluator &eval) {
|
|
type = type->getUnqualifiedDesugaredType();
|
|
|
|
// A function (pointer) refers to code, and a pointer to member is an offset
|
|
// rather than an address, so neither can dangle.
|
|
if (type->isFunctionPointerType() || type->isFunctionType() ||
|
|
type->isMemberPointerType())
|
|
return true;
|
|
|
|
if (const auto *recordType = type->getAs<clang::RecordType>()) {
|
|
auto *recordDecl = recordType->getDecl();
|
|
auto *definition = recordDecl->getDefinition();
|
|
// An explicitly unsafe type is never self-contained, so its unsafety is
|
|
// not silently dropped by an enclosing view. The annotation may sit on any
|
|
// declaration of the type, so consider the whole chain.
|
|
if (importer::hasSwiftAttributeOnAnyRedecl(recordDecl, {"unsafe"}))
|
|
return false;
|
|
// Reference types are managed by Swift, so a pointer to one does not
|
|
// introduce a lifetime dependency. This holds for a reference type that has
|
|
// only been declared, too.
|
|
if (importer::isForeignReferenceRecord(recordDecl, eval))
|
|
return true;
|
|
if (!definition)
|
|
return false;
|
|
if (importer::hasOwnedValueAttr(definition))
|
|
return true;
|
|
}
|
|
|
|
return evaluateOrDefault(eval, ClangTypeEscapability({type, nullptr}),
|
|
CxxEscapability::Unknown) ==
|
|
CxxEscapability::Escapable;
|
|
}
|
|
|
|
bool isDirectViewTypeImpl(const clang::Type *type, Evaluator &eval,
|
|
llvm::SmallDenseSet<const clang::Decl *, 4> &seen) {
|
|
type = type->getUnqualifiedDesugaredType();
|
|
|
|
// (A) A pointer or reference is a direct view if its pointee is
|
|
// self-contained. Block and ObjC-object pointers are not "pointers into a
|
|
// buffer of self-contained objects", so they are deliberately not matched
|
|
// here.
|
|
if (type->isPointerType() || type->isReferenceType()) {
|
|
clang::QualType pointee = type->getPointeeType();
|
|
// We do not know what is stored at the pointed-to memory, so a `void *`
|
|
// cannot be a pointer into a buffer of self-contained objects.
|
|
if (pointee->isFunctionType() || pointee->isVoidType())
|
|
return false;
|
|
return isSelfContainedForDirectView(pointee.getTypePtr(), eval);
|
|
}
|
|
|
|
// (B) A record is a direct view if every field and base is either
|
|
// self-contained or itself a direct view. A record with no indirection at
|
|
// all is treated as having a single level of indirection, so that a
|
|
// non-escapable marker type (`struct SWIFT_NONESCAPABLE Token { long id; };`)
|
|
// stays a direct view: it holds no pointer, so nothing in it can dangle.
|
|
if (const auto *recordType = type->getAs<clang::RecordType>()) {
|
|
auto *recordDecl = recordType->getDecl()->getDefinition();
|
|
if (!recordDecl)
|
|
return false;
|
|
if (importer::hasSwiftAttribute(recordDecl, {"unsafe"}))
|
|
return false;
|
|
if (!seen.insert(recordDecl).second)
|
|
return true;
|
|
|
|
// A base or field that is neither self-contained nor itself a direct view
|
|
// can dangle, which disqualifies the enclosing record.
|
|
auto canDangle = [&](clang::QualType t) {
|
|
const clang::Type *ty = t.getTypePtr();
|
|
return !isSelfContainedForDirectView(ty, eval) &&
|
|
!isDirectViewTypeImpl(ty, eval, seen);
|
|
};
|
|
|
|
return !anySubobjectTypeSatisfies(recordDecl, canDangle);
|
|
}
|
|
|
|
// (C) Anything else is not itself a direct view.
|
|
return false;
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
bool importer::isDirectViewType(const clang::Type *type, Evaluator &eval) {
|
|
llvm::SmallDenseSet<const clang::Decl *, 4> seen;
|
|
return isDirectViewTypeImpl(type, eval, seen);
|
|
}
|
|
|
|
bool importer::isDirectViewType(const clang::Decl *decl, ASTContext &swiftCtx) {
|
|
if (const auto *typeDecl = dyn_cast<clang::TypeDecl>(decl)) {
|
|
clang::QualType type = typeDecl->getASTContext().getTypeDeclType(typeDecl);
|
|
return isDirectViewType(type.getTypePtr(), swiftCtx.evaluator);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
/// The retain:/release: attributes written on any declaration of a record.
|
|
struct RetainReleaseInfo {
|
|
RetainReleaseInfo(const clang::RecordDecl *decl) : decl(decl) {
|
|
// The annotation can sit on any declaration of the record, so gather from
|
|
// the whole chain. Key on the attribute string, so that a copy inherited by
|
|
// a later redeclaration is not counted as a second annotation.
|
|
llvm::SmallDenseSet<StringRef, 2> seen;
|
|
for (auto *redecl : decl->redecls()) {
|
|
for (auto *attr : redecl->specific_attrs<clang::SwiftAttrAttr>()) {
|
|
StringRef attrStr = attr->getAttribute();
|
|
StringRef name = attrStr;
|
|
if (name.consume_front("retain:")) {
|
|
if (seen.insert(attrStr).second) {
|
|
retainAttrs.push_back(attr);
|
|
retainName = name;
|
|
}
|
|
} else if (name.consume_front("release:")) {
|
|
if (seen.insert(attrStr).second) {
|
|
releaseAttrs.push_back(attr);
|
|
releaseName = name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The name of the last retain: operation (empty if none).
|
|
StringRef getRetain() const { return retainName; }
|
|
|
|
/// The name of the last release: operation (empty if none).
|
|
StringRef getRelease() const { return releaseName; }
|
|
|
|
bool isImmortal() const {
|
|
return isValid() && retainName == "immortal" && releaseName == "immortal";
|
|
}
|
|
|
|
bool hasMixedImmortality() const {
|
|
bool retainImmortal = retainName == "immortal";
|
|
bool releaseImmortal = releaseName == "immortal";
|
|
return retainImmortal != releaseImmortal;
|
|
}
|
|
|
|
bool isValid() const {
|
|
return retainAttrs.size() == 1 && releaseAttrs.size() == 1 &&
|
|
!hasMixedImmortality();
|
|
}
|
|
|
|
/// Emit "retain and release functions specified on / inherited from" note.
|
|
void
|
|
noteRetainReleaseOrigin(ClangImporter::Implementation &Impl,
|
|
clang::SourceLocation loc,
|
|
std::optional<bool> isRelease = std::nullopt) const {
|
|
if (loc.isValid()) {
|
|
unsigned sel = 2 /* retain and release functions */;
|
|
if (isRelease.has_value())
|
|
sel = isRelease.value() ? 1 /* release function */
|
|
: 0 /* retain function */;
|
|
Impl.diagnose(HeaderLoc(loc), diag::retain_release_function_origin, sel,
|
|
/*isInherited=*/false, decl);
|
|
}
|
|
// Otherwise: no usable attribute location and not inherited -> omit.
|
|
}
|
|
|
|
/// Diagnose malformed retain:/release: attributes via \p Impl (if non-null).
|
|
/// Returns whether the annotations are structurally well-formed.
|
|
bool checkShape(ClangImporter::Implementation *Impl) const {
|
|
HeaderLoc loc(decl->getLocation());
|
|
auto checkOp = [&](bool isRelease) {
|
|
auto &attrs = isRelease ? releaseAttrs : retainAttrs;
|
|
auto &name = isRelease ? releaseName : retainName;
|
|
if (attrs.size() != 1) {
|
|
if (Impl) {
|
|
Impl->diagnose(loc,
|
|
diag::reference_type_exactly_one_retain_release_attr,
|
|
isRelease, decl);
|
|
for (auto *attr : attrs)
|
|
noteRetainReleaseOrigin(*Impl, attr->getLocation(), isRelease);
|
|
}
|
|
return false;
|
|
}
|
|
if (name.empty()) {
|
|
if (Impl) {
|
|
Impl->diagnose(loc, diag::reference_type_empty_retain_release_name,
|
|
isRelease, decl);
|
|
noteRetainReleaseOrigin(*Impl, attrs[0]->getLocation(), isRelease);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
bool retainOk = checkOp(/*isRelease=*/false);
|
|
bool releaseOk = checkOp(/*isRelease=*/true);
|
|
if (!retainOk || !releaseOk)
|
|
return false;
|
|
|
|
if (hasMixedImmortality()) {
|
|
if (Impl) {
|
|
Impl->diagnose(loc, diag::reference_type_mixed_immortal_marker, decl);
|
|
noteRetainReleaseOrigin(*Impl, retainAttrs[0]->getLocation());
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const clang::RecordDecl *decl;
|
|
StringRef retainName, releaseName;
|
|
llvm::SmallVector<const clang::SwiftAttrAttr *, 1> retainAttrs, releaseAttrs;
|
|
};
|
|
|
|
class ForeignReferenceTypeChecker {
|
|
/// We are checking this to determine whether it is a foreign reference type.
|
|
const clang::CXXRecordDecl *checkedDecl;
|
|
|
|
/// Used for emitting diagnostics.
|
|
ClangImporter::Implementation *Impl = nullptr;
|
|
|
|
/// Whether we encountered a non-record base during the base traversal.
|
|
bool hasNonRecordBase = false;
|
|
|
|
/// Base classes that are marked as FRTs. Populated by \c visitBases().
|
|
llvm::SmallVector<const clang::CXXRecordDecl *, 1> FRTBases;
|
|
|
|
/// Virtual bases, which we only need to visit once.
|
|
llvm::SmallPtrSet<const clang::CXXRecordDecl *, 1> virtualBases;
|
|
|
|
/// Recursively visits the bases of \p decl to accumulate FRT information.
|
|
///
|
|
/// If exactly one base of \p decl leads to an annotated FRT base class, then
|
|
/// this function returns a pointer to that direct base. Returns \c nullptr
|
|
/// otherwise.
|
|
const clang::CXXRecordDecl *visitBases(const clang::CXXRecordDecl *decl) {
|
|
if (!decl->hasDefinition())
|
|
// Without a definition, there's no inheritance info to check.
|
|
return nullptr;
|
|
|
|
const clang::CXXRecordDecl *singleFRTSuperclass = nullptr;
|
|
bool multipleFRTSuperclasses = false;
|
|
for (auto declBase : decl->bases()) {
|
|
auto *base = declBase.getType()->getAsCXXRecordDecl();
|
|
if (!base) {
|
|
// It is possible to encounter `clang::TemplateSpecializationType`s.
|
|
// In such cases, report this as invalid and continue past it.
|
|
hasNonRecordBase = true;
|
|
continue;
|
|
}
|
|
|
|
ASSERT(base->hasDefinition() && "base record should be complete");
|
|
base = base->getDefinition();
|
|
ASSERT(!base->isDependentContext() && "base should not be dependent");
|
|
|
|
if (!declBase.isVirtual() || virtualBases.insert(base).second) {
|
|
bool baseIsFRT;
|
|
if (importer::hasImportReferenceAttr(base)) {
|
|
FRTBases.push_back(base);
|
|
baseIsFRT = true;
|
|
} else {
|
|
baseIsFRT = static_cast<bool>(visitBases(base));
|
|
}
|
|
|
|
if (baseIsFRT && !declBase.isVirtual() &&
|
|
declBase.getAccessSpecifier() ==
|
|
clang::AccessSpecifier::AS_public) {
|
|
if (singleFRTSuperclass)
|
|
multipleFRTSuperclasses = true;
|
|
else
|
|
singleFRTSuperclass = base;
|
|
}
|
|
}
|
|
}
|
|
|
|
return multipleFRTSuperclasses ? nullptr : singleFRTSuperclass;
|
|
}
|
|
|
|
/// Whether \p base is non-null and has an offset of zero from \c checkedDecl.
|
|
bool isPresentAndAtOffsetZero(const clang::CXXRecordDecl *base) const {
|
|
ASSERT(checkedDecl);
|
|
if (base == nullptr)
|
|
return false;
|
|
auto &clangCtx = checkedDecl->getASTContext();
|
|
auto &layout = clangCtx.getASTRecordLayout(checkedDecl);
|
|
return layout.getBaseClassOffset(base).isZero();
|
|
}
|
|
|
|
public:
|
|
ForeignReferenceTypeChecker(const clang::CXXRecordDecl *checkedDecl)
|
|
: checkedDecl{checkedDecl} {}
|
|
|
|
ForeignReferenceTypeChecker &&
|
|
withDiagnostics(ClangImporter::Implementation &ImplRef) && {
|
|
Impl = &ImplRef;
|
|
return std::move(*this);
|
|
}
|
|
|
|
ForeignReferenceTypeInfo check() && {
|
|
ASSERT(checkedDecl && "ForeignReferenceTypeInfo should only be used once");
|
|
SWIFT_DEFER { checkedDecl = nullptr; };
|
|
|
|
if (importer::hasImportReferenceAttr(checkedDecl)) {
|
|
// checkedDecl is explicitly annotated as a foreign reference type.
|
|
// Do not let it have a primarySuperclass, to prevent upcasting past the
|
|
// annotation boundary in the class hierarchy.
|
|
auto rrInfo = RetainReleaseInfo(checkedDecl);
|
|
if (rrInfo.isImmortal())
|
|
return ForeignReferenceTypeInfo::Immortal(checkedDecl,
|
|
/*primarySuperclass=*/nullptr,
|
|
/*isValid=*/rrInfo.isValid());
|
|
else
|
|
return ForeignReferenceTypeInfo::Shared(checkedDecl,
|
|
/*primarySuperclass=*/nullptr,
|
|
/*isValid=*/rrInfo.isValid());
|
|
}
|
|
|
|
const clang::CXXRecordDecl *uniqueDirectFRTBase = visitBases(checkedDecl);
|
|
if (FRTBases.empty()) {
|
|
// Neither checkedDecl nor any of its base classes are annotated as
|
|
// a reference type, so checkedDecl is a value type.
|
|
ASSERT(uniqueDirectFRTBase == nullptr &&
|
|
"there should be no superclass if there are no FRT bases");
|
|
return ForeignReferenceTypeInfo::Value();
|
|
}
|
|
|
|
// The primary FRT superclass is the unique direct FRT base of checkedDecl,
|
|
// but only if it is at offset 0 (so a pointer bitcast suffices for upcast).
|
|
auto *primarySuperclass = isPresentAndAtOffsetZero(uniqueDirectFRTBase)
|
|
? uniqueDirectFRTBase
|
|
: nullptr;
|
|
|
|
const clang::CXXRecordDecl *FRTBase = nullptr;
|
|
bool seenShared = false, seenMultipleShared = false, seenImmortal = false,
|
|
seenInvalidOps = false;
|
|
|
|
for (auto *base : FRTBases) {
|
|
auto rrInfo = RetainReleaseInfo(base);
|
|
seenInvalidOps |= !rrInfo.isValid();
|
|
if (rrInfo.isImmortal()) {
|
|
seenImmortal = true;
|
|
} else {
|
|
if (!FRTBase) {
|
|
FRTBase = base;
|
|
seenShared = true;
|
|
} else {
|
|
seenMultipleShared = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are no shared references, FRTBase is the first immortal base.
|
|
if (!FRTBase) {
|
|
ASSERT(seenImmortal && "should have encountered immortal FRTBase");
|
|
FRTBase = FRTBases.front();
|
|
}
|
|
|
|
if (seenMultipleShared || (seenShared && seenImmortal) || seenInvalidOps) {
|
|
// checkedDecl is an invalid FRT, either because it has multiple shared
|
|
// FRT bases (ambiguous retain/release ops), or because it has mixed
|
|
// ancestry between shared and immortal bases.
|
|
if (Impl)
|
|
Impl->diagnose(HeaderLoc{checkedDecl->getLocation()},
|
|
diag::cant_infer_frt_in_cxx_inheritance, checkedDecl);
|
|
|
|
// decl inherits from FRT base, so we should treat it as a reference
|
|
// type, albeit an invalid one (due to ambiguity).
|
|
//
|
|
// return ForeignReferenceTypeInfo::Shared(FRTBase, nullptr,
|
|
// /*isValid=*/false);
|
|
//
|
|
// However, to honor the existing behavior, (for now) we will report
|
|
// that this is an (invalid) value type.
|
|
return ForeignReferenceTypeInfo::Value(/*isValid=*/false);
|
|
}
|
|
|
|
if (seenImmortal)
|
|
return ForeignReferenceTypeInfo::Immortal(FRTBase, primarySuperclass);
|
|
|
|
return ForeignReferenceTypeInfo::Shared(FRTBase, primarySuperclass);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void swift::simple_display(llvm::raw_ostream &out,
|
|
const ForeignReferenceTypeInfoDescriptor &desc) {
|
|
out << "Checking foreign reference type info for '";
|
|
printRecordName(out, desc.decl);
|
|
out << "'\n";
|
|
}
|
|
|
|
SourceLoc
|
|
swift::extractNearestSourceLoc(const ForeignReferenceTypeInfoDescriptor &desc) {
|
|
return SourceLoc();
|
|
}
|
|
|
|
ForeignReferenceTypeInfo
|
|
importer::getUncachedForeignReferenceTypeInfo(const clang::RecordDecl *decl) {
|
|
// The annotation can sit on any declaration of the record: within a
|
|
// translation unit a swift_attr is inherited by later redeclarations only,
|
|
// and a chain assembled across modules is not merged at all. Answer for the
|
|
// declaration that carries the information: the definition when there is one,
|
|
// since reference-ness can also be inherited from a base class, and otherwise
|
|
// the declaration spelling the annotation. Clients can then use the request
|
|
// without walking the chain themselves.
|
|
if (auto *definition = decl->getDefinition()) {
|
|
decl = definition;
|
|
} else if (!importer::hasImportReferenceAttr(decl)) {
|
|
for (auto *redecl : decl->redecls()) {
|
|
auto *record = cast<clang::RecordDecl>(redecl);
|
|
if (importer::hasImportReferenceAttr(record)) {
|
|
decl = record;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (auto *cxxDecl = dyn_cast<clang::CXXRecordDecl>(decl))
|
|
return ForeignReferenceTypeChecker(cxxDecl).check();
|
|
|
|
// If this isn't a C++ record, then there's no inheritance (nor any of the
|
|
// associated complications) to worry about. Just look for ref attributes.
|
|
|
|
if (importer::hasImportReferenceAttr(decl)) {
|
|
auto rrInfo = RetainReleaseInfo(decl);
|
|
if (rrInfo.isImmortal())
|
|
return ForeignReferenceTypeInfo::Immortal(decl,
|
|
/*primarySuperclass=*/nullptr,
|
|
/*isValid=*/rrInfo.isValid());
|
|
else
|
|
return ForeignReferenceTypeInfo::Shared(decl,
|
|
/*primarySuperclass=*/nullptr,
|
|
/*isValid=*/rrInfo.isValid());
|
|
}
|
|
|
|
return ForeignReferenceTypeInfo::Value();
|
|
}
|
|
|
|
ForeignReferenceTypeInfo ForeignReferenceTypeInfoRequest::evaluate(
|
|
Evaluator &evaluator, ForeignReferenceTypeInfoDescriptor desc) const {
|
|
|
|
// Decls belonging to a module-building Clang sub-instance are freed once that
|
|
// sub-instance goes away, so this request must not cache decls allocated from
|
|
// that sub-instance's clang::ASTContext.
|
|
ASSERT(!desc.decl->getASTContext().getLangOpts().isCompilingModule() &&
|
|
"caching FRT info for a decl from a transient Clang sub-instance");
|
|
|
|
return importer::getUncachedForeignReferenceTypeInfo(desc.decl);
|
|
}
|
|
|
|
bool importer::diagnoseForeignReferenceType(
|
|
const clang::CXXRecordDecl *decl, ClangImporter::Implementation &Impl) {
|
|
|
|
// First, evaluate as a request. This does not emit diagnostics, but caches
|
|
// the result for future requests. This ensures that we perform the checkFRT()
|
|
// routine at most once for valid decls.
|
|
auto info = evaluateOrDefault(Impl.SwiftContext.evaluator,
|
|
ForeignReferenceTypeInfoRequest({decl}), {});
|
|
if (info.isValid())
|
|
return true;
|
|
|
|
// If the result was invalid, we need to run the underlying check again, but
|
|
// this time with ClangImporter::Implemention in order to emit diagnostics.
|
|
// This slow path does redundant work but only for invalid decls.
|
|
auto infoAgain =
|
|
ForeignReferenceTypeChecker(decl).withDiagnostics(Impl).check();
|
|
// FIXME: this appears to be non-deterministic in some configurations
|
|
// ASSERT(!infoAgain.isValid() && "FRT check should be deterministic");
|
|
(void)infoAgain;
|
|
return false;
|
|
}
|
|
|
|
static const clang::RecordDecl *
|
|
getReturnTypeAsRecordDeclPtr(const clang::NamedDecl *ND) {
|
|
clang::QualType retTy;
|
|
|
|
auto &clangCtx = ND->getASTContext();
|
|
|
|
if (auto *CD = dyn_cast<clang::CXXConstructorDecl>(ND))
|
|
retTy = clangCtx.getCanonicalTagType(CD->getParent());
|
|
else if (auto *FD = dyn_cast<clang::FunctionDecl>(ND))
|
|
retTy = FD->getReturnType();
|
|
else if (auto *MD = dyn_cast<clang::ObjCMethodDecl>(ND))
|
|
retTy = MD->getReturnType();
|
|
else
|
|
return nullptr;
|
|
|
|
if (!retTy->isPointerOrReferenceType())
|
|
return nullptr;
|
|
// N.B. We can't use QualType::just getPointeeCXXRecordDecl here because we
|
|
// also need to account for ObjC interop, where FRTs are clang::RecordDecls.
|
|
return retTy->getPointeeType()->getAsRecordDecl();
|
|
}
|
|
|
|
static void diagnoseMissingReturnsRetained(ClangImporter::Implementation &Impl,
|
|
const ValueDecl *func,
|
|
SourceLoc callSiteLoc) {
|
|
auto &ctx = Impl.SwiftContext;
|
|
auto *clangFunc = cast<clang::NamedDecl>(func->getClangDecl());
|
|
|
|
if (!isa<clang::FunctionDecl, clang::ObjCMethodDecl>(clangFunc))
|
|
// Ownership attrs are not yet supported for non-(functions|ObjCMethods),
|
|
// in particular clang::BlockDecls and clang::VarDecls of function/block
|
|
// pointers, so we exclude them from these diagnostics.
|
|
//
|
|
// Furthermore, we do not diagnose clang::FunctionTemplateDecls here;
|
|
// instead, we need to diagnose calls to their specializations.
|
|
return;
|
|
|
|
if (const auto *methodDecl = dyn_cast<clang::CXXMethodDecl>(clangFunc)) {
|
|
ASSERT((!isa<clang::CXXDeductionGuideDecl, clang::CXXDestructorDecl>(
|
|
clangFunc)) &&
|
|
"C++ deduction guides and destructors can't be called in Swift");
|
|
|
|
if (methodDecl->isOverloadedOperator())
|
|
return; // Ownership attrs are not yet supported for overloaded operators
|
|
|
|
if (!methodDecl->isUserProvided())
|
|
return; // Implicit methods shouldn't be diagnosed because users can't
|
|
// annotate them
|
|
}
|
|
|
|
auto attrInfo = importer::ReturnOwnershipInfo(clangFunc);
|
|
if (attrInfo.hasRetainAttr())
|
|
return; // function is annotated, so it can't be missing
|
|
|
|
auto *recordDecl = getReturnTypeAsRecordDeclPtr(clangFunc);
|
|
if (!recordDecl)
|
|
return; // Not returning a pointer to a clang::RecordDecl
|
|
|
|
auto info =
|
|
evaluateOrDefault(Impl.SwiftContext.evaluator,
|
|
ForeignReferenceTypeInfoRequest({recordDecl}), {});
|
|
if (!info.isReference() || info.isImmortal())
|
|
return; // recordDecl is not a shared reference type
|
|
|
|
if (importer::matchSwiftAttr<bool>(
|
|
info.getDecl(), {{"returned_as_unretained_by_default", true}}))
|
|
return;
|
|
|
|
// If this returns OSObject or one of its subclasses, rely on libkern's
|
|
// ownership rules.
|
|
if (importer::getLibkernOwnershipOfReturnedFRT(clangFunc, ctx))
|
|
return;
|
|
|
|
// If we reached here, then we have a call to an unannotated, Clang-imported
|
|
// function that returns a pointer to a shared reference type that doesn't
|
|
// have a default return ownership convention. Emit diagnostics.
|
|
|
|
ctx.Diags.diagnose(callSiteLoc, diag::unannotated_cxx_func_returning_frt,
|
|
func);
|
|
|
|
Impl.diagnose(HeaderLoc{clangFunc->getLocation()},
|
|
diag::unannotated_cxx_func_returning_frt_suggestion, func);
|
|
}
|
|
|
|
void ClangImporter::checkCalledClangFunction(const ValueDecl *func,
|
|
SourceLoc callSiteLoc) {
|
|
diagnoseMissingReturnsRetained(Impl, func, callSiteLoc);
|
|
}
|
|
|
|
/// Whether \p record is the top-level libkern class named \p name.
|
|
static bool isLibkernClass(const clang::CXXRecordDecl *record, StringRef name) {
|
|
return record && record->getIdentifier() && record->getName() == name &&
|
|
record->getDeclContext()->getRedeclContext()->isTranslationUnit();
|
|
}
|
|
|
|
LibkernSubclass ClangImporter::Implementation::getLibkernSubclass(
|
|
const clang::CXXRecordDecl *record) {
|
|
if (!record || !record->hasDefinition())
|
|
return LibkernSubclass::None;
|
|
|
|
record = record->getDefinition();
|
|
auto it = libkernSubclasses.find(record);
|
|
if (it != libkernSubclasses.end())
|
|
return it->second;
|
|
|
|
// OSIterator is the strongest answer there is, so no base can change it.
|
|
if (isLibkernClass(record, "OSIterator")) {
|
|
libkernSubclasses[record] = LibkernSubclass::OSIterator;
|
|
return LibkernSubclass::OSIterator;
|
|
}
|
|
|
|
auto result = isLibkernClass(record, "OSObject") ? LibkernSubclass::OSObject
|
|
: LibkernSubclass::None;
|
|
|
|
for (const auto &base : record->bases()) {
|
|
auto baseSubclass =
|
|
getLibkernSubclass(base.getType()->getAsCXXRecordDecl());
|
|
result = std::max(result, baseSubclass);
|
|
if (result == LibkernSubclass::OSIterator)
|
|
break;
|
|
}
|
|
|
|
libkernSubclasses[record] = result;
|
|
return result;
|
|
}
|
|
|
|
std::optional<ResultConvention>
|
|
swift::importer::getOwnershipOfReturnedFRT(const clang::NamedDecl *decl,
|
|
ASTContext &ctx) {
|
|
|
|
auto attrInfo = importer::ReturnOwnershipInfo(decl);
|
|
if (attrInfo.hasReturnsUnretained)
|
|
return ResultConvention::Unowned;
|
|
|
|
if (attrInfo.hasReturnsRetained)
|
|
return ResultConvention::Owned;
|
|
|
|
if (auto *recordDecl = getReturnTypeAsRecordDeclPtr(decl)) {
|
|
if (auto convention = importer::matchSwiftAttr<ResultConvention>(
|
|
recordDecl,
|
|
{{"returned_as_unretained_by_default", ResultConvention::Unowned}}))
|
|
return convention.value();
|
|
|
|
if (auto convention = getLibkernOwnershipOfReturnedFRT(decl, ctx))
|
|
return convention.value();
|
|
|
|
// FIXME: this is only here to preserve legacy behavior; we really shouldn't
|
|
// consider returned_as_unretained_by_default annotations on anything
|
|
// other than the "canonical" FRT base (the one whose retain/release
|
|
// methods we use)
|
|
if (auto *cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl);
|
|
cxxRecordDecl && cxxRecordDecl->hasDefinition()) {
|
|
auto hasAttr = false;
|
|
cxxRecordDecl->forallBases([&hasAttr](auto *base) {
|
|
hasAttr =
|
|
hasAttr || importer::matchSwiftAttr<bool>(
|
|
base, {{"returned_as_unretained_by_default", true}});
|
|
return true;
|
|
});
|
|
if (hasAttr)
|
|
return ResultConvention::Unowned;
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<ResultConvention>
|
|
swift::importer::getLibkernOwnershipOfReturnedFRT(const clang::NamedDecl *decl,
|
|
ASTContext &ctx) {
|
|
if (!ctx.LangOpts.hasFeature(Feature::LibkernOwnershipConventions))
|
|
return std::nullopt;
|
|
|
|
auto *func = dyn_cast<clang::FunctionDecl>(decl);
|
|
if (!func)
|
|
return std::nullopt;
|
|
|
|
auto *recordDecl = getReturnTypeAsRecordDeclPtr(func);
|
|
if (!recordDecl)
|
|
return std::nullopt;
|
|
|
|
auto *importer = static_cast<ClangImporter *>(ctx.getClangModuleLoader());
|
|
auto libkernSubclass = importer->getLibkernSubclass(recordDecl);
|
|
if (libkernSubclass == LibkernSubclass::None)
|
|
return std::nullopt;
|
|
|
|
if (!func->getIdentifier())
|
|
return std::nullopt;
|
|
|
|
auto consumeSynthesizedPrefixes = [](StringRef &funcName) -> bool {
|
|
bool consumed = false;
|
|
while (funcName.consume_front("__synthesizedVirtualCall_") ||
|
|
funcName.consume_front("__synthesizedBaseCall_"))
|
|
consumed = true;
|
|
return consumed;
|
|
};
|
|
|
|
StringRef funcName = func->getName();
|
|
// If this is a synthesized thunk, consume the prefix we added.
|
|
if (func->isImplicit()) {
|
|
if (!consumeSynthesizedPrefixes(funcName))
|
|
return std::nullopt;
|
|
if (funcName.starts_with("operator"))
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Strip leading underscores.
|
|
funcName = funcName.substr(funcName.find_first_not_of('_'));
|
|
|
|
if (funcName == "safeMetaCast" || funcName == "requiredMetaCast" ||
|
|
funcName == "metaCast")
|
|
return ResultConvention::Unowned;
|
|
|
|
if (funcName.ends_with("Matching"))
|
|
return std::nullopt;
|
|
|
|
if ((!funcName.starts_with("get") && !funcName.starts_with("Get")) ||
|
|
libkernSubclass == LibkernSubclass::OSIterator)
|
|
return ResultConvention::Owned;
|
|
|
|
return ResultConvention::Unowned;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Foreign reference type retain/release operations
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Whether \p op is a valid retain/release operation for the foreign reference
|
|
/// type \p classDecl (\p isRetain selects which). If \p Impl is non-null, the
|
|
/// specific problem is diagnosed at \p loc; otherwise the check is silent (used
|
|
/// to disambiguate an overloaded operation name).
|
|
static bool checkRefCountOperation(const ClassDecl *classDecl, ValueDecl *op,
|
|
bool isRetain, StringRef name,
|
|
ClangImporter::Implementation *Impl,
|
|
HeaderLoc loc) {
|
|
auto diagnose = [&Impl, &loc](auto diag, auto &&...args) {
|
|
if (Impl)
|
|
Impl->diagnose(loc, diag, std::forward<decltype(args)>(args)...);
|
|
};
|
|
|
|
auto *fn = dyn_cast<FuncDecl>(op);
|
|
if (!fn) {
|
|
diagnose(diag::foreign_reference_types_retain_release_not_a_function_decl,
|
|
!isRetain, name);
|
|
return false;
|
|
}
|
|
|
|
if (fn->isStatic()) {
|
|
diagnose(
|
|
diag::foreign_reference_types_retain_release_not_an_instance_function,
|
|
!isRetain, name);
|
|
return false;
|
|
}
|
|
|
|
// Instance operations take no parameters; free operations take one.
|
|
if (fn->getParameters()->size() != (fn->isInstanceMember() ? 0 : 1)) {
|
|
diagnose(diag::foreign_reference_retain_release_param_type, !isRetain, name,
|
|
classDecl->getNameStr());
|
|
return false;
|
|
}
|
|
|
|
Type paramType;
|
|
NominalTypeDecl *paramDecl;
|
|
if (fn->isInstanceMember()) {
|
|
paramDecl = cast<NominalTypeDecl>(fn->getParent());
|
|
paramType = paramDecl->getDeclaredInterfaceType();
|
|
} else {
|
|
paramType = fn->getParameters()
|
|
->get(0)
|
|
->getInterfaceType()
|
|
->lookThroughSingleOptionalType();
|
|
paramDecl = paramType->getAnyNominal();
|
|
}
|
|
|
|
// The return type must be void or an integer
|
|
auto resultTy = fn->getResultInterfaceType();
|
|
bool validReturn =
|
|
resultTy->isVoid() || resultTy->isUInt() || resultTy->isUInt8() ||
|
|
resultTy->isUInt16() || resultTy->isUInt32() || resultTy->isUInt64() ||
|
|
resultTy->isInt() || resultTy->isInt8() || resultTy->isInt16() ||
|
|
resultTy->isInt32() || resultTy->isInt64();
|
|
// A retain may also return the parameter (self) type
|
|
if (isRetain && !validReturn)
|
|
validReturn = resultTy->lookThroughSingleOptionalType()->isEqual(paramType);
|
|
if (!validReturn) {
|
|
diagnose(diag::foreign_reference_retain_release_return_type, !isRetain,
|
|
name);
|
|
return false;
|
|
}
|
|
|
|
// The operation must take the FRT as its parameter
|
|
if (paramDecl != classDecl) {
|
|
auto *cxxDecl = dyn_cast<clang::CXXRecordDecl>(classDecl->getClangDecl());
|
|
auto *paramCxxDecl = dyn_cast_or_null<clang::CXXRecordDecl>(
|
|
paramDecl ? paramDecl->getClangDecl() : nullptr);
|
|
if (cxxDecl && paramCxxDecl && cxxDecl->isDerivedFrom(paramCxxDecl)) {
|
|
// The parameter may also be one of the FRT's bases
|
|
} else {
|
|
diagnose(diag::foreign_reference_retain_release_param_type, !isRetain,
|
|
name, classDecl->getNameStr());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Resolve and diagnose the retain (\p isRetain) or release operation for the
|
|
/// foreign reference type \p classDecl, based on the annotation read from
|
|
/// \p annotatedDecl. Returns null on any (semantic) error.
|
|
///
|
|
/// This performs semantic checking only: the caller guarantees, via
|
|
/// \c RetainReleaseInfo::checkShape, that \p annotatedDecl has exactly one
|
|
/// \c retain: and one \c release: attribute, and (via the FRT request) that
|
|
/// neither is immortal.
|
|
static ValueDecl *
|
|
resolveRefCountOperation(const ClassDecl *classDecl,
|
|
const ClassDecl *annotatedDecl, StringRef name,
|
|
bool isRetain, ClangImporter::Implementation &Impl) {
|
|
auto *record = cast<clang::RecordDecl>(classDecl->getClangDecl());
|
|
HeaderLoc loc(record->getLocation());
|
|
|
|
ASSERT(!name.empty() &&
|
|
"structural check should guarantee a retain/release attr");
|
|
ASSERT(name != "immortal" && "immortal FRTs are handled before resolution");
|
|
|
|
auto results = importer::getValueDeclsForName(
|
|
const_cast<ClassDecl *>(annotatedDecl), name);
|
|
|
|
// Pick the operation, silently disambiguating an overloaded name.
|
|
ValueDecl *op = nullptr;
|
|
if (results.size() == 1) {
|
|
op = results.front();
|
|
} else {
|
|
for (auto *candidate : results) {
|
|
if (!checkRefCountOperation(classDecl, candidate, isRetain, name,
|
|
/*Impl=*/nullptr, loc))
|
|
continue;
|
|
if (op) {
|
|
Impl.diagnose(loc,
|
|
diag::too_many_reference_type_retain_release_operations,
|
|
!isRetain, name, record);
|
|
return nullptr;
|
|
}
|
|
op = candidate;
|
|
}
|
|
}
|
|
|
|
if (!op) {
|
|
Impl.diagnose(loc, diag::foreign_reference_types_cannot_find_retain_release,
|
|
!isRetain, name, record);
|
|
if (!Impl.SwiftContext.LangOpts.DisableExperimentalClangImporterDiagnostics)
|
|
Impl.diagnoseTopLevelValue(
|
|
DeclName(Impl.SwiftContext.getIdentifier(name)));
|
|
return nullptr;
|
|
}
|
|
|
|
checkRefCountOperation(classDecl, op, isRetain, name, &Impl, loc);
|
|
return op;
|
|
}
|
|
|
|
/// Synthesize an inline C++ method on \p clangDecl that forwards to \p baseFn.
|
|
/// Returns the synthesized method, or nullptr on failure.
|
|
///
|
|
/// Unlike SwiftDeclSynthesizer::synthesizeCXXForwardingMethod, this function
|
|
/// does not use Sema::SynthesizedFunctionScope (which is not re-entrant), so
|
|
/// it is safe to use during importing.
|
|
static const clang::CXXMethodDecl *
|
|
synthesizeForwardingRefCountMethod(clang::CXXRecordDecl *clangDecl,
|
|
const clang::FunctionDecl *baseFn,
|
|
ClangImporter::Implementation &Impl) {
|
|
if (!baseFn)
|
|
return nullptr;
|
|
auto &clangCtx = Impl.getClangASTContext();
|
|
auto &clangSema = Impl.getClangSema();
|
|
|
|
clang::QualType methodType = clangCtx.getFunctionType(
|
|
clangCtx.VoidTy, {}, clang::FunctionProtoType::ExtProtoInfo{});
|
|
|
|
auto loc = baseFn->getLocation();
|
|
auto &ident = clangCtx.Idents.get("__synthesized_lifetimeAccessor_" +
|
|
baseFn->getNameAsString());
|
|
clang::DeclarationName methodName(&ident);
|
|
auto method = clang::CXXMethodDecl::Create(
|
|
clangCtx, clangDecl, baseFn->getSourceRange().getBegin(),
|
|
clang::DeclarationNameInfo(methodName, clang::SourceLocation()),
|
|
methodType, clangCtx.getTrivialTypeSourceInfo(methodType), clang::SC_None,
|
|
/*usesFPIntrin=*/false, /*isInline=*/true,
|
|
clang::ConstexprSpecKind::Unspecified, baseFn->getSourceRange().getEnd());
|
|
method->setImplicit();
|
|
method->setImplicitlyInline();
|
|
method->setAccess(clang::AccessSpecifier::AS_public);
|
|
method->addAttr(clang::NoDebugAttr::CreateImplicit(clangCtx));
|
|
|
|
clang::Expr *argExpr =
|
|
clang::CXXThisExpr::Create(clangCtx, clang::SourceLocation(),
|
|
method->getThisType(), /*IsImplicit=*/false);
|
|
|
|
if (auto calledMethod = dyn_cast<clang::CXXMethodDecl>(baseFn)) {
|
|
if (calledMethod->isStatic())
|
|
return nullptr;
|
|
auto memberExpr = clangSema.BuildMemberExpr(
|
|
argExpr, /*isArrow=*/true, loc, clang::NestedNameSpecifierLoc(),
|
|
clang::SourceLocation(),
|
|
const_cast<clang::CXXMethodDecl *>(calledMethod),
|
|
clang::DeclAccessPair::make(
|
|
const_cast<clang::CXXMethodDecl *>(calledMethod), clang::AS_public),
|
|
/*HadMultipleCandidates=*/false, calledMethod->getNameInfo(),
|
|
clangCtx.BoundMemberTy, clang::VK_PRValue, clang::OK_Ordinary);
|
|
auto memberCall =
|
|
clangSema.BuildCallExpr(nullptr, memberExpr, clang::SourceLocation(),
|
|
{}, clang::SourceLocation());
|
|
ASSERT(memberCall.isUsable());
|
|
method->setBody(clang::CompoundStmt::Create(
|
|
clangCtx, {memberCall.get()}, clang::FPOptionsOverride(), loc, loc));
|
|
} else {
|
|
clang::Expr *fnExpr = clang::DeclRefExpr::Create(
|
|
clangCtx, clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
|
|
const_cast<clang::FunctionDecl *>(baseFn),
|
|
/*RefersToEnclosingVariableOrCapture=*/false, loc, baseFn->getType(),
|
|
clang::VK_LValue);
|
|
auto call =
|
|
clangSema.BuildCallExpr(nullptr, fnExpr, clang::SourceLocation(),
|
|
{argExpr}, clang::SourceLocation());
|
|
method->setBody(clang::CompoundStmt::Create(
|
|
clangCtx, {call.get()}, clang::FPOptionsOverride(), loc, loc));
|
|
}
|
|
return method;
|
|
}
|
|
|
|
/// Synthesize forwarding retain/release methods on the derived FRT \p decl that
|
|
/// performs the derived-to-base adjustment on behalf of Swift. This function
|
|
/// assumes both the derived and base types have reachable definitions.
|
|
static std::pair<const clang::CXXMethodDecl *, const clang::CXXMethodDecl *>
|
|
synthesizeInheritedRefCountOperations(ClassDecl *decl,
|
|
clang::CXXRecordDecl *clangDecl,
|
|
const clang::FunctionDecl *baseRetainFn,
|
|
const clang::FunctionDecl *baseReleaseFn,
|
|
ClangImporter::Implementation &Impl) {
|
|
auto &context = Impl.SwiftContext;
|
|
auto &clangCtx = Impl.getClangASTContext();
|
|
|
|
auto synthesizedRetain =
|
|
synthesizeForwardingRefCountMethod(clangDecl, baseRetainFn, Impl);
|
|
auto synthesizedRelease =
|
|
synthesizeForwardingRefCountMethod(clangDecl, baseReleaseFn, Impl);
|
|
if (!synthesizedRetain || !synthesizedRelease)
|
|
return {nullptr, nullptr};
|
|
|
|
// Add attributes to class.
|
|
clangDecl->addAttr(clang::SwiftAttrAttr::Create(
|
|
clangCtx,
|
|
context.AllocateCopy("retain:." + synthesizedRetain->getNameAsString())));
|
|
clangDecl->addAttr(clang::SwiftAttrAttr::Create(
|
|
clangCtx, context.AllocateCopy("release:." +
|
|
synthesizedRelease->getNameAsString())));
|
|
|
|
// Update the Swift type
|
|
auto importRefCountOp = [&](const clang::CXXMethodDecl *op) {
|
|
auto importedOp =
|
|
cast<ValueDecl>(context.getClangModuleLoader()->importDeclDirectly(op));
|
|
Impl.markMemberSynthesizedPerType(importedOp);
|
|
decl->addMember(importedOp);
|
|
decl->addMemberToLookupTable(importedOp);
|
|
};
|
|
importRefCountOp(synthesizedRetain);
|
|
importRefCountOp(synthesizedRelease);
|
|
|
|
return {synthesizedRetain, synthesizedRelease};
|
|
}
|
|
|
|
void importer::checkRetainReleaseFunctions(
|
|
ClassDecl *classDecl, ClangImporter::Implementation &Impl) {
|
|
auto *recordDecl = cast<clang::RecordDecl>(classDecl->getClangDecl());
|
|
auto *cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl);
|
|
auto frtInfo =
|
|
evaluateOrDefault(Impl.SwiftContext.evaluator,
|
|
ForeignReferenceTypeInfoRequest({recordDecl}), {});
|
|
|
|
// Where classDecl inherits its FRT annotations. When this is null, classDecl
|
|
// (i.e., recordDecl) is directly annotated itself (i.e., does not inherit).
|
|
auto *baseCxxRecordDecl =
|
|
dyn_cast_or_null<clang::CXXRecordDecl>(frtInfo.getDecl());
|
|
|
|
// Imported class of whatever was annotated.
|
|
// FIXME: should not be necessary to keep track of this, it is confusing.
|
|
const ClassDecl *annotatedClassDecl = classDecl;
|
|
|
|
// Compare canonical decls: frtInfo may name a different redeclaration of
|
|
// recordDecl, which is not inheritance.
|
|
if (cxxRecordDecl && baseCxxRecordDecl &&
|
|
baseCxxRecordDecl->getCanonicalDecl() != cxxRecordDecl->getCanonicalDecl())
|
|
annotatedClassDecl = cast<ClassDecl>(
|
|
Impl.importDecl(baseCxxRecordDecl, Impl.CurrentVersion));
|
|
else
|
|
baseCxxRecordDecl = nullptr;
|
|
|
|
// Check that the record carrying the retain:/release: attributes has exactly
|
|
// one of each. Only diagnose when those attributes are on this type, to avoid
|
|
// repeating diagnostics once per derived type.
|
|
//
|
|
// frtInfo names the declaration the annotation was found on, which for a
|
|
// forward-declared type need not be the one classDecl was imported from.
|
|
const clang::RecordDecl *annotatedDecl =
|
|
baseCxxRecordDecl ? baseCxxRecordDecl
|
|
: (frtInfo.getDecl() ? frtInfo.getDecl() : recordDecl);
|
|
|
|
auto rrInfo = RetainReleaseInfo(annotatedDecl);
|
|
if (!rrInfo.checkShape(/*Impl=*/baseCxxRecordDecl ? nullptr : &Impl))
|
|
return;
|
|
|
|
if (!frtInfo.isValid())
|
|
// If this FRT is invalid for any other reason, do not resolve or synthesize
|
|
// retain/release operations and just bail.
|
|
return;
|
|
|
|
// Immortal FRTs have no custom reference counting, so there is nothing to
|
|
// resolve or synthesize.
|
|
if (frtInfo.isImmortal()) {
|
|
Impl.setForeignReferenceTypeOperations(recordDecl, /*retain=*/nullptr,
|
|
/*release=*/nullptr);
|
|
return;
|
|
}
|
|
|
|
auto isReachable = [&Impl](const clang::CXXRecordDecl *Decl) -> bool {
|
|
if (!Decl->getDefinition())
|
|
return false;
|
|
clang::Sema::SFINAETrap trap(Impl.getClangSema());
|
|
return Impl.getClangSema().hasReachableDefinition(
|
|
const_cast<clang::CXXRecordDecl *>(Decl));
|
|
};
|
|
|
|
// An FRT by inheritance must have a reachable definition; check that here.
|
|
// (A directly-annotated FRT may be forward-declared.)
|
|
if (baseCxxRecordDecl &&
|
|
(!isReachable(cxxRecordDecl) || !isReachable(baseCxxRecordDecl))) {
|
|
Impl.diagnose(HeaderLoc(recordDecl->getLocation()),
|
|
diag::foreign_reference_type_unreachable,
|
|
classDecl->getNameStr());
|
|
return;
|
|
}
|
|
|
|
// Resolve (and semantically diagnose) the retain/release operations.
|
|
ValueDecl *retainOp = resolveRefCountOperation(classDecl, annotatedClassDecl,
|
|
rrInfo.getRetain(),
|
|
/*isRetain=*/true, Impl);
|
|
ValueDecl *releaseOp = resolveRefCountOperation(classDecl, annotatedClassDecl,
|
|
rrInfo.getRelease(),
|
|
/*isRetain=*/false, Impl);
|
|
|
|
const clang::FunctionDecl *retainFn = nullptr;
|
|
const clang::FunctionDecl *releaseFn = nullptr;
|
|
|
|
// Look through a cloned (inherited) member to the original base method,
|
|
// without forcing any synthesis (getCalledBaseCxxMethod would call
|
|
// getBody()).
|
|
auto baseClangFn = [&](ValueDecl *op, bool *cloned = nullptr) {
|
|
if (auto *original = Impl.getOriginalForClonedMember(op)) {
|
|
op = original;
|
|
if (cloned)
|
|
*cloned = true;
|
|
} else {
|
|
if (cloned)
|
|
*cloned = false;
|
|
}
|
|
return dyn_cast_or_null<clang::FunctionDecl>(op->getClangDecl());
|
|
};
|
|
|
|
if (baseCxxRecordDecl && retainOp && releaseOp) {
|
|
// FRT annotation was inherited: always synthesize forwarding methods that
|
|
// call the base's operations, performing the derived-to-base adjustment.
|
|
std::tie(retainFn, releaseFn) = synthesizeInheritedRefCountOperations(
|
|
classDecl, const_cast<clang::CXXRecordDecl *>(cxxRecordDecl),
|
|
baseClangFn(retainOp), baseClangFn(releaseOp), Impl);
|
|
} else if (!baseCxxRecordDecl && retainOp && releaseOp) {
|
|
// FRT annotation appears directly on clangDecl/cxxDecl.
|
|
bool retainCloned = false, releaseCloned = false;
|
|
retainFn = baseClangFn(retainOp, &retainCloned);
|
|
releaseFn = baseClangFn(releaseOp, &releaseCloned);
|
|
|
|
// Even if cxxDecl was itself directly annotated, its retain/release may
|
|
// still be inherited from some (reachable) base. If so, synthesize
|
|
// forwarding retain/release methods as well.
|
|
if (cxxRecordDecl && (retainCloned || releaseCloned) &&
|
|
isReachable(cxxRecordDecl)) {
|
|
auto *cxxDeclMut = const_cast<clang::CXXRecordDecl *>(cxxRecordDecl);
|
|
if (retainCloned) {
|
|
if (auto *retainThunk =
|
|
synthesizeForwardingRefCountMethod(cxxDeclMut, retainFn, Impl))
|
|
retainFn = retainThunk;
|
|
}
|
|
if (releaseCloned) {
|
|
if (auto *releaseThunk =
|
|
synthesizeForwardingRefCountMethod(cxxDeclMut, releaseFn, Impl))
|
|
releaseFn = releaseThunk;
|
|
}
|
|
}
|
|
}
|
|
Impl.setForeignReferenceTypeOperations(recordDecl, retainFn, releaseFn);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Unsafe projection ("__fooUnsafe") analysis
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Is \a type a pointer or reference to a foreign reference type?
|
|
static bool clangTypeIsForeignReference(const clang::QualType type,
|
|
ASTContext &ctx) {
|
|
if (!type->isPointerOrReferenceType())
|
|
return false;
|
|
auto *pointee = type->getPointeeType().getCanonicalType()->getAsRecordDecl();
|
|
if (!pointee)
|
|
return false;
|
|
auto info = evaluateOrDefault(ctx.evaluator,
|
|
ForeignReferenceTypeInfoRequest({pointee}), {});
|
|
return info.isReference();
|
|
}
|
|
|
|
static bool hasCustomCopyOrMoveConstructor(const clang::CXXRecordDecl *decl) {
|
|
return decl->hasUserDeclaredCopyConstructor() ||
|
|
decl->hasUserDeclaredMoveConstructor();
|
|
}
|
|
|
|
bool importer::isSwiftClassType(const clang::CXXRecordDecl *decl) {
|
|
// Swift type must be annotated with external_source_symbol attribute.
|
|
auto essAttr = decl->getAttr<clang::ExternalSourceSymbolAttr>();
|
|
if (!essAttr || essAttr->getLanguage() != "Swift" ||
|
|
essAttr->getDefinedIn().empty() || essAttr->getUSR().empty())
|
|
return false;
|
|
|
|
// Ensure that the baseclass is swift::RefCountedClass.
|
|
auto baseDecl = decl->getDefinition();
|
|
if (!baseDecl)
|
|
return false;
|
|
do {
|
|
if (baseDecl->getNumBases() != 1)
|
|
return false;
|
|
auto baseClassSpecifier = *baseDecl->bases_begin();
|
|
auto Ty = baseClassSpecifier.getType();
|
|
auto nextBaseDecl = Ty->getAsCXXRecordDecl();
|
|
if (!nextBaseDecl)
|
|
return false;
|
|
baseDecl = nextBaseDecl->getDefinition();
|
|
if (!baseDecl)
|
|
return false;
|
|
} while (baseDecl->getName() != "RefCountedClass");
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool anySubobjectsSelfContained(const clang::CXXRecordDecl *decl) {
|
|
// std::pair and std::tuple might have copy and move constructors, or base
|
|
// classes with copy and move constructors, but they are not self-contained
|
|
// types, e.g. `std::pair<UnsafeType, T>`.
|
|
if (decl->isInStdNamespace() &&
|
|
(decl->getName() == "pair" || decl->getName() == "tuple"))
|
|
return false;
|
|
|
|
if (!decl->getDefinition())
|
|
return false;
|
|
|
|
if (hasCustomCopyOrMoveConstructor(decl) || importer::hasOwnedValueAttr(decl))
|
|
return true;
|
|
|
|
auto checkType = [](clang::QualType t) {
|
|
// N.B. Use Type::getAsCXXRecordDecl() rather than reaching for
|
|
// RecordType::getDecl(): the latter can be any declaration of the record,
|
|
// and Swift attributes are not propagated across redeclarations.
|
|
if (auto *cxxRecord = t->getAsCXXRecordDecl())
|
|
return anySubobjectsSelfContained(cxxRecord);
|
|
|
|
return false;
|
|
};
|
|
|
|
return anySubobjectTypeSatisfies(decl, checkType);
|
|
}
|
|
|
|
std::optional<importer::CxxUnsafetyReason>
|
|
importer::shouldRenameCXXMethodAsUnsafe(const clang::CXXMethodDecl *method,
|
|
ASTContext &ctx) {
|
|
// Returning the reason rather than reporting it through an out-parameter
|
|
// keeps the verdict and its explanation inseparable.
|
|
auto safe = []() -> std::optional<CxxUnsafetyReason> { return std::nullopt; };
|
|
auto unsafe = [](CxxUnsafetyReason reason)
|
|
-> std::optional<CxxUnsafetyReason> { return reason; };
|
|
|
|
// The user explicitly explicitly acknowledged this method's unsafety
|
|
// and asked us to import it as is anyway. No renaming needed.
|
|
if (hasUnsafeAPIAttr(method))
|
|
return safe();
|
|
|
|
// If it's a static method, it cannot project anything. It's fine.
|
|
if (method->isOverloadedOperator() || method->isStatic() ||
|
|
isa<clang::CXXConstructorDecl>(method))
|
|
return safe();
|
|
|
|
// begin and end methods likely return an iterator, so they're unsafe.
|
|
// This is required so that automatic the conformance to RAC works properly.
|
|
if (method->getNameAsString() == "begin" ||
|
|
method->getNameAsString() == "end")
|
|
return unsafe(CxxUnsafetyReason::IteratorFromBeginEnd);
|
|
|
|
// The user vouched for this method's safety. Without the feature, such a
|
|
// method is still renamed, for source compatibility. This doesn't apply to
|
|
// begin and end, whose renamed spellings the conformance to RAC relies on.
|
|
if (ctx.LangOpts.hasFeature(Feature::ImportUnsafeCxxMethodsAsAlwaysUnsafe) &&
|
|
hasSwiftAttribute(method, {"safe"}))
|
|
return safe();
|
|
|
|
// A method template returning one of its own template parameters returns
|
|
// whatever type the caller picked, not a projection of 'this'.
|
|
if (auto *primary = method->getPrimaryTemplate()) {
|
|
auto *parmType = primary->getTemplatedDecl()
|
|
->getReturnType()
|
|
->getAs<clang::TemplateTypeParmType>();
|
|
if (parmType &&
|
|
parmType->getDepth() == primary->getTemplateParameters()->getDepth())
|
|
return safe();
|
|
}
|
|
|
|
if (clangTypeIsForeignReference(method->getReturnType(), ctx))
|
|
return safe();
|
|
|
|
auto *parentDecl = method->getParent();
|
|
auto parentQualType = method->getASTContext().getCanonicalTagType(parentDecl);
|
|
|
|
bool parentIsSelfContained =
|
|
!clangTypeIsForeignReference(parentQualType, ctx) &&
|
|
anySubobjectsSelfContained(parentDecl);
|
|
|
|
// If it returns a pointer or reference from an owned parent, that's a
|
|
// projection (unsafe).
|
|
if (method->getReturnType()->isPointerType() ||
|
|
method->getReturnType()->isReferenceType())
|
|
return parentIsSelfContained ? unsafe(CxxUnsafetyReason::PointerProjection)
|
|
: safe();
|
|
|
|
// Check if it's one of the known unsafe methods we currently
|
|
// mark as safe by default.
|
|
if (isUnsafeStdMethod(method))
|
|
return unsafe(CxxUnsafetyReason::KnownUnsafeStdMethod);
|
|
|
|
// Try to figure out the semantics of the return type. If it's a
|
|
// pointer/iterator, it's unsafe.
|
|
if (auto *cxxRecordReturnType =
|
|
method->getReturnType()->getAsCXXRecordDecl()) {
|
|
if (isSwiftClassType(cxxRecordReturnType))
|
|
return safe();
|
|
|
|
if (hasIteratorAPIAttr(cxxRecordReturnType) ||
|
|
hasIteratorCategory(cxxRecordReturnType))
|
|
return unsafe(CxxUnsafetyReason::ReturnsIterator);
|
|
|
|
// Mark this as safe to help our diganostics down the road.
|
|
if (!cxxRecordReturnType->getDefinition()) {
|
|
return safe();
|
|
}
|
|
|
|
// A projection of a view type (such as a string_view) from a self
|
|
// contained parent is a proejction (unsafe).
|
|
if (!anySubobjectsSelfContained(cxxRecordReturnType) &&
|
|
isViewType(cxxRecordReturnType)) {
|
|
return parentIsSelfContained ? unsafe(CxxUnsafetyReason::ViewProjection)
|
|
: safe();
|
|
}
|
|
}
|
|
|
|
// Otherwise, it's safe.
|
|
return safe();
|
|
}
|
|
|
|
/// Whether a note at \p loc would land in a system header. Such a note names
|
|
/// something the user cannot annotate (a libc++ implementation detail, say), so
|
|
/// it is dropped in favour of explaining a type they control.
|
|
static bool isInSystemHeader(const clang::Decl *decl) {
|
|
return decl->getASTContext().getSourceManager().isInSystemHeader(
|
|
decl->getLocation());
|
|
}
|
|
|
|
/// Emit the note explaining \p explanation at \p loc.
|
|
static void
|
|
diagnoseUnsafetyReason(ClangImporter::Implementation &Impl, HeaderLoc loc,
|
|
importer::CxxUnsafetyExplanation explanation) {
|
|
bool named = explanation.culprit;
|
|
StringRef culprit = named ? explanation.culprit->getName() : StringRef();
|
|
auto note = [&](auto &&...args) { Impl.diagnose(loc, args...); };
|
|
|
|
switch (explanation.reason) {
|
|
case importer::CxxUnsafetyReason::IteratorFromBeginEnd:
|
|
return note(diag::cxx_unsafe_iterator_from_begin_end);
|
|
case importer::CxxUnsafetyReason::PointerProjection:
|
|
return note(diag::cxx_unsafe_pointer_projection);
|
|
case importer::CxxUnsafetyReason::KnownUnsafeStdMethod:
|
|
return note(diag::cxx_unsafe_known_std_method);
|
|
case importer::CxxUnsafetyReason::ReturnsIterator:
|
|
return note(diag::cxx_unsafe_returns_iterator);
|
|
case importer::CxxUnsafetyReason::ViewProjection:
|
|
return note(diag::cxx_unsafe_view_projection);
|
|
|
|
case importer::CxxUnsafetyReason::UnsafeField:
|
|
return note(diag::cxx_unsafe_field, named, culprit);
|
|
case importer::CxxUnsafetyReason::UnsafeTemplateArgument:
|
|
return note(diag::cxx_unsafe_template_argument, named,
|
|
culprit);
|
|
case importer::CxxUnsafetyReason::ExplicitAnnotation:
|
|
return note(diag::cxx_unsafe_explicit_annotation, named,
|
|
culprit);
|
|
case importer::CxxUnsafetyReason::IndirectView:
|
|
return note(diag::cxx_unsafe_indirect_view);
|
|
}
|
|
llvm_unreachable("covered switch");
|
|
}
|
|
|
|
/// Emit the note explaining \p unknown at \p loc.
|
|
static void
|
|
diagnoseUnknownEscapability(ClangImporter::Implementation &Impl, HeaderLoc loc,
|
|
importer::CxxUnknownEscapabilityReason reason,
|
|
const clang::NamedDecl *culpritDecl) {
|
|
bool named = culpritDecl;
|
|
StringRef culprit = named ? culpritDecl->getName() : StringRef();
|
|
auto note = [&](auto &&...args) { Impl.diagnose(loc, args...); };
|
|
switch (reason) {
|
|
case importer::CxxUnknownEscapabilityReason::ConditionalArgument:
|
|
return note(diag::cxx_unknown_escapability_conditional_argument, named,
|
|
culprit);
|
|
case importer::CxxUnknownEscapabilityReason::CannotDeriveFromMembers:
|
|
return note(diag::cxx_unknown_escapability_cannot_derive);
|
|
case importer::CxxUnknownEscapabilityReason::NonEscapableMember:
|
|
// Named only when the member belongs to the type being explained, as below.
|
|
return note(diag::cxx_unknown_escapability_nonescapable_member, named,
|
|
culprit);
|
|
case importer::CxxUnknownEscapabilityReason::Pointer:
|
|
// Named only when the member belongs to the type being explained; the
|
|
// traversal is flattened, so otherwise the caller follows the chain to the
|
|
// record that owns it.
|
|
return note(diag::cxx_unknown_escapability_pointer, named,
|
|
culprit);
|
|
}
|
|
llvm_unreachable("covered switch");
|
|
}
|
|
|
|
void ClangImporter::diagnoseCxxUnsafetyReason(const ValueDecl *decl, Type type,
|
|
SourceLoc useLoc) {
|
|
if (decl)
|
|
if (auto *original = Impl.getOriginalForClonedMember(decl))
|
|
decl = original;
|
|
if (auto *func = dyn_cast_or_null<FuncDecl>(decl))
|
|
if (auto *original = Impl.getOriginalForVirtualThunk(func))
|
|
decl = original;
|
|
|
|
// A declaration: explain which rule made this method unsafe.
|
|
if (decl && decl->hasClangNode()) {
|
|
auto *clangDecl = decl->getClangNode().getAsDecl();
|
|
|
|
// As for types below, a note in a system header names something the user
|
|
// cannot annotate -- 'begin' on std::vector, say -- so it is dropped.
|
|
if (isInSystemHeader(clangDecl))
|
|
return;
|
|
|
|
// An annotation written in the header speaks for itself, whatever kind of
|
|
// declaration carries it.
|
|
if (auto *named = dyn_cast_or_null<clang::NamedDecl>(clangDecl))
|
|
if (importer::hasSwiftAttribute(named, {"unsafe", "unsafe(always)"}))
|
|
return;
|
|
|
|
// Lifetime inference records its reason when it adds the attribute, since
|
|
// inferred annotations might make it impossible to reconstruct the same
|
|
// decision later.
|
|
auto recorded = Impl.LifetimeUnsafetyReasons.find(decl);
|
|
if (recorded != Impl.LifetimeUnsafetyReasons.end()) {
|
|
Impl.diagnose(HeaderLoc(clangDecl->getLocation(), useLoc),
|
|
recorded->second);
|
|
return;
|
|
}
|
|
|
|
if (auto *method = dyn_cast_or_null<clang::CXXMethodDecl>(clangDecl)) {
|
|
if (auto reason =
|
|
importer::shouldRenameCXXMethodAsUnsafe(method,
|
|
Impl.SwiftContext)) {
|
|
diagnoseUnsafetyReason(Impl,
|
|
HeaderLoc(method->getLocation(), useLoc),
|
|
{*reason, nullptr});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// A type: explain which part of the record is responsible.
|
|
if (!type)
|
|
return;
|
|
auto *nominal = type->getAnyNominal();
|
|
if (!nominal || !nominal->hasClangNode())
|
|
return;
|
|
auto *recordDecl =
|
|
dyn_cast_or_null<clang::RecordDecl>(nominal->getClangNode().getAsDecl());
|
|
if (!recordDecl)
|
|
return;
|
|
if (importer::hasSwiftAttributeOnAnyRedecl(recordDecl,
|
|
{"unsafe", "unsafe(always)"}))
|
|
return;
|
|
|
|
// Unknown escapability is the root cause when it applies, and it is what the
|
|
// user can act on. The safety walk falls through it to the fields, so without
|
|
// this a conditionally-escapable type such as std::shared_ptr would be
|
|
// explained by whichever raw pointer its implementation happens to hold.
|
|
//
|
|
// Follow the chain of blame to its end, so the last note lands on the type
|
|
// whose annotation would settle the question. The visited set both prevents
|
|
// cycles and bounds the walk.
|
|
llvm::SmallPtrSet<const clang::RecordDecl *, 4> visited;
|
|
bool explained = false;
|
|
for (const clang::RecordDecl *current = recordDecl;
|
|
current && visited.insert(current).second;) {
|
|
auto unknown =
|
|
importer::explainUnknownEscapability(current, Impl.SwiftContext);
|
|
if (!unknown)
|
|
break;
|
|
explained = true;
|
|
|
|
// We can have a chain of reasons why a type is considered unsafe. Follow
|
|
// the chain to the record that owns the culprit and let it explain itself,
|
|
// rather than attributing its member here.
|
|
const clang::RecordDecl *next = nullptr;
|
|
if (unknown->owner &&
|
|
unknown->owner->getCanonicalDecl() != current->getCanonicalDecl())
|
|
next = unknown->owner;
|
|
else if (auto *asRecord =
|
|
dyn_cast_or_null<clang::RecordDecl>(unknown->culprit);
|
|
asRecord && asRecord != current)
|
|
next = asRecord;
|
|
|
|
if (!isInSystemHeader(current)) {
|
|
// Naming the record the explanation moves to, not its member: the next
|
|
// note describes that record.
|
|
HeaderLoc loc(current->getLocation(), useLoc);
|
|
if (next)
|
|
diagnoseUnknownEscapability(
|
|
Impl, loc,
|
|
importer::CxxUnknownEscapabilityReason::ConditionalArgument, next);
|
|
else
|
|
diagnoseUnknownEscapability(Impl, loc, unknown->reason,
|
|
unknown->culprit);
|
|
}
|
|
|
|
current = next;
|
|
}
|
|
if (explained)
|
|
return;
|
|
|
|
if (isInSystemHeader(recordDecl))
|
|
return;
|
|
|
|
if (auto unsafe = importer::explainRecordUnsafety(
|
|
recordDecl, Impl.SwiftContext, isa<ClassDecl>(nominal)))
|
|
diagnoseUnsafetyReason(Impl, HeaderLoc(recordDecl->getLocation(), useLoc),
|
|
*unsafe);
|
|
}
|