Categories
PHP Zend

Hacking PHP to allow the redeclaration of functions. (II)

This is the next step in my adventure trying to redeclarate functions in PHP, you can see the previous effort in the linked post.

We are going again to the do_bind_function to use what we learned in the last chapter if you look at it is looks strange know since it is finding the zv key when it has still not putting a value in the table.

ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
{   
    zend_function *function;
    zval *rtd_key, *zv;
    rtd_key = lcname + 1;
    zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), NULL, 0);
        return FAILURE;
    }
    function = (zend_function*)Z_PTR_P(zv);
    if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
            && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
        zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
    } else {
        zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
    }
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);
        return FAILURE;
    }
    return SUCCESS;
}

Would be great to solve that mistery before getting forward breaking things. We search for that function:

sergio@bahdder ~/php-8.0.3 $ rg do_bind_function
Zend/zend_compile.h
776:ZEND_API zend_result do_bind_function(zval *lcname);

Zend/zend_vm_def.h
7589:   do_bind_function(RT_CONSTANT(opline, opline->op1));

Zend/zend_vm_execute.h
2821:   do_bind_function(RT_CONSTANT(opline, opline->op1));

Zend/zend_compile.c
1054:ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
1061:        do_bind_function_error(Z_STR_P(lcname), NULL, 0);
1072:        do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);

UPGRADING.INTERNALS
281:        - do_bind_function()

And then we are going to look at zend_vm_def.h what it does.

ZEND_VM_HANDLER(141, ZEND_DECLARE_FUNCTION, ANY, ANY)
{   
    USE_OPLINE
    
    SAVE_OPLINE();
    do_bind_function(RT_CONSTANT(opline, opline->op1));
    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

Macros everywhere…

Zend/zend_vm_execute.h
402:# define SAVE_OPLINE() EX(opline) = opline

This gives a important hint about how opline is declared, but not why the function is expected to be in the hash table.

Whatever, I am going to do this modification and restore do_bind_function_error.

ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
{       
    zend_function *function;
    zval *rtd_key, *zv;
    rtd_key = lcname + 1;
    zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), NULL, 0);
        return FAILURE;
    }
    function = (zend_function*)Z_PTR_P(zv);
    if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
            && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
        zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
        if (!zv) {
            zv = zend_hash_update(EG(function_table), Z_STR_P(lcname), zv);
        }
    } else {
        zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
    }
    if (UNEXPECTED(!zv)) {
        do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);
        return FAILURE;
    }
    return SUCCESS;
}
sergio@bahdder ~/php-8.0.3 $ php -r 'function a(){} function a(){ print "hello";} a();'
PHP Fatal error:  Cannot redeclare a() (previously declared in Command line code:1) in Command line code on line 1

It is still happening, let’s look elsewhere…

if (toplevel) {
        if (zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL) {
            zend_hash_update_ptr(CG(function_table), lcname, op_array);
        }
        zend_string_release_ex(lcname, 0);
        return;
    }
ZEND_API zend_result do_bind_function(zval *lcname) /* {{{ */
{
    zend_function *function;
    zval *rtd_key, *zv;
    rtd_key = lcname + 1;
    zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
    function = (zend_function*)Z_PTR_P(zv);
    if (UNEXPECTED(function->common.fn_flags & ZEND_ACC_PRELOADED)
            && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
        zv = zend_hash_add(EG(function_table), Z_STR_P(lcname), zv);
        if (!zv) {
            zv = zend_hash_update(EG(function_table), Z_STR_P(lcname), zv);
        }
    } else {
        zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
    }
    return SUCCESS;
}

Let’s try now…

Let’s create a file named a.php with this content:

<?php
function a() {
    print "hola";
}

And now we are going to try this:

 b/usr/local/bin/php -r 'function a(){} a(); include "a.php"; a();'
sergio@bahdder ~/php-8.0.3 $ b/usr/local/bin/php -r 'function a(){ echo "adios"; } a(); include "a.php"; a();'
adiosholasergio@bahdder ~/php-8.0.3 $ 

This appears to have worked, if I continue with this I will try to do this with a function named mock in a extension to get into extension development keeping my hands out of core.

By sergiotarxz

I am a software developer with high interest on free software.

Leave a Reply

Your email address will not be published. Required fields are marked *