summaryrefslogtreecommitdiff
path: root/tests/test_decoder.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_decoder.cc')
-rw-r--r--tests/test_decoder.cc811
1 files changed, 811 insertions, 0 deletions
diff --git a/tests/test_decoder.cc b/tests/test_decoder.cc
new file mode 100644
index 0000000..13403bb
--- /dev/null
+++ b/tests/test_decoder.cc
@@ -0,0 +1,811 @@
+/*
+ * 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.
+ */
+
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS // For PRIuS, etc.
+#endif
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "upb/handlers.h"
+#include "upb/pb/decoder.h"
+#include "upb/pb/varint.h"
+#include "upb/upb.h"
+#include "upb_test.h"
+
+// Copied from decoder.c, since this is not a public interface.
+typedef struct {
+ uint8_t native_wire_type;
+ bool is_numeric;
+} upb_decoder_typeinfo;
+
+static const upb_decoder_typeinfo upb_decoder_types[] = {
+ {UPB_WIRE_TYPE_END_GROUP, false}, // ENDGROUP
+ {UPB_WIRE_TYPE_64BIT, true}, // DOUBLE
+ {UPB_WIRE_TYPE_32BIT, true}, // FLOAT
+ {UPB_WIRE_TYPE_VARINT, true}, // INT64
+ {UPB_WIRE_TYPE_VARINT, true}, // UINT64
+ {UPB_WIRE_TYPE_VARINT, true}, // INT32
+ {UPB_WIRE_TYPE_64BIT, true}, // FIXED64
+ {UPB_WIRE_TYPE_32BIT, true}, // FIXED32
+ {UPB_WIRE_TYPE_VARINT, true}, // BOOL
+ {UPB_WIRE_TYPE_DELIMITED, false}, // STRING
+ {UPB_WIRE_TYPE_START_GROUP, false}, // GROUP
+ {UPB_WIRE_TYPE_DELIMITED, false}, // MESSAGE
+ {UPB_WIRE_TYPE_DELIMITED, false}, // BYTES
+ {UPB_WIRE_TYPE_VARINT, true}, // UINT32
+ {UPB_WIRE_TYPE_VARINT, true}, // ENUM
+ {UPB_WIRE_TYPE_32BIT, true}, // SFIXED32
+ {UPB_WIRE_TYPE_64BIT, true}, // SFIXED64
+ {UPB_WIRE_TYPE_VARINT, true}, // SINT32
+ {UPB_WIRE_TYPE_VARINT, true}, // SINT64
+};
+
+
+class buffer {
+ public:
+ buffer(const void *data, size_t len) : len_(0) { append(data, len); }
+ explicit buffer(const char *data) : len_(0) { append(data); }
+ explicit buffer(size_t len) : len_(len) { memset(buf_, 0, len); }
+ buffer(const buffer& buf) : len_(0) { append(buf); }
+ buffer() : len_(0) {}
+
+ void append(const void *data, size_t len) {
+ ASSERT_NOCOUNT(len + len_ < sizeof(buf_));
+ memcpy(buf_ + len_, data, len);
+ len_ += len;
+ buf_[len_] = NULL;
+ }
+
+ void append(const buffer& buf) {
+ append(buf.buf_, buf.len_);
+ }
+
+ void append(const char *str) {
+ append(str, strlen(str));
+ }
+
+ void vappendf(const char *fmt, va_list args) {
+ size_t avail = sizeof(buf_) - len_;
+ size_t size = vsnprintf(buf_ + len_, avail, fmt, args);
+ ASSERT_NOCOUNT(avail > size);
+ len_ += size;
+ }
+
+ void appendf(const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vappendf(fmt, args);
+ va_end(args);
+ }
+
+ void assign(const buffer& buf) {
+ clear();
+ append(buf);
+ }
+
+ bool eql(const buffer& other) const {
+ return len_ == other.len_ && memcmp(buf_, other.buf_, len_) == 0;
+ }
+
+ void clear() { len_ = 0; }
+ size_t len() const { return len_; }
+ const char *buf() const { return buf_; }
+
+ private:
+ // Has to be big enough for the largest string used in the test.
+ char buf_[32768];
+ size_t len_;
+};
+
+
+/* Routines for building arbitrary protos *************************************/
+
+const buffer empty;
+
+buffer cat(const buffer& a, const buffer& b,
+ const buffer& c = empty,
+ const buffer& d = empty,
+ const buffer& e = empty) {
+ buffer ret;
+ ret.append(a);
+ ret.append(b);
+ ret.append(c);
+ ret.append(d);
+ ret.append(e);
+ return ret;
+}
+
+buffer varint(uint64_t x) {
+ char buf[UPB_PB_VARINT_MAX_LEN];
+ size_t len = upb_vencode64(x, buf);
+ return buffer(buf, len);
+}
+
+// TODO: proper byte-swapping for big-endian machines.
+buffer fixed32(void *data) { return buffer(data, 4); }
+buffer fixed64(void *data) { return buffer(data, 8); }
+
+buffer delim(const buffer& buf) { return cat(varint(buf.len()), buf); }
+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, const buffer& buf) {
+ return cat( tag(fn, UPB_WIRE_TYPE_DELIMITED), delim(buf) );
+}
+
+
+/* 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. We indent
+// using the closure depth to test that the stack of closures is properly
+// handled.
+
+int closures[UPB_MAX_NESTING];
+buffer output;
+
+void indentbuf(buffer *buf, int depth) {
+ for (int i = 0; i < depth; i++)
+ buf->append(" ", 2);
+}
+
+void indent(void *depth) {
+ indentbuf(&output, *(int*)depth);
+}
+
+#define VALUE_HANDLER(member, fmt) \
+ upb_flow_t value_ ## member(void *closure, upb_value fval, upb_value val) { \
+ indent(closure); \
+ output.appendf("%" PRIu32 ":%" fmt "\n", \
+ upb_value_getuint32(fval), upb_value_get ## member(val)); \
+ return UPB_CONTINUE; \
+ }
+
+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) {
+ indent(closure);
+ output.appendf("%" PRIu32 ":%s\n",
+ 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.
+ indent(closure);
+ char *str = upb_byteregion_strdup(upb_value_getbyteregion(val));
+ output.appendf("%" PRIu32 ":%s\n", upb_value_getuint32(fval), str);
+ free(str);
+ return UPB_CONTINUE;
+}
+
+upb_sflow_t startsubmsg(void *closure, upb_value fval) {
+ indent(closure);
+ output.appendf("%" PRIu32 ":{\n", upb_value_getuint32(fval));
+ return UPB_CONTINUE_WITH(((int*)closure) + 1);
+}
+
+upb_flow_t endsubmsg(void *closure, upb_value fval) {
+ indent(closure);
+ output.append("}\n");
+ return UPB_CONTINUE;
+}
+
+upb_sflow_t startseq(void *closure, upb_value fval) {
+ indent(closure);
+ output.appendf("%" PRIu32 ":[\n", upb_value_getuint32(fval));
+ return UPB_CONTINUE_WITH(((int*)closure) + 1);
+}
+
+upb_flow_t endseq(void *closure, upb_value fval) {
+ indent(closure);
+ output.append("]\n");
+ return UPB_CONTINUE;
+}
+
+upb_flow_t startmsg(void *closure) {
+ indent(closure);
+ output.append("<\n");
+ return UPB_CONTINUE;
+}
+
+void endmsg(void *closure, upb_status *status) {
+ (void)status;
+ indent(closure);
+ output.append(">\n");
+}
+
+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) {
+ upb_mhandlers_setstartmsg(m, &startmsg);
+ upb_mhandlers_setendmsg(m, &endmsg);
+
+ // 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 = (upb_seamsrc*)_src;
+ assert(ofs < src->len);
+ if (ofs == src->len) {
+ upb_status_seteof(&src->bytesrc.status);
+ return UPB_BYTE_EOF;
+ }
+ *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 = (const upb_seamsrc*)_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 = (const upb_seamsrc*)_s;
+ *len = upb_seamsrc_avail(src, ofs);
+ return src->str + ofs;
+}
+
+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;
+#define LINE(x) x "\n"
+void run_decoder(const buffer& proto, const 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 < UPB_MIN(proto.len(), i + 5); j++) {
+ upb_seamsrc_resetseams(&src, i, j);
+ upb_byteregion *input = upb_seamsrc_allbytes(&src);
+ output.clear();
+ upb_decoder_resetinput(&d, input, &closures[0]);
+ 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_STATUS(success == UPB_OK, upb_decoder_status(&d));
+ // 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 (!output.eql(*expected_output)) {
+ fprintf(stderr, "Text mismatch: '%s' vs '%s'\n",
+ output.buf(), expected_output->buf());
+ }
+ ASSERT(output.eql(*expected_output));
+ } else {
+ ASSERT(success == UPB_ERROR);
+ }
+ }
+ }
+ upb_decoder_uninit(&d);
+ upb_seamsrc_uninit(&src);
+}
+
+const static buffer thirty_byte_nop = buffer(cat(
+ tag(NOP_FIELD, UPB_WIRE_TYPE_DELIMITED), delim(buffer(30)) ));
+
+void assert_successful_parse(const buffer& proto,
+ const char *expected_fmt, ...) {
+ buffer expected_text;
+ va_list args;
+ va_start(args, expected_fmt);
+ expected_text.vappendf(expected_fmt, args);
+ va_end(args);
+ // 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.
+ run_decoder(proto, &expected_text);
+ run_decoder(cat( proto, thirty_byte_nop ), &expected_text);
+}
+
+void assert_does_not_parse_at_eof(const buffer& proto) {
+ run_decoder(proto, NULL);
+}
+
+void assert_does_not_parse(const 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(proto);
+ assert_does_not_parse_at_eof(cat( proto, thirty_byte_nop ));
+}
+
+
+/* The actual tests ***********************************************************/
+
+void test_premature_eof_for_type(upb_fieldtype_t type) {
+ // Incomplete values for each wire type.
+ static const buffer incompletes[6] = {
+ buffer("\x80"), // UPB_WIRE_TYPE_VARINT
+ buffer("abcdefg"), // UPB_WIRE_TYPE_64BIT
+ buffer("\x80"), // UPB_WIRE_TYPE_DELIMITED (partial length)
+ buffer(), // UPB_WIRE_TYPE_START_GROUP (no value required)
+ buffer(), // UPB_WIRE_TYPE_END_GROUP (no value required)
+ buffer("abc") // UPB_WIRE_TYPE_32BIT
+ };
+
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_decoder_types[type].native_wire_type;
+ const buffer& 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), incomplete ));
+
+ // EOF inside a known repeated value.
+ assert_does_not_parse_at_eof(
+ cat( tag(rep_fieldnum, wire_type), incomplete ));
+
+ // EOF inside an unknown value.
+ assert_does_not_parse_at_eof(
+ cat( tag(UNKNOWN_FIELD, wire_type), incomplete ));
+
+ 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) ));
+
+ // 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) ));
+
+ // EOF in the middle of delimited data for unknown value.
+ assert_does_not_parse_at_eof(
+ cat( tag(UNKNOWN_FIELD, wire_type), varint(1) ));
+
+ 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),
+ incompletes[UPB_WIRE_TYPE_VARINT] );
+ assert_does_not_parse(
+ cat( tag(fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ varint(incomplete_submsg.len()),
+ incomplete_submsg ));
+ }
+ } else {
+ // Packed region ends in the middle of a value.
+ assert_does_not_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ varint(incomplete.len()),
+ incomplete ));
+
+ // EOF in the middle of packed region.
+ assert_does_not_parse_at_eof(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED), varint(1) ));
+ }
+}
+
+// "33" and "66" are just two random values that all numeric types can
+// represent.
+void test_valid_data_for_type(upb_fieldtype_t type,
+ const buffer& enc33, const buffer& enc66) {
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_decoder_types[type].native_wire_type;
+
+ // Non-repeated
+ assert_successful_parse(
+ cat( tag(fieldnum, wire_type), enc33,
+ tag(fieldnum, wire_type), enc66 ),
+ LINE("<")
+ LINE("%u:33")
+ LINE("%u:66")
+ LINE(">"), fieldnum, fieldnum);
+
+ // Non-packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, wire_type), enc33,
+ tag(rep_fieldnum, wire_type), enc66 ),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:33")
+ LINE(" %u:66")
+ LINE("]")
+ LINE(">"), rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ // Packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ delim(cat( enc33, enc66 )) ),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:33")
+ LINE(" %u:66")
+ LINE("]")
+ LINE(">"), rep_fieldnum, rep_fieldnum, rep_fieldnum);
+}
+
+void test_valid_data_for_signed_type(upb_fieldtype_t type,
+ const buffer& enc33, const buffer& enc66) {
+ uint32_t fieldnum = type;
+ uint32_t rep_fieldnum = rep_fn(type);
+ int wire_type = upb_decoder_types[type].native_wire_type;
+
+ // Non-repeated
+ assert_successful_parse(
+ cat( tag(fieldnum, wire_type), enc33,
+ tag(fieldnum, wire_type), enc66 ),
+ LINE("<")
+ LINE("%u:33")
+ LINE("%u:-66")
+ LINE(">"), fieldnum, fieldnum);
+
+ // Non-packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, wire_type), enc33,
+ tag(rep_fieldnum, wire_type), enc66 ),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:33")
+ LINE(" %u:-66")
+ LINE("]")
+ LINE(">"), rep_fieldnum, rep_fieldnum, rep_fieldnum);
+
+ // Packed repeated.
+ assert_successful_parse(
+ cat( tag(rep_fieldnum, UPB_WIRE_TYPE_DELIMITED),
+ delim(cat( enc33, enc66 )) ),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:33")
+ LINE(" %u:-66")
+ LINE("]")
+ LINE(">"), rep_fieldnum, rep_fieldnum, rep_fieldnum);
+}
+
+// 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("\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) );
+
+ // End group that we are not currently in.
+ assert_does_not_parse( tag(4, UPB_WIRE_TYPE_END_GROUP) );
+
+ // Field number is 0.
+ assert_does_not_parse(
+ cat( tag(0, UPB_WIRE_TYPE_DELIMITED), varint(0) ));
+
+ // Field number is too large.
+ assert_does_not_parse(
+ cat( tag(UPB_MAX_FIELDNUMBER + 1, UPB_WIRE_TYPE_DELIMITED),
+ varint(0) ));
+
+ // Test exceeding the resource limit of stack depth.
+ buffer buf;
+ for (int i = 0; i < UPB_MAX_NESTING; i++) {
+ buf.assign(submsg(UPB_TYPE(MESSAGE), buf));
+ }
+ assert_does_not_parse(buf);
+}
+
+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));
+
+ // Test implicit startseq/endseq.
+ uint32_t repfl_fn = rep_fn(UPB_TYPE(FLOAT));
+ uint32_t repdb_fn = rep_fn(UPB_TYPE(DOUBLE));
+ assert_successful_parse(
+ cat( tag(repfl_fn, UPB_WIRE_TYPE_32BIT), flt(33),
+ tag(repdb_fn, UPB_WIRE_TYPE_64BIT), dbl(66) ),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:33")
+ LINE("]")
+ LINE("%u:[")
+ LINE(" %u:66")
+ LINE("]")
+ LINE(">"), repfl_fn, repfl_fn, repdb_fn, repdb_fn);
+
+ // Submessage tests.
+ uint32_t msg_fn = UPB_TYPE(MESSAGE);
+ assert_successful_parse(
+ submsg(msg_fn, submsg(msg_fn, submsg(msg_fn, buffer()))),
+ LINE("<")
+ LINE("%u:{")
+ LINE(" <")
+ LINE(" %u:{")
+ LINE(" <")
+ LINE(" %u:{")
+ LINE(" <")
+ LINE(" >")
+ LINE(" }")
+ LINE(" >")
+ LINE(" }")
+ LINE(" >")
+ LINE("}")
+ LINE(">"), 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())),
+ LINE("<")
+ LINE("%u:[")
+ LINE(" %u:{")
+ LINE(" <")
+ LINE(" %u:[")
+ LINE(" %u:{")
+ LINE(" <")
+ LINE(" >")
+ LINE(" }")
+ LINE(" ]")
+ LINE(" >")
+ LINE(" }")
+ LINE("]")
+ LINE(">"), repm_fn, repm_fn, repm_fn, repm_fn);
+
+ // Staying within the stack limit should work properly.
+ buffer buf;
+ buffer textbuf;
+ int total = UPB_MAX_NESTING - 1;
+ for (int i = 0; i < total; i++) {
+ buf.assign(submsg(UPB_TYPE(MESSAGE), buf));
+ indentbuf(&textbuf, i);
+ textbuf.append("<\n");
+ indentbuf(&textbuf, i);
+ textbuf.appendf("%u:{\n", UPB_TYPE(MESSAGE));
+ }
+ indentbuf(&textbuf, total);
+ textbuf.append("<\n");
+ indentbuf(&textbuf, total);
+ textbuf.append(">\n");
+ for (int i = 0; i < total; i++) {
+ indentbuf(&textbuf, total - i - 1);
+ textbuf.append("}\n");
+ indentbuf(&textbuf, total - i - 1);
+ textbuf.append(">\n");
+ }
+ assert_successful_parse(buf, "%s", textbuf.buf());
+}
+
+void run_tests() {
+ test_invalid();
+ test_valid();
+}
+
+int main() {
+ for (int i = 0; i < UPB_MAX_NESTING; i++) {
+ closures[i] = i;
+ }
+ // Construct decoder plan.
+ upb_handlers *h = upb_handlers_new();
+ reghandlers(upb_handlers_newmhandlers(h));
+
+ // Create an empty handlers to make sure that the decoder can handle empty
+ // messages.
+ 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);
+#ifdef UPB_USE_JIT_X64
+ ASSERT(upb_decoderplan_hasjitcode(plan));
+#else
+ ASSERT(!upb_decoderplan_hasjitcode(plan));
+#endif
+ 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