FFI extension / NULL-Checks

  106910
September 9, 2019 06:03 phofstetter@sensational.ch (Philip Hofstetter)
Hello,

(I'm writing to the internals list because I don't think that at this
point, FFI usage is wide-spread enough to get an opinion on other venues)

maybe I'm just stupid, but I think there has been a slight oversight in the
API design for the FFI interface.

My problem is with functions that return a pointer to a struct as an out
parameter.

So in C, it would look like this:

Error* err;
func(&err);

if (err != NULL){
   do_error_handling();
}

If I translate this to PHP FFI, I would do

$err = $ffi->new("Error*");
$ffi->func(FFI::addr($err));

if I `var_dump` $err, I do see a public {0} property be set to NULL or to
actual error data.

My issue is though: How do I check whether err* as been assgined to?

isset($err)
$err[0] != null
-> is always true, regardless of whether func as assigned an error or not

isset($err->{0})
isset($err[0])
-> Cannot use object of type FFI\CData as array

$err->{0} != NULL;
-> FFI\Exception: NULL pointer dereference

$err[0];
-> segfault

I'm sure I must be doing something wrong, but I don't know what. Also, the
documentation could do with an example on how to achieve the desired result
and, TBH, I think some of the various attempts produce a somewhat
unexpected output.

Any comments?

Philip


var_dump(isset($r->{0}));
-> Error:  Cannot use object of type FFI\CData as array

$a = '{0}';
var_dump(isset($r->$a));
-> Error:  Cannot use object of type FFI\CData as array


    var_dump(isset($r[0]));
    var_dump($r[0]);
  106915
September 9, 2019 08:13 smalyshev@gmail.com (Stanislav Malyshev)
Hi!

> isset($err) > $err[0] != null > -> is always true, regardless of whether func as assigned an error or not > > $err[0]; > -> segfault
This is a bit confusing - if $err[0] segfaults, how it can be != null? Does this behave differently in different contexts? Anyway, it shouldn't probably segfault in any case, so this looks like a bug. -- Stas Malyshev smalyshev@gmail.com
  106916
September 9, 2019 09:27 phofstetter@sensational.ch (Philip Hofstetter)
On Mon, Sep 9, 2019 at 10:13 AM Stanislav Malyshev <smalyshev@gmail.com>
wrote:


