React: Bug: Infinite iterator in props causes crash in react-dev-tools

Created on 20 Aug 2020  路  12Comments  路  Source: facebook/react

React version: Any
React Developer Tools: 4.8.2

Steps To Reproduce

  1. create and render a Component which contains infinite iterator
function* makeCounter() {
  let c = 0;
  while (true) {
    yield c++;
  }
}
const counter = makeCounter();

const Sample = () => <Component counter={counter} />;
  1. open chrome devtools and select this component with React Dev Tools (Components tab)

  2. crashes after few seconds

Link to code example:

https://codesandbox.io/s/happy-lake-c1vyf?file=/src/App.js

The current behavior

The page crashes due to devtools crash.

The expected behavior

devtools doesn't crash.

This is because ReactDevTools is trying to display elements inside iterator object with transforming it into array here: https://github.com/facebook/react/blob/b6e1d086043a801682ff01b00c7a623d529b46c0/packages/react-devtools-shared/src/utils.js#L606

Iterators could be generally infinite (and also have side effect, like counter in the example). So I think it should not be extracted to array here.

Developer Tools Bug

All 12 comments

The code you described is not the same as in your codesandbox. Your codesandbox is problematic as is since its render function has a side-effect:

export default class Sample extends React.Component {
  render() {
    const counter = makeCounter();
    //              ^^^^^^^^^^^^^ every time render runs (with the same input) the output is different
    return <GeneratorInProps counter={counter} />;
  }
}

Devtools might work under the assumption that render is side-effect free and thus crash.

The code you described works fine: https://codesandbox.io/s/dawn-cdn-gf1m0?file=/src/App.js. So this might just be an issue that class components are treated differently.

The question is if devtools should not handle this and simply crash or guard against render functions with infinite iterators.

Thanks, @eps1lon .

Sorry, side-effect here was my misleading. I had to say that extracting iterator with Array.from to display props in devtools could cause unexpected crash with infinite iterator.

The question is if devtools should not handle this and simply crash or guard against render functions with infinite iterators.

Yes, this is my point. I think there are 3 choices:

  1. crash, no protection (current behavior)
  2. limit the number of extracted items of iterator
  3. don't extract iterator (most protective)

In my case, current behavior was really unexpected because I was simply passing a counter (infinite iterator) to props, and just counting inside a handler in a descendant component. 2 or 3 behavior is helpful for me.

Yeah an infinite iterator is not inherently bad. You could use it safely by calling next() in an effect. The line you mentioned should probably iterate incrementally until some big number and then stop.

Since you already identified the line of code would you mind opening a PR?

Could you check if it's even safe for devtools to iterate over an iterator? wouldn't this change the behavior of the components using since devtools already moved the pointer ahead?

Can I take this one ?

Can I take this one ?

Sure thing. Go ahead.

Could you check if it's even safe for devtools to iterate over an iterator? wouldn't this change the behavior of the components using since devtools already moved the pointer ahead?

Quickly tested it myself and devtools don't seem to interfere with the generator state.

Updated my example: https://codesandbox.io/s/happy-lake-c1vyf?file=/src/App.js

After selecting the component includes infinite iterator, devtools hang up few seconds and crashed.

Aug-22-2020 08-12-47

If I set infinite loop protection true in sandbox.config.json, then loop aborts and codesandbox shows this log:

tryCatch
https://codesandbox.io/static/js/sandbox.f03e10a51.js:1:201658
Generator.invoke [as _invoke]
https://codesandbox.io/static/js/sandbox.f03e10a51.js:1:201658
Generator.prototype.<computed> [as next]
https://codesandbox.io/static/js/sandbox.f03e10a51.js:1:201658
formatDataForPreview
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:839:27
dehydrate
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:1445:100
dehydrate
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:1492:26
cleanForBridge
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:1085:91
Object.inspectElement
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:5376:108
(anonymous function)
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:8559:57
Bridge.emit
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:2349:20
(anonymous function)
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:12126:37
listener
chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:9759:9

I have a fix but it鈥檚 related to what @gaearon says in https://github.com/facebook/react/issues/19726 . I don鈥檛 know if I can publish it?

I have a fix but it鈥檚 related to what @gaearon says in #19726 . I don鈥檛 know if I can publish it?

That issue has already someone working on it. Please be so kind and wait with the PR. If the assigned person stops working on it you can go ahead and propose your change.

Ok I鈥檒l wait @todortotev before I publish my PR.

is the solution to this now available?

function* makeFiniteCounter(n) {
  let c = 0;
  while (c < n) {
    yield c++;
  }
}

if (makeFiniteCounter().toString() === '[object Generator]') {

       do something .............
}

Knowing that it is a generator might allow you to differentiate from an iterable and process accordingly

This should be resolved by #19831 once it lands.

Was this page helpful?
0 / 5 - 0 ratings