schema-loader.c++ 53.6 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
21

Kenton Varda's avatar
Kenton Varda committed
22
#define CAPNP_PRIVATE
23 24 25 26 27
#include "schema-loader.h"
#include <unordered_map>
#include <map>
#include "message.h"
#include "arena.h"
Kenton Varda's avatar
Kenton Varda committed
28
#include <kj/debug.h>
29
#include <kj/exception.h>
30
#include <kj/arena.h>
31 32
#include <kj/vector.h>
#include <algorithm>
33

34
namespace capnp {
35

36 37 38 39
bool hasDiscriminantValue(const schema::Field::Reader& reader) {
  return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
}

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
class SchemaLoader::InitializerImpl: public _::RawSchema::Initializer {
public:
  inline explicit InitializerImpl(const SchemaLoader& loader): loader(loader), callback(nullptr) {}
  inline InitializerImpl(const SchemaLoader& loader, const LazyLoadCallback& callback)
      : loader(loader), callback(callback) {}

  inline kj::Maybe<const LazyLoadCallback&> getCallback() const { return callback; }

  void init(const _::RawSchema* schema) const override;

  inline bool operator==(decltype(nullptr)) const { return callback == nullptr; }

private:
  const SchemaLoader& loader;
  kj::Maybe<const LazyLoadCallback&> callback;
};

57 58
class SchemaLoader::Impl {
public:
59 60 61 62
  inline explicit Impl(const SchemaLoader& loader): initializer(loader) {}
  inline Impl(const SchemaLoader& loader, const LazyLoadCallback& callback)
      : initializer(loader, callback) {}

Kenton Varda's avatar
Kenton Varda committed
63
  _::RawSchema* load(const schema::Node::Reader& reader, bool isPlaceholder);
64

65
  _::RawSchema* loadNative(const _::RawSchema* nativeSchema);
66

67 68
  _::RawSchema* loadEmpty(uint64_t id, kj::StringPtr name, schema::Node::Which kind,
                          bool isPlaceholder);
69 70
  // Create a dummy empty schema of the given kind for the given id and load it.

71 72 73 74 75 76
  struct TryGetResult {
    _::RawSchema* schema;
    kj::Maybe<const LazyLoadCallback&> callback;
  };

  TryGetResult tryGet(uint64_t typeId) const;
77
  kj::Array<Schema> getAllLoaded() const;
78

Kenton Varda's avatar
Kenton Varda committed
79
  void requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount,
Kenton Varda's avatar
Kenton Varda committed
80
                         schema::ElementSize preferredListEncoding);
81 82 83 84 85 86
  // Require any struct nodes loaded with this ID -- in the past and in the future -- to have at
  // least the given sizes.  Struct nodes that don't comply will simply be rewritten to comply.
  // This is used to ensure that parents of group nodes have at least the size of the group node,
  // so that allocating a struct that contains a group then getting the group node and setting
  // its fields can't possibly write outside of the allocated space.

87
  kj::Arena arena;
88 89

private:
90
  std::unordered_map<uint64_t, _::RawSchema*> schemas;
91

92 93 94
  struct RequiredSize {
    uint16_t dataWordCount;
    uint16_t pointerCount;
Kenton Varda's avatar
Kenton Varda committed
95
    schema::ElementSize preferredListEncoding;
96 97 98
  };
  std::unordered_map<uint64_t, RequiredSize> structSizeRequirements;

99
  InitializerImpl initializer;
100

Kenton Varda's avatar
Kenton Varda committed
101
  kj::ArrayPtr<word> makeUncheckedNode(schema::Node::Reader node);
102 103 104
  // Construct a copy of the given schema node, allocated as a single-segment ("unchecked") node
  // within the loader's arena.

Kenton Varda's avatar
Kenton Varda committed
105
  kj::ArrayPtr<word> makeUncheckedNodeEnforcingSizeRequirements(schema::Node::Reader node);
106 107 108 109 110 111 112
  // Like makeUncheckedNode() but if structSizeRequirements has a requirement for this node which
  // is larger than the node claims to be, the size will be edited to comply.  This should be rare.
  // If the incoming node is not a struct, any struct size requirements will be ignored, but if
  // such requirements exist, this indicates an inconsistency that could cause exceptions later on
  // (but at least can't cause memory corruption).

  kj::ArrayPtr<word> rewriteStructNodeWithSizes(
Kenton Varda's avatar
Kenton Varda committed
113 114
      schema::Node::Reader node, uint dataWordCount, uint pointerCount,
      schema::ElementSize preferredListEncoding);
115 116 117 118 119
  // Make a copy of the given node (which must be a struct node) and set its sizes to be the max
  // of what it said already and the given sizes.

  // If the encoded node does not meet the given struct size requirements, make a new copy that
  // does.
Kenton Varda's avatar
Kenton Varda committed
120
  void applyStructSizeRequirement(_::RawSchema* raw, uint dataWordCount, uint pointerCount,
Kenton Varda's avatar
Kenton Varda committed
121
                                  schema::ElementSize preferredListEncoding);
122 123 124 125
};

// =======================================================================================

126 127 128 129
inline static void verifyVoid(Void value) {}
// Calls to this will break if the parameter type changes to non-void.  We use this to detect
// when the code needs updating.

130 131 132 133
class SchemaLoader::Validator {
public:
  Validator(SchemaLoader::Impl& loader): loader(loader) {}

Kenton Varda's avatar
Kenton Varda committed
134
  bool validate(const schema::Node::Reader& node) {
135 136 137 138
    isValid = true;
    nodeName = node.getDisplayName();
    dependencies.clear();

139
    KJ_CONTEXT("validating schema node", nodeName, (uint)node.which());
140

141
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
142
      case schema::Node::FILE:
143
        verifyVoid(node.getFile());
144
        break;
Kenton Varda's avatar
Kenton Varda committed
145
      case schema::Node::STRUCT:
146
        validate(node.getStruct(), node.getScopeId());
147
        break;
Kenton Varda's avatar
Kenton Varda committed
148
      case schema::Node::ENUM:
149
        validate(node.getEnum());
150
        break;
Kenton Varda's avatar
Kenton Varda committed
151
      case schema::Node::INTERFACE:
152
        validate(node.getInterface());
153
        break;
Kenton Varda's avatar
Kenton Varda committed
154
      case schema::Node::CONST:
155
        validate(node.getConst());
156
        break;
Kenton Varda's avatar
Kenton Varda committed
157
      case schema::Node::ANNOTATION:
158
        validate(node.getAnnotation());
159 160 161 162 163 164 165
        break;
    }

    // We accept and pass through node types we don't recognize.
    return isValid;
  }

166
  const _::RawSchema** makeDependencyArray(uint32_t* count) {
167
    *count = dependencies.size();
168 169
    kj::ArrayPtr<const _::RawSchema*> result =
        loader.arena.allocateArray<const _::RawSchema*>(*count);
170 171 172 173
    uint pos = 0;
    for (auto& dep: dependencies) {
      result[pos++] = dep.second;
    }
174
    KJ_DASSERT(pos == *count);
175
    return result.begin();
176 177
  }

178
  const uint16_t* makeMemberInfoArray(uint32_t* count) {
179
    *count = members.size();
180
    kj::ArrayPtr<uint16_t> result = loader.arena.allocateArray<uint16_t>(*count);
181 182
    uint pos = 0;
    for (auto& member: members) {
183
      result[pos++] = member.second;
184
    }
185
    KJ_DASSERT(pos == *count);
186
    return result.begin();
187 188
  }

189 190 191 192
  const uint16_t* makeMembersByDiscriminantArray() {
    return membersByDiscriminant.begin();
  }

