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

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

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

38
namespace capnp {
39

40 41 42 43 44 45 46 47 48
namespace {

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

  inline bool operator==(const SchemaBindingsPair& other) const {
    return schema == other.schema && scopeBindings == other.scopeBindings;
  }
49 50
  inline uint hashCode() const {
    return kj::hashCode(schema, scopeBindings);
51 52 53 54 55
  }
};

}  // namespace

56 57 58 59
bool hasDiscriminantValue(const schema::Field::Reader& reader) {
  return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
}

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
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;
};

77 78 79 80 81 82 83 84 85 86
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;
};

87 88
class SchemaLoader::Impl {
public:
89 90
  inline explicit Impl(const SchemaLoader& loader)
      : initializer(loader), brandedInitializer(loader) {}
91
  inline Impl(const SchemaLoader& loader, const LazyLoadCallback& callback)
92
      : initializer(loader, callback), brandedInitializer(loader) {}
93

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

96
  _::RawSchema* loadNative(const _::RawSchema* nativeSchema);
97

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

102 103
  const _::RawBrandedSchema* makeBranded(
      const _::RawSchema* schema, schema::Brand::Reader proto,
104
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> clientBrand);
105

106 107 108 109 110 111
  struct TryGetResult {
    _::RawSchema* schema;
    kj::Maybe<const LazyLoadCallback&> callback;
  };

  TryGetResult tryGet(uint64_t typeId) const;
112

113 114
  const _::RawBrandedSchema* getUnbound(const _::RawSchema* schema);

115
  kj::Array<Schema> getAllLoaded() const;
116

117
  void requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount);
118 119 120 121 122 123
  // 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.

124
  kj::Arena arena;
125 126

private:
127
  kj::HashSet<kj::ArrayPtr<const byte>> dedupTable;
128 129 130
  // 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.

131 132 133
  kj::HashMap<uint64_t, _::RawSchema*> schemas;
  kj::HashMap<SchemaBindingsPair, _::RawBrandedSchema*> brands;
  kj::HashMap<const _::RawSchema*, _::RawBrandedSchema*> unboundBrands;
134

135 136 137 138
  struct RequiredSize {
    uint16_t dataWordCount;
    uint16_t pointerCount;
  };
139
  kj::HashMap<uint64_t, RequiredSize> structSizeRequirements;
140

141
  InitializerImpl initializer;
142
  BrandedInitializerImpl brandedInitializer;
143

Kenton Varda's avatar
Kenton Varda committed
144
  kj::ArrayPtr<word> makeUncheckedNode(schema::Node::Reader node);
145 146 147
  // 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
148
  kj::ArrayPtr<word> makeUncheckedNodeEnforcingSizeRequirements(schema::Node::Reader node);
149 150 151 152 153 154 155
  // 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(
156
      schema::Node::Reader node, uint dataWordCount, uint pointerCount);
157 158 159 160 161
  // 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.
162
  void applyStructSizeRequirement(_::RawSchema* raw, uint dataWordCount, uint pointerCount);
163

164
  const _::RawBrandedSchema* makeBranded(const _::RawSchema* schema,
165 166 167
      kj::ArrayPtr<const _::RawBrandedSchema::Scope> scopes);

  kj::ArrayPtr<const _::RawBrandedSchema::Dependency> makeBrandedDependencies(
168 169
      const _::RawSchema* schema,
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> bindings);
170

Kenton Varda's avatar
Kenton Varda committed
171
  void makeDep(_::RawBrandedSchema::Binding& result,
172
      schema::Type::Reader type, kj::StringPtr scopeName,
173
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
Kenton Varda's avatar
Kenton Varda committed
174
  void makeDep(_::RawBrandedSchema::Binding& result,
175
      uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
176
      schema::Brand::Reader brand, kj::StringPtr scopeName,
177
      kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings);
178
  // Looks up the schema and brand for a dependency, or creates lazily-evaluated placeholders if
Kenton Varda's avatar
Kenton Varda committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  // 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.
196 197 198 199 200 201 202 203 204

  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;
205 206 207 208
};

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

209 210 211 212
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.

213 214 215 216
class SchemaLoader::Validator {
public:
  Validator(SchemaLoader::Impl& loader): loader(loader) {}

Kenton Varda's avatar
Kenton Varda committed
217
  bool validate(const schema::Node::Reader& node) {
218 219 220 221
    isValid = true;
    nodeName = node.getDisplayName();
    dependencies.clear();

222
    KJ_CONTEXT("validating schema node", nodeName, (uint)node.which());
223

224 225 226 227 228 229 230
    if (node.getParameters().size() > 0) {
      KJ_REQUIRE(node.getIsGeneric(), "if parameter list is non-empty, isGeneric must be true") {
        isValid = false;
        return false;
      }
    }

231
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
232
      case schema::Node::FILE:
233
        verifyVoid(node.getFile());
234
        break;
Kenton Varda's avatar
Kenton Varda committed
235
      case schema::Node::STRUCT:
236
        validate(node.getStruct(), node.getScopeId());
237
        break;
Kenton Varda's avatar
Kenton Varda committed
238
      case schema::Node::ENUM:
239
        validate(node.getEnum());
240
        break;
Kenton Varda's avatar
Kenton Varda committed
241
      case schema::Node::INTERFACE:
242
        validate(node.getInterface());
243
        break;
Kenton Varda's avatar
Kenton Varda committed
244
      case schema::Node::CONST:
245
        validate(node.getConst());
246
        break;
Kenton Varda's avatar
Kenton Varda committed
247
      case schema::Node::ANNOTATION:
248
        validate(node.getAnnotation());
249 250 251 252 253 254 255
        break;
    }

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

256
  const _::RawSchema** makeDependencyArray(uint32_t* count) {
257
    *count = dependencies.size();
258 259
    kj::ArrayPtr<const _::RawSchema*> result =
        loader.arena.allocateArray<const _::RawSchema*>(*count);
260 261
    uint pos = 0;
    for (auto& dep: dependencies) {
262
      result[pos++] = dep.value;
263
    }
264
    KJ_DASSERT(pos == *count);
265
    return result.begin();
266 267
  }

268
  const uint16_t* makeMemberInfoArray(uint32_t* count) {
269
    *count = members.size();
270
    kj::ArrayPtr<uint16_t> result = loader.arena.allocateArray<uint16_t>(*count);
271 272
    uint pos = 0;
    for (auto& member: members) {
273
      result[pos++] = member.value;
274
    }
275
    KJ_DASSERT(pos == *count);
276
    return result.begin();
277 278
  }

279 280 281 282
  const uint16_t* makeMembersByDiscriminantArray() {
    return membersByDiscriminant.begin();
  }

283 284 285 286
private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
  bool isValid;
287 288 289 290

  // Maps type IDs -> compiled schemas for each dependency.
  // Order is important because makeDependencyArray() compiles a sorted array.
  kj::TreeMap<uint64_t, _::RawSchema*> dependencies;
291

292
  // Maps name -> index for each member.
293 294
  // Order is important because makeMemberInfoArray() compiles a sorted array.
  kj::TreeMap<Text::Reader, uint> members;
295 296

  kj::ArrayPtr<uint16_t> membersByDiscriminant;
297 298

#define VALIDATE_SCHEMA(condition, ...) \
299
  KJ_REQUIRE(condition, ##__VA_ARGS__) { isValid = false; return; }
300
#define FAIL_VALIDATE_SCHEMA(...) \
301
  KJ_FAIL_REQUIRE(__VA_ARGS__) { isValid = false; return; }
302

303
  void validateMemberName(kj::StringPtr name, uint index) {
304 305 306
    members.upsert(name, index, [&](auto&, auto&&) {
      FAIL_VALIDATE_SCHEMA("duplicate name", name);
    });
Kenton Varda's avatar
Kenton Varda committed
307 308
  }

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

313
    auto fields = structNode.getFields();
314

315
    KJ_STACK_ARRAY(bool, sawCodeOrder, fields.size(), 32, 256);
316 317
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

318 319 320 321 322 323 324 325 326 327 328 329
    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");
330 331
    }

332 333 334
    membersByDiscriminant = loader.arena.allocateArray<uint16_t>(fields.size());
    uint discriminantPos = 0;
    uint nonDiscriminantPos = structNode.getDiscriminantCount();
335

336 337 338 339
    uint index = 0;
    uint nextOrdinal = 0;
    for (auto field: fields) {
      KJ_CONTEXT("validating struct field", field.getName());
340

341 342 343 344 345
      validateMemberName(field.getName(), index);
      VALIDATE_SCHEMA(field.getCodeOrder() < sawCodeOrder.size() &&
                      !sawCodeOrder[field.getCodeOrder()],
                      "invalid codeOrder");
      sawCodeOrder[field.getCodeOrder()] = true;
346

347
      auto ordinal = field.getOrdinal();
348
      if (ordinal.isExplicit()) {
349 350 351
        VALIDATE_SCHEMA(ordinal.getExplicit() >= nextOrdinal,
                        "fields were not ordered by ordinal");
        nextOrdinal = ordinal.getExplicit() + 1;
352
      }
Kenton Varda's avatar
Kenton Varda committed
353

354
      if (hasDiscriminantValue(field)) {
355 356 357 358 359 360 361 362 363 364 365
        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
366

367
      switch (field.which()) {
368 369
        case schema::Field::SLOT: {
          auto slot = field.getSlot();
Kenton Varda's avatar
Kenton Varda committed
370

371 372
          uint fieldBits = 0;
          bool fieldIsPointer = false;
373 374 375
          validate(slot.getType(), slot.getDefaultValue(), &fieldBits, &fieldIsPointer);
          VALIDATE_SCHEMA(fieldBits * (slot.getOffset() + 1) <= dataSizeInBits &&
                          fieldIsPointer * (slot.getOffset() + 1) <= pointerCount,
376
                          "field offset out-of-bounds",
377
                          slot.getOffset(), dataSizeInBits, pointerCount);
Kenton Varda's avatar
Kenton Varda committed
378

379
          break;
Kenton Varda's avatar
Kenton Varda committed
380 381
        }

Kenton Varda's avatar
Kenton Varda committed
382
        case schema::Field::GROUP:
383
          // Require that the group is a struct node.
384
          validateTypeId(field.getGroup().getTypeId(), schema::Node::STRUCT);
385
          break;
Kenton Varda's avatar
Kenton Varda committed
386
      }
387 388

      ++index;
389 390
    }

391 392 393 394 395 396
    // 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");
397

398 399
      // 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.
400
      loader.requireStructSize(scopeId, structNode.getDataWordCount(),
401
                               structNode.getPointerCount());
402 403

      // Require that the parent type is a struct.
Kenton Varda's avatar
Kenton Varda committed
404
      validateTypeId(scopeId, schema::Node::STRUCT);
405 406 407
    }
  }

408 409
  void validate(const schema::Node::Enum::Reader& enumNode) {
    auto enumerants = enumNode.getEnumerants();
410
    KJ_STACK_ARRAY(bool, sawCodeOrder, enumerants.size(), 32, 256);
411 412 413 414
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto enumerant: enumerants) {
415
      validateMemberName(enumerant.getName(), index++);
416 417 418 419 420 421 422 423

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

424
  void validate(const schema::Node::Interface::Reader& interfaceNode) {
425
    for (auto extend: interfaceNode.getSuperclasses()) {
Kenton Varda's avatar
Kenton Varda committed
426
      validateTypeId(extend.getId(), schema::Node::INTERFACE);
427
      validate(extend.getBrand());
428 429
    }

430
    auto methods = interfaceNode.getMethods();
431
    KJ_STACK_ARRAY(bool, sawCodeOrder, methods.size(), 32, 256);
432 433 434 435
    memset(sawCodeOrder.begin(), 0, sawCodeOrder.size() * sizeof(sawCodeOrder[0]));

    uint index = 0;
    for (auto method: methods) {
436
      KJ_CONTEXT("validating method", method.getName());
437
      validateMemberName(method.getName(), index++);
438 439 440 441 442 443

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

444
      validateTypeId(method.getParamStructType(), schema::Node::STRUCT);
445
      validate(method.getParamBrand());
446
      validateTypeId(method.getResultStructType(), schema::Node::STRUCT);
447
      validate(method.getResultBrand());
448 449 450
    }
  }

Kenton Varda's avatar
Kenton Varda committed
451
  void validate(const schema::Node::Const::Reader& constNode) {
452 453 454 455 456
    uint dummy1;
    bool dummy2;
    validate(constNode.getType(), constNode.getValue(), &dummy1, &dummy2);
  }

Kenton Varda's avatar
Kenton Varda committed
457
  void validate(const schema::Node::Annotation::Reader& annotationNode) {
458 459 460
    validate(annotationNode.getType());
  }

Kenton Varda's avatar
Kenton Varda committed
461
  void validate(const schema::Type::Reader& type, const schema::Value::Reader& value,
462 463 464
                uint* dataSizeInBits, bool* isPointer) {
    validate(type);

Kenton Varda's avatar
Kenton Varda committed
465
    schema::Value::Which expectedValueType = schema::Value::VOID;
466
    bool hadCase = false;
467
    switch (type.which()) {
468
#define HANDLE_TYPE(name, bits, ptr) \
Kenton Varda's avatar
Kenton Varda committed
469 470
      case schema::Type::name: \
        expectedValueType = schema::Value::name; \
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
        *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)
492
      HANDLE_TYPE(ANY_POINTER, 0, true)
493 494 495 496
#undef HANDLE_TYPE
    }

    if (hadCase) {
497 498
      VALIDATE_SCHEMA(value.which() == expectedValueType, "Value did not match type.",
                      (uint)value.which(), (uint)expectedValueType);
499 500 501
    }
  }

Kenton Varda's avatar
Kenton Varda committed
502
  void validate(const schema::Type::Reader& type) {
503
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
504 505 506 507 508 509 510 511 512 513 514 515 516 517
      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:
518
      case schema::Type::ANY_POINTER:
519 520
        break;

521 522 523
      case schema::Type::STRUCT: {
        auto structType = type.getStruct();
        validateTypeId(structType.getTypeId(), schema::Node::STRUCT);
524
        validate(structType.getBrand());
525
        break;
526 527 528 529
      }
      case schema::Type::ENUM: {
        auto enumType = type.getEnum();
        validateTypeId(enumType.getTypeId(), schema::Node::ENUM);
530
        validate(enumType.getBrand());
531
        break;
532 533 534 535
      }
      case schema::Type::INTERFACE: {
        auto interfaceType = type.getInterface();
        validateTypeId(interfaceType.getTypeId(), schema::Node::INTERFACE);
536
        validate(interfaceType.getBrand());
537
        break;
538
      }
539

Kenton Varda's avatar
Kenton Varda committed
540
      case schema::Type::LIST:
541
        validate(type.getList().getElementType());
542 543 544 545 546 547
        break;
    }

    // We intentionally allow unknown types.
  }

