diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe68b07ed7..e324863496 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,9 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - php: ["8.0", "8.1", "8.2"] + php: ["8.0", "8.1", "8.2", "8.3"] rust: [stable, nightly] - clang: ["14"] + clang: ["15", "17"] phpts: [ts, nts] exclude: # ext-php-rs requires nightly Rust when on Windows. @@ -28,6 +28,12 @@ jobs: phpts: ts - os: ubuntu-latest phpts: ts + - os: macos-latest + clang: "17" + - os: ubuntu-latest + clang: "15" + - os: windows-latest + clang: "15" env: CARGO_TERM_COLOR: always steps: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5f55df1c91..522682ba03 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: matrix: os: ["ubuntu-latest"] php: ["8.2"] - clang: ["14"] + clang: ["17"] mdbook: ["latest"] steps: - name: Checkout code diff --git a/Cargo.toml b/Cargo.toml index f2af9623a4..0deea94671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.3" +version = "0.10.5" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 49b15856d8..5b543f84a8 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -26,6 +26,8 @@ bind! { _efree, _emalloc, _zend_executor_globals, + _sapi_globals_struct, + _sapi_module_struct, _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY, _zend_expected_type_Z_EXPECTED_BOOL, @@ -92,6 +94,7 @@ bind! { zend_internal_arg_info, zend_is_callable, zend_is_identical, + zend_is_iterable, zend_long, zend_lookup_class_ex, zend_module_entry, @@ -110,6 +113,7 @@ bind! { zend_string, zend_string_init_interned, zend_throw_exception_ex, + zend_throw_exception_object, zend_type, zend_value, zend_wrong_parameters_count_error, @@ -141,6 +145,7 @@ bind! { IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, + IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_MIXED, @@ -159,6 +164,7 @@ bind! { IS_UNDEF, IS_VOID, IS_PTR, + IS_ITERABLE, MAY_BE_ANY, MAY_BE_BOOL, PHP_INI_USER, @@ -241,6 +247,7 @@ bind! { zend_class_serialize_deny, zend_class_unserialize_deny, zend_executor_globals, + sapi_module_struct, zend_objects_store_del, zend_hash_move_forward_ex, zend_hash_get_current_key_type_ex, @@ -251,14 +258,45 @@ bind! { gc_possible_root, ZEND_ACC_NOT_SERIALIZABLE, executor_globals, + php_core_globals, + core_globals, + sapi_globals_struct, + sapi_globals, + sapi_module, php_printf, __zend_malloc, tsrm_get_ls_cache, executor_globals_offset, + core_globals_offset, + sapi_globals_offset, + php_file_globals, + file_globals, + file_globals_id, + TRACK_VARS_POST, + TRACK_VARS_GET, + TRACK_VARS_COOKIE, + TRACK_VARS_SERVER, + TRACK_VARS_ENV, + TRACK_VARS_FILES, + TRACK_VARS_REQUEST, + sapi_request_info, + sapi_header_struct, + zend_is_auto_global, + zend_llist_get_next_ex, + zend_llist_get_prev_ex, + php_register_url_stream_wrapper, + php_stream_locate_url_wrapper, + php_unregister_url_stream_wrapper, + php_unregister_url_stream_wrapper_volatile, + php_register_url_stream_wrapper_volatile, + php_stream_wrapper, + php_stream_stdio_ops, zend_atomic_bool_store, zend_interrupt_function, zend_eval_string, zend_file_handle, zend_stream_init_filename, - php_execute_script + php_execute_script, + zend_register_module_ex, + _zend_bailout } diff --git a/build.rs b/build.rs index 4ee804171f..5afef52e2d 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,7 @@ use bindgen::RustTarget; use impl_::Provider; const MIN_PHP_API_VER: u32 = 20200930; -const MAX_PHP_API_VER: u32 = 20220829; +const MAX_PHP_API_VER: u32 = 20230831; pub trait PHPProvider<'a>: Sized { /// Create a new PHP provider. @@ -228,6 +228,8 @@ fn check_php_version(info: &PHPInfo) -> Result<()> { const PHP_82_API_VER: u32 = 20220829; + const PHP_83_API_VER: u32 = 20230831; + println!("cargo:rustc-cfg=php80"); if (PHP_81_API_VER..PHP_82_API_VER).contains(&version) { @@ -238,6 +240,10 @@ fn check_php_version(info: &PHPInfo) -> Result<()> { println!("cargo:rustc-cfg=php82"); } + if version >= PHP_83_API_VER { + println!("cargo:rustc-cfg=php83"); + } + Ok(()) } @@ -248,6 +254,8 @@ fn main() -> Result<()> { for path in [ manifest.join("src").join("wrapper.h"), manifest.join("src").join("wrapper.c"), + manifest.join("src").join("embed").join("embed.h"), + manifest.join("src").join("embed").join("embed.c"), manifest.join("allowed_bindings.rs"), manifest.join("windows_build.rs"), manifest.join("unix_build.rs"), diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 2d224908c6..c5cdd8061e 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,5 +1,85 @@ /* automatically generated by rust-bindgen 0.68.1 */ +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct __BindgenBitfieldUnit { + storage: Storage, +} +impl __BindgenBitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} +impl __BindgenBitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + pub fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + #[inline] + pub fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { + *byte |= mask; + } else { + *byte &= !mask; + } + } + #[inline] + pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if self.get_bit(i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + #[inline] + pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + self.set_bit(index + bit_offset, val_bit_is_set); + } + } +} pub const ZEND_DEBUG: u32 = 1; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2; @@ -17,8 +97,10 @@ pub const IS_RESOURCE: u32 = 9; pub const IS_REFERENCE: u32 = 10; pub const IS_CONSTANT_AST: u32 = 11; pub const IS_CALLABLE: u32 = 12; +pub const IS_ITERABLE: u32 = 13; pub const IS_VOID: u32 = 14; pub const IS_MIXED: u32 = 16; +pub const IS_INDIRECT: u32 = 12; pub const IS_PTR: u32 = 13; pub const _IS_BOOL: u32 = 18; pub const Z_TYPE_FLAGS_SHIFT: u32 = 8; @@ -97,10 +179,17 @@ pub const ZEND_EVAL_CODE: u32 = 4; pub const ZEND_ISEMPTY: u32 = 1; pub const _ZEND_SEND_MODE_SHIFT: u32 = 25; pub const _ZEND_IS_VARIADIC_BIT: u32 = 134217728; -pub const ZEND_MODULE_API_NO: u32 = 20220829; +pub const ZEND_MODULE_API_NO: u32 = 20230831; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; +pub const TRACK_VARS_POST: u32 = 0; +pub const TRACK_VARS_GET: u32 = 1; +pub const TRACK_VARS_COOKIE: u32 = 2; +pub const TRACK_VARS_SERVER: u32 = 3; +pub const TRACK_VARS_ENV: u32 = 4; +pub const TRACK_VARS_FILES: u32 = 5; +pub const TRACK_VARS_REQUEST: u32 = 6; pub const PHP_INI_USER: u32 = 1; pub const PHP_INI_PERDIR: u32 = 2; pub const PHP_INI_SYSTEM: u32 = 4; @@ -109,68 +198,84 @@ pub const CONST_CS: u32 = 0; pub const CONST_PERSISTENT: u32 = 1; pub const CONST_NO_FILE_CACHE: u32 = 2; pub const CONST_DEPRECATED: u32 = 4; -pub type __int64_t = ::std::os::raw::c_longlong; -pub type __darwin_off_t = __int64_t; -pub type fpos_t = __darwin_off_t; +pub type __dev_t = ::std::os::raw::c_ulong; +pub type __uid_t = ::std::os::raw::c_uint; +pub type __gid_t = ::std::os::raw::c_uint; +pub type __ino_t = ::std::os::raw::c_ulong; +pub type __mode_t = ::std::os::raw::c_uint; +pub type __nlink_t = ::std::os::raw::c_ulong; +pub type __off_t = ::std::os::raw::c_long; +pub type __off64_t = ::std::os::raw::c_long; +pub type __time_t = ::std::os::raw::c_long; +pub type __blksize_t = ::std::os::raw::c_long; +pub type __blkcnt_t = ::std::os::raw::c_long; +pub type __syscall_slong_t = ::std::os::raw::c_long; +pub type gid_t = __gid_t; +pub type uid_t = __uid_t; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sbuf { - pub _base: *mut ::std::os::raw::c_uchar, - pub _size: ::std::os::raw::c_int, +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sFILEX { +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +pub type FILE = _IO_FILE; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_marker { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sFILE { - pub _p: *mut ::std::os::raw::c_uchar, - pub _r: ::std::os::raw::c_int, - pub _w: ::std::os::raw::c_int, - pub _flags: ::std::os::raw::c_short, - pub _file: ::std::os::raw::c_short, - pub _bf: __sbuf, - pub _lbfsize: ::std::os::raw::c_int, - pub _cookie: *mut ::std::os::raw::c_void, - pub _close: ::std::option::Option< - unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int, - >, - pub _read: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: *mut ::std::os::raw::c_char, - arg3: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, - >, - pub _seek: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: fpos_t, - arg3: ::std::os::raw::c_int, - ) -> fpos_t, - >, - pub _write: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: *const ::std::os::raw::c_char, - arg3: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, - >, - pub _ub: __sbuf, - pub _extra: *mut __sFILEX, - pub _ur: ::std::os::raw::c_int, - pub _ubuf: [::std::os::raw::c_uchar; 3usize], - pub _nbuf: [::std::os::raw::c_uchar; 1usize], - pub _lb: __sbuf, - pub _blksize: ::std::os::raw::c_int, - pub _offset: fpos_t, -} -pub type FILE = __sFILE; +pub struct _IO_codecvt { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_wide_data { + _unused: [u8; 0], +} +pub type _IO_lock_t = ::std::os::raw::c_void; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_FILE { + pub _flags: ::std::os::raw::c_int, + pub _IO_read_ptr: *mut ::std::os::raw::c_char, + pub _IO_read_end: *mut ::std::os::raw::c_char, + pub _IO_read_base: *mut ::std::os::raw::c_char, + pub _IO_write_base: *mut ::std::os::raw::c_char, + pub _IO_write_ptr: *mut ::std::os::raw::c_char, + pub _IO_write_end: *mut ::std::os::raw::c_char, + pub _IO_buf_base: *mut ::std::os::raw::c_char, + pub _IO_buf_end: *mut ::std::os::raw::c_char, + pub _IO_save_base: *mut ::std::os::raw::c_char, + pub _IO_backup_base: *mut ::std::os::raw::c_char, + pub _IO_save_end: *mut ::std::os::raw::c_char, + pub _markers: *mut _IO_marker, + pub _chain: *mut _IO_FILE, + pub _fileno: ::std::os::raw::c_int, + pub _flags2: ::std::os::raw::c_int, + pub _old_offset: __off_t, + pub _cur_column: ::std::os::raw::c_ushort, + pub _vtable_offset: ::std::os::raw::c_schar, + pub _shortbuf: [::std::os::raw::c_char; 1usize], + pub _lock: *mut _IO_lock_t, + pub _offset: __off64_t, + pub _codecvt: *mut _IO_codecvt, + pub _wide_data: *mut _IO_wide_data, + pub _freeres_list: *mut _IO_FILE, + pub _freeres_buf: *mut ::std::os::raw::c_void, + pub __pad5: usize, + pub _mode: ::std::os::raw::c_int, + pub _unused2: [::std::os::raw::c_char; 20usize], +} pub type zend_long = i64; pub type zend_ulong = u64; -pub type zend_uchar = ::std::os::raw::c_uchar; +pub type zend_off_t = i64; pub const ZEND_RESULT_CODE_SUCCESS: ZEND_RESULT_CODE = 0; pub const ZEND_RESULT_CODE_FAILURE: ZEND_RESULT_CODE = -1; pub type ZEND_RESULT_CODE = ::std::os::raw::c_int; @@ -234,8 +339,8 @@ pub union _zval_struct__bindgen_ty_1 { #[repr(C)] #[derive(Copy, Clone)] pub struct _zval_struct__bindgen_ty_1__bindgen_ty_1 { - pub type_: zend_uchar, - pub type_flags: zend_uchar, + pub type_: u8, + pub type_flags: u8, pub u: _zval_struct__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1, } #[repr(C)] @@ -253,7 +358,7 @@ pub union _zval_struct__bindgen_ty_2 { pub num_args: u32, pub fe_pos: u32, pub fe_iter_idx: u32, - pub property_guard: u32, + pub guard: u32, pub constant_flags: u32, pub extra: u32, } @@ -311,10 +416,10 @@ pub union _zend_array__bindgen_ty_1 { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_array__bindgen_ty_1__bindgen_ty_1 { - pub flags: zend_uchar, - pub _unused: zend_uchar, - pub nIteratorsCount: zend_uchar, - pub _unused2: zend_uchar, + pub flags: u8, + pub _unused: u8, + pub nIteratorsCount: u8, + pub _unused2: u8, } #[repr(C)] #[derive(Copy, Clone)] @@ -329,6 +434,7 @@ pub type HashPosition = u32; pub struct _HashTableIterator { pub ht: *mut HashTable, pub pos: HashPosition, + pub next_copy: u32, } pub type HashTableIterator = _HashTableIterator; #[repr(C)] @@ -386,6 +492,41 @@ extern "C" { extern "C" { pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist_element { + pub next: *mut _zend_llist_element, + pub prev: *mut _zend_llist_element, + pub data: [::std::os::raw::c_char; 1usize], +} +pub type zend_llist_element = _zend_llist_element; +pub type llist_dtor_func_t = + ::std::option::Option; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist { + pub head: *mut zend_llist_element, + pub tail: *mut zend_llist_element, + pub count: usize, + pub size: usize, + pub dtor: llist_dtor_func_t, + pub persistent: ::std::os::raw::c_uchar, + pub traverse_ptr: *mut zend_llist_element, +} +pub type zend_llist = _zend_llist; +pub type zend_llist_position = *mut zend_llist_element; +extern "C" { + pub fn zend_llist_get_next_ex( + l: *mut zend_llist, + pos: *mut zend_llist_position, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn zend_llist_get_prev_ex( + l: *mut zend_llist, + pos: *mut zend_llist_position, + ) -> *mut ::std::os::raw::c_void; +} pub type zend_string_init_interned_func_t = ::std::option::Option< unsafe extern "C" fn( str_: *const ::std::os::raw::c_char, @@ -541,6 +682,25 @@ pub struct _zend_class_arrayaccess_funcs { pub zf_offsetunset: *mut zend_function, } pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct stat { + pub st_dev: __dev_t, + pub st_ino: __ino_t, + pub st_nlink: __nlink_t, + pub st_mode: __mode_t, + pub st_uid: __uid_t, + pub st_gid: __gid_t, + pub __pad0: ::std::os::raw::c_int, + pub st_rdev: __dev_t, + pub st_size: __off_t, + pub st_blksize: __blksize_t, + pub st_blocks: __blkcnt_t, + pub st_atim: timespec, + pub st_mtim: timespec, + pub st_ctim: timespec, + pub __glibc_reserved: [__syscall_slong_t; 3usize], +} pub type zend_stream_fsizer_t = ::std::option::Option usize>; pub type zend_stream_reader_t = ::std::option::Option< @@ -568,7 +728,7 @@ pub struct _zend_file_handle { pub handle: _zend_file_handle__bindgen_ty_1, pub filename: *mut zend_string, pub opened_path: *mut zend_string, - pub type_: zend_uchar, + pub type_: u8, pub primary_script: bool, pub in_list: bool, pub buf: *mut ::std::os::raw::c_char, @@ -587,6 +747,7 @@ extern "C" { filename: *const ::std::os::raw::c_char, ); } +pub type zend_stat_t = stat; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { @@ -700,6 +861,7 @@ pub struct _zend_class_entry { pub __debugInfo: *mut zend_function, pub __serialize: *mut zend_function, pub __unserialize: *mut zend_function, + pub default_object_handlers: *const zend_object_handlers, pub iterator_funcs_ptr: *mut zend_class_iterator_funcs, pub arrayaccess_funcs_ptr: *mut zend_class_arrayaccess_funcs, pub __bindgen_anon_2: _zend_class_entry__bindgen_ty_2, @@ -789,6 +951,9 @@ pub struct _zend_class_entry__bindgen_ty_4__bindgen_ty_2 { pub builtin_functions: *const _zend_function_entry, pub module: *mut _zend_module_entry, } +extern "C" { + pub fn _zend_bailout(filename: *const ::std::os::raw::c_char, lineno: u32) -> !; +} extern "C" { pub static mut zend_interrupt_function: ::std::option::Option; @@ -926,7 +1091,7 @@ pub type zend_object_get_gc_t = ::std::option::Option< >; pub type zend_object_do_operation_t = ::std::option::Option< unsafe extern "C" fn( - opcode: zend_uchar, + opcode: u8, result: *mut zval, op1: *mut zval, op2: *mut zval, @@ -993,10 +1158,10 @@ extern "C" { ) -> ::std::os::raw::c_int; } extern "C" { - pub fn zend_is_identical(op1: *mut zval, op2: *mut zval) -> bool; + pub fn zend_is_identical(op1: *const zval, op2: *const zval) -> bool; } extern "C" { - pub fn zend_is_true(op: *mut zval) -> ::std::os::raw::c_int; + pub fn zend_is_true(op: *const zval) -> ::std::os::raw::c_int; } pub type zend_op_array = _zend_op_array; pub type zend_op = _zend_op; @@ -1019,10 +1184,10 @@ pub struct _zend_op { pub result: znode_op, pub extended_value: u32, pub lineno: u32, - pub opcode: zend_uchar, - pub op1_type: zend_uchar, - pub op2_type: zend_uchar, - pub result_type: zend_uchar, + pub opcode: u8, + pub op1_type: u8, + pub op2_type: u8, + pub result_type: u8, } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1071,8 +1236,8 @@ pub type zend_arg_info = _zend_arg_info; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_op_array { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1081,8 +1246,8 @@ pub struct _zend_op_array { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, pub cache_size: ::std::os::raw::c_int, pub last_var: ::std::os::raw::c_int, pub last: u32, @@ -1111,8 +1276,8 @@ pub type zif_handler = ::std::option::Option< #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_internal_function { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1121,8 +1286,8 @@ pub struct _zend_internal_function { pub required_num_args: u32, pub arg_info: *mut zend_internal_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, pub handler: zif_handler, pub module: *mut _zend_module_entry, pub reserved: [*mut ::std::os::raw::c_void; 6usize], @@ -1131,7 +1296,7 @@ pub type zend_internal_function = _zend_internal_function; #[repr(C)] #[derive(Copy, Clone)] pub union _zend_function { - pub type_: zend_uchar, + pub type_: u8, pub quick_arg_flags: u32, pub common: _zend_function__bindgen_ty_1, pub op_array: zend_op_array, @@ -1140,8 +1305,8 @@ pub union _zend_function { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_function__bindgen_ty_1 { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1150,8 +1315,8 @@ pub struct _zend_function__bindgen_ty_1 { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, } #[repr(C)] pub struct _zend_execute_data { @@ -1165,7 +1330,15 @@ pub struct _zend_execute_data { pub run_time_cache: *mut *mut ::std::os::raw::c_void, pub extra_named_params: *mut zend_array, } -pub type sigjmp_buf = [::std::os::raw::c_int; 49usize]; +pub type __jmp_buf = [::std::os::raw::c_long; 8usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __jmp_buf_tag { + pub __jmpbuf: __jmp_buf, + pub __mask_was_saved: ::std::os::raw::c_int, + pub __saved_mask: __sigset_t, +} +pub type jmp_buf = [__jmp_buf_tag; 1usize]; pub type zend_executor_globals = _zend_executor_globals; extern "C" { pub static mut executor_globals: zend_executor_globals; @@ -1212,6 +1385,13 @@ pub type zend_objects_store = _zend_objects_store; extern "C" { pub fn zend_objects_store_del(object: *mut zend_object); } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_call_stack { + pub base: *mut ::std::os::raw::c_void, + pub max_size: usize, +} +pub type zend_call_stack = _zend_call_stack; pub type zend_vm_stack = *mut _zend_vm_stack; pub type zend_ini_entry = _zend_ini_entry; #[repr(C)] @@ -1235,7 +1415,7 @@ pub struct _zend_executor_globals { pub symtable_cache_ptr: *mut *mut zend_array, pub symbol_table: zend_array, pub included_files: HashTable, - pub bailout: *mut sigjmp_buf, + pub bailout: *mut jmp_buf, pub error_reporting: ::std::os::raw::c_int, pub exit_status: ::std::os::raw::c_int, pub function_table: *mut HashTable, @@ -1248,29 +1428,32 @@ pub struct _zend_executor_globals { pub current_execute_data: *mut _zend_execute_data, pub fake_scope: *mut zend_class_entry, pub jit_trace_num: u32, - pub precision: zend_long, pub ticks_count: ::std::os::raw::c_int, + pub precision: zend_long, pub persistent_constants_count: u32, pub persistent_functions_count: u32, pub persistent_classes_count: u32, - pub in_autoload: *mut HashTable, - pub full_tables_cleanup: bool, pub no_extensions: bool, + pub full_tables_cleanup: bool, pub vm_interrupt: zend_atomic_bool, pub timed_out: zend_atomic_bool, + pub in_autoload: *mut HashTable, pub hard_timeout: zend_long, + pub stack_base: *mut ::std::os::raw::c_void, + pub stack_limit: *mut ::std::os::raw::c_void, pub regular_list: HashTable, pub persistent_list: HashTable, pub user_error_handler_error_reporting: ::std::os::raw::c_int, + pub exception_ignore_args: bool, pub user_error_handler: zval, pub user_exception_handler: zval, pub user_error_handlers_error_reporting: zend_stack, pub user_error_handlers: zend_stack, pub user_exception_handlers: zend_stack, - pub error_handling: zend_error_handling_t, pub exception_class: *mut zend_class_entry, - pub timeout_seconds: zend_long, + pub error_handling: zend_error_handling_t, pub capture_warnings_during_sccp: ::std::os::raw::c_int, + pub timeout_seconds: zend_long, pub ini_directives: *mut HashTable, pub modified_ini_directives: *mut HashTable, pub error_reporting_ini_entry: *mut zend_ini_entry, @@ -1281,7 +1464,7 @@ pub struct _zend_executor_globals { pub exception_op: [zend_op; 3usize], pub current_module: *mut _zend_module_entry, pub active: bool, - pub flags: zend_uchar, + pub flags: u8, pub assertions: zend_long, pub ht_iterators_count: u32, pub ht_iterators_used: u32, @@ -1291,20 +1474,25 @@ pub struct _zend_executor_globals { pub trampoline: zend_function, pub call_trampoline_op: zend_op, pub weakrefs: HashTable, - pub exception_ignore_args: bool, pub exception_string_param_max_len: zend_long, pub get_gc_buffer: zend_get_gc_buffer, pub main_fiber_context: *mut zend_fiber_context, pub current_fiber_context: *mut zend_fiber_context, pub active_fiber: *mut zend_fiber, - pub fiber_stack_size: zend_long, + pub fiber_stack_size: usize, pub record_errors: bool, pub num_errors: u32, pub errors: *mut *mut zend_error_info, pub filename_override: *mut zend_string, pub lineno_override: zend_long, + pub call_stack: zend_call_stack, + pub max_allowed_stack_size: zend_long, + pub reserved_stack_size: zend_ulong, pub reserved: [*mut ::std::os::raw::c_void; 6usize], } +extern "C" { + pub fn zend_is_auto_global(name: *mut zend_string) -> bool; +} pub type zend_module_entry = _zend_module_entry; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1401,6 +1589,19 @@ pub struct _zend_function_entry { pub flags: u32, } pub type zend_function_entry = _zend_function_entry; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_fcall_info_cache { + pub function_handler: *mut zend_function, + pub calling_scope: *mut zend_class_entry, + pub called_scope: *mut zend_class_entry, + pub object: *mut zend_object, + pub closure: *mut zend_object, +} +pub type zend_fcall_info_cache = _zend_fcall_info_cache; +extern "C" { + pub fn zend_register_module_ex(module: *mut zend_module_entry) -> *mut zend_module_entry; +} extern "C" { pub fn zend_register_internal_class_ex( class_entry: *mut zend_class_entry, @@ -1455,6 +1656,9 @@ extern "C" { named_params: *mut HashTable, ); } +extern "C" { + pub fn zend_is_iterable(iterable: *const zval) -> bool; +} pub const _zend_expected_type_Z_EXPECTED_LONG: _zend_expected_type = 0; pub const _zend_expected_type_Z_EXPECTED_LONG_OR_NULL: _zend_expected_type = 1; pub const _zend_expected_type_Z_EXPECTED_BOOL: _zend_expected_type = 2; @@ -1479,15 +1683,17 @@ pub const _zend_expected_type_Z_EXPECTED_DOUBLE: _zend_expected_type = 20; pub const _zend_expected_type_Z_EXPECTED_DOUBLE_OR_NULL: _zend_expected_type = 21; pub const _zend_expected_type_Z_EXPECTED_NUMBER: _zend_expected_type = 22; pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_NULL: _zend_expected_type = 23; -pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING: _zend_expected_type = 24; -pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING_OR_NULL: _zend_expected_type = 25; -pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG: _zend_expected_type = 26; -pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG_OR_NULL: _zend_expected_type = 27; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME: _zend_expected_type = 28; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL: _zend_expected_type = 29; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING: _zend_expected_type = 30; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING_OR_NULL: _zend_expected_type = 31; -pub const _zend_expected_type_Z_EXPECTED_LAST: _zend_expected_type = 32; +pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_STRING: _zend_expected_type = 24; +pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_STRING_OR_NULL: _zend_expected_type = 25; +pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING: _zend_expected_type = 26; +pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING_OR_NULL: _zend_expected_type = 27; +pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG: _zend_expected_type = 28; +pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG_OR_NULL: _zend_expected_type = 29; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME: _zend_expected_type = 30; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL: _zend_expected_type = 31; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING: _zend_expected_type = 32; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING_OR_NULL: _zend_expected_type = 33; +pub const _zend_expected_type_Z_EXPECTED_LAST: _zend_expected_type = 34; pub type _zend_expected_type = ::std::os::raw::c_uint; extern "C" { pub fn zend_wrong_parameters_count_error(min_num_args: u32, max_num_args: u32); @@ -1503,6 +1709,539 @@ extern "C" { ... ); } +pub type php_stream = _php_stream; +pub type php_stream_wrapper = _php_stream_wrapper; +pub type php_stream_context = _php_stream_context; +pub type php_stream_filter = _php_stream_filter; +pub type php_stream_notification_func = ::std::option::Option< + unsafe extern "C" fn( + context: *mut php_stream_context, + notifycode: ::std::os::raw::c_int, + severity: ::std::os::raw::c_int, + xmsg: *mut ::std::os::raw::c_char, + xcode: ::std::os::raw::c_int, + bytes_sofar: usize, + bytes_max: usize, + ptr: *mut ::std::os::raw::c_void, + ), +>; +pub type php_stream_notifier = _php_stream_notifier; +#[repr(C)] +pub struct _php_stream_notifier { + pub func: php_stream_notification_func, + pub dtor: ::std::option::Option, + pub ptr: zval, + pub mask: ::std::os::raw::c_int, + pub progress: usize, + pub progress_max: usize, +} +#[repr(C)] +pub struct _php_stream_context { + pub notifier: *mut php_stream_notifier, + pub options: zval, + pub res: *mut zend_resource, +} +pub type php_stream_bucket = _php_stream_bucket; +pub type php_stream_bucket_brigade = _php_stream_bucket_brigade; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_bucket { + pub next: *mut php_stream_bucket, + pub prev: *mut php_stream_bucket, + pub brigade: *mut php_stream_bucket_brigade, + pub buf: *mut ::std::os::raw::c_char, + pub buflen: usize, + pub own_buf: u8, + pub is_persistent: u8, + pub refcount: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_bucket_brigade { + pub head: *mut php_stream_bucket, + pub tail: *mut php_stream_bucket, +} +pub const php_stream_filter_status_t_PSFS_ERR_FATAL: php_stream_filter_status_t = 0; +pub const php_stream_filter_status_t_PSFS_FEED_ME: php_stream_filter_status_t = 1; +pub const php_stream_filter_status_t_PSFS_PASS_ON: php_stream_filter_status_t = 2; +pub type php_stream_filter_status_t = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_filter_ops { + pub filter: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + thisfilter: *mut php_stream_filter, + buckets_in: *mut php_stream_bucket_brigade, + buckets_out: *mut php_stream_bucket_brigade, + bytes_consumed: *mut usize, + flags: ::std::os::raw::c_int, + ) -> php_stream_filter_status_t, + >, + pub dtor: ::std::option::Option, + pub label: *const ::std::os::raw::c_char, +} +pub type php_stream_filter_ops = _php_stream_filter_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_filter_chain { + pub head: *mut php_stream_filter, + pub tail: *mut php_stream_filter, + pub stream: *mut php_stream, +} +pub type php_stream_filter_chain = _php_stream_filter_chain; +#[repr(C)] +pub struct _php_stream_filter { + pub fops: *const php_stream_filter_ops, + pub abstract_: zval, + pub next: *mut php_stream_filter, + pub prev: *mut php_stream_filter, + pub is_persistent: ::std::os::raw::c_int, + pub chain: *mut php_stream_filter_chain, + pub buffer: php_stream_bucket_brigade, + pub res: *mut zend_resource, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_statbuf { + pub sb: zend_stat_t, +} +pub type php_stream_statbuf = _php_stream_statbuf; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_ops { + pub write: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + buf: *const ::std::os::raw::c_char, + count: usize, + ) -> isize, + >, + pub read: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + buf: *mut ::std::os::raw::c_char, + count: usize, + ) -> isize, + >, + pub close: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + close_handle: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub flush: ::std::option::Option< + unsafe extern "C" fn(stream: *mut php_stream) -> ::std::os::raw::c_int, + >, + pub label: *const ::std::os::raw::c_char, + pub seek: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + offset: zend_off_t, + whence: ::std::os::raw::c_int, + newoffset: *mut zend_off_t, + ) -> ::std::os::raw::c_int, + >, + pub cast: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + castas: ::std::os::raw::c_int, + ret: *mut *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >, + pub stat: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + ssb: *mut php_stream_statbuf, + ) -> ::std::os::raw::c_int, + >, + pub set_option: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + option: ::std::os::raw::c_int, + value: ::std::os::raw::c_int, + ptrparam: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >, +} +pub type php_stream_ops = _php_stream_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_wrapper_ops { + pub stream_opener: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + filename: *const ::std::os::raw::c_char, + mode: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + opened_path: *mut *mut zend_string, + context: *mut php_stream_context, + __php_stream_call_depth: ::std::os::raw::c_int, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ) -> *mut php_stream, + >, + pub stream_closer: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + stream: *mut php_stream, + ) -> ::std::os::raw::c_int, + >, + pub stream_stat: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + stream: *mut php_stream, + ssb: *mut php_stream_statbuf, + ) -> ::std::os::raw::c_int, + >, + pub url_stat: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + flags: ::std::os::raw::c_int, + ssb: *mut php_stream_statbuf, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub dir_opener: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + filename: *const ::std::os::raw::c_char, + mode: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + opened_path: *mut *mut zend_string, + context: *mut php_stream_context, + __php_stream_call_depth: ::std::os::raw::c_int, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ) -> *mut php_stream, + >, + pub label: *const ::std::os::raw::c_char, + pub unlink: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub rename: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url_from: *const ::std::os::raw::c_char, + url_to: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_mkdir: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + mode: ::std::os::raw::c_int, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_rmdir: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_metadata: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + value: *mut ::std::os::raw::c_void, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, +} +pub type php_stream_wrapper_ops = _php_stream_wrapper_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_wrapper { + pub wops: *const php_stream_wrapper_ops, + pub abstract_: *mut ::std::os::raw::c_void, + pub is_url: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct _php_stream { + pub ops: *const php_stream_ops, + pub abstract_: *mut ::std::os::raw::c_void, + pub readfilters: php_stream_filter_chain, + pub writefilters: php_stream_filter_chain, + pub wrapper: *mut php_stream_wrapper, + pub wrapperthis: *mut ::std::os::raw::c_void, + pub wrapperdata: zval, + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub mode: [::std::os::raw::c_char; 16usize], + pub flags: u32, + pub res: *mut zend_resource, + pub stdiocast: *mut FILE, + pub orig_path: *mut ::std::os::raw::c_char, + pub ctx: *mut zend_resource, + pub position: zend_off_t, + pub readbuf: *mut ::std::os::raw::c_uchar, + pub readbuflen: usize, + pub readpos: zend_off_t, + pub writepos: zend_off_t, + pub chunk_size: usize, + pub open_filename: *const ::std::os::raw::c_char, + pub open_lineno: u32, + pub enclosing_stream: *mut _php_stream, +} +impl _php_stream { + #[inline] + pub fn is_persistent(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u16) } + } + #[inline] + pub fn set_is_persistent(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 1u8, val as u64) + } + } + #[inline] + pub fn in_free(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u16) } + } + #[inline] + pub fn set_in_free(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(1usize, 2u8, val as u64) + } + } + #[inline] + pub fn eof(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u16) } + } + #[inline] + pub fn set_eof(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(3usize, 1u8, val as u64) + } + } + #[inline] + pub fn __exposed(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u16) } + } + #[inline] + pub fn set___exposed(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(4usize, 1u8, val as u64) + } + } + #[inline] + pub fn fclose_stdiocast(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u16) } + } + #[inline] + pub fn set_fclose_stdiocast(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(5usize, 2u8, val as u64) + } + } + #[inline] + pub fn has_buffered_data(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u16) } + } + #[inline] + pub fn set_has_buffered_data(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(7usize, 1u8, val as u64) + } + } + #[inline] + pub fn fclose_stdiocast_flush_in_progress(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u16) } + } + #[inline] + pub fn set_fclose_stdiocast_flush_in_progress(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(8usize, 1u8, val as u64) + } + } + #[inline] + pub fn new_bitfield_1( + is_persistent: u16, + in_free: u16, + eof: u16, + __exposed: u16, + fclose_stdiocast: u16, + has_buffered_data: u16, + fclose_stdiocast_flush_in_progress: u16, + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 1u8, { + let is_persistent: u16 = unsafe { ::std::mem::transmute(is_persistent) }; + is_persistent as u64 + }); + __bindgen_bitfield_unit.set(1usize, 2u8, { + let in_free: u16 = unsafe { ::std::mem::transmute(in_free) }; + in_free as u64 + }); + __bindgen_bitfield_unit.set(3usize, 1u8, { + let eof: u16 = unsafe { ::std::mem::transmute(eof) }; + eof as u64 + }); + __bindgen_bitfield_unit.set(4usize, 1u8, { + let __exposed: u16 = unsafe { ::std::mem::transmute(__exposed) }; + __exposed as u64 + }); + __bindgen_bitfield_unit.set(5usize, 2u8, { + let fclose_stdiocast: u16 = unsafe { ::std::mem::transmute(fclose_stdiocast) }; + fclose_stdiocast as u64 + }); + __bindgen_bitfield_unit.set(7usize, 1u8, { + let has_buffered_data: u16 = unsafe { ::std::mem::transmute(has_buffered_data) }; + has_buffered_data as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { + let fclose_stdiocast_flush_in_progress: u16 = + unsafe { ::std::mem::transmute(fclose_stdiocast_flush_in_progress) }; + fclose_stdiocast_flush_in_progress as u64 + }); + __bindgen_bitfield_unit + } +} +extern "C" { + pub static mut php_stream_stdio_ops: php_stream_ops; +} +extern "C" { + pub fn php_register_url_stream_wrapper( + protocol: *const ::std::os::raw::c_char, + wrapper: *const php_stream_wrapper, + ) -> zend_result; +} +extern "C" { + pub fn php_unregister_url_stream_wrapper( + protocol: *const ::std::os::raw::c_char, + ) -> zend_result; +} +extern "C" { + pub fn php_register_url_stream_wrapper_volatile( + protocol: *mut zend_string, + wrapper: *mut php_stream_wrapper, + ) -> zend_result; +} +extern "C" { + pub fn php_unregister_url_stream_wrapper_volatile(protocol: *mut zend_string) -> zend_result; +} +extern "C" { + pub fn php_stream_locate_url_wrapper( + path: *const ::std::os::raw::c_char, + path_for_open: *mut *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + ) -> *mut php_stream_wrapper; +} +pub type php_core_globals = _php_core_globals; +#[repr(C)] +pub struct _php_core_globals { + pub output_buffering: zend_long, + pub implicit_flush: bool, + pub enable_dl: bool, + pub display_errors: u8, + pub display_startup_errors: bool, + pub log_errors: bool, + pub ignore_repeated_errors: bool, + pub ignore_repeated_source: bool, + pub report_memleaks: bool, + pub output_handler: *mut ::std::os::raw::c_char, + pub unserialize_callback_func: *mut ::std::os::raw::c_char, + pub serialize_precision: zend_long, + pub memory_limit: zend_long, + pub max_input_time: zend_long, + pub error_log: *mut ::std::os::raw::c_char, + pub doc_root: *mut ::std::os::raw::c_char, + pub user_dir: *mut ::std::os::raw::c_char, + pub include_path: *mut ::std::os::raw::c_char, + pub open_basedir: *mut ::std::os::raw::c_char, + pub open_basedir_modified: bool, + pub extension_dir: *mut ::std::os::raw::c_char, + pub php_binary: *mut ::std::os::raw::c_char, + pub sys_temp_dir: *mut ::std::os::raw::c_char, + pub upload_tmp_dir: *mut ::std::os::raw::c_char, + pub upload_max_filesize: zend_long, + pub error_append_string: *mut ::std::os::raw::c_char, + pub error_prepend_string: *mut ::std::os::raw::c_char, + pub auto_prepend_file: *mut ::std::os::raw::c_char, + pub auto_append_file: *mut ::std::os::raw::c_char, + pub input_encoding: *mut ::std::os::raw::c_char, + pub internal_encoding: *mut ::std::os::raw::c_char, + pub output_encoding: *mut ::std::os::raw::c_char, + pub arg_separator: arg_separators, + pub variables_order: *mut ::std::os::raw::c_char, + pub rfc1867_protected_variables: HashTable, + pub connection_status: ::std::os::raw::c_short, + pub ignore_user_abort: bool, + pub header_is_being_sent: ::std::os::raw::c_uchar, + pub tick_functions: zend_llist, + pub http_globals: [zval; 6usize], + pub expose_php: bool, + pub register_argc_argv: bool, + pub auto_globals_jit: bool, + pub html_errors: bool, + pub xmlrpc_errors: bool, + pub docref_root: *mut ::std::os::raw::c_char, + pub docref_ext: *mut ::std::os::raw::c_char, + pub xmlrpc_error_number: zend_long, + pub activated_auto_globals: [bool; 8usize], + pub modules_activated: bool, + pub file_uploads: bool, + pub during_request_startup: bool, + pub allow_url_fopen: bool, + pub enable_post_data_reading: bool, + pub report_zend_debug: bool, + pub last_error_type: ::std::os::raw::c_int, + pub last_error_lineno: ::std::os::raw::c_int, + pub last_error_message: *mut zend_string, + pub last_error_file: *mut zend_string, + pub php_sys_temp_dir: *mut ::std::os::raw::c_char, + pub disable_classes: *mut ::std::os::raw::c_char, + pub max_input_nesting_level: zend_long, + pub max_input_vars: zend_long, + pub user_ini_filename: *mut ::std::os::raw::c_char, + pub user_ini_cache_ttl: zend_long, + pub request_order: *mut ::std::os::raw::c_char, + pub mail_log: *mut ::std::os::raw::c_char, + pub mail_x_header: bool, + pub mail_mixed_lf_and_crlf: bool, + pub in_error_log: bool, + pub allow_url_include: bool, + pub in_user_include: bool, + pub have_called_openlog: bool, + pub syslog_facility: zend_long, + pub syslog_ident: *mut ::std::os::raw::c_char, + pub syslog_filter: zend_long, + pub error_log_mode: zend_long, +} +extern "C" { + pub static mut core_globals: _php_core_globals; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _arg_separators { + pub output: *mut ::std::os::raw::c_char, + pub input: *mut ::std::os::raw::c_char, +} +pub type arg_separators = _arg_separators; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry_def { @@ -1610,6 +2349,37 @@ extern "C" { extern "C" { pub fn php_info_print_table_end(); } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct hostent { + pub h_name: *mut ::std::os::raw::c_char, + pub h_aliases: *mut *mut ::std::os::raw::c_char, + pub h_addrtype: ::std::os::raw::c_int, + pub h_length: ::std::os::raw::c_int, + pub h_addr_list: *mut *mut ::std::os::raw::c_char, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct php_file_globals { + pub pclose_ret: ::std::os::raw::c_int, + pub def_chunk_size: usize, + pub auto_detect_line_endings: bool, + pub default_socket_timeout: zend_long, + pub user_agent: *mut ::std::os::raw::c_char, + pub from_address: *mut ::std::os::raw::c_char, + pub user_stream_current_filename: *const ::std::os::raw::c_char, + pub default_context: *mut php_stream_context, + pub stream_wrappers: *mut HashTable, + pub stream_filters: *mut HashTable, + pub wrapper_errors: *mut HashTable, + pub pclose_wait: ::std::os::raw::c_int, + pub tmp_host_info: hostent, + pub tmp_host_buf: *mut ::std::os::raw::c_char, + pub tmp_host_buf_len: usize, +} +extern "C" { + pub static mut file_globals: php_file_globals; +} extern "C" { pub static mut zend_ce_throwable: *mut zend_class_entry; } @@ -1651,6 +2421,9 @@ extern "C" { ... ) -> *mut zend_object; } +extern "C" { + pub fn zend_throw_exception_object(exception: *mut zval); +} extern "C" { pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); } @@ -1675,3 +2448,191 @@ extern "C" { extern "C" { pub static mut zend_ce_stringable: *mut zend_class_entry; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_header_struct { + pub header: *mut ::std::os::raw::c_char, + pub header_len: usize, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_headers_struct { + pub headers: zend_llist, + pub http_response_code: ::std::os::raw::c_int, + pub send_default_content_type: ::std::os::raw::c_uchar, + pub mimetype: *mut ::std::os::raw::c_char, + pub http_status_line: *mut ::std::os::raw::c_char, +} +pub type sapi_post_entry = _sapi_post_entry; +pub type sapi_module_struct = _sapi_module_struct; +extern "C" { + pub static mut sapi_module: sapi_module_struct; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_request_info { + pub request_method: *const ::std::os::raw::c_char, + pub query_string: *mut ::std::os::raw::c_char, + pub cookie_data: *mut ::std::os::raw::c_char, + pub content_length: zend_long, + pub path_translated: *mut ::std::os::raw::c_char, + pub request_uri: *mut ::std::os::raw::c_char, + pub request_body: *mut _php_stream, + pub content_type: *const ::std::os::raw::c_char, + pub headers_only: bool, + pub no_headers: bool, + pub headers_read: bool, + pub post_entry: *mut sapi_post_entry, + pub content_type_dup: *mut ::std::os::raw::c_char, + pub auth_user: *mut ::std::os::raw::c_char, + pub auth_password: *mut ::std::os::raw::c_char, + pub auth_digest: *mut ::std::os::raw::c_char, + pub argv0: *mut ::std::os::raw::c_char, + pub current_user: *mut ::std::os::raw::c_char, + pub current_user_length: ::std::os::raw::c_int, + pub argc: ::std::os::raw::c_int, + pub argv: *mut *mut ::std::os::raw::c_char, + pub proto_num: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct _sapi_globals_struct { + pub server_context: *mut ::std::os::raw::c_void, + pub request_info: sapi_request_info, + pub sapi_headers: sapi_headers_struct, + pub read_post_bytes: i64, + pub post_read: ::std::os::raw::c_uchar, + pub headers_sent: ::std::os::raw::c_uchar, + pub global_stat: zend_stat_t, + pub default_mimetype: *mut ::std::os::raw::c_char, + pub default_charset: *mut ::std::os::raw::c_char, + pub rfc1867_uploaded_files: *mut HashTable, + pub post_max_size: zend_long, + pub options: ::std::os::raw::c_int, + pub sapi_started: bool, + pub global_request_time: f64, + pub known_post_content_types: HashTable, + pub callback_func: zval, + pub fci_cache: zend_fcall_info_cache, +} +pub type sapi_globals_struct = _sapi_globals_struct; +extern "C" { + pub static mut sapi_globals: sapi_globals_struct; +} +pub const sapi_header_op_enum_SAPI_HEADER_REPLACE: sapi_header_op_enum = 0; +pub const sapi_header_op_enum_SAPI_HEADER_ADD: sapi_header_op_enum = 1; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE: sapi_header_op_enum = 2; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE_ALL: sapi_header_op_enum = 3; +pub const sapi_header_op_enum_SAPI_HEADER_SET_STATUS: sapi_header_op_enum = 4; +pub type sapi_header_op_enum = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _sapi_module_struct { + pub name: *mut ::std::os::raw::c_char, + pub pretty_name: *mut ::std::os::raw::c_char, + pub startup: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub shutdown: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub activate: ::std::option::Option ::std::os::raw::c_int>, + pub deactivate: ::std::option::Option ::std::os::raw::c_int>, + pub ub_write: ::std::option::Option< + unsafe extern "C" fn(str_: *const ::std::os::raw::c_char, str_length: usize) -> usize, + >, + pub flush: + ::std::option::Option, + pub get_stat: ::std::option::Option *mut zend_stat_t>, + pub getenv: ::std::option::Option< + unsafe extern "C" fn( + name: *const ::std::os::raw::c_char, + name_len: usize, + ) -> *mut ::std::os::raw::c_char, + >, + pub sapi_error: ::std::option::Option< + unsafe extern "C" fn( + type_: ::std::os::raw::c_int, + error_msg: *const ::std::os::raw::c_char, + ... + ), + >, + pub header_handler: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + op: sapi_header_op_enum, + sapi_headers: *mut sapi_headers_struct, + ) -> ::std::os::raw::c_int, + >, + pub send_headers: ::std::option::Option< + unsafe extern "C" fn(sapi_headers: *mut sapi_headers_struct) -> ::std::os::raw::c_int, + >, + pub send_header: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + server_context: *mut ::std::os::raw::c_void, + ), + >, + pub read_post: ::std::option::Option< + unsafe extern "C" fn(buffer: *mut ::std::os::raw::c_char, count_bytes: usize) -> usize, + >, + pub read_cookies: ::std::option::Option *mut ::std::os::raw::c_char>, + pub register_server_variables: + ::std::option::Option, + pub log_message: ::std::option::Option< + unsafe extern "C" fn( + message: *const ::std::os::raw::c_char, + syslog_type_int: ::std::os::raw::c_int, + ), + >, + pub get_request_time: + ::std::option::Option zend_result>, + pub terminate_process: ::std::option::Option, + pub php_ini_path_override: *mut ::std::os::raw::c_char, + pub default_post_reader: ::std::option::Option, + pub treat_data: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + str_: *mut ::std::os::raw::c_char, + destArray: *mut zval, + ), + >, + pub executable_location: *mut ::std::os::raw::c_char, + pub php_ini_ignore: ::std::os::raw::c_int, + pub php_ini_ignore_cwd: ::std::os::raw::c_int, + pub get_fd: ::std::option::Option< + unsafe extern "C" fn(fd: *mut ::std::os::raw::c_int) -> ::std::os::raw::c_int, + >, + pub force_http_10: ::std::option::Option ::std::os::raw::c_int>, + pub get_target_uid: + ::std::option::Option ::std::os::raw::c_int>, + pub get_target_gid: + ::std::option::Option ::std::os::raw::c_int>, + pub input_filter: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + var: *const ::std::os::raw::c_char, + val: *mut *mut ::std::os::raw::c_char, + val_len: usize, + new_val_len: *mut usize, + ) -> ::std::os::raw::c_uint, + >, + pub ini_defaults: + ::std::option::Option, + pub phpinfo_as_text: ::std::os::raw::c_int, + pub ini_entries: *const ::std::os::raw::c_char, + pub additional_functions: *const zend_function_entry, + pub input_filter_init: ::std::option::Option ::std::os::raw::c_uint>, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _sapi_post_entry { + pub content_type: *mut ::std::os::raw::c_char, + pub content_type_len: u32, + pub post_reader: ::std::option::Option, + pub post_handler: ::std::option::Option< + unsafe extern "C" fn( + content_type_dup: *mut ::std::os::raw::c_char, + arg: *mut ::std::os::raw::c_void, + ), + >, +} diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 2ca6c5f778..db614111d3 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -24,12 +24,14 @@ - [Class Object](./types/class_object.md) - [Closure](./types/closure.md) - [Functions & methods](./types/functions.md) + - [Async futures](./macros/async_impl.md) - [Macros](./macros/index.md) - [Module](./macros/module.md) - [Module Startup Function](./macros/module_startup.md) - [Function](./macros/function.md) - [Classes](./macros/classes.md) - [`impl`s](./macros/impl.md) + - [async `impl`s](./macros/async_impl.md) - [Constants](./macros/constant.md) - [`ZvalConvert`](./macros/zval_convert.md) - [Exceptions](./exceptions.md) diff --git a/guide/src/macros/async_impl.md b/guide/src/macros/async_impl.md new file mode 100644 index 0000000000..979c58bdee --- /dev/null +++ b/guide/src/macros/async_impl.md @@ -0,0 +1,130 @@ +# `#[php_async_impl]` + +Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Traits annotated with `#[php_async_impl]` can freely expose any async function, using `await` and any async Rust library. + +Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). + +Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. + +## Async example + +In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. + +```rust,ignore +use ext_php_rs::prelude::*; +use php_tokio::{php_async_impl, EventLoop}; + +#[php_class] +struct Client {} + +#[php_async_impl] +impl Client { + pub fn init() -> PhpResult { + EventLoop::init() + } + pub fn wakeup() -> PhpResult<()> { + EventLoop::wakeup() + } + pub async fn get(url: &str) -> anyhow::Result { + Ok(reqwest::get(url).await?.text().await?) + } +} + +pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { + EventLoop::shutdown(); + 0 +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module.request_shutdown_function(request_shutdown) +} +``` + +Here's the async PHP code we use to interact with the Rust class we just exposed. + +The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. + +See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). + +```php + \Client::wakeup()); + } + + public static function reference(): void + { + EventLoop::reference(self::$id); + } + public static function unreference(): void + { + EventLoop::unreference(self::$id); + } + + public static function __callStatic(string $name, array $args): mixed + { + return \Client::$name(...$args); + } +} + + +Client::init(); + +function test(int $delay): void +{ + $url = "https://httpbin.org/delay/$delay"; + $t = time(); + echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; + Client::get($url); + $t = time() - $t; + echo "Got response from $url after ~".$t." seconds!".PHP_EOL; +}; + +$futures = []; +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); + +await($futures); +``` + +Result: + +``` +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +``` + +[`php_function`]: ./function.md diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 7214ab9394..db12ecaf3d 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -8,6 +8,8 @@ implementations cannot be exported to PHP. If you do not want a function exported to PHP, you should place it in a separate `impl` block. +If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](./async_impl.md) for more info. + ## Methods Methods basically follow the same rules as functions, so read about the @@ -162,4 +164,4 @@ var_dump(Human::get_max_age()); // int(100) var_dump(Human::MAX_AGE); // int(100) ``` -[`php_function`]: ./function.md +[`php_async_impl`]: ./async_impl.md diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md new file mode 100644 index 0000000000..fec8072e3f --- /dev/null +++ b/guide/src/types/iterable.md @@ -0,0 +1,55 @@ +# `Iterable` + +`Iterable`s are represented either by an `array` or `Traversable` type. + +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +|---------------|----------------|-----------------| ---------------- |----------------------------------| +| Yes | No | No | No | `ZendHashTable` or `ZendIterator` | + +Converting from a zval to a `Iterable` is valid when the value is either an array or an object +that implements the `Traversable` interface. This means that any value that can be used in a +`foreach` loop can be converted into a `Iterable`. + +## Rust example + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types::Iterable; +#[php_function] +pub fn test_iterable(mut iterable: Iterable) { + for (k, v) in iterable.iter().expect("cannot rewind iterator") { + println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); + } +} +# fn main() {} +``` + +## PHP example + +```php + 'world'; + yield 'rust' => 'php'; +}; + +$array = [ + 'hello' => 'world', + 'rust' => 'php', +]; + +test_iterable($generator()); +test_iterable($array); +``` + +Output: + +```text +k: hello v: world +k: rust v: php +k: hello v: world +k: rust v: php +``` diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md new file mode 100644 index 0000000000..4c53e6727d --- /dev/null +++ b/guide/src/types/iterator.md @@ -0,0 +1,52 @@ +# `ZendIterator` + +`ZendIterator`s are represented by the `Traversable` type. + +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +|---------------| -------------- |-----------------| ---------------- | ------------------ | +| No | Yes | No | No | `ZendIterator` | + +Converting from a zval to a `ZendIterator` is valid when there is an associated iterator to +the variable. This means that any value, at the exception of an `array`, that can be used in +a `foreach` loop can be converted into a `ZendIterator`. As an example, a `Generator` can be +used but also a the result of a `query` call with `PDO`. + +If you want a more universal `iterable` type that also supports arrays, see [Iterable](./iterable.md). + +## Rust example + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types::ZendIterator; +#[php_function] +pub fn test_iterator(iterator: &mut ZendIterator) { + for (k, v) in iterator.iter().expect("cannot rewind iterator") { + // Note that the key can be anything, even an object + // when iterating over Traversables! + println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); + } +} +# fn main() {} +``` + +## PHP example + +```php + 'world'; + yield 'rust' => 'php'; +}; + +test_iterator($generator()); +``` + +Output: + +```text +k: hello v: world +k: rust v: php +``` diff --git a/src/builders/class.rs b/src/builders/class.rs index 91a6a010fc..28e8463841 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -161,10 +161,10 @@ impl ClassBuilder { /// Panics if the class name associated with `T` is not the same as the /// class name specified when creating the builder. pub fn object_override(mut self) -> Self { - extern "C" fn create_object(_: *mut ClassEntry) -> *mut ZendObject { + extern "C" fn create_object(ce: *mut ClassEntry) -> *mut ZendObject { // SAFETY: After calling this function, PHP will always call the constructor // defined below, which assumes that the object is uninitialized. - let obj = unsafe { ZendClassObject::::new_uninit() }; + let obj = unsafe { ZendClassObject::::new_uninit(ce.as_ref()) }; obj.into_raw().get_mut_zend_obj() } diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 065b27549a..17cfbfde36 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -172,6 +172,7 @@ impl ToStub for DataType { DataType::Reference => "reference", DataType::Callable => "callable", DataType::Bool => "bool", + DataType::Iterable => "iterable", _ => "mixed", } ) diff --git a/src/embed/embed.c b/src/embed/embed.c index 6aaa30c986..ae7d8bc625 100644 --- a/src/embed/embed.c +++ b/src/embed/embed.c @@ -2,10 +2,14 @@ // We actually use the PHP embed API to run PHP code in test // At some point we might want to use our own SAPI to do that -void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx) { +void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx) { + void *result = NULL; + PHP_EMBED_START_BLOCK(argc, argv) - callback(ctx); + result = callback(ctx); PHP_EMBED_END_BLOCK() -} \ No newline at end of file + + return result; +} diff --git a/src/embed/embed.h b/src/embed/embed.h index 910dcd987e..beabffbb3a 100644 --- a/src/embed/embed.h +++ b/src/embed/embed.h @@ -1,4 +1,4 @@ #include "zend.h" #include "sapi/embed/php_embed.h" -void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx); +void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx); diff --git a/src/embed/ffi.rs b/src/embed/ffi.rs index 74124a9a9b..b52ce6a92a 100644 --- a/src/embed/ffi.rs +++ b/src/embed/ffi.rs @@ -10,7 +10,7 @@ extern "C" { pub fn ext_php_rs_embed_callback( argc: c_int, argv: *mut *mut c_char, - func: unsafe extern "C" fn(*const c_void), + func: unsafe extern "C" fn(*const c_void) -> *const c_void, ctx: *const c_void, - ); + ) -> *mut c_void; } diff --git a/src/embed/mod.rs b/src/embed/mod.rs index 93e973b00c..9e9f2a8587 100644 --- a/src/embed/mod.rs +++ b/src/embed/mod.rs @@ -1,8 +1,9 @@ //! Provides implementations for running php code from rust. //! It only works on linux for now and you should have `php-embed` installed //! -//! This crate was only test with PHP 8.2 please report any issue with other version -//! You should only use this crate for test purpose, it's not production ready +//! This crate was only test with PHP 8.2 please report any issue with other +//! version You should only use this crate for test purpose, it's not production +//! ready mod ffi; @@ -13,9 +14,10 @@ use crate::ffi::{ zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS, }; use crate::types::{ZendObject, Zval}; -use crate::zend::ExecutorGlobals; +use crate::zend::{panic_wrapper, try_catch, ExecutorGlobals}; use parking_lot::{const_rwlock, RwLock}; use std::ffi::{c_char, c_void, CString, NulError}; +use std::panic::{resume_unwind, RefUnwindSafe}; use std::path::Path; use std::ptr::null_mut; @@ -28,6 +30,13 @@ pub enum EmbedError { ExecuteScriptError, InvalidEvalString(NulError), InvalidPath, + CatchError, +} + +impl EmbedError { + pub fn is_bailout(&self) -> bool { + matches!(self, EmbedError::CatchError) + } } static RUN_FN_LOCK: RwLock<()> = const_rwlock(()); @@ -35,13 +44,14 @@ static RUN_FN_LOCK: RwLock<()> = const_rwlock(()); impl Embed { /// Run a php script from a file /// - /// This function will only work correctly when used inside the `Embed::run` function - /// otherwise behavior is unexpected + /// This function will only work correctly when used inside the `Embed::run` + /// function otherwise behavior is unexpected /// /// # Returns /// /// * `Ok(())` - The script was executed successfully - /// * `Err(EmbedError)` - An error occured during the execution of the script + /// * `Err(EmbedError)` - An error occured during the execution of the + /// script /// /// # Example /// @@ -78,19 +88,27 @@ impl Embed { zend_stream_init_filename(&mut file_handle, path.as_ptr()); } - if unsafe { php_execute_script(&mut file_handle) } { - Ok(()) - } else { - Err(EmbedError::ExecuteScriptError) + let exec_result = try_catch(|| unsafe { php_execute_script(&mut file_handle) }); + + match exec_result { + Err(_) => Err(EmbedError::CatchError), + Ok(true) => Ok(()), + Ok(false) => Err(EmbedError::ExecuteScriptError), } } /// Start and run embed sapi engine /// - /// This function will allow to run php code from rust, the same PHP context is keep between calls - /// inside the function passed to this method. - /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access - /// variables defined in previous calls + /// This function will allow to run php code from rust, the same PHP context + /// is keep between calls inside the function passed to this method. + /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` + /// will be able to access variables defined in previous calls + /// + /// # Returns + /// + /// * R - The result of the function passed to this method + /// + /// R must implement [`Default`] so it can be returned in case of a bailout /// /// # Example /// @@ -104,30 +122,44 @@ impl Embed { /// assert_eq!(foo.unwrap().string().unwrap(), "foo"); /// }); /// ``` - pub fn run(func: F) { + pub fn run R + RefUnwindSafe>(func: F) -> R + where + R: Default, + { // @TODO handle php thread safe // // This is to prevent multiple threads from running php at the same time - // At some point we should detect if php is compiled with thread safety and avoid doing that in this case + // At some point we should detect if php is compiled with thread safety and + // avoid doing that in this case let _guard = RUN_FN_LOCK.write(); - unsafe extern "C" fn wrapper(ctx: *const c_void) { - (*(ctx as *const F))(); - } - - unsafe { + let panic = unsafe { ext_php_rs_embed_callback( 0, null_mut(), - wrapper::, + panic_wrapper::, &func as *const F as *const c_void, - ); + ) + }; + + // This can happen if there is a bailout + if panic.is_null() { + return R::default(); + } + + match unsafe { *Box::from_raw(panic as *mut std::thread::Result) } { + Ok(r) => r, + Err(err) => { + // we resume the panic here so it can be catched correctly by the test framework + resume_unwind(err); + } } } /// Evaluate a php code /// - /// This function will only work correctly when used inside the `Embed::run` function + /// This function will only work correctly when used inside the `Embed::run` + /// function /// /// # Returns /// @@ -152,21 +184,18 @@ impl Embed { let mut result = Zval::new(); - // this eval is very limited as it only allow simple code, it's the same eval used by php -r - let exec_result = unsafe { + let exec_result = try_catch(|| unsafe { zend_eval_string( cstr.as_ptr() as *const c_char, &mut result, b"run\0".as_ptr() as *const _, ) - }; - - let exception = ExecutorGlobals::take_exception(); + }); - if exec_result != ZEND_RESULT_CODE_SUCCESS { - Err(EmbedError::ExecuteError(exception)) - } else { - Ok(result) + match exec_result { + Err(_) => Err(EmbedError::CatchError), + Ok(ZEND_RESULT_CODE_SUCCESS) => Ok(result), + Ok(_) => Err(EmbedError::ExecuteError(ExecutorGlobals::take_exception())), } } } @@ -218,4 +247,31 @@ mod tests { assert!(!result.is_ok()); }); } + + #[test] + #[should_panic] + fn test_panic() { + Embed::run(|| { + panic!("test panic"); + }); + } + + #[test] + fn test_return() { + let foo = Embed::run(|| { + return "foo"; + }); + + assert_eq!(foo, "foo"); + } + + #[test] + fn test_eval_bailout() { + Embed::run(|| { + let result = Embed::eval("trigger_error(\"Fatal error\", E_USER_ERROR);"); + + assert!(result.is_err()); + assert!(result.unwrap_err().is_bailout()); + }); + } } diff --git a/src/error.rs b/src/error.rs index 5c4c9ceeda..071e997584 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,6 +65,10 @@ pub enum Error { IntegerOverflow, /// An exception was thrown in a function. Exception(ZBox), + /// A failure occurred while registering the stream wrapper + StreamWrapperRegistrationFailure, + /// A failure occurred while unregistering the stream wrapper + StreamWrapperUnregistrationFailure, } impl Display for Error { @@ -99,6 +103,15 @@ impl Display for Error { write!(f, "Converting integer arguments resulted in an overflow.") } Error::Exception(e) => write!(f, "Exception was thrown: {e:?}"), + Error::StreamWrapperRegistrationFailure => { + write!(f, "A failure occurred while registering the stream wrapper") + } + Error::StreamWrapperUnregistrationFailure => { + write!( + f, + "A failure occurred while unregistering the stream wrapper" + ) + } } } } diff --git a/src/exception.rs b/src/exception.rs index ae694e0cc4..aee0d9099f 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -6,7 +6,9 @@ use crate::{ class::RegisteredClass, error::{Error, Result}, ffi::zend_throw_exception_ex, + ffi::zend_throw_exception_object, flags::ClassFlags, + types::Zval, zend::{ce, ClassEntry}, }; @@ -25,6 +27,7 @@ pub struct PhpException { message: String, code: i32, ex: &'static ClassEntry, + object: Option, } impl PhpException { @@ -36,7 +39,12 @@ impl PhpException { /// * `code` - Integer code to go inside the exception. /// * `ex` - Exception type to throw. pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex } + Self { + message, + code, + ex, + object: None, + } } /// Creates a new default exception instance, using the default PHP @@ -59,10 +67,25 @@ impl PhpException { Self::new(message, 0, T::get_metadata().ce()) } + /// Set the Zval object for the exception. + /// + /// Exceptions can be based of instantiated Zval objects when you are + /// throwing a custom exception with stateful properties. + /// + /// # Parameters + /// + /// * `object` - The Zval object. + pub fn set_object(&mut self, object: Option) { + self.object = object; + } + /// Throws the exception, returning nothing inside a result if successful /// and an error otherwise. pub fn throw(self) -> Result<()> { - throw_with_code(self.ex, self.code, &self.message) + match self.object { + Some(object) => throw_object(object), + None => throw_with_code(self.ex, self.code, &self.message), + } } } @@ -146,3 +169,44 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> }; Ok(()) } + +/// Throws an exception object. +/// +/// Returns a result containing nothing if the exception was successfully +/// thrown. +/// +/// # Parameters +/// +/// * `object` - The zval of type object +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::prelude::*; +/// use ext_php_rs::exception::throw_object; +/// use crate::ext_php_rs::convert::IntoZval; +/// +/// #[php_class] +/// #[extends(ext_php_rs::zend::ce::exception())] +/// pub struct JsException { +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// message: String, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// code: i32, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// file: String, +/// } +/// +/// #[php_module] +/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +/// module +/// } +/// +/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() }; +/// throw_object( error.into_zval(true).unwrap() ); +/// ``` +pub fn throw_object(zval: Zval) -> Result<()> { + let mut zv = core::mem::ManuallyDrop::new(zval); + unsafe { zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) }; + Ok(()) +} diff --git a/src/ffi.rs b/src/ffi.rs index 92614c475e..28ba625940 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,6 +26,16 @@ extern "C" { pub fn ext_php_rs_zend_object_alloc(obj_size: usize, ce: *mut zend_class_entry) -> *mut c_void; pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; + pub fn ext_php_rs_process_globals() -> *mut php_core_globals; + pub fn ext_php_rs_sapi_globals() -> *mut sapi_globals_struct; + pub fn ext_php_rs_file_globals() -> *mut php_file_globals; + pub fn ext_php_rs_sapi_module() -> *mut sapi_module_struct; + pub fn ext_php_rs_zend_try_catch( + func: unsafe extern "C" fn(*const c_void) -> *const c_void, + ctx: *const c_void, + result: *mut *mut c_void, + ) -> bool; + pub fn ext_php_rs_zend_bailout() -> !; } include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/src/flags.rs b/src/flags.rs index 60897a0654..da3a6844c8 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,14 +8,14 @@ use crate::ffi::{ CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR, E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE, E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, - E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, - IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, - IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, - PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, - ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, - ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, - ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, - ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, + E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, + IS_ITERABLE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, + IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, + PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, + ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, + ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, + ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, + ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, @@ -49,6 +49,7 @@ bitflags! { const ConstantExpression = IS_CONSTANT_AST; const Void = IS_VOID; const Ptr = IS_PTR; + const Iterable = IS_ITERABLE; const InternedStringEx = Self::String.bits(); const StringEx = Self::String.bits() | Self::RefCounted.bits(); @@ -237,6 +238,7 @@ pub enum DataType { Double, String, Array, + Iterable, Object(Option<&'static str>), Resource, Reference, @@ -246,6 +248,7 @@ pub enum DataType { Mixed, Bool, Ptr, + Indirect, } impl Default for DataType { @@ -269,12 +272,14 @@ impl DataType { DataType::Object(_) => IS_OBJECT, DataType::Resource => IS_RESOURCE, DataType::Reference => IS_RESOURCE, + DataType::Indirect => IS_INDIRECT, DataType::Callable => IS_CALLABLE, DataType::ConstantExpression => IS_CONSTANT_AST, DataType::Void => IS_VOID, DataType::Mixed => IS_MIXED, DataType::Bool => _IS_BOOL, DataType::Ptr => IS_PTR, + DataType::Iterable => IS_ITERABLE, } } } @@ -337,6 +342,7 @@ impl From for DataType { contains!(IS_VOID, Void); contains!(IS_PTR, Ptr); + contains!(IS_INDIRECT, Indirect); contains!(IS_CALLABLE, Callable); contains!(IS_CONSTANT_AST, ConstantExpression); contains!(IS_REFERENCE, Reference); @@ -379,6 +385,8 @@ impl Display for DataType { DataType::Bool => write!(f, "Bool"), DataType::Mixed => write!(f, "Mixed"), DataType::Ptr => write!(f, "Pointer"), + DataType::Indirect => write!(f, "Indirect"), + DataType::Iterable => write!(f, "Iterable"), } } } @@ -387,8 +395,8 @@ impl Display for DataType { mod tests { use super::DataType; use crate::ffi::{ - IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, - IS_FALSE, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR, + IS_ARRAY, IS_ARRAY_EX, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, + IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR, IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, IS_TRUE, IS_UNDEF, IS_VOID, }; @@ -414,7 +422,7 @@ mod tests { test!(IS_RESOURCE, Resource); test!(IS_REFERENCE, Reference); test!(IS_CONSTANT_AST, ConstantExpression); - test!(IS_CALLABLE, Callable); + test!(IS_INDIRECT, Indirect); test!(IS_VOID, Void); test!(IS_PTR, Ptr); diff --git a/src/types/array.rs b/src/types/array.rs index c40b8f3993..19768f3165 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -5,7 +5,7 @@ use std::{ collections::HashMap, convert::{TryFrom, TryInto}, ffi::CString, - fmt::Debug, + fmt::{Debug, Display}, iter::FromIterator, u64, }; @@ -463,7 +463,7 @@ impl ZendHashTable { /// assert!(!ht.has_numerical_keys()); /// ``` pub fn has_numerical_keys(&self) -> bool { - !self.iter().any(|(_, k, _)| k.is_some()) + !self.into_iter().any(|(k, _)| !k.is_long()) } /// Checks if the hashtable has numerical, sequential keys. @@ -490,13 +490,13 @@ impl ZendHashTable { /// ``` pub fn has_sequential_keys(&self) -> bool { !self - .iter() + .into_iter() .enumerate() - .any(|(i, (k, strk, _))| i as u64 != k || strk.is_some()) + .any(|(i, (k, _))| ArrayKey::Long(i as i64) != k) } - /// Returns an iterator over the key(s) and value contained inside the - /// hashtable. + /// Returns an iterator over the values contained inside the hashtable, as + /// if it was a set or list. /// /// # Example /// @@ -505,20 +505,16 @@ impl ZendHashTable { /// /// let mut ht = ZendHashTable::new(); /// - /// for (idx, key, val) in ht.iter() { - /// // ^ Index if inserted at an index. - /// // ^ Optional string key, if inserted like a hashtable. - /// // ^ Inserted value. - /// - /// dbg!(idx, key, val); + /// for val in ht.values() { + /// dbg!(val); /// } #[inline] - pub fn iter(&self) -> Iter { - Iter::new(self) + pub fn values(&self) -> Values { + Values::new(self) } - /// Returns an iterator over the values contained inside the hashtable, as - /// if it was a set or list. + /// Returns an iterator over the key(s) and value contained inside the + /// hashtable. /// /// # Example /// @@ -527,12 +523,16 @@ impl ZendHashTable { /// /// let mut ht = ZendHashTable::new(); /// - /// for val in ht.values() { - /// dbg!(val); + /// for (key, val) in ht.iter() { + /// // ^ Index if inserted at an index. + /// // ^ Optional string key, if inserted like a hashtable. + /// // ^ Inserted value. + /// + /// dbg!(key, val); /// } #[inline] - pub fn values(&self) -> Values { - Values::new(self) + pub fn iter(&self) -> Iter { + self.into_iter() } } @@ -546,10 +546,7 @@ unsafe impl ZBoxable for ZendHashTable { impl Debug for ZendHashTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_map() - .entries( - self.iter() - .map(|(k, k2, v)| (k2.unwrap_or_else(|| k.to_string()), v)), - ) + .entries(self.into_iter().map(|(k, v)| (k.to_string(), v))) .finish() } } @@ -574,10 +571,54 @@ impl ToOwned for ZendHashTable { /// Immutable iterator upon a reference to a hashtable. pub struct Iter<'a> { ht: &'a ZendHashTable, - current_num: u64, + current_num: i64, pos: HashPosition, } +#[derive(Debug, PartialEq)] +pub enum ArrayKey { + Long(i64), + String(String), +} + +/// Represent the key of a PHP array, which can be either a long or a string. +impl ArrayKey { + /// Check if the key is an integer. + /// + /// # Returns + /// + /// Returns true if the key is an integer, false otherwise. + pub fn is_long(&self) -> bool { + match self { + ArrayKey::Long(_) => true, + ArrayKey::String(_) => false, + } + } +} + +impl Display for ArrayKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArrayKey::Long(key) => write!(f, "{}", key), + ArrayKey::String(key) => write!(f, "{}", key), + } + } +} + +impl<'a> FromZval<'a> for ArrayKey { + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &'a Zval) -> Option { + if let Some(key) = zval.long() { + return Some(ArrayKey::Long(key)); + } + if let Some(key) = zval.string() { + return Some(ArrayKey::String(key)); + } + None + } +} + impl<'a> Iter<'a> { /// Creates a new iterator over a hashtable. /// @@ -593,10 +634,57 @@ impl<'a> Iter<'a> { } } +impl<'a> IntoIterator for &'a ZendHashTable { + type Item = (ArrayKey, &'a Zval); + type IntoIter = Iter<'a>; + + /// Returns an iterator over the key(s) and value contained inside the + /// hashtable. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// for (key, val) in ht.iter() { + /// // ^ Index if inserted at an index. + /// // ^ Optional string key, if inserted like a hashtable. + /// // ^ Inserted value. + /// + /// dbg!(key, val); + /// } + #[inline] + fn into_iter(self) -> Self::IntoIter { + Iter::new(self) + } +} + impl<'a> Iterator for Iter<'a> { - type Item = (u64, Option, &'a Zval); + type Item = (ArrayKey, &'a Zval); fn next(&mut self) -> Option { + self.next_zval() + .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v)) + } + + fn count(self) -> usize + where + Self: Sized, + { + self.ht.len() + } +} + +impl<'a> ExactSizeIterator for Iter<'a> { + fn len(&self) -> usize { + self.ht.len() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option { let key_type = unsafe { zend_hash_get_current_key_type_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, @@ -609,6 +697,7 @@ impl<'a> Iterator for Iter<'a> { } let key = Zval::new(); + unsafe { zend_hash_get_current_key_zval_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, @@ -622,38 +711,26 @@ impl<'a> Iterator for Iter<'a> { &mut self.pos as *mut HashPosition, ) }; - let r: (u64, Option, &Zval) = match key.is_long() { - true => (key.long().unwrap_or(0) as u64, None, value), - false => (self.current_num, key.try_into().ok(), value), + + let key = match ArrayKey::from_zval(&key) { + Some(key) => key, + None => ArrayKey::Long(self.current_num), }; unsafe { - zend_hash_move_forward_ex( + zend_hash_move_backwards_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, &mut self.pos as *mut HashPosition, ) }; - self.current_num += 1; + self.current_num -= 1; - Some(r) - } - - fn count(self) -> usize - where - Self: Sized, - { - self.ht.len() + Some((key, value)) } } -impl<'a> ExactSizeIterator for Iter<'a> { - fn len(&self) -> usize { - self.ht.len() - } -} - -impl<'a> DoubleEndedIterator for Iter<'a> { - fn next_back(&mut self) -> Option { +impl<'a, 'b> Iter<'a> { + pub fn next_zval(&'b mut self) -> Option<(Zval, &'a Zval)> { let key_type = unsafe { zend_hash_get_current_key_type_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, @@ -665,7 +742,8 @@ impl<'a> DoubleEndedIterator for Iter<'a> { return None; } - let key = Zval::new(); + let mut key = Zval::new(); + unsafe { zend_hash_get_current_key_zval_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, @@ -679,20 +757,20 @@ impl<'a> DoubleEndedIterator for Iter<'a> { &mut self.pos as *mut HashPosition, ) }; - let r: (u64, Option, &Zval) = match key.is_long() { - true => (key.long().unwrap_or(0) as u64, None, value), - false => (self.current_num, key.try_into().ok(), value), - }; + + if !key.is_long() && !key.is_string() { + key.set_long(self.current_num) + } unsafe { - zend_hash_move_backwards_ex( + zend_hash_move_forward_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, &mut self.pos as *mut HashPosition, ) }; - self.current_num -= 1; + self.current_num += 1; - Some(r) + Some((key, value)) } } @@ -715,7 +793,7 @@ impl<'a> Iterator for Values<'a> { type Item = &'a Zval; fn next(&mut self) -> Option { - self.0.next().map(|(_, _, zval)| zval) + self.0.next().map(|(_, zval)| zval) } fn count(self) -> usize @@ -734,7 +812,7 @@ impl<'a> ExactSizeIterator for Values<'a> { impl<'a> DoubleEndedIterator for Values<'a> { fn next_back(&mut self) -> Option { - self.0.next_back().map(|(_, _, zval)| zval) + self.0.next_back().map(|(_, zval)| zval) } } @@ -780,9 +858,9 @@ where fn try_from(value: &'a ZendHashTable) -> Result { let mut hm = HashMap::with_capacity(value.len()); - for (idx, key, val) in value.iter() { + for (key, val) in value { hm.insert( - key.unwrap_or_else(|| idx.to_string()), + key.to_string(), V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?, ); } @@ -849,7 +927,7 @@ where fn try_from(value: &'a ZendHashTable) -> Result { let mut vec = Vec::with_capacity(value.len()); - for (_, _, val) in value.iter() { + for (_, val) in value { vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?); } diff --git a/src/types/class_object.rs b/src/types/class_object.rs index e9b116529b..c9ac51ed62 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -5,6 +5,7 @@ use std::{ fmt::Debug, mem, ops::{Deref, DerefMut}, + os::raw::c_char, ptr::{self, NonNull}, }; @@ -19,6 +20,7 @@ use crate::{ }, flags::DataType, types::{ZendObject, Zval}, + zend::ClassEntry, }; /// Representation of a Zend class object in memory. @@ -43,7 +45,7 @@ impl ZendClassObject { /// Panics if memory was unable to be allocated for the new object. pub fn new(val: T) -> ZBox { // SAFETY: We are providing a value to initialize the object with. - unsafe { Self::internal_new(Some(val)) } + unsafe { Self::internal_new(Some(val), None) } } /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized @@ -67,8 +69,8 @@ impl ZendClassObject { /// # Panics /// /// Panics if memory was unable to be allocated for the new object. - pub unsafe fn new_uninit() -> ZBox { - Self::internal_new(None) + pub unsafe fn new_uninit(ce: Option<&'static ClassEntry>) -> ZBox { + Self::internal_new(None, ce) } /// Creates a new [`ZendObject`] of type `T`, storing the given (and @@ -102,10 +104,10 @@ impl ZendClassObject { /// # Panics /// /// Panics if memory was unable to be allocated for the new object. - unsafe fn internal_new(val: Option) -> ZBox { + unsafe fn internal_new(val: Option, ce: Option<&'static ClassEntry>) -> ZBox { let size = mem::size_of::>(); let meta = T::get_metadata(); - let ce = meta.ce() as *const _ as *mut _; + let ce = ce.unwrap_or_else(|| meta.ce()) as *const _ as *mut _; let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject; let obj = obj .as_mut() @@ -161,13 +163,13 @@ impl ZendClassObject { } fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> { - let std = std as *const zend_object as *const i8; + let std = std as *const zend_object as *const c_char; let ptr = unsafe { let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self; (ptr as *mut Self).as_mut()? }; - if ptr.std.is_instance::() { + if ptr.std.instance_of(T::get_metadata().ce()) { Some(ptr) } else { None diff --git a/src/types/iterable.rs b/src/types/iterable.rs new file mode 100644 index 0000000000..1440a679d9 --- /dev/null +++ b/src/types/iterable.rs @@ -0,0 +1,66 @@ +use super::array::Iter as ZendHashTableIter; +use super::iterator::Iter as ZendIteratorIter; +use crate::convert::FromZval; +use crate::flags::DataType; +use crate::types::{ZendHashTable, ZendIterator, Zval}; + +/// This type represents a PHP iterable, which can be either an array or an +/// object implementing the Traversable interface. +#[derive(Debug)] +pub enum Iterable<'a> { + Array(&'a ZendHashTable), + Traversable(&'a mut ZendIterator), +} + +impl<'a> Iterable<'a> { + /// Creates a new rust iterator from a PHP iterable. + /// May return None if a Traversable cannot be rewound. + pub fn iter(&mut self) -> Option { + match self { + Iterable::Array(array) => Some(Iter::Array(array.iter())), + Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)), + } + } +} + +impl<'a> IntoIterator for &'a mut Iterable<'a> { + type Item = (Zval, &'a Zval); + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter().expect("Could not rewind iterator!") + } +} + +impl<'a> FromZval<'a> for Iterable<'a> { + const TYPE: DataType = DataType::Iterable; + + fn from_zval(zval: &'a Zval) -> Option { + if let Some(array) = zval.array() { + return Some(Iterable::Array(array)); + } + + if let Some(traversable) = zval.traversable() { + return Some(Iterable::Traversable(traversable)); + } + + None + } +} + +/// Rust iterator over a PHP iterable. +pub enum Iter<'a> { + Array(ZendHashTableIter<'a>), + Traversable(ZendIteratorIter<'a>), +} + +impl<'a> Iterator for Iter<'a> { + type Item = (Zval, &'a Zval); + + fn next(&mut self) -> Option { + match self { + Iter::Array(array) => array.next_zval(), + Iter::Traversable(traversable) => traversable.next(), + } + } +} diff --git a/src/types/iterator.rs b/src/types/iterator.rs new file mode 100644 index 0000000000..b6591ab348 --- /dev/null +++ b/src/types/iterator.rs @@ -0,0 +1,327 @@ +use crate::convert::FromZvalMut; +use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS}; +use crate::flags::DataType; +use crate::types::Zval; +use crate::zend::ExecutorGlobals; +use std::fmt::{Debug, Formatter}; + +/// A PHP Iterator. +/// +/// In PHP, iterators are represented as zend_object_iterator. This allows user +/// to iterate over objects implementing Traversable interface using foreach. +/// +/// Use ZendIterable to iterate over both iterators and arrays. +pub type ZendIterator = zend_object_iterator; + +impl ZendIterator { + /// Creates a new rust iterator from a zend_object_iterator. + /// + /// Returns a iterator over the zend_object_iterator, or None if the + /// iterator cannot be rewound. + pub fn iter(&mut self) -> Option { + self.index = 0; + + if self.rewind() { + return Some(Iter { zi: self }); + } + + None + } + + /// Check if the current position of the iterator is valid. + /// + /// As an example this will call the user defined valid method of the + /// ['\Iterator'] interface. see + pub fn valid(&mut self) -> bool { + if let Some(valid) = unsafe { (*self.funcs).valid } { + let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS }; + + if ExecutorGlobals::has_exception() { + return false; + } + + valid + } else { + true + } + } + + /// Rewind the iterator to the first element. + /// + /// As an example this will call the user defined rewind method of the + /// ['\Iterator'] interface. see + /// + /// # Returns + /// + /// Returns true if the iterator was successfully rewind, false otherwise. + /// (when there is an exception during rewind) + pub fn rewind(&mut self) -> bool { + if let Some(rewind) = unsafe { (*self.funcs).rewind } { + unsafe { + rewind(&mut *self); + } + } + + !ExecutorGlobals::has_exception() + } + + /// Move the iterator forward to the next element. + /// + /// As an example this will call the user defined next method of the + /// ['\Iterator'] interface. see + /// + /// # Returns + /// + /// Returns true if the iterator was successfully move, false otherwise. + /// (when there is an exception during next) + pub fn move_forward(&mut self) -> bool { + if let Some(move_forward) = unsafe { (*self.funcs).move_forward } { + unsafe { + move_forward(&mut *self); + } + } + + !ExecutorGlobals::has_exception() + } + + /// Get the current data of the iterator. + /// + /// # Returns + /// + /// Returns a reference to the current data of the iterator if available + /// , ['None'] otherwise. + pub fn get_current_data<'a>(&mut self) -> Option<&'a Zval> { + let get_current_data = unsafe { (*self.funcs).get_current_data }?; + let value = unsafe { &*get_current_data(&mut *self) }; + + if ExecutorGlobals::has_exception() { + return None; + } + + Some(value) + } + + /// Get the current key of the iterator. + /// + /// # Returns + /// + /// Returns a new ['Zval'] containing the current key of the iterator if + /// available , ['None'] otherwise. + pub fn get_current_key(&mut self) -> Option { + let get_current_key = unsafe { (*self.funcs).get_current_key? }; + let mut key = Zval::new(); + + unsafe { + get_current_key(&mut *self, &mut key); + } + + if ExecutorGlobals::has_exception() { + return None; + } + + Some(key) + } +} + +impl<'a> IntoIterator for &'a mut ZendIterator { + type Item = (Zval, &'a Zval); + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter().expect("Could not rewind iterator!") + } +} + +impl Debug for ZendIterator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ZendIterator").finish() + } +} + +/// Immutable iterator upon a reference to a PHP iterator. +pub struct Iter<'a> { + zi: &'a mut ZendIterator, +} + +impl<'a> Iterator for Iter<'a> { + type Item = (Zval, &'a Zval); + + fn next(&mut self) -> Option { + // Call next when index > 0, so next is really called at the start of each + // iteration, which allow to work better with generator iterator + if self.zi.index > 0 && !self.zi.move_forward() { + return None; + } + + if !self.zi.valid() { + return None; + } + + self.zi.index += 1; + + let real_index = self.zi.index - 1; + + let key = match self.zi.get_current_key() { + None => { + let mut z = Zval::new(); + z.set_long(real_index as i64); + z + } + Some(key) => key, + }; + + self.zi.get_current_data().map(|value| (key, value)) + } +} + +impl<'a> FromZvalMut<'a> for &'a mut ZendIterator { + const TYPE: DataType = DataType::Object(Some("Traversable")); + + fn from_zval_mut(zval: &'a mut Zval) -> Option { + zval.object()?.get_class_entry().get_iterator(zval, false) + } +} + +#[cfg(test)] +#[cfg(feature = "embed")] +mod tests { + use crate::embed::Embed; + + #[test] + fn test_generator() { + Embed::run(|| { + let result = Embed::run_script("src/types/iterator.test.php"); + + assert!(result.is_ok()); + + let generator = Embed::eval("$generator;"); + + assert!(generator.is_ok()); + + let zval = generator.unwrap(); + + assert!(zval.is_traversable()); + + let iterator = zval.traversable().unwrap(); + + assert!(iterator.valid()); + + { + let mut iter = iterator.iter().unwrap(); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(0)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 1); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(1)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 2); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(2)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 3); + + let (key, value) = iter.next().unwrap(); + + assert!(key.is_object()); + assert!(value.is_object()); + + let next = iter.next(); + + assert!(next.is_none()); + } + }); + } + + #[test] + fn test_iterator() { + Embed::run(|| { + let result = Embed::run_script("src/types/iterator.test.php"); + + assert!(result.is_ok()); + + let generator = Embed::eval("$iterator;"); + + assert!(generator.is_ok()); + + let zval = generator.unwrap(); + + assert!(zval.is_traversable()); + + let iterator = zval.traversable().unwrap(); + + assert!(iterator.valid()); + + { + let mut iter = iterator.iter().unwrap(); + + let (key, value) = iter.next().unwrap(); + + assert!(!key.is_long()); + assert_eq!(key.str(), Some("key")); + assert!(value.is_string()); + assert_eq!(value.str(), Some("foo")); + + let (key, value) = iter.next().unwrap(); + + assert!(key.is_long()); + assert_eq!(key.long(), Some(10)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "bar"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(2)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "baz"); + + let (key, value) = iter.next().unwrap(); + + assert!(key.is_object()); + assert!(value.is_object()); + + let next = iter.next(); + + assert!(next.is_none()); + } + + // Test rewind + { + let mut iter = iterator.iter().unwrap(); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.str(), Some("key")); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "foo"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(10)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "bar"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key.long(), Some(2)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "baz"); + + let (key, value) = iter.next().unwrap(); + + assert!(key.is_object()); + assert!(value.is_object()); + + let next = iter.next(); + + assert!(next.is_none()); + } + }); + } +} diff --git a/src/types/iterator.test.php b/src/types/iterator.test.php new file mode 100644 index 0000000000..891f0a9156 --- /dev/null +++ b/src/types/iterator.test.php @@ -0,0 +1,52 @@ + new class {}; +} + +class TestIterator implements \Iterator { + private $count = 0; + + public function current(): mixed + { + return match ($this->count) { + 0 => 'foo', + 1 => 'bar', + 2 => 'baz', + 3 => new class {}, + default => null, + }; + } + + public function next(): void + { + $this->count++; + } + + public function key(): mixed + { + return match ($this->count) { + 0 => 'key', + 1 => 10, + 2 => 2, + 3 => new class {}, + default => null, + }; + } + + public function valid(): bool + { + return $this->count < 4; + } + + public function rewind(): void + { + $this->count = 0; + } +} + +$generator = create_generator(); +$iterator = new TestIterator(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 888e056e78..846c8d1bb4 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,6 +6,8 @@ mod array; mod callable; mod class_object; +mod iterable; +mod iterator; mod long; mod object; mod string; @@ -14,6 +16,8 @@ mod zval; pub use array::ZendHashTable; pub use callable::ZendCallable; pub use class_object::ZendClassObject; +pub use iterable::Iterable; +pub use iterator::ZendIterator; pub use long::ZendLong; pub use object::{PropertyQuery, ZendObject}; pub use string::ZendStr; diff --git a/src/types/object.rs b/src/types/object.rs index ca8e526ebe..68ad535d97 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -1,7 +1,7 @@ //! Represents an object in PHP. Allows for overriding the internal object used //! by classes, allowing users to store Rust data inside a PHP object. -use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; +use std::{convert::TryInto, fmt::Debug, ops::DerefMut, os::raw::c_char}; use crate::{ boxed::{ZBox, ZBoxable}, @@ -133,6 +133,15 @@ impl ZendObject { (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) } + /// Returns whether this object is an instance of \Traversable + /// + /// # Panics + /// + /// Panics if the class entry is invalid. + pub fn is_traversable(&self) -> bool { + self.instance_of(ce::traversable()) + } + #[inline(always)] pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { let mut retval = Zval::new(); @@ -146,7 +155,7 @@ impl ZendObject { unsafe { let res = zend_hash_str_find_ptr_lc( &(*self.ce).function_table, - name.as_ptr() as *const i8, + name.as_ptr() as *const c_char, name.len(), ) as *mut zend_function; if res.is_null() { @@ -317,8 +326,8 @@ impl Debug for ZendObject { ); if let Ok(props) = self.get_properties() { - for (id, key, val) in props.iter() { - dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val); + for (key, val) in props.iter() { + dbg.field(key.to_string().as_str(), val); } } diff --git a/src/types/zval.rs b/src/types/zval.rs index 94b62aa985..f031214f32 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -4,6 +4,8 @@ use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr}; +use crate::types::iterable::Iterable; +use crate::types::ZendIterator; use crate::{ binary::Pack, binary_slice::PackSlice, @@ -12,7 +14,7 @@ use crate::{ error::{Error, Result}, ffi::{ _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, - zend_is_identical, zend_resource, zend_value, zval, zval_ptr_dtor, + zend_is_identical, zend_is_iterable, zend_resource, zend_value, zval, zval_ptr_dtor, }, flags::DataType, flags::ZvalTypeFlags, @@ -213,6 +215,25 @@ impl Zval { .try_call_method(name, params) } + /// Returns the value of the zval if it is an internal indirect reference. + pub fn indirect(&self) -> Option<&Zval> { + if self.is_indirect() { + Some(unsafe { &*(self.value.zv as *mut Zval) }) + } else { + None + } + } + + /// Returns a mutable reference to the zval if it is an internal indirect + /// reference. + pub fn indirect_mut(&self) -> Option<&mut Zval> { + if self.is_indirect() { + Some(unsafe { &mut *(self.value.zv as *mut Zval) }) + } else { + None + } + } + /// Returns the value of the zval if it is a reference. pub fn reference(&self) -> Option<&Zval> { if self.is_reference() { @@ -237,6 +258,25 @@ impl Zval { ZendCallable::new(self).ok() } + /// Returns an iterator over the zval if it is traversable. + pub fn traversable(&self) -> Option<&mut ZendIterator> { + if self.is_traversable() { + self.object()?.get_class_entry().get_iterator(self, false) + } else { + None + } + } + + /// Returns an iterable over the zval if it is an array or traversable. (is + /// iterable) + pub fn iterable(&self) -> Option { + if self.is_iterable() { + Iterable::from_zval(self) + } else { + None + } + } + /// Returns the value of the zval if it is a pointer. /// /// # Safety @@ -329,6 +369,11 @@ impl Zval { self.get_type() == DataType::Reference } + /// Returns true if the zval is a reference, false otherwise. + pub fn is_indirect(&self) -> bool { + self.get_type() == DataType::Indirect + } + /// Returns true if the zval is callable, false otherwise. pub fn is_callable(&self) -> bool { let ptr: *const Self = self; @@ -347,6 +392,21 @@ impl Zval { unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) } } + /// Returns true if the zval is traversable, false otherwise. + pub fn is_traversable(&self) -> bool { + match self.object() { + None => false, + Some(obj) => obj.is_traversable(), + } + } + + /// Returns true if the zval is iterable (array or traversable), false + /// otherwise. + pub fn is_iterable(&self) -> bool { + let ptr: *const Self = self; + unsafe { zend_is_iterable(ptr as *mut Self) } + } + /// Returns true if the zval contains a pointer, false otherwise. pub fn is_ptr(&self) -> bool { self.get_type() == DataType::Ptr @@ -601,6 +661,8 @@ impl Debug for Zval { DataType::ConstantExpression => field!(Option::<()>::None), DataType::Void => field!(Option::<()>::None), DataType::Bool => field!(self.bool()), + DataType::Indirect => field!(self.indirect()), + DataType::Iterable => field!(self.iterable()), // SAFETY: We are not accessing the pointer. DataType::Ptr => field!(unsafe { self.ptr::() }), }; diff --git a/src/wrapper.c b/src/wrapper.c index faf585e417..fcb0c28227 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -39,3 +39,53 @@ zend_executor_globals *ext_php_rs_executor_globals() { return &executor_globals; #endif } + +php_core_globals *ext_php_rs_process_globals() { +#ifdef ZTS +#ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE + return TSRMG_FAST_BULK_STATIC(core_globals_offset, php_core_globals); +#else + return TSRMG_FAST_BULK(core_globals_offset, php_core_globals *); +#endif +#else + return &core_globals; +#endif +} + +sapi_globals_struct *ext_php_rs_sapi_globals() { +#ifdef ZTS +#ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE + return TSRMG_FAST_BULK_STATIC(sapi_globals_offset, sapi_globals_struct); +#else + return TSRMG_FAST_BULK(sapi_globals_offset, sapi_globals_struct *); +#endif +#else + return &sapi_globals; +#endif +} + +php_file_globals *ext_php_rs_file_globals() { +#ifdef ZTS + return TSRMG_FAST_BULK(file_globals_id, php_file_globals *); +#else + return &file_globals; +#endif +} + +sapi_module_struct *ext_php_rs_sapi_module() { + return &sapi_module; +} + +bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result) { + zend_try { + *result = callback(ctx); + } zend_catch { + return true; + } zend_end_try(); + + return false; +} + +void ext_php_rs_zend_bailout() { + zend_bailout(); +} diff --git a/src/wrapper.h b/src/wrapper.h index ed9dea6294..9aef14f151 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -17,10 +17,14 @@ #include "php.h" #include "ext/standard/info.h" +#include "ext/standard/php_var.h" +#include "ext/standard/file.h" #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" +#include "php_variables.h" #include "zend_ini.h" +#include "main/SAPI.h" zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); @@ -30,4 +34,10 @@ void ext_php_rs_set_known_valid_utf8(zend_string *zs); const char *ext_php_rs_php_build_id(); void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); -zend_executor_globals *ext_php_rs_executor_globals(); \ No newline at end of file +zend_executor_globals *ext_php_rs_executor_globals(); +php_core_globals *ext_php_rs_process_globals(); +sapi_globals_struct *ext_php_rs_sapi_globals(); +php_file_globals *ext_php_rs_file_globals(); +sapi_module_struct *ext_php_rs_sapi_module(); +bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result); +void ext_php_rs_zend_bailout(); diff --git a/src/zend/class.rs b/src/zend/class.rs index 4c3c23dcd8..d9765a3c50 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,5 +1,6 @@ //! Builder and objects for creating classes in the PHP world. +use crate::types::{ZendIterator, Zval}; use crate::{ boxed::ZBox, ffi::zend_class_entry, @@ -97,6 +98,24 @@ impl ClassEntry { } } + /// Returns the iterator for the class for a specific instance + /// + /// Returns [`None`] if there is no associated iterator for the class. + pub fn get_iterator<'a>(&self, zval: &'a Zval, by_ref: bool) -> Option<&'a mut ZendIterator> { + let ptr: *const Self = self; + let zval_ptr: *const Zval = zval; + + let iterator = unsafe { + (*ptr).get_iterator?( + ptr as *mut ClassEntry, + zval_ptr as *mut Zval, + if by_ref { 1 } else { 0 }, + ) + }; + + unsafe { iterator.as_mut() } + } + pub fn name(&self) -> Option<&str> { unsafe { self.name.as_ref().and_then(|s| s.as_str().ok()) } } diff --git a/src/zend/ex.rs b/src/zend/ex.rs index dada00e0c8..cb389cb2e7 100644 --- a/src/zend/ex.rs +++ b/src/zend/ex.rs @@ -6,6 +6,8 @@ use crate::{ types::{ZendClassObject, ZendObject, Zval}, }; +use super::function::Function; + /// Execute data passed when a function is called from PHP. /// /// This generally contains things related to the call, including but not @@ -194,6 +196,16 @@ impl ExecuteData { self.This.object_mut() } + /// Attempt to retrieve the function that is being called. + pub fn function(&self) -> Option<&Function> { + unsafe { self.func.as_ref() } + } + + /// Attempt to retrieve the previous execute data on the call stack. + pub fn previous(&self) -> Option<&Self> { + unsafe { self.prev_execute_data.as_ref() } + } + /// Translation of macro `ZEND_CALL_ARG(call, n)` /// zend_compile.h:578 /// diff --git a/src/zend/function.rs b/src/zend/function.rs index a1ef8663cc..6e6dd1a39e 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -9,6 +9,7 @@ use crate::{ zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry, zend_hash_str_find_ptr_lc, }, + flags::FunctionType, types::Zval, }; @@ -50,9 +51,13 @@ impl FunctionEntry { pub type Function = zend_function; impl Function { + pub fn function_type(&self) -> FunctionType { + FunctionType::from(unsafe { self.type_ }) + } + pub fn try_from_function(name: &str) -> Option { unsafe { - let res = zend_fetch_function_str(name.as_ptr() as *const i8, name.len()); + let res = zend_fetch_function_str(name.as_ptr() as *const c_char, name.len()); if res.is_null() { return None; } @@ -65,7 +70,7 @@ impl Function { Some(ce) => unsafe { let res = zend_hash_str_find_ptr_lc( &ce.function_table, - name.as_ptr() as *const i8, + name.as_ptr() as *const c_char, name.len(), ) as *mut zend_function; if res.is_null() { diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 9761ab91d5..2e3789e6ce 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,19 +1,36 @@ -//! Types related to the PHP executor globals. +//! Types related to the PHP executor, sapi and process globals. use std::collections::HashMap; +use std::ffi::CStr; use std::ops::{Deref, DerefMut}; +use std::slice; +use std::str; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; +use crate::exception::PhpResult; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; -use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, zend_ini_entry}; -use crate::types::{ZendHashTable, ZendObject}; +use crate::ffi::{ + _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals, + ext_php_rs_file_globals, ext_php_rs_process_globals, ext_php_rs_sapi_globals, + ext_php_rs_sapi_module, php_core_globals, php_file_globals, sapi_globals_struct, + sapi_header_struct, sapi_headers_struct, sapi_request_info, zend_ini_entry, + zend_is_auto_global, TRACK_VARS_COOKIE, TRACK_VARS_ENV, TRACK_VARS_FILES, TRACK_VARS_GET, + TRACK_VARS_POST, TRACK_VARS_REQUEST, TRACK_VARS_SERVER, +}; + +use crate::types::{ZendHashTable, ZendObject, ZendStr}; + +use super::linked_list::ZendLinkedListIterator; /// Stores global variables used in the PHP executor. pub type ExecutorGlobals = _zend_executor_globals; +/// Stores the SAPI module used in the PHP executor. +pub type SapiModule = _sapi_module_struct; + impl ExecutorGlobals { /// Returns a reference to the PHP executor globals. /// @@ -52,27 +69,35 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } + /// Attempts to retrieve the global functions hash table. + pub fn function_table(&self) -> Option<&ZendHashTable> { + unsafe { self.function_table.as_ref() } + } + + /// Attempts to retrieve the global functions hash table as mutable. + pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> { + unsafe { self.function_table.as_mut() } + } + /// Retrieves the ini values for all ini directives in the current executor /// context.. pub fn ini_values(&self) -> HashMap> { let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = HashMap::new(); - for (_index, key, value) in hash_table.iter() { - if let Some(key) = key { - ini_hash_map.insert(key, unsafe { - let ini_entry = &*value.ptr::().expect("Invalid ini entry"); - if ini_entry.value.is_null() { - None - } else { - Some( - (*ini_entry.value) - .as_str() - .expect("Ini value is not a string") - .to_owned(), - ) - } - }); - } + for (key, value) in hash_table.iter() { + ini_hash_map.insert(key.to_string(), unsafe { + let ini_entry = &*value.ptr::().expect("Invalid ini entry"); + if ini_entry.value.is_null() { + None + } else { + Some( + (*ini_entry.value) + .as_str() + .expect("Ini value is not a string") + .to_owned(), + ) + } + }); } ini_hash_map } @@ -89,6 +114,13 @@ impl ExecutorGlobals { /// could lead to a deadlock if the globals are already borrowed immutably /// or mutably. pub fn take_exception() -> Option> { + { + // This avoid a write lock if there is no exception. + if Self::get().exception.is_null() { + return None; + } + } + let mut globals = Self::get_mut(); let mut exception_ptr = std::ptr::null_mut(); @@ -98,6 +130,25 @@ impl ExecutorGlobals { Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) }) } + /// Checks if the executor globals contain an exception. + pub fn has_exception() -> bool { + !Self::get().exception.is_null() + } + + /// Attempts to extract the last PHP exception captured by the interpreter. + /// Returned inside a [`PhpResult`]. + /// + /// This function requires the executor globals to be mutably held, which + /// could lead to a deadlock if the globals are already borrowed immutably + /// or mutably. + pub fn throw_if_exception() -> PhpResult<()> { + if let Some(e) = Self::take_exception() { + Err(crate::error::Error::Exception(e).into()) + } else { + Ok(()) + } + } + /// Request an interrupt of the PHP VM. This will call the registered /// interrupt handler function. /// set with [`crate::ffi::zend_interrupt_function`]. @@ -127,11 +178,376 @@ impl ExecutorGlobals { } } +impl SapiModule { + /// Returns a reference to the PHP SAPI module. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_module().as_ref() } + .expect("Static executor globals were invalid"); + let guard = SAPI_MODULE_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_module().as_mut() } + .expect("Static executor globals were invalid"); + let guard = SAPI_MODULE_LOCK.write(); + GlobalWriteGuard { globals, guard } + } +} + +/// Stores global variables used in the PHP executor. +pub type ProcessGlobals = php_core_globals; + +impl ProcessGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &*ext_php_rs_process_globals() }; + let guard = PROCESS_GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &mut *ext_php_rs_process_globals() }; + let guard = PROCESS_GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + + /// Get the HTTP Server variables. Equivalent of $_SERVER. + pub fn http_server_vars(&self) -> Option<&ZendHashTable> { + // $_SERVER is lazy-initted, we need to call zend_is_auto_global + // if it's not already populated. + if !self.http_globals[TRACK_VARS_SERVER as usize].is_array() { + let name = ZendStr::new("_SERVER", false).as_mut_ptr(); + unsafe { zend_is_auto_global(name) }; + } + if self.http_globals[TRACK_VARS_SERVER as usize].is_array() { + self.http_globals[TRACK_VARS_SERVER as usize].array() + } else { + None + } + } + + /// Get the HTTP POST variables. Equivalent of $_POST. + pub fn http_post_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_POST as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP GET variables. Equivalent of $_GET. + pub fn http_get_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_GET as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Cookie variables. Equivalent of $_COOKIE. + pub fn http_cookie_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_COOKIE as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Request variables. Equivalent of $_REQUEST. + pub fn http_request_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_REQUEST as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Environment variables. Equivalent of $_ENV. + pub fn http_env_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_ENV as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Files variables. Equivalent of $_FILES. + pub fn http_files_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_FILES as usize] + .array() + .expect("Type is not a ZendArray") + } +} + +/// Stores global variables used in the SAPI. +pub type SapiGlobals = sapi_globals_struct; + +impl SapiGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &*ext_php_rs_sapi_globals() }; + let guard = SAPI_GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &mut *ext_php_rs_sapi_globals() }; + let guard = SAPI_GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + // Get the request info for the Sapi. + pub fn request_info(&self) -> &SapiRequestInfo { + &self.request_info + } + + pub fn sapi_headers(&self) -> &SapiHeaders { + &self.sapi_headers + } +} + +pub type SapiHeaders = sapi_headers_struct; + +impl<'a> SapiHeaders { + pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> { + self.headers.iter() + } +} + +pub type SapiHeader = sapi_header_struct; + +impl<'a> SapiHeader { + pub fn as_str(&'a self) -> &'a str { + unsafe { + let slice = slice::from_raw_parts(self.header as *const u8, self.header_len); + str::from_utf8(slice).expect("Invalid header string") + } + } + + pub fn name(&'a self) -> &'a str { + self.as_str().split(':').next().unwrap_or("").trim() + } + + pub fn value(&'a self) -> Option<&'a str> { + self.as_str().split(':').nth(1).map(|s| s.trim()) + } +} + +pub type SapiRequestInfo = sapi_request_info; + +impl SapiRequestInfo { + pub fn request_method(&self) -> Option<&str> { + if self.request_method.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.request_method).to_str().ok() } + } + + pub fn query_string(&self) -> Option<&str> { + if self.query_string.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.query_string).to_str().ok() } + } + + pub fn cookie_data(&self) -> Option<&str> { + if self.cookie_data.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() } + } + + pub fn content_length(&self) -> i64 { + self.content_length + } + + pub fn path_translated(&self) -> Option<&str> { + if self.path_translated.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.path_translated).to_str().ok() } + } + + pub fn request_uri(&self) -> Option<&str> { + if self.request_uri.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.request_uri).to_str().ok() } + } + + // Todo: request_body _php_stream + + pub fn content_type(&self) -> Option<&str> { + if self.content_type.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.content_type).to_str().ok() } + } + + pub fn headers_only(&self) -> bool { + self.headers_only + } + + pub fn no_headers(&self) -> bool { + self.no_headers + } + + pub fn headers_read(&self) -> bool { + self.headers_read + } + + // Todo: post_entry sapi_post_entry + + pub fn auth_user(&self) -> Option<&str> { + if self.auth_user.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_user).to_str().ok() } + } + + pub fn auth_password(&self) -> Option<&str> { + if self.auth_password.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_password).to_str().ok() } + } + + pub fn auth_digest(&self) -> Option<&str> { + if self.auth_digest.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_digest).to_str().ok() } + } + + pub fn argv0(&self) -> Option<&str> { + if self.argv0.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.argv0).to_str().ok() } + } + + pub fn current_user(&self) -> Option<&str> { + if self.current_user.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.current_user).to_str().ok() } + } + + pub fn current_user_length(&self) -> i32 { + self.current_user_length + } + + pub fn argvc(&self) -> i32 { + self.argc + } + + pub fn argv(&self) -> Option<&str> { + if self.argv.is_null() { + return None; + } + unsafe { CStr::from_ptr(*self.argv).to_str().ok() } + } + + pub fn proto_num(&self) -> i32 { + self.proto_num + } +} + +/// Stores global variables used in the SAPI. +pub type FileGlobals = php_file_globals; + +impl FileGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_file_globals().as_ref() } + .expect("Static file globals were invalid"); + let guard = FILE_GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &mut *ext_php_rs_file_globals() }; + let guard = SAPI_GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + + pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> { + unsafe { self.stream_wrappers.as_ref() } + } +} + /// Executor globals rwlock. /// /// PHP provides no indication if the executor globals are being accessed so /// this is only effective on the Rust side. static GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +static PROCESS_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +static SAPI_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +static FILE_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); + +/// SAPI globals rwlock. +/// +/// PHP provides no indication if the executor globals are being accessed so +/// this is only effective on the Rust side. +static SAPI_MODULE_LOCK: RwLock<()> = const_rwlock(()); /// Wrapper guard that contains a reference to a given type `T`. Dropping a /// guard releases the lock on the relevant rwlock. diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index 11d220ba08..7e88a7e053 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -238,6 +238,7 @@ impl ZendObjectHandlers { let mut zv = Zval::new(); val.get(self_, &mut zv)?; + #[allow(clippy::unnecessary_mut_passed)] if zend_is_true(&mut zv) == 1 { return Ok(1); } diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 8817300316..d9c8f64d8c 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -7,8 +7,9 @@ use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniE /// A Zend ini entry definition. /// -/// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini -/// entries should be registered in your module's startup_function via `IniEntryDef::register(Vec)`. +/// To register ini definitions for extensions, the IniEntryDef builder should +/// be used. Ini entries should be registered in your module's startup_function +/// via `IniEntryDef::register(Vec)`. pub type IniEntryDef = zend_ini_entry_def; impl IniEntryDef { diff --git a/src/zend/linked_list.rs b/src/zend/linked_list.rs new file mode 100644 index 0000000000..481b229790 --- /dev/null +++ b/src/zend/linked_list.rs @@ -0,0 +1,46 @@ +use std::marker::PhantomData; + +use crate::ffi::{zend_llist, zend_llist_element, zend_llist_get_next_ex}; + +pub type ZendLinkedList = zend_llist; + +impl ZendLinkedList { + pub fn iter(&self) -> ZendLinkedListIterator { + ZendLinkedListIterator::new(self) + } +} + +pub struct ZendLinkedListIterator<'a, T> { + list: &'a zend_llist, + position: *mut zend_llist_element, + _marker: PhantomData, +} + +impl<'a, T> ZendLinkedListIterator<'a, T> { + fn new(list: &'a ZendLinkedList) -> Self { + ZendLinkedListIterator { + list, + position: list.head, + _marker: PhantomData, + } + } +} + +impl<'a, T: 'a> Iterator for ZendLinkedListIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.position.is_null() { + return None; + } + let ptr = unsafe { (*self.position).data.as_mut_ptr() }; + let value = unsafe { &*(ptr as *const T as *mut T) }; + unsafe { + zend_llist_get_next_ex( + self.list as *const ZendLinkedList as *mut ZendLinkedList, + &mut self.position, + ) + }; + Some(value) + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index b3b1cfbdb3..357f2934aa 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -8,9 +8,15 @@ mod function; mod globals; mod handlers; mod ini_entry_def; +mod linked_list; mod module; +mod streams; +mod try_catch; -use crate::{error::Result, ffi::php_printf}; +use crate::{ + error::Result, + ffi::{php_printf, sapi_module}, +}; use std::ffi::CString; pub use _type::ZendType; @@ -19,9 +25,18 @@ pub use ex::ExecuteData; pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; +pub use globals::FileGlobals; +pub use globals::ProcessGlobals; +pub use globals::SapiGlobals; +pub use globals::SapiModule; pub use handlers::ZendObjectHandlers; pub use ini_entry_def::IniEntryDef; +pub use linked_list::ZendLinkedList; pub use module::ModuleEntry; +pub use streams::*; +#[cfg(feature = "embed")] +pub(crate) use try_catch::panic_wrapper; +pub use try_catch::{bailout, try_catch}; // Used as the format string for `php_printf`. const FORMAT_STR: &[u8] = b"%s\0"; @@ -45,3 +60,9 @@ pub fn printf(message: &str) -> Result<()> { }; Ok(()) } + +/// Get the name of the SAPI module. +pub fn php_sapi_name() -> String { + let c_str = unsafe { std::ffi::CStr::from_ptr(sapi_module.name) }; + c_str.to_str().expect("Unable to parse CStr").to_string() +} diff --git a/src/zend/streams.rs b/src/zend/streams.rs new file mode 100644 index 0000000000..8ea5da2287 --- /dev/null +++ b/src/zend/streams.rs @@ -0,0 +1,101 @@ +use std::ptr::{self, NonNull}; + +use crate::{ + error::Error, + ffi::{ + php_register_url_stream_wrapper, php_register_url_stream_wrapper_volatile, php_stream, + php_stream_context, php_stream_locate_url_wrapper, php_stream_wrapper, + php_stream_wrapper_ops, php_unregister_url_stream_wrapper, + php_unregister_url_stream_wrapper_volatile, zend_string, + }, + types::ZendStr, +}; + +pub type StreamWrapper = php_stream_wrapper; + +pub type StreamOpener = unsafe extern "C" fn( + *mut StreamWrapper, + *const std::ffi::c_char, + *const std::ffi::c_char, + i32, + *mut *mut zend_string, + *mut php_stream_context, + i32, + *const std::ffi::c_char, + u32, + *const std::ffi::c_char, + u32, +) -> *mut Stream; + +impl StreamWrapper { + pub fn get(name: &str) -> Option<&Self> { + unsafe { + let result = php_stream_locate_url_wrapper(name.as_ptr().cast(), ptr::null_mut(), 0); + Some(NonNull::new(result)?.as_ref()) + } + } + + pub fn get_mut(name: &str) -> Option<&mut Self> { + unsafe { + let result = php_stream_locate_url_wrapper(name.as_ptr().cast(), ptr::null_mut(), 0); + Some(NonNull::new(result)?.as_mut()) + } + } + + pub fn register(self, name: &str) -> Result { + // We have to convert it to a static so owned streamwrapper doesn't get dropped. + let copy = Box::new(self); + let copy = Box::leak(copy); + let name = std::ffi::CString::new(name).expect("Could not create C string for name!"); + let result = unsafe { php_register_url_stream_wrapper(name.as_ptr(), copy) }; + if result == 0 { + Ok(*copy) + } else { + Err(Error::StreamWrapperRegistrationFailure) + } + } + + pub fn register_volatile(self, name: &str) -> Result { + // We have to convert it to a static so owned streamwrapper doesn't get dropped. + let copy = Box::new(self); + let copy = Box::leak(copy); + let name = ZendStr::new(name, false); + let result = + unsafe { php_register_url_stream_wrapper_volatile((*name).as_ptr() as _, copy) }; + if result == 0 { + Ok(*copy) + } else { + Err(Error::StreamWrapperRegistrationFailure) + } + } + + pub fn unregister(name: &str) -> Result<(), Error> { + let name = std::ffi::CString::new(name).expect("Could not create C string for name!"); + match unsafe { php_unregister_url_stream_wrapper(name.as_ptr()) } { + 0 => Ok(()), + _ => Err(Error::StreamWrapperUnregistrationFailure), + } + } + + pub fn unregister_volatile(name: &str) -> Result<(), Error> { + let name = ZendStr::new(name, false); + match unsafe { php_unregister_url_stream_wrapper_volatile((*name).as_ptr() as _) } { + 0 => Ok(()), + _ => Err(Error::StreamWrapperUnregistrationFailure), + } + } + + pub fn wops(&self) -> &php_stream_wrapper_ops { + unsafe { &*self.wops } + } + + pub fn wops_mut(&mut self) -> &mut php_stream_wrapper_ops { + unsafe { &mut *(self.wops as *mut php_stream_wrapper_ops) } + } +} + +pub type Stream = php_stream; + +pub type StreamWrapperOps = php_stream_wrapper_ops; + +impl StreamWrapperOps {} diff --git a/src/zend/try_catch.rs b/src/zend/try_catch.rs new file mode 100644 index 0000000000..37cd89655c --- /dev/null +++ b/src/zend/try_catch.rs @@ -0,0 +1,166 @@ +use crate::ffi::{ext_php_rs_zend_bailout, ext_php_rs_zend_try_catch}; +use std::ffi::c_void; +use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe}; +use std::ptr::null_mut; + +#[derive(Debug)] +pub struct CatchError; + +pub(crate) unsafe extern "C" fn panic_wrapper R + RefUnwindSafe>( + ctx: *const c_void, +) -> *const c_void { + // we try to catch panic here so we correctly shutdown php if it happens + // mandatory when we do assert on test as other test would not run correctly + let panic = catch_unwind(|| (*(ctx as *mut F))()); + + Box::into_raw(Box::new(panic)) as *mut c_void +} + +/// PHP propose a try catch mechanism in C using setjmp and longjmp (bailout) +/// It store the arg of setjmp into the bailout field of the global executor +/// If a bailout is triggered, the executor will jump to the setjmp and restore +/// the previous setjmp +/// +/// try_catch allow to use this mechanism +/// +/// # Returns +/// +/// * `Ok(R)` - The result of the function +/// * `Err(CatchError)` - A bailout occurred during the execution +pub fn try_catch R + RefUnwindSafe>(func: F) -> Result { + let mut panic_ptr = null_mut(); + let has_bailout = unsafe { + ext_php_rs_zend_try_catch( + panic_wrapper::, + &func as *const F as *const c_void, + (&mut panic_ptr) as *mut *mut c_void, + ) + }; + + let panic = panic_ptr as *mut std::thread::Result; + + // can be null if there is a bailout + if panic.is_null() || has_bailout { + return Err(CatchError); + } + + match unsafe { *Box::from_raw(panic as *mut std::thread::Result) } { + Ok(r) => Ok(r), + Err(err) => { + // we resume the panic here so it can be catched correctly by the test framework + resume_unwind(err); + } + } +} + +/// Trigger a bailout +/// +/// This function will stop the execution of the current script +/// and jump to the last try catch block +/// +/// # Safety +/// +/// This function is unsafe because it can cause memory leaks +/// Since it will jump to the last try catch block, it will not call the +/// destructor of the current scope +/// +/// When using this function you should ensure that all the memory allocated in +/// the current scope is released +pub unsafe fn bailout() -> ! { + ext_php_rs_zend_bailout(); +} + +#[cfg(feature = "embed")] +#[cfg(test)] +mod tests { + use crate::embed::Embed; + use crate::zend::{bailout, try_catch}; + use std::ptr::null_mut; + + #[test] + fn test_catch() { + Embed::run(|| { + let catch = try_catch(|| { + unsafe { + bailout(); + } + + #[allow(unreachable_code)] + { + assert!(false); + } + }); + + assert!(catch.is_err()); + }); + } + + #[test] + fn test_no_catch() { + Embed::run(|| { + let catch = try_catch(|| { + assert!(true); + }); + + assert!(catch.is_ok()); + }); + } + + #[test] + fn test_bailout() { + Embed::run(|| { + unsafe { + bailout(); + } + + #[allow(unreachable_code)] + { + assert!(false); + } + }); + } + + #[test] + #[should_panic] + fn test_panic() { + Embed::run(|| { + let _ = try_catch(|| { + panic!("should panic"); + }); + }); + } + + #[test] + fn test_return() { + let foo = Embed::run(|| { + let result = try_catch(|| { + return "foo"; + }); + + assert!(result.is_ok()); + + result.unwrap() + }); + + assert_eq!(foo, "foo"); + } + + #[test] + fn test_memory_leak() { + let mut ptr = null_mut(); + + let _ = try_catch(|| { + let mut result = "foo".to_string(); + ptr = &mut result; + + unsafe { + bailout(); + } + }); + + // Check that the string is never released + let result = unsafe { &*ptr as &str }; + + assert_eq!(result, "foo"); + } +} diff --git a/tests/module.rs b/tests/module.rs new file mode 100644 index 0000000000..f1ef22b852 --- /dev/null +++ b/tests/module.rs @@ -0,0 +1,44 @@ +#![cfg_attr(windows, feature(abi_vectorcall))] +extern crate ext_php_rs; + +#[cfg(feature = "embed")] +use ext_php_rs::embed::Embed; +#[cfg(feature = "embed")] +use ext_php_rs::ffi::zend_register_module_ex; +use ext_php_rs::prelude::*; + +#[test] +#[cfg(feature = "embed")] +fn test_module() { + Embed::run(|| { + // Allow to load the module + unsafe { zend_register_module_ex(get_module()) }; + + let result = Embed::eval("$foo = hello_world('foo');"); + + assert!(result.is_ok()); + + let zval = result.unwrap(); + + assert!(zval.is_string()); + + let string = zval.string().unwrap(); + + assert_eq!(string.to_string(), "Hello, foo!"); + }); +} + +/// Gives you a nice greeting! +/// +/// @param string $name Your name. +/// +/// @return string Nice greeting! +#[php_function] +pub fn hello_world(name: String) -> String { + format!("Hello, {}!", name) +} + +#[php_module] +pub fn module(module: ModuleBuilder) -> ModuleBuilder { + module +}