307 lines
11 KiB
JavaScript
307 lines
11 KiB
JavaScript
var readFileSync = require("fs").readFileSync;
|
|
var semver = require("semver");
|
|
var spawnSync = require("child_process").spawnSync;
|
|
var vm = require("vm");
|
|
|
|
setup_log();
|
|
var setup_code = "(" + setup + ")(" + [
|
|
"this",
|
|
find_builtins(),
|
|
setup_log,
|
|
"function(process) {" + readFileSync(require.resolve("../tools/tty", "utf8")) + "}",
|
|
].join(",\n") + ");\n";
|
|
exports.has_toplevel = function(options) {
|
|
return options.toplevel
|
|
|| options.mangle && options.mangle.toplevel
|
|
|| options.compress && options.compress.toplevel;
|
|
};
|
|
exports.is_error = is_error;
|
|
exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, toplevel, timeout) {
|
|
var stdout = run_code_vm(code, toplevel, timeout);
|
|
if (typeof stdout != "string" || !/arguments/.test(code)) return stdout;
|
|
do {
|
|
var prev = stdout;
|
|
stdout = run_code_vm(code, toplevel, timeout);
|
|
} while (prev !== stdout);
|
|
return stdout;
|
|
} : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) {
|
|
if ([
|
|
/\basync[ \t]*\([\s\S]*?\)[ \t]*=>/,
|
|
/\b(async[ \t]+function|Promise|setImmediate|setInterval|setTimeout)\b/,
|
|
/\basync([ \t]+|[ \t]*#|[ \t]*\*[ \t]*)[^\s()[\]{},.&|!~=*%/+-]+(\s*\(|[ \t]*=>)/,
|
|
].some(function(pattern) {
|
|
return pattern.test(code);
|
|
})) {
|
|
return run_code_exec(code, toplevel, timeout);
|
|
} else {
|
|
return run_code_vm(code, toplevel, timeout);
|
|
}
|
|
};
|
|
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
|
|
if (typeof expected != typeof actual) return false;
|
|
if (is_error(expected)) {
|
|
if (expected.name !== actual.name) return false;
|
|
if (typeof actual.message != "string") return false;
|
|
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
|
|
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
|
|
}
|
|
return strip_func_ids(expected) == strip_func_ids(actual);
|
|
} : function(expected, actual) {
|
|
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
|
|
};
|
|
exports.patch_module_statements = function(code) {
|
|
var count = 0, imports = [];
|
|
code = code.replace(/\bexport(?:\s*\{[^}]*}\s*?(?:$|\n|;)|\s+default\b(?:\s*(\(|\{|class\s*\{|class\s+(?=extends\b)|(?:async\s+)?function\s*(?:\*\s*)?\())?|\b)/g, function(match, header) {
|
|
if (!header) return "";
|
|
if (header.length == 1) return "0, " + header;
|
|
return header.slice(0, -1) + " _" + ++count + header.slice(-1);
|
|
}).replace(/\bimport\.meta\b/g, function() {
|
|
return '({ url: "https://example.com/path/index.html" })';
|
|
}).replace(/\bimport\b(?:\s*([^('"]+)\bfrom\b)?\s*(['"]).*?\2(?:$|\n|;)/g, function(match, symbols) {
|
|
if (symbols) {
|
|
if (!/^[{*]/.test(symbols)) symbols = "default:" + symbols;
|
|
symbols = symbols.replace(/[{}]/g, "").trim().replace(/\s*,\s*/g, ",");
|
|
symbols = symbols.replace(/\*/, '"*"').replace(/\bas\s+(?!$|,|as\s)/g, ":");
|
|
imports.push([
|
|
"const {",
|
|
symbols,
|
|
"} = new Proxy(Object.create(null), { get(_, value) { return { value }; } });",
|
|
].join(""));
|
|
}
|
|
return "";
|
|
});
|
|
imports.push("");
|
|
return imports.join("\n") + code;
|
|
};
|
|
|
|
function is_error(result) {
|
|
return result && typeof result.name == "string" && typeof result.message == "string";
|
|
}
|
|
|
|
function strip_color_codes(value) {
|
|
return value.replace(/\u001b\[\d+m/g, "");
|
|
}
|
|
|
|
function strip_func_ids(text) {
|
|
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
|
|
}
|
|
|
|
function setup_log() {
|
|
var inspect = require("util").inspect;
|
|
if (inspect.defaultOptions) {
|
|
var log_options = {
|
|
breakLength: Infinity,
|
|
colors: false,
|
|
compact: true,
|
|
customInspect: false,
|
|
depth: Infinity,
|
|
maxArrayLength: Infinity,
|
|
maxStringLength: Infinity,
|
|
showHidden: false,
|
|
};
|
|
for (var name in log_options) {
|
|
if (name in inspect.defaultOptions) inspect.defaultOptions[name] = log_options[name];
|
|
}
|
|
}
|
|
return inspect;
|
|
}
|
|
|
|
function find_builtins() {
|
|
setup_code = "console.log(Object.keys(this));";
|
|
var builtins = run_code_vm("");
|
|
if (semver.satisfies(process.version, ">=0.12")) builtins += ".concat(" + run_code_exec("") + ")";
|
|
return builtins;
|
|
}
|
|
|
|
function setup(global, builtins, setup_log, setup_tty) {
|
|
[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {
|
|
f.toString = Function.prototype.toString;
|
|
});
|
|
Function.prototype.toString = function() {
|
|
var configurable = Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable;
|
|
var id = 100000;
|
|
return function() {
|
|
var n = this.name;
|
|
if (!/^F[0-9]{6}N$/.test(n)) {
|
|
n = "F" + ++id + "N";
|
|
if (configurable) Object.defineProperty(this, "name", {
|
|
get: function() {
|
|
return n;
|
|
}
|
|
});
|
|
}
|
|
return "function(){}";
|
|
};
|
|
}();
|
|
var process = global.process;
|
|
if (process) {
|
|
setup_tty(process);
|
|
var inspect = setup_log();
|
|
process.on("uncaughtException", function(ex) {
|
|
var value = ex;
|
|
if (value instanceof Error) {
|
|
value = {};
|
|
for (var name in ex) {
|
|
value[name] = ex[name];
|
|
delete ex[name];
|
|
}
|
|
}
|
|
process.stderr.write(inspect(value) + "\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
|
|
throw ex;
|
|
}).on("unhandledRejection", function() {});
|
|
}
|
|
var log = console.log;
|
|
var safe_console = {
|
|
log: function(msg) {
|
|
if (arguments.length == 1 && typeof msg == "string") return log("%s", msg);
|
|
return log.apply(null, [].map.call(arguments, function(arg) {
|
|
return safe_log(arg, {
|
|
level: 5,
|
|
original: [],
|
|
replaced: [],
|
|
});
|
|
}));
|
|
},
|
|
};
|
|
var props = {
|
|
// for Node.js v8
|
|
console: {
|
|
get: function() {
|
|
return safe_console;
|
|
},
|
|
},
|
|
global: { get: self },
|
|
self: { get: self },
|
|
window: { get: self },
|
|
};
|
|
[
|
|
// for Node.js v0.12
|
|
"Buffer",
|
|
"clearImmediate",
|
|
"clearInterval",
|
|
"clearTimeout",
|
|
// for Node.js v0.12
|
|
"DTRACE_NET_STREAM_END",
|
|
// for Node.js v8
|
|
"process",
|
|
"setImmediate",
|
|
"setInterval",
|
|
"setTimeout",
|
|
].forEach(function(name) {
|
|
var value = global[name];
|
|
props[name] = {
|
|
get: function() {
|
|
return value;
|
|
},
|
|
};
|
|
});
|
|
builtins.forEach(function(name) {
|
|
try {
|
|
delete global[name];
|
|
} catch (e) {}
|
|
});
|
|
Object.defineProperties(global, props);
|
|
// for Node.js v8+
|
|
if (global.toString !== Object.prototype.toString) {
|
|
global.__proto__ = Object.defineProperty(Object.create(global.__proto__), "toString", {
|
|
value: function() {
|
|
return "[object global]";
|
|
},
|
|
});
|
|
}
|
|
|
|
function self() {
|
|
return this;
|
|
}
|
|
|
|
function safe_log(arg, cache) {
|
|
if (arg) switch (typeof arg) {
|
|
case "function":
|
|
return arg.toString();
|
|
case "object":
|
|
if (arg === global) return "[object global]";
|
|
if (/Error$/.test(arg.name)) return arg.toString();
|
|
if (typeof arg.then == "function") return "[object Promise]";
|
|
if (arg.constructor) arg.constructor.toString();
|
|
var index = cache.original.indexOf(arg);
|
|
if (index >= 0) return cache.replaced[index];
|
|
if (--cache.level < 0) return "[object Object]";
|
|
var value = {};
|
|
cache.original.push(arg);
|
|
cache.replaced.push(value);
|
|
for (var key in arg) {
|
|
var desc = Object.getOwnPropertyDescriptor(arg, key);
|
|
if (desc && (desc.get || desc.set)) {
|
|
Object.defineProperty(value, key, desc);
|
|
} else {
|
|
value[key] = safe_log(arg[key], cache);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
return arg;
|
|
}
|
|
}
|
|
|
|
function run_code_vm(code, toplevel, timeout) {
|
|
timeout = timeout || 5000;
|
|
var stdout = "";
|
|
var original_write = process.stdout.write;
|
|
process.stdout.write = function(chunk) {
|
|
stdout += chunk;
|
|
};
|
|
try {
|
|
var ctx = vm.createContext({ console: console });
|
|
// for Node.js v6
|
|
vm.runInContext(setup_code, ctx);
|
|
vm.runInContext(toplevel ? "(function(){" + code + "})();" : code, ctx, { timeout: timeout });
|
|
return strip_color_codes(stdout);
|
|
} catch (ex) {
|
|
return ex;
|
|
} finally {
|
|
process.stdout.write = original_write;
|
|
}
|
|
}
|
|
|
|
function run_code_exec(code, toplevel, timeout) {
|
|
if (toplevel) {
|
|
code = setup_code + "(function(){" + code + "})();";
|
|
} else {
|
|
code = code.replace(/^((["'])[^"']*\2(;|$))?/, function(directive) {
|
|
return directive + setup_code;
|
|
});
|
|
}
|
|
var result = spawnSync(process.argv[0], [ '--max-old-space-size=2048' ], {
|
|
encoding: "utf8",
|
|
input: code,
|
|
stdio: "pipe",
|
|
timeout: timeout || 5000,
|
|
});
|
|
if (result.status === 0) return result.stdout;
|
|
if (result.error && result.error.code == "ETIMEDOUT" || /FATAL ERROR:/.test(msg)) {
|
|
return new Error("Script execution timed out.");
|
|
}
|
|
if (result.error) return result.error;
|
|
var msg = result.stderr.replace(/\r\n/g, "\n");
|
|
var end = msg.indexOf("\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
|
|
var details;
|
|
if (end >= 0) {
|
|
details = msg.slice(0, end).replace(/<([1-9][0-9]*) empty items?>/g, function(match, count) {
|
|
return new Array(+count).join();
|
|
});
|
|
try {
|
|
details = vm.runInNewContext("(" + details + ")");
|
|
} catch (e) {}
|
|
}
|
|
var match = /\n([^:\s]*Error)(?:: ([\s\S]+?))?\n( at [\s\S]+)\n$/.exec(msg);
|
|
if (!match) return details;
|
|
var ex = new global[match[1]](match[2]);
|
|
ex.stack = ex.stack.slice(0, ex.stack.indexOf(" at ")) + match[3];
|
|
if (typeof details == "object") {
|
|
for (var name in details) ex[name] = details[name];
|
|
} else if (end >= 0) {
|
|
ex.details = details;
|
|
}
|
|
return ex;
|
|
}
|