From bc5d2085849ee7bca23a63515c31d26020c0d8bf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 10 Aug 2018 10:45:13 +0200 Subject: [PATCH] [Cache] make PhpMarshaller handle hard references --- .../Cache/Adapter/PhpArrayAdapter.php | 18 -- .../Cache/Marshaller/PhpMarshaller.php | 159 +++---------- .../Marshaller/PhpMarshaller/Configurator.php | 13 +- .../Marshaller/PhpMarshaller/Marshaller.php | 212 ++++++++++++++++++ .../Marshaller/PhpMarshaller/Reference.php | 9 +- .../Marshaller/PhpMarshaller/Registry.php | 71 +++++- .../Cache/Marshaller/PhpMarshaller/Values.php | 34 +++ .../Component/Cache/Simple/PhpArrayCache.php | 16 -- .../Marshaller/Fixtures/array-iterator.php | 9 +- .../array-object-custom.optimized.php | 28 --- .../Fixtures/array-object-custom.php | 9 +- .../Fixtures/array-object.optimized.php | 36 --- .../Marshaller/Fixtures/array-object.php | 17 +- .../Marshaller/Fixtures/clone.optimized.php | 20 -- .../Cache/Tests/Marshaller/Fixtures/clone.php | 15 +- .../Fixtures/datetime.optimized.php | 30 --- .../Tests/Marshaller/Fixtures/datetime.php | 9 +- .../Fixtures/hard-references-recursive.php | 22 ++ ...able.optimized.php => hard-references.php} | 15 +- ...tor.optimized.php => incomplete-class.php} | 23 +- .../Marshaller/Fixtures/private.optimized.php | 39 ---- .../Tests/Marshaller/Fixtures/private.php | 15 +- .../Marshaller/Fixtures/serializable.php | 15 +- .../Fixtures/spl-object-storage.optimized.php | 27 --- .../Fixtures/spl-object-storage.php | 13 +- .../Marshaller/Fixtures/wakeup.optimized.php | 30 --- .../Tests/Marshaller/Fixtures/wakeup.php | 13 +- .../Tests/Marshaller/PhpMarshallerTest.php | 108 +++++++-- .../Component/Cache/Traits/PhpArrayTrait.php | 24 +- .../Component/Cache/Traits/PhpFilesTrait.php | 28 +-- 30 files changed, 536 insertions(+), 541 deletions(-) create mode 100644 src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Marshaller.php create mode 100644 src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Values.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.optimized.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.optimized.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.optimized.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.optimized.php create mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references-recursive.php rename src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/{serializable.optimized.php => hard-references.php} (59%) rename src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/{array-iterator.optimized.php => incomplete-class.php} (55%) delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.optimized.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.optimized.php delete mode 100644 src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.optimized.php diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index 8fedc0422cb4e..8bd135d84d085 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -16,7 +16,6 @@ use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; -use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\GetTrait; @@ -35,7 +34,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte use GetTrait; private $createCacheItem; - private $marshaller; /** * @param string $file The PHP file were values are cached @@ -106,9 +104,6 @@ public function get(string $key, callable $callback, float $beta = null) if ($value instanceof \Closure) { return $value(); } - if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value); - } } catch (\Throwable $e) { unset($this->keys[$key]); goto get_from_pool; @@ -144,13 +139,6 @@ public function getItem($key) $value = null; $isHit = false; } - } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - try { - $value = unserialize($value); - } catch (\Throwable $e) { - $value = null; - $isHit = false; - } } $f = $this->createCacheItem; @@ -284,12 +272,6 @@ private function generateItems(array $keys): \Generator } catch (\Throwable $e) { yield $key => $f($key, null, false); } - } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - try { - yield $key => $f($key, $this->unserializeValue($value), true); - } catch (\Throwable $e) { - yield $key => $f($key, null, false); - } } else { yield $key => $f($key, $value, true); } diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller.php index e4dd9a2c66deb..a3d43ac945a7c 100644 --- a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller.php +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Cache\Marshaller; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator; +use Symfony\Component\Cache\Marshaller\PhpMarshaller\Marshaller; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry; +use Symfony\Component\Cache\Marshaller\PhpMarshaller\Values; /** * @author Nicolas Grekas @@ -27,14 +29,31 @@ */ class PhpMarshaller { - public static function marshall($value, int &$objectsCount) + public static function marshall($value, bool &$isStaticValue = null): string { - if (!\is_object($value) && !\is_array($value)) { - return $value; + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) { + return var_export($value, true); } + $objectsPool = new \SplObjectStorage(); - $value = array($value); - $objectsCount = self::doMarshall($value, $objectsPool); + $refsPool = array(); + $objectsCount = 0; + + try { + $value = Marshaller::marshall(array($value), $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = array(); + foreach ($refsPool as $i => $v) { + $v[0] = $v[1]; + $references[1 + $i] = $v[2]; + } + } + + if ($isStaticValue) { + return var_export($value, true); + } $classes = array(); $values = array(); @@ -46,6 +65,7 @@ public static function marshall($value, int &$objectsCount) } } ksort($wakeups); + $properties = array(); foreach ($values as $i => $vars) { foreach ($vars as $class => $values) { @@ -54,131 +74,14 @@ public static function marshall($value, int &$objectsCount) } } } - if (!$classes) { - return $value[0]; - } - - return new Configurator(new Registry($classes), $properties, $value[0], $wakeups); - } - - public static function optimize(string $exportedValue) - { - return preg_replace(sprintf("{%s::__set_state\(array\(\s++'0' => (\d+),\s++\)\)}", preg_quote(Reference::class)), Registry::class.'::$objects[$1]', $exportedValue); - } - - private static function doMarshall(array &$array, \SplObjectStorage $objectsPool): int - { - $objectsCount = 0; - - foreach ($array as &$value) { - if (\is_array($value) && $value) { - $objectsCount += self::doMarshall($value, $objectsPool); - } - if (!\is_object($value)) { - continue; - } - if (isset($objectsPool[$value])) { - ++$objectsCount; - $value = new Reference($objectsPool[$value][0]); - continue; - } - $class = \get_class($value); - $properties = array(); - $sleep = null; - $arrayValue = (array) $value; - $proto = (Registry::$reflectors[$class] ?? Registry::getClassReflector($class))->newInstanceWithoutConstructor(); - if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) { - // ArrayIterator and ArrayObject need special care because their "flags" - // option changes the behavior of the (array) casting operator. - $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; - $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + $value = new Configurator($classes ? new Registry($classes) : null, $references ? new Values($references) : null, $properties, $value, $wakeups); + $value = var_export($value, true); - $properties = array( - $arrayValue, - $reflector->getMethod('getFlags')->invoke($value), - $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', - ); - - $reflector = $reflector->getMethod('setFlags'); - $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); - - if ($properties[1] & \ArrayObject::STD_PROP_LIST) { - $reflector->invoke($value, 0); - $properties[0] = (array) $value; - } else { - $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); - $arrayValue = (array) $value; - } - $reflector->invoke($value, $properties[1]); - - if (array(array(), 0, 'ArrayIterator') === $properties) { - $properties = array(); - } else { - if ('ArrayIterator' === $properties[2]) { - unset($properties[2]); - } - $properties = array($reflector->class => array("\0" => $properties)); - } - } elseif ($value instanceof \SplObjectStorage) { - foreach (clone $value as $v) { - $properties[] = $v; - $properties[] = $value[$v]; - } - $properties = array('SplObjectStorage' => array("\0" => $properties)); - } elseif ($value instanceof \Serializable) { - ++$objectsCount; - $objectsPool[$value] = array($id = \count($objectsPool), serialize($value), array(), 0); - $value = new Reference($id); - continue; - } - - if (\method_exists($class, '__sleep')) { - if (!\is_array($sleep = $value->__sleep())) { - trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE); - $value = null; - continue; - } - $sleep = array_flip($sleep); - } - - $proto = (array) $proto; - - foreach ($arrayValue as $name => $v) { - $k = (string) $name; - if ('' === $k || "\0" !== $k[0]) { - $c = $class; - } elseif ('*' === $k[1]) { - $c = $class; - $k = substr($k, 3); - } else { - $i = strpos($k, "\0", 2); - $c = substr($k, 1, $i - 1); - $k = substr($k, 1 + $i); - } - if (null === $sleep) { - $properties[$c][$k] = $v; - } elseif (isset($sleep[$k]) && $c === $class) { - $properties[$c][$k] = $v; - unset($sleep[$k]); - } - if (\array_key_exists($name, $proto) && $proto[$name] === $v) { - unset($properties[$c][$k]); - } - } - if ($sleep) { - foreach ($sleep as $k => $v) { - trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $k), E_USER_NOTICE); - } - } - - $objectsPool[$value] = array($id = \count($objectsPool)); - $objectsCount += 1 + self::doMarshall($properties, $objectsPool); - $objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0); - - $value = new Reference($id); - } + $regexp = sprintf("{%s::__set_state\(array\(\s++'id' => %%s(\d+),\s++\)\)}", preg_quote(Reference::class)); + $value = preg_replace(sprintf($regexp, ''), Registry::class.'::$objects[$1]', $value); + $value = preg_replace(sprintf($regexp, '-'), '&'.Registry::class.'::$references[$1]', $value); - return $objectsCount; + return $value; } } diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Configurator.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Configurator.php index 49658f42b6509..c2398b14095e3 100644 --- a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Configurator.php +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Configurator.php @@ -20,19 +20,20 @@ class Configurator { public static $configurators = array(); - public function __construct(Registry $registry, array $properties, $value, array $wakeups) + public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups) { $this->{0} = $registry; - $this->{1} = $properties; - $this->{2} = $value; - $this->{3} = $wakeups; + $this->{1} = $values; + $this->{2} = $properties; + $this->{3} = $value; + $this->{4} = $wakeups; } public static function __set_state($state) { $objects = Registry::$objects; - Registry::$objects = \array_pop(Registry::$stack); - list(, $properties, $value, $wakeups) = $state; + list(Registry::$objects, Registry::$references) = \array_pop(Registry::$stack); + list(, , $properties, $value, $wakeups) = $state; foreach ($properties as $class => $vars) { (self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects); diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Marshaller.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Marshaller.php new file mode 100644 index 0000000000000..3fe24baf9438f --- /dev/null +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Marshaller.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller\PhpMarshaller; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Marshaller +{ + /** + * Prepares an array of values for PhpMarshaller. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @return int + * + * @throws \Exception When a value cannot be serialized + * + * @internal + */ + public static function marshall($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new \Exception(sprintf("Serialization of '%s' resource is not allowed", \get_resource_type($value))); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + continue; + } + $refsPool[] = array(&$refs[$k], $value, &$value); + $refs[$k] = $values[$k] = new Reference(-\count($refsPool)); + } + + if (\is_array($value)) { + if ($value) { + $value = self::marshall($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = \get_class($value); + $properties = array(); + $sleep = null; + $arrayValue = (array) $value; + + if (!isset(Registry::$prototypes[$class])) { + // Might throw Exception("Serialization of '...' is not allowed") + Registry::getClassReflector($class); + serialize(Registry::$prototypes[$class]); + } + $proto = Registry::$prototypes[$class]; + + if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + $proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : Registry::$reflectors[$class]->newInstanceWithoutConstructor(); + $properties = self::getArrayObjectProperties($value, $arrayValue, $proto); + } elseif ($value instanceof \SplObjectStorage) { + // By implementing Serializable, SplObjectStorage breaks internal references, + // let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = array('SplObjectStorage' => array("\0" => $properties)); + } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) { + ++$objectsCount; + $objectsPool[$value] = array($id = \count($objectsPool), serialize($value), array(), 0); + $value = new Reference($id); + goto handle_value; + } + + if (\method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = $class; + } elseif ('*' === $n[1]) { + $c = $class; + $n = substr($n, 3); + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null === $sleep) { + $properties[$c][$n] = $v; + } elseif (isset($sleep[$n]) && $c === $class) { + $properties[$c][$n] = $v; + unset($sleep[$n]); + } + if (\array_key_exists($name, $proto) && $proto[$name] === $v) { + unset($properties[$c][$n]); + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), E_USER_NOTICE); + } + } + + $objectsPool[$value] = array($id = \count($objectsPool)); + $properties = self::marshall($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0); + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + /** + * Extracts the state of an ArrayIterator or ArrayObject instance. + * + * For performance this method is public and has no type-hints. + * + * @param \ArrayIterator|\ArrayObject $value + * @param array &$arrayValue + * @param object $proto + * + * @return array + * + * @internal + */ + public static function getArrayObjectProperties($value, &$arrayValue, $proto) + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + + $properties = array( + $arrayValue, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ); + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if (array(array(), 0, 'ArrayIterator') === $properties) { + $properties = array(); + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = array($reflector->class => array("\0" => $properties)); + } + + return $properties; + } +} diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Reference.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Reference.php index 52c43af63618e..f5dd01fd80032 100644 --- a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Reference.php +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Reference.php @@ -18,13 +18,10 @@ */ class Reference { - public function __construct(int $id) - { - $this->{0} = $id; - } + public $id; - public static function __set_state($state) + public function __construct(int $id) { - return Registry::$objects[$state[0]]; + $this->id = $id; } } diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Registry.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Registry.php index 1eb250c3909cc..4bf5290e1177a 100644 --- a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Registry.php +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Registry.php @@ -20,8 +20,11 @@ class Registry { public static $stack = array(); public static $objects = array(); + public static $references = array(); public static $reflectors = array(); public static $prototypes = array(); + public static $cloneable = array(); + public static $instantiableWithoutConstructor = array(); public function __construct(array $classes) { @@ -32,15 +35,33 @@ public function __construct(array $classes) public static function __set_state($classes) { - self::$stack[] = self::$objects; + $unserializeCallback = null; + self::$stack[] = array(self::$objects, self::$references); self::$objects = $classes; - foreach (self::$objects as &$class) { - if (isset(self::$prototypes[$class])) { - $class = clone self::$prototypes[$class]; - } elseif (':' === ($class[1] ?? null)) { - $class = \unserialize($class); - } else { - $class = (self::$reflectors[$class] ?? self::getClassReflector($class))->newInstanceWithoutConstructor(); + self::$references = array(); + try { + foreach (self::$objects as &$class) { + if (':' === ($class[1] ?? null)) { + if (null === $unserializeCallback) { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + } + $class = \unserialize($class); + continue; + } + $r = self::$reflectors[$class] ?? self::getClassReflector($class); + + if (self::$cloneable[$class]) { + $class = clone self::$prototypes[$class]; + } else { + $class = self::$instantiableWithoutConstructor[$class] ? $r->newInstanceWithoutConstructor() : $r->newInstance(); + } + } + } catch (\Throwable $e) { + list(self::$objects, self::$references) = \array_pop(self::$stack); + throw $e; + } finally { + if (null !== $unserializeCallback) { + ini_set('unserialize_callback_func', $unserializeCallback); } } } @@ -49,8 +70,38 @@ public static function getClassReflector($class) { $reflector = new \ReflectionClass($class); - if (!$reflector->hasMethod('__clone')) { - self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor(); + if (self::$instantiableWithoutConstructor[$class] = !$reflector->isFinal() || !$reflector->isInternal()) { + $proto = $reflector->newInstanceWithoutConstructor(); + } else { + try { + $proto = $reflector->newInstance(); + } catch (\Throwable $e) { + throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class), 0, $e); + } + } + + if ($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType) { + if (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup')) { + throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class)); + } + } + + self::$prototypes[$class] = $proto; + self::$cloneable[$class] = !$reflector->hasMethod('__clone'); + + if ($proto instanceof \Throwable) { + static $trace; + + if (null === $trace) { + $trace = array( + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ); + $trace[0]->setAccessible(true); + $trace[1]->setAccessible(true); + } + + $trace[$proto instanceof \Exception]->setValue($proto, array()); } return self::$reflectors[$class] = $reflector; diff --git a/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Values.php b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Values.php new file mode 100644 index 0000000000000..3aa404dd5fcd3 --- /dev/null +++ b/src/Symfony/Component/Cache/Marshaller/PhpMarshaller/Values.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller\PhpMarshaller; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public function __construct(array $values) + { + foreach ($values as $i => $v) { + $this->$i = $v; + } + } + + public static function __set_state($values) + { + foreach ($values as $i => $v) { + Registry::$references[$i] = $v; + } + } +} diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php index 20dfa550310f3..047ceea4ef4c5 100644 --- a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -13,7 +13,6 @@ use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; -use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\PhpArrayTrait; @@ -29,8 +28,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt { use PhpArrayTrait; - private $marshaller; - /** * @param string $file The PHP file were values are cached * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit @@ -84,13 +81,6 @@ public function get($key, $default = null) return $default; } } - if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - try { - return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value); - } catch (\Throwable $e) { - return $default; - } - } return $value; } @@ -243,12 +233,6 @@ private function generateItems(array $keys, $default) } catch (\Throwable $e) { yield $key => $default; } - } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - try { - yield $key => unserialize($value); - } catch (\Throwable $e) { - yield $key => $default; - } } else { yield $key => $value; } diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.php index cc910b3fa296a..47eb76e09d1ee 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.php @@ -3,7 +3,8 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( '0' => 'ArrayIterator', )), - '1' => + '1' => NULL, + '2' => array ( 'ArrayIterator' => array ( @@ -20,11 +21,9 @@ ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.optimized.php deleted file mode 100644 index 06e9cd8cc1e2a..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.optimized.php +++ /dev/null @@ -1,28 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject', - )), - '1' => - array ( - 'ArrayObject' => - array ( - '' . "\0" . '' => - array ( - 0 => - array ( - 0 => - array ( - 0 => 234, - ), - 1 => 1, - ), - ), - ), - ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - '3' => - array ( - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.php index 1474853f00064..3f99f018e5487 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object-custom.php @@ -3,7 +3,8 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject', )), - '1' => + '1' => NULL, + '2' => array ( 'ArrayObject' => array ( @@ -20,11 +21,9 @@ ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.optimized.php deleted file mode 100644 index 511f9f8a6bd0e..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.optimized.php +++ /dev/null @@ -1,36 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'ArrayObject', - '1' => 'ArrayObject', - )), - '1' => - array ( - 'ArrayObject' => - array ( - '' . "\0" . '' => - array ( - 0 => - array ( - 0 => - array ( - 0 => 1, - 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - ), - 1 => 0, - ), - ), - 'foo' => - array ( - 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], - ), - ), - ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - '3' => - array ( - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.php index 30c516a0e6d2c..fc4b0d48c35d8 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-object.php @@ -4,7 +4,8 @@ '0' => 'ArrayObject', '1' => 'ArrayObject', )), - '1' => + '1' => NULL, + '2' => array ( 'ArrayObject' => array ( @@ -16,9 +17,7 @@ array ( 0 => 1, 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], ), 1 => 0, ), @@ -26,17 +25,13 @@ 'foo' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 1, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.optimized.php deleted file mode 100644 index e91c71c076104..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.optimized.php +++ /dev/null @@ -1,20 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable', - '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable', - )), - '1' => - array ( - ), - '2' => - array ( - 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], - ), - '3' => - array ( - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.php index b82bd43e56bf8..160b547f9e865 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/clone.php @@ -4,21 +4,18 @@ '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable', )), - '1' => + '1' => NULL, + '2' => array ( ), - '2' => + '3' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 1, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], ), - '3' => + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.optimized.php deleted file mode 100644 index 0bbd6f0b75e33..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.optimized.php +++ /dev/null @@ -1,30 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'DateTime', - )), - '1' => - array ( - 'DateTime' => - array ( - 'date' => - array ( - 0 => '1970-01-01 00:00:00.000000', - ), - 'timezone_type' => - array ( - 0 => 1, - ), - 'timezone' => - array ( - 0 => '+00:00', - ), - ), - ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - '3' => - array ( - 1 => 0, - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.php index c89375bf00571..fa2cb9d6c141f 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/datetime.php @@ -3,7 +3,8 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( '0' => 'DateTime', )), - '1' => + '1' => NULL, + '2' => array ( 'DateTime' => array ( @@ -21,11 +22,9 @@ ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( 1 => 0, ), diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references-recursive.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references-recursive.php new file mode 100644 index 0000000000000..84c6a8a009596 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references-recursive.php @@ -0,0 +1,22 @@ + NULL, + '1' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array( + '1' => + array ( + 0 => + &Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1], + ), + )), + '2' => + array ( + ), + '3' => + array ( + 0 => + &Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1], + ), + '4' => + array ( + ), +)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references.php similarity index 59% rename from src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.optimized.php rename to src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references.php index ce1c6ca5f751a..00b8f6a7e88a4 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.optimized.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/hard-references.php @@ -1,19 +1,26 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}', + '0' => 'stdClass', )), '1' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array( + '1' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + )), + '2' => array ( ), - '2' => + '3' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + &Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1], 1 => + &Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1], + 2 => Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], ), - '3' => + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/incomplete-class.php similarity index 55% rename from src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.optimized.php rename to src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/incomplete-class.php index f8964fed96656..ea92f52ab056a 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/array-iterator.optimized.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/incomplete-class.php @@ -1,28 +1,15 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'ArrayIterator', + '0' => 'O:20:"SomeNotExistingClass":0:{}', )), - '1' => + '1' => NULL, + '2' => array ( - 'ArrayIterator' => - array ( - '' . "\0" . '' => - array ( - 0 => - array ( - 0 => - array ( - 0 => 123, - ), - 1 => 1, - ), - ), - ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.optimized.php deleted file mode 100644 index c835a199afe67..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.optimized.php +++ /dev/null @@ -1,39 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue', - '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue', - )), - '1' => - array ( - 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' => - array ( - 'prot' => - array ( - 0 => 123, - ), - 'priv' => - array ( - 0 => 234, - 1 => 234, - ), - ), - 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue' => - array ( - 'prot' => - array ( - 1 => 123, - ), - ), - ), - '2' => - array ( - 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], - ), - '3' => - array ( - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.php index ef9ce4f1e6744..bda368b6becf2 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/private.php @@ -4,7 +4,8 @@ '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue', )), - '1' => + '1' => NULL, + '2' => array ( 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' => array ( @@ -26,18 +27,14 @@ ), ), ), - '2' => + '3' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 1, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], ), - '3' => + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.php index 4030fde63403a..b86ddc86f2744 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/serializable.php @@ -3,21 +3,18 @@ Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( '0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}', )), - '1' => + '1' => NULL, + '2' => array ( ), - '2' => + '3' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], 1 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], ), - '3' => + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.optimized.php deleted file mode 100644 index e604b13c86c15..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.optimized.php +++ /dev/null @@ -1,27 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'SplObjectStorage', - '1' => 'stdClass', - )), - '1' => - array ( - 'SplObjectStorage' => - array ( - '' . "\0" . '' => - array ( - 0 => - array ( - 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], - 1 => 345, - ), - ), - ), - ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - '3' => - array ( - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.php index 1249f7c45d730..4c72a1097122e 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/spl-object-storage.php @@ -4,7 +4,8 @@ '0' => 'SplObjectStorage', '1' => 'stdClass', )), - '1' => + '1' => NULL, + '2' => array ( 'SplObjectStorage' => array ( @@ -13,19 +14,15 @@ 0 => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 1, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], 1 => 345, ), ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( ), )); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.optimized.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.optimized.php deleted file mode 100644 index b09b5036efdfb..0000000000000 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.optimized.php +++ /dev/null @@ -1,30 +0,0 @@ - - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( - '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', - '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', - )), - '1' => - array ( - 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' => - array ( - 'sub' => - array ( - 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], - 1 => 123, - ), - 'baz' => - array ( - 1 => 123, - ), - ), - ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], - '3' => - array ( - 1 => 1, - 2 => 0, - ), -)); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.php b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.php index baa3e87ea0c24..fdf22185121e1 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/Fixtures/wakeup.php @@ -4,16 +4,15 @@ '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', )), - '1' => + '1' => NULL, + '2' => array ( 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' => array ( 'sub' => array ( 0 => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 1, - )), + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1], 1 => 123, ), 'baz' => @@ -22,11 +21,9 @@ ), ), ), - '2' => - Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( - '0' => 0, - )), '3' => + Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], + '4' => array ( 1 => 1, 2 => 0, diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/PhpMarshallerTest.php b/src/Symfony/Component/Cache/Tests/Marshaller/PhpMarshallerTest.php index 8b1f65211a1b5..05c64d27c056d 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/PhpMarshallerTest.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/PhpMarshallerTest.php @@ -13,35 +13,82 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Marshaller\PhpMarshaller; +use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; class DoctrineProviderTest extends TestCase { use VarDumperTestTrait; + /** + * @expectedException \ReflectionException + * @expectedExceptionMessage Class SomeNotExistingClass does not exist + */ + public function testPhpIncompleteClassesAreForbidden() + { + $unserializeCallback = ini_set('unserialize_callback_func', 'var_dump'); + try { + Registry::__set_state(array('O:20:"SomeNotExistingClass":0:{}')); + } finally { + $this->assertSame('var_dump', ini_set('unserialize_callback_func', $unserializeCallback)); + } + } + + /** + * @dataProvider provideFailingSerialization + * @expectedException \Exception + * @expectedExceptionMessageRegexp Serialization of '.*' is not allowed + */ + public function testFailingSerialization($value) + { + $expectedDump = $this->getDump($value); + try { + PhpMarshaller::marshall($value); + } finally { + $this->assertDumpEquals(rtrim($expectedDump), $value); + } + } + + public function provideFailingSerialization() + { + yield array(hash_init('md5')); + yield array(new \ReflectionClass('stdClass')); + yield array((new \ReflectionFunction(function (): int {}))->getReturnType()); + yield array(new \ReflectionGenerator((function () { yield 123; })())); + yield array(function () {}); + yield array(function () { yield 123; }); + yield array(new \SplFileInfo(__FILE__)); + yield array($h = fopen(__FILE__, 'r')); + yield array(array($h)); + + $a = array(null, $h); + $a[0] = &$a; + + yield array($a); + } + /** * @dataProvider provideMarshall */ - public function testMarshall(string $testName, $value, int $expectedObjectsCount) + public function testMarshall(string $testName, $value, bool $staticValueExpected = false) { - $objectsCount = 0; - $marshalledValue = PhpMarshaller::marshall($value, $objectsCount); + $serializedValue = serialize($value); + $isStaticValue = true; + $marshalledValue = PhpMarshaller::marshall($value, $isStaticValue); - $this->assertSame($expectedObjectsCount, $objectsCount); + $this->assertSame($staticValueExpected, $isStaticValue); + $this->assertSame($serializedValue, serialize($value)); - $dump = 'assertStringEqualsFile($fixtureFile, $dump); - if ($objectsCount) { - $marshalledValue = include $fixtureFile; - $this->assertDumpEquals($value, $marshalledValue); - - $dump = PhpMarshaller::optimize($dump); - $fixtureFile = __DIR__.'/Fixtures/'.$testName.'.optimized.php'; - $this->assertStringEqualsFile($fixtureFile, $dump); + if ('incomplete-class' === $testName) { + return; + } + $marshalledValue = include $fixtureFile; - $marshalledValue = include $fixtureFile; + if (!$isStaticValue) { $this->assertDumpEquals($value, $marshalledValue); } else { $this->assertSame($value, $marshalledValue); @@ -50,23 +97,23 @@ public function testMarshall(string $testName, $value, int $expectedObjectsCount public function provideMarshall() { - yield array('bool', true, 0); - yield array('simple-array', array(123, array('abc')), 0); - yield array('datetime', \DateTime::createFromFormat('U', 0), 1); + yield array('bool', true, true); + yield array('simple-array', array(123, array('abc')), true); + yield array('datetime', \DateTime::createFromFormat('U', 0)); $value = new \ArrayObject(); $value[0] = 1; $value->foo = new \ArrayObject(); $value[1] = $value; - yield array('array-object', $value, 3); + yield array('array-object', $value); - yield array('array-iterator', new \ArrayIterator(array(123), 1), 1); - yield array('array-object-custom', new MyArrayObject(array(234)), 1); + yield array('array-iterator', new \ArrayIterator(array(123), 1)); + yield array('array-object-custom', new MyArrayObject(array(234))); $value = new MySerializable(); - yield array('serializable', array($value, $value), 2); + yield array('serializable', array($value, $value)); $value = new MyWakeup(); $value->sub = new MyWakeup(); @@ -74,16 +121,29 @@ public function provideMarshall() $value->sub->bis = 123; $value->sub->baz = 123; - yield array('wakeup', $value, 2); + yield array('wakeup', $value); - yield array('clone', array(new MyCloneable(), new MyNotCloneable()), 2); + yield array('clone', array(new MyCloneable(), new MyNotCloneable())); - yield array('private', array(new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234)), 2); + yield array('private', array(new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234))); $value = new \SplObjectStorage(); $value[new \stdClass()] = 345; - yield array('spl-object-storage', $value, 2); + yield array('spl-object-storage', $value); + + yield array('incomplete-class', unserialize('O:20:"SomeNotExistingClass":0:{}')); + + $value = array((object) array()); + $value[1] = &$value[0]; + $value[2] = $value[0]; + + yield array('hard-references', $value); + + $value = array(); + $value[0] = &$value; + + yield array('hard-references-recursive', $value); } } diff --git a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php index cb5eb31f366a9..37b03c2fb9656 100644 --- a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php @@ -70,33 +70,29 @@ public function warmUp(array $values) foreach ($values as $key => $value) { CacheItem::validateKey(\is_int($key) ? (string) $key : $key); - $objectsCount = 0; + $isStaticValue = true; if (null === $value) { - $value = 'N;'; + $value = "'N;'"; } elseif (\is_object($value) || \is_array($value)) { try { - $e = null; - $serialized = serialize($value); + $value = PhpMarshaller::marshall($value, $isStaticValue); } catch (\Exception $e) { - } - if (null !== $e || false === $serialized) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); } - // Keep value serialized if it contains any internal references - $value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount); } elseif (\is_string($value)) { - // Wrap strings if they could be confused with serialized objects or arrays - if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { - ++$objectsCount; + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; } + $value = var_export($value, true); } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value))); + } else { + $value = var_export($value, true); } - $value = var_export($value, true); - if ($objectsCount) { - $value = PhpMarshaller::optimize($value); + if (!$isStaticValue) { $value = "static function () {\nreturn {$value};\n}"; } $hash = hash('md5', $value); diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index ed5afb0471744..dcf428f23cf73 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -13,7 +13,6 @@ use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; -use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\PhpMarshaller; /** @@ -30,7 +29,6 @@ trait PhpFilesTrait doDelete as private doCommonDelete; } - private $marshaller; private $includeHandler; private $appendOnly; private $values = array(); @@ -92,8 +90,6 @@ protected function doFetch(array $ids) $values[$id] = null; } elseif ($value instanceof \Closure) { $values[$id] = $value(); - } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { - $values[$id] = ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value); } else { $values[$id] = $value; } @@ -165,32 +161,28 @@ protected function doSave(array $values, $lifetime) foreach ($values as $key => $value) { unset($this->values[$key]); - $objectsCount = 0; + $isStaticValue = true; if (null === $value) { - $value = 'N;'; + $value = "'N;'"; } elseif (\is_object($value) || \is_array($value)) { try { - $e = null; - $serialized = serialize($value); + $value = PhpMarshaller::marshall($value, $isStaticValue); } catch (\Exception $e) { - } - if (null !== $e || false === $serialized) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); } - // Keep value serialized if it contains any internal references - $value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount); } elseif (\is_string($value)) { - // Wrap strings if they could be confused with serialized objects or arrays - if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { - ++$objectsCount; + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; } + $value = var_export($value, true); } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value))); + } else { + $value = var_export($value, true); } - $value = var_export($value, true); - if ($objectsCount) { - $value = PhpMarshaller::optimize($value); + if (!$isStaticValue) { $value = "static function () {\n\nreturn {$value};\n\n}"; }