Skip to content

Commit ca4c474

Browse files
authored
Validate supertypes after isorecursive canonicalization (#4506)
Validating nominally declared supertypes depends on being able to test type equality, which in turn depends on the compared types having already been canonicalized. Previously we validated supertypes before canonicalization, so validation would fail in cases where it should have succeeded. Fix the bug by canonicalizing first. This means that the global type store can now end up holding invalid types, but those types will never be exposed to the user, so that's not a huge problem. Also fix an unrelated bug that was preventing the test from passing, which is that supertypes were being hashed and compared without the special logic for detecting self-references. This bug preventing the equivalent recursion groups in the test from being deduplicated.
1 parent ab64698 commit ca4c474

File tree

2 files changed

+48
-13
lines changed

2 files changed

+48
-13
lines changed

src/wasm/wasm-type.cpp

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,7 +2444,10 @@ size_t RecGroupHasher::hash(const TypeInfo& info) const {
24442444

24452445
size_t RecGroupHasher::hash(const HeapTypeInfo& info) const {
24462446
assert(info.isFinalized);
2447-
size_t digest = wasm::hash(uintptr_t(info.supertype));
2447+
size_t digest = wasm::hash(bool(info.supertype));
2448+
if (info.supertype) {
2449+
hash_combine(digest, hash(HeapType(uintptr_t(info.supertype))));
2450+
}
24482451
wasm::rehash(digest, info.kind);
24492452
switch (info.kind) {
24502453
case HeapTypeInfo::BasicKind:
@@ -2568,9 +2571,16 @@ bool RecGroupEquator::eq(const TypeInfo& a, const TypeInfo& b) const {
25682571
}
25692572

25702573
bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const {
2571-
if (a.supertype != b.supertype) {
2574+
if (bool(a.supertype) != bool(b.supertype)) {
25722575
return false;
25732576
}
2577+
if (a.supertype) {
2578+
HeapType superA(uintptr_t(a.supertype));
2579+
HeapType superB(uintptr_t(b.supertype));
2580+
if (!eq(superA, superB)) {
2581+
return false;
2582+
}
2583+
}
25742584
if (a.kind != b.kind) {
25752585
return false;
25762586
}
@@ -3550,7 +3560,11 @@ void canonicalizeEquirecursive(CanonicalizationState& state) {
35503560
}
35513561

35523562
std::optional<TypeBuilder::Error>
3553-
validateStructuralSubtyping(const std::vector<HeapType>& types) {
3563+
validateSubtyping(const std::vector<HeapType>& types) {
3564+
if (getTypeSystem() == TypeSystem::Equirecursive) {
3565+
// Subtyping is not explicitly declared, so nothing to check.
3566+
return {};
3567+
}
35543568
for (size_t i = 0; i < types.size(); ++i) {
35553569
HeapType type = types[i];
35563570
if (type.isBasic()) {
@@ -3623,11 +3637,6 @@ canonicalizeNominal(CanonicalizationState& state) {
36233637
checked.insert(path.begin(), path.end());
36243638
}
36253639

3626-
// Check that the declared supertypes are valid.
3627-
if (auto error = validateStructuralSubtyping(state.results)) {
3628-
return {*error};
3629-
}
3630-
36313640
return {};
36323641
}
36333642

@@ -3700,11 +3709,6 @@ std::optional<TypeBuilder::Error> canonicalizeIsorecursive(
37003709
return *error;
37013710
}
37023711

3703-
// Check that the declared supertypes are structurally valid.
3704-
if (auto error = validateStructuralSubtyping(state.results)) {
3705-
return {*error};
3706-
}
3707-
37083712
// Now that we know everything is valid, start canonicalizing recursion
37093713
// groups. Before canonicalizing each group, update all the HeapType use sites
37103714
// within it to make sure it only refers to other canonical groups or to
@@ -3839,6 +3843,11 @@ TypeBuilder::BuildResult TypeBuilder::build() {
38393843
<< getMillis(afterStructureCanonicalization, end) << " ms\n";
38403844
#endif
38413845

3846+
// Check that the declared supertypes are structurally valid.
3847+
if (auto error = validateSubtyping(state.results)) {
3848+
return {*error};
3849+
}
3850+
38423851
// Note built signature types. See comment in `HeapType::HeapType(Signature)`.
38433852
for (auto type : state.results) {
38443853
if (type.isSignature() && (getTypeSystem() == TypeSystem::Nominal)) {

test/gtest/type-builder.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,29 @@ TEST_F(IsorecursiveTest, HeapTypeConstructors) {
431431
EXPECT_EQ(struct_, struct2);
432432
EXPECT_EQ(array, array2);
433433
}
434+
435+
TEST_F(IsorecursiveTest, CanonicalizeTypesBeforeSubtyping) {
436+
TypeBuilder builder(6);
437+
// A rec group
438+
builder.createRecGroup(0, 2);
439+
builder[0] = Struct{};
440+
builder[1] = Struct{};
441+
builder[1].subTypeOf(builder[0]);
442+
443+
// The same rec group again
444+
builder.createRecGroup(2, 2);
445+
builder[2] = Struct{};
446+
builder[3] = Struct{};
447+
builder[3].subTypeOf(builder[2]);
448+
449+
// This subtyping only validates if the previous two groups are deduplicated
450+
// before checking subtype validity.
451+
builder[4] =
452+
Struct({Field(builder.getTempRefType(builder[0], Nullable), Immutable)});
453+
builder[5] =
454+
Struct({Field(builder.getTempRefType(builder[3], Nullable), Immutable)});
455+
builder[5].subTypeOf(builder[4]);
456+
457+
auto result = builder.build();
458+
EXPECT_TRUE(result);
459+
}

0 commit comments

Comments
 (0)