|  | @@ -1770,6 +1770,7 @@ static void *default_alloc(void *_ud, void *ptr, size_t oldsize, size_t size) {
 | 
	
		
			
				|  |  |         * updated to its new location. */
 | 
	
		
			
				|  |  |        if (block->next) block->next->prev = block;
 | 
	
		
			
				|  |  |        if (block->prev) block->prev->next = block;
 | 
	
		
			
				|  |  | +      if (ud->head == from) ud->head = block;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  |      /* Insert at head of linked list. */
 | 
	
	
		
			
				|  | @@ -1798,7 +1799,7 @@ static void default_alloc_cleanup(void *_ud) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static bool default_err(void *ud, const upb_status *status) {
 | 
	
		
			
				|  |  |    UPB_UNUSED(ud);
 | 
	
		
			
				|  |  | -  fprintf(stderr, "upb error: %s\n", upb_status_errmsg(status));
 | 
	
		
			
				|  |  | +  UPB_UNUSED(status);
 | 
	
		
			
				|  |  |    return false;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1919,7 +1920,6 @@ static size_t align_up(size_t size) {
 | 
	
		
			
				|  |  |  UPB_FORCEINLINE static void *seeded_alloc(void *ud, void *ptr, size_t oldsize,
 | 
	
		
			
				|  |  |                                            size_t size) {
 | 
	
		
			
				|  |  |    upb_seededalloc *a = ud;
 | 
	
		
			
				|  |  | -  UPB_UNUSED(ptr);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    size = align_up(size);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1937,7 +1937,11 @@ UPB_FORCEINLINE static void *seeded_alloc(void *ud, void *ptr, size_t oldsize,
 | 
	
		
			
				|  |  |      /* Is `ptr` part of the user-provided initial block? Don't pass it to the
 | 
	
		
			
				|  |  |       * default allocator if so; otherwise, it may try to realloc() the block. */
 | 
	
		
			
				|  |  |      if (chptr >= a->mem_base && chptr < a->mem_limit) {
 | 
	
		
			
				|  |  | -      return a->alloc(a->alloc_ud, NULL, 0, size);
 | 
	
		
			
				|  |  | +      void *ret;
 | 
	
		
			
				|  |  | +      assert(chptr + oldsize <= a->mem_limit);
 | 
	
		
			
				|  |  | +      ret = a->alloc(a->alloc_ud, NULL, 0, size);
 | 
	
		
			
				|  |  | +      if (ret) memcpy(ret, ptr, oldsize);
 | 
	
		
			
				|  |  | +      return ret;
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |        return a->alloc(a->alloc_ud, ptr, oldsize, size);
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -3692,24 +3696,48 @@ const upb_def *upb_symtab_resolve(const upb_symtab *s, const char *base,
 | 
	
		
			
				|  |  |    return ret;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/* Searches def and its children to find defs that have the same name as any
 | 
	
		
			
				|  |  | - * def in "addtab."  Returns true if any where found, and as a side-effect adds
 | 
	
		
			
				|  |  | - * duplicates of these defs into addtab.
 | 
	
		
			
				|  |  | +/* 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.
 | 
	
		
			
				|  |  |   *
 | 
	
		
			
				|  |  | - * We use a modified depth-first traversal that traverses each SCC (which we
 | 
	
		
			
				|  |  | - * already computed) as if it were a single node.  This allows us to traverse
 | 
	
		
			
				|  |  | - * the possibly-cyclic graph as if it were a DAG and to dup the correct set of
 | 
	
		
			
				|  |  | - * nodes with O(n) time. */
 | 
	
		
			
				|  |  | + * 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) {
 | 
	
		
			
				|  |  | -  /* Memoize results of this function for efficiency (since we're traversing a
 | 
	
		
			
				|  |  | -   * DAG this is not needed to limit the depth of the search). */
 | 
	
		
			
				|  |  |    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_lookup(seen, (uintptr_t)def, &v))
 | 
	
		
			
				|  |  | +  if (upb_inttable_lookupptr(seen, memoize_key, &v))
 | 
	
		
			
				|  |  |      return upb_value_getbool(v);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    /* Visit submessages for all messages in the SCC. */
 | 
	
	
		
			
				|  | @@ -3725,7 +3753,8 @@ static bool upb_resolve_dfs(const upb_def *def, upb_strtable *addtab,
 | 
	
		
			
				|  |  |        need_dup = true;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /* For messages, continue the recursion by visiting all subdefs. */
 | 
	
		
			
				|  |  | +    /* 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;
 | 
	
	
		
			
				|  | @@ -3733,17 +3762,23 @@ static bool upb_resolve_dfs(const upb_def *def, upb_strtable *addtab,
 | 
	
		
			
				|  |  |            !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(
 | 
	
		
			
				|  |  | -            upb_fielddef_subdef(f), addtab, new_owner, seen, s);
 | 
	
		
			
				|  |  | +        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 any defs that don't already have entries in addtab. */
 | 
	
		
			
				|  |  | +    /* Dup all defs in this SCC that don't already have entries in addtab. */
 | 
	
		
			
				|  |  |      def = base;
 | 
	
		
			
				|  |  |      do {
 | 
	
		
			
				|  |  |        const char *name;
 | 
	
	
		
			
				|  | @@ -3760,7 +3795,7 @@ static bool upb_resolve_dfs(const upb_def *def, upb_strtable *addtab,
 | 
	
		
			
				|  |  |      } while ((def = (upb_def*)def->base.next) != base);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  upb_inttable_insert(seen, (uintptr_t)def, upb_value_bool(need_dup));
 | 
	
		
			
				|  |  | +  upb_inttable_insertptr(seen, memoize_key, upb_value_bool(need_dup));
 | 
	
		
			
				|  |  |    return need_dup;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  oom:
 |