Skip to content

Commit c678049

Browse files
beccadaxtshortli
authored andcommitted
Add resilience boundary around non-inlinable bodies
Previously, the availability checker has assumed that no code in a module is available on less than the minimum deployment target. In modules using library evolution, this is not actually true--certain function bodies can be inlined into modules with lower minimum deployment targets, where they can run against versions of the library that had lower minimum deployment targets. By failing to check for availability violations in these function bodies that relate to versions below the minimum deployment target, we can end up allowing inlinable code that doesn't compile with the correct runtime linkage or has other serious problems. This commit lowers the root type refinement context's avialability to the value of the -target-min-inlining-version option (if provided) and then raises it again inside the bodies of functions whose implementations are not exposed to clients. This introduces some incorrect typechecking of declaration signatures, but we'll fix that in another commit.
1 parent c80a19e commit c678049

File tree

6 files changed

+600
-11
lines changed

6 files changed

+600
-11
lines changed

include/swift/AST/Availability.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ class AvailabilityContext {
217217
/// deployment target.
218218
static AvailabilityContext forDeploymentTarget(ASTContext &Ctx);
219219

220+
/// Creates a context that imposes the constraints of the ASTContext's
221+
/// inlining target (i.e. minimum inlining version).
222+
static AvailabilityContext forInliningTarget(ASTContext &Ctx);
223+
220224
/// Creates a context that imposes no constraints.
221225
///
222226
/// \see isAlwaysAvailable

include/swift/AST/TypeRefinementContext.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
5858
/// function declaration or the contents of a class declaration).
5959
Decl,
6060

61+
/// The context was introduced by a resilience boundary; that is, we are in
62+
/// a module with library evolution enabled and the parent context's
63+
/// contents can be visible to the module's clients, but this context's
64+
/// contents are not.
65+
ResilienceBoundary,
66+
6167
/// The context was introduced for the Then branch of an IfStmt.
6268
IfStmtThenBranch,
6369

@@ -102,7 +108,10 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
102108

103109
public:
104110
IntroNode(SourceFile *SF) : IntroReason(Reason::Root), SF(SF) {}
105-
IntroNode(Decl *D) : IntroReason(Reason::Decl), D(D) {}
111+
IntroNode(Decl *D, Reason introReason = Reason::Decl)
112+
: IntroReason(introReason), D(D) {
113+
(void)getAsDecl(); // check that assertion succeeds
114+
}
106115
IntroNode(IfStmt *IS, bool IsThen) :
107116
IntroReason(IsThen ? Reason::IfStmtThenBranch : Reason::IfStmtElseBranch),
108117
IS(IS) {}
@@ -122,7 +131,8 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
122131
}
123132

124133
Decl *getAsDecl() const {
125-
assert(IntroReason == Reason::Decl);
134+
assert(IntroReason == Reason::Decl ||
135+
IntroReason == Reason::ResilienceBoundary);
126136
return D;
127137
}
128138

@@ -182,7 +192,14 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
182192
const AvailabilityContext &Info,
183193
const AvailabilityContext &ExplicitInfo,
184194
SourceRange SrcRange);
185-
195+
196+
/// Create a refinement context for the given declaration.
197+
static TypeRefinementContext *
198+
createForResilienceBoundary(ASTContext &Ctx, Decl *D,
199+
TypeRefinementContext *Parent,
200+
const AvailabilityContext &Info,
201+
SourceRange SrcRange);
202+
186203
/// Create a refinement context for the Then branch of the given IfStmt.
187204
static TypeRefinementContext *
188205
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,

lib/AST/Availability.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ AvailabilityContext AvailabilityContext::forDeploymentTarget(ASTContext &Ctx) {
3030
VersionRange::allGTE(Ctx.LangOpts.getMinPlatformVersion()));
3131
}
3232

33+
AvailabilityContext AvailabilityContext::forInliningTarget(ASTContext &Ctx) {
34+
return AvailabilityContext(
35+
VersionRange::allGTE(Ctx.LangOpts.MinimumInliningTargetVersion));
36+
}
37+
3338
namespace {
3439

3540
/// The inferred availability required to access a group of declarations

lib/AST/TypeRefinementContext.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
6464
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, ExplicitInfo);
6565
}
6666

