PHP Notes

A reasonable tutorial exists at http://www.w3schools.com.

Explore Zend issues here.


Language notes

PHP blocks (interspersed amoung HTML code) are bracketed thus:

	<?php

	...

	?>

Comments are identical to C/C++.

Operators

Operators of all sorts are identical to C except that there are no bitwise operations.

Statement syntax

Statement syntax is more or less identical to C using if, else, etc. as well as curly braces. Statements end with ; .

elseif is a built-in construct in PHP.

Other constructs include switch case default break continue while do...while for foreach .

Variables

All variables are marked with $ just as referenced variables in bash, but they are also defined using $ as well:

	$string="This is a string with the number";
	$number=10;

	echo $string . $number . "in it.";

	output:
	This is a string with the number 10 in it.

Arrays

Arrays come in three types: numeric (numerically indexed as in C) and associative (with an id key). They can be multidimensional.

Numeric:

	$single_digits = array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );

Associative:

	$ages = array( "Peter" => 32, "Max" =>41, "Maxine" >= 50 );

	$worms['redworm'] = 99;
	$worms['tape'] = 98;

Functions

Functions are defined as if in C, except that they are preceded by the keyword function. They can be invoked as soon as defined.

	function hellothere()
	{
	  echo "Hello there";
	}


	hellothere();

Function arguments (parameters) are just variables prefixed with $ as expected. They are not type-declared just as in bash. Functions can return values to be gathered just as in C, but they are not type-declared.


Forms and input...

PHP handles forms and user input identically to other scripting languages including HTML.


Failure...
cd /usr/lib/php/extensions
ln -s /home/russ/php-vas/libphp_vas.so.4.3.0 ./php_vas.so

Is there just something I’m not seeing here?

The library built:

	[email protected]:~/php-vas> ll libphp_vas.*
	-rw-r--r--  1 russ users 241378 2007-03-14 17:59 libphp_vas.a
	lrwxrwxrwx  1 russ users     15 2007-03-14 17:59 libphp_vas.so -> libphp_vas.so.4
	lrwxrwxrwx  1 russ users     19 2007-03-14 17:59 libphp_vas.so.4 -> libphp_vas.so.4.3.0
	-rwxr-xr-x  1 russ users 218534 2007-03-14 17:59 libphp_vas.so.4.3.0

The link in PHP to the library:

	[email protected]:~/php-vas> ll /usr/lib/php/extensions/php_vas.so
	lrwxrwxrwx  1 root root 38 2007-03-15 08:43 /usr/lib/php/extensions/php_vas.so -> /home/russ/php-vas/libphp_vas.so.4.3.0

What ldd returns:

	[email protected]:~/php-vas> ldd libphp_vas.so.4.3.0
        linux-gate.so.1 =>  (0xffffe000)
        libvas.so.4 => /opt/quest/lib/libvas.so.4 (0x40015000)
        libc.so.6 => /lib/tls/libc.so.6 (0x40229000)
        libresolv.so.2 => /lib/libresolv.so.2 (0x40348000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x4035b000)
        libdl.so.2 => /lib/libdl.so.2 (0x4038e000)
        /lib/ld-linux.so.2 (0x80000000)

The continued, dismal failure:

	[email protected]:~/php-vas> php dan.php

	Warning: dl(): Unable to load dynamic library '/usr/local/lib/php/extensions/no-debug-non-zts-20020429/php_vas.so'
	- /usr/local/lib/php/extensions/no-debug-non-zts-20020429/php_vas.so:
	cannot open shared object file: No such file or directory in /home/russ/php-vas/dan.php on line 5

Yes, it’s that I’m using a different PHP than was Dan. What is the distinction between the two different ones I have? Unknown at this point.

Of the following versions, the first one failed getting the library module error reported above, the second did not. Based on the date of the first, I must have built and installed this version when I started this project.

	[email protected]:~/php-vas> php --version
	PHP 4.4.6 (cli) (built: Mar 12 2007 13:37:27)
	Copyright (c) 1997-2007 The PHP Group
	Zend Engine v1.3.0, Copyright (c) 1998-2004 Zend Technologies

	[email protected]:~/php-vas> /usr/bin/php --version
	PHP 4.4.0 (cli) (built: Nov  7 2006 14:18:57)
	Copyright (c) 1997-2004 The PHP Group
	Zend Engine v1.3.0, Copyright (c) 1998-2004 Zend Technologies

When I went to make use of PHP-5, I found the path ./php-5.2.1/sapi/cli/php. I also found ./php-5.2.1/sapi/cgi/php. Doubtless, it would be possible to configure to end up with an Apache httpd PHP. The PHP-5 version failed for reasons we discuss in the very next section.

	[email protected]:~/php-vas> ~/php-vas.original/php-5.2.1/sapi/cli/php --version PHP 5.2.1 (cli) (built: Mar  6 2007 10:49:26)
	Copyright (c) 1997-2007 The PHP Group
	Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies



Common problems (advanced)...

If you see the PHP source instead of the result the script should produce, Apache probably doesn’t know about PHP. /etc/apache/http.conf needs:

	#include /etc/apache/conf.d

...and the extension needs to be listed in php.ini (and the .so added to the extensions directory).



Seeing the phpinfo() dump...
You have chosen to open

    info.php
	 
which is a: PHP script
from: /srv/www/htdocs

What should Firefox do with this file?

x Open with  | Text Editor (default)
  Save to disk
  Do this automatically for files...
Russ says:
What I'm up against right now is that I need some information of the sort that
comes from the call phpinfo(); (see  http://support.nusphere.com/viewtopic.php?t=576)

Russ says:
To do this, I have in the past, just as the page there suggests, created a
file, info.php, containing:

Russ says:
<?php phpinfo(); ?>

Russ says:
However, when I go to open the file (at my httpd root) now, I get...

Russ says:
(see above dialog)

Russ says:
You know, like "I know what this is, but I'm not going to dignify your effort
by doing what you want EVEN IF I have done this before."

Satori says:
Are you using Apache?

Russ says:
No, I think I'm using XAMPP.

Russ says:
It's actually Apache that served up this page before.

Satori says:
I have never used xampp. But it lookes like the server is merely serving the
file, not running the script.

Russ says:
However, I'm being told out there that I have to use XAMPP instead of Apache
for debugging PHP.

Russ says:
I guess I could tear down xampp and relaunch Apache just to get this information.

Satori says:
Start Apache on a different port.

Russ says:
Okay. I'm unsure of how to do that--modify /etc/apache2/httpd.conf?

Russ says:
a command-line option?

Satori says:
/etc/apache2/listen.conf

Satori says:
The line 'Listen 80' to 'Listen '

Russ says:
Okay. I'll choose 80+1

Satori says:
Of course, if you use a firewall....

Russ says:
I don't think I use a firewall.  This is on my own box too, of course.
I'm not doing anything off-box.

Russ says:
Do I need to go through Yast to start Apache? /etc/init.d/httpd start doesn't work.

Russ says:
And, how to launch Firefox to use port 81 instead?

Satori says:
http://localhost:81/info.php

Russ says:
Sure.

Russ says:
And, to launch Apache?

Satori says:
rcapache2 start

Russ says:
Okay, that worked quite well. Thanks. It's off to the next nightmare.

php-vas Bindings



Zend development...

These are the guts of PHP—how to extend it such as adding in bindings to the VAS API set. Here are some notes as we go...

Module structure...

All PHP bindings follow a common structure:

There are two ways of extending via an external shared-object module (such as libphp_vas.so.4.3.0). In this discussion, realize that there is an external module, php_vas.so, a link placed in /usr/lib/php/extensions to libphp_vas.so.4.3.0 and named php_vas.so. (For now, I haven’t been tempted to name our shared object to php_vas.so since it’s actually more explicit to create this link to a file with such as descriptive name—that way, you know what you have got version- and otherwise. A word to the wise sufficeth: VAS changes versions and so does PHP.) In our example here, we posit our VAS bindings as for php5, something we’re working on getting running (php4 is already up and going) at the moment.

	if( ! extension_loaded( "php_vas5.so" ) )
	{
	   if( ! dl( "php_vas5.so" ) )
	      return;
	}

This has the theoretical disadvantage, however, of requiring potentially expensive loading time each time a PHP module is run that needs it. Another way is to annotate php.ini using the extension tag.

The reason our existing bindings failed to compile with php5 is because the zend_function_entry structure is because a) the structure itself is radically different between the two versions with a substructure of some complexity and b) the arguments to the handler (wrapper) are also more numerous in php5:

	php4:
	typedef struct
	{
	   char          *fname;
	   void         (*handler)( int     ht,          (glue-code)
	                            zval   *return_value,
	                            zval   *this_ptr,
	                            int     return_value_used,
	                            void ***tsrm_ls );
	   unsigned char *func_arg_types;
	} zend_function_entry;

	php5:
	typedef struct
	{
	   char     *name;
	   zend_uint name_len;
	   char     *class_name;
	   zend_uint class_name_len;
	   zend_bool array_type_hint;
	   zend_bool allow_null;
	   zend_bool pass_by_reference;
	   zend_bool return_reference;
	   int       required_num_args;
	} zend_arg_info;

	typedef struct
	{
	   char          *fname;
	   void         (*handler)( int     ht,          (glue-code)
	                            zval   *return_value,
	                            zval  **return_value_ptr,
	                            zval   *this_ptr,
	                            int     return_value_used,
	                            void ***tsrm_ls );
	   zend_arg_info *arg_info;
	   zend_uint      num_args;
	   zend_uint      flags;
	} zend_function_entry;

