Repository: WasmEdge/WasmEdge (2026-04-07T19:43:47Z)

Issue:
bug: [GC] WasmEdge segfault on `ref.cast eqref` after `table.get`
### Summary

# WasmEdge SIGSEGV: `ref.cast` on null reference from concrete-typed table

## Version

- WasmEdge 0.16.1, commit `3ad922d67525975f6b4ec0e382604d91f97354bd`
- Linux 5.15.0-168-generic x86\_64

## Minimal Reproducer

```wat
(module
  (type $s (struct))
  (table 1 (ref null $s))
  (func (export "run")
    i32.const 0
    table.get 0      ;; null (ref null $s) from uninitialized slot
    ref.cast eqref   ;; CRASH
    drop
  )
)
```

```bash
wasm-tools parse crash.wat -o crash.wasm
wasm-tools validate crash.wasm   # OK
wasmedge crash.wasm run          # Segmentation fault
d8 run.js -- crash.wasm                 # OK
```

## GDB Analysis

```bash
gdb -batch -ex "run" -ex "bt" -ex "x/5i \$rip-4" -ex "info registers rax r15" \
  -ex "x/2gx \$r15" --args /users/khan22/.wasmedge/bin/wasmedge crash.wasm run
```

```
Thread 2 "wasmedge" received signal SIGSEGV, Segmentation fault.
0x00007ffff1c6ca05 in ?? () from /users/khan22/.wasmedge/lib/libwasmedge.so.0.1.0

#0  0x00007ffff1c6ca05  libwasmedge.so.0
#1  0x00007ffff1c6d25c  libwasmedge.so.0
#2  0x00007ffff1c6d8a8  libwasmedge.so.0
#3  0x00007ffff1ca6a26  libwasmedge.so.0
#4  0x00007ffff1b91cab  libwasmedge.so.0
#5  0x00007ffff1b9595f  libwasmedge.so.0
#6  0x00007ffff1b96808  libwasmedge.so.0
#7  0x00007ffff1bd0629  libwasmedge.so.0
#8  0x00007ffff1262253  libstdc++.so.6
#9  0x00007ffff0eeaac3  start_thread (pthread_create.c:442)

   0x7ffff1c6ca01:  mov    0x8(%r15),%rax    ; load object pointer
=> 0x7ffff1c6ca05:  mov    (%rax),%rax        ; CRASH: rax = 0x0
   0x7ffff1c6ca08:  mov    %rdi,%rcx
   0x7ffff1c6ca0b:  mov    %rsi,%r8
   0x7ffff1c6ca0e:  test   %rax,%rax          ; null check (too late)
   0x7ffff1c6ca11:  je     0x7ffff1c6ca28

rax  0x0
r15  0x7fffec000cd0

0x7fffec000cd0:  0x0000000800630000   0x0000000000000000
                 (type: struct idx 8)  (object pointer: NULL)
```

Corresponding location in `/users/khan22/.wasmedge/lib/libwasmedge.so.0.1.0` (text base `0x7ffff141c000`):

```bash
objdump -d --start-address=0x8509a0 --stop-address=0x850a30 \
  /users/khan22/.wasmedge/lib/libwasmedge.so.0.1.0
```

```asm
850a01:  49 8b 47 08          mov    0x8(%r15),%rax
850a05:  48 8b 00             mov    (%rax),%rax       ; NULL deref
850a0e:  48 85 c0             test   %rax,%rax         ; null check after deref maybe?
850a11:  74 15                je     850a28
```

## (Potential) Root Cause

`table.get` on a concrete-typed table (`(table N (ref null $type))`) returns a null reference with object pointer `0x0` on the interpreter value stack. The `ref.cast` handler then dereferences this pointer to read the type descriptor before checking for null. The null check exists but is placed after the dereference.

## Acknowledgement
This bug has been found during development process of `GCsmith`. 

_MORE INFO WILL BE ADDED ABOUT ACKNOWLEDGEMENT_


### Current State

