Skip to content

Commit 835237e

Browse files
committed
Handle object that define a do_operation handler but no cast handler
Introduce such a dummy class in the zend_test extension
1 parent 92aea13 commit 835237e

File tree

7 files changed

+216
-8
lines changed

7 files changed

+216
-8
lines changed

ext/standard/array.c

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5939,15 +5939,21 @@ PHP_FUNCTION(array_sum)
59395939
}
59405940
} ZEND_HASH_FOREACH_END();
59415941

5942-
/* Traversal of array encountered a numerically catable object */
5942+
/* Traversal of array encountered objects that support multiplication */
59435943
if (Z_TYPE_P(return_value) == IS_OBJECT) {
5944-
/* First try to convert to int */
5944+
/* Cannot use convert_scalar_to_number() as we don't know if the cast succeeded */
59455945
zval dst;
5946-
if (Z_OBJ_HT_P(return_value)->cast_object(Z_OBJ_P(return_value), &dst, IS_LONG) == SUCCESS) {
5946+
zend_result status = Z_OBJ_HT_P(return_value)->cast_object(Z_OBJ_P(return_value), &dst, _IS_NUMBER);
5947+
5948+
/* Do not type error for BC */
5949+
if (status == FAILURE || (Z_TYPE(dst) != IS_LONG && Z_TYPE(dst) != IS_DOUBLE)) {
5950+
zend_error(E_WARNING, "Object of class %s could not be converted to int|float",
5951+
ZSTR_VAL(Z_OBJCE_P(return_value)->name));
59475952
zval_ptr_dtor(return_value);
5948-
RETURN_COPY(&dst);
5953+
RETURN_LONG(0);
59495954
}
5950-
convert_scalar_to_number(return_value);
5955+
zval_ptr_dtor(return_value);
5956+
RETURN_COPY_VALUE(&dst);
59515957
}
59525958
}
59535959
/* }}} */
@@ -5990,9 +5996,21 @@ PHP_FUNCTION(array_product)
59905996
}
59915997
} ZEND_HASH_FOREACH_END();
59925998

5993-
/* Traversal of array encountered a numerically castable object */
5999+
/* Traversal of array encountered objects that support multiplication */
59946000
if (Z_TYPE_P(return_value) == IS_OBJECT) {
5995-
convert_scalar_to_number(return_value);
6001+
/* Cannot use convert_scalar_to_number() as we don't know if the cast succeeded */
6002+
zval dst;
6003+
zend_result status = Z_OBJ_HT_P(return_value)->cast_object(Z_OBJ_P(return_value), &dst, _IS_NUMBER);
6004+
6005+
/* Do not type error for BC */
6006+
if (status == FAILURE || (Z_TYPE(dst) != IS_LONG && Z_TYPE(dst) != IS_DOUBLE)) {
6007+
zend_error(E_WARNING, "Object of class %s could not be converted to int|float",
6008+
ZSTR_VAL(Z_OBJCE_P(return_value)->name));
6009+
zval_ptr_dtor(return_value);
6010+
RETURN_LONG(1);
6011+
}
6012+
zval_ptr_dtor(return_value);
6013+
RETURN_COPY_VALUE(&dst);
59966014
}
59976015
}
59986016
/* }}} */
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test array_product() function with objects that implement addition but not castable to numeric type
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
$input = [new DoOperationNoCast(25), new DoOperationNoCast(6), new DoOperationNoCast(10)];
8+
var_dump(array_product($input));
9+
?>
10+
--EXPECTF--
11+
Warning: Object of class DoOperationNoCast could not be converted to int|float in %s on line %d
12+
int(1)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test array_sum() function with objects that implement addition but not castable to numeric type
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
$input = [new DoOperationNoCast(25), new DoOperationNoCast(6)];
8+
var_dump(array_sum($input));
9+
?>
10+
--EXPECTF--
11+
Warning: Object of class DoOperationNoCast could not be converted to int|float in %s on line %d
12+
int(0)

ext/zend_test/test.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,110 @@ static ZEND_METHOD(ZendTestForbidDynamicCall, callStatic)
706706
zend_forbid_dynamic_call();
707707
}
708708

709+
/* donc refers to DoOperationNoCast */
710+
static zend_class_entry *donc_ce;
711+
static zend_object_handlers donc_object_handlers;
712+
713+
static zend_object* donc_object_create_ex(zend_class_entry* ce, zend_long l) {
714+
zend_object *obj = zend_objects_new(ce);
715+
object_properties_init(obj, ce);
716+
obj->handlers = &donc_object_handlers;
717+
ZVAL_LONG(OBJ_PROP_NUM(obj, 0), l);
718+
return obj;
719+
}
720+
static zend_object *donc_object_create(zend_class_entry *ce) /* {{{ */
721+
{
722+
return donc_object_create_ex(ce, 0);
723+
}
724+
/* }}} */
725+
726+
static inline void donc_create(zval *target, zend_long l) /* {{{ */
727+
{
728+
ZVAL_OBJ(target, donc_object_create_ex(donc_ce, l));
729+
}
730+
731+
#define IS_DONC(zval) \
732+
(Z_TYPE_P(zval) == IS_OBJECT && instanceof_function(Z_OBJCE_P(zval), donc_ce))
733+
734+
static void donc_add(zval *result, zval *op1, zval *op2)
735+
{
736+
zend_long val_1;
737+
zend_long val_2;
738+
if (IS_DONC(op1)) {
739+
val_1 = Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op1), 0));
740+
} else {
741+
val_1 = zval_get_long(op1);
742+
}
743+
if (IS_DONC(op2)) {
744+
val_2 = Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op2), 0));
745+
} else {
746+
val_2 = zval_get_long(op2);
747+
}
748+
749+
donc_create(result, val_1 + val_2);
750+
}
751+
static void donc_mul(zval *result, zval *op1, zval *op2)
752+
{
753+
zend_long val_1;
754+
zend_long val_2;
755+
if (IS_DONC(op1)) {
756+
val_1 = Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op1), 0));
757+
} else {
758+
val_1 = zval_get_long(op1);
759+
}
760+
if (IS_DONC(op2)) {
761+
val_2 = Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op2), 0));
762+
} else {
763+
val_2 = zval_get_long(op2);
764+
}
765+
766+
donc_create(result, val_1 * val_2);
767+
}
768+
769+
static zend_result donc_do_operation(zend_uchar opcode, zval *result, zval *op1, zval *op2)
770+
{
771+
zval op1_copy;
772+
zend_result status;
773+
774+
if (result == op1) {
775+
ZVAL_COPY_VALUE(&op1_copy, op1);
776+
op1 = &op1_copy;
777+
}
778+
779+
switch (opcode) {
780+
case ZEND_ADD:
781+
donc_add(result, op1, op2);
782+
if (UNEXPECTED(EG(exception))) { status = FAILURE; }
783+
status = SUCCESS;
784+
break;
785+
case ZEND_MUL:
786+
donc_mul(result, op1, op2);
787+
if (UNEXPECTED(EG(exception))) { status = FAILURE; }
788+
status = SUCCESS;
789+
break;
790+
default:
791+
status = FAILURE;
792+
break;
793+
}
794+
795+
if (status == SUCCESS && op1 == &op1_copy) {
796+
zval_ptr_dtor(op1);
797+
}
798+
799+
return status;
800+
}
801+
802+
PHP_METHOD(DoOperationNoCast, __construct)
803+
{
804+
zend_long l;
805+
806+
ZEND_PARSE_PARAMETERS_START(1, 1)
807+
Z_PARAM_LONG(l)
808+
ZEND_PARSE_PARAMETERS_END();
809+
810+
ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l);
811+
}
812+
709813
PHP_INI_BEGIN()
710814
STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals)
711815
STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals)
@@ -813,6 +917,12 @@ PHP_MINIT_FUNCTION(zend_test)
813917
zend_test_string_enum = register_class_ZendTestStringEnum();
814918
zend_test_int_enum = register_class_ZendTestIntEnum();
815919

920+
/* DoOperationNoCast class */
921+
donc_ce = register_class_DoOperationNoCast();
922+
donc_ce->create_object = donc_object_create;
923+
memcpy(&donc_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
924+
donc_object_handlers.do_operation = donc_do_operation;
925+
816926
zend_register_functions(NULL, ext_function_legacy, NULL, EG(current_module)->type);
817927

818928
// Loading via dl() not supported with the observer API

ext/zend_test/test.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ enum ZendTestIntEnum: int {
105105
case Baz = -1;
106106
}
107107

108+
final class DoOperationNoCast {
109+
private int $val;
110+
public function __construct(int $val) {}
111+
}
112+
108113
function zend_test_array_return(): array {}
109114

110115
function zend_test_nullable_array_return(): ?array {}

ext/zend_test/test_arginfo.h

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Test DoOperationNotCast dummy class
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
$a = new DoOperationNoCast(25);
9+
$b = new DoOperationNoCast(6);
10+
11+
var_dump($a + $b);
12+
var_dump($a * $b);
13+
14+
?>
15+
--EXPECT--
16+
object(DoOperationNoCast)#3 (1) {
17+
["val":"DoOperationNoCast":private]=>
18+
int(31)
19+
}
20+
object(DoOperationNoCast)#3 (1) {
21+
["val":"DoOperationNoCast":private]=>
22+
int(150)
23+
}

0 commit comments

Comments
 (0)