Angular.js: TypeError: Cannot read property 'nodeType' of undefined

Created on 6 Feb 2016  Â·  47Comments  Â·  Source: angular/angular.js

I recently caught this exception in a user's browser:

TypeError: Cannot read property 'nodeType' of undefined
  at apply(bower_components/raven-js/dist/raven.js:267:28)
  at apply(bower_components/angular/angular.js:6006:6)
  at completeOutstandingRequest(bower_components/angular/angular.js:5729:9)
  at apply(bower_components/angular/angular.js:18141:34)
  at $apply(src/extras/fixes.js:22:24)
  at call(bower_components/angular/angular.js:16359:22)
  at $digest(src/extras/fixes.js:12:23)
  at call(bower_components/angular/angular.js:16069:29)
  at $eval(bower_components/angular/angular.js:16251:22)
  at expr(bower_components/angular/angular.js:15007:26)
  at processQueue(bower_components/angular/angular.js:14991:27)
  at fn(bower_components/angular/angular.js:8741:37)
  at compileNodes(bower_components/angular/angular.js:7864:14)
  at compileNodes(bower_components/angular/angular.js:7852:14)
  at applyDirectivesToNode(bower_components/angular/angular.js:8227:32)
  at compile(bower_components/angular/angular.js:7751:14)
  at compileNodes(bower_components/angular/angular.js:7864:14)
  at compileNodes(bower_components/angular/angular.js:7848:21)
  at collectDirectives(bower_components/angular/angular.js:7968:21)

This is against Angular 1.4.9 in Chrome 50 on OS X 10.11.3. It happened ~8s into the session, so probably still while setting up the initial page. It's the first time I've seen this exception in over a year of running my app so it's likely an exceedingly rare race condition, or some recent regression in Angular. I don't know how to reproduce it so I'm filing this issue mostly so that others can find it and add more information if they should run into the same error.

chrome $compile

Most helpful comment

No other useful information popped up, so I opened an issue against Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=640003

All 47 comments

This is not actionable I'm afraid. Without a reproduction, without any context/code and with a very generic error message that could be related to a ton of stuff :disappointed:

I'm going to close it, but people will be still able to find it and add more info if they run into this.
Once/if there are enough details, we can reopen this and investigate further.

Thx for reporting though !

This has started happening more often as of late. The stack traces are still similar, though some are triggered by document.onready rather than user events. Would it be possible to instrument compileNodes to throw an exception with a bit more information (e.g., which node is being compiled, what directive?) if nodeList[i] is undefined? I'm at a loss how to debug this issue otherwise.

I'm seeing the same issue - somehow collectDirectives is being called with an undefined node. I guess a band-aid fix would be to just have a guard clause to do something like return directives if !node, but it's not clear to me why how the code is getting into this state.

This error is popping up more and more often in my app, though so far only in Chrome v50+. Have you observed it in other versions / browsers yet?

We've seen it with Chrome 50 and IE 11

I don't know about IE 11, but I'm pretty sure this is some sort of regression triggered by Chrome v50. I haven't seen the exception with any previous versions and I've started seeing a lot more occurrences since v50 was pushed to the stable branch:
image

I tried scanning through the commits introduced in v50 but nothing jumped out at me as a potential cause. @gkalpak, given that this is happening multiple times a day now, and will only get worse as more people update their Chrome installs, any thoughts on how to instrument the code to get more info and maybe figure out a repro?

Hi, all. We also have this error on the project after updating Chrome to 50 version.
This error caused on different nodes randomly and it occurs very often.
The problem is that DOM node has been already compiled before compiler reach it again.
Maybe Chrome runs JS in parallel threads, but it's weird I think.
Angular 1.2.9, Windows 7

I wonder if it's related to the Chrome GC bug identified in https://github.com/angular/angular.js/pull/14286? Perhaps the GC fix for that (in Chrome v50) broke something else... I'm still seeing the error in v51, v52, and v53, so it doesn't look like a fix from Chrome's side is in the pipeline.

