#include "ir/possible-contents.h" #include "ir/subtypes.h" #include "parser/wat-parser.h" #include "wasm.h" #include "gtest/gtest.h" using namespace wasm; // Asserts a == b, in any order. template void assertEqualSymmetric(const T& a, const T& b) { EXPECT_EQ(a, b); EXPECT_EQ(b, a); EXPECT_PRED2([](const T& a, const T& b) { return !(a != b); }, a, b); EXPECT_PRED2([](const T& a, const T& b) { return !(b != a); }, a, b); } // Asserts a != b, in any order. template void assertNotEqualSymmetric(const T& a, const T& b) { EXPECT_NE(a, b); EXPECT_NE(b, a); EXPECT_PRED2([](const T& a, const T& b) { return !(a == b); }, a, b); EXPECT_PRED2([](const T& a, const T& b) { return !(b == a); }, a, b); } // Asserts a combined with b (in any order) is equal to c. template void assertCombination(const T& a, const T& b, const T& c) { T temp1 = PossibleContents::combine(a, b); assertEqualSymmetric(temp1, c); // Also check the type, as nulls will compare equal even if their types // differ. We want to make sure even the types are identical. assertEqualSymmetric(temp1.getType(), c.getType()); T temp2 = PossibleContents::combine(b, a); assertEqualSymmetric(temp2, c); assertEqualSymmetric(temp2.getType(), c.getType()); // Verify the shorthand API works like the static one. T temp3 = a; temp3.combine(b); assertEqualSymmetric(temp3, temp1); T temp4 = b; temp4.combine(a); assertEqualSymmetric(temp4, temp2); } // Parse a module from text and return it. static std::unique_ptr parse(std::string module) { auto wasm = std::make_unique(); wasm->features = FeatureSet::All; auto parsed = WATParser::parseModule(*wasm, module); if (auto* err = parsed.getErr()) { Fatal() << err->msg << "\n"; } return wasm; }; // We want to declare a bunch of globals that are used in various tests. Doing // so in the global scope is risky, as their constructors use types, and the // type system itself uses global constructors. Instead, we use a fixture for // that. class PossibleContentsTest : public testing::Test { protected: Type anyref = Type(HeapType::any, Nullable); Type funcref = Type(HeapType::func, Nullable); Type i31ref = Type(HeapType::i31, Nullable); Type structref = Type(HeapType::struct_, Nullable); PossibleContents none = PossibleContents::none(); PossibleContents i32Zero = PossibleContents::literal(Literal(int32_t(0))); PossibleContents i32One = PossibleContents::literal(Literal(int32_t(1))); PossibleContents f64One = PossibleContents::literal(Literal(double(1))); PossibleContents anyNull = PossibleContents::literal(Literal::makeNull(HeapType::any)); PossibleContents funcNull = PossibleContents::literal(Literal::makeNull(HeapType::func)); PossibleContents i31Null = PossibleContents::literal(Literal::makeNull(HeapType::i31)); PossibleContents i32Global1 = PossibleContents::global("i32Global1", Type::i32); PossibleContents i32Global2 = PossibleContents::global("i32Global2", Type::i32); PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64); PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref); PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref); PossibleContents nonNullFuncGlobal = PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable)); PossibleContents nonNullFunc = PossibleContents::literal( Literal("func", Signature(Type::none, Type::none))); PossibleContents exactI32 = PossibleContents::exactType(Type::i32); PossibleContents exactAnyref = PossibleContents::exactType(anyref); PossibleContents exactFuncref = PossibleContents::exactType(funcref); PossibleContents exactStructref = PossibleContents::exactType(structref); PossibleContents exactI31ref = PossibleContents::exactType(i31ref); PossibleContents exactNonNullAnyref = PossibleContents::exactType(Type(HeapType::any, NonNullable)); PossibleContents exactNonNullFuncref = PossibleContents::exactType(Type(HeapType::func, NonNullable)); PossibleContents exactNonNullI31ref = PossibleContents::exactType(Type(HeapType::i31, NonNullable)); PossibleContents exactFuncSignatureType = PossibleContents::exactType( Type(Signature(Type::none, Type::none), Nullable)); PossibleContents exactNonNullFuncSignatureType = PossibleContents::exactType( Type(Signature(Type::none, Type::none), NonNullable)); PossibleContents many = PossibleContents::many(); PossibleContents coneAnyref = PossibleContents::fullConeType(anyref); PossibleContents coneFuncref = PossibleContents::fullConeType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); }; TEST_F(PossibleContentsTest, TestComparisons) { assertEqualSymmetric(none, none); assertNotEqualSymmetric(none, i32Zero); assertNotEqualSymmetric(none, i32Global1); assertNotEqualSymmetric(none, exactI32); assertNotEqualSymmetric(none, many); assertEqualSymmetric(i32Zero, i32Zero); assertNotEqualSymmetric(i32Zero, i32One); assertNotEqualSymmetric(i32Zero, f64One); assertNotEqualSymmetric(i32Zero, i32Global1); assertNotEqualSymmetric(i32Zero, exactI32); assertNotEqualSymmetric(i32Zero, many); assertEqualSymmetric(i32Global1, i32Global1); assertNotEqualSymmetric(i32Global1, i32Global2); assertNotEqualSymmetric(i32Global1, exactI32); assertNotEqualSymmetric(i32Global1, many); assertEqualSymmetric(exactI32, exactI32); assertNotEqualSymmetric(exactI32, exactAnyref); assertNotEqualSymmetric(exactI32, many); assertEqualSymmetric(many, many); // Nulls assertNotEqualSymmetric(i32Zero, anyNull); assertNotEqualSymmetric(anyNull, funcNull); assertEqualSymmetric(anyNull, anyNull); assertEqualSymmetric(exactNonNullAnyref, exactNonNullAnyref); assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref); } TEST_F(PossibleContentsTest, TestHash) { // Hashes should be deterministic. EXPECT_EQ(none.hash(), none.hash()); EXPECT_EQ(many.hash(), many.hash()); // Hashes should be different. (In theory hash collisions could appear here, // but if such simple things collide and the test fails then we should really // rethink our hash functions!) EXPECT_NE(none.hash(), many.hash()); EXPECT_NE(none.hash(), i32Zero.hash()); EXPECT_NE(none.hash(), i32One.hash()); EXPECT_NE(none.hash(), anyGlobal.hash()); EXPECT_NE(none.hash(), funcGlobal.hash()); EXPECT_NE(none.hash(), exactAnyref.hash()); EXPECT_NE(none.hash(), exactFuncSignatureType.hash()); EXPECT_NE(none.hash(), coneAnyref.hash()); EXPECT_NE(none.hash(), coneFuncref.hash()); EXPECT_NE(none.hash(), coneFuncref1.hash()); EXPECT_NE(i32Zero.hash(), i32One.hash()); EXPECT_NE(anyGlobal.hash(), funcGlobal.hash()); EXPECT_NE(exactAnyref.hash(), exactFuncSignatureType.hash()); EXPECT_NE(coneAnyref.hash(), coneFuncref.hash()); EXPECT_NE(coneAnyref.hash(), coneFuncref1.hash()); EXPECT_NE(coneFuncref.hash(), coneFuncref1.hash()); } TEST_F(PossibleContentsTest, TestCombinations) { // None with anything else becomes the other thing. assertCombination(none, none, none); assertCombination(none, i32Zero, i32Zero); assertCombination(none, i32Global1, i32Global1); assertCombination(none, exactI32, exactI32); assertCombination(none, many, many); // i32(0) will become Many, unless the value or the type is identical. assertCombination(i32Zero, i32Zero, i32Zero); assertCombination(i32Zero, i32One, exactI32); assertCombination(i32Zero, f64One, many); assertCombination(i32Zero, i32Global1, exactI32); assertCombination(i32Zero, f64Global, many); assertCombination(i32Zero, exactI32, exactI32); assertCombination(i32Zero, exactAnyref, many); assertCombination(i32Zero, many, many); assertCombination(i32Global1, i32Global1, i32Global1); assertCombination(i32Global1, i32Global2, exactI32); assertCombination(i32Global1, f64Global, many); assertCombination(i32Global1, exactI32, exactI32); assertCombination(i32Global1, exactAnyref, many); assertCombination(i32Global1, many, many); assertCombination(exactI32, exactI32, exactI32); assertCombination(exactI32, exactAnyref, many); assertCombination(exactI32, many, many); assertCombination(many, many, many); // Exact references: An exact reference only stays exact when combined with // the same heap type (nullability may be added, but nothing else). Otherwise // we go to a cone type or to many. assertCombination(exactFuncref, exactAnyref, many); assertCombination(exactFuncref, anyGlobal, many); assertCombination(exactFuncref, nonNullFunc, coneFuncref1); assertCombination(exactFuncref, exactFuncref, exactFuncref); assertCombination(exactFuncref, exactNonNullFuncref, exactFuncref); // Nulls. assertCombination(anyNull, i32Zero, many); assertCombination(anyNull, anyNull, anyNull); assertCombination(anyNull, exactAnyref, exactAnyref); // Two nulls go to the lub. assertCombination(anyNull, i31Null, anyNull); // Incompatible nulls go to Many. assertCombination(anyNull, funcNull, many); assertCombination(exactNonNullAnyref, exactNonNullAnyref, exactNonNullAnyref); // If one is a null and the other is not, it makes the one that is not a // null be a nullable type - but keeps the heap type of the other (since the // type of the null does not matter, all nulls compare equal). assertCombination(anyNull, exactNonNullAnyref, exactAnyref); assertCombination(anyNull, exactNonNullI31ref, exactI31ref); // Funcrefs // A function reference + a null becomes an exact type (of that sig), plus // nullability. assertCombination(nonNullFunc, funcNull, exactFuncSignatureType); assertCombination(exactFuncSignatureType, funcNull, exactFuncSignatureType); assertCombination( exactNonNullFuncSignatureType, funcNull, exactFuncSignatureType); assertCombination( nonNullFunc, exactFuncSignatureType, exactFuncSignatureType); assertCombination( nonNullFunc, exactNonNullFuncSignatureType, exactNonNullFuncSignatureType); assertCombination(nonNullFunc, exactI32, many); // Globals vs nulls. The result is either the global or a null, so all we can // say is that it is something of the global's type, or a null: a cone. assertCombination(anyGlobal, anyNull, coneAnyref); assertCombination(anyGlobal, i31Null, coneAnyref); } static PassOptions options; TEST_F(PossibleContentsTest, TestOracleMinimal) { // A minimal test of the public API of PossibleTypesOracle. See the lit test // for coverage of all the internals (using lit makes the result more // fuzzable). auto wasm = parse(R"( (module (global $null (ref null any) (ref.null any)) (global $something i32 (i32.const 42)) ) )"); ContentOracle oracle(*wasm, options); // This will be a null constant. EXPECT_TRUE(oracle.getContents(GlobalLocation{"null"}).isNull()); // This will be 42. EXPECT_EQ(oracle.getContents(GlobalLocation{"something"}).getLiteral(), Literal(int32_t(42))); } // Asserts a and b have an intersection (or do not), and checks both orderings. void assertHaveIntersection(PossibleContents a, PossibleContents b) { EXPECT_TRUE(PossibleContents::haveIntersection(a, b)); EXPECT_TRUE(PossibleContents::haveIntersection(b, a)); #if BINARYEN_TEST_DEBUG if (!PossibleContents::haveIntersection(a, b) || !PossibleContents::haveIntersection(b, a)) { std::cout << "\nFailure: no intersection:\n" << a << '\n' << b << '\n'; abort(); } #endif } void assertLackIntersection(PossibleContents a, PossibleContents b) { EXPECT_FALSE(PossibleContents::haveIntersection(a, b)); EXPECT_FALSE(PossibleContents::haveIntersection(b, a)); } TEST_F(PossibleContentsTest, TestIntersection) { // None has no contents, so nothing to intersect. assertLackIntersection(none, none); assertLackIntersection(none, i32Zero); assertLackIntersection(none, many); // Many intersects with anything (but none). assertHaveIntersection(many, many); assertHaveIntersection(many, i32Zero); // Different exact types cannot intersect. assertLackIntersection(exactI32, exactAnyref); assertLackIntersection(i32Zero, exactAnyref); // But nullable ones can - the null can be the intersection, if they are not // in separate hierarchies. assertHaveIntersection(exactFuncSignatureType, funcNull); assertLackIntersection(exactFuncSignatureType, exactAnyref); assertLackIntersection(anyNull, funcNull); // Identical types might. assertHaveIntersection(exactI32, exactI32); assertHaveIntersection(i32Zero, i32Zero); assertHaveIntersection(exactFuncSignatureType, exactFuncSignatureType); assertLackIntersection(i32Zero, i32One); // Exact types only differing by nullability can intersect (not on the null, // but on something else). assertHaveIntersection(exactAnyref, exactNonNullAnyref); // Due to subtyping, an intersection might exist. assertHaveIntersection(funcGlobal, funcGlobal); assertHaveIntersection(funcGlobal, exactFuncSignatureType); assertHaveIntersection(nonNullFuncGlobal, exactFuncSignatureType); assertHaveIntersection(funcGlobal, exactNonNullFuncSignatureType); assertHaveIntersection(nonNullFuncGlobal, exactNonNullFuncSignatureType); // Separate hierarchies. assertLackIntersection(funcGlobal, anyGlobal); } TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { // Whenever we combine C = A + B, both A and B must intersect with C. This // helper function gets a set of things and checks that property on them. It // returns the set of all contents it ever observed (see below for how we use // that). auto doTest = [](std::unordered_set set) { std::vector vec(set.begin(), set.end()); // Find the maximum depths for the normalized cone tests later down. std::unordered_set heapTypes; for (auto& contents : set) { auto type = contents.getType(); if (type.isRef()) { auto heapType = type.getHeapType(); if (!heapType.isBasic()) { heapTypes.insert(heapType); } } } std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); SubTypes subTypes(heapTypesVec); auto maxDepths = subTypes.getMaxDepths(); // Go over all permutations up to a certain size (this quickly becomes // extremely slow, obviously, so keep this low). size_t max = 3; auto n = set.size(); // |indexes| contains the indexes of the items in vec for the current // permutation. std::vector indexes(max); std::fill(indexes.begin(), indexes.end(), 0); while (1) { // Test the current permutation: Combine all the relevant things, and then // check they all have an intersection. PossibleContents combination; for (auto index : indexes) { combination.combine(vec[index]); } // Note the combination in the set. set.insert(combination); #if BINARYEN_TEST_DEBUG for (auto index : indexes) { std::cout << index << ' '; combination.combine(vec[index]); } std::cout << '\n'; #endif for (auto index : indexes) { auto item = vec[index]; if (item.isNone()) { assertLackIntersection(combination, item); continue; } #if BINARYEN_TEST_DEBUG if (!PossibleContents::haveIntersection(combination, item)) { std::cout << "\nFailure: no expected intersection. Indexes:\n"; for (auto index : indexes) { std::cout << index << "\n "; vec[index].dump(std::cout); std::cout << '\n'; } std::cout << "combo:\n"; combination.dump(std::cout); std::cout << "\ncompared item (index " << index << "):\n"; item.dump(std::cout); std::cout << '\n'; abort(); } #endif assertHaveIntersection(combination, item); auto type = combination.getType(); if (type.isRef()) { // If we normalize the combination's depth, the item must still have // an intersection. That is, normalization must not have a bug that // results in cones that are too shallow. auto normalizedDepth = maxDepths[type.getHeapType()]; auto normalizedCone = PossibleContents::coneType(type, normalizedDepth); assertHaveIntersection(normalizedCone, item); } // Test intersect() method, which is supported with a full cone type. // In that case we can test that the intersection of A with A + B is // simply A. if (combination.isFullConeType()) { auto intersection = item; intersection.intersect(combination); EXPECT_EQ(intersection, item); #if BINARYEN_TEST_DEBUG if (intersection != item) { std::cout << "\nFailure: wrong intersection.\n"; std::cout << "item: " << item << '\n'; std::cout << "combination: " << combination << '\n'; std::cout << "intersection: " << intersection << '\n'; abort(); } #endif // The intersection is contained in each of the things we intersected // (but we can only compare to the full cone, as the API is restricted // to that). EXPECT_TRUE( PossibleContents::isSubContents(intersection, combination)); } } // Move to the next permutation. size_t i = 0; while (1) { indexes[i]++; if (indexes[i] == n) { // Overflow. indexes[i] = 0; i++; if (i == max) { // All done. return set; } } else { break; } } } WASM_UNREACHABLE("loop above returns manually"); }; // Start from an initial set of the hardcoded contents we have in our test // fixture. std::unordered_set initial = {none, f64One, anyNull, funcNull, i31Null, i32Global1, i32Global2, f64Global, anyGlobal, funcGlobal, nonNullFuncGlobal, nonNullFunc, exactI32, exactAnyref, exactFuncref, exactStructref, exactI31ref, exactNonNullAnyref, exactNonNullFuncref, exactNonNullI31ref, exactFuncSignatureType, exactNonNullFuncSignatureType, many, coneAnyref, coneFuncref, coneFuncref1}; // Add some additional interesting types. auto structType = Type(HeapType(Struct({Field(Type::i32, Immutable)})), NonNullable); initial.insert(PossibleContents::coneType(structType, 0)); auto arrayType = Type(HeapType(Array(Field(Type::i32, Immutable))), NonNullable); initial.insert(PossibleContents::coneType(arrayType, 0)); // After testing on the initial contents, also test using anything new that // showed up while combining them. auto subsequent = doTest(initial); while (subsequent.size() > initial.size()) { initial = subsequent; subsequent = doTest(subsequent); } } void assertIntersection(PossibleContents a, PossibleContents b, PossibleContents result) { auto intersection = a; intersection.intersect(b); EXPECT_EQ(intersection, result); EXPECT_EQ(PossibleContents::haveIntersection(a, b), !result.isNone()); } TEST_F(PossibleContentsTest, TestStructCones) { /* A E / \ B C \ D */ TypeBuilder builder(5); builder.createRecGroup(0, 5); builder[0].setOpen() = Struct(FieldList{}); builder[1].setOpen().subTypeOf(builder[0]) = Struct(FieldList{}); builder[2].setOpen().subTypeOf(builder[0]) = Struct(FieldList{}); builder[3].setOpen().subTypeOf(builder[2]) = Struct(FieldList{}); builder[4].setOpen() = Struct(FieldList{}); auto result = builder.build(); ASSERT_TRUE(result); auto types = *result; auto A = types[0]; auto B = types[1]; auto C = types[2]; auto D = types[3]; auto E = types[4]; ASSERT_FALSE(A.getDeclaredSuperType()); ASSERT_EQ(B.getDeclaredSuperType(), A); ASSERT_EQ(C.getDeclaredSuperType(), A); ASSERT_EQ(D.getDeclaredSuperType(), C); auto nullA = Type(A, Nullable); auto nullB = Type(B, Nullable); auto nullC = Type(C, Nullable); auto nullD = Type(D, Nullable); auto nullE = Type(E, Nullable); auto exactA = PossibleContents::exactType(nullA); auto exactB = PossibleContents::exactType(nullB); auto exactC = PossibleContents::exactType(nullC); auto exactD = PossibleContents::exactType(nullD); auto exactE = PossibleContents::exactType(nullE); auto nnA = Type(A, NonNullable); auto nnB = Type(B, NonNullable); auto nnC = Type(C, NonNullable); auto nnD = Type(D, NonNullable); auto nnE = Type(E, NonNullable); auto nnExactA = PossibleContents::exactType(nnA); auto nnExactB = PossibleContents::exactType(nnB); auto nnExactC = PossibleContents::exactType(nnC); auto nnExactD = PossibleContents::exactType(nnD); auto nnExactE = PossibleContents::exactType(nnE); assertCombination(exactA, exactA, exactA); assertCombination(exactA, exactA, PossibleContents::coneType(nullA, 0)); assertCombination(exactA, exactB, PossibleContents::coneType(nullA, 1)); assertCombination(exactA, exactC, PossibleContents::coneType(nullA, 1)); assertCombination(exactA, exactD, PossibleContents::coneType(nullA, 2)); assertCombination(exactA, exactE, PossibleContents::coneType(structref, 1)); assertCombination( exactA, exactStructref, PossibleContents::coneType(structref, 1)); assertCombination(exactB, exactB, exactB); assertCombination(exactB, exactC, PossibleContents::coneType(nullA, 1)); assertCombination(exactB, exactD, PossibleContents::coneType(nullA, 2)); assertCombination(exactB, exactE, PossibleContents::coneType(structref, 2)); assertCombination( exactB, exactStructref, PossibleContents::coneType(structref, 2)); assertCombination(exactC, exactC, exactC); assertCombination(exactC, exactD, PossibleContents::coneType(nullC, 1)); assertCombination(exactC, exactE, PossibleContents::coneType(structref, 2)); assertCombination( exactC, exactStructref, PossibleContents::coneType(structref, 2)); assertCombination(exactD, exactD, exactD); assertCombination(exactD, exactE, PossibleContents::coneType(structref, 3)); assertCombination( exactD, exactStructref, PossibleContents::coneType(structref, 3)); assertCombination(exactE, exactE, exactE); assertCombination( exactE, exactStructref, PossibleContents::coneType(structref, 1)); assertCombination(exactStructref, exactStructref, exactStructref); assertCombination( exactStructref, exactAnyref, PossibleContents::coneType(anyref, 2)); // Combinations of cones. assertCombination(PossibleContents::coneType(nullA, 5), PossibleContents::coneType(nullA, 7), PossibleContents::coneType(nullA, 7)); // Increment the cone of D as we go here, until it matters. assertCombination(PossibleContents::coneType(nullA, 5), PossibleContents::coneType(nullD, 2), PossibleContents::coneType(nullA, 5)); assertCombination(PossibleContents::coneType(nullA, 5), PossibleContents::coneType(nullD, 3), PossibleContents::coneType(nullA, 5)); assertCombination(PossibleContents::coneType(nullA, 5), PossibleContents::coneType(nullD, 4), PossibleContents::coneType(nullA, 6)); assertCombination(PossibleContents::coneType(nullA, 5), PossibleContents::coneType(nullE, 7), PossibleContents::coneType(structref, 8)); assertCombination(PossibleContents::coneType(nullB, 4), PossibleContents::coneType(structref, 1), PossibleContents::coneType(structref, 6)); // Combinations of cones and exact types. assertCombination(exactA, PossibleContents::coneType(nullA, 3), PossibleContents::coneType(nullA, 3)); assertCombination(exactA, PossibleContents::coneType(nullD, 3), PossibleContents::coneType(nullA, 5)); assertCombination(exactD, PossibleContents::coneType(nullA, 3), PossibleContents::coneType(nullA, 3)); assertCombination(exactA, PossibleContents::coneType(nullE, 2), PossibleContents::coneType(structref, 3)); assertCombination(exactA, PossibleContents::coneType(structref, 1), PossibleContents::coneType(structref, 1)); assertCombination(exactA, PossibleContents::coneType(structref, 2), PossibleContents::coneType(structref, 2)); assertCombination(exactStructref, PossibleContents::coneType(nullB, 3), PossibleContents::coneType(structref, 5)); // Full cones. assertCombination(PossibleContents::fullConeType(nullA), exactA, PossibleContents::fullConeType(nullA)); assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::coneType(nullA, 2), PossibleContents::fullConeType(nullA)); // All full cones with A remain full cones, except for E. assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullA)); assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullB), PossibleContents::fullConeType(nullA)); assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullC), PossibleContents::fullConeType(nullA)); assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullD), PossibleContents::fullConeType(nullA)); assertCombination(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullE), PossibleContents::fullConeType(structref)); // Intersections. Test with non-nullable types to avoid the null being a // possible intersection. assertHaveIntersection(nnExactA, nnExactA); assertLackIntersection(nnExactA, nnExactB); assertLackIntersection(nnExactA, nnExactC); assertLackIntersection(nnExactA, nnExactD); assertLackIntersection(nnExactA, nnExactE); assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactB); assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactC); assertHaveIntersection(PossibleContents::coneType(nnA, 2), nnExactD); assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactD); assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactE); assertLackIntersection(PossibleContents::coneType(nnA, 2), nnExactE); assertHaveIntersection(PossibleContents::coneType(nnA, 1), PossibleContents::coneType(nnC, 100)); assertLackIntersection(PossibleContents::coneType(nnA, 1), PossibleContents::coneType(nnD, 100)); // Neither is a subtype of the other, but nulls are possible, so a null can be // the intersection. assertHaveIntersection(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullE)); // Without null on one side, we cannot intersect. assertLackIntersection(PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nullE)); // Computing intersections is supported with a full cone type. assertIntersection(none, PossibleContents::fullConeType(nnA), none); assertIntersection(many, PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nnA)); assertIntersection(many, PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullA)); assertIntersection(exactA, PossibleContents::fullConeType(nullA), exactA); assertIntersection(nnExactA, PossibleContents::fullConeType(nullA), nnExactA); assertIntersection(exactA, PossibleContents::fullConeType(nnA), nnExactA); assertIntersection(exactB, PossibleContents::fullConeType(nullA), exactB); assertIntersection(nnExactB, PossibleContents::fullConeType(nullA), nnExactB); assertIntersection(exactB, PossibleContents::fullConeType(nnA), nnExactB); auto literalNullA = PossibleContents::literal(Literal::makeNull(A)); assertIntersection( literalNullA, PossibleContents::fullConeType(nullA), literalNullA); assertIntersection(literalNullA, PossibleContents::fullConeType(nnA), none); assertIntersection( literalNullA, PossibleContents::fullConeType(nullB), literalNullA); assertIntersection(literalNullA, PossibleContents::fullConeType(nnB), none); assertIntersection( literalNullA, PossibleContents::fullConeType(nullE), literalNullA); assertIntersection(literalNullA, PossibleContents::fullConeType(nnE), none); assertIntersection(exactA, PossibleContents::fullConeType(nullB), PossibleContents::literal(Literal::makeNull(B))); assertIntersection(nnExactA, PossibleContents::fullConeType(nullB), none); assertIntersection(exactA, PossibleContents::fullConeType(nnB), none); // A and E have no intersection, so the only possibility is a null, and that // null must be the bottom type. assertIntersection( exactA, PossibleContents::fullConeType(nullE), PossibleContents::literal(Literal::makeNull(HeapType::none))); assertIntersection(PossibleContents::coneType(nnA, 1), PossibleContents::fullConeType(nnB), nnExactB); assertIntersection(PossibleContents::coneType(nnB, 1), PossibleContents::fullConeType(nnA), PossibleContents::coneType(nnB, 1)); assertIntersection(PossibleContents::coneType(nnD, 2), PossibleContents::fullConeType(nnA), PossibleContents::coneType(nnD, 2)); assertIntersection(PossibleContents::coneType(nnA, 5), PossibleContents::fullConeType(nnD), PossibleContents::coneType(nnD, 3)); assertIntersection(PossibleContents::coneType(nnA, 1), PossibleContents::fullConeType(nnD), none); // Globals stay as globals, but their type might get refined. assertIntersection( funcGlobal, PossibleContents::fullConeType(funcref), funcGlobal); // No global filtering. auto signature = Type(Signature(Type::none, Type::none), Nullable); assertIntersection( nonNullFunc, PossibleContents::fullConeType(signature), nonNullFunc); // Filter a global to a more specific type. assertIntersection(funcGlobal, PossibleContents::fullConeType(signature), PossibleContents::global("funcGlobal", signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); assertIntersection(funcGlobal, PossibleContents::fullConeType(nonNullFuncRef), nonNullFuncGlobal); // Incompatible global and cone types have no intersection. assertIntersection(funcGlobal, PossibleContents::fullConeType(nullE), none); // Incompatible hierarchies have no intersection. assertIntersection( literalNullA, PossibleContents::fullConeType(funcref), none); // Computing intersections is also supported with a Literal. assertIntersection(i32Zero, i32Zero, i32Zero); assertIntersection(i32One, i32Zero, none); assertIntersection(i32Global1, i32Zero, i32Zero); assertIntersection(funcGlobal, i32Zero, none); assertIntersection( PossibleContents::fullConeType(Type::i32), i32Zero, i32Zero); assertIntersection(PossibleContents::fullConeType(Type::f64), i32Zero, none); // Computing intersections is also supported with empty contents. assertIntersection(none, none, none); assertIntersection(literalNullA, none, none); assertIntersection(funcGlobal, none, none); assertIntersection(PossibleContents::fullConeType(signature), none, none); // Subcontents. This API only supports the case where one of the inputs is a // full cone type. // First, compare exact types to such a cone. EXPECT_TRUE(PossibleContents::isSubContents( exactA, PossibleContents::fullConeType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( nnExactA, PossibleContents::fullConeType(nnA))); EXPECT_TRUE(PossibleContents::isSubContents( nnExactA, PossibleContents::fullConeType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( nnExactD, PossibleContents::fullConeType(nullA))); EXPECT_FALSE(PossibleContents::isSubContents( exactA, PossibleContents::fullConeType(nnA))); EXPECT_FALSE(PossibleContents::isSubContents( exactA, PossibleContents::fullConeType(nullB))); // Next, compare cones. EXPECT_TRUE( PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullA))); EXPECT_TRUE( PossibleContents::isSubContents(PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nnA))); EXPECT_TRUE( PossibleContents::isSubContents(PossibleContents::fullConeType(nullD), PossibleContents::fullConeType(nullA))); EXPECT_FALSE( PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nnA))); EXPECT_FALSE( PossibleContents::isSubContents(PossibleContents::fullConeType(nullA), PossibleContents::fullConeType(nullD))); // Trivial values. EXPECT_TRUE(PossibleContents::isSubContents( PossibleContents::none(), PossibleContents::fullConeType(nullA))); EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::many(), PossibleContents::fullConeType(nullA))); EXPECT_TRUE(PossibleContents::isSubContents( anyNull, PossibleContents::fullConeType(nullA))); EXPECT_FALSE(PossibleContents::isSubContents( anyNull, PossibleContents::fullConeType(nnA))); // Tests cases with a full cone only on the left. Such a cone is only a sub- // contents of Many. EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::fullConeType(nullA), exactA)); EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::fullConeType(nullA), nnExactA)); EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::fullConeType(nullA), PossibleContents::none())); EXPECT_TRUE(PossibleContents::isSubContents( PossibleContents::fullConeType(nullA), PossibleContents::many())); EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::fullConeType(nullA), anyNull)); EXPECT_FALSE(PossibleContents::isSubContents( PossibleContents::fullConeType(nnA), anyNull)); } TEST_F(PossibleContentsTest, TestOracleManyTypes) { // Test for a node with many possible types. The pass limits how many it // notices to not use excessive memory, so even though 4 are possible here, // we'll just report that more than one is possible, a cone of data. auto wasm = parse(R"( (module (type $A (sub (struct (field i32)))) (type $B (sub (struct (field i64)))) (type $C (sub (struct (field f32)))) (type $D (sub (struct (field f64)))) (func $foo (result (ref any)) (select (result (ref any)) (select (result (ref any)) (struct.new_default $A) (struct.new_default $B) (i32.const 0) ) (select (result (ref any)) (struct.new_default $C) (struct.new_default $D) (i32.const 0) ) (i32.const 0) ) ) ) )"); ContentOracle oracle(*wasm, options); // The body's contents must be a cone of data with depth 1. auto bodyContents = oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0}); ASSERT_TRUE(bodyContents.isConeType()); EXPECT_EQ(bodyContents.getType().getHeapType(), HeapType::struct_); EXPECT_EQ(bodyContents.getCone().depth, Index(1)); } TEST_F(PossibleContentsTest, TestOracleNoFullCones) { // PossibleContents should be normalized, so we never have full cones (depth // infinity). auto wasm = parse(R"( (module (type $A (sub (struct (field i32)))) (type $B (sub $A (struct (field i32)))) (type $C (sub $B (struct (field i32)))) (func $foo (export "foo") ;; Note we must declare $C so that $B and $C have uses and are not ;; removed automatically from consideration. (param $a (ref $A)) (param $c (ref $C)) (result (ref any)) (local.get $a) ) ) )"); ContentOracle oracle(*wasm, options); // The function is exported, and all we know about the parameter $a is that it // is some subtype of $A. This is normalized to depth 2 because that is the // actual depth of subtypes. auto bodyContents = oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0}); ASSERT_TRUE(bodyContents.isConeType()); EXPECT_EQ(bodyContents.getCone().depth, Index(2)); }