> > isset($err) > > $err[0] != null > > -> is always true, regardless of whether func as assigned an error or not > > > > $err[0]; > > -> segfault > > This is a bit confusing - if $err[0] segfaults, how it can be != null? > Does this behave differently in different contexts? Anyway, it shouldn't > probably segfault in any case, so this looks like a bug. >
I think there are a few disconnected issues. One is that checking something for being NULL is a bit tricky with the given API and the other is that the API around FFI\CData is a bit inconsistent and tricky to use. Given $err = $ffi->new("Error*"); what the library does is assume that $err is now a pointer to an array (which is what C does I guess), but yet, there seems to be a bit of an issue with actually accessing that array (if it's set to NULL) - and also, don't use var_dump() to inspect it - that's deceptive because there's a debug_info handler that knows ore than the actual field accessors (which too makes this more complicated than needed). So. Let's assume that in $err = $ffi->new("Error*"); $ffi->func(FFI::addr($err)); func() sets that argument to NULL, then $f = $err[0]; var_dump(gettype($f)); returns "object" - so the zeroth index exists (hence the isset being true). However there's also $f = $err->{0}; which normally would throw "Attempt to read undefined field '0' of C struct/union" but because $err is seen as an array, accessing a property named 0 is possible, but then a second null check will happen via https://github.com/php/php-src/blob/09e9c4f3c11a875c67334578621084f156155307/ext/ffi/ffi.c#L1103-L1107 and you get "NULL pointer dereference" instead. I believe this difference between [0] and {0} shouldn't exist. And even aside of that, I see no way in which user-code can ever check any reference for NULL because any access of a Cdata property or index that's happen to be set to NULL will either segfault, throw or fail a comparison in PHP because Cdata instances, according to PHP's type conversion rules, always convert to something that compares truthy when put in boolean context. The inconsistency between [] and {} aside, I believe there should be an FFI::isNull() static method or a Cdata::isNull() instance method that allow user-code to check things for being NULL. Or the throw in the code I liked to above is removed, but that would leave the inconsistency between [] and {} Or maybe I'm just overlooking something and there's a simple way to check something for being NULL, but if there is, it IMHO needs better docs. Philip
  106917
September 9, 2019 11:33 phofstetter@sensational.ch (Philip Hofstetter)
In response to these findings so far, I have now submitted

https://github.com/php/php-src/pull/4691

that adds an FFI::isNull() method that returns true if the passed CData
instance points to NULL.

If you agree that something like this is indeed needed, I will gladly
update the PR and add unit tests.

Philip

On Mon, Sep 9, 2019 at 8:03 AM Philip Hofstetter <phofstetter@sensational.ch>
wrote:

> Hello, > > (I'm writing to the internals list because I don't think that at this > point, FFI usage is wide-spread enough to get an opinion on other venues) > > maybe I'm just stupid, but I think there has been a slight oversight in > the API design for the FFI interface. > > My problem is with functions that return a pointer to a struct as an out > parameter. > > So in C, it would look like this: > > Error* err; > func(&err); > > if (err != NULL){ > do_error_handling(); > } > > If I translate this to PHP FFI, I would do > > $err = $ffi->new("Error*"); > $ffi->func(FFI::addr($err)); > > if I `var_dump` $err, I do see a public {0} property be set to NULL or to > actual error data. > > My issue is though: How do I check whether err* as been assgined to? > > isset($err) > $err[0] != null > -> is always true, regardless of whether func as assigned an error or not > > isset($err->{0}) > isset($err[0]) > -> Cannot use object of type FFI\CData as array > > $err->{0} != NULL; > -> FFI\Exception: NULL pointer dereference > > $err[0]; > -> segfault > > I'm sure I must be doing something wrong, but I don't know what. Also, the > documentation could do with an example on how to achieve the desired result > and, TBH, I think some of the various attempts produce a somewhat > unexpected output. > > Any comments? > > Philip > > > var_dump(isset($r->{0})); > -> Error: Cannot use object of type FFI\CData as array > > $a = '{0}'; > var_dump(isset($r->$a)); > -> Error: Cannot use object of type FFI\CData as array > > > var_dump(isset($r[0])); > var_dump($r[0]); > > >
-- Sensational AG Giesshübelstrasse 62c, Postfach 1966, 8021 Zürich Tel. +41 43 544 09 60, Mobile +41 79 341 01 99 info@sensational.ch, http://www.sensational.ch
  106918
September 9, 2019 12:15 cmbecker69@gmx.de ("Christoph M. Becker")
On 09.09.2019 at 08:03, Philip Hofstetter wrote:

> (I'm writing to the internals list because I don't think that at this > point, FFI usage is wide-spread enough to get an opinion on other venues) > > maybe I'm just stupid, but I think there has been a slight oversight in the > API design for the FFI interface. > > My problem is with functions that return a pointer to a struct as an out > parameter. > > So in C, it would look like this: > > Error* err; > func(&err); > > if (err != NULL){ > do_error_handling(); > } > > If I translate this to PHP FFI, I would do > > $err = $ffi->new("Error*"); > $ffi->func(FFI::addr($err)); > > if I `var_dump` $err, I do see a public {0} property be set to NULL or to > actual error data. > > My issue is though: How do I check whether err* as been assgined to?
You could check FFI::addr($err)[0]: $err = $ffi->new("Error*"); $perr = FFI::addr($err); $ffi->func($perr); var_dump(is_null($perr[0])); FFI::free($perr); Not sure if it's supposed to require this indirection, though. -- Christoph M. Becker