=== modified file 'includes/form.inc'
--- includes/form.inc	2010-10-21 20:46:58 +0000
+++ includes/form.inc	2010-10-26 04:26:22 +0000
@@ -163,7 +163,7 @@
  *   Any additional arguments are passed on to the functions called by
  *   drupal_get_form(), including the unique form constructor function. For
  *   example, the node_edit form requires that a node object is passed in here
- *   when it is called. These are available to implementations of 
+ *   when it is called. These are available to implementations of
  *   hook_form_alter() and hook_form_FORM_ID_alter() as the array
  *   $form_state['build_info']['args'].
  *
@@ -2073,11 +2073,17 @@ function form_type_image_button_value($f
  *   for this element. Return nothing to use the default.
  */
 function form_type_checkbox_value($element, $input = FALSE) {
-  if ($input !== FALSE) {
+  if ($input === FALSE) {
+    // For non-existing or NULL default_values return FALSE as it means
+    // unchecked and NULL is changed to empty string later in
+    // _form_builder_handle_input_element().
+    return isset($element['#default_value']) ? $element['#default_value'] : 0;
+  }
+  else {
     // Successful (checked) checkboxes are present with a value (possibly '0').
     // http://www.w3.org/TR/html401/interact/forms.html#successful-controls
-    // For an unchecked checkbox, we return integer 0, so we can explicitly
-    // test for a value different than string '0'.
+    // For an unchecked checkbox, we return 0, so we can explicitly test for a
+    // value different than string '0'.
     return isset($input) ? $element['#return_value'] : 0;
   }
 }
@@ -2109,7 +2115,8 @@ function form_type_checkboxes_value($ele
     // NULL elements from the array before constructing the return value, to
     // simulate the behavior of web browsers (which do not send unchecked
     // checkboxes to the server at all). This will not affect non-programmatic
-    // form submissions, since a checkbox can never legitimately be NULL.
+    // form submissions, since all values in $_POST are strings so they can
+    // never legitimately be NULL.
     foreach ($input as $key => $value) {
       if (!isset($value)) {
         unset($input[$key]);
@@ -2807,7 +2814,7 @@ function theme_checkbox($variables) {
   element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
 
   // Unchecked checkbox has #value of integer 0.
-  if (isset($element['#return_value']) && isset($element['#value']) && $element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
+  if (!empty($element['#checked'])) {
     $element['#attributes']['checked'] = 'checked';
   }
   _form_set_class($element, array('form-checkbox'));
@@ -2859,6 +2866,36 @@ function form_pre_render_conditional_for
   return $element;
 }
 
+/**
+ * Set the #checked attribute of a checkbox element.
+ */
+function form_process_checkbox($element) {
+  // On form submission, the #value of a checked checkbox element is
+  // #return_value. On form submission, the #value of an unchecked checkbox
+  // element is 0. On not submitted forms, the #value of a checkbox is
+  // #default_value.
+  $value = $element['#value'];
+  $return_value = $element['#return_value'];
+  // Most of the time, a relaxed equals check is adequate so that a #value of
+  // '1' matches #return_value 1. The isset in form_process_checkboxes() is
+  // type agnostic too so that won't interfere with this. There are a few
+  // exceptions: TRUE means checked, FALSE simply means unchecked. The value 0
+  // needs special handling because the integer 0 is equal to almost every
+  // string.
+  if (is_bool($value)) {
+    $element['#checked'] = $value;
+  }
+  elseif ($value === 0) {
+    $element['#checked'] = ($return_value === '0');
+  }
+  else {
+    // cast both sides to string to avoid PHP casting them to int and causing
+    // 15 to be equal to 15monkeys.
+    $element['#checked'] = (string)$value === (string)$return_value;
+  }
+  return $element;
+}
+
 function form_process_checkboxes($element) {
   $value = is_array($element['#value']) ? $element['#value'] : array();
   $element['#tree'] = TRUE;
@@ -2872,7 +2909,7 @@ function form_process_checkboxes($elemen
           '#type' => 'checkbox',
           '#processed' => TRUE,
           '#title' => $choice,
-          '#return_value' => $key,
+          '#return_value' => $key === 0 ? '0' : $key,
           '#default_value' => isset($value[$key]) ? $key : NULL,
           '#attributes' => $element['#attributes'],
           '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,

=== modified file 'modules/simpletest/tests/form.test'
--- modules/simpletest/tests/form.test	2010-10-04 18:00:45 +0000
+++ modules/simpletest/tests/form.test	2010-10-26 04:23:48 +0000
@@ -1352,3 +1352,53 @@ class FormsFileInclusionTestCase extends
     $this->assertText('Submit callback called.');
   }
 }
+
+/**
+ * Test checkbox element.
+ */
+class FormCheckboxTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Form API checkbox',
+      'description' => 'Tests form API checkbox handling especially 0, empty string etc.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  function testFormCheckbox() {
+    $default_value_array = array(FALSE, NULL, TRUE, 0, '0', '', 1, '1', 'foobar', '1foobar');
+    $return_value_array = array('0', '', 1, '1', 'foobar', '1foobar');
+    foreach ($default_value_array as $default_value_key => $default_value) {
+      foreach ($return_value_array as $return_value) {
+        $form_array = drupal_get_form('form_test_checkbox_type_juggling', $default_value, $return_value);
+        $form = drupal_render($form_array);
+        if ($default_value === TRUE) {
+          $checked = TRUE;
+        }
+        elseif ($return_value === '0') {
+          $checked = ($default_value === 0 || $default_value === '0');
+        }
+        elseif ($return_value === '') {
+          $checked = ($default_value === '');
+        }
+        elseif ($return_value === 1 || $return_value === '1') {
+          $checked = ($default_value === 1 || $default_value === '1');
+        }
+        elseif ($return_value === 'foobar') {
+          $checked = ($default_value === 'foobar');
+        }
+        elseif ($return_value === '1foobar') {
+          $checked = ($default_value === '1foobar');
+        }
+        $checked_in_html = strpos($form, 'checked') !== FALSE;
+        $message = t('#default_value is !default_value #return_value is !return_value.', array('!default_value' => var_export($default_value, TRUE), '!return_value' => var_export($return_value, TRUE)));
+        $this->assertIdentical($checked, $checked_in_html, $message);
+      }
+    }
+  }
+}

=== modified file 'modules/simpletest/tests/form_test.module'
--- modules/simpletest/tests/form_test.module	2010-10-20 01:15:58 +0000
+++ modules/simpletest/tests/form_test.module	2010-10-25 06:39:59 +0000
@@ -1395,3 +1395,12 @@ function form_test_load_include_custom($
   $form_state['cache'] = TRUE;
   return $form;
 }
+
+function form_test_checkbox_type_juggling($form, $form_state, $default_value, $return_value) {
+  $form['checkbox'] = array(
+    '#type' => 'checkbox',
+    '#return_value' => $return_value,
+    '#default_value' => $default_value,
+  );
+  return $form;
+}

=== modified file 'modules/system/system.module'
--- modules/system/system.module	2010-10-21 12:09:41 +0000
+++ modules/system/system.module	2010-10-26 06:25:30 +0000
@@ -410,6 +410,7 @@ function system_element_info() {
     '#return_value' => 1,
     '#process' => array('ajax_process_form'),
     '#theme' => 'checkbox',
+    '#process' => array('form_process_checkbox'),
     '#theme_wrappers' => array('form_element'),
     '#title_display' => 'after',
   );
@@ -2883,7 +2884,7 @@ function system_get_module_admin_tasks($
           }
           if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
             // Append the parent item's title to the duplicated task's title.
-            // We use $links[$duplicate_path] in case there are triplicates. 
+            // We use $links[$duplicate_path] in case there are triplicates.
             $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
           }
         }