548 549
  void validate(const schema::Brand::Reader& brand) {
    for (auto scope: brand.getScopes()) {
550
      switch (scope.which()) {
551
        case schema::Brand::Scope::BIND:
552 553
          for (auto binding: scope.getBind()) {
            switch (binding.which()) {
554
              case schema::Brand::Binding::UNBOUND:
555
                break;
556
              case schema::Brand::Binding::TYPE: {
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
                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;
594
        case schema::Brand::Scope::INHERIT:
595 596 597 598 599
          break;
      }
    }
  }

Kenton Varda's avatar
Kenton Varda committed
600
  void validateTypeId(uint64_t id, schema::Node::Which expectedKind) {
601
    _::RawSchema* existing = loader.tryGet(id).schema;
602
    if (existing != nullptr) {
Kenton Varda's avatar
Kenton Varda committed
603
      auto node = readMessageUnchecked<schema::Node>(existing->encodedNode);
604
      VALIDATE_SCHEMA(node.which() == expectedKind,
605
          "expected a different kind of node for this ID",
606
          id, (uint)expectedKind, (uint)node.which(), node.getDisplayName());
607
      dependencies.upsert(id, existing, [](auto&,auto&&) { /* ignore dupe */ });
608 609 610
      return;
    }

611 612 613
    dependencies.upsert(id, loader.loadEmpty(
        id, kj::str("(unknown type used by ", nodeName , ")"), expectedKind, true),
        [](auto&,auto&&) { /* ignore dupe */ });
614 615 616 617 618 619 620 621 622 623 624 625
  }

#undef VALIDATE_SCHEMA
#undef FAIL_VALIDATE_SCHEMA
};

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

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

Kenton Varda's avatar
Kenton Varda committed
626 627
  bool shouldReplace(const schema::Node::Reader& existingNode,
                     const schema::Node::Reader& replacement,
628
                     bool preferReplacementIfEquivalent) {
629 630 631
    this->existingNode = existingNode;
    this->replacementNode = replacement;

632 633
    KJ_CONTEXT("checking compatibility with previously-loaded node of the same id",
               existingNode.getDisplayName());
634

635
    KJ_DREQUIRE(existingNode.getId() == replacement.getId());
636 637 638 639 640 641

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

    checkCompatibility(existingNode, replacement);

642 643
    // Prefer the newer schema.
    return preferReplacementIfEquivalent ? compatibility != OLDER : compatibility == NEWER;
644 645 646 647 648
  }

private:
  SchemaLoader::Impl& loader;
  Text::Reader nodeName;
649 650
  schema::Node::Reader existingNode;
  schema::Node::Reader replacementNode;
651 652 653 654 655 656 657 658 659 660

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

#define VALIDATE_SCHEMA(condition, ...) \
661
  KJ_REQUIRE(condition, ##__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
662
#define FAIL_VALIDATE_SCHEMA(...) \
663
  KJ_FAIL_REQUIRE(__VA_ARGS__) { compatibility = INCOMPATIBLE; return; }
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696

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

702
    VALIDATE_SCHEMA(node.which() == replacement.which(),
703 704
                    "kind of declaration changed");

705
    // No need to check compatibility of most of the non-body parts of the node:
706 707 708
    // - Arbitrary renaming and moving between scopes is allowed.
    // - Annotations are ignored for compatibility purposes.

709 710 711 712 713 714
    if (replacement.getParameters().size() > node.getParameters().size()) {
      replacementIsNewer();
    } else if (replacement.getParameters().size() < node.getParameters().size()) {
      replacementIsOlder();
    }

715
    switch (node.which()) {
Kenton Varda's avatar
Kenton Varda committed
716
      case schema::Node::FILE:
717
        verifyVoid(node.getFile());
718
        break;
Kenton Varda's avatar
Kenton Varda committed
719
      case schema::Node::STRUCT:
720 721
        checkCompatibility(node.getStruct(), replacement.getStruct(),
                           node.getScopeId(), replacement.getScopeId());
722
        break;
Kenton Varda's avatar
Kenton Varda committed
723
      case schema::Node::ENUM:
724
        checkCompatibility(node.getEnum(), replacement.getEnum());
725
        break;
Kenton Varda's avatar
Kenton Varda committed
726
      case schema::Node::INTERFACE:
727
        checkCompatibility(node.getInterface(), replacement.getInterface());
728
        break;
Kenton Varda's avatar
Kenton Varda committed
729
      case schema::Node::CONST:
730
        checkCompatibility(node.getConst(), replacement.getConst());
731
        break;
Kenton Varda's avatar
Kenton Varda committed
732
      case schema::Node::ANNOTATION:
733
        checkCompatibility(node.getAnnotation(), replacement.getAnnotation());
734 735 736 737
        break;
    }
  }

Kenton Varda's avatar
Kenton Varda committed
738 739
  void checkCompatibility(const schema::Node::Struct::Reader& structNode,
                          const schema::Node::Struct::Reader& replacement,
740
                          uint64_t scopeId, uint64_t replacementScopeId) {
741
    if (replacement.getDataWordCount() > structNode.getDataWordCount()) {
742
      replacementIsNewer();
743
    } else if (replacement.getDataWordCount() < structNode.getDataWordCount()) {
744 745
      replacementIsOlder();
    }
746
    if (replacement.getPointerCount() > structNode.getPointerCount()) {
747
      replacementIsNewer();
748
    } else if (replacement.getPointerCount() < structNode.getPointerCount()) {
749 750
      replacementIsOlder();
    }
751 752 753 754 755 756 757 758 759 760 761
    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");
    }

762 763
    // The shared members should occupy corresponding positions in the member lists, since the
    // lists are sorted by ordinal.
764 765 766
    auto fields = structNode.getFields();
    auto replacementFields = replacement.getFields();
    uint count = std::min(fields.size(), replacementFields.size());
767

768
    if (replacementFields.size() > fields.size()) {
769
      replacementIsNewer();
770
    } else if (replacementFields.size() < fields.size()) {
771 772 773 774
      replacementIsOlder();
    }

    for (uint i = 0; i < count; i++) {
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
      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();
      }
794 795 796
    }
  }

