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

fix(minErr/ngRepeat/$rootScope): serialization of error objects #10085

Closed
wants to merge 3 commits into from

Conversation

petebacondarwin
Copy link
Contributor

This is my preferred solution to #9838.
toJson should throw errors on cyclic object references. JSON is not really designed to handle this out of the box and we should not be hiding the error from the developer.
Adding a hidden parameter is just making this method convoluted and slowing it down.

Instead we should consider that for error messages, we do not high fidelity serialization of objects but instead a readable string that is useful for debugging. Returning undefined for broken JSON stringification is not helpful.

This fix provides a new function stringify which is available within the angular.js file and also within the angular-loader.js file. This function is then used inside minErr and $rootScope to provide nice string forms of objects.

Places that were using toJson to create error messages (i.e. ngRepeat and $rootScope) no either pass the object directly to minErr or they use the stringify method directly.

This solution ensures that we don't confuse the point of toJson - it is not a generic stringification tool, it is designed for converting to and from valid JSON. The new method stringify also provides us with a single future improvement point if we wished to make the stringification of objects to strings more comprehensive.

@caitp
Copy link
Contributor

caitp commented Nov 17, 2014

The circular reference detection scheme does not look right, you'd need to do a depth-first traversal of object properties and return them as strings to make that work. It's also not really a cheap operation, so we probably don't want to do that during interpolation.

@petebacondarwin
Copy link
Contributor Author

I agree that it is not a real circular reference scheme. It is a basic algorithm that will get us a half decent error message.
Also, I agree that it should not be used in sensitive code. This fix only implements it for error messages.

@caitp
Copy link
Contributor

caitp commented Nov 17, 2014

You're right, $interpolate has its own stringify --- we should rename it to avoid confusion. How about toString()?

@petebacondarwin
Copy link
Contributor Author

rename which one :-) ?

@petebacondarwin
Copy link
Contributor Author

How about this new method is called toDebugString?

@petebacondarwin
Copy link
Contributor Author

Actually the $rootScope usage is in hot code but it can be refactored nicely, which will actually even provide a minor performance enhancement as well.

Fix the JSON stringification to output a more meaningful string when an
object cannot be normally converted to a JSON string, such as when the
object contains cyclic references that would cause `JSON.stringify()`
to throw an error.
Now that `minErr` can cope with objects that cannot be normally stringified
to JSON, just pass the error arguments straight through without trying to
stringify them first.

Closes angular#9838
Closes angular#10065
…or messages

Use the new private function `stringify` to convert scope values to strings,
since this can cope with cyclic references and other oddities.
@petebacondarwin
Copy link
Contributor Author

I fixed up the $rootScope issue and renamed to toDebugString.

@kwypchlo
Copy link
Contributor

I guess it looks fine, I like the idea to separate toJson and toDebugString.
In fact, now that the performance is not of such a big issue (it is just for debugging), I've come up with a decycle function that could be used there (it respects a straight-line reference):

function decycleObject(object, seen) {
  if (isObject(object)) {
    var safeObject = {};

    // make local copy of the reference array to be sure
    // objects are referenced in straight line
    seen = seen ? seen.slice() : [];

    for (var key in object) {
      var property = object[key];

      if (isObject(property)) {
        if (seen.indexOf(property) >= 0) {
          safeObject[key] = '<<already seen>>';
        } else {
          if(seen.indexOf(object) === -1) seen.push(object);
          safeObject[key] = decycleObject(property, seen)
        }
      } else {
        safeObject[key] = property;
      }
    }
    return safeObject;
  }

  return object;
}

You can see diffirence that it produce compared to your function in this plunkr. What do you think?

@kwypchlo
Copy link
Contributor

Actually, I would also change <<already seen>> to << already seen >> because it distrupted the output of plunker when I tried to render it in html - it was treated as html tag.

@petebacondarwin
Copy link
Contributor Author

Thanks @kwypchlo. I appreciate that it feels like I pulled the rug out from under you on this one. Sorry about that but it was as easy to implement it than to describe it.

How about we merge this and then you can provide PRs to make the changes you suggest?

We probably should go with something like [[ already seen ]] to ensure that it definitely doesn't get turned into HTML.

@petebacondarwin
Copy link
Contributor Author

Or we could even just change it to "..." since this is only for error messages

@petebacondarwin
Copy link
Contributor Author

The only other thing about the algorithm is that we don't want to add too many more bytes to the download.

@petebacondarwin
Copy link
Contributor Author

Interestingly this PR actually knocks a little bit off the gzipped angular.min.js file (44.47Kb over 44.49Kb)

@kwypchlo
Copy link
Contributor

@petebacondarwin don't worry about it :) I love angular and it feels good to help out anyhow 👍 Just go ahead with your PR and if you feel that the decycle function is worth the bytes, I can add it in in separate PR with tests. Also I guess "..." would be good enough for error messages.

petebacondarwin added a commit that referenced this pull request Nov 17, 2014
Now that `minErr` can cope with objects that cannot be normally stringified
to JSON, just pass the error arguments straight through without trying to
stringify them first.

Closes #9838
Closes #10065
Closes #10085
petebacondarwin added a commit that referenced this pull request Nov 17, 2014
…or messages

Use the new private function `stringify` to convert scope values to strings,
since this can cope with cyclic references and other oddities.

Closes #10085
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085 this commit adds a function for replacing cycle references in object. It is recursive and it knows if the reference has been made in a straight lin
e (sibling objects will be left but object referencing same object will be replaced).
It also changes the replacement string to '...' because the older one could be mistaken for html tag by browsers.
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085 this commit adds a function for replacing cycle references in object. It is recursive and it knows if the reference has been made in a straight lin
e (sibling objects will be left but object referencing same object will be replaced).
It also changes the replacement string to '...' because the older one could be mistaken for html tag by browsers.
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085 this commit adds a function for replacing cycle references in object. It is recursive and it knows if the reference has been made in a straight lin
e (sibling objects will be left but object referencing same object will be replaced).
It also changes the replacement string to '...' because the older one could be mistaken for html tag by browsers.
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085 this commit adds a function for replacing cycle references in object. It is recursive and it knows if the reference has been made in a straight lin
e (sibling objects will be left but object referencing same object will be replaced).
It also changes the replacement string to '...' because the older one could be mistaken for html tag by browsers.
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085 this commit adds a function for replacing cycle references in object. It is recursive and it knows if the reference has been made in a straight lin
e (sibling objects will be left but object referencing same object will be replaced).
It also changes the replacement string to '...' because the older one could be mistaken for html tag by browsers.
kwypchlo added a commit to kwypchlo/angular.js that referenced this pull request Nov 17, 2014
As discussed in angular#10085, the original replacement string can be treated as html when displayed by the browser so it replaces it with '...' string.
petebacondarwin pushed a commit that referenced this pull request Sep 7, 2015
As discussed in #10085, the original replacement string can be treated
as html when displayed by the browser so it replaces it with '...' string.

Closes #10103
@petebacondarwin petebacondarwin deleted the cyclic-refs branch November 24, 2016 09:20
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants