Skip to content

Commit e61ffea

Browse files
committed
[Sema] Check availability in inlinable code using the explicit version
Inlinable functions can be inlined in clients with a lower OS target version than the framework defining the function. For this reason, the availability in inlinable functions should always be checked using the explicit introduction OS version as lower bound and not the minimum deployment version. rdar://problem/67975153
1 parent 6d599ea commit e61ffea

File tree

4 files changed

+103
-22
lines changed

4 files changed

+103
-22
lines changed

include/swift/AST/TypeRefinementContext.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,23 @@ class TypeRefinementContext {
154154

155155
SourceRange SrcRange;
156156

157+
/// Runtime availability information for the code in this context.
157158
AvailabilityContext AvailabilityInfo;
158159

160+
/// Runtime availability information as explicitly declared by attributes
161+
/// for the inlinable code in this context. Compared to AvailabilityInfo,
162+
/// this is not bounded to the minimum deployment OS version.
163+
AvailabilityContext AvailabilityInfoExplicit;
164+
159165
std::vector<TypeRefinementContext *> Children;
160166

161167
TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
162168
TypeRefinementContext *Parent, SourceRange SrcRange,
163-
const AvailabilityContext &Info);
169+
const AvailabilityContext &Info,
170+
const AvailabilityContext &InfoExplicit);
164171

165172
public:
166-
173+
167174
/// Create the root refinement context for the given SourceFile.
168175
static TypeRefinementContext *createRoot(SourceFile *SF,
169176
const AvailabilityContext &Info);
@@ -172,8 +179,9 @@ class TypeRefinementContext {
172179
static TypeRefinementContext *createForDecl(ASTContext &Ctx, Decl *D,
173180
TypeRefinementContext *Parent,
174181
const AvailabilityContext &Info,
182+
const AvailabilityContext &InfoExplicit,
175183
SourceRange SrcRange);
176-
184+
177185
/// Create a refinement context for the Then branch of the given IfStmt.
178186
static TypeRefinementContext *
179187
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
@@ -240,11 +248,19 @@ class TypeRefinementContext {
240248
SourceRange getSourceRange() const { return SrcRange; }
241249

242250
/// Returns the information on what can be assumed present at run time when
243-
/// running code contained in this context.
251+
/// running code contained in this context, taking into account the minimum
252+
/// deployment target.
244253
const AvailabilityContext &getAvailabilityInfo() const {
245254
return AvailabilityInfo;
246255
}
247256

257+
/// Returns the information on what can be assumed present at run time when
258+
/// running code contained in this context if it were to be inlined,
259+
/// without considering the minimum deployment target.
260+
const AvailabilityContext &getAvailabilityInfoExplicit() const {
261+
return AvailabilityInfoExplicit;
262+
}
263+
248264
/// Adds a child refinement context.
249265
void addChild(TypeRefinementContext *Child) {
250266
assert(Child->getSourceRange().isValid());

lib/AST/TypeRefinementContext.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ using namespace swift;
2828
TypeRefinementContext::TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
2929
TypeRefinementContext *Parent,
3030
SourceRange SrcRange,
31-
const AvailabilityContext &Info)
32-
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info) {
31+
const AvailabilityContext &Info,
32+
const AvailabilityContext &InfoExplicit)
33+
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info),
34+
AvailabilityInfoExplicit(InfoExplicit) {
3335
if (Parent) {
3436
assert(SrcRange.isValid());
3537
Parent->addChild(this);
36-
assert(Info.isContainedIn(Parent->getAvailabilityInfo()));
38+
assert(InfoExplicit.isContainedIn(Parent->getAvailabilityInfoExplicit()));
3739
}
3840
Ctx.addDestructorCleanup(Children);
3941
}
@@ -46,18 +48,20 @@ TypeRefinementContext::createRoot(SourceFile *SF,
4648
ASTContext &Ctx = SF->getASTContext();
4749
return new (Ctx)
4850
TypeRefinementContext(Ctx, SF,
49-
/*Parent=*/nullptr, SourceRange(), Info);
51+
/*Parent=*/nullptr, SourceRange(), Info,
52+
AvailabilityContext::alwaysAvailable());
5053
}
5154

5255
TypeRefinementContext *
5356
TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
5457
TypeRefinementContext *Parent,
5558
const AvailabilityContext &Info,
59+
const AvailabilityContext &InfoExplicit,
5660
SourceRange SrcRange) {
5761
assert(D);
5862
assert(Parent);
5963
return new (Ctx)
60-
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info);
64+
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, InfoExplicit);
6165
}
6266

6367
TypeRefinementContext *
@@ -68,7 +72,7 @@ TypeRefinementContext::createForIfStmtThen(ASTContext &Ctx, IfStmt *S,
6872
assert(Parent);
6973
return new (Ctx)
7074
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/true), Parent,
71-
S->getThenStmt()->getSourceRange(), Info);
75+
S->getThenStmt()->getSourceRange(), Info, Info);
7276
}
7377

7478
TypeRefinementContext *
@@ -79,7 +83,7 @@ TypeRefinementContext::createForIfStmtElse(ASTContext &Ctx, IfStmt *S,
7983
assert(Parent);
8084
return new (Ctx)
8185
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/false), Parent,
82-
S->getElseStmt()->getSourceRange(), Info);
86+
S->getElseStmt()->getSourceRange(), Info, Info);
8387
}
8488

8589
TypeRefinementContext *
@@ -92,7 +96,7 @@ TypeRefinementContext::createForConditionFollowingQuery(ASTContext &Ctx,
9296
assert(Parent);
9397
SourceRange Range(PAI->getEndLoc(), LastElement.getEndLoc());
9498
return new (Ctx) TypeRefinementContext(Ctx, PAI, Parent, Range,
95-
Info);
99+
Info, Info);
96100
}
97101

98102
TypeRefinementContext *
@@ -107,7 +111,7 @@ TypeRefinementContext::createForGuardStmtFallthrough(ASTContext &Ctx,
107111
SourceRange Range(RS->getEndLoc(), ContainingBraceStmt->getEndLoc());
108112
return new (Ctx) TypeRefinementContext(Ctx,
109113
IntroNode(RS, /*IsFallthrough=*/true),
110-
Parent, Range, Info);
114+
Parent, Range, Info, Info);
111115
}
112116

113117
TypeRefinementContext *
@@ -118,7 +122,7 @@ TypeRefinementContext::createForGuardStmtElse(ASTContext &Ctx, GuardStmt *RS,
118122
assert(Parent);
119123
return new (Ctx)
120124
TypeRefinementContext(Ctx, IntroNode(RS, /*IsFallthrough=*/false), Parent,
121-
RS->getBody()->getSourceRange(), Info);
125+
RS->getBody()->getSourceRange(), Info, Info);
122126
}
123127

124128
TypeRefinementContext *
@@ -128,7 +132,7 @@ TypeRefinementContext::createForWhileStmtBody(ASTContext &Ctx, WhileStmt *S,
128132
assert(S);
129133
assert(Parent);
130134
return new (Ctx) TypeRefinementContext(
131-
Ctx, S, Parent, S->getBody()->getSourceRange(), Info);
135+
Ctx, S, Parent, S->getBody()->getSourceRange(), Info, Info);
132136
}
133137

134138
// Only allow allocation of TypeRefinementContext using the allocator in

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,26 @@ class TypeRefinementContextBuilder : private ASTWalker {
170170
// The potential versions in the declaration are constrained by both
171171
// the declared availability of the declaration and the potential versions
172172
// of its lexical context.
173-
AvailabilityContext DeclInfo =
173+
AvailabilityContext ExplicitDeclInfo =
174174
swift::AvailabilityInference::availableRange(D, Context);
175-
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
175+
ExplicitDeclInfo.intersectWith(
176+
getCurrentTRC()->getAvailabilityInfoExplicit());
177+
AvailabilityContext DeclInfo = ExplicitDeclInfo;
178+
179+
// When the body is inlinable consider only the explicitly declared range
180+
// for checking availability. Otherwise, use the parent range which may
181+
// begin at the minimum deployment target.
182+
bool isInlinable = D->getAttrs().hasAttribute<InlinableAttr>() ||
183+
D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>();
184+
if (!isInlinable)) {
185+
DeclInfo.intersectWith(
186+
getCurrentTRC()->getAvailabilityInfo());
187+
}
176188

177189
TypeRefinementContext *NewTRC =
178190
TypeRefinementContext::createForDecl(Context, D, getCurrentTRC(),
179191
DeclInfo,
192+
ExplicitDeclInfo,
180193
refinementSourceRangeForDecl(D));
181194

182195
// Record the TRC for this storage declaration so that
@@ -198,8 +211,10 @@ class TypeRefinementContextBuilder : private ASTWalker {
198211
}
199212

200213
// No need to introduce a context if the declaration does not have an
201-
// availability attribute.
202-
if (!hasActiveAvailableAttribute(D, Context)) {
214+
// availability or inlinable attribute.
215+
if (!hasActiveAvailableAttribute(D, Context) &&
216+
!D->getAttrs().hasAttribute<InlinableAttr>() &&
217+
!D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>()) {
203218
return false;
204219
}
205220

@@ -674,9 +689,7 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
674689
// refined. For now, this is fine -- but if we ever synthesize #available(),
675690
// this will be a real problem.
676691

677-
// We can assume we are running on at least the minimum deployment target.
678-
auto OverApproximateContext =
679-
AvailabilityContext::forDeploymentTarget(Context);
692+
auto OverApproximateContext = AvailabilityContext::alwaysAvailable();
680693
auto isInvalidLoc = [SF](SourceLoc loc) {
681694
return SF ? loc.isInvalid() : true;
682695
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/// Inlinable functions should check availability by ignoring the current
2+
/// deployment target as clients could inline the function in a lower target.
3+
4+
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx11.0
5+
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.10
6+
7+
// REQUIRES: OS=macosx
8+
9+
@available(macOS 10.10, *)
10+
@inlinable
11+
public func availMacOS10() {
12+
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
13+
// expected-note @-1 {{add 'if #available' version check}}
14+
15+
if #available(macOS 11.0, *) {
16+
availMacOS11()
17+
} else {
18+
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
19+
// expected-note @-1 {{add 'if #available' version check}}
20+
}
21+
22+
if #available(macOS 10.15, *) {
23+
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
24+
// expected-note @-1 {{add 'if #available' version check}}
25+
}
26+
27+
func nestedFunc() {
28+
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
29+
// expected-note @-1 {{add 'if #available' version check}}
30+
}
31+
}
32+
33+
@available(macOS 11.0, *)
34+
public func availMacOS11() { }
35+
36+
@available(macOS 10.10, *)
37+
public struct StructAvailMacOS10 {
38+
@inlinable
39+
public func availabilityFromTheContextInlinable() {
40+
// expected-note @-1 {{add @available attribute to enclosing instance method}}
41+
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
42+
// expected-note @-1 {{add 'if #available' version check}}
43+
44+
availabilityFromContext()
45+
}
46+
47+
public func availabilityFromContext() {}
48+
}

0 commit comments

Comments
 (0)