Commit d7f3123c authored by Kenton Varda's avatar Kenton Varda

SchemaLoader -- lets you load and use schemas that aren't compiled in.

parent bf04264d
......@@ -71,6 +71,7 @@ includecapnp_HEADERS = \
src/capnproto/list.h \
src/capnproto/message.h \
src/capnproto/schema.h \
src/capnproto/schema-loader.h \
src/capnproto/dynamic.h \
src/capnproto/stringify.h \
src/capnproto/io.h \
......@@ -100,6 +101,7 @@ libcapnproto_a_SOURCES= \
src/capnproto/list.c++ \
src/capnproto/message.c++ \
src/capnproto/schema.c++ \
src/capnproto/schema-loader.c++ \
src/capnproto/dynamic.c++ \
src/capnproto/stringify.c++ \
src/capnproto/io.c++ \
......@@ -146,6 +148,7 @@ capnproto_test_SOURCES = \
src/capnproto/layout-test.c++ \
src/capnproto/message-test.c++ \
src/capnproto/schema-test.c++ \
src/capnproto/schema-loader-test.c++ \
src/capnproto/dynamic-test.c++ \
src/capnproto/stringify-test.c++ \
src/capnproto/encoding-test.c++ \
......
......@@ -30,6 +30,9 @@
#include "../serialize.h"
#include "../logging.h"
#include "../io.h"
#include "../schema-loader.h"
#include "../dynamic.h"
#include "../stringify.h"
#include <unistd.h>
#include <unordered_map>
#include <vector>
......@@ -207,18 +210,13 @@ inline Indent operator*(Stringifier, Indent i) { return i; }
// =======================================================================================
std::unordered_map<uint64_t, schema::Node::Reader> schemaMap;
SchemaLoader schemaLoader;
schema::Node::Reader findNode(uint64_t id) {
auto iter = schemaMap.find(id);
PRECOND(iter != schemaMap.end(), "Missing schema node.", hex(id));
return iter->second;
}
Text::Reader getUnqualifiedName(schema::Node::Reader node) {
auto parent = findNode(node.getScopeId());
for (auto nested: parent.getNestedNodes()) {
if (nested.getId() == node.getId()) {
Text::Reader getUnqualifiedName(Schema schema) {
auto proto = schema.getProto();
auto parent = schemaLoader.get(proto.getScopeId());
for (auto nested: parent.getProto().getNestedNodes()) {
if (nested.getId() == proto.getId()) {
return nested.getName();
}
}
......@@ -226,39 +224,42 @@ Text::Reader getUnqualifiedName(schema::Node::Reader node) {
return "(?)";
}
TextBlob nodeName(schema::Node::Reader target, schema::Node::Reader scope) {
std::vector<schema::Node::Reader> targetParents;
std::vector<schema::Node::Reader> scopeParts;
TextBlob nodeName(Schema target, Schema scope) {
std::vector<Schema> targetParents;
std::vector<Schema> scopeParts;
{
schema::Node::Reader parent = target;
while (parent.getScopeId() != 0) {
parent = findNode(parent.getScopeId());
Schema parent = target;
while (parent.getProto().getScopeId() != 0) {
parent = schemaLoader.get(parent.getProto().getScopeId());
targetParents.push_back(parent);
}
}
{
schema::Node::Reader parent = scope;
scopeParts.push_back(scope);
while (parent.getScopeId() != 0) {
parent = findNode(parent.getScopeId());
Schema parent = scope;
scopeParts.push_back(parent);
while (parent.getProto().getScopeId() != 0) {
parent = schemaLoader.get(parent.getProto().getScopeId());
scopeParts.push_back(parent);
}
}
// Remove common scope.
while (!scopeParts.empty() && !targetParents.empty() &&
scopeParts.back().getId() == targetParents.back().getId()) {
scopeParts.back() == targetParents.back()) {
scopeParts.pop_back();
targetParents.pop_back();
}
// TODO(someday): This is broken in that we aren't checking for shadowing.
TextBlob path = text();
while (!targetParents.empty()) {
auto part = targetParents.back();
if (part.getScopeId() == 0) {
path = text(move(path), "import \"", part.getDisplayName(), "\".");
auto proto = part.getProto();
if (proto.getScopeId() == 0) {
path = text(move(path), "import \"", proto.getDisplayName(), "\".");
} else {
path = text(move(path), getUnqualifiedName(part), ".");
}
......@@ -268,7 +269,7 @@ TextBlob nodeName(schema::Node::Reader target, schema::Node::Reader scope) {
return text(move(path), getUnqualifiedName(target));
}
TextBlob genType(schema::Type::Reader type, schema::Node::Reader scope) {
TextBlob genType(schema::Type::Reader type, Schema scope) {
auto body = type.getBody();
switch (body.which()) {
case schema::Type::Body::VOID_TYPE: return text("Void");
......@@ -287,10 +288,12 @@ TextBlob genType(schema::Type::Reader type, schema::Node::Reader scope) {
case schema::Type::Body::DATA_TYPE: return text("Data");
case schema::Type::Body::LIST_TYPE:
return text("List(", genType(body.getListType(), scope), ")");
case schema::Type::Body::ENUM_TYPE: return nodeName(findNode(body.getEnumType()), scope);
case schema::Type::Body::STRUCT_TYPE: return nodeName(findNode(body.getStructType()), scope);
case schema::Type::Body::ENUM_TYPE:
return nodeName(scope.getDependency(body.getEnumType()), scope);
case schema::Type::Body::STRUCT_TYPE:
return nodeName(scope.getDependency(body.getStructType()), scope);
case schema::Type::Body::INTERFACE_TYPE:
return nodeName(findNode(body.getInterfaceType()), scope);
return nodeName(scope.getDependency(body.getInterfaceType()), scope);
case schema::Type::Body::OBJECT_TYPE: return text("Object");
}
return text();
......@@ -336,18 +339,18 @@ bool isEmptyValue(schema::Value::Reader value) {
case schema::Value::Body::UINT64_VALUE: return body.getUint64Value() == 0;
case schema::Value::Body::FLOAT32_VALUE: return body.getFloat32Value() == 0;
case schema::Value::Body::FLOAT64_VALUE: return body.getFloat64Value() == 0;
case schema::Value::Body::TEXT_VALUE: return body.getTextValue().size() == 0;
case schema::Value::Body::DATA_VALUE: return body.getDataValue().size() == 0;
case schema::Value::Body::LIST_VALUE: return true; // TODO(soon): list values
case schema::Value::Body::TEXT_VALUE: return !body.hasTextValue();
case schema::Value::Body::DATA_VALUE: return !body.hasDataValue();
case schema::Value::Body::LIST_VALUE: return !body.hasListValue();
case schema::Value::Body::ENUM_VALUE: return body.getEnumValue() == 0;
case schema::Value::Body::STRUCT_VALUE: return true; // TODO(soon): struct values
case schema::Value::Body::STRUCT_VALUE: return !body.hasStructValue();
case schema::Value::Body::INTERFACE_VALUE: return true;
case schema::Value::Body::OBJECT_VALUE: return true;
}
return true;
}
TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value) {
TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value, Schema scope) {
auto body = value.getBody();
switch (body.which()) {
case schema::Value::Body::VOID_VALUE: return text("void");
......@@ -362,25 +365,17 @@ TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value) {
case schema::Value::Body::UINT64_VALUE: return text(body.getUint64Value());
case schema::Value::Body::FLOAT32_VALUE: return text(body.getFloat32Value());
case schema::Value::Body::FLOAT64_VALUE: return text(body.getFloat64Value());
case schema::Value::Body::TEXT_VALUE: return text("TODO"); // TODO(soon): escape strings
case schema::Value::Body::DATA_VALUE: return text("TODO"); // TODO(soon): escape strings
case schema::Value::Body::TEXT_VALUE: return stringify(body.getTextValue());
case schema::Value::Body::DATA_VALUE: return stringify(body.getDataValue());
case schema::Value::Body::LIST_VALUE: {
PRECOND(type.getBody().which() == schema::Type::Body::LIST_TYPE, "type/value mismatch");
// TODO(soon): Requires dynamic message reading.
return text("TODO");
// int i = 0;
// return text("[",
// FOR_EACH(body.getListValue(), element) {
// return text(i++ > 0 ? ", " : "",
// genValue(type.getBody().getListType(), element));
// },
// "]");
auto value = body.getListValue<DynamicList>(
ListSchema::of(type.getBody().getListType(), scope));
return text(stringify(value));
}
case schema::Value::Body::ENUM_VALUE: {
PRECOND(type.getBody().which() == schema::Type::Body::ENUM_TYPE, "type/value mismatch");
auto enumNode = findNode(type.getBody().getEnumType());
PRECOND(enumNode.getBody().which() == schema::Node::Body::ENUM_NODE,
"schema.Type claimed to be an enum, but referred to some other node type.");
auto enumNode = scope.getDependency(type.getBody().getEnumType()).asEnum().getProto();
auto enumType = enumNode.getBody().getEnumNode();
auto enumerants = enumType.getEnumerants();
PRECOND(body.getEnumValue() < enumerants.size(),
......@@ -389,8 +384,9 @@ TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value) {
}
case schema::Value::Body::STRUCT_VALUE: {
PRECOND(type.getBody().which() == schema::Type::Body::STRUCT_TYPE, "type/value mismatch");
// TODO(soon): Requires dynamic message reading.
return text("TODO");
auto value = body.getStructValue<DynamicStruct>(
scope.getDependency(type.getBody().getStructType()).asStruct());
return text(stringify(value));
}
case schema::Value::Body::INTERFACE_VALUE: {
return text("");
......@@ -403,23 +399,24 @@ TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value) {
}
TextBlob genAnnotation(schema::Annotation::Reader annotation,
schema::Node::Reader scope,
Schema scope,
const char* prefix = " ", const char* suffix = "") {
auto decl = findNode(annotation.getId());
auto body = decl.getBody();
auto decl = schemaLoader.get(annotation.getId());
auto body = decl.getProto().getBody();
PRECOND(body.which() == schema::Node::Body::ANNOTATION_NODE);
auto annDecl = body.getAnnotationNode();
// TODO: Don't use displayName.
return text(prefix, "$", nodeName(decl, scope), "(",
genValue(annDecl.getType(), annotation.getValue()), ")", suffix);
genValue(annDecl.getType(), annotation.getValue(), scope), ")", suffix);
}
TextBlob genAnnotations(List<schema::Annotation>::Reader list, schema::Node::Reader scope) {
TextBlob genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
return FOR_EACH(list, ann) { return genAnnotation(ann, scope); };
}
TextBlob genAnnotations(schema::Node::Reader node) {
return genAnnotations(node.getAnnotations(), findNode(node.getScopeId()));
TextBlob genAnnotations(Schema schema) {
auto proto = schema.getProto();
return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
}
const char* elementSizeName(schema::ElementSize size) {
......@@ -437,7 +434,7 @@ const char* elementSizeName(schema::ElementSize size) {
}
TextBlob genStructMember(schema::StructNode::Member::Reader member,
schema::Node::Reader scope, Indent indent, int unionTag = -1) {
Schema scope, Indent indent, int unionTag = -1) {
switch (member.getBody().which()) {
case schema::StructNode::Member::Body::FIELD_MEMBER: {
auto field = member.getBody().getFieldMember();
......@@ -445,7 +442,7 @@ TextBlob genStructMember(schema::StructNode::Member::Reader member,
return text(indent, member.getName(), " @", member.getOrdinal(),
" :", genType(field.getType(), scope),
isEmptyValue(field.getDefaultValue()) ? text("") :
text(" = ", genValue(field.getType(), field.getDefaultValue())),
text(" = ", genValue(field.getType(), field.getDefaultValue(), scope)),
genAnnotations(member.getAnnotations(), scope),
"; # ", size == -1 ? text("ptr[", field.getOffset(), "]")
: text("bits[", field.getOffset() * size, ", ",
......@@ -469,22 +466,23 @@ TextBlob genStructMember(schema::StructNode::Member::Reader member,
return text();
}
TextBlob genNestedDecls(schema::Node::Reader node, Indent indent);
TextBlob genNestedDecls(Schema schema, Indent indent);
TextBlob genDecl(schema::Node::Reader node, Text::Reader name, uint64_t scopeId, Indent indent) {
if (node.getScopeId() != scopeId) {
TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent indent) {
auto proto = schema.getProto();
if (proto.getScopeId() != scopeId) {
// This appears to be an alias for something declared elsewhere.
FAIL_PRECOND("Aliases not implemented.");
}
switch (node.getBody().which()) {
switch (proto.getBody().which()) {
case schema::Node::Body::FILE_NODE:
FAIL_PRECOND("Encountered nested file node.");
break;
case schema::Node::Body::STRUCT_NODE: {
auto body = node.getBody().getStructNode();
auto body = proto.getBody().getStructNode();
return text(
indent, "struct ", name, " @0x", hex(node.getId()), genAnnotations(node), " { # ",
indent, "struct ", name, " @0x", hex(proto.getId()), genAnnotations(schema), " { # ",
body.getDataSectionWordSize() * 8, " bytes, ",
body.getPointerSectionSize(), " ptrs",
body.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
......@@ -492,28 +490,28 @@ TextBlob genDecl(schema::Node::Reader node, Text::Reader name, uint64_t scopeId,
: text(", packed as ", elementSizeName(body.getPreferredListEncoding())),
"\n",
FOR_EACH(body.getMembers(), member) {
return genStructMember(member, node, indent.next());
return genStructMember(member, schema, indent.next());
},
genNestedDecls(node, indent.next()),
genNestedDecls(schema, indent.next()),
indent, "}\n");
}
case schema::Node::Body::ENUM_NODE: {
auto body = node.getBody().getEnumNode();
auto body = proto.getBody().getEnumNode();
uint i = 0;
return text(
indent, "enum ", name, " @0x", hex(node.getId()), genAnnotations(node), " {\n",
indent, "enum ", name, " @0x", hex(proto.getId()), genAnnotations(schema), " {\n",
FOR_EACH(body.getEnumerants(), enumerant) {
return text(indent.next(), enumerant.getName(), " @", i++,
genAnnotations(enumerant.getAnnotations(), node), ";\n");
genAnnotations(enumerant.getAnnotations(), schema), ";\n");
},
genNestedDecls(node, indent.next()),
genNestedDecls(schema, indent.next()),
indent, "}\n");
}
case schema::Node::Body::INTERFACE_NODE: {
auto body = node.getBody().getInterfaceNode();
auto body = proto.getBody().getInterfaceNode();
uint i = 0;
return text(
indent, "interface ", name, " @0x", hex(node.getId()), genAnnotations(node), " {\n",
indent, "interface ", name, " @0x", hex(proto.getId()), genAnnotations(schema), " {\n",
FOR_EACH(body.getMethods(), method) {
int j = 0;
return text(
......@@ -523,26 +521,26 @@ TextBlob genDecl(schema::Node::Reader node, Text::Reader name, uint64_t scopeId,
!isEmptyValue(param.getDefaultValue());
return text(
j++ > 0 ? ", " : "",
param.getName(), ": ", genType(param.getType(), node),
param.getName(), ": ", genType(param.getType(), schema),
hasDefault
? text(" = ", genValue(param.getType(), param.getDefaultValue()))
? text(" = ", genValue(param.getType(), param.getDefaultValue(), schema))
: text(),
genAnnotations(param.getAnnotations(), node));
genAnnotations(param.getAnnotations(), schema));
},
") :", genType(method.getReturnType(), node),
genAnnotations(method.getAnnotations(), node), ";\n");
") :", genType(method.getReturnType(), schema),
genAnnotations(method.getAnnotations(), schema), ";\n");
},
genNestedDecls(node, indent.next()),
genNestedDecls(schema, indent.next()),
indent, "}\n");
}
case schema::Node::Body::CONST_NODE: {
auto body = node.getBody().getConstNode();
auto body = proto.getBody().getConstNode();
return text(
indent, "const ", name, " @0x", hex(node.getId()), " :", genType(body.getType(), node),
" = ", genValue(body.getType(), body.getValue()), ";\n");
indent, "const ", name, " @0x", hex(proto.getId()), " :", genType(body.getType(), schema),
" = ", genValue(body.getType(), body.getValue(), schema), ";\n");
}
case schema::Node::Body::ANNOTATION_NODE: {
auto body = node.getBody().getAnnotationNode();
auto body = proto.getBody().getAnnotationNode();
CappedArray<const char*, 11> targets;
uint i = 0;
if (body.getTargetsFile()) targets[i++] = "file";
......@@ -563,29 +561,31 @@ TextBlob genDecl(schema::Node::Reader node, Text::Reader name, uint64_t scopeId,
targets.setSize(i);
}
return text(
indent, "annotation ", name, " @0x", hex(node.getId()),
indent, "annotation ", name, " @0x", hex(proto.getId()),
" (", strArray(targets, ", "), ") :",
genType(body.getType(), node), genAnnotations(node), ";\n");
genType(body.getType(), schema), genAnnotations(schema), ";\n");
}
}
return text();
}
TextBlob genNestedDecls(schema::Node::Reader node, Indent indent) {
return FOR_EACH(node.getNestedNodes(), nested) {
return genDecl(findNode(nested.getId()), nested.getName(), node.getId(), indent);
TextBlob genNestedDecls(Schema schema, Indent indent) {
uint64_t id = schema.getProto().getId();
return FOR_EACH(schema.getProto().getNestedNodes(), nested) {
return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
};
}
TextBlob genFile(schema::Node::Reader file) {
auto body = file.getBody();
TextBlob genFile(Schema file) {
auto proto = file.getProto();
auto body = proto.getBody();
PRECOND(body.which() == schema::Node::Body::FILE_NODE, "Expected a file node.", body.which());
return text(
"# ", file.getDisplayName(), "\n",
"@0x", hex(file.getId()), ";\n",
FOR_EACH(file.getAnnotations(), ann) { return genAnnotation(ann, file, "", ";\n"); },
"# ", proto.getDisplayName(), "\n",
"@0x", hex(proto.getId()), ";\n",
FOR_EACH(proto.getAnnotations(), ann) { return genAnnotation(ann, file, "", ";\n"); },
genNestedDecls(file, Indent(0)));
}
......@@ -596,14 +596,14 @@ int main(int argc, char* argv[]) {
auto request = reader.getRoot<schema::CodeGeneratorRequest>();
for (auto node: request.getNodes()) {
schemaMap[node.getId()] = node;
schemaLoader.load(node);
}
FdOutputStream rawOut(STDOUT_FILENO);
BufferedOutputStreamWrapper out(rawOut);
for (auto fileId: request.getRequestedFiles()) {
genFile(findNode(fileId)).writeTo(out);
genFile(schemaLoader.get(fileId)).writeTo(out);
}
return 0;
......
......@@ -55,428 +55,30 @@ namespace capnproto {
namespace internal {
namespace {
void dynamicInitTestMessage(DynamicStruct::Builder builder) {
builder.set("voidField", Void::VOID);
builder.set("boolField", true);
builder.set("int8Field", -123);
builder.set("int16Field", -12345);
builder.set("int32Field", -12345678);
builder.set("int64Field", -123456789012345ll);
builder.set("uInt8Field", 234u);
builder.set("uInt16Field", 45678u);
builder.set("uInt32Field", 3456789012u);
builder.set("uInt64Field", 12345678901234567890ull);
builder.set("float32Field", 1234.5);
builder.set("float64Field", -123e45);
builder.set("textField", "foo");
builder.set("dataField", "bar");
{
auto subBuilder = builder.init("structField").as<DynamicStruct>();
subBuilder.set("voidField", Void::VOID);
subBuilder.set("boolField", true);
subBuilder.set("int8Field", -12);
subBuilder.set("int16Field", 3456);
subBuilder.set("int32Field", -78901234);
subBuilder.set("int64Field", 56789012345678ll);
subBuilder.set("uInt8Field", 90u);
subBuilder.set("uInt16Field", 1234u);
subBuilder.set("uInt32Field", 56789012u);
subBuilder.set("uInt64Field", 345678901234567890ull);
subBuilder.set("float32Field", -1.25e-10);
subBuilder.set("float64Field", 345);
subBuilder.set("textField", "baz");
subBuilder.set("dataField", "qux");
{
auto subSubBuilder = subBuilder.init("structField").as<DynamicStruct>();
subSubBuilder.set("textField", "nested");
subSubBuilder.init("structField").as<DynamicStruct>().set("textField", "really nested");
}
subBuilder.set("enumField", TestEnum::BAZ);
subBuilder.set("voidList", {Void::VOID, Void::VOID, Void::VOID});
subBuilder.set("boolList", {false, true, false, true, true});
subBuilder.set("int8List", {12, -34, -0x80, 0x7f});
subBuilder.set("int16List", {1234, -5678, -0x8000, 0x7fff});
// gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1.
subBuilder.set("int32List", {12345678, -90123456, -0x7fffffff - 1, 0x7fffffff});
subBuilder.set("int64List", {123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll-1, 0x7fffffffffffffffll});
subBuilder.set("uInt8List", {12u, 34u, 0u, 0xffu});
subBuilder.set("uInt16List", {1234u, 5678u, 0u, 0xffffu});
subBuilder.set("uInt32List", {12345678u, 90123456u, 0u, 0xffffffffu});
subBuilder.set("uInt64List", {123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull});
subBuilder.set("float32List", {0, 1234567, 1e37, -1e37, 1e-37, -1e-37});
subBuilder.set("float64List", {0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306});
subBuilder.set("textList", {"quux", "corge", "grault"});
subBuilder.set("dataList", {"garply", "waldo", "fred"});
{
auto listBuilder = subBuilder.init("structList", 3).as<DynamicList>();
listBuilder[0].as<DynamicStruct>().set("textField", "x structlist 1");
listBuilder[1].as<DynamicStruct>().set("textField", "x structlist 2");
listBuilder[2].as<DynamicStruct>().set("textField", "x structlist 3");
}
subBuilder.set("enumList", {TestEnum::QUX, "bar", "grault"});
}
builder.set("enumField", "corge");
builder.init("voidList", 6);
builder.set("boolList", {true, false, false, true});
builder.set("int8List", {111, -111});
builder.set("int16List", {11111, -11111});
builder.set("int32List", {111111111, -111111111});
builder.set("int64List", {1111111111111111111ll, -1111111111111111111ll});
builder.set("uInt8List", {111u, 222u});
builder.set("uInt16List", {33333u, 44444u});
builder.set("uInt32List", {3333333333u});
builder.set("uInt64List", {11111111111111111111ull});
builder.set("float32List", {5555.5,
std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::quiet_NaN()});
builder.set("float64List", {7777.75,
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()});
builder.set("textList", {"plugh", "xyzzy", "thud"});
builder.set("dataList", {"oops", "exhausted", "rfc3092"});
{
auto listBuilder = builder.init("structList", 3).as<DynamicList>();
listBuilder[0].as<DynamicStruct>().set("textField", "structlist 1");
listBuilder[1].as<DynamicStruct>().set("textField", "structlist 2");
listBuilder[2].as<DynamicStruct>().set("textField", "structlist 3");
}
builder.set("enumList", {TestEnum::FOO, TestEnum::GARPLY});
}
template <typename T> void expectPrimitiveEq(T a, T b) { EXPECT_EQ(a, b); }
void expectPrimitiveEq(float a, float b) { EXPECT_FLOAT_EQ(a, b); }
void expectPrimitiveEq(double a, double b) { EXPECT_DOUBLE_EQ(a, b); }
void expectPrimitiveEq(Text::Reader a, Text::Builder b) { EXPECT_EQ(a, b); }
void expectPrimitiveEq(Data::Reader a, Data::Builder b) { EXPECT_EQ(a, b); }
// Hack because as<>() is a template-parameter-dependent lookup everywhere below...
#define as template as
template <typename Element, typename T>
void checkList(T reader, std::initializer_list<ReaderFor<Element>> expected) {
auto list = reader.as<DynamicList>();
auto list = reader.template as<DynamicList>();
ASSERT_EQ(expected.size(), list.size());
for (uint i = 0; i < expected.size(); i++) {
expectPrimitiveEq(expected.begin()[i], list[i].as<Element>());
EXPECT_EQ(expected.begin()[i], list[i].template as<Element>());
}
auto typed = reader.as<List<Element>>();
auto typed = reader.template as<List<Element>>();
ASSERT_EQ(expected.size(), typed.size());
for (uint i = 0; i < expected.size(); i++) {
expectPrimitiveEq(expected.begin()[i], typed[i]);
}
}
inline bool isNaN(float f) { return f != f; }
inline bool isNaN(double f) { return f != f; }
template <typename Reader>
void dynamicCheckTestMessage(Reader reader) {
EXPECT_EQ(Void::VOID, reader.get("voidField").as<Void>());
EXPECT_EQ(true, reader.get("boolField").as<bool>());
EXPECT_EQ(-123, reader.get("int8Field").as<int8_t>());
EXPECT_EQ(-12345, reader.get("int16Field").as<int16_t>());
EXPECT_EQ(-12345678, reader.get("int32Field").as<int32_t>());
EXPECT_EQ(-123456789012345ll, reader.get("int64Field").as<int64_t>());
EXPECT_EQ(234u, reader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(45678u, reader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(3456789012u, reader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(12345678901234567890ull, reader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(1234.5f, reader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(-123e45, reader.get("float64Field").as<double>());
EXPECT_EQ("foo", reader.get("textField").as<Text>());
EXPECT_EQ("bar", reader.get("dataField").as<Data>());
{
auto subReader = reader.get("structField").as<DynamicStruct>();
EXPECT_EQ(Void::VOID, subReader.get("voidField").as<Void>());
EXPECT_EQ(true, subReader.get("boolField").as<bool>());
EXPECT_EQ(-12, subReader.get("int8Field").as<int8_t>());
EXPECT_EQ(3456, subReader.get("int16Field").as<int16_t>());
EXPECT_EQ(-78901234, subReader.get("int32Field").as<int32_t>());
EXPECT_EQ(56789012345678ll, subReader.get("int64Field").as<int64_t>());
EXPECT_EQ(90u, subReader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(1234u, subReader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(56789012u, subReader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(345678901234567890ull, subReader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(-1.25e-10f, subReader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(345, subReader.get("float64Field").as<double>());
EXPECT_EQ("baz", subReader.get("textField").as<Text>());
EXPECT_EQ("qux", subReader.get("dataField").as<Data>());
{
auto subSubReader = subReader.get("structField").as<DynamicStruct>();
EXPECT_EQ("nested", subSubReader.get("textField").as<Text>());
EXPECT_EQ("really nested", subSubReader.get("structField").as<DynamicStruct>()
.get("textField").as<Text>());
}
EXPECT_EQ(TestEnum::BAZ, subReader.get("enumField").as<TestEnum>());
checkList<Void>(subReader.get("voidList"), {Void::VOID, Void::VOID, Void::VOID});
checkList<bool>(subReader.get("boolList"), {false, true, false, true, true});
checkList<int8_t>(subReader.get("int8List"), {12, -34, -0x80, 0x7f});
checkList<int16_t>(subReader.get("int16List"), {1234, -5678, -0x8000, 0x7fff});
// gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1.
checkList<int32_t>(subReader.get("int32List"), {12345678, -90123456, -0x7fffffff-1, 0x7fffffff});
checkList<int64_t>(subReader.get("int64List"), {123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll-1, 0x7fffffffffffffffll});
checkList<uint8_t>(subReader.get("uInt8List"), {12u, 34u, 0u, 0xffu});
checkList<uint16_t>(subReader.get("uInt16List"), {1234u, 5678u, 0u, 0xffffu});
checkList<uint32_t>(subReader.get("uInt32List"), {12345678u, 90123456u, 0u, 0xffffffffu});
checkList<uint64_t>(subReader.get("uInt64List"), {123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull});
checkList<float>(subReader.get("float32List"), {0.0f, 1234567.0f, 1e37f, -1e37f, 1e-37f, -1e-37f});
checkList<double>(subReader.get("float64List"), {0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306});
checkList<Text>(subReader.get("textList"), {"quux", "corge", "grault"});
checkList<Data>(subReader.get("dataList"), {"garply", "waldo", "fred"});
{
auto listReader = subReader.get("structList").as<DynamicList>();
ASSERT_EQ(3u, listReader.size());
EXPECT_EQ("x structlist 1", listReader[0].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("x structlist 2", listReader[1].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("x structlist 3", listReader[2].as<DynamicStruct>().get("textField").as<Text>());
}
checkList<TestEnum>(subReader.get("enumList"), {TestEnum::QUX, TestEnum::BAR, TestEnum::GRAULT});
}
EXPECT_EQ(TestEnum::CORGE, reader.get("enumField").as<TestEnum>());
EXPECT_EQ(6u, reader.get("voidList").as<DynamicList>().size());
checkList<bool>(reader.get("boolList"), {true, false, false, true});
checkList<int8_t>(reader.get("int8List"), {111, -111});
checkList<int16_t>(reader.get("int16List"), {11111, -11111});
checkList<int32_t>(reader.get("int32List"), {111111111, -111111111});
checkList<int64_t>(reader.get("int64List"), {1111111111111111111ll, -1111111111111111111ll});
checkList<uint8_t>(reader.get("uInt8List"), {111u, 222u});
checkList<uint16_t>(reader.get("uInt16List"), {33333u, 44444u});
checkList<uint32_t>(reader.get("uInt32List"), {3333333333u});
checkList<uint64_t>(reader.get("uInt64List"), {11111111111111111111ull});
{
auto listReader = reader.get("float32List").as<DynamicList>();
ASSERT_EQ(4u, listReader.size());
EXPECT_EQ(5555.5f, listReader[0].as<float>());
EXPECT_EQ(std::numeric_limits<float>::infinity(), listReader[1].as<float>());
EXPECT_EQ(-std::numeric_limits<float>::infinity(), listReader[2].as<float>());
EXPECT_TRUE(isNaN(listReader[3].as<float>()));
}
{
auto listReader = reader.get("float64List").as<DynamicList>();
ASSERT_EQ(4u, listReader.size());
EXPECT_EQ(7777.75, listReader[0].as<double>());
EXPECT_EQ(std::numeric_limits<double>::infinity(), listReader[1].as<double>());
EXPECT_EQ(-std::numeric_limits<double>::infinity(), listReader[2].as<double>());
EXPECT_TRUE(isNaN(listReader[3].as<double>()));
}
checkList<Text>(reader.get("textList"), {"plugh", "xyzzy", "thud"});
checkList<Data>(reader.get("dataList"), {"oops", "exhausted", "rfc3092"});
{
auto listReader = reader.get("structList").as<DynamicList>();
ASSERT_EQ(3u, listReader.size());
EXPECT_EQ("structlist 1", listReader[0].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("structlist 2", listReader[1].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("structlist 3", listReader[2].as<DynamicStruct>().get("textField").as<Text>());
}
checkList<TestEnum>(reader.get("enumList"), {TestEnum::FOO, TestEnum::GARPLY});
}
template <typename Reader>
void dynamicCheckTestMessageAllZero(Reader reader) {
EXPECT_EQ(Void::VOID, reader.get("voidField").as<Void>());
EXPECT_EQ(false, reader.get("boolField").as<bool>());
EXPECT_EQ(0, reader.get("int8Field").as<int8_t>());
EXPECT_EQ(0, reader.get("int16Field").as<int16_t>());
EXPECT_EQ(0, reader.get("int32Field").as<int32_t>());
EXPECT_EQ(0, reader.get("int64Field").as<int64_t>());
EXPECT_EQ(0u, reader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(0u, reader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(0u, reader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(0u, reader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(0, reader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(0, reader.get("float64Field").as<double>());
EXPECT_EQ("", reader.get("textField").as<Text>());
EXPECT_EQ("", reader.get("dataField").as<Data>());
{
auto subReader = reader.get("structField").as<DynamicStruct>();
EXPECT_EQ(Void::VOID, subReader.get("voidField").as<Void>());
EXPECT_EQ(false, subReader.get("boolField").as<bool>());
EXPECT_EQ(0, subReader.get("int8Field").as<int8_t>());
EXPECT_EQ(0, subReader.get("int16Field").as<int16_t>());
EXPECT_EQ(0, subReader.get("int32Field").as<int32_t>());
EXPECT_EQ(0, subReader.get("int64Field").as<int64_t>());
EXPECT_EQ(0u, subReader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(0u, subReader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(0u, subReader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(0u, subReader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(0, subReader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(0, subReader.get("float64Field").as<double>());
EXPECT_EQ("", subReader.get("textField").as<Text>());
EXPECT_EQ("", subReader.get("dataField").as<Data>());
{
auto subSubReader = subReader.get("structField").as<DynamicStruct>();
EXPECT_EQ("", subSubReader.get("textField").as<Text>());
EXPECT_EQ("", subSubReader.get("structField").as<DynamicStruct>()
.get("textField").as<Text>());
}
EXPECT_EQ(0u, subReader.get("voidList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("boolList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int8List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int16List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt8List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt16List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("float32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("float64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("textList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("dataList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("structList").as<DynamicList>().size());
}
EXPECT_EQ(0u, reader.get("voidList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("boolList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int8List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int16List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt8List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt16List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("float32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("float64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("textList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("dataList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("structList").as<DynamicList>().size());
}
#undef as
void dynamicInitListDefaults(DynamicStruct::Builder builder) {
auto lists = builder.init("lists").as<DynamicStruct>();
lists.init("list0", 2);
lists.init("list1", 4);
lists.init("list8", 2);
lists.init("list16", 2);
lists.init("list32", 2);
lists.init("list64", 2);
lists.init("listP", 2);
lists.get("list0").as<DynamicList>()[0].as<DynamicStruct>().set("f", Void::VOID);
lists.get("list0").as<DynamicList>()[1].as<DynamicStruct>().set("f", Void::VOID);
lists.get("list1").as<DynamicList>()[0].as<DynamicStruct>().set("f", true);
lists.get("list1").as<DynamicList>()[1].as<DynamicStruct>().set("f", false);
lists.get("list1").as<DynamicList>()[2].as<DynamicStruct>().set("f", true);
lists.get("list1").as<DynamicList>()[3].as<DynamicStruct>().set("f", true);
lists.get("list8").as<DynamicList>()[0].as<DynamicStruct>().set("f", 123u);
lists.get("list8").as<DynamicList>()[1].as<DynamicStruct>().set("f", 45u);
lists.get("list16").as<DynamicList>()[0].as<DynamicStruct>().set("f", 12345u);
lists.get("list16").as<DynamicList>()[1].as<DynamicStruct>().set("f", 6789u);
lists.get("list32").as<DynamicList>()[0].as<DynamicStruct>().set("f", 123456789u);
lists.get("list32").as<DynamicList>()[1].as<DynamicStruct>().set("f", 234567890u);
lists.get("list64").as<DynamicList>()[0].as<DynamicStruct>().set("f", 1234567890123456u);
lists.get("list64").as<DynamicList>()[1].as<DynamicStruct>().set("f", 2345678901234567u);
lists.get("listP").as<DynamicList>()[0].as<DynamicStruct>().set("f", "foo");
lists.get("listP").as<DynamicList>()[1].as<DynamicStruct>().set("f", "bar");
{
auto l = lists.init("int32ListList", 3).as<DynamicList>();
l.init(0, 3).as<DynamicList>().copyFrom({1, 2, 3});
l.init(1, 2).as<DynamicList>().copyFrom({4, 5});
l.init(2, 1).as<DynamicList>().copyFrom({12341234});
}
{
auto l = lists.init("textListList", 3).as<DynamicList>();
l.init(0, 2).as<DynamicList>().copyFrom({"foo", "bar"});
l.init(1, 1).as<DynamicList>().copyFrom({"baz"});
l.init(2, 2).as<DynamicList>().copyFrom({"qux", "corge"});
}
{
auto l = lists.init("structListList", 2).as<DynamicList>();
auto e = l.init(0, 2).as<DynamicList>();
e[0].as<TestAllTypes>().setInt32Field(123);
e[1].as<TestAllTypes>().setInt32Field(456);
e = l.init(1, 1).as<DynamicList>();
e[0].as<TestAllTypes>().setInt32Field(789);
}
}
// Hack because as<>() is a template-parameter-dependent lookup everywhere below...
#define as template as
template <typename Reader>
void dynamicCheckListDefaults(Reader reader) {
auto lists = reader.get("lists").as<DynamicStruct>();
ASSERT_EQ(2u, lists.get("list0").as<DynamicList>().size());
ASSERT_EQ(4u, lists.get("list1").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list8").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list16").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list32").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list64").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("listP").as<DynamicList>().size());
EXPECT_EQ(Void::VOID, lists.get("list0").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<Void>());
EXPECT_EQ(Void::VOID, lists.get("list0").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<Void>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<bool>());
EXPECT_FALSE(lists.get("list1").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<bool>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[2].as<DynamicStruct>().get("f").as<bool>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[3].as<DynamicStruct>().get("f").as<bool>());
EXPECT_EQ(123u, lists.get("list8").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint8_t>());
EXPECT_EQ(45u, lists.get("list8").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint8_t>());
EXPECT_EQ(12345u, lists.get("list16").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint16_t>());
EXPECT_EQ(6789u, lists.get("list16").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint16_t>());
EXPECT_EQ(123456789u, lists.get("list32").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint32_t>());
EXPECT_EQ(234567890u, lists.get("list32").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint32_t>());
EXPECT_EQ(1234567890123456u, lists.get("list64").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint64_t>());
EXPECT_EQ(2345678901234567u, lists.get("list64").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint64_t>());
EXPECT_EQ("foo", lists.get("listP").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<Text>());
EXPECT_EQ("bar", lists.get("listP").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<Text>());
{
auto l = lists.get("int32ListList").as<DynamicList>();
ASSERT_EQ(3u, l.size());
checkList<int32_t>(l[0], {1, 2, 3});
checkList<int32_t>(l[1], {4, 5});
checkList<int32_t>(l[2], {12341234});
}
{
auto l = lists.get("textListList").as<DynamicList>();
ASSERT_EQ(3u, l.size());
checkList<Text>(l[0], {"foo", "bar"});
checkList<Text>(l[1], {"baz"});
checkList<Text>(l[2], {"qux", "corge"});
}
{
auto l = lists.get("structListList").as<DynamicList>();
ASSERT_EQ(2u, l.size());
auto e = l[0].as<DynamicList>();
ASSERT_EQ(2u, e.size());
EXPECT_EQ(123, e[0].as<TestAllTypes>().getInt32Field());
EXPECT_EQ(456, e[1].as<TestAllTypes>().getInt32Field());
e = l[1].as<DynamicList>();
ASSERT_EQ(1u, e.size());
EXPECT_EQ(789, e[0].as<TestAllTypes>().getInt32Field());
EXPECT_EQ(expected.begin()[i], typed[i]);
}
}
#undef as
TEST(DynamicApi, Build) {
MallocMessageBuilder builder;
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());
dynamicInitTestMessage(root);
initDynamicTestMessage(root);
checkTestMessage(root.asReader().as<TestAllTypes>());
dynamicCheckTestMessage(root.asReader());
dynamicCheckTestMessage(root);
checkDynamicTestMessage(root.asReader());
checkDynamicTestMessage(root);
}
TEST(DynamicApi, Read) {
......@@ -485,9 +87,9 @@ TEST(DynamicApi, Read) {
initTestMessage(root);
dynamicCheckTestMessage(toDynamic(root.asReader()));
dynamicCheckTestMessage(toDynamic(root).asReader());
dynamicCheckTestMessage(toDynamic(root));
checkDynamicTestMessage(toDynamic(root.asReader()));
checkDynamicTestMessage(toDynamic(root).asReader());
checkDynamicTestMessage(toDynamic(root));
}
TEST(DynamicApi, Defaults) {
......@@ -495,7 +97,7 @@ TEST(DynamicApi, Defaults) {
ArrayPtr<const word> segments[1] = {arrayPtr(nullRoot.words, 1)};
SegmentArrayMessageReader reader(arrayPtr(segments, 1));
auto root = reader.getRoot<DynamicStruct>(Schema::from<TestDefaults>());
dynamicCheckTestMessage(root);
checkDynamicTestMessage(root);
}
TEST(DynamicApi, DefaultsBuilder) {
......@@ -503,24 +105,24 @@ TEST(DynamicApi, DefaultsBuilder) {
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestDefaults>());
checkTestMessage(root.asReader().as<TestDefaults>());
dynamicCheckTestMessage(root.asReader());
checkDynamicTestMessage(root.asReader());
// This will initialize the whole message, replacing null pointers with copies of defaults.
dynamicCheckTestMessage(root);
checkDynamicTestMessage(root);
// Check again now that the message is initialized.
checkTestMessage(root.asReader().as<TestDefaults>());
dynamicCheckTestMessage(root.asReader());
dynamicCheckTestMessage(root);
checkDynamicTestMessage(root.asReader());
checkDynamicTestMessage(root);
}
TEST(DynamicApi, Zero) {
MallocMessageBuilder builder;
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());
dynamicCheckTestMessageAllZero(root.asReader());
checkDynamicTestMessageAllZero(root.asReader());
checkTestMessageAllZero(root.asReader().as<TestAllTypes>());
dynamicCheckTestMessageAllZero(root);
checkDynamicTestMessageAllZero(root);
checkTestMessageAllZero(root.asReader().as<TestAllTypes>());
}
......@@ -528,11 +130,11 @@ TEST(DynamicApi, ListListsBuild) {
MallocMessageBuilder builder;
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestListDefaults>());
dynamicInitListDefaults(root);
initDynamicTestLists(root);
checkTestMessage(root.asReader().as<TestListDefaults>());
dynamicCheckListDefaults(root.asReader());
dynamicCheckListDefaults(root);
checkDynamicTestLists(root.asReader());
checkDynamicTestLists(root);
}
TEST(DynamicApi, ListListsRead) {
......@@ -541,21 +143,21 @@ TEST(DynamicApi, ListListsRead) {
initTestMessage(root);
dynamicCheckListDefaults(toDynamic(root.asReader()));
dynamicCheckListDefaults(toDynamic(root).asReader());
dynamicCheckListDefaults(toDynamic(root));
checkDynamicTestLists(toDynamic(root.asReader()));
checkDynamicTestLists(toDynamic(root).asReader());
checkDynamicTestLists(toDynamic(root));
}
TEST(DynamicApi, GenericObjects) {
MallocMessageBuilder builder;
auto root = builder.getRoot<test::TestObject>();
dynamicInitTestMessage(root.initObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
initDynamicTestMessage(root.initObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
checkTestMessage(root.asReader().getObjectField<TestAllTypes>());
dynamicCheckTestMessage(
checkDynamicTestMessage(
root.asReader().getObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
dynamicCheckTestMessage(root.getObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
checkDynamicTestMessage(root.getObjectField<DynamicStruct>(Schema::from<TestAllTypes>()));
{
{
......@@ -586,14 +188,14 @@ TEST(DynamicApi, DynamicGenericObjects) {
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(Schema::from<test::TestObject>());
dynamicInitTestMessage(root.initObject("objectField", Schema::from<TestAllTypes>()));
initDynamicTestMessage(root.initObject("objectField", Schema::from<TestAllTypes>()));
checkTestMessage(root.asReader().as<test::TestObject>().getObjectField<TestAllTypes>());
dynamicCheckTestMessage(
checkDynamicTestMessage(
root.asReader().get("objectField").as<DynamicObject>().as(Schema::from<TestAllTypes>()));
dynamicCheckTestMessage(
checkDynamicTestMessage(
root.get("objectField").as<DynamicObject>().as(Schema::from<TestAllTypes>()));
dynamicCheckTestMessage(
checkDynamicTestMessage(
root.getObject("objectField", Schema::from<TestAllTypes>()));
{
......@@ -743,7 +345,7 @@ TEST(DynamicApi, LateUnion) {
TEST(DynamicApi, Has) {
MallocMessageBuilder builder;
auto root = builder.initRoot<DynamicStruct>(Schema::from<test::TestDefaults>());
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestDefaults>());
EXPECT_FALSE(root.has("int32Field"));
root.set("int32Field", 123);
......@@ -756,6 +358,28 @@ TEST(DynamicApi, Has) {
EXPECT_TRUE(root.has("structField"));
}
TEST(DynamicApi, HasWhenEmpty) {
AlignedData<1> nullRoot = {{0, 0, 0, 0, 0, 0, 0, 0}};
ArrayPtr<const word> segments[1] = {arrayPtr(nullRoot.words, 1)};
SegmentArrayMessageReader reader(arrayPtr(segments, 1));
auto root = reader.getRoot<DynamicStruct>(Schema::from<TestDefaults>());
EXPECT_FALSE(root.has("voidField"));
EXPECT_FALSE(root.has("int32Field"));
EXPECT_FALSE(root.has("structField"));
EXPECT_FALSE(root.has("int32List"));
}
TEST(DynamicApi, SetEnumFromNative) {
MallocMessageBuilder builder;
auto root = builder.initRoot<DynamicStruct>(Schema::from<TestAllTypes>());
root.set("enumField", TestEnum::BAZ);
root.set("enumList", {TestEnum::BAR, TestEnum::FOO});
EXPECT_EQ(TestEnum::BAZ, root.get("enumField").as<TestEnum>());
checkList<TestEnum>(root.get("enumList"), {TestEnum::BAR, TestEnum::FOO});
}
} // namespace
} // namespace internal
} // namespace capnproto
......@@ -282,13 +282,6 @@ void DynamicUnion::Builder::setObjectDiscriminant(StructSchema::Member member) {
// =======================================================================================
void DynamicStruct::Reader::verifySchema(StructSchema expected) {
PRECOND(schema == expected, "Type mismatch when using DynamicStruct::Reader::as().");
}
void DynamicStruct::Builder::verifySchema(StructSchema expected) {
PRECOND(schema == expected, "Type mismatch when using DynamicStruct::Builder::as().");
}
DynamicValue::Reader DynamicStruct::Reader::get(StructSchema::Member member) {
PRECOND(member.getContainingStruct() == schema, "`member` is not a member of this struct.");
return getImpl(reader, member);
......@@ -1320,15 +1313,6 @@ DynamicList::Reader DynamicList::Builder::asReader() {
return DynamicList::Reader(schema, builder.asReader());
}
void DynamicList::Reader::verifySchema(ListSchema expectedSchema) {
PRECOND(schema == expectedSchema,
"Type mismatch when using DynamicList::Reader::as().");
}
void DynamicList::Builder::verifySchema(ListSchema expectedSchema) {
PRECOND(schema == expectedSchema,
"Type mismatch when using DynamicList::Reader::as().");
}
// =======================================================================================
namespace {
......
......@@ -275,8 +275,6 @@ private:
inline Reader(StructSchema schema, internal::StructReader reader)
: schema(schema), reader(reader) {}
void verifySchema(StructSchema expected);
static DynamicValue::Reader getImpl(internal::StructReader reader, StructSchema::Member member);
template <typename T>
......@@ -357,8 +355,6 @@ private:
inline Builder(StructSchema schema, internal::StructBuilder builder)
: schema(schema), builder(builder) {}
void verifySchema(StructSchema expected);
static DynamicValue::Builder getImpl(
internal::StructBuilder builder, StructSchema::Member member);
static DynamicStruct::Builder getObjectImpl(
......@@ -425,8 +421,6 @@ private:
Reader(ListSchema schema, internal::ListReader reader): schema(schema), reader(reader) {}
void verifySchema(ListSchema expectedSchema);
template <typename T>
friend struct internal::PointerHelpers;
friend struct DynamicStruct;
......@@ -469,8 +463,6 @@ private:
Builder(ListSchema schema, internal::ListBuilder builder): schema(schema), builder(builder) {}
void verifySchema(ListSchema expectedSchema);
template <typename T>
friend struct internal::PointerHelpers;
friend struct DynamicStruct;
......@@ -880,14 +872,14 @@ template <typename T>
typename T::Reader DynamicStruct::Reader::as() {
static_assert(kind<T>() == Kind::STRUCT,
"DynamicStruct::Reader::as<T>() can only convert to struct types.");
verifySchema(Schema::from<T>());
schema.requireUsableAs<T>();
return typename T::Reader(reader);
}
template <typename T>
typename T::Builder DynamicStruct::Builder::as() {
static_assert(kind<T>() == Kind::STRUCT,
"DynamicStruct::Builder::as<T>() can only convert to struct types.");
verifySchema(Schema::from<T>());
schema.requireUsableAs<T>();
return typename T::Builder(builder);
}
......@@ -910,14 +902,14 @@ template <typename T>
typename T::Reader DynamicList::Reader::as() {
static_assert(kind<T>() == Kind::LIST,
"DynamicStruct::Reader::as<T>() can only convert to list types.");
verifySchema(Schema::from<T>());
schema.requireUsableAs<T>();
return typename T::Reader(reader);
}
template <typename T>
typename T::Builder DynamicList::Builder::as() {
static_assert(kind<T>() == Kind::LIST,
"DynamicStruct::Builder::as<T>() can only convert to list types.");
verifySchema(Schema::from<T>());
schema.requireUsableAs<T>();
return typename T::Builder(builder);
}
......
......@@ -1307,6 +1307,34 @@ TEST(Encoding, ZeroOldObject) {
checkTestMessageAllZero(oldSub2);
}
TEST(Encoding, Has) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
EXPECT_FALSE(root.hasTextField());
EXPECT_FALSE(root.hasDataField());
EXPECT_FALSE(root.hasStructField());
EXPECT_FALSE(root.hasInt32List());
EXPECT_FALSE(root.asReader().hasTextField());
EXPECT_FALSE(root.asReader().hasDataField());
EXPECT_FALSE(root.asReader().hasStructField());
EXPECT_FALSE(root.asReader().hasInt32List());
initTestMessage(root);
EXPECT_TRUE(root.hasTextField());
EXPECT_TRUE(root.hasDataField());
EXPECT_TRUE(root.hasStructField());
EXPECT_TRUE(root.hasInt32List());
EXPECT_TRUE(root.asReader().hasTextField());
EXPECT_TRUE(root.asReader().hasDataField());
EXPECT_TRUE(root.asReader().hasStructField());
EXPECT_TRUE(root.asReader().hasInt32List());
}
} // namespace
} // namespace internal
} // namespace capnproto
......@@ -66,10 +66,21 @@ Exception::Exception(const Exception& other) noexcept
: file(other.file), line(other.line), nature(other.nature), durability(other.durability),
description(str(other.description)), traceCount(other.traceCount) {
memcpy(trace, other.trace, sizeof(trace[0]) * traceCount);
if (other.context != nullptr) {
context = heap<Context>(**other.context);
}
}
Exception::~Exception() noexcept {}
Exception::Context::Context(const Context& other) noexcept
: file(other.file), line(other.line), description(str(other.description)) {
if (other.next != nullptr) {
next = heap<Context>(**other.next);
}
}
void Exception::wrapContext(const char* file, int line, Array<char>&& description) {
context = heap<Context>(file, line, move(description), move(context));
}
......
......@@ -65,6 +65,7 @@ public:
Exception(Nature nature, Durability durability, const char* file, int line,
Array<char> description = nullptr) noexcept;
Exception(const Exception& other) noexcept;
Exception(Exception&& other) = default;
~Exception() noexcept;
const char* getFile() const { return file; }
......@@ -83,6 +84,7 @@ public:
Context(const char* file, int line, Array<char>&& description, Maybe<Own<Context>>&& next)
: file(file), line(line), description(move(description)), next(move(next)) {}
Context(const Context& other) noexcept;
};
inline Maybe<const Context&> getContext() const {
......@@ -125,6 +127,7 @@ class ExceptionCallback {
public:
ExceptionCallback();
CAPNPROTO_DISALLOW_COPY(ExceptionCallback);
virtual ~ExceptionCallback();
virtual void onRecoverableException(Exception&& exception);
......@@ -160,6 +163,7 @@ public:
public:
ScopedRegistration(ExceptionCallback& callback);
CAPNPROTO_DISALLOW_COPY(ScopedRegistration);
~ScopedRegistration();
inline ExceptionCallback& getCallback() { return callback; }
......
......@@ -141,6 +141,9 @@ struct RawSchema {
struct MemberInfo {
uint16_t unionIndex; // 0 = not in a union, >0 = parent union's index + 1
uint16_t index; // index of the member
MemberInfo() = default;
inline MemberInfo(uint16_t unionIndex, uint16_t index): unionIndex(unionIndex), index(index) {}
};
const MemberInfo* membersByName;
......@@ -150,6 +153,11 @@ struct RawSchema {
uint32_t dependencyCount;
uint32_t memberCount;
// Sizes of above tables.
const RawSchema* canCastTo;
// Points to the RawSchema of a compiled-in type to which it is safe to cast any DynamicValue
// with this schema. This is null for all compiled-in types; it is only set by SchemaLoader on
// dynamically-loaded types.
};
template <typename T>
......
......@@ -1956,7 +1956,7 @@ const word* StructReader::getUncheckedPointer(WirePointerCount ptrIndex) const {
}
bool StructReader::isPointerFieldNull(WirePointerCount ptrIndex) const {
return (pointers + ptrIndex)->isNull();
return ptrIndex >= pointerCount || (pointers + ptrIndex)->isNull();
}
WordCount64 StructReader::totalSize() const {
......
......@@ -156,6 +156,7 @@ public:
class Context: public ExceptionCallback {
public:
Context();
CAPNPROTO_DISALLOW_COPY(Context);
virtual ~Context();
virtual void addTo(Exception& exception) = 0;
......@@ -171,20 +172,16 @@ public:
template <typename Func>
class ContextImpl: public Context {
public:
inline ContextImpl(Func&& func): func(capnproto::move(func)) {}
inline ContextImpl(Func& func): func(func) {}
CAPNPROTO_DISALLOW_COPY(ContextImpl);
void addTo(Exception& exception) override {
func(exception);
}
private:
Func func;
Func& func;
};
template <typename Func>
static ContextImpl<RemoveReference<Func>> context(Func&& func) {
return ContextImpl<RemoveReference<Func>>(capnproto::forward<Func>(func));
}
template <typename... Params>
static void addContextTo(Exception& exception, const char* file,
int line, const char* macroArgs, Params&&... params);
......@@ -271,11 +268,11 @@ ArrayPtr<const char> operator*(const Stringifier&, Log::Severity severity);
} while (false)
#define CONTEXT(...) \
auto _capnpLoggingContext = ::capnproto::Log::context( \
[&](::capnproto::Exception& exception) { \
auto _capnpContextFunc = [&](::capnproto::Exception& exception) { \
return ::capnproto::Log::addContextTo(exception, \
__FILE__, __LINE__, #__VA_ARGS__, ##__VA_ARGS__); \
})
}; \
::capnproto::Log::ContextImpl<decltype(_capnpContextFunc)> _capnpContext(_capnpContextFunc)
#ifdef NDEBUG
#define DLOG(...) do {} while (false)
......
......@@ -212,4 +212,20 @@ ArrayPtr<word> MallocMessageBuilder::allocateSegment(uint minimumSize) {
return arrayPtr(reinterpret_cast<word*>(result), size);
}
// -------------------------------------------------------------------
FlatMessageBuilder::FlatMessageBuilder(ArrayPtr<word> array): array(array), allocated(false) {}
FlatMessageBuilder::~FlatMessageBuilder() {}
void FlatMessageBuilder::requireFilled() {
PRECOND(getSegmentsForOutput()[0].end() == array.end(),
"FlatMessageBuilder's buffer was too large.");
}
ArrayPtr<word> FlatMessageBuilder::allocateSegment(uint minimumSize) {
PRECOND(!allocated, "FlatMessageBuilder's buffer was not large enough.");
allocated = true;
return array;
}
} // namespace capnproto
......@@ -175,7 +175,7 @@ private:
};
template <typename RootType>
static typename RootType::Reader readMessageUnchecked(const word* data);
typename RootType::Reader readMessageUnchecked(const word* data);
// IF THE INPUT IS INVALID, THIS MAY CRASH, CORRUPT MEMORY, CREATE A SECURITY HOLE IN YOUR APP,
// MURDER YOUR FIRST-BORN CHILD, AND/OR BRING ABOUT ETERNAL DAMNATION ON ALL OF HUMANITY. DO NOT
// USE UNLESS YOU UNDERSTAND THE CONSEQUENCES.
......@@ -198,12 +198,16 @@ static typename RootType::Reader readMessageUnchecked(const word* data);
// MyMessage::Reader reader = Message<MyMessage>::readMessageUnchecked(MyMessage::DEFAULT.words);
//
// To sanitize a message from an untrusted source such that it can be safely passed to
// readMessageUnchecked(), construct a MessageBuilder whose first segment is large enough to store
// the message, and then use MessageBuilder::setRoot() to copy the message in. The process of
// copying the message implicitly validates all pointers.
// readMessageUnchecked(), use copyToUnchecked().
template <typename Reader>
void copyToUnchecked(Reader&& reader, ArrayPtr<word> uncheckedBuffer);
// Copy the content of the given reader into the given buffer, such that it can safely be passed to
// readMessageUnchecked(). The buffer's size must be exactly reader.totalSizeInWords() + 1,
// otherwise an exception will be thrown.
template <typename Type>
static typename Type::Reader defaultValue();
typename Type::Reader defaultValue();
// Get a default instance of the given struct or list type.
//
// TODO(cleanup): Find a better home for this function?
......@@ -296,6 +300,25 @@ private:
std::unique_ptr<MoreSegments> moreSegments;
};
class FlatMessageBuilder: public MessageBuilder {
// A message builder implementation which allocates from a single flat array, throwing an
// exception if it runs out of space.
public:
explicit FlatMessageBuilder(ArrayPtr<word> array);
CAPNPROTO_DISALLOW_COPY(FlatMessageBuilder);
virtual ~FlatMessageBuilder();
void requireFilled();
// Throws an exception if the flat array is not exactly full.
virtual ArrayPtr<word> allocateSegment(uint minimumSize) override;
private:
ArrayPtr<word> array;
bool allocated;
};
// =======================================================================================
// implementation details
......@@ -333,6 +356,13 @@ typename RootType::Reader readMessageUnchecked(const word* data) {
return typename RootType::Reader(internal::StructReader::readRootUnchecked(data));
}
template <typename Reader>
void copyToUnchecked(Reader&& reader, ArrayPtr<word> uncheckedBuffer) {
FlatMessageBuilder builder(uncheckedBuffer);
builder.setRoot(capnproto::forward<Reader>(reader));
builder.requireFilled();
}
template <typename Type>
static typename Type::Reader defaultValue() {
// TODO(soon): Correctly handle lists. Maybe primitives too?
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define CAPNPROTO_PRIVATE
#include "schema-loader.h"
#include <gtest/gtest.h>
#include "test-util.h"
#include "logging.h"
namespace capnproto {
namespace internal {
namespace {
TEST(SchemaLoader, Load) {
SchemaLoader loader;
Schema struct32Schema = loader.load(Schema::from<test::TestLists::Struct32>().getProto());
auto nativeSchema = Schema::from<test::TestLists>();
Schema testListsSchema = loader.load(nativeSchema.getProto());
Schema struct8Schema = loader.load(Schema::from<test::TestLists::Struct8>().getProto());
Schema structPSchema = loader.load(Schema::from<test::TestLists::StructP>().getProto());
EXPECT_STREQ(nativeSchema.getProto().debugString().cStr(),
testListsSchema.getProto().debugString().cStr());
EXPECT_FALSE(testListsSchema == nativeSchema);
EXPECT_FALSE(struct32Schema == Schema::from<test::TestLists::Struct32>());
EXPECT_FALSE(struct8Schema == Schema::from<test::TestLists::Struct8>());
EXPECT_FALSE(structPSchema == Schema::from<test::TestLists::StructP>());
EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::Struct32>()) == struct32Schema);
EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::Struct8>()) == struct8Schema);
EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::StructP>()) == structPSchema);
auto struct16Schema = testListsSchema.getDependency(typeId<test::TestLists::Struct16>());
EXPECT_EQ(0u, struct16Schema.getProto().getBody().getStructNode().getMembers().size());
}
TEST(SchemaLoader, Use) {
SchemaLoader loader;
StructSchema schema = loader.load(Schema::from<TestAllTypes>().getProto()).asStruct();
// Also have to load TestEnum.
loader.load(Schema::from<TestEnum>().getProto());
{
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(schema);
initDynamicTestMessage(root);
checkDynamicTestMessage(root.asReader());
// Can't convert to TestAllTypes because we didn't use loadCompiledTypeAndDependencies().
EXPECT_ANY_THROW(root.as<TestAllTypes>());
// But if we reinterpret the raw bytes, it works.
checkTestMessage(builder.getRoot<TestAllTypes>());
}
loader.loadCompiledTypeAndDependencies<TestAllTypes>();
{
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(schema);
initDynamicTestMessage(root);
// Now we can actually cast.
checkTestMessage(root.as<TestAllTypes>());
}
// Let's also test TestListDefaults, but as we do so, let's load the compiled types first, to
// make sure the opposite order works.
loader.loadCompiledTypeAndDependencies<TestListDefaults>();
StructSchema testListsSchema = loader.get(typeId<TestListDefaults>()).asStruct();
EXPECT_TRUE(testListsSchema != Schema::from<TestListDefaults>());
{
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(testListsSchema);
initDynamicTestLists(root);
checkDynamicTestLists(root.asReader());
checkTestMessage(root.as<TestListDefaults>());
}
EXPECT_TRUE(loader.load(Schema::from<TestListDefaults>().getProto()) == testListsSchema);
{
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(testListsSchema);
initDynamicTestLists(root);
checkTestMessage(root.as<TestListDefaults>());
}
// Finally, let's test some unions.
StructSchema unionSchema = loader.load(Schema::from<TestUnion>().getProto()).asStruct();
{
MallocMessageBuilder builder;
auto root = builder.getRoot<DynamicStruct>(unionSchema);
root.get("union0").as<DynamicUnion>().set("u0f1s16", 123);
root.get("union1").as<DynamicUnion>().set("u1f0sp", "hello");
auto reader = builder.getRoot<TestUnion>().asReader();
EXPECT_EQ(123, reader.getUnion0().getU0f1s16());
EXPECT_EQ("hello", reader.getUnion1().getU1f0sp());
}
}
template <typename T>
Schema loadUnderAlternateTypeId(SchemaLoader& loader, uint64_t id) {
MallocMessageBuilder schemaBuilder;
schemaBuilder.setRoot(Schema::from<T>().getProto());
auto root = schemaBuilder.getRoot<schema::Node>();
root.setId(id);
if (root.getBody().which() == schema::Node::Body::STRUCT_NODE) {
// If the struct contains any self-referential members, change their type IDs as well.
auto members = root.getBody().getStructNode().getMembers();
for (auto member: members) {
if (member.getBody().which() == schema::StructNode::Member::Body::FIELD_MEMBER) {
auto type = member.getBody().getFieldMember().getType().getBody();
if (type.which() == schema::Type::Body::STRUCT_TYPE &&
type.getStructType() == typeId<T>()) {
type.setStructType(id);
}
}
}
}
return loader.load(root);
}
TEST(SchemaLoader, Upgrade) {
SchemaLoader loader;
loader.loadCompiledTypeAndDependencies<test::TestOldVersion>();
StructSchema schema = loader.get(typeId<test::TestOldVersion>()).asStruct();
EXPECT_STREQ(Schema::from<test::TestOldVersion>().getProto().debugString().cStr(),
schema.getProto().debugString().cStr());
loadUnderAlternateTypeId<test::TestNewVersion>(loader, typeId<test::TestOldVersion>());
// The new version replaced the old.
EXPECT_STREQ(Schema::from<test::TestNewVersion>().getProto().getDisplayName(),
schema.getProto().getDisplayName());
// But it is still usable as the old version.
schema.requireUsableAs<test::TestOldVersion>();
}
TEST(SchemaLoader, Downgrade) {
SchemaLoader loader;
loader.loadCompiledTypeAndDependencies<test::TestNewVersion>();
StructSchema schema = loader.get(typeId<test::TestNewVersion>()).asStruct();
EXPECT_STREQ(Schema::from<test::TestNewVersion>().getProto().debugString().cStr(),
schema.getProto().debugString().cStr());
loadUnderAlternateTypeId<test::TestOldVersion>(loader, typeId<test::TestNewVersion>());
// We kept the new version, because the replacement was older.
EXPECT_STREQ(Schema::from<test::TestNewVersion>().getProto().getDisplayName(),
schema.getProto().getDisplayName());
schema.requireUsableAs<test::TestNewVersion>();
}
TEST(SchemaLoader, Incompatible) {
SchemaLoader loader;
loader.loadCompiledTypeAndDependencies<test::TestListDefaults>();
EXPECT_ANY_THROW(
loadUnderAlternateTypeId<test::TestAllTypes>(loader, typeId<test::TestListDefaults>()));
}
TEST(SchemaLoader, Enumerate) {
SchemaLoader loader;
loader.loadCompiledTypeAndDependencies<TestAllTypes>();
auto list = loader.getAllLoaded();
ASSERT_EQ(2u, list.size());
if (list[0] == loader.get(typeId<TestAllTypes>())) {
EXPECT_TRUE(list[1] == loader.get(typeId<TestEnum>()));
} else {
EXPECT_TRUE(list[0] == loader.get(typeId<TestEnum>()));
EXPECT_TRUE(list[1] == loader.get(typeId<TestAllTypes>()));
}
}
// TODO(test): More extensively test upgrade/downgrade checks.
} // namespace
} // namespace internal
} // namespace capnproto
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define CAPNPROTO_PRIVATE
#include "schema-loader.h"
#include <unordered_map>
#include <map>
#include "message.h"
#include "arena.h"
#include "logging.h"
#include "exception.h"
namespace capnproto {
class SchemaLoader::Impl {
public:
Impl();
internal::RawSchema* load(schema::Node::Reader reader);
internal::RawSchema* loadNative(const internal::RawSchema* nativeSchema);
internal::RawSchema* loadEmpty(uint64_t id, Text::Reader name, schema::Node::Body::Which kind);
// Create a dummy empty schema of the given kind for the given id and load it.
internal::RawSchema* tryGet(uint64_t typeId) const;
Array<Schema> getAllLoaded() const;
template <typename T>
T* allocate(size_t count = 1) {
ByteCount bytes = count * sizeof(T) * BYTES;
static_assert(sizeof(word) == 8, "This code assumes 64-bit words.");
WordCount words = (bytes + 7 * BYTES) / BYTES_PER_WORD;
while (true) {
word* result = segment->allocate(words);
if (result != nullptr) {
return reinterpret_cast<T*>(result);
}
segment = arena->getSegmentWithAvailable(words);
}
}
private:
MallocMessageBuilder allocator;
internal::BuilderArena* arena;
internal::SegmentBuilder* segment;
// HACK: We don't actually use these to build messages, we use them to allocate memory that
// should be freed when the loader is freed. We're reusing BuilderArena as a convenient
// implementation of the general concept of arenas.
// TODO(cleanup): Develop a stand-alone Arena class that can be used for things like this.
std::unordered_map<uint64_t, internal::RawSchema*> schemas;
};
// =======================================================================================
class SchemaLoader::Validator {
public:
Validator(SchemaLoader::Impl& loader): loader(loader) {}
bool validate(schema::Node::Reader node) {
isValid = true;
nodeName = node.getDisplayName();
dependencies.clear();
CONTEXT("validating schema node", nodeName, node.getBody().which());
switch (node.getBody().which()) {
case schema::Node::Body::FILE_NODE:
validate(node.getBody().getFileNode());
break;
case schema::Node::Body::STRUCT_NODE:
validate(node.getBody().getStructNode());
break;
case schema::Node::Body::ENUM_NODE:
validate(node.getBody().getEnumNode());
break;
case schema::Node::Body::INTERFACE_NODE:
validate(node.getBody().getInterfaceNode());
break;
case schema::Node::Body::CONST_NODE:
validate(node.getBody().getConstNode());
break;
case schema::Node::Body::ANNOTATION_NODE:
validate(node.getBody().getAnnotationNode());
break;
}
// We accept and pass through node types we don't recognize.
return isValid;
}
const internal::RawSchema** makeDependencyArray(uint32_t* count) {
*count = dependencies.size();
const internal::RawSchema** result =
loader.allocate<const internal::RawSchema*>(*count);
uint pos = 0;
for (auto& dep: dependencies) {
result[pos++] = dep.second;
}
DCHECK(pos == *count);
return result;
}
const internal::RawSchema::MemberInfo* makeMemberInfoArray(uint32_t* count) {
*count = members.size();
internal::RawSchema::MemberInfo* result =
loader.allocate<internal::RawSchema::MemberInfo>(*count);
uint pos = 0;
for (auto& member: members) {
result[pos++] = internal::RawSchema::MemberInfo(member.first.first, member.second);
}
DCHECK(pos == *count);
return result;
}
private:
SchemaLoader::Impl& loader;
Text::Reader nodeName;
bool isValid;
std::map<uint64_t, internal::RawSchema*> dependencies;
// Maps (unionIndex, name) -> index for each member.
std::map<std::pair<uint, Text::Reader>, uint> members;
#define VALIDATE_SCHEMA(condition, ...) \
VALIDATE_INPUT(condition, ##__VA_ARGS__) { isValid = false; return; }
#define FAIL_VALIDATE_SCHEMA(...) \
FAIL_VALIDATE_INPUT(__VA_ARGS__) { isValid = false; return; }
void validate(schema::FileNode::Reader fileNode) {
// Nothing needs validation.
}
void validate(schema::StructNode::Reader structNode) {
uint dataSizeInBits;
uint pointerCount;
switch (structNode.getPreferredListEncoding()) {
case schema::ElementSize::EMPTY:
dataSizeInBits = 0;
pointerCount = 0;
break;
case schema::ElementSize::BIT:
dataSizeInBits = 1;
pointerCount = 0;
break;
case schema::ElementSize::BYTE:
dataSizeInBits = 8;
pointerCount = 0;
break;
case schema::ElementSize::TWO_BYTES:
dataSizeInBits = 16;
pointerCount = 0;
break;
case schema::ElementSize::FOUR_BYTES:
dataSizeInBits = 32;
pointerCount = 0;
break;
case schema::ElementSize::EIGHT_BYTES:
dataSizeInBits = 64;
pointerCount = 0;
break;
case schema::ElementSize::POINTER:
dataSizeInBits = 0;
pointerCount = 1;
break;
case schema::ElementSize::INLINE_COMPOSITE:
dataSizeInBits = structNode.getDataSectionWordSize() * 64;
pointerCount = structNode.getPointerSectionSize();
break;
default:
FAIL_VALIDATE_SCHEMA("Invalid preferredListEncoding.");
dataSizeInBits = 0;
pointerCount = 0;
break;
}
VALIDATE_SCHEMA(structNode.getDataSectionWordSize() == (dataSizeInBits + 63) / 64 &&
structNode.getPointerSectionSize() == pointerCount,
"Struct size does not match preferredListEncoding.");
uint ordinalCount = 0;
auto members = structNode.getMembers();
for (auto member: members) {
++ordinalCount;
if (member.getBody().which() == schema::StructNode::Member::Body::UNION_MEMBER) {
ordinalCount += member.getBody().getUnionMember().getMembers().size();
}
}
CAPNPROTO_STACK_ARRAY(bool, sawCodeOrder, members.size(), 256);
memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));
CAPNPROTO_STACK_ARRAY(bool, sawOrdinal, ordinalCount, 256);
memset(sawOrdinal.begin(), 0, sawOrdinal.size() * sizeof(sawOrdinal[0]));
uint index = 0;
for (auto member: members) {
CONTEXT("validating struct member", member.getName());
validate(member, sawCodeOrder, sawOrdinal, dataSizeInBits, pointerCount, 0, index++);
}
}
void validateMemberName(Text::Reader name, uint unionIndex, uint index) {
bool isNewName = members.insert(std::make_pair(
std::pair<uint, Text::Reader>(unionIndex, name), index)).second;
VALIDATE_SCHEMA(isNewName, "duplicate name", name);
}
void validate(schema::StructNode::Member::Reader member,
ArrayPtr<bool> sawCodeOrder, ArrayPtr<bool> sawOrdinal,
uint dataSizeInBits, uint pointerCount,
uint unionIndex, uint index) {
validateMemberName(member.getName(), unionIndex, index);
VALIDATE_SCHEMA(member.getOrdinal() < sawOrdinal.size() &&
!sawOrdinal[member.getOrdinal()],
"Invalid ordinal.");
sawOrdinal[member.getOrdinal()] = true;
VALIDATE_SCHEMA(member.getCodeOrder() < sawCodeOrder.size() &&
!sawCodeOrder[member.getCodeOrder()],
"Invalid codeOrder.");
sawCodeOrder[member.getCodeOrder()] = true;
switch (member.getBody().which()) {
case schema::StructNode::Member::Body::FIELD_MEMBER: {
auto field = member.getBody().getFieldMember();
uint fieldBits;
bool fieldIsPointer;
validate(field.getType(), field.getDefaultValue(), &fieldBits, &fieldIsPointer);
VALIDATE_SCHEMA(fieldBits * (field.getOffset() + 1) <= dataSizeInBits &&
fieldIsPointer * (field.getOffset() + 1) <= pointerCount,
"field offset out-of-bounds",
field.getOffset(), dataSizeInBits, pointerCount);
break;
}
case schema::StructNode::Member::Body::UNION_MEMBER: {
auto u = member.getBody().getUnionMember();
VALIDATE_SCHEMA((u.getDiscriminantOffset() + 1) * 16 <= dataSizeInBits,
"Schema invalid: Union discriminant out-of-bounds.");
auto uMembers = u.getMembers();
CAPNPROTO_STACK_ARRAY(bool, uSawCodeOrder, uMembers.size(), 256);
memset(uSawCodeOrder.begin(), 0, uSawCodeOrder.size() * sizeof(uSawCodeOrder[0]));
uint subIndex = 0;
for (auto uMember: uMembers) {
CONTEXT("validating union member", uMember.getName());
VALIDATE_SCHEMA(
uMember.getBody().which() == schema::StructNode::Member::Body::FIELD_MEMBER,
"Union members must be fields.");
validate(uMember, uSawCodeOrder, sawOrdinal, dataSizeInBits, pointerCount,
index + 1, subIndex++);
}
break;
}
}
}
void validate(schema::EnumNode::Reader enumNode) {
auto enumerants = enumNode.getEnumerants();
CAPNPROTO_STACK_ARRAY(bool, sawCodeOrder, enumerants.size(), 256);
memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));
uint index = 0;
for (auto enumerant: enumerants) {
validateMemberName(enumerant.getName(), 0, index++);
VALIDATE_SCHEMA(enumerant.getCodeOrder() < enumerants.size() &&
!sawCodeOrder[enumerant.getCodeOrder()],
"invalid codeOrder", enumerant.getName());
sawCodeOrder[enumerant.getCodeOrder()] = true;
}
}
void validate(schema::InterfaceNode::Reader interfaceNode) {
auto methods = interfaceNode.getMethods();
CAPNPROTO_STACK_ARRAY(bool, sawCodeOrder, methods.size(), 256);
memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));
uint index = 0;
for (auto method: methods) {
CONTEXT("validating method", method.getName());
validateMemberName(method.getName(), 0, index++);
VALIDATE_SCHEMA(method.getCodeOrder() < methods.size() &&
!sawCodeOrder[method.getCodeOrder()],
"invalid codeOrder");
sawCodeOrder[method.getCodeOrder()] = true;
auto params = method.getParams();
for (auto param: params) {
CONTEXT("validating parameter", param.getName());
uint dummy1;
bool dummy2;
validate(param.getType(), param.getDefaultValue(), &dummy1, &dummy2);
}
VALIDATE_SCHEMA(method.getRequiredParamCount() <= params.size(),
"invalid requiredParamCount");
validate(method.getReturnType());
}
}
void validate(schema::ConstNode::Reader constNode) {
uint dummy1;
bool dummy2;
validate(constNode.getType(), constNode.getValue(), &dummy1, &dummy2);
}
void validate(schema::AnnotationNode::Reader annotationNode) {
validate(annotationNode.getType());
}
void validate(schema::Type::Reader type, schema::Value::Reader value,
uint* dataSizeInBits, bool* isPointer) {
validate(type);
schema::Value::Body::Which expectedValueType = schema::Value::Body::VOID_VALUE;
bool hadCase = false;
switch (type.getBody().which()) {
#define HANDLE_TYPE(name, bits, ptr) \
case schema::Type::Body::name##_TYPE: \
expectedValueType = schema::Value::Body::name##_VALUE; \
*dataSizeInBits = bits; *isPointer = ptr; \
hadCase = true; \
break;
HANDLE_TYPE(VOID, 0, false)
HANDLE_TYPE(BOOL, 1, false)
HANDLE_TYPE(INT8, 8, false)
HANDLE_TYPE(INT16, 16, false)
HANDLE_TYPE(INT32, 32, false)
HANDLE_TYPE(INT64, 64, false)
HANDLE_TYPE(UINT8, 8, false)
HANDLE_TYPE(UINT16, 16, false)
HANDLE_TYPE(UINT32, 32, false)
HANDLE_TYPE(UINT64, 64, false)
HANDLE_TYPE(FLOAT32, 32, false)
HANDLE_TYPE(FLOAT64, 64, false)
HANDLE_TYPE(TEXT, 0, true)
HANDLE_TYPE(DATA, 0, true)
HANDLE_TYPE(LIST, 0, true)
HANDLE_TYPE(ENUM, 16, false)
HANDLE_TYPE(STRUCT, 0, true)
HANDLE_TYPE(INTERFACE, 0, true)
HANDLE_TYPE(OBJECT, 0, true)
#undef HANDLE_TYPE
}
if (hadCase) {
VALIDATE_SCHEMA(value.getBody().which() == expectedValueType, "Value did not match type.");
}
}
void validate(schema::Type::Reader type) {
switch (type.getBody().which()) {
case schema::Type::Body::VOID_TYPE:
case schema::Type::Body::BOOL_TYPE:
case schema::Type::Body::INT8_TYPE:
case schema::Type::Body::INT16_TYPE:
case schema::Type::Body::INT32_TYPE:
case schema::Type::Body::INT64_TYPE:
case schema::Type::Body::UINT8_TYPE:
case schema::Type::Body::UINT16_TYPE:
case schema::Type::Body::UINT32_TYPE:
case schema::Type::Body::UINT64_TYPE:
case schema::Type::Body::FLOAT32_TYPE:
case schema::Type::Body::FLOAT64_TYPE:
case schema::Type::Body::TEXT_TYPE:
case schema::Type::Body::DATA_TYPE:
case schema::Type::Body::OBJECT_TYPE:
break;
case schema::Type::Body::STRUCT_TYPE:
validateTypeId(type.getBody().getStructType(), schema::Node::Body::STRUCT_NODE);
break;
case schema::Type::Body::ENUM_TYPE:
validateTypeId(type.getBody().getEnumType(), schema::Node::Body::ENUM_NODE);
break;
case schema::Type::Body::INTERFACE_TYPE:
validateTypeId(type.getBody().getInterfaceType(), schema::Node::Body::INTERFACE_NODE);
break;
case schema::Type::Body::LIST_TYPE:
validate(type.getBody().getListType());
break;
}
// We intentionally allow unknown types.
}
void validateTypeId(uint64_t id, schema::Node::Body::Which expectedKind) {
internal::RawSchema* existing = loader.tryGet(id);
if (existing != nullptr) {
auto node = readMessageUnchecked<schema::Node>(existing->encodedNode);
VALIDATE_SCHEMA(node.getBody().which() == expectedKind,
"expected a different kind of node for this ID",
id, expectedKind, node.getBody().which(), node.getDisplayName());
dependencies.insert(std::make_pair(id, existing));
return;
}
// TODO(cleanup): str() really needs to return something NUL-terminated...
dependencies.insert(std::make_pair(id, loader.loadEmpty(
id, str("(unknown type used by ", nodeName , ")", '\0').begin(), expectedKind)));
}
#undef VALIDATE_SCHEMA
#undef FAIL_VALIDATE_SCHEMA
};
// =======================================================================================
class SchemaLoader::CompatibilityChecker {
public:
CompatibilityChecker(SchemaLoader::Impl& loader): loader(loader) {}
bool shouldReplace(schema::Node::Reader existingNode, schema::Node::Reader replacement,
bool replacementIsNative) {
CONTEXT("checking compatibility with previously-loaded node of the same id",
existingNode.getDisplayName());
DPRECOND(existingNode.getId() == replacement.getId());
nodeName = existingNode.getDisplayName();
compatibility = EQUIVALENT;
checkCompatibility(existingNode, replacement);
// Prefer the newer schema. If neither is newer, prefer native types, otherwise prefer the
// existing type.
return replacementIsNative ? compatibility != OLDER : compatibility == NEWER;
}
private:
SchemaLoader::Impl& loader;
Text::Reader nodeName;
enum Compatibility {
EQUIVALENT,
OLDER,
NEWER,
INCOMPATIBLE
};
Compatibility compatibility;
#define VALIDATE_SCHEMA(condition, ...) \
VALIDATE_INPUT(condition, ##__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
#define FAIL_VALIDATE_SCHEMA(...) \
FAIL_VALIDATE_INPUT(__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
void replacementIsNewer() {
switch (compatibility) {
case EQUIVALENT:
compatibility = NEWER;
break;
case OLDER:
FAIL_VALIDATE_SCHEMA("Schema node contains some changes that are upgrades and some "
"that are downgrades. All changes must be in the same direction for compatibility.");
break;
case NEWER:
break;
case INCOMPATIBLE:
break;
}
}
void replacementIsOlder() {
switch (compatibility) {
case EQUIVALENT:
compatibility = OLDER;
break;
case OLDER:
break;
case NEWER:
FAIL_VALIDATE_SCHEMA("Schema node contains some changes that are upgrades and some "
"that are downgrades. All changes must be in the same direction for compatibility.");
break;
case INCOMPATIBLE:
break;
}
}
void checkCompatibility(schema::Node::Reader node, schema::Node::Reader replacement) {
// Returns whether `replacement` is equivalent, older than, newer than, or incompatible with
// `node`. If exceptions are enabled, this will throw an exception on INCOMPATIBLE.
VALIDATE_SCHEMA(node.getBody().which() == replacement.getBody().which(),
"kind of declaration changed");
// No need to check compatibility of the non-body parts of the node:
// - Arbitrary renaming and moving between scopes is allowed.
// - Annotations are ignored for compatibility purposes.
switch (node.getBody().which()) {
case schema::Node::Body::FILE_NODE:
checkCompatibility(node.getBody().getFileNode(),
replacement.getBody().getFileNode());
break;
case schema::Node::Body::STRUCT_NODE:
checkCompatibility(node.getBody().getStructNode(),
replacement.getBody().getStructNode());
break;
case schema::Node::Body::ENUM_NODE:
checkCompatibility(node.getBody().getEnumNode(),
replacement.getBody().getEnumNode());
break;
case schema::Node::Body::INTERFACE_NODE:
checkCompatibility(node.getBody().getInterfaceNode(),
replacement.getBody().getInterfaceNode());
break;
case schema::Node::Body::CONST_NODE:
checkCompatibility(node.getBody().getConstNode(),
replacement.getBody().getConstNode());
break;
case schema::Node::Body::ANNOTATION_NODE:
checkCompatibility(node.getBody().getAnnotationNode(),
replacement.getBody().getAnnotationNode());
break;
}
}
void checkCompatibility(schema::FileNode::Reader file, schema::FileNode::Reader replacement) {
// Nothing to compare.
}
void checkCompatibility(schema::StructNode::Reader structNode,
schema::StructNode::Reader replacement) {
if (replacement.getDataSectionWordSize() > structNode.getDataSectionWordSize()) {
replacementIsNewer();
} else if (replacement.getDataSectionWordSize() < structNode.getDataSectionWordSize()) {
replacementIsOlder();
}
if (replacement.getPointerSectionSize() > structNode.getPointerSectionSize()) {
replacementIsNewer();
} else if (replacement.getPointerSectionSize() < structNode.getPointerSectionSize()) {
replacementIsOlder();
}
// We can do a simple comparison of preferredListEncoding here because the only case where it
// isn't correct to compare this way is when one side is BIT/BYTE/*_BYTES while the other side
// is POINTER, and if that were the case then the above comparisons would already have failed
// or one of the nodes would have failed validation.
if (replacement.getPreferredListEncoding() > structNode.getPreferredListEncoding()) {
replacementIsNewer();
} else if (replacement.getPreferredListEncoding() < structNode.getPreferredListEncoding()) {
replacementIsOlder();
}
// The shared members should occupy corresponding positions in the member lists, since the
// lists are sorted by ordinal.
auto members = structNode.getMembers();
auto replacementMembers = replacement.getMembers();
uint count = std::min(members.size(), replacementMembers.size());
if (replacementMembers.size() > members.size()) {
replacementIsNewer();
} else if (replacementMembers.size() < members.size()) {
replacementIsOlder();
}
for (uint i = 0; i < count; i++) {
checkCompatibility(members[i], replacementMembers[i]);
}
}
void checkCompatibility(schema::StructNode::Member::Reader member,
schema::StructNode::Member::Reader replacement) {
CONTEXT("comparing struct member", member.getName());
switch (member.getBody().which()) {
case schema::StructNode::Member::Body::FIELD_MEMBER: {
auto field = member.getBody().getFieldMember();
auto replacementField = replacement.getBody().getFieldMember();
checkCompatibility(field.getType(), replacementField.getType(),
NO_UPGRADE_TO_STRUCT);
checkDefaultCompatibility(field.getDefaultValue(), replacementField.getDefaultValue());
VALIDATE_SCHEMA(field.getOffset() == replacementField.getOffset(),
"field position changed");
break;
}
case schema::StructNode::Member::Body::UNION_MEMBER: {
auto existingUnion = member.getBody().getUnionMember();
auto replacementUnion = replacement.getBody().getUnionMember();
VALIDATE_SCHEMA(
existingUnion.getDiscriminantOffset() == replacementUnion.getDiscriminantOffset(),
"union discriminant position changed");
auto members = existingUnion.getMembers();
auto replacementMembers = replacementUnion.getMembers();
uint count = std::min(members.size(), replacementMembers.size());
if (replacementMembers.size() > members.size()) {
replacementIsNewer();
} else if (replacementMembers.size() < members.size()) {
replacementIsOlder();
}
for (uint i = 0; i < count; i++) {
checkCompatibility(members[i], replacementMembers[i]);
}
break;
}
}
}
void checkCompatibility(schema::EnumNode::Reader enumNode,
schema::EnumNode::Reader replacement) {
uint size = enumNode.getEnumerants().size();
uint replacementSize = replacement.getEnumerants().size();
if (replacementSize > size) {
replacementIsNewer();
} else if (replacementSize < size) {
replacementIsOlder();
}
}
void checkCompatibility(schema::InterfaceNode::Reader interfaceNode,
schema::InterfaceNode::Reader replacement) {
auto methods = interfaceNode.getMethods();
auto replacementMethods = replacement.getMethods();
if (replacementMethods.size() > methods.size()) {
replacementIsNewer();
} else if (replacementMethods.size() < methods.size()) {
replacementIsOlder();
}
uint count = std::min(methods.size(), replacementMethods.size());
for (uint i = 0; i < count; i++) {
checkCompatibility(methods[i], replacementMethods[i]);
}
}
void checkCompatibility(schema::InterfaceNode::Method::Reader method,
schema::InterfaceNode::Method::Reader replacement) {
CONTEXT("comparing method", method.getName());
auto params = method.getParams();
auto replacementParams = replacement.getParams();
if (replacementParams.size() > params.size()) {
replacementIsNewer();
} else if (replacementParams.size() < params.size()) {
replacementIsOlder();
}
uint count = std::min(params.size(), replacementParams.size());
for (uint i = 0; i < count; i++) {
auto param = params[i];
auto replacementParam = replacementParams[i];
CONTEXT("comparing parameter", param.getName());
checkCompatibility(param.getType(), replacementParam.getType(),
NO_UPGRADE_TO_STRUCT);
checkDefaultCompatibility(param.getDefaultValue(), replacementParam.getDefaultValue());
}
// Before checking that the required parameter counts are equal, check if the user added new
// parameters without defaulting them, as this is the most common reason for this error and we
// can provide a nicer error message.
VALIDATE_SCHEMA(replacement.getRequiredParamCount() <= count &&
method.getRequiredParamCount() <= count,
"Updated method signature contains additional parameters that lack default values");
VALIDATE_SCHEMA(replacement.getRequiredParamCount() == method.getRequiredParamCount(),
"Updated method signature has different number of required parameters (parameters without "
"default values)");
checkCompatibility(method.getReturnType(), replacement.getReturnType(),
ALLOW_UPGRADE_TO_STRUCT);
}
void checkCompatibility(schema::ConstNode::Reader constNode,
schema::ConstNode::Reader replacement) {
// Who cares? These don't appear on the wire.
}
void checkCompatibility(schema::AnnotationNode::Reader annotationNode,
schema::AnnotationNode::Reader replacement) {
// Who cares? These don't appear on the wire.
}
enum UpgradeToStructMode {
ALLOW_UPGRADE_TO_STRUCT,
NO_UPGRADE_TO_STRUCT
};
void checkCompatibility(schema::Type::Reader type,
schema::Type::Reader replacement,
UpgradeToStructMode upgradeToStructMode) {
if (replacement.getBody().which() != type.getBody().which()) {
// Check for allowed "upgrade" to Data or Object.
if (replacement.getBody().which() == schema::Type::Body::DATA_TYPE &&
canUpgradeToData(type)) {
replacementIsNewer();
return;
} else if (type.getBody().which() == schema::Type::Body::DATA_TYPE &&
canUpgradeToData(replacement)) {
replacementIsOlder();
return;
} else if (replacement.getBody().which() == schema::Type::Body::OBJECT_TYPE &&
canUpgradeToObject(type)) {
replacementIsNewer();
return;
} else if (type.getBody().which() == schema::Type::Body::OBJECT_TYPE &&
canUpgradeToObject(replacement)) {
replacementIsOlder();
return;
}
if (upgradeToStructMode == ALLOW_UPGRADE_TO_STRUCT) {
if (type.getBody().which() == schema::Type::Body::STRUCT_TYPE) {
checkUpgradeToStruct(replacement, type.getBody().getStructType());
return;
} else if (replacement.getBody().which() == schema::Type::Body::STRUCT_TYPE) {
checkUpgradeToStruct(type, replacement.getBody().getStructType());
return;
}
}
FAIL_VALIDATE_SCHEMA("a type was changed");
}
switch (type.getBody().which()) {
case schema::Type::Body::VOID_TYPE:
case schema::Type::Body::BOOL_TYPE:
case schema::Type::Body::INT8_TYPE:
case schema::Type::Body::INT16_TYPE:
case schema::Type::Body::INT32_TYPE:
case schema::Type::Body::INT64_TYPE:
case schema::Type::Body::UINT8_TYPE:
case schema::Type::Body::UINT16_TYPE:
case schema::Type::Body::UINT32_TYPE:
case schema::Type::Body::UINT64_TYPE:
case schema::Type::Body::FLOAT32_TYPE:
case schema::Type::Body::FLOAT64_TYPE:
case schema::Type::Body::TEXT_TYPE:
case schema::Type::Body::DATA_TYPE:
case schema::Type::Body::OBJECT_TYPE:
return;
case schema::Type::Body::LIST_TYPE:
return checkCompatibility(type.getBody().getListType(),
replacement.getBody().getListType(),
ALLOW_UPGRADE_TO_STRUCT);
case schema::Type::Body::ENUM_TYPE:
VALIDATE_SCHEMA(replacement.getBody().getEnumType() == type.getBody().getEnumType(),
"type changed enum type");
return;
case schema::Type::Body::STRUCT_TYPE:
// TODO(someday): If the IDs don't match, we should compare the two structs for
// compatibility. This is tricky, though, because the new type's target may not yet be
// loaded. In that case we could take the old type, make a copy of it, assign the new
// ID to the copy, and load() that. That forces any struct type loaded for that ID to
// be compatible. However, that has another problem, which is that it could be that the
// whole reason the type was replaced was to fork that type, and so an incompatibility
// could be very much expected. This could be a rat hole...
VALIDATE_SCHEMA(replacement.getBody().getStructType() == type.getBody().getStructType(),
"type changed to incompatible struct type");
return;
case schema::Type::Body::INTERFACE_TYPE:
VALIDATE_SCHEMA(
replacement.getBody().getInterfaceType() == type.getBody().getInterfaceType(),
"type changed to incompatible interface type");
return;
}
// We assume unknown types (from newer versions of Cap'n Proto?) are equivalent.
}
void checkUpgradeToStruct(schema::Type::Reader type, uint64_t structTypeId) {
// We can't just look up the target struct and check it because it may not have been loaded
// yet. Instead, we contrive a struct that looks like what we want and load() that, which
// guarantees that any incompatibility will be caught either now or when the real version of
// that struct is loaded.
word scratch[32];
memset(scratch, 0, sizeof(scratch));
MallocMessageBuilder builder(arrayPtr(scratch, sizeof(scratch)));
auto node = builder.initRoot<schema::Node>();
node.setId(structTypeId);
// TODO(cleanup): str() really needs to return something NUL-terminated...
node.setDisplayName(str("(unknown type used in ", nodeName, ")", '\0').begin());
auto structNode = node.getBody().initStructNode();
switch (type.getBody().which()) {
case schema::Type::Body::VOID_TYPE:
structNode.setDataSectionWordSize(0);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::EMPTY);
break;
case schema::Type::Body::BOOL_TYPE:
structNode.setDataSectionWordSize(1);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::BIT);
break;
case schema::Type::Body::INT8_TYPE:
case schema::Type::Body::UINT8_TYPE:
structNode.setDataSectionWordSize(1);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::BYTE);
break;
case schema::Type::Body::INT16_TYPE:
case schema::Type::Body::UINT16_TYPE:
case schema::Type::Body::ENUM_TYPE:
structNode.setDataSectionWordSize(1);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::TWO_BYTES);
break;
case schema::Type::Body::INT32_TYPE:
case schema::Type::Body::UINT32_TYPE:
case schema::Type::Body::FLOAT32_TYPE:
structNode.setDataSectionWordSize(1);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::FOUR_BYTES);
break;
case schema::Type::Body::INT64_TYPE:
case schema::Type::Body::UINT64_TYPE:
case schema::Type::Body::FLOAT64_TYPE:
structNode.setDataSectionWordSize(1);
structNode.setPointerSectionSize(0);
structNode.setPreferredListEncoding(schema::ElementSize::EIGHT_BYTES);
break;
case schema::Type::Body::TEXT_TYPE:
case schema::Type::Body::DATA_TYPE:
case schema::Type::Body::LIST_TYPE:
case schema::Type::Body::STRUCT_TYPE:
case schema::Type::Body::INTERFACE_TYPE:
case schema::Type::Body::OBJECT_TYPE:
structNode.setDataSectionWordSize(0);
structNode.setPointerSectionSize(1);
structNode.setPreferredListEncoding(schema::ElementSize::POINTER);
break;
}
auto member = structNode.initMembers(1)[0];
member.setName("member0");
member.setOrdinal(0);
member.setCodeOrder(0);
member.getBody().initFieldMember().setType(type);
loader.load(node);
}
bool canUpgradeToData(schema::Type::Reader type) {
if (type.getBody().which() == schema::Type::Body::TEXT_TYPE) {
return true;
} else if (type.getBody().which() == schema::Type::Body::LIST_TYPE) {
switch (type.getBody().getListType().getBody().which()) {
case schema::Type::Body::INT8_TYPE:
case schema::Type::Body::UINT8_TYPE:
return true;
default:
return false;
}
} else {
return false;
}
}
bool canUpgradeToObject(schema::Type::Reader type) {
switch (type.getBody().which()) {
case schema::Type::Body::VOID_TYPE:
case schema::Type::Body::BOOL_TYPE:
case schema::Type::Body::INT8_TYPE:
case schema::Type::Body::INT16_TYPE:
case schema::Type::Body::INT32_TYPE:
case schema::Type::Body::INT64_TYPE:
case schema::Type::Body::UINT8_TYPE:
case schema::Type::Body::UINT16_TYPE:
case schema::Type::Body::UINT32_TYPE:
case schema::Type::Body::UINT64_TYPE:
case schema::Type::Body::FLOAT32_TYPE:
case schema::Type::Body::FLOAT64_TYPE:
case schema::Type::Body::ENUM_TYPE:
return false;
case schema::Type::Body::TEXT_TYPE:
case schema::Type::Body::DATA_TYPE:
case schema::Type::Body::LIST_TYPE:
case schema::Type::Body::STRUCT_TYPE:
case schema::Type::Body::INTERFACE_TYPE:
case schema::Type::Body::OBJECT_TYPE:
return true;
}
// Be lenient with unknown types.
return true;
}
void checkDefaultCompatibility(schema::Value::Reader value,
schema::Value::Reader replacement) {
// Note that we test default compatibility only after testing type compatibility, and default
// values have already been validated as matching their types, so this should pass.
RECOVERABLE_CHECK(value.getBody().which() == replacement.getBody().which()) {
compatibility = INCOMPATIBLE;
return;
}
switch (value.getBody().which()) {
#define HANDLE_TYPE(discrim, name) \
case schema::Value::Body::discrim##_VALUE: \
VALIDATE_SCHEMA(value.getBody().get##name##Value() == \
replacement.getBody().get##name##Value(), \
"default value changed"); \
break;
HANDLE_TYPE(VOID, Void);
HANDLE_TYPE(BOOL, Bool);
HANDLE_TYPE(INT8, Int8);
HANDLE_TYPE(INT16, Int16);
HANDLE_TYPE(INT32, Int32);
HANDLE_TYPE(INT64, Int64);
HANDLE_TYPE(UINT8, Uint8);
HANDLE_TYPE(UINT16, Uint16);
HANDLE_TYPE(UINT32, Uint32);
HANDLE_TYPE(UINT64, Uint64);
HANDLE_TYPE(FLOAT32, Float32);
HANDLE_TYPE(FLOAT64, Float64);
HANDLE_TYPE(ENUM, Enum);
#undef HANDLE_TYPE
case schema::Value::Body::TEXT_VALUE:
case schema::Value::Body::DATA_VALUE:
case schema::Value::Body::LIST_VALUE:
case schema::Value::Body::STRUCT_VALUE:
case schema::Value::Body::INTERFACE_VALUE:
case schema::Value::Body::OBJECT_VALUE:
// It's not a big deal if default values for pointers change, and it would be difficult for
// us to compare these defaults here, so just let it slide.
break;
}
}
};
// =======================================================================================
SchemaLoader::Impl::Impl()
: arena(allocator.arena()),
segment(allocator.getRootSegment()) {}
internal::RawSchema* SchemaLoader::Impl::load(schema::Node::Reader reader) {
// Make a copy of the node which can be used unchecked.
size_t size = reader.totalSizeInWords() + 1;
word* validated = allocate<word>(size);
copyToUnchecked(reader, arrayPtr(validated, size));
// Validate the copy.
Validator validator(*this);
auto validatedReader = readMessageUnchecked<schema::Node>(validated);
if (!validator.validate(validatedReader)) {
// Not valid. Construct an empty schema of the same type and return that.
return loadEmpty(validatedReader.getId(),
validatedReader.getDisplayName(),
validatedReader.getBody().which());
}
// Check if we already have a schema for this ID.
internal::RawSchema*& slot = schemas[validatedReader.getId()];
if (slot == nullptr) {
// Nope, allocate a new RawSchema.
slot = allocate<internal::RawSchema>();
} else {
// Yes, check if it is compatible and figure out which schema is newer.
auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
CompatibilityChecker checker(*this);
if (!checker.shouldReplace(existing, validatedReader, false)) {
// The new schema does not appear to be any newer than the existing one, so keep the existing.
return slot;
}
}
// Initialize the RawSchema.
slot->encodedNode = validated;
slot->dependencies = validator.makeDependencyArray(&slot->dependencyCount);
slot->membersByName = validator.makeMemberInfoArray(&slot->memberCount);
return slot;
}
internal::RawSchema* SchemaLoader::Impl::loadNative(const internal::RawSchema* nativeSchema) {
auto reader = readMessageUnchecked<schema::Node>(nativeSchema->encodedNode);
internal::RawSchema*& slot = schemas[reader.getId()];
if (slot == nullptr) {
slot = allocate<internal::RawSchema>();
} else if (slot->canCastTo != nullptr) {
PRECOND(slot->canCastTo == nativeSchema,
"two different compiled-in type have the same type ID",
reader.getId(), reader.getDisplayName(),
readMessageUnchecked<schema::Node>(slot->canCastTo->encodedNode).getDisplayName());
// Already loaded.
return slot;
} else {
auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
auto native = readMessageUnchecked<schema::Node>(nativeSchema->encodedNode);
CompatibilityChecker checker(*this);
if (!checker.shouldReplace(existing, native, true)) {
// The existing schema is newer, so just make sure the dependencies are loaded.
slot->canCastTo = nativeSchema;
for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
loadNative(nativeSchema->dependencies[i]);
}
return slot;
}
}
// Set the slot to a copy of the native schema.
*slot = *nativeSchema;
// Indicate that casting is safe.
slot->canCastTo = nativeSchema;
// Except that we need to set the dependency list to point at other loader-owned RawSchemas.
const internal::RawSchema** dependencies =
allocate<const internal::RawSchema*>(slot->dependencyCount);
for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
dependencies[i] = loadNative(nativeSchema->dependencies[i]);
}
slot->dependencies = dependencies;
return slot;
}
internal::RawSchema* SchemaLoader::Impl::loadEmpty(
uint64_t id, Text::Reader name, schema::Node::Body::Which kind) {
word scratch[32];
memset(scratch, 0, sizeof(scratch));
MallocMessageBuilder builder(arrayPtr(scratch, sizeof(scratch)));
auto node = builder.initRoot<schema::Node>();
node.setId(id);
node.setDisplayName(name);
switch (kind) {
case schema::Node::Body::STRUCT_NODE: node.getBody().initStructNode(); break;
case schema::Node::Body::ENUM_NODE: node.getBody().initEnumNode(); break;
case schema::Node::Body::INTERFACE_NODE: node.getBody().initInterfaceNode(); break;
case schema::Node::Body::FILE_NODE:
case schema::Node::Body::CONST_NODE:
case schema::Node::Body::ANNOTATION_NODE:
FAIL_PRECOND("Not a type.");
break;
}
return load(node);
}
internal::RawSchema* SchemaLoader::Impl::tryGet(uint64_t typeId) const {
auto iter = schemas.find(typeId);
if (iter == schemas.end()) {
return nullptr;
} else {
return iter->second;
}
}
Array<Schema> SchemaLoader::Impl::getAllLoaded() const {
Array<Schema> result = newArray<Schema>(schemas.size());
size_t i = 0;
for (auto& schema: schemas) {
result[i++] = Schema(schema.second);
}
return result;
}
// =======================================================================================
SchemaLoader::SchemaLoader(): impl(heap<Impl>()) {}
SchemaLoader::~SchemaLoader() {}
Schema SchemaLoader::get(uint64_t id) const {
internal::RawSchema* raw = impl->tryGet(id);
PRECOND(raw != nullptr, "no schema node loaded for id", id);
return Schema(raw);
}
Maybe<Schema> SchemaLoader::tryGet(uint64_t id) const {
internal::RawSchema* raw = impl->tryGet(id);
if (raw == nullptr) {
return nullptr;
} else {
return Schema(raw);
}
}
Schema SchemaLoader::load(schema::Node::Reader reader) {
return Schema(impl->load(reader));
}
Array<Schema> SchemaLoader::getAllLoaded() const {
return impl->getAllLoaded();
}
void SchemaLoader::loadNative(const internal::RawSchema* nativeSchema) {
impl->loadNative(nativeSchema);
}
} // namespace capnproto
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNPROTO_SCHEMA_LOADER_H_
#define CAPNPROTO_SCHEMA_LOADER_H_
#include "schema.h"
namespace capnproto {
class SchemaLoader {
public:
SchemaLoader();
~SchemaLoader();
CAPNPROTO_DISALLOW_COPY(SchemaLoader);
Schema get(uint64_t id) const;
// Gets the schema for the given ID, throwing an exception if it isn't present.
//
// The returned schema may be invalidated if load() is called with a new schema for the same ID.
// In general, you should not call load() while a schema from this loader is in-use.
Maybe<Schema> tryGet(uint64_t id) const;
// Like get() but doesn't throw.
Schema load(schema::Node::Reader reader);
// Loads the given schema node. Validates the node and throws an exception if invalid. This
// makes a copy of the schema, so the object passed in can be destroyed after this returns.
//
// If the node has any dependencies which are not already loaded, they will be initialized as
// stubs -- empty schemas of whichever kind is expected.
//
// If another schema for the given reader has already been seen, the loader will inspect both
// schemas to determine which one is newer, and use that that one. If the two versions are
// found to be incompatible, an exception is thrown. If the two versions differ but are
// compatible and the loader cannot determine which is newer (e.g., the only changes are renames),
// the existing schema will be preferred. Note that in any case, the loader will end up keeping
// around copies of both schemas, so you shouldn't repeatedly reload schemas into the same loader.
//
// The following properties of the schema node are validated:
// - Struct size and preferred list encoding are valid and consistent.
// - Struct members are fields or unions.
// - Union members are fields.
// - Field offsets are in-bounds.
// - Ordinals and codeOrders are sequential starting from zero.
// - Values are of the right union case to match their types.
//
// You should assume anything not listed above is NOT validated. In particular, things that are
// not validated now, but could be in the future, include but are not limited to:
// - Names.
// - Annotation values. (This is hard because the annotation declaration is not always
// available.)
// - Content of default/constant values of pointer type. (Validating these would require knowing
// their schema, but even if the schemas are available at validation time, they could be
// updated by a subsequent load(), invalidating existing values. Instead, these values are
// validated at the time they are used, as usual for Cap'n Proto objects.)
//
// Also note that unknown types are not considered invalid. Instead, the dynamic API returns
// a DynamicValue with type UNKNOWN for these.
template <typename T>
void loadCompiledTypeAndDependencies();
// Load the schema for the given compiled-in type and all of its dependencies.
//
// If you want to be able to cast a DynamicValue built from this SchemaLoader to the compiled-in
// type using as<T>(), you must call this method before constructing the DynamicValue. Otherwise,
// as<T>() will throw an exception complaining about type mismatch.
Array<Schema> getAllLoaded() const;
// Get a complete list of all loaded schema nodes. It is particularly useful to call this after
// loadCompiledTypeAndDependencies<T>() in order to get a flat list of all of T's transitive
// dependencies.
private:
class Validator;
class CompatibilityChecker;
class Impl;
Own<Impl> impl;
void loadNative(const internal::RawSchema* nativeSchema);
};
template <typename T>
inline void SchemaLoader::loadCompiledTypeAndDependencies() {
loadNative(&internal::rawSchema<T>());
}
} // namespace capnproto
#endif // CAPNPROTO_SCHEMA_LOADER_H_
......@@ -76,6 +76,12 @@ InterfaceSchema Schema::asInterface() const {
return InterfaceSchema(raw);
}
void Schema::requireUsableAs(const internal::RawSchema* expected) {
PRECOND(raw == expected ||
(raw != nullptr && expected != nullptr && raw->canCastTo == expected),
"This schema is not compatible with the requested native type.");
}
// =======================================================================================
namespace {
......@@ -285,4 +291,10 @@ ListSchema ListSchema::getListElementType() const {
return ListSchema(elementType, nestingDepth - 1, elementSchema);
}
void ListSchema::requireUsableAs(ListSchema expected) {
PRECOND(elementType == expected.elementType && nestingDepth == expected.nestingDepth,
"This schema is not compatible with the requested native type.");
elementSchema.requireUsableAs(expected.elementSchema.raw);
}
} // namespace capnproto
......@@ -75,6 +75,14 @@ public:
// you want to check if two Schemas represent the same type (but possibly different versions of
// it), compare their IDs instead.
template <typename T>
void requireUsableAs();
// Throws an exception if a value with this Schema cannot safely be cast to a native value of
// the given type. This passes if either:
// - *this == from<T>()
// - This schema was loaded with SchemaLoader, the type ID matches typeId<T>(), and
// loadCompiledTypeAndDependencies<T>() was called on the SchemaLoader.
private:
const internal::RawSchema* raw;
......@@ -84,9 +92,13 @@ private:
return Schema(&internal::rawSchema<T>());
}
void requireUsableAs(const internal::RawSchema* expected);
friend class StructSchema;
friend class EnumSchema;
friend class InterfaceSchema;
friend class ListSchema;
friend class SchemaLoader;
};
// -------------------------------------------------------------------
......@@ -356,6 +368,9 @@ public:
inline bool operator==(const ListSchema& other) const;
inline bool operator!=(const ListSchema& other) const { return !(*this == other); }
template <typename T>
void requireUsableAs();
private:
schema::Type::Body::Which elementType;
uint8_t nestingDepth; // 0 for T, 1 for List(T), 2 for List(List(T)), ...
......@@ -375,6 +390,8 @@ private:
return FromImpl<T>::get();
}
void requireUsableAs(ListSchema expected);
friend class Schema;
};
......@@ -396,6 +413,11 @@ template <> inline schema::Type::Body::Which Schema::from<double>() { return sch
template <> inline schema::Type::Body::Which Schema::from<Text>() { return schema::Type::Body::TEXT_TYPE; }
template <> inline schema::Type::Body::Which Schema::from<Data>() { return schema::Type::Body::DATA_TYPE; }
template <typename T>
inline void Schema::requireUsableAs() {
requireUsableAs(&internal::rawSchema<T>());
}
inline bool StructSchema::Member::operator==(const Member& other) const {
return parent == other.parent && unionIndex == other.unionIndex && index == other.index;
}
......@@ -429,6 +451,13 @@ inline bool ListSchema::operator==(const ListSchema& other) const {
elementSchema == other.elementSchema;
}
template <typename T>
inline void ListSchema::requireUsableAs() {
static_assert(kind<T>() == Kind::LIST,
"ListSchema::requireUsableAs<T>() requires T is a list type.");
requireUsableAs(Schema::from<T>());
}
template <typename T>
struct ListSchema::FromImpl<List<T>> {
static inline ListSchema get() { return of(Schema::from<T>()); }
......
......@@ -122,6 +122,98 @@ void genericInitTestMessage(Builder builder) {
builder.setEnumList({TestEnum::FOO, TestEnum::GARPLY});
}
void dynamicInitTestMessage(DynamicStruct::Builder builder) {
builder.set("voidField", Void::VOID);
builder.set("boolField", true);
builder.set("int8Field", -123);
builder.set("int16Field", -12345);
builder.set("int32Field", -12345678);
builder.set("int64Field", -123456789012345ll);
builder.set("uInt8Field", 234u);
builder.set("uInt16Field", 45678u);
builder.set("uInt32Field", 3456789012u);
builder.set("uInt64Field", 12345678901234567890ull);
builder.set("float32Field", 1234.5);
builder.set("float64Field", -123e45);
builder.set("textField", "foo");
builder.set("dataField", "bar");
{
auto subBuilder = builder.init("structField").as<DynamicStruct>();
subBuilder.set("voidField", Void::VOID);
subBuilder.set("boolField", true);
subBuilder.set("int8Field", -12);
subBuilder.set("int16Field", 3456);
subBuilder.set("int32Field", -78901234);
subBuilder.set("int64Field", 56789012345678ll);
subBuilder.set("uInt8Field", 90u);
subBuilder.set("uInt16Field", 1234u);
subBuilder.set("uInt32Field", 56789012u);
subBuilder.set("uInt64Field", 345678901234567890ull);
subBuilder.set("float32Field", -1.25e-10);
subBuilder.set("float64Field", 345);
subBuilder.set("textField", "baz");
subBuilder.set("dataField", "qux");
{
auto subSubBuilder = subBuilder.init("structField").as<DynamicStruct>();
subSubBuilder.set("textField", "nested");
subSubBuilder.init("structField").as<DynamicStruct>().set("textField", "really nested");
}
subBuilder.set("enumField", "baz");
subBuilder.set("voidList", {Void::VOID, Void::VOID, Void::VOID});
subBuilder.set("boolList", {false, true, false, true, true});
subBuilder.set("int8List", {12, -34, -0x80, 0x7f});
subBuilder.set("int16List", {1234, -5678, -0x8000, 0x7fff});
// gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1.
subBuilder.set("int32List", {12345678, -90123456, -0x7fffffff - 1, 0x7fffffff});
subBuilder.set("int64List", {123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll-1, 0x7fffffffffffffffll});
subBuilder.set("uInt8List", {12u, 34u, 0u, 0xffu});
subBuilder.set("uInt16List", {1234u, 5678u, 0u, 0xffffu});
subBuilder.set("uInt32List", {12345678u, 90123456u, 0u, 0xffffffffu});
subBuilder.set("uInt64List", {123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull});
subBuilder.set("float32List", {0, 1234567, 1e37, -1e37, 1e-37, -1e-37});
subBuilder.set("float64List", {0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306});
subBuilder.set("textList", {"quux", "corge", "grault"});
subBuilder.set("dataList", {"garply", "waldo", "fred"});
{
auto listBuilder = subBuilder.init("structList", 3).as<DynamicList>();
listBuilder[0].as<DynamicStruct>().set("textField", "x structlist 1");
listBuilder[1].as<DynamicStruct>().set("textField", "x structlist 2");
listBuilder[2].as<DynamicStruct>().set("textField", "x structlist 3");
}
subBuilder.set("enumList", {"qux", "bar", "grault"});
}
builder.set("enumField", "corge");
builder.init("voidList", 6);
builder.set("boolList", {true, false, false, true});
builder.set("int8List", {111, -111});
builder.set("int16List", {11111, -11111});
builder.set("int32List", {111111111, -111111111});
builder.set("int64List", {1111111111111111111ll, -1111111111111111111ll});
builder.set("uInt8List", {111u, 222u});
builder.set("uInt16List", {33333u, 44444u});
builder.set("uInt32List", {3333333333u});
builder.set("uInt64List", {11111111111111111111ull});
builder.set("float32List", {5555.5,
std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::quiet_NaN()});
builder.set("float64List", {7777.75,
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()});
builder.set("textList", {"plugh", "xyzzy", "thud"});
builder.set("dataList", {"oops", "exhausted", "rfc3092"});
{
auto listBuilder = builder.init("structList", 3).as<DynamicList>();
listBuilder[0].as<DynamicStruct>().set("textField", "structlist 1");
listBuilder[1].as<DynamicStruct>().set("textField", "structlist 2");
listBuilder[2].as<DynamicStruct>().set("textField", "structlist 3");
}
builder.set("enumList", {"foo", "garply"});
}
template <typename T, typename U>
void checkList(T reader, std::initializer_list<U> expected) {
ASSERT_EQ(expected.size(), reader.size());
......@@ -252,6 +344,148 @@ void genericCheckTestMessage(Reader reader) {
checkList(reader.getEnumList(), {TestEnum::FOO, TestEnum::GARPLY});
}
// Hack because as<>() is a template-parameter-dependent lookup everywhere below...
#define as template as
template <typename T> void expectPrimitiveEq(T a, T b) { EXPECT_EQ(a, b); }
void expectPrimitiveEq(float a, float b) { EXPECT_FLOAT_EQ(a, b); }
void expectPrimitiveEq(double a, double b) { EXPECT_DOUBLE_EQ(a, b); }
void expectPrimitiveEq(Text::Reader a, Text::Builder b) { EXPECT_EQ(a, b); }
void expectPrimitiveEq(Data::Reader a, Data::Builder b) { EXPECT_EQ(a, b); }
template <typename Element, typename T>
void checkList(T reader, std::initializer_list<ReaderFor<Element>> expected) {
auto list = reader.as<DynamicList>();
ASSERT_EQ(expected.size(), list.size());
for (uint i = 0; i < expected.size(); i++) {
expectPrimitiveEq(expected.begin()[i], list[i].as<Element>());
}
auto typed = reader.as<List<Element>>();
ASSERT_EQ(expected.size(), typed.size());
for (uint i = 0; i < expected.size(); i++) {
expectPrimitiveEq(expected.begin()[i], typed[i]);
}
}
template <typename T>
void checkEnumList(T reader, std::initializer_list<const char*> expected) {
auto list = reader.as<DynamicList>();
ASSERT_EQ(expected.size(), list.size());
for (uint i = 0; i < expected.size(); i++) {
EXPECT_EQ(expected.begin()[i],
list[i].as<DynamicEnum>().getEnumerant()->getProto().getName());
}
}
template <typename Reader>
void dynamicCheckTestMessage(Reader reader) {
EXPECT_EQ(Void::VOID, reader.get("voidField").as<Void>());
EXPECT_EQ(true, reader.get("boolField").as<bool>());
EXPECT_EQ(-123, reader.get("int8Field").as<int8_t>());
EXPECT_EQ(-12345, reader.get("int16Field").as<int16_t>());
EXPECT_EQ(-12345678, reader.get("int32Field").as<int32_t>());
EXPECT_EQ(-123456789012345ll, reader.get("int64Field").as<int64_t>());
EXPECT_EQ(234u, reader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(45678u, reader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(3456789012u, reader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(12345678901234567890ull, reader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(1234.5f, reader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(-123e45, reader.get("float64Field").as<double>());
EXPECT_EQ("foo", reader.get("textField").as<Text>());
EXPECT_EQ("bar", reader.get("dataField").as<Data>());
{
auto subReader = reader.get("structField").as<DynamicStruct>();
EXPECT_EQ(Void::VOID, subReader.get("voidField").as<Void>());
EXPECT_EQ(true, subReader.get("boolField").as<bool>());
EXPECT_EQ(-12, subReader.get("int8Field").as<int8_t>());
EXPECT_EQ(3456, subReader.get("int16Field").as<int16_t>());
EXPECT_EQ(-78901234, subReader.get("int32Field").as<int32_t>());
EXPECT_EQ(56789012345678ll, subReader.get("int64Field").as<int64_t>());
EXPECT_EQ(90u, subReader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(1234u, subReader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(56789012u, subReader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(345678901234567890ull, subReader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(-1.25e-10f, subReader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(345, subReader.get("float64Field").as<double>());
EXPECT_EQ("baz", subReader.get("textField").as<Text>());
EXPECT_EQ("qux", subReader.get("dataField").as<Data>());
{
auto subSubReader = subReader.get("structField").as<DynamicStruct>();
EXPECT_EQ("nested", subSubReader.get("textField").as<Text>());
EXPECT_EQ("really nested", subSubReader.get("structField").as<DynamicStruct>()
.get("textField").as<Text>());
}
EXPECT_EQ("baz",
subReader.get("enumField").as<DynamicEnum>().getEnumerant()->getProto().getName());
checkList<Void>(subReader.get("voidList"), {Void::VOID, Void::VOID, Void::VOID});
checkList<bool>(subReader.get("boolList"), {false, true, false, true, true});
checkList<int8_t>(subReader.get("int8List"), {12, -34, -0x80, 0x7f});
checkList<int16_t>(subReader.get("int16List"), {1234, -5678, -0x8000, 0x7fff});
// gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1.
checkList<int32_t>(subReader.get("int32List"), {12345678, -90123456, -0x7fffffff-1, 0x7fffffff});
checkList<int64_t>(subReader.get("int64List"), {123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll-1, 0x7fffffffffffffffll});
checkList<uint8_t>(subReader.get("uInt8List"), {12u, 34u, 0u, 0xffu});
checkList<uint16_t>(subReader.get("uInt16List"), {1234u, 5678u, 0u, 0xffffu});
checkList<uint32_t>(subReader.get("uInt32List"), {12345678u, 90123456u, 0u, 0xffffffffu});
checkList<uint64_t>(subReader.get("uInt64List"), {123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull});
checkList<float>(subReader.get("float32List"), {0.0f, 1234567.0f, 1e37f, -1e37f, 1e-37f, -1e-37f});
checkList<double>(subReader.get("float64List"), {0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306});
checkList<Text>(subReader.get("textList"), {"quux", "corge", "grault"});
checkList<Data>(subReader.get("dataList"), {"garply", "waldo", "fred"});
{
auto listReader = subReader.get("structList").as<DynamicList>();
ASSERT_EQ(3u, listReader.size());
EXPECT_EQ("x structlist 1", listReader[0].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("x structlist 2", listReader[1].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("x structlist 3", listReader[2].as<DynamicStruct>().get("textField").as<Text>());
}
checkEnumList(subReader.get("enumList"), {"qux", "bar", "grault"});
}
EXPECT_EQ("corge",
reader.get("enumField").as<DynamicEnum>().getEnumerant()->getProto().getName());
EXPECT_EQ(6u, reader.get("voidList").as<DynamicList>().size());
checkList<bool>(reader.get("boolList"), {true, false, false, true});
checkList<int8_t>(reader.get("int8List"), {111, -111});
checkList<int16_t>(reader.get("int16List"), {11111, -11111});
checkList<int32_t>(reader.get("int32List"), {111111111, -111111111});
checkList<int64_t>(reader.get("int64List"), {1111111111111111111ll, -1111111111111111111ll});
checkList<uint8_t>(reader.get("uInt8List"), {111u, 222u});
checkList<uint16_t>(reader.get("uInt16List"), {33333u, 44444u});
checkList<uint32_t>(reader.get("uInt32List"), {3333333333u});
checkList<uint64_t>(reader.get("uInt64List"), {11111111111111111111ull});
{
auto listReader = reader.get("float32List").as<DynamicList>();
ASSERT_EQ(4u, listReader.size());
EXPECT_EQ(5555.5f, listReader[0].as<float>());
EXPECT_EQ(std::numeric_limits<float>::infinity(), listReader[1].as<float>());
EXPECT_EQ(-std::numeric_limits<float>::infinity(), listReader[2].as<float>());
EXPECT_TRUE(isNaN(listReader[3].as<float>()));
}
{
auto listReader = reader.get("float64List").as<DynamicList>();
ASSERT_EQ(4u, listReader.size());
EXPECT_EQ(7777.75, listReader[0].as<double>());
EXPECT_EQ(std::numeric_limits<double>::infinity(), listReader[1].as<double>());
EXPECT_EQ(-std::numeric_limits<double>::infinity(), listReader[2].as<double>());
EXPECT_TRUE(isNaN(listReader[3].as<double>()));
}
checkList<Text>(reader.get("textList"), {"plugh", "xyzzy", "thud"});
checkList<Data>(reader.get("dataList"), {"oops", "exhausted", "rfc3092"});
{
auto listReader = reader.get("structList").as<DynamicList>();
ASSERT_EQ(3u, listReader.size());
EXPECT_EQ("structlist 1", listReader[0].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("structlist 2", listReader[1].as<DynamicStruct>().get("textField").as<Text>());
EXPECT_EQ("structlist 3", listReader[2].as<DynamicStruct>().get("textField").as<Text>());
}
checkEnumList(reader.get("enumList"), {"foo", "garply"});
}
#undef as
template <typename Reader>
void genericCheckTestMessageAllZero(Reader reader) {
EXPECT_EQ(Void::VOID, reader.getVoidField());
......@@ -324,6 +558,84 @@ void genericCheckTestMessageAllZero(Reader reader) {
EXPECT_EQ(0u, reader.getStructList().size());
}
// Hack because as<>() is a template-parameter-dependent lookup everywhere below...
#define as template as
template <typename Reader>
void dynamicCheckTestMessageAllZero(Reader reader) {
EXPECT_EQ(Void::VOID, reader.get("voidField").as<Void>());
EXPECT_EQ(false, reader.get("boolField").as<bool>());
EXPECT_EQ(0, reader.get("int8Field").as<int8_t>());
EXPECT_EQ(0, reader.get("int16Field").as<int16_t>());
EXPECT_EQ(0, reader.get("int32Field").as<int32_t>());
EXPECT_EQ(0, reader.get("int64Field").as<int64_t>());
EXPECT_EQ(0u, reader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(0u, reader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(0u, reader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(0u, reader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(0, reader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(0, reader.get("float64Field").as<double>());
EXPECT_EQ("", reader.get("textField").as<Text>());
EXPECT_EQ("", reader.get("dataField").as<Data>());
{
auto subReader = reader.get("structField").as<DynamicStruct>();
EXPECT_EQ(Void::VOID, subReader.get("voidField").as<Void>());
EXPECT_EQ(false, subReader.get("boolField").as<bool>());
EXPECT_EQ(0, subReader.get("int8Field").as<int8_t>());
EXPECT_EQ(0, subReader.get("int16Field").as<int16_t>());
EXPECT_EQ(0, subReader.get("int32Field").as<int32_t>());
EXPECT_EQ(0, subReader.get("int64Field").as<int64_t>());
EXPECT_EQ(0u, subReader.get("uInt8Field").as<uint8_t>());
EXPECT_EQ(0u, subReader.get("uInt16Field").as<uint16_t>());
EXPECT_EQ(0u, subReader.get("uInt32Field").as<uint32_t>());
EXPECT_EQ(0u, subReader.get("uInt64Field").as<uint64_t>());
EXPECT_FLOAT_EQ(0, subReader.get("float32Field").as<float>());
EXPECT_DOUBLE_EQ(0, subReader.get("float64Field").as<double>());
EXPECT_EQ("", subReader.get("textField").as<Text>());
EXPECT_EQ("", subReader.get("dataField").as<Data>());
{
auto subSubReader = subReader.get("structField").as<DynamicStruct>();
EXPECT_EQ("", subSubReader.get("textField").as<Text>());
EXPECT_EQ("", subSubReader.get("structField").as<DynamicStruct>()
.get("textField").as<Text>());
}
EXPECT_EQ(0u, subReader.get("voidList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("boolList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int8List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int16List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("int64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt8List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt16List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("uInt64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("float32List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("float64List").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("textList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("dataList").as<DynamicList>().size());
EXPECT_EQ(0u, subReader.get("structList").as<DynamicList>().size());
}
EXPECT_EQ(0u, reader.get("voidList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("boolList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int8List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int16List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("int64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt8List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt16List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("uInt64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("float32List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("float64List").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("textList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("dataList").as<DynamicList>().size());
EXPECT_EQ(0u, reader.get("structList").as<DynamicList>().size());
}
#undef as
template <typename Builder>
void genericInitListDefaults(Builder builder) {
auto lists = builder.initLists();
......@@ -377,6 +689,58 @@ void genericInitListDefaults(Builder builder) {
}
}
void dynamicInitListDefaults(DynamicStruct::Builder builder) {
auto lists = builder.init("lists").as<DynamicStruct>();
lists.init("list0", 2);
lists.init("list1", 4);
lists.init("list8", 2);
lists.init("list16", 2);
lists.init("list32", 2);
lists.init("list64", 2);
lists.init("listP", 2);
lists.get("list0").as<DynamicList>()[0].as<DynamicStruct>().set("f", Void::VOID);
lists.get("list0").as<DynamicList>()[1].as<DynamicStruct>().set("f", Void::VOID);
lists.get("list1").as<DynamicList>()[0].as<DynamicStruct>().set("f", true);
lists.get("list1").as<DynamicList>()[1].as<DynamicStruct>().set("f", false);
lists.get("list1").as<DynamicList>()[2].as<DynamicStruct>().set("f", true);
lists.get("list1").as<DynamicList>()[3].as<DynamicStruct>().set("f", true);
lists.get("list8").as<DynamicList>()[0].as<DynamicStruct>().set("f", 123u);
lists.get("list8").as<DynamicList>()[1].as<DynamicStruct>().set("f", 45u);
lists.get("list16").as<DynamicList>()[0].as<DynamicStruct>().set("f", 12345u);
lists.get("list16").as<DynamicList>()[1].as<DynamicStruct>().set("f", 6789u);
lists.get("list32").as<DynamicList>()[0].as<DynamicStruct>().set("f", 123456789u);
lists.get("list32").as<DynamicList>()[1].as<DynamicStruct>().set("f", 234567890u);
lists.get("list64").as<DynamicList>()[0].as<DynamicStruct>().set("f", 1234567890123456u);
lists.get("list64").as<DynamicList>()[1].as<DynamicStruct>().set("f", 2345678901234567u);
lists.get("listP").as<DynamicList>()[0].as<DynamicStruct>().set("f", "foo");
lists.get("listP").as<DynamicList>()[1].as<DynamicStruct>().set("f", "bar");
{
auto l = lists.init("int32ListList", 3).as<DynamicList>();
l.init(0, 3).as<DynamicList>().copyFrom({1, 2, 3});
l.init(1, 2).as<DynamicList>().copyFrom({4, 5});
l.init(2, 1).as<DynamicList>().copyFrom({12341234});
}
{
auto l = lists.init("textListList", 3).as<DynamicList>();
l.init(0, 2).as<DynamicList>().copyFrom({"foo", "bar"});
l.init(1, 1).as<DynamicList>().copyFrom({"baz"});
l.init(2, 2).as<DynamicList>().copyFrom({"qux", "corge"});
}
{
auto l = lists.init("structListList", 2).as<DynamicList>();
auto e = l.init(0, 2).as<DynamicList>();
e[0].as<TestAllTypes>().setInt32Field(123);
e[1].as<TestAllTypes>().setInt32Field(456);
e = l.init(1, 1).as<DynamicList>();
e[0].as<TestAllTypes>().setInt32Field(789);
}
}
template <typename Reader>
void genericCheckListDefaults(Reader reader) {
auto lists = reader.getLists();
......@@ -435,6 +799,69 @@ void genericCheckListDefaults(Reader reader) {
}
}
// Hack because as<>() is a template-parameter-dependent lookup everywhere below...
#define as template as
template <typename Reader>
void dynamicCheckListDefaults(Reader reader) {
auto lists = reader.get("lists").as<DynamicStruct>();
ASSERT_EQ(2u, lists.get("list0").as<DynamicList>().size());
ASSERT_EQ(4u, lists.get("list1").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list8").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list16").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list32").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("list64").as<DynamicList>().size());
ASSERT_EQ(2u, lists.get("listP").as<DynamicList>().size());
EXPECT_EQ(Void::VOID, lists.get("list0").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<Void>());
EXPECT_EQ(Void::VOID, lists.get("list0").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<Void>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<bool>());
EXPECT_FALSE(lists.get("list1").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<bool>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[2].as<DynamicStruct>().get("f").as<bool>());
EXPECT_TRUE(lists.get("list1").as<DynamicList>()[3].as<DynamicStruct>().get("f").as<bool>());
EXPECT_EQ(123u, lists.get("list8").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint8_t>());
EXPECT_EQ(45u, lists.get("list8").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint8_t>());
EXPECT_EQ(12345u, lists.get("list16").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint16_t>());
EXPECT_EQ(6789u, lists.get("list16").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint16_t>());
EXPECT_EQ(123456789u, lists.get("list32").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint32_t>());
EXPECT_EQ(234567890u, lists.get("list32").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint32_t>());
EXPECT_EQ(1234567890123456u, lists.get("list64").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<uint64_t>());
EXPECT_EQ(2345678901234567u, lists.get("list64").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<uint64_t>());
EXPECT_EQ("foo", lists.get("listP").as<DynamicList>()[0].as<DynamicStruct>().get("f").as<Text>());
EXPECT_EQ("bar", lists.get("listP").as<DynamicList>()[1].as<DynamicStruct>().get("f").as<Text>());
{
auto l = lists.get("int32ListList").as<DynamicList>();
ASSERT_EQ(3u, l.size());
checkList<int32_t>(l[0], {1, 2, 3});
checkList<int32_t>(l[1], {4, 5});
checkList<int32_t>(l[2], {12341234});
}
{
auto l = lists.get("textListList").as<DynamicList>();
ASSERT_EQ(3u, l.size());
checkList<Text>(l[0], {"foo", "bar"});
checkList<Text>(l[1], {"baz"});
checkList<Text>(l[2], {"qux", "corge"});
}
{
auto l = lists.get("structListList").as<DynamicList>();
ASSERT_EQ(2u, l.size());
auto e = l[0].as<DynamicList>();
ASSERT_EQ(2u, e.size());
EXPECT_EQ(123, e[0].as<TestAllTypes>().getInt32Field());
EXPECT_EQ(456, e[1].as<TestAllTypes>().getInt32Field());
e = l[1].as<DynamicList>();
ASSERT_EQ(1u, e.size());
EXPECT_EQ(789, e[0].as<TestAllTypes>().getInt32Field());
}
}
#undef as
} // namespace
void initTestMessage(TestAllTypes::Builder builder) { genericInitTestMessage(builder); }
......@@ -456,5 +883,30 @@ void checkTestMessageAllZero(TestAllTypes::Reader reader) {
genericCheckTestMessageAllZero(reader);
}
void initDynamicTestMessage(DynamicStruct::Builder builder) {
dynamicInitTestMessage(builder);
}
void initDynamicTestLists(DynamicStruct::Builder builder) {
dynamicInitListDefaults(builder);
}
void checkDynamicTestMessage(DynamicStruct::Builder builder) {
dynamicCheckTestMessage(builder);
}
void checkDynamicTestLists(DynamicStruct::Builder builder) {
dynamicCheckListDefaults(builder);
}
void checkDynamicTestMessage(DynamicStruct::Reader reader) {
dynamicCheckTestMessage(reader);
}
void checkDynamicTestLists(DynamicStruct::Reader reader) {
dynamicCheckListDefaults(reader);
}
void checkDynamicTestMessageAllZero(DynamicStruct::Builder builder) {
dynamicCheckTestMessageAllZero(builder);
}
void checkDynamicTestMessageAllZero(DynamicStruct::Reader reader) {
dynamicCheckTestMessageAllZero(reader);
}
} // namespace internal
} // namespace capnproto
......@@ -27,6 +27,7 @@
#include "test.capnp.h"
#include <iostream>
#include "blob.h"
#include "dynamic.h"
namespace capnproto {
......@@ -76,6 +77,15 @@ void checkTestMessage(TestListDefaults::Reader reader);
void checkTestMessageAllZero(TestAllTypes::Builder builder);
void checkTestMessageAllZero(TestAllTypes::Reader reader);
void initDynamicTestMessage(DynamicStruct::Builder builder);
void initDynamicTestLists(DynamicStruct::Builder builder);
void checkDynamicTestMessage(DynamicStruct::Builder builder);
void checkDynamicTestLists(DynamicStruct::Builder builder);
void checkDynamicTestMessage(DynamicStruct::Reader reader);
void checkDynamicTestLists(DynamicStruct::Reader reader);
void checkDynamicTestMessageAllZero(DynamicStruct::Builder builder);
void checkDynamicTestMessageAllZero(DynamicStruct::Reader reader);
} // namespace internal
} // namespace capnproto
......
......@@ -168,6 +168,7 @@ public:
inline {{fieldType}} get{{fieldTitleCase}}();
{{/fieldIsPrimitive}}
{{^fieldIsPrimitive}}
inline bool has{{fieldTitleCase}}();
{{^fieldIsGenericObject}}
inline {{fieldType}}::Reader get{{fieldTitleCase}}();
{{/fieldIsGenericObject}}
......@@ -218,6 +219,7 @@ public:
inline void set{{fieldTitleCase}}({{fieldType}} value);
{{/fieldIsPrimitive}}
{{^fieldIsPrimitive}}
inline bool has{{fieldTitleCase}}();
{{^fieldIsGenericObject}}
inline {{fieldType}}::Builder get{{fieldTitleCase}}();
inline void set{{fieldTitleCase}}({{fieldType}}::Reader other);
......@@ -310,8 +312,16 @@ inline void {{typeFullName}}::Builder::set{{fieldTitleCase}}({{fieldType}} value
}
{{/fieldIsPrimitive}}
{{! ------------------------------------------------------------------------------------------- }}
{{^fieldIsGenericObject}}
{{^fieldIsPrimitive}}
inline bool {{typeFullName}}::Reader::has{{fieldTitleCase}}() {
return !_reader.isPointerFieldNull({{fieldOffset}} * ::capnproto::POINTERS);
}
inline bool {{typeFullName}}::Builder::has{{fieldTitleCase}}() {
return !_builder.isPointerFieldNull({{fieldOffset}} * ::capnproto::POINTERS);
}
{{^fieldIsGenericObject}}
inline {{fieldType}}::Reader {{typeFullName}}::Reader::get{{fieldTitleCase}}() {
{{#fieldUnion}}
CAPNPROTO_INLINE_DPRECOND(which() == {{unionTitleCase}}::{{fieldUpperCase}},
......@@ -375,7 +385,6 @@ inline {{fieldType}}::Builder {{typeFullName}}::Builder::init{{fieldTitleCase}}(
}
{{/fieldIsStruct}}
{{/fieldIsPrimitive}}
{{/fieldIsGenericObject}}
{{! ------------------------------------------------------------------------------------------- }}
{{#fieldIsGenericObject}}
......@@ -450,6 +459,7 @@ inline typename T::Builder {{typeFullName}}::Builder::init{{fieldTitleCase}}(Par
}
{{/fieldIsGenericObject}}
{{/fieldIsPrimitive}}
{{/typeFields}}
{{/typeStructOrUnion}}
{{/fileTypes}}
......
......@@ -69,7 +69,8 @@ static const ::capnproto::internal::RawSchema::MemberInfo m_{{schemaId}}[] = {
{{/schemaMembersByName}}
};
const ::capnproto::internal::RawSchema s_{{schemaId}} = {
b_{{schemaId}}.words, d_{{schemaId}}, m_{{schemaId}}, {{schemaDependencyCount}}, {{schemaMemberCount}}
b_{{schemaId}}.words, d_{{schemaId}}, m_{{schemaId}},
{{schemaDependencyCount}}, {{schemaMemberCount}}, nullptr
};
{{/typeSchema}}
{{/fileTypes}}
......
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