Compare commits

..

5 Commits

Author SHA1 Message Date
Christian Flothmann
3ba0dfd9af Merge branch '3.4' into 4.1
* 3.4:
  Fix: Adjust DocBlock
  \"ParserTest->getParserTestData()\" -> only some more tests
  [Lock] Pedantic improvements for lock
  [EventDispatcher] Fixed phpdoc on interface
  update year in license files
  [Console] Fix help text for single command applications
  Fix random test failure on lock
  improve error message when using test client without the BrowserKit component
  [Event Dispatcher] fixed 29703: TraceableEventDispatcher reset now sets callStack to null with test to dispatch after reset.
  Fixed minor typos
  Fix: Method can also return null
  [Stopwatch] Fixed phpdoc for category name
2019-01-03 10:05:57 +01:00
Christian Flothmann
26d7f23b9b update year in license files 2019-01-01 14:45:19 +01:00
Nicolas Grekas
e9c81d5231 Merge branch '3.4' into 4.1
* 3.4:
  [Debug] ignore underscore vs backslash namespaces in DebugClassLoader
  [TwigBridge][Form] Prevent multiple rendering of form collection prototypes
  [FrameworkBundle] fix describing routes with no controllers
  [DI] move RegisterServiceSubscribersPass before DecoratorServicePass
  Update ValidationListener.php
  [Yaml] ensures that the mb_internal_encoding is reset to its initial value
  [WebLink] Fixed documentation link
  [Security] getTargetPath of TargetPathTrait must return string or null
  [Hackday][Serializer] Deserialization ignores argument type hint from phpdoc for array in constructor argument
  [Security] defer log message in guard authenticator
  merge conflicts
  Fix HeaderBag::get phpdoc
2018-12-13 13:30:33 +01:00
Nicolas Grekas
6eb115e3a5 [Debug] ignore underscore vs backslash namespaces in DebugClassLoader 2018-12-12 21:57:22 +01:00
Gabriel Ostrolucký
26bd579e62 Optimize perf by replacing call_user_func with dynamic vars 2018-12-09 23:58:21 +01:00
12 changed files with 24 additions and 251 deletions

View File

