Browse Source

When changing watched literals of a rule, update the parent's next pointer

The previous rule was not previously updated to point to the next rule when
removing a middle rule from the watch tree for a literal. This resulted in
jumping from one literal's watch tree to another's, which could then jump
back to the original and cause infinite loop in a case like #265.

Fixes #265
Nils Adermann 13 years ago
parent
commit
1ee5d99405

+ 15 - 4
src/Composer/DependencyResolver/Solver.php

@@ -1222,7 +1222,8 @@ class Solver
                 continue;
             }
 
-            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) {
+            $prevRule = null;
+            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
                 $nextRule = $rule->getNext($literal);
 
                 if ($rule->isDisabled()) {
@@ -1242,13 +1243,23 @@ class Solver
                         if ($otherWatch !== $ruleLiteral->getId() &&
                             !$this->decisionsConflict($ruleLiteral)) {
 
-
                             if ($literal->getId() === $rule->watch1) {
                                 $rule->watch1 = $ruleLiteral->getId();
-                                $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ;
+                                $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
                             } else {
                                 $rule->watch2 = $ruleLiteral->getId();
-                                $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ;
+                                $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
+                            }
+
+                            if ($prevRule) {
+                                if ($prevRule->watch1 === $literal->getId()) {
+                                    $prevRule->next1 = $nextRule;
+                                } else {
+                                    $prevRule->next2 = $nextRule;
+                                }
+                            }
+                            else {
+                                $this->watches[$literal->getId()] = $nextRule;
                             }
 
                             $this->watches[$ruleLiteral->getId()] = $rule;

+ 34 - 0
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -485,6 +485,40 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testIssue265()
+    {
+        $this->repo->addPackage($packageA1 = $this->getPackage('A', '2.0.999999-dev'));
+        $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.1-dev'));
+        $this->repo->addPackage($packageA3 = $this->getPackage('A', '2.2-dev'));
+        $this->repo->addPackage($packageB1 = $this->getPackage('B', '2.0.10'));
+        $this->repo->addPackage($packageB2 = $this->getPackage('B', '2.0.9'));
+        $this->repo->addPackage($packageC = $this->getPackage('C', '2.0-dev'));
+        $this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9'));
+
+        $packageC->setRequires(array(
+            new Link('C', 'A', new VersionConstraint('>=', '2.0'), 'requires'),
+            new Link('C', 'D', new VersionConstraint('>=', '2.0'), 'requires'),
+        ));
+
+        $packageD->setRequires(array(
+            new Link('D', 'A', new VersionConstraint('>=', '2.1'), 'requires'),
+            new Link('D', 'B', new VersionConstraint('>=', '2.0-dev'), 'requires'),
+        ));
+
+        $packageB1->setRequires(array(new Link('B', 'A', new VersionConstraint('==', '2.1.0.0-dev'), 'requires')));
+        $packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('==', '2.1.0.0-dev'), 'requires')));
+
+        $packageB2->setReplaces(array(new Link('B', 'D', new VersionConstraint('==', '2.0.9.0'), 'replaces')));
+
+        $this->reposComplete();
+
+        $this->request->install('C', new VersionConstraint('==', '2.0.0.0-dev'));
+
+        $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
+
+        $this->solver->solve($this->request);
+    }
+
     public function testConflictResultEmpty()
     {
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));