We are rendering a form with a multi-select, and pass it some #default_values. Strangely, more values than the default values which were given were showing up selected. The reason boils down to the use of in_array() in form_select_options(), in form.inc. Our keys are strings, but are composed only of digits, as they correspond to 64-bit values from a database. Unfortunately, in_array() seems to recognise the strings as numbers, and performs an integer comparison, which gives equality, because the 64-bit strings are truncated to equal 32-bit numbers. See for yourself by running this PHP code:

var_dump(in_array('28428972648161743', array('28428972648161746'))); // Prints boolean true
var_dump('28428972648161743' == '28428972648161746');                // Prints boolean true

Passing TRUE as the third parameter to is_array() solves this for a values array with string keys. To handle values arrays with integer keys, how about a drupal_in_array function?

function drupal_in_array($needle, $haystack) {
  return in_array((string)$needle, $haystack, TRUE) || in_array((int)$needle, $haystack, TRUE);
}

Comments

treksler’s picture

subscribing

treksler’s picture

Version: 5.16 » 7.x-dev
Priority: Normal » Critical

Without this fix, the forms API multi-selects are broken,
Reliable multiselects are critical for module development.
Let's discuss any implications around fixing the use of in_array in form_select_options()
The way proposed above works when all values in the haystack are strings of 64 bit numbers
and as far as i can see, it still works for all current cases.

pingers’s picture

Well, this is a documented feature in PHP (not a bug).

Using === will stop the implicit cast I believe.

treksler’s picture

@pingers
LOL, that IS the point by the way.
'===' acts the same way as in_array(..., ..., TRUE)

So, yes PHP has this feature to stop the implicit cast, BUT DRUPAL is not using it.
So, in Drupal, multiselects aren't guaranteed to work.

That is the bug.

sun.core’s picture

Priority: Critical » Normal

Seems like only 3 users were able to replicate this bug. Potentially, your module's code might be wrong in this case.

For that sake, please post a proper bug report, so people can try to replicate this problem.

In any case, this is not critical.

pingers’s picture

Okay, so the main point here is that when running a 32bit OS, you can't deal with 64bit numbers.

I.e. under 64bit OS

var_dump(in_array('28428972648161743', array('28428972648161746'))); // Prints boolean false
var_dump('28428972648161743' == '28428972648161746'); // Prints boolean false
var_dump(28428972648161743 == 28428972648161746); // Prints boolean false
treksler’s picture

The way Drupal is coded, long strings of numbers get converted to 64 bit numbers, which are not handled by 32 bit OSs

so, if your form options contain long strings, they are not guaranteed to work.
From the bug report: form_select_options() uses in_array() which does an == check by default
the proposed solution, which works, is to use a drupal_in_array() function (provided in bug report) instead in form_select_options()

The main point is that abstract APIs should do what they advertise they are doing and Drupal's FAPI multiselects are broken right now, admittedly in pretty rare cases.