Skip to content

Commit 720406f

Browse files
authored
Diagnose unannotated C++ APIs returning SWIFT_SHARED_REFERENCE at Swift call sites (swiftlang#83025)
This patch improves the warning for C++ APIs returning `SWIFT_SHARED_REFERENCE` types but not annotated with either `SWIFT_RETURNS_RETAINED` or `SWIFT_RETURNS_UNRETAINED` in the following ways: 1. The warning for missing `SWIFT_RETURNS_(UN)RETAINED` annotations is now emitted on Swift use sites, rather than while importing the API (func/method decls). - This logic is now implemented as a Misl Diagnostic in function `diagnoseCxxFunctionCalls` in file lib/Sema/MiscDiagnostics.cpp. - The warning is now triggered only when the API is actually used, which reduces noise in large C++ headers. - These warnings are still gated behind experimental-feature-flag `WarnUnannotatedReturnOfCxxFrt` rdar://150800115
1 parent 373ff5b commit 720406f

14 files changed

+222
-134
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,6 @@ ERROR(returns_retained_or_returns_unretained_for_non_cxx_frt_values, none,
305305
"a SWIFT_SHARED_REFERENCE type",
306306
(const clang::NamedDecl *))
307307

308-
// TODO: In the next C++ interop version, convert this warning into an error and
309-
// stop importing unannotated C++ APIs that return SWIFT_SHARED_REFERENCE.
310-
// rdar://138806722
311-
GROUPED_WARNING(no_returns_retained_returns_unretained, ClangDeclarationImport, none,
312-
"%0 should be annotated with either SWIFT_RETURNS_RETAINED or "
313-
"SWIFT_RETURNS_UNRETAINED as it is returning a SWIFT_SHARED_REFERENCE",
314-
(const clang::NamedDecl *))
315-
316308
GROUPED_WARNING(returns_retained_returns_unretained_on_overloaded_operator, ClangDeclarationImport, none,
317309
"SWIFT_RETURNS_RETAINED and SWIFT_RETURNS_UNRETAINED is not supported "
318310
"yet for overloaded C++ %0. Overloaded C++ operators always "

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,12 @@ ERROR(expose_nested_type_to_cxx,none,
22022202
"nested %kind0 can not yet be represented in C++", (ValueDecl *))
22032203
ERROR(expose_macro_to_cxx,none,
22042204
"Swift macro can not yet be represented in C++", (ValueDecl *))
2205+
WARNING(warn_unannotated_cxx_func_returning_frt, none,
2206+
"cannot infer the ownership of the returned value, annotate %0 with "
2207+
"either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED",
2208+
(const ValueDecl *))
2209+
NOTE(note_unannotated_cxx_func_returning_frt, none, "%0 is defined here",
2210+
(const ValueDecl *))
22052211
ERROR(unexposed_other_decl_in_cxx,none,
22062212
"%kind0 is not yet exposed to C++", (ValueDecl *))
22072213
ERROR(unsupported_other_decl_in_cxx,none,

include/swift/ClangImporter/ClangImporter.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,24 @@ getModuleCachePathFromClang(const clang::CompilerInstance &Instance);
670670
bool isCompletionHandlerParamName(StringRef paramName);
671671

672672
namespace importer {
673+
/// Returns true if the given C/C++ reference type uses "immortal"
674+
/// retain/release functions.
675+
bool hasImmortalAttrs(const clang::RecordDecl *decl);
676+
677+
struct ReturnOwnershipInfo {
678+
ReturnOwnershipInfo(const clang::NamedDecl *decl);
679+
680+
bool hasRetainAttr() const {
681+
return hasReturnsRetained || hasReturnsUnretained;
682+
}
683+
bool hasConflictingAttr() const {
684+
return hasReturnsRetained && hasReturnsUnretained;
685+
}
686+
687+
private:
688+
bool hasReturnsRetained = false;
689+
bool hasReturnsUnretained = false;
690+
};
673691

674692
/// Returns true if the given module has a 'cplusplus' requirement.
675693
bool requiresCPlusPlus(const clang::Module *module);

lib/ClangImporter/ClangImporter.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7890,10 +7890,6 @@ getRefParentDecls(const clang::RecordDecl *decl, ASTContext &ctx,
78907890
decl);
78917891
importerImpl->DiagnosedCxxRefDecls.insert(decl);
78927892
}
7893-
} else {
7894-
ctx.Diags.diagnose({}, diag::cant_infer_frt_in_cxx_inheritance, decl);
7895-
assert(false && "nullpointer passeed for importerImpl when calling "
7896-
"getRefParentOrDiag");
78977893
}
78987894
return matchingDecls;
78997895
}
@@ -7969,10 +7965,6 @@ getRefParentOrDiag(const clang::RecordDecl *decl, ASTContext &ctx,
79697965
decl);
79707966
importerImpl->DiagnosedCxxRefDecls.insert(decl);
79717967
}
7972-
} else {
7973-
ctx.Diags.diagnose({}, diag::cant_infer_frt_in_cxx_inheritance, decl);
7974-
assert(false && "nullpointer passed for importerImpl when calling "
7975-
"getRefParentOrDiag");
79767968
}
79777969
return nullptr;
79787970
}

