Make last array parameter optional in php class method (C)
-
28-06-2021 - |
Question
I am creating a PHP extension in C to access the SPI interface. So far I have gotten pretty much everything working: php_spi on Github
However, I cannot seem to make the $options parameter in the constructor optional. My working code is like this:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lla", &bus, &chipselect, &options) == FAILURE) {
return;
}
_this_zval = getThis();
_this_ce = Z_OBJCE_P(_this_zval);
options_hash = HASH_OF(options);
char device[32];
sprintf(device, "/dev/spidev%d.%d", bus, chipselect);
// If the device doesn't exists, error!
if(access(device, F_OK) == -1) {
char error[128];
sprintf(error, "The device %s does not exist", device);
php_error(E_ERROR, error);
}
// If we can't open it, error!
long fd = open(device, O_RDWR);
if (fd < 0) {
char error[128];
sprintf(error, "Could not open %s for read/write operations, are you running as root?", device);
php_error(E_ERROR, error);
}
// Set the file descriptor as a class property
zend_update_property_long(_this_ce, _this_zval, "device", 6, fd TSRMLS_DC);
// Default property values
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 500000;
uint16_t delay = 0;
// Loop through the options array
zval **data;
for(zend_hash_internal_pointer_reset(options_hash);
zend_hash_get_current_data(options_hash, (void **)&data) == SUCCESS;
zend_hash_move_forward(options_hash)) {
char *key;
int len;
long index;
long value = Z_LVAL_PP(data);
if(zend_hash_get_current_key_ex(options_hash, &key, &len, &index, 1, NULL) == HASH_KEY_IS_STRING) {
// Assign the value accordingly
if(strncmp("mode", key, len) == 0) {
switch(value) {
case SPI_MODE_1:
mode = SPI_MODE_1;
break;
case SPI_MODE_2:
mode = SPI_MODE_2;
break;
case SPI_MODE_3:
mode = SPI_MODE_3;
break;
default:
mode = SPI_MODE_0;
break;
}
}
else if(strncmp("bits", key, len) == 0) {
bits = value;
}
else if(strncmp("speed", key, len) == 0) {
speed = value;
}
else if(strncmp("delay", key, len) == 0) {
delay = value;
}
}
}
However, if I follow the suggestions of all the documentation I can find and add a pipe between the l and the a, like so:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
Then my extension silently fails - can anyone offer me any advice?
Solution
Assuming options
is a zval*
, if you do this:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
return;
}
...if options
is not passed (that is, you omit the third optional argument), options
will not be initialized or modified. Later, you do this:
options_hash = HASH_OF(options);
Therefore, you're using either an uninitialized pointer, or a NULL pointer, which is undefined behavior. This is most likely causing a segmentation fault, causing your PHP script to fail.
What you should do is something like:
zval* options = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|a", &bus, &chipselect, &options) == FAILURE) {
return;
}
// ...
if (options != NULL) {
options_hash = HASH_OF(options);
}
...and handle every instance of options
(and options_hash
) with a condition that checks if it's NULL
or not.