1

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:

Access violation

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);
}
  • [link]http://php.net/manual/en/internals2.variables.arrays.php – Jamie_D Oct 24 '18 at 13:56
  • 2
    Is TestUpdateArray declared in arginfo to accept the array by reference? There are two things you need to do: 1. ZVAL_DEREF(zArr) to get to the value of the reference and then 2. SEPARATE_ARRAY(zArr) to make sure the array is not shared. – NikiC Oct 24 '18 at 13:57
  • @NikiC thank you for your answer. I've updated the question and, thanks to your suggestion I was able to achieve what I wanted, hoping I did it correctly. With that said I'm interested in understanding: 1. How was that working on 5.4? 2. What was the difference between passing $arr = []; and $arr = ['test']. 3. With the new arginfo and PHP 7.3, could I use 'z/' in the zend_parse_parameters to achieve the same behavior? – Carlo Pastorino Oct 24 '18 at 15:22
  • 1
    1. Through luck it worked in the cases you tested. Arguments have to be accepted by reference if you want to modify them in the function. 2. There is a shared [] array living in RODATA, which is likely mapped as non-writable on your system, causing a segfault. I'd expect the other case to assert if you use a debug build. 3. Yes, that would work as well. I'd avoid it in the interest of forward compatibility. – NikiC Oct 26 '18 at 09:08

0 Answers0