splitup zval handling a bit

This commit is contained in:
Robin Appelman 2019-05-12 13:58:28 +02:00
commit f0ccd7ae1c
8 changed files with 185 additions and 63 deletions

View file

@ -1,3 +1,20 @@
//! Writing php extensions in rust made easy
//!
//! # Type casting
//!
//! Ivory automatically converts method parameters and return value from and to php compatible zval's
//!
//! The following types are supported for conversion.
//!
//! - rust signed and unsigned types up to 64bit to/from php `long`
//! - rust `f64` and `f32` to/from php `double`
//! - rust `bool` to/from php `bool`
//! - rust `String` to/from php `string`
//! - rust `Vec<T>` to/from php `array`
//! - rust `Vec<(u64, T)>` to/from php `array`
//!
//! Where `T` is a type that can be converted from/to php
#[macro_use]
pub mod macros;
pub mod error;

View file

@ -108,10 +108,12 @@ macro_rules! impl_from_phpval {
};
}
impl_from_phpval!(i64, Long, isize);
impl_from_phpval!(i64, Long, i64);
impl_from_phpval!(i64, Long, i32);
impl_from_phpval!(i64, Long, i16);
impl_from_phpval!(i64, Long, i8);
impl_from_phpval!(i64, Long, usize);
impl_from_phpval!(i64, Long, u64);
impl_from_phpval!(i64, Long, u32);
impl_from_phpval!(i64, Long, u16);

86
ivory/src/zend/array.rs Normal file
View file

@ -0,0 +1,86 @@
use std::alloc::{alloc, Layout};
use std::cmp::max;
use std::mem::size_of;
use std::ptr;
use ivory_sys::*;
use crate::zend::string::{construct_zend_string, parse_zend_string};
use crate::zend::{ZVal, ZValType};
use crate::{ArrayKey, PhpVal};
pub(super) unsafe fn parse_zend_array(arr: zend_array) -> Vec<(ArrayKey, PhpVal)> {
let len = arr.nNumUsed;
let mut result = Vec::new();
for i in 0..len {
let elem = *arr.arData.add(i as usize);
let key = if elem.key.is_null() {
ArrayKey::Int(elem.h)
} else {
ArrayKey::String(parse_zend_string(&*elem.key))
};
let val: PhpVal = ZVal::from(elem.val).as_php_val();
match val {
PhpVal::Undef => {}
_ => result.push((key, val)),
}
}
result
}
// WIP pure rust implementation of array construction in order to be able to run conversion test without linking to php
pub(super) fn create_zend_array(vec: Vec<(ArrayKey, PhpVal)>) -> zend_array {
let bucket_size = size_of::<Bucket>();
let size_min = max(8, vec.len());
let layout =
Layout::from_size_align(bucket_size * size_min, bucket_size).expect("invalid layout");
let bucket_mem: *mut Bucket = unsafe { alloc(layout) } as *mut Bucket;
let array = zend_array {
gc: _zend_refcounted_h {
refcount: 1,
u: _zend_refcounted_h__bindgen_ty_1 {
type_info: u8::from(ZValType::Array) as u32,
},
},
u: _zend_array__bindgen_ty_1 { flags: 1 << 3 }, // uninitialized
nTableMask: u32::max_value() - 1,
arData: bucket_mem,
nNumUsed: vec.len() as u32,
nNumOfElements: vec.len() as u32,
nTableSize: size_min as u32,
nInternalPointer: 0,
nNextFreeElement: i64::min_value(),
pDestructor: Some(zval_ptr_dtor),
};
let mut curr_bucket: *mut Bucket = bucket_mem;
for (key, val) in vec.into_iter() {
unsafe {
match key {
ArrayKey::Int(key) => {
(*curr_bucket).h = key;
(*curr_bucket).key = ptr::null_mut()
}
ArrayKey::String(key) => {
(*curr_bucket).h = hash_djbx33a(key.as_bytes());
(*curr_bucket).key = construct_zend_string(key)
}
}
curr_bucket = curr_bucket.add(1);
}
}
array
}
fn hash_djbx33a(data: &[u8]) -> u64 {
let mut hash = 5381u64;
for byte in data {
hash = (hash * 33) + *byte as u64;
}
hash | 0x8000000000000000
}

View file

@ -2,6 +2,8 @@ pub use self::function::*;
pub use self::module::*;
pub use self::zval::{ExecuteData, ZVal, ZValType};
mod array;
mod function;
mod module;
mod string;
mod zval;

46
ivory/src/zend/string.rs Normal file
View file

@ -0,0 +1,46 @@
use ivory_sys::*;
use std::alloc::{alloc, Layout};
use std::mem::size_of;
use std::ptr;
use std::str;
pub(super) unsafe fn parse_zend_string(string: *const zend_string) -> String {
let len = (*string).len;
let base = string as *const u8;
let str_start = base.add(size_of::<ZendStringHeader>());
let slice: &[u8] = std::slice::from_raw_parts(str_start, len);
str::from_utf8_unchecked(slice).to_string()
}
#[repr(C)]
pub struct ZendStringHeader {
gc: zend_refcounted_h,
h: zend_ulong,
len: usize,
}
pub(super) fn construct_zend_string(string: String) -> *mut zend_string {
let len = string.len();
let header_size = size_of::<ZendStringHeader>();
let layout = Layout::from_size_align(len + header_size, size_of::<zend_string>())
.expect("invalid layout");
let raw = unsafe { alloc(layout) };
let header = ZendStringHeader {
gc: zend_refcounted_h {
refcount: 1, // ?? no clue actually,
u: _zend_refcounted_h__bindgen_ty_1 { type_info: 0 },
},
h: 0,
len,
};
unsafe {
ptr::write(raw as *mut ZendStringHeader, header);
ptr::copy(string.as_ptr(), raw.add(header_size), len);
};
raw as *mut zend_string
}