I found that wrapping the line that throws in a try-catch block actually prevented the exception from occurring in the first place. (Or at least, I couldn't then reproduce the exception with 100Ks of iterations).
But of course putting that in is only a plaster and actually degrades performance of Angular - as the JS compiler is no longer able to optimise so heavily, which is probably the cause of this bug in the first place.

Got a possibly related exception today in nearby code, during bootstrap:

TypeError: Cannot read property 'childNodes' of undefined
  at childLinkFn(bower_components/angular/angular.js:8513:36)
  at compositeLinkFn(bower_components/angular/angular.js:8513:12)
  at apply(bower_components/angular/angular.js:8390:29)
  at transcludeFn(bower_components/angular/angular.js:8728:24)
  at boundTranscludeFn(bower_components/angular/angular.js:8527:15)
  at $transclude(bower_components/angular/angular.js:9265:19)
  at listener(bower_components/angular/angular.js:29607:14)
  at fn(bower_components/angular/angular.js:17145:12)
  at this(bower_components/angular/angular.js:17286:22)
  at $digest(src/extras/fixes.js:12:28)
  at fn(bower_components/angular/angular.js:17552:22)
  at $apply(src/extras/fixes.js:22:35)
  at apply(bower_components/angular/angular.js:1754:13)
  at invoke(bower_components/angular/angular.js:4709:18)
  at doBootstrap(bower_components/angular/angular.js:1752:13)
  at bootstrap(bower_components/angular/angular.js:1772:11)
  at apply(src/reviewable.js:6:10)
  at fire(bower_components/jquery/dist/jquery.js:3094:29)
  at resolveWith(bower_components/jquery/dist/jquery.js:3206:6)
  at ready(bower_components/jquery/dist/jquery.js:3412:12)
  at ready(bower_components/jquery/dist/jquery.js:3428:8)

I wonder if this is related(/similar) to https://bugs.chromium.org/p/chromium/issues/detail?id=604033.

So a fix for this bug landed two weeks ago but I can't find out to which branch or version of Chromium

Still seeing this on Chrome 51 on OS X 10.11.0.

+1 still happening occasionally on Chrome 51, and started happening a few months ago. screen shot 2016-07-26 at 3 51 24 pm

@pkaminski Please re-open this issue.

@bcherny I wish I could reopen, but that's up to the Angular team. :disappointed:

Pinging @petebacondarwin

+1 still happening occasionally on Chrome 51

The fix for https://bugs.chromium.org/p/chromium/issues/detail?id=604033 has never been backported to Chrome 51 but the comments on the issue indicate it should be fixed in Chrome 52. So please check on that version.

I can confirm that I've seen this issue in Chrome 52 and 53, though the most recent occurrence was a month ago and I don't have the precise build number so I guess it's possible a fix has been introduced since then. Since Chrome 52 was released to stable a week ago I would've expected to have seen more recent crashes in that version by now if it was still broken, but given how rarely the problem occurs it's hard to be sure. I'll keep an eye on the stats for another week or two.

@pkaminski Thanks, please follow up once you have more info. The commit landed after the Chrome 50 release so the Canary version at that time was most likely Chrome 52. So old Chrome 52 pre-release versions could still have the bug (no Chrome 53 build should have the issue, though so that might indicate the problem is still there...). We need stats from Chrome 52.0.2743.82 or newer.

EDIT: corrected the versions as I miscalculated.

I just caught a repro in Chrome 52.0.2743.

Can you provide the repo?

I just caught a repro in Chrome 52.0.2743.

That's not the full version, it's missing the fourth number which is the most important one here. :)

Sorry, I was in a hurry and wasn't clear. I meant that my error tracker
just recorded this exception occurring in a customer's browser, with the
stack trace pointing to the same function as originally reported above, and
the user agent string indicating Chrome 52.0.2743. I don't think the full
user agent with the fine release number was captured but I'll double-check
when I get home.

On Jul 28, 2016 11:39 AM, "Martin Staffa" [email protected] wrote:

Can you provide the repo?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular.js/issues/13960#issuecomment-235985694,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABkhMKFc1c2cinqJ30CH8d8gU-psFECEks5qaPdpgaJpZM4HU0oY
.

I see. Once a version gets into a beta (and later stable) phase, only the fourth number gets bumped. So without it there is no way to know if the version recorded is a stable one or a beta.

Ah, sorry -- I did find the full user agent string buried deeper in the report. The exception happened in Chrome 52.0.2743.82 (specifically Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36).

Thanks. It's definitely happening in Chrome stable then...

Given that the bug is still present in the current Chrome release, should this issue be reopened?

Tbh, since this is a browser issue, I am not sure what we can do about it (especially considering there is no reliable reproduction) :confused:

I would be happy to reopen if someone could provide a reliable reproduction we can validate workarounds aganst (until this is fixed in Chrome).

Just my 2c. If we're waiting for a reliable repro then this is never going to go anywhere. We have thousands of users on Chrome and this triggers a few times a week. So most likely it's an optimization that chrome is making and somehow nodes are still referenced by angular. I don't think it's at all possible to get a reliable repro for this.

@Jaco-Pretorius Since this is a browser bug and it's non-deterministic, it's hard for us to do anything; certainly harder than for the Chrome team to find the issue & fix it. Someone that has this problem should report it to Chromium, preferably logging as much stuff as possible (like the objects that have nodeType suddenly missing) etc. If you do so, please post a link here so that others can follow.

A lot of these cases that seem non-deterministic are actually deterministic, though. Often it's one optimisation that needs to happen; Safari had similar bugs (when object shape was changing when a function referencing it was JIT-ted) and a repro was basically declaring a simple function doing the thing that was hitting the bug and running it 1000 times so that the optimisation kicks in. It's not always easy but often possible.

We do know we're hitting the issue from reports of many people like you; it's just not that simple to do anything about it.

I think there's a pretty simple workaround, though, that would dispose of the vast majority of reported errors: check whether the node still exists at the top of collectDirectives.

    function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
      if (!node) return directives;
      // rest as before

Assuming that Chrome doesn't GC a node while it's still in the DOM, and the bug is just that it gets GC'd while there's still JS references to it, then this should also be safe and "correct": a node no longer in the DOM can be considered to have no directives on it. The workaround is also simple enough that it's very unlikely to have side-effects, so it could be added to the release and validated statistically even without a clean repro.

Yes, it would be a hack, but given the low likelihood of a quick fix in Chrome IMHO the priority should be to make Angular work as well as possible under the circumstances.

@pkaminski I am not sure if this workaround would actually cause the application to operate incorrectly?

Another suggested fix is to replace the length property on the JQLite object with setter and getter properties. But this might impact performance in general?

@petebacondarwin Admittedly, it's hard to tell for sure. Another idea might be to check explicitly for nodeList[i] being undefined in compileNodes and throw a custom error with more information instead (e.g., node index, apparent list length, actual list length, other nodes in the list, maybe the parent node if available, etc.). It's possible a pattern would emerge that would allow us to make a better stab at a repro.

@pkaminski These are only guesses, though. Could you apply such modifications to the code on the site(s) where you see the error and if you find one workaround that works, post it here? Then at least we'd know we're adding a workaround that works for someone.

And that all doesn't change the fact that I still think you should report it to Chrome and post the link to the issue here. They won't know something is wrong if no one tells them.

I agree the bug should be reported to Chrome but I don't understand the internals of Angular well enough to make a precise report. (For example, I had no idea the nodeList was jqLite-wrapped.) I can provide the range of Chrome versions where the issue was observed if somebody would like to collaborate on a writeup.

