Skip to content

Commit de3dc77

Browse files
committed
[Sema] Catch use-before-declarations in nested closures
Previously we would allow these in Sema and diagnose them in SILGen, but allowing them in Sema is unsound because it means the constraint system ends up kicking interface type requests for declarations that should be type-checked as part of the closure itself. Adjust the name lookup logic to look through parent closures when detecting invalid forward references. For now we don't fallback to an outer result on encountering a use-before-declaration to preserve the current behavior. I'm planning on changing that in the next commit though.
4 parents a0d33c3 + 55ebd07 + 62f0926 + 05e3d15 commit de3dc77

38 files changed

+276
-128
lines changed

include/swift/AST/Decl.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6725,13 +6725,18 @@ class VarDecl : public AbstractStorageDecl {
67256725
Bits.VarDecl.IsDebuggerVar = IsDebuggerVar;
67266726
}
67276727

6728-
/// Visit all auxiliary declarations to this VarDecl.
6728+
/// Visit all auxiliary variables for this VarDecl.
67296729
///
6730-
/// An auxiliary declaration is a declaration synthesized by the compiler to support
6731-
/// this VarDecl, such as synthesized property wrapper variables.
6730+
/// An auxiliary variable is one that is synthesized by the compiler to
6731+
/// support this VarDecl, such as synthesized property wrapper variables.
67326732
///
6733-
/// \note this function only visits auxiliary decls that are not part of the AST.
6734-
void visitAuxiliaryDecls(llvm::function_ref<void(VarDecl *)>) const;
6733+
/// \param forNameLookup If \c true, will only visit auxiliary variables that
6734+
/// may appear in name lookup results.
6735+
///
6736+
/// \note this function only visits auxiliary variables that are not part of
6737+
/// the AST.
6738+
void visitAuxiliaryVars(bool forNameLookup,
6739+
llvm::function_ref<void(VarDecl *)>) const;
67356740

