diff --git a/include/swift/AST/Evaluator.h b/include/swift/AST/Evaluator.h index 5d7e47f29e65c..8741b02083a66 100644 --- a/include/swift/AST/Evaluator.h +++ b/include/swift/AST/Evaluator.h @@ -304,6 +304,10 @@ class Evaluator { void clearCache() { cache.clear(); } /// Is the given request, or an equivalent, currently being evaluated? + /// + /// WARN: do not rely on this function to avoid request cycles. Doing so can + /// lead to bugs that are very difficult to debug, especially when request + /// caching is involved. template bool hasActiveRequest(const Request &request) const { return activeRequests.count(ActiveRequest(request)); diff --git a/include/swift/ClangImporter/ClangImporterRequests.h b/include/swift/ClangImporter/ClangImporterRequests.h index fe83d0fae0405..d435571c12272 100644 --- a/include/swift/ClangImporter/ClangImporterRequests.h +++ b/include/swift/ClangImporter/ClangImporterRequests.h @@ -624,37 +624,37 @@ class CxxValueSemantics void simple_display(llvm::raw_ostream &out, CxxValueSemanticsDescriptor desc); SourceLoc extractNearestSourceLoc(CxxValueSemanticsDescriptor desc); -struct CxxDeclExplicitSafetyDescriptor final { +struct ClangDeclExplicitSafetyDescriptor final { const clang::Decl *decl; bool isClass; - CxxDeclExplicitSafetyDescriptor(const clang::Decl *decl, bool isClass) + ClangDeclExplicitSafetyDescriptor(const clang::Decl *decl, bool isClass) : decl(decl), isClass(isClass) {} friend llvm::hash_code - hash_value(const CxxDeclExplicitSafetyDescriptor &desc) { + hash_value(const ClangDeclExplicitSafetyDescriptor &desc) { return llvm::hash_combine(desc.decl, desc.isClass); } - friend bool operator==(const CxxDeclExplicitSafetyDescriptor &lhs, - const CxxDeclExplicitSafetyDescriptor &rhs) { + friend bool operator==(const ClangDeclExplicitSafetyDescriptor &lhs, + const ClangDeclExplicitSafetyDescriptor &rhs) { return lhs.decl == rhs.decl && lhs.isClass == rhs.isClass; } - friend bool operator!=(const CxxDeclExplicitSafetyDescriptor &lhs, - const CxxDeclExplicitSafetyDescriptor &rhs) { + friend bool operator!=(const ClangDeclExplicitSafetyDescriptor &lhs, + const ClangDeclExplicitSafetyDescriptor &rhs) { return !(lhs == rhs); } }; void simple_display(llvm::raw_ostream &out, - CxxDeclExplicitSafetyDescriptor desc); -SourceLoc extractNearestSourceLoc(CxxDeclExplicitSafetyDescriptor desc); + ClangDeclExplicitSafetyDescriptor desc); +SourceLoc extractNearestSourceLoc(ClangDeclExplicitSafetyDescriptor desc); /// Determine the safety of the given Clang declaration. class ClangDeclExplicitSafety : public SimpleRequest { public: using SimpleRequest::SimpleRequest; @@ -668,7 +668,7 @@ class ClangDeclExplicitSafety // Evaluation. ExplicitSafety evaluate(Evaluator &evaluator, - CxxDeclExplicitSafetyDescriptor desc) const; + ClangDeclExplicitSafetyDescriptor desc) const; }; #define SWIFT_TYPEID_ZONE ClangImporter diff --git a/include/swift/ClangImporter/ClangImporterTypeIDZone.def b/include/swift/ClangImporter/ClangImporterTypeIDZone.def index 0d562311a9c72..206819d8fe245 100644 --- a/include/swift/ClangImporter/ClangImporterTypeIDZone.def +++ b/include/swift/ClangImporter/ClangImporterTypeIDZone.def @@ -49,5 +49,5 @@ SWIFT_REQUEST(ClangImporter, CxxValueSemantics, CxxValueSemanticsKind(CxxValueSemanticsDescriptor), Cached, NoLocationInfo) SWIFT_REQUEST(ClangImporter, ClangDeclExplicitSafety, - ExplicitSafety(CxxDeclExplicitSafetyDescriptor), Cached, + ExplicitSafety(ClangDeclExplicitSafetyDescriptor), Cached, NoLocationInfo) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 6694955b31392..3c284594e592c 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -93,6 +93,7 @@ #include "clang/Serialization/ObjectFilePCHContainerReader.h" #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" #include "clang/Tooling/DependencyScanning/ScanAndUpdateArgs.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -8701,7 +8702,7 @@ SourceLoc swift::extractNearestSourceLoc(SafeUseOfCxxDeclDescriptor desc) { } void swift::simple_display(llvm::raw_ostream &out, - CxxDeclExplicitSafetyDescriptor desc) { + ClangDeclExplicitSafetyDescriptor desc) { out << "Checking if '"; if (auto namedDecl = dyn_cast(desc.decl)) out << namedDecl->getNameAsString(); @@ -8710,7 +8711,7 @@ void swift::simple_display(llvm::raw_ostream &out, out << "' is explicitly safe.\n"; } -SourceLoc swift::extractNearestSourceLoc(CxxDeclExplicitSafetyDescriptor desc) { +SourceLoc swift::extractNearestSourceLoc(ClangDeclExplicitSafetyDescriptor desc) { return SourceLoc(); } @@ -8769,103 +8770,132 @@ CustomRefCountingOperationResult CustomRefCountingOperation::evaluate( return {CustomRefCountingOperationResult::tooManyFound, nullptr, name}; } -/// Check whether the given Clang type involves an unsafe type. -static bool hasUnsafeType(Evaluator &evaluator, clang::QualType clangType) { - // Handle pointers. - auto pointeeType = clangType->getPointeeType(); - if (!pointeeType.isNull()) { - // Function pointers are okay. - if (pointeeType->isFunctionType()) - return false; - - // Pointers to record types are okay if they come in as foreign reference - // types. - if (auto recordDecl = pointeeType->getAsRecordDecl()) { - if (hasImportAsRefAttr(recordDecl)) - return false; +ExplicitSafety ClangDeclExplicitSafety::evaluate( + Evaluator &evaluator, ClangDeclExplicitSafetyDescriptor desc) const { + // FIXME: Also similar to hasPointerInSubobjects + // FIXME: should probably also subsume IsSafeUseOfCxxDecl + + if (desc.isClass) + // Safety for class types is handled a bit differently than other types. + // If it is not explicitly marked unsafe, it is always explicitly safe. + return hasSwiftAttribute(desc.decl, "unsafe") ? ExplicitSafety::Unsafe + : ExplicitSafety::Safe; + + // Clang record types are considered explicitly unsafe if any of their fields, + // base classes, and template type parameters are unsafe. We use a stack for + // this recursive traversal. + // + // Invariant: if any Decl in the stack is unsafe, then desc.decl is unsafe. + llvm::SmallVector stack; + + // Keep track of which Decls we've seen to avoid cycles. + llvm::SmallDenseSet seen; + + // Check whether a type is unsafe. This function may also push to the stack. + auto isUnsafe = [&](clang::QualType type) -> bool { + auto pointeeType = type->getPointeeType(); + if (!pointeeType.isNull()) { + if (pointeeType->isFunctionType()) + return false; // Function pointers are not unsafe + auto *recordDecl = pointeeType->getAsRecordDecl(); + if (recordDecl && hasImportAsRefAttr(recordDecl)) + return false; // Pointers are ok if imported as foreign reference types + return true; // All other pointers are considered unsafe. + } + if (auto *decl = type->getAsTagDecl()) { + // We need to check the safety of the TagDecl corresponding to this type + if (seen.insert(decl).second) + // Only visit decl if we have not seen it before, to avoid cycles + stack.push_back(decl); + } + return false; // This type does not look unsafe on its own + }; + + stack.push_back(desc.decl); + seen.insert(desc.decl); + while (!stack.empty()) { + const clang::Decl *decl = stack.back(); + stack.pop_back(); + + // Found unsafe; whether decl == desc.decl or not, desc.decl is unsafe + // (see invariant, above) + if (hasSwiftAttribute(decl, "unsafe")) + return ExplicitSafety::Unsafe; + + if (hasSwiftAttribute(decl, "safe")) + continue; + + // Enums are always safe + if (isa(decl)) + continue; + + auto *recordDecl = dyn_cast(decl); + if (!recordDecl) { + if (decl == desc.decl) + // If desc.decl is not a RecordDecl or EnumDecl, safety is unspecified. + return ExplicitSafety::Unspecified; + // If we encountered non-Record non-Enum decl during recursive traversal, + // we need to continue checking safety of other decls. + continue; } - - // All other pointers are considered unsafe. - return true; - } - // Handle records recursively. - if (auto recordDecl = clangType->getAsTagDecl()) { - // If we reached this point the types is not imported as a shared reference, - // so we don't need to check the bases whether they are shared references. - auto safety = evaluateOrDefault( - evaluator, ClangDeclExplicitSafety({recordDecl, false}), - ExplicitSafety::Unspecified); - switch (safety) { - case ExplicitSafety::Unsafe: - return true; - - case ExplicitSafety::Safe: - case ExplicitSafety::Unspecified: - return false; + // Escapability annotations imply that the declaration is safe + if (evaluateOrDefault( + evaluator, + ClangTypeEscapability({recordDecl->getTypeForDecl(), nullptr}), + CxxEscapability::Unknown) != CxxEscapability::Unknown) + continue; + + // A template class is unsafe if any of its type arguments are unsafe. + // Note that this does not rely on the record being defined. + if (auto *specDecl = + dyn_cast(recordDecl)) { + for (auto arg : specDecl->getTemplateArgs().asArray()) { + switch (arg.getKind()) { + case clang::TemplateArgument::Type: + if (isUnsafe(arg.getAsType())) + return ExplicitSafety::Unsafe; + break; + case clang::TemplateArgument::Pack: + for (auto pkArg : arg.getPackAsArray()) { + if (pkArg.getKind() == clang::TemplateArgument::Type && + isUnsafe(pkArg.getAsType())) + return ExplicitSafety::Unsafe; + } + break; + default: + continue; + } + } } - } - - // Everything else is safe. - return false; -} -ExplicitSafety -ClangDeclExplicitSafety::evaluate(Evaluator &evaluator, - CxxDeclExplicitSafetyDescriptor desc) const { - // FIXME: Also similar to hasPointerInSubobjects - // FIXME: should probably also subsume IsSafeUseOfCxxDecl - - // Explicitly unsafe. - auto decl = desc.decl; - if (hasSwiftAttribute(decl, "unsafe")) - return ExplicitSafety::Unsafe; - - // Explicitly safe. - if (hasSwiftAttribute(decl, "safe")) - return ExplicitSafety::Safe; - - // Shared references are considered safe. - if (desc.isClass) - return ExplicitSafety::Safe; - - // Enums are always safe. - if (isa(decl)) - return ExplicitSafety::Safe; - - // If it's not a record, leave it unspecified. - auto recordDecl = dyn_cast(decl); - if (!recordDecl) - return ExplicitSafety::Unspecified; - - // Escapable and non-escapable annotations imply that the declaration is - // safe. - if (evaluateOrDefault( - evaluator, - ClangTypeEscapability({recordDecl->getTypeForDecl(), nullptr}), - CxxEscapability::Unknown) != CxxEscapability::Unknown) - return ExplicitSafety::Safe; - - // If we don't have a definition, leave it unspecified. - recordDecl = recordDecl->getDefinition(); - if (!recordDecl) - return ExplicitSafety::Unspecified; - - // If this is a C++ class, check its bases. - if (auto cxxRecordDecl = dyn_cast(recordDecl)) { - for (auto base : cxxRecordDecl->bases()) { - if (hasUnsafeType(evaluator, base.getType())) + recordDecl = recordDecl->getDefinition(); + if (!recordDecl) { + if (decl == desc.decl) + // If desc.decl doesn't have a definition, safety is unspecified. + return ExplicitSafety::Unspecified; + // If we encountered decl without definition during recursive traversal, + // we need to continue checking safety of other decls. + continue; + } + + if (auto *cxxRecordDecl = dyn_cast(recordDecl)) { + for (auto base : cxxRecordDecl->bases()) { + if (isUnsafe(base.getType())) + return ExplicitSafety::Unsafe; + } + } + + for (auto *field : recordDecl->fields()) { + if (isUnsafe(field->getType())) return ExplicitSafety::Unsafe; } } - - // Check the fields. - for (auto field : recordDecl->fields()) { - if (hasUnsafeType(evaluator, field->getType())) - return ExplicitSafety::Unsafe; - } - // Okay, call it safe. + // desc.decl isn't explicitly marked unsafe, and none of the types/decls + // reachable from desc.decl are considered unsafe either. Cases where we would + // consider desc.decl's safety unspecified should have returned early from the + // loop. Thus, we can conclude that desc.decl is safe. return ExplicitSafety::Safe; } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 3a5312f4ce622..02eca57db7aeb 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2299,34 +2299,6 @@ namespace { } } - // We have to do this after populating ImportedDecls to avoid importing - // the same decl multiple times. Also after we imported the bases. - if (const auto *ctsd = - dyn_cast(decl)) { - for (auto arg : ctsd->getTemplateArgs().asArray()) { - llvm::SmallVector nonPackArgs; - if (arg.getKind() == clang::TemplateArgument::Pack) { - auto pack = arg.getPackAsArray(); - nonPackArgs.assign(pack.begin(), pack.end()); - } else { - nonPackArgs.push_back(arg); - } - for (auto realArg : nonPackArgs) { - if (realArg.getKind() != clang::TemplateArgument::Type) - continue; - auto SwiftType = Impl.importTypeIgnoreIUO( - realArg.getAsType(), ImportTypeKind::Abstract, - [](Diagnostic &&diag) {}, false, Bridgeability::None, - ImportTypeAttrs()); - if (SwiftType && SwiftType->isUnsafe()) { - auto attr = new (Impl.SwiftContext) UnsafeAttr(/*implicit=*/true); - result->getAttrs().add(attr); - break; - } - } - } - } - bool isNonEscapable = false; if (evaluateOrDefault( Impl.SwiftContext.evaluator, diff --git a/test/Interop/Cxx/class/safe-interop-mode.swift b/test/Interop/Cxx/class/safe-interop-mode.swift index 2696b88b19c2e..ab664d16bb6a0 100644 --- a/test/Interop/Cxx/class/safe-interop-mode.swift +++ b/test/Interop/Cxx/class/safe-interop-mode.swift @@ -83,6 +83,17 @@ struct OwnedData { void takeSharedObject(SharedObject *) const; }; +// A class template that throws away its type argument. +// +// If this template is instantiated with an unsafe type, it should be considered +// unsafe even if that type is never used. +template struct TTake {}; + +using TTakeInt = TTake; +using TTakePtr = TTake; +using TTakeSafeTuple = TTake; +using TTakeUnsafeTuple = TTake; + struct HoldsShared { SharedObject* obj; @@ -90,6 +101,12 @@ struct HoldsShared { SWIFT_RETURNS_UNRETAINED; }; +template struct TTake2 {}; +template struct PassThru {}; +struct IsUnsafe { int *p; }; +struct HasUnsafe : TTake2, IsUnsafe> {}; +using AlsoUnsafe = PassThru; + //--- test.swift import Test @@ -184,3 +201,31 @@ func useSharedReference(frt: DerivedFromSharedObject, h: HoldsShared) { let _ = frt let _ = h.getObj() } + +func useTTakeInt(x: TTakeInt) { + _ = x +} + +func useTTakePtr(x: TTakePtr) { + // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} + _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} +} + +func useTTakeSafeTuple(x: TTakeSafeTuple) { + _ = x +} + +func useTTakeUnsafeTuple(x: TTakeUnsafeTuple) { + // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} + _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} +} + +func useTTakeUnsafeTuple(x: HasUnsafe) { + // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} + _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} +} + +func useTTakeUnsafeTuple(x: AlsoUnsafe) { + // expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}} + _ = x // expected-note{{reference to parameter 'x' involves unsafe type}} +} diff --git a/test/Interop/Cxx/templates/sfinae-template-type-arguments.swift b/test/Interop/Cxx/templates/sfinae-template-type-arguments.swift new file mode 100644 index 0000000000000..e24b8bfe842ef --- /dev/null +++ b/test/Interop/Cxx/templates/sfinae-template-type-arguments.swift @@ -0,0 +1,53 @@ +// RUN: split-file %s %t +// RUN: %target-clang -c -o /dev/null -Xclang -verify -I %t/Inputs %t/cxx.cpp +// RUN: %target-swift-frontend -typecheck -verify -cxx-interoperability-mode=default -I %t%{fs-sep}Inputs -verify-additional-file %t%{fs-sep}Inputs%{fs-sep}CxxHeader.h %t%{fs-sep}main.swift +// RUN: %target-swift-ide-test -print-module -module-to-print=CxxModule -I %t/Inputs -source-filename=x -cxx-interoperability-mode=default | %FileCheck %t/Inputs/CxxHeader.h + +//--- Inputs/module.modulemap +module CxxModule { + requires cplusplus + header "CxxHeader.h" +} + +//--- Inputs/CxxHeader.h +#pragma once + +struct Empty {}; +template struct MissingMember { typename T::Missing member; }; +using SUB = MissingMember; + +struct Incomplete; +template struct IncompleteField { T member; }; +using INC = IncompleteField; + +template struct DefaultedTemplateArgs {}; +using DefaultedTemplateArgsInst = DefaultedTemplateArgs<>; + +// CHECK: struct DefaultedTemplateArgs, IncompleteField> { +// CHECK: } +// CHECK: typealias DefaultedTemplateArgsInst = DefaultedTemplateArgs, IncompleteField> + +template struct ThrowsAwayTemplateArgs {}; +using ThrowsAwayTemplateArgsInst = ThrowsAwayTemplateArgs; + +// CHECK: struct ThrowsAwayTemplateArgs, IncompleteField> { +// CHECK: } +// CHECK: typealias ThrowsAwayTemplateArgsInst = ThrowsAwayTemplateArgs, IncompleteField> + +//--- cxx.cpp +// expected-no-diagnostics +#include +void make(void) { + DefaultedTemplateArgs<> dta{}; + DefaultedTemplateArgsInst dtai{}; + + ThrowsAwayTemplateArgs tata{}; + ThrowsAwayTemplateArgsInst tatai{}; +} + +//--- main.swift +import CxxModule +func make() { + let _: DefaultedTemplateArgsInst = .init() + let _: ThrowsAwayTemplateArgsInst = .init() +}