Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange JavaScript behavior in CodePen with humongous arrays

Tags:

javascript

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).
like image 248
ShrekOverflow Avatar asked Apr 05 '16 11:04

ShrekOverflow


1 Answers

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.

like image 129
Benjamin Gruenbaum Avatar answered Sep 20 '22 08:09

Benjamin Gruenbaum