27

The following code performs a silent logical error:

const arr = [];
class Point{
  constructor(){
    this.x = Math.random() * 1000000;
    this.y = Math.random() * 1000000;
  }
}
console.time('foo');
let avg = 0;

for(let i = 0; i < 114000000; i++ ){
  arr.push(new Point());
  avg += arr[i].x / 1000;
}
console.log(avg, arr.length);

// shouldn't this double the avg ?
for(let i = 0; i < 114000000; i++ ){
  avg += arr[i].x / 1000;
}

console.log(avg, arr.length);
console.timeEnd('foo');

CodePen - http://codepen.io/darkyen/pen/yOPMZg?editors=0010

Possible behaviour(s):

  • The variable avg after the second for loop should be doubled and The length of array should be 114 million.

  • I should get a memory error.

Output when run as a script:

  • avg Does not change after the second for loop.
  • Length of the array is not 114 Mil, (Chrome 2-3M, Firefox Dev 5 Mil, MS Edge 788k).
4
  • A small note: avg not changing is due to the precision of floating point numbers. At some point, the value of avg gets so large that adding a small number to it has no effect, because of the bit size of the mantissa. Commented Apr 5, 2016 at 12:15
  • 1
    may be issue with i<114000000 ? codepen without any arrays Commented Apr 5, 2016 at 12:17
  • @AndersTornblad Math.random() * 1000000 / 1000 should be in half cases more than 500, which is definitely not enough to be swallowed by rounding errors/precision for such a small number as 1261167461.290721 (for my current run) Commented Apr 5, 2016 at 12:20
  • Ah, that is true... If the array stops growing (.push() fails), there will be no arr[i] to fetch the .x property from, which is more likely the reason for avg not growing. If all calls to .push() had succeeded, avg would still be off because of float errors. But I still don't know why avg is unchanged after the second loop. Commented Apr 5, 2016 at 12:25

2 Answers 2

33

When you write code in Codepen - they actually don't execute it as-is but rather first apply some transformations to it.

They parse it into an abstract syntax tree, find loops and insert instructions explicitly to stop executing the loop if too much time has passed.

When you do:

for(let i = 0; i < 114000000; i++ ){
  arr.push(new Point());
  avg += arr[i].x / 1000;
}

Your code runs as:

for (var i = 0; i < 114000000; i++) {
    if (window.CP.shouldStopExecution(1)) { // <- injected by Codepen!!!
        break;
    }
    arr.push(new Point());
    avg += arr[i].x / 1000;
    iter++;
}

You can see this by inspecting the frame code inside CodePen itself.

They inject shouldStopLoop calls inside your code. They have a script called stopExecutionOnTimeout which does something like this (source from Codepen):

 var PenTimer {
   programNoLongerBeingMonitored:false,
   timeOfFirstCallToShouldStopLoop:0, // measure time
   _loopExits:{}, // keep track of leaving loops
   _loopTimers:{}, // time loops
   START_MONITORING_AFTER:2e3, // give the script some time to bootstrap
   STOP_ALL_MONITORING_TIMEOUT:5e3, // don't monitor after some time
   MAX_TIME_IN_LOOP_WO_EXIT:2200, // kill loops over 2200 ms
   exitedLoop:function(o) { // we exited a loop 
     this._loopExits[o] = false; // mark
   },
   shouldStopLoop:function(o) { // the important one, called in loops
      if(this.programKilledSoStopMonitoring)  return false; // already done
      if(this.programNoLongerBeingMonitored)return true;
      if(this._loopExits[o])  return false; 
      var t=this._getTime(); // get current time
      if(this.timeOfFirstCallToShouldStopLoop === false) 
        this.timeOfFirstCallToShouldStopLoop = t;
        return false;
      }
      var i= t - this.timeOfFirstCallToShouldStopLoop; // check time passed
      if(i<this.START_MONITORING_AFTER) return false; // still good   
      if(i>this.STOP_ALL_MONITORING_TIMEOUT){
        this.programNoLongerBeingMonitored = true;
        return false;
      }
      try{
        this._checkOnInfiniteLoop(o,t);
      } catch(n) {
        this._sendErrorMessageToEditor(); // send error about loop
        this.programKilledSoStopMonitoring=false;
        return true; // killed
      }
      return false; // no need
   },
   _sendErrorMessageToEditor:function(){/*... */
      throw "We found an infinite loop in your Pen. We've stopped the Pen from running. Please correct it or contact [email protected].";
};

If you want to run it yourself - JSBin has similar functionality and they have open sourced it as the loop-protect library - under 500 LoC.

Sign up to request clarification or add additional context in comments.

Comments

11

It's just codepen script runner restrictions.

I run script in Chrome Developer Tools and in Node.JS REPL - all seems ok.


Codepen docs

4 Comments

Perfect! This was totally unexpected ... am removing the question as its totally misleading.
Removing question is bad option. It can be helpful for some future readers.
Actually it would be fine to add codepen to the title.
I started editing this answer but it ended up as being too extensive - so I've upvoted this and posted my own answer going into more details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.