From 53649c82b292e450cbf1b7a4ef459e984cccabce Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Sun, 22 Aug 2021 15:04:50 +0200
Subject: [PATCH] [TASK] Deprecate
 TypoScriptFrontendController->cObjectDepthCounter

TypoScriptFrontendController has a property to count nesting
of content objects. This is used in ContentObjectRenderer
to prevent recursion scenarios. If that kicks in, the rendering
is silently stopped.

This is ugly for various reasons: First, creating a recursion
via TypoScript is rather unlikely. Second, the property creates
a dependency between ContentObjectRenderer and
TypoScriptFrontendController. And most importantly: If a
TypoScript setup somehow manages to create a recursion, this
should create an error, not a silent suppression.

The patch drops handling of property cObjectDepthCounter. If
a recursion appears now, PHP will error out as soon as max
nesting level is reached. This obsoletes the property and its
handling.

Resolves: #94957
Releases: master
Change-Id: I4d25472c6c0680a96cc223b7e81bbcbc813b2f52
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70719
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
---
 ...FrontendController-cObjectDepthCounter.rst | 42 +++++++++++++++
 .../Utility/FrontendSimulatorUtility.php      |  4 +-
 .../Classes/ViewHelpers/CObjectViewHelper.php |  4 +-
 .../ContentObject/ContentObjectRenderer.php   | 54 +++++++++----------
 .../TypoScriptFrontendController.php          |  1 +
 .../Tests/Unit/Plugin/AbstractPluginTest.php  |  1 -
 .../Php/PropertyPublicMatcher.php             |  5 ++
 7 files changed, 74 insertions(+), 37 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-94957-TypoScriptFrontendController-cObjectDepthCounter.rst

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94957-TypoScriptFrontendController-cObjectDepthCounter.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94957-TypoScriptFrontendController-cObjectDepthCounter.rst
new file mode 100644
index 000000000000..29b5abe492bb
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-94957-TypoScriptFrontendController-cObjectDepthCounter.rst
@@ -0,0 +1,42 @@
+.. include:: ../../Includes.txt
+
+=======================================================================
+Deprecation: #94957 - TypoScriptFrontendController->cObjectDepthCounter
+=======================================================================
+
+See :issue:`94957`
+
+Description
+===========
+
+The :php:`TypoScriptFrontendController` contains a property to prevent endless
+recursion of content objects during frontend rendering. With TypoScript
+becoming less complex, this check becomes obsolete. To reduce dependencies
+between :php:`TypoScriptFrontendController` and :php:`ContentObjectRenderer`,
+the handling has been removed and property :php:`TypoScriptFrontendController->cObjectDepthCounter`
+has bee marked as deprecated.
+
+
+Impact
+======
+
+If a TypoScript setup somehow manages to create a recursion, PHP will now stop
+with a fatal PHP nesting level error at some point, instead of TYPO3 frontend
+rendering silently stopping.
+
+
+Affected Installations
+======================
+
+Instances using property :php:`TypoScriptFrontendController->cObjectDepthCounter`
+are affected. That property has been handled mostly internally, this case is unlikely.
+The extension scanner will find usages with a weak match.
+
+
+Migration
+=========
+
+Drop usages of property :php:`TypoScriptFrontendController->cObjectDepthCounter`,
+it is unused within TYPO3 v11.
+
+.. index:: Frontend, PHP-API, FullyScanned, ext:frontend
diff --git a/typo3/sysext/extbase/Classes/Utility/FrontendSimulatorUtility.php b/typo3/sysext/extbase/Classes/Utility/FrontendSimulatorUtility.php
index 359db6bc8ee3..cf77bc543d1e 100644
--- a/typo3/sysext/extbase/Classes/Utility/FrontendSimulatorUtility.php
+++ b/typo3/sysext/extbase/Classes/Utility/FrontendSimulatorUtility.php
@@ -34,8 +34,7 @@ class FrontendSimulatorUtility
     protected static $tsfeBackup;
 
     /**
-     * Sets the $TSFE->cObjectDepthCounter in Backend mode
-     * This somewhat hacky work around is currently needed because the cObjGetSingle() function of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting
+     * \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->cObjGetSingle() relies on $GLOBALS['TSFE']
      *
      * @param ContentObjectRenderer|null $cObj
      */
