Adding a few debug helpers to php4.x

php.internals

Todd Ruth

22 years ago
I work for a company that is using php and would like to contribute a few helper functions that we find useful. They are most helpful for anyone doing OO work (especially if using singletons) who isn't ready to move to php5. Two of the three are trivial to incorporate, but for the third I'm looking for some advice. I'm also not sure if I should get a cvs account for this stuff or if posting it here is sufficient inspiration for some good natured soul to get the functions into php. :) I'll start with the easy ones: ============ ref_id(mixed var) ============ ref_id returns a value that is the same for any variables that are references to eachother, but different for any variables that are not references to eachother. In the file system analogy, this corresponds to doing an "ls -i". In some cases it is useful to check if an object really is the object it is expected to be. A forgotten "&" can lead to hours of debugging that can be reduced with appropriate ref_id checks. Here is an example: <?php $x = 5; $y = 7; $z =& $x; if (ref_id($x) == ref_id($y)) { print "Changing x or y changes the other.\n"; } if (ref_id($x) == ref_id($z)) { print "Changing x or z changes the other.\n"; } ?> causes the output: Changing x or z changes the other. Here's the code: /* {{{ proto int ref_id(mixed var) * Returns the pointer of the variable */ PHP_FUNCTION(ref_id) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } RETURN_LONG((int)*arg); } /* }}} */ PHP_FE(ref_id, first_arg_force_ref) ============ ref_count(mixed var) ============ ref_count returns the number of references for the given variable. In the file system analogy, this corresponds to the second column of the output of "ls -l". In some cases it is useful to check if an object really is the object it is expected to be. A forgotten "&" can lead to hours of debugging that can be reduced with appropriate ref_count checks. Here is an example: <?php function f($x, $y) { if (ref_count($x) > 1) { print "Changing x affects another var.\n"; } else { print "x is strictly local.\n"; } if (ref_count($y) > 1) { print "Changing y affects another var.\n"; } else { print "y is strictly local.\n"; } } $x = 5; $y = 7; f($x, &$y); ?> causes the output: x is strictly local. Changing y affects another var. (Ordinarily, the "&" would appear in the parameter list to ensure a reference is passed, but in certain situations it is preferable to place the "&" in the calling code.) Here's the code: /* {{{ proto int ref_count(mixed var) * Returns the number of references to the variable */ PHP_FUNCTION(ref_count) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } /* "-1" because a reference is created just to call this function */ RETURN_LONG(ZVAL_REFCOUNT(*arg)-1); } /* }}} */ PHP_FE(ref_count, first_arg_force_ref) ============ (dis)allow_copies(mixed var) ============ When the design calls for a singleton, the design calls for an asserting copy constructor. It can take hours to discover that the reason a singleton is returning strange values is that there are accidentally multiple instances of the singleton class. The bug may be a missing "&" in what should be "$singleton =& getSingleton();" or the bug may be a missing "&" in the declaration or calling of a function. The ill effects are often seen in a largely unrelated piece of code. In C++, the answer is to declare a copy constructor and leave it unimplemented. In php, disallow_copies($x) makes the following code an error: "$y = $x;". Likewise "f($x)" where f does not take a reference would be an error. For completeness, allow_copies($x) returns a variable to "normal", ie "$y = $x" is OK again. Here is the code, but see below for a major issue. /* {{{ proto void allow_copies(mixed var) * Causes php to not generate an error if the script later tries to copy * the variable */ PHP_FUNCTION(allow_copies) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } ZVAL_ALLOW_COPIES(*arg) = 1; } /* }}} */ PHP_FE(allow_copies, first_arg_force_ref) /* {{{ proto void disallow_copies(mixed var) * Causes php to generate an error if the script later tries to copy * the variable */ PHP_FUNCTION(disallow_copies) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } ZVAL_ALLOW_COPIES(*arg) = 0; } /* }}} */ PHP_FE(disallow_copies, first_arg_force_ref) In zend.h: struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uchar type; /* active type */ zend_uchar is_ref; zend_ushort refcount; zend_bool allow_copies; /* NEW NEW NEW */ }; #define ZVAL_ALLOW_COPIES(pz) ((pz)->allow_copies) #define INIT_PZVAL(z) \ (z)->allow_copies = 1; /* NEW NEW NEW */ \ (z)->refcount = 1; \ (z)->is_ref = 0; In zend.c: zval_used_for_init.is_ref = 0; zval_used_for_init.refcount = 1; zval_used_for_init.allow_copies = 1; /* NEW NEW NEW */ zval_used_for_init.type = IS_NULL; In zend_variable.c: ZEND_API int _zval_copy_ctor(zval *zvalue ZEND_FILE_LINE_DC) { /* This is a notice instead of error because of the issue discussed below */ if (!zvalue->allow_copies) { zend_error(E_NOTICE, "Cannot copy value for which allow_copies is false"); } ... Whew! This email is a lot longer than I realized it would be and I'm only just now getting to the problem. Hopefully, at least a couple of people are reading this part. "In theory," the code above should be a complete implementation of the feature. In practice, I found that a lot of the code in php doesn't initialize the variables used. That may be fine for a particular piece of code that only relies on a couple of members of the struct, but it makes it hard to add functionality later. Here's an example from my notes of part of my effort to fix this: Replace any call to ALLOC_ZVAL(z) that is not immediately followed by a line of the form "*z = whatever" or INIT_PZVAL(z) with ALLOC_INIT_ZVAL(z). vi `find . -name "*.[hc]" | xargs grep -l 'ALLOC_ZVAL'` That helped and so did initializing the 5 cases of bare zend_constant variables and the many bare znode variables, but eventually I gave up tracking down every last piece of code that lacked initialization. If I was on the tip with a cvs account, I would have at least felt it was worthwhile. (Who knows; maybe uninitialized values are contributing to bugs in php...) As it was, it was a throw-away effort. My boss suggested keeping a separate table instead of adding to the zval struct and just ignoring the cases in which an unitialized zval is looked for in the table. We agreed that isn't as clean and has worse performance. We also agreed that the best course is to write to the php development community for guidance. So... any thoughts? Thanks! Todd Ruth

Adam Bregenzer

22 years ago
On Tue, 2004-03-02 at 20:49, Todd Ruth wrote:
> I work for a company that is using php and would like to > contribute a few helper functions that we find useful. > They are most helpful for anyone doing OO work (especially > if using singletons) who isn't ready to move to php5. > Two of the three are trivial to incorporate, but > for the third I'm looking for some advice. I'm also not > sure if I should get a cvs account for this stuff or > if posting it here is sufficient inspiration for some > good natured soul to get the functions into php. :) > I'll start with the easy ones: > > ============ ref_id(mixed var) ============
[snip]
> ============ ref_count(mixed var) ============
I agree that these could be quite useful, I just ran into a situation a few minutes ago where they would have saved me some time.
> ============ (dis)allow_copies(mixed var) ============
What about implementing this directly in php? You could use trigger_error in the constructor to stop the script from executing if the object is instantiated more than once.
-- Adam Bregenzer adam@bregenzer.net http://adam.bregenzer.net/

Todd Ruth

22 years ago
Thanks for the replies. First, in response to Stefan...
> et@edea:~$ php -r '$a = 2; $b = $a; debug_zval_dump($a);' > long(2) refcount(3) > et@edea:~$
I missed that one. Thanks! With that I'd at least be able to implement ref_count in php. And then in response to Adam...
> > ============ (dis)allow_copies(mixed var) ============ > > What about implementing this directly in php? You could use > trigger_error in the constructor to stop the script from executing if > the object is instantiated more than once.
I'll end up doing both. I'll do what you've mentioned to get an error from the following code: $s1 = new Singleton(); // Constructor sets non-member var to true $s2 = new Singleton(); // The constructor will fail I need disallow_copies() also though in order to get an error from the following code: $s1 = new Singleton(); // constructor calls disallow_copies($this) $s2 = $s1; // With php4 I get a copy without any function call or the following code: function f($server) { // Bug! Should have been &$server ... } $s1 = new Singleton(); // constructor calls disallow_copies($this) f($s1); // With php4 I get a copy without any function call Finding those bugs when you have tens of thousands of lines of php can be a real headache. That's why I'd very much like a disallow_copies function. Is anyone familiar with the extent of the uninitialized struct issue I faced with the implemention I posted yesterday? Thanks again, Todd

Derick Rethans

22 years ago
On Wed, 3 Mar 2004, Todd Ruth wrote:
> Finding those bugs when you have tens of thousands of lines of > php can be a real headache. That's why I'd very much like > a disallow_copies function.
But as you demonstrated with your patch you need to modify some of the basic data types that PHP uses and thus increasing memory footprint and decreasing performance. It is therefore very unlikely that this will be ever allowed into the PHP source. regards, Derick

Andi Gutmans

22 years ago
At 09:25 PM 3/3/2004 +0100, Derick Rethans wrote:
>On Wed, 3 Mar 2004, Todd Ruth wrote: > > > Finding those bugs when you have tens of thousands of lines of > > php can be a real headache. That's why I'd very much like > > a disallow_copies function. > >But as you demonstrated with your patch you need to modify some of the >basic data types that PHP uses and thus increasing memory footprint and >decreasing performance. It is therefore very unlikely that this will be >ever allowed into the PHP source.
Yep, I agree with Derick. Needless to say that it's very dangerous to try and make sense out of the low level stuff the engine and the PHP extensions are doing (such as copy constructing, separating and other stuff). Andi

Todd Ruth

22 years ago
Actually, I found the code reasonably clear - a kudos to someone. The points about the footprint and performance are well taken. Because the feature is most important before any customers see the latest rev of product, the feature would be just as useful as an optional part of the php build. It's already useful to do testing with a different php build than the production build just to have php debugging enabled. Although the feature performs best with an added field in the zval struct, if it is considered bad form to have an ifdef in the struct, the feature could be implemented with a separate table. (The table would have zval pointers as indices and allow_copies values as values. Any pointer not in the table would be assumed to have allow_copies set to true. The table, checks, and new functions would only be avaiable for debug builds (or whatever build flag is most appropriate).) Better? - Todd On Wed, 2004-03-03 at 12:54, Andi Gutmans wrote:

Stig S. Bakken

22 years ago
It's not quite as simple as that. To qualify dynamically loadable extensions, you will need "debug-zval / non-debug-zval" in addition to the existing debug/non-debug + zts/no-zts. Or it can be made part of debug mode, but then the API number has to be bumped, which requires a minor version change (4.3 -> 4.4). - Stig On Wed, 2004-03-03 at 22:13, Todd Ruth wrote:

Stefan Walk

22 years ago
> ============ ref_count(mixed var) ============
Just a quick note, do you know debug_zval_dump()? et@edea:~$ php -r '$a = 2; $b = $a; debug_zval_dump($a);' long(2) refcount(3) et@edea:~$
-- Regards, Stefan Walk <swalk@prp.physik.tu-darmstadt.de>