193 194 195 196
private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
  bool isValid;
197
  std::map<uint64_t, _::RawSchema*> dependencies;
198

199 200 201 202
  // Maps name -> index for each member.
  std::map<Text::Reader, uint> members;

  kj::ArrayPtr<uint16_t> membersByDiscriminant;
203 204

#define VALIDATE_SCHEMA(condition, ...) \
205
  KJ_REQUIRE(condition, ##__VA_ARGS__) { isValid = false; return; }
206
#define FAIL_VALIDATE_SCHEMA(...) \
207
  KJ_FAIL_REQUIRE(__VA_ARGS__) { isValid = false; return; }
208

209 210 211
  void validateMemberName(kj::StringPtr name, uint index) {
    bool isNewName = members.insert(std::make_pair(name, index)).second;
    VALIDATE_SCHEMA(isNewName, "duplicate name", name);
Kenton Varda's avatar
Kenton Varda committed
212 213
  }

Kenton Varda's avatar
Kenton Varda committed
214
  void validate(const schema::Node::Struct::Reader& structNode, uint64_t scopeId) {
215 216 217 218
    uint dataSizeInBits;
    uint pointerCount;

    switch (structNode.getPreferredListEncoding()) {
Kenton Varda's avatar
Kenton Varda committed
219
      case schema::ElementSize::EMPTY:
220 221 222
        dataSizeInBits = 0;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
223
      case schema::ElementSize::BIT:
224 225 226
        dataSizeInBits = 1;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
227
      case schema::ElementSize::BYTE:
228 229 230
        dataSizeInBits = 8;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
231
      case schema::ElementSize::TWO_BYTES:
232 233 234
        dataSizeInBits = 16;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
235
      case schema::ElementSize::FOUR_BYTES:
236 237 238
        dataSizeInBits = 32;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
239
      case schema::ElementSize::EIGHT_BYTES:
240 241 242
        dataSizeInBits = 64;
        pointerCount = 0;
        break;
Kenton Varda's avatar
Kenton Varda committed
243
      case schema::ElementSize::POINTER:
244 245 246
        dataSizeInBits = 0;
        pointerCount = 1;
        break;
Kenton Varda's avatar
Kenton Varda committed
247
      case schema::ElementSize::INLINE_COMPOSITE:
248 249
        dataSizeInBits = structNode.getDataWordCount() * 64;
        pointerCount = structNode.getPointerCount();
250 251
        break;
      default:
252
        FAIL_VALIDATE_SCHEMA("invalid preferredListEncoding");
253 254 255 256 257
        dataSizeInBits = 0;
        pointerCount = 0;
        break;
    }

258 259
    VALIDATE_SCHEMA(structNode.getDataWordCount() == (dataSizeInBits + 63) / 64 &&
                    structNode.getPointerCount() == pointerCount,
260
                    "struct size does not match preferredListEncoding");
261

262
    auto fields = structNode.getFields();
263

264
    KJ_STACK_ARRAY(bool, sawCodeOrder, fields.size(), 32, 256);
265 266
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

267 268 269 270 271 272 273 274 275 276 277 278
    KJ_STACK_ARRAY(bool, sawDiscriminantValue, structNode.getDiscriminantCount(), 32, 256);
    memset(sawDiscriminantValue.begin(), 0,
           sawDiscriminantValue.size() * sizeof(sawDiscriminantValue[0]));

    if (structNode.getDiscriminantCount() > 0) {
      VALIDATE_SCHEMA(structNode.getDiscriminantCount() != 1,
                      "union must have at least two members");
      VALIDATE_SCHEMA(structNode.getDiscriminantCount() <= fields.size(),
                      "struct can't have more union fields than total fields");

      VALIDATE_SCHEMA((structNode.getDiscriminantOffset() + 1) * 16 <= dataSizeInBits,
                      "union discriminant is out-of-bounds");
279 280
    }

281 282 283
    membersByDiscriminant = loader.arena.allocateArray<uint16_t>(fields.size());
    uint discriminantPos = 0;
    uint nonDiscriminantPos = structNode.getDiscriminantCount();
284

285 286 287 288
    uint index = 0;
    uint nextOrdinal = 0;
    for (auto field: fields) {
      KJ_CONTEXT("validating struct field", field.getName());
289

290 291 292 293 294
      validateMemberName(field.getName(), index);
      VALIDATE_SCHEMA(field.getCodeOrder() < sawCodeOrder.size() &&
                      !sawCodeOrder[field.getCodeOrder()],
                      "invalid codeOrder");
      sawCodeOrder[field.getCodeOrder()] = true;
295

296
      auto ordinal = field.getOrdinal();
297
      if (ordinal.isExplicit()) {
298 299 300
        VALIDATE_SCHEMA(ordinal.getExplicit() >= nextOrdinal,
                        "fields were not ordered by ordinal");
        nextOrdinal = ordinal.getExplicit() + 1;
301
      }
Kenton Varda's avatar
Kenton Varda committed
302

303
      if (hasDiscriminantValue(field)) {
304 305 306 307 308 309 310 311 312 313 314
        VALIDATE_SCHEMA(field.getDiscriminantValue() < sawDiscriminantValue.size() &&
                        !sawDiscriminantValue[field.getDiscriminantValue()],
                        "invalid discriminantValue");
        sawDiscriminantValue[field.getDiscriminantValue()] = true;

        membersByDiscriminant[discriminantPos++] = index;
      } else {
        VALIDATE_SCHEMA(nonDiscriminantPos <= fields.size(),
                        "discriminantCount did not match fields");
        membersByDiscriminant[nonDiscriminantPos++] = index;
      }
Kenton Varda's avatar
Kenton Varda committed
315

316
      switch (field.which()) {
317 318
        case schema::Field::SLOT: {
          auto slot = field.getSlot();
Kenton Varda's avatar
Kenton Varda committed
319

320 321
          uint fieldBits = 0;
          bool fieldIsPointer = false;
322 323 324
          validate(slot.getType(), slot.getDefaultValue(), &fieldBits, &fieldIsPointer);
          VALIDATE_SCHEMA(fieldBits * (slot.getOffset() + 1) <= dataSizeInBits &&
                          fieldIsPointer * (slot.getOffset() + 1) <= pointerCount,
325
                          "field offset out-of-bounds",
326
                          slot.getOffset(), dataSizeInBits, pointerCount);
Kenton Varda's avatar
Kenton Varda committed
327

328
          break;
Kenton Varda's avatar
Kenton Varda committed
329 330
        }

Kenton Varda's avatar
Kenton Varda committed
331
        case schema::Field::GROUP:
332
          // Require that the group is a struct node.
333
          validateTypeId(field.getGroup().getTypeId(), schema::Node::STRUCT);
334
          break;
Kenton Varda's avatar
Kenton Varda committed
335
      }
336 337

      ++index;
338 339
    }

340 341 342 343 344 345
    // If the above code is correct, these should pass.
    KJ_ASSERT(discriminantPos == structNode.getDiscriminantCount());
    KJ_ASSERT(nonDiscriminantPos == fields.size());

    if (structNode.getIsGroup()) {
      VALIDATE_SCHEMA(scopeId != 0, "group node missing scopeId");
346

347 348
      // Require that the group's scope has at least the same size as the group, so that anyone
      // constructing an instance of the outer scope can safely read/write the group.
349 350
      loader.requireStructSize(scopeId, structNode.getDataWordCount(),
                               structNode.getPointerCount(),
Kenton Varda's avatar
Kenton Varda committed
351
                               structNode.getPreferredListEncoding());
352 353

      // Require that the parent type is a struct.
Kenton Varda's avatar
Kenton Varda committed
354
      validateTypeId(scopeId, schema::Node::STRUCT);
355 356 357
    }
  }

358 359
  void validate(const schema::Node::Enum::Reader& enumNode) {
    auto enumerants = enumNode.getEnumerants();
360
    KJ_STACK_ARRAY(bool, sawCodeOrder, enumerants.size(), 32, 256);
361 362 363 364
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto enumerant: enumerants) {
365
      validateMemberName(enumerant.getName(), index++);
366 367 368 369 370 371 372 373

      VALIDATE_SCHEMA(enumerant.getCodeOrder() < enumerants.size() &&
                      !sawCodeOrder[enumerant.getCodeOrder()],
                      "invalid codeOrder", enumerant.getName());
      sawCodeOrder[enumerant.getCodeOrder()] = true;
    }
  }

