Browse Source

Detect infinite script call recursion

Giorgio Premi 9 years ago
parent
commit
fd0026b542

+ 33 - 0
src/Composer/EventDispatcher/EventDispatcher.php

@@ -45,6 +45,7 @@ class EventDispatcher
     protected $loader;
     protected $process;
     protected $listeners;
+    private $eventStack;
 
     /**
      * Constructor.
@@ -58,6 +59,7 @@ class EventDispatcher
         $this->composer = $composer;
         $this->io = $io;
         $this->process = $process ?: new ProcessExecutor($io);
+        $this->eventStack = array();
     }
 
     /**
@@ -145,6 +147,8 @@ class EventDispatcher
     {
         $listeners = $this->getListeners($event);
 
+        $this->pushEvent($event);
+
         $return = 0;
         foreach ($listeners as $callable) {
             if (!is_string($callable) && is_callable($callable)) {
@@ -198,6 +202,8 @@ class EventDispatcher
             }
         }
 
+        $this->popEvent();
+
         return $return;
     }
 
@@ -381,4 +387,31 @@ class EventDispatcher
     {
         return '@' === substr($callable, 0, 1);
     }
+
+    /**
+     * Push an event to the stack of active event
+     *
+     * @param  Event             $event
+     * @throws \RuntimeException
+     * @return number
+     */
+    protected function pushEvent(Event $event)
+    {
+        $eventName = $event->getName();
+        if (in_array($eventName, $this->eventStack)) {
+            throw new \RuntimeException(sprintf("Recursive call to '%s' detected", $eventName));
+        }
+
+        return array_push($this->eventStack, $eventName);
+    }
+
+    /**
+     * Pops the active event from the stack
+     *
+     * @return mixed
+     */
+    protected function popEvent()
+    {
+        return array_pop($this->eventStack);
+    }
 }

+ 32 - 0
tests/Composer/Test/EventDispatcher/EventDispatcherTest.php

@@ -201,6 +201,38 @@ class EventDispatcherTest extends TestCase
         $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
     }
 
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testDispatcherDetectInfiniteRecursion()
+    {
+        $process = $this->getMock('Composer\Util\ProcessExecutor');
+        $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
+        ->setConstructorArgs(array(
+            $composer = $this->getMock('Composer\Composer'),
+            $io = $this->getMock('Composer\IO\IOInterface'),
+            $process,
+        ))
+        ->setMethods(array(
+            'getListeners',
+        ))
+        ->getMock();
+
+        $dispatcher->expects($this->atLeastOnce())
+            ->method('getListeners')
+            ->will($this->returnCallback(function (Event $event) {
+                if ($event->getName() === 'root') {
+                    return array('@recurse');
+                } elseif ($event->getName() === 'recurse') {
+                    return array('@root');
+                }
+
+                return array();
+            }));
+
+        $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
+    }
+
     private function getDispatcherStubForListenersTest($listeners, $io)
     {
         $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')