Kenton Varda's avatar
Kenton Varda committed
797 798
  void checkCompatibility(const schema::Field::Reader& field,
                          const schema::Field::Reader& replacement) {
799
    KJ_CONTEXT("comparing struct field", field.getName());
800

801 802
    // A field that is initially not in a union can be upgraded to be in one, as long as it has
    // discriminant 0.
803
    uint discriminant = hasDiscriminantValue(field) ? field.getDiscriminantValue() : 0;
804
    uint replacementDiscriminant =
805
        hasDiscriminantValue(replacement) ? replacement.getDiscriminantValue() : 0;
806
    VALIDATE_SCHEMA(discriminant == replacementDiscriminant, "Field discriminant changed.");
807

808
    switch (field.which()) {
809 810
      case schema::Field::SLOT: {
        auto slot = field.getSlot();
811

812
        switch (replacement.which()) {
813 814
          case schema::Field::SLOT: {
            auto replacementSlot = replacement.getSlot();
815

816
            checkCompatibility(slot.getType(), replacementSlot.getType(),
817
                               NO_UPGRADE_TO_STRUCT);
818 819
            checkDefaultCompatibility(slot.getDefaultValue(),
                                      replacementSlot.getDefaultValue());
820

821
            VALIDATE_SCHEMA(slot.getOffset() == replacementSlot.getOffset(),
822 823 824 825
                            "field position changed");
            break;
          }
          case schema::Field::GROUP:
826
            checkUpgradeToStruct(slot.getType(), replacement.getGroup().getTypeId(),
827
                                 existingNode, field);
828 829
            break;
        }
830 831 832

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

Kenton Varda's avatar
Kenton Varda committed
834
      case schema::Field::GROUP:
835
        switch (replacement.which()) {
836 837
          case schema::Field::SLOT:
            checkUpgradeToStruct(replacement.getSlot().getType(), field.getGroup().getTypeId(),
838 839 840
                                 replacementNode, replacement);
            break;
          case schema::Field::GROUP:
841 842
            VALIDATE_SCHEMA(field.getGroup().getTypeId() == replacement.getGroup().getTypeId(),
                            "group id changed");
843 844
            break;
        }
845 846 847 848
        break;
    }
  }

849 850 851 852
  void checkCompatibility(const schema::Node::Enum::Reader& enumNode,
                          const schema::Node::Enum::Reader& replacement) {
    uint size = enumNode.getEnumerants().size();
    uint replacementSize = replacement.getEnumerants().size();
853 854 855 856 857 858 859
    if (replacementSize > size) {
      replacementIsNewer();
    } else if (replacementSize < size) {
      replacementIsOlder();
    }
  }

860 861
  void checkCompatibility(const schema::Node::Interface::Reader& interfaceNode,
                          const schema::Node::Interface::Reader& replacement) {
862 863 864
    {
      // Check superclasses.

865 866 867 868
      kj::Vector<uint64_t> superclasses;
      kj::Vector<uint64_t> replacementSuperclasses;
      for (auto superclass: interfaceNode.getSuperclasses()) {
        superclasses.add(superclass.getId());
869
      }
870 871
      for (auto superclass: replacement.getSuperclasses()) {
        replacementSuperclasses.add(superclass.getId());
872
      }
873 874
      std::sort(superclasses.begin(), superclasses.end());
      std::sort(replacementSuperclasses.begin(), replacementSuperclasses.end());
875

876 877
      auto iter = superclasses.begin();
      auto replacementIter = replacementSuperclasses.begin();
878

879 880
      while (iter != superclasses.end() || replacementIter != replacementSuperclasses.end()) {
        if (iter == superclasses.end()) {
881 882
          replacementIsNewer();
          break;
883
        } else if (replacementIter == replacementSuperclasses.end()) {
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
          replacementIsOlder();
          break;
        } else if (*iter < *replacementIter) {
          replacementIsOlder();
          ++iter;
        } else if (*iter > *replacementIter) {
          replacementIsNewer();
          ++replacementIter;
        } else {
          ++iter;
          ++replacementIter;
        }
      }
    }

899 900 901
    auto methods = interfaceNode.getMethods();
    auto replacementMethods = replacement.getMethods();

902 903 904 905 906 907 908 909 910 911 912 913 914
    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
915 916
  void checkCompatibility(const schema::Method::Reader& method,
                          const schema::Method::Reader& replacement) {
917
    KJ_CONTEXT("comparing method", method.getName());
918

919 920 921 922 923
    // 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.");
924 925
  }

Kenton Varda's avatar
Kenton Varda committed
926 927
  void checkCompatibility(const schema::Node::Const::Reader& constNode,
                          const schema::Node::Const::Reader& replacement) {
928 929 930
    // Who cares?  These don't appear on the wire.
  }

Kenton Varda's avatar
Kenton Varda committed
931 932
  void checkCompatibility(const schema::Node::Annotation::Reader& annotationNode,
                          const schema::Node::Annotation::Reader& replacement) {
933 934 935 936 937 938 939 940
    // 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
941 942
  void checkCompatibility(const schema::Type::Reader& type,
                          const schema::Type::Reader& replacement,
943
                          UpgradeToStructMode upgradeToStructMode) {
944
    if (replacement.which() != type.which()) {
945
      // Check for allowed "upgrade" to Data or AnyPointer.
946
      if (replacement.isData() && canUpgradeToData(type)) {
947 948
        replacementIsNewer();
        return;
949
      } else if (type.isData() && canUpgradeToData(replacement)) {
950 951
        replacementIsOlder();
        return;
952
      } else if (replacement.isAnyPointer() && canUpgradeToAnyPointer(type)) {
953 954
        replacementIsNewer();
        return;
955
      } else if (type.isAnyPointer() && canUpgradeToAnyPointer(replacement)) {
956 957 958 959 960
        replacementIsOlder();
        return;
      }

      if (upgradeToStructMode == ALLOW_UPGRADE_TO_STRUCT) {
961
        if (type.isStruct()) {
962
          checkUpgradeToStruct(replacement, type.getStruct().getTypeId());
963
          return;
964
        } else if (replacement.isStruct()) {
965
          checkUpgradeToStruct(type, replacement.getStruct().getTypeId());
966 967 968 969 970 971 972
          return;
        }
      }

      FAIL_VALIDATE_SCHEMA("a type was changed");
    }

973
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
974 975 976 977 978 979 980 981 982 983 984 985 986 987
      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:
988
      case schema::Type::ANY_POINTER:
989 990
        return;

Kenton Varda's avatar
Kenton Varda committed
991
      case schema::Type::LIST:
992 993
        checkCompatibility(type.getList().getElementType(), replacement.getList().getElementType(),
                           ALLOW_UPGRADE_TO_STRUCT);
994
        return;
995

Kenton Varda's avatar
Kenton Varda committed
996
      case schema::Type::ENUM:
997 998
        VALIDATE_SCHEMA(replacement.getEnum().getTypeId() == type.getEnum().getTypeId(),
                        "type changed enum type");
999 1000
        return;

Kenton Varda's avatar
Kenton Varda committed
1001
      case schema::Type::STRUCT:
1002 1003 1004 1005 1006 1007 1008
        // 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...
1009
        VALIDATE_SCHEMA(replacement.getStruct().getTypeId() == type.getStruct().getTypeId(),
1010 1011 1012
                        "type changed to incompatible struct type");
        return;

Kenton Varda's avatar
Kenton Varda committed
1013
      case schema::Type::INTERFACE:
1014
        VALIDATE_SCHEMA(replacement.getInterface().getTypeId() == type.getInterface().getTypeId(),
1015
                        "type changed to incompatible interface type");
1016 1017 1018 1019 1020 1021
        return;
    }

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

1022 1023 1024
  void checkUpgradeToStruct(const schema::Type::Reader& type, uint64_t structTypeId,
                            kj::Maybe<schema::Node::Reader> matchSize = nullptr,
                            kj::Maybe<schema::Field::Reader> matchPosition = nullptr) {
1025 1026 1027 1028 1029 1030 1031
    // 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));
1032
    MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
1033
    auto node = builder.initRoot<schema::Node>();
1034
    node.setId(structTypeId);
1035
    node.setDisplayName(kj::str("(unknown type used in ", nodeName, ")"));
1036
    auto structNode = node.initStruct();
1037

1038
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
1039
      case schema::Type::VOID:
1040 1041
        structNode.setDataWordCount(0);
        structNode.setPointerCount(0);
1042 1043
        break;

Kenton Varda's avatar
Kenton Varda committed
1044
      case schema::Type::BOOL:
1045 1046
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1047 1048
        break;

Kenton Varda's avatar
Kenton Varda committed
1049 1050
      case schema::Type::INT8:
      case schema::Type::UINT8:
1051 1052
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1053 1054
        break;

Kenton Varda's avatar
Kenton Varda committed
1055 1056 1057
      case schema::Type::INT16:
      case schema::Type::UINT16:
      case schema::Type::ENUM:
1058 1059
        structNode.setDataWordCount(1);
        structNode.setPointerCount(0);
1060 1061
        break;

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

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

Kenton Varda's avatar
Kenton Varda committed
1076 1077 1078 1079 1080
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
1081
      case schema::Type::ANY_POINTER:
1082 1083
        structNode.setDataWordCount(0);
        structNode.setPointerCount(1);
1084 1085 1086
        break;
    }

1087 1088 1089 1090 1091 1092
    KJ_IF_MAYBE(s, matchSize) {
      auto match = s->getStruct();
      structNode.setDataWordCount(match.getDataWordCount());
      structNode.setPointerCount(match.getPointerCount());
    }

1093 1094 1095
    auto field = structNode.initFields(1)[0];
    field.setName("member0");
    field.setCodeOrder(0);
1096 1097
    auto slot = field.initSlot();
    slot.setType(type);
1098 1099 1100 1101 1102 1103 1104

    KJ_IF_MAYBE(p, matchPosition) {
      if (p->getOrdinal().isExplicit()) {
        field.getOrdinal().setExplicit(p->getOrdinal().getExplicit());
      } else {
        field.getOrdinal().setImplicit();
      }
1105 1106 1107
      auto matchSlot = p->getSlot();
      slot.setOffset(matchSlot.getOffset());
      slot.setDefaultValue(matchSlot.getDefaultValue());
1108 1109
    } else {
      field.getOrdinal().setExplicit(0);
1110
      slot.setOffset(0);
1111

1112
      schema::Value::Builder value = slot.initDefaultValue();
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
      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;
1129 1130
        case schema::Type::LIST: value.initList(); break;
        case schema::Type::STRUCT: value.initStruct(); break;
1131
        case schema::Type::INTERFACE: value.setInterface(); break;
1132
        case schema::Type::ANY_POINTER: value.initAnyPointer(); break;
1133 1134
      }
    }
1135

1136
    loader.load(node, true);
1137 1138
  }

Kenton Varda's avatar
Kenton Varda committed
1139
  bool canUpgradeToData(const schema::Type::Reader& type) {
1140
    if (type.isText()) {
1141
      return true;
1142
    } else if (type.isList()) {
1143
      switch (type.getList().getElementType().which()) {
Kenton Varda's avatar
Kenton Varda committed
1144 1145
        case schema::Type::INT8:
        case schema::Type::UINT8:
1146 1147 1148 1149 1150 1151 1152 1153 1154
          return true;
        default:
          return false;
      }
    } else {
      return false;
    }
  }

1155
  bool canUpgradeToAnyPointer(const schema::Type::Reader& type) {
1156
    switch (type.which()) {
Kenton Varda's avatar
Kenton Varda committed
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
      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:
1170 1171
        return false;

Kenton Varda's avatar
Kenton Varda committed
1172 1173 1174 1175 1176
      case schema::Type::TEXT:
      case schema::Type::DATA:
      case schema::Type::LIST:
      case schema::Type::STRUCT:
      case schema::Type::INTERFACE:
1177
      case schema::Type::ANY_POINTER:
1178 1179 1180 1181 1182 1183 1184
        return true;
    }

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

Kenton Varda's avatar
Kenton Varda committed
1185 1186
  void checkDefaultCompatibility(const schema::Value::Reader& value,
                                 const schema::Value::Reader& replacement) {
1187 1188
    // 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.
1189
    KJ_ASSERT(value.which() == replacement.which()) {
1190 1191 1192 1193
      compatibility = INCOMPATIBLE;
      return;
    }

1194
    switch (value.which()) {
1195
#define HANDLE_TYPE(discrim, name) \
Kenton Varda's avatar
Kenton Varda committed
1196
      case schema::Value::discrim: \
1197
        VALIDATE_SCHEMA(value.get##name() == replacement.get##name(), "default value changed"); \
1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
        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
1214 1215 1216 1217 1218
      case schema::Value::TEXT:
      case schema::Value::DATA:
      case schema::Value::LIST:
      case schema::Value::STRUCT:
      case schema::Value::INTERFACE:
1219
      case schema::Value::ANY_POINTER:
1220 1221 1222 1223 1224 1225 1226 1227 1228
        // 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
1229
_::RawSchema* SchemaLoader::Impl::load(const schema::Node::Reader& reader, bool isPlaceholder) {
1230
  // Make a copy of the node which can be used unchecked.
1231
  kj::ArrayPtr<word> validated = makeUncheckedNodeEnforcingSizeRequirements(reader);
1232 1233 1234

  // Validate the copy.
  Validator validator(*this);
Kenton Varda's avatar
Kenton Varda committed
1235
  auto validatedReader = readMessageUnchecked<schema::Node>(validated.begin());
1236 1237 1238 1239 1240

  if (!validator.validate(validatedReader)) {
    // Not valid.  Construct an empty schema of the same type and return that.
    return loadEmpty(validatedReader.getId(),
                     validatedReader.getDisplayName(),
1241 1242
                     validatedReader.which(),
                     false);
1243 1244 1245
  }

  // Check if we already have a schema for this ID.
1246
  _::RawSchema* schema;
1247
  bool shouldReplace;
1248
  bool shouldClearInitializer;
1249
  KJ_IF_MAYBE(match, schemas.find(validatedReader.getId())) {
1250
    // Yes, check if it is compatible and figure out which schema is newer.
1251

1252 1253 1254
    schema = *match;

    // If the existing schema is a placeholder, but we're upgrading it to a non-placeholder, we
1255
    // need to clear the initializer later.
1256
    shouldClearInitializer = schema->lazyInitializer != nullptr && !isPlaceholder;
1257

1258
    auto existing = readMessageUnchecked<schema::Node>(schema->encodedNode);
1259
    CompatibilityChecker checker(*this);
1260 1261 1262 1263

    // Prefer to replace the existing schema if the existing schema is a placeholder.  Otherwise,
    // prefer to keep the existing schema.
    shouldReplace = checker.shouldReplace(
1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
        existing, validatedReader, schema->lazyInitializer != nullptr);
  } else {
    // Nope, allocate a new RawSchema.
    schema = &arena.allocate<_::RawSchema>();
    memset(&schema->defaultBrand, 0, sizeof(schema->defaultBrand));
    schema->id = validatedReader.getId();
    schema->canCastTo = nullptr;
    schema->defaultBrand.generic = schema;
    schema->lazyInitializer = isPlaceholder ? &initializer : nullptr;
    schema->defaultBrand.lazyInitializer = isPlaceholder ? &brandedInitializer : nullptr;
    shouldReplace = true;
    shouldClearInitializer = false;
    schemas.insert(validatedReader.getId(), schema);
1277 1278
  }

1279 1280
  if (shouldReplace) {
    // Initialize the RawSchema.
1281 1282 1283 1284 1285
    schema->encodedNode = validated.begin();
    schema->encodedSize = validated.size();
    schema->dependencies = validator.makeDependencyArray(&schema->dependencyCount);
    schema->membersByName = validator.makeMemberInfoArray(&schema->memberCount);
    schema->membersByDiscriminant = validator.makeMembersByDiscriminantArray();
1286 1287 1288

    // 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.
1289 1290 1291
    auto deps = makeBrandedDependencies(schema, kj::ArrayPtr<const _::RawBrandedSchema::Scope>());
    schema->defaultBrand.dependencies = deps.begin();
    schema->defaultBrand.dependencyCount = deps.size();
1292 1293
  }

1294
  if (shouldClearInitializer) {
1295 1296 1297
    // 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.
1298
#if __GNUC__
1299 1300
    __atomic_store_n(&schema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
    __atomic_store_n(&schema->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1301 1302
#elif _MSC_VER
    std::atomic_thread_fence(std::memory_order_release);
1303
    *static_cast<_::RawSchema::Initializer const* volatile*>(&schema->lazyInitializer) = nullptr;
1304
    *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
1305
        &schema->defaultBrand.lazyInitializer) = nullptr;
1306 1307 1308
#else
#error "Platform not supported"
#endif
1309
  }
1310

1311
  return schema;
1312 1313
}

1314
_::RawSchema* SchemaLoader::Impl::loadNative(const _::RawSchema* nativeSchema) {
1315
  _::RawSchema* schema;
1316
  bool shouldReplace;
1317
  bool shouldClearInitializer;
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
  KJ_IF_MAYBE(match, schemas.find(nativeSchema->id)) {
    schema = *match;
    if (schema->canCastTo != nullptr) {
      // Already loaded natively, or we're currently in the process of loading natively and there
      // was a dependency cycle.
      KJ_REQUIRE(schema->canCastTo == nativeSchema,
          "two different compiled-in type have the same type ID",
          nativeSchema->id,
          readMessageUnchecked<schema::Node>(nativeSchema->encodedNode).getDisplayName(),
          readMessageUnchecked<schema::Node>(schema->canCastTo->encodedNode).getDisplayName());
      return schema;
    } else {
      auto existing = readMessageUnchecked<schema::Node>(schema->encodedNode);
      auto native = readMessageUnchecked<schema::Node>(nativeSchema->encodedNode);
      CompatibilityChecker checker(*this);
      shouldReplace = checker.shouldReplace(existing, native, true);
      shouldClearInitializer = schema->lazyInitializer != nullptr;
    }
  } else {
    schema = &arena.allocate<_::RawSchema>();
    memset(&schema->defaultBrand, 0, sizeof(schema->defaultBrand));
    schema->defaultBrand.generic = schema;
    schema->lazyInitializer = nullptr;
    schema->defaultBrand.lazyInitializer = nullptr;
1342
    shouldReplace = true;
1343
    shouldClearInitializer = false;  // already cleared above
1344
    schemas.insert(nativeSchema->id, schema);
1345 1346
  }

1347
  if (shouldReplace) {
1348 1349 1350
    // Set the schema to a copy of the native schema, but make sure not to null out lazyInitializer
    // yet.
    _::RawSchema temp = *nativeSchema;
1351 1352
    temp.lazyInitializer = schema->lazyInitializer;
    *schema = temp;
1353

1354
    schema->defaultBrand.generic = schema;
1355

1356 1357
    // 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!
1358
    schema->canCastTo = nativeSchema;
1359

1360
    // We need to set the dependency list to point at other loader-owned RawSchemas.
1361
    kj::ArrayPtr<const _::RawSchema*> dependencies =
1362
        arena.allocateArray<const _::RawSchema*>(schema->dependencyCount);
1363 1364 1365
    for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
      dependencies[i] = loadNative(nativeSchema->dependencies[i]);
    }
1366
    schema->dependencies = dependencies.begin();
1367

1368
    // Also need to re-do the branded dependencies.
1369 1370 1371
    auto deps = makeBrandedDependencies(schema, kj::ArrayPtr<const _::RawBrandedSchema::Scope>());
    schema->defaultBrand.dependencies = deps.begin();
    schema->defaultBrand.dependencyCount = deps.size();
1372

1373
    // If there is a struct size requirement, we need to make sure that it is satisfied.
1374 1375 1376
    KJ_IF_MAYBE(sizeReq, structSizeRequirements.find(nativeSchema->id)) {
      applyStructSizeRequirement(schema, sizeReq->dataWordCount,
                                 sizeReq->pointerCount);
1377
    }
1378 1379
  } else {
    // The existing schema is newer.
1380

1381 1382
    // 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!
1383
    schema->canCastTo = nativeSchema;
1384

1385 1386 1387 1388
    // Make sure the dependencies are loaded and compatible.
    for (uint i = 0; i < nativeSchema->dependencyCount; i++) {
      loadNative(nativeSchema->dependencies[i]);
    }
1389 1390
  }

1391 1392 1393 1394
  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.
1395
#if __GNUC__
1396 1397
    __atomic_store_n(&schema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
    __atomic_store_n(&schema->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1398 1399
#elif _MSC_VER
    std::atomic_thread_fence(std::memory_order_release);
1400
    *static_cast<_::RawSchema::Initializer const* volatile*>(&schema->lazyInitializer) = nullptr;
1401
    *static_cast<_::RawBrandedSchema::Initializer const* volatile*>(
1402
        &schema->defaultBrand.lazyInitializer) = nullptr;
1403 1404 1405
#else
#error "Platform not supported"
#endif
1406
  }
1407

1408
  return schema;
1409 1410
}

1411
_::RawSchema* SchemaLoader::Impl::loadEmpty(
1412
    uint64_t id, kj::StringPtr name, schema::Node::Which kind, bool isPlaceholder) {
1413 1414
  word scratch[32];
  memset(scratch, 0, sizeof(scratch));
1415
  MallocMessageBuilder builder(scratch);
Kenton Varda's avatar
Kenton Varda committed
1416
  auto node = builder.initRoot<schema::Node>();
1417 1418 1419
  node.setId(id);
  node.setDisplayName(name);
  switch (kind) {
Kenton Varda's avatar
Kenton Varda committed
1420
    case schema::Node::STRUCT: node.initStruct(); break;
1421 1422
    case schema::Node::ENUM: node.initEnum(); break;
    case schema::Node::INTERFACE: node.initInterface(); break;
1423

Kenton Varda's avatar
Kenton Varda committed
1424 1425 1426
    case schema::Node::FILE:
    case schema::Node::CONST:
    case schema::Node::ANNOTATION:
1427
      KJ_FAIL_REQUIRE("Not a type.");
1428 1429 1430
      break;
  }

1431
  return load(node, isPlaceholder);
1432 1433
}

1434 1435
const _::RawBrandedSchema* SchemaLoader::Impl::makeBranded(
    const _::RawSchema* schema, schema::Brand::Reader proto,
1436
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> clientBrand) {
1437 1438 1439 1440 1441 1442
  kj::StringPtr scopeName =
      readMessageUnchecked<schema::Node>(schema->encodedNode).getDisplayName();

  auto srcScopes = proto.getScopes();

  KJ_STACK_ARRAY(_::RawBrandedSchema::Scope, dstScopes, srcScopes.size(), 16, 32);
1443
  memset(dstScopes.begin(), 0, dstScopes.size() * sizeof(dstScopes[0]));
1444 1445 1446 1447

  uint dstScopeCount = 0;
  for (auto srcScope: srcScopes) {
    switch (srcScope.which()) {
1448
      case schema::Brand::Scope::BIND: {
1449 1450
        auto srcBindings = srcScope.getBind();
        KJ_STACK_ARRAY(_::RawBrandedSchema::Binding, dstBindings, srcBindings.size(), 16, 32);
1451
        memset(dstBindings.begin(), 0, dstBindings.size() * sizeof(dstBindings[0]));
1452 1453 1454 1455 1456 1457 1458 1459 1460

        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()) {
1461
            case schema::Brand::Binding::UNBOUND:
1462
              break;
1463
            case schema::Brand::Binding::TYPE: {
Kenton Varda's avatar
Kenton Varda committed
1464
              makeDep(dstBinding, srcBinding.getType(), scopeName, clientBrand);
1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
              break;
            }
          }
        }

        auto& dstScope = dstScopes[dstScopeCount++];
        dstScope.typeId = srcScope.getScopeId();
        dstScope.bindingCount = dstBindings.size();
        dstScope.bindings = copyDeduped(dstBindings).begin();
        break;
      }
1476
      case schema::Brand::Scope::INHERIT: {
1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
        // 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;
            }
1490
          }
1491 1492
        } else {
          dstScope.isUnbound = true;
1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505
        }
        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;
  });

1506
  return makeBranded(schema, copyDeduped(dstScopes));
1507 1508
}

1509
const _::RawBrandedSchema* SchemaLoader::Impl::makeBranded(
1510
    const _::RawSchema* schema, kj::ArrayPtr<const _::RawBrandedSchema::Scope> bindings) {
1511 1512 1513 1514 1515 1516 1517
  // 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.

1518 1519 1520 1521
  if (bindings.size() == 0) {
    return &schema->defaultBrand;
  }

1522 1523 1524 1525
  SchemaBindingsPair key { schema, bindings.begin() };
  KJ_IF_MAYBE(existing, brands.find(key)) {
    return *existing;
  } else {
1526
    auto& brand = arena.allocate<_::RawBrandedSchema>();
1527
    memset(&brand, 0, sizeof(brand));
1528
    brands.insert(key, &brand);
1529 1530 1531 1532 1533

    brand.generic = schema;
    brand.scopes = bindings.begin();
    brand.scopeCount = bindings.size();
    brand.lazyInitializer = &brandedInitializer;
1534
    return &brand;
1535 1536 1537 1538 1539
  }
}

kj::ArrayPtr<const _::RawBrandedSchema::Dependency>
SchemaLoader::Impl::makeBrandedDependencies(
1540 1541
    const _::RawSchema* schema,
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> bindings) {
1542 1543 1544 1545 1546 1547 1548 1549 1550
  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) { \
1551 1552 1553 1554 1555
      auto& slot = deps.add(); \
      memset(&slot, 0, sizeof(slot)); \
      slot.location = _::RawBrandedSchema::makeDepLocation( \
        _::RawBrandedSchema::DepKind::kind, index); \
      slot.schema = dep; \
1556 1557 1558 1559 1560 1561 1562 1563 1564
    }

  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
1565 1566
      ADD_ENTRY(CONST_TYPE, 0, makeDepSchema(
          node.getConst().getType(), scopeName, bindings));
1567 1568 1569 1570 1571 1572 1573 1574
      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
1575 1576
            ADD_ENTRY(FIELD, i, makeDepSchema(
                field.getSlot().getType(), scopeName, bindings))
1577 1578 1579 1580 1581
            break;
          case schema::Field::GROUP: {
            const _::RawSchema* group = loadEmpty(
                field.getGroup().getTypeId(),
                "(unknown group type)", schema::Node::STRUCT, true);
1582 1583 1584 1585 1586
            KJ_IF_MAYBE(b, bindings) {
              ADD_ENTRY(FIELD, i, makeBranded(group, *b));
            } else {
              ADD_ENTRY(FIELD, i, getUnbound(group));
            }
1587 1588 1589 1590 1591 1592 1593 1594 1595 1596
            break;
          }
        }
      }
      break;
    }

    case schema::Node::INTERFACE: {
      auto interface = node.getInterface();
      {
1597
        auto superclasses = interface.getSuperclasses();
1598 1599
        for (auto i: kj::indices(superclasses)) {
          auto superclass = superclasses[i];
Kenton Varda's avatar
Kenton Varda committed
1600
          ADD_ENTRY(SUPERCLASS, i, makeDepSchema(
1601
              superclass.getId(), schema::Type::INTERFACE, schema::Node::INTERFACE,
Kenton Varda's avatar
Kenton Varda committed
1602
              superclass.getBrand(), scopeName, bindings))
1603 1604 1605 1606 1607 1608
        }
      }
      {
        auto methods = interface.getMethods();
        for (auto i: kj::indices(methods)) {
          auto method = methods[i];
Kenton Varda's avatar
Kenton Varda committed
1609
          ADD_ENTRY(METHOD_PARAMS, i, makeDepSchema(
1610
              method.getParamStructType(), schema::Type::STRUCT, schema::Node::STRUCT,
Kenton Varda's avatar
Kenton Varda committed
1611 1612
              method.getParamBrand(), scopeName, bindings))
          ADD_ENTRY(METHOD_RESULTS, i, makeDepSchema(
1613
              method.getResultStructType(), schema::Type::STRUCT, schema::Node::STRUCT,
Kenton Varda's avatar
Kenton Varda committed
1614
              method.getResultBrand(), scopeName, bindings))
1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630
        }
      }
      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
1631
void SchemaLoader::Impl::makeDep(_::RawBrandedSchema::Binding& result,
1632
    schema::Type::Reader type, kj::StringPtr scopeName,
1633
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
  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
1649 1650
      result.which = static_cast<uint8_t>(type.which());
      return;
1651 1652 1653

    case schema::Type::STRUCT: {
      auto structType = type.getStruct();
Kenton Varda's avatar
Kenton Varda committed
1654 1655 1656
      makeDep(result, structType.getTypeId(), schema::Type::STRUCT, schema::Node::STRUCT,
              structType.getBrand(), scopeName, brandBindings);
      return;
1657 1658 1659
    }
    case schema::Type::ENUM: {
      auto enumType = type.getEnum();
Kenton Varda's avatar
Kenton Varda committed
1660 1661 1662
      makeDep(result, enumType.getTypeId(), schema::Type::ENUM, schema::Node::ENUM,
              enumType.getBrand(), scopeName, brandBindings);
      return;
1663 1664 1665
    }
    case schema::Type::INTERFACE: {
      auto interfaceType = type.getInterface();
Kenton Varda's avatar
Kenton Varda committed
1666 1667 1668
      makeDep(result, interfaceType.getTypeId(), schema::Type::INTERFACE, schema::Node::INTERFACE,
              interfaceType.getBrand(), scopeName, brandBindings);
      return;
1669 1670 1671
    }

    case schema::Type::LIST: {
Kenton Varda's avatar
Kenton Varda committed
1672
      makeDep(result, type.getList().getElementType(), scopeName, brandBindings);
1673
      ++result.listDepth;
Kenton Varda's avatar
Kenton Varda committed
1674
      return;
1675 1676 1677
    }

    case schema::Type::ANY_POINTER: {
Kenton Varda's avatar
Kenton Varda committed
1678
      result.which = static_cast<uint8_t>(schema::Type::ANY_POINTER);
1679 1680 1681
      auto anyPointer = type.getAnyPointer();
      switch (anyPointer.which()) {
        case schema::Type::AnyPointer::UNCONSTRAINED:
Kenton Varda's avatar
Kenton Varda committed
1682
          return;
1683 1684
        case schema::Type::AnyPointer::PARAMETER: {
          auto param = anyPointer.getParameter();
1685
          uint64_t id = param.getScopeId();
1686
          uint16_t index = param.getParameterIndex();
1687 1688 1689 1690 1691 1692 1693

          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
1694 1695 1696
                  result.scopeId = id;
                  result.paramIndex = index;
                  return;
1697 1698 1699 1700
                } 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
1701
                  return;
1702
                } else {
Kenton Varda's avatar
Kenton Varda committed
1703 1704
                  result = scope.bindings[index];
                  return;
1705
                }
1706 1707
              }
            }
Kenton Varda's avatar
Kenton Varda committed
1708
            return;
1709 1710
          } else {
            // Unbound brand parameter.
Kenton Varda's avatar
Kenton Varda committed
1711 1712 1713
            result.scopeId = id;
            result.paramIndex = index;
            return;
1714 1715
          }
        }
