summaryrefslogtreecommitdiff
path: root/tests/test_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_decoder.c')
-rw-r--r--tests/test_decoder.c700
1 files changed, 645 insertions, 55 deletions
diff --git a/tests/test_decoder.c b/tests/test_decoder.c
index 84a90cd..0db3bfa 100644
--- a/tests/test_decoder.c
+++ b/tests/test_decoder.c
@@ -1,76 +1,666 @@
+/*
+ * upb - a minimalist implementation of protocol buffers.
+ *
+ * Copyright (c) 2011 Google Inc. See LICENSE for details.
+ *
+ * An exhaustive set of tests for parsing both valid and invalid protobuf
+ * input, with buffer breaks in arbitrary places.
+ *
+ * Tests to add:
+ * - unknown field handler called appropriately
+ * - unknown fields can be inserted in random places
+ * - fuzzing of valid input
+ * - resource limits (max stack depth, max string len)
+ * - testing of groups
+ * - more throrough testing of sequences
+ * - test skipping of submessages
+ * - test suspending the decoder
+ * - buffers that are close enough to the end of the address space that
+ * pointers overflow (this might be difficult).
+ * - a few "kitchen sink" examples (one proto that uses all types, lots
+ * of submsg/sequences, etc.
+ */
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdint.h>
#include <stdlib.h>
-#include "upb/bytestream.h"
+#include <string.h>
+#include "upb/handlers.h"
#include "upb/pb/decoder.h"
-#include "upb/pb/glue.h"
-#include "upb/pb/textprinter.h"
+#include "upb/pb/varint.h"
+#include "upb/upb.h"
+#include "upb_test.h"
-int main(int argc, char *argv[]) {
- if (argc < 3) {
- fprintf(stderr, "Usage: test_decoder <descfile> <msgname>\n");
- return 1;
+typedef struct {
+ char *buf;
+ size_t len;
+} buffer;
+
+// Mem is initialized to NULL.
+buffer *buffer_new(size_t len) {
+ buffer *buf = malloc(sizeof(*buf));
+ buf->buf = malloc(len);
+ buf->len = len;
+ memset(buf->buf, 0, buf->len);
+ return buf;
+}
+
+buffer *buffer_new2(const void *data, size_t len) {
+ buffer *buf = buffer_new(len);
+ memcpy(buf->buf, data, len);
+ return buf;
+}
+
+buffer *buffer_new3(const char *data) {
+ return buffer_new2(data, strlen(data));
+}
+
+buffer *buffer_dup(buffer *buf) { return buffer_new2(buf->buf, buf->len); }
+
+void buffer_free(buffer *buf) {
+ free(buf->buf);
+ free(buf);
+}
+
+void buffer_appendf(buffer *buf, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ size_t size = buf->len;
+ buf->len += upb_vrprintf(&buf->buf, &size, buf->len, fmt, args);
+ va_end(args);
+}
+
+void buffer_cat(buffer *buf, buffer *buf2) {
+ size_t newlen = buf->len + buf2->len;
+ buf->buf = realloc(buf->buf, newlen);
+ memcpy(buf->buf + buf->len, buf2->buf, buf2->len);
+ buf->len = newlen;
+ buffer_free(buf2);
+}
+
+bool buffer_eql(buffer *buf, buffer *buf2) {
+ return buf->len == buf2->len && memcmp(buf->buf, buf2->buf, buf->len) == 0;
+}
+
+
+/* Routines for building arbitrary protos *************************************/
+
+buffer *cat(buffer *arg1, ...) {
+ va_list ap;
+ buffer *arg;
+ va_start(ap, arg1);
+ while ((arg = va_arg(ap, buffer*)) != NULL) {
+ buffer_cat(arg1, arg);
}
+ va_end(ap);
+ return arg1;
+}
+
+buffer *varint(uint64_t x) {
+ buffer *buf = buffer_new(UPB_PB_VARINT_MAX_LEN + 1);
+ buf->len = upb_vencode64(x, buf->buf);
+ return buf;
+}
+
+// TODO: proper byte-swapping for big-endian machines.
+buffer *fixed32(void *data) { return buffer_new2(data, 4); }
+buffer *fixed64(void *data) { return buffer_new2(data, 8); }
+
+buffer *delim(buffer *buf) { return cat( varint(buf->len), buf, NULL ); }
+buffer *uint32(uint32_t u32) { return fixed32(&u32); }
+buffer *uint64(uint64_t u64) { return fixed64(&u64); }
+buffer *flt(float f) { return fixed32(&f); }
+buffer *dbl(double d) { return fixed64(&d); }
+buffer *zz32(int32_t x) { return varint(upb_zzenc_32(x)); }
+buffer *zz64(int64_t x) { return varint(upb_zzenc_64(x)); }
+
+buffer *tag(uint32_t fieldnum, char wire_type) {
+ return varint((fieldnum << 3) | wire_type);
+}
+
+buffer *submsg(uint32_t fn, buffer *buf) {
+ return cat( tag(fn, UPB_WIRE_TYPE_DELIMITED), delim(buf), NULL );
+}
- upb_symtab *symtab = upb_symtab_new();
- size_t desc_len;
- const char *desc = upb_readfile(argv[1], &desc_len);
- if (!desc) {
- fprintf(stderr, "Couldn't open descriptor file: %s\n", argv[1]);
- return 1;
+
+/* A set of handlers that covers all .proto types *****************************/
+
+// The handlers simply append to a string indicating what handlers were called.
+// This string is similar to protobuf text format but fields are referred to by
+// number instead of name and sequences are explicitly delimited.
+
+#define VALUE_HANDLER(member, fmt) \
+ upb_flow_t value_ ## member(void *closure, upb_value fval, upb_value val) { \
+ buffer_appendf(closure, "%" PRIu32 ":%" fmt "; ", \
+ upb_value_getuint32(fval), upb_value_get ## member(val)); \
+ return UPB_CONTINUE; \
}
- upb_status status = UPB_STATUS_INIT;
- upb_load_descriptor_into_symtab(symtab, desc, desc_len, &status);
- if (!upb_ok(&status)) {
- fprintf(stderr, "Error parsing descriptor: %s", upb_status_getstr(&status));
- return 1;
+VALUE_HANDLER(uint32, PRIu32)
+VALUE_HANDLER(uint64, PRIu64)
+VALUE_HANDLER(int32, PRId32)
+VALUE_HANDLER(int64, PRId64)
+VALUE_HANDLER(float, "g")
+VALUE_HANDLER(double, "g")
+
+upb_flow_t value_bool(void *closure, upb_value fval, upb_value val) {
+ buffer_appendf(closure, "%" PRIu32 ":%s; ",
+ upb_value_getuint32(fval),
+ upb_value_getbool(val) ? "true" : "false");
+ return UPB_CONTINUE;
+}
+
+upb_flow_t value_string(void *closure, upb_value fval, upb_value val) {
+ // Note: won't work with strings that contain NULL.
+ char *str = upb_byteregion_strdup(upb_value_getbyteregion(val));
+ buffer_appendf(closure, "%" PRIu32 ":%s; ", upb_value_getuint32(fval), str);
+ free(str);
+ return UPB_CONTINUE;
+}
+
+upb_sflow_t startsubmsg(void *closure, upb_value fval) {
+ buffer_appendf(closure, "%" PRIu32 ":{ ", upb_value_getuint32(fval));
+ return UPB_CONTINUE_WITH(closure);
+}
+
+upb_flow_t endsubmsg(void *closure, upb_value fval) {
+ buffer_appendf(closure, "} ");
+ return UPB_CONTINUE;
+}
+
+upb_sflow_t startseq(void *closure, upb_value fval) {
+ buffer_appendf(closure, "%" PRIu32 ":[ ", upb_value_getuint32(fval));
+ return UPB_CONTINUE_WITH(closure);
+}
+
+upb_flow_t endseq(void *closure, upb_value fval) {
+ buffer_appendf(closure, "] ");
+ return UPB_CONTINUE;
+}
+
+void doreg(upb_mhandlers *m, uint32_t num, upb_fieldtype_t type, bool repeated,
+ upb_value_handler *handler) {
+ upb_fhandlers *f = upb_mhandlers_newfhandlers(m, num, type, repeated);
+ ASSERT(f);
+ upb_fhandlers_setvalue(f, handler);
+ upb_fhandlers_setstartseq(f, &startseq);
+ upb_fhandlers_setendseq(f, &endseq);
+ upb_fhandlers_setfval(f, upb_value_uint32(num));
+}
+
+// The repeated field number to correspond to the given non-repeated field
+// number.
+uint32_t rep_fn(uint32_t fn) {
+ return (UPB_MAX_FIELDNUMBER - 1000) + fn;
+}
+
+#define NOP_FIELD 40
+#define UNKNOWN_FIELD 666
+
+void reg(upb_mhandlers *m, upb_fieldtype_t type, upb_value_handler *handler) {
+ // We register both a repeated and a non-repeated field for every type.
+ // For the non-repeated field we make the field number the same as the
+ // type. For the repeated field we make it a function of the type.
+ doreg(m, type, type, false, handler);
+ doreg(m, rep_fn(type), type, true, handler);
+}
+
+void reg_subm(upb_mhandlers *m, uint32_t num, upb_fieldtype_t type,
+ bool repeated) {
+ upb_fhandlers *f =
+ upb_mhandlers_newfhandlers_subm(m, num, type, repeated, m);
+ ASSERT(f);
+ upb_fhandlers_setstartseq(f, &startseq);
+ upb_fhandlers_setendseq(f, &endseq);
+ upb_fhandlers_setstartsubmsg(f, &startsubmsg);
+ upb_fhandlers_setendsubmsg(f, &endsubmsg);
+ upb_fhandlers_setfval(f, upb_value_uint32(num));
+}
+
+void reghandlers(upb_mhandlers *m) {
+ // Register handlers for each type.
+ reg(m, UPB_TYPE(DOUBLE), &value_double);
+ reg(m, UPB_TYPE(FLOAT), &value_float);
+ reg(m, UPB_TYPE(INT64), &value_int64);
+ reg(m, UPB_TYPE(UINT64), &value_uint64);
+ reg(m, UPB_TYPE(INT32) , &value_int32);
+ reg(m, UPB_TYPE(FIXED64), &value_uint64);
+ reg(m, UPB_TYPE(FIXED32), &value_uint32);
+ reg(m, UPB_TYPE(BOOL), &value_bool);
+ reg(m, UPB_TYPE(STRING), &value_string);
+ reg(m, UPB_TYPE(BYTES), &value_string);
+ reg(m, UPB_TYPE(UINT32), &value_uint32);
+ reg(m, UPB_TYPE(ENUM), &value_int32);
+ reg(m, UPB_TYPE(SFIXED32), &value_int32);
+ reg(m, UPB_TYPE(SFIXED64), &value_int64);
+ reg(m, UPB_TYPE(SINT32), &value_int32);
+ reg(m, UPB_TYPE(SINT64), &value_int64);
+
+ // Register submessage/group handlers that are self-recursive
+ // to this type, eg: message M { optional M m = 1; }
+ reg_subm(m, UPB_TYPE(MESSAGE), UPB_TYPE(MESSAGE), false);
+ reg_subm(m, UPB_TYPE(GROUP), UPB_TYPE(GROUP), false);
+ reg_subm(m, rep_fn(UPB_TYPE(MESSAGE)), UPB_TYPE(MESSAGE), true);
+ reg_subm(m, rep_fn(UPB_TYPE(GROUP)), UPB_TYPE(GROUP), true);
+
+ // Register a no-op string field so we can pad the proto wherever we want.
+ upb_mhandlers_newfhandlers(m, NOP_FIELD, UPB_TYPE(STRING), false);
+}
+
+
+/* Custom bytesrc that can insert buffer seams in arbitrary places ************/
+
+typedef struct {
+ upb_bytesrc bytesrc;
+ const char *str;
+ size_t len, seam1, seam2;
+ upb_byteregion byteregion;
+} upb_seamsrc;
+
+size_t upb_seamsrc_avail(const upb_seamsrc *src, size_t ofs) {
+ if (ofs < src->seam1) return src->seam1 - ofs;
+ if (ofs < src->seam2) return src->seam2 - ofs;
+ return src->len - ofs;
+}
+
+upb_bytesuccess_t upb_seamsrc_fetch(void *_src, uint64_t ofs, size_t *read) {
+ upb_seamsrc *src = _src;
+ assert(ofs < src->len);
+ if (ofs == src->len) {
+ upb_status_seteof(&src->bytesrc.status);
+ return UPB_BYTE_EOF;
}
- free((void*)desc);
+ *read = upb_seamsrc_avail(src, ofs);
+ return UPB_BYTE_OK;
+}
+
+void upb_seamsrc_copy(const void *_src, uint64_t ofs,
+ size_t len, char *dst) {
+ const upb_seamsrc *src = _src;
+ assert(ofs + len <= src->len);
+ memcpy(dst, src->str + ofs, len);
+}
+
+void upb_seamsrc_discard(void *src, uint64_t ofs) {
+ (void)src;
+ (void)ofs;
+}
+
+const char *upb_seamsrc_getptr(const void *_s, uint64_t ofs, size_t *len) {
+ const upb_seamsrc *src = _s;
+ *len = upb_seamsrc_avail(src, ofs);
+ return src->str + ofs;
+}
- const upb_def *md = upb_symtab_lookup(symtab, argv[2]);
- if (!md) {
- fprintf(stderr, "Descriptor did not contain message: %s\n", argv[2]);
- return 1;
+void upb_seamsrc_init(upb_seamsrc *s, const char *str, size_t len) {
+ static upb_bytesrc_vtbl vtbl = {
+ &upb_seamsrc_fetch,
+ &upb_seamsrc_discard,
+ &upb_seamsrc_copy,
+ &upb_seamsrc_getptr,
+ };
+ upb_bytesrc_init(&s->bytesrc, &vtbl);
+ s->seam1 = 0;
+ s->seam2 = 0;
+ s->str = str;
+ s->len = len;
+ s->byteregion.bytesrc = &s->bytesrc;
+ s->byteregion.toplevel = true;
+ s->byteregion.start = 0;
+ s->byteregion.end = len;
+}
+
+void upb_seamsrc_resetseams(upb_seamsrc *s, size_t seam1, size_t seam2) {
+ ASSERT(seam1 <= seam2);
+ s->seam1 = seam1;
+ s->seam2 = seam2;
+ s->byteregion.discard = 0;
+ s->byteregion.fetch = 0;
+}
+
+void upb_seamsrc_uninit(upb_seamsrc *s) { (void)s; }
+
+upb_bytesrc *upb_seamsrc_bytesrc(upb_seamsrc *s) {
+ return &s->bytesrc;
+}
+
+// Returns the top-level upb_byteregion* for this seamsrc. Invalidated when
+// the seamsrc is reset.
+upb_byteregion *upb_seamsrc_allbytes(upb_seamsrc *s) {
+ return &s->byteregion;
+}
+
+
+/* Running of test cases ******************************************************/
+
+upb_decoderplan *plan;
+
+void run_decoder(buffer *proto, buffer *expected_output) {
+ upb_seamsrc src;
+ upb_seamsrc_init(&src, proto->buf, proto->len);
+ upb_decoder d;
+ upb_decoder_init(&d);
+ upb_decoder_resetplan(&d, plan, 0);
+ for (size_t i = 0; i < proto->len; i++) {
+ for (size_t j = i; j < proto->len; j++) {
+ upb_seamsrc_resetseams(&src, i, j);
+ upb_byteregion *input = upb_seamsrc_allbytes(&src);
+ buffer *output = buffer_new(0);
+ upb_decoder_resetinput(&d, input, output);
+ upb_success_t success = UPB_SUSPENDED;
+ while (success == UPB_SUSPENDED)
+ success = upb_decoder_decode(&d);
+ ASSERT(upb_ok(upb_decoder_status(&d)) == (success == UPB_OK));
+ if (expected_output) {
+ ASSERT(success == UPB_OK);
+ // The input should be fully consumed.
+ ASSERT(upb_byteregion_fetchofs(input) == upb_byteregion_endofs(input));
+ ASSERT(upb_byteregion_discardofs(input) ==
+ upb_byteregion_endofs(input));
+ if (!buffer_eql(output, expected_output)) {
+ fprintf(stderr, "Text mismatch: '%s' vs '%s'\n",
+ output->buf, expected_output->buf);
+ }
+ ASSERT(strcmp(output->buf, expected_output->buf) == 0);
+ } else {
+ ASSERT(success == UPB_ERROR);
+ }
+ buffer_free(output);
+ }
}
+ upb_seamsrc_uninit(&src);
+ upb_decoder_uninit(&d);
+ buffer_free(proto);
+}
+
+void assert_successful_parse_at_eof(buffer *proto, const char *expected_fmt,
+ va_list args) {
+ buffer *expected_text = buffer_new(0);
+ size_t size = expected_text->len;
+ expected_text->len += upb_vrprintf(&expected_text->buf, &size,
+ expected_text->len, expected_fmt, args);
+ run_decoder(proto, expected_text);
+ buffer_free(expected_text);
+}
+
+void assert_does_not_parse_at_eof(buffer *proto) {
+ run_decoder(proto, NULL);
+}
+
+void assert_successful_parse(buffer *proto, const char *expected_fmt, ...) {
+ // The JIT is only used for data >=20 bytes from end-of-buffer, so
+ // repeat once with no-op padding data at the end of buffer.
+ va_list args, args2;
+ va_start(args, expected_fmt);
+ va_copy(args2, args);
+ assert_successful_parse_at_eof(buffer_dup(proto), expected_fmt, args);
+ assert_successful_parse_at_eof(
+ cat( proto,
+ tag(NOP_FIELD, UPB_WIRE_TYPE_DELIMITED), delim(buffer_new(30)),
+ NULL ),
+ expected_fmt, args2);
+ va_end(args);
+ va_end(args2);
+}
+
+void assert_does_not_parse(buffer *proto) {
+ // The JIT is only used for data >=20 bytes from end-of-buffer, so
+ // repeat once with no-op padding data at the end of buffer.
+ assert_does_not_parse_at_eof(buffer_dup(proto));
+ assert_does_not_parse_at_eof(
+ cat( proto,
+ tag(NOP_FIELD, UPB_WIRE_TYPE_DELIMITED), delim( buffer_new(30)),
+ NULL ));
+}
+
+
+/* The actual tests ***********************************************************/
+
+void test_premature_eof_for_type(upb_fieldtype_t type) {
+ // Incomplete values for each wire type.
+ static const char *incompletes[] = {
+ "\x80", // UPB_WIRE_TYPE_VARINT
+ "abcdefg", // UPB_WIRE_TYPE_64BIT
+ "\x80", // UPB_WIRE_TYPE_DELIMITED (partial length)
+ NULL, // UPB_WIRE_TYPE_START_GROUP (no value required)
+ NULL, // UPB_WIRE_TYPE_END_GROUP (no value required)
+ "abc" // UPB_WIRE_TYPE_32BIT
+ };
+
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_types[type].native_wire_type;
+ const char *incomplete = incompletes[wire_type];
+
+ // EOF before a known non-repeated value.
+ assert_does_not_parse_at_eof(tag(fieldnum, wire_type));
+
+ // EOF before a known repeated value.
+ assert_does_not_parse_at_eof(tag(rep_fieldnum, wire_type));
+
+ // EOF before an unknown value.
+ assert_does_not_parse_at_eof(tag(UNKNOWN_FIELD, wire_type));
+
+ // EOF inside a known non-repeated value.
+ assert_does_not_parse_at_eof(
+ cat( tag(fieldnum, wire_type), buffer_new3(incomplete), NULL ));
+
+ // EOF inside a known repeated value.
+ assert_does_not_parse_at_eof(
+ cat( tag(rep_fieldnum, wire_type), buffer_new3(incomplete), NULL ));
+
+ // EOF inside an unknown value.
+ assert_does_not_parse_at_eof(
+ cat( tag(UNKNOWN_FIELD, wire_type), buffer_new3(incomplete), NULL ));
+
+ if (wire_type == UPB_WIRE_TYPE_DELIMITED) {
+ // EOF in the middle of delimited data for known non-repeated value.
+ assert_does_not_parse_at_eof(
+ cat( tag(fieldnum, wire_type), varint(1), NULL ));
+
+ // EOF in the middle of delimited data for known repeated value.
+ assert_does_not_parse_at_eof(
+ cat( tag(rep_fieldnum, wire_type), varint(1), NULL ));
- const upb_msgdef *m = upb_dyncast_msgdef_const(md);
- if (!m) {
- fprintf(stderr, "Def was not a msgdef.\n");
- return 1;
+ // EOF in the middle of delimited data for unknown value.
+ assert_does_not_parse_at_eof(
+ cat( tag(UNKNOWN_FIELD, wire_type), varint(1), NULL ));
+
+ if (type == UPB_TYPE(MESSAGE)) {
+ // Submessage ends in the middle of a value.
+ buffer *incomplete_submsg =
+ cat ( tag(UPB_TYPE(INT32), UPB_WIRE_TYPE_VARINT),
+ buffer_new3(incompletes[UPB_WIRE_TYPE_VARINT]), NULL );
+ assert_does_not_parse(
+ cat( tag(fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ varint(incomplete_submsg->len),
+ incomplete_submsg, NULL ));
+ }
+ } else {
+ // Packed region ends in the middle of a value.
+ assert_does_not_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ varint(strlen(incomplete)),
+ buffer_new3(incomplete), NULL ));
+
+ // EOF in the middle of packed region.
+ assert_does_not_parse_at_eof(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED), varint(1), NULL ));
}
+}
- upb_stdio in, out;
- upb_stdio_init(&in);
- upb_stdio_init(&out);
- upb_stdio_reset(&in, stdin);
- upb_stdio_reset(&out, stdout);
+// "33" and "66" are just two random values that all numeric types can
+// represent.
+void test_valid_data_for_type(upb_fieldtype_t type,
+ buffer *enc33, buffer *enc66) {
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_types[type].native_wire_type;
- upb_handlers *handlers = upb_handlers_new();
- upb_textprinter *p = upb_textprinter_new();
- upb_textprinter_reset(p, upb_stdio_bytesink(&out), false);
- upb_textprinter_reghandlers(handlers, m);
+ // Non-repeated
+ assert_successful_parse(
+ cat( tag(fieldnum, wire_type), buffer_dup(enc33),
+ tag(fieldnum, wire_type), buffer_dup(enc66), NULL ),
+ "%u:33; %u:66; ", fieldnum, fieldnum);
- upb_decoder d;
- upb_decoder_init(&d, handlers);
- upb_decoder_reset(&d, upb_stdio_allbytes(&in), p);
+ // Non-packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, wire_type), buffer_dup(enc33),
+ tag(rep_fieldnum, wire_type), buffer_dup(enc66), NULL ),
+ "%u:[ %u:33; %u:66; ] ", rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ // Packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ delim(cat( buffer_dup(enc33), buffer_dup(enc66), NULL )), NULL ),
+ "%u:[ %u:33; %u:66; ] ", rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ buffer_free(enc33);
+ buffer_free(enc66);
+}
+
+void test_valid_data_for_signed_type(upb_fieldtype_t type,
+ buffer *enc33, buffer *enc66) {
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_types[type].native_wire_type;
+
+ // Non-repeated
+ assert_successful_parse(
+ cat( tag(fieldnum, wire_type), buffer_dup(enc33),
+ tag(fieldnum, wire_type), buffer_dup(enc66), NULL ),
+ "%u:33; %u:-66; ", fieldnum, fieldnum);
+
+ // Non-packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, wire_type), buffer_dup(enc33),
+ tag(rep_fieldnum, wire_type), buffer_dup(enc66), NULL ),
+ "%u:[ %u:33; %u:-66; ] ", rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ // Packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ delim(cat( buffer_dup(enc33), buffer_dup(enc66), NULL )), NULL ),
+ "%u:[ %u:33; %u:-66; ] ", rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ buffer_free(enc33);
+ buffer_free(enc66);
+}
+
+// Test that invalid protobufs are properly detected (without crashing) and
+// have an error reported. Field numbers match registered handlers above.
+void test_invalid() {
+ test_premature_eof_for_type(UPB_TYPE(DOUBLE));
+ test_premature_eof_for_type(UPB_TYPE(FLOAT));
+ test_premature_eof_for_type(UPB_TYPE(INT64));
+ test_premature_eof_for_type(UPB_TYPE(UINT64));
+ test_premature_eof_for_type(UPB_TYPE(INT32));
+ test_premature_eof_for_type(UPB_TYPE(FIXED64));
+ test_premature_eof_for_type(UPB_TYPE(FIXED32));
+ test_premature_eof_for_type(UPB_TYPE(BOOL));
+ test_premature_eof_for_type(UPB_TYPE(STRING));
+ test_premature_eof_for_type(UPB_TYPE(BYTES));
+ test_premature_eof_for_type(UPB_TYPE(UINT32));
+ test_premature_eof_for_type(UPB_TYPE(ENUM));
+ test_premature_eof_for_type(UPB_TYPE(SFIXED32));
+ test_premature_eof_for_type(UPB_TYPE(SFIXED64));
+ test_premature_eof_for_type(UPB_TYPE(SINT32));
+ test_premature_eof_for_type(UPB_TYPE(SINT64));
+
+ // EOF inside a tag's varint.
+ assert_does_not_parse_at_eof( buffer_new3("\x80") );
+
+ // EOF inside a known group.
+ assert_does_not_parse_at_eof( tag(4, UPB_WIRE_TYPE_START_GROUP) );
+
+ // EOF inside an unknown group.
+ assert_does_not_parse_at_eof( tag(UNKNOWN_FIELD, UPB_WIRE_TYPE_START_GROUP) );
- upb_status_clear(&status);
- upb_decoder_decode(&d, &status);
+ // End group that we are not currently in.
+ assert_does_not_parse( tag(4, UPB_WIRE_TYPE_END_GROUP) );
- if (!upb_ok(&status)) {
- fprintf(stderr, "Error parsing input: %s", upb_status_getstr(&status));
+ // Field number is 0.
+ assert_does_not_parse(
+ cat( tag(0, UPB_WIRE_TYPE_DELIMITED), varint(0), NULL ));
+
+ // Field number is too large.
+ assert_does_not_parse(
+ cat( tag(UPB_MAX_FIELDNUMBER + 1, UPB_WIRE_TYPE_DELIMITED),
+ varint(0), NULL ));
+
+ // Test exceeding the resource limit of stack depth.
+ buffer *buf = buffer_new3("");
+ for (int i = 0; i < UPB_MAX_NESTING; i++) {
+ buf = submsg(UPB_TYPE(MESSAGE), buf);
}
+ assert_does_not_parse(buf);
- upb_status_uninit(&status);
- upb_stdio_uninit(&in);
- upb_stdio_uninit(&out);
- upb_decoder_uninit(&d);
- upb_textprinter_free(p);
- upb_def_unref(UPB_UPCAST(m));
- upb_symtab_unref(symtab);
-
- // Prevent C library from holding buffers open, so Valgrind doesn't see
- // memory leaks.
- fclose(stdin);
- fclose(stdout);
+ // Staying within the stack limit should work properly.
+ buf = buffer_new3("");
+ buffer *textbuf = buffer_new3("");
+ int total = UPB_MAX_NESTING - 1;
+ for (int i = 0; i < total; i++) {
+ buf = submsg(UPB_TYPE(MESSAGE), buf);
+ buffer_appendf(textbuf, "%u:{ ", UPB_TYPE(MESSAGE));
+ }
+ for (int i = 0; i < total; i++) {
+ buffer_appendf(textbuf, "} ");
+ }
+ assert_successful_parse(buf, "%s", textbuf->buf);
+ buffer_free(textbuf);
+}
+
+void test_valid() {
+ test_valid_data_for_signed_type(UPB_TYPE(DOUBLE), dbl(33), dbl(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(FLOAT), flt(33), flt(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(INT64), varint(33), varint(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(INT32), varint(33), varint(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(ENUM), varint(33), varint(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(SFIXED32), uint32(33), uint32(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(SFIXED64), uint64(33), uint64(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(SINT32), zz32(33), zz32(-66));
+ test_valid_data_for_signed_type(UPB_TYPE(SINT64), zz64(33), zz64(-66));
+
+ test_valid_data_for_type(UPB_TYPE(UINT64), varint(33), varint(66));
+ test_valid_data_for_type(UPB_TYPE(UINT32), varint(33), varint(66));
+ test_valid_data_for_type(UPB_TYPE(FIXED64), uint64(33), uint64(66));
+ test_valid_data_for_type(UPB_TYPE(FIXED32), uint32(33), uint32(66));
+
+ // Submessage tests.
+ uint32_t msg_fn = UPB_TYPE(MESSAGE);
+ assert_successful_parse(
+ submsg(msg_fn, submsg(msg_fn, submsg(msg_fn, buffer_new3("")))),
+ "%u:{ %u:{ %u:{ } } } ", msg_fn, msg_fn, msg_fn);
+
+ uint32_t repm_fn = rep_fn(UPB_TYPE(MESSAGE));
+ assert_successful_parse(
+ submsg(repm_fn, submsg(repm_fn, buffer_new3(""))),
+ "%u:[ %u:{ %u:[ %u:{ } ] } ] ", repm_fn, repm_fn, repm_fn, repm_fn);
+}
+
+void run_tests() {
+ test_invalid();
+ test_valid();
+}
+
+int main() {
+ // Construct decoder plan.
+ upb_handlers *h = upb_handlers_new();
+ reghandlers(upb_handlers_newmhandlers(h));
+
+ // Test without JIT.
+ plan = upb_decoderplan_new(h, false);
+ run_tests();
+ upb_decoderplan_unref(plan);
+
+ // Test JIT.
+ plan = upb_decoderplan_new(h, true);
+ run_tests();
+ upb_decoderplan_unref(plan);
+
+ plan = NULL;
+ printf("All tests passed, %d assertions.\n", num_assertions);
+ upb_handlers_unref(h);
+ return 0;
}
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback