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
1 Answer 1
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 objectglobal
at the end of the function you return therevoke
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, egdeburr(String(property))
If its that important why are you not doing it within the functiondeburr
- No reason to use the name
toggle-strict
. It forces you to use bracket notation for no reason. You could have usedtoggleStrict
- Use
Object.defineProperties(
when creating several properties, rather than many lines ofdefineProperty
- 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 dotogglestrict.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 returntrue
or will throw an error in strict mode. egtoggleableStrict("b = 2;'toggle strict';b=false;'toggle strict';b=false;")
- When not in strict mode the
set
handler incorrectly sets false toundefined
toggleableStrict("b = 2;'toggle strict';b=false;console.log('b: '+b)")()
outputb: 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.
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.
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.
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
}
}
})())
}`;
}
-
\$\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\$FreezePhoenix– FreezePhoenix2019年01月24日 14:10:15 +00:00Commented Jan 24, 2019 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 throwsb as undefined
, more dangerouslyb
may be in a higher scope, thus after the toggle you access the outerb
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– Blindman672019年01月24日 17:24:21 +00:00Commented Jan 24, 2019 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\$FreezePhoenix– FreezePhoenix2019年01月24日 17:49:33 +00:00Commented Jan 24, 2019 at 17:49
-
\$\begingroup\$ I'd also have to expose an immuteable value that says whether or not it is strict. \$\endgroup\$FreezePhoenix– FreezePhoenix2019年01月24日 17:57:00 +00:00Commented Jan 24, 2019 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– Blindman672019年01月24日 20:02:52 +00:00Commented Jan 24, 2019 at 20:02
console.log(b)
withoutb
defined, it will throw in strict mode. \$\endgroup\$'use strict'
can be inserted or removed from the function body usingString
andRegExp
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 foreval()
usage. Can you include test cases for the prospective functionality of the function at the question? Utilizing block scope stackoverflow.com/a/45260422 ortry..catch..finally
could possibly be substituted for the current implementation, given the purpose of the function? \$\endgroup\${'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\$