Skip to content

FFI add get value of the pointer variable #14609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

chopins
Copy link
Contributor

@chopins chopins commented Jun 20, 2024

Get value of the pointer variable, smailer:

int foo() {
    int a = 1;
    int* ptr = &a;
    return ptr;
}

in php

$x = FFI::cdef()->cast("void*", 20);
$v = FFI::addrValue($x);
var_dump($v);// output 20

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the introduction of the new FFI::addrValue() method.

Instead, I would propose to use a special "cdata" property for ZEND_FFI_TYPE_POINTER similar to scalar FFI values.
So you will able to achieve the desired behavior with the following code.

$x = FFI::cdef()->cast("void*", 20);
$v = $x->cdata;
var_dump($v);// output 20

This looks more consistent.
Can you try to implement this?

@dstogov
Copy link
Member

dstogov commented Jun 20, 2024

Unfortunately this is not simple. I tried to implement this myself.

diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index 8d9ddaca053..e6066b0aaca 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -1107,6 +1107,17 @@ static zval *zend_ffi_cdata_read_field(zend_object *obj, zend_string *field_name
 		field = *(cache_slot + 1);
 	} else {
 		if (type->kind == ZEND_FFI_TYPE_POINTER) {
+			if (zend_string_equals_literal(field_name, "cdata")) {
+				if (ptr == &cdata->ptr_holder) {
+					ptr = *(void**)ptr;
+				}
+				if (ptr == NULL) {
+					ZVAL_NULL(rv);
+				} else {
+					ZVAL_LONG(rv, (intptr_t)ptr);
+				}
+				return rv;
+			}
 			type = ZEND_FFI_TYPE(type->pointer.type);
 		}
 		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
@@ -1184,6 +1195,16 @@ static zval *zend_ffi_cdata_write_field(zend_object *obj, zend_string *field_nam
 			return value;
 		}
 		if (type->kind == ZEND_FFI_TYPE_POINTER) {
+			if (zend_string_equals_literal(field_name, "cdata")) {
+				if (Z_TYPE_P(value) == IS_NULL) {
+					*(void**)ptr = NULL;
+				} else if (Z_TYPE_P(value) == IS_LONG) {
+					*(void**)ptr = (void*)(intptr_t)Z_LVAL_P(value);
+				} else {
+					zend_ffi_zval_to_cdata(ptr, type, value);
+				}
+				return value;
+			}
 			type = ZEND_FFI_TYPE(type->pointer.type);
 		}
 		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {

But this breaks ext/ffi/tests/047.phpt that relays on automatic pointer dereference.

The pointer support in ext/FFI is really messy :(

@dstogov
Copy link
Member

dstogov commented Jun 20, 2024

It's possible to support "cdata" only for "hold" pointers (the original PR also supported only "hold" pointers). The following patch works, but it's hard to explain when "cdata" is used for pointer and when for the referenced data.

diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index 8d9ddaca053..ac9952b7519 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -1107,6 +1107,15 @@ static zval *zend_ffi_cdata_read_field(zend_object *obj, zend_string *field_name
 		field = *(cache_slot + 1);
 	} else {
 		if (type->kind == ZEND_FFI_TYPE_POINTER) {
+			if (ptr == &cdata->ptr_holder && zend_string_equals_literal(field_name, "cdata")) {
+				ptr = *(void**)ptr;
+				if (ptr == NULL) {
+					ZVAL_NULL(rv);
+				} else {
+					ZVAL_LONG(rv, (intptr_t)ptr);
+				}
+				return rv;
+			}
 			type = ZEND_FFI_TYPE(type->pointer.type);
 		}
 		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {
@@ -1184,6 +1193,16 @@ static zval *zend_ffi_cdata_write_field(zend_object *obj, zend_string *field_nam
 			return value;
 		}
 		if (type->kind == ZEND_FFI_TYPE_POINTER) {
+			if (ptr == &cdata->ptr_holder && zend_string_equals_literal(field_name, "cdata")) {
+				if (Z_TYPE_P(value) == IS_NULL) {
+					*(void**)ptr = NULL;
+				} else if (Z_TYPE_P(value) == IS_LONG) {
+					*(void**)ptr = (void*)(intptr_t)Z_LVAL_P(value);
+				} else {
+					zend_ffi_zval_to_cdata(ptr, type, value);
+				}
+				return value;
+			}
 			type = ZEND_FFI_TYPE(type->pointer.type);
 		}
 		if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) {

@nielsdos @arnaud-lb @iluuu1994 @bwoebi any thoughts?

@arnaud-lb
Copy link
Member

arnaud-lb commented Jun 20, 2024

I think that casting to uintptr_t and then reading the value would make sense, as this mirrors what we would do in C:

$x = FFI::cdef()->cast("char*", 20);
$v = FFI::cdef()->cast("uintptr_t", $x)->cdata;
var_dump($v); // int(20)

This works currently, except for void* because we dereference void* pointers when casting to a non-pointer type, as a special case:

php-src/ext/ffi/ffi.c

Lines 4055 to 4059 in 5a3c4a2

if (old_type->kind == ZEND_FFI_TYPE_POINTER
&& type->kind != ZEND_FFI_TYPE_POINTER
&& ZEND_FFI_TYPE(old_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) {
/* automatically dereference void* pointers ??? */
cdata->ptr = *(void**)ptr;

A workaround is to cast void* pointers to char* first:

$x = FFI::cdef()->cast("void*", 20);
$x = FFI::cdef()->cast("char*", $x);
$v = FFI::cdef()->cast("uintptr_t", $x)->cdata;
var_dump($v); // int(20)

However casting to uintptr_t is not super efficient as this creates a new CData.

I like what you suggested in #14609 (review), but as property accesses on CData pointers are equivalent to -> in C, I think we should not give a special meaning to ->cdata. An alternative would to be add a method on CData to access the address, but it would prevent using method calls for a similar use-case as property accesses in the future.

If a method on FFI is the only solution, I would suggest to make it more general and implement it for CData values and pointers:

$x = FFI::cdef()->cast("int", 20);
FFI::value($x); // int(20)

$x = FFI::cdef()->cast("void*", 20);
FFI::value($x); // int(20)

@chopins
Copy link
Contributor Author

chopins commented Jun 21, 2024

CData->cdata fetches the actual value. If the pointer uses CData->cdata to get the value of the address it will be ambiguous, and cast() is also the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants