schema-loader.c++ 76.5 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
#include "schema-loader.h"
#include <unordered_map>
25
#include <unordered_set>
26 27 28
#include <map>
#include "message.h"
#include "arena.h"
Kenton Varda's avatar
Kenton Varda committed
29
#include <kj/debug.h>
30
#include <kj/exception.h>
31
#include <kj/arena.h>
32 33
#include <kj/vector.h>
#include <algorithm>
34

35 36 37 38
#if _MSC_VER
#include <atomic>
#endif

39
namespace capnp {
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
namespace {

struct ByteArrayHash {
  size_t operator()(kj::ArrayPtr<const byte> bytes) const {
    // FNV hash. Probably sucks, but the code is simple.
    //
    // TODO(perf): Add CityHash or something to KJ and use it here.

    uint64_t hash = 0xcbf29ce484222325ull;
    for (byte b: bytes) {
      hash = hash * 0x100000001b3ull;
      hash ^= b;
    }
    return hash;
  }
};

struct ByteArrayEq {
  bool operator()(kj::ArrayPtr<const byte> a, kj::ArrayPtr<const byte> b) const {
    return a.size() == b.size() && memcmp(a.begin(), b.begin(), a.size()) == 0;
  }
};

struct SchemaBindingsPair {
  const _::RawSchema* schema;
  const _::RawBrandedSchema::Scope* scopeBindings;

  inline bool operator==(const SchemaBindingsPair& other) const {
    return schema == other.schema && scopeBindings == other.scopeBindings;
  }
};

struct SchemaBindingsPairHash {
  size_t operator()(SchemaBindingsPair pair) const {
    return 31 * reinterpret_cast<uintptr_t>(pair.schema) +
                reinterpret_cast<uintptr_t>(pair.scopeBindings);
  }
};

}  // namespace

82 83 84 85
bool hasDiscriminantValue(const schema::Field::Reader& reader) {
  return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
}

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
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;
};

103 104 105 106 107 108 109 110 111 112
class SchemaLoader::BrandedInitializerImpl: public _::RawBrandedSchema::Initializer {
public:
  inline explicit BrandedInitializerImpl(const SchemaLoader& loader): loader(loader) {}

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

private:
  const SchemaLoader& loader;
};

113 114
class SchemaLoader::Impl {
public:
115 116
  inline explicit Impl(const SchemaLoader& loader)
      : initializer(loader), brandedInitializer(loader) {}
117
  inline Impl(const SchemaLoader& loader, const LazyLoadCallback& callback)
118
      : initializer(loader, callback), brandedInitializer(loader) {}
119

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

122
  _::RawSchema* loadNative(const _::RawSchema* nativeSchema);
123

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

128 129
  const _::RawBrandedSchema* makeBranded(
      const _::RawSchema* schema, schema::Brand::Reader proto,
130
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> clientBrand);
131

132 133 134 135 136 137
  struct TryGetResult {
    _::RawSchema* schema;
    kj::Maybe<const LazyLoadCallback&> callback;
  };

  TryGetResult tryGet(uint64_t typeId) const;
138

139 140
  const _::RawBrandedSchema* getUnbound(const _::RawSchema* schema);

141
  kj::Array<Schema> getAllLoaded() const;
142

143
  void requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount);
144 145 146 147 148 149
  // 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.

150
  kj::Arena arena;
151 152

private:
153 154 155 156
  std::unordered_set<kj::ArrayPtr<const byte>, ByteArrayHash, ByteArrayEq> dedupTable;
  // Records raw segments of memory in the arena against which we my want to de-dupe later
  // additions. Specifically, RawBrandedSchema binding tables are de-duped.

157
  std::unordered_map<uint64_t, _::RawSchema*> schemas;
158
  std::unordered_map<SchemaBindingsPair, _::RawBrandedSchema*, SchemaBindingsPairHash> brands;
159
  std::unordered_map<const _::RawSchema*, _::RawBrandedSchema*> unboundBrands;
160

161 162 163 164 165 166
  struct RequiredSize {
    uint16_t dataWordCount;
    uint16_t pointerCount;
  };
  std::unordered_map<uint64_t, RequiredSize> structSizeRequirements;

167
  InitializerImpl initializer;
168
  BrandedInitializerImpl brandedInitializer;
169

Kenton Varda's avatar
Kenton Varda committed
170
  kj::ArrayPtr<word> makeUncheckedNode(schema::Node::Reader node);
171 172 173
  // 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
174
  kj::ArrayPtr<word> makeUncheckedNodeEnforcingSizeRequirements(schema::Node::Reader node);
175 176 177 178 179 180 181
  // 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(
182
      schema::Node::Reader node, uint dataWordCount, uint pointerCount);
183 184 185 186 187
  // 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.
188
  void applyStructSizeRequirement(_::RawSchema* raw, uint dataWordCount, uint pointerCount);
189

190
  const _::RawBrandedSchema* makeBranded(const _::RawSchema* schema,
191 192 193
      kj::ArrayPtr<const _::RawBrandedSchema::Scope> scopes);

  kj::ArrayPtr<const _::RawBrandedSchema::Dependency> makeBrandedDependencies(
194 195
      const _::RawSchema* schema,
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> bindings);
196

