Skip to content

Saner array_(sum|product)() #10161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ PHP 8.3 INTERNALS UPGRADE NOTES
* The return types of the following functions have been changed from
`bool` to `zend_result`:
- zend_fiber_init_context()
* The fast_add_function() has been removed, use add_function() that will
call the static inline add_function_fast() instead.

========================
2. Build system changes
Expand Down
22 changes: 0 additions & 22 deletions Zend/zend_operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -707,28 +707,6 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL
#endif
}

static zend_always_inline zend_result fast_add_function(zval *result, zval *op1, zval *op2)
{
if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) {
if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
fast_long_add_function(result, op1, op2);
return SUCCESS;
} else if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) {
ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));
return SUCCESS;
}
} else if (EXPECTED(Z_TYPE_P(op1) == IS_DOUBLE)) {
if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) {
ZVAL_DOUBLE(result, Z_DVAL_P(op1) + Z_DVAL_P(op2));
return SUCCESS;
} else if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
ZVAL_DOUBLE(result, Z_DVAL_P(op1) + ((double)Z_LVAL_P(op2)));
return SUCCESS;
}
}
return add_function(result, op1, op2);
}

static zend_always_inline void fast_long_sub_function(zval *result, zval *op1, zval *op2)
{
#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__)
Expand Down
93 changes: 49 additions & 44 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -5915,65 +5915,70 @@ PHP_FUNCTION(array_rand)
}
/* }}} */

/* {{{ Returns the sum of the array entries */
PHP_FUNCTION(array_sum)
/* Wrapper for array_sum and array_product */
static void php_array_binop(INTERNAL_FUNCTION_PARAMETERS, const char *op_name, binary_op_type op, zend_long initial)
{
zval *input,
*entry,
entry_n;
HashTable *input;
zval *entry;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(input)
Z_PARAM_ARRAY_HT(input)
ZEND_PARSE_PARAMETERS_END();

ZVAL_LONG(return_value, 0);
if (zend_hash_num_elements(input) == 0) {
RETURN_LONG(initial);
}

ZVAL_LONG(return_value, initial);
ZEND_HASH_FOREACH_VAL(input, entry) {
/* For objects we try to cast them to a numeric type */
if (Z_TYPE_P(entry) == IS_OBJECT) {
zval dst;
zend_result status = Z_OBJ_HT_P(entry)->cast_object(Z_OBJ_P(entry), &dst, _IS_NUMBER);

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), entry) {
if (Z_TYPE_P(entry) == IS_ARRAY || Z_TYPE_P(entry) == IS_OBJECT) {
/* Do not type error for BC */
if (status == FAILURE || (Z_TYPE(dst) != IS_LONG && Z_TYPE(dst) != IS_DOUBLE)) {
php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
op_name, zend_zval_type_name(entry));
continue;
}
op(return_value, return_value, &dst);
continue;
}
ZVAL_COPY(&entry_n, entry);
convert_scalar_to_number(&entry_n);
fast_add_function(return_value, return_value, &entry_n);

zend_result status = op(return_value, return_value, entry);
if (status == FAILURE) {
ZEND_ASSERT(EG(exception));
zend_clear_exception();
/* BC resources: previously resources were cast to int */
if (Z_TYPE_P(entry) == IS_RESOURCE) {
zval tmp;
ZVAL_LONG(&tmp, Z_RES_HANDLE_P(entry));
op(return_value, return_value, &tmp);
}
/* BC non numeric strings: previously were cast to 0 */
else if (Z_TYPE_P(entry) == IS_STRING) {
zval tmp;
ZVAL_LONG(&tmp, 0);
op(return_value, return_value, &tmp);
}
php_error_docref(NULL, E_WARNING, "%s is not supported on type %s",
op_name, zend_zval_type_name(entry));
}
} ZEND_HASH_FOREACH_END();
}

/* {{{ Returns the sum of the array entries */
PHP_FUNCTION(array_sum)
{
php_array_binop(INTERNAL_FUNCTION_PARAM_PASSTHRU, "Addition", add_function, 0);
}
/* }}} */

