2
\$\begingroup\$

I made this code because, I figured that it might be an interesting concept, and that it would be nice to see it work. It enables the toggling of strict mode in any string of code.

It even allows you to set a scope to run it through, although it does use eval.

Examples:

toggleableStrict("var a = 1; console.log(b) 'toggle strict'; console.log(a);")()

toggleableStrict("var a = 1; console.log(a) 'toggle strict'; console.log(a);")()

These produce the following functions:

function anonymous(_a,_b
) {

                ;with(_a) {
                    this.addGlobalSource((() => {
                        "use strict";
                        var a = 1; console.log(b) 
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }
                ;with(_b) {
                    this.addGlobalSource((() => {
                        ; console.log(a);
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }
}

function anonymous(_a,_b
) {

                ;with(_a) {
                    this.addGlobalSource((() => {
                        "use strict";
                        var a = 1; console.log(a) 
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }
                ;with(_b) {
                    this.addGlobalSource((() => {
                        ; console.log(a);
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }
}

Which are then wrapped in a function that is returned to the user.

Code:

function toggleableStrict(code, startstrict = true) {
    const globalThis = (0, eval)('this')
    const deburr = function() {
        const b = function(a) {
                return (b) => null == a ? void 0 : a[b]
            }({
                "\u00c0": "A",
                "\u00c1": "A",
                "\u00c2": "A",
                "\u00c3": "A",
                "\u00c4": "A",
                "\u00c5": "A",
                "\u00e0": "a",
                "\u00e1": "a",
                "\u00e2": "a",
                "\u00e3": "a",
                "\u00e4": "a",
                "\u00e5": "a",
                "\u00c7": "C",
                "\u00e7": "c",
                "\u00d0": "D",
                "\u00f0": "d",
                "\u00c8": "E",
                "\u00c9": "E",
                "\u00ca": "E",
                "\u00cb": "E",
                "\u00e8": "e",
                "\u00e9": "e",
                "\u00ea": "e",
                "\u00eb": "e",
                "\u00cc": "I",
                "\u00cd": "I",
                "\u00ce": "I",
                "\u00cf": "I",
                "\u00ec": "i",
                "\u00ed": "i",
                "\u00ee": "i",
                "\u00ef": "i",
                "\u00d1": "N",
                "\u00f1": "n",
                "\u00d2": "O",
                "\u00d3": "O",
                "\u00d4": "O",
                "\u00d5": "O",
                "\u00d6": "O",
                "\u00d8": "O",
                "\u00f2": "o",
                "\u00f3": "o",
                "\u00f4": "o",
                "\u00f5": "o",
                "\u00f6": "o",
                "\u00f8": "o",
                "\u00d9": "U",
                "\u00da": "U",
                "\u00db": "U",
                "\u00dc": "U",
                "\u00f9": "u",
                "\u00fa": "u",
                "\u00fb": "u",
                "\u00fc": "u",
                "\u00dd": "Y",
                "\u00fd": "y",
                "\u00ff": "y",
                "\u00c6": "Ae",
                "\u00e6": "ae",
                "\u00de": "Th",
                "\u00fe": "th",
                "\u00df": "ss",
                "\u0100": "A",
                "\u0102": "A",
                "\u0104": "A",
                "\u0101": "a",
                "\u0103": "a",
                "\u0105": "a",
                "\u0106": "C",
                "\u0108": "C",
                "\u010a": "C",
                "\u010c": "C",
                "\u0107": "c",
                "\u0109": "c",
                "\u010b": "c",
                "\u010d": "c",
                "\u010e": "D",
                "\u0110": "D",
                "\u010f": "d",
                "\u0111": "d",
                "\u0112": "E",
                "\u0114": "E",
                "\u0116": "E",
                "\u0118": "E",
                "\u011a": "E",
                "\u0113": "e",
                "\u0115": "e",
                "\u0117": "e",
                "\u0119": "e",
                "\u011b": "e",
                "\u011c": "G",
                "\u011e": "G",
                "\u0120": "G",
                "\u0122": "G",
                "\u011d": "g",
                "\u011f": "g",
                "\u0121": "g",
                "\u0123": "g",
                "\u0124": "H",
                "\u0126": "H",
                "\u0125": "h",
                "\u0127": "h",
                "\u0128": "I",
                "\u012a": "I",
                "\u012c": "I",
                "\u012e": "I",
                "\u0130": "I",
                "\u0129": "i",
                "\u012b": "i",
                "\u012d": "i",
                "\u012f": "i",
                "\u0131": "i",
                "\u0134": "J",
                "\u0135": "j",
                "\u0136": "K",
                "\u0137": "k",
                "\u0138": "k",
                "\u0139": "L",
                "\u013b": "L",
                "\u013d": "L",
                "\u013f": "L",
                "\u0141": "L",
                "\u013a": "l",
                "\u013c": "l",
                "\u013e": "l",
                "\u0140": "l",
                "\u0142": "l",
                "\u0143": "N",
                "\u0145": "N",
                "\u0147": "N",
                "\u014a": "N",
                "\u0144": "n",
                "\u0146": "n",
                "\u0148": "n",
                "\u014b": "n",
                "\u014c": "O",
                "\u014e": "O",
                "\u0150": "O",
                "\u014d": "o",
                "\u014f": "o",
                "\u0151": "o",
                "\u0154": "R",
                "\u0156": "R",
                "\u0158": "R",
                "\u0155": "r",
                "\u0157": "r",
                "\u0159": "r",
                "\u015a": "S",
                "\u015c": "S",
                "\u015e": "S",
                "\u0160": "S",
                "\u015b": "s",
                "\u015d": "s",
                "\u015f": "s",
                "\u0161": "s",
                "\u0162": "T",
                "\u0164": "T",
                "\u0166": "T",
                "\u0163": "t",
                "\u0165": "t",
                "\u0167": "t",
                "\u0168": "U",
                "\u016a": "U",
                "\u016c": "U",
                "\u016e": "U",
                "\u0170": "U",
                "\u0172": "U",
                "\u0169": "u",
                "\u016b": "u",
                "\u016d": "u",
                "\u016f": "u",
                "\u0171": "u",
                "\u0173": "u",
                "\u0174": "W",
                "\u0175": "w",
                "\u0176": "Y",
                "\u0177": "y",
                "\u0178": "Y",
                "\u0179": "Z",
                "\u017b": "Z",
                "\u017d": "Z",
                "\u017a": "z",
                "\u017c": "z",
                "\u017e": "z",
                "\u0132": "IJ",
                "\u0133": "ij",
                "\u0152": "Oe",
                "\u0153": "oe",
                "\u0149": "'n",
                "\u017f": "s"
            }),
            c = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,
            d = /[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff]/g;
        return function(a) {
            return a && a.replace(c, b).replace(d, "")
        }
    }();

    function createProxyGlobalAndUpdater(global, strict, sources) {
        strict = Boolean(strict)
        let allowed = null;
        let no_result = Symbol("no result");
        let proxy = Proxy.revocable(global, {
            get: function(target, property, receiver) {
                if (property == "this") {
                    return 1;
                } else if ((property == '_a' || property == '_b') && arguments.callee.caller == allowed) {
                    return proxy;
                } else if (property == 'addGlobalSource' && arguments.callee.caller == allowed) {
                    return (source) => {
                        sources.push(source.bind(global));
                    }
                }
                return target[property] || globalThis[property] || (deburr(String(property)).replace(/[_a-z][_a-z0-9]*/i, "") == "" ? (sources.map((source) => source(property)).filter(a => typeof a != "undefined")[0]) : undefined)
            },
            set: (target, property, value) => {
                return target[property] = value;
            },
            has: (target, property, receiver) => {
                return !strict || property in target || ((property == '_a' || property == '_b')) || ((deburr(String(property)).replace(/[_a-z][_a-z0-9]*/i, "") == "" && arguments.callee.caller == allowed ? (sources.map((source) => source(property, no_result)).filter(a => typeof a != "undefined")[0]) : undefined) != undefined)
            },
            getOwnPropertyDescriptor: (target, property) => {
                return Object.getOwnPropertyDescriptor(target, property)
            }
        })
        return [proxy.proxy, (a) => {
            allowed = a
        }, proxy.revoke];
    }
    let chunks = code.split(/('toggle strict'|"toggle strict")/).filter((item) => {
        return !/('toggle strict'|"toggle strict")/.test(item)
    });
    for (var i = 0; i < chunks.length; i++) {
        if (i % 2 == 0) {
            chunks[i] = `
                ;with(_a) {
                    this.addGlobalSource((() => {
                        "use strict";
                        ${chunks[i]}
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }`
        } else {
            chunks[i] = `
                ;with(_b) {
                    this.addGlobalSource((() => {
                        ${chunks[i]}
                        return (_, no_result) => {
                            try{
                                return eval(_) || no_result;
                            } catch(e) {
                                return no_result
                            }
                        }
                    })())
                }`
        }
    }
    let func = Function("_a", "_b", chunks.join(''))
    let scope_evals = [];
    let sources = [];
    let [firstProxy, firstUpdate] = createProxyGlobalAndUpdater(this, true, sources);
    let [secondProxy, secondUpdate] = createProxyGlobalAndUpdater(this, false, sources)
    firstUpdate(func);
    secondUpdate(func);
    let args = [firstProxy, secondProxy];
    let togglestrict = {
        ['toggle-strict']() {
            func.bind(firstProxy)(...args, ...arguments)
        }
    } ["toggle-strict"];
    Object.defineProperty(togglestrict, "setScopeWithEval", {
        value: function setScopeWithEval(func) {
            scope_evals.push(func)
        }
    })
    Object.defineProperty(togglestrict, "toString", {
        value: function toString() { return `function ${this.name}() { [shim code] }` }
    })
    Object.defineProperty(togglestrict.toString, "toString", {
        value: togglestrict.toString
    })
    return togglestrict;
}

Credits to Lodash for the deburr function

\$\endgroup\$
20
  • \$\begingroup\$ if it is throwing then it belongs on stackoverflow, not code review. that said, the fact that this contains obfuscated code and is completely unreadable means i would never use this. why would you want to toggle strict mode anyway... \$\endgroup\$ Jan 23 '19 at 14:49
  • 1
    \$\begingroup\$ @iwrestledabearonce It's throwing because you're running code that would throw anyhow... if you try console.log(b) without b defined, it will throw in strict mode. \$\endgroup\$ Jan 23 '19 at 14:50
  • 1
    \$\begingroup\$ And it's not obfuscated, that's the implementation of deburr \$\endgroup\$ Jan 23 '19 at 14:53
  • 1
    \$\begingroup\$ Yes, 'use strict' can be inserted or removed from the function body using String and RegExp methods, given the current implementation uses string methods anyway? At first glance the code appears to be unnecessarily verbose. new Function could probably be substituted for eval() usage. Can you include test cases for the prospective functionality of the function at the question? Utilizing block scope stackoverflow.com/a/45260422 or try..catch..finally could possibly be substituted for the current implementation, given the purpose of the function? \$\endgroup\$ Jan 23 '19 at 17:44
  • 2
    \$\begingroup\$ {'use strict';console.log(b = 1)} according to what you're saying, this code to the left should error. Thing is, it doesn't. Because it never enters strict mode. Because block scope is not considered a valid area to enter strict mode. \$\endgroup\$ Jan 23 '19 at 18:09
3
\$\begingroup\$

But why?

This is a clever trick but is terrible code.

Bad naming, poor use of appropriate declaration types const, var and let, full of redundant code, full of bugs, inappropriate use of logic operators (Strict equality rather than ==) and so long winded it looks like it's deliberately obfuscated.

Redundant

  • strict = Boolean(strict) this is in a function only called from within. You only ever pass a boolean.
  • You create a revocable proxy on the object global at the end of the function you return the revoke function as the third item in an array. Yet the calling function ignores the revoke function, not even keeping a reference. The function is thus never used. So why use a revocable proxy in the first place, and why return it when its not used.
  • Each time you call deburr you create a new string, eg deburr(String(property)) If its that important why are you not doing it within the function deburr
  • No reason to use the name toggle-strict. It forces you to use bracket notation for no reason. You could have used toggleStrict
  • Use Object.defineProperties( when creating several properties, rather than many lines of defineProperty
  • Redefining a function name is redundant. Object.definePropertie(togglestrict, "setScopeWithEval", { value: function setScopeWithEval(func) { the second function name is not needed as yoiu do not use it at any point.
  • I do not understand why you assign togglestrict.toString to itself??? You effectively do togglestrict.toString.toString = togglestrict.toString; Is here a reason??
  • The whole last section is just so strange, I can not workout the reasoning. It can be replaced with just a few lines. See Example A
  • The loop iterating the chunks is duplicating the string, when only the global name and the directive "use strict" change. See Example B

Bugs.

  • This code will not work in a module as modules are automatically in strict mode.
  • This code will not work within a function, or javascript that is in strict mode as with is prohibited in strict mode.
  • The set handler on the global proxy needs to return true or will throw an error in strict mode. eg toggleableStrict("b = 2;'toggle strict';b=false;'toggle strict';b=false;")
  • When not in strict mode the set handler incorrectly sets false to undefined toggleableStrict("b = 2;'toggle strict';b=false;console.log('b: '+b)")() output b: undefined
  • Throws syntax error for toggleableStrict("(()=>{'toggle strict';console.log('hi there');})()");

There are plenty more bugs, but you get the picture. I get the feeling you have not done any form of rigorous testing on this code.

Problems.

  1. Testing the code I found it hard to know if I was in strict mode or not. As it is toggled, it easy to lose track of just how many toggles had been performed.

  2. Not for development. It is next to useless as a development tool as you lose the ability to trace errors effectively. I can see no reason why someone would use this in release code.

  3. Unmanageable. This is so poorly written that it was next to impossible to work out what it was doing. In fact I gave up on what deburr does, and after seeing the proxy was a mess I did not bother going further into that. I am sure there are many more issues associated.

Examples

Examples as referenced above

Example A

// you had...
let togglestrict = {
    ['toggle-strict']() {
        func.bind(firstProxy)(...args, ...arguments)
    }
} ["toggle-strict"];
Object.defineProperty(togglestrict, "setScopeWithEval", {
    value: function setScopeWithEval(func) {
        scope_evals.push(func)
    }
})
Object.defineProperty(togglestrict, "toString", {
    value: function toString() { return `function ${this.name}() { [shim code] }` }
})
Object.defineProperty(togglestrict.toString, "toString", {
    value: togglestrict.toString
})
return togglestrict;

//======================================================================================
// Can be writen as
return Object.defineProperties(() => { func.bind(firstProxy)(...args, ...arguments) }, {
    setScopeWithEval : {value : (func) => {scope_evals.push(func)}},
    toString : {value : () => `function toggle-strict() { [shim code] }`},
});    

Example B

// you had
for (var i = 0; i < chunks.length; i++) {
    if (i % 2 == 0) {
        chunks[i] = `
            ;with(_a) {
                this.addGlobalSource((() => {
                    "use strict";
                    ${chunks[i]}
                    return (_, no_result) => {
                        try{
                            return eval(_) || no_result;
                        } catch(e) {
                            return no_result
                        }
                    }
                })())
            }`
    } else {
        chunks[i] = `
            ;with(_b) {
                this.addGlobalSource((() => {
                    ${chunks[i]}
                    return (_, no_result) => {
                        try{
                            return eval(_) || no_result;
                        } catch(e) {
                            return no_result
                        }
                    }
                })())
            }`
    }
}

//======================================================================================
// can be
for (let i = 0; i < chunks.length; i++) {
    chunks[i] = `
        ;with(_${i % 2 ? "b" : "a"}) {
            this.addGlobalSource((() => {
                ${i % 2 ? "" : "'use strict';"}
                ${chunks[i]}
                return (_, no_result) => {
                    try{
                        return eval(_) || no_result;
                    } catch(e) {
                        return no_result
                    }
                }
            })())
        }`;

}
\$\endgroup\$
12
  • \$\begingroup\$ deburr is the same function as _.deburr. And yes, just last night I thought of making some global that would make it possible to determine whether or not it is in strict mode... Also, the point is that the code is run outside of strict mode... so it shouldn't be subject to the restrictions of strict mode itself. \$\endgroup\$ Jan 24 '19 at 14:10
  • 1
    \$\begingroup\$ @FreezePhoenix Strict mode is unavoidable in modules. Also the toggle is unusable as it adds ambiguity to the scope. Consider toggleableStrict(var b=2; 'toggle strict'; log(b);)(); It throws b as undefined, more dangerously b may be in a higher scope, thus after the toggle you access the outer b not the one in the same apparent scope. However the major problem is block continuity eg {'toggle-strict'} throws in your function and never runs the code. Toggle is limited to top level scope only. Much easier to just use the old fashioned approach (function(){"use strict"; log(b)})() \$\endgroup\$
    – Blindman67
    Jan 24 '19 at 17:24
  • \$\begingroup\$ Actually, that first one shouldn't log b as undefined... it accesses it as 2. And yeah, I'll have to figure out how to change the proxy. It shouldn't use 2 proxies, just 1 proxy with a flag, indicating strict or not, and exposing a function that toggles it. This would also eliminate the need to use regex, and fix the scope continuity. \$\endgroup\$ Jan 24 '19 at 17:49
  • \$\begingroup\$ I'd also have to expose an immuteable value that says whether or not it is strict. \$\endgroup\$ Jan 24 '19 at 17:57
  • \$\begingroup\$ @FreezePhoenix My bad, was meant to be let b=2; "toggle strict"; log(b); "toggle strict"; log(b) throws on the second log \$\endgroup\$
    – Blindman67
    Jan 24 '19 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.