feature #2530 Throw exception on circular reference detection (yceruto)

This PR was merged into the 1.x branch.

Discussion
----------

Throw exception on circular reference detection

Out-the-box this isn't common... but into Symfony framework this kind of issue can happen frequently when we're overriding a template from third-party bundle (see main issue https://github.com/symfony/symfony/issues/17557)

```twig
{# app/Resources/AcmeBlogBundle/views/Blog/index.html.twig #}
{% extends '@AcmeBlog/Blog/index.html.twig' %}

{% block title 'New Title' %}
```

This exception avoids inifite loop or maximum execution time for this case, by detecting a recursive loading when a template extends from itself, thinking to override the parent template which has the same namespace + template name.

Commits
-------

074e1fbe Throw exception on circular reference detection
This commit is contained in:
Fabien Potencier
2017-07-22 09:30:00 +02:00
2 changed files with 44 additions and 1 deletions
+17 -1
View File
@@ -58,6 +58,7 @@ class Twig_Environment
private $runtimeLoaders = array();
private $runtimes = array();
private $optionsHash;
private $loading = array();
/**
* Constructor.
@@ -472,7 +473,22 @@ class Twig_Environment
$this->initRuntime();
}
return $this->loadedTemplates[$cls] = new $cls($this);
if (isset($this->loading[$cls])) {
throw new Twig_Error_Runtime(sprintf('Circular reference detected for Twig template "%s", path: %s.', $name, implode(' -> ', array_merge($this->loading, array($name)))));
}
$this->loading[$cls] = $name;
try {
$this->loadedTemplates[$cls] = new $cls($this);
unset($this->loading[$cls]);
} catch (\Exception $e) {
unset($this->loading[$cls]);
throw $e;
}
return $this->loadedTemplates[$cls];
}
/**
+27
View File
@@ -480,6 +480,33 @@ EOF
$this->assertEquals('foo', $twig->render('func_string_named_args'));
}
/**
* @expectedException Twig_Error_Runtime
* @expectedExceptionMessage Circular reference detected for Twig template "base.html.twig", path: base.html.twig -> base.html.twig in "base.html.twig" at line 1
*/
public function testFailLoadTemplateOnCircularReference()
{
$twig = new Twig_Environment(new Twig_Loader_Array(array(
'base.html.twig' => '{% extends "base.html.twig" %}',
)));
$twig->loadTemplate('base.html.twig');
}
/**
* @expectedException Twig_Error_Runtime
* @expectedExceptionMessage Circular reference detected for Twig template "base1.html.twig", path: base1.html.twig -> base2.html.twig -> base1.html.twig in "base1.html.twig" at line 1
*/
public function testFailLoadTemplateOnComplexCircularReference()
{
$twig = new Twig_Environment(new Twig_Loader_Array(array(
'base1.html.twig' => '{% extends "base2.html.twig" %}',
'base2.html.twig' => '{% extends "base1.html.twig" %}',
)));
$twig->loadTemplate('base1.html.twig');
}
protected function getMockLoader($templateName, $templateContent)
{
// to be removed in 2.0