lib/ClangImporter/ImportDecl.cpp

Lines changed: 21 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ bool importer::hasImmortalAttrs(const clang::RecordDecl *decl) {
194194
});
195195
}
196196

197+
importer::ReturnOwnershipInfo::ReturnOwnershipInfo(
198+
const clang::NamedDecl *decl) {
199+
if (!decl->hasAttrs())
200+
return;
201+
202+
for (const auto *attr : decl->getAttrs()) {
203+
if (const auto *swiftAttr = llvm::dyn_cast<clang::SwiftAttrAttr>(attr)) {
204+
if (swiftAttr->getAttribute() == "returns_unretained") {
205+
hasReturnsUnretained = true;
206+
} else if (swiftAttr->getAttribute() == "returns_retained") {
207+
hasReturnsRetained = true;
208+
}
209+
}
210+
}
211+
}
212+
197213
#ifndef NDEBUG
198214
static bool verifyNameMapping(MappedTypeNameKind NameMapping,
199215
StringRef left, StringRef right) {
@@ -3540,19 +3556,8 @@ namespace {
35403556
isa<clang::ObjCMethodDecl>(decl) &&
35413557
"checkBridgingAttrs called with a clang::NamedDecl which is "
35423558
"neither clang::FunctionDecl nor clang::ObjCMethodDecl");
3543-
bool returnsRetainedAttrIsPresent = false;
3544-
bool returnsUnretainedAttrIsPresent = false;
3545-
if (decl->hasAttrs()) {
3546-
for (const auto *attr : decl->getAttrs()) {
3547-
if (const auto *swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) {
3548-
if (swiftAttr->getAttribute() == "returns_unretained") {
3549-
returnsUnretainedAttrIsPresent = true;
3550-
} else if (swiftAttr->getAttribute() == "returns_retained") {
3551-
returnsRetainedAttrIsPresent = true;
3552-
}
3553-
}
3554-
}
3555-
}
3559+
3560+
auto attrInfo = importer::ReturnOwnershipInfo(decl);
35563561

35573562
HeaderLoc loc(decl->getLocation());
35583563
const auto retType =
@@ -3571,51 +3576,14 @@ namespace {
35713576

35723577
if (recordDecl && recordHasReferenceSemantics(recordDecl) &&
35733578
!hasImmortalAttrs(recordDecl)) {
3574-
if (returnsRetainedAttrIsPresent && returnsUnretainedAttrIsPresent) {
3579+
if (attrInfo.hasConflictingAttr()) {
35753580
Impl.diagnose(loc, diag::both_returns_retained_returns_unretained,
35763581
decl);
3577-
} else if (!returnsRetainedAttrIsPresent &&
3578-
!returnsUnretainedAttrIsPresent) {
3579-
bool unannotatedAPIWarningNeeded = false;
3580-
if (isa<clang::FunctionDecl>(decl) &&
3581-
!isa<clang::CXXDeductionGuideDecl>(decl)) {
3582-
if (const auto *methodDecl = dyn_cast<clang::CXXMethodDecl>(decl)) {
3583-
// FIXME: In the future we should support SWIFT_RETURNS_RETAINED
3584-
// and SWIFT_RETURNS_UNRETAINED for overloaded C++ operators
3585-
// returning SWIFT_SHARED_REFERENCE types
3586-
if (!isa<clang::CXXConstructorDecl>(methodDecl) &&
3587-
!isa<clang::CXXDestructorDecl>(methodDecl) &&
3588-
!methodDecl->isOverloadedOperator() &&
3589-
methodDecl->isUserProvided()) {
3590-
// normal c++ member method
3591-
unannotatedAPIWarningNeeded = true;
3592-
}
3593-
} else {
3594-
// global or top-level C/C++ function
3595-
unannotatedAPIWarningNeeded = true;
3596-
}
3597-
} else if (isa<clang::ObjCMethodDecl>(decl)) {
3598-
unannotatedAPIWarningNeeded = true;
3599-
}
3600-
3601-
if (importer::matchSwiftAttrOnRecordPtr<bool>(
3602-
retType, {{"returned_as_unretained_by_default", true}})) {
3603-
unannotatedAPIWarningNeeded = false;
3604-
}
3605-
3606-
if (unannotatedAPIWarningNeeded &&
3607-
Impl.SwiftContext.LangOpts.hasFeature(
3608-
Feature::WarnUnannotatedReturnOfCxxFrt)) {
3609-
Impl.addImportDiagnostic(
3610-
decl,
3611-
Diagnostic(diag::no_returns_retained_returns_unretained, decl),
3612-
decl->getLocation());
3613-
}
36143582
} else if (const auto *methodDecl =
36153583
dyn_cast<clang::CXXMethodDecl>(decl)) {
36163584
// Warning for annotated overloaded C++ operators as they currently
36173585
// follow Swift method's convention and always return owned.
3618-
if (methodDecl->isOverloadedOperator()) {
3586+
if (methodDecl->isOverloadedOperator() && attrInfo.hasRetainAttr()) {
36193587
Impl.diagnose(
36203588
loc,
36213589
diag::
@@ -3624,7 +3592,7 @@ namespace {
36243592
}
36253593
}
36263594
} else {
3627-
if (returnsRetainedAttrIsPresent || returnsUnretainedAttrIsPresent) {
3595+
if (attrInfo.hasRetainAttr()) {
36283596
if (const auto *functionDecl = dyn_cast<clang::FunctionDecl>(decl)) {
36293597
if (functionDecl->isTemplateInstantiation()) {
36303598
return;

lib/ClangImporter/ImporterImpl.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,10 +1971,6 @@ namespace importer {
19711971
bool recordHasReferenceSemantics(const clang::RecordDecl *decl,
19721972
ClangImporter::Implementation *importerImpl);
19731973

1974-
/// Returns true if the given C/C++ reference type uses "immortal"
1975-
/// retain/release functions.
1976-
bool hasImmortalAttrs(const clang::RecordDecl *decl);
1977-
19781974
/// Whether this is a forward declaration of a type. We ignore forward
19791975
/// declarations in certain cases, and instead process the real declarations.
19801976
bool isForwardDeclOfType(const clang::Decl *decl);

lib/Sema/MiscDiagnostics.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include "swift/Basic/SourceManager.h"
4141
#include "swift/Basic/Statistic.h"
4242
#include "swift/Basic/StringExtras.h"
43+
#include "swift/ClangImporter/ClangImporter.h"
44+
#include "swift/ClangImporter/ClangImporterRequests.h"
4345
#include "swift/Parse/Lexer.h"
4446
#include "swift/Sema/ConstraintSystem.h"
4547
#include "swift/Sema/IDETypeChecking.h"
@@ -6392,6 +6394,132 @@ static void diagnoseMissingMemberImports(const Expr *E, const DeclContext *DC) {
63926394
const_cast<Expr *>(E)->walk(walker);
63936395
}
63946396

6397+
static bool isReturningFRT(const clang::NamedDecl *ND,
6398+
clang::QualType &outReturnType, ASTContext &Ctx) {
6399+
if (auto *FD = dyn_cast<clang::FunctionDecl>(ND))
6400+
outReturnType = FD->getReturnType();
6401+
else if (auto *MD = dyn_cast<clang::ObjCMethodDecl>(ND))
6402+
outReturnType = MD->getReturnType();
6403+
else
6404+
return false;
6405+
6406+
clang::QualType pointeeType = outReturnType;
6407+
if (outReturnType->isPointerType() || outReturnType->isReferenceType())
6408+
pointeeType = outReturnType->getPointeeType();
6409+
6410+
const auto *recordDecl = pointeeType->getAsRecordDecl();
6411+
if (!recordDecl)
6412+
return false;
6413+
6414+
return !importer::hasImmortalAttrs(recordDecl) &&
6415+
evaluateOrDefault(Ctx.evaluator,
6416+
CxxRecordSemantics({recordDecl, Ctx, nullptr}),
6417+
{}) == CxxRecordSemanticsKind::Reference;
6418+
}
6419+
6420+
static bool shouldDiagnoseMissingReturnsRetained(const clang::NamedDecl *ND,
6421+
clang::QualType retType,
6422+
ASTContext &Ctx) {
6423+
if (!Ctx.LangOpts.hasFeature(Feature::WarnUnannotatedReturnOfCxxFrt))
6424+
return false;
6425+
6426+
auto attrInfo = importer::ReturnOwnershipInfo(ND);
6427+
if (attrInfo.hasRetainAttr())
6428+
return false;
6429+
6430+
if (importer::matchSwiftAttrOnRecordPtr<bool>(
6431+
retType, {{"returned_as_unretained_by_default", true}}))
6432+
return false;
6433+
6434+
if (isa<clang::ObjCMethodDecl>(ND))
6435+
// All ObjCMethods can be annotated with ownership attrs
6436+
return true;
6437+
6438+
if (auto *FD = dyn_cast<clang::FunctionDecl>(ND)) {
6439+
if (isa<clang::CXXDeductionGuideDecl>(FD))
6440+
// Deduction guides don't need ownership attrs because they aren't
6441+
// functions.
6442+
return false;
6443+
6444+
if (const auto *methodDecl = dyn_cast<clang::CXXMethodDecl>(FD)) {
6445+
if (isa<clang::CXXConstructorDecl, clang::CXXDestructorDecl>(methodDecl))
6446+
// Ownership attrs are not yet supported for ctors and dtors if FRTs
6447+
return false;
6448+
6449+
if (methodDecl->isOverloadedOperator())
6450+
// Ownership attrs are not yet supported for overloaded operators
6451+
return false;
6452+
6453+
if (!methodDecl->isUserProvided())
6454+
// Implicitly defined methods don't need ownership attrs since users
6455+
// can't annotate them.
6456+
return false;
6457+
}
6458+
6459+
return true;
6460+
}
6461+
6462+
// Decls that aren't functions or ObjCMethods don't need ownership attrs.
6463+
return false;
6464+
}
6465+
6466+
// Diagnose calls to imported C++ functions that return `SWIFT_SHARED_REFERENCE`
6467+
// types without explicit ownership annotations SWIFT_RETURNS_(UN)RETAINED
6468+
static void diagnoseCxxFunctionCalls(const Expr *E, const DeclContext *DC) {
6469+
class DiagnoseWalker : public BaseDiagnosticWalker {
6470+
ASTContext &Ctx;
6471+
6472+
public:
6473+
explicit DiagnoseWalker(ASTContext &ctx) : Ctx(ctx) {}
6474+
6475+
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
6476+
if (!E)
6477+
return Action::SkipNode(E);
6478+
6479+
auto *CE = dyn_cast<CallExpr>(E);
6480+
if (!CE)
6481+
return Action::Continue(E);
6482+
6483+
auto *func = CE->getCalledValue(/*skipFunctionConversions=*/true);
6484+
if (!func)
6485+
return Action::Continue(E);
6486+
6487+
auto *clangDecl = func->getClangDecl();
6488+
if (!clangDecl)
6489+
return Action::Continue(E);
6490+
6491+
auto *ND = dyn_cast<clang::NamedDecl>(clangDecl);
6492+
if (!ND)
6493+
return Action::Continue(E);
6494+
6495+
clang::QualType retType;
6496+
if (!isReturningFRT(ND, retType, Ctx))
6497+
return Action::Continue(E);
6498+
6499+
if (shouldDiagnoseMissingReturnsRetained(ND, retType, Ctx)) {
6500+
SourceLoc diagnosticLoc = func->getLoc();
6501+
if (diagnosticLoc.isInvalid() && func->getClangDecl()) {
6502+
// Fixme: Remove the diagnosticLoc once the source locations of the
6503+
// objc method declarations are imported correctly.
6504+
diagnosticLoc = Ctx.getClangModuleLoader()->importSourceLocation(
6505+
ND->getLocation());
6506+
}
6507+
6508+
Ctx.Diags.diagnose(CE->getLoc(),
6509+
diag::warn_unannotated_cxx_func_returning_frt, func);
6510+
6511+
Ctx.Diags.diagnose(diagnosticLoc,
6512+
diag::note_unannotated_cxx_func_returning_frt, func);
6513+
}
6514+
6515+
return Action::Continue(E);
6516+
}
6517+
};
6518+
6519+
DiagnoseWalker walker(DC->getASTContext());
6520+
const_cast<Expr *>(E)->walk(walker);
6521+
}
6522+
63956523
//===----------------------------------------------------------------------===//
63966524
// High-level entry points.
63976525
//===----------------------------------------------------------------------===//
@@ -6423,6 +6551,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
64236551
diagUnqualifiedAccessToMethodNamedSelf(E, DC);
64246552
diagnoseDictionaryLiteralDuplicateKeyEntries(E, DC);
64256553
diagnoseMissingMemberImports(E, DC);
6554+
diagnoseCxxFunctionCalls(E, DC);
64266555
}
64276556

64286557
void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {

0 commit comments

Comments
 (0)