From e105186172a34f4c3ba05af594e34dfa63083b72 Mon Sep 17 00:00:00 2001
From: David Rothstein <drothstein@gmail.com>
Date: Sat, 10 Oct 2015 11:40:33 -0400
Subject: [PATCH] Issue #496170 by stefan.r, chx, Fabianx, jbrauer,
 David_Rothstein, roderik, rwohleb, pounard, kenorb, Jose Reyero, joelpittet,
 catch: module_implements() cache can be polluted by module_invoke_all() being
 called (in)directly prior to full bootstrap completion

---
 CHANGELOG.txt                               |  3 ++
 includes/common.inc                         |  5 +++
 includes/module.inc                         |  5 +++
 modules/simpletest/simpletest.info          |  1 +
 modules/simpletest/tests/boot.test          | 38 +++++++++++++++++++++
 modules/simpletest/tests/boot_test_1.info   |  6 ++++
 modules/simpletest/tests/boot_test_1.module | 21 ++++++++++++
 modules/simpletest/tests/boot_test_2.info   |  6 ++++
 modules/simpletest/tests/boot_test_2.module | 13 +++++++
 9 files changed, 98 insertions(+)
 create mode 100644 modules/simpletest/tests/boot.test
 create mode 100644 modules/simpletest/tests/boot_test_1.info
 create mode 100644 modules/simpletest/tests/boot_test_1.module
 create mode 100644 modules/simpletest/tests/boot_test_2.info
 create mode 100644 modules/simpletest/tests/boot_test_2.module

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 069595046350..4e9e4f0da12c 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,9 @@
 
 Drupal 7.40, xxxx-xx-xx (development version)
 -----------------------
+- Fixed various bugs that occurred after hooks were invoked early in the Drupal
+  bootstrap and that caused module_implements() and drupal_alter() to cache an
+  incomplete set of hook implementations for later use.
 - Set the X-Content-Type-Options header to "nosniff" when possible, to prevent
   certain web browsers from picking an unsafe MIME type.
 - Prevented the database API from executing multiple queries at once on MySQL,
diff --git a/includes/common.inc b/includes/common.inc
index ceac115a5204..ef71ee8b6a78 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5212,6 +5212,11 @@ function _drupal_bootstrap_full() {
   fix_gpc_magic();
   // Load all enabled modules
   module_load_all();
+  // Reset drupal_alter() and module_implements() static caches as these
+  // include implementations for vital modules only when called early on
+  // in the bootstrap.
+  drupal_static_reset('drupal_alter');
+  drupal_static_reset('module_implements');
   // Make sure all stream wrappers are registered.
   file_get_stream_wrappers();
   // Ensure mt_rand is reseeded, to prevent random values from one page load
diff --git a/includes/module.inc b/includes/module.inc
index 076992ca9421..7bf619b47973 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -867,6 +867,11 @@ function module_hook_info() {
  * @see module_implements()
  */
 function module_implements_write_cache() {
+  // The list of implementations includes vital modules only before full
+  // bootstrap, so do not write cache if we are not fully bootstrapped yet.
+  if (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL) {
+    return;
+  }
   $implementations = &drupal_static('module_implements');
   if (isset($implementations['#write_cache'])) {
     unset($implementations['#write_cache']);
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index 7b139ba3d2f5..1aec619f5b68 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -11,6 +11,7 @@ configure = admin/config/development/testing/settings
 files[] = tests/actions.test
 files[] = tests/ajax.test
 files[] = tests/batch.test
+files[] = tests/boot.test
 files[] = tests/bootstrap.test
 files[] = tests/cache.test
 files[] = tests/common.test
diff --git a/modules/simpletest/tests/boot.test b/modules/simpletest/tests/boot.test
new file mode 100644
index 000000000000..562b082e8e73
--- /dev/null
+++ b/modules/simpletest/tests/boot.test
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Perform early bootstrap tests.
+ */
+class EarlyBootstrapTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name'  => 'Early bootstrap test',
+      'description'  => 'Confirm that calling module_implements() during early bootstrap does not pollute the module_implements() cache.',
+      'group' => 'System',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('boot_test_1', 'boot_test_2');
+  }
+
+  /**
+   * Test hook_boot() on both regular and "early exit" pages.
+   */
+  public function testHookBoot() {
+    $paths = array('', 'early_exit');
+    foreach ($paths as $path) {
+      // Empty the module_implements() caches.
+      module_implements(NULL, FALSE, TRUE);
+      // Do a request to the front page, which will call module_implements()
+      // during hook_boot().
+      $this->drupalGet($path);
+      // Reset the static cache so we get implementation data from the persistent
+      // cache.
+      drupal_static_reset();
+      // Make sure we get a full list of all modules implementing hook_help().
+      $modules = module_implements('help');
+      $this->assertTrue(in_array('boot_test_2', $modules));
+    }
+  }
+}
diff --git a/modules/simpletest/tests/boot_test_1.info b/modules/simpletest/tests/boot_test_1.info
new file mode 100644
index 000000000000..a6f2ffb6064d
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_1.info
@@ -0,0 +1,6 @@
+name = Early bootstrap tests
+description = A support module for hook_boot testing.
+core = 7.x
+package = Testing
+version = VERSION
+hidden = TRUE
diff --git a/modules/simpletest/tests/boot_test_1.module b/modules/simpletest/tests/boot_test_1.module
new file mode 100644
index 000000000000..a452e289745d
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_1.module
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Tests calling module_implements() during hook_boot() invocation.
+ */
+
+/**
+ * Implements hook_boot().
+ */
+function boot_test_1_boot() {
+  // Calling module_implements during hook_boot() will return "vital" modules
+  // only, and this list of modules will be statically cached.
+  module_implements('help');
+  // Define a special path to test that the static cache isn't written away
+  // if we exit before having completed the bootstrap.
+  if ($_GET['q'] == 'early_exit') {
+    module_implements_write_cache();
+    exit();
+  }
+}
diff --git a/modules/simpletest/tests/boot_test_2.info b/modules/simpletest/tests/boot_test_2.info
new file mode 100644
index 000000000000..f421997d466f
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_2.info
@@ -0,0 +1,6 @@
+name = Early bootstrap tests
+description = A support module for hook_boot hook testing.
+core = 7.x
+package = Testing
+version = VERSION
+hidden = TRUE
diff --git a/modules/simpletest/tests/boot_test_2.module b/modules/simpletest/tests/boot_test_2.module
new file mode 100644
index 000000000000..c3ab8d61dd9b
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_2.module
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Defines a hook_help() implementation in a non-"bootstrap" module.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function boot_test_2_help($path, $arg) {
+  // Empty hook.
+}
-- 
GitLab