diff --git a/CHANGELOG b/CHANGELOG
index 6fe41c6d4..8f423332e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
# 3.30.1 (2026-XX-XX)
* Reject a deprecated `Template` instance created by another environment in `Environment::resolveTemplate()`
+ * Fix a compilation error inside an `autoescape` tag leaking its escaping strategy into the next compiled template
* Speed up reading object attributes backed by getters or class constants
* Fix the sandbox not reporting the line of a rejected `guard` tag
diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php
index 6063d8586..4c57a8c12 100644
--- a/src/NodeVisitor/EscaperNodeVisitor.php
+++ b/src/NodeVisitor/EscaperNodeVisitor.php
@@ -51,12 +51,14 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
+ $this->defaultStrategy = false;
if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) {
$this->defaultStrategy = $defaultStrategy;
}
$node->setAttribute('strategy', \is_string($this->defaultStrategy) ? $this->defaultStrategy : false);
$this->safeVars = [];
$this->blocks = [];
+ $this->statusStack = [];
$this->usesEscaper = false;
} elseif ($node instanceof AutoEscapeNode) {
$this->statusStack[] = $node->getAttribute('value');
diff --git a/tests/NodeVisitor/EscaperTest.php b/tests/NodeVisitor/EscaperTest.php
index 35e9d071c..4b8f37faf 100644
--- a/tests/NodeVisitor/EscaperTest.php
+++ b/tests/NodeVisitor/EscaperTest.php
@@ -14,6 +14,7 @@ namespace Twig\Tests\NodeVisitor;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
+use Twig\Error\Error;
use Twig\Extension\AbstractExtension;
use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ConstantExpression;
@@ -21,6 +22,7 @@ use Twig\Node\Node;
use Twig\Node\Nodes;
use Twig\Node\PrintNode;
use Twig\NodeVisitor\NodeVisitorInterface;
+use Twig\TwigFilter;
class EscaperTest extends TestCase
{
@@ -46,6 +48,32 @@ class EscaperTest extends TestCase
yield 'escaping in an embedded template only' => [false, '{% embed "embedded" %}{% block content %}{{ foo|e }}{% endblock %}{% endembed %}', 1, '<br>'];
}
+ public function testCompilationErrorInsideAnAutoescapeTagDoesNotAffectTheNextTemplate(): void
+ {
+ $env = new Environment(new ArrayLoader([
+ 'broken.html' => '{% autoescape false %}{{ foo|failing|nl2br }}{% endautoescape %}',
+ 'index.html' => '{{ foo }}',
+ 'index.txt' => '{{ foo }}',
+ ]), ['autoescape' => 'name']);
+ $env->addExtension(new class extends AbstractExtension {
+ public function getFilters(): array
+ {
+ return [new TwigFilter('failing', static fn ($value) => $value, ['is_safe_callback' => static function (): array {
+ throw new \LogicException('Unable to compute the safety of the filter.');
+ }])];
+ }
+ });
+
+ try {
+ $env->load('broken.html');
+ $this->fail('Compiling the template should fail.');
+ } catch (Error) {
+ }
+
+ $this->assertSame('
', $env->render('index.txt', ['foo' => '
']));
+ $this->assertSame('<br>', $env->render('index.html', ['foo' => '
']));
+ }
+
public function testEscapeFilterAddedByALaterVisitorStillEscapes(): void
{
$env = new Environment(new ArrayLoader(['index' => '{{ "
" }}']), ['autoescape' => false]);