Kenton Varda's avatar
Kenton Varda committed
197
  void makeDep(_::RawBrandedSchema::Binding& result,
198
      schema::Type::Reader type, kj::StringPtr scopeName,
199
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
Kenton Varda's avatar
Kenton Varda committed
200
  void makeDep(_::RawBrandedSchema::Binding& result,
201
      uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
202
      schema::Brand::Reader brand, kj::StringPtr scopeName,
203
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
204
  // Looks up the schema and brand for a dependency, or creates lazily-evaluated placeholders if
Kenton Varda's avatar
Kenton Varda committed
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
  // they don't already exist, and fills in `result`. `scopeName` is a human-readable name of the
  // place where the type appeared.
  //
  // Note that we don't simply return a Binding because we need to be careful about initialization
  // to ensure that our byte-based de-duplification works. If we constructed a Binding on the stack
  // and returned it, padding bytes in that Binding could go uninitialized, causing it to appear
  // unique when it's not. It is expected that `result` has been zero'd via memset() before these
  // methods are called.

  const _::RawBrandedSchema* makeDepSchema(
      schema::Type::Reader type, kj::StringPtr scopeName,
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
  const _::RawBrandedSchema* makeDepSchema(
      uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
      schema::Brand::Reader brand, kj::StringPtr scopeName,
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
  // Invoke makeDep() then return the result's schema, or nullptr if it's a primitive type.
222 223 224 225 226 227 228 229 230

  template <typename T>
  kj::ArrayPtr<const T> copyDeduped(kj::ArrayPtr<const T> values);
  template <typename T>
  kj::ArrayPtr<const T> copyDeduped(kj::ArrayPtr<T> values);
  // Copy the given array into the arena and return the copy -- unless an identical array
  // was copied previously, in which case the existing copy is returned.

  friend class SchemaLoader::BrandedInitializerImpl;
231 232 233 234
};

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

235 236 237 238
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.

239 240 241 242
class SchemaLoader::Validator {
public:
  Validator(SchemaLoader::Impl& loader): loader(loader) {}

Kenton Varda's avatar
Kenton Varda committed
243
  bool validate(const schema::Node::Reader& node) {
244 245 246 247
    isValid = true;
    nodeName = node.getDisplayName();
    dependencies.clear();

248
    KJ_CONTEXT("validating schema node", nodeName, (uint)node.which());
249

250 251 252 253 254 255 256
    if (node.getParameters().size() > 0) {
      KJ_REQUIRE(node.getIsGeneric(), "if parameter list is non-empty, isGeneric must be true") {
        isValid = false;
        return false;
      }
    }

257
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
258
      case schema::Node::FILE:
259
        verifyVoid(node.getFile());
260
        break;
Kenton Varda's avatar
Kenton Varda committed
261
      case schema::Node::STRUCT:
262
        validate(node.getStruct(), node.getScopeId());
263
        break;
Kenton Varda's avatar
Kenton Varda committed
264
      case schema::Node::ENUM:
265
        validate(node.getEnum());
266
        break;
Kenton Varda's avatar
Kenton Varda committed
267
      case schema::Node::INTERFACE:
268
        validate(node.getInterface());
269
        break;
Kenton Varda's avatar
Kenton Varda committed
270
      case schema::Node::CONST:
271
        validate(node.getConst());
272
        break;
Kenton Varda's avatar
Kenton Varda committed
273
      case schema::Node::ANNOTATION:
274
        validate(node.getAnnotation());
275 276 277 278 279 280 281
        break;
    }

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

282
  const _::RawSchema** makeDependencyArray(uint32_t* count) {
283
    *count = dependencies.size();
284 285
    kj::ArrayPtr<const _::RawSchema*> result =
        loader.arena.allocateArray<const _::RawSchema*>(*count);
286 287 288 289
    uint pos = 0;
    for (auto& dep: dependencies) {
      result[pos++] = dep.second;
    }
290
    KJ_DASSERT(pos == *count);
291
    return result.begin();
292 293
  }

294
  const uint16_t* makeMemberInfoArray(uint32_t* count) {
295
    *count = members.size();
296
    kj::ArrayPtr<uint16_t> result = loader.arena.allocateArray<uint16_t>(*count);
297 298
    uint pos = 0;
    for (auto& member: members) {
299
      result[pos++] = member.second;
300
    }
301
    KJ_DASSERT(pos == *count);
302
    return result.begin();
303 304
  }

305 306 307 308
  const uint16_t* makeMembersByDiscriminantArray() {
    return membersByDiscriminant.begin();
  }

309 310 311 312
private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
  bool isValid;
313
  std::map<uint64_t, _::RawSchema*> dependencies;
314

315 316 317 318
  // Maps name -> index for each member.
  std::map<Text::Reader, uint> members;

  kj::ArrayPtr<uint16_t> membersByDiscriminant;
319 320

#define VALIDATE_SCHEMA(condition, ...) \
321
  KJ_REQUIRE(condition, ##__VA_ARGS__) { isValid = false; return; }
322
#define FAIL_VALIDATE_SCHEMA(...) \
323
  KJ_FAIL_REQUIRE(__VA_ARGS__) { isValid = false; return; }
324

325 326 327
  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
328 329
  }

Kenton Varda's avatar
Kenton Varda committed
330
  void validate(const schema::Node::Struct::Reader& structNode, uint64_t scopeId) {
331 332
    uint dataSizeInBits = structNode.getDataWordCount() * 64;
    uint pointerCount = structNode.getPointerCount();
333

334
    auto fields = structNode.getFields();
335

336
    KJ_STACK_ARRAY(bool, sawCodeOrder, fields.size(), 32, 256);
337 338
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

339 340 341 342 343 344 345 346 347 348 349 350
    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");
351 352
    }

353 354 355
    membersByDiscriminant = loader.arena.allocateArray<uint16_t>(fields.size());
    uint discriminantPos = 0;
    uint nonDiscriminantPos = structNode.getDiscriminantCount();
356

357 358 359 360
    uint index = 0;
    uint nextOrdinal = 0;
    for (auto field: fields) {
      KJ_CONTEXT("validating struct field", field.getName());
361

362 363 364 365 366
      validateMemberName(field.getName(), index);
      VALIDATE_SCHEMA(field.getCodeOrder() < sawCodeOrder.size() &&
                      !sawCodeOrder[field.getCodeOrder()],
                      "invalid codeOrder");
      sawCodeOrder[field.getCodeOrder()] = true;
367

368
      auto ordinal = field.getOrdinal();
369
      if (ordinal.isExplicit()) {
370 371 372
        VALIDATE_SCHEMA(ordinal.getExplicit() >= nextOrdinal,
                        "fields were not ordered by ordinal");
        nextOrdinal = ordinal.getExplicit() + 1;
373
      }
Kenton Varda's avatar
Kenton Varda committed
374

375
      if (hasDiscriminantValue(field)) {
376 377 378 379 380 381 382 383 384 385 386
        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
387

388
      switch (field.which()) {
389 390
        case schema::Field::SLOT: {
          auto slot = field.getSlot();
Kenton Varda's avatar
Kenton Varda committed
391

392 393
          uint fieldBits = 0;
          bool fieldIsPointer = false;
394 395 396
          validate(slot.getType(), slot.getDefaultValue(), &fieldBits, &fieldIsPointer);
          VALIDATE_SCHEMA(fieldBits * (slot.getOffset() + 1) <= dataSizeInBits &&
                          fieldIsPointer * (slot.getOffset() + 1) <= pointerCount,
397
                          "field offset out-of-bounds",
398
                          slot.getOffset(), dataSizeInBits, pointerCount);
Kenton Varda's avatar
Kenton Varda committed
399

400
          break;
Kenton Varda's avatar
Kenton Varda committed
401 402
        }

Kenton Varda's avatar
Kenton Varda committed
403
        case schema::Field::GROUP:
404
          // Require that the group is a struct node.
405
          validateTypeId(field.getGroup().getTypeId(), schema::Node::STRUCT);
406
          break;
Kenton Varda's avatar
Kenton Varda committed
407
      }
408 409

      ++index;
410 411
    }

412 413 414 415 416 417
    // 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");
418

419 420
      // 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.
421
      loader.requireStructSize(scopeId, structNode.getDataWordCount(),
422
                               structNode.getPointerCount());
423 424

      // Require that the parent type is a struct.
Kenton Varda's avatar
Kenton Varda committed
425
      validateTypeId(scopeId, schema::Node::STRUCT);
426 427 428
    }
  }

429 430
  void validate(const schema::Node::Enum::Reader& enumNode) {
    auto enumerants = enumNode.getEnumerants();
431
    KJ_STACK_ARRAY(bool, sawCodeOrder, enumerants.size(), 32, 256);
432 433 434 435
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto enumerant: enumerants) {
436
      validateMemberName(enumerant.getName(), index++);
437 438 439 440 441 442 443 444

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

445
  void validate(const schema::Node::Interface::Reader& interfaceNode) {
446
    for (auto extend: interfaceNode.getSuperclasses()) {
Kenton Varda's avatar
Kenton Varda committed
447
      validateTypeId(extend.getId(), schema::Node::INTERFACE);
448
      validate(extend.getBrand());
449 450
    }

451
    auto methods = interfaceNode.getMethods();
452
    KJ_STACK_ARRAY(bool, sawCodeOrder, methods.size(), 32, 256);
453 454 455 456
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto method: methods) {
457
      KJ_CONTEXT("validating method", method.getName());
458
      validateMemberName(method.getName(), index++);
459 460 461 462 463 464

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

465
      validateTypeId(method.getParamStructType(), schema::Node::STRUCT);
466
      validate(method.getParamBrand());
467
      validateTypeId(method.getResultStructType(), schema::Node::STRUCT);
468
      validate(method.getResultBrand());
469 470 471
    }
  }

Kenton Varda's avatar
Kenton Varda committed
472
  void validate(const schema::Node::Const::Reader& constNode) {
473 474 475 476 477
    uint dummy1;
    bool dummy2;
    validate(constNode.getType(), constNode.getValue(), &dummy1, &dummy2);
  }

Kenton Varda's avatar
Kenton Varda committed
478
  void validate(const schema::Node::Annotation::Reader& annotationNode) {
479 480 481
    validate(annotationNode.getType());
  }

Kenton Varda's avatar
Kenton Varda committed
482
  void validate(const schema::Type::Reader& type, const schema::Value::Reader& value,
483 484 485
                uint* dataSizeInBits, bool* isPointer) {
    validate(type);

Kenton Varda's avatar
Kenton Varda committed
486
    schema::Value::Which expectedValueType = schema::Value::VOID;
487
    bool hadCase = false;
488
    switch (type.which()) {
489
#define HANDLE_TYPE(name, bits, ptr) \
Kenton Varda's avatar
Kenton Varda committed
490 491
      case schema::Type::name: \
        expectedValueType = schema::Value::name; \
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
        *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)
513
      HANDLE_TYPE(ANY_POINTER, 0, true)
514 515 516 517
#undef HANDLE_TYPE
    }

    if (hadCase) {
518 519
      VALIDATE_SCHEMA(value.which() == expectedValueType, "Value did not match type.",
                      (uint)value.which(), (uint)expectedValueType);
520 521 522
    }
  }

Kenton Varda's avatar
Kenton Varda committed
523
  void validate(const schema::Type::Reader& type) {
524
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
525 526 527 528 529 530 531 532 533 534 535 536 537 538
      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:
539
      case schema::Type::ANY_POINTER:
540 541
        break;

542 543 544
      case schema::Type::STRUCT: {
        auto structType = type.getStruct();
        validateTypeId(structType.getTypeId(), schema::Node::STRUCT);
545
        validate(structType.getBrand());
546
        break;
547 548 549 550
      }
      case schema::Type::ENUM: {
        auto enumType = type.getEnum();
        validateTypeId(enumType.getTypeId(), schema::Node::ENUM);
551
        validate(enumType.getBrand());
552
        break;
553 554 555 556
      }
      case schema::Type::INTERFACE: {
        auto interfaceType = type.getInterface();
        validateTypeId(interfaceType.getTypeId(), schema::Node::INTERFACE);
557
        validate(interfaceType.getBrand());
558
        break;
559
      }
560

Kenton Varda's avatar
Kenton Varda committed
561
      case schema::Type::LIST:
562
        validate(type.getList().getElementType());
563 564 565 566 567 568
        break;
    }

    // We intentionally allow unknown types.
  }

569 570
  void validate(const schema::Brand::Reader& brand) {
    for (auto scope: brand.getScopes()) {
571
      switch (scope.which()) {
572
        case schema::Brand::Scope::BIND:
573 574
          for (auto binding: scope.getBind()) {
            switch (binding.which()) {
575
              case schema::Brand::Binding::UNBOUND:
576
                break;
577
              case schema::Brand::Binding::TYPE: {
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
                auto type = binding.getType();
                validate(type);
                bool isPointer = true;
                switch (type.which()) {
                  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:
                    isPointer = false;
                    break;

                  case schema::Type::TEXT:
                  case schema::Type::DATA:
                  case schema::Type::ANY_POINTER:
                  case schema::Type::STRUCT:
                  case schema::Type::INTERFACE:
                  case schema::Type::LIST:
                    isPointer = true;
                    break;
                }
                VALIDATE_SCHEMA(isPointer,
                    "generic type parameter must be a pointer type", type);

                break;
              }
            }
          }
          break;
615
        case schema::Brand::Scope::INHERIT:
616 617 618 619 620
          break;
      }
    }
  }

Kenton Varda's avatar
Kenton Varda committed
621
  void validateTypeId(uint64_t id, schema::Node::Which expectedKind) {
622
    _::RawSchema* existing = loader.tryGet(id).schema;
623
    if (existing != nullptr) {
Kenton Varda's avatar
Kenton Varda committed
624
      auto node = readMessageUnchecked<schema::Node>(existing->encodedNode);
625
      VALIDATE_SCHEMA(node.which() == expectedKind,
626
          "expected a different kind of node for this ID",
627
          id, (uint)expectedKind, (uint)node.which(), node.getDisplayName());
628 629 630 631 632
      dependencies.insert(std::make_pair(id, existing));
      return;
    }

    dependencies.insert(std::make_pair(id, loader.loadEmpty(
633
        id, kj::str("(unknown type used by ", nodeName , ")"), expectedKind, true)));
634 635 636 637 638 639 640 641 642 643 644 645
  }

#undef VALIDATE_SCHEMA
#undef FAIL_VALIDATE_SCHEMA
};

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

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

