diff --git a/src/lexer.coffee b/src/lexer.coffee index 879cab64..41efc75d 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -106,7 +106,7 @@ exports.Lexer = class Lexer @tokens.pop() id = '!' + id - if id in ['eval', 'arguments'].concat JS_FORBIDDEN + if id in JS_FORBIDDEN if forcedIdentifier tag = 'IDENTIFIER' id = new String id @@ -576,11 +576,14 @@ RESERVED = [ 'private', 'protected', 'public', 'static', 'yield' ] +STRICT_PROSCRIBED = ['arguments', 'eval'] + # The superset of both JavaScript keywords and reserved words, none of which may # be used as identifiers or properties. -JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED +JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED) -exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS) +exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED) +exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED # Token matching regexes. IDENTIFIER = /// ^ diff --git a/src/nodes.coffee b/src/nodes.coffee index e02e0a4e..b9c8b529 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -4,7 +4,7 @@ # the syntax tree into a string of JavaScript code, call `compile()` on the root. {Scope} = require './scope' -{RESERVED} = require './lexer' +{RESERVED, STRICT_PROSCRIBED} = require './lexer' # Import the helpers we plan to use. {compact, flatten, extend, merge, del, starts, ends, last} = require './helpers' @@ -329,7 +329,7 @@ exports.Literal = class Literal extends Base if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0' else if @value is 'this' if o.scope.method?.bound then o.scope.method.context else @value - else if @value.reserved and "#{@value}" not in ['eval', 'arguments'] + else if @value.reserved "\"#{@value}\"" else @value @@ -862,6 +862,8 @@ exports.Class = class Class extends Base tail instanceof Access and tail.name.value else @variable.base.value + if decl in STRICT_PROSCRIBED + throw SyntaxError 'variable name may not be eval or arguments' decl and= IDENTIFIER.test(decl) and decl # For all `this`-references and bound functions in the class definition, @@ -990,6 +992,8 @@ exports.Assign = class Assign extends Base constructor: (@variable, @value, @context, options) -> @param = options and options.param @subpattern = options and options.subpattern + if @variable.unwrapAll().value in STRICT_PROSCRIBED + throw SyntaxError 'variable name may not be "eval" or "arguments"' children: ['variable', 'value'] @@ -1055,7 +1059,7 @@ exports.Assign = class Assign extends Base acc = IDENTIFIER.test idx.unwrap().value or 0 value = new Value value value.properties.push new (if acc then Access else Index) idx - if obj.unwrap().value in ['arguments','eval'].concat RESERVED + if obj.unwrap().value in RESERVED throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}" return new Assign(obj, value, null, param: @param).compile o, LEVEL_TOP vvar = value.compile o, LEVEL_LIST @@ -1100,7 +1104,7 @@ exports.Assign = class Assign extends Base else acc = isObject and IDENTIFIER.test idx.unwrap().value or 0 val = new Value new Literal(vvar), [new (if acc then Access else Index) idx] - if name? and name in ['arguments','eval'].concat RESERVED + if name? and name in RESERVED throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}" assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compile o, LEVEL_LIST assigns.push vvar unless top or @subpattern @@ -1224,6 +1228,8 @@ exports.Code = class Code extends Base # as well as be a splat, gathering up a group of parameters into an array. exports.Param = class Param extends Base constructor: (@name, @value, @splat) -> + if @name.unwrapAll().value in STRICT_PROSCRIBED + throw SyntaxError 'parameter name eval or arguments is not allowed' children: ['name', 'value'] @@ -1575,6 +1581,8 @@ exports.Try = class Try extends Base tryPart = @attempt.compile o, LEVEL_TOP catchPart = if @recovery + if @error.value in STRICT_PROSCRIBED + throw SyntaxError "catch variable may not be eval or arguments" o.scope.add @error.value, 'param' unless o.scope.check @error.value " catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}" else unless @ensure or @recovery diff --git a/test/strict.coffee b/test/strict.coffee index 86c97af6..df125298 100644 --- a/test/strict.coffee +++ b/test/strict.coffee @@ -100,32 +100,50 @@ test "`delete` operand restrictions", -> strict '([a]) -> delete a' strict '({a}) -> delete a' -test "`Future Reserved Word`s as identifiers prohibited", -> - futures = 'implements interface let package private protected public static yield'.split ' ' - for keyword in futures - strict "#{keyword} = 1" - strict "class #{keyword}" - strict "try new Error catch #{keyword}" - strict "(#{keyword}) ->" - strict "{keyword}++" - strict "++{keyword}" - strict "{keyword}--" - strict "--{keyword}" +test "`Future Reserved Word`s, `eval` and `arguments` restrictions", -> -test "`eval` and `arguments` use restricted", -> - proscribeds = 'eval arguments'.split ' ' - for proscribed in proscribeds - strict "#{proscribed} = 1" - strict "class #{proscribed}" - strict "try new Error catch #{proscribed}" - strict "(#{proscribed}) ->" - strict "{proscribed}++" - strict "++{proscribed}" - strict "{proscribed}--" - strict "--{proscribed}" - strictOk "eval 'true'" - strictOk "-> arguments[0]" + access = (keyword, check = strict) -> + check "#{keyword}.a = 1" + check "#{keyword}[0] = 1" + assign = (keyword, check = strict) -> + check "#{keyword} = 1" + check "#{keyword} += 1" + check "#{keyword} -= 1" + check "#{keyword} *= 1" + check "#{keyword} /= 1" + check "#{keyword} ?= 1" + check "{keyword}++" + check "++{keyword}" + check "{keyword}--" + check "--{keyword}" + invoke = (keyword, check = strict) -> + check "#{keyword} yes" + check "do #{keyword}" + fnDecl = (keyword, check = strict) -> + check "class #{keyword}" + param = (keyword, check = strict) -> + check "(#{keyword}) ->" + check "({#{keyword}}) ->" + prop = (keyword, check = strict) -> + check "a.#{keyword} = 1" + tryCatch = (keyword, check = strict) -> + check "try new Error catch #{keyword}" + future = 'implements interface let package private protected public static yield'.split ' ' + for keyword in future + access keyword + assign keyword + invoke keyword + fnDecl keyword + param keyword + prop keyword, strictOk + tryCatch keyword - - \ No newline at end of file + for keyword in ['eval', 'arguments'] + access keyword, strictOk + assign keyword + invoke keyword, strictOk + fnDecl keyword + param keyword + prop keyword, strictOk + tryCatch keyword