67+
TypeRefinementContext *
68+
TypeRefinementContext::createForResilienceBoundary(ASTContext &Ctx, Decl *D,
69+
TypeRefinementContext *Parent,
70+
const AvailabilityContext &Info,
71+
SourceRange SrcRange) {
72+
assert(D);
73+
assert(Parent);
74+
return new (Ctx)
75+
TypeRefinementContext(Ctx, IntroNode(D, Reason::ResilienceBoundary),
76+
Parent, SrcRange, Info,
77+
AvailabilityContext::alwaysAvailable());
78+
79+
}
80+
6781
TypeRefinementContext *
6882
TypeRefinementContext::createForIfStmtThen(ASTContext &Ctx, IfStmt *S,
6983
TypeRefinementContext *Parent,
@@ -170,6 +184,7 @@ void TypeRefinementContext::dump(raw_ostream &OS, SourceManager &SrcMgr) const {
170184
SourceLoc TypeRefinementContext::getIntroductionLoc() const {
171185
switch (getReason()) {
172186
case Reason::Decl:
187+
case Reason::ResilienceBoundary:
173188
return Node.getAsDecl()->getLoc();
174189

175190
case Reason::IfStmtThenBranch:
@@ -283,6 +298,7 @@ TypeRefinementContext::getAvailabilityConditionVersionSourceRange(
283298
Node.getAsWhileStmt()->getCond(), Platform, Version);
284299

285300
case Reason::Root:
301+
case Reason::ResilienceBoundary:
286302
return SourceRange();
287303
}
288304

@@ -296,13 +312,16 @@ void TypeRefinementContext::print(raw_ostream &OS, SourceManager &SrcMgr,
296312

297313
OS << " versions=" << AvailabilityInfo.getOSVersion().getAsString();
298314

299-
if (getReason() == Reason::Decl) {
315+
if (getReason() == Reason::Decl
316+
|| getReason() == Reason::ResilienceBoundary) {
300317
Decl *D = Node.getAsDecl();
301318
OS << " decl=";
302319
if (auto VD = dyn_cast<ValueDecl>(D)) {
303320
OS << VD->getName();
304321
} else if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
305322
OS << "extension." << ED->getExtendedType().getString();
323+
} else if (isa<TopLevelCodeDecl>(D)) {
324+
OS << "<top-level-code>";
306325
}
307326
}
308327

@@ -336,6 +355,9 @@ StringRef TypeRefinementContext::getReasonName(Reason R) {
336355
case Reason::Decl:
337356
return "decl";
338357

358+
case Reason::ResilienceBoundary:
359+
return "resilience_boundary";
360+
339361
case Reason::IfStmtThenBranch:
340362
return "if_then";
341363

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ ExportContext ExportContext::forDeclSignature(Decl *D) {
181181
auto runningOSVersion =
182182
(Ctx.LangOpts.DisableAvailabilityChecking
183183
? AvailabilityContext::alwaysAvailable()
184-
: TypeChecker::overApproximateAvailabilityAtLocation(D->getEndLoc(), DC));
184+
: TypeChecker::overApproximateAvailabilityAtLocation(D->getLoc(), DC));
185185
bool spi = Ctx.LangOpts.LibraryLevel == LibraryLevel::SPI;
186186
bool implicit = false;
187187
bool deprecated = false;
@@ -288,6 +288,27 @@ static bool hasActiveAvailableAttribute(Decl *D,
288288
return getActiveAvailableAttribute(D, AC);
289289
}
290290

291+
static bool bodyIsResilienceBoundary(Decl *D) {
292+
// The declaration contains code...
293+
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
294+
// And it has a location so we can check it...
295+
if (!afd->isImplicit() && afd->getBodySourceRange().isValid()) {
296+
// And the code is within our resilience domain, so it should be
297+
// compiled with the minimum deployment target, not the minimum inlining
298+
// target.
299+
return afd->getResilienceExpansion() != ResilienceExpansion::Minimal;
300+
}
301+
}
302+
303+
return false;
304+
}
305+
306+
static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC,
307+
ASTContext &ctx) {
308+
return TRC->getAvailabilityInfo()
309+
.isContainedIn(AvailabilityContext::forDeploymentTarget(ctx));
310+
}
311+
291312
namespace {
292313

293314
/// A class to walk the AST to build the type refinement context hierarchy.
@@ -302,6 +323,8 @@ class TypeRefinementContextBuilder : private ASTWalker {
302323
/// indicating that custom logic elsewhere will handle removing
303324
/// the context when needed.
304325
ParentTy ScopeNode;
326+
327+
bool ContainedByDeploymentTarget;
305328
};
306329

307330
std::vector<ContextInfo> ContextStack;
@@ -318,10 +341,24 @@ class TypeRefinementContextBuilder : private ASTWalker {
318341
return ContextStack.back().TRC;
319342
}
320343

344+
bool isCurrentTRCContainedByDeploymentTarget() {
345+
return ContextStack.back().ContainedByDeploymentTarget;
346+
}
347+
321348
void pushContext(TypeRefinementContext *TRC, ParentTy PopAfterNode) {
322349
ContextInfo Info;
323350
Info.TRC = TRC;
324351
Info.ScopeNode = PopAfterNode;
352+
353+
if (!ContextStack.empty() && isCurrentTRCContainedByDeploymentTarget()) {
354+
assert(computeContainedByDeploymentTarget(TRC, Context) &&
355+
"incorrectly skipping computeContainedByDeploymentTarget()");
356+
Info.ContainedByDeploymentTarget = true;
357+
} else {
358+
Info.ContainedByDeploymentTarget =
359+
computeContainedByDeploymentTarget(TRC, Context);
360+
}
361+
325362
ContextStack.push_back(Info);
326363
}
327364

@@ -367,11 +404,16 @@ class TypeRefinementContextBuilder : private ASTWalker {
367404
pushContext(DeclTRC, D);
368405
}
369406

407+
// Adds in a TRC that covers only the body of the declaration.
408+
if (auto BodyTRC = getNewContextForBodyOfDecl(D)) {
409+
pushContext(BodyTRC, D);
410+
}
411+
370412
return true;
371413
}
372414

373415
bool walkToDeclPost(Decl *D) override {
374-
// As seen above, we could have up to two TRCs in the stack for a single
416+
// As seen above, we could have up to three TRCs in the stack for a single
375417
// declaration.
376418
while (ContextStack.back().ScopeNode.getAsDecl() == D) {
377419
ContextStack.pop_back();
@@ -510,6 +552,39 @@ class TypeRefinementContextBuilder : private ASTWalker {
510552
return D->getSourceRange();
511553
}
512554

555+
TypeRefinementContext *getNewContextForBodyOfDecl(Decl *D) {
556+
if (bodyIntroducesNewContext(D))
557+
return buildBodyRefinementContext(D);
558+
559+
return nullptr;
560+
}
561+
562+
bool bodyIntroducesNewContext(Decl *D) {
563+
// Are we already effectively in a resilience boundary? If not, adding one
564+
// wouldn't change availability.
565+
if (isCurrentTRCContainedByDeploymentTarget())
566+
return false;
567+
568+
// If we're in a function, is its body a resilience boundary?
569+
if (auto afd = dyn_cast<AbstractFunctionDecl>(D))
570+
return bodyIsResilienceBoundary(afd);
571+
572+
return false;
573+
}
574+
575+
TypeRefinementContext *buildBodyRefinementContext(Decl *D) {
576+
auto afd = cast<AbstractFunctionDecl>(D);
577+
SourceRange range = afd->getBodySourceRange();
578+
579+
AvailabilityContext DeploymentTargetInfo =
580+
AvailabilityContext::forDeploymentTarget(Context);
581+
DeploymentTargetInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
582+
583+
return TypeRefinementContext::createForResilienceBoundary(
584+
Context, D, getCurrentTRC(),
585+
DeploymentTargetInfo, range);
586+
}
587+
513588
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
514589
if (auto *IS = dyn_cast<IfStmt>(S)) {
515590
buildIfStmtRefinementContext(IS);
@@ -927,8 +1002,8 @@ void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
9271002
if (!RootTRC) {
9281003
// The root type refinement context reflects the fact that all parts of
9291004
// the source file are guaranteed to be executing on at least the minimum
930-
// platform version.
931-
auto MinPlatformReq = AvailabilityContext::forDeploymentTarget(Context);
1005+
// platform version for inlining.
1006+
auto MinPlatformReq = AvailabilityContext::forInliningTarget(Context);
9321007
RootTRC = TypeRefinementContext::createRoot(&SF, MinPlatformReq);
9331008
SF.setTypeRefinementContext(RootTRC);
9341009
}
@@ -993,9 +1068,8 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
9931068
// refined. For now, this is fine -- but if we ever synthesize #available(),
9941069
// this will be a real problem.
9951070

996-
// We can assume we are running on at least the minimum deployment target.
997-
auto OverApproximateContext =
998-
AvailabilityContext::forDeploymentTarget(Context);
1071+
// We can assume we are running on at least the minimum inlining target.
1072+
auto OverApproximateContext = AvailabilityContext::forInliningTarget(Context);
9991073
auto isInvalidLoc = [SF](SourceLoc loc) {
10001074
return SF ? loc.isInvalid() : true;
10011075
};

0 commit comments

Comments
 (0)