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);