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
Simplify Callable implementation #303
Conversation
(cherry picked from commit 2bbf103)
Pretty interesting: that code is not invoked… https://codecov.io/gh/sjinks/PHP-CPP/src/tests/zend/callable.cpp#L25 |
It can actually be called, but only if you use a deprecated way of defining your member functions. If you look in include/class.h you will find a number of overloads for the add method that are deprecated. They give the callback as a regular parameter instead of a template parameter. These methods should use the code you have improved. The whole point of this was to eventually be able to remove the old methods (because they are so inefficient) - but until we do that it won't hurt to optimize for this. Can you see if your new code works properly with this? If so I'd be happy to merge it. About setting up Travis CI, this is also something I'm interested in, but haven't done due to lack of time and experience in this field. We used to have tests for PHP-CPP, but they were removed by someone when they had grown outdated and people were asking questions. If you can help me set up automated tests I think it would be a huge improvement. |
I just tested your code, and this approach unfortunately doesn't work. As you can see in callable.h, we create an array of zend_internal_arg_info structures. You can never actually retrieve them anywhere from PHP. They get copied to an array of zend_arg_info structures which look alike but aren't. The extra entries are not copied, so we end up with undefined behavior. |
@martijnotto The code does fail but for another reason. In fact, arginfo is never copied. lxr.php.net is down, so I will have to paste the code here: zend_register_functions() in Zend/zend_API.c, line 2177 in PHP 7.0.9: if (ptr->arg_info) {
zend_internal_function_info *info = (zend_internal_function_info*)ptr->arg_info;
internal_function->arg_info = (zend_internal_arg_info*)ptr->arg_info+1;
internal_function->num_args = ptr->num_args;
/* Currently you cannot denote that the function can accept less arguments than num_args */
if (info->required_num_args == (zend_uintptr_t)-1) {
internal_function->required_num_args = ptr->num_args;
} else {
internal_function->required_num_args = info->required_num_args;
}
if (info->return_reference) {
internal_function->fn_flags |= ZEND_ACC_RETURN_REFERENCE;
}
if (ptr->arg_info[ptr->num_args].is_variadic) {
// ...
}
if (info->type_hint) {
// ...
}
} else {
internal_function->arg_info = NULL;
internal_function->num_args = 0;
internal_function->required_num_args = 0;
} The line of interest is this: internal_function->arg_info = (zend_internal_arg_info*)ptr->arg_info+1; function's argument information is just a pointer to the originally provided arginfo array plus one element (zeroth one contains information about the function itself).
/* arg_info for internal functions */
typedef struct _zend_internal_arg_info {
const char *name;
const char *class_name;
zend_uchar type_hint;
zend_uchar pass_by_reference;
zend_bool allow_null;
zend_bool is_variadic;
} zend_internal_arg_info;
/* arg_info for user functions */
typedef struct _zend_arg_info {
zend_string *name;
zend_string *class_name;
zend_uchar type_hint;
zend_uchar pass_by_reference;
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info; The issue is that I was off by one in // Sanity check
- assert(info[argc+1].class_name == nullptr && info[argc+1].name == nullptr);
- assert(info[argc+2].class_name != nullptr && info[argc+2].name == nullptr);
+ assert(info[argc+0].class_name == nullptr && info[argc+0].name == nullptr);
+ assert(info[argc+1].class_name != nullptr && info[argc+1].name == nullptr);
// the callable we are retrieving
- Callable *callable = reinterpret_cast<Callable*>(info[argc+2].class_name);
+ Callable *callable = reinterpret_cast<Callable*>(info[argc+1].class_name); If you use the corrected implementation of Tested with: #include <ostream>
static void test303()
{
Php::out << "Hello world!" << std::endl;
}
// ...
e.add("test303", &test303); --TEST--
Issue 303
--FILE--
<?php
test303();
?>
--EXPECT--
Hello world! |
The updated code is here: https://github.com/sjinks/PHP-CPP/tree/callable |
Ah, that does solve the issues. I have merged it manually now, since I was unable to reopen the pull request. I have also removed the additional empty element. It seems to work just fine without this. |
This PR addresses the following comment found in the code of zend/callable.cpp:
Though the required information cannot be found in
EG(This)
(especially for function calls — functions have no$this
), yet still there is a way to store and retrieve that information in Zend structures.The idea is that we allocate two arginfo's more than necessary:
The first extra arginfo will be a sentinel with its
name
andclass_name
set tonullptr
, the second one will storethis
of the Callable itself inclass_name
(andname
will benullptr
).If necessary, we can live without the sentinel and save some memory (12 bytes for 32 bit, 24 bytes for 64 bit), it is used only to simplify debugging.
The initialization becomes as simple as that:
Retrieval of the Callable:
Bonus: since this code was asking for trouble:
I have marked that copy constructor and other copy constructors depending on it as deleted. It looks like copying of callables was not used by the library…
This modified implementation successfully passes all tests (BTW, if you are interested in setting up Travis CI, I have tests to share 😉 )