Skip to content

Commit 6c997ae

Browse files
committed
[cxx-interop] Make ClangDeclExplicitSafety request non-recursive
Doing so reduces opportunities for caching, but it's not obvious to me that we benefit from such fine-grained caching. The benefit here is that we also avoid the pitfalls of incremental caching, as illustrated by the added test case. Finally, we eliminate the use of hasActiveRequest(), which is an anti-pattern.
1 parent 3255ffb commit 6c997ae

File tree

2 files changed

+126
-113
lines changed

2 files changed

+126
-113
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 110 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
#include "clang/Serialization/ObjectFilePCHContainerReader.h"
9494
#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
9595
#include "clang/Tooling/DependencyScanning/ScanAndUpdateArgs.h"
96+
#include "llvm/ADT/DenseSet.h"
9697
#include "llvm/ADT/IntrusiveRefCntPtr.h"
9798
#include "llvm/ADT/STLExtras.h"
9899
#include "llvm/ADT/SmallVector.h"
@@ -8769,132 +8770,128 @@ CustomRefCountingOperationResult CustomRefCountingOperation::evaluate(
87698770
return {CustomRefCountingOperationResult::tooManyFound, nullptr, name};
87708771
}
87718772

8772-
/// Check whether the given Clang type involves an unsafe type.
8773-
static bool hasUnsafeType(Evaluator &evaluator, clang::QualType clangType) {
8774-
// Handle pointers.
8775-
auto pointeeType = clangType->getPointeeType();
8776-
if (!pointeeType.isNull()) {
8777-
// Function pointers are okay.
8778-
if (pointeeType->isFunctionType())
8779-
return false;
8780-
8781-
// Pointers to record types are okay if they come in as foreign reference
8782-
// types.
8783-
if (auto *recordDecl = pointeeType->getAsRecordDecl()) {
8784-
if (hasImportAsRefAttr(recordDecl))
8785-
return false;
8786-
}
8787-
8788-
// All other pointers are considered unsafe.
8789-
return true;
8790-
}
8791-
8792-
// Handle records recursively.
8793-
if (auto recordDecl = clangType->getAsTagDecl()) {
8794-
// If we reached this point the types is not imported as a shared reference,
8795-
// so we don't need to check the bases whether they are shared references.
8796-
auto req = ClangDeclExplicitSafety({recordDecl, false});
8797-
if (evaluator.hasActiveRequest(req))
8798-
// Normally, using hasActiveRequest() to avoid cycles is an anti-pattern
8799-
// because cycles should be treated as errors. However, cycles are allowed
8800-
// in C++ template, e.g.:
8801-
//
8802-
// template <typename> class Foo { ... }; // throws away template arg
8803-
// template <typename T> class Bar : Foo<Bar<T>> { ... };
8804-
//
8805-
// A common use case of cyclic templates is the CRTP pattern.
8806-
//
8807-
// We need to avoid request cycles, so if there is already an active
8808-
// request for this type, just assume it is not explicitly unsafe for now
8809-
// (i.e., as if it were unspecified).
8810-
return false;
8811-
return evaluateOrDefault(evaluator, req, ExplicitSafety::Unspecified) ==
8812-
ExplicitSafety::Unsafe;
8813-
}
8814-
8815-
// Everything else is safe.
8816-
return false;
8817-
}
8818-
88198773
ExplicitSafety
88208774
ClangDeclExplicitSafety::evaluate(Evaluator &evaluator,
88218775
CxxDeclExplicitSafetyDescriptor desc) const {
88228776
// FIXME: Also similar to hasPointerInSubobjects
88238777
// FIXME: should probably also subsume IsSafeUseOfCxxDecl
8824-
8825-
// Explicitly unsafe.
8826-
auto decl = desc.decl;
8827-
if (hasSwiftAttribute(decl, "unsafe"))
8828-
return ExplicitSafety::Unsafe;
8829-
8830-
// Explicitly safe.
8831-
if (hasSwiftAttribute(decl, "safe"))
8832-
return ExplicitSafety::Safe;
8833-
8834-
// Shared references are considered safe.
8778+
88358779
if (desc.isClass)
8836-
return ExplicitSafety::Safe;
8837-
8838-
// Enums are always safe.
8839-
if (isa<clang::EnumDecl>(decl))
8840-
return ExplicitSafety::Safe;
8841-
8842-
// If it's not a record, leave it unspecified.
8843-
auto recordDecl = dyn_cast<clang::RecordDecl>(decl);
8844-
if (!recordDecl)
8845-
return ExplicitSafety::Unspecified;
8846-
8847-
// Escapable and non-escapable annotations imply that the declaration is
8848-
// safe.
8849-
if (evaluateOrDefault(
8850-
evaluator,
8851-
ClangTypeEscapability({recordDecl->getTypeForDecl(), nullptr}),
8852-
CxxEscapability::Unknown) != CxxEscapability::Unknown)
8853-
return ExplicitSafety::Safe;
8854-
8855-
// A template class is unsafe if any of its type arguments are unsafe.
8856-
// Note that this does not rely on the record being defined.
8857-
if (const auto *ctsd =
8858-
dyn_cast<clang::ClassTemplateSpecializationDecl>(recordDecl)) {
8859-
for (auto arg : ctsd->getTemplateArgs().asArray()) {
8860-
switch (arg.getKind()) {
8861-
case clang::TemplateArgument::Type:
8862-
if (hasUnsafeType(evaluator, arg.getAsType()))
8863-
return ExplicitSafety::Unsafe;
8864-
break;
8865-
case clang::TemplateArgument::Pack:
8866-
for (auto pkArg : arg.getPackAsArray()) {
8867-
if (pkArg.getKind() == clang::TemplateArgument::Type &&
8868-
hasUnsafeType(evaluator, pkArg.getAsType()))
8780+
// Safety for class types is handled a bit differently than other types.
8781+
// If it is not explicitly marked unsafe, it is always explicitly safe.
8782+
return hasSwiftAttribute(desc.decl, "unsafe") ? ExplicitSafety::Unsafe
8783+
: ExplicitSafety::Safe;
8784+
8785+
// Clang record types are considered explicitly unsafe if any of their fields,
8786+
// base classes, and template type parameters are unsafe. We use a stack for
8787+
// this recursive traversal.
8788+
//
8789+
// Invariant: if any Decl in the stack is unsafe, then desc.decl is unsafe.
8790+
llvm::SmallVector<const clang::Decl *, 4> stack;
8791+
8792+
// Keep track of which Decls we've seen to avoid cycles.
8793+
llvm::SmallDenseSet<const clang::Decl *, 4> seen;
8794+
8795+
// Check whether a type is unsafe. This function may also push to the stack.
8796+
auto isUnsafe = [&](clang::QualType type) -> bool {
8797+
auto pointeeType = type->getPointeeType();
8798+
if (!pointeeType.isNull()) {
8799+
if (pointeeType->isFunctionType())
8800+
return false; // Function pointers are not unsafe
8801+
auto *recordDecl = pointeeType->getAsRecordDecl();
8802+
if (recordDecl && hasImportAsRefAttr(recordDecl))
8803+
return false; // Pointers are ok if imported as foreign reference types
8804+
return true; // All other pointers are considered unsafe.
8805+
}
8806+
if (auto *decl = type->getAsTagDecl()) {
8807+
// We need to check the safety of the TagDecl corresponding to this type
8808+
if (seen.insert(decl).second)
8809+
// Only visit decl if we have not seen it before, to avoid cycles
8810+
stack.push_back(decl);
8811+
}
8812+
return false; // This type does not look unsafe on its own
8813+
};
8814+
8815+
stack.push_back(desc.decl);
8816+
seen.insert(desc.decl);
8817+
while (!stack.empty()) {
8818+
const clang::Decl *decl = stack.back();
8819+
stack.pop_back();
8820+
8821+
// Found unsafe; whether decl == desc.decl or not, desc.decl is unsafe
8822+
// (see invariant, above)
8823+
if (hasSwiftAttribute(decl, "unsafe"))
8824+
return ExplicitSafety::Unsafe;
8825+
8826+
if (hasSwiftAttribute(decl, "safe"))
8827+
continue;
8828+
8829+
// Enums are always safe
8830+
if (isa<clang::EnumDecl>(decl))
8831+
continue;
8832+
8833+
auto *recordDecl = dyn_cast<clang::RecordDecl>(decl);
8834+
if (!recordDecl) {
8835+
if (decl == desc.decl)
8836+
// If desc.decl is not a RecordDecl or EnumDecl, safety is unspecified.
8837+
return ExplicitSafety::Unspecified;
8838+
// If we encountered non-Record non-Enum decl during recursive traversal,
8839+
// we need to continue checking safety of other decls.
8840+
continue;
8841+
}
8842+
8843+
// Escapability annotations imply that the declaration is safe
8844+
if (evaluateOrDefault(
8845+
evaluator,
8846+
ClangTypeEscapability({recordDecl->getTypeForDecl(), nullptr}),
8847+
CxxEscapability::Unknown) != CxxEscapability::Unknown)
8848+
continue;
8849+
8850+
// A template class is unsafe if any of its type arguments are unsafe.
8851+
// Note that this does not rely on the record being defined.
8852+
if (auto *specDecl =
8853+
dyn_cast<clang::ClassTemplateSpecializationDecl>(recordDecl)) {
8854+
for (auto arg : specDecl->getTemplateArgs().asArray()) {
8855+
switch (arg.getKind()) {
8856+
case clang::TemplateArgument::Type:
8857+
if (isUnsafe(arg.getAsType()))
88698858
return ExplicitSafety::Unsafe;
8859+
break;
8860+
case clang::TemplateArgument::Pack:
8861+
for (auto pkArg : arg.getPackAsArray()) {
8862+
if (pkArg.getKind() == clang::TemplateArgument::Type &&
8863+
isUnsafe(pkArg.getAsType()))
8864+
return ExplicitSafety::Unsafe;
8865+
}
8866+
break;
8867+
default:
8868+
continue;
88708869
}
8871-
break;
8872-
default:
8873-
continue;
88748870
}
88758871
}
8876-
}
8877-
8878-
// If we don't have a definition, leave it unspecified.
8879-
recordDecl = recordDecl->getDefinition();
8880-
if (!recordDecl)
8881-
return ExplicitSafety::Unspecified;
8882-
8883-
// If this is a C++ class, check its bases.
8884-
if (auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl)) {
8885-
for (auto base : cxxRecordDecl->bases()) {
8886-
if (hasUnsafeType(evaluator, base.getType()))
8872+
8873+
recordDecl = recordDecl->getDefinition();
8874+
if (!recordDecl) {
8875+
if (decl == desc.decl)
8876+
// If desc.decl doesn't have a definition, safety is unspecified.
8877+
return ExplicitSafety::Unspecified;
8878+
// If we encountered decl without definition during recursive traversal,
8879+
// we need to continue checking safety of other decls.
8880+
continue;
8881+
}
8882+
8883+
if (auto *cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl)) {
8884+
for (auto base : cxxRecordDecl->bases()) {
8885+
if (isUnsafe(base.getType()))
8886+
return ExplicitSafety::Unsafe;
8887+
}
8888+
}
8889+
8890+
for (auto *field : recordDecl->fields()) {
8891+
if (isUnsafe(field->getType()))
88878892
return ExplicitSafety::Unsafe;
88888893
}
88898894
}
8890-
8891-
// Check the fields.
8892-
for (auto field : recordDecl->fields()) {
8893-
if (hasUnsafeType(evaluator, field->getType()))
8894-
return ExplicitSafety::Unsafe;
8895-
}
8896-
8897-
// Okay, call it safe.
88988895
return ExplicitSafety::Safe;
88998896
}
89008897

test/Interop/Cxx/class/safe-interop-mode.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ struct HoldsShared {
101101
SWIFT_RETURNS_UNRETAINED;
102102
};
103103

104+
template <typename, typename> struct TTake2 {};
105+
template <typename T> struct PassThru {};
106+
struct IsUnsafe { int *p; };
107+
struct HasUnsafe : TTake2<PassThru<HasUnsafe>, IsUnsafe> {};
108+
using AlsoUnsafe = PassThru<HasUnsafe>;
109+
104110
//--- test.swift
105111

106112
import Test
@@ -213,3 +219,13 @@ func useTTakeUnsafeTuple(x: TTakeUnsafeTuple) {
213219
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
214220
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
215221
}
222+
223+
func useTTakeUnsafeTuple(x: HasUnsafe) {
224+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
225+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
226+
}
227+
228+
func useTTakeUnsafeTuple(x: AlsoUnsafe) {
229+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
230+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
231+
}

0 commit comments

Comments
 (0)