This discovered, I will examine the very binding code to see what must be done to accommodate php5 and, further, to what extent the result may still be single-sourced.

Link to sample extension code: http://devzone.zend.com/manual/view/page/zend.creating.html.

Link to Zend API: Hacking the Core of PHP: http://devzone.zend.com/manual/view/page/zend.html.

Entry list

Every user-visible function must have an entry in an array. This introduces them to Zend by name as it should appear in PHP (the expected, VAS API name) and the underlying, implementation name (which, for now, is the same but prefixed by zif_—PHP really wants this to be prefixed with zif_ and we should change this later). This information comes from http://devzone.zend.com/manual/view/page/zend.structure.html. This document covers only php4 and there are differences introduced in php5.

Wrapped (glue-code) implementations...

void zif_function-name( int    ht,
                        zval  *return_value,
                      [ zval **return_value_ptr, ]    —new in php5
                        zval  *this_ptr,
                        int    return_value_used );

Each function needs a definition and, for php5, a filled-in arg_info structure. In each of these functions, there are 5 or 6 arguments (depending on whether this is built for php4 or php5). Most are accessed only through special macros and some are only seen in this code after preprocessing the macros out. Except as marked, each of these is in both php4 and php5. They are:

ht   Number of arguments passed to Zend, obtained only using ZEND_NUM_ARGS().
return_value   Used to pass return values from this function back to PHP using predefined macros.
return_value_ptr   (php5) Don’t know (at this point) what this does. It is this argument smack in the middle of the list that caused these bindings to fail to compile originally for php5.
  (Returning values is discussed in: http://devzone.zend.com/manual/view/page/zend.returning.html.)
this_ptr   Gains access to the object in which function is contained, if used within an object. The VAS APIs aren’t object-oriented, so this isn’t specially used, but some macros in use actually touch it. Apparently, function getThis can be called to get this pointer.
return_value_used   Flag indicating whether the return value of the guts of this function will be consumed by the PHP calling code, 0 for won’t be used, 1 indicates that it will be expected.
executor_globals   Points to global settings of the Zend engine used only if creating new variables (which VAS doesn’t do). Because at the time of the original implementation, Red Hat platform implementations of PHP didn’t support this argument, it doesn’t appear in INTERNAL_FUNCTION_PARAMETERS (defined in zend.h).


php5 support...

Looking at the preprocessed code and comparing between php4 and php5 to see the differences, the zend- interfaces, etc., it appears they have not changed over php4. This means that, for the most part, the conversion over to php5 will not entail much real work:

Impediments include:



PHP glue...

Notes on how the PHP-VAS API glue works

Lots of stringization (#) and glue macros (##) are used in the formation of the macros consumed in this code both in Zend and in the VAS bindings code.

Macros prefixed with ZEND_- belong to the Zend (PHP) implementation. Those with PHP_ to the PHP implementation. Those beginning with SPE_ belong to the VAS (Kerns) implementation.

Somehow, zm_startup_vas, macro PHP_MINIT_FUNCTION, is called which runs through some initializations, mostly for complex types, registering destructor (object clean-up) functions, using macro SPE_REGISTER_DTOR.

The converse of the initialization function, PHP_MSHUTDOWN_FUNCTION or vm_shutdown_vas, happens to do nothing in the case of VAS.

An example is the product of vas_user_info_t, which must be cleaned up using vas_user_info_free. The VAS API is called by vas_user_t_free at the time the destructor, php_vas_user_t_dtor, is called.

Note: SPEPRINTF is called with a notice that this is happening. How to turn this on for debugging purposes? Define SPE_DEBUG at the top of vasapi.c.

Here’s the life of a VAS object in pseudocode...

	static int  le_vas_user_t;
	static char *PHP_vas_user_t_RES_NAME = "vas_user_t";

	// initialization...
	PHP_MINIT_FUNCTION—zm_startup_vas( ... );
	{
	   .
	   .
	   .
	   SPE_REGISTER_DTOR—
	   le_vas_user_t = zend_register_list_destructors_ex( php_vas_user_t_dtor,
	                                                      NULL,
	                                                      PHP_vas_user_t_RES_NAME,
	                                                      module_number );
	   .
	   .
	   .
	   php_vas_init_globals( &vas_globals );
	}


	// usage (consumption in PHP code)...
	[vas_user_t *user;]
	$user = vas_user_init( $ctx, $id, "administrator", 0 );


	// destruction (happens when object no longer needed)...
	typedef struct
	{
	   SPE_vas_ctx_t *ctx;
	   vas_user_t    *raw;      // (apparently refers to VAS' allocated object)
	   unsigned char noFree;
	} SPE_vas_user_t;

	php_vas_user_t_dtor( zend_rsrc_list *rsrc );
	{
	  SPE_vas_user_t *thing = rsrc->ptr;
	  SPEPRINT( "<addr>: Calling vas_user_t_dtor, noFree=N\n" );
	  if( thing & thing->raw )
	  {
	     vas_user_t_free( thing->ctx->ctx, thing->raw );
	     SPE_vas_ctx_t_free( thing->ctx );   // (destructor for vas_ctx_t)
	     _efree( thing );                    // (free rest of vas_user_t)
	  }
	}

Notes on writing the glue itself...

The glue (or binding code) for a C library/shared object is written in C, of course, using macros from Zend that call functions prefixed with zend- and do Zend things, as well as specially created ones in the VAS (Kerns) implementation, usually prefixed with SPE_.

Zend macros are generally from -I Zend (Zend/zend_API.h or Zend/zend_list.h). The other macros are at the top of vasapi.c or php_vas.h.

Pseudocode for a sample binding...

	// glue code function definition...
	typedef struct
	{
	   char          *fname;
	   void         (*handler)( int ht,
	                            zval *return_value,
	                            zval *this_ptr,
	                            int  return_value_used );
	   unsigned char *func_arg_types;
	} zend_function_entry;

	// The glue-code function definition is (see Wrapped implementations above:
	void zif_function-name( int ht, zval *return_value, zval *this_ptr, int return_value_used );

	ZEND_VAS_NAMED_FUNC( function-name )
	{
	   auto-class variables...
	   ...including zval copies of some pointers (objects)

	   SPE_CHECK_ARGS( argument-count );

	   zend_parse_parameters( argument-count, ... )

	   ZEND_FETCH_RESOURCE( which ... );

	   err = vas API call...

	   SPE_SET_VAS_ERR( err );

	   Check for errors, if none:
	      SPE_CONS_RETURN_VALUE() or
	      RETVAL_STRING(), etc.
	   otherwise:
	      RETURN_NULL();
	}
ZEND_VAS_NAMED_FUNC

This generates the function definition/header including Zend arguments.

SPE_CHECK_ARGS

There are no examples of arguments that, like doubles, are more more than standard width, so it’s not clear at this writing whether this is a significant check.

Sets an argument base (to zero) which it bumps by one if this_ptr is non-nil and an object.

The argument count passed to the macro plus what’s in this_ptr->ht and the argument base must be equal. (Explain this.) If not the case, zend_wrong_param_count is called the glue function returns.

zend_parse_parameters

This parses out the “real” function arguments from the PHP script invocation. The first string argument appears to be an arbitrary type specification as yet not understood ("rzsl").

Also, zval versions of the arguments, at least for passed in objects, are got for use in other situations. It appears that to pass deeper a vas_ctx_t or vas_id_t, you must use these.

ZEND_FETCH_RESOURCE

This macro is used to fill out/get an actual VAS object like vas_ctx_t from z zval version thereof. However, the resulting object is still wrapped in a PHP wrapper. For each type of these, there is a corresponding structure definition, but the VAS animal in question is referred to by a field bearing its name (ctx, id, etc.).

ZVAL_IS_NULL

Zend macro.

Validate that one of these zval animals isn’t nil.

SPE_SET_VAS_ERR

Sets the global VAS error value to the return, almost always of the actual VAS function, so that PHP can report it back.

SPE_CONS_RETURN_VALUE

In the case where an object was created by the underlying VAS API, a pointer to it gets recorded as a raw data object in a wrapped up object with a special type—the one spoken of in the paragraph on ZEND_FETCH_RESOURCE.

When it has created the object, this macro (or, rather, another it calls) uses ZEND_REGISTER_RESOURCE to register the resource, which is pretty much just a call to zend_register_resource.

RETVAL_STRING

Zend macro.

This macro is used in place of SPE_CONS_RETURN_VALUE when the object being returned is merely a string (that will later be cleaned by a call to free). This macro makes a call to ZVAL_STRING which duplicates the string object, recording it in a zval structure for strings. The calling code, in this case the glue code in vas_user_get_sid, disposes of the string passed back from the VAS API as soon as RETVAL_STRING is finished making its own copy, a decided inefficiency.

RETURN_NULL

Zend macro.

Otherwise, there’s an error and we set the return value type to zip and blow out. The VAS error occurring has already been registered by SPE_SET_VAS_ERR for communication to the PHP script above which calls vas_err_get_code as in:

	if( vas_err_get_code( $ctx ) != VAS_ERR_SUCCESS )
	{
	   print_php_vas_error( $ctx );
	   exit( 1 );
	}

...so it’s not communicated quite the way you might expect. Note importantly that these bindings return (in PHP) complex objects as their “return value”, rather than errors. See sample PHP code from RC php-vas project (or look for and click on “php-vas” at RC website).


Sample code: vas_user_init...

First, the glue function as coded...

ZEND_VAS_NAMED_FUNC( vas_user_init )
{
    SPE_vas_ctx_t *ctx;
    SPE_vas_id_t  *id = NULL;
    vas_user_t    *user = NULL;
    vas_err_t     err;
    zval          *zctx;
    zval          *zId;
    int           flags;
    const char    *szName;
    int           lName;

    SPE_CHECK_ARGS( 4 );

    if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rzsl", &zctx, &zId,
                               &szName, &lName, &flags ) == FAILURE )
    {
        RETURN_NULL();
    }

    ZEND_FETCH_RESOURCE( ctx, SPE_vas_ctx_t*, &zctx, -1, PHP_vas_ctx_t_RES_NAME, le_vas_ctx_t );

    if( ! ZVAL_IS_NULL( zId ) )
    {
        ZEND_FETCH_RESOURCE( id, SPE_vas_id_t *, &zId, -1, PHP_vas_id_t_RES_NAME, le_vas_id_t );
    }

    err = vas_user_init( ctx->ctx, RAW( id ), szName, flags, &user );

    SPE_SET_VAS_ERR( err );

    if( err == VAS_ERR_SUCCESS )
    {
        SPE_CONS_RETURN_VALUE( vas_user_t, user );
    }
    else
    {
        RETURN_NULL();
    }
}

...then, the cleaned up preprocessor output of this function revealing the internals (with interleaved macros for orientation):

void zif_vas_user_init( int  ht,
                          zval *rval,
                          zval *this_ptr,
                          int  rval_used )
{
    SPE_vas_ctx_t   *ctx;
    SPE_vas_id_t    *id = NULL;
    vas_user_t      *user = NULL;
    vas_err_t       err;
    zval            *zctx;
    zval            *zId;
    int             flags;
    const char      *szName;
    int             lName;
    int argbase = 0;

    SPE_CHECK_ARGS( 4 ):
    if( this_ptr && this_ptr->type == 5 )
        argbase++;

    if( ht + argbase != 4 )
    {
        RETURN_NULL():
        zend_wrong_param_count();
        return;
    }

    if( zend_parse_parameters( ht, "rzsl", &zctx, &zId, &szName, &lName, &flags ) == -1 )
    {
        rval->type = 0;
        return;
    }

    ZEND_FETCH_RESOURCE( ctx, ... ):
    ctx = (SPE_vas_ctx_t *) zend_fetch_resource( &zctx, -1, "vas_ctx_t", NULL, 1, le_vas_ctx_t);
    if (!ctx)
    {
        rval->type = 0;
        return;
    }

    ZVAL_IS_NULL( zId ):
    if( !( zId->type == 0 ) )
    {
        ZEND_FETCH_RESOURCE( id, ... ):
        id = (SPE_vas_id_t *) zend_fetch_resource( &zId, -1, PHP_vas_id_t_RES_NAME, NULL, 1, le_vas_id_t );
        if (!id)
        {
            rval->type = 0;
            return;
        }
    }

    err = vas_user_init( ctx->ctx, ( id == NULL ) ? NULL : id->raw, szName, flags, &user );

    SPE_SET_VAS_ERR( err ):
    vas_globals.g_vas_err = err;`

    if( err == VAS_ERR_SUCCESS )
    {
        SPE_CONS_RETURN_VALUE( vas_user_t, user ):
        SPE_vas_user_t *thing;
        thing         = ( SPE_vas_user_t *) _emalloc( sizeof( SPE_vas_user_t ) );
        thing->ctx    = ctx;
        thing->ctx->referenceCount++;
        thing->raw    = user;
        thing->noFree = 0;
        ZEND_REGISTER_RESOURCE( hidden inside SPE_CONS_RETURN_VALUE ):
        zend_register_resource( rval, thing, le_vas_user_t );
    }
    else
    {
        RETURN_NULL():
        rval->type = 0;
        return;
    }
}

Sample code: vas_user_get_sid...

Here are the last bits of vas_user_get_sid, which differs in that it returns a string (as discussed higher up). The sid returned from VAS is freed by free instead of efree because it wasn’t created by emalloc in the first place. The first bits of this function follow pretty much the same lines as vas_user_init already shown.

    .
    .
    .
    err = vas_user_get_sid( ctx->ctx, RAW( id ), user->raw, &sid );

    SPE_SET_VAS_ERR( err );

    if( err == VAS_ERR_SUCCESS )
    {
        RETVAL_STRING( sid, 1 );
        free( sid );
        return;
    }
    else
    {
        RETURN_NULL();
    }
}

...and, afterward, the preprocessed version.

    .
    .
    .
    err = vas_user_get_sid( ctx->ctx, ( id == NULL ) ? NULL : id->raw, user->raw, &sid );

    SPE_SET_VAS_ERR( err ):
    vas_globals.g_vas_err = err;

    if( err == VAS_ERR_SUCCESS )
    {
        RETVAL_STRING( sid, 1 ):
        char    *__s        = sid;

        rval->value.str.len = strlen(__s);
        rval->value.str.val = _estrndup( __s, rval->value.str.len );
        rval->type          = 3;

        free( sid );
        return;
    }
    else
    {
        RETURN_NULL():
        rval->type = 0;
        return;
    }
}

How it works...

Because of the careful registration including the VAS API functions that dispose of complex objects (vas_ctx_free, vas_id_free, vas_user_frrr, etc), PHP’s normal destructor mechanism understands that it needs to call these when it’s finished with the object, at least by shut-down time (call to exit run off right brace, etc.).

Likely errors in this mechanism...

We have so far found that the most likely errors arise from imbalanced semantics. In one example, vas_user_get_sid, the sid string being returned wasn’t always stable. In the C code by which VAS implements that function (libs/vasapi/user.c), the string returned was, in some cases, created by strdup and, in others, by returning a pointer to the sid field on the vas_user_t structure. Since that field was disposed of already by PHP in one destructor, clean-up of the string return caused a segmentation fault or double free when passed to free.

valgrind was useful in pin-pointing the otherwise mysterious fault, however, only inspection led to solving the bug as I don’t yet have the ability to step through these bindings. Defining SPE_DEBUG at the top of vasapi.c might have been as useful as valgrind.