Skip to content

Commit dbf37b4

Browse files
committed
Improve count() in regards to argument checks
Add ZPP checks for the first argument to ensure it is an array or a Countable object. Due to earlier discussion NULL is special cased and deprecated.
1 parent e72bf63 commit dbf37b4

22 files changed

+802
-827
lines changed

Zend/tests/generators/errors/count_error.phpt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ try {
1111
count($gen);
1212
} catch (Exception $e) {
1313
echo $e;
14-
}
14+
} catch (\TypeError $e) {
15+
echo $e->getMessage() . "\n";
16+
}
1517

1618
?>
17-
--EXPECTF--
18-
Warning: count(): Parameter must be an array or an object that implements Countable in %s on line %d
19+
--EXPECT--
20+
Parameter must be an array or an object that implements Countable

Zend/zend_vm_def.h

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8416,53 +8416,46 @@ ZEND_VM_COLD_CONST_HANDLER(190, ZEND_COUNT, CONST|TMPVAR|CV, UNUSED)
84168416
{
84178417
USE_OPLINE
84188418
zval *op1;
8419-
zend_long count;
8419+
zend_long count = 1;
84208420

84218421
SAVE_OPLINE();
84228422
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8423-
while (1) {
8424-
if (Z_TYPE_P(op1) == IS_ARRAY) {
8425-
count = zend_array_count(Z_ARRVAL_P(op1));
8426-
break;
8427-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
8428-
zend_object *zobj = Z_OBJ_P(op1);
8429-
8430-
/* first, we check if the handler is defined */
8431-
if (zobj->handlers->count_elements) {
8432-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
8433-
break;
8434-
}
8435-
if (UNEXPECTED(EG(exception))) {
8436-
count = 0;
8437-
break;
8438-
}
8439-
}
84408423

8441-
/* if not and the object implements Countable we call its count() method */
8442-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
8443-
zval retval;
8424+
if ((OP1_TYPE & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
8425+
op1 = Z_REFVAL_P(op1);
8426+
}
84448427

8445-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
8446-
count = zval_get_long(&retval);
8447-
zval_ptr_dtor(&retval);
8448-
break;
8428+
if (Z_TYPE_P(op1) <= IS_NULL) {
8429+
/* Intentionally not converted to an exception */
8430+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
8431+
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
8432+
ZVAL_UNDEFINED_OP1();
8433+
}
8434+
count = 0;
8435+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
8436+
count = zend_array_count(Z_ARRVAL_P(op1));
8437+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
8438+
zend_object *zobj = Z_OBJ_P(op1);
8439+
/* if the object implements Countable we call its count() method */
8440+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
8441+
zval retval;
8442+
8443+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
8444+
count = zval_get_long(&retval);
8445+
zval_ptr_dtor(&retval);
8446+
/* Else if the object has a count handler defined */
8447+
} else if (zobj->handlers->count_elements) {
8448+
if (FAILURE == zobj->handlers->count_elements(zobj, &count)) {
8449+
zend_type_error("Parameter must be an array or an object that implements Countable");
84498450
}
8450-
8451-
/* If There's no handler and it doesn't implement Countable then add a warning */
8452-
count = 1;
8453-
} else if ((OP1_TYPE & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
8454-
op1 = Z_REFVAL_P(op1);
8455-
continue;
8456-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
8457-
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
8458-
ZVAL_UNDEFINED_OP1();
8451+
if (UNEXPECTED(EG(exception))) {
8452+
count = 0;
84598453
}
8460-
count = 0;
84618454
} else {
8462-
count = 1;
8455+
zend_type_error("Parameter must be an array or an object that implements Countable");
84638456
}
8464-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
8465-
break;
8457+
} else {
8458+
zend_type_error("Parameter must be an array or an object that implements Countable");
84668459
}
84678460

84688461
ZVAL_LONG(EX_VAR(opline->result.var), count);

Zend/zend_vm_execute.h

Lines changed: 93 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -9250,53 +9250,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CONST_
92509250
{
92519251
USE_OPLINE
92529252
zval *op1;
9253-
zend_long count;
9253+
zend_long count = 1;
92549254

92559255
SAVE_OPLINE();
92569256
op1 = RT_CONSTANT(opline, opline->op1);
9257-
while (1) {
9258-
if (Z_TYPE_P(op1) == IS_ARRAY) {
9259-
count = zend_array_count(Z_ARRVAL_P(op1));
9260-
break;
9261-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9262-
zend_object *zobj = Z_OBJ_P(op1);
92639257

9264-
/* first, we check if the handler is defined */
9265-
if (zobj->handlers->count_elements) {
9266-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
9267-
break;
9268-
}
9269-
if (UNEXPECTED(EG(exception))) {
9270-
count = 0;
9271-
break;
9272-
}
9273-
}
9274-
9275-
/* if not and the object implements Countable we call its count() method */
9276-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9277-
zval retval;
9258+
if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9259+
op1 = Z_REFVAL_P(op1);
9260+
}
92789261

9279-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9280-
count = zval_get_long(&retval);
9281-
zval_ptr_dtor(&retval);
9282-
break;
9262+
if (Z_TYPE_P(op1) <= IS_NULL) {
9263+
/* Intentionally not converted to an exception */
9264+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
9265+
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9266+
ZVAL_UNDEFINED_OP1();
9267+
}
9268+
count = 0;
9269+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
9270+
count = zend_array_count(Z_ARRVAL_P(op1));
9271+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9272+
zend_object *zobj = Z_OBJ_P(op1);
9273+
/* if the object implements Countable we call its count() method */
9274+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9275+
zval retval;
9276+
9277+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9278+
count = zval_get_long(&retval);
9279+
zval_ptr_dtor(&retval);
9280+
/* Else if the object has a count handler defined */
9281+
} else if (zobj->handlers->count_elements) {
9282+
if (FAILURE == zobj->handlers->count_elements(zobj, &count)) {
9283+
zend_type_error("Parameter must be an array or an object that implements Countable");
92839284
}
9284-
9285-
/* If There's no handler and it doesn't implement Countable then add a warning */
9286-
count = 1;
9287-
} else if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9288-
op1 = Z_REFVAL_P(op1);
9289-
continue;
9290-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
9291-
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9292-
ZVAL_UNDEFINED_OP1();
9285+
if (UNEXPECTED(EG(exception))) {
9286+
count = 0;
92939287
}
9294-
count = 0;
92959288
} else {
9296-
count = 1;
9289+
zend_type_error("Parameter must be an array or an object that implements Countable");
92979290
}
9298-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
9299-
break;
9291+
} else {
9292+
zend_type_error("Parameter must be an array or an object that implements Countable");
93009293
}
93019294

93029295
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -16266,53 +16259,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_TMPVAR_UNUSED_HANDL
1626616259
{
1626716260
USE_OPLINE
1626816261
zval *op1;
16269-
zend_long count;
16262+
zend_long count = 1;
1627016263

1627116264
SAVE_OPLINE();
1627216265
op1 = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
16273-
while (1) {
16274-
if (Z_TYPE_P(op1) == IS_ARRAY) {
16275-
count = zend_array_count(Z_ARRVAL_P(op1));
16276-
break;
16277-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
16278-
zend_object *zobj = Z_OBJ_P(op1);
16279-
16280-
/* first, we check if the handler is defined */
16281-
if (zobj->handlers->count_elements) {
16282-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
16283-
break;
16284-
}
16285-
if (UNEXPECTED(EG(exception))) {
16286-
count = 0;
16287-
break;
16288-
}
16289-
}
1629016266

16291-
/* if not and the object implements Countable we call its count() method */
16292-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
16293-
zval retval;
16267+
if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
16268+
op1 = Z_REFVAL_P(op1);
16269+
}
1629416270

16295-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16296-
count = zval_get_long(&retval);
16297-
zval_ptr_dtor(&retval);
16298-
break;
16271+
if (Z_TYPE_P(op1) <= IS_NULL) {
16272+
/* Intentionally not converted to an exception */
16273+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
16274+
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16275+
ZVAL_UNDEFINED_OP1();
16276+
}
16277+
count = 0;
16278+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
16279+
count = zend_array_count(Z_ARRVAL_P(op1));
16280+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
16281+
zend_object *zobj = Z_OBJ_P(op1);
16282+
/* if the object implements Countable we call its count() method */
16283+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
16284+
zval retval;
16285+
16286+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16287+
count = zval_get_long(&retval);
16288+
zval_ptr_dtor(&retval);
16289+
/* Else if the object has a count handler defined */
16290+
} else if (zobj->handlers->count_elements) {
16291+
if (FAILURE == zobj->handlers->count_elements(zobj, &count)) {
16292+
zend_type_error("Parameter must be an array or an object that implements Countable");
1629916293
}
16300-
16301-
/* If There's no handler and it doesn't implement Countable then add a warning */
16302-
count = 1;
16303-
} else if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
16304-
op1 = Z_REFVAL_P(op1);
16305-
continue;
16306-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
16307-
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16308-
ZVAL_UNDEFINED_OP1();
16294+
if (UNEXPECTED(EG(exception))) {
16295+
count = 0;
1630916296
}
16310-
count = 0;
1631116297
} else {
16312-
count = 1;
16298+
zend_type_error("Parameter must be an array or an object that implements Countable");
1631316299
}
16314-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
16315-
break;
16300+
} else {
16301+
zend_type_error("Parameter must be an array or an object that implements Countable");
1631616302
}
1631716303

1631816304
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -45033,53 +45019,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CV_UNUSED_HANDLER(Z
4503345019
{
4503445020
USE_OPLINE
4503545021
zval *op1;
45036-
zend_long count;
45022+
zend_long count = 1;
4503745023

4503845024
SAVE_OPLINE();
4503945025
op1 = EX_VAR(opline->op1.var);
45040-
while (1) {
45041-
if (Z_TYPE_P(op1) == IS_ARRAY) {
45042-
count = zend_array_count(Z_ARRVAL_P(op1));
45043-
break;
45044-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45045-
zend_object *zobj = Z_OBJ_P(op1);
45046-
45047-
/* first, we check if the handler is defined */
45048-
if (zobj->handlers->count_elements) {
45049-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
45050-
break;
45051-
}
45052-
if (UNEXPECTED(EG(exception))) {
45053-
count = 0;
45054-
break;
45055-
}
45056-
}
4505745026

45058-
/* if not and the object implements Countable we call its count() method */
45059-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45060-
zval retval;
45027+
if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45028+
op1 = Z_REFVAL_P(op1);
45029+
}
4506145030

45062-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45063-
count = zval_get_long(&retval);
45064-
zval_ptr_dtor(&retval);
45065-
break;
45031+
if (Z_TYPE_P(op1) <= IS_NULL) {
45032+
/* Intentionally not converted to an exception */
45033+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
45034+
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45035+
ZVAL_UNDEFINED_OP1();
45036+
}
45037+
count = 0;
45038+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
45039+
count = zend_array_count(Z_ARRVAL_P(op1));
45040+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45041+
zend_object *zobj = Z_OBJ_P(op1);
45042+
/* if the object implements Countable we call its count() method */
45043+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45044+
zval retval;
45045+
45046+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45047+
count = zval_get_long(&retval);
45048+
zval_ptr_dtor(&retval);
45049+
/* Else if the object has a count handler defined */
45050+
} else if (zobj->handlers->count_elements) {
45051+
if (FAILURE == zobj->handlers->count_elements(zobj, &count)) {
45052+
zend_type_error("Parameter must be an array or an object that implements Countable");
4506645053
}
45067-
45068-
/* If There's no handler and it doesn't implement Countable then add a warning */
45069-
count = 1;
45070-
} else if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45071-
op1 = Z_REFVAL_P(op1);
45072-
continue;
45073-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
45074-
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45075-
ZVAL_UNDEFINED_OP1();
45054+
if (UNEXPECTED(EG(exception))) {
45055+
count = 0;
4507645056
}
45077-
count = 0;
4507845057
} else {
45079-
count = 1;
45058+
zend_type_error("Parameter must be an array or an object that implements Countable");
4508045059
}
45081-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
45082-
break;
45060+
} else {
45061+
zend_type_error("Parameter must be an array or an object that implements Countable");
4508345062
}
4508445063

4508545064
ZVAL_LONG(EX_VAR(opline->result.var), count);

0 commit comments

Comments
 (0)