@@ -11,8 +11,6 @@
namespace Symfony\Component\Debug;
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
/**
* Autoloader checking if the class is really defined in the file found.
*
@@ -23,7 +21,6 @@ use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Nicolas Grekas <p@tchwork.com>
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
class DebugClassLoader
{
@@ -37,7 +34,6 @@ class DebugClassLoader
private static $deprecated = array();
private static $internal = array();
private static $internalMethods = array();
private static $annotatedParameters = array();
private static $darwinCache = array('/' => array('/', array()));
public function __construct(callable $classLoader)
@@ -151,7 +147,7 @@ class DebugClassLoader
require $file;
}
} else {
\call_user_func($this->classLoader, $class);
($this->classLoader)($class);
$file = false;
}
} finally {
@@ -218,7 +214,7 @@ class DebugClassLoader
$len = 0;
$ns = '';
} else {
$ns = \substr($class, 0, $len);
$ns = \str_replace('_', '\\', \substr($class, 0, $len));
}
// Detect annotations on the class
@@ -249,13 +245,13 @@ class DebugClassLoader
if (!isset(self::$checkedClasses[$use])) {
$this->checkClass($use);
}
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
if (isset(self::$deprecated[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
}
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
if (isset(self::$internal[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
$deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
}
}
@@ -264,12 +260,11 @@ class DebugClassLoader
return $deprecations;
}
// Inherit @final, @internal and @param annotations for methods
// Inherit @final and @internal annotations for methods
self::$finalMethods[$class] = array();
self::$internalMethods[$class] = array();
self::$annotatedParameters[$class] = array();
foreach ($parentAndOwnInterfaces as $use) {
foreach (array('finalMethods', 'internalMethods', 'annotatedParameters') as $property) {
foreach (array('finalMethods', 'internalMethods') as $property) {
if (isset(self::${$property}[$use])) {
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
}
@@ -293,52 +288,15 @@ class DebugClassLoader
}
}
// To read method annotations
$doc = $method->getDocComment();
if (isset(self::$annotatedParameters[$class][$method->name])) {
$definedParameters = array();
foreach ($method->getParameters() as $parameter) {
$definedParameters[$parameter->name] = true;
}
foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param (.*?)(?<= )\\\${$parameterName}\\b/", $doc))) {
$deprecations[] = sprintf($deprecation, $class);
}
}
}
if (!$doc) {
// Detect method annotations
if (false === $doc = $method->getDocComment()) {
continue;
}
$finalOrInternal = false;
foreach (array('final', 'internal') as $annotation) {
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
self::${$annotation.'Methods'}[$class][$method->name] = array($class, $message);
$finalOrInternal = true;
}
}
if ($finalOrInternal || $method->isConstructor() || false === \strpos($doc, '@param') || StatelessInvocation::class === $class) {
continue;
}
if (!preg_match_all('#\n\s+\* @param (.*?)(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) {
continue;
}
if (!isset(self::$annotatedParameters[$class][$method->name])) {
$definedParameters = array();
foreach ($method->getParameters() as $parameter) {
$definedParameters[$parameter->name] = true;
}
}
foreach ($matches as list(, $parameterType, $parameterName)) {
if (!isset($definedParameters[$parameterName])) {
$parameterType = trim($parameterType);
self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class);
}
}
}

View File

@@ -15,7 +15,6 @@ use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
@@ -406,19 +405,15 @@ class ErrorHandler
$context = $e;
}
if (false !== strpos($message, "class@anonymous\0")) {
$logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage();
} else {
$logMessage = $this->levels[$type].': '.$message;
}
$logMessage = $this->levels[$type].': '.$message;
if (null !== self::$toStringException) {
$errorAsException = self::$toStringException;
self::$toStringException = null;
} elseif (!$throw && !($type & $level)) {
if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
$lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : array();
$errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? array($lightTrace[0]) : $lightTrace);
$lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : array();
$errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace);
} elseif (isset(self::$silencedErrorCache[$id][$message])) {
$lightTrace = null;
$errorAsException = self::$silencedErrorCache[$id][$message];
@@ -441,6 +436,7 @@ class ErrorHandler
} else {
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
// Clean the trace by removing function arguments and the first frames added by the error handler itself.
if ($throw || $this->tracedErrors & $type) {
$backtrace = $errorAsException->getTrace();
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
@@ -522,24 +518,21 @@ class ErrorHandler
$handlerException = null;
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
$message = (new FlattenException())->setMessage($message)->getMessage();
}
if ($exception instanceof FatalErrorException) {
if ($exception instanceof FatalThrowableError) {
$error = array(
'type' => $type,
'message' => $message,
'message' => $message = $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
);
} else {
$message = 'Fatal '.$message;
$message = 'Fatal '.$exception->getMessage();
}
} elseif ($exception instanceof \ErrorException) {
$message = 'Uncaught '.$message;
$message = 'Uncaught '.$exception->getMessage();
} else {
$message = 'Uncaught Exception: '.$message;
$message = 'Uncaught Exception: '.$exception->getMessage();
}
}
if ($this->loggedErrors & $type) {
@@ -560,7 +553,7 @@ class ErrorHandler
$this->exceptionHandler = null;
try {
if (null !== $exceptionHandler) {
return \call_user_func($exceptionHandler, $exception);
return $exceptionHandler($exception);
}
$handlerException = $handlerException ?: $exception;
} catch (\Throwable $handlerException) {
@@ -668,9 +661,6 @@ class ErrorHandler
);
}
/**
* Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader.
*/
private function cleanTrace($backtrace, $type, $file, $line, $throw)
{
$lightTrace = $backtrace;
@@ -681,13 +671,6 @@ class ErrorHandler
break;
}
}
if (class_exists(DebugClassLoader::class, false)) {
for ($i = \count($lightTrace) - 2; 0 < $i; --$i) {
if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) {
array_splice($lightTrace, --$i, 2);
}
}
}
if (!($throw || $this->scopedErrors & $type)) {
for ($i = 0; isset($lightTrace[$i]); ++$i) {
unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);

View File

@@ -90,14 +90,9 @@ class FlattenException
return $this->statusCode;
}
/**
* @return $this
*/
public function setStatusCode($code)
{
$this->statusCode = $code;
return $this;
}
public function getHeaders()
@@ -105,14 +100,9 @@ class FlattenException
return $this->headers;
}
/**
* @return $this
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;
return $this;
}
public function getClass()
@@ -120,14 +110,9 @@ class FlattenException
return $this->class;
}
/**
* @return $this
*/
public function setClass($class)
{
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
return $this;
$this->class = $class;
}
public function getFile()
@@ -135,14 +120,9 @@ class FlattenException
return $this->file;
}
/**
* @return $this
*/
public function setFile($file)
{
$this->file = $file;
return $this;
}
public function getLine()
@@ -150,14 +130,9 @@ class FlattenException
return $this->line;
}
/**
* @return $this
*/
public function setLine($line)
{
$this->line = $line;
return $this;
}
public function getMessage()
@@ -165,20 +140,9 @@ class FlattenException
return $this->message;
}
/**
* @return $this
*/
public function setMessage($message)
{
if (false !== strpos($message, "class@anonymous\0")) {
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
}, $message);
}
$this->message = $message;
return $this;
}
public function getCode()
@@ -186,14 +150,9 @@ class FlattenException
return $this->code;
}
/**
* @return $this
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
public function getPrevious()
@@ -201,14 +160,9 @@ class FlattenException
return $this->previous;
}
/**
* @return $this
*/
public function setPrevious(self $previous)
{
$this->previous = $previous;
return $this;
}
public function getAllPrevious()
@@ -237,14 +191,11 @@ class FlattenException
$this->setTraceFromThrowable($exception);
}
public function setTraceFromThrowable(\Throwable $throwable)
public function setTraceFromThrowable(\Throwable $throwable): void
{
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
$this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
}
/**
* @return $this
*/
public function setTrace($trace, $file, $line)
{
$this->trace = array();
@@ -278,8 +229,6 @@ class FlattenException
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
);
}
return $this;
}
private function flattenArgs($args, $level = 0, &$count = 0)

View File

@@ -142,7 +142,7 @@ class ExceptionHandler
$this->caughtBuffer = null;
try {
\call_user_func($this->handler, $exception);
($this->handler)($exception);
$this->caughtLength = $caughtLength;
} catch (\Exception $e) {
if (!$caughtLength) {
@@ -253,8 +253,7 @@ EOF
} catch (\Exception $e) {
// something nasty happened and we cannot throw an exception anymore
if ($this->debug) {
$e = FlattenException::create($e);
$title = sprintf('Exception thrown when handling an exception (%s: %s)', $e->getClass(), $this->escapeHtml($e->getMessage()));
$title = sprintf('Exception thrown when handling an exception (%s: %s)', \get_class($e), $this->escapeHtml($e->getMessage()));
} else {
$title = 'Whoops, looks like something went wrong.';
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -273,24 +273,6 @@ class DebugClassLoaderTest extends TestCase
));
}
public function testExtendedMethodDefinesNewParameters()
{
$deprecations = array();
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
$e = error_reporting(E_USER_DEPRECATED);
class_exists(__NAMESPACE__.'\\Fixtures\SubClassWithAnnotatedParameters', true);
error_reporting($e);
restore_error_handler();
$this->assertSame(array(
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::quzMethod()" method will require a new "Quz $quz" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::whereAmI()" method will require a new "bool $matrix" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
'The "Symfony\Component\Debug\Tests\Fixtures\SubClassWithAnnotatedParameters::isSymfony()" method will require a new "true $yes" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
), $deprecations);
}
public function testUseTraitWithInternalMethod()
{
$deprecations = array();

View File

@@ -333,19 +333,6 @@ class FlattenExceptionTest extends TestCase
$this->assertNotContains('*value1*', $serializeTrace);
}
public function testAnonymousClass()
{
$flattened = FlattenException::create(new class() extends \RuntimeException {
});
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
$flattened = FlattenException::create(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
}))));
$this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage());
}
private function createException($foo)
{
return new \Exception();

View File

@@ -1,34 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class ClassWithAnnotatedParameters
{
/**
* @param string $foo this is a foo parameter
*/
public function fooMethod(string $foo)
{
}
/**
* @param string $bar parameter not implemented yet
*/
public function barMethod(/* string $bar = null */)
{
}
/**
* @param Quz $quz parameter not implemented yet
*/
public function quzMethod(/* Quz $quz = null */)
{
}
/**
* @param true $yes
*/
public function isSymfony()
{
}
}

View File

@@ -1,14 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* Ensures a deprecation is triggered when a new parameter is not declared in child classes.
*/
interface InterfaceWithAnnotatedParameters
{
/**
* @param bool $matrix
*/
public function whereAmI();
}

View File

@@ -1,24 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
class SubClassWithAnnotatedParameters extends ClassWithAnnotatedParameters implements InterfaceWithAnnotatedParameters
{
use TraitWithAnnotatedParameters;
public function fooMethod(string $foo)
{
}
public function barMethod($bar = null)
{
}
public function quzMethod()
{
}
public function whereAmI()
{
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
trait TraitWithAnnotatedParameters
{
/**
* `@param` annotations in traits are not parsed.
*/
public function isSymfony()
{
}
}

View File

@@ -34,7 +34,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.1-dev"
}
}
}