Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.207
diff -u -p -r1.207 module.inc
--- includes/module.inc	24 Nov 2010 16:47:44 -0000	1.207
+++ includes/module.inc	25 Nov 2010 22:26:49 -0000
@@ -592,7 +592,20 @@ function module_disable($module_list, $d
  *   implemented in that module.
  */
 function module_hook($module, $hook) {
-  return function_exists($module . '_' . $hook);
+  $function = $module . '_' . $hook;
+  if (function_exists($function)) {
+    return TRUE;
+  }
+  // If the hook implementation does not exist, check whether it may live in an
+  // optional include file registered via hook_hook_info().
+  $hook_info = module_hook_info();
+  if (isset($hook_info[$hook]['group'])) {
+    module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
+    if (function_exists($function)) {
+      return TRUE;
+    }
+  }
+  return FALSE;
 }
 
 /**
@@ -660,7 +673,9 @@ function module_implements($hook, $sort 
     $list = module_list(FALSE, FALSE, $sort);
     foreach ($list as $module) {
       $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
-      if (module_hook($module, $hook)) {
+      // Since module_hook() may needlessly try to load the include file again,
+      // function_exists() is used directly here.
+      if (function_exists($module . '_' . $hook)) {
         $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
       }
     }
@@ -678,9 +693,11 @@ function module_implements($hook, $sort 
         module_load_include('inc', $module, "$module.$group");
       }
       // It is possible that a module removed a hook implementation without the
-      // implementations cache being rebuilt yet, so we check module_hook() on
-      // each request to avoid undefined function errors.
-      if (!module_hook($module, $hook)) {
+      // implementations cache being rebuilt yet, so we check whether the
+      // function exists on each request to avoid undefined function errors.
+      // Since module_hook() may needlessly try to load the include file again,
+      // function_exists() is used directly here.
+      if (!function_exists($module . '_' . $hook)) {
         // Clear out the stale implementation from the cache and force a cache
         // refresh to forget about no longer existing hook implementations.
         unset($implementations[$hook][$module]);
@@ -696,9 +713,17 @@ function module_implements($hook, $sort 
  * Retrieve a list of what hooks are explicitly declared.
  */
 function module_hook_info() {
-  $hook_info = &drupal_static(__FUNCTION__, array());
+  // This function is indirectly invoked from bootstrap_invoke_all(), in which
+  // case common.inc, subsystems, and modules are not loaded yet, so it does not
+  // make sense to support hook groups resp. lazy-loaded include files prior to
+  // full bootstrap.
+  if (drupal_bootstrap(NULL, FALSE) != DRUPAL_BOOTSTRAP_FULL) {
+    return array();
+  }
+  $hook_info = &drupal_static(__FUNCTION__);
 
-  if (empty($hook_info)) {
+  if (!isset($hook_info)) {
+    $hook_info = array();
     $cache = cache_get('hook_info', 'cache_bootstrap');
     if ($cache === FALSE) {
       // Rebuild the cache and save it.
@@ -768,6 +793,7 @@ function module_invoke() {
     return call_user_func_array($module . '_' . $hook, $args);
   }
 }
+
 /**
  * Invoke a hook in all enabled modules that implement it.
  *
Index: modules/simpletest/tests/module.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module.test,v
retrieving revision 1.25
diff -u -p -r1.25 module.test
--- modules/simpletest/tests/module.test	24 Nov 2010 16:47:44 -0000	1.25
+++ modules/simpletest/tests/module.test	25 Nov 2010 22:23:46 -0000
@@ -112,6 +112,26 @@ class ModuleUnitTest extends DrupalWebTe
   }
 
   /**
+   * Test that module_invoke() can load a hook defined in hook_hook_info().
+   */
+  function testModuleInvoke() {
+    module_enable(array('module_test'), FALSE);
+    $this->resetAll();
+    $this->drupalGet('module-test/hook-dynamic-loading-invoke');
+    $this->assertText('success!', t('module_invoke() dynamically loads a hook defined in hook_hook_info().'));
+  }
+
+  /**
+   * Test that module_invoke_all() can load a hook defined in hook_hook_info().
+   */
+  function testModuleInvokeAll() {
+    module_enable(array('module_test'), FALSE);
+    $this->resetAll();
+    $this->drupalGet('module-test/hook-dynamic-loading-invoke-all');
+    $this->assertText('success!', t('module_invoke_all() dynamically loads a hook defined in hook_hook_info().'));
+  }
+
+  /**
    * Test dependency resolution.
    */
   function testDependencyResolution() {
Index: modules/simpletest/tests/module_test.file.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module_test.file.inc,v
retrieving revision 1.2
diff -u -p -r1.2 module_test.file.inc
--- modules/simpletest/tests/module_test.file.inc	26 May 2010 19:51:01 -0000	1.2
+++ modules/simpletest/tests/module_test.file.inc	25 Nov 2010 22:23:46 -0000
@@ -10,5 +10,5 @@
  * Implements hook_test_hook().
  */
 function module_test_test_hook() {
-
+  return array('module_test' => 'success!');
 }
Index: modules/simpletest/tests/module_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module_test.module,v
retrieving revision 1.9
diff -u -p -r1.9 module_test.module
--- modules/simpletest/tests/module_test.module	20 Nov 2010 03:34:30 -0000	1.9
+++ modules/simpletest/tests/module_test.module	25 Nov 2010 22:23:46 -0000
@@ -52,6 +52,45 @@ function module_test_hook_info() {
 }
 
 /**
+ * Implements hook_menu().
+ */
+function module_test_menu() {
+  $items['module-test/hook-dynamic-loading-invoke'] = array(
+    'title' => 'Test hook dynamic loading (invoke)',
+    'page callback' => 'module_test_hook_dynamic_loading_invoke',
+    'access arguments' => array('access content'),
+  );
+  $items['module-test/hook-dynamic-loading-invoke-all'] = array(
+    'title' => 'Test hook dynamic loading (invoke_all)',
+    'page callback' => 'module_test_hook_dynamic_loading_invoke_all',
+    'access arguments' => array('access content'),
+  );
+  return $items;
+}
+
+/**
+ * Page callback for 'hook dynamic loading' test.
+ *
+ * If the hook is dynamically loaded correctly, the menu callback should
+ * return 'success!'.
+ */
+function module_test_hook_dynamic_loading_invoke() {
+  $result = module_invoke('module_test', 'test_hook');
+  return $result['module_test'];
+}
+
+/**
+ * Page callback for 'hook dynamic loading' test.
+ *
+ * If the hook is dynamically loaded correctly, the menu callback should
+ * return 'success!'.
+ */
+function module_test_hook_dynamic_loading_invoke_all() {
+  $result = module_invoke_all('test_hook');
+  return $result['module_test'];
+}
+
+/**
  * Implements hook_modules_enabled().
  */
 function module_test_modules_enabled($modules) {
