Skip to content

Commit

Permalink
Use regexps instead of magic generated functions
Browse files Browse the repository at this point in the history
Remove need for acorn_csp.js
  • Loading branch information
marijnh committed Oct 6, 2015
1 parent f049401 commit aed55f3
Show file tree
Hide file tree
Showing 9 changed files with 30 additions and 122 deletions.
14 changes: 0 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,20 +220,6 @@ console.log(escodegen.generate(ast, {comment: true}));

[escodegen]: https://github.com/Constellation/escodegen

#### Using Acorn in an environment with a Content Security Policy

Some contexts, such as Chrome Web Apps, disallow run-time code evaluation.
Acorn uses `new Function` to generate fast functions that test whether
a word is in a given set, and will trigger a security error when used
in a context with such a
[Content Security Policy](http://www.html5rocks.com/en/tutorials/security/content-security-policy/#eval-too)
(see [#90](https://github.com/marijnh/acorn/issues/90) and
[#123](https://github.com/marijnh/acorn/issues/123)).

The `dist/acorn_csp.js` file in the distribution (which is built
by the `bin/without_eval` script) has the generated code inlined, and
can thus run without evaluating anything.

### dist/acorn_loose.js ###

This file implements an error-tolerant parser. It exposes a single
Expand Down
48 changes: 0 additions & 48 deletions bin/without_eval

This file was deleted.

1 change: 1 addition & 0 deletions dist/acorn_csp.js
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"license": "MIT",
"scripts": {
"prepublish": "node bin/build-acorn.js && node bin/without_eval > dist/acorn_csp.js",
"prepublish": "node bin/build-acorn.js",
"test": "node test/run.js"
},
"bin": {
Expand Down
13 changes: 5 additions & 8 deletions src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {reservedWords} from "./identifier"
import {has} from "./util"

const pp = Parser.prototype
Expand Down Expand Up @@ -510,9 +509,8 @@ pp.parsePropertyValue = function(prop, isPattern, isGenerator, startPos, startLo
} else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") {
prop.kind = "init"
if (isPattern) {
if (this.isKeyword(prop.key.name) ||
(this.strict && (reservedWords.strictBind(prop.key.name) || reservedWords.strict(prop.key.name))) ||
(!this.options.allowReserved && this.isReservedWord(prop.key.name)))
if (this.keywords.test(prop.key.name) ||
(this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name))
this.raise(prop.key.start, "Binding " + prop.key.name)
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key)
} else if (this.type === tt.eq && refShorthandDefaultPos) {
Expand Down Expand Up @@ -641,10 +639,9 @@ pp.parseIdent = function(liberal) {
if (liberal && this.options.allowReserved == "never") liberal = false
if (this.type === tt.name) {
if (!liberal &&
((!this.options.allowReserved && this.isReservedWord(this.value)) ||
(this.strict && reservedWords.strict(this.value)) &&
(this.options.ecmaVersion >= 6 ||
this.input.slice(this.start, this.end).indexOf("\\") == -1)))
(this.strict ? this.reservedWordsStrict : this.reservedWords).test(this.value) &&
(this.options.ecmaVersion >= 6 ||
this.input.slice(this.start, this.end).indexOf("\\") == -1))
this.raise(this.start, "The keyword '" + this.value + "' is reserved")
node.name = this.value
} else if (liberal && this.type.keyword) {
Expand Down
53 changes: 7 additions & 46 deletions src/identifier.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/lval.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {reservedWords} from "./identifier"
import {has} from "./util"

const pp = Parser.prototype
Expand Down Expand Up @@ -161,7 +160,7 @@ pp.parseMaybeDefault = function(startPos, startLoc, left) {
pp.checkLVal = function(expr, isBinding, checkClashes) {
switch (expr.type) {
case "Identifier":
if (this.strict && (reservedWords.strictBind(expr.name) || reservedWords.strict(expr.name)))
if (this.strict && this.reservedWordsStrictBind.test(expr.name)) // FIXME match shorter regexp?
this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode")
if (checkClashes) {
if (has(checkClashes, expr.name))
Expand Down
16 changes: 14 additions & 2 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ import {getOptions} from "./options"
// Registered plugins
export const plugins = {}

function keywordRegexp(words) {
return new RegExp("^(" + words.replace(/ /g, "|") + ")$")
}

export class Parser {
constructor(options, input, startPos) {
this.options = getOptions(options)
this.sourceFile = this.options.sourceFile
this.isKeyword = keywords[this.options.ecmaVersion >= 6 ? 6 : 5]
this.isReservedWord = reservedWords[this.options.ecmaVersion]
this.keywords = keywordRegexp(keywords[this.options.ecmaVersion >= 6 ? 6 : 5])
let reserved = this.options.allowReserved ? "" : reservedWords[this.options.ecmaVersion]
this.reservedWords = keywordRegexp(reserved)
let reservedStrict = (reserved ? reserved + " " : "") + reservedWords.strict
this.reservedWordsStrict = keywordRegexp(reservedStrict)
this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + reservedWords.strictBind)
this.input = String(input)

// Used to signal to callers of `readWord1` whether the word
Expand Down Expand Up @@ -71,6 +79,10 @@ export class Parser {
this.skipLineComment(2)
}

// DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them

This comment has been minimized.

Copy link
@RReverser

RReverser Nov 10, 2015

Member

@marijnh I would rather keep those generic APIs around so that should we want to change behavior again / optimize using Sets / whatever in the future, API wouldn't break and plugins could continue to work. It costs nothing to us to keep them, but hides implementation details in a good way. (And, well, such small functions are inlined in any engine anyway)

This comment has been minimized.

Copy link
@marijnh

marijnh Nov 23, 2015

Author Member

The reason I didn't is that I added a bunch of additional regexps for more specialized categories, and figured that providing boilerplate accessors for all of would be noisy.

This comment has been minimized.

Copy link
@RReverser

RReverser Nov 23, 2015

Member

I guess those more specialized categories will be rarely needed by plugins, in opposite to the common ones (isKeyword / isReservedWord) which are part of API we might break more than once.

isKeyword(word) { return this.keywords.test(word) }
isReservedWord(word) { return this.reservedWords.test(word) }

extend(name, f) {
this[name] = f(this[name])
}
Expand Down
2 changes: 1 addition & 1 deletion src/tokenize.js
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ pp.readWord1 = function() {
pp.readWord = function() {
let word = this.readWord1()
let type = tt.name
if ((this.options.ecmaVersion >= 6 || !this.containsEsc) && this.isKeyword(word))
if ((this.options.ecmaVersion >= 6 || !this.containsEsc) && this.keywords.test(word))
type = keywordTypes[word]
return this.finishToken(type, word)
}

0 comments on commit aed55f3

Please sign in to comment.