Skip to content

Commit

Permalink
[Sema] Check availability in inlinable code using the explicit version
Browse files Browse the repository at this point in the history
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
  • Loading branch information
xymus committed Oct 9, 2020
1 parent 6d599ea commit e61ffea
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 22 deletions.
24 changes: 20 additions & 4 deletions include/swift/AST/TypeRefinementContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,23 @@ class TypeRefinementContext {

SourceRange SrcRange;

/// Runtime availability information for the code in this context.
AvailabilityContext AvailabilityInfo;

/// Runtime availability information as explicitly declared by attributes
/// for the inlinable code in this context. Compared to AvailabilityInfo,
/// this is not bounded to the minimum deployment OS version.
AvailabilityContext AvailabilityInfoExplicit;

std::vector<TypeRefinementContext *> Children;

TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
TypeRefinementContext *Parent, SourceRange SrcRange,
const AvailabilityContext &Info);
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit);

public:

/// Create the root refinement context for the given SourceFile.
static TypeRefinementContext *createRoot(SourceFile *SF,
const AvailabilityContext &Info);
Expand All @@ -172,8 +179,9 @@ class TypeRefinementContext {
static TypeRefinementContext *createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext *Parent,
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit,
SourceRange SrcRange);

/// Create a refinement context for the Then branch of the given IfStmt.
static TypeRefinementContext *
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
Expand Down Expand Up @@ -240,11 +248,19 @@ class TypeRefinementContext {
SourceRange getSourceRange() const { return SrcRange; }

/// Returns the information on what can be assumed present at run time when
/// running code contained in this context.
/// running code contained in this context, taking into account the minimum
/// deployment target.
const AvailabilityContext &getAvailabilityInfo() const {
return AvailabilityInfo;
}

/// Returns the information on what can be assumed present at run time when
/// running code contained in this context if it were to be inlined,
/// without considering the minimum deployment target.
const AvailabilityContext &getAvailabilityInfoExplicit() const {
return AvailabilityInfoExplicit;
}

/// Adds a child refinement context.
void addChild(TypeRefinementContext *Child) {
assert(Child->getSourceRange().isValid());
Expand Down
26 changes: 15 additions & 11 deletions lib/AST/TypeRefinementContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ using namespace swift;
TypeRefinementContext::TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
TypeRefinementContext *Parent,
SourceRange SrcRange,
const AvailabilityContext &Info)
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info) {
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit)
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info),
AvailabilityInfoExplicit(InfoExplicit) {
if (Parent) {
assert(SrcRange.isValid());
Parent->addChild(this);
assert(Info.isContainedIn(Parent->getAvailabilityInfo()));
assert(InfoExplicit.isContainedIn(Parent->getAvailabilityInfoExplicit()));
}
Ctx.addDestructorCleanup(Children);
}
Expand All @@ -46,18 +48,20 @@ TypeRefinementContext::createRoot(SourceFile *SF,
ASTContext &Ctx = SF->getASTContext();
return new (Ctx)
TypeRefinementContext(Ctx, SF,
/*Parent=*/nullptr, SourceRange(), Info);
/*Parent=*/nullptr, SourceRange(), Info,
AvailabilityContext::alwaysAvailable());
}

TypeRefinementContext *
TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext *Parent,
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit,
SourceRange SrcRange) {
assert(D);
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info);
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, InfoExplicit);
}

TypeRefinementContext *
Expand All @@ -68,7 +72,7 @@ TypeRefinementContext::createForIfStmtThen(ASTContext &Ctx, IfStmt *S,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/true), Parent,
S->getThenStmt()->getSourceRange(), Info);
S->getThenStmt()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -79,7 +83,7 @@ TypeRefinementContext::createForIfStmtElse(ASTContext &Ctx, IfStmt *S,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/false), Parent,
S->getElseStmt()->getSourceRange(), Info);
S->getElseStmt()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -92,7 +96,7 @@ TypeRefinementContext::createForConditionFollowingQuery(ASTContext &Ctx,
assert(Parent);
SourceRange Range(PAI->getEndLoc(), LastElement.getEndLoc());
return new (Ctx) TypeRefinementContext(Ctx, PAI, Parent, Range,
Info);
Info, Info);
}

