Unlimited block scopes

Unlimited block scopes

I like the idea of scopes which have the limited ability to be read-from and or written-into in order to perform secure operations

A ***blockscope*** would have this functionality

blockscope[ [params [, params]*]* ]{
    
}

let a = "a"
let a2 = "a2"
let a3 = "a3"

let b,c = blockscope[ let innerScopeA = a ]{
    a // referenceError: "a" is undefined 
    a2 // referenceError: "a2" is undefined
    a3 // referenceError: "a3" is undefined
    innerScopeA // "a" 
    let B = "B"
    let C = "C"
    return B,C
}

innerScopeA // referenceError: "innerScopeA" is undefined 
    
blockscope[ a,b,c,f = Worker("myfile.js") ]{
    innerScopeA // referenceError: "innerScopeA" is undefined 
    a // "a" 
    b // "B"
    c // "C"
    let msg = f.postMessage(a,b,c)
    let d = blockscope[]{
        let D = "D"
        blockscope[]{
            let e = "e"
            blockscope[]{
                let g = "g"
            }
        }
        return D
    }
    d // "D"
}

d // referenceError: "d" is undefined

cf. RELATED REFERENCES
SANDBOXED SCOPED FUNCTIONS - Sandboxed / Scoped function
REALMS PROPOSAL - https://github.com/tc39/proposal-realms

This can already be achieved with a mix of Proxy and with and this helper:

function blockScope(allowed = {}) {
  return new Proxy({}, {
        has: (v) => true,
        get: (_target, prop) => {
            if (prop === Symbol.unscopables) {
                return {};
            }
            if (prop in allowed) {
                return allowed[prop];
            }
            throw new ReferenceError(`${prop} is undefined`);
        }
    });
}

Example:

let a1 = "a1"
let a2 = "a2"
let a3 = "a3"

let [b,c] = (() => {with(blockScope({a1, innerScopeA2: a2})) {
    a1; // 'a1'
    a2; // referenceError: "a2" is undefined
    a3; // referenceError: "a3" is undefined
    innerScopeA2; // "a2" 
    let B = "B"
    let C = "C"
    return [B,C];
}})()

b; // 'B'
c; // 'C'
2 Likes

I see the interesting pattern with with + Proxy in many sandbox-like solutions. The main usage is to capture free variable access. What if we provide a modern syntax for this task?

I don’t think ANY use of with should be encouraged. It is harmful to JavaScript.

There's a difference between "can" and "should". And @aclaymore was only saying the former.

Wouldn't that just be more or less new Compartment({endowments: {...globalThis, ...allowed}}).evaluate("code to run in block")? Not sure what the use of this is outside of dynamic code, personally. (And dynamic code is better served with a global like that anyways.)

1 Like

Indeed compartments are a way to create a new global scope, and the shim does use with for that to happen. Compartments are a bit heavy weight for this specific case however.

One issue with with used to create a transparent scope is the problem of this. If you set a function as available on the scope proxy, and call it as a free function, the this in the function will evaluate to the scope object, regardless of strict mode. See `with (scopeProxy) { foo() }` leaks scopeProxy if foo mentions `this`. · Issue #31 · endojs/endo · GitHub for more details.

That sounds like a spec bug. Would it be web-compatible to correct that? (Shouldn't be hard to detect, as it's almost pure syntax analysis.)

It definitely wouldn't be web compatible, and was the original intent for with. However @awbjs suggests a very simple change to the spec to opt out of that behavior (through a symbol on the scope object). We've never pushed for it since there seem to be little appetite to change legacy with.

1 Like

Given how little with is used in practice, I'm not even convinced this is useful unless it would somehow simplify implementations, so yeah, I don't plan to push that issue much.