@@ -43,7 +42,6 @@ class FrontendSimulatorUtility
     {
         self::$tsfeBackup = $GLOBALS['TSFE'] ?? null;
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->cObjectDepthCounter = 100;
         $GLOBALS['TSFE']->cObj = $cObj ?? GeneralUtility::makeInstance(ContentObjectRenderer::class);
     }
 
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php
index cb65502758e2..aa108c5f8dae 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php
@@ -237,15 +237,13 @@ class CObjectViewHelper extends AbstractViewHelper
     }
 
     /**
-     * Sets the $TSFE->cObjectDepthCounter in Backend mode
-     * This somewhat hacky work around is currently needed because the cObjGetSingle() function of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting
+     * \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->cObjGetSingle() relies on $GLOBALS['TSFE']
      */
     protected static function simulateFrontendEnvironment()
     {
         static::$tsfeBackup = $GLOBALS['TSFE'] ?? null;
         $GLOBALS['TSFE'] = new \stdClass();
         $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
-        $GLOBALS['TSFE']->cObjectDepthCounter = 100;
     }
 
     /**
diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
index bb84104979cf..7dd3b666bc06 100644
--- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
+++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
@@ -706,38 +706,32 @@ class ContentObjectRenderer implements LoggerAwareInterface
     public function cObjGetSingle($name, $conf, $TSkey = '__')
     {
         $content = '';
-        // Checking that the function is not called eternally. This is done by interrupting at a depth of 100
-        $this->getTypoScriptFrontendController()->cObjectDepthCounter--;
-        if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) {
-            $timeTracker = $this->getTimeTracker();
-            $name = trim($name);
-            if ($timeTracker->LR) {
-                $timeTracker->push($TSkey, $name);
-            }
-            // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
-            if (isset($name[0]) && $name[0] === '<') {
-                $key = trim(substr($name, 1));
-                $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
-                // $name and $conf is loaded with the referenced values.
-                $confOverride = is_array($conf) ? $conf : [];
-                [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
-                $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
-                // Getting the cObject
-                $timeTracker->incStackPointer();
-                $content .= $this->cObjGetSingle($name, $conf, $key);
-                $timeTracker->decStackPointer();
-            } else {
-                $contentObject = $this->getContentObject($name);
-                if ($contentObject) {
-                    $content .= $this->render($contentObject, $conf);
-                }
-            }
-            if ($timeTracker->LR) {
-                $timeTracker->pull($content);
+        $timeTracker = $this->getTimeTracker();
+        $name = trim($name);
+        if ($timeTracker->LR) {
+            $timeTracker->push($TSkey, $name);
+        }
+        // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something')
+        if (isset($name[0]) && $name[0] === '<') {
+            $key = trim(substr($name, 1));
+            $cF = GeneralUtility::makeInstance(TypoScriptParser::class);
+            // $name and $conf is loaded with the referenced values.
+            $confOverride = is_array($conf) ? $conf : [];
+            [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup);
+            $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride);
+            // Getting the cObject
+            $timeTracker->incStackPointer();
+            $content .= $this->cObjGetSingle($name, $conf, $key);
+            $timeTracker->decStackPointer();
+        } else {
+            $contentObject = $this->getContentObject($name);
+            if ($contentObject) {
+                $content .= $this->render($contentObject, $conf);
             }
         }
-        // Increasing on exit...
-        $this->getTypoScriptFrontendController()->cObjectDepthCounter++;
+        if ($timeTracker->LR) {
+            $timeTracker->pull($content);
+        }
         return $content;
     }
 
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index c39fd55dde9c..4f74261fc933 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -438,6 +438,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * Checking that the function is not called eternally. This is done by
      * interrupting at a depth of 50
      * @var int
+     * @deprecated since v11, will be removed in v12.
      */
     public $cObjectDepthCounter = 50;
 
diff --git a/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php b/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php
index d73752ec44bd..e1bc7daa618c 100644
--- a/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php
@@ -53,7 +53,6 @@ class AbstractPluginTest extends UnitTestCase
         parent::setUp();
 
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
-        $tsfe->cObjectDepthCounter = 100;
         $tsfe->getLanguage(Argument::cetera())->willReturn(
             $this->createSiteWithDefaultLanguage()->getLanguageById(0)
         );
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php
index 34adf66b907e..17ab1bb71521 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php
@@ -870,4 +870,9 @@ return [
             'Deprecation-94958-ContentObjectRendererProperties.rst',
         ],
     ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->cObjectDepthCounter' => [
+        'restFiles' => [
+            'Deprecation-94957-TypoScriptFrontendController-cObjectDepthCounter.rst'
+        ],
+    ],
 ];
-- 
GitLab