From fd61476106bfae963af3d16e3b472ebb164b3fdd Mon Sep 17 00:00:00 2001 From: Michal Srb Date: Tue, 23 Apr 2013 04:28:45 +0200 Subject: [PATCH] Fix #1069. Non-callable literals shouldn't compile --- lib/coffee-script/nodes.js | 29 +++++++++++++++++++++++++---- src/nodes.coffee | 19 +++++++++++++++---- test/function_invocation.coffee | 25 +++++++++++++++++++++++++ test/soaks.coffee | 4 ++-- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 0d78ce88..9f1c17ee 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, _ref2, _ref3, + var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, _ref2, _ref3, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, @@ -675,8 +675,16 @@ return !!this.properties.length; }; + Value.prototype.bareLiteral = function(type) { + return !this.properties.length && this.base instanceof type; + }; + Value.prototype.isArray = function() { - return !this.properties.length && this.base instanceof Arr; + return this.bareLiteral(Arr); + }; + + Value.prototype.isRange = function() { + return this.bareLiteral(Range); }; Value.prototype.isComplex = function() { @@ -688,11 +696,15 @@ }; Value.prototype.isSimpleNumber = function() { - return this.base instanceof Literal && SIMPLENUM.test(this.base.value); + return this.bareLiteral(Literal) && SIMPLENUM.test(this.base.value); }; Value.prototype.isString = function() { - return this.base instanceof Literal && IS_STRING.test(this.base.value); + return this.bareLiteral(Literal) && IS_STRING.test(this.base.value); + }; + + Value.prototype.isRegex = function() { + return this.bareLiteral(Literal) && IS_REGEX.test(this.base.value); }; Value.prototype.isAtomic = function() { @@ -707,6 +719,10 @@ return true; }; + Value.prototype.isNotCallable = function() { + return this.isSimpleNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject(); + }; + Value.prototype.isStatement = function(o) { return !this.properties.length && this.base.isStatement(o); }; @@ -842,6 +858,9 @@ this.isNew = false; this.isSuper = variable === 'super'; this.variable = this.isSuper ? null : variable; + if (variable instanceof Value && variable.isNotCallable()) { + variable.error("literal is not a function"); + } } Call.prototype.children = ['variable', 'args']; @@ -3030,6 +3049,8 @@ IS_STRING = /^['"]/; + IS_REGEX = /^\//; + utility = function(name) { var ref; ref = "__" + name; diff --git a/src/nodes.coffee b/src/nodes.coffee index f6e2919d..fd8bb006 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -468,17 +468,25 @@ exports.Value = class Value extends Base hasProperties: -> !!@properties.length + bareLiteral: (type) -> + not @properties.length and @base instanceof type + # Some boolean checks for the benefit of other nodes. - isArray : -> not @properties.length and @base instanceof Arr + isArray : -> @bareLiteral(Arr) + isRange : -> @bareLiteral(Range) isComplex : -> @hasProperties() or @base.isComplex() isAssignable : -> @hasProperties() or @base.isAssignable() - isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value - isString : -> @base instanceof Literal and IS_STRING.test @base.value + isSimpleNumber : -> @bareLiteral(Literal) and SIMPLENUM.test @base.value + isString : -> @bareLiteral(Literal) and IS_STRING.test @base.value + isRegex : -> @bareLiteral(Literal) and IS_REGEX.test @base.value isAtomic : -> for node in @properties.concat @base return no if node.soak or node instanceof Call yes + isNotCallable : -> @isSimpleNumber() or @isString() or @isRegex() or + @isArray() or @isRange() or @isSplice() or @isObject() + isStatement : (o) -> not @properties.length and @base.isStatement o assigns : (name) -> not @properties.length and @base.assigns name jumps : (o) -> not @properties.length and @base.jumps o @@ -568,6 +576,8 @@ exports.Call = class Call extends Base @isNew = false @isSuper = variable is 'super' @variable = if @isSuper then null else variable + if variable instanceof Value and variable.isNotCallable() + variable.error "literal is not a function" children: ['variable', 'args'] @@ -2160,8 +2170,9 @@ METHOD_DEF = /// $ /// -# Is a literal value a string? +# Is a literal value a string/regex? IS_STRING = /^['"]/ +IS_REGEX = /^\// # Utility Functions # ----------------- diff --git a/test/function_invocation.coffee b/test/function_invocation.coffee index 4f514eab..efda21b2 100644 --- a/test/function_invocation.coffee +++ b/test/function_invocation.coffee @@ -9,6 +9,9 @@ # shared identity function id = (_) -> if arguments.length is 1 then _ else [arguments...] +# helper to assert that a string should fail compilation +cantCompile = (code) -> + throws -> CoffeeScript.compile code test "basic argument passing", -> @@ -649,3 +652,25 @@ test "Loose tokens inside of explicit call lists", -> bar = first( first one: 1) eq bar.one, 1 + +test "Non-callable literals shouldn't compile", -> + cantCompile '1(2)' + cantCompile '1 2' + cantCompile '/t/(2)' + cantCompile '/t/ 2' + cantCompile '///t///(2)' + cantCompile '///t/// 2' + cantCompile "''(2)" + cantCompile "'' 2" + cantCompile '""(2)' + cantCompile '"" 2' + cantCompile '""""""(2)' + cantCompile '"""""" 2' + cantCompile '{}(2)' + cantCompile '{} 2' + cantCompile '[](2)' + cantCompile '[] 2' + cantCompile '[2..9] 2' + cantCompile '[2..9](2)' + cantCompile '[1..10][2..9] 2' + cantCompile '[1..10][2..9](2)' diff --git a/test/soaks.coffee b/test/soaks.coffee index 97130c4d..330a99b2 100644 --- a/test/soaks.coffee +++ b/test/soaks.coffee @@ -130,5 +130,5 @@ test "soaked constructor invocations with caching and property access", -> eq 1, semaphore test "soaked function invocation safe on non-functions", -> - eq undefined, 0?(1) - eq undefined, 0? 1, 2 + eq undefined, (0)?(1) + eq undefined, (0)? 1, 2