From b16df24b081bd8dbc3affaa3665ec62041cf04db Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 29 Jun 2022 15:21:43 +0100
Subject: [PATCH] Issue #2896993 by cburschka, andypost, mxr576, jedihe:
 Decorated services crash on serialization

---
 .../DependencySerializationTraitPass.php      | 22 ++++++++++++
 .../decorated_service_test.info.yml           |  5 +++
 .../decorated_service_test.services.yml       | 17 ++++++++++
 .../src/TestService.php                       |  7 ++++
 .../src/TestServiceDecorator.php              |  7 ++++
 .../tests/src/Kernel/DecoratedServiceTest.php | 34 +++++++++++++++++++
 6 files changed, 92 insertions(+)
 create mode 100644 core/modules/system/tests/modules/decorated_service_test/decorated_service_test.info.yml
 create mode 100644 core/modules/system/tests/modules/decorated_service_test/decorated_service_test.services.yml
 create mode 100644 core/modules/system/tests/modules/decorated_service_test/src/TestService.php
 create mode 100644 core/modules/system/tests/modules/decorated_service_test/src/TestServiceDecorator.php
 create mode 100644 core/modules/system/tests/src/Kernel/DecoratedServiceTest.php

diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
index 495228396786..a0ba7f0225e3 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
@@ -16,12 +16,34 @@ class DependencySerializationTraitPass implements CompilerPassInterface {
    * {@inheritdoc}
    */
   public function process(ContainerBuilder $container) {
+    $decorations = new \SplPriorityQueue();
+    $order = PHP_INT_MAX;
+
     foreach ($container->getDefinitions() as $service_id => $definition) {
       // Only add the property to services that are public (as private services
       // can not be reloaded through Container::get()) and are objects.
       if (!$definition->hasTag('parameter_service') && $definition->isPublic()) {
         $definition->setProperty('_serviceId', $service_id);
       }
+
+      if ($decorated = $definition->getDecoratedService()) {
+        $decorations->insert([$service_id, $definition], [$decorated[2], --$order]);
+      }
+    }
+
+    foreach ($decorations as list($service_id, $definition)) {
+      list($inner, $renamedId) = $definition->getDecoratedService();
+      if (!$renamedId) {
+        $renamedId = $service_id . '.inner';
+      }
+
+      $original = $container->getDefinition($inner);
+      if ($original->isPublic()) {
+        // The old service is renamed.
+        $original->setProperty('_serviceId', $renamedId);
+        // The decorating service takes over the old ID.
+        $definition->setProperty('_serviceId', $inner);
+      }
     }
   }
 
diff --git a/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.info.yml b/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.info.yml
new file mode 100644
index 000000000000..08d66bdb6904
--- /dev/null
+++ b/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Decorated Service Test'
+type: module
+description: 'Support module for decorated service test.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.services.yml b/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.services.yml
new file mode 100644
index 000000000000..a00c56c7d0cc
--- /dev/null
+++ b/core/modules/system/tests/modules/decorated_service_test/decorated_service_test.services.yml
@@ -0,0 +1,17 @@
+services:
+  test_service:
+    class: 'Drupal\decorated_service_test\TestService'
+  test_service_decorator:
+    class: 'Drupal\decorated_service_test\TestServiceDecorator'
+    public: false
+    decorates: test_service
+  test_service2:
+    class: 'Drupal\decorated_service_test\TestService'
+  test_service2_decorator:
+    class: 'Drupal\decorated_service_test\TestServiceDecorator'
+    public: false
+    decorates: test_service2
+  test_service2_decorator2:
+    class: 'Drupal\decorated_service_test\TestServiceDecorator'
+    public: false
+    decorates: test_service2
diff --git a/core/modules/system/tests/modules/decorated_service_test/src/TestService.php b/core/modules/system/tests/modules/decorated_service_test/src/TestService.php
new file mode 100644
index 000000000000..8e708019ffae
--- /dev/null
+++ b/core/modules/system/tests/modules/decorated_service_test/src/TestService.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\decorated_service_test;
+
+class TestService {
+
+}
diff --git a/core/modules/system/tests/modules/decorated_service_test/src/TestServiceDecorator.php b/core/modules/system/tests/modules/decorated_service_test/src/TestServiceDecorator.php
new file mode 100644
index 000000000000..30dcb023bd95
--- /dev/null
+++ b/core/modules/system/tests/modules/decorated_service_test/src/TestServiceDecorator.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\decorated_service_test;
+
+class TestServiceDecorator extends TestService {
+
+}
diff --git a/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php b/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php
new file mode 100644
index 000000000000..854662df18bb
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\system\Kernel;
+
+use Drupal\decorated_service_test\TestServiceDecorator;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Test handling of decorated services in DependencySerializationTraitPass.
+ *
+ * @group system
+ */
+class DecoratedServiceTest extends KernelTestBase {
+
+  protected static $modules = [
+    'decorated_service_test',
+  ];
+
+  /**
+   * Check that decorated services keep their original service ID.
+   */
+  public function testDecoratedServiceId() {
+    // Service decorated once.
+    $test_service = $this->container->get('test_service');
+    $this->assertEquals('test_service', $test_service->_serviceId);
+    $this->assertInstanceOf(TestServiceDecorator::class, $test_service);
+
+    // Service decorated twice.
+    $test_service2 = $this->container->get('test_service2');
+    $this->assertEquals('test_service2', $test_service2->_serviceId);
+    $this->assertInstanceOf(TestServiceDecorator::class, $test_service2);
+  }
+
+}
-- 
GitLab