Skip to content

Commit 7a586e5

Browse files
petebacondarwinvojtajina
authored andcommitted
fix(*): protect calls to hasOwnProperty in public API
Objects received from outside AngularJS may have had their `hasOwnProperty` method overridden with something else. In cases where we can do this without incurring a performance penalty we call directly on Object.prototype.hasOwnProperty to ensure that we use the correct method. Also, we have some internal hash objects, where the keys for the map are provided from outside AngularJS. In such cases we either prevent `hasOwnProperty` from being used as a key or provide some other way of preventing our objects from having their `hasOwnProperty` overridden. BREAKING CHANGE: Inputs with name equal to "hasOwnProperty" are not allowed inside form or ngForm directives. Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added to the scope. Now a badname exception is thrown. Using "hasOwnProperty" for an input name would be very unusual and bad practice. Either do not include such an input in a `form` or `ngForm` directive or change the name of the input. Closes angular#3331
1 parent fb99f54 commit 7a586e5

File tree

24 files changed

+196
-17
lines changed

24 files changed

+196
-17
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@ngdoc error
2+
@name ng:badname
3+
@fullName Bad `hasOwnProperty` Name
4+
@description
5+
6+
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allow.
7+
Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object
8+
and allowing such a name would break lookups on this object.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@ngdoc error
2+
@name $resource:badname
3+
@fullName Cannot use hasOwnProperty as a parameter name
4+
@description
5+
6+
Occurs when you try to use the name `hasOwnProperty` as a name of a parameter.
7+
Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object
8+
and allowing such a name would break lookups on this object.

src/Angular.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@
22

33
////////////////////////////////////
44

5-
/**
6-
* hasOwnProperty may be overwritten by a property of the same name, or entirely
7-
* absent from an object that does not inherit Object.prototype; this copy is
8-
* used instead
9-
*/
10-
var hasOwnPropertyFn = Object.prototype.hasOwnProperty;
11-
var hasOwnPropertyLocal = function(obj, key) {
12-
return hasOwnPropertyFn.call(obj, key);
13-
};
14-
155
/**
166
* @ngdoc function
177
* @name angular.lowercase
@@ -691,6 +681,8 @@ function shallowCopy(src, dst) {
691681
dst = dst || {};
692682

693683
for(var key in src) {
684+
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
685+
// so we don't need to worry hasOwnProperty here
694686
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
695687
dst[key] = src[key];
696688
}
@@ -1187,6 +1179,17 @@ function assertArgFn(arg, name, acceptArrayAnnotation) {
11871179
return arg;
11881180
}
11891181

1182+
/**
1183+
* throw error if the name given is hasOwnProperty
1184+
* @param {String} name the name to test
1185+
* @param {String} context the context in which the name is used, such as module or directive
1186+
*/
1187+
function assertNotHasOwnProperty(name, context) {
1188+
if (name === 'hasOwnProperty') {
1189+
throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
1190+
}
1191+
}
1192+
11901193
/**
11911194
* Return the value accessible from the object by path. Any undefined traversals are ignored
11921195
* @param {Object} obj starting object

src/auto/injector.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ function createInjector(modulesToLoad) {
455455
}
456456

457457
function provider(name, provider_) {
458+
assertNotHasOwnProperty(name, 'service');
458459
if (isFunction(provider_) || isArray(provider_)) {
459460
provider_ = providerInjector.instantiate(provider_);
460461
}
@@ -475,6 +476,7 @@ function createInjector(modulesToLoad) {
475476
function value(name, value) { return factory(name, valueFn(value)); }
476477

477478
function constant(name, value) {
479+
assertNotHasOwnProperty(name, 'constant');
478480
providerCache[name] = value;
479481
instanceCache[name] = value;
480482
}

src/loader.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
function setupModuleLoader(window) {
1212

13+
var $injectorMinErr = minErr('$injector');
14+
1315
function ensure(obj, name, factory) {
1416
return obj[name] || (obj[name] = factory());
1517
}
@@ -68,12 +70,13 @@ function setupModuleLoader(window) {
6870
* @returns {module} new module with the {@link angular.Module} api.
6971
*/
7072
return function module(name, requires, configFn) {
73+
assertNotHasOwnProperty(name, 'module');
7174
if (requires && modules.hasOwnProperty(name)) {
7275
modules[name] = null;
7376
}
7477
return ensure(modules, name, function() {
7578
if (!requires) {
76-
throw minErr('$injector')('nomod', "Module '{0}' is not available! You either misspelled the module name " +
79+
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled the module name " +
7780
"or forgot to load it. If registering a module ensure that you specify the dependencies as the second " +
7881
"argument.", name);
7982
}

src/ng/compile.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ function $CompileProvider($provide) {
178178
* @returns {ng.$compileProvider} Self for chaining.
179179
*/
180180
this.directive = function registerDirective(name, directiveFactory) {
181+
assertNotHasOwnProperty(name, 'directive');
181182
if (isString(name)) {
182183
assertArg(directiveFactory, 'directiveFactory');
183184
if (!hasDirectives.hasOwnProperty(name)) {
@@ -1175,6 +1176,9 @@ function $CompileProvider($provide) {
11751176
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
11761177
} else if (key == 'style') {
11771178
$element.attr('style', $element.attr('style') + ';' + value);
1179+
// `dst` will never contain hasOwnProperty as DOM parser won't let it.
1180+
// You will get an "InvalidCharacterError: DOM Exception 5" error if you
1181+
// have an attribute like "has-own-property" or "data-has-own-property", etc.
11781182
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
11791183
dst[key] = value;
11801184
dstAttr[key] = srcAttr[key];

src/ng/controller.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function $ControllerProvider() {
2525
* annotations in the array notation).
2626
*/
2727
this.register = function(name, constructor) {
28+
assertNotHasOwnProperty(name, 'controller');
2829
if (isObject(name)) {
2930
extend(controllers, name)
3031
} else {

src/ng/directive/form.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ function FormController(element, attrs) {
7373
* Input elements using ngModelController do this automatically when they are linked.
7474
*/
7575
form.$addControl = function(control) {
76+
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
77+
// and not added to the scope. Now we throw an error.
78+
assertNotHasOwnProperty(control.$name, 'input');
7679
controls.push(control);
7780

78-
if (control.$name && !form.hasOwnProperty(control.$name)) {
81+
if (control.$name) {
7982
form[control.$name] = control;
8083
}
8184
};

src/ng/directive/ngRepeat.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
305305
key = (collection === collectionKeys) ? index : collectionKeys[index];
306306
value = collection[key];
307307
trackById = trackByIdFn(key, value, index);
308+
assertNotHasOwnProperty(trackById, '`track by` id');
308309
if(lastBlockMap.hasOwnProperty(trackById)) {
309310
block = lastBlockMap[trackById]
310311
delete lastBlockMap[trackById];
@@ -327,6 +328,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
327328

328329
// remove existing items
329330
for (key in lastBlockMap) {
331+
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
330332
if (lastBlockMap.hasOwnProperty(key)) {
331333
block = lastBlockMap[key];
332334
$animate.leave(block.elements);

src/ng/directive/select.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
var ngOptionsMinErr = minErr('ngOptions');
34
/**
45
* @ngdoc directive
56
* @name ng.directive:select
@@ -152,6 +153,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
152153

153154

154155
self.addOption = function(value) {
156+
assertNotHasOwnProperty(value, '"option value"');
155157
optionsMap[value] = true;
156158

157159
if (ngModelCtrl.$viewValue == value) {
@@ -300,7 +302,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
300302
var match;
301303

302304
if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
303-
throw minErr('ngOptions')('iexp',
305+
throw ngOptionsMinErr('iexp',
304306
"Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}",
305307
optionsExp, startingTag(selectElement));
306308
}

0 commit comments

Comments
 (0)