1716
        case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
Kenton Varda's avatar
Kenton Varda committed
1717 1718 1719
          result.isImplicitParameter = true;
          result.paramIndex = anyPointer.getImplicitMethodParameter().getParameterIndex();
          return;
1720
      }
1721
      KJ_UNREACHABLE;
1722 1723 1724 1725 1726 1727
    }
  }

  KJ_UNREACHABLE;
}

Kenton Varda's avatar
Kenton Varda committed
1728
void SchemaLoader::Impl::makeDep(_::RawBrandedSchema::Binding& result,
1729
    uint64_t typeId, schema::Type::Which whichType, schema::Node::Which expectedKind,
1730
    schema::Brand::Reader brand, kj::StringPtr scopeName,
1731
    kj::Maybe<kj::ArrayPtr<const _::RawBrandedSchema::Scope>> brandBindings) {
1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742
  const _::RawSchema* schema;
  if (typeId == capnp::typeId<StreamResult>()) {
    // StreamResult is a very special type that is used to mark when a method is declared as
    // streaming ("foo @0 () -> stream;"). We like to auto-load it if we see it as someone's
    // dependency.
    schema = loadNative(&_::rawSchema<StreamResult>());
  } else {
    schema = loadEmpty(typeId,
        kj::str("(unknown type; seen as dependency of ", scopeName, ")"),
        expectedKind, true);
  }
Kenton Varda's avatar
Kenton Varda committed
1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763
  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;
1764 1765 1766 1767 1768 1769 1770 1771
}

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

