bool upb_symtab_dfs(upb_def *def, upb_def **open_defs, int n, upb_strtable *addtab) { // This linear search makes the DFS O(n^2) in the length of the paths. // Could make this O(n) with a hash table, but n is small. for (int i = 0; i < n; i++) { if (def == open_defs[i]) return false; } bool needcopy = false; upb_msgdef *m = upb_dyncast_msgdef(def); if (m) { upb_msg_iter i; open_defs[n++] = def; for(i = upb_msg_begin(m); !upb_msg_done(i); i = upb_msg_next(m, i)) { upb_fielddef *f = upb_msg_iter_field(i); if (!upb_hassubdef(f)) continue; needcopy |= upb_symtab_dfs(f->def, open_defs, n, addtab); } } bool replacing = (upb_strtable_lookup(addtab, m->base.fqname) != NULL); if (needcopy && !replacing) { upb_symtab_ent e = {upb_def_dup(def)}; upb_strtable_insert(addtab, def->fqname, &e); replacing = true; } return replacing; }
/* Starts a depth-first traversal at "def", recursing into any subdefs * (ie. submessage types). Adds duplicates of existing defs to addtab * wherever necessary, so that the resulting symtab will be consistent once * addtab is added. * * More specifically, if any def D is found in the DFS that: * * 1. can reach a def that is being replaced by something in addtab, AND * * 2. is not itself being replaced already (ie. this name doesn't already * exist in addtab) * * ...then a duplicate (new copy) of D will be added to addtab. * * Returns true if this happened for any def reachable from "def." * * It is slightly tricky to do this correctly in the presence of cycles. If we * detect that our DFS has hit a cycle, we might not yet know if any SCCs on * our stack can reach a def in addtab or not. Once we figure this out, that * answer needs to apply to *all* defs in these SCCs, even if we visited them * already. So a straight up one-pass cycle-detecting DFS won't work. * * To work around this problem, we traverse each SCC (which we already * computed, since these defs are frozen) as a single node. We first compute * whether the SCC as a whole can reach any def in addtab, then we dup (or not) * the entire SCC. This requires breaking the encapsulation of upb_refcounted, * since that is where we get the data about what SCC we are in. */ static bool upb_resolve_dfs(const upb_def *def, upb_strtable *addtab, const void *new_owner, upb_inttable *seen, upb_status *s) { upb_value v; bool need_dup; const upb_def *base; const void* memoize_key; /* Memoize results of this function for efficiency (since we're traversing a * DAG this is not needed to limit the depth of the search). * * We memoize by SCC instead of by individual def. */ memoize_key = def->base.group; if (upb_inttable_lookupptr(seen, memoize_key, &v)) return upb_value_getbool(v); /* Visit submessages for all messages in the SCC. */ need_dup = false; base = def; do { upb_value v; const upb_msgdef *m; assert(upb_def_isfrozen(def)); if (def->type == UPB_DEF_FIELD) continue; if (upb_strtable_lookup(addtab, upb_def_fullname(def), &v)) { need_dup = true; } /* For messages, continue the recursion by visiting all subdefs, but only * ones in different SCCs. */ m = upb_dyncast_msgdef(def); if (m) { upb_msg_field_iter i; for(upb_msg_field_begin(&i, m); !upb_msg_field_done(&i); upb_msg_field_next(&i)) { upb_fielddef *f = upb_msg_iter_field(&i); const upb_def *subdef; if (!upb_fielddef_hassubdef(f)) continue; subdef = upb_fielddef_subdef(f); /* Skip subdefs in this SCC. */ if (def->base.group == subdef->base.group) continue; /* |= to avoid short-circuit; we need its side-effects. */ need_dup |= upb_resolve_dfs(subdef, addtab, new_owner, seen, s); if (!upb_ok(s)) return false; } } } while ((def = (upb_def*)def->base.next) != base); if (need_dup) { /* Dup all defs in this SCC that don't already have entries in addtab. */ def = base; do { const char *name; if (def->type == UPB_DEF_FIELD) continue; name = upb_def_fullname(def); if (!upb_strtable_lookup(addtab, name, NULL)) { upb_def *newdef = upb_def_dup(def, new_owner); if (!newdef) goto oom; newdef->came_from_user = false; if (!upb_strtable_insert(addtab, name, upb_value_ptr(newdef))) goto oom; } } while ((def = (upb_def*)def->base.next) != base); } upb_inttable_insertptr(seen, memoize_key, upb_value_bool(need_dup)); return need_dup; oom: upb_status_seterrmsg(s, "out of memory"); return false; }