Bug 1479794 - Do not expose Ref types outside the defining module. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Mon, 27 Aug 2018 18:16:13 +0200
changeset 491667 9fcca8293f56756d32636b9cc36b4ffa9d0e3d0d
parent 491666 24f835f7926dc9e789c9fd8d8e4675adb513d6ec
child 491668 aaab9dff4a225f3097a5b480a51d2bdc1926ac26
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1479794
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479794 - Do not expose Ref types outside the defining module. r=luke For the time being, we do not want to expose struct types outside of the module where they are defined, and so there must be restrictions on how functions and global variables that traffic in struct types can be used. At the same time, intra-module uses of functions and globals that use struct types must be allowed to the greatest extent possible. Terminology: A function that takes a Ref parameter or returns a Ref result is "exposed for Ref", as is a global of Ref type. Anyref is OK though, in all cases. To keep it simple we have the following restrictions that can all be checked statically. - Exported and imported functions cannot be exposed for Ref. - If the module has an exported or imported table then no function stored in that table by the module (by means of an element segment) can be exposed for Ref. - If the module has an exported or imported table then no call_indirect via that table may reference a type that is exposed for Ref. - An exported or imported global cannot be exposed for Ref. Conversely, - If a module has a private table then it can contain private functions that are exposed for Ref and it is possible to call those functions via that table. - If a module has a private global then it can be exposed for ref. Note that code generators can work around the restrictions by instead using functions and globals that use anyref, and by using downcasts to check that the types are indeed correct. (Though the meaning of downcast will change as the GC feature evolves.) All the code implementing the restrictions is under an #ifdef so that we can easily find it later; all the test cases testing the restrictions are in a separate file. Few existing tests needed to be adapted, and none substantially.
js/src/jit-test/tests/wasm/gc/ref-global.js
js/src/jit-test/tests/wasm/gc/ref-restrict.js
js/src/jit-test/tests/wasm/gc/ref.js
js/src/moz.build
js/src/shell/moz.build
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/tests/wasm/gc/ref-global.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-global.js
@@ -11,17 +11,20 @@ if (!wasmGcEnabled())
           (type $point (struct
                         (field $x f64)
                         (field $y f64)))
 
           (global $g1 (mut (ref $point)) (ref.null (ref $point)))
           (global $g2 (mut (ref $point)) (ref.null (ref $point)))
           (global $g3 (ref $point) (ref.null (ref $point)))
 
