Skip to content

Commit 9027edd

Browse files
author
Gabor Horvath
committed
[cxx-interop] Inject escapability for the C++ standard library
We only add conditional annotations because those do not break backward compatibility (we might import span and similar view types as non-escapable in the future). We inject these annotations in the importer to make sure we have consistent behavior acress the different standard library implementations. Once we can ship APINotes for the STL and we have conditional escapability support in APINotes we can migrate to that solution. But it is not possible as of today and Clang already has precedent of injecting information for the STL with lifetimebound. rdar://139065558
1 parent 4822920 commit 9027edd

File tree

3 files changed

+110
-55
lines changed

3 files changed

+110
-55
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
#include <memory>
108108
#include <optional>
109109
#include <string>
110+
#include <utility>
110111

111112
using namespace swift;
112113
using namespace importer;
@@ -5062,6 +5063,60 @@ TinyPtrVector<ValueDecl *> CXXNamespaceMemberLookup::evaluate(
50625063
return result;
50635064
}
50645065

5066+
static const llvm::StringMap<std::vector<int>> STLConditionalEscapableParams{
5067+
{"vector", {0}},
5068+
{"array", {0}},
5069+
{"inplace_vector", {0}},
5070+
{"deque", {0}},
5071+
{"forward_list", {0}},
5072+
{"list", {0}},
5073+
{"set", {0}},
5074+
{"flat_set", {0}},
5075+
{"unordered_set", {0}},
5076+
{"multiset", {0}},
5077+
{"flat_multiset", {0}},
5078+
{"unordered_multiset", {0}},
5079+
{"stack", {0}},
5080+
{"queue", {0}},
5081+
{"priority_queue", {0}},
5082+
{"tuple", {0}},
5083+
{"variant", {0}},
5084+
{"optional", {0}},
5085+
{"pair", {0, 1}},
5086+
{"expected", {0, 1}},
5087+
{"map", {0, 1}},
5088+
{"flat_map", {0, 1}},
5089+
{"unordered_map", {0, 1}},
5090+
{"multimap", {0, 1}},
5091+
{"flat_multimap", {0, 1}},
5092+
{"unordered_multimap", {0, 1}},
5093+
{"span", {0}}, // TODO: remove when span is non-escapable by default
5094+
{"mdspan", {0}}, // TODO: remove when mdspan is non-escapable by default
5095+
};
5096+
5097+
static std::set<StringRef>
5098+
getConditionalEscapableAttrParams(const clang::RecordDecl *decl) {
5099+
std::set<StringRef> result;
5100+
if (!decl->hasAttrs())
5101+
return result;
5102+
for (auto attr : decl->getAttrs()) {
5103+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
5104+
if (swiftAttr->getAttribute().starts_with("escapable_if:")) {
5105+
StringRef params = swiftAttr->getAttribute().drop_front(
5106+
StringRef("escapable_if:").size());
5107+
auto commaPos = params.find(',');
5108+
StringRef nextParam = params.take_front(commaPos);
5109+
while (!nextParam.empty() && commaPos != StringRef::npos) {
5110+
result.insert(nextParam.trim());
5111+
params = params.drop_front(nextParam.size() + 1);
5112+
commaPos = params.find(',');
5113+
nextParam = params.take_front(commaPos);
5114+
}
5115+
}
5116+
}
5117+
return result;
5118+
}
5119+
50655120
CxxEscapability
50665121
ClangTypeEscapability::evaluate(Evaluator &evaluator,
50675122
EscapabilityLookupDescriptor desc) const {
@@ -5083,18 +5138,30 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator,
50835138
return CxxEscapability::NonEscapable;
50845139
if (hasEscapableAttr(recordDecl))
50855140
return CxxEscapability::Escapable;
5086-
auto conditionalParams =
5087-
importer::getConditionalEscapableAttrParams(recordDecl);
5088-
if (!conditionalParams.empty()) {
5141+
auto injectedStlAnnotation =
5142+
recordDecl->isInStdNamespace()
5143+
? STLConditionalEscapableParams.find(recordDecl->getName())
5144+
: STLConditionalEscapableParams.end();
5145+
bool hasInjectedSTLAnnotation =
5146+
injectedStlAnnotation != STLConditionalEscapableParams.end();
5147+
auto conditionalParams = getConditionalEscapableAttrParams(recordDecl);
5148+
if (!conditionalParams.empty() || hasInjectedSTLAnnotation) {
50895149
auto specDecl = cast<clang::ClassTemplateSpecializationDecl>(recordDecl);
50905150
SmallVector<std::pair<unsigned, StringRef>, 4> argumentsToCheck;
50915151
HeaderLoc loc{recordDecl->getLocation()};
50925152
while (specDecl) {
50935153
auto templateDecl = specDecl->getSpecializedTemplate();
5094-
for (auto [idx, param] :
5095-
llvm::enumerate(*templateDecl->getTemplateParameters())) {
5096-
if (conditionalParams.erase(param->getName()))
5097-
argumentsToCheck.push_back(std::make_pair(idx, param->getName()));
5154+
if (hasInjectedSTLAnnotation) {
5155+
auto params = templateDecl->getTemplateParameters();
5156+
for (auto idx : injectedStlAnnotation->second)
5157+
argumentsToCheck.push_back(
5158+
std::make_pair(idx, params->getParam(idx)->getName()));
5159+
} else {
5160+
for (auto [idx, param] :
5161+
llvm::enumerate(*templateDecl->getTemplateParameters())) {
5162+
if (conditionalParams.erase(param->getName()))
5163+
argumentsToCheck.push_back(std::make_pair(idx, param->getName()));
5164+
}
50985165
}
50995166
auto &argList = specDecl->getTemplateArgs();
51005167
for (auto argToCheck : argumentsToCheck) {
@@ -5118,6 +5185,8 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator,
51185185
return CxxEscapability::NonEscapable;
51195186
}
51205187
}
5188+
if (hasInjectedSTLAnnotation)
5189+
break;
51215190
clang::DeclContext *dc = specDecl;
51225191
specDecl = nullptr;
51235192
while ((dc = dc->getParent())) {
@@ -7597,29 +7666,6 @@ bool importer::hasEscapableAttr(const clang::RecordDecl *decl) {
75977666
return hasSwiftAttribute(decl, "Escapable");
75987667
}
75997668

7600-
std::set<StringRef>
7601-
importer::getConditionalEscapableAttrParams(const clang::RecordDecl *decl) {
7602-
std::set<StringRef> result;
7603-
if (!decl->hasAttrs())
7604-
return result;
7605-
for (auto attr : decl->getAttrs()) {
7606-
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
7607-
if (swiftAttr->getAttribute().starts_with("escapable_if:")) {
7608-
StringRef params = swiftAttr->getAttribute().drop_front(
7609-
StringRef("escapable_if:").size());
7610-
auto commaPos = params.find(',');
7611-
StringRef nextParam = params.take_front(commaPos);
7612-
while (!nextParam.empty() && commaPos != StringRef::npos) {
7613-
result.insert(nextParam.trim());
7614-
params = params.drop_front(nextParam.size() + 1);
7615-
commaPos = params.find(',');
7616-
nextParam = params.take_front(commaPos);
7617-
}
7618-
}
7619-
}
7620-
return result;
7621-
}
7622-
76237669
/// Recursively checks that there are no pointers in any fields or base classes.
76247670
/// Does not check C++ records with specific API annotations.
76257671
static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) {

lib/ClangImporter/ImporterImpl.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,9 +2044,6 @@ bool hasNonEscapableAttr(const clang::RecordDecl *decl);
20442044

20452045
bool hasEscapableAttr(const clang::RecordDecl *decl);
20462046

2047-
std::set<StringRef>
2048-
getConditionalEscapableAttrParams(const clang::RecordDecl *decl);
2049-
20502047
bool isViewType(const clang::CXXRecordDecl *decl);
20512048

20522049
} // end namespace importer

test/Interop/Cxx/class/nonescapable-errors.swift

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module Test {
1313

1414
//--- Inputs/nonescapable.h
1515
#include "swift/bridging"
16+
#include <vector>
1617

1718
struct SWIFT_NONESCAPABLE View {
1819
View() : member(nullptr) {}
@@ -84,54 +85,65 @@ MyTuple<View> k1();
8485
MyTuple<Owner, View> k2();
8586
MyTuple<Owner, Owner> k3();
8687

88+
using ViewVector = std::vector<View>;
89+
using OwnerVector = std::vector<Owner>;
90+
91+
ViewVector l1();
92+
OwnerVector l2();
93+
8794
//--- test.swift
8895
import Test
96+
import CxxStdlib
8997

9098
// CHECK: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
91-
// CHECK-NO-LIFETIMES: test.swift:5:32: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
99+
// CHECK-NO-LIFETIMES: test.swift:6:32: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
92100
public func noAnnotations() -> View {
93-
// CHECK: nonescapable.h:15:7: warning: the returned type 'Owner' is annotated as escapable; it cannot have lifetime dependencies
101+
// CHECK: nonescapable.h:16:7: warning: the returned type 'Owner' is annotated as escapable; it cannot have lifetime dependencies
94102
f(nil)
95-
// CHECK: nonescapable.h:19:7: warning: the returned type 'Owner' is annotated as escapable; it cannot have lifetime dependencies
103+
// CHECK: nonescapable.h:20:7: warning: the returned type 'Owner' is annotated as escapable; it cannot have lifetime dependencies
96104
// No duplicate warning for f2:
97-
// CHECK-NOT: nonescapable.h:19
105+
// CHECK-NOT: nonescapable.h:20
98106
f2(nil, nil)
99-
// CHECK: nonescapable.h:23:6: warning: the returned type 'View' is annotated as non-escapable; its lifetime dependencies must be annotated
100-
// CHECK: nonescapable.h:23:6: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
101-
// CHECK-NO-LIFETIMES: nonescapable.h:23:6: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
107+
// CHECK: nonescapable.h:24:6: warning: the returned type 'View' is annotated as non-escapable; its lifetime dependencies must be annotated
108+
// CHECK: nonescapable.h:24:6: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
109+
// CHECK-NO-LIFETIMES: nonescapable.h:24:6: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
102110
g(nil)
103111
h1(nil)
104-
// CHECK: nonescapable.h:33:21: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
105-
// CHECK-NO-LIFETIMES: nonescapable.h:33:21: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
106-
h2(nil)
107112
// CHECK: nonescapable.h:34:21: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
108113
// CHECK-NO-LIFETIMES: nonescapable.h:34:21: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
114+
h2(nil)
115+
// CHECK: nonescapable.h:35:21: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
116+
// CHECK-NO-LIFETIMES: nonescapable.h:35:21: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
109117
h3(nil)
110118
i1()
111-
// CHECK: nonescapable.h:38:39: error: template parameter 'Missing' does not exist
112-
// CHECK-NO-LIFETIMES: nonescapable.h:38:39: error: template parameter 'Missing' does not exist
119+
// CHECK: nonescapable.h:39:39: error: template parameter 'Missing' does not exist
120+
// CHECK-NO-LIFETIMES: nonescapable.h:39:39: error: template parameter 'Missing' does not exist
113121
i2()
114-
// CHECK: nonescapable.h:44:33: error: template parameter 'S' expected to be a type parameter
115-
// CHECK-NO-LIFETIMES: nonescapable.h:44:33: error: template parameter 'S' expected to be a type parameter
122+
// CHECK: nonescapable.h:45:33: error: template parameter 'S' expected to be a type parameter
123+
// CHECK-NO-LIFETIMES: nonescapable.h:45:33: error: template parameter 'S' expected to be a type parameter
116124
j1()
117-
// CHECK: nonescapable.h:62:41: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
118-
// CHECK-NO-LIFETIMES: nonescapable.h:62:41: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
119-
j2()
120125
// CHECK: nonescapable.h:63:41: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
121126
// CHECK-NO-LIFETIMES: nonescapable.h:63:41: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
127+
j2()
128+
// CHECK: nonescapable.h:64:41: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
129+
// CHECK-NO-LIFETIMES: nonescapable.h:64:41: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
122130
j3()
123131
k1();
124-
// CHECK: nonescapable.h:69:15: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
125-
// CHECK-NO-LIFETIMES: nonescapable.h:69:15: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
132+
// CHECK: nonescapable.h:70:15: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
133+
// CHECK-NO-LIFETIMES: nonescapable.h:70:15: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
126134
k2();
127-
// CHECK: nonescapable.h:70:22: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
128-
// CHECK-NO-LIFETIMES: nonescapable.h:70:22: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
135+
// CHECK: nonescapable.h:71:22: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
136+
// CHECK-NO-LIFETIMES: nonescapable.h:71:22: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
129137
k3();
138+
l1();
139+
// CHECK: nonescapable.h:77:12: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
140+
// CHECK-NO-LIFETIMES: nonescapable.h:77:12: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
141+
l2();
130142
// CHECK-NOT: error
131143
// CHECK-NOT: warning
132144
return View()
133-
// CHECK-NO-LIFETIMES: nonescapable.h:4:5: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
134145
// CHECK-NO-LIFETIMES: nonescapable.h:5:5: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
146+
// CHECK-NO-LIFETIMES: nonescapable.h:6:5: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
135147
// CHECK-NO-LIFETIMES-NOT: error
136148
// CHECK-NO-LIFETIMES-NOT: warning
137149
}

0 commit comments

Comments
 (0)