-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[cxx-interop] Do not import template type arguments #85379
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
base: main
Are you sure you want to change the base?
Changes from all commits
b80ab98
3255ffb
6c997ae
cbae2fc
2b9b507
6b3f8c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<clang::NamedDecl>(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<const clang::Decl *, 4> stack; | ||
|
|
||
| // Keep track of which Decls we've seen to avoid cycles. | ||
| llvm::SmallDenseSet<const clang::Decl *, 4> 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<clang::EnumDecl>(decl)) | ||
| continue; | ||
|
|
||
| auto *recordDecl = dyn_cast<clang::RecordDecl>(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<clang::ClassTemplateSpecializationDecl>(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<clang::EnumDecl>(decl)) | ||
| return ExplicitSafety::Safe; | ||
|
|
||
| // If it's not a record, leave it unspecified. | ||
| auto recordDecl = dyn_cast<clang::RecordDecl>(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<clang::CXXRecordDecl>(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; | ||
|
Comment on lines
+8875
to
+8876
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a decl has a field that has unspecified safety, and another field that is explicitly unsafe, is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This early return only triggers when Here, if |
||
| // If we encountered decl without definition during recursive traversal, | ||
| // we need to continue checking safety of other decls. | ||
| continue; | ||
| } | ||
|
|
||
| if (auto *cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(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; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we end up visiting the same decl multiple time across different requests?
I wonder if we should also cache the responses explicitly for all the decls that we visited and computed safety for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what we used to do, but caching makes it quite difficult to do this correctly.
Consider:
Upon visual inspection, we can see that
HasUnsafeshould be unsafe because it inherits fromTTake2<PassThru<HasUnsafe>, IsUnsafe>, which is unsafe because its second template type parameter is unsafe. But we might not know that while we're checking the safety of the first template parameter,PassThru<HasUnsafe>.When we look through
PassThru<HasUnsafe>and arrive at its template parameter, we need to break the cycle and assume that safety is unspecified for the time being. We will later discover thatHasUnsafeis actually unsafe via its second template parameter. However, if we are caching intermediate results, then we will incorrectly remember thatPassThru<HasUnsafe>is unspecified, which is incorrect because it has an unsafe template parameter.It's possible to make this work with conditional caching, but it is not clear to me that the added complexity is worth it.