-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Fix(filter): Filter objects with circular references #6319
Fix(filter): Filter objects with circular references #6319
Conversation
Thanks for the PR! Please check the items below to help us merge this faster. See the contributing docs for more information.
If you need to make changes to your pull request, you can update the commit with Thanks again for your help! |
|
||
var items = [originalItem]; | ||
expect(function() { filter(items, 'not misko') }) | ||
.not.toThrow(new Error("Maximum call stack size exceeded")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is probably not right, better to just use not.toThrow()
, to avoid issues with different VM implementations.
It should also verify that the output looks as expected
So, I'm all for avoiding descending into circular references, but it seems that this change will only avoid throwing or infinite looping when the circular reference is exactly one level deeper than the original object, which is a fairly narrow selection of circular references. This could be avoided by pushing to a stack, and testing if a property references any of the values within the stack. Also, this check should probably only be performed when the value is not a primitive or null. |
@@ -166,7 +166,8 @@ function filterFilter() { | |||
return comparator(obj, text); | |||
default: | |||
for ( var objKey in obj) { | |||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { | |||
if (obj[objKey] === orig) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will skip other properties in the object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points. I'll go at this again :)
…ences from re-evaluating
… can have properties
Thanks, @caitp! I think I covered the right bases now; I'm only performing the circular ref check on things capable of having properties (objects, arrays, functions), and keeping these in the |
if (objKey.charAt(0) !== '$' && search(value, text)) { return true; } | ||
} | ||
} else { | ||
if (objKey.charAt(0) !== '$' && search(value, text)) { return true; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can simplify this block a bit:
for (var objKey in obj) {
if (objKey.charAt(0) !== '$') {
var value = obj[objKey];
// Will avoid adding `null` to the collection (typeof null === "object")
if (isObject(value) || isFunction(value)) {
// If it's a circular reference, just proceed to the next property
if (evaluatedObjects.indexOf(value) >= 0) continue;
evaluatedObjects.push(value);
}
// No need to duplicate this call in two branches
if (search(value, text)) {
return true;
}
}
}
So I was talking about this with Igor, and a concern he's having is that because indexOf is O(N), this could majorly impede performance when people use large collections. With Angular 2.0, we can get this down to O(1) using WeakMap, but we're going to have to wait to check this in until we start the 1.3 branch, so that we can easily revert it if it causes too many problems |
@caitp that makes sense :) I'll push up the simplified block and let me know where this should go from there. Appreciate your consideration on this! |
} | ||
if (search(value, text)) { | ||
return true; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
free up references with evaluatedObjects.length = 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nevermind, that won't do anything. ignore :)
alright, so I still have an issue with the log(n) of the algorithm used. instead of indexOf, I propose that we use |
@IgorMinar I recall we opted not to support this (I think it was mentioned in one of the other issues regarding circular references), am I mis-remembering? |
@brettshollenberger I was discussing this with Igor today, and I think the conclusion was that the right thing to do is write a custom filter for this. The main issue is that it's not clear how common of a use-case it is to have data structures being iterated over which have circular references to begin with, and because it's not clear how common it is, it would be bad to penalize likely more common cases with guards against circular references. So, I would suggest putting together a solution and publishing it on bower or npm as a module, so that other developers could benefit from your hard work. Sorry it's taken so long to get here, and the PR is appreciated regardless. I'm sure people might appreciate having this as a third party module instead. |
Overview of the issue - When filtering confronts a circular reference, it causes an infinite recursion & subsequent stack overflow.
Motivation for or Use Case - As modeling libraries evolve for Angular (I've spent quite a bit of time on one myself https://github.com/FacultyCreative/ngActiveResource), circular references on related objects are likely to become more frequent. For instance, you have a post that has many comments:
Currently, the
search
method calls itself on each attribute on each object in the collection being filtered. Sincepost
references this particularcomment
, and thecomment
references back, thesearch
method will never end.Angular Version(s) - 1.2.13 (current) and before
Browsers and Operating System - This is a problem for all browsers
Reproduce the error - Test included, plus see this Fiddle: http://jsfiddle.net/S2UY6/9/
Related issues - I do not see the same issue reported, though users seem to have particular difficulty with circular references in the DI system and
angular.extend
.Suggest a Fix - Fix included