Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layer 3: Evaluators, how to inherit intrinsic? #73

Open
Jack-Works opened this issue Aug 4, 2022 · 3 comments
Open

Layer 3: Evaluators, how to inherit intrinsic? #73

Jack-Works opened this issue Aug 4, 2022 · 3 comments

Comments

@Jack-Works
Copy link
Member

const e = new Evaluator({ globalThis: {} })
e.eval("Math")
// object? ReferenceError?
e.eval("Module")
// equal to e.Module? ReferenceError?
e.eval("Evaluator")
// ?
@Jamesernator
Copy link

Jamesernator commented Aug 4, 2022

I feel like it would simpler to go with one of the previous ideas where there was an actual Global constructor that made new global objects. This means the host could just perform the population of the global object for the user i.e.:

// Creates a new global object with the appropriate
// intrinsics already attached
const g = new Global();
g.Array === Array; // true
const e = new Evaluator({ global: g });

e.eval(`Math`); // object Math { ... }

Customizing these globals wouldn't be particularly hard, just an Object.assign or similar:

const e = new Evaluator({
    global: Object.assign(new Global(), {
        myApi: () => { ... },
    }),
});

If there's desire for exotic behaviour (i.e. #38) one could just allow new Global() to take proxy traps:

const fakeDocument = new FakeDocument();
const g = new Global({
    // proxy hooks...
    get(global, prop, receiver) {
    
    },
});

I feel like the questions about e.eval("Evaluator") is a much wider problem than just inheriting, like the point of having say evaluator.Function !== Function is so that new Function("return someGlobal") has the right evaluator, but this becomes weird given that inherited intrinsics will have the original Function i.e.:

const g = new Global();
const e = new Evaluator({
    // the following problem is independent on this API shape
    global: g,
});
// Set the Function global
g.Function = e.Function;

// Multiple Function constructors floating around in the same evaluator
e.eval(`
    Function === Array.constructor; // false
`);
// And so code run inside the evaluator can still access parent globals fairly unrestrictedly
e.eval(`
    const OuterFunction = Array.constructor;
    const OuterModule = new OuterFunction("return Module");
    const module = new OuterModule(new ModuleSourceText(`
         // do thing in the parent evaluator's global scope
    `)):
`);

I don't see any obvious way to repair this if we allow multiple Function in particular to exist within the same evaluator.

Nevermind this, I thought .constructor was an own property on builtin functions, but it's actually just inherited from Function.prototype.constructor.

@Jamesernator
Copy link

Jamesernator commented Aug 4, 2022

Nevermind this, I thought .constructor was an own property on builtin functions, but it's actually just inherited from Function.prototype.constructor.

Actually no this is still a problem, Array.constructor can't be e.Function, as the outer and inner Array.constructor need to agree:

const g = CREATE_GLOBAL_SOMEHOW();
const e = new Evaluator({ global: g });
g.Function = e.Function;

// If this agrees:
Array === e.eval(`Array`); // true
// then so must
Array.constructor === e.eval(`Array.constructor`); // true
// hence Array.constructor is the outer Function
Function === e.eval(`Array.constructor`); // true

The only way for this not to agree would be if Function.prototype.constructor actually checked the caller's evaluator, although other than direct eval this is unlike simply calling a function (.prototype.[[Get]](...)) would usually be capable of doing.

@kriskowal
Copy link
Member

My intent is that whomever creates the intrinsics is in a position to arrange the new intrinsics however they see fit, and that the common case would be:

const localThis = Object.create(globalThis);
const evaluators = new Evaluators(localThis);
Object.assign(localThis, evaluators);

Such that direct eval would work as-expected in evaluated code. This would be sufficient for the DSL usecase.

This is not very different from a Global constructor, except that it allows for the possibility of:

const evaluators = new Evaluators(globalThis, { importHook, importMeta });

Where the globalThis is object identical to the surrounding environment but import behavior is virtualized. I imagine this to be a common need as well. This would have the surprising but probably okay side-effect of disabling direct eval. That could of course be recovered if it’s actually needed, by binding eval lexically, as in:

new evaluators.Function('eval', 'text', 'eval(text)')(evaluators.eval, text);

I’m not strongly partial to either Global, Evaluators, or just lockdown Compartment. Any of these are sufficient for isolating dependencies or other guest programs.

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

No branches or pull requests

3 participants