1772
  auto bytes = values.asBytes();
1773

1774 1775
  KJ_IF_MAYBE(dupe, dedupTable.find(bytes)) {
    return kj::arrayPtr(reinterpret_cast<const T*>(dupe->begin()), values.size());
1776 1777 1778 1779 1780 1781
  }

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

1782
  dedupTable.insert(copy.asBytes());
1783 1784 1785 1786 1787 1788 1789 1790 1791

  return copy;
}

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

1792
SchemaLoader::Impl::TryGetResult SchemaLoader::Impl::tryGet(uint64_t typeId) const {
1793 1794
  KJ_IF_MAYBE(schema, schemas.find(typeId)) {
    return {*schema, initializer.getCallback()};
1795
  } else {
1796
    return {nullptr, initializer.getCallback()};
1797 1798 1799
  }
}

1800 1801 1802 1803 1804 1805
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;
  }

1806 1807 1808 1809
  KJ_IF_MAYBE(existing, unboundBrands.find(schema)) {
    return *existing;
  } else {
    auto slot = &arena.allocate<_::RawBrandedSchema>();
1810
    memset(slot, 0, sizeof(*slot));
1811 1812 1813 1814
    slot->generic = schema;
    auto deps = makeBrandedDependencies(schema, nullptr);
    slot->dependencies = deps.begin();
    slot->dependencyCount = deps.size();
1815 1816
    unboundBrands.insert(schema, slot);
    return slot;
1817 1818 1819
  }
}

1820
kj::Array<Schema> SchemaLoader::Impl::getAllLoaded() const {
1821 1822
  size_t count = 0;
  for (auto& schema: schemas) {
1823
    if (schema.value->lazyInitializer == nullptr) ++count;
1824 1825 1826
  }

  kj::Array<Schema> result = kj::heapArray<Schema>(count);
1827 1828
  size_t i = 0;
  for (auto& schema: schemas) {
1829 1830
    if (schema.value->lazyInitializer == nullptr) {
      result[i++] = Schema(&schema.value->defaultBrand);
1831
    }
1832 1833 1834 1835
  }
  return result;
}

1836
void SchemaLoader::Impl::requireStructSize(uint64_t id, uint dataWordCount, uint pointerCount) {
1837 1838 1839 1840 1841
  structSizeRequirements.upsert(id, { uint16_t(dataWordCount), uint16_t(pointerCount) },
      [&](RequiredSize& existingValue, RequiredSize&& newValue) {
    existingValue.dataWordCount = kj::max(existingValue.dataWordCount, newValue.dataWordCount);
    existingValue.pointerCount = kj::max(existingValue.pointerCount, newValue.pointerCount);
  });
1842

1843 1844
  KJ_IF_MAYBE(schema, schemas.find(id)) {
    applyStructSizeRequirement(*schema, dataWordCount, pointerCount);
1845 1846 1847
  }
}

Kenton Varda's avatar
Kenton Varda committed
1848
kj::ArrayPtr<word> SchemaLoader::Impl::makeUncheckedNode(schema::Node::Reader node) {
1849
  size_t size = node.totalSize().wordCount + 1;
1850 1851 1852 1853 1854 1855 1856
  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
1857
    schema::Node::Reader node) {
1858
  if (node.isStruct()) {
1859
    KJ_IF_MAYBE(requirement, structSizeRequirements.find(node.getId())) {
1860
      auto structNode = node.getStruct();
1861 1862 1863 1864
      if (structNode.getDataWordCount() < requirement->dataWordCount ||
          structNode.getPointerCount() < requirement->pointerCount) {
        return rewriteStructNodeWithSizes(node, requirement->dataWordCount,
                                          requirement->pointerCount);
1865 1866 1867 1868 1869 1870 1871 1872
      }
    }
  }

  return makeUncheckedNode(node);
}

kj::ArrayPtr<word> SchemaLoader::Impl::rewriteStructNodeWithSizes(
1873
    schema::Node::Reader node, uint dataWordCount, uint pointerCount) {
1874 1875 1876
  MallocMessageBuilder builder;
  builder.setRoot(node);

Kenton Varda's avatar
Kenton Varda committed
1877
  auto root = builder.getRoot<schema::Node>();
1878
  auto newStruct = root.getStruct();
1879 1880
  newStruct.setDataWordCount(kj::max(newStruct.getDataWordCount(), dataWordCount));
  newStruct.setPointerCount(kj::max(newStruct.getPointerCount(), pointerCount));
1881 1882 1883 1884 1885

  return makeUncheckedNode(root);
}

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

  auto structNode = node.getStruct();
1890
  if (structNode.getDataWordCount() < dataWordCount ||
1891
      structNode.getPointerCount() < pointerCount) {
1892
    // Sizes need to be increased.  Must rewrite.
1893
    kj::ArrayPtr<word> words = rewriteStructNodeWithSizes(node, dataWordCount, pointerCount);
1894 1895 1896 1897 1898 1899 1900 1901

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

1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912
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.
1913
    auto lock = loader.impl.lockShared();
1914 1915 1916 1917 1918 1919 1920

    // 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.
1921
#if __GNUC__
1922
    __atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
1923
    __atomic_store_n(&mutableSchema->defaultBrand.lazyInitializer, nullptr, __ATOMIC_RELEASE);
1924 1925 1926 1927 1928 1929 1930 1931 1932
#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
1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943
  }
}

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;
1944
  }
1945 1946

  // Get the mutable version.
1947 1948
  _::RawBrandedSchema* mutableSchema = KJ_ASSERT_NONNULL(
      lock->get()->brands.find(SchemaBindingsPair { schema->generic, schema->scopes }));
1949 1950
  KJ_ASSERT(mutableSchema == schema);

1951
  // Construct its dependency map.
1952 1953 1954 1955 1956 1957
  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.
1958
#if __GNUC__
1959
  __atomic_store_n(&mutableSchema->lazyInitializer, nullptr, __ATOMIC_RELEASE);
1960 1961 1962 1963 1964 1965 1966
#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
1967 1968
}

1969 1970
// =======================================================================================

1971 1972 1973
SchemaLoader::SchemaLoader(): impl(kj::heap<Impl>(*this)) {}
SchemaLoader::SchemaLoader(const LazyLoadCallback& callback)
    : impl(kj::heap<Impl>(*this, callback)) {}
1974
SchemaLoader::~SchemaLoader() noexcept(false) {}
1975

1976 1977
Schema SchemaLoader::get(uint64_t id, schema::Brand::Reader brand, Schema scope) const {
  KJ_IF_MAYBE(result, tryGet(id, brand, scope)) {
1978 1979
    return *result;
  } else {
Kenton Varda's avatar
Kenton Varda committed
1980
    KJ_FAIL_REQUIRE("no schema node loaded for id", kj::hex(id));
1981
  }
1982 1983
}

1984 1985
kj::Maybe<Schema> SchemaLoader::tryGet(
    uint64_t id, schema::Brand::Reader brand, Schema scope) const {
1986
  auto getResult = impl.lockShared()->get()->tryGet(id);
1987
  if (getResult.schema == nullptr || getResult.schema->lazyInitializer != nullptr) {
1988 1989
    // 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.
1990 1991 1992
    KJ_IF_MAYBE(c, getResult.callback) {
      c->load(*this, id);
    }
1993
    getResult = impl.lockShared()->get()->tryGet(id);
1994 1995
  }
  if (getResult.schema != nullptr && getResult.schema->lazyInitializer == nullptr) {
1996
    if (brand.getScopes().size() > 0) {
1997 1998 1999 2000
      auto brandedSchema = impl.lockExclusive()->get()->makeBranded(
          getResult.schema, brand, kj::arrayPtr(scope.raw->scopes, scope.raw->scopeCount));
      brandedSchema->ensureInitialized();
      return Schema(brandedSchema);
2001 2002 2003
    } else {
      return Schema(&getResult.schema->defaultBrand);
    }
2004
  } else {
2005
    return nullptr;
2006 2007 2008
  }
}

2009 2010 2011 2012 2013
Schema SchemaLoader::getUnbound(uint64_t id) const {
  auto schema = get(id);
  return Schema(impl.lockExclusive()->get()->getUnbound(schema.raw->generic));
}

2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033
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();
2034
      return get(structType.getTypeId(), structType.getBrand(), scope).asStruct();
2035 2036 2037 2038
    }

    case schema::Type::ENUM: {
      auto enumType = proto.getEnum();
2039
      return get(enumType.getTypeId(), enumType.getBrand(), scope).asEnum();
2040 2041 2042 2043
    }

    case schema::Type::INTERFACE: {
      auto interfaceType = proto.getInterface();
2044
      return get(interfaceType.getTypeId(), interfaceType.getBrand(), scope)
2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057
          .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();
2058
          return scope.getBrandBinding(param.getScopeId(), param.getParameterIndex());
2059
        }
2060 2061 2062
        case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
          // We don't support binding implicit method params here.
          return schema::Type::ANY_POINTER;
2063 2064 2065 2066 2067 2068 2069 2070 2071
      }

      KJ_UNREACHABLE;
    }
  }

  KJ_UNREACHABLE;
}

Kenton Varda's avatar
Kenton Varda committed
2072
Schema SchemaLoader::load(const schema::Node::Reader& reader) {
2073
  return Schema(&impl.lockExclusive()->get()->load(reader, false)->defaultBrand);
2074 2075
}

Kenton Varda's avatar
Kenton Varda committed
2076
Schema SchemaLoader::loadOnce(const schema::Node::Reader& reader) const {
2077
  auto locked = impl.lockExclusive();
2078 2079 2080 2081
  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.
2082
    return Schema(&locked->get()->load(reader, false)->defaultBrand);
2083
  } else {
2084
    return Schema(&getResult.schema->defaultBrand);
2085
  }
2086 2087
}

2088
kj::Array<Schema> SchemaLoader::getAllLoaded() const {
2089
  return impl.lockShared()->get()->getAllLoaded();
2090 2091
}

2092
void SchemaLoader::loadNative(const _::RawSchema* nativeSchema) {
2093
  impl.lockExclusive()->get()->loadNative(nativeSchema);
2094 2095
}

2096
}  // namespace capnp