/* {{{ Returns the product of the array entries */
PHP_FUNCTION(array_product)
{
zval *input,
*entry,
entry_n;
double dval;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(input)
ZEND_PARSE_PARAMETERS_END();

ZVAL_LONG(return_value, 1);
if (!zend_hash_num_elements(Z_ARRVAL_P(input))) {
return;
}

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), entry) {
if (Z_TYPE_P(entry) == IS_ARRAY || Z_TYPE_P(entry) == IS_OBJECT) {
continue;
}
ZVAL_COPY(&entry_n, entry);
convert_scalar_to_number(&entry_n);

if (Z_TYPE(entry_n) == IS_LONG && Z_TYPE_P(return_value) == IS_LONG) {
dval = (double)Z_LVAL_P(return_value) * (double)Z_LVAL(entry_n);
if ( (double)ZEND_LONG_MIN <= dval && dval <= (double)ZEND_LONG_MAX ) {
Z_LVAL_P(return_value) *= Z_LVAL(entry_n);
continue;
}
}
convert_to_double(return_value);
convert_to_double(&entry_n);
Z_DVAL_P(return_value) *= Z_DVAL(entry_n);
} ZEND_HASH_FOREACH_END();
php_array_binop(INTERNAL_FUNCTION_PARAM_PASSTHRU, "Multiplication", mul_function, 1);
}
/* }}} */

Expand Down
16 changes: 8 additions & 8 deletions ext/standard/tests/array/003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ require(__DIR__ . '/data.inc');

function cmp ($a, $b) {
is_array ($a)
and $a = array_sum ($a);
and $a = count($a);
is_array ($b)
and $b = array_sum ($b);
and $b = count($b);
return strcmp ($a, $b);
}

echo " -- Testing uasort() -- \n";
echo "-- Testing uasort() --\n";
uasort ($data, 'cmp');
var_dump ($data);


echo "\n -- Testing uksort() -- \n";
echo "\n-- Testing uksort() --\n";
uksort ($data, 'cmp');
var_dump ($data);

echo "\n -- Testing usort() -- \n";
echo "\n-- Testing usort() --\n";
usort ($data, 'cmp');
var_dump ($data);
?>
--EXPECT--
-- Testing uasort() --
-- Testing uasort() --
array(8) {
[16777216]=>
float(-0.3333333333333333)
Expand All @@ -53,7 +53,7 @@ array(8) {
string(4) "test"
}

-- Testing uksort() --
-- Testing uksort() --
array(8) {
[-1000]=>
array(2) {
Expand All @@ -78,7 +78,7 @@ array(8) {
int(27)
}

-- Testing usort() --
-- Testing usort() --
array(8) {
[0]=>
float(-0.3333333333333333)
Expand Down
17 changes: 17 additions & 0 deletions ext/standard/tests/array/array_product_empty_array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Test array_product() function with empty array
--FILE--
<?php
$input = [];

echo "array_product() version:\n";
var_dump(array_product($input));

echo "array_reduce() version:\n";
var_dump(array_reduce($input, fn($carry, $value) => $carry * $value, 1));
?>
--EXPECT--
array_product() version:
int(1)
array_reduce() version:
int(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Test array_product() function with objects that implement addition but not castable to numeric type
--EXTENSIONS--
zend_test
--FILE--
<?php
$input = [new DoOperationNoCast(25), new DoOperationNoCast(6), new DoOperationNoCast(10)];

echo "array_product() version:\n";
var_dump(array_product($input));

echo "array_reduce() version:\n";
var_dump(array_reduce($input, fn($carry, $value) => $carry * $value, 1));
?>
--EXPECTF--
array_product() version:

Warning: array_product(): Multiplication is not supported on type DoOperationNoCast in %s on line %d

Warning: array_product(): Multiplication is not supported on type DoOperationNoCast in %s on line %d

Warning: array_product(): Multiplication is not supported on type DoOperationNoCast in %s on line %d
int(1)
array_reduce() version:
object(DoOperationNoCast)#5 (1) {
["val":"DoOperationNoCast":private]=>
int(1500)
}
18 changes: 12 additions & 6 deletions ext/standard/tests/array/array_product_variation1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@ echo "*** Testing array_product() : variation - using non numeric values ***\n";
class A {
static function help() { echo "hello\n"; }
}
$fp = fopen(__FILE__, "r");

$types = array("boolean (true)" => true, "boolean (false)" => false,
"string" => "hello", "numeric string" => "12",
"resource" => $fp, "object" => new A(), "null" => null,
"resource" => STDERR, "object" => new A(), "null" => null,
"array" => array(3,2));

foreach ($types as $desc => $type) {
echo $desc . "\n";
var_dump(array_product(array($type)));
echo $desc, "\n";
var_dump(array_product([1, $type]));
echo "\n";
}

fclose($fp);
?>
--EXPECTF--
*** Testing array_product() : variation - using non numeric values ***
Expand All @@ -31,20 +29,28 @@ boolean (false)
int(0)

string

Warning: array_product(): Multiplication is not supported on type string in %s on line %d
int(0)

numeric string
int(12)

resource
int(%d)

Warning: array_product(): Multiplication is not supported on type resource in %s on line %d
int(3)

object

Warning: array_product(): Multiplication is not supported on type A in %s on line %d
int(1)

null
int(0)

array

Warning: array_product(): Multiplication is not supported on type array in %s on line %d
int(1)

23 changes: 23 additions & 0 deletions ext/standard/tests/array/array_product_variation5.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Test array_product() function: resources in array
--FILE--
<?php
$input = [10, STDERR /* Should get casted to 3 as an integer */];

echo "array_product() version:\n";
var_dump(array_product($input));

echo "array_reduce() version:\n";
try {
var_dump(array_reduce($input, fn($carry, $value) => $carry * $value, 1));
} catch (TypeError $e) {
echo $e->getMessage();
}
?>
--EXPECTF--
array_product() version:

Warning: array_product(): Multiplication is not supported on type resource in %s on line %d
int(30)
array_reduce() version:
Unsupported operand types: int * resource
22 changes: 22 additions & 0 deletions ext/standard/tests/array/array_product_variation6.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Test array_product() function with objects castable to numeric type
--EXTENSIONS--
gmp
--FILE--
<?php
$input = [gmp_init(25), gmp_init(6)];

echo "array_product() version:\n";
var_dump(array_product($input));

echo "array_reduce() version:\n";
var_dump(array_reduce($input, fn($carry, $value) => $carry * $value, 1));
?>
--EXPECT--
array_product() version:
int(150)
array_reduce() version:
object(GMP)#5 (1) {
["num"]=>
string(3) "150"
}
17 changes: 17 additions & 0 deletions ext/standard/tests/array/array_sum_empty_array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Test array_sum() function with empty array
--FILE--
<?php
$input = [];

echo "array_sum() version:\n";
var_dump(array_sum($input));

echo "array_reduce() version:\n";
var_dump(array_reduce($input, fn($carry, $value) => $carry + $value, 0));
?>
--EXPECT--
array_sum() version:
int(0)
array_reduce() version:
int(0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Test array_sum() function with objects that implement addition but not castable to numeric type
--EXTENSIONS--
zend_test
--FILE--
<?php
$input = [new DoOperationNoCast(25), new DoOperationNoCast(6)];

echo "array_sum() version:\n";
var_dump(array_sum($input));

echo "array_reduce() version:\n";
var_dump(array_reduce($input, fn($carry, $value) => $carry + $value, 0));
?>
--EXPECTF--
array_sum() version:

Warning: array_sum(): Addition is not supported on type DoOperationNoCast in %s on line %d

Warning: array_sum(): Addition is not supported on type DoOperationNoCast in %s on line %d
int(0)
array_reduce() version:
object(DoOperationNoCast)#5 (1) {
["val":"DoOperationNoCast":private]=>
int(31)
}
Loading