Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix #1066 #1067

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,23 @@ export function logError(...args: any[]) {
* (There might be a large number of date in `series.data`).
* So date should not be modified in and out of echarts.
*/
export function clone<T extends any>(source: T): T {
function cloneHelper<T extends any>(source: T, alreadyCloned: Map<Object, Object>): T {
if (source == null || typeof source !== 'object') {
return source;
}
if (alreadyCloned.has(source)) {
return alreadyCloned.get(source) as T;
}

let result = source as any;
const typeStr = <string>objToString.call(source);

if (typeStr === '[object Array]') {
if (!isPrimitive(source)) {
result = [] as any;
alreadyCloned.set(source, []);
for (let i = 0, len = (source as any[]).length; i < len; i++) {
result[i] = clone((source as any[])[i]);
result[i] = cloneHelper((source as any[])[i], alreadyCloned);
}
}
}
Expand All @@ -108,17 +112,24 @@ export function clone<T extends any>(source: T): T {
}
else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
result = {} as any;
alreadyCloned.set(source, result);
for (let key in source) {
// Check if key is __proto__ to avoid prototype pollution
if (source.hasOwnProperty(key) && key !== protoKey) {
result[key] = clone(source[key]);
result[key] = cloneHelper(source[key], alreadyCloned);
}
}
}

alreadyCloned.delete(source);
return result;
}

export function clone<T extends any>(source: T): T {
const alreadyCloned = new Map<Object, Object>();
return cloneHelper(source, alreadyCloned);
}

export function merge<
T extends Dictionary<any>,
S extends Dictionary<any>
Expand Down
28 changes: 28 additions & 0 deletions test/ut/spec/core/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,33 @@ describe('zrUtil', function() {

});

it("circular", function () {
class TreeNode {
public children: TreeNode[]
public parent: TreeNode | null

constructor(parent: TreeNode = null, children: TreeNode[] = []) {
this.children = children;
this.parent = parent;
}
}

const root = new TreeNode();
const a = new TreeNode(root, [new TreeNode(), new TreeNode()]);
a.children.forEach(c => c.parent = a);
root.children.push(a)
root.children.push(new TreeNode(root, [new TreeNode()]))
expect(zrUtil.clone(root)).toEqual(root);

const b: { key: any } = {key: null};
b.key = b;
const bCloned = zrUtil.clone(b);
expect(bCloned === b).toBeFalsy();
expect(bCloned.key === b).toBeFalsy();
expect(bCloned === b.key).toBeFalsy();
expect(bCloned.key === b.key).toBeFalsy();
expect(bCloned).toEqual(b);
});

});
});