Mutable state attached to parse nodes, in strict mode

24 views
Skip to first unread message

Luca Casonato

unread,
Jun 14, 2024, 1:41:09 PMJun 14
to ses-st...@googlegroups.com
Hello folks,

While working through various design considerations for the module expressions proposal, we realized that in a possible direction of the proposal, the proposal may, as a side effect, add a new capability to the language: mutable state attached to parse nodes, in strict mode. The concrete example is:

```
async function x() {
  const mod = module { export default { counter: 0 } };
  const y = (await import(mod)).default;
  y.counter++;
  return y.counter;
}

await x() // 1
await x() // 2
```

This capability exists for functions in non strict mode by using `arguments.callee`

```
function x() {
  const y = arguments.callee;
  y.counter ??= 0;
  y.counter++;
  return y.counter;
}

x() // 1
x() // 2
```

I have not been able to find a similar existing capability for strict mode functions.

I'd be interested to hear your thoughts on if this capability impacts SES in any way.

Best regards,
Luca


Kris Kowal

unread,
Jun 15, 2024, 6:22:21 AMJun 15
to ses-st...@googlegroups.com
Is this formulation of the problem equivalent and equally concerning, though it makes no use of module expressions but does rely on a host-defined mapping from a module source to a module instance?

counter.js
```
export default { counter: 0 }
```

```
import source mod from 'counter.js';

async function x() {
  const y = (await import(mod)).default;
  y.counter++;
  return y.counter;
}

await x() // 1
await x() // 2

--
You received this message because you are subscribed to the Google Groups "SES-strategy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ses-strategy...@googlegroups.com.

Guy Bedford

unread,
Jun 16, 2024, 4:17:02 AMJun 16
to Kris Kowal, ses-st...@googlegroups.com
I think the issue is specifically in the expressions case, where the expression itself creates a local module instance identity that is tied to the parse node, and applies to all invocations / rebindings of the function itself. Identity for source imports is already the case, but the module expression parse node itself acting as a new constant state key is the question here.

Kris Kowal

unread,
Jun 20, 2024, 3:16:52 PMJun 20
to SES-strategy
First, thank you for your empathy. It is not a small act to internally simulate arguments ocap proponents will make and the effort is appreciated.

I see the problem now. The language currently holds invariant that the evaluation of an expression produces either a primitive value or a new instance. The module expression, as currently proposed, does not produce a new Module or ModuleSource instance, but will produce the same value for every evaluation. (To be pedantically clear, this holds for each module instance of a module source that contains module expressions, not across module instances for the same module source.)

Clarifying question: do you currently imagine module expressions produce a ModuleSource or Module instance?

Aside: if module expressions produce a ModuleSource and a host is free to associate that instance with a corresponding Module instance, we will need to anticipate a hook on handlers for both Module and Evaluators such that this behavior can be virtualized for module expressions.

So, holding both an import capability a ModuleSource instance produced by a module expression allows the holder to combine these (rights amplification) to gain access to shared mutable state (communication channel). This amplification can’t be denied by providing an importHook since instantiating the ModuleSource creates the relevant entries in the Module instance’s internal module map.

So, regardless of whether we need a hook to associate a module expression’s ModuleSource instance with a Module instance, a hook might be needed to virtualize the evaluation of a module expression, giving the virtual host an opportunity to either emulate or deny the web host behavior.

Proposal made of straw: Constructing a Module instance from a ModuleSource causes the construction of a ModuleSource instance for every module expression in the given text. For a virtual Module instance, evaluating a module expression delegates to a moduleHook that receives the ModuleSource instance and must return a Module instance. The virtual Module handler may elect to emulate the proposed HTML host integration behavior by memoizing a Module for each given ModuleSource and consistently returning the same instance. To deny a guest program this side channel, the implementer may simply omit the moduleHook from the handler. To deny a guest program this side channel and also still support module expressions, they may provide a moduleHook that creates a new Module instance from the given ModuleSource upon every evaluation of a module expression.

We might quibble about which behavior should be default, and whether this hazard should be present, but I think this straw proposal gives Hardened JavaScript a place to stand.

Kris Kowal

On Jun 20, 2024, at 7:59 AM, Luca Casonato <lu...@deno.com> wrote:

I do not think so.

The thing I thought you may be concerned about was the fact that a function created with `new Function()` could now persist state across calls, even when the host module loader disables module loading by specifier entirely, and the global object and all intrinsics that this function can reach are frozen, and the function can close over no mutable values. There would be no way to deny this capability other than to disallow the dynamic import syntax.

Your example does not exhibit this behaviour, because you are closing over a value.

An example:

```
const x = new Function(`
  const mod = module { export default { counter: 0 } };
  return import(mod).then((y) => {
    y.counter++;
    return y.counter;
  });
`);

await x() // 1
await x() // 2
```

A different way to phrase this would be that you could never restrict a function to be pure from the outside.

Luca

On Fri, Jun 14, 2024 at 11:21 PM Kris Kowal <kris...@kriskowal.com> wrote:
Is this formulation of the problem equivalent and equally concerning, though it makes no use of module expressions but does rely on a host-defined mapping from a module source to a module instance?

counter.js
```
export default { counter: 0 }
```

```
import source mod from 'counter.js';

async function x() {
  const y = (await import(mod)).default;
  y.counter++;
  return y.counter;
}

await x() // 1
await x() // 2
```

On Jun 14, 2024, at 10:40 AM, 'Luca Casonato' via SES-strategy <ses-st...@googlegroups.com> wrote:

```
async function x() {
  const mod = module { export default { counter: 0 } };
  const y = (await import(mod)).default;
  y.counter++;
  return y.counter;
}

await x() // 1
await x() // 2
```

Chip Morningstar

unread,
Jun 20, 2024, 6:08:38 PMJun 20
to Kris Kowal, SES-strategy

> On Jun 20, 2024, at 12:16 PM, 'Kris Kowal' via SES-strategy <ses-st...@googlegroups.com> wrote:
>
> ...

> The language currently holds invariant that the evaluation of an expression produces either a primitive value or a new instance.


This seems like it’s not even remotely true.
Am I missing some context here that narrows the scope of this assertion?

Chip

Kris Kowal

unread,
Jun 20, 2024, 6:19:29 PMJun 20
to Chip Morningstar, SES-strategy
On Thu, Jun 20, 2024 at 3:08 PM Chip Morningstar <ch...@fudco.com> wrote:
> On Jun 20, 2024, at 12:16 PM, 'Kris Kowal' via SES-strategy <ses-st...@googlegroups.com> wrote:
> The language currently holds invariant that the evaluation of an expression produces either a primitive value or a new instance.
This seems like it’s not even remotely true.

It’s not remotely true. I’m not sure how to qualify what’s special about module expressions.

Nicolò Ribaudo

unread,
Jun 21, 2024, 4:37:28 AMJun 21
to ses-st...@googlegroups.com

Is the word you are looking for "literals"? i.e. the evaluation of a literal currently produces a primitive value (for number, strings, bigints, booleans, and null) or a new instance that is not related in any way to the other evaluations (for objects, arrays, functions, classes, regexps).

Although I'm not sure whether function/class/module expressions would fall in the "literals" category or not.

--
You received this message because you are subscribed to the Google Groups "SES-strategy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ses-strategy...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages