Commit d43aec91 authored by Kenton Varda's avatar Kenton Varda

When getting a list from a builder, if the list elements are too small, upgrade it.

parent 917f459e
......@@ -665,11 +665,20 @@ DynamicValue::Builder DynamicStruct::Builder::getImpl(
typedDval.data(), typedDval.size() * BYTES));
}
case schema::Type::Body::LIST_TYPE:
return DynamicValue::Builder(DynamicList::Builder(
ListSchema::of(type.getListType(), member.getContainingStruct()),
case schema::Type::Body::LIST_TYPE: {
ListSchema listType = ListSchema::of(type.getListType(), member.getContainingStruct());
if (listType.whichElementType() == schema::Type::Body::STRUCT_TYPE) {
return DynamicValue::Builder(DynamicList::Builder(listType,
builder.getStructListField(field.getOffset() * POINTERS,
structSizeFromSchema(listType.getStructElementType()),
dval.getListValue<internal::TrustedMessage>())));
} else {
return DynamicValue::Builder(DynamicList::Builder(listType,
builder.getListField(field.getOffset() * POINTERS,
elementSizeFor(listType.whichElementType()),
dval.getListValue<internal::TrustedMessage>())));
}
}
case schema::Type::Body::STRUCT_TYPE: {
auto structSchema =
......@@ -711,10 +720,19 @@ DynamicStruct::Builder DynamicStruct::Builder::getObjectImpl(
}
DynamicList::Builder DynamicStruct::Builder::getObjectImpl(
internal::StructBuilder builder, StructSchema::Member field, ListSchema type) {
if (type.whichElementType() == schema::Type::Body::STRUCT_TYPE) {
return DynamicList::Builder(type,
builder.getStructListField(
field.getProto().getBody().getFieldMember().getOffset() * POINTERS,
structSizeFromSchema(type.getStructElementType()),
nullptr));
} else {
return DynamicList::Builder(type,
builder.getListField(
field.getProto().getBody().getFieldMember().getOffset() * POINTERS,
elementSizeFor(type.whichElementType()),
nullptr));
}
}
Text::Builder DynamicStruct::Builder::getObjectAsTextImpl(
internal::StructBuilder builder, StructSchema::Member field) {
......@@ -1007,9 +1025,20 @@ DynamicValue::Builder DynamicList::Builder::operator[](uint index) const {
case schema::Type::Body::DATA_TYPE:
return DynamicValue::Builder(builder.getBlobElement<Data>(index * ELEMENTS));
case schema::Type::Body::LIST_TYPE:
return DynamicValue::Builder(DynamicList::Builder(
schema.getListElementType(), builder.getListElement(index * ELEMENTS)));
case schema::Type::Body::LIST_TYPE: {
ListSchema elementType = schema.getListElementType();
if (elementType.whichElementType() == schema::Type::Body::STRUCT_TYPE) {
return DynamicValue::Builder(DynamicList::Builder(elementType,
builder.getStructListElement(
index * ELEMENTS,
structSizeFromSchema(elementType.getStructElementType()))));
} else {
return DynamicValue::Builder(DynamicList::Builder(elementType,
builder.getListElement(
index * ELEMENTS,
elementSizeFor(elementType.whichElementType()))));
}
}
case schema::Type::Body::STRUCT_TYPE:
return DynamicValue::Builder(DynamicStruct::Builder(
......@@ -1388,7 +1417,15 @@ DynamicList::Reader PointerHelpers<DynamicList, Kind::UNKNOWN>::getDynamic(
}
DynamicList::Builder PointerHelpers<DynamicList, Kind::UNKNOWN>::getDynamic(
StructBuilder builder, WirePointerCount index, ListSchema schema) {
return DynamicList::Builder(schema, builder.getListField(index, nullptr));
if (schema.whichElementType() == schema::Type::Body::STRUCT_TYPE) {
return DynamicList::Builder(schema,
builder.getStructListField(index,
structSizeFromSchema(schema.getStructElementType()),
nullptr));
} else {
return DynamicList::Builder(schema,
builder.getListField(index, elementSizeFor(schema.whichElementType()), nullptr));
}
}
void PointerHelpers<DynamicList, Kind::UNKNOWN>::set(
StructBuilder builder, WirePointerCount index, DynamicList::Reader value) {
......
......@@ -577,12 +577,12 @@ TEST(Encoding, BitListUpgrade) {
root.initObjectField<List<bool>>(4).copyFrom({true, false, true, true});
{
auto l = root.getObjectField<List<test::TestFieldZeroIsBit>>();
auto l = root.getObjectField<List<test::TestLists::Struct1>>();
ASSERT_EQ(4u, l.size());
EXPECT_TRUE(l[0].getBit());
EXPECT_FALSE(l[1].getBit());
EXPECT_TRUE(l[2].getBit());
EXPECT_TRUE(l[3].getBit());
EXPECT_TRUE(l[0].getF());
EXPECT_FALSE(l[1].getF());
EXPECT_TRUE(l[2].getF());
EXPECT_TRUE(l[3].getF());
}
auto reader = root.asReader();
......@@ -865,6 +865,292 @@ TEST(Encoding, UpgradeStructInBuilderDoubleFarPointers) {
EXPECT_EQ(2u, builder.getSegmentsForOutput()[2].size());
}
void checkList(List<test::TestNewVersion>::Builder builder,
std::initializer_list<int64_t> expectedData,
std::initializer_list<Text::Reader> expectedPointers) {
ASSERT_EQ(expectedData.size(), builder.size());
for (uint i = 0; i < expectedData.size(); i++) {
EXPECT_EQ(expectedData.begin()[i], builder[i].getOld1());
EXPECT_EQ(expectedPointers.begin()[i], builder[i].getOld2());
// Other fields shouldn't be set.
EXPECT_EQ(0, builder[i].asReader().getOld3().getOld1());
EXPECT_EQ("", builder[i].asReader().getOld3().getOld2());
EXPECT_EQ(987, builder[i].getNew1());
EXPECT_EQ("baz", builder[i].getNew2());
}
}
void checkUpgradedList(test::TestObject::Builder root,
std::initializer_list<int64_t> expectedData,
std::initializer_list<Text::Reader> expectedPointers) {
{
auto builder = root.getObjectField<List<test::TestNewVersion>>();
ASSERT_EQ(expectedData.size(), builder.size());
for (uint i = 0; i < expectedData.size(); i++) {
EXPECT_EQ(expectedData.begin()[i], builder[i].getOld1());
EXPECT_EQ(expectedPointers.begin()[i], builder[i].getOld2());
// Other fields shouldn't be set.
EXPECT_EQ(0, builder[i].asReader().getOld3().getOld1());
EXPECT_EQ("", builder[i].asReader().getOld3().getOld2());
EXPECT_EQ(987, builder[i].getNew1());
EXPECT_EQ("baz", builder[i].getNew2());
// Write some new data.
builder[i].setOld1(i * 123);
builder[i].setOld2(str("qux", i, '\0').begin());
builder[i].setNew1(i * 456);
builder[i].setNew2(str("corge", i, '\0').begin());
}
}
// Read the newly-written data as TestOldVersion to ensure it was updated.
{
auto builder = root.getObjectField<List<test::TestOldVersion>>();
ASSERT_EQ(expectedData.size(), builder.size());
for (uint i = 0; i < expectedData.size(); i++) {
EXPECT_EQ(i * 123, builder[i].getOld1());
EXPECT_EQ(Text::Reader(str("qux", i, "\0").begin()), builder[i].getOld2());
}
}
// Also read back as TestNewVersion again.
{
auto builder = root.getObjectField<List<test::TestNewVersion>>();
ASSERT_EQ(expectedData.size(), builder.size());
for (uint i = 0; i < expectedData.size(); i++) {
EXPECT_EQ(i * 123, builder[i].getOld1());
EXPECT_EQ(Text::Reader(str("qux", i, '\0').begin()), builder[i].getOld2());
EXPECT_EQ(i * 456, builder[i].getNew1());
EXPECT_EQ(Text::Reader(str("corge", i, '\0').begin()), builder[i].getNew2());
}
}
}
TEST(Encoding, UpgradeListInBuilder) {
// Test every damned list upgrade.
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
// -----------------------------------------------------------------
root.initObjectField<List<Void>>(4);
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID, Void::VOID});
EXPECT_ANY_THROW(root.getObjectField<List<bool>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint8_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkList(root.getObjectField<List<test::TestNewVersion>>(), {0, 0, 0, 0}, {"", "", "", ""});
// -----------------------------------------------------------------
root.initObjectField<List<bool>>(4).copyFrom({true, false, true, true});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {true, false, true, true});
EXPECT_ANY_THROW(root.getObjectField<List<uint8_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkUpgradedList(root, {1, 0, 1, 1}, {"", "", "", ""});
// -----------------------------------------------------------------
root.initObjectField<List<uint8_t>>(4).copyFrom({0x12, 0x23, 0x33, 0x44});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {false, true, true, false});
checkList(root.getObjectField<List<uint8_t>>(), {0x12, 0x23, 0x33, 0x44});
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkUpgradedList(root, {0x12, 0x23, 0x33, 0x44}, {"", "", "", ""});
// -----------------------------------------------------------------
root.initObjectField<List<uint16_t>>(4).copyFrom({0x5612, 0x7823, 0xab33, 0xcd44});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {false, true, true, false});
checkList(root.getObjectField<List<uint8_t>>(), {0x12, 0x23, 0x33, 0x44});
checkList(root.getObjectField<List<uint16_t>>(), {0x5612, 0x7823, 0xab33, 0xcd44});
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkUpgradedList(root, {0x5612, 0x7823, 0xab33, 0xcd44}, {"", "", "", ""});
// -----------------------------------------------------------------
root.initObjectField<List<uint32_t>>(4).copyFrom({0x17595612, 0x29347823, 0x5923ab32, 0x1a39cd45});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {false, true, false, true});
checkList(root.getObjectField<List<uint8_t>>(), {0x12, 0x23, 0x32, 0x45});
checkList(root.getObjectField<List<uint16_t>>(), {0x5612, 0x7823, 0xab32, 0xcd45});
checkList(root.getObjectField<List<uint32_t>>(), {0x17595612u, 0x29347823u, 0x5923ab32u, 0x1a39cd45u});
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkUpgradedList(root, {0x17595612, 0x29347823, 0x5923ab32, 0x1a39cd45}, {"", "", "", ""});
// -----------------------------------------------------------------
root.initObjectField<List<uint64_t>>(2).copyFrom({0x1234abcd8735fe21, 0x7173bc0e1923af36});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {true, false});
checkList(root.getObjectField<List<uint8_t>>(), {0x21, 0x36});
checkList(root.getObjectField<List<uint16_t>>(), {0xfe21, 0xaf36});
checkList(root.getObjectField<List<uint32_t>>(), {0x8735fe21u, 0x1923af36u});
checkList(root.getObjectField<List<uint64_t>>(), {0x1234abcd8735fe21ull, 0x7173bc0e1923af36ull});
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
checkUpgradedList(root, {0x1234abcd8735fe21ull, 0x7173bc0e1923af36ull}, {"", ""});
// -----------------------------------------------------------------
root.initObjectField<List<Text>>(3).copyFrom({"foo", "bar", "baz"});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID});
EXPECT_ANY_THROW(root.getObjectField<List<bool>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint8_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
checkList(root.getObjectField<List<Text>>(), {"foo", "bar", "baz"});
checkUpgradedList(root, {0, 0, 0}, {"foo", "bar", "baz"});
// -----------------------------------------------------------------
root.initObjectField<List<Text>>(3).copyFrom({"foo", "bar", "baz"});
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID});
EXPECT_ANY_THROW(root.getObjectField<List<bool>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint8_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
checkList(root.getObjectField<List<Text>>(), {"foo", "bar", "baz"});
checkUpgradedList(root, {0, 0, 0}, {"foo", "bar", "baz"});
// -----------------------------------------------------------------
{
auto l = root.initObjectField<List<test::TestOldVersion>>(3);
l[0].setOld1(0x1234567890abcdef);
l[1].setOld1(0x234567890abcdef1);
l[2].setOld1(0x34567890abcdef12);
l[0].setOld2("foo");
l[1].setOld2("bar");
l[2].setOld2("baz");
}
checkList(root.getObjectField<List<Void>>(), {Void::VOID, Void::VOID, Void::VOID});
checkList(root.getObjectField<List<bool>>(), {true, true, false});
checkList(root.getObjectField<List<uint8_t>>(), {0xefu, 0xf1u, 0x12u});
checkList(root.getObjectField<List<uint16_t>>(), {0xcdefu, 0xdef1u, 0xef12u});
checkList(root.getObjectField<List<uint32_t>>(), {0x90abcdefu, 0x0abcdef1u, 0xabcdef12u});
checkList(root.getObjectField<List<uint64_t>>(),
{0x1234567890abcdefull, 0x234567890abcdef1ull, 0x34567890abcdef12ull});
checkList(root.getObjectField<List<Text>>(), {"foo", "bar", "baz"});
checkUpgradedList(root, {0x1234567890abcdefull, 0x234567890abcdef1ull, 0x34567890abcdef12ull},
{"foo", "bar", "baz"});
// -----------------------------------------------------------------
// OK, now we've tested upgrading every primitive list to every primitive list, every primitive
// list to a multi-word struct, and a multi-word struct to every primitive list. But we haven't
// tried upgrading primitive lists to sub-word structs.
// Upgrade from bool.
root.initObjectField<List<bool>>(4).copyFrom({true, false, true, true});
{
auto l = root.getObjectField<List<test::TestLists::Struct16>>();
ASSERT_EQ(4u, l.size());
EXPECT_EQ(1u, l[0].getF());
EXPECT_EQ(0u, l[1].getF());
EXPECT_EQ(1u, l[2].getF());
EXPECT_EQ(1u, l[3].getF());
l[0].setF(12573);
l[1].setF(3251);
l[2].setF(9238);
l[3].setF(5832);
}
checkList(root.getObjectField<List<bool>>(), {true, true, false, false});
checkList(root.getObjectField<List<uint16_t>>(), {12573u, 3251u, 9238u, 5832u});
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
// Upgrade from multi-byte, sub-word data.
root.initObjectField<List<uint16_t>>(4).copyFrom({12u, 34u, 56u, 78u});
{
auto l = root.getObjectField<List<test::TestLists::Struct32>>();
ASSERT_EQ(4u, l.size());
EXPECT_EQ(12u, l[0].getF());
EXPECT_EQ(34u, l[1].getF());
EXPECT_EQ(56u, l[2].getF());
EXPECT_EQ(78u, l[3].getF());
l[0].setF(0x65ac1235u);
l[1].setF(0x13f12879u);
l[2].setF(0x33423082u);
l[3].setF(0x12988948u);
}
checkList(root.getObjectField<List<bool>>(), {true, true, false, false});
checkList(root.getObjectField<List<uint8_t>>(), {0x35u, 0x79u, 0x82u, 0x48u});
checkList(root.getObjectField<List<uint16_t>>(), {0x1235u, 0x2879u, 0x3082u, 0x8948u});
checkList(root.getObjectField<List<uint32_t>>(),
{0x65ac1235u, 0x13f12879u, 0x33423082u, 0x12988948u});
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
// Upgrade from void -> data struct
root.initObjectField<List<Void>>(4).copyFrom({Void::VOID, Void::VOID, Void::VOID, Void::VOID});
{
auto l = root.getObjectField<List<test::TestLists::Struct16>>();
ASSERT_EQ(4u, l.size());
EXPECT_EQ(0u, l[0].getF());
EXPECT_EQ(0u, l[1].getF());
EXPECT_EQ(0u, l[2].getF());
EXPECT_EQ(0u, l[3].getF());
l[0].setF(12573);
l[1].setF(3251);
l[2].setF(9238);
l[3].setF(5832);
}
checkList(root.getObjectField<List<bool>>(), {true, true, false, false});
checkList(root.getObjectField<List<uint16_t>>(), {12573u, 3251u, 9238u, 5832u});
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
// Upgrade from void -> pointer struct
root.initObjectField<List<Void>>(4).copyFrom({Void::VOID, Void::VOID, Void::VOID, Void::VOID});
{
auto l = root.getObjectField<List<test::TestLists::StructP>>();
ASSERT_EQ(4u, l.size());
EXPECT_EQ("", l[0].getF());
EXPECT_EQ("", l[1].getF());
EXPECT_EQ("", l[2].getF());
EXPECT_EQ("", l[3].getF());
l[0].setF("foo");
l[1].setF("bar");
l[2].setF("baz");
l[3].setF("qux");
}
EXPECT_ANY_THROW(root.getObjectField<List<bool>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint16_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint32_t>>());
EXPECT_ANY_THROW(root.getObjectField<List<uint64_t>>());
checkList(root.getObjectField<List<Text>>(), {"foo", "bar", "baz", "qux"});
// Verify that we cannot "side-grade" a pointer list to a data struct list, or a data list to
// a pointer struct list.
root.initObjectField<List<Text>>(4).copyFrom({"foo", "bar", "baz", "qux"});
EXPECT_ANY_THROW(root.getObjectField<List<test::TestLists::Struct32>>());
root.initObjectField<List<uint32_t>>(4).copyFrom({12, 34, 56, 78});
EXPECT_ANY_THROW(root.getObjectField<List<Text>>());
}
// =======================================================================================
// Tests of generated code, not really of the encoding.
// TODO(cleanup): Move to a different test?
......
......@@ -182,7 +182,7 @@ static void checkStruct(StructBuilder builder) {
}
{
ListBuilder list = builder.getListField(1 * POINTERS, nullptr);
ListBuilder list = builder.getListField(1 * POINTERS, FieldSize::FOUR_BYTES, nullptr);
ASSERT_EQ(3 * ELEMENTS, list.size());
EXPECT_EQ(200, list.getDataElement<int32_t>(0 * ELEMENTS));
EXPECT_EQ(201, list.getDataElement<int32_t>(1 * ELEMENTS));
......@@ -190,7 +190,7 @@ static void checkStruct(StructBuilder builder) {
}
{
ListBuilder list = builder.getListField(2 * POINTERS, nullptr);
ListBuilder list = builder.getStructListField(2 * POINTERS, STRUCTLIST_ELEMENT_SIZE, nullptr);
ASSERT_EQ(4 * ELEMENTS, list.size());
for (int i = 0; i < 4; i++) {
StructBuilder element = list.getStructElement(i * ELEMENTS);
......@@ -204,10 +204,10 @@ static void checkStruct(StructBuilder builder) {
}
{
ListBuilder list = builder.getListField(3 * POINTERS, nullptr);
ListBuilder list = builder.getListField(3 * POINTERS, FieldSize::POINTER, nullptr);
ASSERT_EQ(5 * ELEMENTS, list.size());
for (uint i = 0; i < 5; i++) {
ListBuilder element = list.getListElement(i * ELEMENTS);
ListBuilder element = list.getListElement(i * ELEMENTS, FieldSize::TWO_BYTES);
ASSERT_EQ((i + 1) * ELEMENTS, element.size());
for (uint j = 0; j <= i; j++) {
EXPECT_EQ(500u + j, element.getDataElement<uint16_t>(j * ELEMENTS));
......
......@@ -588,39 +588,363 @@ struct WireHelpers {
}
static CAPNPROTO_ALWAYS_INLINE(ListBuilder getWritableListPointer(
WirePointer* ref, SegmentBuilder* segment, const word* defaultValue)) {
const WirePointer* defaultRef = reinterpret_cast<const WirePointer*>(defaultValue);
word* ptr;
WirePointer* origRef, SegmentBuilder* origSegment, FieldSize elementSize,
const word* defaultValue)) {
DPRECOND(elementSize != FieldSize::INLINE_COMPOSITE,
"Use getStructList{Element,Field}() for structs.");
if (ref->isNull()) {
if (origRef->isNull()) {
useDefault:
if (defaultValue == nullptr ||
reinterpret_cast<const WirePointer*>(defaultValue)->isNull()) {
memset(origRef, 0, sizeof(*origRef));
return ListBuilder();
}
ptr = copyMessage(segment, ref, defaultRef);
word* ptr = copyMessage(origSegment, origRef,
reinterpret_cast<const WirePointer*>(defaultValue));
BitCount dataSize = dataBitsPerElement(elementSize) * ELEMENTS;
WirePointerCount pointerCount = pointersPerElement(elementSize) * ELEMENTS;
auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS;
return ListBuilder(origSegment, ptr, step, origRef->listRef.elementCount(),
dataSize, pointerCount);
} else {
ptr = followFars(ref, segment);
// The pointer is already initialized. We must verify that it has the right size. Unlike
// in getWritableStructListReference(), we never need to "upgrade" the data, because this
// method is called only for non-struct lists, and there is no allowed upgrade path *to*
// a non-struct list, only *from* them.
PRECOND(ref->kind() == WirePointer::LIST,
"Called getList{Field,Element}() but existing pointer is not a list.");
WirePointer* ref = origRef;
SegmentBuilder* segment = origSegment;
word* ptr = followFars(ref, segment);
VALIDATE_INPUT(ref->kind() == WirePointer::LIST,
"Called getList{Field,Element}() but existing pointer is not a list.") {
goto useDefault;
}
if (ref->listRef.elementSize() == FieldSize::INLINE_COMPOSITE) {
FieldSize oldSize = ref->listRef.elementSize();
if (oldSize == FieldSize::INLINE_COMPOSITE) {
// The existing element size is INLINE_COMPOSITE, which means that it is at least two
// words, which makes it bigger than the expected element size. Since fields can only
// grow when upgraded, the existing data must have been written with a newer version of
// the protocol. We therefore never need to upgrade the data in this case, but we do
// need to validate that it is a valid upgrade from what we expected.
// Read the tag to get the actual element count.
WirePointer* tag = reinterpret_cast<WirePointer*>(ptr);
PRECOND(tag->kind() == WirePointer::STRUCT,
"INLINE_COMPOSITE list with non-STRUCT elements not supported.");
ptr += POINTER_SIZE_IN_WORDS;
// First list element is at tag + 1 pointer.
return ListBuilder(segment, tag + 1, tag->structRef.wordSize() * BITS_PER_WORD / ELEMENTS,
WordCount dataSize = tag->structRef.dataSize.get();
WirePointerCount pointerCount = tag->structRef.ptrCount.get();
switch (elementSize) {
case FieldSize::VOID:
// Anything is a valid upgrade from Void.
break;
case FieldSize::BIT:
case FieldSize::BYTE:
case FieldSize::TWO_BYTES:
case FieldSize::FOUR_BYTES:
case FieldSize::EIGHT_BYTES:
VALIDATE_INPUT(dataSize >= 1 * WORDS,
"Existing list value is incompatible with expected type.");
break;
case FieldSize::POINTER:
VALIDATE_INPUT(pointerCount >= 1 * POINTERS,
"Existing list value is incompatible with expected type.");
// Adjust the pointer to point at the reference segment.
ptr += dataSize;
break;
case FieldSize::INLINE_COMPOSITE:
FAIL_CHECK("Can't get here.");
break;
}
// OK, looks valid.
return ListBuilder(segment, ptr,
tag->structRef.wordSize() * BITS_PER_WORD / ELEMENTS,
tag->inlineCompositeListElementCount(),
tag->structRef.dataSize.get() * BITS_PER_WORD,
tag->structRef.ptrCount.get());
dataSize * BITS_PER_WORD, pointerCount);
} else {
BitCount dataSize = dataBitsPerElement(ref->listRef.elementSize()) * ELEMENTS;
WirePointerCount pointerCount = pointersPerElement(ref->listRef.elementSize()) * ELEMENTS;
BitCount dataSize = dataBitsPerElement(oldSize) * ELEMENTS;
WirePointerCount pointerCount = pointersPerElement(oldSize) * ELEMENTS;
VALIDATE_INPUT(dataSize >= dataBitsPerElement(elementSize) * ELEMENTS,
"Existing list value is incompatible with expected type.");
VALIDATE_INPUT(pointerCount >= pointersPerElement(elementSize) * ELEMENTS,
"Existing list value is incompatible with expected type.");
auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS;
return ListBuilder(segment, ptr, step, ref->listRef.elementCount(), dataSize, pointerCount);
return ListBuilder(segment, ptr, step, ref->listRef.elementCount(),
dataSize, pointerCount);
}
}
}
static CAPNPROTO_ALWAYS_INLINE(ListBuilder getWritableStructListPointer(
WirePointer* origRef, SegmentBuilder* origSegment, StructSize elementSize,
const word* defaultValue)) {
if (origRef->isNull()) {
useDefault:
if (defaultValue == nullptr ||
reinterpret_cast<const WirePointer*>(defaultValue)->isNull()) {
memset(origRef, 0, sizeof(*origRef));
return ListBuilder();
}
word* ptr = copyMessage(origSegment, origRef,
reinterpret_cast<const WirePointer*>(defaultValue));
// Assume the default value is valid.
if (elementSize.preferredListEncoding == FieldSize::INLINE_COMPOSITE) {
WirePointer* tag = reinterpret_cast<WirePointer*>(ptr);
return ListBuilder(origSegment, tag + 1, elementSize.total() * BITS_PER_WORD / ELEMENTS,
tag->inlineCompositeListElementCount(),
elementSize.data * BITS_PER_WORD,
elementSize.pointers);
} else {
BitCount dataSize = dataBitsPerElement(elementSize.preferredListEncoding) * ELEMENTS;
WirePointerCount pointerCount =
pointersPerElement(elementSize.preferredListEncoding) * ELEMENTS;
auto step = (dataSize + pointerCount * BITS_PER_POINTER) / ELEMENTS;
return ListBuilder(origSegment, ptr, step, origRef->listRef.elementCount(),
dataSize, pointerCount);
}
} else {
// The pointer is already initialized. We must verify that it has the right size and
// potentially upgrade it if not.
WirePointer* oldRef = origRef;
SegmentBuilder* oldSegment = origSegment;
word* oldPtr = followFars(oldRef, oldSegment);
VALIDATE_INPUT(oldRef->kind() == WirePointer::LIST,
"Called getList{Field,Element}() but existing pointer is not a list.") {
goto useDefault;
}
FieldSize oldSize = oldRef->listRef.elementSize();
if (oldSize == FieldSize::INLINE_COMPOSITE) {
// Existing list is INLINE_COMPOSITE, but we need to verify that the sizes match.
WirePointer* oldTag = reinterpret_cast<WirePointer*>(oldPtr);
oldPtr += POINTER_SIZE_IN_WORDS;
VALIDATE_INPUT(oldTag->kind() == WirePointer::STRUCT,
"INLINE_COMPOSITE list with non-STRUCT elements not supported.") {
goto useDefault;
}
WordCount oldDataSize = oldTag->structRef.dataSize.get();
WirePointerCount oldPointerCount = oldTag->structRef.ptrCount.get();
auto oldStep = (oldDataSize + oldPointerCount * WORDS_PER_POINTER) / ELEMENTS;
ElementCount elementCount = oldTag->inlineCompositeListElementCount();
if (oldDataSize >= elementSize.data && oldPointerCount >= elementSize.pointers) {
// Old size is at least as large as we need. Ship it.
return ListBuilder(oldSegment, oldPtr, oldStep * BITS_PER_WORD, elementCount,
oldDataSize * BITS_PER_WORD, oldPointerCount);
}
// The structs in this list are smaller than expected, probably written using an older
// version of the protocol. We need to make a copy and expand them.
WordCount newDataSize = std::max<WordCount>(oldDataSize, elementSize.data);
WirePointerCount newPointerCount =
std::max<WirePointerCount>(oldPointerCount, elementSize.pointers);
auto newStep = (newDataSize + newPointerCount * WORDS_PER_POINTER) / ELEMENTS;
WordCount totalSize = newStep * elementCount;
word* newPtr = allocate(origRef, origSegment, totalSize + POINTER_SIZE_IN_WORDS,
WirePointer::LIST);
origRef->listRef.setInlineComposite(totalSize);
WirePointer* newTag = reinterpret_cast<WirePointer*>(newPtr);
newTag->setKindAndInlineCompositeListElementCount(WirePointer::STRUCT, elementCount);
newTag->structRef.set(newDataSize, newPointerCount);
newPtr += POINTER_SIZE_IN_WORDS;
word* src = oldPtr;
word* dst = newPtr;
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
// Copy data section.
memcpy(dst, src, oldDataSize * BYTES_PER_WORD / BYTES);
// Copy pointer section.
WirePointer* newPointerSection = reinterpret_cast<WirePointer*>(dst + newDataSize);
WirePointer* oldPointerSection = reinterpret_cast<WirePointer*>(src + oldDataSize);
for (uint i = 0; i < oldPointerCount / POINTERS; i++) {
transferPointer(origSegment, newPointerSection + i, oldSegment, oldPointerSection + i);
}
dst += newStep * (1 * ELEMENTS);
src += oldStep * (1 * ELEMENTS);
}
return ListBuilder(origSegment, newPtr, newStep * BITS_PER_WORD, elementCount,
newDataSize * BITS_PER_WORD, newPointerCount);
} else if (oldSize == elementSize.preferredListEncoding) {
// Old size matches exactly.
auto dataSize = dataBitsPerElement(oldSize);
auto pointerCount = pointersPerElement(oldSize);
auto step = dataSize + pointerCount * BITS_PER_POINTER;
return ListBuilder(oldSegment, oldPtr, step, oldRef->listRef.elementCount(),
dataSize * (1 * ELEMENTS), pointerCount * (1 * ELEMENTS));
} else {
switch (elementSize.preferredListEncoding) {
case FieldSize::VOID:
// No expectations.
break;
case FieldSize::POINTER:
VALIDATE_INPUT(oldSize == FieldSize::POINTER || oldSize == FieldSize::VOID,
"Struct list has incompatible element size.") {
goto useDefault;
}
break;
case FieldSize::INLINE_COMPOSITE:
// Old size can be anything.
break;
case FieldSize::BIT:
case FieldSize::BYTE:
case FieldSize::TWO_BYTES:
case FieldSize::FOUR_BYTES:
case FieldSize::EIGHT_BYTES:
// Preferred size is data-only.
VALIDATE_INPUT(oldSize != FieldSize::POINTER,
"Struct list has incompatible element size.") {
goto useDefault;
}
break;
}
// OK, the old size is compatible with the preferred, but is not exactly the same. We may
// need to upgrade it.
BitCount oldDataSize = dataBitsPerElement(oldSize) * ELEMENTS;
WirePointerCount oldPointerCount = pointersPerElement(oldSize) * ELEMENTS;
auto oldStep = (oldDataSize + oldPointerCount * BITS_PER_POINTER) / ELEMENTS;
ElementCount elementCount = oldRef->listRef.elementCount();
if (oldSize >= elementSize.preferredListEncoding) {
// The old size is at least as large as the preferred, so we don't need to upgrade.
return ListBuilder(oldSegment, oldPtr, oldStep, elementCount,
oldDataSize, oldPointerCount);
}
// Upgrade is necessary.
if (oldSize == FieldSize::VOID) {
// Nothing to copy, just allocate a new list.
return initStructListPointer(origRef, origSegment, elementCount, elementSize);
} else if (elementSize.preferredListEncoding == FieldSize::INLINE_COMPOSITE) {
// Upgrading to an inline composite list.
WordCount newDataSize = elementSize.data;
WirePointerCount newPointerCount = elementSize.pointers;
if (oldSize == FieldSize::POINTER) {
newPointerCount = std::max(newPointerCount, 1 * POINTERS);
} else {
// Old list contains data elements, so we need at least 1 word of data.
newDataSize = std::max(newDataSize, 1 * WORDS);
}
auto newStep = (newDataSize + newPointerCount * WORDS_PER_POINTER) / ELEMENTS;
WordCount totalWords = elementCount * newStep;
word* newPtr = allocate(origRef, origSegment, totalWords + POINTER_SIZE_IN_WORDS,
WirePointer::LIST);
origRef->listRef.setInlineComposite(totalWords);
WirePointer* tag = reinterpret_cast<WirePointer*>(newPtr);
tag->setKindAndInlineCompositeListElementCount(WirePointer::STRUCT, elementCount);
tag->structRef.set(newDataSize, newPointerCount);
newPtr += POINTER_SIZE_IN_WORDS;
if (oldSize == FieldSize::POINTER) {
WirePointer* dst = reinterpret_cast<WirePointer*>(newPtr + newDataSize);
WirePointer* src = reinterpret_cast<WirePointer*>(oldPtr);
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
transferPointer(origSegment, dst, oldSegment, src);
dst += newStep / WORDS_PER_POINTER * (1 * ELEMENTS);
++src;
}
} else if (oldSize == FieldSize::BIT) {
word* dst = newPtr;
char* src = reinterpret_cast<char*>(oldPtr);
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
*reinterpret_cast<char*>(dst) = (src[i/8] >> (i%8)) & 1;
dst += newStep * (1 * ELEMENTS);
}
} else {
word* dst = newPtr;
char* src = reinterpret_cast<char*>(oldPtr);
ByteCount oldByteStep = oldDataSize / BITS_PER_BYTE;
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
memcpy(dst, src, oldByteStep / BYTES);
src += oldByteStep / BYTES;
dst += newStep * (1 * ELEMENTS);
}
}
return ListBuilder(origSegment, newPtr, newStep * BITS_PER_WORD, elementCount,
newDataSize * BITS_PER_WORD, newPointerCount);
} else {
// If oldSize were POINTER or EIGHT_BYTES then the preferred size must be
// INLINE_COMPOSITE because any other compatible size would not require an upgrade.
CHECK(oldSize < FieldSize::EIGHT_BYTES);
// If the preferred size were BIT then oldSize must be VOID, but we handled that case
// above.
CHECK(elementSize.preferredListEncoding >= FieldSize::BIT);
// OK, so the expected list elements are all data and between 1 byte and 1 word each,
// and the old element are data between 1 bit and 4 bytes. We're upgrading from one
// primitive data type to another, larger one.
BitCount newDataSize =
dataBitsPerElement(elementSize.preferredListEncoding) * ELEMENTS;
WordCount totalWords =
roundUpToWords(BitCount64(newDataSize) * (elementCount / ELEMENTS));
word* newPtr = allocate(origRef, origSegment, totalWords, WirePointer::LIST);
origRef->listRef.set(elementSize.preferredListEncoding, elementCount);
char* newBytePtr = reinterpret_cast<char*>(newPtr);
char* oldBytePtr = reinterpret_cast<char*>(oldPtr);
ByteCount newDataByteSize = newDataSize / BITS_PER_BYTE;
if (oldSize == FieldSize::BIT) {
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
*newBytePtr = (oldBytePtr[i/8] >> (i%8)) & 1;
newBytePtr += newDataByteSize / BYTES;
}
} else {
ByteCount oldDataByteSize = oldDataSize / BITS_PER_BYTE;
for (uint i = 0; i < elementCount / ELEMENTS; i++) {
memcpy(newBytePtr, oldBytePtr, oldDataByteSize / BYTES);
oldBytePtr += oldDataByteSize / BYTES;
newBytePtr += newDataByteSize / BYTES;
}
}
return ListBuilder(origSegment, newPtr, newDataSize / ELEMENTS, elementCount,
newDataSize, 0 * POINTERS);
}
}
}
}
......@@ -1205,9 +1529,15 @@ ListBuilder StructBuilder::initStructListField(
}
ListBuilder StructBuilder::getListField(
WirePointerCount ptrIndex, const word* defaultValue) const {
WirePointerCount ptrIndex, FieldSize elementSize, const word* defaultValue) const {
return WireHelpers::getWritableListPointer(
pointers + ptrIndex, segment, defaultValue);
pointers + ptrIndex, segment, elementSize, defaultValue);
}
ListBuilder StructBuilder::getStructListField(
WirePointerCount ptrIndex, StructSize elementSize, const word* defaultValue) const {
return WireHelpers::getWritableStructListPointer(
pointers + ptrIndex, segment, elementSize, defaultValue);
}
template <>
......@@ -1363,9 +1693,16 @@ ListBuilder ListBuilder::initStructListElement(
segment, elementCount, elementSize);
}
ListBuilder ListBuilder::getListElement(ElementCount index) const {
ListBuilder ListBuilder::getListElement(ElementCount index, FieldSize elementSize) const {
return WireHelpers::getWritableListPointer(
reinterpret_cast<WirePointer*>(ptr + index * step / BITS_PER_BYTE), segment, nullptr);
reinterpret_cast<WirePointer*>(ptr + index * step / BITS_PER_BYTE), segment,
elementSize, nullptr);
}
ListBuilder ListBuilder::getStructListElement(ElementCount index, StructSize elementSize) const {
return WireHelpers::getWritableStructListPointer(
reinterpret_cast<WirePointer*>(ptr + index * step / BITS_PER_BYTE), segment,
elementSize, nullptr);
}
template <>
......
......@@ -54,6 +54,11 @@ class SegmentBuilder;
enum class FieldSize: uint8_t {
// TODO: Rename to FieldLayout or maybe ValueLayout.
// Notice that each member of this enum, when representing a list element size, represents a
// size that is greater than or equal to the previous members, since INLINE_COMPOSITE is used
// only for multi-word structs. This is important because it allows us to compare FieldSize
// values for the purpose of deciding when we need to upgrade a list.
VOID = 0,
BIT = 1,
BYTE = 2,
......@@ -340,8 +345,17 @@ public:
// Allocates a new list of the given size for the field at the given index in the pointer
// segment, and return a pointer to it. Each element is initialized to its empty state.
ListBuilder getListField(WirePointerCount ptrIndex, const word* defaultValue) const;
// Gets the already-allocated list field for the given pointer index. If the list is not
ListBuilder getListField(WirePointerCount ptrIndex, FieldSize elementSize,
const word* defaultValue) const;
// Gets the already-allocated list field for the given pointer index, ensuring that the list is
// suitable for storing non-struct elements of the given size. If the list is not already
// allocated, it is allocated as a deep copy of the given default value (a trusted message). If
// the default value is null, an empty list is used.
ListBuilder getStructListField(WirePointerCount ptrIndex, StructSize elementSize,
const word* defaultValue) const;
// Gets the already-allocated list field for the given pointer index, ensuring that the list
// is suitable for storing struct elements of the given size. If the list is not
// already allocated, it is allocated as a deep copy of the given default value (a trusted
// message). If the default value is null, an empty list is used.
......@@ -515,9 +529,15 @@ public:
// Allocates a new list of the given size for the field at the given index in the pointer
// segment, and return a pointer to it. Each element is initialized to its empty state.
ListBuilder getListElement(ElementCount index) const;
// Get the existing list element at the given index. Returns an empty list if the element is
// not initialized.
ListBuilder getListElement(ElementCount index, FieldSize elementSize) const;
// Get the existing list element at the given index, making sure it is suitable for storing
// non-struct elements of the given size. Returns an empty list if the element is not
// initialized.
ListBuilder getStructListElement(ElementCount index, StructSize elementSize) const;
// Get the existing list element at the given index, making sure it is suitable for storing
// struct elements of the given size. Returns an empty list if the element is not
// initialized.
template <typename T>
typename T::Builder initBlobElement(ElementCount index, ByteCount size) const;
......
......@@ -257,7 +257,7 @@ private:
}
inline static internal::ListBuilder getAsElementOf(
const internal::ListBuilder& builder, uint index) {
return builder.getListElement(index * ELEMENTS);
return builder.getListElement(index * ELEMENTS, internal::FieldSizeForType<T>::value);
}
inline static internal::ListReader getAsElementOf(
const internal::ListReader& reader, uint index) {
......@@ -270,7 +270,7 @@ private:
}
inline static internal::ListBuilder getAsFieldOf(
internal::StructBuilder& builder, WirePointerCount index, const word* defaultValue) {
return builder.getListField(index, defaultValue);
return builder.getListField(index, internal::FieldSizeForType<T>::value, defaultValue);
}
inline static internal::ListReader getAsFieldOf(
internal::StructReader& reader, WirePointerCount index, const word* defaultValue) {
......@@ -343,7 +343,7 @@ private:
}
inline static internal::ListBuilder getAsElementOf(
const internal::ListBuilder& builder, uint index) {
return builder.getListElement(index * ELEMENTS);
return builder.getStructListElement(index * ELEMENTS, internal::structSize<T>());
}
inline static internal::ListReader getAsElementOf(
const internal::ListReader& reader, uint index) {
......@@ -356,7 +356,7 @@ private:
}
inline static internal::ListBuilder getAsFieldOf(
internal::StructBuilder& builder, WirePointerCount index, const word* defaultValue) {
return builder.getListField(index, defaultValue);
return builder.getStructListField(index, internal::structSize<T>(), defaultValue);
}
inline static internal::ListReader getAsFieldOf(
internal::StructReader& reader, WirePointerCount index, const word* defaultValue) {
......@@ -429,7 +429,7 @@ private:
}
inline static internal::ListBuilder getAsElementOf(
const internal::ListBuilder& builder, uint index) {
return builder.getListElement(index * ELEMENTS);
return builder.getListElement(index * ELEMENTS, internal::FieldSize::POINTER);
}
inline static internal::ListReader getAsElementOf(
const internal::ListReader& reader, uint index) {
......@@ -442,7 +442,7 @@ private:
}
inline static internal::ListBuilder getAsFieldOf(
internal::StructBuilder& builder, WirePointerCount index, const word* defaultValue) {
return builder.getListField(index, defaultValue);
return builder.getListField(index, internal::FieldSize::POINTER, defaultValue);
}
inline static internal::ListReader getAsFieldOf(
internal::StructReader& reader, WirePointerCount index, const word* defaultValue) {
......@@ -530,7 +530,7 @@ private:
}
inline static internal::ListBuilder getAsElementOf(
const internal::ListBuilder& builder, uint index) {
return builder.getListElement(index * ELEMENTS);
return builder.getListElement(index * ELEMENTS, internal::FieldSize::POINTER);
}
inline static internal::ListReader getAsElementOf(
const internal::ListReader& reader, uint index) {
......@@ -543,7 +543,7 @@ private:
}
inline static internal::ListBuilder getAsFieldOf(
internal::StructBuilder& builder, WirePointerCount index, const word* defaultValue) {
return builder.getListField(index, defaultValue);
return builder.getListField(index, internal::FieldSize::POINTER, defaultValue);
}
inline static internal::ListReader getAsFieldOf(
internal::StructReader& reader, WirePointerCount index, const word* defaultValue) {
......
......@@ -357,14 +357,14 @@ struct TestLateUnion {
struct TestOldVersion {
# A subset of TestNewVersion.
old1 @0 :Int32;
old1 @0 :Int64;
old2 @1 :Text;
old3 @2 :TestOldVersion;
}
struct TestNewVersion {
# A superset of TestOldVersion.
old1 @0 :Int32;
old1 @0 :Int64;
old2 @1 :Text;
old3 @2 :TestNewVersion;
new1 @3 :Int64 = 987;
......
......@@ -519,6 +519,19 @@ public:
inline constexpr UnitRatio(const UnitRatio<OtherNumber, Unit1, Unit2>& other)
: unit1PerUnit2(other.unit1PerUnit2) {}
template <typename OtherNumber>
inline constexpr UnitRatio<decltype(Number(1)+OtherNumber(1)), Unit1, Unit2>
operator+(UnitRatio<OtherNumber, Unit1, Unit2> other) {
return UnitRatio<decltype(Number(1)+OtherNumber(1)), Unit1, Unit2>(
unit1PerUnit2 + other.unit1PerUnit2);
}
template <typename OtherNumber>
inline constexpr UnitRatio<decltype(Number(1)-OtherNumber(1)), Unit1, Unit2>
operator-(UnitRatio<OtherNumber, Unit1, Unit2> other) {
return UnitRatio<decltype(Number(1)-OtherNumber(1)), Unit1, Unit2>(
unit1PerUnit2 - other.unit1PerUnit2);
}
template <typename OtherNumber, typename Unit3>
inline constexpr UnitRatio<decltype(Number(1)*OtherNumber(1)), Unit3, Unit2>
operator*(UnitRatio<OtherNumber, Unit3, Unit1> other) {
......
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