Repository: twigphp/Twig (php)

Issue:
[3.x] 3 failing tests related to trim in twig_compare
1 from https://github.com/twigphp/Twig/pull/3256#discussion_r438157867 + 2 more:

```twig
{{ 42 > ' foo' ? '.' : 'F' }}
{{ 42 == "42\f" ? '.' : 'F' }}
{{ 42 > "\x00\x34\x32" ? '.' : 'F' }}
```

(https://twigfiddle.com/h5x8fk)

PHP:

```php
echo 1 === twig_compare(42, ' foo') ? '.' : 'F', "\n";
echo 0 === twig_compare(42, "42\f") ? '.' : 'F', "\n";
echo 1 === twig_compare(42, "\x00\x34\x32") ? '.' : 'F', "\n";
```

More detailed: https://3v4l.org/VkVU2

I guess the fixes would be like
(1) use a new variable for the trimmed string and use it *except* in `return (string) $a <=> $b;`, and
(2) trim explicitly the chars `" \t\n\r\v\f"` (from https://github.com/php/php-src/blob/php-7.4.7/Zend/zend_operators.c#L3041), because the default misses `\f` and includes `\0`.

Viz. (just for the `int <=> string` case): https://3v4l.org/VFPbu

Pull request description:
[3.x] Fix twig_compare w.r.t. whitespace trimming
Fixes #3352

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 4eaafa67..2a2aae98 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,17 +1,3 @@
-# 3.0.5 (2020-XX-XX)
-
- * n/a
-
-# 3.0.4 (2020-07-05)
-
- * Fix comparison operators
- * Fix options not taken into account when using "Michelf\MarkdownExtra"
- * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument
- * Throw exception in case non-Traversable data is passed to "filter"
- * Fix context optimization on PHP 7.4
- * Fix PHP 8 compatibility
- * Fix ambiguous syntax parsing
-
 # 3.0.3 (2020-02-11)
 
  * Add a check to ensure that iconv() is defined
@@ -48,19 +34,9 @@
  * bumped minimum PHP version to 7.2
  * removed PSR-0 classes
 
-# 2.13.1 (2020-XX-XX)
+# 2.13.0 (2020-XX-XX)
 
- * n/a
-
-# 2.13.0 (2020-07-05)
-
- * Fix options not taken into account when using "Michelf\MarkdownExtra"
- * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument
  * Drop support for PHP 7.0
- * Throw exception in case non-Traversable data is passed to "filter"
- * Fix context optimization on PHP 7.4
- * Fix PHP 8 compatibility
- * Fix ambiguous syntax parsing
 
 # 2.12.5 (2020-02-11)
 
@@ -364,14 +340,8 @@
  * improved the performance of the filesystem loader
  * removed features that were deprecated in 1.x
 
-# 1.43.1 (2020-XX-XX)
-
- * n/a
-
-# 1.43.0 (2020-07-05)
+# 1.43.0 (2020-XX-XX)
 
- * Throw exception in case non-Traversable data is passed to "filter"
- * Fix context optimization on PHP 7.4
  * Fix PHP 8 compatibility
  * Drop PHP 5.5 5.6, and 7.0 support
  * Fix ambiguous syntax parsing
diff --git a/doc/intro.rst b/doc/intro.rst
index c2a0ea26..3613c750 100644
--- a/doc/intro.rst
+++ b/doc/intro.rst
@@ -26,7 +26,7 @@ Slim, Yii, Laravel, and Codeigniter — just to name a few.
 Prerequisites
 -------------
 
-Twig 3.x needs at least **PHP 7.2.5** to run.
+Twig needs at least **PHP 7.2.0** to run.
 
 Installation
 ------------
diff --git a/extra/intl-extra/src/IntlExtension.php b/extra/intl-extra/src/IntlExtension.php
index 1895c296..34f33c80 100644
--- a/extra/intl-extra/src/IntlExtension.php
+++ b/extra/intl-extra/src/IntlExtension.php
@@ -157,7 +157,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getCountryName(?string $country, string $locale = null): string
     {
-        if (null === $country) {
+        if ($country === null) {
             return '';
         }
 
@@ -170,7 +170,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getCurrencyName(?string $currency, string $locale = null): string
     {
-        if (null === $currency) {
+        if ($currency === null) {
             return '';
         }
 
@@ -183,7 +183,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getCurrencySymbol(?string $currency, string $locale = null): string
     {
-        if (null === $currency) {
+        if ($currency === null) {
             return '';
         }
 
@@ -196,7 +196,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getLanguageName(?string $language, string $locale = null): string
     {
-        if (null === $language) {
+        if ($language === null) {
             return '';
         }
 
@@ -209,7 +209,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getLocaleName(?string $data, string $locale = null): string
     {
-        if (null === $data) {
+        if ($data === null) {
             return '';
         }
 
@@ -222,7 +222,7 @@ final class IntlExtension extends AbstractExtension
 
     public function getTimezoneName(?string $timezone, string $locale = null): string
     {
-        if (null === $timezone) {
+        if ($timezone === null) {
             return '';
         }
 
diff --git a/src/Environment.php b/src/Environment.php
index f4ce96bb..a71b956a 100644
--- a/src/Environment.php
+++ b/src/Environment.php
@@ -38,11 +38,11 @@ use Twig\TokenParser\TokenParserInterface;
  */
 class Environment
 {
-    const VERSION = '3.0.5';
-    const VERSION_ID = 30005;
+    const VERSION = '3.0.4-DEV';
+    const VERSION_ID = 30004;
     const MAJOR_VERSION = 3;
     const MINOR_VERSION = 0;
-    const RELEASE_VERSION = 5;
+    const RELEASE_VERSION = 4;
     const EXTRA_VERSION = 'DEV';
 
     private $charset;
diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php
index fef47221..6642cff1 100644
--- a/src/Extension/CoreExtension.php
+++ b/src/Extension/CoreExtension.php
@@ -944,25 +944,25 @@ function twig_compare($a, $b)
 {
     // int <=> string
     if (\is_int($a) && \is_string($b)) {
-        $b = trim($b);
-        if (!is_numeric($b)) {
+        $bTrim = trim($b, " \t\n\r\v\f");
+        if (!is_numeric($bTrim)) {
             return (string) $a <=> $b;
         }
-        if ((int) $b == $b) {
-            return $a <=> (int) $b;
+        if ((int) $bTrim == $bTrim) {
+            return $a <=> (int) $bTrim;
         } else {
-            return (float) $a <=> (float) $b;
+            return (float) $a <=> (float) $bTrim;
         }
     }
     if (\is_string($a) && \is_int($b)) {
-        $a = trim($a);
-        if (!is_numeric($a)) {
+        $aTrim = trim($a, " \t\n\r\v\f");
+        if (!is_numeric($aTrim)) {
             return $a <=> (string) $b;
         }
-        if ((int) $a == $a) {
-            return (int) $a <=> $b;
+        if ((int) $aTrim == $aTrim) {
+            return (int) $aTrim <=> $b;
         } else {
-            return (float) $a <=> (float) $b;
+            return (float) $aTrim <=> (float) $b;
         }
     }
 
@@ -971,23 +971,23 @@ function twig_compare($a, $b)
         if (is_nan($a)) {
             return 1;
         }
-        $b = trim($b);
-        if (!is_numeric($b)) {
+        $bTrim = trim($b, " \t\n\r\v\f");
+        if (!is_numeric($bTrim)) {
             return (string) $a <=> $b;
         }
 
-        return $a <=> (float) $b;
+        return $a <=> (float) $bTrim;
     }
     if (\is_string($a) && \is_float($b)) {
         if (is_nan($b)) {
             return 1;
         }
-        $a = trim($a);
-        if (!is_numeric($a)) {
+        $aTrim = trim($a, " \t\n\r\v\f");
+        if (!is_numeric($aTrim)) {
             return $a <=> (string) $b;
         }
 
-        return (float) $a <=> $b;
+        return (float) $aTrim <=> $b;
     }
 
     // fallback to <=>
@@ -1031,7 +1031,7 @@ function twig_spaceless($content)
 
 function twig_convert_encoding($string, $to, $from)
 {
-    if (!\function_exists('iconv')) {
+    if (!function_exists('iconv')) {
         throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
     }
 
diff --git a/src/Extension/ProfilerExtension.php b/src/Extension/ProfilerExtension.php
index 43e4a449..d8125b2d 100644
--- a/src/Extension/ProfilerExtension.php
+++ b/src/Extension/ProfilerExtension.php
@@ -47,6 +47,6 @@ class ProfilerExtension extends AbstractExtension
 
     public function getNodeVisitors(): array
     {
-        return [new ProfilerNodeVisitor(static::class)];
+        return [new ProfilerNodeVisitor(\get_class($this))];
     }
 }
diff --git a/src/Node/Node.php b/src/Node/Node.php
index d1a5b900..6895a21d 100644
--- a/src/Node/Node.php
+++ b/src/Node/Node.php
@@ -40,7 +40,7 @@ class Node implements \Countable, \IteratorAggregate
     {
         foreach ($nodes as $name => $node) {
             if (!$node instanceof self) {
-                throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class));
+                throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)));
             }
         }
         $this->nodes = $nodes;
@@ -56,7 +56,7 @@ class Node implements \Countable, \IteratorAggregate
             $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
         }
 
-        $repr = [static::class.'('.implode(', ', $attributes)];
+        $repr = [\get_class($this).'('.implode(', ', $attributes)];
 
         if (\count($this->nodes)) {
             foreach ($this->nodes as $name => $node) {
@@ -105,7 +105,7 @@ class Node implements \Countable, \IteratorAggregate
     public function getAttribute(string $name)
     {
         if (!\array_key_exists($name, $this->attributes)) {
-            throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
+            throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, \get_class($this)));
         }
 
         return $this->attributes[$name];
@@ -129,7 +129,7 @@ class Node implements \Countable, \IteratorAggregate
     public function getNode(string $name): self
     {
         if (!isset($this->nodes[$name])) {
-            throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
+            throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, \get_class($this)));
         }
 
         return $this->nodes[$name];
diff --git a/src/Template.php b/src/Template.php
index 3a2ed982..98ca6319 100644
--- a/src/Template.php
+++ b/src/Template.php
@@ -74,6 +74,8 @@ abstract class Template
      * This method is for internal use only and should never be called
      * directly.
      *
+     * @param array $context
+     *
      * @return Template|TemplateWrapper|false The parent template or false if there is no parent
      */
     public function getParent(array $context)
@@ -311,7 +313,7 @@ abstract class Template
             }
 
             if ($template === $this->getTemplateName()) {
-                $class = static::class;
+                $class = \get_class($this);
                 if (false !== $pos = strrpos($class, '___', -1)) {
                     $class = substr($class, 0, $pos);
                 }


Test patch:
diff --git a/drupal_test.sh b/drupal_test.sh
index a25d886f..e15bd667 100755
--- a/drupal_test.sh
+++ b/drupal_test.sh
@@ -6,13 +6,15 @@ set -e
 REPO=`pwd`
 cd /tmp
 rm -rf drupal-twig-test
-composer create-project --no-interaction drupal/recommended-project:9.1.x-dev drupal-twig-test
+composer create-project --no-install --no-interaction drupal-composer/drupal-project:8.x-dev drupal-twig-test
 cd drupal-twig-test
+composer require --no-update webflo/drupal-core-require-dev "egulias/email-validator:^2.0"
+composer update
 (cd vendor/twig && rm -rf twig && ln -sf $REPO twig)
+echo '$config["system.logging"]["error_level"] = "verbose";' >> web/sites/default/settings.php
 php ./web/core/scripts/drupal install --no-interaction demo_umami > output
 perl -p -i -e 's/^([A-Za-z]+)\: (.+)$/export DRUPAL_\1=\2/' output
 source output
-#echo '$config["system.logging"]["error_level"] = "verbose";' >> web/sites/default/settings.php
 
 wget https://get.symfony.com/cli/installer -O - | bash
 export PATH="$HOME/.symfony/bin:$PATH"
diff --git a/src/Test/IntegrationTestCase.php b/src/Test/IntegrationTestCase.php
index d3258743..1bad5491 100644
--- a/src/Test/IntegrationTestCase.php
+++ b/src/Test/IntegrationTestCase.php
@@ -257,7 +257,7 @@ abstract class IntegrationTestCase extends TestCase
         $templates = [];
         preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
-            $templates[($match[1] ?: 'index.twig')] = $match[2];
+            $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2];
         }
 
         return $templates;
diff --git a/tests/Cache/FilesystemTest.php b/tests/Cache/FilesystemTest.php
index 942854dc..0791cb56 100644
--- a/tests/Cache/FilesystemTest.php
+++ b/tests/Cache/FilesystemTest.php
@@ -168,7 +168,7 @@ class FilesystemTest extends TestCase
     public function testGenerateKey($expected, $input)
     {
         $cache = new FilesystemCache($input);
-        $this->assertRegExp($expected, $cache->generateKey('_test_', static::class));
+        $this->assertRegExp($expected, $cache->generateKey('_test_', \get_class($this)));
     }
 
     public function provideDirectories()
diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php
index 4c4f7a86..b94dee2a 100644
--- a/tests/ErrorTest.php
+++ b/tests/ErrorTest.php
@@ -206,14 +206,14 @@ EOHTML
 
     public function testTwigLeakOutputInDebugMode()
     {
-        $output = exec(sprintf('%s %s debug', PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php')));
+        $output = exec(sprintf('%s %s debug', \PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php')));
 
         $this->assertSame('Hello OOPS', $output);
     }
 
     public function testDoesNotTwigLeakOutput()
     {
-        $output = exec(sprintf('%s %s', PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php')));
+        $output = exec(sprintf('%s %s', \PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php')));
 
         $this->assertSame('', $output);
     }
diff --git a/tests/Extension/CoreTest.php b/tests/Extension/CoreTest.php
index 91cced5b..aae0f0f5 100644
--- a/tests/Extension/CoreTest.php
+++ b/tests/Extension/CoreTest.php
@@ -320,6 +320,10 @@ class CoreTest extends TestCase
             [-1, 10, 20],
             [-1, '10', 20],
             [-1, 10, '20'],
+
+            [1, 42, ' foo'],
+            [0, 42, "42\f"],
+            [1, 42, "\x00\x34\x32"],
         ];
     }
 }
diff --git a/tests/Fixtures/errors/leak-output.php b/tests/Fixtures/errors/leak-output.php
index 732383ea..7f436fe9 100644
--- a/tests/Fixtures/errors/leak-output.php
+++ b/tests/Fixtures/errors/leak-output.php
@@ -20,7 +20,7 @@ class BrokenExtension extends AbstractExtension
 
     public function broken()
     {
-        exit('OOPS');
+        die('OOPS');
     }
 }
 
diff --git a/tests/Profiler/Dumper/AbstractTest.php b/tests/Profiler/Dumper/AbstractTest.php
index 29e40d26..daaf83cd 100644
--- a/tests/Profiler/Dumper/AbstractTest.php
+++ b/tests/Profiler/Dumper/AbstractTest.php
@@ -76,6 +76,7 @@ abstract class AbstractTest extends TestCase
      * @param bool   $isTemplate
      * @param string $type
      * @param string $templateName
+     * @param array  $subProfiles
      *
      * @return Profile
      */