Regression test for ref.test on externalized nullable references. The bug: runRefTestOp always created non-nullable types for externalized references (TypeCode::Ref), discarding the original nullability info. The fix preserves nullability by checking isNullableRefType(). This test exercises the externalized reference code path in ref.test via the extern.convert_any + ref.test pattern, verifying
| 528 | /// both nullable and non-nullable ref.test targets, as well as null and |
| 529 | /// non-null input values. |
| 530 | TEST(ExecutorRegression, RefTestNullAbility) { |
| 531 | WasmEdge::Configure Conf; |
| 532 | WasmEdge::VM::VM VM(Conf); |
| 533 | |
| 534 | ASSERT_TRUE(VM.loadWasm(RefTestNullabilityWasm)); |
| 535 | ASSERT_TRUE(VM.validate()); |
| 536 | ASSERT_TRUE(VM.instantiate()); |
| 537 | |
| 538 | // --- Part 1: Non-null anyref input --- |
| 539 | // |
| 540 | // When a non-null anyref is externalized via extern.convert_any, the |
| 541 | // externalized flag is set on the value. The ref.test instruction must |
| 542 | // correctly handle this externalized type when matching against both |
| 543 | // nullable and non-nullable extern targets. |
| 544 | // |
| 545 | // Note: The runtime parameter-pushing code converts non-null nullable refs |
| 546 | // to non-nullable before execution. So the externalized type at ref.test |
| 547 | // time will be non-nullable. Both ref.test targets should succeed. |
| 548 | { |
| 549 | int DummyObj = 42; |
| 550 | RefVariant AnyRefVal(ValType(TypeCode::RefNull, TypeCode::AnyRef), |
| 551 | reinterpret_cast<void *>(&DummyObj)); |
| 552 | |
| 553 | std::vector<ValVariant> Params = {ValVariant(AnyRefVal)}; |
| 554 | std::vector<ValType> ParamTypes = {ValType(TypeCode::AnyRef)}; |
| 555 | |
| 556 | // Test 1: extern.convert_any + ref.test (ref null extern) |
| 557 | // Non-null externalized ref with non-nullable type should match |
| 558 | // nullable extern target. |
| 559 | { |
| 560 | auto Result = VM.execute("test_nullable", Params, ParamTypes); |
| 561 | ASSERT_TRUE(Result); |
| 562 | ASSERT_EQ((*Result).size(), 1U); |
| 563 | EXPECT_EQ((*Result)[0].first.get<uint32_t>(), 1U) |
| 564 | << "Non-null externalized anyref should match (ref null extern)"; |
| 565 | } |
| 566 | |
| 567 | // Test 2: extern.convert_any + ref.test (ref extern) |
| 568 | // Non-null externalized ref with non-nullable type should also match |
| 569 | // non-nullable extern target (the externalized type is (ref extern) |
| 570 | // because the runtime made it non-nullable before execution). |
| 571 | { |
| 572 | auto Result = VM.execute("test_nonnull", Params, ParamTypes); |
| 573 | ASSERT_TRUE(Result); |
| 574 | ASSERT_EQ((*Result).size(), 1U); |
| 575 | EXPECT_EQ((*Result)[0].first.get<uint32_t>(), 1U) |
| 576 | << "Non-null externalized anyref should match (ref extern)"; |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | // --- Part 2: Null anyref input --- |
| 581 | // |
| 582 | // When a null anyref is externalized, extern.convert_any creates a new |
| 583 | // RefVariant with type (ref null noextern) without setting the externalized |
| 584 | // flag. ref.test follows the normal (non-externalized) code path. |
| 585 | // A null value should match nullable target but not non-nullable. |
| 586 | { |
| 587 | RefVariant NullAnyRef(ValType(TypeCode::RefNull, TypeCode::NullRef)); |
nothing calls this directly
no test coverage detected