I'm trying to port an old PHP extension of mine working for PHP 5.4 to PHP 7.3 (7.3.0 RC3 more precisely).
I'm building the extension using Visual Studio 2017 on Windows 10 Pro x64, building both for x64 and x86 architectures.
The extension provides a simple function (among others) which takes an array as argument, it returns a boolean value and pushes several strings to the array. The input array is usually empty.
Here is the c code I'm focusing on which targets both PHP 5.x and PHP 7.x:
ZEND_FUNCTION(TestUpdateArray)
{
zval *zArr;
zend_bool retVal = FALSE;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zArr) != FAILURE)
{
if (Z_TYPE_P(zArr) == IS_ARRAY)
{
char str[256];
for (int i = 1; i < 11; i++)
{
sprintf_s(str, "This is a test %d", i);
#if PHP_VERSION_ID > 70000
add_next_index_string(zArr, str);
#else
add_next_index_string(zArr, str, true);
#endif
}
retVal = TRUE;
}
}
RETURN_BOOL(retVal);
}
And this is the simple php script I'm using to try it out:
<?php
$arr = [];
$retVal = TestUpdateArray($arr);
echo "<pre>";
var_export($retVal);
echo "\n";
var_export($arr);
echo "</pre>";
Outputting:
true
array (
0 => 'This is a test 1',
1 => 'This is a test 2',
2 => 'This is a test 3',
3 => 'This is a test 4',
4 => 'This is a test 5',
5 => 'This is a test 6',
6 => 'This is a test 7',
7 => 'This is a test 8',
8 => 'This is a test 9',
9 => 'This is a test 10',
)
While the functions works fine in PHP 5.4 when
$arr = [];
, httpd/php throws an exception then simply crashes on PHP 7.3 with $arr declared like above:
What is interesting to me is that I obtain a completely different behavior on 7.3 if $arr is declared like this:
$arr = ['test'];
In that case both on 5.4 and 7.3 the function correctly appends 10 'This is a test x' strings to the array, the end result being:
true
array (
0 => 'test',
1 => 'This is a test 1',
2 => 'This is a test 2',
3 => 'This is a test 3',
4 => 'This is a test 4',
5 => 'This is a test 5',
6 => 'This is a test 6',
7 => 'This is a test 7',
8 => 'This is a test 8',
9 => 'This is a test 9',
10 => 'This is a test 10',
)
In the light of what I've seen, I'm assuming that, given the differences and optimization introduced with PHP 7, the PHP virtual machine does not actually allocate space for items of the array (when declared empty), but then the add_next_index_string function expects that to be the case (please feel free to correct me on this).
So, what I am asking is, what is the correct way to check and (eventually) initialize an empty array passed to a ZEND_FUNCTION in order to make changes (to the array) available to the calling script?
I've also tried to use the init_array function on the zArr zval but, in this case, the calling script is not getting the appended strings (I guess that the reference to $arr is lost when calling init_array).
EDIT after NikiC comment:
when I added the question this was the function entry declaration:
ZEND_FE(TestUpdateArray, NULL)
after NikiC comment I've added the following arginfo:
ZEND_BEGIN_ARG_INFO_EX(arginfo_test_update_array, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(1, update_array, 0)
ZEND_END_ARG_INFO()
and updated the function code to:
ZEND_FUNCTION(TestUpdateArray)
{
zval *zArr;
zend_bool retVal = FALSE;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zArr) != FAILURE)
{
if (Z_ISREF_P(zArr))
{
ZVAL_DEREF(zArr);
SEPARATE_ARRAY(zArr);
}
if (Z_TYPE_P(zArr) == IS_ARRAY)
{
char str[256];
for (int i = 1; i < 11; i++)
{
sprintf_s(str, "This is a test %d", i);
#if PHP_VERSION_ID > 70000
add_next_index_string(zArr, str);
#else
add_next_index_string(&myArr, str, true);
#endif
}
retVal = TRUE;
}
}
RETURN_BOOL(retVal);
}