Commit a5bb798d authored by Kenton Varda's avatar Kenton Varda

Actually fix the bug, which was a doozy: OrphanBuilder::tag was sometimes…

Actually fix the bug, which was a doozy:  OrphanBuilder::tag was sometimes initialized using WirePointer::setKindAndTarget(), but since the tag didn't live inside the target segment, this used illegal pointer arithmetic.  The target is never read from an orphan tag anyway, so I thought it would be no big deal.  But it turns out Clang actually optimizes under the assumption that pointer arithmetic returns a whole value.  As a result, on 32-bit system where 64-bit values are only 32-bit aligned, the tag and target might not have been a whole number of words apart, and the extra bit actually found its way into the 'kind' bits, causing e.g. a struct pointer to become an invalid far pointer.  Crash.  The fix required refactoring to ensure that setKindAndOffset() is never used for orphan tags.
parent eeaaaabc
......@@ -1747,6 +1747,51 @@ TEST(Encoding, GlobalConstants) {
}
}
TEST(Encoding, HasEmptyStruct) {
MallocMessageBuilder message;
auto root = message.initRoot<test::TestObject>();
EXPECT_EQ(1, root.totalSizeInWords());
EXPECT_FALSE(root.asReader().hasObjectField());
EXPECT_FALSE(root.hasObjectField());
root.initObjectField<test::TestEmptyStruct>();
EXPECT_TRUE(root.asReader().hasObjectField());
EXPECT_TRUE(root.hasObjectField());
EXPECT_EQ(1, root.totalSizeInWords());
}
TEST(Encoding, HasEmptyList) {
MallocMessageBuilder message;
auto root = message.initRoot<test::TestObject>();
EXPECT_EQ(1, root.totalSizeInWords());
EXPECT_FALSE(root.asReader().hasObjectField());
EXPECT_FALSE(root.hasObjectField());
root.initObjectField<List<int32_t>>(0);
EXPECT_TRUE(root.asReader().hasObjectField());
EXPECT_TRUE(root.hasObjectField());
EXPECT_EQ(1, root.totalSizeInWords());
}
TEST(Encoding, HasEmptyStructList) {
MallocMessageBuilder message;
auto root = message.initRoot<test::TestObject>();
EXPECT_EQ(1, root.totalSizeInWords());
EXPECT_FALSE(root.asReader().hasObjectField());
EXPECT_FALSE(root.hasObjectField());
root.initObjectField<List<TestAllTypes>>(0);
EXPECT_TRUE(root.asReader().hasObjectField());
EXPECT_TRUE(root.hasObjectField());
EXPECT_EQ(2, root.totalSizeInWords());
}
} // namespace
} // namespace _ (private)
} // namespace capnp
......@@ -86,12 +86,43 @@ struct WirePointer {
return reinterpret_cast<const word*>(this) + 1 +
(static_cast<int32_t>(offsetAndKind.get()) >> 2);
}
KJ_ALWAYS_INLINE(void setKindAndTarget(Kind kind, word* target)) {
KJ_ALWAYS_INLINE(void setKindAndTarget(Kind kind, word* target, SegmentBuilder* segment)) {
// Check that the target is really in the same segment, otherwise subtracting pointers is
// undefined behavior. As it turns out, it's undefined behavior that actually produces
// unexpected results in a real-world situation that actually happened: At one time,
// OrphanBuilder's "tag" (a WirePointer) was allowed to be initialized as if it lived in
// a particular segment when in fact it does not. On 32-bit systems, where words might
// only be 32-bit aligned, it's possible that the difference between `this` and `target` is
// not a whole number of words. But clang optimizes:
// (target - (word*)this - 1) << 2
// to:
// (((ptrdiff_t)target - (ptrdiff_t)this - 8) >> 1)
// So now when the pointers are not aligned the same, we can end up corrupting the bottom
// two bits, where `kind` is stored. For example, this turns a struct into a far pointer.
// Ouch!
KJ_DREQUIRE(segment->containsInterval(
reinterpret_cast<word*>(this), reinterpret_cast<word*>(this + 1)));
KJ_DREQUIRE(segment->containsInterval(target, target));
offsetAndKind.set(((target - reinterpret_cast<word*>(this) - 1) << 2) | kind);
}
KJ_ALWAYS_INLINE(void setKindWithZeroOffset(Kind kind)) {
offsetAndKind.set(kind);
}
KJ_ALWAYS_INLINE(void setKindAndTargetForEmptyStruct()) {
// This pointer points at an empty struct. Assuming the WirePointer itself is in-bounds, we
// can set the target to point either at the WirePointer itself or immediately after it. The
// latter would cause the WirePointer to be "null" (since for an empty struct the upper 32
// bits are going to be zero). So we set an offset of -1, as if the struct were allocated
// immediately before this pointer, to distinguish it from null.
offsetAndKind.set(0xfffffffc);
}
KJ_ALWAYS_INLINE(void setKindForOrphan(Kind kind)) {
// OrphanBuilder contains a WirePointer, but since it isn't located in a segment, it should
// not have a valid offset (unless it is a FAR pointer). We set its offset to -1 because
// setting it to zero would mean a pointer to an empty struct would appear to be a null pointer.
KJ_DREQUIRE(kind != FAR);
offsetAndKind.set(kind | 0xfffffffc);
}
KJ_ALWAYS_INLINE(ElementCount inlineCompositeListElementCount() const) {
return (offsetAndKind.get() >> 2) * ELEMENTS;
......@@ -272,23 +303,16 @@ struct WireHelpers {
// segment belonging to the arena. `ref` will be initialized as a non-far pointer, but its
// target offset will be set to zero.
if (amount == 0 * WORDS && kind == WirePointer::STRUCT) {
// Allocating a zero-sized struct. If it happens to be allocated in the space immediately
// after the pointer, we'll have a problem: the pointer will end up all-zero, so isNull()
// will be true. This can lead to all kinds of weird behavior later on. Since the target
// has zero-size anyway, we can really set the pointer to point anywhere, as long as it
// is in-bounds. So, we can have the pointer point back at itself (an offset of -1). This
// should be exceedingly rare in practice since empty structs are pretty useless.
//
// Note that the check for kind == WirePointer::STRUCT will hopefully cause this whole branch
// to be optimized away from all the call sites that are allocating non-structs.
ref->setKindAndTarget(WirePointer::STRUCT, reinterpret_cast<word*>(ref));
return reinterpret_cast<word*>(ref);
}
if (orphanArena == nullptr) {
if (!ref->isNull()) zeroObject(segment, ref);
if (amount == 0 * WORDS && kind == WirePointer::STRUCT) {
// Note that the check for kind == WirePointer::STRUCT will hopefully cause this whole
// branch to be optimized away from all the call sites that are allocating non-structs.
ref->setKindAndTargetForEmptyStruct();
return reinterpret_cast<word*>(ref);
}
word* ptr = segment->allocate(amount);
if (ptr == nullptr) {
......@@ -306,19 +330,20 @@ struct WireHelpers {
// Initialize the landing pad to indicate that the data immediately follows the pad.
ref = reinterpret_cast<WirePointer*>(ptr);
ref->setKindAndTarget(kind, ptr + POINTER_SIZE_IN_WORDS);
ref->setKindAndTarget(kind, ptr + POINTER_SIZE_IN_WORDS, segment);
// Allocated space follows new pointer.
return ptr + POINTER_SIZE_IN_WORDS;
} else {
ref->setKindAndTarget(kind, ptr);
ref->setKindAndTarget(kind, ptr, segment);
return ptr;
}
} else {
// orphanArena is non-null. Allocate an orphan.
KJ_DASSERT(ref->isNull());
auto allocation = orphanArena->allocate(amount);
segment = allocation.segment;
ref->setKindWithZeroOffset(kind);
ref->setKindForOrphan(kind);
return allocation.words;
}
}
......@@ -790,7 +815,7 @@ struct WireHelpers {
if (dstSegment == srcSegment) {
// Same segment, so create a direct pointer.
dst->setKindAndTarget(srcTag->kind(), srcPtr);
dst->setKindAndTarget(srcTag->kind(), srcPtr, dstSegment);
// We can just copy the upper 32 bits. (Use memcpy() to comply with aliasing rules.)
memcpy(&dst->upper32Bits, &srcTag->upper32Bits, sizeof(srcTag->upper32Bits));
......@@ -816,7 +841,7 @@ struct WireHelpers {
dst->farRef.set(farSegment->getSegmentId());
} else {
// Simple landing pad is just a pointer.
landingPad->setKindAndTarget(srcTag->kind(), srcPtr);
landingPad->setKindAndTarget(srcTag->kind(), srcPtr, srcSegment);
memcpy(&landingPad->upper32Bits, &srcTag->upper32Bits, sizeof(srcTag->upper32Bits));
dst->setFar(false, srcSegment->getOffsetTo(reinterpret_cast<word*>(landingPad)));
......@@ -853,7 +878,7 @@ struct WireHelpers {
useDefault:
if (defaultValue == nullptr ||
reinterpret_cast<const WirePointer*>(defaultValue)->isNull()) {
return initStructPointer(ref, segment, size);
return initStructPointer(ref, segment, size, orphanArena);
}
refTarget = copyMessage(segment, ref, reinterpret_cast<const WirePointer*>(defaultValue));
defaultValue = nullptr; // If the default value is itself invalid, don't use it again.
......@@ -942,7 +967,8 @@ struct WireHelpers {
StructSize elementSize, BuilderArena* orphanArena = nullptr)) {
if (elementSize.preferredListEncoding != FieldSize::INLINE_COMPOSITE) {
// Small data-only struct. Allocate a list of primitives instead.
return initListPointer(ref, segment, elementCount, elementSize.preferredListEncoding);
return initListPointer(ref, segment, elementCount, elementSize.preferredListEncoding,
orphanArena);
}
auto wordsPerElement = elementSize.total() / ELEMENTS;
......@@ -976,7 +1002,7 @@ struct WireHelpers {
static KJ_ALWAYS_INLINE(ListBuilder getWritableListPointer(
WirePointer* origRef, word* origRefTarget, SegmentBuilder* origSegment, FieldSize elementSize,
const word* defaultValue)) {
const word* defaultValue, BuilderArena* orphanArena = nullptr)) {
KJ_DREQUIRE(elementSize != FieldSize::INLINE_COMPOSITE,
"Use getStructList{Element,Field}() for structs.");
......@@ -1614,7 +1640,7 @@ struct WireHelpers {
zeroObject(segment, ref);
}
if (value.segment == nullptr) {
if (value == nullptr) {
// Set null.
memset(ref, 0, sizeof(*ref));
} else if (value.tagAsPtr()->kind() == WirePointer::FAR) {
......@@ -1631,14 +1657,17 @@ struct WireHelpers {
}
static OrphanBuilder disown(SegmentBuilder* segment, WirePointer* ref) {
if (ref->isNull()) {
return OrphanBuilder();
} else {
OrphanBuilder result(ref, segment,
ref->kind() == WirePointer::FAR ? nullptr : ref->target());
memset(ref, 0, sizeof(*ref));
return result;
OrphanBuilder result(ref, segment,
getWritableObjectPointer(segment, ref, nullptr).getLocation());
if (!ref->isNull() && ref->kind() != WirePointer::FAR) {
result.tagAsPtr()->setKindForOrphan(ref->kind());
}
// Zero out the pointer that was disowned.
memset(ref, 0, sizeof(*ref));
return result;
}
// -----------------------------------------------------------------
......@@ -2589,7 +2618,7 @@ OrphanBuilder OrphanBuilder::initStruct(BuilderArena* arena, StructSize size) {
OrphanBuilder result;
StructBuilder builder = WireHelpers::initStructPointer(result.tagAsPtr(), nullptr, size, arena);
result.segment = builder.segment;
result.location = reinterpret_cast<word*>(builder.data);
result.location = builder.getLocation();
return result;
}
......@@ -2599,23 +2628,18 @@ OrphanBuilder OrphanBuilder::initList(
ListBuilder builder = WireHelpers::initListPointer(
result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
result.segment = builder.segment;
result.location = reinterpret_cast<word*>(builder.ptr);
result.location = builder.getLocation();
return result;
}
OrphanBuilder OrphanBuilder::initStructList(
BuilderArena* arena, ElementCount elementCount, StructSize elementSize) {
if (elementSize.preferredListEncoding != FieldSize::INLINE_COMPOSITE) {
// Small data-only struct. Allocate a list of primitives instead.
return initList(arena, elementCount, elementSize.preferredListEncoding);
} else {
OrphanBuilder result;
ListBuilder builder = WireHelpers::initStructListPointer(
result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
result.segment = builder.segment;
result.location = reinterpret_cast<word*>(builder.ptr) - POINTER_SIZE_IN_WORDS;
return result;
}
OrphanBuilder result;
ListBuilder builder = WireHelpers::initStructListPointer(
result.tagAsPtr(), nullptr, elementCount, elementSize, arena);
result.segment = builder.segment;
result.location = builder.getLocation();
return result;
}
OrphanBuilder OrphanBuilder::initText(BuilderArena* arena, ByteCount size) {
......@@ -2669,106 +2693,93 @@ OrphanBuilder OrphanBuilder::copy(BuilderArena* arena, Data::Reader copyFrom) {
}
StructBuilder OrphanBuilder::asStruct(StructSize size) {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
StructBuilder result = WireHelpers::getWritableStructPointer(
tagAsPtr(), location, segment, size, nullptr);
tagAsPtr(), location, segment, size, nullptr, segment->getArena());
// Watch out, the pointer could have been updated if the object had to be relocated.
if (tagAsPtr()->kind() == WirePointer::FAR) {
location = nullptr;
} else {
location = reinterpret_cast<word*>(result.data);
}
location = reinterpret_cast<word*>(result.data);
return result;
}
ListBuilder OrphanBuilder::asList(FieldSize elementSize) {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
ListBuilder result = WireHelpers::getWritableListPointer(
tagAsPtr(), location, segment, elementSize, nullptr);
tagAsPtr(), location, segment, elementSize, nullptr, segment->getArena());
// Watch out, the pointer could have been updated if the object had to be relocated.
if (tagAsPtr()->kind() == WirePointer::FAR) {
location = nullptr;
} else {
location = reinterpret_cast<word*>(result.ptr);
}
// (Actually, currently this is not true for primitive lists, but let's not turn into a bug if
// it changes!)
location = result.getLocation();
return result;
}
ListBuilder OrphanBuilder::asStructList(StructSize elementSize) {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
ListBuilder result = WireHelpers::getWritableStructListPointer(
tagAsPtr(), location, segment, elementSize, nullptr);
tagAsPtr(), location, segment, elementSize, nullptr, segment->getArena());
// Watch out, the pointer could have been updated if the object had to be relocated.
if (tagAsPtr()->kind() == WirePointer::FAR) {
location = nullptr;
} else if (result.step * ELEMENTS <= BITS_PER_WORD * WORDS) {
location = reinterpret_cast<word*>(result.ptr);
} else {
location = reinterpret_cast<word*>(result.ptr) - POINTER_SIZE_IN_WORDS;
}
location = result.getLocation();
return result;
}
Text::Builder OrphanBuilder::asText() {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
// Never relocates.
return WireHelpers::getWritableTextPointer(tagAsPtr(), location, segment, nullptr, 0 * BYTES);
}
Data::Builder OrphanBuilder::asData() {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
// Never relocates.
return WireHelpers::getWritableDataPointer(tagAsPtr(), location, segment, nullptr, 0 * BYTES);
}
ObjectBuilder OrphanBuilder::asObject() {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
ObjectBuilder result = WireHelpers::getWritableObjectPointer(
segment, tagAsPtr(), location, nullptr);
// Watch out, the pointer could have been updated if the object had to be relocated.
if (tagAsPtr()->kind() == WirePointer::FAR) {
location = nullptr;
} else {
switch (result.kind) {
case ObjectKind::STRUCT:
location = reinterpret_cast<word*>(result.structBuilder.data);
break;
case ObjectKind::LIST:
if (tagAsPtr()->listRef.elementSize() == FieldSize::INLINE_COMPOSITE) {
location = reinterpret_cast<word*>(result.listBuilder.ptr) - POINTER_SIZE_IN_WORDS;
} else {
location = reinterpret_cast<word*>(result.listBuilder.ptr);
}
break;
case ObjectKind::NULL_POINTER:
location = nullptr;
break;
}
}
location = result.getLocation();
return result;
}
StructReader OrphanBuilder::asStructReader(StructSize size) const {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
return WireHelpers::readStructPointer(
segment, tagAsPtr(), location, nullptr, std::numeric_limits<int>::max());
}
ListReader OrphanBuilder::asListReader(FieldSize elementSize) const {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
return WireHelpers::readListPointer(
segment, tagAsPtr(), location, nullptr, elementSize, std::numeric_limits<int>::max());
}
Text::Reader OrphanBuilder::asTextReader() const {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
return WireHelpers::readTextPointer(segment, tagAsPtr(), location, nullptr, 0 * BYTES);
}
Data::Reader OrphanBuilder::asDataReader() const {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
return WireHelpers::readDataPointer(segment, tagAsPtr(), location, nullptr, 0 * BYTES);
}
ObjectReader OrphanBuilder::asObjectReader() const {
KJ_DASSERT(tagAsPtr()->isNull() == (location == nullptr));
return WireHelpers::readObjectPointer(
segment, tagAsPtr(), location, nullptr, std::numeric_limits<int>::max());
}
......@@ -2777,14 +2788,13 @@ void OrphanBuilder::euthanize() {
// Carefully catch any exceptions and rethrow them as recoverable exceptions since we may be in
// a destructor.
auto exception = kj::runCatchingExceptions([&]() {
auto ref = reinterpret_cast<WirePointer*>(&tag);
if (ref->kind() == WirePointer::FAR) {
WireHelpers::zeroObject(segment, ref);
if (tagAsPtr()->kind() == WirePointer::FAR) {
WireHelpers::zeroObject(segment, tagAsPtr());
} else {
WireHelpers::zeroObject(segment, reinterpret_cast<WirePointer*>(&tag), location);
WireHelpers::zeroObject(segment, tagAsPtr(), location);
}
memset(ref, 0, sizeof(*ref));
memset(&tag, 0, sizeof(tag));
segment = nullptr;
location = nullptr;
});
......
......@@ -279,6 +279,10 @@ public:
static StructBuilder getRoot(SegmentBuilder* segment, word* location, StructSize size);
static void adoptRoot(SegmentBuilder* segment, word* location, OrphanBuilder orphan);
inline word* getLocation() { return reinterpret_cast<word*>(data); }
// Get the object's location. Only valid for independently-allocated objects (i.e. not list
// elements).
inline BitCount getDataSectionSize() const { return dataSize; }
inline WirePointerCount getPointerSectionSize() const { return pointerCount; }
inline Data::Builder getDataSectionAsBlob();
......@@ -533,6 +537,17 @@ public:
: segment(nullptr), ptr(nullptr), elementCount(0 * ELEMENTS),
step(0 * BITS / ELEMENTS) {}
inline word* getLocation() {
// Get the object's location. Only valid for independently-allocated objects (i.e. not list
// elements).
if (step * ELEMENTS <= BITS_PER_WORD * WORDS) {
return reinterpret_cast<word*>(ptr);
} else {
return reinterpret_cast<word*>(ptr) - POINTER_SIZE_IN_WORDS;
}
}
inline ElementCount size() const;
// The number of elements in the list.
......@@ -722,6 +737,15 @@ struct ObjectBuilder {
ObjectBuilder(ListBuilder listBuilder)
: kind(ObjectKind::LIST), listBuilder(listBuilder) {}
inline word* getLocation() {
switch (kind) {
case ObjectKind::NULL_POINTER: return nullptr;
case ObjectKind::STRUCT: return structBuilder.getLocation();
case ObjectKind::LIST: return listBuilder.getLocation();
}
return nullptr;
}
ObjectReader asReader() const;
inline ObjectBuilder(ObjectBuilder& other) { memcpy(this, &other, sizeof(*this)); }
......@@ -773,8 +797,8 @@ public:
OrphanBuilder& operator=(const OrphanBuilder& other) = delete;
inline OrphanBuilder& operator=(OrphanBuilder&& other);
inline bool operator==(decltype(nullptr)) const { return segment == nullptr; }
inline bool operator!=(decltype(nullptr)) const { return segment != nullptr; }
inline bool operator==(decltype(nullptr)) const { return location == nullptr; }
inline bool operator!=(decltype(nullptr)) const { return location != nullptr; }
StructBuilder asStruct(StructSize size);
// Interpret as a struct, or throw an exception if not a struct.
......@@ -816,8 +840,7 @@ private:
// FAR pointer.
word* location;
// Pointer to the object. Invalid if the tag is a FAR pointer (in which case you need to follow
// the FAR pointer instead).
// Pointer to the object, or nullptr if the pointer is null.
inline OrphanBuilder(const void* tagPtr, SegmentBuilder* segment, word* location)
: segment(segment), location(location) {
......
......@@ -781,12 +781,103 @@ TEST(Orphans, FarPointer) {
EXPECT_TRUE(orphan != nullptr);
EXPECT_FALSE(orphan == nullptr);
KJ_DBG(orphan != nullptr, orphan == nullptr);
checkTestMessage(orphan.getReader());
checkTestMessage(orphan.get());
}
TEST(Orphans, UpgradeStruct) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
auto old = root.initObjectField<test::TestOldVersion>();
old.setOld1(1234);
old.setOld2("foo");
auto orphan = root.disownObjectField<test::TestNewVersion>();
// Relocation has not occurred yet.
old.setOld1(12345);
EXPECT_EQ(12345, orphan.getReader().getOld1());
EXPECT_EQ("foo", old.getOld2());
// This will relocate the struct.
auto newVersion = orphan.get();
EXPECT_EQ(0, old.getOld1());
EXPECT_EQ("", old.getOld2());
EXPECT_EQ(12345, newVersion.getOld1());
EXPECT_EQ("foo", newVersion.getOld2());
}
TEST(Orphans, UpgradeStructList) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
auto old = root.initObjectField<List<test::TestOldVersion>>(2);
old[0].setOld1(1234);
old[0].setOld2("foo");
old[1].setOld1(4321);
old[1].setOld2("bar");
auto orphan = root.disownObjectField<List<test::TestNewVersion>>();
// Relocation has not occurred yet.
old[0].setOld1(12345);
EXPECT_EQ(12345, orphan.getReader()[0].getOld1());
EXPECT_EQ("foo", old[0].getOld2());
// This will relocate the struct.
auto newVersion = orphan.get();
EXPECT_EQ(0, old[0].getOld1());
EXPECT_EQ("", old[0].getOld2());
EXPECT_EQ(12345, newVersion[0].getOld1());
EXPECT_EQ("foo", newVersion[0].getOld2());
EXPECT_EQ(4321, newVersion[1].getOld1());
EXPECT_EQ("bar", newVersion[1].getOld2());
}
TEST(Orphans, DisownNull) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
{
Orphan<TestAllTypes> orphan = root.disownStructField();
EXPECT_TRUE(orphan == nullptr);
checkTestMessageAllZero(orphan.getReader());
EXPECT_TRUE(orphan == nullptr);
// get()ing the orphan allocates an object, for security reasons.
checkTestMessageAllZero(orphan.get());
EXPECT_FALSE(orphan == nullptr);
}
{
Orphan<List<int32_t>> orphan = root.disownInt32List();
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(0, orphan.getReader().size());
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(0, orphan.get().size());
EXPECT_TRUE(orphan == nullptr);
}
{
Orphan<List<TestAllTypes>> orphan = root.disownStructList();
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(0, orphan.getReader().size());
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(0, orphan.get().size());
EXPECT_TRUE(orphan == nullptr);
}
}
} // namespace
} // namespace _ (private)
} // namespace capnp
......@@ -53,6 +53,12 @@ public:
Orphan& operator=(Orphan&&) = default;
inline typename T::Builder get();
// Get the underlying builder. If the orphan is null, this will allocate and return a default
// object rather than crash. This is done for security -- otherwise, you might enable a DoS
// attack any time you disown a field and fail to check if it is null. In the case of structs,
// this means that the orphan is no longer null after get() returns. In the case of lists,
// no actual object is allocated since a simple empty ListBuilder can be returned.
inline typename T::Reader getReader() const;
inline bool operator==(decltype(nullptr)) const { return builder == nullptr; }
......
......@@ -162,6 +162,9 @@ struct TestDefaults {
struct TestObject {
objectField @0 :Object;
# Do not add any other fields here! Some tests rely on objectField being the last pointer
# in the struct.
}
struct TestOutOfOrder {
......@@ -478,6 +481,8 @@ struct TestStructUnion {
}
}
struct TestEmptyStruct {}
struct TestConstants {
const voidConst :Void = void;
const boolConst :Bool = true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment