Skip to content

Commit f593ead

Browse files
committed
fix($parse): treat falsy values as defined in assignment expressions
Closes angular#14990 Closes angular#14994
1 parent 1660ddd commit f593ead

File tree

2 files changed

+108
-54
lines changed

2 files changed

+108
-54
lines changed

src/ng/parse.js

+67-52
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@
1313

1414
var $parseMinErr = minErr('$parse');
1515

16+
var ARRAY_CTOR = [].constructor;
17+
var BOOLEAN_CTOR = (false).constructor;
18+
var FUNCTION_CTOR = Function.constructor;
19+
var NUMBER_CTOR = (0).constructor;
20+
var OBJECT_CTOR = {}.constructor;
21+
var STRING_CTOR = ''.constructor;
22+
var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype;
23+
var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype;
24+
var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype;
25+
var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype;
26+
var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype;
27+
var STRING_CTOR_PROTO = STRING_CTOR.prototype;
28+
29+
var CALL = FUNCTION_CTOR_PROTO.call;
30+
var APPLY = FUNCTION_CTOR_PROTO.apply;
31+
var BIND = FUNCTION_CTOR_PROTO.bind;
32+
33+
var objectValueOf = OBJECT_CTOR_PROTO.valueOf;
34+
1635
// Sandboxing Angular Expressions
1736
// ------------------------------
1837
// Angular expressions are generally considered safe because these expressions only have direct
@@ -38,9 +57,9 @@ var $parseMinErr = minErr('$parse');
3857

3958

