Throw errors in strict mode for octal strings
Adds a directive tracker for the parser/tokenizer to allow parsing depending on directive context.
This commit is contained in:
committed by
Richard van Velzen
parent
31d5825a86
commit
4d7746baf3
58
lib/parse.js
58
lib/parse.js
@@ -223,7 +223,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||||||
tokcol : 0,
|
tokcol : 0,
|
||||||
newline_before : false,
|
newline_before : false,
|
||||||
regex_allowed : false,
|
regex_allowed : false,
|
||||||
comments_before : []
|
comments_before : [],
|
||||||
|
directives : {},
|
||||||
|
directive_stack : []
|
||||||
};
|
};
|
||||||
|
|
||||||
function peek() { return S.text.charAt(S.pos); };
|
function peek() { return S.text.charAt(S.pos); };
|
||||||
@@ -392,8 +394,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
var ch = next(true, true);
|
var ch = next(true, true);
|
||||||
if (ch == "\\") {
|
if (ch == "\\") {
|
||||||
// read OctalEscapeSequence (XXX: deprecated if "strict mode")
|
|
||||||
// https://github.com/mishoo/UglifyJS/issues/178
|
|
||||||
var octal_len = 0, first = null;
|
var octal_len = 0, first = null;
|
||||||
ch = read_while(function(ch){
|
ch = read_while(function(ch){
|
||||||
if (ch >= "0" && ch <= "7") {
|
if (ch >= "0" && ch <= "7") {
|
||||||
@@ -406,8 +406,13 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
|
if (octal_len > 0) {
|
||||||
else ch = read_escaped_char(true);
|
if (ch !== "0" && next_token.has_directive("use strict"))
|
||||||
|
parse_error("Octal literals are not allowed in strict mode");
|
||||||
|
ch = String.fromCharCode(parseInt(ch, 8));
|
||||||
|
} else {
|
||||||
|
ch = read_escaped_char(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant");
|
else if ("\r\n\u2028\u2029".indexOf(ch) >= 0) parse_error("Unterminated string constant");
|
||||||
else if (ch == quote) break;
|
else if (ch == quote) break;
|
||||||
@@ -608,6 +613,35 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
|||||||
return S;
|
return S;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
next_token.add_directive = function(directive) {
|
||||||
|
S.directive_stack[S.directive_stack.length - 1].push(directive);
|
||||||
|
|
||||||
|
if (S.directives[directive] === undefined) {
|
||||||
|
S.directives[directive] = 1;
|
||||||
|
} else {
|
||||||
|
S.directives[directive]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next_token.push_directives_stack = function() {
|
||||||
|
S.directive_stack.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
next_token.pop_directives_stack = function() {
|
||||||
|
var directives = S.directive_stack[S.directive_stack.length - 1];
|
||||||
|
|
||||||
|
for (var i = 0; i < directives.length; i++) {
|
||||||
|
S.directives[directives[i]]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
S.directive_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
next_token.has_directive = function(directive) {
|
||||||
|
return S.directives[directive] !== undefined &&
|
||||||
|
S.directives[directive] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
return next_token;
|
return next_token;
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -781,9 +815,15 @@ function parse($TEXT, options) {
|
|||||||
handle_regexp();
|
handle_regexp();
|
||||||
switch (S.token.type) {
|
switch (S.token.type) {
|
||||||
case "string":
|
case "string":
|
||||||
|
if (S.in_directives) {
|
||||||
|
if (is_token(peek(), "punc", ";") || peek().nlb) {
|
||||||
|
S.input.add_directive(S.token.raw.substr(1, S.token.raw.length - 2));
|
||||||
|
} else {
|
||||||
|
S.in_directives = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
var dir = S.in_directives, stat = simple_statement();
|
var dir = S.in_directives, stat = simple_statement();
|
||||||
// XXXv2: decide how to fix directives
|
if (dir) {
|
||||||
if (dir && stat.body instanceof AST_String && !is("punc", ",")) {
|
|
||||||
return new AST_Directive({
|
return new AST_Directive({
|
||||||
start : stat.body.start,
|
start : stat.body.start,
|
||||||
end : stat.body.end,
|
end : stat.body.end,
|
||||||
@@ -1012,9 +1052,11 @@ function parse($TEXT, options) {
|
|||||||
body: (function(loop, labels){
|
body: (function(loop, labels){
|
||||||
++S.in_function;
|
++S.in_function;
|
||||||
S.in_directives = true;
|
S.in_directives = true;
|
||||||
|
S.input.push_directives_stack();
|
||||||
S.in_loop = 0;
|
S.in_loop = 0;
|
||||||
S.labels = [];
|
S.labels = [];
|
||||||
var a = block_();
|
var a = block_();
|
||||||
|
S.input.pop_directives_stack();
|
||||||
--S.in_function;
|
--S.in_function;
|
||||||
S.in_loop = loop;
|
S.in_loop = loop;
|
||||||
S.labels = labels;
|
S.labels = labels;
|
||||||
@@ -1514,8 +1556,10 @@ function parse($TEXT, options) {
|
|||||||
return (function(){
|
return (function(){
|
||||||
var start = S.token;
|
var start = S.token;
|
||||||
var body = [];
|
var body = [];
|
||||||
|
S.input.push_directives_stack();
|
||||||
while (!is("eof"))
|
while (!is("eof"))
|
||||||
body.push(statement());
|
body.push(statement());
|
||||||
|
S.input.pop_directives_stack();
|
||||||
var end = prev();
|
var end = prev();
|
||||||
var toplevel = options.toplevel;
|
var toplevel = options.toplevel;
|
||||||
if (toplevel) {
|
if (toplevel) {
|
||||||
|
|||||||
61
test/mocha/directives.js
Normal file
61
test/mocha/directives.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
var assert = require("assert");
|
||||||
|
var uglify = require("../../");
|
||||||
|
|
||||||
|
describe("Directives", function() {
|
||||||
|
it ("Should allow tokenizer to store directives state", function() {
|
||||||
|
var tokenizer = uglify.tokenizer("", "foo.js");
|
||||||
|
|
||||||
|
// Stack level 0
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 2
|
||||||
|
tokenizer.push_directives_stack();
|
||||||
|
tokenizer.push_directives_stack();
|
||||||
|
tokenizer.add_directive("use strict");
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 3
|
||||||
|
tokenizer.push_directives_stack();
|
||||||
|
tokenizer.add_directive("use strict");
|
||||||
|
tokenizer.add_directive("use asm");
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 2
|
||||||
|
tokenizer.pop_directives_stack();
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 3
|
||||||
|
tokenizer.push_directives_stack();
|
||||||
|
tokenizer.add_directive("use thing");
|
||||||
|
tokenizer.add_directive("use\\\nasm");
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false); // Directives are strict!
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), true);
|
||||||
|
|
||||||
|
// Stack level 2
|
||||||
|
tokenizer.pop_directives_stack();
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), true);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 1
|
||||||
|
tokenizer.pop_directives_stack();
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
|
||||||
|
// Stack level 0
|
||||||
|
tokenizer.pop_directives_stack();
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use strict"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use asm"), false);
|
||||||
|
assert.strictEqual(tokenizer.has_directive("use thing"), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -31,4 +31,51 @@ describe("String literals", function() {
|
|||||||
var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string();
|
var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string();
|
||||||
assert.equal(output, 'var a="ab";');
|
assert.equal(output, 'var a="ab";');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should throw error in strict mode if string contains escaped octalIntegerLiteral", function() {
|
||||||
|
var inputs = [
|
||||||
|
'"use strict";\n"\\76";',
|
||||||
|
'"use strict";\nvar foo = "\\76";',
|
||||||
|
'"use strict";\n"\\1";',
|
||||||
|
'"use strict";\n"\\07";',
|
||||||
|
'"use strict";\n"\\011"'
|
||||||
|
];
|
||||||
|
|
||||||
|
var test = function(input) {
|
||||||
|
return function() {
|
||||||
|
var output = UglifyJS.parse(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var error = function(e) {
|
||||||
|
return e instanceof UglifyJS.JS_Parse_Error &&
|
||||||
|
e.message === "Octal literals are not allowed in strict mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var input in inputs) {
|
||||||
|
assert.throws(test(inputs[input]), error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not throw error outside strict mode if string contains escaped octalIntegerLiteral", function() {
|
||||||
|
var tests = [
|
||||||
|
['"\\76";', '">";'],
|
||||||
|
['"\\0"', '"\\x00";'],
|
||||||
|
['"\\08"', '"\\x008";'],
|
||||||
|
['"\\008"', '"\\x008";'],
|
||||||
|
['"\\0008"', '"\\x008";'],
|
||||||
|
['"use strict" === "use strict";\n"\\76";', '"use strict"==="use strict";">";'],
|
||||||
|
// ['"use\\\n strict";\n"\\07";', '"use\\\n strict";\n"\\u0007";'] // TODO No way to store this content literally yet as directive
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var test in tests) {
|
||||||
|
var output = UglifyJS.parse(tests[test][0]).print_to_string();
|
||||||
|
assert.equal(output, tests[test][1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not throw error when digit is 8 or 9", function() {
|
||||||
|
assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\x008";');
|
||||||
|
assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\x009";');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user