Kenton Varda's avatar
Kenton Varda committed
646 647
  bool shouldReplace(const schema::Node::Reader& existingNode,
                     const schema::Node::Reader& replacement,
648
                     bool preferReplacementIfEquivalent) {
649 650 651
    this->existingNode = existingNode;
    this->replacementNode = replacement;

652 653
    KJ_CONTEXT("checking compatibility with previously-loaded node of the same id",
               existingNode.getDisplayName());
654

655
    KJ_DREQUIRE(existingNode.getId() == replacement.getId());
656 657 658 659 660 661

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

    checkCompatibility(existingNode, replacement);

662 663
    // Prefer the newer schema.
    return preferReplacementIfEquivalent ? compatibility != OLDER : compatibility == NEWER;
664 665 666 667 668
  }

private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
669 670
  schema::Node::Reader existingNode;
  schema::Node::Reader replacementNode;
671 672 673 674 675 676 677 678 679 680

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

#define VALIDATE_SCHEMA(condition, ...) \
681
  KJ_REQUIRE(condition, ##__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
682
#define FAIL_VALIDATE_SCHEMA(...) \
683
  KJ_FAIL_REQUIRE(__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716

  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
717 718
  void checkCompatibility(const schema::Node::Reader& node,
                          const schema::Node::Reader& replacement) {
719 720 721
    // Returns whether `replacement` is equivalent, older than, newer than, or incompatible with
    // `node`.  If exceptions are enabled, this will throw an exception on INCOMPATIBLE.

722
    VALIDATE_SCHEMA(node.which() == replacement.which(),
723 724
                    "kind of declaration changed");

725
    // No need to check compatibility of most of the non-body parts of the node:
726 727 728
    // - Arbitrary renaming and moving between scopes is allowed.
    // - Annotations are ignored for compatibility purposes.

729 730 731 732 733 734
    if (replacement.getParameters().size() > node.getParameters().size()) {
      replacementIsNewer();
    } else if (replacement.getParameters().size() < node.getParameters().size()) {
      replacementIsOlder();
    }

735
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
736
      case schema::Node::FILE:
737
        verifyVoid(node.getFile());
738
        break;
Kenton Varda's avatar
Kenton Varda committed
739
      case schema::Node::STRUCT:
740 741
        checkCompatibility(node.getStruct(), replacement.getStruct(),
                           node.getScopeId(), replacement.getScopeId());
742
        break;
Kenton Varda's avatar
Kenton Varda committed
743
      case schema::Node::ENUM:
744
        checkCompatibility(node.getEnum(), replacement.getEnum());
745
        break;
Kenton Varda's avatar
Kenton Varda committed
746
      case schema::Node::INTERFACE:
747
        checkCompatibility(node.getInterface(), replacement.getInterface());
748
        break;
Kenton Varda's avatar
Kenton Varda committed
749
      case schema::Node::CONST:
750
        checkCompatibility(node.getConst(), replacement.getConst());
751
        break;
Kenton Varda's avatar
Kenton Varda committed
752
      case schema::Node::ANNOTATION:
753
        checkCompatibility(node.getAnnotation(), replacement.getAnnotation());
754 755 756 757
        break;
    }
  }

Kenton Varda's avatar
Kenton Varda committed
758 759
  void checkCompatibility(const schema::Node::Struct::Reader& structNode,
                          const schema::Node::Struct::Reader& replacement,
760
                          uint64_t scopeId, uint64_t replacementScopeId) {
761
    if (replacement.getDataWordCount() > structNode.getDataWordCount()) {
762
      replacementIsNewer();
763
    } else if (replacement.getDataWordCount() < structNode.getDataWordCount()) {
764 765
      replacementIsOlder();
    }
766
    if (replacement.getPointerCount() > structNode.getPointerCount()) {
767
      replacementIsNewer();
768
    } else if (replacement.getPointerCount() < structNode.getPointerCount()) {
769 770
      replacementIsOlder();
    }
771 772 773 774 775 776 777 778 779 780 781
    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");
    }

782 783
    // The shared members should occupy corresponding positions in the member lists, since the
    // lists are sorted by ordinal.
784 785 786
    auto fields = structNode.getFields();
    auto replacementFields = replacement.getFields();
    uint count = std::min(fields.size(), replacementFields.size());
787

788
    if (replacementFields.size() > fields.size()) {
789
      replacementIsNewer();
790
    } else if (replacementFields.size() < fields.size()) {
791 792 793 794
      replacementIsOlder();
    }

    for (uint i = 0; i < count; i++) {
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
      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();
      }
814 815 816
    }
  }

Kenton Varda's avatar
Kenton Varda committed
817 818
  void checkCompatibility(const schema::Field::Reader& field,
                          const schema::Field::Reader& replacement) {
819
    KJ_CONTEXT("comparing struct field", field.getName());
820

821 822
    // A field that is initially not in a union can be upgraded to be in one, as long as it has
    // discriminant 0.
823
    uint discriminant = hasDiscriminantValue(field) ? field.getDiscriminantValue() : 0;
824
    uint replacementDiscriminant =
825
        hasDiscriminantValue(replacement) ? replacement.getDiscriminantValue() : 0;
826
    VALIDATE_SCHEMA(discriminant == replacementDiscriminant, "Field discriminant changed.");
827

828
    switch (field.which()) {
829 830
      case schema::Field::SLOT: {
        auto slot = field.getSlot();
831

832
        switch (replacement.which()) {
833 834
          case schema::Field::SLOT: {
            auto replacementSlot = replacement.getSlot();
835

836
            checkCompatibility(slot.getType(), replacementSlot.getType(),
837
                               NO_UPGRADE_TO_STRUCT);
838 839
            checkDefaultCompatibility(slot.getDefaultValue(),
                                      replacementSlot.getDefaultValue());
840

841
            VALIDATE_SCHEMA(slot.getOffset() == replacementSlot.getOffset(),
842 843 844 845
                            "field position changed");
            break;
          }
          case schema::Field::GROUP:
846
            checkUpgradeToStruct(slot.getType(), replacement.getGroup().getTypeId(),
847
                                 existingNode, field);
848 849
            break;
        }
850 851 852

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

Kenton Varda's avatar
Kenton Varda committed
854
      case schema::Field::GROUP:
855
        switch (replacement.which()) {
856 857
          case schema::Field::SLOT:
            checkUpgradeToStruct(replacement.getSlot().getType(), field.getGroup().getTypeId(),
858 859 860
                                 replacementNode, replacement);
            break;
          case schema::Field::GROUP:
861 862
            VALIDATE_SCHEMA(field.getGroup().getTypeId() == replacement.getGroup().getTypeId(),
                            "group id changed");
863 864
            break;
        }
865 866 867 868
        break;
    }
  }

869 870 871 872
  void checkCompatibility(const schema::Node::Enum::Reader& enumNode,
                          const schema::Node::Enum::Reader& replacement) {
    uint size = enumNode.getEnumerants().size();
    uint replacementSize = replacement.getEnumerants().size();
873 874 875 876 877 878 879
    if (replacementSize > size) {
      replacementIsNewer();
    } else if (replacementSize < size) {
      replacementIsOlder();
    }
  }

880 881
  void checkCompatibility(const schema::Node::Interface::Reader& interfaceNode,
                          const schema::Node::Interface::Reader& replacement) {
882 883 884
    {
      // Check superclasses.

885 886 887 888
      kj::Vector<uint64_t> superclasses;
      kj::Vector<uint64_t> replacementSuperclasses;
      for (auto superclass: interfaceNode.getSuperclasses()) {
        superclasses.add(superclass.getId());
889
      }
890 891
      for (auto superclass: replacement.getSuperclasses()) {
        replacementSuperclasses.add(superclass.getId());
892
      }
893 894
      std::sort(superclasses.begin(), superclasses.end());
      std::sort(replacementSuperclasses.begin(), replacementSuperclasses.end());
895

896 897
      auto iter = superclasses.begin();
      auto replacementIter = replacementSuperclasses.begin();
898

899 900
      while (iter != superclasses.end() || replacementIter != replacementSuperclasses.end()) {
        if (iter == superclasses.end()) {
901 902
          replacementIsNewer();
          break;
903
        } else if (replacementIter == replacementSuperclasses.end()) {
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918
          replacementIsOlder();
          break;
        } else if (*iter < *replacementIter) {
          replacementIsOlder();
          ++iter;
        } else if (*iter > *replacementIter) {
          replacementIsNewer();
          ++replacementIter;
        } else {
          ++iter;
          ++replacementIter;
        }
      }
    }

919 920 921
    auto methods = interfaceNode.getMethods();
    auto replacementMethods = replacement.getMethods();

922 923 924 925 926 927 928 929 930 931 932 933 934
    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
935 936
  void checkCompatibility(const schema::Method::Reader& method,
                          const schema::Method::Reader& replacement) {
937
    KJ_CONTEXT("comparing method", method.getName());
938

939 940 941 942 943
    // 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.");
944 945
  }

Kenton Varda's avatar
Kenton Varda committed
946 947
  void checkCompatibility(const schema::Node::Const::Reader& constNode,
                          const schema::Node::Const::Reader& replacement) {
948 949 950
    // Who cares?  These don't appear on the wire.
  }

Kenton Varda's avatar
Kenton Varda committed
951 952
  void checkCompatibility(const schema::Node::Annotation::Reader& annotationNode,
                          const schema::Node::Annotation::Reader& replacement) {
953 954 955 956 957 958 959 960
    // 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
961 962
  void checkCompatibility(const schema::Type::Reader& type,
                          const schema::Type::Reader& replacement,
963
                          UpgradeToStructMode upgradeToStructMode) {
964
    if (replacement.which() != type.which()) {
965
      // Check for allowed "upgrade" to Data or AnyPointer.
966
      if (replacement.isData() && canUpgradeToData(type)) {
967 968
        replacementIsNewer();
        return;
969
      } else if (type.isData() && canUpgradeToData(replacement)) {
970 971
        replacementIsOlder();
        return;
972
      } else if (replacement.isAnyPointer() && canUpgradeToAnyPointer(type)) {
973 974
        replacementIsNewer();
        return;
975
      } else if (type.isAnyPointer() && canUpgradeToAnyPointer(replacement)) {
976 977 978 979 980
        replacementIsOlder();
        return;
      }

      if (upgradeToStructMode == ALLOW_UPGRADE_TO_STRUCT) {
981
        if (type.isStruct()) {
982
          checkUpgradeToStruct(replacement, type.getStruct().getTypeId());
983
          return;
984
        } else if (replacement.isStruct()) {
985
          checkUpgradeToStruct(type, replacement.getStruct().getTypeId());
986 987 988 989 990 991 992
          return;
        }
      }

      FAIL_VALIDATE_SCHEMA("a type was changed");
    }

993
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
      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:
1008
      case schema::Type::ANY_POINTER:
1009 1010
        return;

Kenton Varda's avatar
Kenton Varda committed
1011
      case schema::Type::LIST:
1012 1013
        checkCompatibility(type.getList().getElementType(), replacement.getList().getElementType(),
                           ALLOW_UPGRADE_TO_STRUCT);
1014
        return;
1015

Kenton Varda's avatar
Kenton Varda committed
1016
      case schema::Type::ENUM:
1017 1018
        VALIDATE_SCHEMA(replacement.getEnum().getTypeId() == type.getEnum().getTypeId(),
                        "type changed enum type");
1019 1020
        return;

Kenton Varda's avatar
Kenton Varda committed
1021
      case schema::Type::STRUCT:
1022 1023 1024 1025 1026 1027 1028
        // 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...
1029
        VALIDATE_SCHEMA(replacement.getStruct().getTypeId() == type.getStruct().getTypeId(),
1030 1031 1032
                        "type changed to incompatible struct type");
        return;

Kenton Varda's avatar
Kenton Varda committed
1033
      case schema::Type::INTERFACE:
1034
        VALIDATE_SCHEMA(replacement.getInterface().getTypeId() == type.getInterface().getTypeId(),
1035
                        "type changed to incompatible interface type");
1036 1037 1038 1039 1040 1041
        return;
    }

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

1042 1043 1044
  void checkUpgradeToStruct(const schema::Type::Reader& type, uint64_t structTypeId,
                            kj::Maybe<schema::Node::Reader> matchSize = nullptr,
                            kj::Maybe<schema::Field::Reader> matchPosition = nullptr) {
1045 1046 1047 1048 1049 1050 1051
    // 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));
1052
    MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
1053
    auto node = builder.initRoot<schema::Node>();
1054
    node.setId(structTypeId);
1055
    node.setDisplayName(kj::str("(unknown type used in ", nodeName, ")"));
1056
    auto structNode = node.initStruct();
1057

1058
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
1059
      case schema::Type::VOID:
1060 1061
        structNode.setDataWordCount(0);
        structNode.setPointerCount(0);
1062 1063
        break;

Kenton Varda's avatar
Kenton Varda committed
1064
      case schema::Type::BOOL:
1065 1066
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1067 1068
        break;

Kenton Varda's avatar
Kenton Varda committed
1069 1070
      case schema::Type::INT8:
      case schema::Type::UINT8:
1071 1072
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1073 1074
        break;

Kenton Varda's avatar
Kenton Varda committed
1075 1076 1077
      case schema::Type::INT16:
      case schema::Type::UINT16:
      case schema::Type::ENUM:
1078 1079
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1080 1081
        break;

Kenton Varda's avatar
Kenton Varda committed
1082 1083 1084
      case schema::Type::INT32:
      case schema::Type::UINT32:
      case schema::Type::FLOAT32:
1085 1086
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1087 1088
        break;

Kenton Varda's avatar
Kenton Varda committed
1089 1090 1091
      case schema::Type::INT64:
      case schema::Type::UINT64:
      case schema::Type::FLOAT64:
1092 1093
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1094 1095
        break;

Kenton Varda's avatar
Kenton Varda committed
1096 1097 1098 1099 1100
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
1101
      case schema::Type::ANY_POINTER:
1102 1103
        structNode.setDataWordCount(0);
        structNode.setPointerCount(1);
1104 1105 1106
        break;
    }

1107 1108 1109 1110 1111 1112
    KJ_IF_MAYBE(s, matchSize) {
      auto match = s->getStruct();
      structNode.setDataWordCount(match.getDataWordCount());
      structNode.setPointerCount(match.getPointerCount());
    }

1113 1114 1115
    auto field = structNode.initFields(1)[0];
    field.setName("member0");
    field.setCodeOrder(0);
1116 1117
    auto slot = field.initSlot();
    slot.setType(type);
1118 1119 1120 1121 1122 1123 1124

    KJ_IF_MAYBE(p, matchPosition) {
      if (p->getOrdinal().isExplicit()) {
        field.getOrdinal().setExplicit(p->getOrdinal().getExplicit());
      } else {
        field.getOrdinal().setImplicit();
      }
1125 1126 1127
      auto matchSlot = p->getSlot();
      slot.setOffset(matchSlot.getOffset());
      slot.setDefaultValue(matchSlot.getDefaultValue());
1128 1129
    } else {
      field.getOrdinal().setExplicit(0);
1130
      slot.setOffset(0);
1131

1132
      schema::Value::Builder value = slot.initDefaultValue();
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
      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;
1149 1150
        case schema::Type::LIST: value.initList(); break;
        case schema::Type::STRUCT: value.initStruct(); break;
1151
        case schema::Type::INTERFACE: value.setInterface(); break;
1152
        case schema::Type::ANY_POINTER: value.initAnyPointer(); break;
1153 1154
      }
    }
1155

1156
    loader.load(node, true);
1157 1158
  }

Kenton Varda's avatar
Kenton Varda committed
1159
  bool canUpgradeToData(const schema::Type::Reader& type) {
1160
    if (type.isText()) {
1161
      return true;
1162
    } else if (type.isList()) {
1163
      switch (type.getList().getElementType().which()) {
Kenton Varda's avatar
Kenton Varda committed
1164 1165
        case schema::Type::INT8:
        case schema::Type::UINT8:
1166 1167 1168 1169 1170 1171 1172 1173 1174
          return true;
        default:
          return false;
      }
    } else {
      return false;
    }
  }

1175
  bool canUpgradeToAnyPointer(const schema::Type::Reader& type) {
1176
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
      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:
1190 1191
        return false;

Kenton Varda's avatar
Kenton Varda committed
1192 1193 1194 1195 1196
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
1197
      case schema::Type::ANY_POINTER:
1198 1199 1200 1201 1202 1203 1204
        return true;
    }

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

Kenton Varda's avatar
Kenton Varda committed
1205 1206
  void checkDefaultCompatibility(const schema::Value::Reader& value,
                                 const schema::Value::Reader& replacement) {
1207 1208
    // 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.
1209
    KJ_ASSERT(value.which() == replacement.which()) {
1210 1211 1212 1213
      compatibility = INCOMPATIBLE;
      return;
    }

1214
    switch (value.which()) {
1215
#define HANDLE_TYPE(discrim, name) \
Kenton Varda's avatar
Kenton Varda committed
1216
      case schema::Value::discrim: \
1217
        VALIDATE_SCHEMA(value.get##name() == replacement.get##name(), "default value changed"); \
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
        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
1234 1235 1236 1237 1238
      case schema::Value::TEXT:
      case schema::Value::DATA:
      case schema::Value::LIST:
      case schema::Value::STRUCT:
      case schema::Value::INTERFACE:
1239
      case schema::Value::ANY_POINTER:
1240 1241 1242 1243 1244 1245 1246 1247 1248
        // 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
1249
_::RawSchema* SchemaLoader::Impl::load(const schema::Node::Reader& reader, bool isPlaceholder) {
1250
  // Make a copy of the node which can be used unchecked.
1251
  kj::ArrayPtr<word> validated = makeUncheckedNodeEnforcingSizeRequirements(reader);
1252 1253 1254

  // Validate the copy.
  Validator validator(*this);
Kenton Varda's avatar
Kenton Varda committed
1255
  auto validatedReader = readMessageUnchecked<schema::Node>(validated.begin());
1256 1257 1258 1259 1260

  if (!validator.validate(validatedReader)) {
    // Not valid.  Construct an empty schema of the same type and return that.
    return loadEmpty(validatedReader.getId(),
                     validatedReader.getDisplayName(),
1261 1262
                     validatedReader.which(),
                     false);
1263 1264 1265
  }

  // Check if we already have a schema for this ID.
1266
  _::RawSchema*& slot = schemas[validatedReader.getId()];
1267
  bool shouldReplace;
1268
  bool shouldClearInitializer;
1269 1270
  if (slot == nullptr) {
    // Nope, allocate a new RawSchema.
1271
    slot = &arena.allocate<_::RawSchema>();
1272
    memset(&slot->defaultBrand, 0, sizeof(slot->defaultBrand));
1273 1274
    slot->id = validatedReader.getId();
    slot->canCastTo = nullptr;
1275
    slot->defaultBrand.generic = slot;
1276 1277
    slot->lazyInitializer = isPlaceholder ? &initializer : nullptr;
    slot->defaultBrand.lazyInitializer = isPlaceholder ? &brandedInitializer : nullptr;
1278
    shouldReplace = true;
1279
    shouldClearInitializer = false;
1280 1281
  } else {
    // Yes, check if it is compatible and figure out which schema is newer.
1282

1283 1284 1285
    // If the existing slot is a placeholder, but we're upgrading it to a non-placeholder, we
    // need to clear the initializer later.
    shouldClearInitializer = slot->lazyInitializer != nullptr && !isPlaceholder;
1286

Kenton Varda's avatar
Kenton Varda committed
1287
    auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
1288
    CompatibilityChecker checker(*this);
1289 1290 1291 1292 1293

    // 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);
1294 1295
  }

1296 1297 1298
  if (shouldReplace) {
    // Initialize the RawSchema.
    slot->encodedNode = validated.begin();
Kenton Varda's avatar
Kenton Varda committed
1299
    slot->encodedSize = validated.size();
1300 1301
    slot->dependencies = validator.makeDependencyArray(&slot->dependencyCount);
    slot->membersByName = validator.makeMemberInfoArray(&slot->memberCount);
1302
    slot->membersByDiscriminant = validator.makeMembersByDiscriminantArray();
1303 1304 1305

    // Even though this schema isn't itself branded, it may have dependencies that are. So, we
    // need to set up the "dependencies" map under defaultBrand.
1306
    auto deps = makeBrandedDependencies(slot, kj::ArrayPtr<const _::RawBrandedSchema::Scope>());
1307 1308
    slot->defaultBrand.dependencies = deps.begin();
    slot->defaultBrand.dependencyCount = deps.size();
1309 1310
  }

1311
  if (shouldClearInitializer) {
1312 1313 1314
    // 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.
1315
#if __GNUC__
1316
    __atomic_store_n(&slot->lazyInitializer, nullptr, __ATOMIC_RELEASE);
1317
    __atomic_store_n(&slot->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1318 1319 1320 1321 1322 1323 1324 1325
#elif _MSC_VER
    std::atomic_thread_fence(std::memory_order_release);
    *static_cast<_::RawSchema::Initializer const* volatile*>(&slot->lazyInitializer) = nullptr;
    *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
        &slot->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
1326
  }
1327 1328 1329 1330

  return slot;
}

1331
_::RawSchema* SchemaLoader::Impl::loadNative(const _::RawSchema* nativeSchema) {
1332 1333
  _::RawSchema*& slot = schemas[nativeSchema->id];
  bool shouldReplace;
1334
  bool shouldClearInitializer;
1335
  if (slot == nullptr) {
1336
    slot = &arena.allocate<_::RawSchema>();
1337
    memset(&slot->defaultBrand, 0, sizeof(slot->defaultBrand));
1338
    slot->defaultBrand.generic = slot;
1339 1340
    slot->lazyInitializer = nullptr;
    slot->defaultBrand.lazyInitializer = nullptr;
1341
    shouldReplace = true;
1342
    shouldClearInitializer = false;  // already cleared above
1343
  } else if (slot->canCastTo != nullptr) {
1344 1345
    // Already loaded natively, or we're currently in the process of loading natively and there
    // was a dependency cycle.
1346
    KJ_REQUIRE(slot->canCastTo == nativeSchema,
1347
        "two different compiled-in type have the same type ID",
1348
        nativeSchema->id,
Kenton Varda's avatar
Kenton Varda committed
1349 1350
        readMessageUnchecked<schema::Node>(nativeSchema->encodedNode).getDisplayName(),
        readMessageUnchecked<schema::Node>(slot->canCastTo->encodedNode).getDisplayName());
1351 1352
    return slot;
  } else {
Kenton Varda's avatar
Kenton Varda committed
1353 1354
    auto existing = readMessageUnchecked<schema::Node>(slot->encodedNode);
    auto native = readMessageUnchecked<schema::Node>(nativeSchema->encodedNode);
1355
    CompatibilityChecker checker(*this);
1356
    shouldReplace = checker.shouldReplace(existing, native, true);
1357
    shouldClearInitializer = slot->lazyInitializer != nullptr;
1358 1359
  }

1360 1361
  // Since we recurse below, the slot in the hash map could move around.  Copy out the pointer
  // for subsequent use.
1362 1363
  // TODO(cleanup): Above comment is actually not true of unordered_map. Leaving here to explain
  //   code pattern below.
1364 1365 1366
  _::RawSchema* result = slot;

  if (shouldReplace) {
1367 1368 1369 1370 1371
    // 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;
1372

1373 1374
    result->defaultBrand.generic = result;

1375 1376 1377 1378
    // 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;

1379
    // We need to set the dependency list to point at other loader-owned RawSchemas.
1380 1381 1382 1383 1384 1385
    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();
1386

1387
    // Also need to re-do the branded dependencies.
1388
    auto deps = makeBrandedDependencies(slot, kj::ArrayPtr<const _::RawBrandedSchema::Scope>());
1389 1390 1391
    slot->defaultBrand.dependencies = deps.begin();
    slot->defaultBrand.dependencyCount = deps.size();

1392 1393 1394 1395
    // 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,
1396
                                 reqIter->second.pointerCount);
1397
    }
1398 1399
  } else {
    // The existing schema is newer.
1400

1401 1402 1403
    // 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;
1404

1405 1406 1407 1408
    // Make sure the dependencies are loaded and compatible.
    for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
      loadNative(nativeSchema->dependencies[i]);
    }
1409 1410
  }

1411 1412 1413 1414
  if (shouldClearInitializer) {
    // 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.
1415
#if __GNUC__
1416 1417
    __atomic_store_n(&result->lazyInitializer, nullptr, __ATOMIC_RELEASE);
    __atomic_store_n(&result->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1418 1419 1420 1421 1422 1423 1424 1425
#elif _MSC_VER
    std::atomic_thread_fence(std::memory_order_release);
    *static_cast<_::RawSchema::Initializer const* volatile*>(&result->lazyInitializer) = nullptr;
    *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
        &result->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
1426
  }
1427 1428

  return result;
1429 1430
}

1431
_::RawSchema* SchemaLoader::Impl::loadEmpty(
1432
    uint64_t id, kj::StringPtr name, schema::Node::Which kind, bool isPlaceholder) {
1433 1434
  word scratch[32];
  memset(scratch, 0, sizeof(scratch));
1435
  MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
1436
  auto node = builder.initRoot<schema::Node>();
1437 1438 1439
  node.setId(id);
  node.setDisplayName(name);
  switch (kind) {
Kenton Varda's avatar
Kenton Varda committed
1440
    case schema::Node::STRUCT: node.initStruct(); break;
1441 1442
    case schema::Node::ENUM: node.initEnum(); break;
    case schema::Node::INTERFACE: node.initInterface(); break;
1443

Kenton Varda's avatar
Kenton Varda committed
1444 1445 1446
    case schema::Node::FILE:
    case schema::Node::CONST:
    case schema::Node::ANNOTATION:
1447
      KJ_FAIL_REQUIRE("Not a type.");
1448 1449 1450
      break;
  }

1451
  return load(node, isPlaceholder);
1452 1453
}

1454 1455
const _::RawBrandedSchema* SchemaLoader::Impl::makeBranded(
    const _::RawSchema* schema, schema::Brand::Reader proto,
1456
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> clientBrand) {
1457 1458 1459 1460 1461 1462
  kj::StringPtr scopeName =
      readMessageUnchecked<schema::Node>(schema->encodedNode).getDisplayName();

  auto srcScopes = proto.getScopes();

  KJ_STACK_ARRAY(_::RawBrandedSchema::Scope, dstScopes, srcScopes.size(), 16, 32);
1463
  memset(dstScopes.begin(), 0, dstScopes.size() * sizeof(dstScopes[0]));
1464 1465 1466 1467

  uint dstScopeCount = 0;
  for (auto srcScope: srcScopes) {
    switch (srcScope.which()) {
1468
      case schema::Brand::Scope::BIND: {
1469 1470
        auto srcBindings = srcScope.getBind();
        KJ_STACK_ARRAY(_::RawBrandedSchema::Binding, dstBindings, srcBindings.size(), 16, 32);
1471
        memset(dstBindings.begin(), 0, dstBindings.size() * sizeof(dstBindings[0]));
1472 1473 1474 1475 1476 1477 1478 1479 1480

        for (auto j: kj::indices(srcBindings)) {
          auto srcBinding = srcBindings[j];
          auto& dstBinding = dstBindings[j];

          memset(&dstBinding, 0, sizeof(dstBinding));
          dstBinding.which = schema::Type::ANY_POINTER;

          switch (srcBinding.which()) {
1481
            case schema::Brand::Binding::UNBOUND:
1482
              break;
1483
            case schema::Brand::Binding::TYPE: {
Kenton Varda's avatar
Kenton Varda committed
1484
              makeDep(dstBinding, srcBinding.getType(), scopeName, clientBrand);
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495
              break;
            }
          }
        }

        auto& dstScope = dstScopes[dstScopeCount++];
        dstScope.typeId = srcScope.getScopeId();
        dstScope.bindingCount = dstBindings.size();
        dstScope.bindings = copyDeduped(dstBindings).begin();
        break;
      }
1496
      case schema::Brand::Scope::INHERIT: {
1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
        // Inherit the whole scope from the client -- or if the client doesn't have it, at least
        // include an empty dstScope in the list just to show that this scope was specified as
        // inherited, as opposed to being unspecified (which would be treated as all AnyPointer).
        auto& dstScope = dstScopes[dstScopeCount++];
        dstScope.typeId = srcScope.getScopeId();

        KJ_IF_MAYBE(b, clientBrand) {
          for (auto& clientScope: *b) {
            if (clientScope.typeId == dstScope.typeId) {
              // Overwrite the whole thing.
              dstScope = clientScope;
              break;
            }
1510
          }
1511 1512
        } else {
          dstScope.isUnbound = true;
1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
        }
        break;
      }
    }
  }

  dstScopes = dstScopes.slice(0, dstScopeCount);

  std::sort(dstScopes.begin(), dstScopes.end(),
      [](const _::RawBrandedSchema::Scope& a, const _::RawBrandedSchema::Scope& b) {
    return a.typeId < b.typeId;
  });

1526
  return makeBranded(schema, copyDeduped(dstScopes));
1527 1528
}

1529
const _::RawBrandedSchema* SchemaLoader::Impl::makeBranded(
1530
    const _::RawSchema* schema, kj::ArrayPtr<const _::RawBrandedSchema::Scope> bindings) {
1531 1532 1533 1534 1535 1536 1537
  // Note that even if `bindings` is empty, we never want to return defaultBrand here because
  // defaultBrand has special status. Normally, the lack of bindings means all parameters are
  // "unspecified", which means their bindings are unknown and should be treated as AnyPointer.
  // But defaultBrand represents a special case where all parameters are still parameters -- they
  // haven't been bound in the first place. defaultBrand is used to represent the unbranded generic
  // type, while a no-binding brand is equivalent to binding all parameters to AnyPointer.

1538 1539 1540 1541 1542 1543 1544 1545
  if (bindings.size() == 0) {
    return &schema->defaultBrand;
  }

  auto& slot = brands[SchemaBindingsPair { schema, bindings.begin() }];

  if (slot == nullptr) {
    auto& brand = arena.allocate<_::RawBrandedSchema>();
1546
    memset(&brand, 0, sizeof(brand));
1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559
    slot = &brand;

    brand.generic = schema;
    brand.scopes = bindings.begin();
    brand.scopeCount = bindings.size();
    brand.lazyInitializer = &brandedInitializer;
  }

  return slot;
}

kj::ArrayPtr<const _::RawBrandedSchema::Dependency>
SchemaLoader::Impl::makeBrandedDependencies(
1560 1561
    const _::RawSchema* schema,
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> bindings) {
1562 1563 1564 1565 1566 1567 1568 1569 1570
  kj::StringPtr scopeName =
      readMessageUnchecked<schema::Node>(schema->encodedNode).getDisplayName();

  kj::Vector<_::RawBrandedSchema::Dependency> deps;

  schema::Node::Reader node = readMessageUnchecked<schema::Node>(schema->encodedNode);

#define ADD_ENTRY(kind, index, make) \
    if (const _::RawBrandedSchema* dep = make) { \
1571 1572 1573 1574 1575
      auto& slot = deps.add(); \
      memset(&slot, 0, sizeof(slot)); \
      slot.location = _::RawBrandedSchema::makeDepLocation( \
        _::RawBrandedSchema::DepKind::kind, index); \
      slot.schema = dep; \
1576 1577 1578 1579 1580 1581 1582 1583 1584
    }

  switch (node.which()) {
    case schema::Node::FILE:
    case schema::Node::ENUM:
    case schema::Node::ANNOTATION:
      break;

    case schema::Node::CONST:
Kenton Varda's avatar
Kenton Varda committed
1585 1586
      ADD_ENTRY(CONST_TYPE, 0, makeDepSchema(
          node.getConst().getType(), scopeName, bindings));
1587 1588 1589 1590 1591 1592 1593 1594
      break;

    case schema::Node::STRUCT: {
      auto fields = node.getStruct().getFields();
      for (auto i: kj::indices(fields)) {
        auto field = fields[i];
        switch (field.which()) {
          case schema::Field::SLOT:
Kenton Varda's avatar
Kenton Varda committed
1595 1596
            ADD_ENTRY(FIELD, i, makeDepSchema(
                field.getSlot().getType(), scopeName, bindings))
1597 1598 1599 1600 1601
            break;
          case schema::Field::GROUP: {
            const _::RawSchema* group = loadEmpty(
                field.getGroup().getTypeId(),
                "(unknown group type)", schema::Node::STRUCT, true);
1602 1603 1604 1605 1606
            KJ_IF_MAYBE(b, bindings) {
              ADD_ENTRY(FIELD, i, makeBranded(group, *b));
            } else {
              ADD_ENTRY(FIELD, i, getUnbound(group));
            }
1607 1608 1609 1610 1611 1612 1613 1614 1615 1616
            break;
          }
        }
      }
      break;
    }

    case schema::Node::INTERFACE: {
      auto interface = node.getInterface();
      {
1617
        auto superclasses = interface.getSuperclasses();
1618 1619
        for (auto i: kj::indices(superclasses)) {
          auto superclass = superclasses[i];
Kenton Varda's avatar
Kenton Varda committed
1620
          ADD_ENTRY(SUPERCLASS, i, makeDepSchema(
1621
              superclass.getId(), schema::Type::INTERFACE, schema::Node::INTERFACE,
Kenton Varda's avatar
Kenton Varda committed
1622
              superclass.getBrand(), scopeName, bindings))
1623 1624 1625 1626 1627 1628
        }
      }
      {
        auto methods = interface.getMethods();
        for (auto i: kj::indices(methods)) {
          auto method = methods[i];
Kenton Varda's avatar
Kenton Varda committed
1629
          ADD_ENTRY(METHOD_PARAMS, i, makeDepSchema(
1630
              method.getParamStructType(), schema::Type::STRUCT, schema::Node::STRUCT,
Kenton Varda's avatar
Kenton Varda committed
1631 1632
              method.getParamBrand(), scopeName, bindings))
          ADD_ENTRY(METHOD_RESULTS, i, makeDepSchema(
1633
              method.getResultStructType(), schema::Type::STRUCT, schema::Node::STRUCT,
Kenton Varda's avatar
Kenton Varda committed
1634
              method.getResultBrand(), scopeName, bindings))
1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650
        }
      }
      break;
    }
  }

#undef ADD_ENTRY

  std::sort(deps.begin(), deps.end(),
      [](const _::RawBrandedSchema::Dependency& a, const _::RawBrandedSchema::Dependency& b) {
    return a.location < b.location;
  });

  return copyDeduped(deps.asPtr());
}

Kenton Varda's avatar
Kenton Varda committed
1651
void SchemaLoader::Impl::makeDep(_::RawBrandedSchema::Binding& result,
1652
    schema::Type::Reader type, kj::StringPtr scopeName,
1653
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668
  switch (type.which()) {
    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:
Kenton Varda's avatar
Kenton Varda committed
1669 1670
      result.which = static_cast<uint8_t>(type.which());
      return;
1671 1672 1673

    case schema::Type::STRUCT: {
      auto structType = type.getStruct();
Kenton Varda's avatar
Kenton Varda committed
1674 1675 1676
      makeDep(result, structType.getTypeId(), schema::Type::STRUCT, schema::Node::STRUCT,
              structType.getBrand(), scopeName, brandBindings);
      return;
1677 1678 1679
    }
    case schema::Type::ENUM: {
      auto enumType = type.getEnum();
Kenton Varda's avatar
Kenton Varda committed
1680 1681 1682
      makeDep(result, enumType.getTypeId(), schema::Type::ENUM, schema::Node::ENUM,
              enumType.getBrand(), scopeName, brandBindings);
      return;
1683 1684 1685
    }
    case schema::Type::INTERFACE: {
      auto interfaceType = type.getInterface();
Kenton Varda's avatar
Kenton Varda committed
1686 1687 1688
      makeDep(result, interfaceType.getTypeId(), schema::Type::INTERFACE, schema::Node::INTERFACE,
              interfaceType.getBrand(), scopeName, brandBindings);
      return;
1689 1690 1691
    }

    case schema::Type::LIST: {
Kenton Varda's avatar
Kenton Varda committed
1692
      makeDep(result, type.getList().getElementType(), scopeName, brandBindings);
1693
      ++result.listDepth;
Kenton Varda's avatar
Kenton Varda committed
1694
      return;
1695 1696 1697
    }

    case schema::Type::ANY_POINTER: {
Kenton Varda's avatar
Kenton Varda committed
1698
      result.which = static_cast<uint8_t>(schema::Type::ANY_POINTER);
1699 1700 1701
      auto anyPointer = type.getAnyPointer();
      switch (anyPointer.which()) {
        case schema::Type::AnyPointer::UNCONSTRAINED:
Kenton Varda's avatar
Kenton Varda committed
1702
          return;
1703 1704
        case schema::Type::AnyPointer::PARAMETER: {
          auto param = anyPointer.getParameter();
1705
          uint64_t id = param.getScopeId();
1706
          uint16_t index = param.getParameterIndex();
1707 1708 1709 1710 1711 1712 1713

          KJ_IF_MAYBE(b, brandBindings) {
            // TODO(perf): We could binary search here, but... bleh.
            for (auto& scope: *b) {
              if (scope.typeId == id) {
                if (scope.isUnbound) {
                  // Unbound brand parameter.
Kenton Varda's avatar
Kenton Varda committed
1714 1715 1716
                  result.scopeId = id;
                  result.paramIndex = index;
                  return;
1717 1718 1719 1720
                } else if (index >= scope.bindingCount) {
                  // Binding index out-of-range. Treat as AnyPointer. This is important to allow
                  // new type parameters to be added to existing types without breaking dependent
                  // schemas.
Kenton Varda's avatar
Kenton Varda committed
1721
                  return;
1722
                } else {
Kenton Varda's avatar
Kenton Varda committed
1723 1724
                  result = scope.bindings[index];
                  return;
1725
                }
1726 1727
              }
            }
Kenton Varda's avatar
Kenton Varda committed
1728
            return;
1729 1730
          } else {
            // Unbound brand parameter.
Kenton Varda's avatar
Kenton Varda committed
1731 1732 1733
            result.scopeId = id;
            result.paramIndex = index;
            return;
1734 1735
          }
        }
1736
        case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
Kenton Varda's avatar
Kenton Varda committed
1737 1738 1739
          result.isImplicitParameter = true;
          result.paramIndex = anyPointer.getImplicitMethodParameter().getParameterIndex();
          return;
1740
      }
1741
      KJ_UNREACHABLE;
1742 1743 1744 1745 1746 1747
    }
  }

  KJ_UNREACHABLE;
}

Kenton Varda's avatar
Kenton Varda committed
1748
void SchemaLoader::Impl::makeDep(_::RawBrandedSchema::Binding& result,
1749
    uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
1750
    schema::Brand::Reader brand, kj::StringPtr scopeName,
1751
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
1752 1753 1754
  const _::RawSchema* schema = loadEmpty(typeId,
      kj::str("(unknown type; seen as dependency of ", scopeName, ")"),
      expectedKind, true);
Kenton Varda's avatar
Kenton Varda committed
1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775
  result.which = static_cast<uint8_t>(whichType);
  result.schema = makeBranded(schema, brand, brandBindings);
}

const _::RawBrandedSchema* SchemaLoader::Impl::makeDepSchema(
    schema::Type::Reader type, kj::StringPtr scopeName,
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
  _::RawBrandedSchema::Binding binding;
  memset(&binding, 0, sizeof(binding));
  makeDep(binding, type, scopeName, brandBindings);
  return binding.schema;
}

const _::RawBrandedSchema* SchemaLoader::Impl::makeDepSchema(
    uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
    schema::Brand::Reader brand, kj::StringPtr scopeName,
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
  _::RawBrandedSchema::Binding binding;
  memset(&binding, 0, sizeof(binding));
  makeDep(binding, typeId, whichType, expectedKind, brand, scopeName, brandBindings);
  return binding.schema;
1776 1777 1778 1779 1780 1781 1782 1783
}

template <typename T>
kj::ArrayPtr<const T> SchemaLoader::Impl::copyDeduped(kj::ArrayPtr<const T> values) {
  if (values.size() == 0) {
    return kj::arrayPtr(kj::implicitCast<const T*>(nullptr), 0);
  }

1784
  auto bytes = values.asBytes();
1785 1786 1787 1788 1789 1790 1791 1792 1793 1794

  auto iter = dedupTable.find(bytes);
  if (iter != dedupTable.end()) {
    return kj::arrayPtr(reinterpret_cast<const T*>(iter->begin()), values.size());
  }

  // Need to make a new copy.
  auto copy = arena.allocateArray<T>(values.size());
  memcpy(copy.begin(), values.begin(), values.size() * sizeof(T));

1795
  KJ_ASSERT(dedupTable.insert(copy.asBytes()).second);
1796 1797 1798 1799 1800 1801 1802 1803 1804

  return copy;
}

template <typename T>
kj::ArrayPtr<const T> SchemaLoader::Impl::copyDeduped(kj::ArrayPtr<T> values) {
  return copyDeduped(kj::ArrayPtr<const T>(values));
}

1805
SchemaLoader::Impl::TryGetResult SchemaLoader::Impl::tryGet(uint64_t typeId) const {
1806 1807
  auto iter = schemas.find(typeId);
  if (iter == schemas.end()) {
1808
    return {nullptr, initializer.getCallback()};
1809
  } else {
1810
    return {iter->second, initializer.getCallback()};
1811 1812 1813
  }
}

1814 1815 1816 1817 1818 1819 1820 1821 1822
const _::RawBrandedSchema* SchemaLoader::Impl::getUnbound(const _::RawSchema* schema) {
  if (!readMessageUnchecked<schema::Node>(schema->encodedNode).getIsGeneric()) {
    // Not a generic type, so just return the default brand.
    return &schema->defaultBrand;
  }

  auto& slot = unboundBrands[schema];
  if (slot == nullptr) {
    slot = &arena.allocate<_::RawBrandedSchema>();
1823
    memset(slot, 0, sizeof(*slot));
1824 1825 1826 1827 1828 1829 1830 1831 1832
    slot->generic = schema;
    auto deps = makeBrandedDependencies(schema, nullptr);
    slot->dependencies = deps.begin();
    slot->dependencyCount = deps.size();
  }

  return slot;
}

1833
kj::Array<Schema> SchemaLoader::Impl::getAllLoaded() const {
1834 1835 1836 1837 1838 1839
  size_t count = 0;
  for (auto& schema: schemas) {
    if (schema.second->lazyInitializer == nullptr) ++count;
  }

  kj::Array<Schema> result = kj::heapArray<Schema>(count);
1840 1841
  size_t i = 0;
  for (auto& schema: schemas) {
1842 1843 1844
    if (schema.second->lazyInitializer == nullptr) {
      result[i++] = Schema(&schema.second->defaultBrand);
    }
1845 1846 1847 1848
  }
  return result;
}

1849
void SchemaLoader::Impl::requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount) {
1850 1851 1852 1853 1854 1855
  auto& slot = structSizeRequirements[id];
  slot.dataWordCount = kj::max(slot.dataWordCount, dataWordCount);
  slot.pointerCount = kj::max(slot.pointerCount, pointerCount);

  auto iter = schemas.find(id);
  if (iter != schemas.end()) {
1856
    applyStructSizeRequirement(iter->second, dataWordCount, pointerCount);
1857 1858 1859
  }
}

Kenton Varda's avatar
Kenton Varda committed
1860
kj::ArrayPtr<word> SchemaLoader::Impl::makeUncheckedNode(schema::Node::Reader node) {
1861
  size_t size = node.totalSize().wordCount + 1;
1862 1863 1864 1865 1866 1867 1868
  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
1869
    schema::Node::Reader node) {
1870
  if (node.isStruct()) {
1871 1872 1873 1874
    auto iter = structSizeRequirements.find(node.getId());
    if (iter != structSizeRequirements.end()) {
      auto requirement = iter->second;
      auto structNode = node.getStruct();
1875
      if (structNode.getDataWordCount() < requirement.dataWordCount ||
1876
          structNode.getPointerCount() < requirement.pointerCount) {
1877
        return rewriteStructNodeWithSizes(node, requirement.dataWordCount,
1878
                                          requirement.pointerCount);
1879 1880 1881 1882 1883 1884 1885 1886
      }
    }
  }

  return makeUncheckedNode(node);
}

kj::ArrayPtr<word> SchemaLoader::Impl::rewriteStructNodeWithSizes(
1887
    schema::Node::Reader node, uint dataWordCount, uint pointerCount) {
1888 1889 1890
  MallocMessageBuilder builder;
  builder.setRoot(node);

Kenton Varda's avatar
Kenton Varda committed
1891
  auto root = builder.getRoot<schema::Node>();
1892
  auto newStruct = root.getStruct();
1893 1894
  newStruct.setDataWordCount(kj::max(newStruct.getDataWordCount(), dataWordCount));
  newStruct.setPointerCount(kj::max(newStruct.getPointerCount(), pointerCount));
1895 1896 1897 1898 1899

  return makeUncheckedNode(root);
}

void SchemaLoader::Impl::applyStructSizeRequirement(
1900
    _::RawSchema* raw, uint dataWordCount, uint pointerCount) {
Kenton Varda's avatar
Kenton Varda committed
1901
  auto node = readMessageUnchecked<schema::Node>(raw->encodedNode);
1902 1903

  auto structNode = node.getStruct();
1904
  if (structNode.getDataWordCount() < dataWordCount ||
1905
      structNode.getPointerCount() < pointerCount) {
1906
    // Sizes need to be increased.  Must rewrite.
1907
    kj::ArrayPtr<word> words = rewriteStructNodeWithSizes(node, dataWordCount, pointerCount);
1908 1909 1910 1911 1912 1913 1914 1915

    // 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();
  }
}

1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926
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.
1927
    auto lock = loader.impl.lockShared();
1928 1929 1930 1931 1932 1933 1934

    // 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.
1935
#if __GNUC__
1936
    __atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
1937
    __atomic_store_n(&mutableSchema->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1938 1939 1940 1941 1942 1943 1944 1945 1946
#elif _MSC_VER
    std::atomic_thread_fence(std::memory_order_release);
    *static_cast<_::RawSchema::Initializer const* volatile*>(
        &mutableSchema->lazyInitializer) = nullptr;
    *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
        &mutableSchema->defaultBrand.lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957
  }
}

void SchemaLoader::BrandedInitializerImpl::init(const _::RawBrandedSchema* schema) const {
  schema->generic->ensureInitialized();

  auto lock = loader.impl.lockExclusive();

  if (schema->lazyInitializer == nullptr) {
    // Never mind, someone beat us to it.
    return;
1958
  }
1959 1960 1961 1962 1963 1964 1965 1966

  // Get the mutable version.
  auto iter = lock->get()->brands.find(SchemaBindingsPair { schema->generic, schema->scopes });
  KJ_ASSERT(iter != lock->get()->brands.end());

  _::RawBrandedSchema* mutableSchema = iter->second;
  KJ_ASSERT(mutableSchema == schema);

1967
  // Construct its dependency map.
1968 1969 1970 1971 1972 1973
  auto deps = lock->get()->makeBrandedDependencies(mutableSchema->generic,
      kj::arrayPtr(mutableSchema->scopes, mutableSchema->scopeCount));
  mutableSchema->dependencies = deps.begin();
  mutableSchema->dependencyCount = deps.size();

  // It's initialized now, so disable the initializer.
1974
#if __GNUC__
1975
  __atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
1976 1977 1978 1979 1980 1981 1982
#elif _MSC_VER
  std::atomic_thread_fence(std::memory_order_release);
  *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
      &mutableSchema->lazyInitializer) = nullptr;
#else
#error "Platform not supported"
#endif
1983 1984
}