My build system doesn't make it easy to deploy a non-standard version of Angular to production but looks like I'll have to bite the bullet and make it happen anyway. I'll follow up if/when I have more information.

I got a first hit on the error with extra instrumentation. @petebacondarwin's guess was right: nodeList.length is changing during the loop -- in this case it went from 15 down to 4. With the extra details I was able to track down the disappearing elements with high probability to a directive that moves its own element when linked (mentio-menu from jeff-collins/ment.io):

            link: function (scope, element) {
                element[0].parentNode.removeChild(element[0]);
                $document[0].body.appendChild(element[0]);

The part that doesn't jive is that, AFAICT, compileNodes doesn't actually execute the link functions, and the code doesn't fail deterministically. Any idea what might be going on? Is there anything going on inside compileNodes that might be mutating the DOM? Are directives supposed to be able to move their own element out at link time, anyway?

Theoretically, none of this is possible, so I am not even try to find a logical explanation :smiley:

The fact that the failure in non-deterministic means that it is either a very rare race-condition or a browser bug (related to some optimization that isn't always in effect).

If I understand correctly from previous comments, the issue happens inside compileNodes and is related to nodeList[i] being undefined (even though i < nodeList.length). Iirc, the nodeList passed to compileNodes can be one of 3 types:

  1. An array (e.g. when we can multi-slot transclusion).
  2. A jqLite/jQuery collection (e.g. when passing a collection to $compile).
  3. A NodeList object (e.g. as returned by someNode.childList). This is the most common scenario for calls made from within Angular.

So, the problem of "nodeList[i] === undefined while i < nodeList.length" could be caused (among several things) by:

  1. Someone removing elements from an array or jqLite collection, without updating the length property, or replacing them with undefined. (But why would anyone do such a horrible thing :stuck_out_tongue: )
  2. A browser bug that (under certain circumstances) allows a NodeList's length property to be out-of-sync with the list's actual contents. (Normally, NodeList objects are _live_, meaning that if someone removes or inserts children from/to the corresponding node, the NodeList will automatically update. But a bug could change things.)
    I am totally speculating here though :smiley:

It would definitely help if we had a consistent reproduction.

A couple of questions:

  • What version of Angular have you seen this error with? In what line is the error thrown?
    (Is the stack trace from your original comment still valid?)
  • Are you able to reproduce this locally?
  • What does the template where this error occurs look like (if you happen to know)?

I can clarify at least a few things above:

  • The nodeList in question is an actual NodeList ({type: 'NodeList'} in my code below).
  • Its length decreases part way through the loop, but the loop continues past the new length. My instrumented code (see below) returns errors with values such as {originalLength: 15, currentLength: 4, index: 4}.
  • The error has occurred in Angular versions ranging from 1.4.9 through 1.5.8, but only in Chrome v50+ (all platforms). The error does not occur in Angular 1.4.9 prior to Chrome v50 or in other browsers. I appended a more recent stack trace from Angular 1.5.8 (non-instrumented) below, but it's equivalent to the one in the original comment.
  • I cannot reproduce it locally and, so far, it doesn't seem specific to a single template. (I now suspect that the self-moving directives I mentioned yesterday are a red herring.)

My current hypothesis is that there's an optimization in Chrome v50+ that (sometimes?) caches the nodeList.length in the loop's control expression, so the loop ends up overrunning the list when elements are removed in the middle of the loop. More dangerously, it's also likely that it ends up underrunning with no reported errors but with skipped elements if elements are added in the middle of the loop.

Two questions:

  1. Is it expected that a NodeList-typed nodeList might change while the loop is running, or is the code within the loop supposed to leave the DOM alone?
  2. If it's expected, should Angular make a defensive copy of the list up front? Otherwise, even without the length bug, you could end up in a situation where elements are skipped because previously processed elements have been removed part way through the loop.
    function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
                            previousCompileContext) {
      var linkFns = [],
          attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;

      var originalLength = nodeList.length;
      for (var i = 0; i < nodeList.length; i++) {
        if (!nodeList[i]) {
          var prevNode = i >= 1 && nodeList[i-1];
          var nodeListContents = [];
          for (var j = 0; j < nodeList.length; j++) {
            nodeListContents.push(!!nodeList[j] ? '1' : '0');
          }
          var e = new Error('Internal Angular error: missing node');
          e.extra = {
            type: nodeList.constructor.name, index: i, currentLength: nodeList.length,
            originalLength: originalLength, context: nodeList.context,
            nodeList: nodeListContents,
            prevNode: prevNode && prevNode.toString(),
            prevNodeTag: prevNode && prevNode.tagName,
            prevNodeClass: prevNode && prevNode.className,
            rootElement: $rootElement && $rootElement.toString(),
            rootElementTag: $rootElement && $rootElement.tagName,
            rootElementClass: $rootElement && $rootElement.className
          };
          throw e;
        }
TypeError: Cannot read property 'nodeType' of undefined
  at collectDirectives(bower_components/angular/angular.js:8669:26)
  at compileNodes(bower_components/angular/angular.js:8539:21)
  at compileNodes(bower_components/angular/angular.js:8555:14)
  at compile(bower_components/angular/angular.js:8442:14)
  at transcludeFn(bower_components/angular/angular.js:8838:21)
  at boundTranscludeFn(bower_components/angular/angular.js:8637:15)
  at $transclude(bower_components/angular/angular.js:9385:19)
  at apply(bower_components/angular/angular.js:26400:14)
  at fn(bower_components/angular/angular.js:15988:19)
  at this(bower_components/angular/angular.js:17524:22)
  at $digest(src/extras/fixes.js:12:28)
  at fn(bower_components/angular/angular.js:17790:22)
  at $apply(src/extras/fixes.js:22:35)
  at apply(bower_components/angular/angular.js:1761:13)
  at invoke(bower_components/angular/angular.js:4718:18)
  at doBootstrap(bower_components/angular/angular.js:1759:13)
  at bootstrap(bower_components/angular/angular.js:1779:11)
  at apply(src/reviewable.js:6:10)
  at fire(bower_components/jquery/dist/jquery.js:3094:29)
  at resolveWith(bower_components/jquery/dist/jquery.js:3206:6)
  at ready(bower_components/jquery/dist/jquery.js:3412:12)
  at ready(bower_components/jquery/dist/jquery.js:3428:8)

Is it expected that a NodeList-typed nodeList might change while the loop is running, or is the code within the loop supposed to leave the DOM alone?

Yes, this can happen when using *-start *-end, but this must only be done by $compile and not by the directives. #9198 is an example of things that can go wrong if the directive decides to remove elements, and the view was that this is wrong and should not be done at all (at least in the specific way that was done in that issue).

If it's expected, should Angular make a defensive copy of the list up front? Otherwise, even without the length bug, you could end up in a situation where elements are skipped because previously processed elements have been removed part way through the loop.

I do not want to go thru the internals of $compile, but there is some constraint that is kept while going thru this loop. That said, it is not possible to make a copy as *-start *-end depend the fact that nodeList can change (while keeping the previously referred constrain).

Is it expected that a NodeList-typed nodeList might change while the loop is running, or is the code within the loop supposed to leave the DOM alone?

In addition to what @lgalfaso said, it can also happen if you are on MSIE 11 and are compiling consequtive text nodes (don't ask :stuck_out_tongue:).
That said, I don't think it is too bad to add stuff to the NodeList during compile as long as you add it _after_ the currenctly compiling node.

@pkaminski , from your findings, I think it is obvious that this is a bug, since we enter the loop (meaning that the condition i < nodeList.length holds) and without doing any operation on nodeList we have i === nodeList.length.

I would be curious to see the contents of that list :smile:

Any idea what other directives are in play on the elements in the original list? (E.g. are there transclusion directives, multi-element directives, custom directives that do stuff in their compile functions)
What does prevNode look like?

Thanks for the additional clarifications. Yes, I agree there's clearly a bug in Chrome since the loop's condition is violated, but I'm really curious what's causing nodes to disappear from the list -- is it something in Angular / my app, or is it part of the same Chrome bug.

So far I have 3 instances of the bug captured and have been able to identify the template for 2 of them. The templates are completely unrelated and neither parent element has any transclusion, multi-element directives, or directives with compile functions (other than whatever's built into Angular, of course). In both cases it appears likely that the tail of the node list suddenly disappeared -- 5 elements and associated whitespace text nodes in one case, and just the final whitespace text node in the other. The prevNode matches the i'th child node in the template, anyway, and there are no holes in the list.

Based on what you said above I'm leaning towards Chrome mangling the node list. I'm going to give it a few more days to collect more events but, if no pattern becomes apparent, I'll go ahead and file a Chromium bug with the data I have so far. If you have any ideas what else to capture that might help please let me know!

It sounds possible that Chrome is doing some optimization eliminating empty/whitespace-only text nodes (but failing to update the NodeList's length correctly or in time. Who knows. It would be interesting to compare the innerHTML or the NodeList's parent node at the beginning and at the end of the loop (but it might be just too much to ask for :smiley:).

No other useful information popped up, so I opened an issue against Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=640003

This really needs a repro. If it is indeed a Chrome optimization bug (which it may well be), then a stress test for the function in question should be able to hit it reliably. Can someone who's seeing this try to build a test case that runs the function that sometimes fails 10,000 times?

Hi folks,

Just FYI: we have an app built with Angular, and this error appears randomly (quite often tho) in our tests ... the problem is that we don't have a clue about how to reproduce @jakobkummerow, that would be great... Any hints / tips about how we can do that? Currently we have almost 700 tests running against a real Google Chrome (v55) via Karma.

Tests that randomly fail look like this:

it('should have the right class', () => {
  const markup = '<gc-button gc-size="md">Button</gc-button>';
  const component = $compile(angular.element(markup))($scope);

  $scope.$digest();

  expect(component.children().hasClass('gc-button-md')).to.be.true;
});

Output:

TypeError: Cannot read property 'nodeType' of undefined
          at collectDirectives (/opt/atlassian/pipelines/agent/build/karma.context.js:114440:26)
          at compileNodes (/opt/atlassian/pipelines/agent/build/karma.context.js:114310:22)
          at compileNodes (/opt/atlassian/pipelines/agent/build/karma.context.js:114326:15)
          at compile (/opt/atlassian/pipelines/agent/build/karma.context.js:114213:15)
          at Context.<anonymous> (/opt/atlassian/pipelines/agent/build/karma.context.js:153304:25)

The curious thing is that the error seems to happen only on certain environment, i.e. our "Bitbucket Pipelines". There, we run our test script on each commit so we can validate the content of the Pull Requests. It not happens when we run the same tests in local environments (we use Mac), neither in our CI environment (we use Jenkins, it's an Ubuntu 14.04).

😕

_Edit_

Forgot to mention that we have a lot of those tests... hope it can be helpful.

Thanks!

Unfortunately I don't have any magic reproducing powers.

What I can say is that the issue discussed here has been fixed as of Chrome M54. That was a year ago! Chrome 55 is also really old at this point, you definitely should update that (the current version is 62, so that's what you want to test with). In fact, wild guess, maybe what's different about the various environments is that your "Buildbucket Pipelines" use an even older version?

@jakobkummerow Thanks for the additional info! I didn't know that the issue was fixed... And yes, we'll update to the latest version, maybe it's just simple as that at this point.

_Edit_

Bitbucket pipelines Chrome version was v53.0.2785 :feelsgood:

Was this page helpful?
0 / 5 - 0 ratings