Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 083f496

Browse files
committed
fix(angular.copy): support circular references in the value being copied
Closes #7618
1 parent a87135b commit 083f496

File tree

4 files changed

+45
-7
lines changed

4 files changed

+45
-7
lines changed

src/Angular.js

+29-5
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ function isLeafNode (node) {
757757
</file>
758758
</example>
759759
*/
760-
function copy(source, destination) {
760+
function copy(source, destination, stackSource, stackDest) {
761761
if (isWindow(source) || isScope(source)) {
762762
throw ngMinErr('cpws',
763763
"Can't copy! Making copies of Window or Scope instances is not supported.");
@@ -767,33 +767,57 @@ function copy(source, destination) {
767767
destination = source;
768768
if (source) {
769769
if (isArray(source)) {
770-
destination = copy(source, []);
770+
destination = copy(source, [], stackSource, stackDest);
771771
} else if (isDate(source)) {
772772
destination = new Date(source.getTime());
773773
} else if (isRegExp(source)) {
774774
destination = new RegExp(source.source);
775775
} else if (isObject(source)) {
776-
destination = copy(source, {});
776+
destination = copy(source, {}, stackSource, stackDest);
777777
}
778778
}
779779
} else {
780780
if (source === destination) throw ngMinErr('cpi',
781781
"Can't copy! Source and destination are identical.");
782+
783+
stackSource = stackSource || [];
784+
stackDest = stackDest || [];
785+
786+
if (isObject(source)) {
787+
var index = indexOf(stackSource, source);
788+
if (index !== -1) return stackDest[index];
789+
790+
stackSource.push(source);
791+
stackDest.push(destination);
792+
}
793+
794+
var result;
782795
if (isArray(source)) {
783796
destination.length = 0;
784797
for ( var i = 0; i < source.length; i++) {
785-
destination.push(copy(source[i]));
798+
result = copy(source[i], null, stackSource, stackDest);
799+
if (isObject(source[i])) {
800+
stackSource.push(source[i]);
801+
stackDest.push(result);
802+
}
803+
destination.push(result);
786804
}
787805
} else {
788806
var h = destination.$$hashKey;
789807
forEach(destination, function(value, key) {
790808
delete destination[key];
791809
});
792810
for ( var key in source) {
793-
destination[key] = copy(source[key]);
811+
result = copy(source[key], null, stackSource, stackDest);
812+
if (isObject(source[key])) {
813+
stackSource.push(source[key]);
814+
stackDest.push(result);
815+
}
816+
destination[key] = result;
794817
}
795818
setHashKey(destination,h);
796819
}
820+
797821
}
798822
return destination;
799823
}

src/ng/parse.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ function $ParseProvider() {
10721072
self.$$postDigestQueue.push(function () {
10731073
// create a copy if the value is defined and it is not a $sce value
10741074
if ((stable = isDefined(lastValue)) && !lastValue.$$unwrapTrustedValue) {
1075-
lastValue = copy(lastValue);
1075+
lastValue = copy(lastValue, null);
10761076
}
10771077
});
10781078
}

src/ng/rootScope.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ function $RootScopeProvider(){
702702
&& isNaN(value) && isNaN(last)))) {
703703
dirty = true;
704704
lastDirtyWatch = watch;
705-
watch.last = watch.eq ? copy(value) : value;
705+
watch.last = watch.eq ? copy(value, null) : value;
706706
watch.fn(value, ((last === initWatchVal) ? value : last), current);
707707
if (ttl < 5) {
708708
logIdx = 4 - ttl;

test/AngularSpec.js

+14
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,20 @@ describe('angular', function() {
146146
// make sure we retain the old key
147147
expect(hashKey(dst)).toEqual(h);
148148
});
149+
150+
it('should handle circular references when circularRefs is turned on', function () {
151+
var a = {b: {a: null}, self: null, selfs: [null, null, [null]]};
152+
a.b.a = a;
153+
a.self = a;
154+
a.selfs = [a, a.b, [a]];
155+
156+
var aCopy = copy(a, null);
157+
expect(aCopy).toEqual(a);
158+
159+
expect(aCopy).not.toBe(a);
160+
expect(aCopy).toBe(aCopy.self);
161+
expect(aCopy.selfs[2]).not.toBe(a.selfs[2]);
162+
});
149163
});
150164

151165
describe("extend", function() {

0 commit comments

Comments
 (0)