镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[js-api] Fix ToWebAssemblyValue's host-value-cache hit skipping the type check - #2257

Open
f52985 wants to merge 1 commit into
WebAssembly:mainfrom
kaist-plrg:fix/js-api-to-wasm-value-cache-hit-check
Open

f52985 wants to merge 1 commit into
WebAssembly:mainfrom
kaist-plrg:fix/js-api-to-wasm-value-cache-hit-check

Conversation

@f52985

@f52985 f52985 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

ToWebAssemblyValue's host-value-cache hit returns early and skips the type check every other path through this algorithm goes through, so a value cached once under a broad type can later be reused for a narrower, incompatible type without ever being rejected.

This is ToWebAssemblyValue's ref null heaptype case as it stands today:

1. If |type| is of the form ref null heaptype,
    1. If |v| is null, let |r| be ref.null heaptype.
    1. Else if ..., let |r| be ...
    1. Else if ..., let |r| be ...
    1. Else if ..., let |r| be ...
    1. Else if ..., let |r| be ...
    1. Else,
        1. Let |map| be the [=surrounding agent=]'s associated [=host value cache=].
        1. If a |hostaddr| exists such that |map|[|hostaddr|] is the same as |v|,
            ;; Early return happens here
            1. Return [=ref.host=] |hostaddr|.
        1. Let |hostaddr| be the smallest [=host address=]
            such that |map|[|hostaddr|] [=map/exists=] is false.
        1. Set |map|[|hostaddr|] to |v|.
        1. Let |r| be [=ref.host=] |hostaddr|.
    1. Let |store| be the [=surrounding agent=]'s [=associated store=].
    1. Let |actualtype| be [=ref_type=](|store|, |r|).
    ;; Type check happens here
    1. If [=match_valtype=](|actualtype|, |type|) is false,
        1. Throw a {{TypeError}}.
    1. Return |r|.

Every other way this algorithm can produce an r (including the cache-miss) falls through to the shared ref_type/match_valtype check a few steps below before ever returning, which is what makes converting a value to, say, eqref reject an object that isn't actually eq-castable.

The cache-hit branch is the one exception: it returns ref.host |hostaddr| immediately, so once a value has been successfully converted once (under whatever type that was), every later conversion of that same value — even to a completely different, narrower type — short-circuits past the type check and returns the same cached ref.host, regardless of whether it's actually valid for the new target type.

Concrete example

(module
  (func (export "f") (param anyref) (param eqref)))
const v = 10n;
instance.exports.f(v, v);

f is an exported function that takes two arguments with different types: one with anyref and one with eqref. Calling instance.exports.f(v, v) in JS goes through call an Exported Function algorithm, which converts each argument via ToWebAssemblyValue against the function's declared parameter type.

Converting v for the first (anyref) parameter succeeds: it doesn't match any of the earlier cases, so it falls to the final Else branch, gets wrapped as a fresh ref.host, and is cached. anyref accepts anything, so the type check passes.

Converting that same v for the second (eqref) parameter should throw a TypeError since a bare host reference doesn't satisfy eq — and it would, if this were the first time v was converted. But the cache already has an entry for v from the first argument, so this second conversion hits the cache-hit branch, returns the cached ref.host immediately, and never reaches the match_valtype check that would otherwise reject it.

Fix

Reword the cache-hit branch's Return to Let |r| be ...,
and wrap the cache-miss branch's three steps in an Else,,
so exactly one of the two branches runs and both join the shared tail:

    1. Else,
        1. Let |map| be the [=surrounding agent=]'s associated [=host value cache=].
        1. If a [=host address=] |hostaddr| exists such that |map|[|hostaddr|] is the same as |v|,
            1. Let |r| be [=ref.host=] |hostaddr|.
        1. Else,
            1. Let |hostaddr| be the smallest [=host address=] such that |map|[|hostaddr|] [=map/exists=] is false.
            1. Set |map|[|hostaddr|] to |v|.
            1. Let |r| be [=ref.host=] |hostaddr|.
    1. Let |store| be the [=surrounding agent=]'s [=associated store=].
    1. Let |actualtype| be [=ref_type=](|store|, |r|).
    1. If [=match_valtype=](|actualtype|, |type|) is false,
        1. Throw a {{TypeError}}.
    1. Return |r|.

@Ms2ger Ms2ger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems plausible, but should have tests.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants