Skip to content

Commit b80ab98

Browse files
committed
[cxx-interop] Check template argument safety in ClangDeclExplicitSafety
Checking this upon import may overlook annotations that explicitly mark a type as safe (or unsafe). Instead, we consolidate this logic in the ClangDeclExplicitSafety request. rdar://163196609
1 parent a302d4e commit b80ab98

File tree

2 files changed

+73
-15
lines changed

2 files changed

+73
-15
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8777,14 +8777,14 @@ static bool hasUnsafeType(Evaluator &evaluator, clang::QualType clangType) {
87778777
// Function pointers are okay.
87788778
if (pointeeType->isFunctionType())
87798779
return false;
8780-
8780+
87818781
// Pointers to record types are okay if they come in as foreign reference
87828782
// types.
8783-
if (auto recordDecl = pointeeType->getAsRecordDecl()) {
8783+
if (auto *recordDecl = pointeeType->getAsRecordDecl()) {
87848784
if (hasImportAsRefAttr(recordDecl))
87858785
return false;
87868786
}
8787-
8787+
87888788
// All other pointers are considered unsafe.
87898789
return true;
87908790
}
@@ -8793,19 +8793,25 @@ static bool hasUnsafeType(Evaluator &evaluator, clang::QualType clangType) {
87938793
if (auto recordDecl = clangType->getAsTagDecl()) {
87948794
// If we reached this point the types is not imported as a shared reference,
87958795
// so we don't need to check the bases whether they are shared references.
8796-
auto safety = evaluateOrDefault(
8797-
evaluator, ClangDeclExplicitSafety({recordDecl, false}),
8798-
ExplicitSafety::Unspecified);
8799-
switch (safety) {
8800-
case ExplicitSafety::Unsafe:
8801-
return true;
8802-
8803-
case ExplicitSafety::Safe:
8804-
case ExplicitSafety::Unspecified:
8805-
return false;
8806-
}
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;
88078813
}
8808-
8814+
88098815
// Everything else is safe.
88108816
return false;
88118817
}
@@ -8845,6 +8851,29 @@ ClangDeclExplicitSafety::evaluate(Evaluator &evaluator,
88458851
ClangTypeEscapability({recordDecl->getTypeForDecl(), nullptr}),
88468852
CxxEscapability::Unknown) != CxxEscapability::Unknown)
88478853
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()))
8869+
return ExplicitSafety::Unsafe;
8870+
}
8871+
break;
8872+
default:
8873+
continue;
8874+
}
8875+
}
8876+
}
88488877

88498878
// If we don't have a definition, leave it unspecified.
88508879
recordDecl = recordDecl->getDefinition();

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ struct OwnedData {
8383
void takeSharedObject(SharedObject *) const;
8484
};
8585

86+
// A class template that throws away its type argument.
87+
//
88+
// If this template is instantiated with an unsafe type, it should be considered
89+
// unsafe even if that type is never used.
90+
template <typename> struct TTake {};
91+
92+
using TTakeInt = TTake<int>;
93+
using TTakePtr = TTake<int *>;
94+
using TTakeSafeTuple = TTake<SafeTuple>;
95+
using TTakeUnsafeTuple = TTake<UnsafeTuple>;
96+
8697
struct HoldsShared {
8798
SharedObject* obj;
8899

@@ -184,3 +195,21 @@ func useSharedReference(frt: DerivedFromSharedObject, h: HoldsShared) {
184195
let _ = frt
185196
let _ = h.getObj()
186197
}
198+
199+
func useTTakeInt(x: TTakeInt) {
200+
_ = x
201+
}
202+
203+
func useTTakePtr(x: TTakePtr) {
204+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
205+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
206+
}
207+
208+
func useTTakeSafeTuple(x: TTakeSafeTuple) {
209+
_ = x
210+
}
211+
212+
func useTTakeUnsafeTuple(x: TTakeUnsafeTuple) {
213+
// expected-warning@+1{{expression uses unsafe constructs but is not marked with 'unsafe'}}
214+
_ = x // expected-note{{reference to parameter 'x' involves unsafe type}}
215+
}

0 commit comments

Comments
 (0)