4059
function ensureSafeMemberName(name, fullExpression) {
41-
if (name === "__defineGetter__" || name === "__defineSetter__"
42-
|| name === "__lookupGetter__" || name === "__lookupSetter__"
43-
|| name === "__proto__") {
60+
if (name === '__defineGetter__' || name === '__defineSetter__'
61+
|| name === '__lookupGetter__' || name === '__lookupSetter__'
62+
|| name === '__proto__') {
4463
throw $parseMinErr('isecfld',
4564
'Attempting to access a disallowed field in Angular expressions! '
4665
+ 'Expression: {0}', fullExpression);
@@ -93,10 +112,6 @@ function ensureSafeObject(obj, fullExpression) {
93112
return obj;
94113
}
95114

96-
var CALL = Function.prototype.call;
97-
var APPLY = Function.prototype.apply;
98-
var BIND = Function.prototype.bind;
99-
100115
function ensureSafeFunction(obj, fullExpression) {
101116
if (obj) {
102117
if (obj.constructor === obj) {
@@ -113,25 +128,18 @@ function ensureSafeFunction(obj, fullExpression) {
113128

114129
function ensureSafeAssignContext(obj, fullExpression) {
115130
if (obj) {
116-
var booleanConstructor = (false).constructor;
117-
var numberConstructor = (0).constructor;
118-
var stringConstructor = ''.constructor;
119-
var objectConstructor = {}.constructor;
120-
var arrayConstructor = [].constructor;
121-
var functionConstructor = Function.constructor;
122-
123-
if (obj === booleanConstructor ||
124-
obj === numberConstructor ||
125-
obj === stringConstructor ||
126-
obj === objectConstructor ||
127-
obj === arrayConstructor ||
128-
obj === functionConstructor ||
129-
obj === booleanConstructor.prototype ||
130-
obj === numberConstructor.prototype ||
131-
obj === stringConstructor.prototype ||
132-
obj === objectConstructor.prototype ||
133-
obj === arrayConstructor.prototype ||
134-
obj === functionConstructor.prototype) {
131+
if (obj === ARRAY_CTOR ||
132+
obj === BOOLEAN_CTOR ||
133+
obj === FUNCTION_CTOR ||
134+
obj === NUMBER_CTOR ||
135+
obj === OBJECT_CTOR ||
136+
obj === STRING_CTOR ||
137+
obj === ARRAY_CTOR_PROTO ||
138+
obj === BOOLEAN_CTOR_PROTO ||
139+
obj === FUNCTION_CTOR_PROTO ||
140+
obj === NUMBER_CTOR_PROTO ||
141+
obj === OBJECT_CTOR_PROTO ||
142+
obj === STRING_CTOR_PROTO) {
135143
throw $parseMinErr('isecaf',
136144
'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
137145
fullExpression);
@@ -141,7 +149,7 @@ function ensureSafeAssignContext(obj, fullExpression) {
141149

142150
var OPERATORS = createMap();
143151
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
144-
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
152+
var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'};
145153

146154

147155
/////////////////////////////////////////
@@ -150,7 +158,7 @@ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'
150158
/**
151159
* @constructor
152160
*/
153-
var Lexer = function(options) {
161+
var Lexer = function Lexer(options) {
154162
this.options = options;
155163
};
156164

@@ -164,7 +172,7 @@ Lexer.prototype = {
164172

165173
while (this.index < this.text.length) {
166174
var ch = this.text.charAt(this.index);
167-
if (ch === '"' || ch === "'") {
175+
if (ch === '"' || ch === '\'') {
168176
this.readString(ch);
169177
} else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
170178
this.readNumber();
@@ -203,7 +211,7 @@ Lexer.prototype = {
203211
},
204212

205213
isNumber: function(ch) {
206-
return ('0' <= ch && ch <= '9') && typeof ch === "string";
214+
return ('0' <= ch && ch <= '9') && typeof ch === 'string';
207215
},
208216

209217
isWhitespace: function(ch) {
@@ -236,9 +244,8 @@ Lexer.prototype = {
236244

237245
codePointAt: function(ch) {
238246
if (ch.length === 1) return ch.charCodeAt(0);
239-
/*jshint bitwise: false*/
247+
// eslint-disable-next-line no-bitwise
240248
return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
241-
/*jshint bitwise: true*/
242249
},
243250

244251
peekMultichar: function() {
@@ -360,7 +367,7 @@ Lexer.prototype = {
360367
}
361368
};
362369

363-
var AST = function(lexer, options) {
370+
var AST = function AST(lexer, options) {
364371
this.lexer = lexer;
365372
this.options = options;
366373
};
@@ -628,7 +635,7 @@ AST.prototype = {
628635
this.consume(':');
629636
property.value = this.expression();
630637
} else {
631-
this.throwError("invalid key", this.peek());
638+
this.throwError('invalid key', this.peek());
632639
}
633640
properties.push(property);
634641
} while (this.expect(','));
@@ -902,7 +909,7 @@ ASTCompiler.prototype = {
902909
this.watchFns() +
903910
'return fn;';
904911

905-
/* jshint -W054 */
912+
// eslint-disable-next-line no-new-func
906913
var fn = (new Function('$filter',
907914
'ensureSafeMemberName',
908915
'ensureSafeObject',
@@ -922,7 +929,6 @@ ASTCompiler.prototype = {
922929
ifDefined,
923930
plusFn,
924931
expression);
925-
/* jshint +W054 */
926932
this.state = this.stage = undefined;
927933
fn.literal = isLiteral(ast);
928934
fn.constant = isConstant(ast);
@@ -1042,7 +1048,7 @@ ASTCompiler.prototype = {
10421048
self.if_(self.stage === 'inputs' || 's', function() {
10431049
if (create && create !== 1) {
10441050
self.if_(
1045-
self.not(self.nonComputedMember('s', ast.name)),
1051+
self.isNull(self.nonComputedMember('s', ast.name)),
10461052
self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
10471053
}
10481054
self.assign(intoId, self.nonComputedMember('s', ast.name));
@@ -1079,7 +1085,7 @@ ASTCompiler.prototype = {
10791085
} else {
10801086
ensureSafeMemberName(ast.property.name);
10811087
if (create && create !== 1) {
1082-
self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
1088+
self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
10831089
}
10841090
expression = self.nonComputedMember(left, ast.property.name);
10851091
if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
@@ -1274,6 +1280,10 @@ ASTCompiler.prototype = {
12741280
return '!(' + expression + ')';
12751281
},
12761282

1283+
isNull: function(expression) {
1284+
return expression + '==null';
1285+
},
1286+
12771287
notNull: function(expression) {
12781288
return expression + '!=null';
12791289
},
@@ -1354,7 +1364,7 @@ ASTCompiler.prototype = {
13541364
},
13551365

13561366
escape: function(value) {
1357-
if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'";
1367+
if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\'';
13581368
if (isNumber(value)) return value.toString();
13591369
if (value === true) return 'true';
13601370
if (value === false) return 'false';
@@ -1433,7 +1443,7 @@ ASTInterpreter.prototype = {
14331443
},
14341444

14351445
recurse: function(ast, context, create) {
1436-
var left, right, self = this, args, expression;
1446+
var left, right, self = this, args;
14371447
if (ast.input) {
14381448
return this.inputs(ast.input, ast.watchId);
14391449
}
@@ -1645,14 +1655,14 @@ ASTInterpreter.prototype = {
16451655
},
16461656
'binary==': function(left, right, context) {
16471657
return function(scope, locals, assign, inputs) {
1648-
/* jshint eqeqeq:false */
1658+
// eslint-disable-next-line eqeqeq
16491659
var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs);
16501660
return context ? {value: arg} : arg;
16511661
};
16521662
},
16531663
'binary!=': function(left, right, context) {
16541664
return function(scope, locals, assign, inputs) {
1655-
/* jshint eqeqeq:false */
1665+
// eslint-disable-next-line eqeqeq
16561666
var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs);
16571667
return context ? {value: arg} : arg;
16581668
};
@@ -1705,7 +1715,7 @@ ASTInterpreter.prototype = {
17051715
identifier: function(name, expensiveChecks, context, create, expression) {
17061716
return function(scope, locals, assign, inputs) {
17071717
var base = locals && (name in locals) ? locals : scope;
1708-
if (create && create !== 1 && base && !(base[name])) {
1718+
if (create && create !== 1 && base && base[name] == null) {
17091719
base[name] = {};
17101720
}
17111721
var value = base ? base[name] : undefined;
@@ -1749,7 +1759,7 @@ ASTInterpreter.prototype = {
17491759
var lhs = left(scope, locals, assign, inputs);
17501760
if (create && create !== 1) {
17511761
ensureSafeAssignContext(lhs);
1752-
if (lhs && !(lhs[right])) {
1762+
if (lhs && lhs[right] == null) {
17531763
lhs[right] = {};
17541764
}
17551765
}
@@ -1775,7 +1785,7 @@ ASTInterpreter.prototype = {
17751785
/**
17761786
* @constructor
17771787
*/
1778-
var Parser = function(lexer, $filter, options) {
1788+
var Parser = function Parser(lexer, $filter, options) {
17791789
this.lexer = lexer;
17801790
this.$filter = $filter;
17811791
this.options = options;
@@ -1796,8 +1806,6 @@ function isPossiblyDangerousMemberName(name) {
17961806
return name === 'constructor';
17971807
}
17981808

1799-
var objectValueOf = Object.prototype.valueOf;
1800-
18011809
function getValueOf(value) {
18021810
return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
18031811
}
@@ -1848,6 +1856,7 @@ function getValueOf(value) {
18481856
/**
18491857
* @ngdoc provider
18501858
* @name $parseProvider
1859+
* @this
18511860
*
18521861
* @description
18531862
* `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
@@ -1882,6 +1891,7 @@ function $ParseProvider() {
18821891
/**
18831892
* @ngdoc method
18841893
* @name $parseProvider#setIdentifierFns
1894+
*
18851895
* @description
18861896
*
18871897
* Allows defining the set of characters that are allowed in Angular expressions. The function
@@ -2023,6 +2033,7 @@ function $ParseProvider() {
20232033
}
20242034

20252035
//Primitive or NaN
2036+
// eslint-disable-next-line no-self-compare
20262037
return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue);
20272038
}
20282039

@@ -2072,10 +2083,12 @@ function $ParseProvider() {
20722083
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
20732084
var unwatch, lastValue;
20742085
if (parsedExpression.inputs) {
2075-
return unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
2086+
unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression);
20762087
} else {
2077-
return unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
2088+
unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality);
20782089
}
2090+
return unwatch;
2091+
20792092
function oneTimeWatch(scope) {
20802093
return parsedExpression(scope);
20812094
}
@@ -2096,7 +2109,7 @@ function $ParseProvider() {
20962109

20972110
function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) {
20982111
var unwatch, lastValue;
2099-
return unwatch = scope.$watch(function oneTimeWatch(scope) {
2112+
unwatch = scope.$watch(function oneTimeWatch(scope) {
21002113
return parsedExpression(scope);
21012114
}, function oneTimeListener(value, old, scope) {
21022115
lastValue = value;
@@ -2110,6 +2123,8 @@ function $ParseProvider() {
21102123
}
21112124
}, objectEquality);
21122125

2126+
return unwatch;
2127+
21132128
function isAllDefined(value) {
21142129
var allDefined = true;
21152130
forEach(value, function(val) {
@@ -2120,11 +2135,11 @@ function $ParseProvider() {
21202135
}
21212136

21222137
function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
2123-
var unwatch;
2124-
return unwatch = scope.$watch(function constantWatch(scope) {
2138+
var unwatch = scope.$watch(function constantWatch(scope) {
21252139
unwatch();
21262140
return parsedExpression(scope);
21272141
}, listener, objectEquality);
2142+
return unwatch;
21282143
}
21292144

21302145
function addInterceptor(parsedExpression, interceptorFn) {

0 commit comments

Comments
 (0)