Ejemplo n.º 1
0
Archivo: reader.c Proyecto: YauzZ/upb
static bool field_endmsg(void *closure, const void *hd, upb_status *status) {
  UPB_UNUSED(hd);
  upb_descreader *r = closure;
  upb_fielddef *f = r->f;
  // TODO: verify that all required fields were present.
  assert(upb_fielddef_number(f) != 0);
  assert(upb_fielddef_name(f) != NULL);
  assert((upb_fielddef_subdefname(f) != NULL) == upb_fielddef_hassubdef(f));

  if (r->default_string) {
    if (upb_fielddef_issubmsg(f)) {
      upb_status_seterrmsg(status, "Submessages cannot have defaults.");
      return false;
    }
    if (upb_fielddef_isstring(f) || upb_fielddef_type(f) == UPB_TYPE_ENUM) {
      upb_fielddef_setdefaultcstr(f, r->default_string, NULL);
    } else {
      if (r->default_string && !parse_default(r->default_string, f)) {
        // We don't worry too much about giving a great error message since the
        // compiler should have ensured this was correct.
        upb_status_seterrmsg(status, "Error converting default value.");
        return false;
      }
    }
  }
  return true;
}
Ejemplo n.º 2
0
Archivo: reader.c Proyecto: YauzZ/upb
static bool enum_endmsg(void *closure, const void *hd, upb_status *status) {
  UPB_UNUSED(hd);
  upb_descreader *r = closure;
  upb_enumdef *e = upb_downcast_enumdef_mutable(upb_descreader_last(r));
  if (upb_def_fullname(upb_descreader_last(r)) == NULL) {
    upb_status_seterrmsg(status, "Enum had no name.");
    return false;
  }
  if (upb_enumdef_numvals(e) == 0) {
    upb_status_seterrmsg(status, "Enum had no values.");
    return false;
  }
  return true;
}
Ejemplo n.º 3
0
Archivo: def.c Proyecto: YauzZ/upb
static bool upb_subdef_typecheck(upb_fielddef *f, const upb_def *subdef,
                                 upb_status *s) {
  if (f->type_ == UPB_TYPE_MESSAGE) {
    if (upb_dyncast_msgdef(subdef)) return true;
    upb_status_seterrmsg(s, "invalid subdef type for this submessage field");
    return false;
  } else if (f->type_ == UPB_TYPE_ENUM) {
    if (upb_dyncast_enumdef(subdef)) return true;
    upb_status_seterrmsg(s, "invalid subdef type for this enum field");
    return false;
  } else {
    upb_status_seterrmsg(s, "only message and enum fields can have a subdef");
    return false;
  }
}
Ejemplo n.º 4
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_msgdef_addfields(upb_msgdef *m, upb_fielddef *const *fields, int n,
                          const void *ref_donor, upb_status *s) {
  // TODO: extensions need to have a separate namespace, because proto2 allows a
  // top-level extension (ie. one not in any package) to have the same name as a
  // field from the message.
  //
  // This also implies that there needs to be a separate lookup-by-name method
  // for extensions.  It seems desirable for iteration to return both extensions
  // and non-extensions though.
  //
  // We also need to validate that the field number is in an extension range iff
  // it is an extension.

  // Check constraints for all fields before performing any action.
  for (int i = 0; i < n; i++) {
    upb_fielddef *f = fields[i];
    // TODO(haberman): handle the case where two fields of the input duplicate
    // name or number.
    if (upb_fielddef_containingtype(f) != NULL) {
      upb_status_seterrmsg(s, "fielddef already belongs to a message");
      return false;
    } else if (upb_fielddef_name(f) == NULL || upb_fielddef_number(f) == 0) {
      upb_status_seterrmsg(s, "field name or number were not set");
      return false;
    } else if(upb_msgdef_itof(m, upb_fielddef_number(f)) ||
              upb_msgdef_ntof(m, upb_fielddef_name(f))) {
      upb_status_seterrmsg(s, "duplicate field name or number");
      return false;
    }
  }

  // Constraint checks ok, perform the action.
  for (int i = 0; i < n; i++) {
    upb_fielddef *f = fields[i];
    release_containingtype(f);
    f->msg.def = m;
    f->msg_is_symbolic = false;
    upb_inttable_insert(&m->itof, upb_fielddef_number(f), upb_value_ptr(f));
    upb_strtable_insert(&m->ntof, upb_fielddef_name(f), upb_value_ptr(f));
    upb_ref2(f, m);
    upb_ref2(m, f);
    if (ref_donor) upb_fielddef_unref(f, ref_donor);
  }
  return true;
}
Ejemplo n.º 5
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_def_freeze(upb_def *const* defs, int n, upb_status *s) {
  // First perform validation, in two passes so we can check that we have a
  // transitive closure without needing to search.
  for (int i = 0; i < n; i++) {
    upb_def *def = defs[i];
    if (upb_def_isfrozen(def)) {
      // Could relax this requirement if it's annoying.
      upb_status_seterrmsg(s, "def is already frozen");
      goto err;
    } else if (def->type == UPB_DEF_FIELD) {
      upb_status_seterrmsg(s, "standalone fielddefs can not be frozen");
      goto err;
    } else {
      // Set now to detect transitive closure in the second pass.
      def->came_from_user = true;
    }
  }

  // Second pass of validation.  Also assign selector bases and indexes, and
  // compact tables.
  for (int i = 0; i < n; i++) {
    upb_msgdef *m = upb_dyncast_msgdef_mutable(defs[i]);
    upb_enumdef *e = upb_dyncast_enumdef_mutable(defs[i]);
    if (m) {
      upb_inttable_compact(&m->itof);
      assign_msg_indices(m, s);
    } else if (e) {
      upb_inttable_compact(&e->iton);
    }
  }

  // Def graph contains FieldDefs between each MessageDef, so double the limit.
  int maxdepth = UPB_MAX_MESSAGE_DEPTH * 2;

  // Validation all passed; freeze the defs.
  return upb_refcounted_freeze((upb_refcounted * const *)defs, n, s, maxdepth);

err:
  for (int i = 0; i < n; i++) {
    defs[i]->came_from_user = false;
  }
  assert(!upb_ok(s));
  return false;
}
Ejemplo n.º 6
0
Archivo: def.c Proyecto: YauzZ/upb
static bool upb_validate_field(upb_fielddef *f, upb_status *s) {
  if (upb_fielddef_name(f) == NULL || upb_fielddef_number(f) == 0) {
    upb_status_seterrmsg(s, "fielddef must have name and number set");
    return false;
  }
  if (!f->type_is_set_) {
    upb_status_seterrmsg(s, "fielddef type was not initialized");
    return false;
  }
  if (upb_fielddef_lazy(f) &&
      upb_fielddef_descriptortype(f) != UPB_DESCRIPTOR_TYPE_MESSAGE) {
    upb_status_seterrmsg(s,
                         "only length-delimited submessage fields may be lazy");
    return false;
  }
  if (upb_fielddef_hassubdef(f)) {
    if (f->subdef_is_symbolic) {
      upb_status_seterrf(s,
          "field '%s' has not been resolved", upb_fielddef_name(f));
      return false;
    }

    const upb_def *subdef = upb_fielddef_subdef(f);
    if (subdef == NULL) {
      upb_status_seterrf(s,
          "field %s.%s is missing required subdef",
          msgdef_name(f->msg.def), upb_fielddef_name(f));
      return false;
    } else if (!upb_def_isfrozen(subdef) && !subdef->came_from_user) {
      upb_status_seterrf(s,
          "subdef of field %s.%s is not frozen or being frozen",
          msgdef_name(f->msg.def), upb_fielddef_name(f));
      return false;
    } else if (upb_fielddef_default_is_symbolic(f)) {
      upb_status_seterrf(s,
          "enum field %s.%s has not been resolved",
          msgdef_name(f->msg.def), upb_fielddef_name(f));
      return false;
    }
  }
  return true;
}
Ejemplo n.º 7
0
Archivo: reader.c Proyecto: YauzZ/upb
static bool msg_endmsg(void *closure, const void *hd, upb_status *status) {
  UPB_UNUSED(hd);
  upb_descreader *r = closure;
  upb_msgdef *m = upb_descreader_top(r);
  if(!upb_def_fullname(UPB_UPCAST(m))) {
    upb_status_seterrmsg(status, "Encountered message with no name.");
    return false;
  }
  upb_descreader_endcontainer(r);
  return true;
}
Ejemplo n.º 8
0
// Pushes an obj onto the Tarjan stack and sets it to GREEN.
static void push(tarjan *t, const upb_refcounted *r) {
  assert(color(t, r) == BLACK || color(t, r) == GRAY);
  // This defines the attr layout for the GREEN state.  "index" and "lowlink"
  // get 31 bits, which is plenty (limit of 2B objects frozen at a time).
  setattr(t, r, GREEN | (t->index << 2) | (t->index << 33));
  if (++t->index == 0x80000000) {
    upb_status_seterrmsg(t->status, "too many objects to freeze");
    err(t);
  }
  upb_inttable_push(&t->stack, upb_value_ptr((void*)r));
}
Ejemplo n.º 9
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_enumdef_addval(upb_enumdef *e, const char *name, int32_t num,
                        upb_status *status) {
  if (!upb_isident(name, strlen(name), false, status)) {
    return false;
  }
  if (upb_enumdef_ntoi(e, name, NULL)) {
    upb_status_seterrf(status, "name '%s' is already defined", name);
    return false;
  }
  if (!upb_strtable_insert(&e->ntoi, name, upb_value_int32(num))) {
    upb_status_seterrmsg(status, "out of memory");
    return false;
  }
  if (!upb_inttable_lookup(&e->iton, num, NULL) &&
      !upb_inttable_insert(&e->iton, num, upb_value_cstr(upb_strdup(name)))) {
    upb_status_seterrmsg(status, "out of memory");
    upb_strtable_remove(&e->ntoi, name, NULL);
    return false;
  }
  return true;
}
Ejemplo n.º 10
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_fielddef_setnumber(upb_fielddef *f, uint32_t number, upb_status *s) {
  if (upb_fielddef_containingtype(f)) {
    upb_status_seterrmsg(
        s, "cannot change field number after adding to a message");
    return false;
  }
  if (number == 0 || number > UPB_MAX_FIELDNUMBER) {
    upb_status_seterrf(s, "invalid field number (%u)", number);
    return false;
  }
  f->number_ = number;
  return true;
}
Ejemplo n.º 11
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_fielddef_setsubdefname(upb_fielddef *f, const char *name,
                                upb_status *s) {
  assert(!upb_fielddef_isfrozen(f));
  if (!upb_fielddef_hassubdef(f)) {
    upb_status_seterrmsg(s, "field type does not accept a subdef");
    return false;
  }
  // TODO: validate name (upb_isident() doesn't quite work atm because this name
  // may have a leading ".").
  release_subdef(f);
  f->sub.name = upb_strdup(name);
  f->subdef_is_symbolic = true;
  return true;
}
Ejemplo n.º 12
0
Archivo: def.c Proyecto: YauzZ/upb
bool upb_fielddef_setcontainingtypename(upb_fielddef *f, const char *name,
                                        upb_status *s) {
  assert(!upb_fielddef_isfrozen(f));
  if (upb_fielddef_containingtype(f)) {
    upb_status_seterrmsg(s, "field has already been added to a message.");
    return false;
  }
  // TODO: validate name (upb_isident() doesn't quite work atm because this name
  // may have a leading ".").
  release_containingtype(f);
  f->msg.name = upb_strdup(name);
  f->msg_is_symbolic = true;
  return true;
}
Ejemplo n.º 13
0
Archivo: reader.c Proyecto: YauzZ/upb
static bool enumval_endmsg(void *closure, const void *hd, upb_status *status) {
  UPB_UNUSED(hd);
  upb_descreader *r = closure;
  if(!r->saw_number || !r->saw_name) {
    upb_status_seterrmsg(status, "Enum value missing name or number.");
    return false;
  }
  upb_enumdef *e = upb_downcast_enumdef_mutable(upb_descreader_last(r));
  if (upb_enumdef_numvals(e) == 0) {
    // The default value of an enum (in the absence of an explicit default) is
    // its first listed value.
    upb_enumdef_setdefault(e, r->number);
  }
  upb_enumdef_addval(e, r->name, r->number, status);
  free(r->name);
  r->name = NULL;
  return true;
}
Ejemplo n.º 14
0
Archivo: symtab.c Proyecto: atdt/upb
/* TODO(haberman): we need a lot more testing of error conditions.
 * The came_from_user stuff in particular is not tested. */
bool upb_symtab_add(upb_symtab *s, upb_def *const*defs, int n, void *ref_donor,
                    upb_status *status) {
  int i;
  upb_strtable_iter iter;
  upb_def **add_defs = NULL;
  upb_strtable addtab;
  upb_inttable seen;

  assert(!upb_symtab_isfrozen(s));
  if (!upb_strtable_init(&addtab, UPB_CTYPE_PTR)) {
    upb_status_seterrmsg(status, "out of memory");
    return false;
  }

  /* Add new defs to our "add" set. */
  for (i = 0; i < n; i++) {
    upb_def *def = defs[i];
    const char *fullname;
    upb_fielddef *f;

    if (upb_def_isfrozen(def)) {
      upb_status_seterrmsg(status, "added defs must be mutable");
      goto err;
    }
    assert(!upb_def_isfrozen(def));
    fullname = upb_def_fullname(def);
    if (!fullname) {
      upb_status_seterrmsg(
          status, "Anonymous defs cannot be added to a symtab");
      goto err;
    }

    f = upb_dyncast_fielddef_mutable(def);

    if (f) {
      if (!upb_fielddef_containingtypename(f)) {
        upb_status_seterrmsg(status,
                             "Standalone fielddefs must have a containing type "
                             "(extendee) name set");
        goto err;
      }
    } else {
      if (upb_strtable_lookup(&addtab, fullname, NULL)) {
        upb_status_seterrf(status, "Conflicting defs named '%s'", fullname);
        goto err;
      }
      /* We need this to back out properly, because if there is a failure we
       * need to donate the ref back to the caller. */
      def->came_from_user = true;
      upb_def_donateref(def, ref_donor, s);
      if (!upb_strtable_insert(&addtab, fullname, upb_value_ptr(def)))
        goto oom_err;
    }
  }

  /* Add standalone fielddefs (ie. extensions) to the appropriate messages.
   * If the appropriate message only exists in the existing symtab, duplicate
   * it so we have a mutable copy we can add the fields to. */
  for (i = 0; i < n; i++) {
    upb_def *def = defs[i];
    upb_fielddef *f = upb_dyncast_fielddef_mutable(def);
    const char *msgname;
    upb_value v;
    upb_msgdef *m;

    if (!f) continue;
    msgname = upb_fielddef_containingtypename(f);
    /* We validated this earlier in this function. */
    assert(msgname);

    /* If the extendee name is absolutely qualified, move past the initial ".".
     * TODO(haberman): it is not obvious what it would mean if this was not
     * absolutely qualified. */
    if (msgname[0] == '.') {
      msgname++;
    }

    if (upb_strtable_lookup(&addtab, msgname, &v)) {
      /* Extendee is in the set of defs the user asked us to add. */
      m = upb_value_getptr(v);
    } else {
      /* Need to find and dup the extendee from the existing symtab. */
      const upb_msgdef *frozen_m = upb_symtab_lookupmsg(s, msgname);
      if (!frozen_m) {
        upb_status_seterrf(status,
                           "Tried to extend message %s that does not exist "
                           "in this SymbolTable.",
                           msgname);
        goto err;
      }
      m = upb_msgdef_dup(frozen_m, s);
      if (!m) goto oom_err;
      if (!upb_strtable_insert(&addtab, msgname, upb_value_ptr(m))) {
        upb_msgdef_unref(m, s);
        goto oom_err;
      }
    }

    if (!upb_msgdef_addfield(m, f, ref_donor, status)) {
      goto err;
    }
  }

  /* Add dups of any existing def that can reach a def with the same name as
   * anything in our "add" set. */
  if (!upb_inttable_init(&seen, UPB_CTYPE_BOOL)) goto oom_err;
  upb_strtable_begin(&iter, &s->symtab);
  for (; !upb_strtable_done(&iter); upb_strtable_next(&iter)) {
    upb_def *def = upb_value_getptr(upb_strtable_iter_value(&iter));
    upb_resolve_dfs(def, &addtab, s, &seen, status);
    if (!upb_ok(status)) goto err;
  }
  upb_inttable_uninit(&seen);

  /* Now using the table, resolve symbolic references for subdefs. */
  upb_strtable_begin(&iter, &addtab);
  for (; !upb_strtable_done(&iter); upb_strtable_next(&iter)) {
    const char *base;
    upb_def *def = upb_value_getptr(upb_strtable_iter_value(&iter));
    upb_msgdef *m = upb_dyncast_msgdef_mutable(def);
    upb_msg_field_iter j;

    if (!m) continue;
    /* Type names are resolved relative to the message in which they appear. */
    base = upb_msgdef_fullname(m);

    for(upb_msg_field_begin(&j, m);
        !upb_msg_field_done(&j);
        upb_msg_field_next(&j)) {
      upb_fielddef *f = upb_msg_iter_field(&j);
      const char *name = upb_fielddef_subdefname(f);
      if (name && !upb_fielddef_subdef(f)) {
        /* Try the lookup in the current set of to-be-added defs first. If not
         * there, try existing defs. */
        upb_def *subdef = upb_resolvename(&addtab, base, name);
        if (subdef == NULL) {
          subdef = upb_resolvename(&s->symtab, base, name);
        }
        if (subdef == NULL) {
          upb_status_seterrf(
              status, "couldn't resolve name '%s' in message '%s'", name, base);
          goto err;
        } else if (!upb_fielddef_setsubdef(f, subdef, status)) {
          goto err;
        }
      }
    }
  }

  /* We need an array of the defs in addtab, for passing to upb_def_freeze. */
  add_defs = malloc(sizeof(void*) * upb_strtable_count(&addtab));
  if (add_defs == NULL) goto oom_err;
  upb_strtable_begin(&iter, &addtab);
  for (n = 0; !upb_strtable_done(&iter); upb_strtable_next(&iter)) {
    add_defs[n++] = upb_value_getptr(upb_strtable_iter_value(&iter));
  }

  if (!upb_def_freeze(add_defs, n, status)) goto err;

  /* This must be delayed until all errors have been detected, since error
   * recovery code uses this table to cleanup defs. */
  upb_strtable_uninit(&addtab);

  /* TODO(haberman) we don't properly handle errors after this point (like
   * OOM in upb_strtable_insert() below). */
  for (i = 0; i < n; i++) {
    upb_def *def = add_defs[i];
    const char *name = upb_def_fullname(def);
    upb_value v;
    bool success;

    if (upb_strtable_remove(&s->symtab, name, &v)) {
      const upb_def *def = upb_value_getptr(v);
      upb_def_unref(def, s);
    }
    success = upb_strtable_insert(&s->symtab, name, upb_value_ptr(def));
    UPB_ASSERT_VAR(success, success == true);
  }
  free(add_defs);
  return true;

oom_err:
  upb_status_seterrmsg(status, "out of memory");
err: {
    /* For defs the user passed in, we need to donate the refs back.  For defs
     * we dup'd, we need to just unref them. */
    upb_strtable_begin(&iter, &addtab);
    for (; !upb_strtable_done(&iter); upb_strtable_next(&iter)) {
      upb_def *def = upb_value_getptr(upb_strtable_iter_value(&iter));
      bool came_from_user = def->came_from_user;
      def->came_from_user = false;
      if (came_from_user) {
        upb_def_donateref(def, s, ref_donor);
      } else {
        upb_def_unref(def, s);
      }
    }
  }
  upb_strtable_uninit(&addtab);
  free(add_defs);
  assert(!upb_ok(status));
  return false;
}
Ejemplo n.º 15
0
UPB_NORETURN static void oom(tarjan *t) {
  upb_status_seterrmsg(t->status, "out of memory");
  err(t);
}
Ejemplo n.º 16
0
Archivo: handlers.c Proyecto: YauzZ/upb
static bool doset(upb_handlers *h, int32_t sel, const upb_fielddef *f,
                  upb_handlertype_t type, upb_func *func,
                  upb_handlerattr *attr) {
  assert(!upb_handlers_isfrozen(h));

  if (sel < 0) {
    upb_status_seterrmsg(&h->status_,
                         "incorrect handler type for this field.");
    return false;
  }

  if (h->table[sel].func) {
    upb_status_seterrmsg(&h->status_,
                         "cannot change handler once it has been set.");
    return false;
  }

  upb_handlerattr set_attr = UPB_HANDLERATTR_INITIALIZER;
  if (attr) {
    set_attr = *attr;
  }

  // Check that the given closure type matches the closure type that has been
  // established for this context (if any).
  const void *closure_type = upb_handlerattr_closuretype(&set_attr);
  const void **context_closure_type;

  if (type == UPB_HANDLER_STRING) {
    context_closure_type = returntype(h, f, UPB_HANDLER_STARTSTR);
  } else if (f && upb_fielddef_isseq(f) &&
             type != UPB_HANDLER_STARTSEQ &&
             type != UPB_HANDLER_ENDSEQ) {
    context_closure_type = returntype(h, f, UPB_HANDLER_STARTSEQ);
  } else {
    context_closure_type = &h->top_closure_type;
  }

  if (closure_type && *context_closure_type &&
      closure_type != *context_closure_type) {
    // TODO(haberman): better message for debugging.
    upb_status_seterrmsg(&h->status_, "closure type does not match");
    return false;
  }

  if (closure_type)
    *context_closure_type = closure_type;

  // If this is a STARTSEQ or STARTSTR handler, check that the returned pointer
  // matches any pre-existing expectations about what type is expected.
  if (type == UPB_HANDLER_STARTSEQ || type == UPB_HANDLER_STARTSTR) {
    const void *return_type = upb_handlerattr_returnclosuretype(&set_attr);
    const void *table_return_type =
        upb_handlerattr_returnclosuretype(&h->table[sel].attr);
    if (return_type && table_return_type && return_type != table_return_type) {
      upb_status_seterrmsg(&h->status_, "closure return type does not match");
      return false;
    }

    if (table_return_type && !return_type)
      upb_handlerattr_setreturnclosuretype(&set_attr, table_return_type);
  }

  h->table[sel].func = (upb_func*)func;
  h->table[sel].attr = set_attr;
  return true;
}
Ejemplo n.º 17
0
static void seterr(upb_pbdecoder *d, const char *msg) {
  // TODO(haberman): encapsulate this access to pipeline->status, but not sure
  // exactly what that interface should look like.
  upb_status_seterrmsg(d->status, msg);
}
Ejemplo n.º 18
0
Archivo: symtab.c Proyecto: atdt/upb
/* 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;
}