| /* |
| * upb - a minimalist implementation of protocol buffers. |
| * |
| * Copyright (c) 2009 Joshua Haberman. See LICENSE for details. |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include "descriptor.h" |
| #include "upb_context.h" |
| #include "upb_enum.h" |
| #include "upb_msg.h" |
| |
| static int memrchr(char *data, char c, size_t len) |
| { |
| int off = len-1; |
| while(off > 0 && data[off] != c) --off; |
| return off; |
| } |
| |
| bool upb_context_init(struct upb_context *c) |
| { |
| upb_strtable_init(&c->symtab, 16, sizeof(struct upb_symtab_entry)); |
| /* Add all the types in descriptor.proto so we can parse descriptors. */ |
| if(!upb_context_addfd(c, &google_protobuf_filedescriptor)) |
| return false; /* Indicates that upb is buggy or corrupt. */ |
| return true; |
| } |
| |
| void upb_context_free(struct upb_context *c) |
| { |
| upb_strtable_free(&c->symtab); |
| for(size_t i = 0; i < c->fd_len; i++) free(c->fd[i]); |
| } |
| |
| struct upb_symtab_entry *upb_context_lookup(struct upb_context *c, |
| struct upb_string *symbol) |
| { |
| return upb_strtable_lookup(&c->symtab, symbol); |
| } |
| |
| static struct upb_symtab_entry *resolve(struct upb_strtable *t, |
| struct upb_string *base, |
| struct upb_string *symbol) |
| { |
| if(base->byte_len + symbol->byte_len + 1 >= UPB_SYM_MAX_LENGTH || |
| symbol->byte_len == 0) return NULL; |
| |
| if(symbol->data[0] == UPB_CONTEXT_SEPARATOR) { |
| /* Symbols starting with '.' are absolute, so we do a single lookup. */ |
| struct upb_string sym_str = {.data = symbol->data+1, |
| .byte_len = symbol->byte_len-1}; |
| return upb_strtable_lookup(t, &sym_str); |
| } else { |
| /* Remove components from base until we find an entry or run out. */ |
| char sym[UPB_SYM_MAX_LENGTH+1]; |
| struct upb_string sym_str = {.data = sym}; |
| int baselen = base->byte_len; |
| while(1) { |
| /* sym_str = base[0...base_len] + UPB_CONTEXT_SEPARATOR + symbol */ |
| memcpy(sym, base->data, baselen); |
| sym[baselen] = UPB_CONTEXT_SEPARATOR; |
| memcpy(sym + baselen + 1, symbol->data, symbol->byte_len); |
| sym_str.byte_len = baselen + symbol->byte_len + 1; |
| |
| struct upb_symtab_entry *e = upb_strtable_lookup(t, &sym_str); |
| if (e) return e; |
| else if(baselen == 0) return NULL; /* No more scopes to try. */ |
| |
| baselen = memrchr(base->data, UPB_CONTEXT_SEPARATOR, baselen); |
| } |
| } |
| } |
| |
| union upb_symbol_ref resolve2(struct upb_strtable *t1, struct upb_strtable *t2, |
| struct upb_string *base, struct upb_string *sym, |
| enum upb_symbol_type expected_type) { |
| union upb_symbol_ref nullref = {.msg = NULL}; |
| struct upb_symtab_entry *e = resolve(t1, base, sym); |
| if(e == NULL) e = resolve(t2, base, sym); |
| |
| if(e && e->type == expected_type) return e->ref; |
| else return nullref; |
| } |
| |
| |
| struct upb_symtab_entry *upb_context_resolve(struct upb_context *c, |
| struct upb_string *base, |
| struct upb_string *symbol) { |
| return resolve(&c->symtab, base, symbol); |
| } |
| |
| /* join("Foo.Bar", "Baz") -> "Foo.Bar.Baz" |
| * join("", "Baz") -> "Baz" |
| * Caller owns the returned string and must free it. */ |
| static struct upb_string join(struct upb_string *base, struct upb_string *name) { |
| size_t len = base->byte_len + name->byte_len; |
| if(base->byte_len > 0) len++; /* For the separator. */ |
| struct upb_string joined = {.byte_len=len, .data=malloc(len)}; |
| if(base->byte_len > 0) { |
| /* nested_base = base + '.' + d->name */ |
| memcpy(joined.data, base->data, base->byte_len); |
| joined.data[base->byte_len] = UPB_CONTEXT_SEPARATOR; |
| memcpy(&joined.data[base->byte_len+1], name->data, name->byte_len); |
| } else { |
| memcpy(joined.data, name->data, name->byte_len); |
| } |
| return joined; |
| } |
| |
| static bool insert_enum(struct upb_strtable *t, |
| google_protobuf_EnumDescriptorProto *ed, |
| struct upb_string *base) |
| { |
| if(!ed->set_flags.has.name) return false; |
| |
| /* We own this and must free it on destruct. */ |
| struct upb_string fqname = join(base, ed->name); |
| |
| /* Redefinition within a FileDescriptorProto is not allowed. */ |
| if(upb_strtable_lookup(t, &fqname)) { |
| free(fqname.data); |
| return false; |
| } |
| |
| struct upb_symtab_entry e; |
| e.e.key = fqname; |
| e.type = UPB_SYM_ENUM; |
| e.ref._enum = malloc(sizeof(*e.ref._enum)); |
| upb_enum_init(e.ref._enum, ed); |
| upb_strtable_insert(t, &e.e); |
| |
| return true; |
| } |
| |
| static bool insert_message(struct upb_strtable *t, |
| google_protobuf_DescriptorProto *d, |
| struct upb_string *base) |
| { |
| if(!d->set_flags.has.name) return false; |
| |
| /* We own this and must free it on destruct. */ |
| struct upb_string fqname = join(base, d->name); |
| |
| /* Redefinition within a FileDescriptorProto is not allowed. */ |
| if(upb_strtable_lookup(t, d->name)) { |
| free(fqname.data); |
| return false; |
| } |
| |
| struct upb_symtab_entry e; |
| e.e.key = fqname; |
| e.type = UPB_SYM_MESSAGE; |
| e.ref.msg = malloc(sizeof(*e.ref.msg)); |
| upb_msg_init(e.ref.msg, d); |
| upb_strtable_insert(t, &e.e); |
| |
| /* Add nested messages and enums. */ |
| if(d->set_flags.has.nested_type) |
| for(unsigned int i = 0; i < d->nested_type->len; i++) |
| if(!insert_message(t, d->nested_type->elements[i], &fqname)) |
| return false; |
| |
| if(d->set_flags.has.enum_type) |
| for(unsigned int i = 0; i < d->enum_type->len; i++) |
| if(!insert_enum(t, d->enum_type->elements[i], &fqname)) |
| return false; |
| |
| return true; |
| } |
| |
| bool upb_context_addfd(struct upb_context *c, |
| google_protobuf_FileDescriptorProto *fd) |
| { |
| struct upb_string package = {.byte_len=0}; |
| if(fd->set_flags.has.package) package = *fd->package; |
| |
| /* We want the entire add operation to be atomic, so we initially insert into |
| * this temporary map of symbols. Once we have verified that there are no |
| * errors (all symbols can be resolved and no illegal redefinitions occurred) |
| * only then do we insert into the context's table. */ |
| struct upb_strtable tmp; |
| int symcount = (fd->set_flags.has.message_type ? fd->message_type->len : 0) + |
| (fd->set_flags.has.enum_type ? fd->enum_type->len : 0) + |
| (fd->set_flags.has.service ? fd->service->len : 0); |
| upb_strtable_init(&tmp, symcount, sizeof(struct upb_symtab_entry)); |
| |
| if(fd->set_flags.has.message_type) |
| for(unsigned int i = 0; i < fd->message_type->len; i++) |
| if(!insert_message(&tmp, fd->message_type->elements[i], &package)) |
| goto error; |
| |
| if(fd->set_flags.has.enum_type) |
| for(unsigned int i = 0; i < fd->enum_type->len; i++) |
| if(!insert_enum(&tmp, fd->enum_type->elements[i], &package)) |
| goto error; |
| |
| /* TODO: handle extensions and services. */ |
| |
| /* Attempt to resolve all references. */ |
| struct upb_symtab_entry *e; |
| for(e = upb_strtable_begin(&tmp); e; e = upb_strtable_next(&tmp, &e->e)) { |
| if(upb_strtable_lookup(&c->symtab, &e->e.key)) |
| goto error; /* Redefinition prohibited. */ |
| if(e->type == UPB_SYM_MESSAGE) { |
| struct upb_msg *m = e->ref.msg; |
| for(unsigned int i = 0; i < m->num_fields; i++) { |
| struct upb_msg_field *f = &m->fields[i]; |
| google_protobuf_FieldDescriptorProto *fd = m->field_descriptors[i]; |
| union upb_symbol_ref ref; |
| if(fd->type == GOOGLE_PROTOBUF_FIELDDESCRIPTORPROTO_TYPE_MESSAGE) |
| ref = resolve2(&c->symtab, &tmp, &e->e.key, fd->name, UPB_SYM_MESSAGE); |
| else if(fd->type == GOOGLE_PROTOBUF_FIELDDESCRIPTORPROTO_TYPE_ENUM) |
| ref = resolve2(&c->symtab, &tmp, &e->e.key, fd->name, UPB_SYM_ENUM); |
| else |
| continue; /* No resolving necessary. */ |
| if(!ref.msg) goto error; /* Ref. to undefined symbol. */ |
| upb_msg_ref(m, f, ref); |
| } |
| } |
| } |
| |
| /* All references were successfully resolved -- add to the symbol table. */ |
| for(e = upb_strtable_begin(&tmp); e; e = upb_strtable_next(&tmp, &e->e)) |
| upb_strtable_insert(&c->symtab, &e->e); |
| |
| return true; |
| |
| error: |
| /* TODO */ |
| return false; |
| } |
| |
| bool upb_context_parsefd(struct upb_context *c, struct upb_string *fd_str) { |
| google_protobuf_FileDescriptorProto *fd = upb_msg_parse(c->fd_msg, fd_str); |
| if(!fd) return false; |
| if(!upb_context_addfd(c, fd)) return false; |
| c->fd[c->fd_len++] = fd; /* Need to keep a ref since we own it. */ |
| return true; |
| } |