-          (func (export "get") (result (ref $point))
+          ;; Restriction: cannot expose Refs outside the module, not even
+          ;; as a return value.  See ref-restrict.js.
+
+          (func (export "get") (result anyref)
            (get_global $g1))
 
           (func (export "copy")
            (set_global $g2 (get_global $g1)))
 
           (func (export "clear")
            (set_global $g1 (get_global $g3))
            (set_global $g2 (ref.null (ref $point)))))`);
@@ -38,24 +41,24 @@ if (!wasmGcEnabled())
 // notion of structural type compatibility yet.
 {
     let bin = wasmTextToBinary(
         `(module
           (type $box (struct (field $val i32)))
           (import "m" "g" (global (mut (ref $box)))))`);
 
     assertErrorMessage(() => new WebAssembly.Module(bin), WebAssembly.CompileError,
-                       /unexpected variable type in global import\/export/);
+                       /cannot expose reference type/);
 }
 
 // We can't export a global of a reference type because we can't later import
 // it.  (Once we can export it, the value setter must also perform the necessary
 // subtype check, which implies we have some notion of exporting types, and we
 // don't have that yet.)
 {
     let bin = wasmTextToBinary(
         `(module
           (type $box (struct (field $val i32)))
           (global $boxg (export "box") (mut (ref $box)) (ref.null (ref $box))))`);
 
     assertErrorMessage(() => new WebAssembly.Module(bin), WebAssembly.CompileError,
-                       /unexpected variable type in global import\/export/);
+                       /cannot expose reference type/);
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-restrict.js
@@ -0,0 +1,317 @@
+if (!wasmGcEnabled())
+    quit(0);
+
+// For the time being, we do not want to expose struct types outside of the
+// module where they are defined, and so there are restrictions on how functions
+// and global variables that traffic in struct types can be used.
+//
+// At the same time, intra-module uses of functions and globals that use struct
+// types must be allowed to the greatest extent possible.
+//
+// Terminology: A function that takes a Ref parameter or returns a Ref result is
+// "exposed for Ref", as is a global of Ref type.  Anyref is OK though, in all
+// cases.
+//
+// To keep it simple we have the following restrictions that can all be checked
+// statically.
+//
+//  - Exported and imported functions cannot be exposed for Ref.
+//
+//  - If the module has an exported or imported table then no function stored in
+//    that table by the module (by means of an element segment) can be exposed
+//    for Ref.
+//
+//  - If the module has an exported or imported table then no call_indirect via
+//    that table may reference a type that is exposed for Ref.
+//
+//  - An exported or imported global cannot be exposed for Ref.
+//
+// Conversely,
+//
+//  - If a module has a private table then it can contain private functions that
+//    are exposed for Ref and it is possible to call those functions via that
+//    table.
+//
+//  - If a module has a private global then it can be exposed for ref.
+//
+// Note that
+//
+//  - code generators can work around the restrictions by instead using
+//    functions and globals that use anyref, and by using downcasts to check
+//    that the types are indeed correct.  (Though the meaning of downcast will
+//    change as the GC feature evolves.)
+//
+//  - we could probably make the restrictions slightly softer but there's really
+//    no advantage to doing so.
+
+function wasmCompile(text) {
+    return new WebAssembly.Module(wasmTextToBinary(text));
+}
+
+// Exported function can't take ref type parameter, but anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $x i32)))
+      (func (export "f") (param (ref $box)) (unreachable)))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (func (export "f") (param anyref) (unreachable)))`),
+         "object");
+
+// Exported function can't return ref result, but anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $x i32)))
+      (func (export "f") (result (ref $box)) (ref.null (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (func (export "f") (result anyref) (ref.null anyref)))`),
+         "object");
+
+// Imported function can't take ref parameter, but anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $x i32)))
+      (import "m" "f" (param (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "f" (param anyref)))`),
+         "object");
+
+// Imported function can't return ref type, but anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $x i32)))
+      (import "m" "f" (param i32) (result (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "f" (param i32) (result anyref)))`),
+         "object");
+
+// Imported global can't be of Ref type (irrespective of mutability), though anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (import "m" "g" (global (mut (ref $box)))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (import "m" "g" (global (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "g" (global (mut anyref))))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "g" (global anyref)))`),
+         "object");
+
+// Exported global can't be of Ref type (irrespective of mutability), though anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (global $boxg (export "box") (mut (ref $box)) (ref.null (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (global $boxg (export "box") (ref $box) (ref.null (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (global $boxg (export "box") (mut anyref) (ref.null anyref)))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (global $boxg (export "box") anyref (ref.null anyref)))`),
+         "object");
+
+// Exported table cannot reference functions that are exposed for Ref, but anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (table (export "tbl") 1 anyfunc)
+      (elem (i32.const 0) $f1)
+      (func $f1 (param (ref $box)) (unreachable)))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (table (export "tbl") 1 anyfunc)
+      (elem (i32.const 0) $f1)
+      (func $f1 (result (ref $box)) (ref.null (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (table (export "tbl") 1 anyfunc)
+      (elem (i32.const 0) $f1)
+      (func $f1 (param anyref) (unreachable)))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (table (export "tbl") 1 anyfunc)
+      (elem (i32.const 0) $f1)
+      (func $f1 (result anyref) (ref.null anyref)))`),
+         "object");
+
+// Imported table cannot reference functions that are exposed for Ref, though anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (import "m" "tbl" (table 1 anyfunc))
+      (elem (i32.const 0) $f1)
+      (func $f1 (param (ref $box)) (unreachable)))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (import "m" "tbl" (table 1 anyfunc))
+      (elem (i32.const 0) $f1)
+      (func $f1 (result (ref $box)) (ref.null (ref $box))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "tbl" (table 1 anyfunc))
+      (elem (i32.const 0) $f1)
+      (func $f1 (param anyref) (unreachable)))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (import "m" "tbl" (table 1 anyfunc))
+      (elem (i32.const 0) $f1)
+      (func $f1 (result anyref) (ref.null anyref)))`),
+         "object");
+
+// Can't call via exported table with type that is exposed for Ref, though anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (type $fn (func (param (ref $box))))
+      (table (export "tbl") 1 anyfunc)
+      (func (param i32)
+       (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (type $fn (func (result (ref $box))))
+      (table (export "tbl") 1 anyfunc)
+      (func (param i32) (result (ref $box))
+       (call_indirect $fn (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (type $fn (func (param anyref)))
+      (table (export "tbl") 1 anyfunc)
+      (func (param i32)
+       (call_indirect $fn (ref.null anyref) (get_local 0))))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (type $fn (func (result anyref)))
+      (table (export "tbl") 1 anyfunc)
+      (func (param i32) (result anyref)
+       (call_indirect $fn (get_local 0))))`),
+         "object");
+
+// Can't call via imported table with type that is exposed for Ref, though anyref is OK.
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (type $fn (func (param (ref $box))))
+      (import "m" "tbl" (table 1 anyfunc))
+      (func (param i32)
+       (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertErrorMessage(() => wasmCompile(
+    `(module
+      (type $box (struct (field $val i32)))
+      (type $fn (func (result (ref $box))))
+      (import "m" "tbl" (table 1 anyfunc))
+      (func (param i32) (result (ref $box))
+       (call_indirect $fn (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /cannot expose reference type/);
+
+assertEq(typeof wasmCompile(
+    `(module
+      (type $fn (func (param anyref)))
+      (import "m" "tbl" (table 1 anyfunc))
+      (func (param i32)
+       (call_indirect $fn (ref.null anyref) (get_local 0))))`),
+         "object");
+
+assertEq(typeof wasmCompile(
+    `(module
+      (type $fn (func (result anyref)))
+      (import "m" "tbl" (table 1 anyfunc))
+      (func (param i32) (result anyref)
+       (call_indirect $fn (get_local 0))))`),
+         "object");
+
+// We can call via a private table with a type that is exposed for Ref.
+
+{
+    let m = wasmCompile(
+        `(module
+          (type $box (struct (field $val i32)))
+          (type $fn (func (param (ref $box)) (result i32)))
+          (table 1 anyfunc)
+          (elem (i32.const 0) $f1)
+          (func $f1 (param (ref $box)) (result i32) (i32.const 37))
+          (func (export "f") (param i32) (result i32)
+           (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`);
+    let i = new WebAssembly.Instance(m).exports;
+    assertEq(i.f(0), 37);
+}
--- a/js/src/jit-test/tests/wasm/gc/ref.js
+++ b/js/src/jit-test/tests/wasm/gc/ref.js
@@ -1,59 +1,62 @@
 if (!wasmGcEnabled()) {
     assertErrorMessage(() => wasmEvalText(`(module (func (param (ref 0)) (unreachable)))`),
                        WebAssembly.CompileError, /bad type/);
     quit(0);
 }
 
-// Parsing and resolving
+// Parsing and resolving.
 
 var bin = wasmTextToBinary(
     `(module
       (type $cons (struct
                    (field $car i32)
                    (field $cdr (ref $cons))))
 
       (type $odd (struct
                   (field $x i32)
                   (field $to_even (ref $even))))
 
       (type $even (struct
                    (field $x i32)
                    (field $to_odd (ref $odd))))
 
-      (import "m" "f" (func $imp (param (ref $cons)) (result (ref $odd))))
+      ;; Use anyref on the API since struct types cannot be exposed outside the module yet.
+
+      (import "m" "f" (func $imp (param anyref) (result anyref)))
 
       ;; The bodies do nothing since we have no operations on structs yet.
+      ;; Note none of these functions are exported, as they use Ref types in their signatures.
 
-      (func (export "car") (param (ref $cons)) (result i32)
+      (func (param (ref $cons)) (result i32)
        (i32.const 0))
 
-      (func $cdr (export "cdr") (param $p (ref $cons)) (result (ref $cons))
+      (func $cdr (param $p (ref $cons)) (result (ref $cons))
        (local $l (ref $cons))
        ;; store null value of correct type
        (set_local $l (ref.null (ref $cons)))
        ;; store local of correct type
        (set_local $l (get_local $p))
        ;; store call result of correct type
        (set_local $l (call $cdr (get_local $p)))
        ;; TODO: eventually also a test with get_global
        ;; blocks and if with result type
        (block (ref $cons)
         (if (ref $cons) (i32.eqz (i32.const 0))
             (unreachable)
             (ref.null (ref $cons)))))
 
-      (func (export "odder") (param (ref $even)) (result (ref $odd))
+      (func (param (ref $even)) (result (ref $odd))
        (ref.null (ref $odd)))
 
-      (func (export "evener") (param (ref $odd)) (result (ref $even))
+      (func (param (ref $odd)) (result (ref $even))
        (ref.null (ref $even)))
 
-      (func (export "passer") (param (ref $cons))
+      (func (param (ref $cons))
        (call $cdr (get_local 0))
        drop
        (call $imp (get_local 0))
        drop)
 
      )`);
 
 // Validation
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -700,16 +700,17 @@ DIRS += [
 FINAL_LIBRARY = 'js'
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_BINARYDATA'] = True
     DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True
     DEFINES['ENABLE_WASM_SATURATING_TRUNC_OPS'] = True
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
     DEFINES['ENABLE_WASM_GC'] = True
+    DEFINES['WASM_PRIVATE_REFTYPES'] = True
 
 if CONFIG['JS_BUILD_BINAST']:
     # Using SOURCES, as UNIFIED_SOURCES causes mysterious bugs on 32-bit platforms.
     # These parts of BinAST are designed only to test evolutions of the
     # specification.
     SOURCES += ['frontend/BinTokenReaderTester.cpp']
     # These parts of BinAST should eventually move to release.
     SOURCES += [
--- a/js/src/shell/moz.build
+++ b/js/src/shell/moz.build
@@ -16,16 +16,17 @@ UNIFIED_SOURCES += [
     'jsshell.cpp',
     'OSObject.cpp'
 ]
 
 DEFINES['EXPORT_JS_API'] = True
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_WASM_GC'] = True
+    DEFINES['WASM_PRIVATE_REFTYPES'] = True
 
 # Also set in ../moz.build
 DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True
 
 if CONFIG['CC_TYPE'] == 'msvc':
     # PGO is unnecessary for the js shell, but clang/gcc cannot turn off PGO
     # because we need to resolve symbols from PGO runtime library when our
     # object files have been compiled for PGO.
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -1871,16 +1871,19 @@ WasmTableObject::create(JSContext* cx, c
     RootedWasmTableObject obj(cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
     if (!obj)
         return nullptr;
 
     MOZ_ASSERT(obj->isNewborn());
 
     TableDesc td(TableKind::AnyFunction, limits);
     td.external = true;
+#ifdef WASM_PRIVATE_REFTYPES
+    td.importedOrExported = true;
+#endif
 
     SharedTable table = Table::create(cx, td, obj);
     if (!table)
         return nullptr;
 
     obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take()));
 
     MOZ_ASSERT(!obj->isNewborn());
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -1658,16 +1658,21 @@ OpIter<Policy>::readCallIndirect(uint32_
     if (!popWithType(ValType::I32, callee))
         return false;
 
     if (!env_.types[*funcTypeIndex].isFuncType())
         return fail("expected signature type");
 
     const FuncType& funcType = env_.types[*funcTypeIndex].funcType();
 
+#ifdef WASM_PRIVATE_REFTYPES
+    if (env_.tables[0].importedOrExported && funcType.exposesRef())
+        return fail("cannot expose reference type");
+#endif
+
     if (!popCallArgs(funcType.args(), argValues))
         return false;
 
     return push(funcType.ret());
 }
 
 template <typename Policy>
 inline bool
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -758,16 +758,25 @@ class FuncType
         if (ret().isRefOrAnyRef())
             return true;
         for (ValType arg : args()) {
             if (arg.isRefOrAnyRef())
                 return true;
         }
         return false;
     }
+#ifdef WASM_PRIVATE_REFTYPES
+    bool exposesRef() const {
+        for (const ValType& arg : args()) {
+            if (arg.isRef())
+                return true;
+        }
+        return ret().isRef();
+    }
+#endif
 
     WASM_DECLARE_SERIALIZABLE(FuncType)
 };
 
 struct FuncTypeHashPolicy
 {
     typedef const FuncType& Lookup;
     static HashNumber hash(Lookup ft) { return ft.hash(); }
@@ -1863,23 +1872,29 @@ enum class TableKind
 
 struct TableDesc
 {
     // If a table is marked 'external' it is because it can contain functions
     // from multiple instances; a table is therefore marked external if it is
     // imported or exported or if it is initialized with an imported function.
 
     TableKind kind;
+#ifdef WASM_PRIVATE_REFTYPES
+    bool importedOrExported;
+#endif
     bool external;
     uint32_t globalDataOffset;
     Limits limits;
 
     TableDesc() = default;
     TableDesc(TableKind kind, const Limits& limits)
      : kind(kind),
+#ifdef WASM_PRIVATE_REFTYPES
+       importedOrExported(false),
+#endif
        external(false),
        globalDataOffset(UINT32_MAX),
        limits(limits)
     {}
 };
 
 typedef Vector<TableDesc, 0, SystemAllocPolicy> TableDescVector;
 
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1124,16 +1124,26 @@ ValidateRefType(Decoder& d, TypeStateVec
       case TypeState::ForwardStruct:
         break;
       case TypeState::Func:
         return d.fail("ref does not reference a struct type");
     }
     return true;
 }
 
+#ifdef WASM_PRIVATE_REFTYPES
+static bool
+FuncTypeIsJSCompatible(Decoder& d, const FuncType& ft)
+{
+    if (ft.exposesRef())
+        return d.fail("cannot expose reference type");
+    return true;
+}
+#endif
+
 static bool
 DecodeFuncType(Decoder& d, ModuleEnvironment* env, TypeStateVector* typeState, uint32_t typeIndex)
 {
     uint32_t numArgs;
     if (!d.readVarU32(&numArgs))
         return d.fail("bad number of function args");
 
     if (numArgs > MaxParams)
@@ -1386,16 +1396,20 @@ GlobalIsJSCompatible(Decoder& d, ValType
 {
     switch (type.code()) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
       case ValType::I64:
       case ValType::AnyRef:
         break;
+#ifdef WASM_PRIVATE_REFTYPES
+      case ValType::Ref:
+        return d.fail("cannot expose reference type");
+#endif
       default:
         return d.fail("unexpected variable type in global import/export");
     }
 
     return true;
 }
 
 static bool
@@ -1490,26 +1504,33 @@ DecodeImport(Decoder& d, ModuleEnvironme
 
     DefinitionKind importKind = DefinitionKind(rawImportKind);
 
     switch (importKind) {
       case DefinitionKind::Function: {
         uint32_t funcTypeIndex;
         if (!DecodeSignatureIndex(d, env->types, &funcTypeIndex))
             return false;
+#ifdef WASM_PRIVATE_REFTYPES
+        if (!FuncTypeIsJSCompatible(d, env->types[funcTypeIndex].funcType()))
+            return false;
+#endif
         if (!env->funcTypes.append(&env->types[funcTypeIndex].funcType()))
             return false;
         if (env->funcTypes.length() > MaxFuncs)
             return d.fail("too many functions");
         break;
       }
       case DefinitionKind::Table: {
         if (!DecodeTableLimits(d, &env->tables))
             return false;
         env->tables.back().external = true;
+#ifdef WASM_PRIVATE_REFTYPES
+        env->tables.back().importedOrExported = true;
+#endif
         break;
       }
       case DefinitionKind::Memory: {
         if (!DecodeMemoryLimits(d, env))
             return false;
         break;
       }
       case DefinitionKind::Global: {
@@ -1800,29 +1821,36 @@ DecodeExport(Decoder& d, ModuleEnvironme
     switch (DefinitionKind(exportKind)) {
       case DefinitionKind::Function: {
         uint32_t funcIndex;
         if (!d.readVarU32(&funcIndex))
             return d.fail("expected function index");
 
         if (funcIndex >= env->numFuncs())
             return d.fail("exported function index out of bounds");
+#ifdef WASM_PRIVATE_REFTYPES
+        if (!FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex]))
+            return false;
+#endif
 
         return env->exports.emplaceBack(std::move(fieldName), funcIndex, DefinitionKind::Function);
       }
       case DefinitionKind::Table: {
         uint32_t tableIndex;
         if (!d.readVarU32(&tableIndex))
             return d.fail("expected table index");
 
         if (tableIndex >= env->tables.length())
             return d.fail("exported table index out of bounds");
 
         MOZ_ASSERT(env->tables.length() == 1);
         env->tables[tableIndex].external = true;
+#ifdef WASM_PRIVATE_REFTYPES
+        env->tables[tableIndex].importedOrExported = true;
+#endif
 
         return env->exports.emplaceBack(std::move(fieldName), DefinitionKind::Table);
       }
       case DefinitionKind::Memory: {
         uint32_t memoryIndex;
         if (!d.readVarU32(&memoryIndex))
             return d.fail("expected memory index");
 
@@ -1958,16 +1986,24 @@ DecodeElemSection(Decoder& d, ModuleEnvi
             if (funcIndex >= env->numFuncs())
                 return d.fail("table element out of range");
 
             // If a table element function value is imported then the table can
             // contain functions from multiple instances and must be marked
             // external.
             if (env->funcIsImport(funcIndex))
                 env->tables[tableIndex].external = true;
+
+#ifdef WASM_PRIVATE_REFTYPES
+            if (env->tables[tableIndex].importedOrExported &&
+                !FuncTypeIsJSCompatible(d, *env->funcTypes[elemFuncIndices[i]]))
+            {
+                return false;
+            }
+#endif
         }
 
         if (!env->elemSegments.emplaceBack(tableIndex, offset, std::move(elemFuncIndices)))
             return false;
     }
 
     return d.finishSection(*range, "elem");
 }