Skip to content

Commit a6923cd

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. Also throw a ValueError on invalid mode values.
1 parent 1cd95c4 commit a6923cd

23 files changed

+835
-845
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: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8411,53 +8411,38 @@ ZEND_VM_COLD_CONST_HANDLER(190, ZEND_COUNT, CONST|TMPVAR|CV, UNUSED)
84118411
{
84128412
USE_OPLINE
84138413
zval *op1;
8414-
zend_long count;
8414+
zend_long count = 1;
84158415

84168416
SAVE_OPLINE();
84178417
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8418-
while (1) {
8419-
if (Z_TYPE_P(op1) == IS_ARRAY) {
8420-
count = zend_array_count(Z_ARRVAL_P(op1));
8421-
break;
8422-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
8423-
zend_object *zobj = Z_OBJ_P(op1);
8424-
8425-
/* first, we check if the handler is defined */
8426-
if (zobj->handlers->count_elements) {
8427-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
8428-
break;
8429-
}
8430-
if (UNEXPECTED(EG(exception))) {
8431-
count = 0;
8432-
break;
8433-
}
8434-
}
8435-
8436-
/* if not and the object implements Countable we call its count() method */
8437-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
8438-
zval retval;
84398418

8440-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
8441-
count = zval_get_long(&retval);
8442-
zval_ptr_dtor(&retval);
8443-
break;
8444-
}
8419+
if ((OP1_TYPE & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
8420+
op1 = Z_REFVAL_P(op1);
8421+
}
84458422

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

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

Zend/zend_vm_execute.h

Lines changed: 72 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9243,53 +9243,38 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CONST_
92439243
{
92449244
USE_OPLINE
92459245
zval *op1;
9246-
zend_long count;
9246+
zend_long count = 1;
92479247

92489248
SAVE_OPLINE();
92499249
op1 = RT_CONSTANT(opline, opline->op1);
9250-
while (1) {
9251-
if (Z_TYPE_P(op1) == IS_ARRAY) {
9252-
count = zend_array_count(Z_ARRVAL_P(op1));
9253-
break;
9254-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9255-
zend_object *zobj = Z_OBJ_P(op1);
92569250

9257-
/* first, we check if the handler is defined */
9258-
if (zobj->handlers->count_elements) {
9259-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
9260-
break;
9261-
}
9262-
if (UNEXPECTED(EG(exception))) {
9263-
count = 0;
9264-
break;
9265-
}
9266-
}
9267-
9268-
/* if not and the object implements Countable we call its count() method */
9269-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9270-
zval retval;
9251+
if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9252+
op1 = Z_REFVAL_P(op1);
9253+
}
92719254

9272-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9273-
count = zval_get_long(&retval);
9274-
zval_ptr_dtor(&retval);
9275-
break;
9276-
}
9255+
if (Z_TYPE_P(op1) <= IS_NULL) {
9256+
/* Intentionally not converted to an exception */
9257+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
9258+
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9259+
ZVAL_UNDEFINED_OP1();
9260+
}
9261+
count = 0;
9262+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
9263+
count = zend_array_count(Z_ARRVAL_P(op1));
9264+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9265+
zend_object *zobj = Z_OBJ_P(op1);
9266+
/* if the object implements Countable we call its count() method */
9267+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9268+
zval retval;
92779269

9278-
/* If There's no handler and it doesn't implement Countable then add a warning */
9279-
count = 1;
9280-
} else if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9281-
op1 = Z_REFVAL_P(op1);
9282-
continue;
9283-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
9284-
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9285-
ZVAL_UNDEFINED_OP1();
9286-
}
9287-
count = 0;
9270+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9271+
count = zval_get_long(&retval);
9272+
zval_ptr_dtor(&retval);
92889273
} else {
9289-
count = 1;
9274+
zend_type_error("Parameter must be an array or an object that implements Countable");
92909275
}
9291-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
9292-
break;
9276+
} else {
9277+
zend_type_error("Parameter must be an array or an object that implements Countable");
92939278
}
92949279

