diff --git includes/common.inc includes/common.inc
index bd51afb..0c32823 100644
--- includes/common.inc
+++ includes/common.inc
@@ -5561,6 +5561,127 @@ function element_get_visible_children(array $elements) {
 }
 
 /**
+ * Sets a value in a nested array.
+ *
+ * Primarily used for form structures and renderable arrays.
+ *
+ * Example:
+ * @code
+ * // Assume you have this in $form:
+ * $form = array();
+ * $form['signature_settings'] = array(
+ *   '#type' => 'fieldset',
+ *   '#title' => t('Signature settings'),
+ * );
+ * $form['signature_settings']['signature'] = array(
+ *   '#type' => 'text_format',
+ *   '#title' => t('Signature'),
+ *  );
+ * // And assume you have this in $parents:
+ * $parents = array('signature_settings', 'signature', '#title');
+ * // Then you can make this function call to alter the element title:
+ * drupal_array_set_value($form, $parents, t('New title'));
+ * @endcode
+ *
+ * This helper function should be used when the depth of the array element you
+ * are changing may vary (that is, the number of parent keys is variable),
+ * because an operation like this can otherwise only be done in one line using
+ * eval(), which is best to avoid:
+ * @code
+ * // Do not do this! Avoid eval().
+ * eval('$form[\'' . implode("']['", $parents) . '\'] = t('New title');');
+ * @endcode
+ *
+ * On the other hand, if the number of array parent keys is static, the value
+ * should always be set directly rather than calling this function. For
+ * instance, in the above example, we could just do:
+ * @code
+ * $form['signature_settings']['signature']['#title'] = t('New title');
+ * @endcode
+ *
+ * @param $array
+ *   A reference to the array to modify.
+ * @param $parents
+ *   An array of parent keys, starting with the outermost key.
+ * @param $value
+ *   The value to set.
+ *
+ * @see drupal_array_get_value()
+ */
+function drupal_array_set_value(&$array, $parents, $value) {
+  $ref = &$array;
+  foreach ($parents as $parent) {
+    // Note that PHP is fine with referencing a not existing array key - in this
+    // case it just creates an entry with NULL as value.
+    $ref = &$ref[$parent];
+  }
+  $ref = $value;
+}
+
+/**
+ * Retrieves a value from a nested array.
+ *
+ * Primarily used for form structures and renderable arrays.
+ *
+ * Example:
+ * @code
+ * // Assume you have this as $form:
+ * $form = array();
+ * $form['signature_settings'] = array(
+ *   '#type' => 'fieldset',
+ *   '#title' => t('Signature settings'),
+ * );
+ * $form['signature_settings']['signature'] = array(
+ *   '#type' => 'text_format',
+ *   '#title' => t('Signature'),
+ *  );
+ * // And assume you have this in $parents:
+ * $parents = array('signature_settings', 'signature', '#title');
+ * // Then you can make this function call to retrieve the title:
+ * list($value, $value_exists) = drupal_array_get_value($form, $parents);
+ * @endcode
+ *
+ * This helper function should be used when the depth of the array element you
+ * are getting may vary (that is, the number of parent keys is variable),
+ * because an operation like this can otherwise only be done in one line using
+ * eval(), which is best to avoid:
+ * @code
+ * // Do not do this! Avoid eval().
+ * $value = eval('$form[\'' . implode("']['", $parents) . '\'];');
+ * @endcode
+ *
+ * On the other hand, if the number of array parent keys is static, the value
+ * should always be retrieved directly rather than calling this function. For
+ * instance, in the above example, we could just do:
+ * @code
+ * $value = $form['signature_settings']['signature']['#title'];
+ * @endcode
+ *
+ * @param $array
+ *   The array from which to get the value.
+ * @param $parents
+ *   An array of parent keys of the value, starting with the outermost key.
+ *
+ * @return
+ *   An array whose first entry is the array value, and whose second entry is
+ *   TRUE if all the parent keys existed, and FALSE if not (in which case the
+ *   value element will be NULL).
+ *
+ * @see drupal_array_set_value()
+ */
+function drupal_array_get_value($array, $parents) {
+  foreach ($parents as $parent) {
+    if (isset($array[$parent])) {
+      $array = $array[$parent];
+    }
+    else {
+      return array(NULL, FALSE);
+    }
+  }
+  return array($array, TRUE);
+}
+
+/**
  * Provide theme registration for themes across .inc files.
  */
 function drupal_common_theme() {
diff --git includes/form.inc includes/form.inc
index e571769..4b1e345 100644
--- includes/form.inc
+++ includes/form.inc
@@ -953,6 +953,24 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
 
   _form_validate($form, $form_state, $form_id);
   $validated_forms[$form_id] = TRUE;
+
+  // If validation errors are limited then remove any non validated form values,
+  // so that only values that passed validation are left for submit callbacks.
+  if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) {
+    $values = array();
+    foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
+      list($value, $value_exists) = drupal_array_get_value($form_state['values'], $section);
+      if ($value_exists) {
+        drupal_array_set_value($values, $section, $value);
+      }
+    }
+    // For convenience we always make the value of the pressed button available.
+    if (isset($form_state['triggering_element']['#button_type'])) {
+      $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value'];
+      drupal_array_set_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']);
+    }
+    $form_state['values'] = $values;
+  }
 }
 
 /**
@@ -1727,11 +1745,9 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
       // submit explicit NULL values when calling drupal_form_submit(), so we do
       // not modify $form_state['input'] for them.
       if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) {
-        // We leverage the internal logic of form_set_value() to change the
-        // input values by passing $form_state['input'] instead of the usual
-        // $form_state['values']. In effect, this adds the necessary parent keys
-        // to $form_state['input'] and sets the element's input value to NULL.
-        _form_set_value($form_state['input'], $element, $element['#parents'], NULL);
+        // Add the necessary parent keys to $form_state['input'] and sets the
+        // element's input value to NULL.
+        drupal_array_set_value($form_state['input'], $element['#parents'], NULL);
         $input_exists = TRUE;
       }
       // If we have input for the current element, assign it to the #value
@@ -1790,11 +1806,8 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
 
   // Set the element's value in $form_state['values'], but only, if its key
   // does not exist yet (a #value_callback may have already populated it).
-  $values = $form_state['values'];
-  foreach ($element['#parents'] as $key) {
-    $values = (isset($values[$key]) ? $values[$key] : NULL);
-  }
-  if (!isset($values)) {
+  list($values, $value_exists) = drupal_array_get_value($form_state['values'], $element['#parents']);
+  if (!$value_exists) {
     form_set_value($element, $element['#value'], $form_state);
   }
 }
@@ -2140,26 +2153,7 @@ function form_type_token_value($element, $input = FALSE) {
  *   Form state array where the value change should be recorded.
  */
 function form_set_value($element, $value, &$form_state) {
-  _form_set_value($form_state['values'], $element, $element['#parents'], $value);
-}
-
-/**
- * Helper function for form_set_value() and _form_builder_handle_input_element().
- *
- * We iterate over $parents and create nested arrays for them in $form_values if
- * needed. Then we insert the value into the last parent key.
- */
-function _form_set_value(&$form_values, $element, $parents, $value) {
-  $parent = array_shift($parents);
-  if (empty($parents)) {
-    $form_values[$parent] = $value;
-  }
-  else {
-    if (!isset($form_values[$parent])) {
-      $form_values[$parent] = array();
-    }
-    _form_set_value($form_values[$parent], $element, $parents, $value);
-  }
+  drupal_array_set_value($form_state['values'], $element['#parents'], $value);
 }
 
 /**
diff --git modules/file/file.module modules/file/file.module
index cfdb212..13705f8 100644
--- modules/file/file.module
+++ modules/file/file.module
@@ -553,10 +553,7 @@ function file_managed_file_submit($form, &$form_state) {
   // and set $element to the managed_file element that contains that button.
   $parents = $form_state['triggering_element']['#array_parents'];
   $button_key = array_pop($parents);
-  $element = $form;
-  foreach ($parents as $parent) {
-    $element = $element[$parent];
-  }
+  list($element) = drupal_array_get_value($form, $parents);
 
   // No action is needed here for the upload button, because all file uploads on
   // the form are processed by file_managed_file_value() regardless of which
@@ -574,13 +571,10 @@ function file_managed_file_submit($form, &$form_state) {
     // run, and for form building functions that run during the rebuild, such as
     // when the managed_file element is part of a field widget.
     // $form_state['input'] must be updated so that file_managed_file_value()
-    // has correct information during the rebuild. The Form API provides no
-    // equivalent of form_set_value() for updating $form_state['input'], so
-    // inline that implementation with the same logic that form_set_value()
-    // uses.
+    // has correct information during the rebuild.
     $values_element = $element['#extended'] ? $element['fid'] : $element;
     form_set_value($values_element, NULL, $form_state);
-    _form_set_value($form_state['input'], $values_element, $values_element['#parents'], NULL);
+    drupal_array_set_value($form_state['input'], $values_element['#parents'], NULL);
   }
 
   // Set the form to rebuild so that $form is correctly updated in response to
diff --git modules/simpletest/tests/form.test modules/simpletest/tests/form.test
index c982dd0..c9c722a 100644
--- modules/simpletest/tests/form.test
+++ modules/simpletest/tests/form.test
@@ -42,7 +42,10 @@ class FormsTestCase extends DrupalWebTestCase {
     $elements['password']['empty_values'] = $empty_strings;
 
     $elements['password_confirm']['element'] = array('#title' => $this->randomName(), '#type' => 'password_confirm');
-    $elements['password_confirm']['empty_values'] = $empty_strings;
+    // Provide empty values for both password fields.
+    foreach ($empty_strings as $key => $value) {
+      $elements['password_confirm']['empty_values'][$key] = array('pass1' => $value, 'pass2' => $value);
+    }
 
     $elements['textarea']['element'] = array('#title' => $this->randomName(), '#type' => 'textarea');
     $elements['textarea']['empty_values'] = $empty_strings;
@@ -77,8 +80,7 @@ class FormsTestCase extends DrupalWebTestCase {
           $element = $data['element']['#title'];
           $form[$element] = $data['element'];
           $form[$element]['#required'] = $required;
-          $form_state['values'][$element] = $empty;
-          $form_state['input'] = $form_state['values'];
+          $form_state['input'][$element] = $empty;
           $form_state['input']['form_id'] = $form_id;
           $form_state['method'] = 'post';
           drupal_prepare_form($form_id, $form, $form_state);
@@ -405,6 +407,10 @@ class FormValidationTestCase extends DrupalWebTestCase {
     $this->assertNoText(t('!name field is required.', array('!name' => 'Title')));
     $this->assertText('Test element is invalid');
 
+    // Ensure not validated values are not available to submit handlers.
+    $this->drupalPost($path, array('title' => '', 'test' => 'valid'), t('Partial validate'));
+    $this->assertText('Only validated values appear in the form values.');
+
     // Now test full form validation and ensure that the #element_validate
     // handler is still triggered.
     $this->drupalPost($path, $edit, t('Full validate'));
@@ -616,11 +622,11 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
     );
 
     // Test with a valid value.
-    list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'row1'));
+    list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('row1' => 'row1')));
     $this->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for checkboxes.'));
 
     // Test with an invalid value.
-    list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'non_existing_value'));
+    list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('non_existing_value' => 'non_existing_value')));
     $this->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for checkboxes.'));
 
   }
diff --git modules/simpletest/tests/form_test.module modules/simpletest/tests/form_test.module
index 32f7592..e4f68bc 100644
--- modules/simpletest/tests/form_test.module
+++ modules/simpletest/tests/form_test.module
@@ -313,7 +313,7 @@ function form_test_limit_validation_errors_form($form, &$form_state) {
   $form['actions']['partial'] = array(
     '#type' => 'submit',
     '#limit_validation_errors' => array(array('test')),
-    '#submit' => array(),
+    '#submit' => array('form_test_limit_validation_errors_form_partial_submit'),
     '#value' => t('Partial validate'),
   );
   $form['actions']['full'] = array(
@@ -333,6 +333,17 @@ function form_test_limit_validation_errors_element_validate_test(&$element, &$fo
 }
 
 /**
+ * Form submit handler for the partial validation submit button.
+ */
+function form_test_limit_validation_errors_form_partial_submit($form, $form_state) {
+  // The title has not been validated, thus its value - in case of the test case
+  // an empty string - may not be set.
+  if (!isset($form_state['values']['title']) && isset($form_state['values']['test'])) {
+    drupal_set_message('Only validated values appear in the form values.');
+  }
+}
+
+/**
  * Create a header and options array. Helper function for callbacks.
  */
 function _form_test_tableselect_get_data() {
