Skip to content

Commit fb5bc72

Browse files
committed
[cxx-interop] Diagnose invalid copyability, escapability and mutability attributes
Fixes: #84559
1 parent 3bbd7da commit fb5bc72

11 files changed

+317
-90
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@ GROUPED_WARNING(import_multiple_mainactor_attr, ClangDeclarationImport, none,
120120
"this attribute for global actor '%0' is invalid; the declaration already has attribute for global actor '%1'",
121121
(StringRef, StringRef))
122122

123-
GROUPED_WARNING(contradicting_mutation_attrs, ClangDeclarationImport, none,
124-
"attribute '%0' is ignored when combined with attribute '%1'",
125-
(StringRef, StringRef))
126-
127123
GROUPED_WARNING(nonmutating_without_const, ClangDeclarationImport, none,
128124
"attribute 'nonmutating' has no effect on non-const method", ())
129125

@@ -199,10 +195,10 @@ NOTE(projection_reference_not_imported, none, "C++ method '%0' that returns a re
199195
NOTE(projection_may_return_interior_ptr, none, "C++ method '%0' may return an "
200196
"interior pointer",
201197
(StringRef))
202-
NOTE(mark_self_contained, none, "annotate type '%0' with 'SWIFT_SELF_CONTAINED' in C++ to "
198+
NOTE(mark_self_contained, none, "annotate type '%0' with SWIFT_SELF_CONTAINED in C++ to "
203199
"make methods that return it available in Swift",
204200
(StringRef))
205-
NOTE(mark_safe_to_import, none, "annotate method '%0' with 'SWIFT_RETURNS_INDEPENDENT_VALUE' in C++ to "
201+
NOTE(mark_safe_to_import, none, "annotate method '%0' with SWIFT_RETURNS_INDEPENDENT_VALUE in C++ to "
206202
"make it available in Swift",
207203
(StringRef))
208204

@@ -231,8 +227,6 @@ ERROR(private_fileid_attr_repeated, none,
231227
ERROR(private_fileid_attr_on_incomplete_type, none,
232228
"SWIFT_PRIVATE_FILEID cannot be applied to incomplete type, '%0'",
233229
(StringRef))
234-
NOTE(private_fileid_attr_here, none,
235-
"SWIFT_PRIVATE_FILEID annotation found here", ())
236230

237231
GROUPED_WARNING(private_fileid_attr_format_invalid, ClangDeclarationImport, none,
238232
"SWIFT_PRIVATE_FILEID annotation on '%0' does not have a valid file ID",
@@ -414,6 +408,18 @@ NOTE(
414408
"SWIFT_REFCOUNTED_PTR on %1 has incorrect signature; expected a function "
415409
"that takes no arguments and returns a pointer to a foreign reference type",
416410
(const Decl *, const clang::NamedDecl *))
411+
ERROR(invalid_conditional_attr, none,
412+
"%0 is invalid because it is only supported in "
413+
"class templates",
414+
(StringRef))
415+
416+
ERROR(conflicting_swift_attrs, none,
417+
"multiple conflicting annotations found on %0", (const clang::NamedDecl *))
418+
419+
ERROR(repeating_swift_attrs, none, "multiple %0 annotations found on %1",
420+
(StringRef, const clang::NamedDecl *))
421+
422+
NOTE(annotation_here, none, "%0 annotation found here", (StringRef))
417423

418424
#define UNDEFINE_DIAGNOSTIC_MACROS
419425
#include "DefineDiagnosticMacros.h"

include/swift/AST/DiagnosticsIRGen.def

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,24 @@ NOTE(use_requires_expression, none,
9696
())
9797

9898
NOTE(annotate_copyable_if, none,
99-
"annotate a type with 'SWIFT_COPYABLE_IF(<T>)' in C++ to specify "
99+
"annotate a type with SWIFT_COPYABLE_IF(<T>) in C++ to specify "
100100
"that the type is Copyable if <T> is Copyable",
101101
())
102102

103103
NOTE(annotate_non_copyable, none,
104-
"annotate a type with 'SWIFT_NONCOPYABLE' in C++ to import it as "
104+
"annotate a type with SWIFT_NONCOPYABLE in C++ to import it as "
105105
"~Copyable",
106106
())
107107

108108
NOTE(maybe_missing_annotation, none,
109109
"one of the types that %0 depends on may need a 'requires' clause (since "
110-
"C++20) in the copy constructor, a 'SWIFT_COPYABLE_IF' annotation or a "
111-
"'SWIFT_NONCOPYABLE' annotation'",
110+
"C++20) in the copy constructor, a SWIFT_COPYABLE_IF annotation or a "
111+
"SWIFT_NONCOPYABLE annotation'",
112112
(const clang::NamedDecl *))
113113

114114
NOTE(maybe_missing_parameter, none,
115115
"the %select{'requires' clause on the copy constructor "
116-
"of|'SWIFT_COPYABLE_IF' annotation on}0 %1 may be missing a "
116+
"of|SWIFT_COPYABLE_IF annotation on}0 %1 may be missing a "
117117
"%select{constraint|parameter}0",
118118
(bool, const clang::NamedDecl *))
119119

lib/ClangImporter/ClangImporter.cpp

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5455,28 +5455,34 @@ static bool checkConditionalParams(
54555455
}
54565456

54575457
static std::set<StringRef>
5458-
getConditionalAttrParams(const clang::RecordDecl *decl, StringRef attrName) {
5458+
getConditionalAttrParams(clang::SwiftAttrAttr *swiftAttr, StringRef attrName) {
54595459
std::set<StringRef> result;
5460-
if (!decl->hasAttrs())
5461-
return result;
5462-
for (auto attr : decl->getAttrs()) {
5463-
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr)) {
5464-
StringRef params = swiftAttr->getAttribute();
5465-
if (params.consume_front(attrName)) {
5466-
auto commaPos = params.find(',');
5467-
StringRef nextParam = params.take_front(commaPos);
5468-
while (!nextParam.empty() && commaPos != StringRef::npos) {
5469-
result.insert(nextParam.trim());
5470-
params = params.drop_front(nextParam.size() + 1);
5471-
commaPos = params.find(',');
5472-
nextParam = params.take_front(commaPos);
5473-
}
5474-
}
5460+
StringRef params = swiftAttr->getAttribute();
5461+
if (params.consume_front(attrName)) {
5462+
auto commaPos = params.find(',');
5463+
StringRef nextParam = params.take_front(commaPos);
5464+
while (!nextParam.empty() && commaPos != StringRef::npos) {
5465+
result.insert(nextParam.trim());
5466+
params = params.drop_front(nextParam.size() + 1);
5467+
commaPos = params.find(',');
5468+
nextParam = params.take_front(commaPos);
54755469
}
54765470
}
54775471
return result;
54785472
}
54795473

5474+
static std::set<StringRef>
5475+
getConditionalAttrParams(const clang::NamedDecl *decl, StringRef attrName) {
5476+
if (decl->hasAttrs()) {
5477+
for (auto attr : decl->getAttrs()) {
5478+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
5479+
return getConditionalAttrParams(swiftAttr, attrName);
5480+
}
5481+
}
5482+
5483+
return {};
5484+
}
5485+
54805486
static std::set<StringRef>
54815487
getConditionalEscapableAttrParams(const clang::RecordDecl *decl) {
54825488
return getConditionalAttrParams(decl, "escapable_if:");
@@ -5509,15 +5515,6 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator,
55095515
// escapability annotations, malformed escapability annotations)
55105516

55115517
bool hasUnknown = false;
5512-
auto desugared = desc.type->getUnqualifiedDesugaredType();
5513-
if (const auto *recordType = desugared->getAs<clang::RecordType>()) {
5514-
auto recordDecl = recordType->getDecl();
5515-
// If the root type has a SWIFT_ESCAPABLE annotation, we import the type as
5516-
// Escapable without entering the loop
5517-
if (hasEscapableAttr(recordDecl))
5518-
return CxxEscapability::Escapable;
5519-
}
5520-
55215518
llvm::SmallVector<const clang::Type *, 4> stack;
55225519
// Keep track of Types we've seen to avoid cycles
55235520
llvm::SmallDenseSet<const clang::Type *, 4> seen;
@@ -5551,13 +5548,6 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator,
55515548
hasUnknown &= checkConditionalParams<CxxEscapability>(
55525549
recordDecl, desc.impl, STLParams, conditionalParams,
55535550
maybePushToStack);
5554-
5555-
if (desc.impl) {
5556-
HeaderLoc loc{recordDecl->getLocation()};
5557-
for (auto name : conditionalParams)
5558-
desc.impl->diagnose(loc, diag::unknown_template_parameter, name);
5559-
}
5560-
55615551
continue;
55625552
}
55635553
// Only try to infer escapability if the record doesn't have any
@@ -8534,16 +8524,12 @@ CxxValueSemantics::evaluate(Evaluator &evaluator,
85348524
auto conditionalParams = getConditionalCopyableAttrParams(recordDecl);
85358525

85368526
if (!STLParams.empty() || !conditionalParams.empty()) {
8537-
hasUnknown &= checkConditionalParams<CxxValueSemanticsKind>(
8538-
recordDecl, importerImpl, STLParams, conditionalParams,
8539-
maybePushToStack);
8540-
8541-
if (importerImpl) {
8542-
HeaderLoc loc{recordDecl->getLocation()};
8543-
for (auto name : conditionalParams)
8544-
importerImpl->diagnose(loc, diag::unknown_template_parameter, name);
8527+
if (isa<clang::ClassTemplateSpecializationDecl>(recordDecl) &&
8528+
importerImpl) {
8529+
hasUnknown &= checkConditionalParams<CxxValueSemanticsKind>(
8530+
recordDecl, importerImpl, STLParams, conditionalParams,
8531+
maybePushToStack);
85458532
}
8546-
85478533
continue;
85488534
}
85498535
}
@@ -8577,7 +8563,8 @@ CxxValueSemantics::evaluate(Evaluator &evaluator,
85778563
if (!hasDestroyTypeOperations(cxxRecordDecl) ||
85788564
(!isCopyable && !isMovable)) {
85798565

8580-
if (hasConstructorWithUnsupportedDefaultArgs(cxxRecordDecl))
8566+
if (hasConstructorWithUnsupportedDefaultArgs(cxxRecordDecl) &&
8567+
importerImpl)
85818568
importerImpl->addImportDiagnostic(
85828569
cxxRecordDecl, Diagnostic(diag::record_unsupported_default_args),
85838570
cxxRecordDecl->getLocation());
@@ -9204,6 +9191,85 @@ swift::importer::getCxxRefConventionWithAttrs(const clang::Decl *decl) {
92049191
return std::nullopt;
92059192
}
92069193

9194+
static const std::vector<std::vector<std::pair<StringRef, StringRef>>>
9195+
ConflictingSwiftAttrs = {
9196+
{{"Escapable", "SWIFT_ESCAPABLE"},
9197+
{"~Escapable", "SWIFT_NONESCAPABLE"},
9198+
{"escapable_if:", "SWIFT_ESCAPABLE_IF"}},
9199+
{{"~Copyable", "SWIFT_NONCOPYABLE"},
9200+
{"copyable_if:", "SWIFT_COPYABLE_IF"}},
9201+
{{"mutating", "SWIFT_MUTATING"}, {"nonmutating", "'nonmutating'"}}};
9202+
9203+
static bool isConditionalAttr(StringRef attrName) {
9204+
return attrName == "escapable_if:" || attrName == "copyable_if:";
9205+
}
9206+
9207+
void ClangImporter::Implementation::validateSwiftAttributes(
9208+
const clang::NamedDecl *decl) {
9209+
if (!decl->hasAttrs())
9210+
return;
9211+
9212+
for (const auto &groupOfAttrs : ConflictingSwiftAttrs) {
9213+
// stores this decl's attributes, grouped by annotation kind
9214+
llvm::StringMap<std::vector<clang::SwiftAttrAttr *>> found;
9215+
unsigned count = 0;
9216+
for (auto [attrName, annotationName] : groupOfAttrs) {
9217+
for (auto attr : decl->getAttrs()) {
9218+
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
9219+
if (swiftAttr && swiftAttr->getAttribute().starts_with(attrName)) {
9220+
found[annotationName].push_back(swiftAttr);
9221+
count += 1;
9222+
9223+
// in the common case, we validate at most one conditional attribute
9224+
if (isConditionalAttr(attrName)) {
9225+
auto specDecl =
9226+
dyn_cast<clang::ClassTemplateSpecializationDecl>(decl);
9227+
if (!specDecl) {
9228+
diagnose(HeaderLoc{swiftAttr->getLocation()},
9229+
diag::invalid_conditional_attr, annotationName);
9230+
continue;
9231+
}
9232+
auto conditionalParams =
9233+
getConditionalAttrParams(swiftAttr, attrName);
9234+
while (specDecl && !conditionalParams.empty()) {
9235+
auto templateDecl = specDecl->getSpecializedTemplate();
9236+
for (auto [idx, param] :
9237+
llvm::enumerate(*templateDecl->getTemplateParameters()))
9238+
conditionalParams.erase(param->getName());
9239+
9240+
const clang::DeclContext *dc = specDecl;
9241+
specDecl = nullptr;
9242+
while ((dc = dc->getParent())) {
9243+
specDecl = dyn_cast<clang::ClassTemplateSpecializationDecl>(dc);
9244+
if (specDecl)
9245+
break;
9246+
}
9247+
}
9248+
for (auto name : conditionalParams)
9249+
diagnose(HeaderLoc{decl->getLocation()},
9250+
diag::unknown_template_parameter, name);
9251+
}
9252+
}
9253+
}
9254+
}
9255+
9256+
if (count > 1) {
9257+
if (found.size() == 1)
9258+
diagnose(HeaderLoc{decl->getLocation()}, diag::repeating_swift_attrs,
9259+
found.begin()->getKey(), decl);
9260+
else
9261+
diagnose(HeaderLoc{decl->getLocation()}, diag::conflicting_swift_attrs,
9262+
decl);
9263+
9264+
for (auto &[annotationName, attrs] : found) {
9265+
for (auto attr : attrs)
9266+
diagnose(HeaderLoc{attr->getLocation()}, diag::annotation_here,
9267+
annotationName);
9268+
}
9269+
}
9270+
}
9271+
}
9272+
92079273
NominalType *swift::stripInlineNamespaces(NominalType *outer,
92089274
NominalType *inner) {
92099275
if (!outer || !inner || inner == outer)

lib/ClangImporter/ImportDecl.cpp

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2355,6 +2355,7 @@ namespace {
23552355
if (alreadyImportedResult != Impl.ImportedDecls.end())
23562356
return alreadyImportedResult->second;
23572357

2358+
Impl.validateSwiftAttributes(decl);
23582359
auto loc = Impl.importSourceLoc(decl->getLocation());
23592360
if (recordHasReferenceSemantics(decl))
23602361
result = Impl.createDeclWithClangNode<ClassDecl>(
@@ -2831,7 +2832,8 @@ namespace {
28312832
Impl.diagnose(HeaderLoc(decl->getLocation()),
28322833
diag::private_fileid_attr_repeated, decl->getName());
28332834
for (auto ann : anns)
2834-
Impl.diagnose(HeaderLoc(ann.second), diag::private_fileid_attr_here);
2835+
Impl.diagnose(HeaderLoc(ann.second), diag::annotation_here,
2836+
"SWIFT_PRIVATE_FILEID");
28352837
} else if (anns.size() == 1) {
28362838
auto ann = anns[0];
28372839
if (!SourceFile::FileIDStr::parse(ann.first)) {
@@ -3117,8 +3119,8 @@ namespace {
31173119
diag::private_fileid_attr_on_incomplete_type,
31183120
decl->getName());
31193121
for (auto attr : attrs)
3120-
Impl.diagnose(HeaderLoc(attr.second),
3121-
diag::private_fileid_attr_here);
3122+
Impl.diagnose(HeaderLoc(attr.second), diag::annotation_here,
3123+
"SWIFT_PRIVATE_FILEID");
31223124
}
31233125

31243126
forwardDeclaration = true;
@@ -3640,6 +3642,7 @@ namespace {
36403642
}
36413643

36423644
checkBridgingAttrs(decl);
3645+
Impl.validateSwiftAttributes(decl);
36433646

36443647
return importFunctionDecl(decl, importedName, correctSwiftName,
36453648
std::nullopt);
@@ -8969,7 +8972,6 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
89698972
ClangDecl = cast<clang::NamedDecl>(maybeDefinition.value());
89708973

89718974
std::optional<const clang::SwiftAttrAttr *> seenMainActorAttr;
8972-
const clang::SwiftAttrAttr *seenMutabilityAttr = nullptr;
89738975
llvm::SmallSet<ProtocolDecl *, 4> conformancesSeen;
89748976
const clang::SwiftAttrAttr *seenSendableSuppressionAttr = nullptr;
89758977

@@ -9020,19 +9022,6 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
90209022
}
90219023
}
90229024
}
9023-
9024-
// Check for contradicting mutability attr
9025-
if (seenMutabilityAttr) {
9026-
StringRef previous = seenMutabilityAttr->getAttribute();
9027-
9028-
if (previous != attr) {
9029-
diagnose(HeaderLoc(swiftAttr->getLocation()),
9030-
diag::contradicting_mutation_attrs, attr, previous);
9031-
continue;
9032-
}
9033-
}
9034-
9035-
seenMutabilityAttr = swiftAttr;
90369025
}
90379026

90389027
// Hard-code @actorIndependent, until Objective-C clients start

lib/ClangImporter/ImporterImpl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,12 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
11241124
Type applyImportTypeAttrs(ImportTypeAttrs attrs, Type type,
11251125
llvm::function_ref<void(Diagnostic &&)> addImportDiagnosticFn);
11261126

1127+
/// Determines whether the given Clang declaration has conflicting
1128+
/// Swift attributes and emits diagnostics for any violations found.
1129+
///
1130+
/// \param decl The Clang record or function declaration to validate.
1131+
void validateSwiftAttributes(const clang::NamedDecl *decl);
1132+
11271133
/// If we already imported a given decl, return the corresponding Swift decl.
11281134
/// Otherwise, return nullptr.
11291135
std::optional<Decl *> importDeclCached(const clang::NamedDecl *ClangDecl,

test/Interop/Cxx/class/Inputs/mutability-annotations.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ struct HasMutableProperty {
2525

2626
int noAnnotation() const { return b; }
2727

28-
// expected-warning@+1 {{attribute 'mutating' is ignored when combined with attribute 'nonmutating'}}
28+
// expected-error@+3 {{multiple conflicting annotations found on 'contradictingAnnotations'}}
29+
// expected-note@+2 {{'nonmutating' annotation found here}}
30+
// expected-note@+1 {{SWIFT_MUTATING annotation found here}}
2931
int contradictingAnnotations() const __attribute__((__swift_attr__("nonmutating"))) __attribute__((__swift_attr__("mutating"))) {
3032
return b;
3133
}
3234

35+
// expected-error@+3 {{multiple 'nonmutating' annotations found on 'duplicateAnnotations'}}
36+
// expected-note@+2 {{'nonmutating' annotation found here}}
37+
// expected-note@+1 {{'nonmutating' annotation found here}}
3338
int duplicateAnnotations() const __attribute__((__swift_attr__("nonmutating"))) __attribute__((__swift_attr__("nonmutating"))) {
3439
return b;
3540
}

test/Interop/Cxx/class/fixit-add-safe-to-import-self-contained.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ struct X {
2323
import Test
2424

2525
public func test(x: X) {
26-
// CHECK: note: annotate method 'test' with 'SWIFT_RETURNS_INDEPENDENT_VALUE' in C++ to make it available in Swift
26+
// CHECK: note: annotate method 'test' with SWIFT_RETURNS_INDEPENDENT_VALUE in C++ to make it available in Swift
2727
// CHECK: int *test() { }
2828
// CHECK: ^
2929
// CHECK: SWIFT_RETURNS_INDEPENDENT_VALUE
3030

3131
x.test()
3232

33-
// CHECK: note: annotate method 'other' with 'SWIFT_RETURNS_INDEPENDENT_VALUE' in C++ to make it available in Swift
33+
// CHECK: note: annotate method 'other' with SWIFT_RETURNS_INDEPENDENT_VALUE in C++ to make it available in Swift
3434
// CHECK: Ptr other() { }
3535
// CHECK: ^
3636
// CHECK: SWIFT_RETURNS_INDEPENDENT_VALUE
3737

38-
// CHECK: note: annotate type 'Ptr' with 'SWIFT_SELF_CONTAINED' in C++ to make methods that return it available in Swift
38+
// CHECK: note: annotate type 'Ptr' with SWIFT_SELF_CONTAINED in C++ to make methods that return it available in Swift
3939
// CHECK: struct Ptr {
4040
// CHECK: ^
4141
// CHECK: SWIFT_SELF_CONTAINED

0 commit comments

Comments
 (0)