374
  void validate(const schema::Node::Interface::Reader& interfaceNode) {
375 376 377 378
    for (auto extend: interfaceNode.getExtends()) {
      validateTypeId(extend, schema::Node::INTERFACE);
    }

379
    auto methods = interfaceNode.getMethods();
380
    KJ_STACK_ARRAY(bool, sawCodeOrder, methods.size(), 32, 256);
381 382 383 384
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto method: methods) {
385
      KJ_CONTEXT("validating method", method.getName());
386
      validateMemberName(method.getName(), index++);
387 388 389 390 391 392

      VALIDATE_SCHEMA(method.getCodeOrder() < methods.size() &&
                      !sawCodeOrder[method.getCodeOrder()],
                      "invalid codeOrder");
      sawCodeOrder[method.getCodeOrder()] = true;

393 394
      validateTypeId(method.getParamStructType(), schema::Node::STRUCT);
      validateTypeId(method.getResultStructType(), schema::Node::STRUCT);
395 396 397
    }
  }

Kenton Varda's avatar
Kenton Varda committed
398
  void validate(const schema::Node::Const::Reader& constNode) {
399 400 401 402 403
    uint dummy1;
    bool dummy2;
    validate(constNode.getType(), constNode.getValue(), &dummy1, &dummy2);
  }

Kenton Varda's avatar
Kenton Varda committed
404
  void validate(const schema::Node::Annotation::Reader& annotationNode) {
405 406 407
    validate(annotationNode.getType());
  }

Kenton Varda's avatar
Kenton Varda committed
408
  void validate(const schema::Type::Reader& type, const schema::Value::Reader& value,
409 410 411
                uint* dataSizeInBits, bool* isPointer) {
    validate(type);

Kenton Varda's avatar
Kenton Varda committed
412
    schema::Value::Which expectedValueType = schema::Value::VOID;
413
    bool hadCase = false;
414
    switch (type.which()) {
415
#define HANDLE_TYPE(name, bits, ptr) \
Kenton Varda's avatar
Kenton Varda committed
416 417
      case schema::Type::name: \
        expectedValueType = schema::Value::name; \
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
        *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)
439
      HANDLE_TYPE(ANY_POINTER, 0, true)
440 441 442 443
#undef HANDLE_TYPE
    }

    if (hadCase) {
444 445
      VALIDATE_SCHEMA(value.which() == expectedValueType, "Value did not match type.",
                      (uint)value.which(), (uint)expectedValueType);
446 447 448
    }
  }

Kenton Varda's avatar
Kenton Varda committed
449
  void validate(const schema::Type::Reader& type) {
450
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
451 452 453 454 455 456 457 458 459 460 461 462 463 464
      case schema::Type::VOID:
      case schema::Type::BOOL:
      case schema::Type::INT8:
      case schema::Type::INT16:
      case schema::Type::INT32:
      case schema::Type::INT64:
      case schema::Type::UINT8:
      case schema::Type::UINT16:
      case schema::Type::UINT32:
      case schema::Type::UINT64:
      case schema::Type::FLOAT32:
      case schema::Type::FLOAT64:
      case schema::Type::TEXT:
      case schema::Type::DATA:
465
      case schema::Type::ANY_POINTER:
466 467
        break;

Kenton Varda's avatar
Kenton Varda committed
468
      case schema::Type::STRUCT:
469
        validateTypeId(type.getStruct().getTypeId(), schema::Node::STRUCT);
470
        break;
Kenton Varda's avatar
Kenton Varda committed
471
      case schema::Type::ENUM:
472
        validateTypeId(type.getEnum().getTypeId(), schema::Node::ENUM);
473
        break;
Kenton Varda's avatar
Kenton Varda committed
474
      case schema::Type::INTERFACE:
475
        validateTypeId(type.getInterface().getTypeId(), schema::Node::INTERFACE);
476 477
        break;

Kenton Varda's avatar
Kenton Varda committed
478
      case schema::Type::LIST:
479
        validate(type.getList().getElementType());
480 481 482 483 484 485
        break;
    }

    // We intentionally allow unknown types.
  }

Kenton Varda's avatar
Kenton Varda committed
486
  void validateTypeId(uint64_t id, schema::Node::Which expectedKind) {
487
    _::RawSchema* existing = loader.tryGet(id).schema;
488
    if (existing != nullptr) {
Kenton Varda's avatar
Kenton Varda committed
489
      auto node = readMessageUnchecked<schema::Node>(existing->encodedNode);
490
      VALIDATE_SCHEMA(node.which() == expectedKind,
491
          "expected a different kind of node for this ID",
492
          id, (uint)expectedKind, (uint)node.which(), node.getDisplayName());
493 494 495 496 497
      dependencies.insert(std::make_pair(id, existing));
      return;
    }

    dependencies.insert(std::make_pair(id, loader.loadEmpty(
498
        id, kj::str("(unknown type used by ", nodeName , ")"), expectedKind, true)));
499 500 501 502 503 504 505 506 507 508 509 510
  }

#undef VALIDATE_SCHEMA
#undef FAIL_VALIDATE_SCHEMA
};

// =======================================================================================

class SchemaLoader::CompatibilityChecker {
public:
  CompatibilityChecker(SchemaLoader::Impl& loader): loader(loader) {}

Kenton Varda's avatar
Kenton Varda committed
511 512
  bool shouldReplace(const schema::Node::Reader& existingNode,
                     const schema::Node::Reader& replacement,
513
                     bool preferReplacementIfEquivalent) {
514 515 516
    this->existingNode = existingNode;
    this->replacementNode = replacement;

517 518
    KJ_CONTEXT("checking compatibility with previously-loaded node of the same id",
               existingNode.getDisplayName());
519

520
    KJ_DREQUIRE(existingNode.getId() == replacement.getId());
521 522 523 524 525 526

    nodeName = existingNode.getDisplayName();
    compatibility = EQUIVALENT;

    checkCompatibility(existingNode, replacement);

527 528
    // Prefer the newer schema.
    return preferReplacementIfEquivalent ? compatibility != OLDER : compatibility == NEWER;
529 530 531 532 533
  }

private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
534 535
  schema::Node::Reader existingNode;
  schema::Node::Reader replacementNode;
536 537 538 539 540 541 542 543 544 545

  enum Compatibility {
    EQUIVALENT,
    OLDER,
    NEWER,
    INCOMPATIBLE
  };
  Compatibility compatibility;

#define VALIDATE_SCHEMA(condition, ...) \
546
  KJ_REQUIRE(condition, ##__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
547
#define FAIL_VALIDATE_SCHEMA(...) \
548
  KJ_FAIL_REQUIRE(__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581

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

Kenton Varda's avatar
Kenton Varda committed
582 583
  void checkCompatibility(const schema::Node::Reader& node,
                          const schema::Node::Reader& replacement) {
584 585 586
    // Returns whether `replacement` is equivalent, older than, newer than, or incompatible with
    // `node`.  If exceptions are enabled, this will throw an exception on INCOMPATIBLE.

587
    VALIDATE_SCHEMA(node.which() == replacement.which(),
588 589 590 591 592 593
                    "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.

594
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
595
      case schema::Node::FILE:
596
        verifyVoid(node.getFile());
597
        break;
Kenton Varda's avatar
Kenton Varda committed
598
      case schema::Node::STRUCT:
599 600
        checkCompatibility(node.getStruct(), replacement.getStruct(),
                           node.getScopeId(), replacement.getScopeId());
601
        break;
Kenton Varda's avatar
Kenton Varda committed
602
      case schema::Node::ENUM:
603
        checkCompatibility(node.getEnum(), replacement.getEnum());
604
        break;
Kenton Varda's avatar
Kenton Varda committed
605
      case schema::Node::INTERFACE:
606
        checkCompatibility(node.getInterface(), replacement.getInterface());
607
        break;
Kenton Varda's avatar
Kenton Varda committed
608
      case schema::Node::CONST:
609
        checkCompatibility(node.getConst(), replacement.getConst());
610
        break;
Kenton Varda's avatar
Kenton Varda committed
611
      case schema::Node::ANNOTATION:
612
        checkCompatibility(node.getAnnotation(), replacement.getAnnotation());
613 614 615 616
        break;
    }
  }

Kenton Varda's avatar
Kenton Varda committed
617 618
  void checkCompatibility(const schema::Node::Struct::Reader& structNode,
                          const schema::Node::Struct::Reader& replacement,
619
                          uint64_t scopeId, uint64_t replacementScopeId) {
620
    if (replacement.getDataWordCount() > structNode.getDataWordCount()) {
621
      replacementIsNewer();
622
    } else if (replacement.getDataWordCount() < structNode.getDataWordCount()) {
623 624
      replacementIsOlder();
    }
625
    if (replacement.getPointerCount() > structNode.getPointerCount()) {
626
      replacementIsNewer();
627
    } else if (replacement.getPointerCount() < structNode.getPointerCount()) {
628 629 630 631 632 633 634 635 636 637 638 639 640
      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();
    }

641 642 643 644 645 646 647 648 649 650 651
    if (replacement.getDiscriminantCount() > structNode.getDiscriminantCount()) {
      replacementIsNewer();
    } else if (replacement.getDiscriminantCount() < structNode.getDiscriminantCount()) {
      replacementIsOlder();
    }

    if (replacement.getDiscriminantCount() > 0 && structNode.getDiscriminantCount() > 0) {
      VALIDATE_SCHEMA(replacement.getDiscriminantOffset() == structNode.getDiscriminantOffset(),
                      "union discriminant position changed");
    }

652 653
    // The shared members should occupy corresponding positions in the member lists, since the
    // lists are sorted by ordinal.
654 655 656
    auto fields = structNode.getFields();
    auto replacementFields = replacement.getFields();
    uint count = std::min(fields.size(), replacementFields.size());
657

658
    if (replacementFields.size() > fields.size()) {
659
      replacementIsNewer();
660
    } else if (replacementFields.size() < fields.size()) {
661 662 663 664
      replacementIsOlder();
    }

    for (uint i = 0; i < count; i++) {
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
      checkCompatibility(fields[i], replacementFields[i]);
    }

    // For the moment, we allow "upgrading" from non-group to group, mainly so that the
    // placeholders we generate for group parents (which in the absence of more info, we assume to
    // be non-groups) can be replaced with groups.
    //
    // TODO(cleanup):  The placeholder approach is really breaking down.  Maybe we need to maintain
    //   a list of expectations for nodes we haven't loaded yet.
    if (structNode.getIsGroup()) {
      if (replacement.getIsGroup()) {
        VALIDATE_SCHEMA(replacementScopeId == scopeId, "group node's scope changed");
      } else {
        replacementIsOlder();
      }
    } else {
      if (replacement.getIsGroup()) {
        replacementIsNewer();
      }
684 685 686
    }
  }

Kenton Varda's avatar
Kenton Varda committed
687 688
  void checkCompatibility(const schema::Field::Reader& field,
                          const schema::Field::Reader& replacement) {
689
    KJ_CONTEXT("comparing struct field", field.getName());
690

691 692
    // A field that is initially not in a union can be upgraded to be in one, as long as it has
    // discriminant 0.
693
    uint discriminant = hasDiscriminantValue(field) ? field.getDiscriminantValue() : 0;
694
    uint replacementDiscriminant =
695
        hasDiscriminantValue(replacement) ? replacement.getDiscriminantValue() : 0;
696
    VALIDATE_SCHEMA(discriminant == replacementDiscriminant, "Field discriminant changed.");
697

698
    switch (field.which()) {
699 700
      case schema::Field::SLOT: {
        auto slot = field.getSlot();
701

702
        switch (replacement.which()) {
703 704
          case schema::Field::SLOT: {
            auto replacementSlot = replacement.getSlot();
705

706
            checkCompatibility(slot.getType(), replacementSlot.getType(),
707
                               NO_UPGRADE_TO_STRUCT);
708 709
            checkDefaultCompatibility(slot.getDefaultValue(),
                                      replacementSlot.getDefaultValue());
710

711
            VALIDATE_SCHEMA(slot.getOffset() == replacementSlot.getOffset(),
712 713 714 715
                            "field position changed");
            break;
          }
          case schema::Field::GROUP:
716
            checkUpgradeToStruct(slot.getType(), replacement.getGroup().getTypeId(),
717
                                 existingNode, field);
718 719
            break;
        }
720 721 722

        break;
      }
Kenton Varda's avatar
Kenton Varda committed
723

Kenton Varda's avatar
Kenton Varda committed
724
      case schema::Field::GROUP:
725
        switch (replacement.which()) {
726 727
          case schema::Field::SLOT:
            checkUpgradeToStruct(replacement.getSlot().getType(), field.getGroup().getTypeId(),
728 729 730
                                 replacementNode, replacement);
            break;
          case schema::Field::GROUP:
731 732
            VALIDATE_SCHEMA(field.getGroup().getTypeId() == replacement.getGroup().getTypeId(),
                            "group id changed");
733 734
            break;
        }
735 736 737 738
        break;
    }
  }

739 740 741 742
  void checkCompatibility(const schema::Node::Enum::Reader& enumNode,
                          const schema::Node::Enum::Reader& replacement) {
    uint size = enumNode.getEnumerants().size();
    uint replacementSize = replacement.getEnumerants().size();
743 744 745 746 747 748 749
    if (replacementSize > size) {
      replacementIsNewer();
    } else if (replacementSize < size) {
      replacementIsOlder();
    }
  }

750 751
  void checkCompatibility(const schema::Node::Interface::Reader& interfaceNode,
                          const schema::Node::Interface::Reader& replacement) {
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
    {
      // Check superclasses.

      kj::Vector<uint64_t> extends;
      kj::Vector<uint64_t> replacementExtends;
      for (uint64_t extend: interfaceNode.getExtends()) {
        extends.add(extend);
      }
      for (uint64_t extend: replacement.getExtends()) {
        replacementExtends.add(extend);
      }
      std::sort(extends.begin(), extends.end());
      std::sort(replacementExtends.begin(), replacementExtends.end());

      auto iter = extends.begin();
      auto replacementIter = replacementExtends.begin();

      while (iter != extends.end() || replacementIter != replacementExtends.end()) {
        if (iter == extends.end()) {
          replacementIsNewer();
          break;
        } else if (replacementIter == replacementExtends.end()) {
          replacementIsOlder();
          break;
        } else if (*iter < *replacementIter) {
          replacementIsOlder();
          ++iter;
        } else if (*iter > *replacementIter) {
          replacementIsNewer();
          ++replacementIter;
        } else {
          ++iter;
          ++replacementIter;
        }
      }
    }

789 790 791
    auto methods = interfaceNode.getMethods();
    auto replacementMethods = replacement.getMethods();

792 793 794 795 796 797 798 799 800 801 802 803 804
    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]);
    }
  }