_No response_

### Expected State

_No response_

### Reproduction steps

1. Build with options '...'
2. Execute with flags '....'
3. Execute with inputs '....'
4. Get error


### Screenshots

![DESCRIPTION](LINK.png)


### Any logs you want to share for showing the specific issue

_No response_

### Components

CLI

### WasmEdge Version or Commit you used

0.16.1

### Operating system information

Ubuntu 22.04.5 LTS

### Hardware Architecture

Linux 5.15.0-168-generic x86\_64

### Compiler flags and options

No-option


Discussion:

Patch:
diff --git a/lib/executor/instantiate/table.cpp b/lib/executor/instantiate/table.cpp
index e377e7af86ba..e9ee5e342c18 100644
--- a/lib/executor/instantiate/table.cpp
+++ b/lib/executor/instantiate/table.cpp
@@ -31,7 +31,11 @@ Expect<void> Executor::instantiate(Runtime::StackManager &StackMgr,
       ModInst.addTable(TabSeg.getTableType(), InitTabValue);
     } else {
       // No init expression case. Use the null reference to initialize.
-      ModInst.addTable(TabSeg.getTableType());
+      // Normalize the type to the bottom abstract heap type so that null
+      // references always carry abstract types, as ref.cast/ref.test assume.
+      auto BotType = toBottomType(StackMgr, TabSeg.getTableType().getRefType());
+      RefVariant InitTabValue(ValType(TypeCode::RefNull, BotType));
+      ModInst.addTable(TabSeg.getTableType(), InitTabValue);
     }
   }
   return {};


