Skip to content

Commit d05a057

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 21cfa03 commit d05a057

22 files changed

+802
-835
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: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8438,53 +8438,41 @@ ZEND_VM_COLD_CONST_HANDLER(190, ZEND_COUNT, CONST|TMPVAR|CV, UNUSED)
84388438
{
84398439
USE_OPLINE
84408440
zval *op1;
8441-
zend_long count;
8441+
zend_long count = 1;
84428442

84438443
SAVE_OPLINE();
84448444
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8445-
while (1) {
8446-
if (Z_TYPE_P(op1) == IS_ARRAY) {
8447-
count = zend_array_count(Z_ARRVAL_P(op1));
8448-
break;
8449-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
8450-
zend_object *zobj = Z_OBJ_P(op1);
8451-
8452-
/* first, we check if the handler is defined */
8453-
if (zobj->handlers->count_elements) {
8454-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
8455-
break;
8456-
}
8457-
if (UNEXPECTED(EG(exception))) {
8458-
count = 0;
8459-
break;
8460-
}
8461-
}
84628445

8463-
/* if not and the object implements Countable we call its count() method */
8464-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
8465-
zval retval;
8466-
8467-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
8468-
count = zval_get_long(&retval);
8469-
zval_ptr_dtor(&retval);
8470-
break;
8471-
}
8446+
if ((OP1_TYPE & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
8447+
op1 = Z_REFVAL_P(op1);
8448+
}
84728449

8473-
/* If There's no handler and it doesn't implement Countable then add a warning */
8474-
count = 1;
8475-
} else if ((OP1_TYPE & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
8476-
op1 = Z_REFVAL_P(op1);
8477-
continue;
8478-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
8479-
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
8480-
ZVAL_UNDEFINED_OP1();
8481-
}
8482-
count = 0;
8450+
if (Z_TYPE_P(op1) <= IS_NULL) {
8451+
/* Intentionally not converted to an exception */
8452+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
8453+
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
8454+
ZVAL_UNDEFINED_OP1();
8455+
}
8456+
count = 0;
8457+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
8458+
count = zend_array_count(Z_ARRVAL_P(op1));
8459+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
8460+
zend_object *zobj = Z_OBJ_P(op1);
8461+
/* First, check if the handler is defined as it is faster */
8462+
if (zobj->handlers->count_elements) {
8463+
zobj->handlers->count_elements(zobj, &count);
8464+
/* Otherwise check if the object implements Countable and call its count() method */
8465+
} else if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
8466+
zval retval;
8467+
8468+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
8469+
count = zval_get_long(&retval);
8470+
zval_ptr_dtor(&retval);
84838471
} else {
8484-
count = 1;
8472+
zend_type_error("Parameter must be an array or an object that implements Countable");
84858473
}
8486-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
8487-
break;
8474+
} else {
8475+
zend_type_error("Parameter must be an array or an object that implements Countable");
84888476
}
84898477

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

Zend/zend_vm_execute.h

Lines changed: 81 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9255,53 +9255,41 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CONST_
92559255
{
92569256
USE_OPLINE
92579257
zval *op1;
9258-
zend_long count;
9258+
zend_long count = 1;
92599259

92609260
SAVE_OPLINE();
92619261
op1 = RT_CONSTANT(opline, opline->op1);
9262-
while (1) {
9263-
if (Z_TYPE_P(op1) == IS_ARRAY) {
9264-
count = zend_array_count(Z_ARRVAL_P(op1));
9265-
break;
9266-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9267-
zend_object *zobj = Z_OBJ_P(op1);
9268-
9269-
/* first, we check if the handler is defined */
9270-
if (zobj->handlers->count_elements) {
9271-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
9272-
break;
9273-
}
9274-
if (UNEXPECTED(EG(exception))) {
9275-
count = 0;
9276-
break;
9277-
}
9278-
}
92799262

9280-
/* if not and the object implements Countable we call its count() method */
9281-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9282-
zval retval;
9263+
if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9264+
op1 = Z_REFVAL_P(op1);
9265+
}
92839266

9284-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9285-
count = zval_get_long(&retval);
9286-
zval_ptr_dtor(&retval);
9287-
break;
9288-
}
9267+
if (Z_TYPE_P(op1) <= IS_NULL) {
9268+
/* Intentionally not converted to an exception */
9269+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
9270+
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9271+
ZVAL_UNDEFINED_OP1();
9272+
}
9273+
count = 0;
9274+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
9275+
count = zend_array_count(Z_ARRVAL_P(op1));
9276+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
9277+
zend_object *zobj = Z_OBJ_P(op1);
9278+
/* First, check if the handler is defined as it is faster */
9279+
if (zobj->handlers->count_elements) {
9280+
zobj->handlers->count_elements(zobj, &count);
9281+
/* Otherwise check if the object implements Countable and call its count() method */
9282+
} else if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
9283+
zval retval;
92899284