67366741
/// Is this the synthesized storage for a 'lazy' property?
67376742
bool isLazyStorageProperty() const {

include/swift/AST/Expr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
506506
/// walk into the body of it (unless it is single-expression).
507507
void forEachChildExpr(llvm::function_ref<Expr *(Expr *)> callback);
508508

509+
/// Apply the specified function to all variables referenced in any
510+
/// child UnresolvedPatternExprs.
511+
void forEachUnresolvedVariable(llvm::function_ref<void(VarDecl *)> f) const;
512+
509513
/// Determine whether this expression refers to a type by name.
510514
///
511515
/// This distinguishes static references to types, like Int, from metatype

include/swift/AST/Pattern.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class alignas(8) Pattern : public ASTAllocated<Pattern> {
192192
/// Collect the set of variables referenced in the given pattern.
193193
void collectVariables(SmallVectorImpl<VarDecl *> &variables) const;
194194

195-
/// apply the specified function to all variables referenced in this
195+
/// Apply the specified function to all variables referenced in this
196196
/// pattern.
197197
void forEachVariable(llvm::function_ref<void(VarDecl *)> f) const;
198198

include/swift/Sema/ConstraintSystem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,6 +2131,7 @@ class SyntacticElementTargetRewriter {
21312131

21322132
virtual void addLocalDeclToTypeCheck(Decl *D) = 0;
21332133

2134+
[[nodiscard]]
21342135
virtual std::optional<SyntacticElementTarget>
21352136
rewriteTarget(SyntacticElementTarget target) = 0;
21362137

lib/AST/Decl.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ void Decl::visitAuxiliaryDecls(
507507
}
508508
}
509509

510-
// FIXME: fold VarDecl::visitAuxiliaryDecls into this.
510+
// FIXME: fold VarDecl::visitAuxiliaryVars into this.
511511
}
512512

513513
void Decl::forEachAttachedMacro(MacroRole role,
@@ -8767,14 +8767,22 @@ bool VarDecl::hasStorageOrWrapsStorage() const {
87678767
return false;
87688768
}
87698769

8770-
void VarDecl::visitAuxiliaryDecls(llvm::function_ref<void(VarDecl *)> visit) const {
8770+
void VarDecl::visitAuxiliaryVars(
8771+
bool forNameLookup, llvm::function_ref<void(VarDecl *)> visit) const {
87718772
if (getDeclContext()->isTypeContext() ||
87728773
(isImplicit() && !isa<ParamDecl>(this)))
87738774
return;
87748775

8775-
if (getAttrs().hasAttribute<LazyAttr>()) {
8776-
if (auto *backingVar = getLazyStorageProperty())
8777-
visit(backingVar);
8776+
// The backing storage for a lazy property is not accessible to name lookup.
8777+
// This is not only a matter of hiding implementation details, but also
8778+
// correctness since triggering LazyStoragePropertyRequest currently eagerly
8779+
// requests the interface type for the var, which could result in incorrectly
8780+
// type-checking a lazy local independently of its surrounding closure.
8781+
if (!forNameLookup) {
8782+
if (getAttrs().hasAttribute<LazyAttr>()) {
8783+
if (auto *backingVar = getLazyStorageProperty())
8784+
visit(backingVar);
8785+
}
87788786
}
87798787

87808788
if (getAttrs().hasAttribute<CustomAttr>() || hasImplicitPropertyWrapper()) {

lib/AST/Pattern.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ namespace {
192192
const std::function<void(VarDecl*)> &fn;
193193
public:
194194

195-
WalkToVarDecls(const std::function<void(VarDecl*)> &fn)
196-
: fn(fn) {}
195+
WalkToVarDecls(const std::function<void(VarDecl*)> &fn) : fn(fn) {}
197196

198197
/// Walk everything that's available; there shouldn't be macro expansions
199198
/// that matter anyway.
@@ -239,6 +238,9 @@ namespace {
239238
};
240239
} // end anonymous namespace
241240

241+
void Expr::forEachUnresolvedVariable(llvm::function_ref<void(VarDecl *)> f) const {
242+
const_cast<Expr *>(this)->walk(WalkToVarDecls(f));
243+
}
242244

243245
/// apply the specified function to all variables referenced in this
244246
/// pattern.

lib/AST/UnqualifiedLookup.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,13 @@ bool ASTScopeDeclConsumerForUnqualifiedLookup::consume(
708708
bool foundMatch = false;
709709
if (auto *varDecl = dyn_cast<VarDecl>(value)) {
710710
// Check if the name matches any auxiliary decls not in the AST
711-
varDecl->visitAuxiliaryDecls([&](VarDecl *auxiliaryVar) {
712-
if (auxiliaryVar->ValueDecl::getName().matchesRef(fullName)) {
713-
value = auxiliaryVar;
714-
foundMatch = true;
715-
}
716-
});
711+
varDecl->visitAuxiliaryVars(
712+
/*forNameLookup*/ true, [&](VarDecl *auxiliaryVar) {
713+
if (auxiliaryVar->ValueDecl::getName().matchesRef(fullName)) {
714+
value = auxiliaryVar;
715+
foundMatch = true;
716+
}
717+
});
717718
}
718719

719720
if (!foundMatch)
@@ -918,13 +919,14 @@ class ASTScopeDeclConsumerForLocalLookup
918919
bool foundMatch = false;
919920
if (auto *varDecl = dyn_cast<VarDecl>(value)) {
920921
// Check if the name matches any auxiliary decls not in the AST
921-
varDecl->visitAuxiliaryDecls([&](VarDecl *auxiliaryVar) {
922-
if (name.isSimpleName(auxiliaryVar->getName())
923-
&& hasCorrectABIRole(auxiliaryVar)) {
924-
results.push_back(auxiliaryVar);
925-
foundMatch = true;
926-
}
927-
});
922+
varDecl->visitAuxiliaryVars(
923+
/*forNameLookup*/ true, [&](VarDecl *auxiliaryVar) {
924+
if (name.isSimpleName(auxiliaryVar->getName()) &&
925+
hasCorrectABIRole(auxiliaryVar)) {
926+
results.push_back(auxiliaryVar);
927+
foundMatch = true;
928+
}
929+
});
928930
}
929931

930932
if (!foundMatch && value->getName().matchesRef(name)

lib/SILGen/SILGenDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,7 @@ void SILGenFunction::visitVarDecl(VarDecl *D) {
17161716
}
17171717

17181718
// Emit lazy and property wrapper backing storage.
1719-
D->visitAuxiliaryDecls([&](VarDecl *var) {
1719+
D->visitAuxiliaryVars(/*forNameLookup*/ false, [&](VarDecl *var) {
17201720
if (auto *patternBinding = var->getParentPatternBinding())
17211721
visitPatternBindingDecl(patternBinding);
17221722

lib/SILGen/SILGenProlog.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ class ArgumentInitHelper {
10641064
void emitParam(ParamDecl *PD) {
10651065
// Register any auxiliary declarations for the parameter to be
10661066
// visited later.
1067-
PD->visitAuxiliaryDecls([&](VarDecl *localVar) {
1067+
PD->visitAuxiliaryVars(/*forNameLookup*/ false, [&](VarDecl *localVar) {
10681068
SGF.LocalAuxiliaryDecls.push_back(localVar);
10691069
});
10701070

lib/Sema/CSApply.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8949,7 +8949,8 @@ namespace {
89498949
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
89508950
// For closures, update the parameter types and check the body.
89518951
if (auto closure = dyn_cast<ClosureExpr>(expr)) {
8952-
rewriteFunction(closure);
8952+
if (rewriteFunction(closure))
8953+
return Action::Stop();
89538954

89548955
if (AnyFunctionRef(closure).hasExternalPropertyWrapperParameters()) {
89558956
auto *thunkTy = Rewriter.cs.getType(closure)->castTo<FunctionType>();
@@ -8962,19 +8963,22 @@ namespace {
89628963
}
89638964

89648965
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(expr)) {
8965-
rewriteSingleValueStmtExpr(SVE);
8966+
if (rewriteSingleValueStmtExpr(SVE))
8967+
return Action::Stop();
89668968
return Action::SkipNode(SVE);
89678969
}
89688970

89698971
if (auto tap = dyn_cast_or_null<TapExpr>(expr)) {
8970-
rewriteTapExpr(tap);
8972+
if (rewriteTapExpr(tap))
8973+
return Action::Stop();
89718974
return Action::SkipNode(tap);
89728975
}
89738976

89748977
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
89758978
// Rewrite captures.
89768979
for (const auto &capture : captureList->getCaptureList()) {
8977-
(void)rewriteTarget(SyntacticElementTarget(capture.PBD));
8980+
if (!rewriteTarget(SyntacticElementTarget(capture.PBD)))
8981+
return Action::Stop();
89788982
}
89798983
}
89808984

@@ -9000,31 +9004,36 @@ namespace {
90009004
return Action::SkipNode();
90019005
}
90029006

9003-
NullablePtr<Pattern>
9004-
rewritePattern(Pattern *pattern, DeclContext *DC);
9007+
[[nodiscard]]
9008+
NullablePtr<Pattern> rewritePattern(Pattern *pattern, DeclContext *DC);
90059009

90069010
/// Rewrite the target, producing a new target.
9011+
[[nodiscard]]
90079012
std::optional<SyntacticElementTarget>
90089013
rewriteTarget(SyntacticElementTarget target) override;
90099014

90109015
/// Rewrite the function for the given solution.
90119016
///
90129017
/// \returns true if an error occurred.
9018+
[[nodiscard]]
90139019
bool rewriteFunction(AnyFunctionRef fn) {
90149020
return Rewriter.cs.applySolution(fn, *this);
90159021
}
90169022

9023+
[[nodiscard]]
90179024
bool rewriteSingleValueStmtExpr(SingleValueStmtExpr *SVE) {
90189025
return Rewriter.cs.applySolutionToSingleValueStmt(SVE, *this);
90199026
}
90209027

9021-
void rewriteTapExpr(TapExpr *tap) {
9028+
[[nodiscard]]
9029+
bool rewriteTapExpr(TapExpr *tap) {
90229030
// First, let's visit the tap expression itself
90239031
// and set all of the inferred types.
9024-
Rewriter.visitTapExpr(tap);
9032+
if (!Rewriter.visitTapExpr(tap))
9033+
return true;
90259034

90269035
// Now, let's apply solution to the body
9027-
(void)Rewriter.cs.applySolutionToBody(tap, *this);
9036+
return Rewriter.cs.applySolutionToBody(tap, *this);
90289037
}
90299038
};
90309039
} // end anonymous namespace

0 commit comments

Comments
 (0)