1985 1986
// =======================================================================================

1987 1988 1989
SchemaLoader::SchemaLoader(): impl(kj::heap<Impl>(*this)) {}
SchemaLoader::SchemaLoader(const LazyLoadCallback& callback)
    : impl(kj::heap<Impl>(*this, callback)) {}
1990
SchemaLoader::~SchemaLoader() noexcept(false) {}
1991

1992 1993
Schema SchemaLoader::get(uint64_t id, schema::Brand::Reader brand, Schema scope) const {
  KJ_IF_MAYBE(result, tryGet(id, brand, scope)) {
1994 1995
    return *result;
  } else {
Kenton Varda's avatar
Kenton Varda committed
1996
    KJ_FAIL_REQUIRE("no schema node loaded for id", kj::hex(id));
1997
  }
1998 1999
}

2000 2001
kj::Maybe<Schema> SchemaLoader::tryGet(
    uint64_t id, schema::Brand::Reader brand, Schema scope) const {
2002
  auto getResult = impl.lockShared()->get()->tryGet(id);
2003
  if (getResult.schema == nullptr || getResult.schema->lazyInitializer != nullptr) {
2004 2005
    // This schema couldn't be found or has yet to be lazily loaded. If we have a lazy loader
    // callback, invoke it now to try to get it to load this schema.
2006 2007 2008
    KJ_IF_MAYBE(c, getResult.callback) {
      c->load(*this, id);
    }
2009
    getResult = impl.lockShared()->get()->tryGet(id);
2010 2011
  }
  if (getResult.schema != nullptr && getResult.schema->lazyInitializer == nullptr) {
2012
    if (brand.getScopes().size() > 0) {
2013 2014 2015 2016
      auto brandedSchema = impl.lockExclusive()->get()->makeBranded(
          getResult.schema, brand, kj::arrayPtr(scope.raw->scopes, scope.raw->scopeCount));
      brandedSchema->ensureInitialized();
      return Schema(brandedSchema);
2017 2018 2019
    } else {
      return Schema(&getResult.schema->defaultBrand);
    }
2020
  } else {
2021
    return nullptr;
2022 2023 2024
  }
}