92959280
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -16265,53 +16250,38 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_TMPVAR_UNUSED_HANDL
1626516250
{
1626616251
USE_OPLINE
1626716252
zval *op1;
16268-
zend_long count;
16253+
zend_long count = 1;
1626916254

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

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

16294-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16295-
count = zval_get_long(&retval);
16296-
zval_ptr_dtor(&retval);
16297-
break;
16298-
}
16262+
if (Z_TYPE_P(op1) <= IS_NULL) {
16263+
/* Intentionally not converted to an exception */
16264+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
16265+
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16266+
ZVAL_UNDEFINED_OP1();
16267+
}
16268+
count = 0;
16269+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
16270+
count = zend_array_count(Z_ARRVAL_P(op1));
16271+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
16272+
zend_object *zobj = Z_OBJ_P(op1);
16273+
/* if the object implements Countable we call its count() method */
16274+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
16275+
zval retval;
1629916276

16300-
/* If There's no handler and it doesn't implement Countable then add a warning */
16301-
count = 1;
16302-
} else if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
16303-
op1 = Z_REFVAL_P(op1);
16304-
continue;
16305-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
16306-
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16307-
ZVAL_UNDEFINED_OP1();
16308-
}
16309-
count = 0;
16277+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16278+
count = zval_get_long(&retval);
16279+
zval_ptr_dtor(&retval);
1631016280
} else {
16311-
count = 1;
16281+
zend_type_error("Parameter must be an array or an object that implements Countable");
1631216282
}
16313-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
16314-
break;
16283+
} else {
16284+
zend_type_error("Parameter must be an array or an object that implements Countable");
1631516285
}
1631616286

1631716287
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -45062,53 +45032,38 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CV_UNUSED_HANDLER(Z
4506245032
{
4506345033
USE_OPLINE
4506445034
zval *op1;
45065-
zend_long count;
45035+
zend_long count = 1;
4506645036

4506745037
SAVE_OPLINE();
4506845038
op1 = EX_VAR(opline->op1.var);
45069-
while (1) {
45070-
if (Z_TYPE_P(op1) == IS_ARRAY) {
45071-
count = zend_array_count(Z_ARRVAL_P(op1));
45072-
break;
45073-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45074-
zend_object *zobj = Z_OBJ_P(op1);
45075-
45076-
/* first, we check if the handler is defined */
45077-
if (zobj->handlers->count_elements) {
45078-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
45079-
break;
45080-
}
45081-
if (UNEXPECTED(EG(exception))) {
45082-
count = 0;
45083-
break;
45084-
}
45085-
}
4508645039

45087-
/* if not and the object implements Countable we call its count() method */
45088-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45089-
zval retval;
45040+
if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45041+
op1 = Z_REFVAL_P(op1);
45042+
}
4509045043

45091-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45092-
count = zval_get_long(&retval);
45093-
zval_ptr_dtor(&retval);
45094-
break;
45095-
}
45044+
if (Z_TYPE_P(op1) <= IS_NULL) {
45045+
/* Intentionally not converted to an exception */
45046+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
45047+
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45048+
ZVAL_UNDEFINED_OP1();
45049+
}
45050+
count = 0;
45051+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
45052+
count = zend_array_count(Z_ARRVAL_P(op1));
45053+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45054+
zend_object *zobj = Z_OBJ_P(op1);
45055+
/* if the object implements Countable we call its count() method */
45056+
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45057+
zval retval;
4509645058

45097-
/* If There's no handler and it doesn't implement Countable then add a warning */
45098-
count = 1;
45099-
} else if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45100-
op1 = Z_REFVAL_P(op1);
45101-
continue;
45102-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
45103-
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45104-
ZVAL_UNDEFINED_OP1();
45105-
}
45106-
count = 0;
45059+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45060+
count = zval_get_long(&retval);
45061+
zval_ptr_dtor(&retval);
4510745062
} else {
45108-
count = 1;
45063+
zend_type_error("Parameter must be an array or an object that implements Countable");
4510945064
}
45110-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
45111-
break;
45065+
} else {
45066+
zend_type_error("Parameter must be an array or an object that implements Countable");
4511245067
}
4511345068

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

0 commit comments

Comments
 (0)