Skip to content

Commit fb7f2d0

Browse files
committed
[CS] Limit the number of chained @dynamicMemberLookup lookups
Set an upper bound on the number of chained lookups we attempt to avoid spinning while trying to recursively apply the same dynamic member lookup to itself. rdar://157288911
1 parent 1f4cca6 commit fb7f2d0

File tree

15 files changed

+156
-23
lines changed

15 files changed

+156
-23
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,11 @@ ERROR(dynamic_member_lookup_candidate_inaccessible,none,
17351735
"enclosing type",
17361736
(ValueDecl *))
17371737

1738+
ERROR(too_many_dynamic_member_lookups,none,
1739+
"could not find member %0; exceeded the maximum number of nested "
1740+
"dynamic member lookups",
1741+
(DeclNameRef))
1742+
17381743
ERROR(string_index_not_integer,none,
17391744
"String must not be indexed with %0, it has variable size elements",
17401745
(Type))

include/swift/Basic/LangOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,10 @@ namespace swift {
952952
/// (It's arbitrary, but will keep the compiler from taking too much time.)
953953
unsigned SwitchCheckingInvocationThreshold = 200000;
954954

955+
/// The maximum number of `@dynamicMemberLookup`s that can be chained to
956+
/// resolve a member reference.
957+
unsigned DynamicMemberLookupDepthLimit = 100;
958+
955959
/// If true, the time it takes to type-check each function will be dumped
956960
/// to llvm::errs().
957961
bool DebugTimeFunctionBodies = false;

include/swift/Option/FrontendOptions.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,12 @@ def disable_invalid_ephemeralness_as_error :
895895
def switch_checking_invocation_threshold_EQ : Joined<["-"],
896896
"switch-checking-invocation-threshold=">;
897897

898+
def dynamic_member_lookup_depth_limit_EQ
899+
: Joined<["-"], "dynamic-member-lookup-depth-limit=">,
900+
HelpText<
901+
"The maximum number of dynamic member lookups that can be chained "
902+
"to resolve a member reference">;
903+
898904
def enable_new_operator_lookup :
899905
Flag<["-"], "enable-new-operator-lookup">,
900906
HelpText<"Enable the new operator decl and precedencegroup lookup behavior">;

include/swift/Sema/CSFix.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,9 @@ enum class FixKind : uint8_t {
488488
/// the type it's attempting to bind to.
489489
AllowInlineArrayLiteralCountMismatch,
490490

491+
/// Reached the limit of @dynamicMemberLookup depth.
492+
TooManyDynamicMemberLookups,
493+
491494
/// Ignore that a conformance is isolated but is not allowed to be.
492495
IgnoreIsolatedConformance,
493496
};
@@ -3881,6 +3884,33 @@ class AllowInlineArrayLiteralCountMismatch final : public ConstraintFix {
38813884
}
38823885
};
38833886

