diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index ff4773f..008c062 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -400,6 +400,7 @@ function aggregator_block_save($delta = '', $edit = array()) {
 function aggregator_block_view($delta = '') {
   if (user_access('access news feeds')) {
     $block = array();
+    $result = array();
     list($type, $id) = explode('-', $delta);
     switch ($type) {
       case 'feed':
@@ -455,13 +456,6 @@ function aggregator_save_category($edit) {
       db_delete('aggregator_category')
         ->condition('cid', $edit['cid'])
         ->execute();
-      // Make sure there is no active block for this category.
-      if (module_exists('block')) {
-        db_delete('block')
-          ->condition('module', 'aggregator')
-          ->condition('delta', 'category-' . $edit['cid'])
-          ->execute();
-      }
       $edit['title'] = '';
       $op = 'delete';
     }
@@ -522,13 +516,6 @@ function aggregator_save_feed($edit) {
     db_delete('aggregator_item')
       ->condition('fid', $edit['fid'])
       ->execute();
-    // Make sure there is no active block for this feed.
-    if (module_exists('block')) {
-      db_delete('block')
-        ->condition('module', 'aggregator')
-        ->condition('delta', 'feed-' . $edit['fid'])
-        ->execute();
-    }
   }
   elseif (!empty($edit['title'])) {
     $edit['fid'] = db_insert('aggregator_feed')
diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc
index 2b3c2dd..1f41c75 100644
--- a/core/modules/block/block.admin.inc
+++ b/core/modules/block/block.admin.inc
@@ -653,14 +653,6 @@ function block_custom_block_delete_submit($form, &$form_state) {
   db_delete('block_custom')
     ->condition('bid', $form_state['values']['bid'])
     ->execute();
-  db_delete('block')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
-  db_delete('block_role')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
   drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info'])));
   cache_clear_all();
   $form_state['redirect'] = 'admin/structure/block';
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 4d942ed..95debbd 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -255,9 +255,11 @@ function block_block_save($delta = 0, $edit = array()) {
  */
 function block_block_view($delta = '') {
   $block = db_query('SELECT body, format FROM {block_custom} WHERE bid = :bid', array(':bid' => $delta))->fetchObject();
-  $data['subject'] = NULL;
-  $data['content'] = check_markup($block->body, $block->format, '', TRUE);
-  return $data;
+  if (isset($block->body)) {
+    $data['subject'] = NULL;
+    $data['content'] = check_markup($block->body, $block->format, '', TRUE);
+    return $data;
+  }
 }
 
 /**
@@ -455,8 +457,6 @@ function _block_rehash($theme = NULL) {
     }
     // Blocks stored in the database override the blocks defined in code.
     $current_blocks[$block->module][$block->delta] = get_object_vars($block);
-    // Preserve this block.
-    $bids[$block->bid] = $block->bid;
   }
   drupal_alter('block_info', $current_blocks, $theme, $code_blocks);
   foreach ($current_blocks as $module => $module_blocks) {
@@ -482,31 +482,78 @@ function _block_rehash($theme = NULL) {
         $block['status'] = 0;
         $block['region'] = BLOCK_REGION_NONE;
       }
-      // There is no point saving disabled blocks. Still, we need to save them
-      // because the 'title' attribute is saved to the {blocks} table.
       if (isset($block['bid'])) {
         // If the block has a bid property, it comes from the database and
         // the record needs to be updated, so set the primary key to 'bid'
         // before passing to drupal_write_record().
         $primary_keys = array('bid');
-        // Remove a block from the list of blocks to keep if it became disabled.
-        unset($bids[$block['bid']]);
       }
       else {
         $primary_keys = array();
       }
       drupal_write_record('block', $block, $primary_keys);
+      // Now that the block has been saved, it is guaranteed to have an ID.
+      // Mark it to be preserved.
+      $bids[$block['bid']] = $block['bid'];
       // Add to the list of blocks we return.
       $blocks[] = $block;
     }
   }
   if ($bids) {
-    // Remove disabled that are no longer defined by the code from the
-    // database.
-    db_delete('block')
+    // Remove disabled blocks that are no longer defined by the code from the
+    // database. However, if the block belongs to a disabled module, we should
+    // keep it so that it retains the same settings if the module is ever
+    // re-enabled. If the module is uninstalled, its blocks will be deleted
+    // from the database then; see block_modules_uninstalled().
+    $blocks_to_delete = db_select('block', 'b')
+      ->fields('b', array('bid', 'module', 'delta'))
       ->condition('bid', $bids, 'NOT IN')
+      ->condition('module', module_list(), 'IN')
       ->condition('theme', $theme)
-      ->execute();
+      ->execute()
+      ->fetchAllAssoc('bid');
+    if (!empty($blocks_to_delete)) {
+      $bids_to_delete = array_keys($blocks_to_delete);
+      // First delete the main record from the {block} table.
+      db_delete('block')
+        ->condition('bid', $bids_to_delete, 'IN')
+        ->execute();
+      // Now delete the record from the {block_role} table, but only if this is
+      // the last instance of the block remaining (since the records in
+      // {block_role} are shared across themes).
+      $delete_from_block_role = FALSE;
+      $or = db_or();
+      foreach ($blocks_to_delete as $block_to_delete) {
+        $condition = db_and()
+          ->condition('module', $block_to_delete->module)
+          ->condition('delta', $block_to_delete->delta);
+          $or->condition($condition);
+      }
+      $remaining_block_records = db_select('block', 'b')
+        ->fields('b', array('module', 'delta'))
+        ->condition($or)
+        ->execute()
+        ->fetchAll();
+      $block_record_exists = array();
+      foreach ($remaining_block_records as $block_record) {
+        $block_record_exists[$block_record->module][$block_record->delta] = TRUE;
+      }
+      $or = db_or();
+      foreach ($blocks_to_delete as $block_to_delete) {
+        if (empty($block_record_exists[$block_to_delete->module][$block_to_delete->delta])) {
+          $delete_from_block_role = TRUE;
+          $condition = db_and()
+            ->condition('module', $block_to_delete->module)
+            ->condition('delta', $block_to_delete->delta);
+          $or->condition($condition);
+        }
+      }
+      if ($delete_from_block_role) {
+        db_delete('block_role')
+          ->condition($or)
+          ->execute();
+      }
+    }
   }
   return $blocks;
 }
@@ -606,7 +653,7 @@ function block_form_user_profile_form_alter(&$form, &$form_state) {
   $blocks = array();
   foreach ($result as $block) {
     $data = module_invoke($block->module, 'block_info');
-    if ($data[$block->delta]['info']) {
+    if (!empty($data[$block->delta]['info'])) {
       $blocks[$block->module][$block->delta] = array(
         '#type' => 'checkbox',
         '#title' => check_plain($data[$block->delta]['info']),
@@ -996,20 +1043,6 @@ function block_user_role_delete($role) {
 }
 
 /**
- * Implements hook_menu_delete().
- */
-function block_menu_delete($menu) {
-  db_delete('block')
-    ->condition('module', 'menu')
-    ->condition('delta', $menu['menu_name'])
-    ->execute();
-  db_delete('block_role')
-    ->condition('module', 'menu')
-    ->condition('delta', $menu['menu_name'])
-    ->execute();
-}
-
-/**
  * Implements hook_admin_paths().
  */
 function block_admin_paths() {
diff --git a/core/modules/block/block.test b/core/modules/block/block.test
index dbd7dc4..2b5b664 100644
--- a/core/modules/block/block.test
+++ b/core/modules/block/block.test
@@ -380,6 +380,32 @@ class BlockTestCase extends DrupalWebTestCase {
     // Verify that the database is updated with the new caching mode.
     $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
     $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
+
+    // Make sure the dynamic block is stored in the database.
+    $block = db_query("SELECT bid FROM {block} WHERE module = 'block_test' AND delta = 'test_dynamic_block'")->fetchField();
+    $this->assertTrue(!empty($block), t('Test block is in the database.'));
+
+    // Disable the module, and flush all caches to trigger _block_rehash(). The
+    // block should still be in the database.
+    module_disable(array('block_test'));
+    drupal_flush_all_caches();
+    $block = db_query("SELECT bid FROM {block} WHERE module = 'block_test' AND delta = 'test_dynamic_block'")->fetchField();
+    $this->assertTrue(!empty($block), t('Test block is still in the database after the module is disabled.'));
+
+    // Re-enable the module, and trigger _block_rehash() again. The block
+    // should still be in the database.
+    module_enable(array('block_test'));
+    drupal_flush_all_caches();
+    $block = db_query("SELECT bid FROM {block} WHERE module = 'block_test' AND delta = 'test_dynamic_block'")->fetchField();
+    $this->assertTrue(!empty($block), t('Test block is still in the database after the module is re-enabled.'));
+
+    // Set a variable that triggers hook_block_info() to no longer return the
+    // block, and trigger _block_rehash() again. The block should no longer be
+    // in the database.
+    variable_set('block_test_dynamic_block', FALSE);
+    drupal_flush_all_caches();
+    $block = db_query("SELECT bid FROM {block} WHERE module = 'block_test' AND delta = 'test_dynamic_block'")->fetchField();
+    $this->assertTrue(empty($block), t('Test block is no longer in the database when it is no longer defined by hook_block_info().'));
   }
 }
 
diff --git a/core/modules/block/tests/block_test.module b/core/modules/block/tests/block_test.module
index 5e06d5c..80aabdf 100644
--- a/core/modules/block/tests/block_test.module
+++ b/core/modules/block/tests/block_test.module
@@ -25,6 +25,12 @@ function block_test_block_info() {
   $blocks['test_html_id'] = array(
     'info' => t('Test block html id'),
   );
+
+  if (variable_get('block_test_dynamic_block', TRUE)) {
+    $blocks['test_dynamic_block'] = array(
+      'info' => t('Test dynamic block'),
+    );
+  }
   return $blocks;
 }
 
diff --git a/core/modules/dashboard/dashboard.module b/core/modules/dashboard/dashboard.module
index 115bc8f..8848cde 100644
--- a/core/modules/dashboard/dashboard.module
+++ b/core/modules/dashboard/dashboard.module
@@ -164,7 +164,7 @@ function dashboard_page_build(&$page) {
             if (!isset($block_info[$item['#block']->module])) {
               $block_info[$item['#block']->module] = module_invoke($item['#block']->module, 'block_info');
             }
-            $item['#block']->subject = $block_info[$item['#block']->module][$item['#block']->delta]['info'];
+            $item['#block']->subject = isset($block_info[$item['#block']->module][$item['#block']->delta]['info']) ? $block_info[$item['#block']->module][$item['#block']->delta]['info'] : NULL;
           }
           $blocks_found[$item['#block']->module . '_' . $item['#block']->delta] = TRUE;
         }
@@ -185,7 +185,7 @@ function dashboard_page_build(&$page) {
           if (!isset($block_info[$block->module])) {
             $block_info[$block->module] = module_invoke($block->module, 'block_info');
           }
-          $block->subject = t('@title', array('@title' => $block_info[$block->module][$block->delta]['info']));
+          $block->subject = isset($block_info[$block->module][$block->delta]['info']) ? t('@title', array('@title' => $block_info[$block->module][$block->delta]['info'])) : NULL;
           $block_render = array($block->module . '_' . $block->delta => $block);
           $build = _block_get_renderable_region($block_render);
           $page['content']['dashboard'][$block->region][] = $build;
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 1840cec..f1c3602 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -484,13 +484,15 @@ function menu_block_info() {
  */
 function menu_block_view($delta = '') {
   $menus = menu_get_menus(FALSE);
-  $data['subject'] = check_plain($menus[$delta]);
-  $data['content'] = menu_tree($delta);
-  // Add contextual links for this block.
-  if (!empty($data['content'])) {
-    $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($delta));
+  if (isset($menus[$delta])) {
+    $data['subject'] = check_plain($menus[$delta]);
+    $data['content'] = menu_tree($delta);
+    // Add contextual links for this block.
+    if (!empty($data['content'])) {
+      $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($delta));
+    }
+    return $data;
   }
-  return $data;
 }
 
 /**
