Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
improve parser under "use strict" (#1836)
- `const` without value
- `delete` of expression
- redefining `arguments` or `eval`

extend `test/ufuzz.js`
- optionally generate "use strict"
- improve handling of test cases with syntax errors
- group IIFE generation
- generate bare anonymous functions
- workaround `console.log()` for `new function()`
- generate expressions with `this`


fixes #1810
  • Loading branch information
alexlamsl committed Apr 23, 2017
1 parent 64d7443 commit 9bf72cf
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 69 deletions.
74 changes: 48 additions & 26 deletions lib/parse.js
Expand Up @@ -629,8 +629,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}

next_token.has_directive = function(directive) {
return S.directives[directive] !== undefined &&
S.directives[directive] > 0;
return S.directives[directive] > 0;
}

return next_token;
Expand Down Expand Up @@ -1033,29 +1032,32 @@ function parse($TEXT, options) {
if (in_statement && !name)
unexpected();
expect("(");
var argnames = [];
for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(",");
argnames.push(as_symbol(AST_SymbolFunarg));
}
next();
var loop = S.in_loop;
var labels = S.labels;
++S.in_function;
S.in_directives = true;
S.input.push_directives_stack();
S.in_loop = 0;
S.labels = [];
var body = block_();
if (S.input.has_directive("use strict")) {
if (name) strict_verify_symbol(name);
argnames.forEach(strict_verify_symbol);
}
S.input.pop_directives_stack();
--S.in_function;
S.in_loop = loop;
S.labels = labels;
return new ctor({
name: name,
argnames: (function(first, a){
while (!is("punc", ")")) {
if (first) first = false; else expect(",");
a.push(as_symbol(AST_SymbolFunarg));
}
next();
return a;
})(true, []),
body: (function(loop, labels){
++S.in_function;
S.in_directives = true;
S.input.push_directives_stack();
S.in_loop = 0;
S.labels = [];
var a = block_();
S.input.pop_directives_stack();
--S.in_function;
S.in_loop = loop;
S.labels = labels;
return a;
})(S.in_loop, S.labels)
argnames: argnames,
body: body
});
};

Expand Down Expand Up @@ -1157,7 +1159,10 @@ function parse($TEXT, options) {
a.push(new AST_VarDef({
start : S.token,
name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar),
value : is("operator", "=") ? (next(), expression(false, no_in)) : null,
value : is("operator", "=")
? (next(), expression(false, no_in))
: in_const && S.input.has_directive("use strict")
? croak("Missing initializer in const declaration") : null,
end : prev()
}));
if (!is("punc", ","))
Expand Down Expand Up @@ -1384,12 +1389,20 @@ function parse($TEXT, options) {
});
};

function strict_verify_symbol(sym) {
if (sym.name == "arguments" || sym.name == "eval")
croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos);
}

function as_symbol(type, noerror) {
if (!is("name")) {
if (!noerror) croak("Name expected");
return null;
}
var sym = _make_symbol(type);
if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) {
strict_verify_symbol(sym);
}
next();
return sym;
};
Expand Down Expand Up @@ -1450,8 +1463,17 @@ function parse($TEXT, options) {

function make_unary(ctor, token, expr) {
var op = token.value;
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
switch (op) {
case "++":
case "--":
if (!is_assignable(expr))
croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
break;
case "delete":
if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
break;
}
return new ctor({ operator: op, expression: expr });
};

Expand Down
8 changes: 8 additions & 0 deletions test/input/invalid/const.js
@@ -0,0 +1,8 @@
function f() {
const a;
}

function g() {
"use strict";
const a;
}
14 changes: 14 additions & 0 deletions test/input/invalid/delete.js
@@ -0,0 +1,14 @@
function f(x) {
delete 42;
delete (0, x);
delete null;
delete x;
}

function g(x) {
"use strict";
delete 42;
delete (0, x);
delete null;
delete x;
}
6 changes: 6 additions & 0 deletions test/input/invalid/function_1.js
@@ -0,0 +1,6 @@
function f(arguments) {
}

function g(arguments) {
"use strict";
}
6 changes: 6 additions & 0 deletions test/input/invalid/function_2.js
@@ -0,0 +1,6 @@
function arguments() {
}

function eval() {
"use strict";
}
6 changes: 6 additions & 0 deletions test/input/invalid/function_3.js
@@ -0,0 +1,6 @@
!function eval() {
}();

!function arguments() {
"use strict";
}();
8 changes: 8 additions & 0 deletions test/input/invalid/try.js
@@ -0,0 +1,8 @@
function f() {
try {} catch (eval) {}
}

function g() {
"use strict";
try {} catch (eval) {}
}
8 changes: 8 additions & 0 deletions test/input/invalid/var.js
@@ -0,0 +1,8 @@
function f() {
var eval;
}

function g() {
"use strict";
var eval;
}
105 changes: 105 additions & 0 deletions test/mocha/cli.js
Expand Up @@ -379,6 +379,111 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should throw syntax error (const a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/const.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/const.js:7,11",
" const a;",
" ^",
"ERROR: Missing initializer in const declaration"
].join("\n"));
done();
});
});
it("Should throw syntax error (delete x)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/delete.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/delete.js:13,11",
" delete x;",
" ^",
"ERROR: Calling delete on expression not allowed in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (function g(arguments))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_1.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_1.js:4,11",
"function g(arguments) {",
" ^",
"ERROR: Unexpected arguments in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (function eval())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_2.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_2.js:4,9",
"function eval() {",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (iife arguments())", function(done) {
var command = uglifyjscmd + ' test/input/invalid/function_3.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/function_3.js:4,10",
"!function arguments() {",
" ^",
"ERROR: Unexpected arguments in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (catch(eval))", function(done) {
var command = uglifyjscmd + ' test/input/invalid/try.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/try.js:7,18",
" try {} catch (eval) {}",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should throw syntax error (var eval)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/var.js';

exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/var.js:7,8",
" var eval;",
" ^",
"ERROR: Unexpected eval in strict mode"
].join("\n"));
done();
});
});
it("Should handle literal string as source map input", function(done) {
var command = [
uglifyjscmd,
Expand Down
38 changes: 28 additions & 10 deletions test/sandbox.js
@@ -1,15 +1,35 @@
var vm = require("vm");

function safe_log(arg) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString();
for (var key in arg) {
arg[key] = safe_log(arg[key]);
}
}
return arg;
}

var FUNC_TOSTRING = [
"Function.prototype.toString = Function.prototype.valueOf = function() {",
" var ids = [];",
" var id = 0;",
" return function() {",
" var i = ids.indexOf(this);",
" if (i < 0) {",
" i = ids.length;",
" ids.push(this);",
' if (this === Array) return "[Function: Array]";',
' if (this === Object) return "[Function: Object]";',
" var i = this.name;",
' if (typeof i != "number") {',
" i = ++id;",
' Object.defineProperty(this, "name", {',
" get: function() {",
" return i;",
" }",
" });",
" }",
' return "[Function: __func_" + i + "__]";',
' return "[Function: " + i + "]";',
" }",
"}();",
].join("\n");
Expand All @@ -21,16 +41,14 @@ exports.run_code = function(code) {
};
try {
vm.runInNewContext([
"!function() {",
FUNC_TOSTRING,
"!function() {",
code,
"}();",
].join("\n"), {
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" || arg && /Error$/.test(arg.name) ? arg.toString() : arg;
}));
return console.log.apply(console, [].map.call(arguments, safe_log));
}
}
}, { timeout: 5000 });
Expand Down

0 comments on commit 9bf72cf

Please sign in to comment.