TypeRefinementContext *
Expand All @@ -107,7 +111,7 @@ TypeRefinementContext::createForGuardStmtFallthrough(ASTContext &Ctx,
SourceRange Range(RS->getEndLoc(), ContainingBraceStmt->getEndLoc());
return new (Ctx) TypeRefinementContext(Ctx,
IntroNode(RS, /*IsFallthrough=*/true),
Parent, Range, Info);
Parent, Range, Info, Info);
}

TypeRefinementContext *
Expand All @@ -118,7 +122,7 @@ TypeRefinementContext::createForGuardStmtElse(ASTContext &Ctx, GuardStmt *RS,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(RS, /*IsFallthrough=*/false), Parent,
RS->getBody()->getSourceRange(), Info);
RS->getBody()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -128,7 +132,7 @@ TypeRefinementContext::createForWhileStmtBody(ASTContext &Ctx, WhileStmt *S,
assert(S);
assert(Parent);
return new (Ctx) TypeRefinementContext(
Ctx, S, Parent, S->getBody()->getSourceRange(), Info);
Ctx, S, Parent, S->getBody()->getSourceRange(), Info, Info);
}

// Only allow allocation of TypeRefinementContext using the allocator in
Expand Down
27 changes: 20 additions & 7 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,26 @@ class TypeRefinementContextBuilder : private ASTWalker {
// The potential versions in the declaration are constrained by both
// the declared availability of the declaration and the potential versions
// of its lexical context.
AvailabilityContext DeclInfo =
AvailabilityContext ExplicitDeclInfo =
swift::AvailabilityInference::availableRange(D, Context);
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
ExplicitDeclInfo.intersectWith(
getCurrentTRC()->getAvailabilityInfoExplicit());
AvailabilityContext DeclInfo = ExplicitDeclInfo;

// When the body is inlinable consider only the explicitly declared range
// for checking availability. Otherwise, use the parent range which may
// begin at the minimum deployment target.
bool isInlinable = D->getAttrs().hasAttribute<InlinableAttr>() ||
D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>();
if (!isInlinable)) {
DeclInfo.intersectWith(
getCurrentTRC()->getAvailabilityInfo());
}

TypeRefinementContext *NewTRC =
TypeRefinementContext::createForDecl(Context, D, getCurrentTRC(),
DeclInfo,
ExplicitDeclInfo,
refinementSourceRangeForDecl(D));

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

// No need to introduce a context if the declaration does not have an
// availability attribute.
if (!hasActiveAvailableAttribute(D, Context)) {
// availability or inlinable attribute.
if (!hasActiveAvailableAttribute(D, Context) &&
!D->getAttrs().hasAttribute<InlinableAttr>() &&
!D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>()) {
return false;
}

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

// We can assume we are running on at least the minimum deployment target.
auto OverApproximateContext =
AvailabilityContext::forDeploymentTarget(Context);
auto OverApproximateContext = AvailabilityContext::alwaysAvailable();
auto isInvalidLoc = [SF](SourceLoc loc) {
return SF ? loc.isInvalid() : true;
};
Expand Down
48 changes: 48 additions & 0 deletions test/Sema/availability_inlinable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// Inlinable functions should check availability by ignoring the current
/// deployment target as clients could inline the function in a lower target.

// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx11.0
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.10

// REQUIRES: OS=macosx

@available(macOS 10.10, *)
@inlinable
public func availMacOS10() {
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
// expected-note @-1 {{add 'if #available' version check}}

if #available(macOS 11.0, *) {
availMacOS11()
} else {
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
// expected-note @-1 {{add 'if #available' version check}}
}

if #available(macOS 10.15, *) {
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
// expected-note @-1 {{add 'if #available' version check}}
}

func nestedFunc() {
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
// expected-note @-1 {{add 'if #available' version check}}
}
}

@available(macOS 11.0, *)
public func availMacOS11() { }

@available(macOS 10.10, *)
public struct StructAvailMacOS10 {
@inlinable
public func availabilityFromTheContextInlinable() {
// expected-note @-1 {{add @available attribute to enclosing instance method}}
availMacOS11() // expected-error {{'availMacOS11()' is only available in macOS 11.0 or newer}}
// expected-note @-1 {{add 'if #available' version check}}

availabilityFromContext()
}

public func availabilityFromContext() {}
}

0 comments on commit e61ffea

Please sign in to comment.