2025 2026 2027 2028 2029
Schema SchemaLoader::getUnbound(uint64_t id) const {
  auto schema = get(id);
  return Schema(impl.lockExclusive()->get()->getUnbound(schema.raw->generic));
}

2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049
Type SchemaLoader::getType(schema::Type::Reader proto, Schema scope) const {
  switch (proto.which()) {
    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:
      return proto.which();

    case schema::Type::STRUCT: {
      auto structType = proto.getStruct();
2050
      return get(structType.getTypeId(), structType.getBrand(), scope).asStruct();
2051 2052 2053 2054
    }

    case schema::Type::ENUM: {
      auto enumType = proto.getEnum();
2055
      return get(enumType.getTypeId(), enumType.getBrand(), scope).asEnum();
2056 2057 2058 2059
    }

    case schema::Type::INTERFACE: {
      auto interfaceType = proto.getInterface();
2060
      return get(interfaceType.getTypeId(), interfaceType.getBrand(), scope)
2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073
          .asInterface();
    }

    case schema::Type::LIST:
      return ListSchema::of(getType(proto.getList().getElementType(), scope));

    case schema::Type::ANY_POINTER: {
      auto anyPointer = proto.getAnyPointer();
      switch (anyPointer.which()) {
        case schema::Type::AnyPointer::UNCONSTRAINED:
          return schema::Type::ANY_POINTER;
        case schema::Type::AnyPointer::PARAMETER: {
          auto param = anyPointer.getParameter();
2074
          return scope.getBrandBinding(param.getScopeId(), param.getParameterIndex());
2075
        }
2076 2077 2078
        case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
          // We don't support binding implicit method params here.
          return schema::Type::ANY_POINTER;
2079 2080 2081 2082 2083 2084 2085 2086 2087
      }

      KJ_UNREACHABLE;
    }
  }

  KJ_UNREACHABLE;
}

Kenton Varda's avatar
Kenton Varda committed
2088
Schema SchemaLoader::load(const schema::Node::Reader& reader) {
2089
  return Schema(&impl.lockExclusive()->get()->load(reader, false)->defaultBrand);
2090 2091
}

Kenton Varda's avatar
Kenton Varda committed
2092
Schema SchemaLoader::loadOnce(const schema::Node::Reader& reader) const {
2093
  auto locked = impl.lockExclusive();
2094 2095 2096 2097
  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.
2098
    return Schema(&locked->get()->load(reader, false)->defaultBrand);
2099
  } else {
2100
    return Schema(&getResult.schema->defaultBrand);
2101
  }
2102 2103
}

2104
kj::Array<Schema> SchemaLoader::getAllLoaded() const {
2105
  return impl.lockShared()->get()->getAllLoaded();
2106 2107
}

2108
void SchemaLoader::loadNative(const _::RawSchema* nativeSchema) {
2109
  impl.lockExclusive()->get()->loadNative(nativeSchema);
2110 2111
}

2112
}  // namespace capnp