0

I'm write simple extension with class definition

extension.h

zend_class_entry * ExampleClass_class;
zend_class_entry * get_ExampleClass_class();

extension.c

#include "php.h"
#include "extension.h"
...
zend_class_entry * get_ExampleClass_class(){
    return ExampleClass_class;
}

.... 
PHP_METHOD(ExampleClass, getInstance){
    ZEND_PARSE_PARAMETERS_START(0, 0)
        Z_PARAM_OPTIONAL
    ZEND_PARSE_PARAMETERS_END();

    RETURN_OBJ(
// ----------- fun objectToZval(obj: PhpObject) = obj.zval  //CPointer<zval>
        example_symbols()->kotlin.root.php.extension.proxy.objectToZval(
            example_symbols()->kotlin.root.exampleclass.getInstance(
// -------   Unused parameter
                  example_symbols()
                    ->kotlin.root.php.extension.proxy.phpObj(
                        ExampleClass_class, getThis()
                      )
// -------   Unused parameter end
              )
           )
   )
}

Also I write and compile static library with logic realization (Kotlin Native)

.def

static inline zval* zend_helper_new_ExampleClass() {
    zval *obj = malloc(sizeof(zval));
    object_init_ex(obj, get_ExampleClass_class());
    return obj;
}

.kt

fun newExampleClass() = zend_helper_new_ExampleClass()!!

//PhpObject is wrapper for two fields CPointer<zend_class_entry> and CPointer<zval>
class PhpObject(val context: CPointer<zend_class_entry>, val zval: PhpMixed) {
    companion object {
        fun fromMixed(zval: PhpMixed) = PhpObject(zval.pointed!!.value.obj!!.pointed!!.ce!!, zval)
    }
....
}

val PhpMixed.phpObject get() = PhpObject.fromMixed(this)

fun getInstance(obj: PhpObject) = newExampleClass().phpObject

Finally I run PHP code

var_dump(ExampleClass::getInstance());

And receive this

# /opt/rh/rh-php71/root/usr/bin/php -dextension=`ls ./phpmodule/modules/*.so` -r "var_dump(ExampleClass::getInstance());"
*RECURSION*
#

Where I mistaken?

UPD

static inline zval* zend_helper_new_{className}() {
    zval *obj = malloc(sizeof(zval));
    object_init_ex(obj, get_{className}_class());
    php_printf("Just created FLAGS %u\n", GC_FLAGS(obj->value.obj));   

    return obj;
}

Just created object have GC_FLAGS equals 0

*RECURSIVE* apears in function php_var_dump by code

    case IS_OBJECT:
        if (Z_IS_RECURSIVE_P(struc)) {
            PUTS("*RECURSION*\n");
            return;
        }

Macro->macro->macro->Oh god!->macro->macro...

Z_IS_RECURSIVE_P(struc) = (GC_FLAGS((*(zval)).value.counted) & GC_PROTECTED)

Okay...

php_printf("%d\n", GC_FLAGS((*(obj)).value.counted));

Returns 0

Must not trigger *RECURSIVE*, but... Why!?

rjhdby
  • 1,278
  • 13
  • 15
  • What version of PHP are you compiling against? If master, then have a look at the GC_FLAGS() of the object (`GC_PROTECTED` flag) – bwoebi Sep 10 '18 at 12:08
  • yea, so `IS_APPLY_COUNT` flag is set on that object and it immediately fails with `*RECURSION*` thus. The mistake is somewhere else; the code you've posted is fine. You'll need to debug that, why the 3 least significant bits of GC_FLAGS() are all three set. – bwoebi Sep 10 '18 at 13:10
  • @bwoebi Sorry, forgot to remove `zval_dtor` from the function (experimented with despair). The actual value of the flag is 0 (zero) – rjhdby Sep 10 '18 at 14:01
  • Then, does your class have _DebugInfo? Otherwise I don't have much suggestions what to look for, except debugging backwards from the php_var_dump() where it prints the `*RECURSION*` – bwoebi Sep 10 '18 at 14:10

1 Answers1

0

First

For compilation I used PHP 7.1.8, but coding based on latest sources.

Recursion protection has been changed 06.10.2017

Actual var_dump code for 7.1.8

case IS_OBJECT:
    if (Z_OBJ_APPLY_COUNT_P(struc) > 0) {
        PUTS("*RECURSION*\n");
        return;
    }

But it doesn't matter

Second

RETURN_OBJ(
        example_symbols()->kotlin.root.php.extension.proxy.objectToZval(
            example_symbols()->kotlin.root.exampleclass.getInstance(/*unused*/)
           )
   )

Let's expand the macro RETURN_OBJ (r)

  1. RETURN_OBJ(r)
  2. { RETVAL_OBJ(r); return; }
  3. { ZVAL_OBJ(return_value, r); return; }
  4. .

    { do {                      
        zval *__z = (return_value);                     
        Z_OBJ_P(__z) = (r);                     
        Z_TYPE_INFO_P(__z) = IS_OBJECT_EX;      
    } while (0); return; }
    
  5. .

    { do {                      
        zval *__z = (return_value);                     
        Z_OBJ(*(__z)) = (r);                        
        Z_TYPE_INFO(*(__z)) = (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT));     
    } while (0); return; }
    
  6. .

    { do {                      
        zval *__z = (return_value);
        (*(__z)).value.obj = (r);
        (*(__z)).u1.type_info = (8 | ((1<<0) << 8));
    } while (0); return; }
    

You see? :)

Yea, this macro must receive zend_object but not zval

Just change return expression to

example_symbols()->kotlin.root.php.extension.proxy.zendObject(
    example_symbols()->kotlin.root.exampleclass.getInstance(/*unused*/)
)

where

fun zendObject(obj: PhpObject) = obj.zval.pointed!!.value.obj!!

Bingo!

PS Special thanks for php developers community for incredible documented macro hell

rjhdby
  • 1,278
  • 13
  • 15
  • TBH this should have given you a compiler warning with gcc or clang (assigning `zval *` to `zend_object *`). I do typically not look for type errors as I guessed the compiler would've caught that. – bwoebi Sep 13 '18 at 07:56
  • @bwoebi Unfortunately, receive no one warning from compiler – rjhdby Sep 13 '18 at 10:52