View file

@ -1,12 +1,12 @@
use std::alloc::{alloc, Layout};
use std::fmt;
use std::fmt::Display;
use std::intrinsics::transmute;
use std::mem::size_of;
use std::{ptr, str};
use ivory_sys::*;
use crate::zend::array::parse_zend_array;
use crate::zend::string::{construct_zend_string, parse_zend_string};
use crate::{ArrayKey, PhpVal};
#[repr(transparent)]
@ -58,51 +58,15 @@ impl Iterator for IntoArgIterator {
}
}
unsafe fn zend_str_as_string(string: *const zend_string) -> String {
let len = (*string).len;
let base = string as *const u8;
let str_start = base.add(size_of::<ZendStringHeader>());
let slice: &[u8] = std::slice::from_raw_parts(str_start, len);
str::from_utf8_unchecked(slice).to_string()
}
#[repr(C)]
pub struct ZendStringHeader {
gc: zend_refcounted_h,
h: zend_ulong,
len: usize,
}
#[allow(clippy::cast_ptr_alignment)] // alignment of pointer casts is guaranteed by the layout
fn string_into_zend_str(string: String) -> *mut zend_string {
let len = string.len();
let header_size = size_of::<ZendStringHeader>();
let layout = Layout::from_size_align(len + header_size, size_of::<zend_string>())
.expect("invalid layout");
let raw = unsafe { alloc(layout) };
let header = ZendStringHeader {
gc: zend_refcounted_h {
refcount: 1, // ?? no clue actually,
u: _zend_refcounted_h__bindgen_ty_1 { type_info: 0 },
},
h: 0,
len,
};
unsafe {
ptr::write(raw as *mut ZendStringHeader, header);
ptr::copy(string.as_ptr(), raw.add(header_size), len);
};
raw as *mut zend_string
}
#[repr(transparent)]
pub struct ZVal(zval);
impl From<zval> for ZVal {
fn from(val: zval) -> Self {
ZVal(val)
}
}
impl ZVal {
pub fn get_type(&self) -> ZValType {
unsafe { self.0.u1.v.type_.into() }
@ -117,27 +81,11 @@ impl ZVal {
}
pub unsafe fn as_str(&self) -> String {
zend_str_as_string(self.0.value.str)
parse_zend_string(self.0.value.str)
}
pub unsafe fn as_array(&self) -> Vec<(ArrayKey, PhpVal)> {
let arr = *self.0.value.arr;
let len = arr.nNumUsed;
let mut result = Vec::new();
for i in 0..len {
let elem = *arr.arData.add(i as usize);
let key = if elem.key.is_null() {
ArrayKey::Int(elem.h)
} else {
ArrayKey::String(zend_str_as_string(&*elem.key))
};
let val: PhpVal = ZVal(elem.val).as_php_val();
match val {
PhpVal::Undef => {}
_ => result.push((key, val)),
}
}
result
parse_zend_array(*self.0.value.arr)
}
pub fn as_php_val(&self) -> PhpVal {
@ -189,6 +137,12 @@ impl Display for ZValType {
}
}
impl From<ZValType> for u8 {
fn from(val: ZValType) -> Self {
unsafe { transmute(val) }
}
}
impl From<u8> for ZValType {
fn from(val: u8) -> Self {
if val > 10 {
@ -253,7 +207,7 @@ impl From<PhpVal> for ZVal {
}),
PhpVal::String(val) => ZVal(zval {
value: zend_value {
str: string_into_zend_str(val),
str: construct_zend_string(val),
},
u1: ty.into(),
u2: _zval_struct__bindgen_ty_2 { extra: 0 },

View file

@ -168,6 +168,7 @@ fn main() {
.whitelist_function("_zend_new_array")
.whitelist_function("add_index_zval")
.whitelist_function("add_assoc_zval_ex")
.whitelist_function("zval_ptr_dtor")
.whitelist_type("zval")
.whitelist_type("zend_execute_data")
.whitelist_type("zend_module_entry")

View file

@ -1,3 +1,4 @@
#[link(libphp)]
use maplit::hashmap;
use pretty_assertions::assert_eq;
@ -40,6 +41,19 @@ fn cast_into_php_val() {
]),
vec![(0u8, 1), (3, 2), (6, 3)].into()
);
assert_eq!(
PhpVal::Array(vec![
(ArrayKey::String("asd".to_string()), PhpVal::Long(1)),
(ArrayKey::String("foo".to_string()), PhpVal::Long(2)),
(ArrayKey::String("bar".to_string()), PhpVal::Long(3))
]),
vec![
("asd".to_string(), 1),
("foo".to_string(), 2),
("bar".to_string(), 3)
]
.into()
);
assert_eq!(
PhpVal::Array(vec![
(ArrayKey::Int(0), PhpVal::Long(1)),