diff --git a/common/requirements.c b/common/requirements.c index 311fbc676e..779e393ed7 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -5445,6 +5445,26 @@ enum fc_tristate tri_req_present(const struct civ_map *nmap, const struct player *other_player, const struct requirement *req) { + /* Recursion detection: Maintain a stack of requirements that are + * currently being checked, and abort if we see the same req twice. + * Note that we're only storing the reqs themselves; we're not making + * an exception for the same req being evaluated against multiple + * different contexts. */ + struct recur_node { + const struct recur_node *next; + const struct requirement *req; + }; + /* The current deepest level of recursion */ + static const struct recur_node *recur_head = nullptr; + /* Stack node representing this call to tri_req_present() */ + const struct recur_node recur_current = { + .next = recur_head, + .req = req, + }; + const struct recur_node *recur_i; + + enum fc_tristate result; + if (context == NULL) { context = req_context_empty(); } @@ -5457,8 +5477,29 @@ enum fc_tristate tri_req_present(const struct civ_map *nmap, fc_assert_ret_val(req_definitions[req->source.kind].cb != NULL, TRI_NO); - return req_definitions[req->source.kind].cb(nmap, context, - other_player, req); + for (recur_i = recur_head; recur_i != nullptr; recur_i = recur_i->next) { + if (are_requirements_equal(req, recur_i->req)) { + /* Infinite recursion! Abort and return TRI_MAYBE. */ + struct astring astr; + + log_warn("Infinite recursion when evaluating requirement: %s", + req_to_fstring(req, &astr)); + astr_free(&astr); + + return TRI_MAYBE; + } + } + + /* push */ + recur_head = &recur_current; + + result = req_definitions[req->source.kind].cb(nmap, context, + other_player, req); + + /* pop */ + recur_head = recur_current.next; + + return result; } /**********************************************************************//**