Kenton Varda's avatar
Kenton Varda committed
805 806
  void checkCompatibility(const schema::Method::Reader& method,
                          const schema::Method::Reader& replacement) {
807
    KJ_CONTEXT("comparing method", method.getName());
808

809 810 811 812 813
    // TODO(someday):  Allow named parameter list to be replaced by compatible struct type.
    VALIDATE_SCHEMA(method.getParamStructType() == replacement.getParamStructType(),
                    "Updated method has different parameters.");
    VALIDATE_SCHEMA(method.getResultStructType() == replacement.getResultStructType(),
                    "Updated method has different results.");
814 815
  }

Kenton Varda's avatar
Kenton Varda committed
816 817
  void checkCompatibility(const schema::Node::Const::Reader& constNode,
                          const schema::Node::Const::Reader& replacement) {
818 819 820
    // Who cares?  These don't appear on the wire.
  }

Kenton Varda's avatar
Kenton Varda committed
821 822
  void checkCompatibility(const schema::Node::Annotation::Reader& annotationNode,
                          const schema::Node::Annotation::Reader& replacement) {
823 824 825 826 827 828 829 830
    // Who cares?  These don't appear on the wire.
  }

  enum UpgradeToStructMode {
    ALLOW_UPGRADE_TO_STRUCT,
    NO_UPGRADE_TO_STRUCT
  };

Kenton Varda's avatar
Kenton Varda committed
831 832
  void checkCompatibility(const schema::Type::Reader& type,
                          const schema::Type::Reader& replacement,
833
                          UpgradeToStructMode upgradeToStructMode) {
834
    if (replacement.which() != type.which()) {
835
      // Check for allowed "upgrade" to Data or AnyPointer.
836
      if (replacement.isData() && canUpgradeToData(type)) {
837 838
        replacementIsNewer();
        return;
839
      } else if (type.isData() && canUpgradeToData(replacement)) {
840 841
        replacementIsOlder();
        return;
842
      } else if (replacement.isAnyPointer() && canUpgradeToAnyPointer(type)) {
843 844
        replacementIsNewer();
        return;
845
      } else if (type.isAnyPointer() && canUpgradeToAnyPointer(replacement)) {
846 847 848 849 850
        replacementIsOlder();
        return;
      }

      if (upgradeToStructMode == ALLOW_UPGRADE_TO_STRUCT) {
851
        if (type.isStruct()) {
852
          checkUpgradeToStruct(replacement, type.getStruct().getTypeId());
853
          return;
854
        } else if (replacement.isStruct()) {
855
          checkUpgradeToStruct(type, replacement.getStruct().getTypeId());
856 857 858 859 860 861 862
          return;
        }
      }

      FAIL_VALIDATE_SCHEMA("a type was changed");
    }

863
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
864 865 866 867 868 869 870 871 872 873 874 875 876 877
      case schema::Type::VOID:
      case schema::Type::BOOL:
      case schema::Type::INT8:
      case schema::Type::INT16:
      case schema::Type::INT32:
      case schema::Type::INT64:
      case schema::Type::UINT8:
      case schema::Type::UINT16:
      case schema::Type::UINT32:
      case schema::Type::UINT64:
      case schema::Type::FLOAT32:
      case schema::Type::FLOAT64:
      case schema::Type::TEXT:
      case schema::Type::DATA:
878
      case schema::Type::ANY_POINTER:
879 880
        return;

Kenton Varda's avatar
Kenton Varda committed
881
      case schema::Type::LIST:
882 883
        checkCompatibility(type.getList().getElementType(), replacement.getList().getElementType(),
                           ALLOW_UPGRADE_TO_STRUCT);
884
        return;
885

Kenton Varda's avatar
Kenton Varda committed
886
      case schema::Type::ENUM:
887 888
        VALIDATE_SCHEMA(replacement.getEnum().getTypeId() == type.getEnum().getTypeId(),
                        "type changed enum type");
889 890
        return;

Kenton Varda's avatar
Kenton Varda committed
891
      case schema::Type::STRUCT:
892 893 894 895 896 897 898
        // 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...
899
        VALIDATE_SCHEMA(replacement.getStruct().getTypeId() == type.getStruct().getTypeId(),
900 901 902
                        "type changed to incompatible struct type");
        return;

Kenton Varda's avatar
Kenton Varda committed
903
      case schema::Type::INTERFACE:
904
        VALIDATE_SCHEMA(replacement.getInterface().getTypeId() == type.getInterface().getTypeId(),
905
                        "type changed to incompatible interface type");
906 907 908 909 910 911
        return;
    }

    // We assume unknown types (from newer versions of Cap'n Proto?) are equivalent.
  }

912 913 914
  void checkUpgradeToStruct(const schema::Type::Reader& type, uint64_t structTypeId,
                            kj::Maybe<schema::Node::Reader> matchSize = nullptr,
                            kj::Maybe<schema::Field::Reader> matchPosition = nullptr) {
915 916 917 918 919 920 921
    // 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));
922
    MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
923
    auto node = builder.initRoot<schema::Node>();
924
    node.setId(structTypeId);
925
    node.setDisplayName(kj::str("(unknown type used in ", nodeName, ")"));
926
    auto structNode = node.initStruct();
927

928
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
929
      case schema::Type::VOID:
930 931
        structNode.setDataWordCount(0);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
932
        structNode.setPreferredListEncoding(schema::ElementSize::EMPTY);
933 934
        break;

Kenton Varda's avatar
Kenton Varda committed
935
      case schema::Type::BOOL:
936 937
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
938
        structNode.setPreferredListEncoding(schema::ElementSize::BIT);
939 940
        break;

Kenton Varda's avatar
Kenton Varda committed
941 942
      case schema::Type::INT8:
      case schema::Type::UINT8:
943 944
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
945
        structNode.setPreferredListEncoding(schema::ElementSize::BYTE);
946 947
        break;

Kenton Varda's avatar
Kenton Varda committed
948 949 950
      case schema::Type::INT16:
      case schema::Type::UINT16:
      case schema::Type::ENUM:
951 952
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
953
        structNode.setPreferredListEncoding(schema::ElementSize::TWO_BYTES);
954 955
        break;

Kenton Varda's avatar
Kenton Varda committed
956 957 958
      case schema::Type::INT32:
      case schema::Type::UINT32:
      case schema::Type::FLOAT32:
959 960
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
961
        structNode.setPreferredListEncoding(schema::ElementSize::FOUR_BYTES);
962 963
        break;

Kenton Varda's avatar
Kenton Varda committed
964 965 966
      case schema::Type::INT64:
      case schema::Type::UINT64:
      case schema::Type::FLOAT64:
967 968
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
Kenton Varda's avatar
Kenton Varda committed
969
        structNode.setPreferredListEncoding(schema::ElementSize::EIGHT_BYTES);
970 971
        break;

Kenton Varda's avatar
Kenton Varda committed
972 973 974 975 976
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
977
      case schema::Type::ANY_POINTER:
978 979
        structNode.setDataWordCount(0);
        structNode.setPointerCount(1);
Kenton Varda's avatar
Kenton Varda committed
980
        structNode.setPreferredListEncoding(schema::ElementSize::POINTER);
981 982 983
        break;
    }

