capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

evolution-test.c++ (31083B)


      1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
      2 // Licensed under the MIT License:
      3 //
      4 // Permission is hereby granted, free of charge, to any person obtaining a copy
      5 // of this software and associated documentation files (the "Software"), to deal
      6 // in the Software without restriction, including without limitation the rights
      7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8 // copies of the Software, and to permit persons to whom the Software is
      9 // furnished to do so, subject to the following conditions:
     10 //
     11 // The above copyright notice and this permission notice shall be included in
     12 // all copies or substantial portions of the Software.
     13 //
     14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20 // THE SOFTWARE.
     21 
     22 // This is a fuzz test which randomly generates a schema for a struct one change at a time.
     23 // Each modification is known a priori to be compatible or incompatible.  The type is compiled
     24 // before and after the change and both versions are loaded into a SchemaLoader with the
     25 // expectation that this will succeed if they are compatible and fail if they are not.  If
     26 // the types are expected to be compatible, the test also constructs an instance of the old
     27 // type and reads it as the new type, and vice versa.
     28 
     29 #ifndef _GNU_SOURCE
     30 #define _GNU_SOURCE
     31 #endif
     32 
     33 #include <capnp/compiler/grammar.capnp.h>
     34 #include <capnp/schema-loader.h>
     35 #include <capnp/message.h>
     36 #include <capnp/pretty-print.h>
     37 #include "compiler.h"
     38 #include <kj/function.h>
     39 #include <kj/debug.h>
     40 #include <stdlib.h>
     41 #include <time.h>
     42 #include <kj/main.h>
     43 #include <kj/io.h>
     44 #include <kj/miniposix.h>
     45 
     46 namespace capnp {
     47 namespace compiler {
     48 namespace {
     49 
     50 static const kj::StringPtr RFC3092[] = {"foo", "bar", "baz", "qux"};
     51 
     52 template <typename T, size_t size>
     53 T& chooseFrom(T (&arr)[size]) {
     54   return arr[rand() % size];
     55 }
     56 template <typename T>
     57 auto chooseFrom(T arr) -> decltype(arr[0]) {
     58   return arr[rand() % arr.size()];
     59 }
     60 
     61 static Declaration::Builder addNested(Declaration::Builder parent) {
     62   auto oldNestedOrphan = parent.disownNestedDecls();
     63   auto oldNested = oldNestedOrphan.get();
     64   auto newNested = parent.initNestedDecls(oldNested.size() + 1);
     65 
     66   uint index = rand() % (oldNested.size() + 1);
     67 
     68   for (uint i = 0; i < index; i++) {
     69     newNested.setWithCaveats(i, oldNested[i]);
     70   }
     71   for (uint i = index + 1; i < newNested.size(); i++) {
     72     newNested.setWithCaveats(i, oldNested[i - 1]);
     73   }
     74 
     75   return newNested[index];
     76 }
     77 
     78 struct TypeOption {
     79   kj::StringPtr name;
     80   kj::ConstFunction<void(Expression::Builder)> makeValue;
     81 };
     82 
     83 static const TypeOption TYPE_OPTIONS[] = {
     84   { "Int32",
     85     [](Expression::Builder builder) {
     86       builder.setPositiveInt(rand() % (1 << 24));
     87     }},
     88   { "Float64",
     89     [](Expression::Builder builder) {
     90       builder.setPositiveInt(rand());
     91     }},
     92   { "Int8",
     93     [](Expression::Builder builder) {
     94       builder.setPositiveInt(rand() % 128);
     95     }},
     96   { "UInt16",
     97     [](Expression::Builder builder) {
     98       builder.setPositiveInt(rand() % (1 << 16));
     99     }},
    100   { "Bool",
    101     [](Expression::Builder builder) {
    102       builder.initRelativeName().setValue("true");
    103     }},
    104   { "Text",
    105     [](Expression::Builder builder) {
    106       builder.setString(chooseFrom(RFC3092));
    107     }},
    108   { "StructType",
    109     [](Expression::Builder builder) {
    110       auto assignment = builder.initTuple(1)[0];
    111       assignment.initNamed().setValue("i");
    112       assignment.initValue().setPositiveInt(rand() % (1 << 24));
    113     }},
    114   { "EnumType",
    115     [](Expression::Builder builder) {
    116       builder.initRelativeName().setValue(chooseFrom(RFC3092));
    117     }},
    118 };
    119 
    120 void setDeclName(Expression::Builder decl, kj::StringPtr name) {
    121   decl.initRelativeName().setValue(name);
    122 }
    123 
    124 static kj::ConstFunction<void(Expression::Builder)> randomizeType(Expression::Builder type) {
    125   auto option = &chooseFrom(TYPE_OPTIONS);
    126 
    127   if (rand() % 4 == 0) {
    128     auto app = type.initApplication();
    129     setDeclName(app.initFunction(), "List");
    130     setDeclName(app.initParams(1)[0].initValue(), option->name);
    131     return [option](Expression::Builder builder) {
    132       for (auto element: builder.initList(rand() % 4 + 1)) {
    133         option->makeValue(element);
    134       }
    135     };
    136   } else {
    137     setDeclName(type, option->name);
    138     return option->makeValue.reference();
    139   }
    140 }
    141 
    142 enum ChangeKind {
    143   NO_CHANGE,
    144   COMPATIBLE,
    145   INCOMPATIBLE,
    146 
    147   SUBTLY_COMPATIBLE
    148   // The change is technically compatible on the wire, but SchemaLoader will complain.
    149 };
    150 
    151 struct ChangeInfo {
    152   ChangeKind kind;
    153   kj::String description;
    154 
    155   ChangeInfo(): kind(NO_CHANGE) {}
    156   ChangeInfo(ChangeKind kind, kj::StringPtr description)
    157       : kind(kind), description(kj::str(description)) {}
    158   ChangeInfo(ChangeKind kind, kj::String&& description)
    159       : kind(kind), description(kj::mv(description)) {}
    160 };
    161 
    162 extern kj::ArrayPtr<kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)>> STRUCT_MODS;
    163 extern kj::ArrayPtr<kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)>> FIELD_MODS;
    164 
    165 // ================================================================================
    166 
    167 static ChangeInfo declChangeName(Declaration::Builder decl, uint& nextOrdinal,
    168                                  bool scopeHasUnion) {
    169   auto name = decl.getName();
    170   if (name.getValue().size() == 0) {
    171     // Naming an unnamed union.
    172     name.setValue(kj::str("unUnnamed", nextOrdinal));
    173     return { SUBTLY_COMPATIBLE, "Assign name to unnamed union." };
    174   } else {
    175     name.setValue(kj::str(name.getValue(), "Xx"));
    176     return { COMPATIBLE, "Rename declaration." };
    177   }
    178 }
    179 
    180 static ChangeInfo structAddField(Declaration::Builder decl, uint& nextOrdinal, bool scopeHasUnion) {
    181   auto fieldDecl = addNested(decl);
    182 
    183   uint ordinal = nextOrdinal++;
    184 
    185   fieldDecl.initName().setValue(kj::str("f", ordinal));
    186   fieldDecl.getId().initOrdinal().setValue(ordinal);
    187 
    188   auto field = fieldDecl.initField();
    189 
    190   auto makeValue = randomizeType(field.initType());
    191   if (rand() % 4 == 0) {
    192     makeValue(field.getDefaultValue().initValue());
    193   } else {
    194     field.getDefaultValue().setNone();
    195   }
    196   return { COMPATIBLE, "Add field." };
    197 }
    198 
    199 static ChangeInfo structModifyField(Declaration::Builder decl, uint& nextOrdinal,
    200                                     bool scopeHasUnion) {
    201   auto nested = decl.getNestedDecls();
    202 
    203   if (nested.size() == 0) {
    204     return { NO_CHANGE, "Modify field, but there were none to modify." };
    205   }
    206 
    207   auto field = chooseFrom(nested);
    208 
    209   bool hasUnion = false;
    210   if (decl.isUnion()) {
    211     hasUnion = true;
    212   } else {
    213     for (auto n: nested) {
    214       if (n.isUnion() && n.getName().getValue().size() == 0) {
    215         hasUnion = true;
    216         break;
    217       }
    218     }
    219   }
    220 
    221   if (field.isGroup() || field.isUnion()) {
    222     return chooseFrom(STRUCT_MODS)(field, nextOrdinal, hasUnion);
    223   } else {
    224     return chooseFrom(FIELD_MODS)(field, nextOrdinal, hasUnion);
    225   }
    226 }
    227 
    228 static ChangeInfo structGroupifyFields(
    229     Declaration::Builder decl, uint& nextOrdinal, bool scopeHasUnion) {
    230   // Place a random subset of the fields into a group.
    231 
    232   if (decl.isUnion()) {
    233     return { NO_CHANGE,
    234       "Randomly make a group out of some fields, but I can't do this to a union." };
    235   }
    236 
    237   kj::Vector<Orphan<Declaration>> groupified;
    238   kj::Vector<Orphan<Declaration>> notGroupified;
    239   auto orphanage = Orphanage::getForMessageContaining(decl);
    240 
    241   for (auto nested: decl.getNestedDecls()) {
    242     if (rand() % 2) {
    243       groupified.add(orphanage.newOrphanCopy(nested.asReader()));
    244     } else {
    245       notGroupified.add(orphanage.newOrphanCopy(nested.asReader()));
    246     }
    247   }
    248 
    249   if (groupified.size() == 0) {
    250     return { NO_CHANGE,
    251       "Randomly make a group out of some fields, but I ended up choosing none of them." };
    252   }
    253 
    254   auto newNested = decl.initNestedDecls(notGroupified.size() + 1);
    255   uint index = rand() % (notGroupified.size() + 1);
    256 
    257   for (uint i = 0; i < index; i++) {
    258     newNested.adoptWithCaveats(i, kj::mv(notGroupified[i]));
    259   }
    260   for (uint i = index; i < notGroupified.size(); i++) {
    261     newNested.adoptWithCaveats(i + 1, kj::mv(notGroupified[i]));
    262   }
    263 
    264   auto newGroup = newNested[index];
    265   auto groupNested = newGroup.initNestedDecls(groupified.size());
    266   for (uint i = 0; i < groupified.size(); i++) {
    267     groupNested.adoptWithCaveats(i, kj::mv(groupified[i]));
    268   }
    269 
    270   newGroup.initName().setValue(kj::str("g", nextOrdinal, "x", groupNested[0].getName().getValue()));
    271   newGroup.getId().setUnspecified();
    272   newGroup.setGroup();
    273 
    274   return { SUBTLY_COMPATIBLE, "Randomly group some set of existing fields." };
    275 }
    276 
    277 static ChangeInfo structPermuteFields(
    278     Declaration::Builder decl, uint& nextOrdinal, bool scopeHasUnion) {
    279   if (decl.getNestedDecls().size() == 0) {
    280     return { NO_CHANGE, "Permute field code order, but there were none." };
    281   }
    282 
    283   auto oldOrphan = decl.disownNestedDecls();
    284   auto old = oldOrphan.get();
    285 
    286   KJ_STACK_ARRAY(uint, mapping, old.size(), 16, 64);
    287 
    288   for (uint i = 0; i < mapping.size(); i++) {
    289     mapping[i] = i;
    290   }
    291   for (uint i = mapping.size() - 1; i > 0; i--) {
    292     uint j = rand() % i;
    293     uint temp = mapping[j];
    294     mapping[j] = mapping[i];
    295     mapping[i] = temp;
    296   }
    297 
    298   auto newNested = decl.initNestedDecls(old.size());
    299   for (uint i = 0; i < old.size(); i++) {
    300     newNested.setWithCaveats(i, old[mapping[i]]);
    301   }
    302 
    303   return { COMPATIBLE, "Permute field code order." };
    304 }
    305 
    306 kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)> STRUCT_MODS_[] = {
    307   structAddField,
    308   structAddField,
    309   structAddField,
    310   structModifyField,
    311   structModifyField,
    312   structModifyField,
    313   structPermuteFields,
    314   declChangeName,
    315   structGroupifyFields     // do more rarely because it creates slowness
    316 };
    317 kj::ArrayPtr<kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)>>
    318     STRUCT_MODS = STRUCT_MODS_;
    319 
    320 // ================================================================================
    321 
    322 static ChangeInfo fieldUpgradeList(Declaration::Builder decl, uint& nextOrdinal,
    323                                    bool scopeHasUnion) {
    324   // Upgrades a non-struct list to a struct list.
    325 
    326   auto field = decl.getField();
    327   if (field.getDefaultValue().isValue()) {
    328     return { NO_CHANGE, "Upgrade primitive list to struct list, but it had a default value." };
    329   }
    330 
    331   auto type = field.getType();
    332   if (!type.isApplication()) {
    333     return { NO_CHANGE, "Upgrade primitive list to struct list, but it wasn't a list." };
    334   }
    335   auto typeParams = type.getApplication().getParams();
    336 
    337   auto elementType = typeParams[0].getValue();
    338   auto relativeName = elementType.getRelativeName();
    339   auto nameText = relativeName.asReader().getValue();
    340   if (nameText == "StructType" || nameText.endsWith("Struct")) {
    341     return { NO_CHANGE, "Upgrade primitive list to struct list, but it was already a struct list."};
    342   }
    343   if (nameText == "Bool") {
    344     return { NO_CHANGE, "Upgrade primitive list to struct list, but bool lists can't be upgraded."};
    345   }
    346 
    347   relativeName.setValue(kj::str(nameText, "Struct"));
    348   return { COMPATIBLE, "Upgrade primitive list to struct list" };
    349 }
    350 
    351 static ChangeInfo fieldExpandGroup(Declaration::Builder decl, uint& nextOrdinal,
    352                                    bool scopeHasUnion) {
    353   Declaration::Builder newDecl = decl.initNestedDecls(1)[0];
    354   newDecl.adoptName(decl.disownName());
    355   newDecl.getId().adoptOrdinal(decl.getId().disownOrdinal());
    356 
    357   auto field = decl.getField();
    358   auto newField = newDecl.initField();
    359 
    360   newField.adoptType(field.disownType());
    361   if (field.getDefaultValue().isValue()) {
    362     newField.getDefaultValue().adoptValue(field.getDefaultValue().disownValue());
    363   } else {
    364     newField.getDefaultValue().setNone();
    365   }
    366 
    367   decl.initName().setValue(kj::str("g", newDecl.getName().getValue()));
    368   decl.getId().setUnspecified();
    369 
    370   if (rand() % 2 == 0) {
    371     decl.setGroup();
    372   } else {
    373     decl.setUnion();
    374     if (!scopeHasUnion && rand() % 2 == 0) {
    375       // Make it an unnamed union.
    376       decl.getName().setValue("");
    377     }
    378     structAddField(decl, nextOrdinal, scopeHasUnion);  // union must have two members
    379   }
    380 
    381   return { COMPATIBLE, "Wrap a field in a singleton group." };
    382 }
    383 
    384 static ChangeInfo fieldChangeType(Declaration::Builder decl, uint& nextOrdinal,
    385                                   bool scopeHasUnion) {
    386   auto field = decl.getField();
    387 
    388   if (field.getDefaultValue().isNone()) {
    389     // Change the type.
    390     auto type = field.getType();
    391     while (type.isApplication()) {
    392       // Either change the list parameter, or revert to a non-list.
    393       if (rand() % 2) {
    394         type = type.getApplication().getParams()[0].getValue();
    395       } else {
    396         type.initRelativeName();
    397       }
    398     }
    399     auto typeName = type.getRelativeName();
    400     if (typeName.asReader().getValue().startsWith("Text")) {
    401       typeName.setValue("Int32");
    402     } else {
    403       typeName.setValue("Text");
    404     }
    405     return { INCOMPATIBLE, "Change the type of a field." };
    406   } else {
    407     // Change the default value.
    408     auto dval = field.getDefaultValue().getValue();
    409     switch (dval.which()) {
    410       case Expression::UNKNOWN: KJ_FAIL_ASSERT("unknown value expression?");
    411       case Expression::POSITIVE_INT: dval.setPositiveInt(dval.getPositiveInt() ^ 1); break;
    412       case Expression::NEGATIVE_INT: dval.setNegativeInt(dval.getNegativeInt() ^ 1); break;
    413       case Expression::FLOAT: dval.setFloat(-dval.getFloat()); break;
    414       case Expression::RELATIVE_NAME: {
    415         auto name = dval.getRelativeName();
    416         auto nameText = name.asReader().getValue();
    417         if (nameText == "true") {
    418           name.setValue("false");
    419         } else if (nameText == "false") {
    420           name.setValue("true");
    421         } else if (nameText == "foo") {
    422           name.setValue("bar");
    423         } else {
    424           name.setValue("foo");
    425         }
    426         break;
    427       }
    428       case Expression::STRING:
    429       case Expression::BINARY:
    430       case Expression::LIST:
    431       case Expression::TUPLE:
    432         return { NO_CHANGE, "Change the default value of a field, but it's a pointer field." };
    433 
    434       case Expression::ABSOLUTE_NAME:
    435       case Expression::IMPORT:
    436       case Expression::EMBED:
    437       case Expression::APPLICATION:
    438       case Expression::MEMBER:
    439         KJ_FAIL_ASSERT("Unexpected expression type.");
    440     }
    441     return { INCOMPATIBLE, "Change the default value of a pritimive field." };
    442   }
    443 }
    444 
    445 kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)> FIELD_MODS_[] = {
    446   fieldUpgradeList,
    447   fieldExpandGroup,
    448   fieldChangeType,
    449   declChangeName
    450 };
    451 kj::ArrayPtr<kj::ConstFunction<ChangeInfo(Declaration::Builder, uint&, bool)>>
    452     FIELD_MODS = FIELD_MODS_;
    453 
    454 // ================================================================================
    455 
    456 uint getOrdinal(StructSchema::Field field) {
    457   auto proto = field.getProto();
    458   if (proto.getOrdinal().isExplicit()) {
    459     return proto.getOrdinal().getExplicit();
    460   }
    461 
    462   KJ_ASSERT(proto.isGroup());
    463 
    464   auto group = field.getType().asStruct();
    465   return getOrdinal(group.getFields()[0]);
    466 }
    467 
    468 Orphan<DynamicStruct> makeExampleStruct(
    469     Orphanage orphanage, StructSchema schema, uint sharedOrdinalCount);
    470 void checkExampleStruct(DynamicStruct::Reader reader, uint sharedOrdinalCount);
    471 
    472 Orphan<DynamicValue> makeExampleValue(
    473     Orphanage orphanage, uint ordinal, Type type, uint sharedOrdinalCount) {
    474   switch (type.which()) {
    475     case schema::Type::INT32: return ordinal * 47327;
    476     case schema::Type::FLOAT64: return ordinal * 313.25;
    477     case schema::Type::INT8: return int(ordinal % 256) - 128;
    478     case schema::Type::UINT16: return ordinal * 13;
    479     case schema::Type::BOOL: return ordinal % 2 == 0;
    480     case schema::Type::TEXT: return orphanage.newOrphanCopy(Text::Reader(kj::str(ordinal)));
    481     case schema::Type::STRUCT: {
    482       auto structType = type.asStruct();
    483       auto result = orphanage.newOrphan(structType);
    484       auto builder = result.get();
    485 
    486       KJ_IF_MAYBE(fieldI, structType.findFieldByName("i")) {
    487         // Type is "StructType"
    488         builder.set(*fieldI, ordinal);
    489       } else {
    490         // Type is "Int32Struct" or the like.
    491         auto field = structType.getFieldByName("f0");
    492         builder.adopt(field, makeExampleValue(
    493             orphanage, ordinal, field.getType(), sharedOrdinalCount));
    494       }
    495 
    496       return kj::mv(result);
    497     }
    498     case schema::Type::ENUM: {
    499       auto enumerants = type.asEnum().getEnumerants();
    500       return DynamicEnum(enumerants[ordinal %enumerants.size()]);
    501     }
    502     case schema::Type::LIST: {
    503       auto listType = type.asList();
    504       auto elementType = listType.getElementType();
    505       auto result = orphanage.newOrphan(listType, 1);
    506       result.get().adopt(0, makeExampleValue(
    507           orphanage, ordinal, elementType, sharedOrdinalCount));
    508       return kj::mv(result);
    509     }
    510     default:
    511       KJ_FAIL_ASSERT("You added a new possible field type!");
    512   }
    513 }
    514 
    515 void checkExampleValue(DynamicValue::Reader value, uint ordinal, schema::Type::Reader type,
    516                        uint sharedOrdinalCount) {
    517   switch (type.which()) {
    518     case schema::Type::INT32: KJ_ASSERT(value.as<int32_t>() == ordinal * 47327); break;
    519     case schema::Type::FLOAT64: KJ_ASSERT(value.as<double>() == ordinal * 313.25); break;
    520     case schema::Type::INT8: KJ_ASSERT(value.as<int8_t>() == int(ordinal % 256) - 128); break;
    521     case schema::Type::UINT16: KJ_ASSERT(value.as<uint16_t>() == ordinal * 13); break;
    522     case schema::Type::BOOL: KJ_ASSERT(value.as<bool>() == (ordinal % 2 == 0)); break;
    523     case schema::Type::TEXT: KJ_ASSERT(value.as<Text>() == kj::str(ordinal)); break;
    524     case schema::Type::STRUCT: {
    525       auto structValue = value.as<DynamicStruct>();
    526       auto structType = structValue.getSchema();
    527 
    528       KJ_IF_MAYBE(fieldI, structType.findFieldByName("i")) {
    529         // Type is "StructType"
    530         KJ_ASSERT(structValue.get(*fieldI).as<uint32_t>() == ordinal);
    531       } else {
    532         // Type is "Int32Struct" or the like.
    533         auto field = structType.getFieldByName("f0");
    534         checkExampleValue(structValue.get(field), ordinal,
    535                           field.getProto().getSlot().getType(), sharedOrdinalCount);
    536       }
    537       break;
    538     }
    539     case schema::Type::ENUM: {
    540       auto enumerant = KJ_ASSERT_NONNULL(value.as<DynamicEnum>().getEnumerant());
    541       KJ_ASSERT(enumerant.getIndex() ==
    542           ordinal % enumerant.getContainingEnum().getEnumerants().size());
    543       break;
    544     }
    545     case schema::Type::LIST:
    546       checkExampleValue(value.as<DynamicList>()[0], ordinal, type.getList().getElementType(),
    547                         sharedOrdinalCount);
    548       break;
    549     default:
    550       KJ_FAIL_ASSERT("You added a new possible field type!");
    551   }
    552 }
    553 
    554 void setExampleField(DynamicStruct::Builder builder, StructSchema::Field field,
    555                      uint sharedOrdinalCount) {
    556   auto fieldProto = field.getProto();
    557   switch (fieldProto.which()) {
    558     case schema::Field::SLOT:
    559       builder.adopt(field, makeExampleValue(
    560           Orphanage::getForMessageContaining(builder),
    561           getOrdinal(field), field.getType(), sharedOrdinalCount));
    562       break;
    563     case schema::Field::GROUP:
    564       builder.adopt(field, makeExampleStruct(
    565           Orphanage::getForMessageContaining(builder),
    566           field.getType().asStruct(), sharedOrdinalCount));
    567       break;
    568   }
    569 }
    570 
    571 void checkExampleField(DynamicStruct::Reader reader, StructSchema::Field field,
    572                        uint sharedOrdinalCount) {
    573   auto fieldProto = field.getProto();
    574   switch (fieldProto.which()) {
    575     case schema::Field::SLOT: {
    576       uint ordinal = getOrdinal(field);
    577       if (ordinal < sharedOrdinalCount) {
    578         checkExampleValue(reader.get(field), ordinal,
    579                           fieldProto.getSlot().getType(), sharedOrdinalCount);
    580       }
    581       break;
    582     }
    583     case schema::Field::GROUP:
    584       checkExampleStruct(reader.get(field).as<DynamicStruct>(), sharedOrdinalCount);
    585       break;
    586   }
    587 }
    588 
    589 Orphan<DynamicStruct> makeExampleStruct(
    590     Orphanage orphanage, StructSchema schema, uint sharedOrdinalCount) {
    591   // Initialize all fields of the struct via reflection, such that they can be verified using
    592   // a different version of the struct.  sharedOrdinalCount is the number of ordinals shared by
    593   // the two versions.  This is used mainly to avoid setting union members that the other version
    594   // doesn't have.
    595 
    596   Orphan<DynamicStruct> result = orphanage.newOrphan(schema);
    597   auto builder = result.get();
    598 
    599   for (auto field: schema.getNonUnionFields()) {
    600     setExampleField(builder, field, sharedOrdinalCount);
    601   }
    602 
    603   auto unionFields = schema.getUnionFields();
    604 
    605   // Pretend the union doesn't have any fields that aren't in the shared ordinal range.
    606   uint range = unionFields.size();
    607   while (range > 0 && getOrdinal(unionFields[range - 1]) >= sharedOrdinalCount) {
    608     --range;
    609   }
    610 
    611   if (range > 0) {
    612     auto field = unionFields[getOrdinal(unionFields[0]) % range];
    613     setExampleField(builder, field, sharedOrdinalCount);
    614   }
    615 
    616   return kj::mv(result);
    617 }
    618 
    619 void checkExampleStruct(DynamicStruct::Reader reader, uint sharedOrdinalCount) {
    620   auto schema = reader.getSchema();
    621 
    622   for (auto field: schema.getNonUnionFields()) {
    623     checkExampleField(reader, field, sharedOrdinalCount);
    624   }
    625 
    626   auto unionFields = schema.getUnionFields();
    627 
    628   // Pretend the union doesn't have any fields that aren't in the shared ordinal range.
    629   uint range = unionFields.size();
    630   while (range > 0 && getOrdinal(unionFields[range - 1]) >= sharedOrdinalCount) {
    631     --range;
    632   }
    633 
    634   if (range > 0) {
    635     auto field = unionFields[getOrdinal(unionFields[0]) % range];
    636     checkExampleField(reader, field, sharedOrdinalCount);
    637   }
    638 }
    639 
    640 // ================================================================================
    641 
    642 class ModuleImpl final: public Module {
    643 public:
    644   explicit ModuleImpl(ParsedFile::Reader content): content(content) {}
    645 
    646   kj::StringPtr getSourceName() override { return "evolving-schema.capnp"; }
    647   Orphan<ParsedFile> loadContent(Orphanage orphanage) override {
    648     return orphanage.newOrphanCopy(content);
    649   }
    650   kj::Maybe<Module&> importRelative(kj::StringPtr importPath) override {
    651     return nullptr;
    652   }
    653   kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) override {
    654     return nullptr;
    655   }
    656 
    657   void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
    658     KJ_FAIL_ASSERT("Unexpected parse error.", startByte, endByte, message);
    659   }
    660   bool hadErrors() override {
    661     return false;
    662   }
    663 
    664 private:
    665   ParsedFile::Reader content;
    666 };
    667 
    668 static void loadStructAndGroups(const SchemaLoader& src, SchemaLoader& dst, uint64_t id) {
    669   auto proto = src.get(id).getProto();
    670   dst.load(proto);
    671 
    672   for (auto field: proto.getStruct().getFields()) {
    673     if (field.isGroup()) {
    674       loadStructAndGroups(src, dst, field.getGroup().getTypeId());
    675     }
    676   }
    677 }
    678 
    679 static kj::Maybe<kj::Exception> loadFile(
    680     ParsedFile::Reader file, SchemaLoader& loader, bool allNodes,
    681     kj::Maybe<kj::Own<MallocMessageBuilder>>& messageBuilder,
    682     uint sharedOrdinalCount) {
    683   Compiler compiler;
    684   ModuleImpl module(file);
    685   KJ_ASSERT(compiler.add(module).getId() == 0x8123456789abcdefllu);
    686 
    687   if (allNodes) {
    688     // Eagerly compile and load the whole thing.
    689     compiler.eagerlyCompile(0x8123456789abcdefllu, Compiler::ALL_RELATED_NODES);
    690 
    691     KJ_IF_MAYBE(m, messageBuilder) {
    692       // Build an example struct using the compiled schema.
    693       m->get()->adoptRoot(makeExampleStruct(
    694           m->get()->getOrphanage(), compiler.getLoader().get(0x823456789abcdef1llu).asStruct(),
    695           sharedOrdinalCount));
    696     }
    697 
    698     for (auto schema: compiler.getLoader().getAllLoaded()) {
    699       loader.load(schema.getProto());
    700     }
    701     return nullptr;
    702   } else {
    703     // Compile the file root so that the children are findable, then load the specific child
    704     // we want.
    705     compiler.eagerlyCompile(0x8123456789abcdefllu, Compiler::NODE);
    706 
    707     KJ_IF_MAYBE(m, messageBuilder) {
    708       // Check that the example struct matches the compiled schema.
    709       auto root = m->get()->getRoot<DynamicStruct>(
    710           compiler.getLoader().get(0x823456789abcdef1llu).asStruct()).asReader();
    711       KJ_CONTEXT(root);
    712       checkExampleStruct(root, sharedOrdinalCount);
    713     }
    714 
    715     return kj::runCatchingExceptions([&]() {
    716       loadStructAndGroups(compiler.getLoader(), loader, 0x823456789abcdef1llu);
    717     });
    718   }
    719 }
    720 
    721 bool checkChange(ParsedFile::Reader file1, ParsedFile::Reader file2, ChangeKind changeKind,
    722                  uint sharedOrdinalCount) {
    723   // Try loading file1 followed by file2 into the same SchemaLoader, expecting it to behave
    724   // according to changeKind.  Returns true if the files are both expected to be compatible and
    725   // actually are -- the main loop uses this to decide which version to keep
    726 
    727   kj::Maybe<kj::Own<MallocMessageBuilder>> exampleBuilder;
    728 
    729   if (changeKind != INCOMPATIBLE) {
    730     // For COMPATIBLE and SUBTLY_COMPATIBLE changes, build an example message with one schema
    731     // and check it with the other.
    732     exampleBuilder = kj::heap<MallocMessageBuilder>();
    733   }
    734 
    735   SchemaLoader loader;
    736   loadFile(file1, loader, true, exampleBuilder, sharedOrdinalCount);
    737   auto exception = loadFile(file2, loader, false, exampleBuilder, sharedOrdinalCount);
    738 
    739   if (changeKind == COMPATIBLE) {
    740     KJ_IF_MAYBE(e, exception) {
    741       kj::getExceptionCallback().onFatalException(kj::mv(*e));
    742       return false;
    743     } else {
    744       return true;
    745     }
    746   } else if (changeKind == INCOMPATIBLE) {
    747     KJ_ASSERT(exception != nullptr, file1, file2);
    748     return false;
    749   } else {
    750     KJ_ASSERT(changeKind == SUBTLY_COMPATIBLE);
    751 
    752     // SchemaLoader is allowed to throw an exception in this case, but we ignore it.
    753     return true;
    754   }
    755 }
    756 
    757 void doTest() {
    758   auto builder = kj::heap<MallocMessageBuilder>();
    759 
    760   {
    761     // Set up the basic file decl.
    762     auto parsedFile = builder->initRoot<ParsedFile>();
    763     auto file = parsedFile.initRoot();
    764     file.setFile();
    765     file.initId().initUid().setValue(0x8123456789abcdefllu);
    766     auto decls = file.initNestedDecls(3 + kj::size(TYPE_OPTIONS));
    767 
    768     {
    769       auto decl = decls[0];
    770       decl.initName().setValue("EvolvingStruct");
    771       decl.initId().initUid().setValue(0x823456789abcdef1llu);
    772       decl.setStruct();
    773     }
    774     {
    775       auto decl = decls[1];
    776       decl.initName().setValue("StructType");
    777       decl.setStruct();
    778 
    779       auto fieldDecl = decl.initNestedDecls(1)[0];
    780       fieldDecl.initName().setValue("i");
    781       fieldDecl.getId().initOrdinal().setValue(0);
    782       auto field = fieldDecl.initField();
    783       setDeclName(field.initType(), "UInt32");
    784     }
    785     {
    786       auto decl = decls[2];
    787       decl.initName().setValue("EnumType");
    788       decl.setEnum();
    789 
    790       auto enumerants = decl.initNestedDecls(4);
    791 
    792       for (uint i = 0; i < kj::size(RFC3092); i++) {
    793         auto enumerantDecl = enumerants[i];
    794         enumerantDecl.initName().setValue(RFC3092[i]);
    795         enumerantDecl.getId().initOrdinal().setValue(i);
    796         enumerantDecl.setEnumerant();
    797       }
    798     }
    799 
    800     // For each of TYPE_OPTIONS, declare a struct type that contains that type as its @0 field.
    801     for (uint i = 0; i < kj::size(TYPE_OPTIONS); i++) {
    802       auto decl = decls[3 + i];
    803       auto& option = TYPE_OPTIONS[i];
    804 
    805       decl.initName().setValue(kj::str(option.name, "Struct"));
    806       decl.setStruct();
    807 
    808       auto fieldDecl = decl.initNestedDecls(1)[0];
    809       fieldDecl.initName().setValue("f0");
    810       fieldDecl.getId().initOrdinal().setValue(0);
    811       auto field = fieldDecl.initField();
    812       setDeclName(field.initType(), option.name);
    813 
    814       uint ordinal = 1;
    815       for (auto j: kj::range(0, rand() % 4)) {
    816         (void)j;
    817         structAddField(decl, ordinal, false);
    818       }
    819     }
    820   }
    821 
    822   uint nextOrdinal = 0;
    823 
    824   for (uint i = 0; i < 96; i++) {
    825     uint oldOrdinalCount = nextOrdinal;
    826 
    827     auto newBuilder = kj::heap<MallocMessageBuilder>();
    828     newBuilder->setRoot(builder->getRoot<ParsedFile>().asReader());
    829 
    830     auto parsedFile = newBuilder->getRoot<ParsedFile>();
    831     Declaration::Builder decl = parsedFile.getRoot().getNestedDecls()[0];
    832 
    833     // Apply a random modification.
    834     ChangeInfo changeInfo;
    835     while (changeInfo.kind == NO_CHANGE) {
    836       auto& mod = chooseFrom(STRUCT_MODS);
    837       changeInfo = mod(decl, nextOrdinal, false);
    838     }
    839 
    840     KJ_CONTEXT(changeInfo.description);
    841 
    842     if (checkChange(builder->getRoot<ParsedFile>(), parsedFile, changeInfo.kind, oldOrdinalCount) &&
    843         checkChange(parsedFile, builder->getRoot<ParsedFile>(), changeInfo.kind, oldOrdinalCount)) {
    844       builder = kj::mv(newBuilder);
    845     }
    846   }
    847 }
    848 
    849 class EvolutionTestMain {
    850 public:
    851   explicit EvolutionTestMain(kj::ProcessContext& context)
    852       : context(context) {}
    853 
    854   kj::MainFunc getMain() {
    855     return kj::MainBuilder(context, "(unknown version)",
    856         "Integration test / fuzzer which randomly modifies schemas is backwards-compatible ways "
    857         "and verifies that they do actually remain compatible.")
    858         .addOptionWithArg({"seed"}, KJ_BIND_METHOD(*this, setSeed), "<num>",
    859             "Set random number seed to <num>.  By default, time() is used.")
    860         .callAfterParsing(KJ_BIND_METHOD(*this, run))
    861         .build();
    862   }
    863 
    864   kj::MainBuilder::Validity setSeed(kj::StringPtr value) {
    865     char* end;
    866     seed = strtol(value.cStr(), &end, 0);
    867     if (value.size() == 0 || *end != '\0') {
    868       return "not an integer";
    869     } else {
    870       return true;
    871     }
    872   }
    873 
    874   kj::MainBuilder::Validity run() {
    875     // https://github.com/sandstorm-io/capnproto/issues/344 describes an obscure bug in the layout
    876     // algorithm, the fix for which breaks backwards-compatibility for any schema triggering the
    877     // bug. In order to avoid silently breaking protocols, we are temporarily throwing an exception
    878     // in cases where this bug would have occurred, so that people can decide what to do.
    879     // However, the evolution test can occasionally trigger the bug (depending on the random path
    880     // it takes). Rather than try to avoid it, we disable the exception-throwing, because the bug
    881     // is actually fixed, and the exception is only there to raise awareness of the compatibility
    882     // concerns.
    883     //
    884     // On Linux, seed 1467142714 (for example) will trigger the exception (without this env var).
    885 #if defined(__MINGW32__) || defined(_MSC_VER)
    886     putenv("CAPNP_IGNORE_ISSUE_344=1");
    887 #else
    888     setenv("CAPNP_IGNORE_ISSUE_344", "1", true);
    889 #endif
    890 
    891     srand(seed);
    892 
    893     {
    894       kj::String text = kj::str(
    895           "Randomly testing backwards-compatibility scenarios with seed: ", seed, "\n");
    896       kj::FdOutputStream(STDOUT_FILENO).write(text.begin(), text.size());
    897     }
    898 
    899     KJ_CONTEXT(seed, "PLEASE REPORT THIS FAILURE AND INCLUDE THE SEED");
    900 
    901     doTest();
    902 
    903     return true;
    904   }
    905 
    906 private:
    907   kj::ProcessContext& context;
    908   uint seed = time(nullptr);
    909 };
    910 
    911 }  // namespace
    912 }  // namespace compiler
    913 }  // namespace capnp
    914 
    915 KJ_MAIN(capnp::compiler::EvolutionTestMain);