diff --git a/.github/actions/embed/Dockerfile b/.github/actions/embed/Dockerfile new file mode 100644 index 0000000..a92ec5a --- /dev/null +++ b/.github/actions/embed/Dockerfile @@ -0,0 +1,15 @@ +FROM php:8.2-bullseye + +WORKDIR /tmp + +RUN apt update -y && apt upgrade -y +RUN apt install lsb-release wget gnupg software-properties-common -y +RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" + +ENV RUSTUP_HOME=/rust +ENV CARGO_HOME=/cargo +ENV PATH=/cargo/bin:/rust/bin:$PATH + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path + +ENTRYPOINT [ "/cargo/bin/cargo", "test", "--lib", "--release", "--all-features" ] diff --git a/.github/actions/embed/action.yml b/.github/actions/embed/action.yml new file mode 100644 index 0000000..c99ebf5 --- /dev/null +++ b/.github/actions/embed/action.yml @@ -0,0 +1,5 @@ +name: 'PHP Embed and Rust' +description: 'Builds the crate after installing the latest PHP with php embed and stable Rust.' +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6176b86..e324863 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: @@ -83,10 +89,10 @@ jobs: - name: Build env: EXT_PHP_RS_TEST: "" - run: cargo build --release --all-features --all + run: cargo build --release --features closure,anyhow --all # Test & lint - name: Test inline examples - run: cargo test --release --all --all-features + run: cargo test --release --all --features closure,anyhow - name: Run rustfmt if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2' run: cargo fmt --all -- --check @@ -110,3 +116,11 @@ jobs: uses: actions/checkout@v3 - name: Build uses: ./.github/actions/zts + test-embed: + name: Test with embed + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Test + uses: ./.github/actions/embed diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5f55df1..522682b 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 2219cba..0deea94 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.1" +version = "0.10.5" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -17,30 +17,31 @@ parking_lot = "0.12" cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } -ext-php-rs-derive = { version = "=0.10.0", path = "./crates/macros" } +ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" [build-dependencies] anyhow = "1" -bindgen = "0.65.1" +bindgen = "0.68.1" cc = "1.0" skeptic = "0.13" [target.'cfg(windows)'.build-dependencies] -ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false } +ureq = { version = "2.4", features = [ + "native-tls", + "gzip", +], default-features = false } native-tls = "0.2" zip = "0.6" [features] closure = [] +embed = [] [workspace] -members = [ - "crates/macros", - "crates/cli" -] +members = ["crates/macros", "crates/cli"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docs"] diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 75d7207..427a6fa 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, @@ -55,6 +57,8 @@ bind! { zend_array_destroy, zend_array_dup, zend_call_known_function, + zend_fetch_function_str, + zend_hash_str_find_ptr_lc, zend_ce_argument_count_error, zend_ce_arithmetic_error, zend_ce_compile_error, @@ -99,6 +103,8 @@ bind! { zend_objects_clone_members, zend_register_bool_constant, zend_register_double_constant, + zend_register_ini_entries, + zend_ini_entry_def, zend_register_internal_class_ex, zend_register_long_constant, zend_register_string_constant, @@ -106,6 +112,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, @@ -137,6 +144,7 @@ bind! { IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, + IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_MIXED, @@ -157,6 +165,10 @@ bind! { IS_PTR, MAY_BE_ANY, MAY_BE_BOOL, + PHP_INI_USER, + PHP_INI_PERDIR, + PHP_INI_SYSTEM, + PHP_INI_ALL, USING_ZTS, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, @@ -233,6 +245,8 @@ bind! { zend_class_serialize_deny, zend_class_unserialize_deny, zend_executor_globals, + sapi_globals_struct, + sapi_module_struct, zend_objects_store_del, zend_hash_move_forward_ex, zend_hash_get_current_key_type_ex, @@ -247,6 +261,8 @@ bind! { core_globals, sapi_globals_struct, sapi_globals, + sapi_globals, + sapi_module, php_printf, __zend_malloc, tsrm_get_ls_cache, @@ -268,6 +284,13 @@ bind! { zend_is_auto_global, zend_llist_get_next_ex, zend_llist_get_prev_ex, + sapi_globals_offset, zend_atomic_bool_store, - zend_interrupt_function + zend_interrupt_function, + zend_eval_string, + zend_file_handle, + zend_stream_init_filename, + php_execute_script, + zend_register_module_ex, + _zend_bailout } diff --git a/build.rs b/build.rs index 40250a9..5afef52 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. @@ -151,9 +151,31 @@ fn build_wrapper(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> { Ok(()) } +#[cfg(feature = "embed")] +/// Builds the embed library. +fn build_embed(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> { + let mut build = cc::Build::new(); + for (var, val) in defines { + build.define(var, *val); + } + build + .file("src/embed/embed.c") + .includes(includes) + .try_compile("embed") + .context("Failed to compile ext-php-rs C embed interface")?; + Ok(()) +} + /// Generates bindings to the Zend API. fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result { - let mut bindgen = bindgen::Builder::default() + let mut bindgen = bindgen::Builder::default(); + + #[cfg(feature = "embed")] + { + bindgen = bindgen.header("src/embed/embed.h"); + } + + bindgen = bindgen .header("src/wrapper.h") .clang_args( includes @@ -206,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) { @@ -216,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(()) } @@ -226,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"), @@ -257,6 +287,10 @@ fn main() -> Result<()> { check_php_version(&info)?; build_wrapper(&defines, &includes)?; + + #[cfg(feature = "embed")] + build_embed(&defines, &includes)?; + let bindings = generate_bindings(&defines, &includes)?; let out_file = diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index aa965d3..004a1cb 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/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.1.8" +version = "0.1.9" authors = ["David Cole "] edition = "2018" categories = ["api-bindings", "command-line-interface"] diff --git a/crates/cli/src/ext.rs b/crates/cli/src/ext.rs index b529a75..91aaff5 100644 --- a/crates/cli/src/ext.rs +++ b/crates/cli/src/ext.rs @@ -4,6 +4,7 @@ use anyhow::{Context, Result}; use ext_php_rs::describe::Description; use libloading::os::unix::{Library, Symbol}; +#[allow(improper_ctypes_definitions)] pub struct Ext { // These need to be here to keep the libraries alive. The extension library needs to be alive // to access the describe function. Missing here is the lifetime on `Symbol<'a, fn() -> diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 44d89bd..0131d72 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs." repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" -version = "0.10.0" +version = "0.10.1" authors = ["David Cole "] edition = "2018" diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 5e3def2..5a389f1 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -26,6 +26,7 @@ pub struct Arg { pub ty: String, pub nullable: bool, pub default: Option, + pub as_ref: bool, } #[derive(Debug, Clone)] @@ -249,12 +250,19 @@ pub fn get_return_type(output_type: &ReturnType) -> Result) -> Self { + pub fn new( + name: String, + ty: String, + nullable: bool, + default: Option, + as_ref: bool, + ) -> Self { Self { name, ty, nullable, default, + as_ref, } } @@ -268,6 +276,7 @@ impl Arg { match ty { Type::Path(TypePath { path, .. }) => { let mut path = path.clone(); + let mut pass_by_ref = false; path.drop_lifetimes(); let seg = path.segments.last()?; @@ -283,9 +292,45 @@ impl Arg { None } }); + + // For for types that are `Option<&mut T>` to turn them into `Option<&T>`, + // marking the Arg as as "passed by reference". + let option = Some(seg) + .filter(|seg| seg.ident == "Option") + .and_then(|seg| { + if let PathArguments::AngleBracketed(args) = &seg.arguments { + args.args + .iter() + .find(|arg| matches!(arg, GenericArgument::Type(_))) + .map(|ga| { + let new_ga = match ga { + GenericArgument::Type(ty) => { + let _rtype = match ty { + Type::Reference(r) => { + let mut new_ref = r.clone(); + new_ref.mutability = None; + pass_by_ref = true; + Type::Reference(new_ref) + } + _ => ty.clone(), + }; + GenericArgument::Type(_rtype) + } + _ => ga.clone(), + }; + new_ga.to_token_stream().to_string() + }) + } else { + None + } + }); + let stringified = match result { Some(result) if is_return => result, - _ => path.to_token_stream().to_string(), + _ => match option { + Some(result) => result, + None => path.to_token_stream().to_string(), + }, }; Some(Arg::new( @@ -293,6 +338,7 @@ impl Arg { stringified, seg.ident == "Option" || default.is_some(), default, + pass_by_ref, )) } Type::Reference(ref_) => { @@ -302,6 +348,7 @@ impl Arg { ref_.to_token_stream().to_string(), false, default, + ref_.mutability.is_some(), )) } _ => None, @@ -361,6 +408,7 @@ impl Arg { let ty = self.get_type_ident(); let null = self.nullable.then(|| quote! { .allow_null() }); + let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() }); let default = self.default.as_ref().map(|val| { quote! { .default(#val) @@ -368,7 +416,7 @@ impl Arg { }); quote! { - ::ext_php_rs::args::Arg::new(#name, #ty) #null #default + ::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default } } } diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 01217bd..b25762d 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.65.1 */ +/* automatically generated by rust-bindgen 0.68.1 */ #[repr(C)] #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -99,6 +99,7 @@ pub const IS_CONSTANT_AST: u32 = 11; pub const IS_CALLABLE: u32 = 12; 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; @@ -177,110 +178,96 @@ 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; +pub const PHP_INI_ALL: u32 = 7; 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 __uint16_t = ::std::os::raw::c_ushort; -pub type __int32_t = ::std::os::raw::c_int; -pub type __uint32_t = ::std::os::raw::c_uint; -pub type __int64_t = ::std::os::raw::c_longlong; -pub type __uint64_t = ::std::os::raw::c_ulonglong; -pub type __darwin_time_t = ::std::os::raw::c_long; -pub type __darwin_blkcnt_t = __int64_t; -pub type __darwin_blksize_t = __int32_t; -pub type __darwin_dev_t = __int32_t; -pub type __darwin_gid_t = __uint32_t; -pub type __darwin_ino64_t = __uint64_t; -pub type __darwin_mode_t = __uint16_t; -pub type __darwin_off_t = __int64_t; -pub type __darwin_uid_t = __uint32_t; -pub type uid_t = __darwin_uid_t; -pub type dev_t = __darwin_dev_t; -pub type mode_t = __darwin_mode_t; -pub type blkcnt_t = __darwin_blkcnt_t; -pub type blksize_t = __darwin_blksize_t; -pub type gid_t = __darwin_gid_t; -pub type nlink_t = __uint16_t; -pub type off_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 __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct timespec { - pub tv_sec: __darwin_time_t, - pub tv_nsec: ::std::os::raw::c_long, + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, } -pub type fpos_t = __darwin_off_t; +pub type FILE = _IO_FILE; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sbuf { - pub _base: *mut ::std::os::raw::c_uchar, - pub _size: ::std::os::raw::c_int, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __sFILEX { +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 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 FILE = __sFILE; pub type zend_long = i64; pub type zend_ulong = u64; pub type zend_off_t = i64; -pub type zend_uchar = ::std::os::raw::c_uchar; 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; @@ -344,8 +331,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)] @@ -363,7 +350,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, } @@ -421,10 +408,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)] @@ -439,6 +426,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)] @@ -518,19 +506,6 @@ pub struct _zend_llist { 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, @@ -613,6 +588,13 @@ extern "C" { extern "C" { pub fn zend_array_destroy(ht: *mut HashTable); } +extern "C" { + pub fn zend_hash_str_find_ptr_lc( + ht: *const HashTable, + str_: *const ::std::os::raw::c_char, + len: usize, + ) -> *mut ::std::os::raw::c_void; +} extern "C" { pub fn gc_possible_root(ref_: *mut zend_refcounted); } @@ -682,24 +664,67 @@ 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_mode: mode_t, - pub st_nlink: nlink_t, - pub st_ino: __darwin_ino64_t, - pub st_uid: uid_t, - pub st_gid: gid_t, - pub st_rdev: dev_t, - pub st_atimespec: timespec, - pub st_mtimespec: timespec, - pub st_ctimespec: timespec, - pub st_birthtimespec: timespec, - pub st_size: off_t, - pub st_blocks: blkcnt_t, - pub st_blksize: blksize_t, - pub st_flags: __uint32_t, - pub st_gen: __uint32_t, - pub st_lspare: __int32_t, - pub st_qspare: [__int64_t; 2usize], + 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< + unsafe extern "C" fn( + handle: *mut ::std::os::raw::c_void, + buf: *mut ::std::os::raw::c_char, + len: usize, + ) -> isize, +>; +pub type zend_stream_closer_t = + ::std::option::Option; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_stream { + pub handle: *mut ::std::os::raw::c_void, + pub isatty: ::std::os::raw::c_int, + pub reader: zend_stream_reader_t, + pub fsizer: zend_stream_fsizer_t, + pub closer: zend_stream_closer_t, +} +pub type zend_stream = _zend_stream; +#[repr(C)] +#[derive(Copy, Clone)] +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_: u8, + pub primary_script: bool, + pub in_list: bool, + pub buf: *mut ::std::os::raw::c_char, + pub len: usize, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union _zend_file_handle__bindgen_ty_1 { + pub fp: *mut FILE, + pub stream: zend_stream, +} +pub type zend_file_handle = _zend_file_handle; +extern "C" { + pub fn zend_stream_init_filename( + handle: *mut zend_file_handle, + filename: *const ::std::os::raw::c_char, + ); } pub type zend_stat_t = stat; #[repr(C)] @@ -815,6 +840,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, @@ -904,6 +930,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; @@ -1041,7 +1070,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, @@ -1108,10 +1137,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; @@ -1134,10 +1163,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)] @@ -1186,8 +1215,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, @@ -1196,8 +1225,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, @@ -1226,8 +1255,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, @@ -1236,8 +1265,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], @@ -1246,7 +1275,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, @@ -1255,8 +1284,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, @@ -1265,8 +1294,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 { @@ -1280,7 +1309,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; @@ -1327,6 +1364,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)] @@ -1350,7 +1394,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, @@ -1363,29 +1407,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, @@ -1396,7 +1443,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, @@ -1406,23 +1453,22 @@ 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)] @@ -1489,6 +1535,13 @@ extern "C" { flags: u32, ) -> *mut zend_class_entry; } +extern "C" { + pub fn zend_eval_string( + str_: *const ::std::os::raw::c_char, + retval_ptr: *mut zval, + string_name: *const ::std::os::raw::c_char, + ) -> zend_result; +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_vm_stack { @@ -1496,6 +1549,12 @@ pub struct _zend_vm_stack { pub end: *mut zval, pub prev: zend_vm_stack, } +extern "C" { + pub fn zend_fetch_function_str( + name: *const ::std::os::raw::c_char, + len: usize, + ) -> *mut zend_function; +} #[repr(C)] #[derive(Copy, Clone)] pub struct _zend_function_entry { @@ -1513,8 +1572,12 @@ pub struct _zend_fcall_info_cache { 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, @@ -1593,15 +1656,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); @@ -1891,7 +1956,7 @@ pub struct _php_stream { pub wrapperthis: *mut ::std::os::raw::c_void, pub wrapperdata: zval, pub _bitfield_align_1: [u8; 0], - pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, pub mode: [::std::os::raw::c_char; 16usize], pub flags: u32, pub res: *mut zend_resource, @@ -1910,182 +1975,151 @@ pub struct _php_stream { } impl _php_stream { #[inline] - pub fn is_persistent(&self) -> u8 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u8) } + 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: u8) { + pub fn set_is_persistent(&mut self, val: u16) { unsafe { - let val: u8 = ::std::mem::transmute(val); + let val: u16 = ::std::mem::transmute(val); self._bitfield_1.set(0usize, 1u8, val as u64) } } #[inline] - pub fn in_free(&self) -> u8 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u8) } + 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: u8) { + pub fn set_in_free(&mut self, val: u16) { unsafe { - let val: u8 = ::std::mem::transmute(val); + let val: u16 = ::std::mem::transmute(val); self._bitfield_1.set(1usize, 2u8, val as u64) } } #[inline] - pub fn eof(&self) -> u8 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u8) } + 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: u8) { + pub fn set_eof(&mut self, val: u16) { unsafe { - let val: u8 = ::std::mem::transmute(val); + let val: u16 = ::std::mem::transmute(val); self._bitfield_1.set(3usize, 1u8, val as u64) } } #[inline] - pub fn __exposed(&self) -> u8 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u8) } + 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: u8) { + pub fn set___exposed(&mut self, val: u16) { unsafe { - let val: u8 = ::std::mem::transmute(val); + let val: u16 = ::std::mem::transmute(val); self._bitfield_1.set(4usize, 1u8, val as u64) } } #[inline] - pub fn fclose_stdiocast(&self) -> u8 { - unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u8) } + 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: u8) { + pub fn set_fclose_stdiocast(&mut self, val: u16) { unsafe { - let val: u8 = ::std::mem::transmute(val); + 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: u8, - in_free: u8, - eof: u8, - __exposed: u8, - fclose_stdiocast: u8, - ) -> __BindgenBitfieldUnit<[u8; 1usize]> { - let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + 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: u8 = unsafe { ::std::mem::transmute(is_persistent) }; + let is_persistent: u16 = unsafe { ::std::mem::transmute(is_persistent) }; is_persistent as u64 }); __bindgen_bitfield_unit.set(1usize, 2u8, { - let in_free: u8 = unsafe { ::std::mem::transmute(in_free) }; + let in_free: u16 = unsafe { ::std::mem::transmute(in_free) }; in_free as u64 }); __bindgen_bitfield_unit.set(3usize, 1u8, { - let eof: u8 = unsafe { ::std::mem::transmute(eof) }; + let eof: u16 = unsafe { ::std::mem::transmute(eof) }; eof as u64 }); __bindgen_bitfield_unit.set(4usize, 1u8, { - let __exposed: u8 = unsafe { ::std::mem::transmute(__exposed) }; + let __exposed: u16 = unsafe { ::std::mem::transmute(__exposed) }; __exposed as u64 }); __bindgen_bitfield_unit.set(5usize, 2u8, { - let fclose_stdiocast: u8 = unsafe { ::std::mem::transmute(fclose_stdiocast) }; + 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 } } -pub type php_core_globals = _php_core_globals; -#[repr(C)] -pub struct _php_core_globals { - pub implicit_flush: bool, - pub output_buffering: zend_long, - pub enable_dl: 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 display_errors: zend_uchar, - pub display_startup_errors: bool, - pub log_errors: bool, - pub ignore_repeated_errors: bool, - pub ignore_repeated_source: bool, - pub report_memleaks: bool, - 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 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 docref_root: *mut ::std::os::raw::c_char, - pub docref_ext: *mut ::std::os::raw::c_char, - pub html_errors: bool, - pub xmlrpc_errors: bool, - 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_message: *mut zend_string, - pub last_error_file: *mut zend_string, - pub last_error_lineno: ::std::os::raw::c_int, - pub php_sys_temp_dir: *mut ::std::os::raw::c_char, - pub disable_classes: *mut ::std::os::raw::c_char, - pub allow_url_include: bool, - pub max_input_nesting_level: zend_long, - pub max_input_vars: zend_long, - pub in_user_include: bool, - 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_x_header: bool, - pub mail_mixed_lf_and_crlf: bool, - pub mail_log: *mut ::std::os::raw::c_char, - pub in_error_log: bool, - pub syslog_facility: zend_long, - pub syslog_ident: *mut ::std::os::raw::c_char, - pub have_called_openlog: bool, - 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 struct _zend_ini_entry_def { + pub name: *const ::std::os::raw::c_char, + pub on_modify: ::std::option::Option< + unsafe extern "C" fn( + entry: *mut zend_ini_entry, + new_value: *mut zend_string, + mh_arg1: *mut ::std::os::raw::c_void, + mh_arg2: *mut ::std::os::raw::c_void, + mh_arg3: *mut ::std::os::raw::c_void, + stage: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub mh_arg1: *mut ::std::os::raw::c_void, + pub mh_arg2: *mut ::std::os::raw::c_void, + pub mh_arg3: *mut ::std::os::raw::c_void, + pub value: *const ::std::os::raw::c_char, + pub displayer: ::std::option::Option< + unsafe extern "C" fn(ini_entry: *mut zend_ini_entry, type_: ::std::os::raw::c_int), + >, + pub value_length: u32, + pub name_length: u16, + pub modifiable: u8, } -pub type arg_separators = _arg_separators; +pub type zend_ini_entry_def = _zend_ini_entry_def; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry { @@ -2113,6 +2147,12 @@ pub struct _zend_ini_entry { pub orig_modifiable: u8, pub modified: u8, } +extern "C" { + pub fn zend_register_ini_entries( + ini_entry: *const zend_ini_entry_def, + module_number: ::std::os::raw::c_int, + ) -> zend_result; +} extern "C" { pub fn zend_register_bool_constant( name: *const ::std::os::raw::c_char, @@ -2161,25 +2201,6 @@ extern "C" { extern "C" { pub fn php_info_print_table_end(); } -#[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, -} -extern "C" { - pub static mut file_globals: php_file_globals; -} extern "C" { pub static mut zend_ce_throwable: *mut zend_class_entry; } @@ -2221,6 +2242,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); } @@ -2261,6 +2285,10 @@ pub struct sapi_headers_struct { 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 { @@ -2311,6 +2339,111 @@ 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 { diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f725964..db61411 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -23,12 +23,16 @@ - [Object](./types/object.md) - [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) +- [INI Settings](./ini-settings.md) diff --git a/guide/src/ini-settings.md b/guide/src/ini-settings.md new file mode 100644 index 0000000..22820c5 --- /dev/null +++ b/guide/src/ini-settings.md @@ -0,0 +1,48 @@ +# INI Settings + +Your PHP Extension may want to provide it's own PHP INI settings to configure behaviour. This can be done in the `#[php_startup]` annotated startup function. + +## Registering INI Settings + +All PHP INI definitions must be registered with PHP to get / set their values via the `php.ini` file or `ini_get() / ini_set()`. + + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::IniEntryDef; +# use ext_php_rs::flags::IniEntryPermission; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + let ini_entries: Vec = vec![ + IniEntryDef::new( + "my_extension.display_emoji".to_owned(), + "yes".to_owned(), + IniEntryPermission::All, + ), + ]; + IniEntryDef::register(ini_entries, module_number); +} +# fn main() {} +``` + +## Getting INI Settings + +The INI values are stored as part of the `GlobalExecutor`, and can be accessed via the `ini_values()` function. To retrieve the value for a registered INI setting + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::ExecutorGlobals; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + // Get all INI values + let ini_values = ExecutorGlobals::get().ini_values(); // HashMap> + let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option> +} +# fn main() {} +``` diff --git a/guide/src/macros/async_impl.md b/guide/src/macros/async_impl.md new file mode 100644 index 0000000..979c58b --- /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 7214ab9..db12eca 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/bool.md b/guide/src/types/bool.md index 1974ff3..498b40e 100644 --- a/guide/src/types/bool.md +++ b/guide/src/types/bool.md @@ -45,3 +45,17 @@ pub fn test_bool(input: bool) -> String { var_dump(test_bool(true)); // string(4) "Yes!" var_dump(test_bool(false)); // string(3) "No!" ``` + +## Rust example, taking by reference + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types; +#[php_function] +pub fn test_bool(input: &mut types::Zval) { + input.reference_mut().unwrap().set_bool(false); +} +# fn main() {} +``` diff --git a/guide/src/types/functions.md b/guide/src/types/functions.md new file mode 100644 index 0000000..475a27b --- /dev/null +++ b/guide/src/types/functions.md @@ -0,0 +1,28 @@ +# Functions & methods + +PHP functions and methods are represented by the `Function` struct. + +You can use the `try_from_function` and `try_from_method` methods to obtain a Function struct corresponding to the passed function or static method name. +It's heavily recommended you reuse returned `Function` objects, to avoid the overhead of looking up the function/method name. + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::prelude::*; + +use ext_php_rs::zend::Function; + +#[php_function] +pub fn test_function() -> () { + let var_dump = Function::try_from_function("var_dump").unwrap(); + let _ = var_dump.try_call(vec![&"abc"]); +} + +#[php_function] +pub fn test_method() -> () { + let f = Function::try_from_method("ClassName", "staticMethod").unwrap(); + let _ = f.try_call(vec![&"abc"]); +} + +# fn main() {} +``` diff --git a/guide/src/types/object.md b/guide/src/types/object.md index c953d99..57f9371 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -15,6 +15,25 @@ object. ## Examples +### Calling a method + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::{prelude::*, types::ZendObject}; + +// Take an object reference and also return it. +#[php_function] +pub fn take_obj(obj: &mut ZendObject) -> () { + let _ = obj.try_call_method("hello", vec![&"arg1", &"arg2"]); +} +# #[php_module] +# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +# module +# } +# fn main() {} +``` + ### Taking an object reference ```rust,no_run diff --git a/src/args.rs b/src/args.rs index ef32334..7cd3f62 100644 --- a/src/args.rs +++ b/src/args.rs @@ -123,6 +123,7 @@ impl<'a> Arg<'a> { /// # Parameters /// /// * `params` - A list of parameters to call the function with. + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { self.zval.as_ref().ok_or(Error::Callable)?.try_call(params) } diff --git a/src/builders/class.rs b/src/builders/class.rs index 91a6a01..28e8463 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/abi.rs b/src/describe/abi.rs index a4e5885..a184cdb 100644 --- a/src/describe/abi.rs +++ b/src/describe/abi.rs @@ -32,7 +32,9 @@ impl Deref for Vec { impl Drop for Vec { fn drop(&mut self) { - unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)) }; + unsafe { + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)); + }; } } diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 65c0e76..065b275 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -52,7 +52,7 @@ impl ToStub for Module { // Inserts a value into the entries hashmap. Takes a key and an entry, creating // the internal vector if it doesn't already exist. let mut insert = |ns, entry| { - let bucket = entries.entry(ns).or_insert_with(StdVec::new); + let bucket = entries.entry(ns).or_default(); bucket.push(entry); }; diff --git a/src/embed/embed.c b/src/embed/embed.c new file mode 100644 index 0000000..ae7d8bc --- /dev/null +++ b/src/embed/embed.c @@ -0,0 +1,15 @@ +#include "embed.h" + +// 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 *result = NULL; + + PHP_EMBED_START_BLOCK(argc, argv) + + result = callback(ctx); + + PHP_EMBED_END_BLOCK() + + return result; +} diff --git a/src/embed/embed.h b/src/embed/embed.h new file mode 100644 index 0000000..beabffb --- /dev/null +++ b/src/embed/embed.h @@ -0,0 +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); diff --git a/src/embed/ffi.rs b/src/embed/ffi.rs new file mode 100644 index 0000000..b52ce6a --- /dev/null +++ b/src/embed/ffi.rs @@ -0,0 +1,16 @@ +//! Raw FFI bindings to the Zend API. + +#![allow(clippy::all)] +#![allow(warnings)] + +use std::ffi::{c_char, c_int, c_void}; + +#[link(name = "wrapper")] +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) -> *const c_void, + ctx: *const c_void, + ) -> *mut c_void; +} diff --git a/src/embed/mod.rs b/src/embed/mod.rs new file mode 100644 index 0000000..d8f914f --- /dev/null +++ b/src/embed/mod.rs @@ -0,0 +1,277 @@ +//! 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 + +mod ffi; + +use crate::boxed::ZBox; +use crate::embed::ffi::ext_php_rs_embed_callback; +use crate::ffi::{ + _zend_file_handle__bindgen_ty_1, php_execute_script, zend_eval_string, zend_file_handle, + zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS, +}; +use crate::types::{ZendObject, Zval}; +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; + +pub struct Embed; + +#[derive(Debug)] +pub enum EmbedError { + InitError, + ExecuteError(Option>), + ExecuteScriptError, + InvalidEvalString(NulError), + InvalidPath, + CatchError, +} + +impl EmbedError { + pub fn is_bailout(&self) -> bool { + matches!(self, EmbedError::CatchError) + } +} + +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 + /// + /// # Returns + /// + /// * `Ok(())` - The script was executed successfully + /// * `Err(EmbedError)` - An error occured during the execution of the + /// script + /// + /// # Example + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let result = Embed::run_script("src/embed/test-script.php"); + /// + /// assert!(result.is_ok()); + /// }); + /// ``` + pub fn run_script>(path: P) -> Result<(), EmbedError> { + let path = match path.as_ref().to_str() { + Some(path) => match CString::new(path) { + Ok(path) => path, + Err(err) => return Err(EmbedError::InvalidEvalString(err)), + }, + None => return Err(EmbedError::InvalidPath), + }; + + let mut file_handle = zend_file_handle { + handle: _zend_file_handle__bindgen_ty_1 { fp: null_mut() }, + filename: null_mut(), + opened_path: null_mut(), + type_: 0, + primary_script: false, + in_list: false, + buf: null_mut(), + len: 0, + }; + + unsafe { + zend_stream_init_filename(&mut file_handle, path.as_ptr()); + } + + 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 + /// + /// # 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 + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let _ = Embed::eval("$foo = 'foo';"); + /// let foo = Embed::eval("$foo;"); + /// assert!(foo.is_ok()); + /// assert_eq!(foo.unwrap().string().unwrap(), "foo"); + /// }); + /// ``` + 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 + let _guard = RUN_FN_LOCK.write(); + + let panic = unsafe { + ext_php_rs_embed_callback( + 0, + null_mut(), + 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 + /// + /// # Returns + /// + /// * `Ok(Zval)` - The result of the evaluation + /// * `Err(EmbedError)` - An error occured during the evaluation + /// + /// # Example + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let foo = Embed::eval("$foo = 'foo';"); + /// assert!(foo.is_ok()); + /// }); + /// ``` + pub fn eval(code: &str) -> Result { + let cstr = match CString::new(code) { + Ok(cstr) => cstr, + Err(err) => return Err(EmbedError::InvalidEvalString(err)), + }; + + let mut result = Zval::new(); + + 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 _, + ) + }); + + match exec_result { + Err(_) => Err(EmbedError::CatchError), + Ok(ZEND_RESULT_CODE_SUCCESS) => Ok(result), + Ok(_) => Err(EmbedError::ExecuteError(ExecutorGlobals::take_exception())), + } + } +} + +#[cfg(test)] +mod tests { + use super::Embed; + + #[test] + fn test_run() { + Embed::run(|| { + let result = Embed::eval("$foo = 'foo';"); + + assert!(result.is_ok()); + }); + } + + #[test] + fn test_run_error() { + Embed::run(|| { + let result = Embed::eval("stupid code;"); + + assert!(!result.is_ok()); + }); + } + + #[test] + fn test_run_script() { + Embed::run(|| { + let result = Embed::run_script("src/embed/test-script.php"); + + assert!(result.is_ok()); + + let zval = Embed::eval("$foo;").unwrap(); + + assert!(zval.is_object()); + + let obj = zval.object().unwrap(); + + assert_eq!(obj.get_class_name().unwrap(), "Test"); + }); + } + + #[test] + fn test_run_script_error() { + Embed::run(|| { + let result = Embed::run_script("src/embed/test-script-exception.php"); + + 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("str_repeat('a', 100_000_000_000_000);"); + + assert!(result.is_err()); + assert!(result.unwrap_err().is_bailout()); + }); + } +} diff --git a/src/embed/test-script-exception.php b/src/embed/test-script-exception.php new file mode 100644 index 0000000..6674846 --- /dev/null +++ b/src/embed/test-script-exception.php @@ -0,0 +1,3 @@ + write!(f, "Invalid Utf8 byte sequence."), Error::Callable => write!(f, "Could not call given function."), + Error::Object => write!(f, "An object was expected."), Error::InvalidException(flags) => { write!(f, "Invalid exception type was thrown: {flags:?}") } diff --git a/src/exception.rs b/src/exception.rs index d1504b4..bf06487 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -1,12 +1,14 @@ //! Types and functions used for throwing exceptions from Rust to PHP. -use std::ffi::CString; +use std::{ffi::CString, fmt::Debug}; 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 465b509..28ba625 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -29,6 +29,13 @@ extern "C" { 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 d2fbc37..9e814d2 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,9 +8,10 @@ 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, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, + E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, 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, @@ -174,6 +175,16 @@ bitflags! { } } +bitflags! { + /// Represents permissions for where a configuration setting may be set. + pub struct IniEntryPermission: u32 { + const User = PHP_INI_USER; + const PerDir = PHP_INI_PERDIR; + const System = PHP_INI_SYSTEM; + const All = PHP_INI_ALL; + } +} + bitflags! { /// Represents error types when used via php_error_docref for example. pub struct ErrorType: u32 { @@ -194,6 +205,7 @@ bitflags! { const UserDeprecated = E_USER_DEPRECATED; } } + #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] pub enum FunctionType { Internal, @@ -234,6 +246,7 @@ pub enum DataType { Mixed, Bool, Ptr, + Indirect, } impl Default for DataType { @@ -257,6 +270,7 @@ 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, @@ -325,6 +339,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); @@ -367,6 +382,7 @@ impl Display for DataType { DataType::Bool => write!(f, "Bool"), DataType::Mixed => write!(f, "Mixed"), DataType::Ptr => write!(f, "Pointer"), + DataType::Indirect => write!(f, "Indirect"), } } } @@ -376,9 +392,9 @@ 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_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, - IS_TRUE, IS_UNDEF, IS_VOID, + 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, }; use std::convert::TryFrom; @@ -402,7 +418,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/lib.rs b/src/lib.rs index 720a23d..ee1d68f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ pub mod class; pub mod closure; pub mod constant; pub mod describe; +#[cfg(feature = "embed")] +pub mod embed; #[doc(hidden)] pub mod internal; pub mod props; @@ -35,6 +37,7 @@ pub mod zend; /// A module typically glob-imported containing the typically required macros /// and imports. pub mod prelude { + pub use crate::builders::ModuleBuilder; #[cfg(any(docs, feature = "closure"))] #[cfg_attr(docs, doc(cfg(feature = "closure")))] diff --git a/src/types/array.rs b/src/types/array.rs index 28bf47a..c40b8f3 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -187,6 +187,33 @@ impl ZendHashTable { unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() } } + /// Attempts to retrieve a value from the hash table with a string key. + /// + /// # Parameters + /// + /// * `key` - The key to search for in the hash table. + /// + /// # Returns + /// + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. + /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("test", "hello world"); + /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world")); + /// ``` + pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> { + let str = CString::new(key).ok()?; + unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() } + } + /// Attempts to retrieve a value from the hash table with an index. /// /// # Parameters @@ -213,6 +240,32 @@ impl ZendHashTable { unsafe { zend_hash_index_find(self, key).as_ref() } } + /// Attempts to retrieve a value from the hash table with an index. + /// + /// # Parameters + /// + /// * `key` - The key to search for in the hash table. + /// + /// # Returns + /// + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. + /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push(100); + /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100)); + /// ``` + pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> { + unsafe { zend_hash_index_find(self, key).as_mut() } + } + /// Attempts to remove a value from the hash table with a string key. /// /// # Parameters @@ -715,7 +768,7 @@ impl<'a> FromZval<'a> for &'a ZendHashTable { } /////////////////////////////////////////// -//// HashMap +/// HashMap /////////////////////////////////////////// impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap @@ -784,7 +837,7 @@ where } /////////////////////////////////////////// -//// Vec +/// Vec /////////////////////////////////////////// impl<'a, T> TryFrom<&'a ZendHashTable> for Vec diff --git a/src/types/callable.rs b/src/types/callable.rs index c1e446e..c22474b 100644 --- a/src/types/callable.rs +++ b/src/types/callable.rs @@ -99,6 +99,7 @@ impl<'a> ZendCallable<'a> { /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); /// assert_eq!(result.long(), Some(1)); /// ``` + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { if !self.0.is_callable() { return Err(Error::Callable); diff --git a/src/types/class_object.rs b/src/types/class_object.rs index f926c02..c9ac51e 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() @@ -155,18 +157,19 @@ impl ZendClassObject { /// # Parameters /// /// * `obj` - The zend object to get the [`ZendClassObject`] for. + #[allow(clippy::needless_pass_by_ref_mut)] pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> { Self::_from_zend_obj(std) } 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/object.rs b/src/types/object.rs index 606c483..f17b65f 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -1,16 +1,17 @@ //! 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}, class::RegisteredClass, - convert::{FromZendObject, FromZval, FromZvalMut, IntoZval}, + convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn}, error::{Error, Result}, ffi::{ - ext_php_rs_zend_object_release, zend_call_known_function, zend_object, zend_objects_new, - HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, + ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function, + zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new, HashTable, + ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, }, flags::DataType, rc::PhpRc, @@ -41,7 +42,18 @@ impl ZendObject { // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to // `*mut` is valid as the function will not mutate `ce`. unsafe { - let ptr = zend_objects_new(ce as *const _ as *mut _); + let ptr = match ce.__bindgen_anon_2.create_object { + None => { + let ptr = zend_objects_new(ce as *const _ as *mut _); + if ptr.is_null() { + panic!("Failed to allocate memory for Zend object") + } + object_properties_init(ptr, ce as *const _ as *mut _); + ptr + } + Some(v) => v(ce as *const _ as *mut _), + }; + ZBox::from_raw( ptr.as_mut() .expect("Failed to allocate memory for Zend object"), @@ -121,6 +133,38 @@ impl ZendObject { (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) } + #[inline(always)] + pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { + let mut retval = Zval::new(); + let len = params.len(); + let params = params + .into_iter() + .map(|val| val.as_zval(false)) + .collect::>>()?; + let packed = params.into_boxed_slice(); + + unsafe { + let res = zend_hash_str_find_ptr_lc( + &(*self.ce).function_table, + name.as_ptr() as *const c_char, + name.len(), + ) as *mut zend_function; + if res.is_null() { + return Err(Error::Callable); + } + zend_call_known_function( + res, + self as *const _ as *mut _, + self.ce, + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } /// Attempts to read a property from the Object. Returns a result containing /// the value of the property if it exists and can be read, and an /// [`Error`] otherwise. diff --git a/src/types/string.rs b/src/types/string.rs index 94cc1f5..efa5a68 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -453,3 +453,23 @@ impl<'a> FromZval<'a> for &'a str { zval.str() } } + +#[cfg(test)] +#[cfg(feature = "embed")] +mod tests { + use crate::embed::Embed; + + #[test] + fn test_string() { + Embed::run(|| { + let result = Embed::eval("'foo';"); + + assert!(result.is_ok()); + + let zval = result.as_ref().unwrap(); + + assert!(zval.is_string()); + assert_eq!(zval.string().unwrap(), "foo"); + }); + } +} diff --git a/src/types/zval.rs b/src/types/zval.rs index d45cdad..97def9a 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -206,6 +206,31 @@ impl Zval { } } + #[inline(always)] + pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { + self.object() + .ok_or(Error::Object)? + .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() { @@ -257,6 +282,7 @@ impl Zval { /// # Parameters /// /// * `params` - A list of parameters to call the function with. + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { self.callable().ok_or(Error::Callable)?.try_call(params) } @@ -321,6 +347,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; @@ -593,6 +624,7 @@ impl Debug for Zval { DataType::ConstantExpression => field!(Option::<()>::None), DataType::Void => field!(Option::<()>::None), DataType::Bool => field!(self.bool()), + DataType::Indirect => field!(self.indirect()), // SAFETY: We are not accessing the pointer. DataType::Ptr => field!(unsafe { self.ptr::() }), }; diff --git a/src/wrapper.c b/src/wrapper.c index c88e2da..fcb0c28 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -52,7 +52,6 @@ php_core_globals *ext_php_rs_process_globals() { #endif } - sapi_globals_struct *ext_php_rs_sapi_globals() { #ifdef ZTS #ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE @@ -65,7 +64,6 @@ sapi_globals_struct *ext_php_rs_sapi_globals() { #endif } - php_file_globals *ext_php_rs_file_globals() { #ifdef ZTS return TSRMG_FAST_BULK(file_globals_id, php_file_globals *); @@ -73,3 +71,21 @@ php_file_globals *ext_php_rs_file_globals() { 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 f2747b7..9aef14f 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -22,8 +22,9 @@ #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" -#include "SAPI.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); @@ -37,3 +38,6 @@ 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 6e7f796..4c3c23d 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,6 +1,12 @@ //! Builder and objects for creating classes in the PHP world. -use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; +use crate::{ + boxed::ZBox, + ffi::zend_class_entry, + flags::ClassFlags, + types::{ZendObject, ZendStr}, + zend::ExecutorGlobals, +}; use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; /// A PHP class entry. @@ -22,6 +28,17 @@ impl ClassEntry { } } + /// Creates a new [`ZendObject`], returned inside an [`ZBox`] + /// wrapper. + /// + /// # Panics + /// + /// Panics when allocating memory for the new object fails. + #[allow(clippy::new_ret_no_self)] + pub fn new(&self) -> ZBox { + ZendObject::new(self) + } + /// Returns the class flags. pub fn flags(&self) -> ClassFlags { ClassFlags::from_bits_truncate(self.ce_flags) diff --git a/src/zend/ex.rs b/src/zend/ex.rs index dada00e..cb389cb 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 ba889d7..6e6dd1a 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -2,7 +2,18 @@ use std::{fmt::Debug, os::raw::c_char, ptr}; -use crate::ffi::zend_function_entry; +use crate::{ + convert::IntoZvalDyn, + error::Result, + ffi::{ + zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry, + zend_hash_str_find_ptr_lc, + }, + flags::FunctionType, + types::Zval, +}; + +use super::ClassEntry; /// A Zend function entry. pub type FunctionEntry = zend_function_entry; @@ -36,3 +47,86 @@ impl FunctionEntry { Box::into_raw(Box::new(self)) } } + +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 c_char, name.len()); + if res.is_null() { + return None; + } + Some(*res) + } + } + pub fn try_from_method(class: &str, name: &str) -> Option { + match ClassEntry::try_find(class) { + None => None, + Some(ce) => unsafe { + let res = zend_hash_str_find_ptr_lc( + &ce.function_table, + name.as_ptr() as *const c_char, + name.len(), + ) as *mut zend_function; + if res.is_null() { + return None; + } + Some(*res) + }, + } + } + + /// Attempts to call the callable with a list of arguments to pass to the + /// function. + /// + /// You should not call this function directly, rather through the + /// [`call_user_func`] macro. + /// + /// # Parameters + /// + /// * `params` - A list of parameters to call the function with. + /// + /// # Returns + /// + /// Returns the result wrapped in [`Ok`] upon success. If calling the + /// callable fails, or an exception is thrown, an [`Err`] is returned. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendCallable; + /// + /// let strpos = ZendCallable::try_from_name("strpos").unwrap(); + /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); + /// assert_eq!(result.long(), Some(1)); + /// ``` + #[inline(always)] + pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { + let mut retval = Zval::new(); + let len = params.len(); + let params = params + .into_iter() + .map(|val| val.as_zval(false)) + .collect::>>()?; + let packed = params.into_boxed_slice(); + + unsafe { + zend_call_known_function( + self as *const _ as *mut _, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } +} diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 50afb52..8a5c71d 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,5 +1,6 @@ //! Types related to the PHP executor, sapi and process globals. -use std::ffi::CStr; + +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::slice; use std::str; @@ -15,15 +16,24 @@ use crate::ffi::{ sapi_globals_struct, sapi_header_struct, sapi_headers_struct, sapi_request_info, 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, + _sapi_globals_struct, _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals, + ext_php_rs_sapi_globals, ext_php_rs_sapi_module, zend_ini_entry, }; use crate::types::{ZendHashTable, ZendObject, ZendStr}; use super::linked_list::ZendLinkedListIterator; +use crate::types::{ZendHashTable, ZendObject}; /// Stores global variables used in the PHP executor. pub type ExecutorGlobals = _zend_executor_globals; +/// Stores global SAPI variables used in the PHP executor. +pub type SapiGlobals = _sapi_globals_struct; + +/// 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. /// @@ -62,6 +72,41 @@ 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(), + ) + } + }); + } + } + ini_hash_map + } + /// Attempts to retrieve the global constants table. pub fn constants(&self) -> Option<&ZendHashTable> { unsafe { self.zend_constants.as_ref() } @@ -97,6 +142,87 @@ impl ExecutorGlobals { } } } + + /// Cancel a requested an interrupt of the PHP VM. + pub fn cancel_interrupt(&mut self) { + cfg_if::cfg_if! { + if #[cfg(php82)] { + unsafe { + zend_atomic_bool_store(&mut self.vm_interrupt, false); + } + } else { + self.vm_interrupt = true; + } + } + } +} + +impl SapiGlobals { + /// Returns a reference to the PHP SAPI 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() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_globals().as_ref() } + .expect("Static executor globals were invalid"); + let guard = SAPI_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_globals().as_mut() } + .expect("Static executor globals were invalid"); + let guard = SAPI_LOCK.write(); + GlobalWriteGuard { globals, guard } + } +} + +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. @@ -430,6 +556,18 @@ 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_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. pub struct GlobalReadGuard { diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index 11d220b..7e88a7e 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 new file mode 100644 index 0000000..d9c8f64 --- /dev/null +++ b/src/zend/ini_entry_def.rs @@ -0,0 +1,58 @@ +//! Builder for creating inis and methods in PHP. +//! See for details. + +use std::{ffi::CString, os::raw::c_char, ptr}; + +use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniEntryPermission}; + +/// 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)`. +pub type IniEntryDef = zend_ini_entry_def; + +impl IniEntryDef { + /// Returns an empty ini entry, signifying the end of a ini list. + pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self { + let mut template = Self::end(); + let name = CString::new(name).expect("Unable to create CString from name"); + let value = CString::new(default_value).expect("Unable to create CString from value"); + template.name_length = name.as_bytes().len() as _; + template.name = name.into_raw(); + template.value_length = value.as_bytes().len() as _; + template.value = value.into_raw(); + template.modifiable = IniEntryPermission::PerDir.bits() as _; + template.modifiable = permission.bits() as _; + template + } + + /// Returns an empty ini entry def, signifying the end of a ini list. + pub fn end() -> Self { + Self { + name: ptr::null() as *const c_char, + on_modify: None, + mh_arg1: std::ptr::null_mut(), + mh_arg2: std::ptr::null_mut(), + mh_arg3: std::ptr::null_mut(), + value: std::ptr::null_mut(), + displayer: None, + modifiable: 0, + value_length: 0, + name_length: 0, + } + } + + /// Converts the ini entry into a raw and pointer, releasing it to the + /// C world. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } + + pub fn register(mut entries: Vec, module_number: i32) { + entries.push(Self::end()); + let entries = Box::into_raw(entries.into_boxed_slice()) as *const Self; + + unsafe { zend_register_ini_entries(entries, module_number) }; + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 9426aae..faf4187 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -8,14 +8,20 @@ mod function; mod globals; mod handlers; mod linked_list; +mod ini_entry_def; mod module; +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; pub use class::ClassEntry; pub use ex::ExecuteData; +pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use globals::FileGlobals; @@ -23,7 +29,14 @@ pub use globals::ProcessGlobals; pub use globals::SapiGlobals; pub use handlers::ZendObjectHandlers; pub use linked_list::ZendLinkedList; +pub use globals::SapiGlobals; +pub use globals::SapiModule; +pub use handlers::ZendObjectHandlers; +pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; +#[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"; @@ -47,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/try_catch.rs b/src/zend/try_catch.rs new file mode 100644 index 0000000..37cd896 --- /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 0000000..f1ef22b --- /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 +} diff --git a/unix_build.rs b/unix_build.rs index 9bdd446..8be6fd8 100644 --- a/unix_build.rs +++ b/unix_build.rs @@ -55,4 +55,11 @@ impl<'a> PHPProvider<'a> for Provider { fn get_defines(&self) -> Result> { Ok(vec![]) } + + fn print_extra_link_args(&self) -> Result<()> { + #[cfg(feature = "embed")] + println!("cargo:rustc-link-lib=php"); + + Ok(()) + } }