Test patch:
diff --git a/test/executor/ExecutorRegressionTest.cpp b/test/executor/ExecutorRegressionTest.cpp
index 6c6baffecd2f..38bbd629bbcf 100644
--- a/test/executor/ExecutorRegressionTest.cpp
+++ b/test/executor/ExecutorRegressionTest.cpp
@@ -25,6 +25,59 @@ namespace {
 
 using namespace WasmEdge;
 
+/// Host function that records i32 values for later inspection.
+class Check : public Runtime::HostFunction<Check> {
+public:
+  Expect<void> body(const Runtime::CallingFrame &, uint32_t Value) {
+    Values.push_back(Value);
+    return {};
+  }
+  Span<const uint32_t> getValues() const noexcept { return Values; }
+
+private:
+  std::vector<uint32_t> Values;
+};
+
+/// Module that provides a "check" host function under the "gc" namespace.
+class GCCheckModule : public Runtime::Instance::ModuleInstance {
+public:
+  GCCheckModule() : ModuleInstance("gc") {
+    auto CP = std::make_unique<Check>();
+    C = CP.get();
+    addHostFunc("check", std::move(CP));
+  }
+  Span<const uint32_t> getValues() const noexcept { return C->getValues(); }
+
+private:
+  Check *C = nullptr;
+};
+
+/// After instantiation, read the exported table "t" at index 0 and verify the
+/// null reference has been normalized to an abstract heap type. This catches
+/// the root cause of #4757 without executing wasm that would segfault if the
+/// fix is absent.
+bool checkNullTableEntry(VM::VM &VMInst) {
+  const auto *ModInst = VMInst.getActiveModule();
+  if (!ModInst) {
+    return false;
+  }
+  auto *TabInst = ModInst->findTableExports("t");
+  if (!TabInst) {
+    return false;
+  }
+  auto RefRes = TabInst->getRefAddr(0);
+  if (!RefRes) {
+    return false;
+  }
+  auto Ref = *RefRes;
+  EXPECT_TRUE(Ref.isNull());
+  EXPECT_TRUE(Ref.getType().isAbsHeapType())
+      << "Null reference in concrete-typed table must be normalized to an "
+         "abstract heap type (issue #4757)";
+  return Ref.isNull() && Ref.getType().isAbsHeapType();
+}
+
+// clang-format off
 /// Binary Wasm module with functions testing ref.test on externalized refs.
 ///
 /// (module
@@ -66,7 +119,6 @@ using namespace WasmEdge;
 ///   (export "test_ext_nullable" (func 2))
 ///   (export "test_ext_nonnull" (func 3))
 /// )
-// clang-format off
 std::array<WasmEdge::Byte, 148> RefTestNullabilityWasm{
     0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0B, 0x02, 0x60,
     0x01, 0x6E, 0x01, 0x7F, 0x60, 0x01, 0x6F, 0x01, 0x7F, 0x03, 0x05, 0x04,
@@ -80,8 +132,149 @@ std::array<WasmEdge::Byte, 148> RefTestNullabilityWasm{
     0xFB, 0x1B, 0xFB, 0x15, 0x6F, 0x0B, 0x09, 0x00, 0x20, 0x00, 0xFB, 0x1B,
     0xFB, 0x14, 0x6F, 0x0B, 0x0B, 0x00, 0x20, 0x00, 0xFB, 0x1A, 0xFB, 0x1B,
     0xFB, 0x15, 0x6F, 0x0B, 0x0B, 0x00, 0x20, 0x00, 0xFB, 0x1A, 0xFB, 0x1B,
-    0xFB, 0x14, 0x6F, 0x0B
-};
+    0xFB, 0x14, 0x6F, 0x0B};
+
+/// Binary Wasm module: ref.test on null ref from concrete-typed table.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table (export "t") 1 (ref null $s))
+///   (func (export "test")
+///     i32.const 0  table.get 0  ref.test eqref   call $check
+///     i32.const 0  table.get 0  ref.test (ref $s) call $check
+///   ))
+std::array<WasmEdge::Byte, 106> NullRefTestWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x03, 0x5f,
+    0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x0c, 0x01, 0x02,
+    0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x00, 0x01, 0x03, 0x02,
+    0x01, 0x02, 0x04, 0x05, 0x01, 0x63, 0x00, 0x00, 0x01, 0x07, 0x0c, 0x02,
+    0x01, 0x74, 0x01, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x01, 0x0a,
+    0x16, 0x01, 0x14, 0x00, 0x41, 0x00, 0x25, 0x00, 0xfb, 0x15, 0x6d, 0x10,
+    0x00, 0x41, 0x00, 0x25, 0x00, 0xfb, 0x14, 0x00, 0x10, 0x00, 0x0b, 0x00,
+    0x15, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x08, 0x01, 0x00, 0x05, 0x63,
+    0x68, 0x65, 0x63, 0x6b, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73};
+
+/// Binary Wasm module: function returning null ref with concrete type,
+/// then ref.test.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table (export "t") 1 (ref null $s))
+///   (func $get_null (result (ref null $s))
+///     i32.const 0  table.get 0)
+///   (func (export "test")
+///     call $get_null  ref.test eqref  call $check
+///   ))
+std::array<WasmEdge::Byte, 118> NullReturnConcreteWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0f, 0x04, 0x5f,
+    0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x01, 0x63, 0x00, 0x60, 0x00,
+    0x00, 0x02, 0x0c, 0x01, 0x02, 0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63,
+    0x6b, 0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x03, 0x04, 0x05, 0x01, 0x63,
+    0x00, 0x00, 0x01, 0x07, 0x0c, 0x02, 0x01, 0x74, 0x01, 0x00, 0x04, 0x74,
+    0x65, 0x73, 0x74, 0x00, 0x02, 0x0a, 0x12, 0x02, 0x06, 0x00, 0x41, 0x00,
+    0x25, 0x00, 0x0b, 0x09, 0x00, 0x10, 0x01, 0xfb, 0x15, 0x6d, 0x10, 0x00,
+    0x0b, 0x00, 0x1f, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x12, 0x02, 0x00,
+    0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x01, 0x08, 0x67, 0x65, 0x74, 0x5f,
+    0x6e, 0x75, 0x6c, 0x6c, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73};
+
+/// Binary Wasm module: table.grow creates null entries, then ref.cast.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table $t (export "t") 0 (ref null $s))
+///   (func (export "test")
+///     ref.null $s  i32.const 1  table.grow $t  drop
+///     i32.const 0  table.get $t  ref.cast eqref  ref.is_null
+///     call $check
+///   ))
+std::array<WasmEdge::Byte, 112> NullTableGrowWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x03, 0x5f,
+    0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x0c, 0x01, 0x02,
+    0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x00, 0x01, 0x03, 0x02,
+    0x01, 0x02, 0x04, 0x05, 0x01, 0x63, 0x00, 0x00, 0x00, 0x07, 0x0c, 0x02,
+    0x01, 0x74, 0x01, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x01, 0x0a,
+    0x16, 0x01, 0x14, 0x00, 0xd0, 0x00, 0x41, 0x01, 0xfc, 0x0f, 0x00, 0x1a,
+    0x41, 0x00, 0x25, 0x00, 0xfb, 0x17, 0x6d, 0xd1, 0x10, 0x00, 0x0b, 0x00,
+    0x1b, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x08, 0x01, 0x00, 0x05, 0x63,
+    0x68, 0x65, 0x63, 0x6b, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73, 0x05, 0x04,
+    0x01, 0x00, 0x01, 0x74};
+
+/// Binary Wasm module: ref.cast eqref on null ref from concrete-typed table.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table (export "t") 1 (ref null $s))
+///   (func (export "test")
+///     i32.const 0  table.get 0  ref.cast eqref
+///     ref.is_null  call $check
+///   ))
+std::array<WasmEdge::Byte, 98> NullRefCastWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x03,
+    0x5f, 0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x0c,
+    0x01, 0x02, 0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x00,
+    0x01, 0x03, 0x02, 0x01, 0x02, 0x04, 0x05, 0x01, 0x63, 0x00, 0x00,
+    0x01, 0x07, 0x0c, 0x02, 0x01, 0x74, 0x01, 0x00, 0x04, 0x74, 0x65,
+    0x73, 0x74, 0x00, 0x01, 0x0a, 0x0e, 0x01, 0x0c, 0x00, 0x41, 0x00,
+    0x25, 0x00, 0xfb, 0x17, 0x6d, 0xd1, 0x10, 0x00, 0x0b, 0x00, 0x15,
+    0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x08, 0x01, 0x00, 0x05, 0x63,
+    0x68, 0x65, 0x63, 0x6b, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73};
+
+/// Binary Wasm module: ref.cast (ref eq) on null -- non-nullable cast
+/// should trap.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table (export "t") 1 (ref null $s))
+///   (func (export "test")
+///     i32.const 0  table.get 0  ref.cast (ref eq)  drop))
+std::array<WasmEdge::Byte, 96> NullRefCastNonNullWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x03, 0x5f,
+    0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x0c, 0x01, 0x02,
+    0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x00, 0x01, 0x03, 0x02,
+    0x01, 0x02, 0x04, 0x05, 0x01, 0x63, 0x00, 0x00, 0x01, 0x07, 0x0c, 0x02,
+    0x01, 0x74, 0x01, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x01, 0x0a,
+    0x0c, 0x01, 0x0a, 0x00, 0x41, 0x00, 0x25, 0x00, 0xfb, 0x16, 0x6d, 0x1a,
+    0x0b, 0x00, 0x15, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x08, 0x01, 0x00,
+    0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73};
+
+/// Binary Wasm module: br_on_cast / br_on_cast_fail with null ref from
+/// concrete-typed table.
+///
+/// (module
+///   (type $s (struct))
+///   (import "gc" "check" (func $check (param i32)))
+///   (table (export "t") 1 (ref null $s))
+///   (func (export "test")
+///     (block $taken (result eqref)
+///       i32.const 0  table.get 0
+///       br_on_cast $taken eqref eqref
+///       drop  i32.const 0  call $check  return)
+///     ref.is_null  call $check
+///     (block $fail_taken (result eqref)
+///       i32.const 0  table.get 0
+///       br_on_cast_fail $fail_taken eqref (ref eq)
+///       drop  i32.const 0  call $check  return)
+///     ref.is_null  call $check
+///   ))
+std::array<WasmEdge::Byte, 156> NullBrOnCastWasm{
+    0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x03, 0x5f,
+    0x00, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x0c, 0x01, 0x02,
+    0x67, 0x63, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x00, 0x01, 0x03, 0x02,
+    0x01, 0x02, 0x04, 0x05, 0x01, 0x63, 0x00, 0x00, 0x01, 0x07, 0x0c, 0x02,
+    0x01, 0x74, 0x01, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x01, 0x0a,
+    0x30, 0x01, 0x2e, 0x00, 0x02, 0x6d, 0x41, 0x00, 0x25, 0x00, 0xfb, 0x18,
+    0x03, 0x00, 0x6d, 0x6d, 0x1a, 0x41, 0x00, 0x10, 0x00, 0x0f, 0x0b, 0xd1,
+    0x10, 0x00, 0x02, 0x6d, 0x41, 0x00, 0x25, 0x00, 0xfb, 0x19, 0x01, 0x00,
+    0x6d, 0x6d, 0x1a, 0x41, 0x00, 0x10, 0x00, 0x0f, 0x0b, 0xd1, 0x10, 0x00,
+    0x0b, 0x00, 0x2d, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x01, 0x08, 0x01, 0x00,
+    0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x03, 0x16, 0x01, 0x01, 0x02, 0x00,
+    0x05, 0x74, 0x61, 0x6b, 0x65, 0x6e, 0x01, 0x0a, 0x66, 0x61, 0x69, 0x6c,
+    0x5f, 0x74, 0x61, 0x6b, 0x65, 0x6e, 0x04, 0x04, 0x01, 0x00, 0x01, 0x73};
 // clang-format on
 
 /// Regression test for ref.test on externalized nullable references.