9290-
/* If There's no handler and it doesn't implement Countable then add a warning */
9291-
count = 1;
9292-
} else if ((IS_CONST & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
9293-
op1 = Z_REFVAL_P(op1);
9294-
continue;
9295-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
9296-
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
9297-
ZVAL_UNDEFINED_OP1();
9298-
}
9299-
count = 0;
9285+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
9286+
count = zval_get_long(&retval);
9287+
zval_ptr_dtor(&retval);
93009288
} else {
9301-
count = 1;
9289+
zend_type_error("Parameter must be an array or an object that implements Countable");
93029290
}
9303-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
9304-
break;
9291+
} else {
9292+
zend_type_error("Parameter must be an array or an object that implements Countable");
93059293
}
93069294

93079295
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -16334,53 +16322,41 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_TMPVAR_UNUSED_HANDL
1633416322
{
1633516323
USE_OPLINE
1633616324
zval *op1;
16337-
zend_long count;
16325+
zend_long count = 1;
1633816326

1633916327
SAVE_OPLINE();
1634016328
op1 = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
16341-
while (1) {
16342-
if (Z_TYPE_P(op1) == IS_ARRAY) {
16343-
count = zend_array_count(Z_ARRVAL_P(op1));
16344-
break;
16345-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
16346-
zend_object *zobj = Z_OBJ_P(op1);
1634716329

16348-
/* first, we check if the handler is defined */
16349-
if (zobj->handlers->count_elements) {
16350-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
16351-
break;
16352-
}
16353-
if (UNEXPECTED(EG(exception))) {
16354-
count = 0;
16355-
break;
16356-
}
16357-
}
16358-
16359-
/* if not and the object implements Countable we call its count() method */
16360-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
16361-
zval retval;
16330+
if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
16331+
op1 = Z_REFVAL_P(op1);
16332+
}
1636216333

16363-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16364-
count = zval_get_long(&retval);
16365-
zval_ptr_dtor(&retval);
16366-
break;
16367-
}
16334+
if (Z_TYPE_P(op1) <= IS_NULL) {
16335+
/* Intentionally not converted to an exception */
16336+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
16337+
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16338+
ZVAL_UNDEFINED_OP1();
16339+
}
16340+
count = 0;
16341+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
16342+
count = zend_array_count(Z_ARRVAL_P(op1));
16343+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
16344+
zend_object *zobj = Z_OBJ_P(op1);
16345+
/* First, check if the handler is defined as it is faster */
16346+
if (zobj->handlers->count_elements) {
16347+
zobj->handlers->count_elements(zobj, &count);
16348+
/* Otherwise check if the object implements Countable and call its count() method */
16349+
} else if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
16350+
zval retval;
1636816351

16369-
/* If There's no handler and it doesn't implement Countable then add a warning */
16370-
count = 1;
16371-
} else if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
16372-
op1 = Z_REFVAL_P(op1);
16373-
continue;
16374-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
16375-
if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
16376-
ZVAL_UNDEFINED_OP1();
16377-
}
16378-
count = 0;
16352+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
16353+
count = zval_get_long(&retval);
16354+
zval_ptr_dtor(&retval);
1637916355
} else {
16380-
count = 1;
16356+
zend_type_error("Parameter must be an array or an object that implements Countable");
1638116357
}
16382-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
16383-
break;
16358+
} else {
16359+
zend_type_error("Parameter must be an array or an object that implements Countable");
1638416360
}
1638516361

1638616362
ZVAL_LONG(EX_VAR(opline->result.var), count);
@@ -45181,53 +45157,41 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_COUNT_SPEC_CV_UNUSED_HANDLER(Z
4518145157
{
4518245158
USE_OPLINE
4518345159
zval *op1;
45184-
zend_long count;
45160+
zend_long count = 1;
4518545161

4518645162
SAVE_OPLINE();
4518745163
op1 = EX_VAR(opline->op1.var);
45188-
while (1) {
45189-
if (Z_TYPE_P(op1) == IS_ARRAY) {
45190-
count = zend_array_count(Z_ARRVAL_P(op1));
45191-
break;
45192-
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45193-
zend_object *zobj = Z_OBJ_P(op1);
4519445164

45195-
/* first, we check if the handler is defined */
45196-
if (zobj->handlers->count_elements) {
45197-
if (SUCCESS == zobj->handlers->count_elements(zobj, &count)) {
45198-
break;
45199-
}
45200-
if (UNEXPECTED(EG(exception))) {
45201-
count = 0;
45202-
break;
45203-
}
45204-
}
45205-
45206-
/* if not and the object implements Countable we call its count() method */
45207-
if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45208-
zval retval;
45165+
if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45166+
op1 = Z_REFVAL_P(op1);
45167+
}
4520945168

45210-
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45211-
count = zval_get_long(&retval);
45212-
zval_ptr_dtor(&retval);
45213-
break;
45214-
}
45169+
if (Z_TYPE_P(op1) <= IS_NULL) {
45170+
/* Intentionally not converted to an exception */
45171+
zend_error(E_DEPRECATED, "%s(): Passing null is deprecated", opline->extended_value ? "sizeof" : "count");
45172+
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45173+
ZVAL_UNDEFINED_OP1();
45174+
}
45175+
count = 0;
45176+
} else if (Z_TYPE_P(op1) == IS_ARRAY) {
45177+
count = zend_array_count(Z_ARRVAL_P(op1));
45178+
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
45179+
zend_object *zobj = Z_OBJ_P(op1);
45180+
/* First, check if the handler is defined as it is faster */
45181+
if (zobj->handlers->count_elements) {
45182+
zobj->handlers->count_elements(zobj, &count);
45183+
/* Otherwise check if the object implements Countable and call its count() method */
45184+
} else if (zend_class_implements_interface(zobj->ce, zend_ce_countable)) {
45185+
zval retval;
4521545186

45216-
/* If There's no handler and it doesn't implement Countable then add a warning */
45217-
count = 1;
45218-
} else if ((IS_CV & (IS_VAR|IS_CV)) != 0 && Z_TYPE_P(op1) == IS_REFERENCE) {
45219-
op1 = Z_REFVAL_P(op1);
45220-
continue;
45221-
} else if (Z_TYPE_P(op1) <= IS_NULL) {
45222-
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
45223-
ZVAL_UNDEFINED_OP1();
45224-
}
45225-
count = 0;
45187+
zend_call_method_with_0_params(zobj, NULL, NULL, "count", &retval);
45188+
count = zval_get_long(&retval);
45189+
zval_ptr_dtor(&retval);
4522645190
} else {
45227-
count = 1;
45191+
zend_type_error("Parameter must be an array or an object that implements Countable");
4522845192
}
45229-
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");
45230-
break;
45193+
} else {
45194+
zend_type_error("Parameter must be an array or an object that implements Countable");
4523145195
}
4523245196

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

0 commit comments

Comments
 (0)