Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.506
diff -u -p -r1.506 form.inc
--- includes/form.inc	21 Oct 2010 20:46:58 -0000	1.506
+++ includes/form.inc	26 Oct 2010 01:12:36 -0000
@@ -1862,7 +1862,7 @@ function _form_builder_handle_input_elem
       // Avoid image buttons (which come with garbage value), so we only get value
       // for the button actually clicked.
       if (!isset($element['#value']) && empty($element['#has_garbage_value'])) {
-        $element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : '';
+        $element['#value'] = (isset($element['#default_value']) || array_key_exists('#default_value', $element)) ? $element['#default_value'] : '';
       }
     }
   }
@@ -2072,13 +2072,14 @@ function form_type_image_button_value($f
  *   The data that will appear in the $element_state['values'] collection
  *   for this element. Return nothing to use the default.
  */
-function form_type_checkbox_value($element, $input = FALSE) {
+function form_type_checkbox_value(&$element, $input = FALSE) {
   if ($input !== FALSE) {
     // 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'.
-    return isset($input) ? $element['#return_value'] : 0;
+    // On form submission, the #value of
+    // - a checked checkbox element is #return_value.
+    // - an unchecked checkbox element is NULL.
+    return isset($input) ? $element['#return_value'] : NULL;
   }
 }
 
@@ -2109,7 +2110,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]);
@@ -2790,6 +2792,25 @@ function form_process_radios($element) {
 }
 
 /**
+ * Processes an element of #type 'checkbox' to set the #checked property.
+ */
+function form_process_checkbox($element) {
+  // 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, NULL and 0 simply means unchecked.
+  if ($element['#value'] === TRUE || $element['#value'] === FALSE || $element['#value'] === 0 || $element['#value'] === NULL) {
+    $element['#checked'] = (bool) $element['#value'];
+  }
+  else {
+    $element['#checked'] = ($element['#value'] == $element['#return_value']);
+  }
+  return $element;
+}
+
+/**
  * Returns HTML for a checkbox form element.
  *
  * @param $variables
@@ -2804,12 +2825,12 @@ function theme_checkbox($variables) {
   $element = $variables['element'];
   $t = get_t();
   $element['#attributes']['type'] = 'checkbox';
-  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 ($element['#checked']) {
     $element['#attributes']['checked'] = 'checked';
   }
+  // #return_value ends up as the HTML attribute 'value', so sanitize it.
+  $element['#return_value'] = check_plain($element['#return_value']);
+  element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
   _form_set_class($element, array('form-checkbox'));
 
   return '<input' . drupal_attributes($element['#attributes']) . ' />';
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.130
diff -u -p -r1.130 common.test
--- modules/simpletest/tests/common.test	8 Oct 2010 15:36:12 -0000	1.130
+++ modules/simpletest/tests/common.test	25 Oct 2010 23:45:35 -0000
@@ -1533,6 +1533,7 @@ class DrupalRenderTestCase extends Drupa
 
     $element = array(
       '#type' => 'checkbox',
+      '#value' => TRUE,
       '#title' => $this->randomName(),
     );
     $this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'checkbox'));
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.72
diff -u -p -r1.72 form.test
--- modules/simpletest/tests/form.test	4 Oct 2010 18:00:46 -0000	1.72
+++ modules/simpletest/tests/form.test	26 Oct 2010 00:20:44 -0000
@@ -1352,3 +1352,53 @@ class FormsFileInclusionTestCase extends
     $this->assertText('Submit callback called.');
   }
 }
+
+/**
+ * Test checkbox element.
+ */
+class FormCheckboxTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => '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(0, FALSE, NULL, TRUE, '0', '', 1, '1', 'foobar');
+    $return_value_array = array('0', '', 1, '1', 'foobar');
+    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_key < 3) {
+          $checked = FALSE;
+        }
+        elseif ($default_value_key == 3) {
+          $checked = TRUE;
+        }
+        else {
+          if ($return_value === '0' || $return_value === '') {
+            $checked = $default_value === $return_value;
+          }
+          else {
+            $checked = $default_value == $return_value;
+          }
+        }
+        $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);
+      }
+    }
+  }
+}
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.52
diff -u -p -r1.52 form_test.module
--- modules/simpletest/tests/form_test.module	20 Oct 2010 01:15:58 -0000	1.52
+++ modules/simpletest/tests/form_test.module	25 Oct 2010 23:45:35 -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;
+}
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.983
diff -u -p -r1.983 system.module
--- modules/system/system.module	21 Oct 2010 12:09:41 -0000	1.983
+++ modules/system/system.module	26 Oct 2010 01:07:45 -0000
@@ -407,8 +407,9 @@ function system_element_info() {
   );
   $types['checkbox'] = array(
     '#input' => TRUE,
+    '#default_value' => NULL,
     '#return_value' => 1,
-    '#process' => array('ajax_process_form'),
+    '#process' => array('form_process_checkbox', 'ajax_process_form'),
     '#theme' => 'checkbox',
     '#theme_wrappers' => array('form_element'),
     '#title_display' => 'after',
