fix: returns a zend_array directly in types.go (#1894)

This commit is contained in:
Alexander Stecher
2025-12-12 22:55:58 +01:00
committed by GitHub
parent 41da660088
commit 11213fd1de
8 changed files with 109 additions and 70 deletions

117
types.go
View File

@@ -1,6 +1,20 @@
package frankenphp
/*
#cgo nocallback __zend_new_array__
#cgo nocallback __zval_null__
#cgo nocallback __zval_bool__
#cgo nocallback __zval_long__
#cgo nocallback __zval_double__
#cgo nocallback __zval_string__
#cgo nocallback __zval_arr__
#cgo noescape __zend_new_array__
#cgo noescape __zval_null__
#cgo noescape __zval_bool__
#cgo noescape __zval_long__
#cgo noescape __zval_double__
#cgo noescape __zval_string__
#cgo noescape __zval_arr__
#include "types.h"
*/
import "C"
@@ -13,7 +27,7 @@ import (
)
type toZval interface {
toZval() *C.zval
toZval(*C.zval)
}
// EXPERIMENTAL: GoString copies a zend_string to a Go string.
@@ -50,8 +64,8 @@ type AssociativeArray[T any] struct {
Order []string
}
func (a AssociativeArray[T]) toZval() *C.zval {
return (*C.zval)(PHPAssociativeArray[T](a))
func (a AssociativeArray[T]) toZval(zval *C.zval) {
C.__zval_arr__(zval, (*C.zend_array)(PHPAssociativeArray[T](a)))
}
// EXPERIMENTAL: GoAssociativeArray converts a zend_array to a Go AssociativeArray
@@ -61,7 +75,7 @@ func GoAssociativeArray[T any](arr unsafe.Pointer) (AssociativeArray[T], error)
return AssociativeArray[T]{entries, order}, err
}
// EXPERIMENTAL: GoMap converts a zval having a zend_array value to an unordered Go map
// EXPERIMENTAL: GoMap converts a zend_array to an unordered Go map
func GoMap[T any](arr unsafe.Pointer) (map[string]T, error) {
entries, _, err := goArray[T](arr, false)
@@ -73,27 +87,25 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
return nil, nil, errors.New("received a nil pointer on array conversion")
}
zval := (*C.zval)(arr)
v, err := extractZvalValue(zval, C.IS_ARRAY)
if err != nil {
return nil, nil, fmt.Errorf("received a *zval that wasn't a HashTable on array conversion: %w", err)
array := (*C.zend_array)(arr)
if array == nil {
return nil, nil, fmt.Errorf("received a *zval that wasn't a HashTable on array conversion")
}
hashTable := (*C.HashTable)(v)
nNumUsed := hashTable.nNumUsed
nNumUsed := array.nNumUsed
entries := make(map[string]T, nNumUsed)
var order []string
if ordered {
order = make([]string, 0, nNumUsed)
}
if htIsPacked(hashTable) {
// if the HashTable is packed, convert all integer keys to strings
if htIsPacked(array) {
// if the array is packed, convert all integer keys to strings
// this is probably a bug by the dev using this function
// still, we'll (inefficiently) convert to an associative array
for i := C.uint32_t(0); i < nNumUsed; i++ {
v := C.get_ht_packed_data(hashTable, i)
v := C.get_ht_packed_data(array, i)
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
strIndex := strconv.Itoa(int(i))
e, err := goValue[T](v)
@@ -114,7 +126,7 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
var zeroVal T
for i := C.uint32_t(0); i < nNumUsed; i++ {
bucket := C.get_ht_bucket_data(hashTable, i)
bucket := C.get_ht_bucket_data(array, i)
if bucket == nil || C.zval_get_type(&bucket.val) == C.IS_UNDEF {
continue
}
@@ -150,26 +162,24 @@ func goArray[T any](arr unsafe.Pointer, ordered bool) (map[string]T, []string, e
return entries, order, nil
}
// EXPERIMENTAL: GoPackedArray converts a zval with a zend_array value to a Go slice
// EXPERIMENTAL: GoPackedArray converts a zend_array to a Go slice
func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
if arr == nil {
return nil, errors.New("GoPackedArray received a nil value")
}
zval := (*C.zval)(arr)
v, err := extractZvalValue(zval, C.IS_ARRAY)
if err != nil {
return nil, fmt.Errorf("GoPackedArray received *zval that wasn't a HashTable: %w", err)
array := (*C.zend_array)(arr)
if array == nil {
return nil, fmt.Errorf("GoPackedArray received *zval that wasn't a HashTable")
}
hashTable := (*C.HashTable)(v)
nNumUsed := hashTable.nNumUsed
nNumUsed := array.nNumUsed
result := make([]T, 0, nNumUsed)
if htIsPacked(hashTable) {
if htIsPacked(array) {
for i := C.uint32_t(0); i < nNumUsed; i++ {
v := C.get_ht_packed_data(hashTable, i)
v := C.get_ht_packed_data(array, i)
if v != nil && C.zval_get_type(v) != C.IS_UNDEF {
v, err := goValue[T](v)
if err != nil {
@@ -185,7 +195,7 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
// fallback if ht isn't packed - equivalent to array_values()
for i := C.uint32_t(0); i < nNumUsed; i++ {
bucket := C.get_ht_bucket_data(hashTable, i)
bucket := C.get_ht_bucket_data(array, i)
if bucket != nil && C.zval_get_type(&bucket.val) != C.IS_UNDEF {
v, err := goValue[T](&bucket.val)
if err != nil {
@@ -199,18 +209,18 @@ func GoPackedArray[T any](arr unsafe.Pointer) ([]T, error) {
return result, nil
}
// EXPERIMENTAL: PHPMap converts an unordered Go map to a PHP zend_array
// EXPERIMENTAL: PHPMap converts an unordered Go map to a zend_array
func PHPMap[T any](arr map[string]T) unsafe.Pointer {
return phpArray[T](arr, nil)
}
// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a PHP zval with a zend_array value
// EXPERIMENTAL: PHPAssociativeArray converts a Go AssociativeArray to a zend_array
func PHPAssociativeArray[T any](arr AssociativeArray[T]) unsafe.Pointer {
return phpArray[T](arr.Map, arr.Order)
}
func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer {
var zendArray *C.HashTable
var zendArray *C.zend_array
if len(order) != 0 {
zendArray = createNewArray((uint32)(len(order)))
@@ -227,10 +237,7 @@ func phpArray[T any](entries map[string]T, order []string) unsafe.Pointer {
}
}
var zval C.zval
C.__zval_arr__(&zval, zendArray)
return unsafe.Pointer(&zval)
return unsafe.Pointer(zendArray)
}
// EXPERIMENTAL: PHPPackedArray converts a Go slice to a PHP zval with a zend_array value.
@@ -241,10 +248,7 @@ func PHPPackedArray[T any](slice []T) unsafe.Pointer {
C.zend_hash_next_index_insert(zendArray, zval)
}
var zval C.zval
C.__zval_arr__(&zval, zendArray)
return unsafe.Pointer(&zval)
return unsafe.Pointer(zendArray)
}
// EXPERIMENTAL: GoValue converts a PHP zval to a Go value
@@ -316,11 +320,11 @@ func goValue[T any](zval *C.zval) (res T, err error) {
return resZero, err
}
hashTable := (*C.HashTable)(v)
if hashTable != nil && htIsPacked(hashTable) {
array := (*C.zend_array)(v)
if array != nil && htIsPacked(array) {
typ := reflect.TypeOf(res)
if typ == nil || typ.Kind() == reflect.Interface && typ.NumMethod() == 0 {
r, e := GoPackedArray[any](unsafe.Pointer(zval))
r, e := GoPackedArray[any](unsafe.Pointer(array))
if e != nil {
return resZero, e
}
@@ -333,7 +337,7 @@ func goValue[T any](zval *C.zval) (res T, err error) {
return resZero, fmt.Errorf("cannot convert packed array to non-any Go type %s", typ.String())
}
a, err := GoAssociativeArray[T](unsafe.Pointer(zval))
a, err := GoAssociativeArray[T](unsafe.Pointer(array))
if err != nil {
return resZero, err
}
@@ -367,7 +371,8 @@ func phpValue(value any) *C.zval {
var zval C.zval
if toZvalObj, ok := value.(toZval); ok {
return toZvalObj.toZval()
toZvalObj.toZval(&zval)
return &zval
}
switch v := value.(type) {
@@ -382,12 +387,18 @@ func phpValue(value any) *C.zval {
case float64:
C.__zval_double__(&zval, C.double(v))
case string:
if v == "" {
C.__zval_empty_string__(&zval)
break
}
str := (*C.zend_string)(PHPString(v, false))
C.__zval_string__(&zval, str)
case AssociativeArray[any]:
C.__zval_arr__(&zval, (*C.zend_array)(PHPAssociativeArray[any](v)))
case map[string]any:
return (*C.zval)(PHPAssociativeArray[any](AssociativeArray[any]{Map: v}))
C.__zval_arr__(&zval, (*C.zend_array)(PHPMap[any](v)))
case []any:
return (*C.zval)(PHPPackedArray(v))
C.__zval_arr__(&zval, (*C.zend_array)(PHPPackedArray[any](v)))
default:
panic(fmt.Sprintf("unsupported Go type %T", v))
}
@@ -396,13 +407,13 @@ func phpValue(value any) *C.zval {
}
// createNewArray creates a new zend_array with the specified size.
func createNewArray(size uint32) *C.HashTable {
func createNewArray(size uint32) *C.zend_array {
arr := C.__zend_new_array__(C.uint32_t(size))
return (*C.HashTable)(unsafe.Pointer(arr))
return (*C.zend_array)(unsafe.Pointer(arr))
}
// htIsPacked checks if a HashTable is a list (packed) or hashmap (not packed).
func htIsPacked(ht *C.HashTable) bool {
// htIsPacked checks if a zend_array is a list (packed) or hashmap (not packed).
func htIsPacked(ht *C.zend_array) bool {
flags := *(*C.uint32_t)(unsafe.Pointer(&ht.u[0]))
return (flags & C.HASH_FLAG_PACKED) != 0
@@ -435,3 +446,13 @@ func extractZvalValue(zval *C.zval, expectedType C.uint8_t) (unsafe.Pointer, err
return nil, fmt.Errorf("unsupported zval type %d", expectedType)
}
func zendStringRelease(p unsafe.Pointer) {
zs := (*C.zend_string)(p)
C.zend_string_release(zs)
}
func zendHashDestroy(p unsafe.Pointer) {
ht := (*C.zend_array)(p)
C.zend_hash_destroy(ht)
}