|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file is part of Composer.
|
|
|
|
*
|
|
|
|
* (c) Nils Adermann <naderman@naderman.de>
|
|
|
|
* Jordi Boggiano <j.boggiano@seld.be>
|
|
|
|
*
|
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Composer\DependencyResolver;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores decisions on installing, removing or keeping packages
|
|
|
|
*
|
|
|
|
* @author Nils Adermann <naderman@naderman.de>
|
|
|
|
* @implements \Iterator<array{0: int, 1: Rule}>
|
|
|
|
*/
|
|
|
|
class Decisions implements \Iterator, \Countable
|
|
|
|
{
|
|
|
|
const DECISION_LITERAL = 0;
|
|
|
|
const DECISION_REASON = 1;
|
|
|
|
|
|
|
|
/** @var Pool */
|
|
|
|
protected $pool;
|
|
|
|
/** @var array<int, int> */
|
|
|
|
protected $decisionMap;
|
|
|
|
/**
|
|
|
|
* @var array<array{0: int, 1: Rule}>
|
|
|
|
*/
|
|
|
|
protected $decisionQueue = array();
|
|
|
|
|
|
|
|
public function __construct(Pool $pool)
|
|
|
|
{
|
|
|
|
$this->pool = $pool;
|
|
|
|
$this->decisionMap = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literal
|
|
|
|
* @param int $level
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function decide(int $literal, int $level, Rule $why): void
|
|
|
|
{
|
|
|
|
$this->addDecision($literal, $level);
|
|
|
|
$this->decisionQueue[] = array(
|
|
|
|
self::DECISION_LITERAL => $literal,
|
|
|
|
self::DECISION_REASON => $why,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literal
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function satisfy(int $literal): bool
|
|
|
|
{
|
|
|
|
$packageId = abs($literal);
|
|
|
|
|
|
|
|
return (
|
|
|
|
$literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
|
|
|
|
$literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literal
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function conflict(int $literal): bool
|
|
|
|
{
|
|
|
|
$packageId = abs($literal);
|
|
|
|
|
|
|
|
return (
|
|
|
|
(isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
|
|
|
|
(isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literalOrPackageId
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function decided(int $literalOrPackageId): bool
|
|
|
|
{
|
|
|
|
return !empty($this->decisionMap[abs($literalOrPackageId)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literalOrPackageId
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function undecided(int $literalOrPackageId): bool
|
|
|
|
{
|
|
|
|
return empty($this->decisionMap[abs($literalOrPackageId)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literalOrPackageId
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function decidedInstall(int $literalOrPackageId): bool
|
|
|
|
{
|
|
|
|
$packageId = abs($literalOrPackageId);
|
|
|
|
|
|
|
|
return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literalOrPackageId
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function decisionLevel(int $literalOrPackageId): int
|
|
|
|
{
|
|
|
|
$packageId = abs($literalOrPackageId);
|
|
|
|
if (isset($this->decisionMap[$packageId])) {
|
|
|
|
return abs($this->decisionMap[$packageId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literalOrPackageId
|
|
|
|
* @return Rule|null
|
|
|
|
*/
|
|
|
|
public function decisionRule(int $literalOrPackageId): ?Rule
|
|
|
|
{
|
|
|
|
$packageId = abs($literalOrPackageId);
|
|
|
|
|
|
|
|
foreach ($this->decisionQueue as $decision) {
|
|
|
|
if ($packageId === abs($decision[self::DECISION_LITERAL])) {
|
|
|
|
return $decision[self::DECISION_REASON];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $queueOffset
|
|
|
|
* @return array{0: int, 1: Rule} a literal and decision reason
|
|
|
|
*/
|
|
|
|
public function atOffset(int $queueOffset): array
|
|
|
|
{
|
|
|
|
return $this->decisionQueue[$queueOffset];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $queueOffset
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function validOffset(int $queueOffset): bool
|
|
|
|
{
|
|
|
|
return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Rule
|
|
|
|
*/
|
|
|
|
public function lastReason(): Rule
|
|
|
|
{
|
|
|
|
return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function lastLiteral(): int
|
|
|
|
{
|
|
|
|
return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function reset(): void
|
|
|
|
{
|
|
|
|
while ($decision = array_pop($this->decisionQueue)) {
|
|
|
|
$this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $offset
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function resetToOffset(int $offset): void
|
|
|
|
{
|
|
|
|
while (\count($this->decisionQueue) > $offset + 1) {
|
|
|
|
$decision = array_pop($this->decisionQueue);
|
|
|
|
$this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function revertLast(): void
|
|
|
|
{
|
|
|
|
$this->decisionMap[abs($this->lastLiteral())] = 0;
|
|
|
|
array_pop($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function count(): int
|
|
|
|
{
|
|
|
|
return \count($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function rewind(): void
|
|
|
|
{
|
|
|
|
end($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array{0: int, 1: Rule}|false
|
|
|
|
*/
|
|
|
|
#[\ReturnTypeWillChange]
|
|
|
|
public function current()
|
|
|
|
{
|
|
|
|
return current($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function key(): ?int
|
|
|
|
{
|
|
|
|
return key($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function next(): void
|
|
|
|
{
|
|
|
|
prev($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function valid(): bool
|
|
|
|
{
|
|
|
|
return false !== current($this->decisionQueue);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isEmpty(): bool
|
|
|
|
{
|
|
|
|
return \count($this->decisionQueue) === 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $literal
|
|
|
|
* @param int $level
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected function addDecision(int $literal, int $level): void
|
|
|
|
{
|
|
|
|
$packageId = abs($literal);
|
|
|
|
|
|
|
|
$previousDecision = $this->decisionMap[$packageId] ?? null;
|
|
|
|
if ($previousDecision != 0) {
|
|
|
|
$literalString = $this->pool->literalToPrettyString($literal, array());
|
|
|
|
$package = $this->pool->literalToPackage($literal);
|
|
|
|
throw new SolverBugException(
|
|
|
|
"Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($literal > 0) {
|
|
|
|
$this->decisionMap[$packageId] = $level;
|
|
|
|
} else {
|
|
|
|
$this->decisionMap[$packageId] = -$level;
|
|
|
|
}
|
|
|
|
}
|
Fix solver problem exceptions with unexpected contradictory "Conclusions"
This 5 character fix comes with a solver test as well as a functional
installer test essentially verifying the same thing. The solver test is
more useful when working on the solver. But the functional test is less
likely to be accidentally modified incorrectly during refactoring, as
every single package, version and link in the rather complex test
scenario is essential, and a modified version of the test may very well
still result in a successful installation but no longer verify the bug
described below.
Background:
In commit 451bab1c2cd58e05af6e21639b829408ad023463 from May 19, 2012 I
refactored literals from complex objects into pure integers to reduce
memory consumption. The absolute value of an integer literal is the id
of the package it refers to in the package pool. The sign indicates
whether the package should be installed (positive) or removed (negative),
So a major part of the refactoring was swapping this call:
$literal->getPackageId()
For this:
abs($literal)
Unintentionally in line 554/523 I incorrectly applied this change to the
line:
$this->literalFromId(-$literal->getPackageId());
It was converted to:
-abs($literal);
The function literalFromId used to create a new literal object. By using
the abs() function this change essentially forces the resulting literal
to be negative, while the minus sign previously inverted the literal, so
positive into negative and vice versa.
This particular line is in a function meant to analyze a conflicting
decision during dependency resolution and to draw a conclusion from it,
then revert the state of the solver to an earlier position, and attempt
to solve the rest of the rules again with this new "learned" conclusion.
Because of this bug these conclusions could only ever occur in the
negative, e.g. "don't install package X". This is by far the most likely
scenario when the solver reaches this particular line, but there are
exceptions.
If you experienced a solver problem description that contained a
statement like "Conclusion: don't install vendor/package 1.2.3" which
directly contradicted other statements listed as part of the problem,
this could likely have been the cause.
6 years ago
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function toString(Pool $pool = null): string
|
Fix solver problem exceptions with unexpected contradictory "Conclusions"
This 5 character fix comes with a solver test as well as a functional
installer test essentially verifying the same thing. The solver test is
more useful when working on the solver. But the functional test is less
likely to be accidentally modified incorrectly during refactoring, as
every single package, version and link in the rather complex test
scenario is essential, and a modified version of the test may very well
still result in a successful installation but no longer verify the bug
described below.
Background:
In commit 451bab1c2cd58e05af6e21639b829408ad023463 from May 19, 2012 I
refactored literals from complex objects into pure integers to reduce
memory consumption. The absolute value of an integer literal is the id
of the package it refers to in the package pool. The sign indicates
whether the package should be installed (positive) or removed (negative),
So a major part of the refactoring was swapping this call:
$literal->getPackageId()
For this:
abs($literal)
Unintentionally in line 554/523 I incorrectly applied this change to the
line:
$this->literalFromId(-$literal->getPackageId());
It was converted to:
-abs($literal);
The function literalFromId used to create a new literal object. By using
the abs() function this change essentially forces the resulting literal
to be negative, while the minus sign previously inverted the literal, so
positive into negative and vice versa.
This particular line is in a function meant to analyze a conflicting
decision during dependency resolution and to draw a conclusion from it,
then revert the state of the solver to an earlier position, and attempt
to solve the rest of the rules again with this new "learned" conclusion.
Because of this bug these conclusions could only ever occur in the
negative, e.g. "don't install package X". This is by far the most likely
scenario when the solver reaches this particular line, but there are
exceptions.
If you experienced a solver problem description that contained a
statement like "Conclusion: don't install vendor/package 1.2.3" which
directly contradicted other statements listed as part of the problem,
this could likely have been the cause.
6 years ago
|
|
|
{
|
|
|
|
$decisionMap = $this->decisionMap;
|
|
|
|
ksort($decisionMap);
|
|
|
|
$str = '[';
|
|
|
|
foreach ($decisionMap as $packageId => $level) {
|
|
|
|
$str .= (($pool) ? $pool->literalToPackage($packageId) : $packageId).':'.$level.',';
|
Fix solver problem exceptions with unexpected contradictory "Conclusions"
This 5 character fix comes with a solver test as well as a functional
installer test essentially verifying the same thing. The solver test is
more useful when working on the solver. But the functional test is less
likely to be accidentally modified incorrectly during refactoring, as
every single package, version and link in the rather complex test
scenario is essential, and a modified version of the test may very well
still result in a successful installation but no longer verify the bug
described below.
Background:
In commit 451bab1c2cd58e05af6e21639b829408ad023463 from May 19, 2012 I
refactored literals from complex objects into pure integers to reduce
memory consumption. The absolute value of an integer literal is the id
of the package it refers to in the package pool. The sign indicates
whether the package should be installed (positive) or removed (negative),
So a major part of the refactoring was swapping this call:
$literal->getPackageId()
For this:
abs($literal)
Unintentionally in line 554/523 I incorrectly applied this change to the
line:
$this->literalFromId(-$literal->getPackageId());
It was converted to:
-abs($literal);
The function literalFromId used to create a new literal object. By using
the abs() function this change essentially forces the resulting literal
to be negative, while the minus sign previously inverted the literal, so
positive into negative and vice versa.
This particular line is in a function meant to analyze a conflicting
decision during dependency resolution and to draw a conclusion from it,
then revert the state of the solver to an earlier position, and attempt
to solve the rest of the rules again with this new "learned" conclusion.
Because of this bug these conclusions could only ever occur in the
negative, e.g. "don't install package X". This is by far the most likely
scenario when the solver reaches this particular line, but there are
exceptions.
If you experienced a solver problem description that contained a
statement like "Conclusion: don't install vendor/package 1.2.3" which
directly contradicted other statements listed as part of the problem,
this could likely have been the cause.
6 years ago
|
|
|
}
|
|
|
|
$str .= ']';
|
|
|
|
|
Fix solver problem exceptions with unexpected contradictory "Conclusions"
This 5 character fix comes with a solver test as well as a functional
installer test essentially verifying the same thing. The solver test is
more useful when working on the solver. But the functional test is less
likely to be accidentally modified incorrectly during refactoring, as
every single package, version and link in the rather complex test
scenario is essential, and a modified version of the test may very well
still result in a successful installation but no longer verify the bug
described below.
Background:
In commit 451bab1c2cd58e05af6e21639b829408ad023463 from May 19, 2012 I
refactored literals from complex objects into pure integers to reduce
memory consumption. The absolute value of an integer literal is the id
of the package it refers to in the package pool. The sign indicates
whether the package should be installed (positive) or removed (negative),
So a major part of the refactoring was swapping this call:
$literal->getPackageId()
For this:
abs($literal)
Unintentionally in line 554/523 I incorrectly applied this change to the
line:
$this->literalFromId(-$literal->getPackageId());
It was converted to:
-abs($literal);
The function literalFromId used to create a new literal object. By using
the abs() function this change essentially forces the resulting literal
to be negative, while the minus sign previously inverted the literal, so
positive into negative and vice versa.
This particular line is in a function meant to analyze a conflicting
decision during dependency resolution and to draw a conclusion from it,
then revert the state of the solver to an earlier position, and attempt
to solve the rest of the rules again with this new "learned" conclusion.
Because of this bug these conclusions could only ever occur in the
negative, e.g. "don't install package X". This is by far the most likely
scenario when the solver reaches this particular line, but there are
exceptions.
If you experienced a solver problem description that contained a
statement like "Conclusion: don't install vendor/package 1.2.3" which
directly contradicted other statements listed as part of the problem,
this could likely have been the cause.
6 years ago
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __toString()
|
|
|
|
{
|
|
|
|
return $this->toString();
|
|
|
|
}
|
|
|
|
}
|