Skip to content
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

Segmentation Fault When Trying To Fetch Built-In PHP Classes #276

Open
Garethp opened this issue Dec 2, 2016 · 2 comments
Open

Segmentation Fault When Trying To Fetch Built-In PHP Classes #276

Garethp opened this issue Dec 2, 2016 · 2 comments

Comments

@Garethp
Copy link

Garethp commented Dec 2, 2016

Hi

I'm trying to add generalised support for allowing a class to extend a built-in PHP class (Such as \Exception or \DateTime, but when I try to fetch the zend_class_entry with the following

            zend_string *name = zend_string_init(std::string("DateTime").data(), std::string("DateTime").size(), 1);

            zend_class_entry *dateTimeCE = zend_fetch_class(name, ZEND_FETCH_CLASS_DEFAULT TSRMLS_CC);

I get a segmentation fault, with gdb giving the following backtrace

(gdb) bt
#0  0x00005555557c7b47 in zend_hash_find ()
#1  0x00005555557a5bb0 in zend_lookup_class_ex ()
#2  0x00005555557a6555 in zend_fetch_class ()
#3  0x00007fffe824df84 in get_module () from /usr/lib/php/20151012/miro-container.so
#4  0x00005555556f927a in php_load_extension ()
#5  0x00005555557a7afe in zend_llist_apply ()
#6  0x000055555575b4ea in php_ini_register_extensions ()
#7  0x00005555557535d6 in php_module_startup ()
#8  0x0000555555848d7d in ?? ()
#9  0x0000555555637e71 in main ()

Using the zend_fetch_class method does work for classes that I define myself, as I'm later doing the following with no issue

            zend_class_entry *notFoundEntry = zend_fetch_class(zend_string_init(std::string("NotFoundException").data(), std::string("NotFoundException").size(), 1), ZEND_FETCH_CLASS_SILENT);

            zend_throw_exception(notFoundEntry, "Custom Exception Class", 1000 TSRMLS_CC);

I can also get the default exception class with zend_exception_get_default(TSRMLS_C), and register a class as extending from that, so I know that at the very least that's already registered. So my question is why does it segfault when I'm trying to fetch a built-in class, and how can I fetch it without Segfaults?

@sjinks
Copy link
Contributor

sjinks commented Dec 17, 2016

It looks like the initialization of the extension takes place right after it is loaded. However, creation of all classes by the PHP Core happens during Module Init phase.

http://lxr.php.net/xref/PHP-7.0/main/main.c#2216 - this is where PHP loads all extensions
http://lxr.php.net/xref/PHP-7.0/main/main.c#2217 - this is where PHP extensions (aka modules) are started.

But the crash itself happens for a different reason: zend_fetch_class() looks up the class name in EG(class_table) which does not yet exist — when PHP modules are started up, only CG(class_table) exists (CG = compiler globals, EG = executor globals).

EG(class_table) gets initialized in init_executor, init_executor() is called from zend_activate, zend_activate is called from php_request_startup which happens during the Request Initialization phase.

Thus the proper way to fetch an internal class would be like this:

#include <iostream>
#include <phpcpp.h>
#include <Zend/zend.h>
#include <Zend/zend_execute.h>

extern "C"
{
        PHPCPP_EXPORT void *get_module()
        {
                // create extension
                static Php::Extension extension("simple","1.0");

                extension.onStartup([]() {
                        zend_string *name = zend_string_init(ZEND_STRL("DateTime"), 1);
                        zend_string *lc_name = zend_string_tolower(name);
                        zend_class_entry *dateTimeCE = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), lc_name));
                        zend_string_release(lc_name);

// if dateTimeCE is nullptr here, the class does not exist
                        std::cerr << dateTimeCE << std::endl;
                });

                return extension.module();
        }
}

However, as this is the very first time I am looking at the PHP-CPP code, I cannot suggest you yet how to register a class from onStartup() :-)

@sjinks
Copy link
Contributor

sjinks commented Dec 18, 2016

Well, good news is that I have found the way to inherit from an internal class. Bad news is that you still cannot inherit from DateTime.

So, this is how you do that:

#include <cassert>
#include <phpcpp.h>
#include <Zend/zend.h>
#include <Zend/zend_execute.h>
#include <Zend/zend_API.h>
#include <Zend/zend_inheritance.h>

class MyClass : public Php::Base {
public:
        virtual Php::Value method()
        {
                return 42;
        }
};

extern "C"
{
        PHPCPP_EXPORT void *get_module()
        {
                static Php::Extension extension("simple","1.0");

                Php::Class<MyClass> myClass("MyClass");
                myClass.method<&MyClass::method>("method");
                extension.add(myClass);

                extension.onStartup([]() {
                        zend_string *name = zend_string_init(ZEND_STRL("LibXMLError"), 1);
                        zend_string *lc_name = zend_string_tolower(name);
                        zend_class_entry *base_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), lc_name));
                        zend_string_release(lc_name);

                        assert(base_ce != nullptr);

                        name = zend_string_init(ZEND_STRL("MyClass"), 1);
                        lc_name = zend_string_tolower(name);
                        zend_class_entry* myclass_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), lc_name));
                        zend_string_release(lc_name);

                        assert(myclass_ce != nullptr);
                        zend_do_inheritance(myclass_ce, base_ce);
                });

                return extension.module();
        }
}

Now why you cannot inherit from DateTime: zend_do_inheritance calls do_inherit_parent_constructor:

static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */
{
	ZEND_ASSERT(ce->parent != NULL);

	/* You cannot change create_object */
	ce->create_object = ce->parent->create_object;

that ce->create_object = ce->parent->create_object; spoils everything.

DateTime class is initialized like this:

        INIT_CLASS_ENTRY(ce_date, "DateTime", date_funcs_date);
        ce_date.create_object = date_object_new_date;

And it appears that you can have only one create_object for the entire inheritance chain. Because PHP-CPP needs to use its own create_object function, you cannot inherit from DateTime.

So,

zend_class_entry* myclass_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), lc_name));
zend_string_release(lc_name);

assert(myclass_ce != nullptr);

if (!base_ce->create_object || base_ce->create_object == myclass_ce->create_object) {
    zend_do_inheritance(myclass_ce, base_ce);
}
else {
    // FAIL, you cannot inherit from the base class
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants