Skip to content

RequirementMachine: Add more limits to catch runaway computation, and fix a bug #82321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3441,7 +3441,7 @@ WARNING(associated_type_override_typealias,none,

ERROR(requirement_machine_completion_failed,none,
"cannot build rewrite system for %select{generic signature|protocol}0; "
"%select{%error|rule count|rule length|concrete nesting}1 limit exceeded",
"%select{%error|rule count|rule length|concrete type nesting|concrete type size|concrete type difference}1 limit exceeded",
(unsigned, unsigned))
NOTE(requirement_machine_completion_rule,none,
"failed rewrite rule is %0", (StringRef))
Expand Down
12 changes: 10 additions & 2 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,10 +580,18 @@ namespace swift {
/// algorithm.
unsigned RequirementMachineMaxRuleLength = 12;

/// Maximum concrete type nesting depth for requirement machine property map
/// algorithm.
/// Maximum concrete type nesting depth (when type is viewed as a tree) for
/// requirement machine property map algorithm.
unsigned RequirementMachineMaxConcreteNesting = 30;

/// Maximum concrete type size (total number of nodes in the type tree) for
/// requirement machine property map algorithm.
unsigned RequirementMachineMaxConcreteSize = 4000;

/// Maximum number of "type difference" structures for the requirement machine
/// property map algorithm.
unsigned RequirementMachineMaxTypeDifferences = 4000;

/// Maximum number of attempts to make when splitting concrete equivalence
/// classes.
unsigned RequirementMachineMaxSplitConcreteEquivClassAttempts = 2;
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,14 @@ def requirement_machine_max_concrete_nesting : Joined<["-"], "requirement-machin
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum concrete type nesting depth before giving up">;

def requirement_machine_max_concrete_size : Joined<["-"], "requirement-machine-max-concrete-size=">,
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum concrete type total size before giving up">;

def requirement_machine_max_type_differences : Joined<["-"], "requirement-machine-max-type-differences=">,
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum number of type difference structures allocated before giving up">;

def requirement_machine_max_split_concrete_equiv_class_attempts : Joined<["-"], "requirement-machine-max-split-concrete-equiv-class-attempts=">,
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum concrete number of attempts at splitting "
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,8 @@ RequirementSignature RequirementSignature::getPlaceholderRequirementSignature(
});

return RequirementSignature(ctx.AllocateCopy(requirements),
ArrayRef<ProtocolTypeAlias>());
ArrayRef<ProtocolTypeAlias>(),
errors);
}

void RequirementSignature::getRequirementsWithInverses(
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/RequirementMachine/HomotopyReduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ RewriteSystem::findRuleToDelete(EliminationPredicate isRedundantRuleFn) {
{
// If both are concrete type requirements, prefer to eliminate the
// one with the more deeply nested type.
unsigned ruleNesting = rule.getNesting();
unsigned otherRuleNesting = otherRule.getNesting();
unsigned ruleNesting = rule.getNestingAndSize().first;
unsigned otherRuleNesting = otherRule.getNestingAndSize().first;

if (ruleNesting != otherRuleNesting) {
if (ruleNesting > otherRuleNesting)
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/RequirementMachine/KnuthBendix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@ RewriteSystem::performKnuthBendix(unsigned maxRuleCount,

// We don't have to consider the same pair of rules more than once,
// since those critical pairs were already resolved.
if (!CheckedOverlaps.insert(std::make_pair(i, j)).second)
unsigned k = from - lhs.getLHS().begin();
if (!CheckedOverlaps.insert(std::make_tuple(i, j, k)).second)
return;

// Try to repair the confluence violation by adding a new rule.
Expand Down
26 changes: 25 additions & 1 deletion lib/AST/RequirementMachine/RequirementMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ RequirementMachine::RequirementMachine(RewriteContext &ctx)
MaxRuleCount = langOpts.RequirementMachineMaxRuleCount;
MaxRuleLength = langOpts.RequirementMachineMaxRuleLength;
MaxConcreteNesting = langOpts.RequirementMachineMaxConcreteNesting;
MaxConcreteSize = langOpts.RequirementMachineMaxConcreteSize;
MaxTypeDifferences = langOpts.RequirementMachineMaxTypeDifferences;
Stats = ctx.getASTContext().Stats;

if (Stats)
Expand Down Expand Up @@ -247,6 +249,18 @@ void RequirementMachine::checkCompletionResult(CompletionResult result) const {
out << "Rewrite system exceeded concrete type nesting depth limit\n";
dump(out);
});

case CompletionResult::MaxConcreteSize:
ABORT([&](auto &out) {
out << "Rewrite system exceeded concrete type size limit\n";
dump(out);
});

case CompletionResult::MaxTypeDifferences:
ABORT([&](auto &out) {
out << "Rewrite system exceeded concrete type difference limit\n";
dump(out);
});
}
}

Expand Down Expand Up @@ -496,16 +510,26 @@ RequirementMachine::computeCompletion(RewriteSystem::ValidityPolicy policy) {
return std::make_pair(CompletionResult::MaxRuleLength,
ruleCount + i);
}
if (newRule.getNesting() > MaxConcreteNesting + System.getDeepestInitialRule()) {
auto nestingAndSize = newRule.getNestingAndSize();
if (nestingAndSize.first > MaxConcreteNesting + System.getMaxNestingOfInitialRule()) {
return std::make_pair(CompletionResult::MaxConcreteNesting,
ruleCount + i);
}
if (nestingAndSize.second > MaxConcreteSize + System.getMaxSizeOfInitialRule()) {
return std::make_pair(CompletionResult::MaxConcreteSize,
ruleCount + i);
}
}

if (System.getLocalRules().size() > MaxRuleCount) {
return std::make_pair(CompletionResult::MaxRuleCount,
System.getRules().size() - 1);
}

if (System.getTypeDifferenceCount() > MaxTypeDifferences) {
return std::make_pair(CompletionResult::MaxTypeDifferences,
System.getRules().size() - 1);
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/AST/RequirementMachine/RequirementMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class RequirementMachine final {
unsigned MaxRuleCount;
unsigned MaxRuleLength;
unsigned MaxConcreteNesting;
unsigned MaxConcreteSize;
unsigned MaxTypeDifferences;

UnifiedStatsReporter *Stats;

Expand Down
10 changes: 8 additions & 2 deletions lib/AST/RequirementMachine/RewriteSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ RewriteSystem::RewriteSystem(RewriteContext &ctx)
Frozen = 0;
RecordLoops = 0;
LongestInitialRule = 0;
DeepestInitialRule = 0;
MaxNestingOfInitialRule = 0;
MaxSizeOfInitialRule = 0;
}

RewriteSystem::~RewriteSystem() {
Expand Down Expand Up @@ -90,7 +91,12 @@ void RewriteSystem::initialize(

for (const auto &rule : getLocalRules()) {
LongestInitialRule = std::max(LongestInitialRule, rule.getDepth());
DeepestInitialRule = std::max(DeepestInitialRule, rule.getNesting());

auto nestingAndSize = rule.getNestingAndSize();
MaxNestingOfInitialRule = std::max(MaxNestingOfInitialRule,
nestingAndSize.first);
MaxSizeOfInitialRule = std::max(MaxSizeOfInitialRule,
nestingAndSize.second);
}
}

Expand Down
35 changes: 26 additions & 9 deletions lib/AST/RequirementMachine/RewriteSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ enum class CompletionResult {
MaxRuleLength,

/// Maximum concrete type nesting depth exceeded.
MaxConcreteNesting
MaxConcreteNesting,

/// Maximum concrete type size exceeded.
MaxConcreteSize,

/// Maximum type difference count exceeded.
MaxTypeDifferences,
};

/// A term rewrite system for working with types in a generic signature.
Expand Down Expand Up @@ -107,13 +113,16 @@ class RewriteSystem final {
/// identities among rewrite rules discovered while resolving critical pairs.
unsigned RecordLoops : 1;

/// The length of the longest initial rule, used for the MaxRuleLength
/// completion non-termination heuristic.
/// The length of the longest initial rule, for the MaxRuleLength limit.
unsigned LongestInitialRule : 16;

/// The most deeply nested concrete type appearing in an initial rule, used
/// for the MaxConcreteNesting completion non-termination heuristic.
unsigned DeepestInitialRule : 16;
/// The most deeply nested concrete type appearing in an initial rule,
/// for the MaxConcreteNesting limit.
unsigned MaxNestingOfInitialRule : 16;

/// The largest concrete type by total tree node count that appears in an
/// initial rule, for the MaxConcreteSize limit.
unsigned MaxSizeOfInitialRule : 16;

public:
explicit RewriteSystem(RewriteContext &ctx);
Expand Down Expand Up @@ -143,8 +152,12 @@ class RewriteSystem final {
return LongestInitialRule;
}

unsigned getDeepestInitialRule() const {
return DeepestInitialRule;
unsigned getMaxNestingOfInitialRule() const {
return MaxNestingOfInitialRule;
}

unsigned getMaxSizeOfInitialRule() const {
return MaxSizeOfInitialRule;
}

ArrayRef<const ProtocolDecl *> getProtocols() const {
Expand Down Expand Up @@ -206,7 +219,7 @@ class RewriteSystem final {
//////////////////////////////////////////////////////////////////////////////

/// Pairs of rules which have already been checked for overlap.
llvm::DenseSet<std::pair<unsigned, unsigned>> CheckedOverlaps;
llvm::DenseSet<std::tuple<unsigned, unsigned, unsigned>> CheckedOverlaps;

std::pair<CompletionResult, unsigned>
performKnuthBendix(unsigned maxRuleCount, unsigned maxRuleLength);
Expand Down Expand Up @@ -311,6 +324,10 @@ class RewriteSystem final {
std::optional<unsigned> &lhsDifferenceID,
std::optional<unsigned> &rhsDifferenceID);

unsigned getTypeDifferenceCount() const {
return Differences.size();
}

const TypeDifference &getTypeDifference(unsigned index) const;

void processTypeDifference(const TypeDifference &difference,
Expand Down
27 changes: 18 additions & 9 deletions lib/AST/RequirementMachine/Rule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ bool Rule::isDerivedFromConcreteProtocolTypeAliasRule() const {
return true;
}

/// Returns the length of the left hand side.
/// Returns the maximum among the length of the left-hand side,
/// and the length of any substitution terms that appear in a
/// property symbol at the end of the left-hand side.
///
/// This is a measure of the complexity of the rule, which stops
/// completion from running forever.
unsigned Rule::getDepth() const {
auto result = LHS.size();

Expand All @@ -234,37 +239,41 @@ unsigned Rule::getDepth() const {
return result;
}

/// Returns the nesting depth of the concrete symbol at the end of the
/// left hand side, or 0 if there isn't one.
unsigned Rule::getNesting() const {
/// Returns the complexity of the concrete type in the property symbol
/// at the end of the left-hand side, if there is one.
///
/// This is a measure of the complexity of the rule, which stops
/// completion from running forever.
std::pair<unsigned, unsigned>
Rule::getNestingAndSize() const {
if (LHS.back().hasSubstitutions()) {
auto type = LHS.back().getConcreteType();

struct Walker : TypeWalker {
unsigned Nesting = 0;
unsigned MaxNesting = 0;
unsigned Size = 0;

Action walkToTypePre(Type ty) override {
++Size;
++Nesting;
MaxNesting = std::max(Nesting, MaxNesting);

MaxNesting = std::max(MaxNesting, Nesting);
return Action::Continue;
}

Action walkToTypePost(Type ty) override {
--Nesting;

return Action::Continue;
}
};

Walker walker;
type.walk(walker);

return walker.MaxNesting;
return std::make_pair(walker.MaxNesting, walker.Size);
}

return 0;
return std::make_pair(0, 0);
}

/// Linear order on rules; compares LHS followed by RHS.
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/RequirementMachine/Rule.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class Rule final {

unsigned getDepth() const;

unsigned getNesting() const;
std::pair<unsigned, unsigned> getNestingAndSize() const;

std::optional<int> compare(const Rule &other, RewriteContext &ctx) const;

Expand Down
22 changes: 22 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,28 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
}
}

if (const Arg *A = Args.getLastArg(OPT_requirement_machine_max_concrete_size)) {
unsigned limit;
if (StringRef(A->getValue()).getAsInteger(10, limit)) {
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
HadError = true;
} else {
Opts.RequirementMachineMaxConcreteSize = limit;
}
}

if (const Arg *A = Args.getLastArg(OPT_requirement_machine_max_type_differences)) {
unsigned limit;
if (StringRef(A->getValue()).getAsInteger(10, limit)) {
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
HadError = true;
} else {
Opts.RequirementMachineMaxTypeDifferences = limit;
}
}

if (const Arg *A = Args.getLastArg(OPT_requirement_machine_max_split_concrete_equiv_class_attempts)) {
unsigned limit;
if (StringRef(A->getValue()).getAsInteger(10, limit)) {
Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/same_types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ protocol P2 {
// CHECK-LABEL: same_types.(file).structuralSameTypeRecursive1@
// CHECK-NEXT: Generic signature: <T, U>

// expected-error@+2 {{cannot build rewrite system for generic signature; concrete nesting limit exceeded}}
// expected-error@+2 {{cannot build rewrite system for generic signature; concrete type nesting limit exceeded}}
// expected-note@+1 {{τ_0_0.[P2:Assoc1].[concrete: ((((((((((((((((((((((((((((((((τ_0_0.[P2:Assoc1], τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1), τ_0_1)] => τ_0_0.[P2:Assoc1]}}
func structuralSameTypeRecursive1<T: P2, U>(_: T, _: U)
where T.Assoc1 == Tuple2<T.Assoc1, U>
Expand Down
4 changes: 2 additions & 2 deletions test/Generics/infinite_concrete_type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

class G<T> {}

protocol P1 { // expected-error {{cannot build rewrite system for protocol; concrete nesting limit exceeded}}
// expected-note@-1 {{failed rewrite rule is [P1:A].[concrete: G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<G<[P1].A>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] => [P1:A]}}
protocol P1 { // expected-error {{cannot build rewrite system for protocol; concrete type difference limit exceeded}}
// expected-note@-1 {{failed rewrite rule is [P1:B].[superclass: G<G<G<G<G<G<G<G<G<G<G<G<G<[P1].A>>>>>>>>>>>>>] => [P1:B]}}
associatedtype A where A == G<B>
associatedtype B where B == G<A>
}
Expand Down
Loading