Description
Symfony version(s) affected: 4.1 (maybe as low as 3.x)
Description
While collecting information for profiler exceptions and errors are converted to arrays. One of the errors in my log crashes ExceptionCaster
, since it contains stacktrace frame with class
referencing abstract class and in the same time without object
.
ExceptionCaster
blindly assumes that if something is a child of Twig_Template
/Twig\Template
it can be instantiated. However in reality I stepped into case where it's not true and stack trace contains direct reference to abstract class Vendor\Custom\TwigTemplate
(which extends \Twig_Template
) with method name referencing __toString()
. ExceptionCaster
attempts to initialize object of this class which leads to complete hard to debug crash.
How to reproduce
So far I'm unable to provide reproducer, since even stepping around the code with XDebug crashes PHP without any errors. The last frame received by \Symfony\Component\VarDumper\Caster\ExceptionCaster::castFrameStub
contains reference class set as base_template_class
and call to __toString()
. The only special thing about this class is it contains call to @trigger_error(..., E_USER_DEPRECATED)
within __toString()
.
The easiest way to reproduce this which I was able to find was to start with fresh 4.1 installation. Then:
- Configure
base_template_class
to point to custom class, e.g.base_template_class: \App\FooTwigBase
- Create custom base class:
abstract class FooTwigBase extends \Twig_Template
{
public function __toString()
{
\trigger_error('Message reprimanding users to not cast templates to string etc...', \E_USER_DEPRECATED); //This is crucial part of the story!
//In actual code I actually return template code
return parent::__toString();
}
}
Possible Solution
The easiest solution is to ensure no code calls loadTemplate()
but recommended load()
. This is however not possible since in some cases template code must be available for use (don't even ask...). This will mask the problem since is_subclass_of()
in caster will never return true with abstract class, however I think the proper fix is actually simple. Another hack is to never ever trigger any errors in __toString
, but this makes monitoring internal deprecations impossible.
I understand this is a massive edge-case from my side, however I think fixing it will be very easy from upstream.
\Symfony\Component\VarDumper\Caster\ExceptionCaster::castFrameStub
contains the following code:
When replaced with the following code safely skips problematic frame:
$template = null;
if (isset($f['object'])) {
$template = $f['object'];
} elseif ((new \ReflectionClass($f['class']))->isInstantiable()) {
$template = unserialize(sprintf('O:%d:"%s":0:{}', strlen($f['class']), $f['class']));
}
if (isset($template)) {
$ellipsis = 0;
//...
//Edit: provided way to reproduce