This breaks the open-source build, will follow up with a change to fix it.pull/13171/head
parent
b5f5ee867e
commit
1bcab1377d
31 changed files with 2118 additions and 774 deletions
@ -0,0 +1,39 @@ |
||||
//
|
||||
// upb - a minimalist implementation of protocol buffers.
|
||||
//
|
||||
// Copyright (c) 2011 Google Inc. See LICENSE for details.
|
||||
// Author: Josh Haberman <jhaberman@gmail.com>
|
||||
|
||||
#include "bytestream.hpp" |
||||
|
||||
namespace upb { |
||||
|
||||
upb_bytesrc_vtbl* ByteSourceBase::vtable() { |
||||
static upb_bytesrc_vtbl vtbl = { |
||||
&ByteSourceBase::VFetch, |
||||
&ByteSourceBase::VDiscard, |
||||
&ByteSourceBase::VCopy, |
||||
&ByteSourceBase::VGetPtr, |
||||
}; |
||||
return &vtbl; |
||||
} |
||||
|
||||
upb_bytesuccess_t ByteSourceBase::VFetch(void *src, uint64_t ofs, size_t *len) { |
||||
return static_cast<ByteSourceBase*>(src)->Fetch(ofs, len); |
||||
} |
||||
|
||||
void ByteSourceBase::VCopy( |
||||
const void *src, uint64_t ofs, size_t len, char* dest) { |
||||
static_cast<const ByteSourceBase*>(src)->Copy(ofs, len, dest); |
||||
} |
||||
|
||||
void ByteSourceBase::VDiscard(void *src, uint64_t ofs) { |
||||
static_cast<ByteSourceBase*>(src)->Discard(ofs); |
||||
} |
||||
|
||||
const char * ByteSourceBase::VGetPtr( |
||||
const void *src, uint64_t ofs, size_t* len) { |
||||
return static_cast<const ByteSourceBase*>(src)->GetPtr(ofs, len); |
||||
} |
||||
|
||||
} // namespace upb
|
@ -0,0 +1,238 @@ |
||||
//
|
||||
// upb - a minimalist implementation of protocol buffers.
|
||||
//
|
||||
// Copyright (c) 2011 Google Inc. See LICENSE for details.
|
||||
// Author: Josh Haberman <jhaberman@gmail.com>
|
||||
//
|
||||
// This file defines three core interfaces:
|
||||
// - upb::ByteSink: for writing streams of data.
|
||||
// - upb::ByteSource: for reading streams of data.
|
||||
// - upb::ByteRegion: for reading from a specific region of a ByteSource;
|
||||
// should be used by decoders instead of using a ByteSource directly.
|
||||
//
|
||||
// These interfaces are used by streaming encoders and decoders: for example, a
|
||||
// protobuf parser gets its input from a upb::ByteRegion. They are virtual
|
||||
// base classes so concrete implementations can get the data from a fd, a
|
||||
// FILE*, a string, etc.
|
||||
//
|
||||
// A ByteRegion represents a region of data from a ByteSource.
|
||||
//
|
||||
// Parsers get data from this interface instead of a bytesrc because we often
|
||||
// want to parse only a specific region of the input. For example, if we parse
|
||||
// a string from our input but know that the string represents a protobuf, we
|
||||
// can pass its ByteRegion to an appropriate protobuf parser.
|
||||
//
|
||||
// Since the bytes may be coming from a file or network socket, bytes must be
|
||||
// fetched before they can be read (though in some cases this fetch may be a
|
||||
// no-op). "fetch" is the only operation on a byteregion that could fail or
|
||||
// block, because it is the only operation that actually performs I/O.
|
||||
//
|
||||
// Bytes can be discarded when they are no longer needed. Parsers should
|
||||
// always discard bytes they no longer need, both so the buffers can be freed
|
||||
// when possible and to give better visibility into what bytes the parser is
|
||||
// still using.
|
||||
//
|
||||
// start discard read fetch end
|
||||
// ofs ofs ofs ofs ofs
|
||||
// | |--->Discard() | |--->Fetch() |
|
||||
// V V V V V
|
||||
// +-------------+-------------------------+-----------------+-----------------+
|
||||
// | discarded | | | fetchable |
|
||||
// +-------------+-------------------------+-----------------+-----------------+
|
||||
// | <------------- loaded ------------------> |
|
||||
// | <- available -> |
|
||||
// | <---------- remaining ----------> |
|
||||
//
|
||||
// Note that the start offset may be something other than zero! A byteregion
|
||||
// is a view into an underlying bytesrc stream, and the region may start
|
||||
// somewhere other than the beginning of that stream.
|
||||
//
|
||||
// The region can be either delimited or nondelimited. A non-delimited region
|
||||
// will keep returning data until the underlying data source returns EOF. A
|
||||
// delimited region will return EOF at a predetermined offset.
|
||||
//
|
||||
// end
|
||||
// ofs
|
||||
// |
|
||||
// V
|
||||
// +-----------------------+
|
||||
// | delimited region | <-- hard EOF, even if data source has more data.
|
||||
// +-----------------------+
|
||||
//
|
||||
// +------------------------
|
||||
// | nondelimited region Z <-- won't return EOF until data source hits EOF.
|
||||
// +------------------------
|
||||
|
||||
#ifndef UPB_BYTESTREAM_HPP |
||||
#define UPB_BYTESTREAM_HPP |
||||
|
||||
#include "upb/bytestream.h" |
||||
#include "upb/upb.hpp" |
||||
|
||||
namespace upb { |
||||
|
||||
typedef upb_bytesuccess_t ByteSuccess; |
||||
|
||||
// Implement this interface to vend bytes to ByteRegions which will be used by
|
||||
// a decoder.
|
||||
class ByteSourceBase : public upb_bytesrc { |
||||
public: |
||||
ByteSourceBase() { upb_bytesrc_init(this, vtable()); } |
||||
virtual ~ByteSourceBase() { upb_bytesrc_uninit(this); } |
||||
|
||||
// Fetches at least one byte starting at ofs, setting *len to the actual
|
||||
// number of bytes fetched (or 0 on EOF or error: see return value for
|
||||
// details). It is valid for bytes to be fetched multiple times, as long as
|
||||
// the bytes have not been previously discarded.
|
||||
virtual ByteSuccess Fetch(uint64_t ofs, size_t* len) = 0; |
||||
|
||||
// Discards all data prior to ofs (except data that is pinned, if pinning
|
||||
// support is added -- see TODO below).
|
||||
virtual void Discard(uint64_t ofs) = 0; |
||||
|
||||
// Copies "len" bytes of data from ofs to "dst", which must be at least "len"
|
||||
// bytes long. The given region must not be discarded.
|
||||
virtual void Copy(uint64_t ofs, size_t len, char *dst) const = 0; |
||||
|
||||
// Returns a pointer to the bytesrc's internal buffer, storing in *len how
|
||||
// much data is available. The given offset must not be discarded. The
|
||||
// returned buffer is valid for as long as its bytes are not discarded (in
|
||||
// the case that part of the returned buffer is discarded, only the
|
||||
// non-discarded bytes remain valid).
|
||||
virtual const char *GetPtr(uint64_t ofs, size_t *len) const = 0; |
||||
|
||||
// TODO: Add if/when there is a demonstrated need:
|
||||
//
|
||||
// // When the caller pins a region (which must not be already discarded), it
|
||||
// // is guaranteed that the region will not be discarded (nor will the
|
||||
// // bytesrc be destroyed) until the region is unpinned. However, not all
|
||||
// // bytesrc's support pinning; a false return indicates that a pin was not
|
||||
// // possible.
|
||||
// virtual bool Pin(uint64_t ofs, size_t len);
|
||||
//
|
||||
// // Releases some number of pinned bytes from the beginning of a pinned
|
||||
// // region (which may be fewer than the total number of bytes pinned).
|
||||
// virtual void Unpin(uint64_t ofs, size_t len, size_t bytes_to_release);
|
||||
//
|
||||
// Adding pinning support would also involve adding a "pin_ofs" parameter to
|
||||
// upb_bytesrc_fetch, so that the fetch can extend an already-pinned region.
|
||||
private: |
||||
static upb_bytesrc_vtbl* vtable(); |
||||
static upb_bytesuccess_t VFetch(void*, uint64_t, size_t*); |
||||
static void VDiscard(void*, uint64_t); |
||||
static void VCopy(const void*, uint64_t, size_t, char*); |
||||
static const char *VGetPtr(const void*, uint64_t, size_t*); |
||||
}; |
||||
|
||||
class ByteRegion : public upb_byteregion { |
||||
public: |
||||
static const uint64_t kNondelimited = UPB_NONDELIMITED; |
||||
|
||||
ByteRegion() { upb_byteregion_init(this); } |
||||
~ByteRegion() { upb_byteregion_uninit(this); } |
||||
|
||||
// Accessors for the regions bounds -- the meaning of these is described in
|
||||
// the diagram above.
|
||||
uint64_t start_ofs() const { return upb_byteregion_startofs(this); } |
||||
uint64_t discard_ofs() const { return upb_byteregion_discardofs(this); } |
||||
uint64_t fetch_ofs() const { return upb_byteregion_fetchofs(this); } |
||||
uint64_t end_ofs() const { return upb_byteregion_endofs(this); } |
||||
|
||||
// Returns how many bytes are fetched and available for reading starting from
|
||||
// offset "offset".
|
||||
uint64_t BytesAvailable(uint64_t offset) const { |
||||
return upb_byteregion_available(this, offset); |
||||
} |
||||
|
||||
// Returns the total number of bytes remaining after offset "offset", or
|
||||
// kNondelimited if the byteregion is non-delimited.
|
||||
uint64_t BytesRemaining(uint64_t offset) const { |
||||
return upb_byteregion_remaining(this, offset); |
||||
} |
||||
|
||||
uint64_t Length() const { return upb_byteregion_len(this); } |
||||
|
||||
// Sets the value of this byteregion to be a subset of the given byteregion's
|
||||
// data. The caller is responsible for releasing this region before the src
|
||||
// region is released (unless the region is first pinned, if pinning support
|
||||
// is added. see below).
|
||||
void Reset(const upb_byteregion *src, uint64_t ofs, uint64_t len) { |
||||
upb_byteregion_reset(this, src, ofs, len); |
||||
} |
||||
void Release() { upb_byteregion_release(this); } |
||||
|
||||
// Attempts to fetch more data, extending the fetched range of this
|
||||
// byteregion. Returns true if the fetched region was extended by at least
|
||||
// one byte, false on EOF or error (see *s for details).
|
||||
ByteSuccess Fetch() { return upb_byteregion_fetch(this); } |
||||
|
||||
// Fetches all remaining data, returning false if the operation failed (see
|
||||
// *s for details). May only be used on delimited byteregions.
|
||||
ByteSuccess FetchAll() { return upb_byteregion_fetchall(this); } |
||||
|
||||
// Discards bytes from the byteregion up until ofs (which must be greater or
|
||||
// equal to discard_ofs()). It is valid to discard bytes that have not been
|
||||
// fetched (such bytes will never be fetched) but it is an error to discard
|
||||
// past the end of a delimited byteregion.
|
||||
void Discard(uint64_t ofs) { return upb_byteregion_discard(this, ofs); } |
||||
|
||||
// Copies "len" bytes of data into "dst", starting at ofs. The specified
|
||||
// region must be available.
|
||||
void Copy(uint64_t ofs, size_t len, char *dst) const { |
||||
upb_byteregion_copy(this, ofs, len, dst); |
||||
} |
||||
|
||||
// Copies all bytes from the byteregion into dst. Requires that the entire
|
||||
// byteregion is fetched and that none has been discarded.
|
||||
void CopyAll(char *dst) const { |
||||
upb_byteregion_copyall(this, dst); |
||||
} |
||||
|
||||
// Returns a pointer to the internal buffer for the byteregion starting at
|
||||
// offset "ofs." Stores the number of bytes available in this buffer in *len.
|
||||
// The returned buffer is invalidated when the byteregion is reset or
|
||||
// released, or when the bytes are discarded. If the byteregion is not
|
||||
// currently pinned, the pointer is only valid for the lifetime of the parent
|
||||
// byteregion.
|
||||
const char *GetPtr(uint64_t ofs, size_t *len) const { |
||||
return upb_byteregion_getptr(this, ofs, len); |
||||
} |
||||
|
||||
// Copies the contents of the byteregion into a newly-allocated,
|
||||
// NULL-terminated string. Requires that the byteregion is fully fetched.
|
||||
char *StrDup() const { |
||||
return upb_byteregion_strdup(this); |
||||
} |
||||
|
||||
// TODO: add if/when there is a demonstrated need.
|
||||
//
|
||||
// // Pins this byteregion's bytes in memory, allowing it to outlive its
|
||||
// // parent byteregion. Normally a byteregion may only be used while its
|
||||
// // parent is still valid, but a pinned byteregion may continue to be used
|
||||
// // until it is reset or released. A byteregion must be fully fetched to
|
||||
// // be pinned (this implies that the byteregion must be delimited).
|
||||
// //
|
||||
// // In some cases this operation may cause the input data to be copied.
|
||||
// //
|
||||
// // void Pin();
|
||||
}; |
||||
|
||||
class StringSource : public upb_stringsrc { |
||||
public: |
||||
StringSource() : upb_stringsrc() { upb_stringsrc_init(this); } |
||||
~StringSource() { upb_stringsrc_uninit(this); } |
||||
|
||||
void Reset(const char* data, size_t len) { |
||||
upb_stringsrc_reset(this, data, len); |
||||
} |
||||
|
||||
ByteRegion* AllBytes() { |
||||
return static_cast<ByteRegion*>(upb_stringsrc_allbytes(this)); |
||||
} |
||||
|
||||
upb_bytesrc* ByteSource() { return upb_stringsrc_bytesrc(this); } |
||||
}; |
||||
|
||||
} // namespace upb
|
||||
|
||||
#endif |
@ -0,0 +1,83 @@ |
||||
//
|
||||
// upb - a minimalist implementation of protocol buffers.
|
||||
//
|
||||
// Copyright (c) 2011 Google Inc. See LICENSE for details.
|
||||
// Author: Josh Haberman <jhaberman@gmail.com>
|
||||
//
|
||||
// upb::Decoder is a high performance, streaming decoder for protobuf
|
||||
// data that works by getting its input data from a ubp::ByteRegion and calling
|
||||
// into a upb::Handlers.
|
||||
//
|
||||
// A DecoderPlan contains whatever data structures and generated (JIT-ted) code
|
||||
// are necessary to decode protobuf data of a specific type to a specific set
|
||||
// of handlers. By generating the plan ahead of time, we avoid having to
|
||||
// redo this work every time we decode.
|
||||
//
|
||||
// A DecoderPlan is threadsafe, meaning that it can be used concurrently by
|
||||
// different upb::Decoders in different threads. However, the upb::Decoders are
|
||||
// *not* thread-safe.
|
||||
|
||||
#ifndef UPB_PB_DECODER_HPP |
||||
#define UPB_PB_DECODER_HPP |
||||
|
||||
#include "upb/pb/decoder.h" |
||||
|
||||
#include "upb/bytestream.hpp" |
||||
#include "upb/upb.hpp" |
||||
|
||||
namespace upb { |
||||
|
||||
class DecoderPlan : public upb_decoderplan { |
||||
public: |
||||
static DecoderPlan* New(Handlers* h, bool allow_jit) { |
||||
return static_cast<DecoderPlan*>(upb_decoderplan_new(h, allow_jit)); |
||||
} |
||||
void Unref() { upb_decoderplan_unref(this); } |
||||
|
||||
// Returns true if the plan contains JIT-ted code. This may not be the same
|
||||
// as the "allowjit" parameter to the constructor if support for JIT-ting was
|
||||
// not compiled in.
|
||||
bool HasJitCode() { return upb_decoderplan_hasjitcode(this); } |
||||
|
||||
private: |
||||
DecoderPlan() {} // Only constructed by New
|
||||
}; |
||||
|
||||
class Decoder : public upb_decoder { |
||||
public: |
||||
Decoder() { upb_decoder_init(this); } |
||||
~Decoder() { upb_decoder_uninit(this); } |
||||
|
||||
// Resets the plan that the decoder will parse from. This will also reset the
|
||||
// decoder's input to be uninitialized -- ResetInput() must be called before
|
||||
// parsing can occur. The plan must live until the decoder is destroyed or
|
||||
// reset to a different plan.
|
||||
//
|
||||
// Must be called before ResetInput() or Decode().
|
||||
void ResetPlan(DecoderPlan* plan, int32_t msg_offset) { |
||||
upb_decoder_resetplan(this, plan, msg_offset); |
||||
} |
||||
|
||||
// Resets the input of the decoder. This puts it in a state where it has not
|
||||
// seen any data, and expects the next data to be from the beginning of a new
|
||||
// protobuf.
|
||||
//
|
||||
// ResetInput() must be called before Decode() but may be called more than
|
||||
// once. "input" must live until the decoder destroyed or ResetInput is
|
||||
// called again. "c" is the closure that will be passed to the handlers.
|
||||
void ResetInput(ByteRegion* byte_region, void* c) { |
||||
upb_decoder_resetinput(this, byte_region, c); |
||||
} |
||||
|
||||
// Decodes serialized data (calling Handlers as the data is parsed) until
|
||||
// error or EOF (see status() for details).
|
||||
Success Decode() { return upb_decoder_decode(this); } |
||||
|
||||
const upb::Status& status() { |
||||
return static_cast<const upb::Status&>(*upb_decoder_status(this)); |
||||
} |
||||
}; |
||||
|
||||
} // namespace upb
|
||||
|
||||
#endif |
@ -0,0 +1,76 @@ |
||||
|
||||
#include <stdlib.h> |
||||
#include "upb/bytestream.h" |
||||
#include "upb/pb/decoder.h" |
||||
#include "upb/pb/glue.h" |
||||
#include "upb/pb/textprinter.h" |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
if (argc < 3) { |
||||
fprintf(stderr, "Usage: stream_transcode <descfile> <msgname>\n"); |
||||
return 1; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
free((void*)desc); |
||||
|
||||
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; |
||||
} |
||||
|
||||
const upb_msgdef *m = upb_dyncast_msgdef_const(md); |
||||
if (!m) { |
||||
fprintf(stderr, "Def was not a msgdef.\n"); |
||||
return 1; |
||||
} |
||||
|
||||
upb_stdio in, out; |
||||
upb_stdio_init(&in); |
||||
upb_stdio_init(&out); |
||||
upb_stdio_reset(&in, stdin); |
||||
upb_stdio_reset(&out, stdout); |
||||
|
||||
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); |
||||
|
||||
upb_decoder d; |
||||
upb_decoder_init(&d, handlers); |
||||
upb_decoder_reset(&d, upb_stdio_bytesrc(&in), 0, UPB_NONDELIMITED, p); |
||||
|
||||
upb_status_clear(&status); |
||||
upb_decoder_decode(&d, &status); |
||||
|
||||
if (!upb_ok(&status)) { |
||||
fprintf(stderr, "Error parsing input: %s", upb_status_getstr(&status)); |
||||
} |
||||
|
||||
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); |
||||
} |
@ -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; |
||||
} |
||||
|
||||
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_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; |
||||
} |
||||
|
||||
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 )); |
||||
|
||||
// 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 )); |
||||
|
||||
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 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; |
||||
} |
||||
|
Loading…
Reference in new issue