Skip to content

Fix #79969: Wrong error message in strict mode in ReflectionMethod::_construct #5995

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

Closed
wants to merge 3 commits into from
Closed
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
64 changes: 43 additions & 21 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -2953,27 +2953,49 @@ ZEND_METHOD(reflection_method, __construct)
char *name_str, *tmp;
size_t name_len, tmp_len;
zval ztmp;
const char *space;

if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "zs", &classname, &name_str, &name_len) == FAILURE) {
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s", &name_str, &name_len) == FAILURE) {
return;
}
if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "z|s!", &classname, &name_str, &name_len) == FAILURE) {
return;
}

if ((tmp = strstr(name_str, "::")) == NULL) {
zend_throw_exception_ex(reflection_exception_ptr, 0,
"Invalid method name %s", name_str);
return;
}
classname = &ztmp;
tmp_len = tmp - name_str;
ZVAL_STRINGL(classname, name_str, tmp_len);
name_len = name_len - (tmp_len + 2);
name_str = tmp + 2;
orig_obj = NULL;
} else if (Z_TYPE_P(classname) == IS_OBJECT) {
orig_obj = classname;
} else {
orig_obj = NULL;
switch (Z_TYPE_P(classname)) {
case IS_STRING:
orig_obj = NULL;

if (ZEND_NUM_ARGS() == 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (ZEND_NUM_ARGS() == 1) {
if (!name_str) {

But we'll need @cmb69 's approval for making the second parameter nullable for PHP 7.3. But it should absolutely work for PHP 8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a strict no from me, but I'd rather not change the signature in a stable version. @derickr what do you think about that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Probably adding the ! modifier only makes sense for PHP 8.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to not complicate it further it would be better if I revert the code back to the first commit in this PR, and then apply this feedback. Then, for PHP 8, a different solution like having a nullable 2nd parameter, could be applied.

However, I am not sure if this is desirable and, in case it is, about the procedure to make a change in different PHP versions.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's also a sensible idea. We could also ask from ourselves if this is really needed to be fixed in PHP 7 at all? Personally, I'd only fix it in 8, but ultimately, it's Christoph's and Derick's call :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that fixing the erroneous error message for PHP-7.3 makes sense (given there is no change in the signature, i.e. second parameter is not nullable). After that has been merged into PHP-7.4 and the "master" branch, another PR removing the function overload (i.e. second parameter made nullable) could follow up.

name_str = Z_STRVAL_P(classname);
name_len = Z_STRLEN_P(classname);

if ((tmp = strstr(name_str, "::")) == NULL) {
zend_throw_exception_ex(reflection_exception_ptr, 0, "Invalid method name %s", name_str);
return;
}

classname = &ztmp;
tmp_len = tmp - name_str;
ZVAL_STRINGL(classname, name_str, tmp_len);
name_len = name_len - (tmp_len + 2);
name_str = tmp + 2;
}

break;

case IS_OBJECT:
if (ZEND_NUM_ARGS() == 1 || !name_str) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (ZEND_NUM_ARGS() == 1 || !name_str) {
if (!name_str) {

name_str will be NULL if 1 argument is given

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to explicitly put ZEND_NUM_ARGS() == 1 || because when no parameter was provided, name_str was filled up with garbage. Despite i had been looking why for a very long time, I still ignore the reason...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to initialize name_str and name_len, since optional parameters are only initialized during ZPP if they are provided. So that's the reason :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, thank you! My C is still a little rusty after so much time...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know what you're talking about :) I needed quite a lot of time to have even a vague idea about what I see in php-src :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, thank you! My C is still a little rusty after so much time...

PHP-src is more it's own version of C than actual C at times due to the bazillion macros, so yeah don't worry lol

tmp = get_active_class_name(&space);

zend_type_error("%s%s%s() expects parameter %d to be %s, %s given",
tmp, space, get_active_function_name(), 2, "string", "null");
return;
}

orig_obj = classname;

break;

default:
_DO_THROW("The parameter class is expected to be either a string or an object");
}

object = getThis();
Expand Down Expand Up @@ -3002,8 +3024,8 @@ ZEND_METHOD(reflection_method, __construct)
if (classname == &ztmp) {
zval_ptr_dtor_str(&ztmp);
}
_DO_THROW("The parameter class is expected to be either a string or an object");
/* returns out of this function */

return;
}

if (classname == &ztmp) {
Expand Down
2 changes: 1 addition & 1 deletion ext/reflection/tests/008.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ echo "Done\n";
?>
--EXPECT--
string(20) "Invalid method name "
string(21) "Invalid method name 1"
string(66) "The parameter class is expected to be either a string or an object"
string(21) "Class does not exist"
string(22) "Class a does not exist"
string(21) "Class does not exist"
Expand Down
39 changes: 31 additions & 8 deletions ext/reflection/tests/ReflectionMethod_006.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,40 @@ Steve Seear <[email protected]>
<?php

try {
new ReflectionMethod();
} catch (TypeError $re) {
echo "Ok - ".$re->getMessage().PHP_EOL;
new ReflectionMethod();
} catch (Throwable $e) {
var_dump($e->getMessage());
}

try {
new ReflectionMethod('a', 'b', 'c');
} catch (TypeError $re) {
echo "Ok - ".$re->getMessage().PHP_EOL;
new ReflectionMethod('a', 'b', 'c');
} catch (Throwable $e) {
var_dump($e->getMessage());
}


class C {
public function f() {}
}

try {
new ReflectionMethod(new C);
} catch (Throwable $e) {
var_dump($e->getMessage());
}

try {
new ReflectionMethod(new C, null);
} catch (Throwable $e) {
var_dump($e->getMessage());
}

try {
new ReflectionMethod(new C, "");
} catch (Throwable $e) {
var_dump($e->getMessage());
}

$rm = new ReflectionMethod('C', 'f');

var_dump($rm->isFinal(1));
Expand All @@ -43,8 +63,11 @@ var_dump($rm->getName(1));

?>
--EXPECTF--
Ok - ReflectionMethod::__construct() expects exactly 1 parameter, 0 given
Ok - ReflectionMethod::__construct() expects exactly 1 parameter, 3 given
string(69) "ReflectionMethod::__construct() expects at least 1 parameter, 0 given"
string(69) "ReflectionMethod::__construct() expects at most 2 parameters, 3 given"
string(76) "ReflectionMethod::__construct() expects parameter 2 to be string, null given"
string(76) "ReflectionMethod::__construct() expects parameter 2 to be string, null given"
string(27) "Method C::() does not exist"

Warning: ReflectionMethod::isFinal() expects exactly 0 parameters, 1 given in %s on line %d
NULL
Expand Down
8 changes: 4 additions & 4 deletions ext/reflection/tests/ReflectionMethod_constructor_error1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ try {
?>
--EXPECTF--
Wrong type of argument (bool):
ReflectionException: Invalid method name 1 in %s
ReflectionException: The parameter class is expected to be either a string or an object in %s:%d
Stack trace:
#0 %s ReflectionMethod->__construct('1')
#0 %s ReflectionMethod->__construct(true)
#1 {main}
Wrong type of argument (int):
ReflectionException: Invalid method name 3 in %s
ReflectionException: The parameter class is expected to be either a string or an object in %s:%d
Stack trace:
#0 %s ReflectionMethod->__construct('3')
#0 %s ReflectionMethod->__construct(3)
#1 {main}
Wrong type of argument (bool, string):
ReflectionException: The parameter class is expected to be either a string or an object in %s
Expand Down
23 changes: 12 additions & 11 deletions ext/reflection/tests/ReflectionMethod_constructor_error2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,22 @@ class TestClass
}
}

echo "Too few arguments:\n";

try {
echo "Too few arguments:\n";
$methodInfo = new ReflectionMethod();
} catch (TypeError $re) {
echo "Ok - ".$re->getMessage().PHP_EOL;
} catch (Throwable $e) {
var_dump($e->getMessage());
}

echo "\nToo many arguments:\n";

try {
echo "\nToo many arguments:\n";
$methodInfo = new ReflectionMethod("TestClass", "foo", true);
} catch (TypeError $re) {
echo "Ok - ".$re->getMessage().PHP_EOL;
} catch (Throwable $e) {
var_dump($e->getMessage());
}


try {
//invalid class
$methodInfo = new ReflectionMethod("InvalidClassName", "foo");
Expand All @@ -50,12 +51,12 @@ try{
}

?>
--EXPECT--
--EXPECTF--
Too few arguments:
Ok - ReflectionMethod::__construct() expects exactly 1 parameter, 0 given
string(69) "ReflectionMethod::__construct() expects at least 1 parameter, 0 given"

Too many arguments:
Ok - ReflectionMethod::__construct() expects exactly 1 parameter, 3 given
string(69) "ReflectionMethod::__construct() expects at most 2 parameters, 3 given"
Ok - Class InvalidClassName does not exist
Ok - The parameter class is expected to be either a string or an object
Ok - ReflectionMethod::__construct() expects exactly 1 parameter, 2 given
Ok - ReflectionMethod::__construct() expects parameter 2 to be string, array given
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing new line before EOF

14 changes: 14 additions & 0 deletions ext/reflection/tests/bug79969.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Bug #79969: ReflectionMethod::__construct throws incorrect exception when 2nd parameter is int
--FILE--
<?php
declare(strict_types=1);

new ReflectionMethod('xxx', 1);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: ReflectionMethod::__construct() expects parameter 2 to be string, int given in %s
Stack trace:
#0 %s(%d): ReflectionMethod->__construct('xxx', 1)
#1 {main}
thrown in %s on line %d