984 985 986 987 988 989 990
    KJ_IF_MAYBE(s, matchSize) {
      auto match = s->getStruct();
      structNode.setDataWordCount(match.getDataWordCount());
      structNode.setPointerCount(match.getPointerCount());
      structNode.setPreferredListEncoding(match.getPreferredListEncoding());
    }

991 992 993
    auto field = structNode.initFields(1)[0];
    field.setName("member0");
    field.setCodeOrder(0);
994 995
    auto slot = field.initSlot();
    slot.setType(type);
996 997 998 999 1000 1001 1002

    KJ_IF_MAYBE(p, matchPosition) {
      if (p->getOrdinal().isExplicit()) {
        field.getOrdinal().setExplicit(p->getOrdinal().getExplicit());
      } else {
        field.getOrdinal().setImplicit();
      }
1003 1004 1005
      auto matchSlot = p->getSlot();
      slot.setOffset(matchSlot.getOffset());
      slot.setDefaultValue(matchSlot.getDefaultValue());
1006 1007
    } else {
      field.getOrdinal().setExplicit(0);
1008
      slot.setOffset(0);
1009

1010
      schema::Value::Builder value = slot.initDefaultValue();
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
      switch (type.which()) {
        case schema::Type::VOID: value.setVoid(); break;
        case schema::Type::BOOL: value.setBool(false); break;
        case schema::Type::INT8: value.setInt8(0); break;
        case schema::Type::INT16: value.setInt16(0); break;
        case schema::Type::INT32: value.setInt32(0); break;
        case schema::Type::INT64: value.setInt64(0); break;
        case schema::Type::UINT8: value.setUint8(0); break;
        case schema::Type::UINT16: value.setUint16(0); break;
        case schema::Type::UINT32: value.setUint32(0); break;
        case schema::Type::UINT64: value.setUint64(0); break;
        case schema::Type::FLOAT32: value.setFloat32(0); break;
        case schema::Type::FLOAT64: value.setFloat64(0); break;
        case schema::Type::ENUM: value.setEnum(0); break;
        case schema::Type::TEXT: value.adoptText(Orphan<Text>()); break;
        case schema::Type::DATA: value.adoptData(Orphan<Data>()); break;
1027 1028
        case schema::Type::LIST: value.initList(); break;
        case schema::Type::STRUCT: value.initStruct(); break;
1029
        case schema::Type::INTERFACE: value.setInterface(); break;
1030
        case schema::Type::ANY_POINTER: value.initAnyPointer(); break;
1031 1032
      }
    }
1033

1034
    loader.load(node, true);
1035 1036
  }

Kenton Varda's avatar
Kenton Varda committed
1037
  bool canUpgradeToData(const schema::Type::Reader& type) {
1038
    if (type.isText()) {
1039
      return true;
1040
    } else if (type.isList()) {
1041
      switch (type.getList().getElementType().which()) {
Kenton Varda's avatar
Kenton Varda committed
1042 1043
        case schema::Type::INT8:
        case schema::Type::UINT8:
1044 1045 1046 1047 1048 1049 1050 1051 1052
          return true;
        default:
          return false;
      }
    } else {
      return false;
    }
  }

1053
  bool canUpgradeToAnyPointer(const schema::Type::Reader& type) {
1054
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
      case schema::Type::VOID:
      case schema::Type::BOOL:
      case schema::Type::INT8:
      case schema::Type::INT16:
      case schema::Type::INT32:
      case schema::Type::INT64:
      case schema::Type::UINT8:
      case schema::Type::UINT16:
      case schema::Type::UINT32:
      case schema::Type::UINT64:
      case schema::Type::FLOAT32:
      case schema::Type::FLOAT64:
      case schema::Type::ENUM:
1068 1069
        return false;

Kenton Varda's avatar
Kenton Varda committed
1070 1071 1072 1073 1074
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
1075
      case schema::Type::ANY_POINTER:
1076 1077 1078 1079 1080 1081 1082
        return true;
    }

    // Be lenient with unknown types.
    return true;
  }

Kenton Varda's avatar
Kenton Varda committed
1083 1084
  void checkDefaultCompatibility(const schema::Value::Reader& value,
                                 const schema::Value::Reader& replacement) {
1085 1086
    // 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.
1087
    KJ_ASSERT(value.which() == replacement.which()) {
1088 1089 1090 1091
      compatibility = INCOMPATIBLE;
      return;
    }

1092
    switch (value.which()) {
1093
#define HANDLE_TYPE(discrim, name) \
Kenton Varda's avatar
Kenton Varda committed
1094
      case schema::Value::discrim: \
1095
        VALIDATE_SCHEMA(value.get##name() == replacement.get##name(), "default value changed"); \
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
        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

Kenton Varda's avatar
Kenton Varda committed
1112 1113 1114 1115 1116
      case schema::Value::TEXT:
      case schema::Value::DATA:
      case schema::Value::LIST:
      case schema::Value::STRUCT:
      case schema::Value::INTERFACE:
1117
      case schema::Value::ANY_POINTER:
1118 1119 1120 1121 1122 1123 1124 1125 1126
        // 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;
    }
  }
};

// =======================================================================================

Kenton Varda's avatar
Kenton Varda committed
1127
_::RawSchema* SchemaLoader::Impl::load(const schema::Node::Reader& reader, bool isPlaceholder) {
1128
  // Make a copy of the node which can be used unchecked.
1129
  kj::ArrayPtr<word> validated = makeUncheckedNodeEnforcingSizeRequirements(reader);
1130 1131 1132

  // Validate the copy.
  Validator validator(*this);
Kenton Varda's avatar
Kenton Varda committed
1133
  auto validatedReader = readMessageUnchecked<schema::Node>(validated.begin());
1134 1135 1136 1137 1138

  if (!validator.validate(validatedReader)) {
    // Not valid.  Construct an empty schema of the same type and return that.
    return loadEmpty(validatedReader.getId(),
                     validatedReader.getDisplayName(),
1139 1140
                     validatedReader.which(),
                     false);
1141 1142 1143
  }

  // Check if we already have a schema for this ID.
1144
  _::RawSchema*& slot = schemas[validatedReader.getId()];
1145
  bool shouldReplace;
1146 1147
  if (slot == nullptr) {
    // Nope, allocate a new RawSchema.
1148
    slot = &arena.allocate<_::RawSchema>();
1149 1150 1151
    slot->id = validatedReader.getId();
    slot->canCastTo = nullptr;
    shouldReplace = true;
1152 1153
  } else {
    // Yes, check if it is compatible and figure out which schema is newer.
1154 1155 1156 1157 1158 1159 1160

    if (slot->lazyInitializer == nullptr) {
      // The existing slot is not a placeholder, so whether we overwrite it or not, we cannot
      // end up with a placeholder.
      isPlaceholder = false;
    }

Kenton Varda's avatar
Kenton Varda committed
1161
    auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
1162
    CompatibilityChecker checker(*this);
1163 1164 1165 1166 1167

    // Prefer to replace the existing schema if the existing schema is a placeholder.  Otherwise,
    // prefer to keep the existing schema.
    shouldReplace = checker.shouldReplace(
        existing, validatedReader, slot->lazyInitializer != nullptr);
1168 1169
  }

1170 1171 1172
  if (shouldReplace) {
    // Initialize the RawSchema.
    slot->encodedNode = validated.begin();
Kenton Varda's avatar
Kenton Varda committed
1173
    slot->encodedSize = validated.size();
1174 1175
    slot->dependencies = validator.makeDependencyArray(&slot->dependencyCount);
    slot->membersByName = validator.makeMemberInfoArray(&slot->memberCount);
1176
    slot->membersByDiscriminant = validator.makeMembersByDiscriminantArray();
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
  }

  if (isPlaceholder) {
    slot->lazyInitializer = &initializer;
  } else {
    // If this schema is not newly-allocated, it may already be in the wild, specifically in the
    // dependency list of other schemas.  Once the initializer is null, it is live, so we must do
    // a release-store here.
    __atomic_store_n(&slot->lazyInitializer, nullptr, __ATOMIC_RELEASE);
  }
1187 1188 1189 1190

  return slot;
}

1191
_::RawSchema* SchemaLoader::Impl::loadNative(const _::RawSchema* nativeSchema) {
1192 1193
  _::RawSchema*& slot = schemas[nativeSchema->id];
  bool shouldReplace;
1194
  if (slot == nullptr) {
1195
    slot = &arena.allocate<_::RawSchema>();
1196
    shouldReplace = true;
1197
  } else if (slot->canCastTo != nullptr) {
1198 1199
    // Already loaded natively, or we're currently in the process of loading natively and there
    // was a dependency cycle.
1200
    KJ_REQUIRE(slot->canCastTo == nativeSchema,
1201
        "two different compiled-in type have the same type ID",
1202
        nativeSchema->id,
Kenton Varda's avatar
Kenton Varda committed
1203 1204
        readMessageUnchecked<schema::Node>(nativeSchema->encodedNode).getDisplayName(),
        readMessageUnchecked<schema::Node>(slot->canCastTo->encodedNode).getDisplayName());
1205 1206
    return slot;
  } else {
Kenton Varda's avatar
Kenton Varda committed
1207 1208
    auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
    auto native = readMessageUnchecked<schema::Node>(nativeSchema->encodedNode);
1209
    CompatibilityChecker checker(*this);
1210
    shouldReplace = checker.shouldReplace(existing, native, true);
1211 1212
  }

1213 1214 1215 1216 1217
  // Since we recurse below, the slot in the hash map could move around.  Copy out the pointer
  // for subsequent use.
  _::RawSchema* result = slot;

  if (shouldReplace) {
1218 1219 1220 1221 1222
    // Set the schema to a copy of the native schema, but make sure not to null out lazyInitializer
    // yet.
    _::RawSchema temp = *nativeSchema;
    temp.lazyInitializer = result->lazyInitializer;
    *result = temp;
1223 1224 1225 1226 1227

    // Indicate that casting is safe.  Note that it's important to set this before recursively
    // loading dependencies, so that cycles don't cause infinite loops!
    result->canCastTo = nativeSchema;

1228
    // We need to set the dependency list to point at other loader-owned RawSchemas.
1229 1230 1231 1232 1233 1234
    kj::ArrayPtr<const _::RawSchema*> dependencies =
        arena.allocateArray<const _::RawSchema*>(result->dependencyCount);
    for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
      dependencies[i] = loadNative(nativeSchema->dependencies[i]);
    }
    result->dependencies = dependencies.begin();
1235 1236 1237 1238 1239

    // If there is a struct size requirement, we need to make sure that it is satisfied.
    auto reqIter = structSizeRequirements.find(nativeSchema->id);
    if (reqIter != structSizeRequirements.end()) {
      applyStructSizeRequirement(result, reqIter->second.dataWordCount,
Kenton Varda's avatar
Kenton Varda committed
1240 1241
                                 reqIter->second.pointerCount,
                                 reqIter->second.preferredListEncoding);
1242
    }
1243 1244
  } else {
    // The existing schema is newer.
1245

1246 1247 1248
    // Indicate that casting is safe.  Note that it's important to set this before recursively
    // loading dependencies, so that cycles don't cause infinite loops!
    result->canCastTo = nativeSchema;
1249

1250 1251 1252 1253
    // Make sure the dependencies are loaded and compatible.
    for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
      loadNative(nativeSchema->dependencies[i]);
    }
1254 1255
  }

1256 1257 1258 1259 1260 1261
  // If this schema is not newly-allocated, it may already be in the wild, specifically in the
  // dependency list of other schemas.  Once the initializer is null, it is live, so we must do
  // a release-store here.
  __atomic_store_n(&result->lazyInitializer, nullptr, __ATOMIC_RELEASE);

  return result;
1262 1263
}

1264
_::RawSchema* SchemaLoader::Impl::loadEmpty(
1265
    uint64_t id, kj::StringPtr name, schema::Node::Which kind, bool isPlaceholder) {
1266 1267
  word scratch[32];
  memset(scratch, 0, sizeof(scratch));
1268
  MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
1269
  auto node = builder.initRoot<schema::Node>();
1270 1271 1272
  node.setId(id);
  node.setDisplayName(name);
  switch (kind) {
Kenton Varda's avatar
Kenton Varda committed
1273
    case schema::Node::STRUCT: node.initStruct(); break;
1274 1275
    case schema::Node::ENUM: node.initEnum(); break;
    case schema::Node::INTERFACE: node.initInterface(); break;
1276

Kenton Varda's avatar
Kenton Varda committed
1277 1278 1279
    case schema::Node::FILE:
    case schema::Node::CONST:
    case schema::Node::ANNOTATION:
1280
      KJ_FAIL_REQUIRE("Not a type.");
1281 1282 1283
      break;
  }

1284
  return load(node, isPlaceholder);
1285 1286
}

1287
SchemaLoader::Impl::TryGetResult SchemaLoader::Impl::tryGet(uint64_t typeId) const {
1288 1289
  auto iter = schemas.find(typeId);
  if (iter == schemas.end()) {
1290
    return {nullptr, initializer.getCallback()};
1291
  } else {
1292
    return {iter->second, initializer.getCallback()};
1293 1294 1295
  }
}

1296
kj::Array<Schema> SchemaLoader::Impl::getAllLoaded() const {
1297 1298 1299 1300 1301 1302
  size_t count = 0;
  for (auto& schema: schemas) {
    if (schema.second->lazyInitializer == nullptr) ++count;
  }

  kj::Array<Schema> result = kj::heapArray<Schema>(count);
1303 1304
  size_t i = 0;
  for (auto& schema: schemas) {
1305
    if (schema.second->lazyInitializer == nullptr) result[i++] = Schema(schema.second);
1306 1307 1308 1309
  }
  return result;
}

Kenton Varda's avatar
Kenton Varda committed
1310
void SchemaLoader::Impl::requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount,
Kenton Varda's avatar
Kenton Varda committed
1311
                                           schema::ElementSize preferredListEncoding) {
1312 1313 1314 1315
  auto& slot = structSizeRequirements[id];
  slot.dataWordCount = kj::max(slot.dataWordCount, dataWordCount);
  slot.pointerCount = kj::max(slot.pointerCount, pointerCount);

Kenton Varda's avatar
Kenton Varda committed
1316
  if (slot.dataWordCount + slot.pointerCount >= 2) {
Kenton Varda's avatar
Kenton Varda committed
1317
    slot.preferredListEncoding = schema::ElementSize::INLINE_COMPOSITE;
Kenton Varda's avatar
Kenton Varda committed
1318 1319 1320 1321
  } else {
    slot.preferredListEncoding = kj::max(slot.preferredListEncoding, preferredListEncoding);
  }

1322 1323
  auto iter = schemas.find(id);
  if (iter != schemas.end()) {
Kenton Varda's avatar
Kenton Varda committed
1324
    applyStructSizeRequirement(iter->second, dataWordCount, pointerCount, preferredListEncoding);
1325 1326 1327
  }
}

Kenton Varda's avatar
Kenton Varda committed
1328
kj::ArrayPtr<word> SchemaLoader::Impl::makeUncheckedNode(schema::Node::Reader node) {
1329
  size_t size = node.totalSize().wordCount + 1;
1330 1331 1332 1333 1334 1335 1336
  kj::ArrayPtr<word> result = arena.allocateArray<word>(size);
  memset(result.begin(), 0, size * sizeof(word));
  copyToUnchecked(node, result);
  return result;
}

kj::ArrayPtr<word> SchemaLoader::Impl::makeUncheckedNodeEnforcingSizeRequirements(
Kenton Varda's avatar
Kenton Varda committed
1337
    schema::Node::Reader node) {
1338
  if (node.isStruct()) {
1339 1340 1341 1342
    auto iter = structSizeRequirements.find(node.getId());
    if (iter != structSizeRequirements.end()) {
      auto requirement = iter->second;
      auto structNode = node.getStruct();
1343 1344
      if (structNode.getDataWordCount() < requirement.dataWordCount ||
          structNode.getPointerCount() < requirement.pointerCount ||
Kenton Varda's avatar
Kenton Varda committed
1345
          structNode.getPreferredListEncoding() < requirement.preferredListEncoding) {
1346
        return rewriteStructNodeWithSizes(node, requirement.dataWordCount,
Kenton Varda's avatar
Kenton Varda committed
1347 1348
                                          requirement.pointerCount,
                                          requirement.preferredListEncoding);
1349 1350 1351 1352 1353 1354 1355 1356
      }
    }
  }

  return makeUncheckedNode(node);
}

kj::ArrayPtr<word> SchemaLoader::Impl::rewriteStructNodeWithSizes(
Kenton Varda's avatar
Kenton Varda committed
1357 1358
    schema::Node::Reader node, uint dataWordCount, uint pointerCount,
    schema::ElementSize preferredListEncoding) {
1359 1360 1361
  MallocMessageBuilder builder;
  builder.setRoot(node);

Kenton Varda's avatar
Kenton Varda committed
1362
  auto root = builder.getRoot<schema::Node>();
1363
  auto newStruct = root.getStruct();
1364 1365
  newStruct.setDataWordCount(kj::max(newStruct.getDataWordCount(), dataWordCount));
  newStruct.setPointerCount(kj::max(newStruct.getPointerCount(), pointerCount));
1366

1367
  if (newStruct.getDataWordCount() + newStruct.getPointerCount() >= 2) {
Kenton Varda's avatar
Kenton Varda committed
1368
    newStruct.setPreferredListEncoding(schema::ElementSize::INLINE_COMPOSITE);
Kenton Varda's avatar
Kenton Varda committed
1369 1370 1371 1372 1373
  } else {
    newStruct.setPreferredListEncoding(
        kj::max(newStruct.getPreferredListEncoding(), preferredListEncoding));
  }

1374 1375 1376 1377
  return makeUncheckedNode(root);
}

void SchemaLoader::Impl::applyStructSizeRequirement(
Kenton Varda's avatar
Kenton Varda committed
1378
    _::RawSchema* raw, uint dataWordCount, uint pointerCount,
Kenton Varda's avatar
Kenton Varda committed
1379 1380
    schema::ElementSize preferredListEncoding) {
  auto node = readMessageUnchecked<schema::Node>(raw->encodedNode);
1381 1382

  auto structNode = node.getStruct();
1383 1384
  if (structNode.getDataWordCount() < dataWordCount ||
      structNode.getPointerCount() < pointerCount ||
Kenton Varda's avatar
Kenton Varda committed
1385
      structNode.getPreferredListEncoding() < preferredListEncoding) {
1386
    // Sizes need to be increased.  Must rewrite.
Kenton Varda's avatar
Kenton Varda committed
1387 1388
    kj::ArrayPtr<word> words = rewriteStructNodeWithSizes(
        node, dataWordCount, pointerCount, preferredListEncoding);
1389 1390 1391 1392 1393 1394 1395 1396

    // We don't need to re-validate the node because we know this change could not possibly have
    // invalidated it.  Just remake the unchecked message.
    raw->encodedNode = words.begin();
    raw->encodedSize = words.size();
  }
}

1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407
void SchemaLoader::InitializerImpl::init(const _::RawSchema* schema) const {
  KJ_IF_MAYBE(c, callback) {
    c->load(loader, schema->id);
  }

  if (schema->lazyInitializer != nullptr) {
    // The callback declined to load a schema.  We need to disable the initializer so that it
    // doesn't get invoked again later, as we can no longer modify this schema once it is in use.

    // Lock the loader for read to make sure no one is concurrently loading a replacement for this
    // schema node.
1408
    auto lock = loader.impl.lockShared();
1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419

    // Get the mutable version of the schema.
    _::RawSchema* mutableSchema = lock->get()->tryGet(schema->id).schema;
    KJ_ASSERT(mutableSchema == schema,
              "A schema not belonging to this loader used its initializer.");

    // Disable the initializer.
    __atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
  }
}

1420 1421
// =======================================================================================

1422 1423 1424
SchemaLoader::SchemaLoader(): impl(kj::heap<Impl>(*this)) {}
SchemaLoader::SchemaLoader(const LazyLoadCallback& callback)
    : impl(kj::heap<Impl>(*this, callback)) {}
1425
SchemaLoader::~SchemaLoader() noexcept(false) {}
1426 1427

Schema SchemaLoader::get(uint64_t id) const {
1428 1429 1430 1431 1432
  KJ_IF_MAYBE(result, tryGet(id)) {
    return *result;
  } else {
    KJ_FAIL_REQUIRE("no schema node loaded for id", id);
  }
1433 1434
}

1435
kj::Maybe<Schema> SchemaLoader::tryGet(uint64_t id) const {
1436
  auto getResult = impl.lockShared()->get()->tryGet(id);
1437 1438 1439 1440
  if (getResult.schema == nullptr || getResult.schema->lazyInitializer != nullptr) {
    KJ_IF_MAYBE(c, getResult.callback) {
      c->load(*this, id);
    }
1441
    getResult = impl.lockShared()->get()->tryGet(id);
1442 1443 1444
  }
  if (getResult.schema != nullptr && getResult.schema->lazyInitializer == nullptr) {
    return Schema(getResult.schema);
1445
  } else {
1446
    return nullptr;
1447 1448 1449
  }
}

Kenton Varda's avatar
Kenton Varda committed
1450
Schema SchemaLoader::load(const schema::Node::Reader& reader) {
1451
  return Schema(impl.lockExclusive()->get()->load(reader, false));
1452 1453
}

Kenton Varda's avatar
Kenton Varda committed
1454
Schema SchemaLoader::loadOnce(const schema::Node::Reader& reader) const {
1455
  auto locked = impl.lockExclusive();
1456 1457 1458 1459 1460 1461 1462 1463
  auto getResult = locked->get()->tryGet(reader.getId());
  if (getResult.schema == nullptr || getResult.schema->lazyInitializer != nullptr) {
    // Doesn't exist yet, or the existing schema is a placeholder and therefore has not yet been
    // seen publicly.  Go ahead and load the incoming reader.
    return Schema(locked->get()->load(reader, false));
  } else {
    return Schema(getResult.schema);
  }
1464 1465
}

1466
kj::Array<Schema> SchemaLoader::getAllLoaded() const {
1467
  return impl.lockShared()->get()->getAllLoaded();
1468 1469
}

1470
void SchemaLoader::loadNative(const _::RawSchema* nativeSchema) {
1471
  impl.lockExclusive()->get()->loadNative(nativeSchema);
1472 1473
}

1474
}  // namespace capnp