Skip to content

Commit 10694e9

Browse files
committed
fix(*): do not rely on an object's hasOwnProperty
Closes angular#1944
1 parent 5fd39e0 commit 10694e9

13 files changed

+49
-25
lines changed

src/Angular.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

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

5+
/**
6+
* hasOwnProperty may be overriden 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 hasOwn = Object.prototype.hasOwnProperty;
11+
512
/**
613
* @ngdoc function
714
* @name angular.lowercase
@@ -139,7 +146,7 @@ function forEach(obj, iterator, context) {
139146
if (obj) {
140147
if (isFunction(obj)){
141148
for (key in obj) {
142-
if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
149+
if (key != 'prototype' && key != 'length' && key != 'name' && hasOwn.call(obj, key)) {
143150
iterator.call(context, obj[key], key);
144151
}
145152
}
@@ -150,7 +157,7 @@ function forEach(obj, iterator, context) {
150157
iterator.call(context, obj[key], key);
151158
} else {
152159
for (key in obj) {
153-
if (obj.hasOwnProperty(key)) {
160+
if (hasOwn.call(obj, key)) {
154161
iterator.call(context, obj[key], key);
155162
}
156163
}
@@ -162,7 +169,7 @@ function forEach(obj, iterator, context) {
162169
function sortedKeys(obj) {
163170
var keys = [];
164171
for (var key in obj) {
165-
if (obj.hasOwnProperty(key)) {
172+
if (hasOwn.call(obj, key)) {
166173
keys.push(key);
167174
}
168175
}
@@ -508,7 +515,7 @@ function size(obj, ownPropsOnly) {
508515
return obj.length;
509516
} else if (isObject(obj)){
510517
for (key in obj)
511-
if (!ownPropsOnly || obj.hasOwnProperty(key))
518+
if (!ownPropsOnly || hasOwn.call(obj, key))
512519
size++;
513520
}
514521

@@ -609,7 +616,7 @@ function shallowCopy(src, dst) {
609616
dst = dst || {};
610617

611618
for(var key in src) {
612-
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
619+
if (hasOwn.call(src, key) && key.substr(0, 2) !== '$$') {
613620
dst[key] = src[key];
614621
}
615622
}

src/angular-bootstrap.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@
191191

192192
if (IGNORE[prop] || prop.match(/^moz[A-Z]/)) { //skip special variables which keep on changing
193193
continue;
194-
} else if (!globalVars.hasOwnProperty(varKey)) {
194+
} else if (!hasOwn.call(globalVars, varKey)) {
195195
//console.log('new global variable found: ', prop);
196196
try {
197197
globalVars[varKey] = window[prop];

src/auto/injector.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ function createInjector(modulesToLoad) {
529529
if (typeof serviceName !== 'string') {
530530
throw Error('Service name expected');
531531
}
532-
if (cache.hasOwnProperty(serviceName)) {
532+
if (hasOwn.call(cache, serviceName)) {
533533
if (cache[serviceName] === INSTANTIATING) {
534534
throw Error('Circular dependency: ' + path.join(' <- '));
535535
}
@@ -554,7 +554,7 @@ function createInjector(modulesToLoad) {
554554
for(i = 0, length = $inject.length; i < length; i++) {
555555
key = $inject[i];
556556
args.push(
557-
locals && locals.hasOwnProperty(key)
557+
locals && hasOwn.call(locals, key)
558558
? locals[key]
559559
: getService(key)
560560
);

src/loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function setupModuleLoader(window) {
6565
* @returns {module} new module with the {@link angular.Module} api.
6666
*/
6767
return function module(name, requires, configFn) {
68-
if (requires && modules.hasOwnProperty(name)) {
68+
if (requires && hasOwn.call(modules, name)) {
6969
modules[name] = null;
7070
}
7171
return ensure(modules, name, function() {

src/ng/compile.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ function $CompileProvider($provide) {
177177
this.directive = function registerDirective(name, directiveFactory) {
178178
if (isString(name)) {
179179
assertArg(directiveFactory, 'directive');
180-
if (!hasDirectives.hasOwnProperty(name)) {
180+
if (!hasOwn.call(hasDirectives, name)) {
181181
hasDirectives[name] = [];
182182
$provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
183183
function($injector, $exceptionHandler) {
@@ -917,7 +917,7 @@ function $CompileProvider($provide) {
917917
*/
918918
function addDirective(tDirectives, name, location, maxPriority) {
919919
var match = false;
920-
if (hasDirectives.hasOwnProperty(name)) {
920+
if (hasOwn.call(hasDirectives, name)) {
921921
for(var directive, directives = $injector.get(name + Suffix),
922922
i = 0, ii = directives.length; i<ii; i++) {
923923
try {
@@ -964,7 +964,7 @@ function $CompileProvider($provide) {
964964
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
965965
} else if (key == 'style') {
966966
$element.attr('style', $element.attr('style') + ';' + value);
967-
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
967+
} else if (key.charAt(0) != '$' && !hasOwn.call(dst, key)) {
968968
dst[key] = value;
969969
dstAttr[key] = srcAttr[key];
970970
}

src/ng/controller.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function $ControllerProvider() {
5959
return function(constructor, locals) {
6060
if(isString(constructor)) {
6161
var name = constructor;
62-
constructor = controllers.hasOwnProperty(name)
62+
constructor = hasOwn.call(controllers, name)
6363
? controllers[name]
6464
: getter(locals.$scope, name, true) || getter($window, name, true);
6565

src/ng/directive/form.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function FormController(element, attrs) {
6565
form.$addControl = function(control) {
6666
controls.push(control);
6767

68-
if (control.$name && !form.hasOwnProperty(control.$name)) {
68+
if (control.$name && !hasOwn.call(form, control.$name)) {
6969
form[control.$name] = control;
7070
}
7171
};

src/ng/directive/ngRepeat.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ var ngRepeatDirective = ngDirective({
108108
// if object, extract keys, sort them and use to determine order of iteration over obj props
109109
array = [];
110110
for(key in collection) {
111-
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
111+
if (hasOwn.call(collection, key) && key.charAt(0) != '$') {
112112
array.push(key);
113113
}
114114
}
@@ -172,7 +172,7 @@ var ngRepeatDirective = ngDirective({
172172

173173
//shrink children
174174
for (key in lastOrder) {
175-
if (lastOrder.hasOwnProperty(key)) {
175+
if (hasOwn.call(lastOrder, key)) {
176176
array = lastOrder[key];
177177
while(array.length) {
178178
value = array.pop();

src/ng/directive/select.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
177177

178178

179179
self.hasOption = function(value) {
180-
return optionsMap.hasOwnProperty(value);
180+
return hasOwn.call(optionsMap, value);
181181
}
182182

183183
$scope.$on('$destroy', function() {

src/ng/parse.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function lex(text, csp){
200200
text:ident
201201
};
202202

203-
if (OPERATORS.hasOwnProperty(ident)) {
203+
if (hasOwn.call(OPERATORS, ident)) {
204204
token.fn = token.json = OPERATORS[ident];
205205
} else {
206206
var getter = getterFn(ident, csp);
@@ -732,7 +732,7 @@ var getterFnCache = {};
732732
*/
733733
function cspSafeGetterFn(key0, key1, key2, key3, key4) {
734734
return function(scope, locals) {
735-
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
735+
var pathVal = (locals && hasOwn.call(locals, key0)) ? locals : scope,
736736
promise;
737737

738738
if (pathVal === null || pathVal === undefined) return pathVal;
@@ -795,7 +795,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4) {
795795
};
796796

797797
function getterFn(path, csp) {
798-
if (getterFnCache.hasOwnProperty(path)) {
798+
if (hasOwn.call(getterFnCache, path)) {
799799
return getterFnCache[path];
800800
}
801801

@@ -827,7 +827,7 @@ function getterFn(path, csp) {
827827
// we simply dereference 's' on any .dot notation
828828
? 's'
829829
// but if we are first then we check locals first, and if so read it first
830-
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
830+
: '((k&&hasOwn.call(k,"' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
831831
'if (s && s.then) {\n' +
832832
' if (!("$$v" in s)) {\n' +
833833
' p=s;\n' +
@@ -892,7 +892,7 @@ function $ParseProvider() {
892892
return function(exp) {
893893
switch(typeof exp) {
894894
case 'string':
895-
return cache.hasOwnProperty(exp)
895+
return hasOwn.call(cache, exp)
896896
? cache[exp]
897897
: cache[exp] = parser(exp, false, $filter, $sniffer.csp);
898898
case 'function':

src/ng/q.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,11 @@ function qFactory(nextTick, exceptionHandler) {
391391
forEach(promises, function(promise, key) {
392392
counter++;
393393
ref(promise).then(function(value) {
394-
if (results.hasOwnProperty(key)) return;
394+
if (hasOwn.call(results, key)) return;
395395
results[key] = value;
396396
if (!(--counter)) deferred.resolve(results);
397397
}, function(reason) {
398-
if (results.hasOwnProperty(key)) return;
398+
if (hasOwn.call(results, key)) return;
399399
deferred.reject(reason);
400400
});
401401
});

src/ngResource/resource.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ angular.module('ngResource', ['ng']).
337337

338338
params = params || {};
339339
forEach(self.urlParams, function(_, urlParam){
340-
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
340+
val = Object.prototype.hasOwnProperty.call(params, urlParam) ? params[urlParam] : self.defaults[urlParam];
341341
if (angular.isDefined(val) && val !== null) {
342342
encodedVal = encodeUriSegment(val);
343343
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");

test/AngularSpec.js

+17
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ describe('angular', function() {
231231
expect(size(obj, true)).toBe(3);
232232
});
233233

234+
it('when counting own properties should work well with objects that define "hasOwnProperty"', function() {
235+
expect(size({hasOwnProperty: 1, foo: 2}, true)).toBe(2);
236+
});
237+
234238
it('should return the string length', function() {
235239
expect(size('')).toBe(0);
236240
expect(size('abc')).toBe(3);
@@ -357,6 +361,19 @@ describe('angular', function() {
357361
forEach(obj, function(value, key) { log.push(key + ':' + value)});
358362
expect(log).toEqual(['length:2', 'foo:bar']);
359363
});
364+
365+
it('should handle objects that define a "hasOwnProperty" key', function() {
366+
var obj = {
367+
foo: 1,
368+
hasOwnProperty: 2
369+
},
370+
log = [];
371+
372+
forEach(obj, function(value, key) {
373+
log.push(key + ':' + value);
374+
});
375+
expect(log).toEqual(['foo:1', 'hasOwnProperty:2']);
376+
});
360377
});
361378

362379

0 commit comments

Comments
 (0)