@@ -299,6 +492,128 @@ TEST(ExecutorRegression, RefTestNullAbility) {
               OldBugResult.isNullableRefType())
         << "Fix should produce same result as bug for non-nullable input";
   }
+
+  // --- Part 6: ref.test on null from concrete-typed table (issue #4757) ---
+  //
+  // table.get on a concrete-typed table (e.g. `(table N (ref null $s))`)
+  // returned null references that retained the concrete type. When ref.test
+  // received such a reference, the code entered the `!VT.isAbsHeapType()`
+  // branch and dereferenced the null object pointer, causing a segfault.
+  {
+    Configure Conf6;
+    VM::VM VM6(Conf6);
+    GCCheckModule GCMod;
+    VM6.registerModule(GCMod);
+    ASSERT_TRUE(VM6.loadWasm(NullRefTestWasm));
+    ASSERT_TRUE(VM6.validate());
+    ASSERT_TRUE(VM6.instantiate());
+    ASSERT_TRUE(checkNullTableEntry(VM6));
+    VM6.execute("test");
+    auto Values = GCMod.getValues();
+    ASSERT_EQ(Values.size(), 2);
+    EXPECT_EQ(Values[0], 1); // null matches nullable eqref
+    EXPECT_EQ(Values[1], 0); // null doesn't match non-nullable (ref $s)
+  }
+
+  // --- Part 7: function returning null ref with concrete type ---
+  {
+    Configure Conf7;
+    VM::VM VM7(Conf7);
+    GCCheckModule GCMod;
+    VM7.registerModule(GCMod);
+    ASSERT_TRUE(VM7.loadWasm(NullReturnConcreteWasm));
+    ASSERT_TRUE(VM7.validate());
+    ASSERT_TRUE(VM7.instantiate());
+    ASSERT_TRUE(checkNullTableEntry(VM7));
+    VM7.execute("test");
+    auto Values = GCMod.getValues();
+    ASSERT_EQ(Values.size(), 1);
+    EXPECT_EQ(Values[0], 1); // null from concrete return matches eqref
+  }
+
+  // --- Part 8: table.grow creates null entries, then ref.cast ---
+  {
+    Configure Conf8;
+    VM::VM VM8(Conf8);
+    GCCheckModule GCMod;
+    VM8.registerModule(GCMod);
+    ASSERT_TRUE(VM8.loadWasm(NullTableGrowWasm));
+    ASSERT_TRUE(VM8.validate());
+    ASSERT_TRUE(VM8.instantiate());
+    VM8.execute("test");
+    auto Values = GCMod.getValues();
+    ASSERT_EQ(Values.size(), 1);
+    EXPECT_EQ(Values[0], 1); // null from grown slot passed through cast
+  }
+}
+
+/// Regression test for ref.cast on null refs from concrete-typed tables
+/// (issue #4757).
+///
+/// When ref.cast received a null reference with a concrete type, the code
+/// entered the `!VT.isAbsHeapType()` branch and dereferenced the null object
+/// pointer before checking for null, causing a segfault.
+///
+/// This test covers:
+///   - ref.cast eqref on null (nullable cast should pass through)
+///   - ref.cast (ref eq) on null (non-nullable cast should trap)
+TEST(ExecutorRegression, RefCastNullAbility) {
+  // --- Part 1: ref.cast eqref on null --- nullable cast passes through
+  {
+    Configure Conf;
+    VM::VM VM(Conf);
+    GCCheckModule GCMod;
+    VM.registerModule(GCMod);
+    ASSERT_TRUE(VM.loadWasm(NullRefCastWasm));
+    ASSERT_TRUE(VM.validate());
+    ASSERT_TRUE(VM.instantiate());
+    ASSERT_TRUE(checkNullTableEntry(VM));
+    VM.execute("test");
+    auto Values = GCMod.getValues();
+    ASSERT_EQ(Values.size(), 1);
+    EXPECT_EQ(Values[0], 1); // null passed through nullable cast
+  }
+
+  // --- Part 2: ref.cast (ref eq) on null --- non-nullable cast must trap
+  {
+    Configure Conf;
+    VM::VM VM(Conf);
+    GCCheckModule GCMod;
+    VM.registerModule(GCMod);
+    ASSERT_TRUE(VM.loadWasm(NullRefCastNonNullWasm));
+    ASSERT_TRUE(VM.validate());
+    ASSERT_TRUE(VM.instantiate());
+    ASSERT_TRUE(checkNullTableEntry(VM));
+    auto Result = VM.execute("test");
+    EXPECT_FALSE(Result); // should trap with CastFailed
+  }
+}
+
+/// Regression test for br_on_cast on null refs from concrete-typed tables
+/// (issue #4757).
+///
+/// When br_on_cast / br_on_cast_fail received a null reference with a
+/// concrete type, the same null-dereference bug as ref.cast could occur in
+/// runBrOnCastOp.
+///
+/// This test covers:
+///   - br_on_cast to eqref: null matches nullable, branch taken
+///   - br_on_cast_fail to (ref eq): null doesn't match non-nullable,
+///     fail branch taken
+TEST(ExecutorRegression, BrOnCastNullAbility) {
+  Configure Conf;
+  VM::VM VM(Conf);
+  GCCheckModule GCMod;
+  VM.registerModule(GCMod);
+  ASSERT_TRUE(VM.loadWasm(NullBrOnCastWasm));
+  ASSERT_TRUE(VM.validate());
+  ASSERT_TRUE(VM.instantiate());
+  ASSERT_TRUE(checkNullTableEntry(VM));
+  VM.execute("test");
+  auto Values = GCMod.getValues();
+  ASSERT_EQ(Values.size(), 2);
+  EXPECT_EQ(Values[0], 1); // br_on_cast taken for nullable match
+  EXPECT_EQ(Values[1], 1); // br_on_cast_fail taken for non-nullable mismatch
 }
 
 } // namespace