diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adb25c7f..c03cb061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,9 @@ on: branches: [ "**" ] env: - CARGO_TERM_COLOR: always - # RUST_BACKTRACE: 1 # RUST_LOG: debug + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 RUSTFLAGS: "-D warnings" jobs: @@ -26,6 +26,7 @@ jobs: - "7.2" - "7.3" - "7.4" + - "8.0" runs-on: ${{ matrix.os }} steps: @@ -41,38 +42,43 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install Rust + - name: Install Rust Nightly uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true - components: rustfmt, clippy + components: rustfmt - # - name: Install Cargo make - # uses: actions-rs/cargo@v1 - # with: - # command: install - # args: cargo-make + - name: Install Rust Stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: rustfmt - name: Cargo fmt uses: actions-rs/cargo@v1 with: + toolchain: nightly command: fmt args: --all -- --check - name: Cargo build uses: actions-rs/cargo@v1 with: + toolchain: stable command: build args: --release - name: Cargo test uses: actions-rs/cargo@v1 with: + toolchain: stable command: test args: --release -- --nocapture - name: Cargo doc uses: actions-rs/cargo@v1 with: + toolchain: stable command: doc diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..d9ba5fdb --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Crate" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 605e0727..c1249312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,5 @@ members = [ # internal "examples/hello", - "examples/hello-class", - "examples/mini-curl", +# "examples/mini-curl", ] diff --git a/README.md b/README.md index e3abe3a4..391cf6a1 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,119 @@ # PHPer -A library that allows us to write PHP extensions using pure Rust and using safe Rust whenever possible. +[![crates](https://img.shields.io/crates/v/phper?style=flat-square)](https://crates.io/crates/phper) +[![](https://img.shields.io/docsrs/phper?style=flat-square)](https://docs.rs/phper) -***Now the project is still under development.*** +A library that allows us to write PHP extensions using pure Rust and using safe Rust whenever possible. ## Requirement -- rust nightly channel (now) -- libclang >= 9 +### Necessary + +**libclang** version >= 9 + +**php** version >= 7 + +### Tested Support + +**os** + +- linux + +**php** + +*version* + +- 7.0 +- 7.1 +- 7.2 +- 7.3 +- 7.4 +- 8.0 + +*mode* + +- nts + +*sapi* + +- cli ## Usage -Now see [examples](examples). +1. Make sure `libclang` and `php` is installed. + +```bash +# If you are using debian like linux system: +sudo apt install libclang-10-dev php-cli +``` - + +7. Edit your `php.ini`, add the below line. + +```ini +extension = myapp +``` + +8. Enjoy. + +## examples + +See [examples](https://github.com/jmjoy/phper/tree/master/examples). ## License -[Unlicense](LICENSE). +[Unlicense](https://github.com/jmjoy/phper/blob/master/LICENSE). diff --git a/examples/hello-class/Cargo.toml b/examples/hello-class/Cargo.toml deleted file mode 100644 index f541aa45..00000000 --- a/examples/hello-class/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "hello_class" -version = "0.2.0" -authors = ["jmjoy <918734043@qq.com>"] -edition = "2018" -publish = false -license = "Unlicense" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -phper = { version = "0.2", path = "../../phper" } - -[dev-dependencies] -phper-test = { version = "0.2", path = "../../phper-test" } - -[build-dependencies] -phper-build = { version = "0.2", path = "../../phper-build" } diff --git a/examples/hello-class/Makefile.toml b/examples/hello-class/Makefile.toml deleted file mode 100644 index 5f4e9061..00000000 --- a/examples/hello-class/Makefile.toml +++ /dev/null @@ -1,30 +0,0 @@ -[env] -PHP_CONFIG = "php-config" -TARGET_DIR = "${CARGO_MAKE_WORKING_DIRECTORY}/../../target" - -[env.development] -TARGET_BUILD_DIR = "${TARGET_DIR}/debug" -BUILD_ARGS = "--" -TEST_ARGS = "--" - -[env.production] -TARGET_BUILD_DIR = "${TARGET_DIR}/release" -BUILD_ARGS = "--release" -TEST_ARGS = "--release" - -[tasks.build] -command = "cargo" -args = ["build", "${BUILD_ARGS}"] - -[tasks.test] -command = "cargo" -args = ["test", "${TEST_ARGS}", "--", "--nocapture"] - -[tasks.install] -dependencies = ["build"] -script = [ - """ - cp ${TARGET_BUILD_DIR}/lib${CARGO_MAKE_CRATE_NAME}.so `${PHP_CONFIG} --extension-dir`/${CARGO_MAKE_CRATE_NAME}.so && \ - echo Installing shared extensions: `${PHP_CONFIG} --extension-dir` - """ -] diff --git a/examples/hello-class/build.rs b/examples/hello-class/build.rs deleted file mode 100644 index 8be39f4f..00000000 --- a/examples/hello-class/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - phper_build::register_configures(); -} diff --git a/examples/hello-class/src/lib.rs b/examples/hello-class/src/lib.rs deleted file mode 100644 index 85245ca6..00000000 --- a/examples/hello-class/src/lib.rs +++ /dev/null @@ -1,148 +0,0 @@ -use phper::{ - c_str_ptr, php_fn, php_function, php_minfo, php_minfo_function, php_minit, php_minit_function, - php_mshutdown, php_mshutdown_function, php_rinit, php_rinit_function, php_rshutdown, - php_rshutdown_function, - sys::{ - php_info_print_table_end, php_info_print_table_row, php_info_print_table_start, - zend_function_entry, OnUpdateBool, OnUpdateString, PHP_INI_SYSTEM, - }, - zend::{ - api::{FunctionEntries, ModuleGlobals}, - compile::{create_zend_arg_info, MultiInternalArgInfo, Visibility}, - ini::IniEntries, - modules::{ModuleArgs, ModuleEntry, ModuleEntryBuilder}, - types::{ClassEntry, ExecuteData, SetVal, Value}, - }, - zend_get_module, -}; -use std::{os::raw::c_char, ptr::null}; - -static HELLO_CLASS_CE: ClassEntry = ClassEntry::new(); - -static HELLO_CLASS_ENABLE: ModuleGlobals = ModuleGlobals::new(false); -static HELLO_CLASS_DESCRIPTION: ModuleGlobals<*const c_char> = ModuleGlobals::new(null()); - -static INI_ENTRIES: IniEntries<2> = IniEntries::new([ - HELLO_CLASS_ENABLE.create_ini_entry( - "hello_class.enable", - "1", - Some(OnUpdateBool), - PHP_INI_SYSTEM, - ), - HELLO_CLASS_DESCRIPTION.create_ini_entry( - "hello_class.description", - "", - Some(OnUpdateString), - PHP_INI_SYSTEM, - ), -]); - -#[php_minit_function] -fn module_init(args: ModuleArgs) -> bool { - args.register_ini_entries(&INI_ENTRIES); - HELLO_CLASS_CE.init("HelloClass", &HELLO_CLASS_METHODS); - HELLO_CLASS_CE.declare_property("name", "world", Visibility::Public); - true -} - -#[php_mshutdown_function] -fn module_shutdown(args: ModuleArgs) -> bool { - args.unregister_ini_entries(); - true -} - -#[php_rinit_function] -fn request_init(_args: ModuleArgs) -> bool { - true -} - -#[php_rshutdown_function] -fn request_shutdown(_args: ModuleArgs) -> bool { - true -} - -#[php_minfo_function] -fn module_info(module: &ModuleEntry) { - unsafe { - php_info_print_table_start(); - php_info_print_table_row( - 2, - c_str_ptr!("hello_class.version"), - (*module.as_ptr()).version, - ); - php_info_print_table_row( - 2, - c_str_ptr!("hello_class.build_id"), - (*module.as_ptr()).build_id, - ); - php_info_print_table_row( - 2, - c_str_ptr!("hello_class.enable"), - if HELLO_CLASS_ENABLE.get() { - c_str_ptr!("1") - } else { - c_str_ptr!("0") - }, - ); - php_info_print_table_row( - 2, - c_str_ptr!("hello_class.description"), - HELLO_CLASS_DESCRIPTION.get(), - ); - php_info_print_table_end(); - } -} - -static ARG_INFO_HELLO_CLASS_SAY_HELLO: MultiInternalArgInfo<1> = MultiInternalArgInfo::new( - 1, - false, - [create_zend_arg_info(c_str_ptr!("prefix"), false)], -); - -static HELLO_CLASS_METHODS: FunctionEntries<1> = FunctionEntries::new([zend_function_entry { - fname: c_str_ptr!("sayHello"), - handler: Some(php_fn!(hello_class_get_hello)), - arg_info: ARG_INFO_HELLO_CLASS_SAY_HELLO.as_ptr(), - num_args: 1, - flags: 0, -}]); - -#[php_function] -pub fn hello_class_get_hello(execute_data: &mut ExecuteData) -> impl SetVal { - execute_data.parse_parameters::<()>().map(|_| { - let this = execute_data.get_this(); - let val = HELLO_CLASS_CE.read_property(this, "name"); - let value = val.try_into_value().unwrap(); - - if let Value::CStr(value) = value { - Some(format!("Hello, {}!", value.to_str().unwrap())) - } else { - None - } - }) -} - -static FUNCTION_ENTRIES: FunctionEntries<1> = FunctionEntries::new([zend_function_entry { - fname: c_str_ptr!("hello_class_get_hello"), - handler: Some(php_fn!(hello_class_get_hello)), - arg_info: null(), - num_args: 0, - flags: 0, -}]); - -static MODULE_ENTRY: ModuleEntry = ModuleEntryBuilder::new( - c_str_ptr!(env!("CARGO_PKG_NAME")), - c_str_ptr!(env!("CARGO_PKG_VERSION")), -) -.functions(FUNCTION_ENTRIES.as_ptr()) -.module_startup_func(php_minit!(module_init)) -.module_shutdown_func(php_mshutdown!(module_shutdown)) -.request_startup_func(php_rinit!(request_init)) -.request_shutdown_func(php_rshutdown!(request_shutdown)) -.info_func(php_minfo!(module_info)) -.build(); - -#[zend_get_module] -pub fn get_module() -> &'static ModuleEntry { - &MODULE_ENTRY -} diff --git a/examples/hello-class/tests/integration.rs b/examples/hello-class/tests/integration.rs deleted file mode 100644 index 573b6660..00000000 --- a/examples/hello-class/tests/integration.rs +++ /dev/null @@ -1,17 +0,0 @@ -use phper_test::test_php_scripts; -use std::{env, path::Path}; - -#[test] -fn test_php() { - test_php_scripts( - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - env!("CARGO_PKG_NAME"), - &[Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("php") - .join("test.php")], - ); -} diff --git a/examples/hello-class/tests/php/test.php b/examples/hello-class/tests/php/test.php deleted file mode 100644 index 65f03f2c..00000000 --- a/examples/hello-class/tests/php/test.php +++ /dev/null @@ -1,13 +0,0 @@ -sayHello(), "Hello, world!"); - -function assert_eq($left, $right) { - if ($left !== $right) { - throw new Exception("left != right,\n left: {$left},\n right: {$right};"); - } -} \ No newline at end of file diff --git a/examples/hello/Cargo.toml b/examples/hello/Cargo.toml index 87e3263e..d650a424 100644 --- a/examples/hello/Cargo.toml +++ b/examples/hello/Cargo.toml @@ -12,6 +12,7 @@ license = "Unlicense" crate-type = ["cdylib"] [dependencies] +once_cell = "1.7.2" phper = { version = "0.2", path = "../../phper" } [dev-dependencies] diff --git a/examples/hello/Makefile.toml b/examples/hello/Makefile.toml deleted file mode 100644 index 5f4e9061..00000000 --- a/examples/hello/Makefile.toml +++ /dev/null @@ -1,30 +0,0 @@ -[env] -PHP_CONFIG = "php-config" -TARGET_DIR = "${CARGO_MAKE_WORKING_DIRECTORY}/../../target" - -[env.development] -TARGET_BUILD_DIR = "${TARGET_DIR}/debug" -BUILD_ARGS = "--" -TEST_ARGS = "--" - -[env.production] -TARGET_BUILD_DIR = "${TARGET_DIR}/release" -BUILD_ARGS = "--release" -TEST_ARGS = "--release" - -[tasks.build] -command = "cargo" -args = ["build", "${BUILD_ARGS}"] - -[tasks.test] -command = "cargo" -args = ["test", "${TEST_ARGS}", "--", "--nocapture"] - -[tasks.install] -dependencies = ["build"] -script = [ - """ - cp ${TARGET_BUILD_DIR}/lib${CARGO_MAKE_CRATE_NAME}.so `${PHP_CONFIG} --extension-dir`/${CARGO_MAKE_CRATE_NAME}.so && \ - echo Installing shared extensions: `${PHP_CONFIG} --extension-dir` - """ -] diff --git a/examples/hello/README.md b/examples/hello/README.md new file mode 100644 index 00000000..750c2142 --- /dev/null +++ b/examples/hello/README.md @@ -0,0 +1,28 @@ +# hello + +Hello world example. + +## Environment + +```bash +# Specify if php isn't installed globally. +export PHP_CONFIG = +``` + +## Build + +```bash +cargo build --release +``` + +## Test + +```bash +cargo test --release +``` + +## Install + +```bash +cargo run --release -- install +``` diff --git a/examples/hello/src/lib.rs b/examples/hello/src/lib.rs index 2bb7f59f..0a40f298 100644 --- a/examples/hello/src/lib.rs +++ b/examples/hello/src/lib.rs @@ -1,101 +1,82 @@ use phper::{ - c_str_ptr, php_fn, php_function, php_minfo, php_minfo_function, php_minit, php_minit_function, - php_mshutdown, php_mshutdown_function, php_rinit, php_rinit_function, php_rshutdown, - php_rshutdown_function, - sys::{ - php_info_print_table_end, php_info_print_table_row, php_info_print_table_start, - zend_function_entry, OnUpdateBool, PHP_INI_SYSTEM, - }, - zend::{ - api::{FunctionEntries, ModuleGlobals}, - compile::{create_zend_arg_info, MultiInternalArgInfo}, - ini::IniEntries, - modules::{ModuleArgs, ModuleEntry, ModuleEntryBuilder}, - types::{ExecuteData, SetVal}, - }, - zend_get_module, + arrays::Array, + classes::{StdClass, This}, + functions::Argument, + ini::Policy, + modules::{Module, ModuleArgs}, + php_get_module, + values::{SetVal, Val}, }; -static SIMPLE_ENABLE: ModuleGlobals = ModuleGlobals::new(false); - -static INI_ENTRIES: IniEntries<1> = IniEntries::new([SIMPLE_ENABLE.create_ini_entry( - "hello.enable", - "1", - Some(OnUpdateBool), - PHP_INI_SYSTEM, -)]); - -#[php_minit_function] -fn module_init(args: ModuleArgs) -> bool { - args.register_ini_entries(&INI_ENTRIES); +fn module_init(_args: ModuleArgs) -> bool { true } -#[php_mshutdown_function] -fn module_shutdown(args: ModuleArgs) -> bool { - args.unregister_ini_entries(); - true +fn say_hello(arguments: &mut [Val]) -> impl SetVal { + let name = arguments[0].as_string(); + format!("Hello, {}!\n", name) } -#[php_rinit_function] -fn request_init(_args: ModuleArgs) -> bool { - true +fn throw_exception(_: &mut [Val]) -> phper::Result<()> { + Err(phper::Error::other("I am sorry")) } -#[php_rshutdown_function] -fn request_shutdown(_args: ModuleArgs) -> bool { - true -} +#[php_get_module] +pub fn get_module(module: &mut Module) { + // set module metadata + module.set_name(env!("CARGO_PKG_NAME")); + module.set_version(env!("CARGO_PKG_VERSION")); + module.set_author(env!("CARGO_PKG_AUTHORS")); -#[php_minfo_function] -fn module_info(module: &ModuleEntry) { - unsafe { - php_info_print_table_start(); - php_info_print_table_row(2, c_str_ptr!("hello.version"), (*module.as_ptr()).version); - php_info_print_table_row( - 2, - c_str_ptr!("hello.enable"), - if SIMPLE_ENABLE.get() { - c_str_ptr!("1") - } else { - c_str_ptr!("0") - }, - ); - php_info_print_table_end(); - } -} + // register module ini + module.add_bool_ini("hello.enable", false, Policy::All); + module.add_long_ini("hello.num", 100, Policy::All); + module.add_real_ini("hello.ratio", 1.5, Policy::All); + module.add_str_ini("hello.description", "hello world.", Policy::All); -#[php_function] -pub fn say_hello(execute_data: &mut ExecuteData) -> impl SetVal { - execute_data - .parse_parameters::<&str>() - .map(|name| format!("Hello, {}!", name)) -} + // register hook functions + module.on_module_init(module_init); + module.on_module_shutdown(|_| true); + module.on_request_init(|_| true); + module.on_request_shutdown(|_| true); + + // register functions + module.add_function("hello_say_hello", say_hello, vec![Argument::by_val("name")]); + module.add_function("hello_throw_exception", throw_exception, vec![]); + module.add_function( + "hello_get_all_ini", + |_: &mut [Val]| { + let mut arr = Array::new(); -static ARG_INFO_SAY_HELLO: MultiInternalArgInfo<1> = - MultiInternalArgInfo::new(1, false, [create_zend_arg_info(c_str_ptr!("name"), false)]); + let mut hello_enable = Val::new(Module::get_bool_ini("hello.enable")); + arr.insert("hello.enable", &mut hello_enable); -static FUNCTION_ENTRIES: FunctionEntries<1> = FunctionEntries::new([zend_function_entry { - fname: c_str_ptr!("say_hello"), - handler: Some(php_fn!(say_hello)), - arg_info: ARG_INFO_SAY_HELLO.as_ptr(), - num_args: 2, - flags: 0, -}]); + let mut hello_description = Val::new(Module::get_str_ini("hello.description")); + arr.insert("hello.description", &mut hello_description); -static MODULE_ENTRY: ModuleEntry = ModuleEntryBuilder::new( - c_str_ptr!(env!("CARGO_PKG_NAME")), - c_str_ptr!(env!("CARGO_PKG_VERSION")), -) -.functions(FUNCTION_ENTRIES.as_ptr()) -.module_startup_func(php_minit!(module_init)) -.module_shutdown_func(php_mshutdown!(module_shutdown)) -.request_startup_func(php_rinit!(request_init)) -.request_shutdown_func(php_rshutdown!(request_shutdown)) -.info_func(php_minfo!(module_info)) -.build(); + arr + }, + vec![], + ); -#[zend_get_module] -pub fn get_module() -> &'static ModuleEntry { - &MODULE_ENTRY + // register classes + let mut foo_class = StdClass::new(); + foo_class.add_property("foo", 100); + foo_class.add_method( + "getFoo", + |this: &mut This, _: &mut [Val]| { + let prop = this.get_property("foo"); + Val::from_val(prop) + }, + vec![], + ); + foo_class.add_method( + "setFoo", + |this: &mut This, arguments: &mut [Val]| { + let prop = this.get_property("foo"); + prop.set(&arguments[0]); + }, + vec![Argument::by_val("foo")], + ); + module.add_class("FooClass", foo_class); } diff --git a/examples/hello/src/main.rs b/examples/hello/src/main.rs new file mode 100644 index 00000000..dd7ec056 --- /dev/null +++ b/examples/hello/src/main.rs @@ -0,0 +1,5 @@ +use phper::cmd::make; + +fn main() { + make(); +} diff --git a/examples/hello/tests/integration.rs b/examples/hello/tests/integration.rs index 573b6660..aa0c5c13 100644 --- a/examples/hello/tests/integration.rs +++ b/examples/hello/tests/integration.rs @@ -4,11 +4,7 @@ use std::{env, path::Path}; #[test] fn test_php() { test_php_scripts( - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - env!("CARGO_PKG_NAME"), + env!("CARGO_BIN_EXE_hello"), &[Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("php") diff --git a/examples/hello/tests/php/test.php b/examples/hello/tests/php/test.php index f13d5875..ac54bc52 100644 --- a/examples/hello/tests/php/test.php +++ b/examples/hello/tests/php/test.php @@ -4,11 +4,29 @@ ini_set("display_startup_errors", "On"); error_reporting(E_ALL); -assert_eq(get_extension_funcs('hello'), ["say_hello"]); -assert_eq(say_hello("world"), "Hello, world!"); +assert_eq(hello_say_hello("world"), "Hello, world!\n"); + +assert_eq(class_exists("PHPerException"), true); + +try { + hello_throw_exception(); +} catch (PHPerException $e) { + assert_eq($e->getMessage(), "I am sorry"); +} + +assert_eq(hello_get_all_ini(), [ + "hello.enable" => false, + "hello.description" => "hello world.", +]); + +$foo = new FooClass(); +assert_eq($foo->getFoo(), 100); + +$foo->setFoo("Hello"); +assert_eq($foo->getFoo(), "Hello"); function assert_eq($left, $right) { if ($left !== $right) { - throw new Exception("left != right,\n left: {$left},\n right: {$right};"); + throw new Exception(sprintf("left != right,\n left: %s,\n right: %s", var_export($left, true), var_export($right, true))); } } \ No newline at end of file diff --git a/examples/mini-curl/Cargo.toml b/examples/mini-curl/Cargo.toml deleted file mode 100644 index eb8023a2..00000000 --- a/examples/mini-curl/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "mini_curl" -version = "0.2.0" -authors = ["jmjoy <918734043@qq.com>"] -edition = "2018" -publish = false -license = "Unlicense" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -curl = "0.4.34" -phper = { version = "0.2", path = "../../phper" } - -[dev-dependencies] -phper-test = { version = "0.2", path = "../../phper-test" } - -[build-dependencies] -phper-build = { version = "0.2", path = "../../phper-build" } diff --git a/examples/mini-curl/Makefile.toml b/examples/mini-curl/Makefile.toml deleted file mode 100644 index 5f4e9061..00000000 --- a/examples/mini-curl/Makefile.toml +++ /dev/null @@ -1,30 +0,0 @@ -[env] -PHP_CONFIG = "php-config" -TARGET_DIR = "${CARGO_MAKE_WORKING_DIRECTORY}/../../target" - -[env.development] -TARGET_BUILD_DIR = "${TARGET_DIR}/debug" -BUILD_ARGS = "--" -TEST_ARGS = "--" - -[env.production] -TARGET_BUILD_DIR = "${TARGET_DIR}/release" -BUILD_ARGS = "--release" -TEST_ARGS = "--release" - -[tasks.build] -command = "cargo" -args = ["build", "${BUILD_ARGS}"] - -[tasks.test] -command = "cargo" -args = ["test", "${TEST_ARGS}", "--", "--nocapture"] - -[tasks.install] -dependencies = ["build"] -script = [ - """ - cp ${TARGET_BUILD_DIR}/lib${CARGO_MAKE_CRATE_NAME}.so `${PHP_CONFIG} --extension-dir`/${CARGO_MAKE_CRATE_NAME}.so && \ - echo Installing shared extensions: `${PHP_CONFIG} --extension-dir` - """ -] diff --git a/examples/mini-curl/build.rs b/examples/mini-curl/build.rs deleted file mode 100644 index 8be39f4f..00000000 --- a/examples/mini-curl/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - phper_build::register_configures(); -} diff --git a/examples/mini-curl/src/lib.rs b/examples/mini-curl/src/lib.rs deleted file mode 100644 index 8b4bb259..00000000 --- a/examples/mini-curl/src/lib.rs +++ /dev/null @@ -1,161 +0,0 @@ -use curl::easy::Easy; -use phper::{ - c_str_ptr, - main::php::error_doc_ref, - php_fn, php_function, php_minfo, php_minfo_function, php_minit, php_minit_function, - php_mshutdown, php_mshutdown_function, php_rinit, php_rinit_function, php_rshutdown, - php_rshutdown_function, - sys::{php_info_print_table_end, php_info_print_table_start, PHP_INI_SYSTEM}, - zend::{ - api::{FunctionEntries, FunctionEntryBuilder}, - compile::{create_zend_arg_info, MultiInternalArgInfo, Visibility}, - errors::Level, - ini::{create_ini_entry, IniEntries}, - modules::{ModuleArgs, ModuleEntry, ModuleEntryBuilder}, - types::{ClassEntry, ExecuteData, ReturnValue, SetVal, Value}, - }, - zend_get_module, -}; - -static MINI_CURL_CE: ClassEntry = ClassEntry::new(); - -static INI_ENTRIES: IniEntries<1> = - IniEntries::new([create_ini_entry("curl.cainfo", "", PHP_INI_SYSTEM)]); - -#[php_minit_function] -fn module_init(args: ModuleArgs) -> bool { - args.register_ini_entries(&INI_ENTRIES); - MINI_CURL_CE.init("MiniCurl", &MINI_CURL_METHODS); - MINI_CURL_CE.declare_property("_rust_easy_ptr", 0, Visibility::Private); - true -} - -#[php_mshutdown_function] -fn module_shutdown(args: ModuleArgs) -> bool { - args.unregister_ini_entries(); - true -} - -#[php_rinit_function] -fn request_init(_args: ModuleArgs) -> bool { - true -} - -#[php_rshutdown_function] -fn request_shutdown(_args: ModuleArgs) -> bool { - true -} - -#[php_minfo_function] -fn module_info(__module: &ModuleEntry) { - unsafe { - php_info_print_table_start(); - php_info_print_table_end(); - } -} - -static ARG_INFO_VOID: MultiInternalArgInfo<0> = MultiInternalArgInfo::new(0, false, []); - -static ARG_INFO_MINI_CURL_CONSTRUCT: MultiInternalArgInfo<1> = - MultiInternalArgInfo::new(0, false, [create_zend_arg_info(c_str_ptr!("url"), false)]); - -static MINI_CURL_METHODS: FunctionEntries<3> = FunctionEntries::new([ - FunctionEntryBuilder::new( - c_str_ptr!("__construct"), - Some(php_fn!(mini_curl_construct)), - ) - .arg_info(&ARG_INFO_MINI_CURL_CONSTRUCT) - .build(), - FunctionEntryBuilder::new(c_str_ptr!("__destruct"), Some(php_fn!(mini_curl_destruct))) - .arg_info(&ARG_INFO_VOID) - .build(), - FunctionEntryBuilder::new(c_str_ptr!("exec"), Some(php_fn!(mini_curl_exec))) - .arg_info(&ARG_INFO_VOID) - .build(), -]); - -#[php_function] -pub fn mini_curl_construct(execute_data: &mut ExecuteData) -> impl SetVal { - let url = match execute_data.parse_parameters_optional::<&str, _>("") { - Some(url) => url, - None => return ReturnValue::Bool(false), - }; - - let this = execute_data.get_this(); - - let mut easy = Box::new(Easy::new()); - - if !url.is_empty() { - if let Err(e) = easy.url(url) { - error_doc_ref(Level::Warning, format!("curl set failed: {}\0", e)); - return ReturnValue::Bool(false); - } - } - - MINI_CURL_CE.update_property(this, "_rust_easy_ptr", Box::into_raw(easy) as i64); - - ReturnValue::Null -} - -#[php_function] -pub fn mini_curl_exec(execute_data: &mut ExecuteData) -> impl SetVal { - if execute_data.parse_parameters::<()>().is_none() { - return ReturnValue::Bool(false); - } - - let mut data = Vec::new(); - - let this = execute_data.get_this(); - let ptr = MINI_CURL_CE.read_property(this, "_rust_easy_ptr"); - let value = ptr.try_into_value().unwrap(); - let ptr = value.into_long().unwrap(); - - let mut handle = unsafe { Box::from_raw(ptr as *mut Easy) }; - let mut transfer = handle.transfer(); - transfer - .write_function(|new_data| { - data.extend_from_slice(new_data); - Ok(new_data.len()) - }) - .unwrap(); - transfer.perform().unwrap(); - drop(transfer); - - Box::into_raw(handle); - - ReturnValue::String(String::from_utf8(data).unwrap()) -} - -#[php_function] -pub fn mini_curl_destruct(execute_data: &mut ExecuteData) -> impl SetVal { - if execute_data.parse_parameters::<()>().is_none() { - return ReturnValue::Bool(false); - } - - let this = execute_data.get_this(); - let ptr = MINI_CURL_CE.read_property(this, "_rust_easy_ptr"); - let ptr = ptr.try_into_value().unwrap(); - if let Value::Long(ptr) = ptr { - unsafe { - drop(Box::from_raw(ptr as *mut Easy)); - } - } - - ReturnValue::Null -} - -static MODULE_ENTRY: ModuleEntry = ModuleEntryBuilder::new( - c_str_ptr!(env!("CARGO_PKG_NAME")), - c_str_ptr!(env!("CARGO_PKG_VERSION")), -) -.module_startup_func(php_minit!(module_init)) -.module_shutdown_func(php_mshutdown!(module_shutdown)) -.request_startup_func(php_rinit!(request_init)) -.request_shutdown_func(php_rshutdown!(request_shutdown)) -.info_func(php_minfo!(module_info)) -.build(); - -#[zend_get_module] -pub fn get_module() -> &'static ModuleEntry { - &MODULE_ENTRY -} diff --git a/examples/mini-curl/tests/integration.rs b/examples/mini-curl/tests/integration.rs deleted file mode 100644 index 573b6660..00000000 --- a/examples/mini-curl/tests/integration.rs +++ /dev/null @@ -1,17 +0,0 @@ -use phper_test::test_php_scripts; -use std::{env, path::Path}; - -#[test] -fn test_php() { - test_php_scripts( - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - env!("CARGO_PKG_NAME"), - &[Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("php") - .join("test.php")], - ); -} diff --git a/examples/mini-curl/tests/php/test.php b/examples/mini-curl/tests/php/test.php deleted file mode 100644 index 6f73cdb8..00000000 --- a/examples/mini-curl/tests/php/test.php +++ /dev/null @@ -1,15 +0,0 @@ -exec(); -var_dump($response); - -function assert_eq($left, $right) { - if ($left !== $right) { - throw new Exception("left != right,\n left: {$left},\n right: {$right};"); - } -} \ No newline at end of file diff --git a/phper-alloc/src/lib.rs b/phper-alloc/src/lib.rs index d80f520a..772c0b46 100644 --- a/phper-alloc/src/lib.rs +++ b/phper-alloc/src/lib.rs @@ -1,80 +1,9 @@ -#![feature(allocator_api)] #![warn(rust_2018_idioms, clippy::dbg_macro, clippy::print_stdout)] -use phper_sys::{_efree, _emalloc}; -use std::{ - alloc::{AllocError, AllocRef, Layout}, - ptr::{slice_from_raw_parts_mut, NonNull}, -}; +/// The Box which use php `emalloc` and `efree` to manage memory. +/// TODO now feature `allocator_api` is still unstable, using global allocator instead. +pub type EBox = Box; -pub type EBox = Box; -pub type EVec = Vec; - -pub struct Allocator { - #[cfg(phper_debug)] - zend_filename: *const std::os::raw::c_char, - #[cfg(phper_debug)] - zend_lineno: u32, - #[cfg(phper_debug)] - zend_orig_filename: *const std::os::raw::c_char, - #[cfg(phper_debug)] - zend_orig_lineno: u32, -} - -impl Allocator { - pub const fn new( - #[cfg(phper_debug)] zend_filename: *const std::os::raw::c_char, - #[cfg(phper_debug)] zend_lineno: u32, - #[cfg(phper_debug)] zend_orig_filename: *const std::os::raw::c_char, - #[cfg(phper_debug)] zend_orig_lineno: u32, - ) -> Self { - Self { - #[cfg(phper_debug)] - zend_filename, - #[cfg(phper_debug)] - zend_lineno, - #[cfg(phper_debug)] - zend_orig_filename, - #[cfg(phper_debug)] - zend_orig_lineno, - } - } -} - -unsafe impl AllocRef for Allocator { - fn alloc(&self, layout: Layout) -> Result, AllocError> { - unsafe { - #[cfg(phper_debug)] - let ptr = _emalloc( - layout.size(), - self.zend_filename, - self.zend_lineno, - self.zend_orig_filename, - self.zend_orig_lineno, - ); - #[cfg(not(phper_debug))] - let ptr = _emalloc(layout.size()); - - if ptr.is_null() { - Err(AllocError) - } else { - let ptr = slice_from_raw_parts_mut(ptr.cast(), layout.size()); - Ok(NonNull::new_unchecked(ptr)) - } - } - } - - unsafe fn dealloc(&self, ptr: NonNull, _layout: Layout) { - // Not the correct position of `efree`, but can work!. - #[cfg(phper_debug)] - _efree( - ptr.as_ptr().cast(), - self.zend_filename, - self.zend_lineno, - self.zend_orig_filename, - self.zend_orig_lineno, - ); - #[cfg(not(phper_debug))] - _efree(ptr.as_ptr().cast()); - } -} +/// The Vec which use php `emalloc` and `efree` to manage memory. +/// TODO now feature `allocator_api` is still unstable, using global allocator instead. +pub type EVec = Vec; diff --git a/phper-macros/src/alloc.rs b/phper-macros/src/alloc.rs index 0ae42a2e..8b137891 100644 --- a/phper-macros/src/alloc.rs +++ b/phper-macros/src/alloc.rs @@ -1,11 +1 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Expr}; -pub(crate) fn ebox(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as Expr); - let result = quote! { - ::phper::alloc::EBox::new_in(#input, ::phper::alloc::Allocator::new(#[cfg(phper_debug)] ::phper::c_str_ptr!(file!()), #[cfg(phper_debug)] ::std::line!(), #[cfg(phper_debug)] ::std::ptr::null(), #[cfg(phper_debug)] 0)) - }; - result.into() -} diff --git a/phper-macros/src/inner.rs b/phper-macros/src/inner.rs index 8e320c1a..53c921a6 100644 --- a/phper-macros/src/inner.rs +++ b/phper-macros/src/inner.rs @@ -1,113 +1,8 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Ident, ItemFn, Visibility}; +use syn::{parse_macro_input, ItemFn, Visibility}; -pub(crate) fn rename(input: TokenStream, prefix: impl ToString) -> TokenStream { - let input = parse_macro_input!(input as Ident); - let name = prefix.to_string() + &input.to_string(); - let name = Ident::new(&name, input.span().clone()); - let result = quote! { #name }; - result.into() -} - -pub(crate) fn hook_fn(input: TokenStream, prefix: impl ToString) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - - let name = prefix.to_string() + &input.sig.ident.to_string(); - let name = Ident::new(&name, input.sig.ident.span()); - let inputs = &input.sig.inputs; - let ret = &input.sig.output; - let body = &input.block; - let attrs = &input.attrs; - - let result = quote! { - #[allow(dead_code)] - #input - - #(#attrs)* - extern "C" fn #name(type_: ::std::os::raw::c_int, module_number: ::std::os::raw::c_int) -> ::std::os::raw::c_int { - fn internal(#inputs) #ret { - #body - } - - let internal: fn(::phper::zend::modules::ModuleArgs) -> bool = internal; - - if internal(::phper::zend::modules::ModuleArgs::new(type_, module_number)) { - ::phper::sys::ZEND_RESULT_CODE_SUCCESS - } else { - ::phper::sys::ZEND_RESULT_CODE_FAILURE - } - } - }; - - result.into() -} - -pub(crate) fn info_fn(input: TokenStream, prefix: impl ToString) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - - let name = prefix.to_string() + &input.sig.ident.to_string(); - let name = Ident::new(&name, input.sig.ident.span()); - let inputs = &input.sig.inputs; - let ret = &input.sig.output; - let body = &input.block; - let attrs = &input.attrs; - - let result = quote! { - #[allow(dead_code)] - #input - - #(#attrs)* - extern "C" fn #name(zend_module: *mut ::phper::sys::zend_module_entry) { - fn internal(#inputs) #ret { - #body - } - - let internal: fn(&::phper::zend::modules::ModuleEntry) = internal; - internal(::phper::zend::modules::ModuleEntry::from_ptr(zend_module)) - } - }; - - result.into() -} - -pub(crate) fn php_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - - let vis = &input.vis; - let ret = &input.sig.output; - let inputs = &input.sig.inputs; - let name = Ident::new( - &format!("zif_{}", &input.sig.ident.to_string()), - input.sig.ident.span().clone(), - ); - let body = &input.block; - let attrs = &input.attrs; - - let result = quote! { - #[allow(dead_code)] - #input - - #(#attrs)* - #vis extern "C" fn #name( - execute_data: *mut ::phper::sys::zend_execute_data, - return_value: *mut ::phper::sys::zval - ) { - fn internal(#inputs) #ret { - #body - } - let internal: fn(&mut ::phper::zend::types::ExecuteData) -> _ = internal; - unsafe { - let value = internal(::phper::zend::types::ExecuteData::from_mut(execute_data)); - ::phper::zend::types::SetVal::set_val(value, ::phper::zend::types::Val::from_mut(return_value)); - } - } - }; - - result.into() -} - -pub(crate) fn zend_get_module(_attr: TokenStream, input: TokenStream) -> TokenStream { +pub(crate) fn php_get_module(_attr: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemFn); let vis = &input.vis; @@ -132,8 +27,14 @@ pub(crate) fn zend_get_module(_attr: TokenStream, input: TokenStream) -> TokenSt fn internal(#inputs) #ret { #body } - let internal: fn() -> &'static ::phper::zend::modules::ModuleEntry = internal; - internal().as_ptr() + let internal: fn(module: &mut ::phper::modules::Module) = internal; + + ::phper::modules::write_global_module(internal); + unsafe { + ::phper::modules::write_global_module(|module| { + module.module_entry() + }) + } } }; diff --git a/phper-macros/src/lib.rs b/phper-macros/src/lib.rs index c75a00e8..2ad5ff0a 100644 --- a/phper-macros/src/lib.rs +++ b/phper-macros/src/lib.rs @@ -2,7 +2,6 @@ mod alloc; mod inner; mod utils; -use crate::inner::{hook_fn, info_fn, rename}; use proc_macro::TokenStream; #[proc_macro] @@ -15,87 +14,7 @@ pub fn c_str_ptr(input: TokenStream) -> TokenStream { utils::c_str_ptr(input) } -#[proc_macro] -pub fn ebox(input: TokenStream) -> TokenStream { - alloc::ebox(input) -} - -#[proc_macro] -pub fn php_fn(input: TokenStream) -> TokenStream { - rename(input, "zif_") -} - -#[proc_macro] -pub fn php_mn(input: TokenStream) -> TokenStream { - rename(input, "zim_") -} - -#[proc_macro] -pub fn php_minit(input: TokenStream) -> TokenStream { - rename(input, "zm_startup_") -} - -#[proc_macro] -pub fn php_mshutdown(input: TokenStream) -> TokenStream { - rename(input, "zm_shutdown_") -} - -#[proc_macro] -pub fn php_rinit(input: TokenStream) -> TokenStream { - rename(input, "zm_activate_") -} - -#[proc_macro] -pub fn php_rshutdown(input: TokenStream) -> TokenStream { - rename(input, "zm_deactivate_") -} - -#[proc_macro] -pub fn php_minfo(input: TokenStream) -> TokenStream { - rename(input, "zm_info_") -} - -#[proc_macro] -pub fn php_ginfo(input: TokenStream) -> TokenStream { - rename(input, "zm_globals_ctor_") -} - -#[proc_macro] -pub fn php_gshutdown(input: TokenStream) -> TokenStream { - rename(input, "zm_globals_dtor_") -} - -#[proc_macro_attribute] -pub fn php_function(attr: TokenStream, input: TokenStream) -> TokenStream { - inner::php_function(attr, input) -} - -#[proc_macro_attribute] -pub fn zend_get_module(attr: TokenStream, input: TokenStream) -> TokenStream { - inner::zend_get_module(attr, input) -} - -#[proc_macro_attribute] -pub fn php_minit_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - hook_fn(input, "zm_startup_") -} - -#[proc_macro_attribute] -pub fn php_mshutdown_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - hook_fn(input, "zm_shutdown_") -} - -#[proc_macro_attribute] -pub fn php_rinit_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - hook_fn(input, "zm_activate_") -} - -#[proc_macro_attribute] -pub fn php_rshutdown_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - hook_fn(input, "zm_deactivate_") -} - #[proc_macro_attribute] -pub fn php_minfo_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - info_fn(input, "zm_info_") +pub fn php_get_module(attr: TokenStream, input: TokenStream) -> TokenStream { + inner::php_get_module(attr, input) } diff --git a/phper-macros/tests/integration.rs b/phper-macros/tests/integration.rs index dff19cc4..845053b2 100644 --- a/phper-macros/tests/integration.rs +++ b/phper-macros/tests/integration.rs @@ -1,4 +1,4 @@ -use phper_macros::{c_str, c_str_ptr, php_fn, php_mn}; +use phper_macros::{c_str, c_str_ptr}; use std::ffi::CStr; #[test] @@ -15,15 +15,3 @@ fn test_c_str() { fn test_c_str_ptr() { assert_eq!(c_str_ptr!("foo"), "foo\0".as_ptr().cast()); } - -#[test] -fn test_php_fn() { - let php_fn!(a): i32 = 1; - assert_eq!(zif_a, 1); -} - -#[test] -fn test_php_mn() { - let php_mn!(a): i32 = 1; - assert_eq!(zim_a, 1); -} diff --git a/phper-sys/php_wrapper.c b/phper-sys/php_wrapper.c index 17fdc357..1367b559 100644 --- a/phper-sys/php_wrapper.c +++ b/phper-sys/php_wrapper.c @@ -1,9 +1,5 @@ #include "php_wrapper.h" -zend_string *phper_zend_string_init(const char *str, size_t len, int persistent) { - return zend_string_init(str, len, persistent); -} - zend_string *phper_zend_new_interned_string(zend_string *str) { return zend_new_interned_string(str); } @@ -22,6 +18,10 @@ zend_uchar phper_zval_get_type(const zval* pz) { return zval_get_type(pz); } +void phper_zval_arr(zval *return_value, zend_array *arr) { + ZVAL_ARR(return_value, arr); +} + void phper_zval_stringl(zval *return_value, const char *s, size_t len) { ZVAL_STRINGL(return_value, s, len); } @@ -33,3 +33,39 @@ char *phper_z_strval_p(const zval *v) { zval *phper_get_this(zend_execute_data *execute_data) { return getThis(); } + +void phper_zval_zval(zval *return_value, zval *zv, int copy, int dtor) { + ZVAL_ZVAL(return_value, zv, copy, dtor); +} + +void phper_zval_dup(zval *return_value, zval *zv) { + ZVAL_DUP(return_value, zv); +} + +void phper_zval_copy(zval *return_value, zval *zv) { + ZVAL_COPY(return_value, zv); +} + +zend_string *phper_zval_get_string(zval *op) { + return zval_get_string(op); +} + +zend_long phper_zval_get_long(zval *op) { + return zval_get_long(op); +} + +zend_string *phper_zend_string_init(const char *str, size_t len, int persistent) { + return zend_string_init(str, len, persistent); +} + +zend_string *phper_zend_string_alloc(size_t len, int persistent) { + return zend_string_alloc(len, persistent); +} + +void phper_zend_string_release(zend_string *s) { + return zend_string_release(s); +} + +void phper_zend_hash_str_update(HashTable *ht, const char *key, size_t len, zval *pData) { + zend_hash_str_update(ht, key, len, pData); +} diff --git a/phper-sys/php_wrapper.h b/phper-sys/php_wrapper.h index 04e07803..36dc1836 100644 --- a/phper-sys/php_wrapper.h +++ b/phper-sys/php_wrapper.h @@ -6,15 +6,29 @@ #include #include -typedef void (ZEND_FASTCALL *zif_handler)(INTERNAL_FUNCTION_PARAMETERS); +typedef ZEND_INI_MH(phper_zend_ini_mh); -zend_string *zend_string_init_(const char *str, size_t len, int persistent); zend_string *zend_new_interned_string_(zend_string *str); zend_class_entry phper_init_class_entry_ex(const char *class_name, size_t class_name_len, const zend_function_entry *functions); void phper_zval_string(zval *return_value, const char *s); zend_uchar phper_zval_get_type(const zval* pz); +void phper_zval_arr(zval *return_value, zend_array *arr); void phper_zval_stringl(zval *return_value, const char *s, size_t len); char *phper_z_strval_p(const zval *v); zval *phper_get_this(zend_execute_data *execute_data); +void phper_zval_zval(zval *return_value, zval *zv, int copy, int dtor); +void phper_zval_dup(zval *return_value, zval *zv); +void phper_zval_copy(zval *return_value, zval *zv); +zend_string *phper_zval_get_string(zval *op); +void phper_zend_string_release(zend_string *s); +zend_long phper_zval_get_long(zval *op); + +zend_string *phper_zend_string_init(const char *str, size_t len, int persistent); +zend_string *phper_zend_string_alloc(size_t len, int persistent); +void phper_zend_string_release(zend_string *s); + +void phper_zend_hash_str_update(HashTable *ht, const char *key, size_t len, zval *pData); + + #endif //PHPER_PHP_WRAPPER_H diff --git a/phper-test/src/lib.rs b/phper-test/src/lib.rs index cfcf2f5b..b31fade0 100644 --- a/phper-test/src/lib.rs +++ b/phper-test/src/lib.rs @@ -1,24 +1,19 @@ use once_cell::sync::OnceCell; use std::{ - env, ffi::OsStr, fmt::Debug, fs::read_to_string, io::Write, path::Path, process::Command, + env, + ffi::{OsStr, OsString}, + fmt::Debug, + fs::read_to_string, + io::Write, + path::{Path, PathBuf}, + process::Command, }; use tempfile::NamedTempFile; -pub fn test_php_scripts( - target_dir: impl AsRef, - lib_name: &str, - scripts: &[impl AsRef], -) { +pub fn test_php_scripts(exe_path: impl AsRef, scripts: &[impl AsRef]) { let context = php_context(); - let lib_path = target_dir - .as_ref() - .join(if cfg!(debug_assertions) { - "debug" - } else { - "release" - }) - .join(format!("lib{}.so", lib_name)); + let lib_path = get_lib_path(exe_path); let mut out_ini_temp_file = NamedTempFile::new().unwrap(); let out_ini_file = out_ini_temp_file.as_file_mut(); @@ -93,11 +88,15 @@ fn php_context() -> &'static Context { "echo php_ini_scanned_files();", ]); - ini_content.push_str(&read_to_string(ini_file).unwrap()); - for file in ini_files.split(',') { - let file = file.trim(); - if !file.is_empty() { - ini_content.push_str(&read_to_string(file).unwrap()); + if !ini_file.is_empty() { + ini_content.push_str(&read_to_string(ini_file).unwrap()); + } + if !ini_files.is_empty() { + for file in ini_files.split(',') { + let file = file.trim(); + if !file.is_empty() { + ini_content.push_str(&read_to_string(file).unwrap()); + } } } @@ -117,3 +116,20 @@ fn execute_command + Debug>(argv: &[S]) -> String { .stdout; String::from_utf8(output).unwrap().trim().to_owned() } + +fn get_lib_path(exe_path: impl AsRef) -> PathBuf { + let exe_path = exe_path.as_ref(); + let exe_stem = exe_path + .file_stem() + .expect("failed to get current exe stem"); + let target_dir = exe_path + .parent() + .expect("failed to get current exe directory"); + + let mut ext_name = OsString::new(); + ext_name.push("lib"); + ext_name.push(exe_stem); + ext_name.push(".so"); + + target_dir.join(ext_name) +} diff --git a/phper/Cargo.toml b/phper/Cargo.toml index e2d72418..39b718a0 100644 --- a/phper/Cargo.toml +++ b/phper/Cargo.toml @@ -5,6 +5,7 @@ authors = ["jmjoy <918734043@qq.com>"] edition = "2018" description = "A library that allows us to write PHP extensions using pure Rust and using safe Rust whenever possible." repository = "https://github.com/jmjoy/phper.git" +documentation = "https://docs.rs/phper" license = "Unlicense" readme = "../README.md" keywords = ["php", "binding", "extension"] @@ -12,6 +13,9 @@ keywords = ["php", "binding", "extension"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.40" +clap = "3.0.0-beta.2" +once_cell = "1.7.2" phper-alloc = { version = "0.2", path = "../phper-alloc" } phper-macros = { version = "0.2", path = "../phper-macros" } phper-sys = { version = "0.2", path = "../phper-sys" } diff --git a/phper/src/arrays.rs b/phper/src/arrays.rs new file mode 100644 index 00000000..0defca03 --- /dev/null +++ b/phper/src/arrays.rs @@ -0,0 +1,67 @@ +use crate::{sys::*, values::Val}; +use std::{ + mem::zeroed, + ops::{Deref, DerefMut}, +}; + +pub struct Array { + inner: Box, +} + +impl Array { + pub fn new() -> Self { + let mut inner = Box::new(unsafe { zeroed::() }); + unsafe { + _zend_hash_init(&mut *inner, 0, None, true.into()); + } + Self { inner } + } + + pub fn as_ptr(&self) -> *const zend_array { + self.inner.deref() + } + + pub fn insert(&mut self, key: impl AsRef, value: &mut Val) { + let key = key.as_ref(); + unsafe { + phper_zend_hash_str_update( + self.inner.deref_mut(), + key.as_ptr().cast(), + key.len(), + value.as_mut(), + ); + } + } + + pub fn get(&mut self, key: impl AsRef) -> &mut Val { + let key = key.as_ref(); + unsafe { + let value = zend_hash_str_find(&mut *self.inner, key.as_ptr().cast(), key.len()); + Val::from_mut(value) + } + } + + pub fn len(&mut self) -> usize { + unsafe { zend_array_count(&mut *self.inner) as usize } + } +} + +impl AsRef for Array { + fn as_ref(&self) -> &zend_array { + self.inner.deref() + } +} + +impl AsMut for Array { + fn as_mut(&mut self) -> &mut zend_array { + self.inner.deref_mut() + } +} + +impl Drop for Array { + fn drop(&mut self) { + unsafe { + zend_hash_destroy(&mut *self.inner); + } + } +} diff --git a/phper/src/classes.rs b/phper/src/classes.rs new file mode 100644 index 00000000..f9541079 --- /dev/null +++ b/phper/src/classes.rs @@ -0,0 +1,229 @@ +use crate::{ + functions::{Argument, Callable, FunctionEntity, FunctionEntry, Method}, + sys::*, + values::{SetVal, Val}, +}; +use once_cell::sync::OnceCell; +use std::{ + mem::zeroed, + os::raw::c_int, + ptr::null_mut, + sync::atomic::{AtomicPtr, Ordering}, +}; + +pub trait Class: Send + Sync { + fn methods(&mut self) -> &mut [FunctionEntity]; + fn properties(&mut self) -> &mut [PropertyEntity]; + fn parent(&self) -> Option<&str>; +} + +pub struct StdClass { + pub(crate) method_entities: Vec, + pub(crate) property_entities: Vec, + pub(crate) parent: Option, +} + +impl StdClass { + pub fn new() -> Self { + Self { + method_entities: Vec::new(), + property_entities: Vec::new(), + parent: None, + } + } + + pub fn add_method( + &mut self, + name: impl ToString, + handler: impl Method + 'static, + arguments: Vec, + ) { + self.method_entities.push(FunctionEntity::new( + name, + Callable::Method(Box::new(handler), AtomicPtr::new(null_mut())), + arguments, + )); + } + + pub fn add_property( + &mut self, + name: impl ToString, + value: impl SetVal + Send + Sync + 'static, + ) { + self.property_entities + .push(PropertyEntity::new(name, value)); + } + + pub fn extends(&mut self, name: impl ToString) { + let name = name.to_string(); + self.parent = Some(name); + } +} + +impl Class for StdClass { + fn methods(&mut self) -> &mut [FunctionEntity] { + &mut self.method_entities + } + + fn properties(&mut self) -> &mut [PropertyEntity] { + &mut self.property_entities + } + + fn parent(&self) -> Option<&str> { + self.parent.as_deref() + } +} + +#[repr(transparent)] +pub struct ClassEntry { + inner: zend_class_entry, +} + +impl ClassEntry { + pub fn as_mut(&mut self) -> *mut zend_class_entry { + &mut self.inner + } +} + +pub struct ClassEntity { + pub(crate) name: String, + pub(crate) entry: AtomicPtr, + pub(crate) class: Box, + pub(crate) function_entries: OnceCell>, +} + +impl ClassEntity { + pub(crate) unsafe fn new(name: impl ToString, class: impl Class + 'static) -> Self { + Self { + name: name.to_string(), + entry: AtomicPtr::new(null_mut()), + class: Box::new(class), + function_entries: Default::default(), + } + } + + pub(crate) unsafe fn init(&mut self) { + let mut class_ce = phper_init_class_entry_ex( + self.name.as_ptr().cast(), + self.name.len(), + self.function_entries().load(Ordering::SeqCst).cast(), + ); + + let parent = self.class.parent().map(|s| match s { + "Exception" | "\\Exception" => zend_ce_exception, + _ => todo!(), + }); + + let ptr = match parent { + Some(parent) => zend_register_internal_class_ex(&mut class_ce, parent).cast(), + None => zend_register_internal_class(&mut class_ce).cast(), + }; + self.entry.store(ptr, Ordering::SeqCst); + + let methods = self.class.methods(); + for method in methods { + match &method.handler { + Callable::Method(_, class) => { + class.store(ptr, Ordering::SeqCst); + } + _ => unreachable!(), + } + } + } + + pub(crate) unsafe fn declare_properties(&mut self) { + let properties = self.class.properties(); + for property in properties { + let mut val = Val::null(); + val.set(&property.value); + zend_declare_property( + self.entry.load(Ordering::SeqCst).cast(), + property.name.as_ptr().cast(), + property.name.len(), + val.as_mut(), + Visibility::Public as c_int, + ); + } + } + + unsafe fn function_entries(&mut self) -> &AtomicPtr { + let methods = &*self.class.methods(); + + self.function_entries.get_or_init(|| { + let mut methods = methods + .iter() + .map(|method| method.entry()) + .collect::>(); + methods.push(zeroed::()); + let entry = Box::into_raw(methods.into_boxed_slice()).cast(); + AtomicPtr::new(entry) + }) + } +} + +pub struct This { + val: *mut Val, + class: *mut ClassEntry, +} + +impl This { + pub(crate) fn new<'a>(val: *mut Val, class: *mut ClassEntry) -> This { + assert!(!val.is_null()); + assert!(!class.is_null()); + Self { val, class } + } + + pub fn get_property(&self, name: impl AsRef) -> &mut Val { + let name = name.as_ref(); + + let prop = unsafe { + #[cfg(phper_major_version = "8")] + { + zend_read_property( + self.class as *mut _, + (*self.val).inner.value.obj, + name.as_ptr().cast(), + name.len(), + false.into(), + null_mut(), + ) + } + + #[cfg(phper_major_version = "7")] + { + zend_read_property( + self.class as *mut _, + self.val as *mut _, + name.as_ptr().cast(), + name.len(), + false.into(), + null_mut(), + ) + } + }; + + unsafe { Val::from_mut(prop) } + } +} + +pub struct PropertyEntity { + name: String, + value: Box, +} + +impl PropertyEntity { + pub fn new(name: impl ToString, value: impl SetVal + Send + Sync + 'static) -> Self { + Self { + name: name.to_string(), + value: Box::new(value), + } + } +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Visibility { + Public = ZEND_ACC_PUBLIC, + Protected = ZEND_ACC_PROTECTED, + Private = ZEND_ACC_PRIVATE, +} diff --git a/phper/src/cmd.rs b/phper/src/cmd.rs new file mode 100644 index 00000000..f8173b33 --- /dev/null +++ b/phper/src/cmd.rs @@ -0,0 +1,63 @@ +use crate::sys::PHP_EXTENSION_DIR; +use anyhow::Context; +use clap::Clap; +use std::{ + env, + ffi::{CStr, OsString}, + fs, + path::{Path, PathBuf}, +}; + +/// Make utility. +#[derive(Clap)] +struct Make { + #[clap(subcommand)] + sub: SubCommand, +} + +#[derive(Clap)] +enum SubCommand { + Install(InstallCommand), +} + +#[derive(Clap)] +struct InstallCommand {} + +pub fn make() { + try_make().expect("make failed"); +} + +pub fn try_make() -> crate::Result<()> { + let make: Make = Make::parse(); + match make.sub { + SubCommand::Install(_) => { + let (lib_path, ext_name) = get_lib_path_and_ext_name()?; + let extension_dir = CStr::from_bytes_with_nul(PHP_EXTENSION_DIR)?.to_str()?; + println!("Installing shared extensions: {}", extension_dir); + let ext_path = Path::new(extension_dir).join(ext_name); + fs::copy(lib_path, ext_path)?; + } + } + Ok(()) +} + +fn get_lib_path_and_ext_name() -> crate::Result<(PathBuf, OsString)> { + let exe_path = env::current_exe()?; + let exe_stem = exe_path + .file_stem() + .context("failed to get current exe stem")?; + let target_dir = exe_path + .parent() + .context("failed to get current exe directory")?; + + let mut exe_name = OsString::new(); + exe_name.push("lib"); + exe_name.push(exe_stem); + exe_name.push(".so"); + + let mut ext_name = OsString::new(); + ext_name.push(exe_stem); + ext_name.push(".so"); + + Ok((target_dir.join(exe_name), ext_name)) +} diff --git a/phper/src/error.rs b/phper/src/error.rs deleted file mode 100644 index efdc02a3..00000000 --- a/phper/src/error.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::str::Utf8Error; - -pub type Result = std::result::Result; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Utf8(#[from] Utf8Error), - - #[error("unknown value type `{0}`")] - UnKnownValueType(u32), -} diff --git a/phper/src/errors.rs b/phper/src/errors.rs new file mode 100644 index 00000000..f7bb322b --- /dev/null +++ b/phper/src/errors.rs @@ -0,0 +1,49 @@ +use crate::{classes::ClassEntity, modules::read_global_module, Error::Other}; +use anyhow::anyhow; +use std::{error, ffi::FromBytesWithNulError, io, str::Utf8Error}; + +pub type Result = std::result::Result; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Io(#[from] io::Error), + + #[error(transparent)] + Utf8(#[from] Utf8Error), + + #[error(transparent)] + FromBytesWithNul(#[from] FromBytesWithNulError), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +impl Error { + pub fn other(message: impl ToString) -> Self { + let message = message.to_string(); + Other(anyhow!(message)) + } +} + +pub trait Throwable: error::Error { + fn class_entity(&self) -> *const ClassEntity; + fn code(&self) -> u64; +} + +pub(crate) const EXCEPTION_CLASS_NAME: &'static str = "PHPerException"; + +impl Throwable for Error { + fn class_entity(&self) -> *const ClassEntity { + read_global_module(|module| { + module + .class_entities + .get(EXCEPTION_CLASS_NAME) + .expect("Must be called after module init") as *const _ + }) + } + + fn code(&self) -> u64 { + 500 + } +} diff --git a/phper/src/functions.rs b/phper/src/functions.rs new file mode 100644 index 00000000..a5c0e245 --- /dev/null +++ b/phper/src/functions.rs @@ -0,0 +1,238 @@ +use crate::{ + classes::{ClassEntry, This}, + sys::*, + values::{ExecuteData, SetVal, Val}, +}; +use std::{ + mem::zeroed, + os::raw::c_char, + ptr::null, + sync::atomic::{AtomicPtr, Ordering}, +}; + +pub trait Function: Send + Sync { + fn call(&self, arguments: &mut [Val], return_value: &mut Val); +} + +impl Function for F +where + F: Fn(&mut [Val]) -> R + Send + Sync, + R: SetVal, +{ + fn call(&self, arguments: &mut [Val], return_value: &mut Val) { + let r = self(arguments); + r.set_val(return_value); + } +} + +pub trait Method: Send + Sync { + fn call(&self, this: &mut This, arguments: &mut [Val], return_value: &mut Val); +} + +impl Method for F +where + F: Fn(&mut This, &mut [Val]) -> R + Send + Sync, + R: SetVal, +{ + fn call(&self, this: &mut This, arguments: &mut [Val], return_value: &mut Val) { + let r = self(this, arguments); + r.set_val(return_value); + } +} + +pub(crate) enum Callable { + Function(Box), + Method(Box, AtomicPtr), +} + +#[repr(transparent)] +pub struct FunctionEntry { + #[allow(dead_code)] + inner: zend_function_entry, +} + +pub struct FunctionEntity { + pub(crate) name: String, + pub(crate) handler: Callable, + pub(crate) arguments: Vec, +} + +impl FunctionEntity { + pub(crate) fn new(name: impl ToString, handler: Callable, arguments: Vec) -> Self { + let mut name = name.to_string(); + name.push('\0'); + FunctionEntity { + name, + handler, + arguments, + } + } + + // Leak memory + pub(crate) unsafe fn entry(&self) -> zend_function_entry { + let mut infos = Vec::new(); + + let require_arg_count = self.arguments.iter().filter(|arg| arg.required).count(); + infos.push(create_zend_arg_info( + require_arg_count as *const c_char, + false, + )); + + for arg in &self.arguments { + infos.push(create_zend_arg_info( + arg.name.as_ptr().cast(), + arg.pass_by_ref, + )); + } + + infos.push(zeroed::()); + + let mut last_arg_info = zeroed::(); + last_arg_info.name = ((&self.handler) as *const _ as *mut i8).cast(); + infos.push(last_arg_info); + + zend_function_entry { + fname: self.name.as_ptr().cast(), + handler: Some(invoke), + arg_info: Box::into_raw(infos.into_boxed_slice()).cast(), + num_args: self.arguments.len() as u32, + flags: 0, + } + } +} + +pub struct Argument { + pub(crate) name: String, + pub(crate) pass_by_ref: bool, + pub(crate) required: bool, +} + +impl Argument { + pub fn by_val(name: impl ToString) -> Self { + let mut name = name.to_string(); + name.push('\0'); + Self { + name, + pass_by_ref: false, + required: true, + } + } + + pub fn by_ref(name: impl ToString) -> Self { + let mut name = name.to_string(); + name.push('\0'); + Self { + name, + pass_by_ref: true, + required: true, + } + } + + pub fn by_val_optional(name: impl ToString) -> Self { + let mut name = name.to_string(); + name.push('\0'); + Self { + name, + pass_by_ref: false, + required: false, + } + } + + pub fn by_ref_optional(name: impl ToString) -> Self { + let mut name = name.to_string(); + name.push('\0'); + Self { + name, + pass_by_ref: true, + required: false, + } + } +} + +pub(crate) unsafe extern "C" fn invoke( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + let execute_data = ExecuteData::from_mut(execute_data); + let return_value = Val::from_mut(return_value); + + let num_args = execute_data.common_num_args(); + let arg_info = execute_data.common_arg_info(); + + let last_arg_info = arg_info.offset((num_args + 1) as isize); + let handler = (*last_arg_info).name as *const Callable; + let handler = handler.as_ref().expect("handler is null"); + + // Check arguments count. + if execute_data.num_args() < execute_data.common_required_num_args() { + let s = format!( + "expects at least {} parameter(s), {} given\0", + execute_data.common_required_num_args(), + execute_data.num_args() + ); + php_error_docref1( + null(), + "\0".as_ptr().cast(), + E_WARNING as i32, + s.as_ptr().cast(), + ); + return_value.set(()); + return; + } + + let mut arguments = execute_data.get_parameters_array(); + + match handler { + Callable::Function(f) => { + f.call(&mut arguments, return_value); + } + Callable::Method(m, class) => { + let mut this = This::new(execute_data.get_this(), class.load(Ordering::SeqCst)); + m.call(&mut this, &mut arguments, return_value); + } + } +} + +pub const fn create_zend_arg_info( + name: *const c_char, + _pass_by_ref: bool, +) -> zend_internal_arg_info { + #[cfg(phper_php_version = "8.0")] + { + use std::ptr::null_mut; + zend_internal_arg_info { + name, + type_: zend_type { + ptr: null_mut(), + type_mask: 0, + }, + default_value: null_mut(), + } + } + + #[cfg(any( + phper_php_version = "7.4", + phper_php_version = "7.3", + phper_php_version = "7.2" + ))] + { + zend_internal_arg_info { + name, + type_: 0 as crate::sys::zend_type, + pass_by_reference: _pass_by_ref as zend_uchar, + is_variadic: 0, + } + } + + #[cfg(any(phper_php_version = "7.1", phper_php_version = "7.0"))] + { + zend_internal_arg_info { + name, + class_name: std::ptr::null(), + type_hint: 0, + allow_null: 0, + pass_by_reference: _pass_by_ref as zend_uchar, + is_variadic: 0, + } + } +} diff --git a/phper/src/ini.rs b/phper/src/ini.rs new file mode 100644 index 00000000..9caa8643 --- /dev/null +++ b/phper/src/ini.rs @@ -0,0 +1,141 @@ +use crate::sys::{ + phper_zend_ini_mh, zend_ini_entry_def, OnUpdateBool, OnUpdateLong, OnUpdateReal, + OnUpdateString, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, +}; +use std::{ + ffi::CStr, + os::raw::{c_char, c_void}, + ptr::null_mut, + str, +}; + +type OnModify = phper_zend_ini_mh; + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum Policy { + All = PHP_INI_ALL, + User = PHP_INI_USER, + Perdir = PHP_INI_PERDIR, + System = PHP_INI_SYSTEM, +} + +pub(crate) struct StrPtrBox { + inner: Box<*mut c_char>, +} + +impl StrPtrBox { + pub(crate) unsafe fn to_string(&self) -> Result { + Ok(CStr::from_ptr(*self.inner).to_str()?.to_string()) + } +} + +impl Default for StrPtrBox { + fn default() -> Self { + Self { + inner: Box::new(null_mut()), + } + } +} + +pub trait IniValue: Default { + fn on_modify() -> OnModify; + + fn arg2(&mut self) -> *mut c_void { + &mut *self as *mut _ as *mut c_void + } +} + +impl IniValue for bool { + fn on_modify() -> OnModify { + Some(OnUpdateBool) + } +} + +impl IniValue for i64 { + fn on_modify() -> OnModify { + Some(OnUpdateLong) + } +} + +impl IniValue for f64 { + fn on_modify() -> OnModify { + Some(OnUpdateReal) + } +} + +impl IniValue for StrPtrBox { + fn on_modify() -> OnModify { + Some(OnUpdateString) + } + + fn arg2(&mut self) -> *mut c_void { + Box::as_mut(&mut self.inner) as *mut _ as *mut c_void + } +} + +pub(crate) struct IniEntity { + name: String, + value: T, + default_value: String, + policy: Policy, +} + +impl IniEntity { + pub(crate) fn new(name: impl ToString, default_value: impl ToString, policy: Policy) -> Self { + Self { + name: name.to_string(), + value: Default::default(), + default_value: default_value.to_string(), + policy, + } + } + + pub(crate) fn value(&self) -> &T { + &self.value + } + + pub(crate) unsafe fn ini_entry_def(&mut self) -> zend_ini_entry_def { + create_ini_entry_ex( + &self.name, + &self.default_value, + ::on_modify(), + self.policy as u32, + self.value.arg2(), + ) + } +} + +pub(crate) fn create_ini_entry_ex( + name: &str, + default_value: &str, + on_modify: OnModify, + modifiable: u32, + arg2: *mut c_void, +) -> zend_ini_entry_def { + #[cfg(any( + phper_php_version = "8.0", + phper_php_version = "7.4", + phper_php_version = "7.3", + ))] + let (modifiable, name_length) = (modifiable as std::os::raw::c_uchar, name.len() as u16); + #[cfg(any( + phper_php_version = "7.2", + phper_php_version = "7.1", + phper_php_version = "7.0", + ))] + let (modifiable, name_length) = (modifiable as std::os::raw::c_int, name.len() as u32); + + zend_ini_entry_def { + name: name.as_ptr().cast(), + on_modify, + mh_arg1: null_mut(), + mh_arg2: arg2, + mh_arg3: null_mut(), + value: default_value.as_ptr().cast(), + displayer: None, + modifiable, + name_length, + value_length: default_value.len() as u32, + } +} diff --git a/phper/src/lib.rs b/phper/src/lib.rs index 3859428e..eef8b071 100644 --- a/phper/src/lib.rs +++ b/phper/src/lib.rs @@ -1,40 +1,140 @@ -#![feature(min_const_generics)] -#![feature(const_fn_fn_ptr_basics)] -#![feature(const_fn_transmute)] #![warn(rust_2018_idioms, clippy::dbg_macro, clippy::print_stdout)] /*! +# PHPer + +[![crates](https://img.shields.io/crates/v/phper?style=flat-square)](https://crates.io/crates/phper) +[![](https://img.shields.io/docsrs/phper?style=flat-square)](https://docs.rs/phper) + A library that allows us to write PHP extensions using pure Rust and using safe Rust whenever possible. -***Now the peojct is still under development.*** +## Requirement + +### Necessary + +**libclang** version >= 9 + +**php** version >= 7 + +### Tested Support + +**os** + +- linux + +**php** + +*version* -# Usage +- 7.0 +- 7.1 +- 7.2 +- 7.3 +- 7.4 +- 8.0 -First you have to install `cargo-generate`: +*mode* + +- nts + +*sapi* + +- cli + +## Usage + +1. Make sure `libclang` and `php` is installed. ```bash -cargo install cargo-generate +# If you are using debian like linux system: +sudo apt install libclang-10-dev php-cli ``` -Then create a PHP extension project from the [template](https://github.com/jmjoy/phper-ext-skel.git): +2. Create you cargo project, suppose your application is called myapp. ```bash -cargo generate --git https://github.com/jmjoy/phper-ext-skel.git +cargo new myapp ``` -# Notice +3. Add the dependencies and metadata to you Cargo project. + +```toml +[lib] +crate-type = ["cdylib"] + +[dependencies] +phper = "0.2" +``` + +4. Add these code to `main.rs`. + +```rust,no_run +use phper::cmd::make; + +fn main() { + make(); +} +``` + +5. Write you owned extension logic in `lib.rs`. + +```rust +use phper::{php_get_module, modules::Module}; + +#[php_get_module] +pub fn get_module(module: &mut Module) { + // set module metadata + module.set_name(env!("CARGO_PKG_NAME")); + module.set_version(env!("CARGO_PKG_VERSION")); + module.set_author(env!("CARGO_PKG_AUTHORS")); + + // ... +} +``` + +6. Build and install, if your php isn't installed globally, you should specify the path of `php-config`. + +```bash +# Specify if php isn't installed globally. +export PHP_CONFIG = + +# Build libmyapp.so. +cargo build --release + +# Install to php extension path, if you install php globally, you should use sudo. +cargo run --release -- install +``` + +7. Edit your `php.ini`, add the below line. + +```ini +extension = myapp +``` + +8. Enjoy. + +## examples + +See [examples](https://github.com/jmjoy/phper/tree/master/examples). -Now the library don't support `ZTS`, the template is using `thread_local!` instead. +## License -Version `0.1.x` will be a preview version. +[Unlicense](https://github.com/jmjoy/phper/blob/master/LICENSE). */ -mod error; -pub mod main; +pub mod arrays; +pub mod classes; +pub mod cmd; +mod errors; +pub mod functions; +pub mod ini; +pub mod logs; +pub mod modules; +pub mod strings; mod utils; -pub mod zend; +pub mod values; -pub use crate::error::*; +pub use crate::errors::*; pub use phper_alloc as alloc; pub use phper_macros::*; pub use phper_sys as sys; diff --git a/phper/src/zend/errors.rs b/phper/src/logs.rs similarity index 100% rename from phper/src/zend/errors.rs rename to phper/src/logs.rs diff --git a/phper/src/main/mod.rs b/phper/src/main/mod.rs deleted file mode 100644 index ec85295d..00000000 --- a/phper/src/main/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod php; diff --git a/phper/src/main/php.rs b/phper/src/main/php.rs deleted file mode 100644 index 44972b02..00000000 --- a/phper/src/main/php.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::zend::errors::Level; -use std::{os::raw::c_int, ptr::null}; - -pub fn error_doc_ref(level: Level, message: impl ToString) { - let mut message = message.to_string(); - message.push('\0'); - - unsafe { - #[cfg(phper_php_version = "7.4")] - crate::sys::php_error_docref(null(), level as c_int, message.as_ptr().cast()); - - #[cfg(any( - phper_php_version = "7.3", - phper_php_version = "7.2", - phper_php_version = "7.1", - phper_php_version = "7.0", - ))] - crate::sys::php_error_docref0(null(), level as c_int, message.as_ptr().cast()); - } -} diff --git a/phper/src/modules.rs b/phper/src/modules.rs new file mode 100644 index 00000000..ac95db05 --- /dev/null +++ b/phper/src/modules.rs @@ -0,0 +1,339 @@ +use crate::{ + c_str_ptr, + classes::{Class, ClassEntity, StdClass}, + functions::{Argument, Callable, Function, FunctionEntity}, + ini::{IniEntity, IniValue, Policy, StrPtrBox}, + sys::*, + EXCEPTION_CLASS_NAME, +}; +use once_cell::sync::Lazy; +use std::{ + borrow::BorrowMut, + cell::RefCell, + collections::HashMap, + mem::{size_of, zeroed}, + os::raw::{c_int, c_uchar, c_uint, c_ushort}, + ptr::{null, null_mut}, + sync::RwLock, +}; + +static GLOBAL_MODULE: Lazy> = Lazy::new(Default::default); + +pub fn read_global_module(f: impl FnOnce(&Module) -> R) -> R { + let module = (&*GLOBAL_MODULE).read().expect("get write lock failed"); + f(&module) +} + +pub fn write_global_module(f: impl FnOnce(&mut Module) -> R) -> R { + let mut module = (&*GLOBAL_MODULE).write().expect("get write lock failed"); + f(&mut module) +} + +unsafe extern "C" fn module_startup(r#type: c_int, module_number: c_int) -> c_int { + let args = ModuleArgs::new(r#type, module_number); + write_global_module(|module| { + args.register_ini_entries(module.ini_entries()); + for (_, class_entity) in &mut module.class_entities { + class_entity.init(); + class_entity.declare_properties(); + } + match &module.module_init { + Some(f) => f(args) as c_int, + None => 1, + } + }) +} + +unsafe extern "C" fn module_shutdown(r#type: c_int, module_number: c_int) -> c_int { + let args = ModuleArgs::new(r#type, module_number); + args.unregister_ini_entries(); + read_global_module(|module| match &module.module_shutdown { + Some(f) => f(args) as c_int, + None => 1, + }) +} + +unsafe extern "C" fn request_startup(r#type: c_int, request_number: c_int) -> c_int { + read_global_module(|module| match &module.request_init { + Some(f) => f(ModuleArgs::new(r#type, request_number)) as c_int, + None => 1, + }) +} + +unsafe extern "C" fn request_shutdown(r#type: c_int, request_number: c_int) -> c_int { + read_global_module(|module| match &module.request_shutdown { + Some(f) => f(ModuleArgs::new(r#type, request_number)) as c_int, + None => 1, + }) +} + +unsafe extern "C" fn module_info(zend_module: *mut zend_module_entry) { + read_global_module(|module| { + php_info_print_table_start(); + if !module.version.is_empty() { + php_info_print_table_row(2, c_str_ptr!("version"), module.version.as_ptr()); + } + if !module.author.is_empty() { + php_info_print_table_row(2, c_str_ptr!("authors"), module.author.as_ptr()); + } + php_info_print_table_end(); + }); + display_ini_entries(zend_module); +} + +#[derive(Default)] +pub struct Module { + name: String, + version: String, + author: String, + module_init: Option bool + Send + Sync>>, + module_shutdown: Option bool + Send + Sync>>, + request_init: Option bool + Send + Sync>>, + request_shutdown: Option bool + Send + Sync>>, + function_entities: Vec, + pub(crate) class_entities: HashMap, +} + +impl Module { + thread_local! { + static BOOL_INI_ENTITIES: RefCell>> = Default::default(); + static LONG_INI_ENTITIES: RefCell>> = Default::default(); + static REAL_INI_ENTITIES: RefCell>> = Default::default(); + static STR_INI_ENTITIES: RefCell>> = Default::default(); + } + + pub fn set_name(&mut self, name: impl ToString) { + let mut name = name.to_string(); + name.push('\0'); + self.name = name; + } + + pub fn set_version(&mut self, version: impl ToString) { + let mut version = version.to_string(); + version.push('\0'); + self.version = version; + } + + pub fn set_author(&mut self, author: impl ToString) { + let mut author = author.to_string(); + author.push('\0'); + self.author = author; + } + + pub fn on_module_init(&mut self, func: impl Fn(ModuleArgs) -> bool + Send + Sync + 'static) { + self.module_init = Some(Box::new(func)); + } + + pub fn on_module_shutdown( + &mut self, + func: impl Fn(ModuleArgs) -> bool + Send + Sync + 'static, + ) { + self.module_shutdown = Some(Box::new(func)); + } + + pub fn on_request_init(&mut self, func: impl Fn(ModuleArgs) -> bool + Send + Sync + 'static) { + self.request_init = Some(Box::new(func)); + } + + pub fn on_request_shutdown( + &mut self, + func: impl Fn(ModuleArgs) -> bool + Send + Sync + 'static, + ) { + self.request_shutdown = Some(Box::new(func)); + } + + pub fn add_bool_ini(&mut self, name: impl ToString, default_value: bool, policy: Policy) { + Self::BOOL_INI_ENTITIES.with(|entities| { + entities.borrow_mut().insert( + name.to_string(), + IniEntity::new(name, default_value, policy), + ); + }) + } + + pub fn get_bool_ini(name: &str) -> Option { + Self::BOOL_INI_ENTITIES + .with(|entities| entities.borrow().get(name).map(|entity| *entity.value())) + } + + pub fn add_long_ini(&mut self, name: impl ToString, default_value: i64, policy: Policy) { + Self::LONG_INI_ENTITIES.with(|entities| { + entities.borrow_mut().insert( + name.to_string(), + IniEntity::new(name, default_value, policy), + ); + }) + } + + pub fn get_long_ini(name: &str) -> Option { + Self::LONG_INI_ENTITIES + .with(|entities| entities.borrow().get(name).map(|entity| *entity.value())) + } + + pub fn add_real_ini(&mut self, name: impl ToString, default_value: f64, policy: Policy) { + Self::REAL_INI_ENTITIES.with(|entities| { + entities.borrow_mut().insert( + name.to_string(), + IniEntity::new(name, default_value, policy), + ); + }) + } + + pub fn get_real_ini(name: &str) -> Option { + Self::REAL_INI_ENTITIES + .with(|entities| entities.borrow().get(name).map(|entity| *entity.value())) + } + + pub fn add_str_ini( + &mut self, + name: impl ToString, + default_value: impl ToString, + policy: Policy, + ) { + Self::STR_INI_ENTITIES.with(|entities| { + entities.borrow_mut().insert( + name.to_string(), + IniEntity::new(name, default_value, policy), + ); + }) + } + + pub fn get_str_ini(name: &str) -> Option { + Self::STR_INI_ENTITIES.with(|entities| { + entities + .borrow() + .get(name) + .and_then(|entity| unsafe { entity.value().to_string() }.ok()) + }) + } + + pub fn add_function( + &mut self, + name: impl ToString, + handler: impl Function + 'static, + arguments: Vec, + ) { + self.function_entities.push(FunctionEntity::new( + name, + Callable::Function(Box::new(handler)), + arguments, + )); + } + + pub fn add_class(&mut self, name: impl ToString, class: impl Class + 'static) { + self.class_entities + .insert(name.to_string(), unsafe { ClassEntity::new(name, class) }); + } + + pub unsafe fn module_entry(&mut self) -> *const zend_module_entry { + assert!(!self.name.is_empty(), "module name must be set"); + assert!(!self.version.is_empty(), "module version must be set"); + + self.add_error_exception_class(); + + let entry: Box = Box::new(zend_module_entry { + size: size_of::() as c_ushort, + zend_api: ZEND_MODULE_API_NO as c_uint, + zend_debug: ZEND_DEBUG as c_uchar, + zts: USING_ZTS as c_uchar, + ini_entry: null(), + deps: null(), + name: self.name.as_ptr().cast(), + functions: self.function_entries(), + module_startup_func: Some(module_startup), + module_shutdown_func: Some(module_shutdown), + request_startup_func: Some(request_startup), + request_shutdown_func: Some(request_shutdown), + info_func: Some(module_info), + version: null(), + globals_size: 0usize, + #[cfg(phper_zts)] + globals_id_ptr: std::ptr::null_mut(), + #[cfg(not(phper_zts))] + globals_ptr: std::ptr::null_mut(), + globals_ctor: None, + globals_dtor: None, + post_deactivate_func: None, + module_started: 0, + type_: 0, + handle: null_mut(), + module_number: 0, + build_id: PHP_MODULE_BUILD_ID, + }); + + Box::into_raw(entry) + } + + fn function_entries(&self) -> *const zend_function_entry { + if self.function_entities.is_empty() { + return null(); + } + + let mut entries = Vec::new(); + for f in &self.function_entities { + entries.push(unsafe { f.entry() }); + } + entries.push(unsafe { zeroed::() }); + + Box::into_raw(entries.into_boxed_slice()).cast() + } + + unsafe fn ini_entries(&self) -> *const zend_ini_entry_def { + let mut entries = Vec::new(); + + Self::BOOL_INI_ENTITIES + .with(|entities| Self::push_ini_entry(&mut entries, &mut *entities.borrow_mut())); + Self::LONG_INI_ENTITIES + .with(|entities| Self::push_ini_entry(&mut entries, &mut *entities.borrow_mut())); + Self::REAL_INI_ENTITIES + .with(|entities| Self::push_ini_entry(&mut entries, &mut *entities.borrow_mut())); + Self::STR_INI_ENTITIES + .with(|entities| Self::push_ini_entry(&mut entries, &mut *entities.borrow_mut())); + + entries.push(zeroed::()); + + Box::into_raw(entries.into_boxed_slice()).cast() + } + + unsafe fn push_ini_entry( + entries: &mut Vec, + entities: &mut HashMap>, + ) { + for (_, entry) in &mut *entities.borrow_mut() { + entries.push(entry.ini_entry_def()); + } + } + + fn add_error_exception_class(&mut self) { + let mut class = StdClass::new(); + class.extends("\\Exception"); + self.add_class(EXCEPTION_CLASS_NAME, class); + } +} + +pub struct ModuleArgs { + #[allow(dead_code)] + r#type: c_int, + module_number: c_int, +} + +impl ModuleArgs { + pub const fn new(r#type: c_int, module_number: c_int) -> Self { + Self { + r#type, + module_number, + } + } + + pub(crate) fn register_ini_entries(&self, ini_entries: *const zend_ini_entry_def) { + unsafe { + zend_register_ini_entries(ini_entries, self.module_number); + } + } + + pub(crate) fn unregister_ini_entries(&self) { + unsafe { + zend_unregister_ini_entries(self.module_number); + } + } +} diff --git a/phper/src/strings.rs b/phper/src/strings.rs new file mode 100644 index 00000000..bce9648f --- /dev/null +++ b/phper/src/strings.rs @@ -0,0 +1,34 @@ +use crate::sys::*; + +pub struct ZString { + inner: *mut zend_string, +} + +impl ZString { + pub fn new() -> Self { + unsafe { + Self { + inner: phper_zend_string_alloc(0, 1), + } + } + } +} + +impl> From for ZString { + fn from(t: T) -> Self { + let s = t.as_ref(); + unsafe { + Self { + inner: phper_zend_string_init(s.as_ptr().cast(), s.len(), 1), + } + } + } +} + +impl Drop for ZString { + fn drop(&mut self) { + unsafe { + phper_zend_string_release(self.inner); + } + } +} diff --git a/phper/src/values.rs b/phper/src/values.rs new file mode 100644 index 00000000..73843896 --- /dev/null +++ b/phper/src/values.rs @@ -0,0 +1,257 @@ +use crate::{arrays::Array, errors::Throwable, sys::*}; +use std::{mem::zeroed, slice::from_raw_parts, str, sync::atomic::Ordering}; + +#[repr(transparent)] +pub struct ExecuteData { + inner: zend_execute_data, +} + +impl ExecuteData { + #[inline] + pub const fn new(inner: zend_execute_data) -> Self { + Self { inner } + } + + pub unsafe fn from_mut<'a>(ptr: *mut zend_execute_data) -> &'a mut Self { + &mut *(ptr as *mut Self) + } + + pub fn as_mut_ptr(&mut self) -> *mut zend_execute_data { + &mut self.inner + } + + #[inline] + pub unsafe fn common_num_args(&self) -> u32 { + (*self.inner.func).common.num_args + } + + #[inline] + pub unsafe fn common_required_num_args(&self) -> u16 { + (*self.inner.func).common.required_num_args as u16 + } + + #[inline] + pub unsafe fn common_arg_info(&self) -> *mut zend_arg_info { + (*self.inner.func).common.arg_info + } + + #[inline] + pub unsafe fn num_args(&self) -> u16 { + self.inner.This.u2.num_args as u16 + } + + #[inline] + pub unsafe fn get_this(&mut self) -> *mut Val { + phper_get_this(&mut self.inner).cast() + } + + pub unsafe fn get_parameters_array(&mut self) -> Vec { + let num_args = self.num_args(); + let mut arguments = vec![zeroed::(); num_args as usize]; + _zend_get_parameters_array_ex(num_args.into(), arguments.as_mut_ptr()); + arguments.into_iter().map(Val::from_inner).collect() + } +} + +#[repr(transparent)] +pub struct Val { + pub(crate) inner: zval, +} + +impl Val { + pub fn new(t: T) -> Self { + let mut val = Self::empty(); + val.set(t); + val + } + + #[inline] + pub const fn from_inner(inner: zval) -> Self { + Self { inner } + } + + pub unsafe fn from_mut<'a>(ptr: *mut zval) -> &'a mut Self { + assert!(!ptr.is_null(), "ptr should not be null"); + &mut *(ptr as *mut Self) + } + + #[inline] + fn empty() -> Self { + Self { + inner: unsafe { zeroed::() }, + } + } + + pub fn null() -> Self { + let mut val = Self::empty(); + val.set(&()); + val + } + + pub fn from_bool(b: bool) -> Self { + let mut val = Self::empty(); + val.set(b); + val + } + + pub fn from_val(other: &Val) -> Self { + let mut val = Self::empty(); + val.set(other); + val + } + + pub fn as_mut(&mut self) -> *mut zval { + &mut self.inner + } + + pub fn set(&mut self, v: impl SetVal) { + v.set_val(self); + } + + unsafe fn type_info(&mut self) -> &mut u32 { + &mut self.inner.u1.type_info + } + + pub fn as_string(&mut self) -> String { + unsafe { + let s = phper_zval_get_string(&mut self.inner); + let buf = from_raw_parts(&(*s).val as *const i8 as *const u8, (*s).len); + phper_zend_string_release(s); + str::from_utf8(buf).unwrap().to_string() + } + } + + pub fn as_i64(&mut self) -> i64 { + unsafe { phper_zval_get_long(&mut self.inner) } + } +} + +pub trait SetVal { + fn set_val(&self, val: &mut Val); +} + +impl SetVal for () { + fn set_val(&self, val: &mut Val) { + unsafe { + *val.type_info() = IS_NULL; + } + } +} + +impl SetVal for bool { + fn set_val(&self, val: &mut Val) { + unsafe { + *val.type_info() = if *self { IS_TRUE } else { IS_FALSE }; + } + } +} + +impl SetVal for i32 { + fn set_val(&self, val: &mut Val) { + (*self as i64).set_val(val) + } +} + +impl SetVal for u32 { + fn set_val(&self, val: &mut Val) { + (*self as i64).set_val(val) + } +} + +impl SetVal for i64 { + fn set_val(&self, val: &mut Val) { + unsafe { + (*val.as_mut()).value.lval = *self; + (*val.as_mut()).u1.type_info = IS_LONG; + } + } +} + +impl SetVal for f64 { + fn set_val(&self, val: &mut Val) { + unsafe { + (*val.as_mut()).value.dval = *self; + (*val.as_mut()).u1.type_info = IS_DOUBLE; + } + } +} + +impl SetVal for str { + fn set_val(&self, val: &mut Val) { + unsafe { + phper_zval_stringl(val.as_mut(), self.as_ptr().cast(), self.len()); + } + } +} + +impl SetVal for String { + fn set_val(&self, val: &mut Val) { + unsafe { + phper_zval_stringl(val.as_mut(), self.as_ptr().cast(), self.len()); + } + } +} + +impl SetVal for Array { + fn set_val(&self, val: &mut Val) { + unsafe { + phper_zval_arr(val.as_mut(), self.as_ptr() as *mut _); + } + } +} + +impl SetVal for Option { + fn set_val(&self, val: &mut Val) { + match self { + Some(t) => t.set_val(val), + None => ().set_val(val), + } + } +} + +impl SetVal for Result { + fn set_val(&self, val: &mut Val) { + match self { + Ok(t) => t.set_val(val), + Err(e) => unsafe { + let class = e + .class_entity() + .as_ref() + .expect("class entry is null pointer"); + let mut message = e.to_string(); + message.push('\0'); + zend_throw_exception( + class.entry.load(Ordering::SeqCst).cast(), + message.as_ptr().cast(), + e.code() as i64, + ); + }, + } + } +} + +impl SetVal for Val { + fn set_val(&self, val: &mut Val) { + unsafe { + phper_zval_copy(val.as_mut(), &self.inner as *const _ as *mut _); + } + } +} + +impl SetVal for Box { + fn set_val(&self, val: &mut Val) { + T::set_val(&self, val) + } +} + +impl SetVal for &T { + fn set_val(&self, val: &mut Val) { + T::set_val(self, val) + } +} + +impl SetVal for &mut T { + fn set_val(&self, val: &mut Val) { + T::set_val(self, val) + } +} diff --git a/phper/src/zend/api.rs b/phper/src/zend/api.rs deleted file mode 100644 index e93dcad8..00000000 --- a/phper/src/zend/api.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::{ - sys::{zend_function_entry, zend_ini_entry_def, zend_internal_arg_info, zif_handler}, - zend::{ - compile::MultiInternalArgInfo, - ini::{create_ini_entry_ex, Mh}, - }, -}; -use std::{ - cell::Cell, - mem::{size_of, transmute}, - os::raw::c_char, - ptr::null, -}; - -const fn function_entry_end() -> zend_function_entry { - unsafe { transmute([0u8; size_of::()]) } -} - -pub struct ModuleGlobals { - inner: Cell, -} - -impl ModuleGlobals { - pub const fn new(inner: T) -> Self { - Self { - inner: Cell::new(inner), - } - } - - pub const fn as_ptr(&self) -> *mut T { - self.inner.as_ptr() - } - - pub const fn create_ini_entry( - &self, - name: &str, - default_value: &str, - on_modify: Option, - modifiable: u32, - ) -> zend_ini_entry_def { - create_ini_entry_ex( - name, - default_value, - on_modify, - modifiable, - self.as_ptr().cast(), - ) - } -} - -impl ModuleGlobals { - pub fn get(&self) -> T { - self.inner.get() - } -} - -unsafe impl Sync for ModuleGlobals {} - -#[repr(C)] -struct ZendFunctionEntriesWithEnd([zend_function_entry; N], zend_function_entry); - -pub struct FunctionEntries { - inner: Cell>, -} - -impl FunctionEntries { - pub const fn new(inner: [zend_function_entry; N]) -> Self { - Self { - inner: Cell::new(ZendFunctionEntriesWithEnd(inner, function_entry_end())), - } - } - - pub const fn as_ptr(&self) -> *mut zend_function_entry { - self.inner.as_ptr().cast() - } -} - -unsafe impl Sync for FunctionEntries {} - -pub struct FunctionEntryBuilder { - fname: *const c_char, - handler: zif_handler, - arg_info: *const zend_internal_arg_info, - num_args: u32, - flags: u32, -} - -impl FunctionEntryBuilder { - pub const fn new(fname: *const c_char, handler: zif_handler) -> Self { - Self { - fname, - handler, - arg_info: null(), - num_args: 0, - flags: 0, - } - } - - pub const fn arg_info( - self, - arg_info: &'static MultiInternalArgInfo, - ) -> Self { - Self { - arg_info: arg_info.as_ptr(), - num_args: arg_info.len() as u32, - ..self - } - } - - pub const fn flags(self, flags: u32) -> Self { - Self { flags, ..self } - } - - pub const fn build(self) -> zend_function_entry { - zend_function_entry { - fname: self.fname, - handler: self.handler, - arg_info: self.arg_info, - num_args: self.num_args, - flags: self.flags, - } - } -} diff --git a/phper/src/zend/compile.rs b/phper/src/zend/compile.rs deleted file mode 100644 index 5d5ea3ed..00000000 --- a/phper/src/zend/compile.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::sys::{ - zend_internal_arg_info, zend_uchar, ZEND_ACC_PRIVATE, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, -}; -use std::{cell::Cell, os::raw::c_char}; - -#[repr(C)] -struct ZendInternalArgInfosWithEnd( - zend_internal_arg_info, - [zend_internal_arg_info; N], -); - -pub struct MultiInternalArgInfo { - inner: Cell>, -} - -impl MultiInternalArgInfo { - pub const fn new( - required_num_args: usize, - return_reference: bool, - inner: [zend_internal_arg_info; N], - ) -> Self { - Self { - inner: Cell::new(ZendInternalArgInfosWithEnd( - create_zend_arg_info(required_num_args as *const _, return_reference), - inner, - )), - } - } - - pub const fn as_ptr(&self) -> *const zend_internal_arg_info { - self.inner.as_ptr().cast() - } - - pub const fn len(&self) -> usize { - N - } -} - -unsafe impl Sync for MultiInternalArgInfo {} - -pub const fn create_zend_arg_info( - name: *const c_char, - pass_by_ref: bool, -) -> zend_internal_arg_info { - #[cfg(any( - phper_php_version = "7.4", - phper_php_version = "7.3", - phper_php_version = "7.2" - ))] - { - zend_internal_arg_info { - name, - type_: 0, - pass_by_reference: pass_by_ref as zend_uchar, - is_variadic: 0, - } - } - - #[cfg(any(phper_php_version = "7.1", phper_php_version = "7.0",))] - { - zend_internal_arg_info { - name, - class_name: std::ptr::null(), - type_hint: 0, - allow_null: 0, - pass_by_reference: pass_by_ref as zend_uchar, - is_variadic: 0, - } - } -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Visibility { - Public = ZEND_ACC_PUBLIC, - Protected = ZEND_ACC_PROTECTED, - Private = ZEND_ACC_PRIVATE, -} diff --git a/phper/src/zend/exceptions.rs b/phper/src/zend/exceptions.rs deleted file mode 100644 index 81a72a16..00000000 --- a/phper/src/zend/exceptions.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub trait Throwable {} - -pub struct MyException; - -impl Throwable for MyException {} diff --git a/phper/src/zend/ini.rs b/phper/src/zend/ini.rs deleted file mode 100644 index ec757d78..00000000 --- a/phper/src/zend/ini.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::sys::{zend_ini_entry, zend_ini_entry_def, zend_string}; -use std::{ - cell::Cell, - mem::{size_of, transmute}, - os::raw::{c_int, c_void}, - ptr::null_mut, -}; - -pub type Mh = unsafe extern "C" fn( - *mut zend_ini_entry, - *mut zend_string, - *mut c_void, - *mut c_void, - *mut c_void, - c_int, -) -> c_int; - -const fn ini_entry_def_end() -> zend_ini_entry_def { - unsafe { transmute([0u8; size_of::()]) } -} - -#[repr(C)] -struct ZendIniEntriesWithEnd([zend_ini_entry_def; N], zend_ini_entry_def); - -pub struct IniEntries { - inner: Cell>, -} - -impl IniEntries { - pub const fn new(inner: [zend_ini_entry_def; N]) -> Self { - Self { - inner: Cell::new(ZendIniEntriesWithEnd(inner, ini_entry_def_end())), - } - } - - #[inline] - pub const fn as_ptr(&self) -> *const zend_ini_entry_def { - self.inner.as_ptr().cast() - } -} - -unsafe impl Sync for IniEntries {} - -pub const fn create_ini_entry( - name: &str, - default_value: &str, - modifiable: u32, -) -> zend_ini_entry_def { - create_ini_entry_ex(name, default_value, None, modifiable, null_mut()) -} - -pub const fn create_ini_entry_ex( - name: &str, - default_value: &str, - on_modify: Option, - modifiable: u32, - arg2: *mut c_void, -) -> zend_ini_entry_def { - #[cfg(any(phper_php_version = "7.4", phper_php_version = "7.3"))] - let (modifiable, name_length) = (modifiable as std::os::raw::c_uchar, name.len() as u16); - #[cfg(any( - phper_php_version = "7.2", - phper_php_version = "7.1", - phper_php_version = "7.0", - ))] - let (modifiable, name_length) = (modifiable as std::os::raw::c_int, name.len() as u32); - - zend_ini_entry_def { - name: name.as_ptr().cast(), - on_modify, - mh_arg1: 0 as *mut _, - mh_arg2: arg2, - mh_arg3: null_mut(), - value: default_value.as_ptr().cast(), - displayer: None, - modifiable, - name_length, - value_length: default_value.len() as u32, - } -} diff --git a/phper/src/zend/mod.rs b/phper/src/zend/mod.rs deleted file mode 100644 index 58ceec22..00000000 --- a/phper/src/zend/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod api; -pub mod compile; -pub mod errors; -pub mod exceptions; -pub mod ini; -pub mod modules; -pub mod portability; -pub mod types; diff --git a/phper/src/zend/modules.rs b/phper/src/zend/modules.rs deleted file mode 100644 index 8a5a63ff..00000000 --- a/phper/src/zend/modules.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::{ - sys::{ - zend_function_entry, zend_module_entry, zend_register_ini_entries, - zend_unregister_ini_entries, PHP_MODULE_BUILD_ID, USING_ZTS, ZEND_DEBUG, - ZEND_MODULE_API_NO, - }, - zend::ini::IniEntries, -}; -use std::{ - cell::Cell, - mem::size_of, - os::raw::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void}, - ptr::{null, null_mut}, -}; - -pub struct ModuleEntryBuilder { - name: *const c_char, - version: *const c_char, - functions: *const zend_function_entry, - module_startup_func: Option c_int>, - module_shutdown_func: Option c_int>, - request_startup_func: Option c_int>, - request_shutdown_func: Option c_int>, - info_func: Option, - globals_ctor: Option, - globals_dtor: Option, -} - -impl ModuleEntryBuilder { - pub const fn new(name: *const c_char, version: *const c_char) -> Self { - Self { - name, - version, - functions: null(), - module_startup_func: None, - module_shutdown_func: None, - request_startup_func: None, - request_shutdown_func: None, - info_func: None, - globals_ctor: None, - globals_dtor: None, - } - } - - pub const fn functions(self, functions: *const zend_function_entry) -> Self { - Self { functions, ..self } - } - - pub const fn module_startup_func( - self, - module_startup_func: unsafe extern "C" fn(c_int, c_int) -> c_int, - ) -> Self { - Self { - module_startup_func: Some(module_startup_func), - ..self - } - } - - pub const fn module_shutdown_func( - self, - module_shutdown_func: unsafe extern "C" fn(c_int, c_int) -> c_int, - ) -> Self { - Self { - module_shutdown_func: Some(module_shutdown_func), - ..self - } - } - - pub const fn request_startup_func( - self, - request_startup_func: unsafe extern "C" fn(c_int, c_int) -> c_int, - ) -> Self { - Self { - request_startup_func: Some(request_startup_func), - ..self - } - } - - pub const fn request_shutdown_func( - self, - request_shutdown_func: unsafe extern "C" fn(c_int, c_int) -> c_int, - ) -> Self { - Self { - request_shutdown_func: Some(request_shutdown_func), - ..self - } - } - - pub const fn info_func(self, info_func: unsafe extern "C" fn(*mut zend_module_entry)) -> Self { - Self { - info_func: Some(info_func), - ..self - } - } - - pub const fn globals_ctor( - self, - globals_ctor: unsafe extern "C" fn(global: *mut c_void), - ) -> Self { - Self { - globals_ctor: Some(globals_ctor), - ..self - } - } - - pub const fn globals_dtor( - self, - globals_dtor: unsafe extern "C" fn(global: *mut c_void), - ) -> Self { - Self { - globals_dtor: Some(globals_dtor), - ..self - } - } - - pub const fn build(self) -> ModuleEntry { - ModuleEntry::new(zend_module_entry { - size: size_of::() as c_ushort, - zend_api: ZEND_MODULE_API_NO as c_uint, - zend_debug: ZEND_DEBUG as c_uchar, - zts: USING_ZTS as c_uchar, - ini_entry: null(), - deps: null(), - name: self.name, - functions: self.functions, - module_startup_func: self.module_startup_func, - module_shutdown_func: self.module_shutdown_func, - request_startup_func: self.request_startup_func, - request_shutdown_func: self.request_shutdown_func, - info_func: self.info_func, - version: self.version, - globals_size: 0usize, - #[cfg(phper_zts)] - globals_id_ptr: std::ptr::null_mut(), - #[cfg(not(phper_zts))] - globals_ptr: std::ptr::null_mut(), - globals_ctor: self.globals_ctor, - globals_dtor: self.globals_dtor, - post_deactivate_func: None, - module_started: 0, - type_: 0, - handle: null_mut(), - module_number: 0, - build_id: PHP_MODULE_BUILD_ID, - }) - } -} - -#[repr(transparent)] -pub struct ModuleEntry { - raw: Cell, -} - -impl ModuleEntry { - pub const fn new(raw: zend_module_entry) -> Self { - Self { - raw: Cell::new(raw), - } - } - - pub const fn as_ptr(&self) -> *mut zend_module_entry { - self.raw.as_ptr() - } - - pub fn from_ptr<'a>(ptr: *const zend_module_entry) -> &'a Self { - unsafe { &*(ptr as *const Self) } - } -} - -unsafe impl Sync for ModuleEntry {} - -pub struct ModuleArgs { - _type_: c_int, - module_number: c_int, -} - -impl ModuleArgs { - pub const fn new(_type_: c_int, module_number: c_int) -> Self { - Self { - _type_, - module_number, - } - } - - pub fn register_ini_entries(&self, ini_entries: &IniEntries) { - unsafe { - zend_register_ini_entries(ini_entries.as_ptr(), self.module_number); - } - } - - pub fn unregister_ini_entries(&self) { - unsafe { - zend_unregister_ini_entries(self.module_number); - } - } -} diff --git a/phper/src/zend/portability.rs b/phper/src/zend/portability.rs deleted file mode 100644 index 8b137891..00000000 --- a/phper/src/zend/portability.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/phper/src/zend/types.rs b/phper/src/zend/types.rs deleted file mode 100644 index 8626bf5e..00000000 --- a/phper/src/zend/types.rs +++ /dev/null @@ -1,739 +0,0 @@ -use crate::{ - c_str_ptr, - sys::{ - self, phper_get_this, phper_init_class_entry_ex, phper_z_strval_p, phper_zval_get_type, - phper_zval_stringl, zend_class_entry, zend_declare_property_bool, - zend_declare_property_long, zend_declare_property_null, zend_declare_property_stringl, - zend_execute_data, zend_long, zend_parse_parameters, zend_read_property, - zend_register_internal_class, zend_throw_exception, zend_update_property_bool, - zend_update_property_long, zend_update_property_null, zend_update_property_stringl, zval, - IS_DOUBLE, IS_FALSE, IS_LONG, IS_NULL, IS_TRUE, ZEND_RESULT_CODE_SUCCESS, - }, - zend::{api::FunctionEntries, compile::Visibility, exceptions::Throwable}, -}; -use std::{ - borrow::Cow, - cell::Cell, - ffi::{c_void, CStr}, - os::raw::{c_char, c_int}, - ptr::null_mut, - slice, str, -}; - -pub struct ClassEntry { - inner: Cell<*mut zend_class_entry>, -} - -impl ClassEntry { - pub const fn new() -> Self { - Self { - inner: Cell::new(null_mut()), - } - } - - pub const fn as_ptr(&self) -> *mut *mut zend_class_entry { - self.inner.as_ptr() - } - - pub fn get(&self) -> *mut zend_class_entry { - self.inner.get() - } - - pub fn init( - &self, - class_name: impl AsRef, - functions: &FunctionEntries, - ) { - let class_name = class_name.as_ref(); - unsafe { - let mut class_ce = phper_init_class_entry_ex( - class_name.as_ptr().cast(), - class_name.len(), - functions.as_ptr(), - ); - *self.as_ptr() = zend_register_internal_class(&mut class_ce); - } - } - - pub fn declare_property( - &self, - name: impl AsRef, - value: impl HandleProperty, - access_type: Visibility, - ) -> bool { - unsafe { - value.declare_property(self.get(), name.as_ref(), access_type as c_int) - == ZEND_RESULT_CODE_SUCCESS - } - } - - pub fn update_property( - &self, - object: *mut zval, - name: impl AsRef, - value: impl HandleProperty, - ) { - unsafe { value.update_property(self.get(), object, name.as_ref()) } - } - - pub fn read_property(&self, this: *mut zval, name: impl AsRef) -> &mut Val { - let name = name.as_ref(); - unsafe { - let v = zend_read_property( - self.get(), - this, - name.as_ptr().cast(), - name.len(), - 1, - null_mut(), - ); - Val::from_mut(v) - } - } -} - -unsafe impl Sync for ClassEntry {} - -pub trait HandleProperty { - unsafe fn declare_property( - self, - ce: *mut zend_class_entry, - name: &str, - access_type: c_int, - ) -> c_int; - - unsafe fn update_property(self, scope: *mut zend_class_entry, object: *mut zval, name: &str); -} - -impl HandleProperty for () { - unsafe fn declare_property( - self, - ce: *mut zend_class_entry, - name: &str, - access_type: i32, - ) -> i32 { - zend_declare_property_null(ce, name.as_ptr().cast(), name.len(), access_type) - } - - unsafe fn update_property(self, scope: *mut zend_class_entry, object: *mut zval, name: &str) { - zend_update_property_null(scope, object, name.as_ptr().cast(), name.len()) - } -} - -impl HandleProperty for bool { - unsafe fn declare_property( - self, - ce: *mut zend_class_entry, - name: &str, - access_type: i32, - ) -> i32 { - zend_declare_property_bool( - ce, - name.as_ptr().cast(), - name.len(), - self as zend_long, - access_type, - ) - } - - unsafe fn update_property(self, scope: *mut zend_class_entry, object: *mut zval, name: &str) { - zend_update_property_bool( - scope, - object, - name.as_ptr().cast(), - name.len(), - self as zend_long, - ) - } -} - -impl HandleProperty for i64 { - unsafe fn declare_property( - self, - ce: *mut zend_class_entry, - name: &str, - access_type: i32, - ) -> i32 { - zend_declare_property_long( - ce, - name.as_ptr().cast(), - name.len(), - self as zend_long, - access_type, - ) - } - - unsafe fn update_property(self, scope: *mut zend_class_entry, object: *mut zval, name: &str) { - zend_update_property_long( - scope, - object, - name.as_ptr().cast(), - name.len(), - self as zend_long, - ) - } -} - -impl HandleProperty for &str { - unsafe fn declare_property( - self, - ce: *mut zend_class_entry, - name: &str, - access_type: i32, - ) -> c_int { - zend_declare_property_stringl( - ce, - name.as_ptr().cast(), - name.len(), - self.as_ptr().cast(), - self.len(), - access_type, - ) - } - - unsafe fn update_property(self, scope: *mut zend_class_entry, object: *mut zval, name: &str) { - zend_update_property_stringl( - scope, - object, - name.as_ptr().cast(), - name.len(), - self.as_ptr().cast(), - self.len(), - ) - } -} - -#[repr(transparent)] -pub struct ExecuteData { - inner: zend_execute_data, -} - -impl ExecuteData { - pub unsafe fn from_mut<'a>(ptr: *mut zend_execute_data) -> &'a mut Self { - &mut *(ptr as *mut Self) - } - - pub fn as_mut(&mut self) -> *mut zend_execute_data { - &mut self.inner - } - - #[inline] - pub fn num_args(&self) -> usize { - unsafe { self.inner.This.u2.num_args as usize } - } - - #[inline] - pub fn get_this(&mut self) -> *mut zval { - unsafe { phper_get_this(&mut self.inner) } - } - - pub fn parse_parameters(&self) -> Option { - ::parse(self.num_args(), ()) - } - - pub fn parse_parameters_optional( - &self, - default: O, - ) -> Option { - ::parse(self.num_args(), default) - } -} - -pub trait ParseParameter: Sized { - fn spec() -> Cow<'static, str>; - - fn num_parameters() -> usize; - - fn parameters() -> Vec<*mut c_void>; - - fn from_parameters(parameters: &[*mut c_void]) -> Option; - - fn parse(num_args: usize, optional: O) -> Option { - let parameters = Self::parameters(); - let mut spec = Self::spec(); - - let num_optional = ::num_optional(); - if num_optional > 0 { - let s = spec.to_mut(); - s.insert(s.len() - num_optional, '|'); - unsafe { - optional.set_optional(¶meters); - } - } - - if zend_parse_fixed_parameters(num_args, &spec, ¶meters) { - Self::from_parameters(¶meters) - } else { - None - } - } -} - -impl ParseParameter for () { - #[inline] - fn spec() -> Cow<'static, str> { - Cow::Borrowed("") - } - - fn num_parameters() -> usize { - 0 - } - - #[inline] - fn parameters() -> Vec<*mut c_void> { - Vec::new() - } - - #[inline] - fn from_parameters(_parameters: &[*mut c_void]) -> Option { - Some(()) - } -} - -impl ParseParameter for bool { - #[inline] - fn spec() -> Cow<'static, str> { - Cow::Borrowed("b") - } - - #[inline] - fn num_parameters() -> usize { - 1 - } - - #[inline] - fn parameters() -> Vec<*mut c_void> { - vec![Box::into_raw(Box::new(false)).cast()] - } - - fn from_parameters(parameters: &[*mut c_void]) -> Option { - let b = unsafe { Box::from_raw(parameters[0] as *mut bool) }; - Some(*b) - } -} - -impl ParseParameter for i64 { - #[inline] - fn spec() -> Cow<'static, str> { - Cow::Borrowed("l") - } - - #[inline] - fn num_parameters() -> usize { - 1 - } - - #[inline] - fn parameters() -> Vec<*mut c_void> { - vec![Box::into_raw(Box::new(0i64)).cast()] - } - - fn from_parameters(parameters: &[*mut c_void]) -> Option { - let i = unsafe { Box::from_raw(parameters[0] as *mut i64) }; - Some(*i) - } -} - -impl ParseParameter for f64 { - #[inline] - fn spec() -> Cow<'static, str> { - Cow::Borrowed("d") - } - - #[inline] - fn num_parameters() -> usize { - 1 - } - - #[inline] - fn parameters() -> Vec<*mut c_void> { - vec![Box::into_raw(Box::new(0f64)).cast()] - } - - fn from_parameters(parameters: &[*mut c_void]) -> Option { - let i = unsafe { Box::from_raw(parameters[0] as *mut f64) }; - Some(*i) - } -} - -impl ParseParameter for &str { - #[inline] - fn spec() -> Cow<'static, str> { - Cow::Borrowed("s") - } - - #[inline] - fn num_parameters() -> usize { - 2 - } - - #[inline] - fn parameters() -> Vec<*mut c_void> { - vec![ - Box::into_raw(Box::new(null_mut::())).cast(), - Box::into_raw(Box::new(0u32)).cast(), - ] - } - - fn from_parameters(parameters: &[*mut c_void]) -> Option { - unsafe { - let ptr = Box::from_raw(parameters[0] as *mut *mut c_char); - let len = Box::from_raw(parameters[1] as *mut c_int); - let bytes = slice::from_raw_parts(*ptr as *const u8, *len as usize); - str::from_utf8(bytes).ok() - } - } -} - -macro_rules! impl_parse_parameter_for_tuple { - ( $(($t:ident,$T:ident)),* ) => { - impl<$($T: ParseParameter,)*> ParseParameter for ($($T,)*) { - fn spec() -> Cow<'static, str> { - let mut s= String::new(); - $(s.push_str(&<$T>::spec());)* - Cow::Owned(s) - } - - #[inline] - fn num_parameters() -> usize { - 0 $( + <$T>::num_parameters())* - } - - fn parameters() -> Vec<*mut c_void> { - let mut parameters = Vec::new(); - $(parameters.extend_from_slice(&<$T>::parameters());)* - parameters - } - - fn from_parameters(parameters: &[*mut c_void]) -> Option { - let mut i = 0; - - $(let $t = { - let j = i; - i += <$T>::num_parameters(); - match <$T>::from_parameters(¶meters[j..i]) { - Some(item) => item, - None => return None, - } - };)* - - Some(($($t,)*)) - } - } - }; -} - -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); -#[rustfmt::skip] impl_parse_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I), (j, J)); - -macro_rules! call_zend_parse_parameters { - ( $num_args:expr, $type_spec:expr, $parameters:expr $(,$i:expr)* ) => { - unsafe { zend_parse_parameters($num_args, $type_spec, $($parameters.get_unchecked($i).clone(),)*) } - } -} - -fn zend_parse_fixed_parameters( - num_args: usize, - type_spec: &str, - parameters: &[*mut c_void], -) -> bool { - assert!(parameters.len() <= 20); - let type_spec = format!("{}\0", type_spec); - - #[rustfmt::skip] - let b = match parameters.len() { - 0 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters), - 1 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0), - 2 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1), - 3 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2), - 4 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3), - 5 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4), - 6 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5), - 7 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6), - 8 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7), - 9 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8), - 10 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), - 11 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), - 12 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), - 13 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), - 14 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), - 15 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), - 16 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), - 17 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), - 18 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), - 19 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18), - 20 => call_zend_parse_parameters!(num_args as c_int, type_spec.as_ptr().cast(), parameters, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), - _ => unreachable!(), - }; - - b == ZEND_RESULT_CODE_SUCCESS -} - -pub trait OptionalParameter: ParseParameter { - fn num_optional() -> usize; - unsafe fn set_optional(self, parameters: &[*mut c_void]); -} - -impl OptionalParameter for () { - fn num_optional() -> usize { - 0 - } - - unsafe fn set_optional(self, _parameters: &[*mut c_void]) {} -} - -impl OptionalParameter for bool { - fn num_optional() -> usize { - 1 - } - - unsafe fn set_optional(self, parameters: &[*mut c_void]) { - *(parameters[parameters.len() - 1] as *mut Self) = self; - } -} - -impl OptionalParameter for i64 { - fn num_optional() -> usize { - 1 - } - - unsafe fn set_optional(self, parameters: &[*mut c_void]) { - *(parameters[parameters.len() - 1] as *mut Self) = self; - } -} - -impl OptionalParameter for f64 { - fn num_optional() -> usize { - 1 - } - - unsafe fn set_optional(self, parameters: &[*mut c_void]) { - *(parameters[parameters.len() - 1] as *mut Self) = self; - } -} - -impl OptionalParameter for &'static str { - fn num_optional() -> usize { - 1 - } - - unsafe fn set_optional(self, parameters: &[*mut c_void]) { - *(parameters[parameters.len() - 2] as *mut *const c_char) = self.as_ptr().cast(); - *(parameters[parameters.len() - 1] as *mut c_int) = self.len() as c_int; - } -} - -macro_rules! impl_optional_parameter_for_tuple { - ( $(($i:ident,$T:ident)),* ) => { - impl<$($T: OptionalParameter,)*> OptionalParameter for ($($T,)*) { - fn num_optional() -> usize { - 0 $( + <$T>::num_optional())* - } - - #[allow(unused_assignments)] - unsafe fn set_optional(self, parameters: &[*mut c_void]) { - let mut i = parameters.len() - ::num_parameters(); - let ($($i, )*) = self; - - $({ - let j = i + <$T as ParseParameter>::num_parameters(); - $i.set_optional(¶meters[i..j]); - i = j; - })* - } - } - } -} - -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); -#[rustfmt::skip] impl_optional_parameter_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I), (j, J)); - -#[repr(transparent)] -pub struct Val { - inner: zval, -} - -impl Val { - pub unsafe fn from_mut<'a>(ptr: *mut zval) -> &'a mut Self { - &mut *(ptr as *mut Self) - } - - pub fn as_mut(&mut self) -> *mut zval { - &mut self.inner - } - - pub fn try_into_value<'a>(&self) -> crate::Result> { - Value::from_ptr(&self.inner) - } - - unsafe fn type_info(&mut self) -> &mut u32 { - &mut self.inner.u1.type_info - } -} - -pub trait SetVal { - fn set_val(self, val: &mut Val); -} - -impl SetVal for () { - fn set_val(self, val: &mut Val) { - unsafe { - *val.type_info() = IS_NULL; - } - } -} - -impl SetVal for bool { - fn set_val(self, val: &mut Val) { - unsafe { - *val.type_info() = if self { IS_TRUE } else { IS_FALSE }; - } - } -} - -impl SetVal for i32 { - fn set_val(self, val: &mut Val) { - (self as i64).set_val(val) - } -} - -impl SetVal for u32 { - fn set_val(self, val: &mut Val) { - (self as i64).set_val(val) - } -} - -impl SetVal for i64 { - fn set_val(self, val: &mut Val) { - unsafe { - (*val.as_mut()).value.lval = self; - (*val.as_mut()).u1.type_info = IS_LONG; - } - } -} - -impl SetVal for f64 { - fn set_val(self, val: &mut Val) { - unsafe { - (*val.as_mut()).value.dval = self; - (*val.as_mut()).u1.type_info = IS_DOUBLE; - } - } -} - -impl SetVal for &str { - fn set_val(self, val: &mut Val) { - unsafe { - phper_zval_stringl(val.as_mut(), self.as_ptr().cast(), self.len()); - } - } -} - -impl SetVal for String { - fn set_val(self, val: &mut Val) { - unsafe { - phper_zval_stringl(val.as_mut(), self.as_ptr().cast(), self.len()); - } - } -} - -impl SetVal for Option { - fn set_val(self, val: &mut Val) { - match self { - Some(t) => t.set_val(val), - None => ().set_val(val), - } - } -} - -impl SetVal for Result { - fn set_val(self, val: &mut Val) { - match self { - Ok(t) => t.set_val(val), - Err(_e) => unsafe { - zend_throw_exception(null_mut(), c_str_ptr!(""), 0); - todo!(); - }, - } - } -} - -#[derive(Debug)] -pub enum Value<'a> { - Null, - Bool(bool), - Long(i64), - Double(f64), - CStr(&'a CStr), - Array(()), - Object(()), - Resource(()), -} - -impl<'a> Value<'a> { - pub fn from_ptr(v: *const zval) -> crate::Result { - unsafe { - match phper_zval_get_type(v) as u32 { - sys::IS_NULL => Ok(Self::Null), - sys::IS_FALSE => Ok(Self::Bool(false)), - sys::IS_TRUE => Ok(Self::Bool(true)), - sys::IS_LONG => Ok(Self::Long((*v).value.lval)), - sys::IS_DOUBLE => Ok(Self::Double((*v).value.dval)), - sys::IS_STRING | sys::IS_STRING_EX => { - let s = phper_z_strval_p(v); - Ok(Self::CStr(CStr::from_ptr(s))) - } - t => Err(crate::Error::UnKnownValueType(t)), - } - } - } - - pub fn into_long(self) -> Option { - match self { - Self::Long(l) => Some(l), - _ => None, - } - } -} - -pub enum ReturnValue<'a> { - Null, - Bool(bool), - Long(i64), - Double(f64), - Str(&'a str), - String(String), - Array(()), - Object(()), - Resource(()), -} - -impl SetVal for ReturnValue<'_> { - fn set_val(self, val: &mut Val) { - match self { - ReturnValue::Null => SetVal::set_val((), val), - ReturnValue::Bool(b) => SetVal::set_val(b, val), - ReturnValue::Long(l) => SetVal::set_val(l, val), - ReturnValue::Double(f) => SetVal::set_val(f, val), - ReturnValue::Str(s) => SetVal::set_val(s.as_ref(), val), - ReturnValue::String(s) => SetVal::set_val(s.as_str(), val), - _ => todo!(), - } - } -} diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 07ade694..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 7d2cf549..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -merge_imports = true