3887+
class TooManyDynamicMemberLookups : public ConstraintFix {
3888+
DeclNameRef Name;
3889+
3890+
TooManyDynamicMemberLookups(ConstraintSystem &cs, DeclNameRef name,
3891+
ConstraintLocator *locator)
3892+
: ConstraintFix(cs, FixKind::TooManyDynamicMemberLookups, locator),
3893+
Name(name) {}
3894+
3895+
public:
3896+
std::string getName() const override {
3897+
return "too many dynamic member lookups";
3898+
}
3899+
3900+
bool diagnose(const Solution &solution, bool asNote = false) const override;
3901+
3902+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
3903+
return diagnose(*commonFixes.front().first);
3904+
}
3905+
3906+
static TooManyDynamicMemberLookups *
3907+
create(ConstraintSystem &cs, DeclNameRef name, ConstraintLocator *locator);
3908+
3909+
static bool classof(const ConstraintFix *fix) {
3910+
return fix->getKind() == FixKind::TooManyDynamicMemberLookups;
3911+
}
3912+
};
3913+
38843914
class IgnoreIsolatedConformance : public ConstraintFix {
38853915
ProtocolConformance *conformance;
38863916

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,8 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args,
18971897
Opts.WarnLongExpressionTypeChecking);
18981898
setUnsignedIntegerArgument(OPT_solver_expression_time_threshold_EQ,
18991899
Opts.ExpressionTimeoutThreshold);
1900+
setUnsignedIntegerArgument(OPT_dynamic_member_lookup_depth_limit_EQ,
1901+
Opts.DynamicMemberLookupDepthLimit);
19001902
setUnsignedIntegerArgument(OPT_switch_checking_invocation_threshold_EQ,
19011903
Opts.SwitchCheckingInvocationThreshold);
19021904
setUnsignedIntegerArgument(OPT_debug_constraints_attempt,

lib/Sema/CSDiagnostics.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9628,6 +9628,12 @@ bool IncorrectInlineArrayLiteralCount::diagnoseAsError() {
96289628
return true;
96299629
}
96309630

9631+
bool TooManyDynamicMemberLookupsFailure::diagnoseAsError() {
9632+
emitDiagnostic(diag::too_many_dynamic_member_lookups, Name)
9633+
.highlight(getSourceRange());
9634+
return true;
9635+
}
9636+
96319637
bool DisallowedIsolatedConformance::diagnoseAsError() {
96329638
emitDiagnostic(diag::isolated_conformance_with_sendable_simple,
96339639
conformance->getType(),

lib/Sema/CSDiagnostics.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3310,6 +3310,17 @@ class IncorrectInlineArrayLiteralCount final : public FailureDiagnostic {
33103310
bool diagnoseAsError() override;
33113311
};
33123312

3313+
class TooManyDynamicMemberLookupsFailure final : public FailureDiagnostic {
3314+
DeclNameRef Name;
3315+
3316+
public:
3317+
TooManyDynamicMemberLookupsFailure(const Solution &solution, DeclNameRef name,
3318+
ConstraintLocator *locator)
3319+
: FailureDiagnostic(solution, locator), Name(name) {}
3320+
3321+
bool diagnoseAsError() override;
3322+
};
3323+
33133324
/// Diagnose when an isolated conformance is used in a place where one cannot
33143325
/// be, e.g., due to a Sendable or SendableMetatype requirement on the
33153326
/// corresponding type parameter.

lib/Sema/CSFix.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,6 +2796,18 @@ bool AllowInlineArrayLiteralCountMismatch::diagnose(const Solution &solution,
27962796
return failure.diagnose(asNote);
27972797
}
27982798

2799+
TooManyDynamicMemberLookups *
2800+
TooManyDynamicMemberLookups::create(ConstraintSystem &cs, DeclNameRef name,
2801+
ConstraintLocator *locator) {
2802+
return new (cs.getAllocator()) TooManyDynamicMemberLookups(cs, name, locator);
2803+
}
2804+
2805+
bool TooManyDynamicMemberLookups::diagnose(const Solution &solution,
2806+
bool asNote) const {
2807+
TooManyDynamicMemberLookupsFailure failure(solution, Name, getLocator());
2808+
return failure.diagnose(asNote);
2809+
}
2810+
27992811
IgnoreIsolatedConformance *
28002812
IgnoreIsolatedConformance::create(ConstraintSystem &cs,
28012813
ConstraintLocator *locator,

lib/Sema/CSSimplify.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16034,6 +16034,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1603416034
case FixKind::IgnoreOutOfPlaceThenStmt:
1603516035
case FixKind::IgnoreMissingEachKeyword:
1603616036
case FixKind::AllowInlineArrayLiteralCountMismatch:
16037+
case FixKind::TooManyDynamicMemberLookups:
1603716038
case FixKind::IgnoreIsolatedConformance:
1603816039
llvm_unreachable("handled elsewhere");
1603916040
}

lib/Sema/TypeOfReference.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,11 +2191,25 @@ void ConstraintSystem::bindOverloadType(const SelectedOverload &overload,
21912191
// FIXME: Should propagate name-as-written through.
21922192
: DeclNameRef(choice.getName());
21932193

2194-
addValueMemberConstraint(
2195-
LValueType::get(rootTy), memberName, memberTy, useDC,
2196-
isSubscriptRef ? FunctionRefInfo::doubleBaseNameApply()
2197-
: FunctionRefInfo::unappliedBaseName(),
2198-
/*outerAlternatives=*/{}, keyPathLoc);
2194+
// Check the current depth of applied dynamic member lookups, if we've
2195+
// exceeded the limit then record a fix and set a hole for the member.
2196+
unsigned lookupDepth = [&]() {
2197+
auto path = keyPathLoc->getPath();
2198+
auto iter = path.begin();
2199+
(void)keyPathLoc->findFirst<LocatorPathElt::KeyPathDynamicMember>(iter);
2200+
return path.end() - iter;
2201+
}();
2202+
if (lookupDepth > ctx.TypeCheckerOpts.DynamicMemberLookupDepthLimit) {
2203+
(void)recordFix(TooManyDynamicMemberLookups::create(
2204+
*this, DeclNameRef(choice.getName()), locator));
2205+
recordTypeVariablesAsHoles(memberTy);
2206+
} else {
2207+
addValueMemberConstraint(
2208+
LValueType::get(rootTy), memberName, memberTy, useDC,
2209+
isSubscriptRef ? FunctionRefInfo::doubleBaseNameApply()
2210+
: FunctionRefInfo::unappliedBaseName(),
2211+
/*outerAlternatives=*/{}, keyPathLoc);
2212+
}
21992213

22002214
// In case of subscript things are more complicated comparing to "dot"
22012215
// syntax, because we have to get "applicable function" constraint

0 commit comments

Comments
 (0)