php-extsample
php-extsample copied to clipboard
Pass array by reference and modify inside function
I have something like this: PHP_FUNCTION(modify) { smart_str ss = { 0 }; zval *val; long opts = 0; zval *strings ; HashTable sht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zla", &val, &opts, &strings) == FAILURE) return; sht = Z_ARRVAL_P(strings) ; cmodify(&ss, val, opts, &sht TSRML_CC) ; RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); }
cmodify expects sht to be HashTable but I would like strings(array) could replace sht
Thanks in advance.
I am not sure I fully understand that the question. Do you want to modify the elements of "strings" array inside cmodify function?
Yes that is exactly what I want. Basicly a would like to rewrite php-amf3 (AMF serializator) extension so that it keeps proper stringReferences. I tried writing to strings inside modify function with add_assoc_* and that worked OK but when I try writing to sht with zend_hash_add_* I get internal error
This is original unmodified function PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val; long opts = 0; HashTable sht, oht, tht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &val, &opts) == FAILURE) return; zend_hash_init(&sht, 0, NULL, NULL, 0); zend_hash_init(&oht, 0, NULL, NULL, 0); zend_hash_init(&tht, 0, NULL, NULL, 0); encodeValue(&ss, val, opts, &sht, &oht, &tht TSRMLS_CC); zend_hash_destroy(&sht); zend_hash_destroy(&oht); zend_hash_destroy(&tht); RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); } I would like sht, oht and tht to be passed in by reference
Essentially what you need to do is to mark the argument as a reference in arg info, see first parameter in: http://lxr.php.net/xref/PHP_5_5/Zend/zend_API.h#101
Also, note that Z_ARRVAL_P gives a pointer to the HashTable so if the cmodify expects HashTable * you need to pass "sht" rather than "&sht".
So change to: ZEND_BEGIN_ARG_INFO_EX(arginfo_amf3_encode, 0, 0, 1) ZEND_ARG_INFO(0, value) ZEND_ARG_INFO(1, sht) ZEND_ARG_INFO(1, oht) ZEND_ARG_INFO(1, tht) ZEND_ARG_INFO(0, options) ZEND_END_ARG_INFO()
and
PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val, *sht, *oht, *tht; long opts = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaa|l", &val, &sht, &oht, &tht, &opts) == FAILURE) return;
encodeValue(&ss, val, opts, HASH_OF(sht), HASH_OF(oht), HASH_OF(tht) TSRMLS_CC);
RETVAL_STRINGL(ss.c, ss.len, 1);
smart_str_free(&ss);
}
Is that correct?
The last argument to ZEND_BEGIN_ARG_INFO_EX should be 4 as there are four mandatory arguments for the function. Other than that it looks ok.
Changed ZEND_BEGIN_ARG_INFO_EX to 4 and it compiled without problems. When I test it I get in kern.log php5-fpm[6403]: segfault at 10 ip 00000000006db764 sp 00007fff75b5cd90 error 4 in php5-fpm[400000+7fd000]
php version: php 5.5.3
if I comment out //encodeValue(&ss, val, opts, HASH_OF(sht), HASH_OF(oht), HASH_OF(tht) TSRMLS_CC); There is no segfault but of course it does nothing
Definition of encodeValue
static void encodeValue(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht TSRMLS_DC) {
What are the contents of encodeValue? Have you tried debugging with gdb?
Full: static void encodeValue(smart_str ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht TSRMLS_DC) { switch (Z_TYPE_P(val)) { default: smart_str_appendc(ss, AMF3_UNDEFINED); break; case IS_NULL: smart_str_appendc(ss, AMF3_NULL); break; case IS_BOOL: smart_str_appendc(ss, Z_LVAL_P(val) ? AMF3_TRUE : AMF3_FALSE); break; case IS_LONG: { int i = Z_LVAL_P(val); if ((i >= AMF3_MIN_INT) && (i <= AMF3_MAX_INT)) { smart_str_appendc(ss, AMF3_INTEGER); encodeU29(ss, i); } else { smart_str_appendc(ss, AMF3_DOUBLE); encodeDouble(ss, i); } break; } case IS_DOUBLE: smart_str_appendc(ss, AMF3_DOUBLE); encodeDouble(ss, Z_DVAL_P(val)); break; case IS_STRING: smart_str_appendc(ss, AMF3_STRING); encodeStr(ss, Z_STRVAL_P(val), Z_STRLEN_P(val), sht TSRMLS_CC); break; case IS_ARRAY: if (!(opts & AMF3_FORCE_OBJECT) || (getHashLen(Z_ARRVAL_P(val)) >= 0)) { smart_str_appendc(ss, AMF3_ARRAY); encodeArray(ss, val, opts, sht, oht, tht TSRMLS_CC); break; } / fall through; encode array as object */ case IS_OBJECT: smart_str_appendc(ss, AMF3_OBJECT); encodeObject(ss, val, opts, sht, oht, tht TSRMLS_CC); break; } }
I did not use GDB.
Whole extension works without problems if not changed.
One function that changes sht value static void encodeStr(smart_str ss, const char *str, int len, HashTable *ht TSRMLS_DC) { if (len > AMF3_MAX_INT) len = AMF3_MAX_INT; if (len) { / empty string is never sent by reference / int *oidx, nidx; if (zend_hash_find(ht, str, len, (void *)&oidx) == SUCCESS) { encodeU29(ss, *oidx << 1); return; } nidx = zend_hash_num_elements(ht); if (nidx <= AMF3_MAX_INT) zend_hash_add(ht, str, len, &nidx, sizeof(nidx), NULL); } encodeU29(ss, (len << 1) | 1); smart_str_appendl(ss, str, len); }
GDB [New Thread 0x7ffff101e700 (LWP 11493)] [Thread 0x7ffff101e700 (LWP 11493) exited] string(12) " test"
Program received signal SIGSEGV, Segmentation fault. _zval_ptr_dtor (zval_ptr=0x7ffff7fcc330) at /build/buildd/php5-5.5.3+dfsg/Zend/zend_execute_API.c:426 426 /build/buildd/php5-5.5.3+dfsg/Zend/zend_execute_API.c: No such file or directory.
It's going to be a lot easier if you compile a debug version of php:
./configure --prefix=/opt/php-debug --disable-all
make
make install
and then compile the extension against the debug version
./configure --with-php-config=/opt/php-debug/bin/php-config
make
And then run the code under GDB from the extension directory:
gdb --args /opt/php-debug/bin/php -d extension_dir=modules/ -d extension=<extnamehere>.so yourscript.php
That way you will get symbols in the backtrace and can see where the crash happens.
now I get
Program received signal SIGSEGV, Segmentation fault. _zval_ptr_dtor (zval_ptr=0x7ffff7fcfd00) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:426 426 i_zval_ptr_dtor(*zval_ptr ZEND_FILE_LINE_RELAY_CC);
Type "bt" into the gdb console when it crashes and press enter and you will get a full backtrace.
#0 _zval_ptr_dtor (zval_ptr=0x7ffff7fcfd00) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:426
#1 0x000000000057a5b8 in zend_hash_destroy (ht=0x7ffff7fcec38)
at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_hash.c:560
#2 0x000000000056c0e2 in _zval_dtor_func (zvalue=0x7ffff7fcece8)
at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_variables.c:45
#3 0x000000000055da30 in _zval_dtor (zvalue=0x7ffff7fcece8)
at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_variables.h:35
#4 i_zval_ptr_dtor (zval_ptr=0x7ffff7fcece8) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute.h:81
#5 _zval_ptr_dtor (zval_ptr=
It looks like one of the arrays gets corrupted as it crashes during shutdown. Arrays returned to PHP must contain zvals as members. It looks like the code is adding something else than zvals?
Code adds strings into hashtabe
int *oidx, nidx;
if (zend_hash_find(ht, str, len, (void **)&oidx) == SUCCESS) {
encodeU29(ss, *oidx << 1);
return;
}
nidx = zend_hash_num_elements(ht);
if (nidx <= AMF3_MAX_INT) zend_hash_add(ht, str, len, &nidx, sizeof(nidx), NULL);
Those cannot be returned to PHP userland code "as is". Most likely you want to modify the code to pass zvals to encodeValue and use add_index_* and add_assoc_* functions to add the values into the arrays.
So I need to make hash of zval loop elements of hash and copy them to empty hash run encodeValue and then do reverse and add hash to zval?
In essence what needs to happen is something similar to:
static void encodeValue(smart_str ss, zval *val, int opts, zval *sht, zval *oht, zval *tht TSRMLS_DC)
{
...
add_next_index_stringl(sht, c_str, c_str_len, 1);
...
}
PHP_FUNCTION(amf3_encode) {
smart_str ss = { 0 };
zval *val, *sht, *oht, *tht;
long opts = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaa|l", &val, &sht, &oht, &tht, &opts) == FAILURE)
return;
encodeValue(&ss, val, opts, sht, oht, tht TSRMLS_CC);
RETVAL_STRINGL(ss.c, ss.len, 1);
smart_str_free(&ss);
}
OK Thanks for now. You have helped me allot.:)
The other option is to pass HashTables to encodeValue function and marshal the values separetely:
PHP_FUNCTION(amf3_encode) {
smart_str ss = { 0 };
zval *val, *sht, *oht, *tht;
long opts = 0;
HashTable sht_ht, oht_ht, tht_ht;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaal", &val, &sht, &oht, &tht, &opts) == FAILURE) return;
zend_hash_init(&sht_ht, 0, NULL, NULL, 0);
zend_hash_init(&oht_ht, 0, NULL, NULL, 0);
zend_hash_init(&tht_ht, 0, NULL, NULL, 0);
encodeValue(&ss, val, opts, &sht_ht, &oht_ht, &tht_ht TSRMLS_CC);
/* Loop through sht_ht HashTable here and copy values to sht using add_next_index_* */
zend_hash_destroy(&sht_ht);
zend_hash_destroy(&oht_ht);
zend_hash_destroy(&tht_ht);
RETVAL_STRINGL(ss.c, ss.len, 1);
smart_str_free(&ss);
}
PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val, *sht, *oht, *tht; long opts = 0; HashTable sht_ht, oht_ht, tht_ht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &val, &sht, &oht, &tht, &opts) == FAILURE) return;
zend_hash_init(&sht_ht, 0, NULL, NULL, 0); zend_hash_init(&oht_ht, 0, NULL, NULL, 0); zend_hash_init(&tht_ht, 0, NULL, NULL, 0); encodeValue(&ss, val, opts, &sht_ht, &oht_ht, &tht_ht TSRMLS_CC);
/* Loop through sht_ht HashTable here and copy values to sht using add_next_index_* */
zend_hash_destroy(&sht_ht); zend_hash_destroy(&oht_ht); zend_hash_destroy(&tht_ht); RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); }
I made small correction to the code is there as simple way of copying from zval to ht like: zend_hash_init(&sht_sh, 0, NULL, NULL, 0); pseudo copy to sht_ht from HASH_OF(sht) encodeValue(...)
I don't really fully understand the question. Do sht, oht and tht parameters contain values when you receive them?
Yes that is the reason I am trying to change the extension.
Can you show the calling PHP code for amf3_encode? I mean how you would call it after the modifications
I would like to use it like this: public function writeArray_n(&$data) {
$this->_stream->writeBytes(amf3_encode($data,$this->_referenceStrings, $this->_referenceObjects, $this->_referenceDefinitions, 0)) ;
}
original use was like this: public function writeArray_n(&$data) {
$this->_stream->writeBytes(amf3_encode($data, 0)) ;
}
but the problem is the amf3_encode has no clue that references allready exists and it makes new ones. When you decode such stream you get scrambled object properties and array associations.
The whole reason why I am trying to use extension is this: Zend_AMF uses 96 seconds to encode huge data array (memory peak before encoding 517MB) this extansion uses 7 seconds.
These arrays you receive from PHP will contain zvals and inside the extension you need to marshall these into native types accordingly and back zvals if you add new values to the arrays.
So most likely you want to pass the zvals to encodeValue function. Instead of using zend_hash_add etc directly you want to use add_index_* and add_assoc_* family functions to add values back to the arrays.