diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33dfbc1c..d6ab914e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,14 +77,14 @@ jobs: run: | cargo fmt --all -- --check - - name: Cargo clippy - run: | - cargo clippy --release - - name: Cargo build run: | cargo build --release + - name: Cargo clippy + run: | + cargo clippy --release --no-deps --all -- --allow dead_code + - name: Cargo test run: | cargo test --release -- --nocapture diff --git a/.gitignore b/.gitignore index eefeaf43..147a54d8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ core .vscode/ .idea/ +php/ +PHP-Parser-*/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7e298a6e..3ee7cd90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -19,84 +19,89 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "async-trait" -version = "0.1.76" +name = "anstream" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.45", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "autocfg" +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] [[package]] -name = "axum" -version = "0.6.20" +name = "anstyle-wincon" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", + "anstyle", + "windows-sys 0.52.0", ] [[package]] -name = "axum-core" -version = "0.3.4" +name = "assert_fs" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "2cd762e110c8ed629b11b6cde59458cc1c71de78ebbcc30099fc8e0403a2a2ec" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", - "rustversion", - "tower-layer", - "tower-service", + "anstream", + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", ] +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -109,30 +114,30 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bindgen" -version = "0.66.1" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.45", + "syn", "which", ] @@ -144,30 +149,37 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bstr" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cexpr" @@ -186,9 +198,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -196,16 +208,16 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "colorchoice" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "cookie" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", "time", @@ -214,12 +226,12 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.16.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" +checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" dependencies = [ "cookie", - "idna 0.2.3", + "idna 0.3.0", "log", "publicsuffix", "serde", @@ -245,6 +257,31 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "deranged" version = "0.3.11" @@ -256,28 +293,58 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0-beta.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 1.0.109", + "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -290,9 +357,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -311,9 +378,18 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] [[package]] name = "fnv" @@ -395,9 +471,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -405,11 +481,35 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.5.0", + "ignore", + "walkdir", +] + [[package]] name = "h2" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -426,15 +526,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" @@ -447,9 +547,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -469,9 +569,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -481,9 +581,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -517,14 +617,121 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.2.3" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -539,31 +746,40 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] -name = "indexmap" -version = "2.1.0" +name = "ignore" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ - "equivalent", - "hashbrown", + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] [[package]] -name = "integration" -version = "0.0.0" +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "indexmap", - "phper", - "phper-test", + "equivalent", + "hashbrown", ] [[package]] @@ -572,17 +788,32 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -601,31 +832,37 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.5", ] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -633,33 +870,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "matches" -version = "0.1.10" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -678,18 +903,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -698,11 +923,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -724,6 +948,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -736,9 +981,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -751,11 +996,11 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -772,7 +1017,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] @@ -783,9 +1028,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -795,9 +1040,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -805,22 +1050,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -832,15 +1077,16 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" name = "phper" version = "0.13.0" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "derive_more", - "indexmap", "memoffset", "once_cell", + "paste", "phper-alloc", "phper-build", "phper-macros", "phper-sys", + "smallvec", "thiserror", ] @@ -856,7 +1102,12 @@ dependencies = [ name = "phper-build" version = "0.13.0" dependencies = [ + "assert_fs", + "bindgen", + "cc", "phper-sys", + "predicates", + "walkdir", ] [[package]] @@ -872,6 +1123,7 @@ dependencies = [ name = "phper-example-complex" version = "0.0.0" dependencies = [ + "paste", "phper", "phper-build", "phper-test", @@ -880,49 +1132,20 @@ dependencies = [ [[package]] name = "phper-example-hello" version = "0.0.0" -dependencies = [ - "phper", -] - -[[package]] -name = "phper-example-http-client" -version = "0.0.0" -dependencies = [ - "phper", - "phper-test", - "reqwest", - "thiserror", -] - -[[package]] -name = "phper-example-http-server" -version = "0.0.0" -dependencies = [ - "axum", - "hyper", - "phper", - "phper-test", - "reqwest", - "thiserror", - "tokio", -] - -[[package]] -name = "phper-example-logging" -version = "0.0.0" dependencies = [ "phper", "phper-build", - "phper-test", + "phper-sys", ] [[package]] name = "phper-macros" version = "0.13.0" dependencies = [ + "phper-sys", "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] @@ -945,31 +1168,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "pin-project" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.45", -] - [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -979,9 +1182,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -989,21 +1192,51 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.45", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.73" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -1026,27 +1259,27 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.34" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1056,9 +1289,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1067,15 +1300,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", @@ -1097,9 +1330,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -1113,9 +1348,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1123,22 +1358,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1146,16 +1372,28 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.14" +name = "rustls-pemfile" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" @@ -1174,11 +1412,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -1187,61 +1425,45 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", ] -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - [[package]] name = "serde" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] name = "serde_json" -version = "1.0.109" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1256,15 +1478,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1280,36 +1502,31 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "syn" -version = "1.0.109" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "2.0.45" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eae3c679c56dc214320b67a1bc04ef3dfbd6411f6443974b5e4893231298e66" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1322,6 +1539,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -1345,45 +1573,51 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" -version = "1.0.53" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.53" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -1398,13 +1632,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1422,9 +1667,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -1441,13 +1686,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] @@ -1462,40 +1707,17 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", ] -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" version = "0.3.2" @@ -1508,7 +1730,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1522,7 +1743,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", ] [[package]] @@ -1542,9 +1763,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -1554,24 +1775,42 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.0", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1584,6 +1823,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1601,9 +1850,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1611,24 +1860,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.45", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -1638,9 +1887,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1648,28 +1897,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -1687,6 +1936,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1702,7 +1960,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -1722,17 +1980,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1743,9 +2002,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1755,9 +2014,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1767,9 +2026,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1779,9 +2044,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1791,9 +2056,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1803,9 +2068,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1815,9 +2080,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" @@ -1828,3 +2093,82 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index d110a6f6..496bacc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,17 +20,20 @@ members = [ "phper-doc", # internal - "examples/*", - "tests/integration", + "examples/hello", + "examples/complex", +# "tests/integration", ] [workspace.package] version = "0.13.0" +description = "Elegant PHP Binding to Rust" authors = ["PHPER Framework Team", "jmjoy ", "Dusan Malusev "] edition = "2021" license = "MulanPSL-2.0" repository = "https://github.com/dmalusev/phper" -rust-version = "1.74" +rust-version = "1.78" +readme = "README.md" [workspace.dependencies] phper = { version = "0.13.0", path = "./phper" } diff --git a/compile-php.sh b/compile-php.sh new file mode 100755 index 00000000..a7080e3b --- /dev/null +++ b/compile-php.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +print_usage() { + echo "" + echo "Usage: compile-php.sh [OPTION] [ARG]" + echo "-v ARG php version" + echo "-o ARG output path, default: $(pwd)" + echo "-z Use ZTS" + echo "-d Compile in debug mode" + echo "-k keep PHP source code" + echo "-a compile PHP without version suffix" + echo "-s Use Memory and Undefined Sanitizers" + echo "----------" + echo "Example: compiling PHP 8.2.7 in debug mode with Thread Safety" + echo "./compile-php.sh -v 8.2.7 -s -d -z" + echo "" +} + +which_linux() { + local val + + val=$(grep "NAME=\"$1\"" "/etc/os-release") + + if [ "$val" = "NAME=$1" ]; then + return 0 + fi + + return 1 +} + +is_linux() { + local value + + value=$(uname -s) + + if [ "$value" = "Linux" ]; then + return 0 + fi + + return 1 +} + +is_macos() { + local value + + value=$(uname -s) + + if [ "$value" = "Darwin" ]; then + return 0 + fi + + return 1 +} + +install_macos_dep() { + local package_path + local path + local PKG_CONFIG="" + local PATH_EXPORT="" + + for package in "$@"; do + output=$(brew install "$package") + + package_path=$(perl -lne 'print $1 if /export PKG_CONFIG_PATH=\"(.*)\"/;' <"$output") + path=$(perl -lne 'print $1 if /export PATH=\"(.*):$PATH\"/;' <"$output") + if [ -n "$package_path" ]; then + PKG_CONFIG="$PKG_CONFIG:$package_path" + fi + if [ -n "$path" ]; then + PATH_EXPORT="$PATH_EXPORT$path:" + fi + done + + if [ -n "$PKG_CONFIG" ]; then + echo "export PKG_CONFIG_PATH=\"\$PKG_CONFIG_PATH$PKG_CONFIG\"" >>~/.profile + fi + + if [ -n "$PATH_EXPORT" ]; then + echo "export PATH=\"$PATH_EXPORT\$PATH\"" >>~/.profile + fi + +} + +install_deps() { + if is_macos; then + install_macos_dep \ + pkg-config \ + bison \ + re2c \ + libxml2 \ + sqlite3 \ + zlib-ng \ + readline \ + libiconv \ + libffi + fi + + if is_linux; then + if which_linux "Fedora Linux"; then + sudo dnf install \ + re2c \ + cmake \ + gcc \ + ninja-build \ + openssl-devel \ + libubsan \ + libasan \ + sqlite-devel \ + zlib-devel \ + libcurl-devel \ + readline-devel \ + libffi-devel \ + oniguruma-devel \ + libxml2-devel \ + libsodium-devel \ + gmp-devel -y || exit 1 + fi + + if which_linux "Ubuntu"; then + sudo apt-get install \ + pkg-config \ + build-essential \ + libssl-dev \ + bison \ + re2c \ + libxml2-dev \ + libicu-dev \ + libsqlite3-dev \ + zlib1g-dev \ + libcurl4-openssl-dev \ + libreadline-dev \ + libffi-dev \ + libonig-dev \ + libsodium-dev \ + libgmp-dev \ + libasan8 \ + libubsan1 \ + libzip-dev -y || exit 1 + fi + + fi + +} + +compile_php() { + local ZTS="$1" + local WITH_DEBUG="$2" + local KEEP_PHP_SOURCE="$3" + + local PHP_BASE_VERSION + + PHP_BASE_VERSION=$(echo "$PHP_VERSION" | cut -d. -f1,2) + + local config=( + --enable-embed=static + --enable-phpdbg + --enable-opcache + --disable-short-tags + --enable-phpdbg-debug + --enable-rtld-now + --with-openssl + --with-zlib + --with-curl + --with-ffi + --enable-pcntl + --with-pear + --enable-sockets + --with-pic + --enable-mbstring + --with-sqlite3 + --enable-calendar + ) + + local OUTPUT_PATH="$OUTPUT/php/" + + if [[ "$WITHOUT_VERSION" == "yes" ]]; then + OUTPUT_PATH="$OUTPUT_PATH/$PHP_BASE_VERSION" + if [[ "$WITH_DEBUG" == "yes" ]]; then + OUTPUT_PATH="$OUTPUT_PATH-debug" + config+=("--enable-debug") + else + OUTPUT_PATH="$OUTPUT_PATH-release" + fi + + if [[ "$ZTS" == "yes" || "$ZTS" == "zts" ]]; then + OUTPUT_PATH="$OUTPUT_PATH-zts" + config+=("--enable-zts") + else + OUTPUT_PATH="$OUTPUT_PATH-nts" + fi + fi + + if [[ "$ENABLE_SANITIZERS" == "yes" ]]; then + config+=("--enable-address-sanitizer" "--enable-undefined-sanitizer") + fi + + if is_macos; then + config+=("--with-iconv=/opt/homebrew/opt/libiconv") + fi + + rm -rf "$OUTPUT_PATH" || exit 1 + mkdir -p "$OUTPUT_PATH" || exit 1 + + if [ ! -f "php-$PHP_VERSION.tar.gz" ]; then + wget -O "php-$PHP_VERSION.tar.gz" "https://github.com/php/php-src/archive/refs/tags/php-$PHP_VERSION.tar.gz" || exit 1 + fi + + tar -C "$OUTPUT_PATH" -xzf "php-$PHP_VERSION.tar.gz" || exit 1 + mv "$OUTPUT_PATH/php-src-php-$PHP_VERSION" "$OUTPUT_PATH/src" || exit 1 + + if [[ "$KEEP_PHP_SOURCE" == "no" ]]; then + rm -f "php-$PHP_VERSION.tar.gz" || exit 1 + fi + + pushd "$OUTPUT_PATH/src" || exit 1 + + { + ./buildconf --force + ./configure CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" --prefix="$OUTPUT_PATH" "${config[@]}" || exit 1 + make "-j$(nproc)" || exit 1 + make install || exit 1 + } + + popd || exit 1 +} + +check_deps() { + deps="wget make git gcc g++" + + for dep in $deps; do + [ -z "$(command -v "$dep")" ] && echo "Unsatisfied dependency: $dep" && exit 1 + done +} + +check_deps + +while getopts "v:zo:sdka" option; do + case "$option" in + "v") PHP_VERSION="$OPTARG" ;; + "z") PHP_ZTS="yes" ;; + "o") OUTPUT="$OPTARG" ;; + "d") ENABLE_DEBUG="yes" ;; + "k") KEEP_PHP_SOURCE="yes" ;; + "s") ENABLE_SANITIZERS="yes" ;; + "a") WITHOUT_VERSION="yes" ;; + *) print_usage ;; + esac +done + +if [[ -z "$WITHOUT_VERSION" ]]; then + WITHOUT_VERSION="no" +fi + +if [[ -z "$PHP_ZTS" ]]; then + PHP_ZTS="nts" +fi + +if [[ -z "$KEEP_PHP_SOURCE" ]]; then + KEEP_PHP_SOURCE="no" +fi + +if [[ -z "$ENABLE_DEBUG" ]]; then + ENABLE_DEBUG="no" +fi + +if [[ -z "$OUTPUT" ]]; then + OUTPUT="$(pwd)" +fi + +if [[ -z "$ENABLE_SANITIZERS" ]]; then + ENABLE_SANITIZERS="no" +fi + +if [[ -z "$PHP_VERSION" ]]; then + print_usage + exit 1 +fi + +CFLAGS="-g -ggdb -g3 -gdwarf-4 -fno-omit-frame-pointer" +CXXFLAGS="-g -ggdb -g3 -gdwarf-4 -fno-omit-frame-pointer" + +install_deps || exit 1 + +printf "\n\nCompiling PHP %s in %s: Debug mode: %s, Thread Safety: %s, Sanitizers: %s\n" \ + "$PHP_VERSION" \ + "$OUTPUT" \ + "$ENABLE_DEBUG" \ + "$PHP_ZTS" \ + "$ENABLE_SANITIZERS" + +compile_php "$PHP_ZTS" "$ENABLE_DEBUG" "$KEEP_PHP_SOURCE" || exit 1 diff --git a/examples/complex/Cargo.toml b/examples/complex/Cargo.toml index 14d382ea..18857931 100644 --- a/examples/complex/Cargo.toml +++ b/examples/complex/Cargo.toml @@ -22,6 +22,7 @@ name = "complex" crate-type = ["lib", "cdylib"] [dependencies] +paste = "^1.0.15" phper = { workspace = true } [dev-dependencies] diff --git a/examples/complex/build.rs b/examples/complex/build.rs index 0c111d7a..c9c1fb2c 100644 --- a/examples/complex/build.rs +++ b/examples/complex/build.rs @@ -8,12 +8,18 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +use std::env; +use std::path::PathBuf; + fn main() { - phper_build::register_configures(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=stubs"); + + phper_build::register_all(); + + let current_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let stubs = current_dir.join("stubs"); - #[cfg(target_os = "macos")] - { - println!("cargo:rustc-link-arg=-undefined"); - println!("cargo:rustc-link-arg=dynamic_lookup"); - } + phper_build::generate_php_function_args(&out_path, &[&stubs], None).unwrap(); } diff --git a/examples/complex/config.m4 b/examples/complex/config.m4 new file mode 100644 index 00000000..34465fe3 --- /dev/null +++ b/examples/complex/config.m4 @@ -0,0 +1,70 @@ +dnl Copyright (c) 2022 PHPER Framework Team +dnl PHPER is licensed under Mulan PSL v2. +dnl You can use this software according to the terms and conditions of the Mulan +dnl PSL v2. You may obtain a copy of Mulan PSL v2 at: +dnl http://license.coscl.org.cn/MulanPSL2 +dnl THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +dnl KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +dnl NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +dnl See the Mulan PSL v2 for more details. + +PHP_ARG_ENABLE([complex], + [whether to enable hello support], + [AS_HELP_STRING([--enable-complex], + [Enable complex support])], + [no]) + +dnl If not enable, `cargo build` run with argument `--release`. +PHP_ARG_ENABLE([cargo_debug], [whether to enable cargo debug mode], +[ --enable-cargo-debug Enable cargo debug], no, no) + +if test "$PHP_hello" != "no"; then + dnl Check cargo command exists or not. + AC_PATH_PROG(CARGO, cargo, no) + if ! test -x "$CARGO"; then + AC_MSG_ERROR([cargo command missing, please reinstall the cargo distribution]) + fi + + AC_DEFINE(HAVE_complex, 1, [ Have complex support ]) + + PHP_NEW_EXTENSION(complex, [ ], $ext_shared) + + CARGO_MODE_FLAGS="--release" + CARGO_MODE_DIR="release" + + if test "$PHP_CARGO_DEBUG" != "no"; then + CARGO_MODE_FLAGS="" + CARGO_MODE_DIR="debug" + fi + + cat >>Makefile.objects<< EOF +all: cargo_build + +clean: cargo_clean + +cargo_build: + # Build the extension file + PHP_CONFIG=$PHP_PHP_CONFIG cargo build $CARGO_MODE_FLAGS + + # Copy the extension file from target dir to modules + if [[ -f ./target/$CARGO_MODE_DIR/libcomplex.dylib ]] ; then \\ + cp ./target/$CARGO_MODE_DIR/libcomplex.dylib ./modules/complex.so ; fi + if [[ -f ./target/$CARGO_MODE_DIR/libcomplex.so ]] ; then \\ + cp ./target/$CARGO_MODE_DIR/libcomplex.so ./modules/complex.so ; fi + +cargo_clean: + cargo clean + +.PHONY: cargo_build cargo_clean +EOF + + dnl Symbolic link the files for `cargo build` + AC_CONFIG_LINKS([ \ + Cargo.lock:Cargo.lock \ + Cargo.toml:Cargo.toml \ + build.rs:build.rs \ + stubs:stubs \ + src:src \ + tests:tests \ + ]) +fi diff --git a/examples/complex/package.xml b/examples/complex/package.xml new file mode 100644 index 00000000..c047bb29 --- /dev/null +++ b/examples/complex/package.xml @@ -0,0 +1,64 @@ + + + + complex + pecl.php.net + Hello world example a bit complex. + The Hello world example of phper. + + jmjoy + jmjoy + jmjoy@apache.org + yes + + 1970-01-01 + + 0.0.0 + 0.0.0 + + + stable + stable + + MulanPSL-2.0 + Release notes. + + + + + + + + + + + + + + + + 8.1.0 + + + 1.4.0 + + + + complex + + + + diff --git a/examples/complex/src/args_bindings.rs b/examples/complex/src/args_bindings.rs new file mode 100644 index 00000000..fcff318d --- /dev/null +++ b/examples/complex/src/args_bindings.rs @@ -0,0 +1,19 @@ +use phper::zend_create_fn; + +#[allow(non_upper_case_globals)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +#[allow(deref_nullptr)] +#[allow(clippy::all)] +mod bindings { + include!(concat!(env!("OUT_DIR"), "/php_args_bindings.rs")); +} + +use bindings::register_class_Complex_Foo; + +pub use bindings::{ + arginfo_Complex_say_hello, arginfo_Complex_throw_exception, arginfo_class_Complex_Foo_getFoo, + arginfo_class_Complex_Foo_setFoo, +}; + +zend_create_fn!(register_class_Complex_Foo, CLASS_COMPLEX_FOO); diff --git a/examples/complex/src/lib.rs b/examples/complex/src/lib.rs index a5b85966..8bbb7a1b 100644 --- a/examples/complex/src/lib.rs +++ b/examples/complex/src/lib.rs @@ -8,23 +8,22 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper::{ - arrays::ZArray, - classes::{entity::ClassEntity, Visibility}, - functions::Argument, - ini::{ini_get, Policy}, - modules::Module, - objects::StateObj, - php_get_module, - values::ZVal, +mod args_bindings; + +use args_bindings::{ + arginfo_Complex_say_hello, arginfo_Complex_throw_exception, arginfo_class_Complex_Foo_getFoo, + arginfo_class_Complex_Foo_setFoo, }; -use std::{convert::Infallible, ffi::CStr}; + +use crate::args_bindings::CLASS_COMPLEX_FOO; +use phper::classes::methods::MethodEntityBuilder; +use phper::classes::ClassEntity; +use phper::objects::StateObj; +use phper::{modules::Module, php_get_module, values::ZVal, zend_args}; fn say_hello(arguments: &mut [ZVal]) -> phper::Result { - let name = &mut arguments[0]; - name.convert_to_string(); - let name = name.as_z_str().unwrap().to_str()?; - Ok(format!("Hello, {}!\n", name)) + let name = arguments[0].as_z_str().unwrap().to_str()?; + Ok(format!("Hello, {name}!\n")) } fn throw_exception(_: &mut [ZVal]) -> phper::Result<()> { @@ -40,14 +39,14 @@ pub fn get_module() -> Module { ); // register module ini - module.add_ini("complex.enable", false, Policy::All); - module.add_ini("complex.num", 100, Policy::All); - module.add_ini("complex.ratio", 1.5, Policy::All); - module.add_ini( - "complex.description", - "hello world.".to_owned(), - Policy::All, - ); + // module.add_ini("complex.enable", false, Policy::All); + // module.add_ini("complex.num", 100, Policy::All); + // module.add_ini("complex.ratio", 1.5, Policy::All); + // module.add_ini( + // "complex.description", + // "hello world.".to_owned(), + // Policy::All, + // ); // register hook functions module.on_module_init(|_info| {}); @@ -55,46 +54,53 @@ pub fn get_module() -> Module { module.on_request_init(|_info| {}); module.on_request_shutdown(|_info| {}); - // register functions module - .add_function("complex_say_hello", say_hello) - .argument(Argument::by_val("name")); - module.add_function("complex_throw_exception", throw_exception); - module.add_function("complex_get_all_ini", |_: &mut [ZVal]| { - let mut arr = ZArray::new(); - - let complex_enable = ZVal::from(ini_get::("complex.enable")); - arr.insert("complex.enable", complex_enable); - - let complex_description = ZVal::from(ini_get::>("complex.description")); - arr.insert("complex.description", complex_description); - Ok::<_, Infallible>(arr) - }); + .add_function( + "Complex\\say_hello", + zend_args!(arginfo_Complex_say_hello), + say_hello, + ) + .add_function( + "Complex\\throw_exception", + zend_args!(arginfo_Complex_throw_exception), + throw_exception, + ); + // .add_function( + // "Complex\\get_all_ini", + // zend_args!(arginfo_Complex_get_all_ini), + // |_: &mut [ZVal]| { + // let mut arr = ZArray::new(); + // + // let complex_enable = ZVal::from(ini_get::("complex.enable")); + // arr.insert("complex.enable", complex_enable); + // + // let complex_description = + // ZVal::from(ini_get::>("complex.description")); + // arr.insert("complex.description",complex_description); + // Ok::<_, Infallible>(arr.clone()) + // }, + // ); + // + let mut foo_class = ClassEntity::new(CLASS_COMPLEX_FOO); - // register classes - let mut foo_class = ClassEntity::new("FooClass"); - foo_class.add_property("foo", Visibility::Private, 100); foo_class.add_method( - "getFoo", - Visibility::Public, |this: &mut StateObj, _: &mut [ZVal]| { - let prop = this.get_property("foo"); - Ok::<_, phper::Error>(prop.clone()) + Ok::<_, phper::Error>(this.get_property("foo").clone()) }, + MethodEntityBuilder::new("getFoo", zend_args!(arginfo_class_Complex_Foo_getFoo)) + .set_public(), + ); + + foo_class.add_method( + |this: &mut StateObj, arguments: &mut [ZVal]| -> phper::Result<()> { + this.set_property("foo", arguments[0].clone()); + Ok(()) + }, + MethodEntityBuilder::new("setFoo", zend_args!(arginfo_class_Complex_Foo_setFoo)) + .set_public(), ); - foo_class - .add_method( - "setFoo", - Visibility::Public, - |this: &mut StateObj, arguments: &mut [ZVal]| -> phper::Result<()> { - this.set_property("foo", arguments[0].clone()); - Ok(()) - }, - ) - .argument(Argument::by_val("foo")); module.add_class(foo_class); - // register extra info module.add_info("extra info key", "extra info value"); module diff --git a/examples/complex/stubs/say_hello.stub.php b/examples/complex/stubs/say_hello.stub.php new file mode 100644 index 00000000..5764aa2d --- /dev/null +++ b/examples/complex/stubs/say_hello.stub.php @@ -0,0 +1,29 @@ +getMessage(), "I am sorry"); } -assert_eq(complex_get_all_ini(), [ - "complex.enable" => false, - "complex.description" => "hello world.", -]); +// assert_eq(Complex\get_all_ini(), [ +// "complex.enable" => false, +// "complex.description" => "hello world.", +// ]); -$foo = new FooClass(); +$foo = new Complex\Foo(); assert_eq($foo->getFoo(), 100); - -$foo->setFoo("Hello"); -assert_eq($foo->getFoo(), "Hello"); +$foo->setFoo(200); +assert_eq($foo->getFoo(), 200); function assert_eq($left, $right) { if ($left !== $right) { diff --git a/examples/hello/Cargo.toml b/examples/hello/Cargo.toml index 151da7ee..ac0210e2 100644 --- a/examples/hello/Cargo.toml +++ b/examples/hello/Cargo.toml @@ -23,3 +23,7 @@ crate-type = ["lib", "cdylib"] [dependencies] phper = { workspace = true } +phper-sys = { workspace = true } + +[build-dependencies] +phper-build = { workspace = true } \ No newline at end of file diff --git a/examples/hello/build.rs b/examples/hello/build.rs index 682a4a6b..ff525632 100644 --- a/examples/hello/build.rs +++ b/examples/hello/build.rs @@ -8,10 +8,18 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +use std::env::var; +use std::path::PathBuf; + fn main() { - #[cfg(target_os = "macos")] - { - println!("cargo:rustc-link-arg=-undefined"); - println!("cargo:rustc-link-arg=dynamic_lookup"); - } + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=stubs"); + + phper_build::register_all(); + + let current_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()); + let out_path = PathBuf::from(var("OUT_DIR").unwrap()); + let stubs = current_dir.join("stubs"); + + phper_build::generate_php_function_args(&out_path, &[&stubs], None).unwrap(); } diff --git a/examples/hello/config.m4 b/examples/hello/config.m4 index 34d9b826..83f17a2a 100644 --- a/examples/hello/config.m4 +++ b/examples/hello/config.m4 @@ -64,5 +64,6 @@ EOF Cargo.toml:Cargo.toml \ build.rs:build.rs \ src:src \ + stubs:stubs \ ]) fi diff --git a/examples/hello/package.xml b/examples/hello/package.xml index d188f1fb..581fe87d 100644 --- a/examples/hello/package.xml +++ b/examples/hello/package.xml @@ -10,9 +10,9 @@ KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. --> - hello pecl.php.net @@ -43,12 +43,13 @@ See the Mulan PSL v2 for more details. + - 7.2.0 + 8.1.0 1.4.0 diff --git a/examples/hello/src/lib.rs b/examples/hello/src/lib.rs index 3becc747..1a897a17 100644 --- a/examples/hello/src/lib.rs +++ b/examples/hello/src/lib.rs @@ -8,7 +8,18 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper::{echo, functions::Argument, modules::Module, php_get_module, values::ZVal}; +mod args { + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + #![allow(non_snake_case)] + #![allow(deref_nullptr)] + #![allow(clippy::all)] + include!(concat!(env!("OUT_DIR"), "/php_args_bindings.rs")); +} + +use args::arginfo_say_hello; +use phper::zend_args; +use phper::{echo, modules::Module, php_get_module, values::ZVal}; /// The php function, receive arguments with type `ZVal`. fn say_hello(arguments: &mut [ZVal]) -> phper::Result<()> { @@ -34,9 +45,7 @@ pub fn get_module() -> Module { ); // Register function `say_hello`, with one argument `name`. - module - .add_function("say_hello", say_hello) - .argument(Argument::by_val("name")); + module.add_function("say_hello", zend_args!(arginfo_say_hello), say_hello); module } diff --git a/examples/hello/stubs/say_hello.stub.php b/examples/hello/stubs/say_hello.stub.php new file mode 100644 index 00000000..1860c2f9 --- /dev/null +++ b/examples/hello/stubs/say_hello.stub.php @@ -0,0 +1,5 @@ + ClassEntity { let builder: ClientBuilder = take(state); *state = builder.timeout(Duration::from_millis(ms as u64)); Ok::<_, phper::Error>(this.to_ref_owned()) - }) - .argument(Argument::by_val("ms")); + }); + // .argument(Argument::by_val("ms")); // Inner call the `ClientBuilder::cookie_store`. class @@ -49,8 +48,8 @@ pub fn make_client_builder_class() -> ClassEntity { let builder: ClientBuilder = take(state); *state = builder.cookie_store(enable); Ok::<_, phper::Error>(this.to_ref_owned()) - }) - .argument(Argument::by_val("enable")); + }); + // .argument(Argument::by_val("enable")); // Inner call the `ClientBuilder::build`, and wrap the result `Client` in // Object. @@ -83,8 +82,8 @@ pub fn make_client_class() -> ClassEntity { let mut object = REQUEST_BUILDER_CLASS.init_object()?; *object.as_mut_state() = Some(request_builder); Ok::<_, phper::Error>(object) - }) - .argument(Argument::by_val("url")); + }); + // .argument(Argument::by_val("url")); class .add_method("post", Visibility::Public, |this, arguments| { @@ -94,8 +93,8 @@ pub fn make_client_class() -> ClassEntity { let mut object = REQUEST_BUILDER_CLASS.init_object()?; *object.as_mut_state() = Some(request_builder); Ok::<_, phper::Error>(object) - }) - .argument(Argument::by_val("url")); + }); + // .argument(Argument::by_val("url")); class } diff --git a/examples/http-server/src/response.rs b/examples/http-server/src/response.rs index 89bff299..0289c3df 100644 --- a/examples/http-server/src/response.rs +++ b/examples/http-server/src/response.rs @@ -15,7 +15,7 @@ use axum::{ }; use phper::{ classes::{entity::ClassEntity, StaticStateClass, Visibility}, - functions::Argument, + arguments::Argument, objects::StateObject, }; diff --git a/examples/http-server/src/server.rs b/examples/http-server/src/server.rs index 6c1279e0..b84e9298 100644 --- a/examples/http-server/src/server.rs +++ b/examples/http-server/src/server.rs @@ -19,7 +19,7 @@ use hyper::body; use phper::{ alloc::ToRefOwned, classes::{entity::ClassEntity, Visibility}, - functions::Argument, + arguments::Argument, values::ZVal, }; use std::{cell::RefCell, collections::HashMap, net::SocketAddr}; diff --git a/examples/logging/src/lib.rs b/examples/logging/src/lib.rs index 1a891455..f2c32f92 100644 --- a/examples/logging/src/lib.rs +++ b/examples/logging/src/lib.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - deprecated, echo, error, functions::Argument, modules::Module, notice, php_get_module, + deprecated, echo, error, arguments::Argument, modules::Module, notice, php_get_module, values::ZVal, warning, }; diff --git a/phper-alloc/src/lib.rs b/phper-alloc/src/lib.rs index 9920a627..6ad44d99 100644 --- a/phper-alloc/src/lib.rs +++ b/phper-alloc/src/lib.rs @@ -58,8 +58,8 @@ impl EBox { /// Consumes and returning a wrapped raw pointer. /// /// Will leak memory. - pub fn into_raw(b: EBox) -> *mut T { - ManuallyDrop::new(b).ptr + pub fn into_raw(self) -> *mut T { + ManuallyDrop::new(self).ptr } /// Consumes the `EBox`, returning the wrapped value. diff --git a/phper-build/Cargo.toml b/phper-build/Cargo.toml index 40748a02..75e1f4b1 100644 --- a/phper-build/Cargo.toml +++ b/phper-build/Cargo.toml @@ -21,3 +21,11 @@ license = { workspace = true } [dependencies] phper-sys = { workspace = true } +bindgen = "0.69.4" +walkdir = "2" +cc = "1.0.79" + + +[dev-dependencies] +assert_fs = { version = "^1", features = ["color"] } +predicates = "3.1.0" diff --git a/phper-build/build.rs b/phper-build/build.rs new file mode 100644 index 00000000..596addaf --- /dev/null +++ b/phper-build/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::env::current_dir; +use std::path::PathBuf; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=gen_stub.php"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + + std::fs::copy( + current_dir().unwrap().join("gen_stub.php"), + out_path.join("gen_stub.php"), + ) + .unwrap(); +} diff --git a/phper-build/gen_stub.php b/phper-build/gen_stub.php new file mode 100644 index 00000000..bc0fe032 --- /dev/null +++ b/phper-build/gen_stub.php @@ -0,0 +1,5011 @@ +#!/usr/bin/env php +getPathName(); + if (preg_match('/\.stub\.php$/', $pathName)) { + $pathNames[] = $pathName; + } + } + + // Make sure stub files are processed in a predictable, system-independent order. + sort($pathNames); + + $fileInfos = []; + foreach ($pathNames as $pathName) { + $fileInfo = processStubFile($pathName, $context); + if ($fileInfo) { + $fileInfos[] = $fileInfo; + } + } + return $fileInfos; +} + +function processStubFile(string $stubFile, Context $context, bool $includeOnly = false): ?FileInfo { + try { + if (!file_exists($stubFile)) { + throw new Exception("File $stubFile does not exist"); + } + + if (!$includeOnly) { + $stubFilenameWithoutExtension = str_replace(".stub.php", "", $stubFile); + $arginfoFile = "{$stubFilenameWithoutExtension}_arginfo.h"; + $legacyFile = "{$stubFilenameWithoutExtension}_legacy_arginfo.h"; + + $stubCode = file_get_contents($stubFile); + $stubHash = computeStubHash($stubCode); + $oldStubHash = extractStubHash($arginfoFile); + if ($stubHash === $oldStubHash && !$context->forceParse) { + /* Stub file did not change, do not regenerate. */ + return null; + } + } + + if (!$fileInfo = $context->parsedFiles[$stubFile] ?? null) { + initPhpParser(); + $fileInfo = parseStubFile($stubCode ?? file_get_contents($stubFile)); + $context->parsedFiles[$stubFile] = $fileInfo; + + foreach ($fileInfo->dependencies as $dependency) { + // TODO add header search path for extensions? + $prefixes = [dirname($stubFile) . "/", dirname(__DIR__) . "/"]; + foreach ($prefixes as $prefix) { + $depFile = $prefix . $dependency; + if (file_exists($depFile)) { + break; + } + $depFile = null; + } + if (!$depFile) { + throw new Exception("File $stubFile includes a file $dependency which does not exist"); + } + processStubFile($depFile, $context, true); + } + + $constInfos = $fileInfo->getAllConstInfos(); + $context->allConstInfos = array_merge($context->allConstInfos, $constInfos); + } + + if ($includeOnly) { + return $fileInfo; + } + + $arginfoCode = generateArgInfoCode( + basename($stubFilenameWithoutExtension), + $fileInfo, + $context->allConstInfos, + $stubHash + ); + if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($arginfoFile, $arginfoCode)) { + echo "Saved $arginfoFile\n"; + } + + if ($fileInfo->generateLegacyArginfoForPhpVersionId !== null && $fileInfo->generateLegacyArginfoForPhpVersionId < PHP_80_VERSION_ID) { + $legacyFileInfo = clone $fileInfo; + + foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) { + $funcInfo->discardInfoForOldPhpVersions(); + } + foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) { + $constInfo->discardInfoForOldPhpVersions(); + } + foreach ($legacyFileInfo->getAllPropertyInfos() as $propertyInfo) { + $propertyInfo->discardInfoForOldPhpVersions(); + } + + $arginfoCode = generateArgInfoCode( + basename($stubFilenameWithoutExtension), + $legacyFileInfo, + $context->allConstInfos, + $stubHash + ); + if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($legacyFile, $arginfoCode)) { + echo "Saved $legacyFile\n"; + } + } + + return $fileInfo; + } catch (Exception $e) { + echo "In $stubFile:\n{$e->getMessage()}\n"; + exit(1); + } +} + +function computeStubHash(string $stubCode): string { + return sha1(str_replace("\r\n", "\n", $stubCode)); +} + +function extractStubHash(string $arginfoFile): ?string { + if (!file_exists($arginfoFile)) { + return null; + } + + $arginfoCode = file_get_contents($arginfoFile); + if (!preg_match('/\* Stub hash: ([0-9a-f]+) \*/', $arginfoCode, $matches)) { + return null; + } + + return $matches[1]; +} + +class Context { + public bool $forceParse = false; + public bool $forceRegeneration = false; + /** @var iterable */ + public iterable $allConstInfos = []; + /** @var FileInfo[] */ + public array $parsedFiles = []; +} + +class ArrayType extends SimpleType { + public Type $keyType; + public Type $valueType; + + public static function createGenericArray(): self + { + return new ArrayType(Type::fromString("int|string"), Type::fromString("mixed|ref")); + } + + public function __construct(Type $keyType, Type $valueType) + { + parent::__construct("array", true); + + $this->keyType = $keyType; + $this->valueType = $valueType; + } + + public function toOptimizerTypeMask(): string { + $typeMasks = [ + parent::toOptimizerTypeMask(), + $this->keyType->toOptimizerTypeMaskForArrayKey(), + $this->valueType->toOptimizerTypeMaskForArrayValue(), + ]; + + return implode("|", $typeMasks); + } + + public function equals(SimpleType $other): bool { + if (!parent::equals($other)) { + return false; + } + + assert(get_class($other) === self::class); + + return Type::equals($this->keyType, $other->keyType) && + Type::equals($this->valueType, $other->valueType); + } +} + +class SimpleType { + public string $name; + public bool $isBuiltin; + + public static function fromNode(Node $node): SimpleType { + if ($node instanceof Node\Name) { + if ($node->toLowerString() === 'static') { + // PHP internally considers "static" a builtin type. + return new SimpleType($node->toLowerString(), true); + } + + if ($node->toLowerString() === 'self') { + throw new Exception('The exact class name must be used instead of "self"'); + } + + assert($node->isFullyQualified()); + return new SimpleType($node->toString(), false); + } + + if ($node instanceof Node\Identifier) { + if ($node->toLowerString() === 'array') { + return ArrayType::createGenericArray(); + } + + return new SimpleType($node->toLowerString(), true); + } + + throw new Exception("Unexpected node type"); + } + + public static function fromString(string $typeString): SimpleType + { + switch (strtolower($typeString)) { + case "void": + case "null": + case "false": + case "true": + case "bool": + case "int": + case "float": + case "string": + case "callable": + case "object": + case "resource": + case "mixed": + case "static": + case "never": + case "ref": + return new SimpleType(strtolower($typeString), true); + case "array": + return ArrayType::createGenericArray(); + case "self": + throw new Exception('The exact class name must be used instead of "self"'); + case "iterable": + throw new Exception('This should not happen'); + } + + $matches = []; + $isArray = preg_match("/(.*)\s*\[\s*\]/", $typeString, $matches); + if ($isArray) { + return new ArrayType(Type::fromString("int"), Type::fromString($matches[1])); + } + + $matches = []; + $isArray = preg_match("/array\s*<\s*([A-Za-z0-9_-|]+)?(\s*,\s*)?([A-Za-z0-9_-|]+)?\s*>/i", $typeString, $matches); + if ($isArray) { + if (empty($matches[1]) || empty($matches[3])) { + throw new Exception("array<> type hint must have both a key and a value"); + } + + return new ArrayType(Type::fromString($matches[1]), Type::fromString($matches[3])); + } + + return new SimpleType($typeString, false); + } + + /** + * @param mixed $value + */ + public static function fromValue($value): SimpleType + { + switch (gettype($value)) { + case "NULL": + return SimpleType::null(); + case "boolean": + return SimpleType::bool(); + case "integer": + return SimpleType::int(); + case "double": + return SimpleType::float(); + case "string": + return SimpleType::string(); + case "array": + return SimpleType::array(); + case "object": + return SimpleType::object(); + default: + throw new Exception("Type \"" . gettype($value) . "\" cannot be inferred based on value"); + } + } + + public static function null(): SimpleType + { + return new SimpleType("null", true); + } + + public static function bool(): SimpleType + { + return new SimpleType("bool", true); + } + + public static function int(): SimpleType + { + return new SimpleType("int", true); + } + + public static function float(): SimpleType + { + return new SimpleType("float", true); + } + + public static function string(): SimpleType + { + return new SimpleType("string", true); + } + + public static function array(): SimpleType + { + return new SimpleType("array", true); + } + + public static function object(): SimpleType + { + return new SimpleType("object", true); + } + + public static function void(): SimpleType + { + return new SimpleType("void", true); + } + + protected function __construct(string $name, bool $isBuiltin) { + $this->name = $name; + $this->isBuiltin = $isBuiltin; + } + + public function isScalar(): bool { + return $this->isBuiltin && in_array($this->name, ["null", "false", "true", "bool", "int", "float"], true); + } + + public function isNull(): bool { + return $this->isBuiltin && $this->name === 'null'; + } + + public function isBool(): bool { + return $this->isBuiltin && $this->name === 'bool'; + } + + public function isInt(): bool { + return $this->isBuiltin && $this->name === 'int'; + } + + public function isFloat(): bool { + return $this->isBuiltin && $this->name === 'float'; + } + + public function isString(): bool { + return $this->isBuiltin && $this->name === 'string'; + } + + public function isArray(): bool { + return $this->isBuiltin && $this->name === 'array'; + } + + public function toTypeCode(): string { + assert($this->isBuiltin); + switch ($this->name) { + case "bool": + return "_IS_BOOL"; + case "int": + return "IS_LONG"; + case "float": + return "IS_DOUBLE"; + case "string": + return "IS_STRING"; + case "array": + return "IS_ARRAY"; + case "object": + return "IS_OBJECT"; + case "void": + return "IS_VOID"; + case "callable": + return "IS_CALLABLE"; + case "mixed": + return "IS_MIXED"; + case "static": + return "IS_STATIC"; + case "never": + return "IS_NEVER"; + case "null": + return "IS_NULL"; + case "false": + return "IS_FALSE"; + case "true": + return "IS_TRUE"; + default: + throw new Exception("Not implemented: $this->name"); + } + } + + public function toTypeMask(): string { + assert($this->isBuiltin); + + switch ($this->name) { + case "null": + return "MAY_BE_NULL"; + case "false": + return "MAY_BE_FALSE"; + case "true": + return "MAY_BE_TRUE"; + case "bool": + return "MAY_BE_BOOL"; + case "int": + return "MAY_BE_LONG"; + case "float": + return "MAY_BE_DOUBLE"; + case "string": + return "MAY_BE_STRING"; + case "array": + return "MAY_BE_ARRAY"; + case "object": + return "MAY_BE_OBJECT"; + case "callable": + return "MAY_BE_CALLABLE"; + case "mixed": + return "MAY_BE_ANY"; + case "void": + return "MAY_BE_VOID"; + case "static": + return "MAY_BE_STATIC"; + case "never": + return "MAY_BE_NEVER"; + default: + throw new Exception("Not implemented: $this->name"); + } + } + + public function toOptimizerTypeMaskForArrayKey(): string { + assert($this->isBuiltin); + + switch ($this->name) { + case "int": + return "MAY_BE_ARRAY_KEY_LONG"; + case "string": + return "MAY_BE_ARRAY_KEY_STRING"; + default: + throw new Exception("Type $this->name cannot be an array key"); + } + } + + public function toOptimizerTypeMaskForArrayValue(): string { + if (!$this->isBuiltin) { + return "MAY_BE_ARRAY_OF_OBJECT"; + } + + switch ($this->name) { + case "null": + return "MAY_BE_ARRAY_OF_NULL"; + case "false": + return "MAY_BE_ARRAY_OF_FALSE"; + case "true": + return "MAY_BE_ARRAY_OF_TRUE"; + case "bool": + return "MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE"; + case "int": + return "MAY_BE_ARRAY_OF_LONG"; + case "float": + return "MAY_BE_ARRAY_OF_DOUBLE"; + case "string": + return "MAY_BE_ARRAY_OF_STRING"; + case "array": + return "MAY_BE_ARRAY_OF_ARRAY"; + case "object": + return "MAY_BE_ARRAY_OF_OBJECT"; + case "resource": + return "MAY_BE_ARRAY_OF_RESOURCE"; + case "mixed": + return "MAY_BE_ARRAY_OF_ANY"; + case "ref": + return "MAY_BE_ARRAY_OF_REF"; + default: + throw new Exception("Type $this->name cannot be an array value"); + } + } + + public function toOptimizerTypeMask(): string { + if (!$this->isBuiltin) { + return "MAY_BE_OBJECT"; + } + + switch ($this->name) { + case "resource": + return "MAY_BE_RESOURCE"; + case "callable": + return "MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT"; + case "iterable": + return "MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_OBJECT"; + case "mixed": + return "MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY"; + } + + return $this->toTypeMask(); + } + + public function toEscapedName(): string { + // Escape backslashes, and also encode \u and \U to avoid compilation errors in generated macros + return str_replace( + ['\\', '\\u', '\\U'], + ['\\\\', '\\\\165', '\\\\125'], + $this->name + ); + } + + public function toVarEscapedName(): string { + return str_replace('\\', '_', $this->name); + } + + public function equals(SimpleType $other): bool { + return $this->name === $other->name && $this->isBuiltin === $other->isBuiltin; + } +} + +class Type { + /** @var SimpleType[] */ + public array $types; + public bool $isIntersection; + + public static function fromNode(Node $node): Type { + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { + $nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types); + $types = []; + foreach ($nestedTypeObjects as $typeObject) { + array_push($types, ...$typeObject->types); + } + return new Type($types, ($node instanceof Node\IntersectionType)); + } + + if ($node instanceof Node\NullableType) { + return new Type( + [ + ...Type::fromNode($node->type)->types, + SimpleType::null(), + ], + false + ); + } + + if ($node instanceof Node\Identifier && $node->toLowerString() === "iterable") { + return new Type( + [ + SimpleType::fromString("Traversable"), + ArrayType::createGenericArray(), + ], + false + ); + } + + return new Type([SimpleType::fromNode($node)], false); + } + + public static function fromString(string $typeString): self { + $typeString .= "|"; + $simpleTypes = []; + $simpleTypeOffset = 0; + $inArray = false; + $isIntersection = false; + + $typeStringLength = strlen($typeString); + for ($i = 0; $i < $typeStringLength; $i++) { + $char = $typeString[$i]; + + if ($char === "<") { + $inArray = true; + continue; + } + + if ($char === ">") { + $inArray = false; + continue; + } + + if ($inArray) { + continue; + } + + if ($char === "|" || $char === "&") { + $isIntersection = ($char === "&"); + $simpleTypeName = trim(substr($typeString, $simpleTypeOffset, $i - $simpleTypeOffset)); + + $simpleTypes[] = SimpleType::fromString($simpleTypeName); + + $simpleTypeOffset = $i + 1; + } + } + + return new Type($simpleTypes, $isIntersection); + } + + /** + * @param SimpleType[] $types + */ + private function __construct(array $types, bool $isIntersection) { + $this->types = $types; + $this->isIntersection = $isIntersection; + } + + public function isScalar(): bool { + foreach ($this->types as $type) { + if (!$type->isScalar()) { + return false; + } + } + + return true; + } + + public function isNullable(): bool { + foreach ($this->types as $type) { + if ($type->isNull()) { + return true; + } + } + + return false; + } + + public function getWithoutNull(): Type { + return new Type( + array_values( + array_filter( + $this->types, + function(SimpleType $type) { + return !$type->isNull(); + } + ) + ), + false + ); + } + + public function tryToSimpleType(): ?SimpleType { + $withoutNull = $this->getWithoutNull(); + /* type has only null */ + if (count($withoutNull->types) === 0) { + return $this->types[0]; + } + if (count($withoutNull->types) === 1) { + return $withoutNull->types[0]; + } + return null; + } + + public function toArginfoType(): ArginfoType { + $classTypes = []; + $builtinTypes = []; + foreach ($this->types as $type) { + if ($type->isBuiltin) { + $builtinTypes[] = $type; + } else { + $classTypes[] = $type; + } + } + return new ArginfoType($classTypes, $builtinTypes); + } + + public function toOptimizerTypeMask(): string { + $optimizerTypes = []; + + foreach ($this->types as $type) { + // TODO Support for toOptimizerMask for intersection + $optimizerTypes[] = $type->toOptimizerTypeMask(); + } + + return implode("|", $optimizerTypes); + } + + public function toOptimizerTypeMaskForArrayKey(): string { + $typeMasks = []; + + foreach ($this->types as $type) { + $typeMasks[] = $type->toOptimizerTypeMaskForArrayKey(); + } + + return implode("|", $typeMasks); + } + + public function toOptimizerTypeMaskForArrayValue(): string { + $typeMasks = []; + + foreach ($this->types as $type) { + $typeMasks[] = $type->toOptimizerTypeMaskForArrayValue(); + } + + return implode("|", $typeMasks); + } + + public function getTypeForDoc(DOMDocument $doc): DOMElement { + if (count($this->types) > 1) { + $typeSort = $this->isIntersection ? "intersection" : "union"; + $typeElement = $doc->createElement('type'); + $typeElement->setAttribute("class", $typeSort); + + foreach ($this->types as $type) { + $unionTypeElement = $doc->createElement('type', $type->name); + $typeElement->appendChild($unionTypeElement); + } + } else { + $type = $this->types[0]; + $name = $type->name; + + $typeElement = $doc->createElement('type', $name); + } + + return $typeElement; + } + + public static function equals(?Type $a, ?Type $b): bool { + if ($a === null || $b === null) { + return $a === $b; + } + + if (count($a->types) !== count($b->types)) { + return false; + } + + for ($i = 0; $i < count($a->types); $i++) { + if (!$a->types[$i]->equals($b->types[$i])) { + return false; + } + } + + return true; + } + + public function __toString() { + if ($this->types === null) { + return 'mixed'; + } + + $char = $this->isIntersection ? '&' : '|'; + return implode($char, array_map( + function ($type) { return $type->name; }, + $this->types) + ); + } +} + +class ArginfoType { + /** @var SimpleType[] $classTypes */ + public array $classTypes; + /** @var SimpleType[] $builtinTypes */ + private array $builtinTypes; + + /** + * @param SimpleType[] $classTypes + * @param SimpleType[] $builtinTypes + */ + public function __construct(array $classTypes, array $builtinTypes) { + $this->classTypes = $classTypes; + $this->builtinTypes = $builtinTypes; + } + + public function hasClassType(): bool { + return !empty($this->classTypes); + } + + public function toClassTypeString(): string { + return implode('|', array_map(function(SimpleType $type) { + return $type->toEscapedName(); + }, $this->classTypes)); + } + + public function toTypeMask(): string { + if (empty($this->builtinTypes)) { + return '0'; + } + return implode('|', array_map(function(SimpleType $type) { + return $type->toTypeMask(); + }, $this->builtinTypes)); + } +} + +class ArgInfo { + const SEND_BY_VAL = 0; + const SEND_BY_REF = 1; + const SEND_PREFER_REF = 2; + + public string $name; + public int $sendBy; + public bool $isVariadic; + public ?Type $type; + public ?Type $phpDocType; + public ?string $defaultValue; + /** @var AttributeInfo[] */ + public array $attributes; + + /** + * @param AttributeInfo[] $attributes + */ + public function __construct( + string $name, + int $sendBy, + bool $isVariadic, + ?Type $type, + ?Type $phpDocType, + ?string $defaultValue, + array $attributes + ) { + $this->name = $name; + $this->sendBy = $sendBy; + $this->isVariadic = $isVariadic; + $this->setTypes($type, $phpDocType); + $this->defaultValue = $defaultValue; + $this->attributes = $attributes; + } + + public function equals(ArgInfo $other): bool { + return $this->name === $other->name + && $this->sendBy === $other->sendBy + && $this->isVariadic === $other->isVariadic + && Type::equals($this->type, $other->type) + && $this->defaultValue === $other->defaultValue; + } + + public function getSendByString(): string { + switch ($this->sendBy) { + case self::SEND_BY_VAL: + return "0"; + case self::SEND_BY_REF: + return "1"; + case self::SEND_PREFER_REF: + return "ZEND_SEND_PREFER_REF"; + } + throw new Exception("Invalid sendBy value"); + } + + public function getMethodSynopsisType(): Type { + if ($this->type) { + return $this->type; + } + + if ($this->phpDocType) { + return $this->phpDocType; + } + + throw new Exception("A parameter must have a type"); + } + + public function hasProperDefaultValue(): bool { + return $this->defaultValue !== null && $this->defaultValue !== "UNKNOWN"; + } + + public function getDefaultValueAsArginfoString(): string { + if ($this->hasProperDefaultValue()) { + return '"' . addslashes($this->defaultValue) . '"'; + } + + return "NULL"; + } + + public function getDefaultValueAsMethodSynopsisString(): ?string { + if ($this->defaultValue === null) { + return null; + } + + switch ($this->defaultValue) { + case 'UNKNOWN': + return null; + case 'false': + case 'true': + case 'null': + return "&{$this->defaultValue};"; + } + + return $this->defaultValue; + } + + private function setTypes(?Type $type, ?Type $phpDocType): void + { + $this->type = $type; + $this->phpDocType = $phpDocType; + } +} + +interface ConstOrClassConstName { + public function __toString(): string; + public function equals(ConstOrClassConstName $const): bool; + public function isClassConst(): bool; + public function isUnknown(): bool; +} + +abstract class AbstractConstName implements ConstOrClassConstName +{ + public function equals(ConstOrClassConstName $const): bool + { + return $this->__toString() === $const->__toString(); + } + + public function isUnknown(): bool + { + return strtolower($this->__toString()) === "unknown"; + } +} + +class ConstName extends AbstractConstName { + public string $const; + + public function __construct(?Name $namespace, string $const) + { + if ($namespace && ($namespace = $namespace->slice(0, -1))) { + $const = $namespace->toString() . '\\' . $const; + } + $this->const = $const; + } + + public function isClassConst(): bool + { + return false; + } + + public function isUnknown(): bool + { + $name = $this->__toString(); + if (($pos = strrpos($name, '\\')) !== false) { + $name = substr($name, $pos + 1); + } + return strtolower($name) === "unknown"; + } + + public function __toString(): string + { + return $this->const; + } +} + +class ClassConstName extends AbstractConstName { + public Name $class; + public string $const; + + public function __construct(Name $class, string $const) + { + $this->class = $class; + $this->const = $const; + } + + public function isClassConst(): bool + { + return true; + } + + public function __toString(): string + { + return $this->class->toString() . "::" . $this->const; + } +} + +class PropertyName { + public Name $class; + public string $property; + + public function __construct(Name $class, string $property) + { + $this->class = $class; + $this->property = $property; + } + + public function __toString() + { + return $this->class->toString() . "::$" . $this->property; + } +} + +interface FunctionOrMethodName { + public function getDeclaration(): string; + public function getArgInfoName(): string; + public function getMethodSynopsisFilename(): string; + public function getNameForAttributes(): string; + public function __toString(): string; + public function isMethod(): bool; + public function isConstructor(): bool; + public function isDestructor(): bool; +} + +class FunctionName implements FunctionOrMethodName { + private Name $name; + + public function __construct(Name $name) { + $this->name = $name; + } + + public function getNamespace(): ?string { + if ($this->name->isQualified()) { + return $this->name->slice(0, -1)->toString(); + } + return null; + } + + public function getNonNamespacedName(): string { + if ($this->name->isQualified()) { + throw new Exception("Namespaced name not supported here"); + } + return $this->name->toString(); + } + + public function getDeclarationName(): string { + return implode('_', $this->name->getParts()); + } + + public function getFunctionName(): string { + return $this->name->getLast(); + } + + public function getDeclaration(): string { + return "ZEND_FUNCTION({$this->getDeclarationName()});\n"; + } + + public function getArgInfoName(): string { + $underscoreName = implode('_', $this->name->getParts()); + return "arginfo_$underscoreName"; + } + + public function getMethodSynopsisFilename(): string { + return implode('_', $this->name->getParts()); + } + + public function getNameForAttributes(): string { + return strtolower($this->name->toString()); + } + + public function __toString(): string { + return $this->name->toString(); + } + + public function isMethod(): bool { + return false; + } + + public function isConstructor(): bool { + return false; + } + + public function isDestructor(): bool { + return false; + } +} + +class MethodName implements FunctionOrMethodName { + public Name $className; + public string $methodName; + + public function __construct(Name $className, string $methodName) { + $this->className = $className; + $this->methodName = $methodName; + } + + public function getDeclarationClassName(): string { + return implode('_', $this->className->getParts()); + } + + public function getDeclaration(): string { + return "ZEND_METHOD({$this->getDeclarationClassName()}, $this->methodName);\n"; + } + + public function getArgInfoName(): string { + return "arginfo_class_{$this->getDeclarationClassName()}_{$this->methodName}"; + } + + public function getMethodSynopsisFilename(): string { + return $this->getDeclarationClassName() . "_{$this->methodName}"; + } + + public function getNameForAttributes(): string { + return strtolower($this->methodName); + } + + public function __toString(): string { + return "$this->className::$this->methodName"; + } + + public function isMethod(): bool { + return true; + } + + public function isConstructor(): bool { + return $this->methodName === "__construct"; + } + + public function isDestructor(): bool { + return $this->methodName === "__destruct"; + } +} + +class ReturnInfo { + const REFCOUNT_0 = "0"; + const REFCOUNT_1 = "1"; + const REFCOUNT_N = "N"; + + const REFCOUNTS = [ + self::REFCOUNT_0, + self::REFCOUNT_1, + self::REFCOUNT_N, + ]; + + public bool $byRef; + public ?Type $type; + public ?Type $phpDocType; + public bool $tentativeReturnType; + public string $refcount; + + public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType, bool $tentativeReturnType, ?string $refcount) { + $this->byRef = $byRef; + $this->setTypes($type, $phpDocType, $tentativeReturnType); + $this->setRefcount($refcount); + } + + public function equalsApartFromPhpDocAndRefcount(ReturnInfo $other): bool { + return $this->byRef === $other->byRef + && Type::equals($this->type, $other->type) + && $this->tentativeReturnType === $other->tentativeReturnType; + } + + public function getMethodSynopsisType(): ?Type { + return $this->type ?? $this->phpDocType; + } + + private function setTypes(?Type $type, ?Type $phpDocType, bool $tentativeReturnType): void + { + $this->type = $type; + $this->phpDocType = $phpDocType; + $this->tentativeReturnType = $tentativeReturnType; + } + + private function setRefcount(?string $refcount): void + { + $type = $this->phpDocType ?? $this->type; + $isScalarType = $type !== null && $type->isScalar(); + + if ($refcount === null) { + $this->refcount = $isScalarType ? self::REFCOUNT_0 : self::REFCOUNT_N; + return; + } + + if (!in_array($refcount, ReturnInfo::REFCOUNTS, true)) { + throw new Exception("@refcount must have one of the following values: \"0\", \"1\", \"N\", $refcount given"); + } + + if ($isScalarType && $refcount !== self::REFCOUNT_0) { + throw new Exception('A scalar return type of "' . $type->__toString() . '" must have a refcount of "' . self::REFCOUNT_0 . '"'); + } + + if (!$isScalarType && $refcount === self::REFCOUNT_0) { + throw new Exception('A non-scalar return type of "' . $type->__toString() . '" cannot have a refcount of "' . self::REFCOUNT_0 . '"'); + } + + $this->refcount = $refcount; + } +} + +class FuncInfo { + public FunctionOrMethodName $name; + public int $classFlags; + public int $flags; + public ?string $aliasType; + public ?FunctionOrMethodName $alias; + public bool $isDeprecated; + public bool $supportsCompileTimeEval; + public bool $verify; + /** @var ArgInfo[] */ + public array $args; + public ReturnInfo $return; + public int $numRequiredArgs; + public ?string $cond; + public bool $isUndocumentable; + + /** + * @param ArgInfo[] $args + */ + public function __construct( + FunctionOrMethodName $name, + int $classFlags, + int $flags, + ?string $aliasType, + ?FunctionOrMethodName $alias, + bool $isDeprecated, + bool $supportsCompileTimeEval, + bool $verify, + array $args, + ReturnInfo $return, + int $numRequiredArgs, + ?string $cond, + bool $isUndocumentable + ) { + $this->name = $name; + $this->classFlags = $classFlags; + $this->flags = $flags; + $this->aliasType = $aliasType; + $this->alias = $alias; + $this->isDeprecated = $isDeprecated; + $this->supportsCompileTimeEval = $supportsCompileTimeEval; + $this->verify = $verify; + $this->args = $args; + $this->return = $return; + $this->numRequiredArgs = $numRequiredArgs; + $this->cond = $cond; + $this->isUndocumentable = $isUndocumentable; + } + + public function isMethod(): bool + { + return $this->name->isMethod(); + } + + public function isFinalMethod(): bool + { + return ($this->flags & Class_::MODIFIER_FINAL) || ($this->classFlags & Class_::MODIFIER_FINAL); + } + + public function isInstanceMethod(): bool + { + return !($this->flags & Class_::MODIFIER_STATIC) && $this->isMethod() && !$this->name->isConstructor(); + } + + /** @return string[] */ + public function getModifierNames(): array + { + if (!$this->isMethod()) { + return []; + } + + $result = []; + + if ($this->flags & Class_::MODIFIER_FINAL) { + $result[] = "final"; + } elseif ($this->flags & Class_::MODIFIER_ABSTRACT && $this->classFlags & ~Class_::MODIFIER_ABSTRACT) { + $result[] = "abstract"; + } + + if ($this->flags & Class_::MODIFIER_PROTECTED) { + $result[] = "protected"; + } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $result[] = "private"; + } else { + $result[] = "public"; + } + + if ($this->flags & Class_::MODIFIER_STATIC) { + $result[] = "static"; + } + + return $result; + } + + public function hasParamWithUnknownDefaultValue(): bool + { + foreach ($this->args as $arg) { + if ($arg->defaultValue && !$arg->hasProperDefaultValue()) { + return true; + } + } + + return false; + } + + public function equalsApartFromNameAndRefcount(FuncInfo $other): bool { + if (count($this->args) !== count($other->args)) { + return false; + } + + for ($i = 0; $i < count($this->args); $i++) { + if (!$this->args[$i]->equals($other->args[$i])) { + return false; + } + } + + return $this->return->equalsApartFromPhpDocAndRefcount($other->return) + && $this->numRequiredArgs === $other->numRequiredArgs + && $this->cond === $other->cond; + } + + public function getArgInfoName(): string { + return $this->name->getArgInfoName(); + } + + public function getDeclarationKey(): string + { + $name = $this->alias ?? $this->name; + + return "$name|$this->cond"; + } + + public function getDeclaration(): ?string + { + if ($this->flags & Class_::MODIFIER_ABSTRACT) { + return null; + } + + $name = $this->alias ?? $this->name; + + return $name->getDeclaration(); + } + + public function getFunctionEntry(): string { + if ($this->name instanceof MethodName) { + if ($this->alias) { + if ($this->alias instanceof MethodName) { + return sprintf( + "\tZEND_MALIAS(%s, %s, %s, %s, %s)\n", + $this->alias->getDeclarationClassName(), $this->name->methodName, + $this->alias->methodName, $this->getArgInfoName(), $this->getFlagsAsArginfoString() + ); + } else if ($this->alias instanceof FunctionName) { + return sprintf( + "\tZEND_ME_MAPPING(%s, %s, %s, %s)\n", + $this->name->methodName, $this->alias->getNonNamespacedName(), + $this->getArgInfoName(), $this->getFlagsAsArginfoString() + ); + } else { + throw new Error("Cannot happen"); + } + } else { + $declarationClassName = $this->name->getDeclarationClassName(); + if ($this->flags & Class_::MODIFIER_ABSTRACT) { + return sprintf( + "\tZEND_ABSTRACT_ME_WITH_FLAGS(%s, %s, %s, %s)\n", + $declarationClassName, $this->name->methodName, $this->getArgInfoName(), + $this->getFlagsAsArginfoString() + ); + } + + return sprintf( + "\tZEND_ME(%s, %s, %s, %s)\n", + $declarationClassName, $this->name->methodName, $this->getArgInfoName(), + $this->getFlagsAsArginfoString() + ); + } + } else if ($this->name instanceof FunctionName) { + $namespace = $this->name->getNamespace(); + $functionName = $this->name->getFunctionName(); + $declarationName = $this->alias ? $this->alias->getNonNamespacedName() : $this->name->getDeclarationName(); + + if ($namespace) { + // Namespaced functions are always declared as aliases to avoid name conflicts when two functions with + // the same name exist in separate namespaces + $macro = $this->isDeprecated ? 'ZEND_NS_DEP_FALIAS' : 'ZEND_NS_FALIAS'; + + // Render A\B as "A\\B" in C strings for namespaces + return sprintf( + "\t%s(\"%s\", %s, %s, %s)\n", + $macro, addslashes($namespace), $this->name->getFunctionName(), $declarationName, $this->getArgInfoName() + ); + } + + if ($this->alias) { + $macro = $this->isDeprecated ? 'ZEND_DEP_FALIAS' : 'ZEND_FALIAS'; + + return sprintf( + "\t%s(%s, %s, %s)\n", + $macro, $functionName, $declarationName, $this->getArgInfoName() + ); + } + + switch (true) { + case $this->isDeprecated: + $macro = 'ZEND_DEP_FE'; + break; + case $this->supportsCompileTimeEval: + $macro = 'ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE'; + break; + default: + $macro = 'ZEND_FE'; + } + + return sprintf("\t%s(%s, %s)\n", $macro, $functionName, $this->getArgInfoName()); + } else { + throw new Error("Cannot happen"); + } + } + + public function getOptimizerInfo(): ?string { + if ($this->isMethod()) { + return null; + } + + if ($this->alias !== null) { + return null; + } + + if ($this->return->refcount !== ReturnInfo::REFCOUNT_1 && $this->return->phpDocType === null) { + return null; + } + + $type = $this->return->phpDocType ?? $this->return->type; + if ($type === null) { + return null; + } + + return "\tF" . $this->return->refcount . '("' . $this->name->__toString() . '", ' . $type->toOptimizerTypeMask() . "),\n"; + } + + public function discardInfoForOldPhpVersions(): void { + $this->return->type = null; + foreach ($this->args as $arg) { + $arg->type = null; + $arg->defaultValue = null; + $arg->attributes = []; + } + } + + private function getFlagsAsArginfoString(): string + { + $flags = "ZEND_ACC_PUBLIC"; + if ($this->flags & Class_::MODIFIER_PROTECTED) { + $flags = "ZEND_ACC_PROTECTED"; + } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $flags = "ZEND_ACC_PRIVATE"; + } + + if ($this->flags & Class_::MODIFIER_STATIC) { + $flags .= "|ZEND_ACC_STATIC"; + } + + if ($this->flags & Class_::MODIFIER_FINAL) { + $flags .= "|ZEND_ACC_FINAL"; + } + + if ($this->flags & Class_::MODIFIER_ABSTRACT) { + $flags .= "|ZEND_ACC_ABSTRACT"; + } + + if ($this->isDeprecated) { + $flags .= "|ZEND_ACC_DEPRECATED"; + } + + return $flags; + } + + /** + * @param array $funcMap + * @param array $aliasMap + * @throws Exception + */ + public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string { + + $doc = new DOMDocument(); + $doc->formatOutput = true; + $methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + if (!$methodSynopsis) { + return null; + } + + $doc->appendChild($methodSynopsis); + + return $doc->saveXML(); + } + + /** + * @param array $funcMap + * @param array $aliasMap + * @throws Exception + */ + public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDocument $doc): ?DOMElement { + if ($this->hasParamWithUnknownDefaultValue()) { + return null; + } + + if ($this->name->isConstructor()) { + $synopsisType = "constructorsynopsis"; + } elseif ($this->name->isDestructor()) { + $synopsisType = "destructorsynopsis"; + } else { + $synopsisType = "methodsynopsis"; + } + + $methodSynopsis = $doc->createElement($synopsisType); + + if ($this->isMethod()) { + assert($this->name instanceof MethodName); + $role = $doc->createAttribute("role"); + $role->value = addslashes($this->name->className->__toString()); + $methodSynopsis->appendChild($role); + } + + $methodSynopsis->appendChild(new DOMText("\n ")); + + foreach ($this->getModifierNames() as $modifierString) { + $modifierElement = $doc->createElement('modifier', $modifierString); + $methodSynopsis->appendChild($modifierElement); + $methodSynopsis->appendChild(new DOMText(" ")); + } + + $returnType = $this->return->getMethodSynopsisType(); + if ($returnType) { + $methodSynopsis->appendChild($returnType->getTypeForDoc($doc)); + } + + $methodname = $doc->createElement('methodname', $this->name->__toString()); + $methodSynopsis->appendChild($methodname); + + if (empty($this->args)) { + $methodSynopsis->appendChild(new DOMText("\n ")); + $void = $doc->createElement('void'); + $methodSynopsis->appendChild($void); + } else { + foreach ($this->args as $arg) { + $methodSynopsis->appendChild(new DOMText("\n ")); + $methodparam = $doc->createElement('methodparam'); + if ($arg->defaultValue !== null) { + $methodparam->setAttribute("choice", "opt"); + } + if ($arg->isVariadic) { + $methodparam->setAttribute("rep", "repeat"); + } + + $methodSynopsis->appendChild($methodparam); + $methodparam->appendChild($arg->getMethodSynopsisType()->getTypeForDoc($doc)); + + $parameter = $doc->createElement('parameter', $arg->name); + if ($arg->sendBy !== ArgInfo::SEND_BY_VAL) { + $parameter->setAttribute("role", "reference"); + } + + $methodparam->appendChild($parameter); + $defaultValue = $arg->getDefaultValueAsMethodSynopsisString(); + if ($defaultValue !== null) { + $initializer = $doc->createElement('initializer'); + if (preg_match('/^[a-zA-Z_][a-zA-Z_0-9]*$/', $defaultValue)) { + $constant = $doc->createElement('constant', $defaultValue); + $initializer->appendChild($constant); + } else { + $initializer->nodeValue = $defaultValue; + } + $methodparam->appendChild($initializer); + } + } + } + $methodSynopsis->appendChild(new DOMText("\n ")); + + return $methodSynopsis; + } + + public function __clone() + { + foreach ($this->args as $key => $argInfo) { + $this->args[$key] = clone $argInfo; + } + $this->return = clone $this->return; + } +} + +class EvaluatedValue +{ + /** @var mixed */ + public $value; + public SimpleType $type; + public Expr $expr; + public bool $isUnknownConstValue; + /** @var ConstInfo[] */ + public array $originatingConsts; + + /** + * @param iterable $allConstInfos + */ + public static function createFromExpression(Expr $expr, ?SimpleType $constType, ?string $cConstName, iterable $allConstInfos): EvaluatedValue + { + // This visitor replaces the PHP constants by C constants. It allows direct expansion of the compiled constants, e.g. later in the pretty printer. + $visitor = new class($allConstInfos) extends PhpParser\NodeVisitorAbstract + { + /** @var iterable */ + public array $visitedConstants = []; + /** @var iterable */ + public iterable $allConstInfos; + + /** @param iterable $allConstInfos */ + public function __construct(iterable $allConstInfos) + { + $this->allConstInfos = $allConstInfos; + } + + /** @return Node|null */ + public function enterNode(Node $expr) + { + if (!$expr instanceof Expr\ConstFetch && !$expr instanceof Expr\ClassConstFetch) { + return null; + } + + if ($expr instanceof Expr\ClassConstFetch) { + $originatingConstName = new ClassConstName($expr->class, $expr->name->toString()); + } else { + $originatingConstName = new ConstName($expr->name->getAttribute('namespacedName'), $expr->name->toString()); + } + + if ($originatingConstName->isUnknown()) { + return null; + } + + foreach ($this->allConstInfos as $const) { + if ($originatingConstName->equals($const->name)) { + $this->visitedConstants[] = $const; + return $const->getValue($this->allConstInfos)->expr; + } + } + } + }; + + $nodeTraverser = new PhpParser\NodeTraverser; + $nodeTraverser->addVisitor($visitor); + $expr = $nodeTraverser->traverse([$expr])[0]; + + $isUnknownConstValue = false; + + $evaluator = new ConstExprEvaluator( + static function (Expr $expr) use ($allConstInfos, &$isUnknownConstValue) { + // $expr is a ConstFetch with a name of a C macro here + if (!$expr instanceof Expr\ConstFetch) { + throw new Exception($this->getVariableTypeName() . " " . $this->getVariableLikeName() . " has an unsupported value"); + } + + $constName = $expr->name->__toString(); + if (strtolower($constName) === "unknown") { + $isUnknownConstValue = true; + return null; + } + + foreach ($allConstInfos as $const) { + if ($constName != $const->cValue) { + continue; + } + + $constType = ($const->phpDocType ?? $const->type)->tryToSimpleType(); + if ($constType) { + if ($constType->isBool()) { + return true; + } elseif ($constType->isInt()) { + return 1; + } elseif ($constType->isFloat()) { + return M_PI; + } elseif ($constType->isString()) { + return $const->name; + } elseif ($constType->isArray()) { + return []; + } + } + + return null; + } + + throw new Exception("Constant " . $originatingConstName->__toString() . " cannot be found"); + } + ); + + $result = $evaluator->evaluateDirectly($expr); + + return new EvaluatedValue( + $result, // note: we are generally not interested in the actual value of $result, unless it's a bare value, without constants + $constType ?? SimpleType::fromValue($result), + $cConstName === null ? $expr : new Expr\ConstFetch(new Node\Name($cConstName)), + $visitor->visitedConstants, + $isUnknownConstValue + ); + } + + public static function null(): EvaluatedValue + { + return new self(null, SimpleType::null(), new Expr\ConstFetch(new Node\Name('null')), [], false); + } + + /** + * @param mixed $value + * @param ConstInfo[] $originatingConsts + */ + private function __construct($value, SimpleType $type, Expr $expr, array $originatingConsts, bool $isUnknownConstValue) + { + $this->value = $value; + $this->type = $type; + $this->expr = $expr; + $this->originatingConsts = $originatingConsts; + $this->isUnknownConstValue = $isUnknownConstValue; + } + + public function initializeZval(string $zvalName): string + { + $cExpr = $this->getCExpr(); + + $code = "\tzval $zvalName;\n"; + + if ($this->type->isNull()) { + $code .= "\tZVAL_NULL(&$zvalName);\n"; + } elseif ($this->type->isBool()) { + if ($cExpr == 'true') { + $code .= "\tZVAL_TRUE(&$zvalName);\n"; + } elseif ($cExpr == 'false') { + $code .= "\tZVAL_FALSE(&$zvalName);\n"; + } else { + $code .= "\tZVAL_BOOL(&$zvalName, $cExpr);\n"; + } + } elseif ($this->type->isInt()) { + $code .= "\tZVAL_LONG(&$zvalName, $cExpr);\n"; + } elseif ($this->type->isFloat()) { + $code .= "\tZVAL_DOUBLE(&$zvalName, $cExpr);\n"; + } elseif ($this->type->isString()) { + if ($cExpr === '""') { + $code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n"; + } else { + $code .= "\tzend_string *{$zvalName}_str = zend_string_init($cExpr, strlen($cExpr), 1);\n"; + $code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n"; + } + } elseif ($this->type->isArray()) { + if ($cExpr == '[]') { + $code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n"; + } else { + throw new Exception("Unimplemented default value"); + } + } else { + throw new Exception("Invalid default value: " . print_r($this->value, true) . ", type: " . print_r($this->type, true)); + } + + return $code; + } + + public function getCExpr(): ?string + { + // $this->expr has all its PHP constants replaced by C constants + $prettyPrinter = new Standard; + $expr = $prettyPrinter->prettyPrintExpr($this->expr); + return $expr[0] == '"' ? $expr : preg_replace('(\bnull\b)', 'NULL', str_replace('\\', '', $expr)); + } +} + +abstract class VariableLike +{ + public int $flags; + public ?Type $type; + public ?Type $phpDocType; + public ?string $link; + public ?int $phpVersionIdMinimumCompatibility; + + public function __construct( + int $flags, + ?Type $type, + ?Type $phpDocType, + ?string $link, + ?int $phpVersionIdMinimumCompatibility + ) { + $this->flags = $flags; + $this->type = $type; + $this->phpDocType = $phpDocType; + $this->link = $link; + $this->phpVersionIdMinimumCompatibility = $phpVersionIdMinimumCompatibility; + } + + abstract protected function getVariableTypeCode(): string; + + abstract protected function getVariableTypeName(): string; + + abstract protected function getVariableLikeName(): string; + + abstract protected function getFieldSynopsisDefaultLinkend(): string; + + abstract protected function getFieldSynopsisName(): string; + + /** + * @param iterable $allConstInfos + */ + abstract protected function getFieldSynopsisValueString(iterable $allConstInfos): ?string; + + abstract public function discardInfoForOldPhpVersions(): void; + + protected function addTypeToFieldSynopsis(DOMDocument $doc, DOMElement $fieldsynopsisElement): void + { + $type = $this->phpDocType ?? $this->type; + + if ($type) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($type->getTypeForDoc($doc)); + } + } + + /** + * @return array + */ + protected function getFlagsByPhpVersion(): array + { + $flags = "ZEND_ACC_PUBLIC"; + if ($this->flags & Class_::MODIFIER_PROTECTED) { + $flags = "ZEND_ACC_PROTECTED"; + } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $flags = "ZEND_ACC_PRIVATE"; + } + + return [ + PHP_70_VERSION_ID => [$flags], + PHP_80_VERSION_ID => [$flags], + PHP_81_VERSION_ID => [$flags], + PHP_82_VERSION_ID => [$flags], + PHP_83_VERSION_ID => [$flags], + ]; + } + + protected function getTypeCode(string $variableLikeName, string &$code): string + { + $variableLikeType = $this->getVariableTypeName(); + + $typeCode = ""; + + if ($this->type) { + $arginfoType = $this->type->toArginfoType(); + if ($arginfoType->hasClassType()) { + if (count($arginfoType->classTypes) >= 2) { + foreach ($arginfoType->classTypes as $classType) { + $escapedClassName = $classType->toEscapedName(); + $varEscapedClassName = $classType->toVarEscapedName(); + $code .= "\tzend_string *{$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"{$escapedClassName}\") - 1, 1);\n"; + } + + $classTypeCount = count($arginfoType->classTypes); + $code .= "\tzend_type_list *{$variableLikeType}_{$variableLikeName}_type_list = malloc(ZEND_TYPE_LIST_SIZE($classTypeCount));\n"; + $code .= "\t{$variableLikeType}_{$variableLikeName}_type_list->num_types = $classTypeCount;\n"; + + foreach ($arginfoType->classTypes as $k => $classType) { + $escapedClassName = $classType->toEscapedName(); + $code .= "\t{$variableLikeType}_{$variableLikeName}_type_list->types[$k] = (zend_type) ZEND_TYPE_INIT_CLASS({$variableLikeType}_{$variableLikeName}_class_{$escapedClassName}, 0, 0);\n"; + } + + $typeMaskCode = $this->type->toArginfoType()->toTypeMask(); + + if ($this->type->isIntersection) { + $code .= "\tzend_type {$variableLikeType}_{$variableLikeName}_type = ZEND_TYPE_INIT_INTERSECTION({$variableLikeType}_{$variableLikeName}_type_list, $typeMaskCode);\n"; + } else { + $code .= "\tzend_type {$variableLikeType}_{$variableLikeName}_type = ZEND_TYPE_INIT_UNION({$variableLikeType}_{$variableLikeName}_type_list, $typeMaskCode);\n"; + } + $typeCode = "{$variableLikeType}_{$variableLikeName}_type"; + } else { + $escapedClassName = $arginfoType->classTypes[0]->toEscapedName(); + $varEscapedClassName = $arginfoType->classTypes[0]->toVarEscapedName(); + $code .= "\tzend_string *{$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"{$escapedClassName}\")-1, 1);\n"; + + $typeCode = "(zend_type) ZEND_TYPE_INIT_CLASS({$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName}, 0, " . $arginfoType->toTypeMask() . ")"; + } + } else { + $typeCode = "(zend_type) ZEND_TYPE_INIT_MASK(" . $arginfoType->toTypeMask() . ")"; + } + } + + return $typeCode; + } + + /** + * @param iterable $allConstInfos + */ + public function getFieldSynopsisElement(DOMDocument $doc, iterable $allConstInfos): DOMElement + { + $fieldsynopsisElement = $doc->createElement("fieldsynopsis"); + + $this->addModifiersToFieldSynopsis($doc, $fieldsynopsisElement); + + $this->addTypeToFieldSynopsis($doc, $fieldsynopsisElement); + + $varnameElement = $doc->createElement("varname", $this->getFieldSynopsisName()); + if ($this->link) { + $varnameElement->setAttribute("linkend", $this->link); + } else { + $varnameElement->setAttribute("linkend", $this->getFieldSynopsisDefaultLinkend()); + } + + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($varnameElement); + + $valueString = $this->getFieldSynopsisValueString($allConstInfos); + if ($valueString) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $initializerElement = $doc->createElement("initializer", $valueString); + $fieldsynopsisElement->appendChild($initializerElement); + } + + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + + return $fieldsynopsisElement; + } + + protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fieldsynopsisElement): void + { + if ($this->flags & Class_::MODIFIER_PUBLIC) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "public")); + } elseif ($this->flags & Class_::MODIFIER_PROTECTED) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "protected")); + } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "private")); + } + } + + /** + * @param array $flags + * @return array + */ + protected function addFlagForVersionsAbove(array $flags, string $flag, int $minimumVersionId): array + { + $write = false; + + foreach ($flags as $version => $versionFlags) { + if ($version === $minimumVersionId || $write === true) { + $flags[$version][] = $flag; + $write = true; + } + } + + return $flags; + } +} + +class ConstInfo extends VariableLike +{ + public ConstOrClassConstName $name; + public Expr $value; + public bool $isDeprecated; + public ?string $valueString; + public ?string $cond; + public ?string $cValue; + + public function __construct( + ConstOrClassConstName $name, + int $flags, + Expr $value, + ?string $valueString, + ?Type $type, + ?Type $phpDocType, + bool $isDeprecated, + ?string $cond, + ?string $cValue, + ?string $link, + ?int $phpVersionIdMinimumCompatibility + ) { + $this->name = $name; + $this->value = $value; + $this->valueString = $valueString; + $this->isDeprecated = $isDeprecated; + $this->cond = $cond; + $this->cValue = $cValue; + parent::__construct($flags, $type, $phpDocType, $link, $phpVersionIdMinimumCompatibility); + } + + /** + * @param iterable $allConstInfos + */ + public function getValue(iterable $allConstInfos): EvaluatedValue + { + return EvaluatedValue::createFromExpression( + $this->value, + ($this->phpDocType ?? $this->type)->tryToSimpleType(), + $this->cValue, + $allConstInfos + ); + } + + protected function getVariableTypeName(): string + { + return "constant"; + } + + protected function getVariableLikeName(): string + { + return $this->name->const; + } + + protected function getVariableTypeCode(): string + { + return "const"; + } + + protected function getFieldSynopsisDefaultLinkend(): string + { + $className = str_replace(["\\", "_"], ["-", "-"], $this->name->class->toLowerString()); + + return "$className.constants." . strtolower(str_replace("_", "-", $this->getVariableLikeName())); + } + + protected function getFieldSynopsisName(): string + { + return $this->name->__toString(); + } + + /** + * @param iterable $allConstInfos + */ + protected function getFieldSynopsisValueString(iterable $allConstInfos): ?string + { + $value = EvaluatedValue::createFromExpression($this->value, null, $this->cValue, $allConstInfos); + if ($value->isUnknownConstValue) { + return null; + } + + if ($value->originatingConsts) { + return implode("\n", array_map(function (ConstInfo $const) use ($allConstInfos) { + return $const->getFieldSynopsisValueString($allConstInfos); + }, $value->originatingConsts)); + } + + return $this->valueString; + } + + public function discardInfoForOldPhpVersions(): void { + $this->type = null; + $this->flags &= ~Class_::MODIFIER_FINAL; + $this->isDeprecated = false; + } + + /** + * @param iterable $allConstInfos + */ + public function getDeclaration(iterable $allConstInfos): string + { + $simpleType = ($this->phpDocType ?? $this->type)->tryToSimpleType(); + if ($simpleType && $simpleType->name === "mixed") { + $simpleType = null; + } + + $value = EvaluatedValue::createFromExpression($this->value, $simpleType, $this->cValue, $allConstInfos); + if ($value->isUnknownConstValue && ($simpleType === null || !$simpleType->isBuiltin)) { + throw new Exception("Constant " . $this->name->__toString() . " must have a built-in PHPDoc type as the type couldn't be inferred from its value"); + } + + // i.e. const NAME = UNKNOWN;, without the annotation + if ($value->isUnknownConstValue && $this->cValue === null && $value->expr instanceof Expr\ConstFetch && $value->expr->name->__toString() === "UNKNOWN") { + throw new Exception("Constant " . $this->name->__toString() . " must have a @cvalue annotation"); + } + + $code = ""; + + if ($this->cond) { + $code .= "#if {$this->cond}\n"; + } + + if ($this->name->isClassConst()) { + $code .= $this->getClassConstDeclaration($value, $allConstInfos); + } else { + $code .= $this->getGlobalConstDeclaration($value, $allConstInfos); + } + $code .= $this->getValueAssertion($value); + + if ($this->cond) { + $code .= "#endif\n"; + } + + return $code; + } + + /** + * @param iterable $allConstInfos + */ + private function getGlobalConstDeclaration(EvaluatedValue $value, iterable $allConstInfos): string + { + $constName = str_replace('\\', '\\\\', $this->name->__toString()); + $constValue = $value->value; + $cExpr = $value->getCExpr(); + + $flags = "CONST_PERSISTENT"; + if ($this->phpVersionIdMinimumCompatibility !== null && $this->phpVersionIdMinimumCompatibility < 80000) { + $flags .= " | CONST_CS"; + } + + if ($this->isDeprecated) { + $flags .= " | CONST_DEPRECATED"; + } + if ($value->type->isNull()) { + return "\tREGISTER_NULL_CONSTANT(\"$constName\", $flags);\n"; + } + + if ($value->type->isBool()) { + return "\tREGISTER_BOOL_CONSTANT(\"$constName\", " . ($cExpr ?: ($constValue ? "true" : "false")) . ", $flags);\n"; + } + + if ($value->type->isInt()) { + return "\tREGISTER_LONG_CONSTANT(\"$constName\", " . ($cExpr ?: (int) $constValue) . ", $flags);\n"; + } + + if ($value->type->isFloat()) { + return "\tREGISTER_DOUBLE_CONSTANT(\"$constName\", " . ($cExpr ?: (float) $constValue) . ", $flags);\n"; + } + + if ($value->type->isString()) { + return "\tREGISTER_STRING_CONSTANT(\"$constName\", " . ($cExpr ?: '"' . addslashes($constValue) . '"') . ", $flags);\n"; + } + + throw new Exception("Unimplemented constant type");} + + /** + * @param iterable $allConstInfos + */ + private function getClassConstDeclaration(EvaluatedValue $value, iterable $allConstInfos): string + { + $constName = $this->getVariableLikeName(); + + $zvalCode = $value->initializeZval("const_{$constName}_value", $allConstInfos); + + $code = "\n" . $zvalCode; + + $code .= "\tzend_string *const_{$constName}_name = zend_string_init_interned(\"$constName\", sizeof(\"$constName\") - 1, 1);\n"; + $nameCode = "const_{$constName}_name"; + + $php83MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_83_VERSION_ID; + + if ($this->type && !$php83MinimumCompatibility) { + $code .= "#if (PHP_VERSION_ID >= " . PHP_83_VERSION_ID . ")\n"; + } + + if ($this->type) { + $typeCode = $this->getTypeCode($constName, $code); + $template = "\tzend_declare_typed_class_constant(class_entry, $nameCode, &const_{$constName}_value, %s, NULL, $typeCode);\n"; + $flagsCode = generateVersionDependentFlagCode( + $template, + $this->getFlagsByPhpVersion(), + $this->phpVersionIdMinimumCompatibility + ); + $code .= implode("", $flagsCode); + } + + if ($this->type && !$php83MinimumCompatibility) { + $code .= "#else\n"; + } + + if (!$this->type || !$php83MinimumCompatibility) { + $template = "\tzend_declare_class_constant_ex(class_entry, $nameCode, &const_{$constName}_value, %s, NULL);\n"; + $flagsCode = generateVersionDependentFlagCode( + $template, + $this->getFlagsByPhpVersion(), + $this->phpVersionIdMinimumCompatibility + ); + $code .= implode("", $flagsCode); + } + + if ($this->type && !$php83MinimumCompatibility) { + $code .= "#endif\n"; + } + + $code .= "\tzend_string_release(const_{$constName}_name);\n"; + + return $code; + } + + private function getValueAssertion(EvaluatedValue $value): string + { + if ($value->isUnknownConstValue || $value->originatingConsts || $this->cValue === null) { + return ""; + } + + $cExpr = $value->getCExpr(); + $constValue = $value->value; + + if ($value->type->isNull()) { + return "\tZEND_ASSERT($cExpr == NULL);\n"; + } + + if ($value->type->isBool()) { + $cValue = $constValue ? "true" : "false"; + return "\tZEND_ASSERT($cExpr == $cValue);\n"; + } + + if ($value->type->isInt()) { + $cValue = (int) $constValue; + return "\tZEND_ASSERT($cExpr == $cValue);\n"; + } + + if ($value->type->isFloat()) { + $cValue = (float) $constValue; + return "\tZEND_ASSERT($cExpr == $cValue);\n"; + } + + if ($value->type->isString()) { + $cValue = '"' . addslashes($constValue) . '"'; + return "\tZEND_ASSERT(strcmp($cExpr, $cValue) == 0);\n"; + } + + throw new Exception("Unimplemented constant type"); + } + + /** + * @return array + */ + protected function getFlagsByPhpVersion(): array + { + $flags = parent::getFlagsByPhpVersion(); + + if ($this->isDeprecated) { + $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_DEPRECATED", PHP_80_VERSION_ID); + } + + if ($this->flags & Class_::MODIFIER_FINAL) { + $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_FINAL", PHP_81_VERSION_ID); + } + + return $flags; + } + + protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fieldsynopsisElement): void + { + parent::addModifiersToFieldSynopsis($doc, $fieldsynopsisElement); + + if ($this->flags & Class_::MODIFIER_FINAL) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "final")); + } + + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "const")); + } +} + +class PropertyInfo extends VariableLike +{ + public PropertyName $name; + public ?Expr $defaultValue; + public ?string $defaultValueString; + public bool $isDocReadonly; + + public function __construct( + PropertyName $name, + int $flags, + ?Type $type, + ?Type $phpDocType, + ?Expr $defaultValue, + ?string $defaultValueString, + bool $isDocReadonly, + ?string $link, + ?int $phpVersionIdMinimumCompatibility + ) { + $this->name = $name; + $this->defaultValue = $defaultValue; + $this->defaultValueString = $defaultValueString; + $this->isDocReadonly = $isDocReadonly; + parent::__construct($flags, $type, $phpDocType, $link, $phpVersionIdMinimumCompatibility); + } + + protected function getVariableTypeCode(): string + { + return "property"; + } + + protected function getVariableTypeName(): string + { + return "property"; + } + + protected function getVariableLikeName(): string + { + return $this->name->property; + } + + protected function getFieldSynopsisDefaultLinkend(): string + { + $className = str_replace(["\\", "_"], ["-", "-"], $this->name->class->toLowerString()); + + return "$className.props." . strtolower(str_replace("_", "-", $this->getVariableLikeName())); + } + + protected function getFieldSynopsisName(): string + { + return $this->getVariableLikeName(); + } + + /** + * @param iterable $allConstInfos + */ + protected function getFieldSynopsisValueString(iterable $allConstInfos): ?string + { + return $this->defaultValueString; + } + + public function discardInfoForOldPhpVersions(): void { + $this->type = null; + $this->flags &= ~Class_::MODIFIER_READONLY; + } + + /** + * @param iterable $allConstInfos + */ + public function getDeclaration(iterable $allConstInfos): string { + $code = "\n"; + + $propertyName = $this->getVariableLikeName(); + + if ($this->defaultValue === null) { + $defaultValue = EvaluatedValue::null(); + } else { + $defaultValue = EvaluatedValue::createFromExpression($this->defaultValue, null, null, $allConstInfos); + if ($defaultValue->isUnknownConstValue || ($defaultValue->originatingConsts && $defaultValue->getCExpr() === null)) { + echo "Skipping code generation for property $this->name, because it has an unknown constant default value\n"; + return ""; + } + } + + $zvalName = "property_{$propertyName}_default_value"; + if ($this->defaultValue === null && $this->type !== null) { + $code .= "\tzval $zvalName;\n\tZVAL_UNDEF(&$zvalName);\n"; + } else { + $code .= $defaultValue->initializeZval($zvalName); + } + + $code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n"; + $nameCode = "property_{$propertyName}_name"; + + if ($this->type !== null) { + $typeCode = $this->getTypeCode($propertyName, $code); + $template = "\tzend_declare_typed_property(class_entry, $nameCode, &$zvalName, %s, NULL, $typeCode);\n"; + } else { + $template = "\tzend_declare_property_ex(class_entry, $nameCode, &$zvalName, %s, NULL);\n"; + } + $flagsCode = generateVersionDependentFlagCode( + $template, + $this->getFlagsByPhpVersion(), + $this->phpVersionIdMinimumCompatibility + ); + $code .= implode("", $flagsCode); + + $code .= "\tzend_string_release(property_{$propertyName}_name);\n"; + + return $code; + } + + /** + * @return array + */ + protected function getFlagsByPhpVersion(): array + { + $flags = parent::getFlagsByPhpVersion(); + + if ($this->flags & Class_::MODIFIER_STATIC) { + $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_STATIC", PHP_70_VERSION_ID); + } + + if ($this->flags & Class_::MODIFIER_READONLY) { + $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_READONLY", PHP_81_VERSION_ID); + } + + return $flags; + } + + protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fieldsynopsisElement): void + { + parent::addModifiersToFieldSynopsis($doc, $fieldsynopsisElement); + + if ($this->flags & Class_::MODIFIER_STATIC) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "static")); + } + + if ($this->flags & Class_::MODIFIER_READONLY || $this->isDocReadonly) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "readonly")); + } + } + + public function __clone() + { + if ($this->type) { + $this->type = clone $this->type; + } + } +} + +class EnumCaseInfo { + public string $name; + public ?Expr $value; + + public function __construct(string $name, ?Expr $value) { + $this->name = $name; + $this->value = $value; + } + + /** + * @param iterable $allConstInfos + */ + public function getDeclaration(iterable $allConstInfos): string { + $escapedName = addslashes($this->name); + if ($this->value === null) { + $code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n"; + } else { + $value = EvaluatedValue::createFromExpression($this->value, null, null, $allConstInfos); + + $zvalName = "enum_case_{$escapedName}_value"; + $code = "\n" . $value->initializeZval($zvalName); + $code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n"; + } + + return $code; + } +} + +class AttributeInfo { + public string $class; + /** @var \PhpParser\Node\Arg[] */ + public array $args; + + /** @param \PhpParser\Node\Arg[] $args */ + public function __construct(string $class, array $args) { + $this->class = $class; + $this->args = $args; + } + + /** @param iterable $allConstInfos */ + public function generateCode(string $invocation, string $nameSuffix, iterable $allConstInfos, ?int $phpVersionIdMinimumCompatibility): string { + $php82MinimumCompatibility = $phpVersionIdMinimumCompatibility === null || $phpVersionIdMinimumCompatibility >= PHP_82_VERSION_ID; + /* see ZEND_KNOWN_STRINGS in Zend/strings.h */ + $knowns = []; + if ($php82MinimumCompatibility) { + $knowns["SensitiveParameter"] = "ZEND_STR_SENSITIVEPARAMETER"; + } + + $code = "\n"; + $escapedAttributeName = strtr($this->class, '\\', '_'); + if (isset($knowns[$escapedAttributeName])) { + $code .= "\t" . ($this->args ? "zend_attribute *attribute_{$escapedAttributeName}_$nameSuffix = " : "") . "$invocation, ZSTR_KNOWN({$knowns[$escapedAttributeName]}), " . count($this->args) . ");\n"; + } else { + $code .= "\tzend_string *attribute_name_{$escapedAttributeName}_$nameSuffix = zend_string_init_interned(\"" . addcslashes($this->class, "\\") . "\", sizeof(\"" . addcslashes($this->class, "\\") . "\") - 1, 1);\n"; + $code .= "\t" . ($this->args ? "zend_attribute *attribute_{$escapedAttributeName}_$nameSuffix = " : "") . "$invocation, attribute_name_{$escapedAttributeName}_$nameSuffix, " . count($this->args) . ");\n"; + $code .= "\tzend_string_release(attribute_name_{$escapedAttributeName}_$nameSuffix);\n"; + } + foreach ($this->args as $i => $arg) { + $value = EvaluatedValue::createFromExpression($arg->value, null, null, $allConstInfos); + $zvalName = "attribute_{$escapedAttributeName}_{$nameSuffix}_arg$i"; + $code .= $value->initializeZval($zvalName); + $code .= "\tZVAL_COPY_VALUE(&attribute_{$escapedAttributeName}_{$nameSuffix}->args[$i].value, &$zvalName);\n"; + if ($arg->name) { + $code .= "\tattribute_{$escapedAttributeName}_{$nameSuffix}->args[$i].name = zend_string_init(\"{$arg->name->name}\", sizeof(\"{$arg->name->name}\") - 1, 1);\n"; + } + } + return $code; + } +} + +class ClassInfo { + public Name $name; + public int $flags; + public string $type; + public ?string $alias; + public ?SimpleType $enumBackingType; + public bool $isDeprecated; + public bool $isStrictProperties; + /** @var AttributeInfo[] */ + public array $attributes; + public bool $isNotSerializable; + /** @var Name[] */ + public array $extends; + /** @var Name[] */ + public array $implements; + /** @var ConstInfo[] */ + public array $constInfos; + /** @var PropertyInfo[] */ + public array $propertyInfos; + /** @var FuncInfo[] */ + public array $funcInfos; + /** @var EnumCaseInfo[] */ + public array $enumCaseInfos; + public ?string $cond; + public ?int $phpVersionIdMinimumCompatibility; + public bool $isUndocumentable; + + /** + * @param AttributeInfo[] $attributes + * @param Name[] $extends + * @param Name[] $implements + * @param ConstInfo[] $constInfos + * @param PropertyInfo[] $propertyInfos + * @param FuncInfo[] $funcInfos + * @param EnumCaseInfo[] $enumCaseInfos + */ + public function __construct( + Name $name, + int $flags, + string $type, + ?string $alias, + ?SimpleType $enumBackingType, + bool $isDeprecated, + bool $isStrictProperties, + array $attributes, + bool $isNotSerializable, + array $extends, + array $implements, + array $constInfos, + array $propertyInfos, + array $funcInfos, + array $enumCaseInfos, + ?string $cond, + ?int $minimumPhpVersionIdCompatibility, + bool $isUndocumentable + ) { + $this->name = $name; + $this->flags = $flags; + $this->type = $type; + $this->alias = $alias; + $this->enumBackingType = $enumBackingType; + $this->isDeprecated = $isDeprecated; + $this->isStrictProperties = $isStrictProperties; + $this->attributes = $attributes; + $this->isNotSerializable = $isNotSerializable; + $this->extends = $extends; + $this->implements = $implements; + $this->constInfos = $constInfos; + $this->propertyInfos = $propertyInfos; + $this->funcInfos = $funcInfos; + $this->enumCaseInfos = $enumCaseInfos; + $this->cond = $cond; + $this->phpVersionIdMinimumCompatibility = $minimumPhpVersionIdCompatibility; + $this->isUndocumentable = $isUndocumentable; + } + + /** + * @param ConstInfo[] $allConstInfos + */ + public function getRegistration(iterable $allConstInfos): string + { + $params = []; + foreach ($this->extends as $extends) { + $params[] = "zend_class_entry *class_entry_" . implode("_", $extends->getParts()); + } + foreach ($this->implements as $implements) { + $params[] = "zend_class_entry *class_entry_" . implode("_", $implements->getParts()); + } + + $escapedName = implode("_", $this->name->getParts()); + + $code = ''; + + $php80MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_80_VERSION_ID; + $php81MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_81_VERSION_ID; + + if ($this->type === "enum" && !$php81MinimumCompatibility) { + $code .= "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; + } + + if ($this->cond) { + $code .= "#if {$this->cond}\n"; + } + + $code .= "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n"; + + $code .= "{\n"; + if ($this->type === "enum") { + $name = addslashes((string) $this->name); + $backingType = $this->enumBackingType + ? $this->enumBackingType->toTypeCode() : "IS_UNDEF"; + $code .= "\tzend_class_entry *class_entry = zend_register_internal_enum(\"$name\", $backingType, class_{$escapedName}_methods);\n"; + } else { + $code .= "\tzend_class_entry ce, *class_entry;\n\n"; + if (count($this->name->getParts()) > 1) { + $className = $this->name->getLast(); + $namespace = addslashes((string) $this->name->slice(0, -1)); + + $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n"; + } else { + $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n"; + } + + if ($this->type === "class" || $this->type === "trait") { + $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n"; + } else { + $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n"; + } + } + + $flagCodes = generateVersionDependentFlagCode("\tclass_entry->ce_flags |= %s;\n", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); + $code .= implode("", $flagCodes); + + $implements = array_map( + function (Name $item) { + return "class_entry_" . implode("_", $item->getParts()); + }, + $this->type === "interface" ? $this->extends : $this->implements + ); + + if (!empty($implements)) { + $code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n"; + } + + if ($this->alias) { + $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n"; + } + + foreach ($this->constInfos as $const) { + $code .= $const->getDeclaration($allConstInfos); + } + + foreach ($this->enumCaseInfos as $enumCase) { + $code .= $enumCase->getDeclaration($allConstInfos); + } + + foreach ($this->propertyInfos as $property) { + $code .= $property->getDeclaration($allConstInfos); + } + + if (!empty($this->attributes)) { + if (!$php80MinimumCompatibility) { + $code .= "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")"; + } + + foreach ($this->attributes as $attribute) { + $code .= $attribute->generateCode("zend_add_class_attribute(class_entry", "class_$escapedName", $allConstInfos, $this->phpVersionIdMinimumCompatibility); + } + + if (!$php80MinimumCompatibility) { + $code .= "#endif\n"; + } + } + + if ($attributeInitializationCode = generateAttributeInitialization($this->funcInfos, $allConstInfos, $this->phpVersionIdMinimumCompatibility, $this->cond)) { + if (!$php80MinimumCompatibility) { + $code .= "#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")\n"; + } + + $code .= "\n" . $attributeInitializationCode; + + if (!$php80MinimumCompatibility) { + $code .= "#endif\n"; + } + } + + $code .= "\n\treturn class_entry;\n"; + + $code .= "}\n"; + + if ($this->cond) { + $code .= "#endif\n"; + } + + if ($this->type === "enum" && !$php81MinimumCompatibility) { + $code .= "#endif\n"; + } + + return $code; + } + + /** + * @return array + */ + private function getFlagsByPhpVersion(): array + { + $php70Flags = []; + + if ($this->type === "trait") { + $php70Flags[] = "ZEND_ACC_TRAIT"; + } + + if ($this->flags & Class_::MODIFIER_FINAL) { + $php70Flags[] = "ZEND_ACC_FINAL"; + } + + if ($this->flags & Class_::MODIFIER_ABSTRACT) { + $php70Flags[] = "ZEND_ACC_ABSTRACT"; + } + + if ($this->isDeprecated) { + $php70Flags[] = "ZEND_ACC_DEPRECATED"; + } + + $php80Flags = $php70Flags; + + if ($this->isStrictProperties) { + $php80Flags[] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES"; + } + + $php81Flags = $php80Flags; + + if ($this->isNotSerializable) { + $php81Flags[] = "ZEND_ACC_NOT_SERIALIZABLE"; + } + + $php82Flags = $php81Flags; + + if ($this->flags & Class_::MODIFIER_READONLY) { + $php82Flags[] = "ZEND_ACC_READONLY_CLASS"; + } + + foreach ($this->attributes as $attr) { + if ($attr->class === "AllowDynamicProperties") { + $php82Flags[] = "ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES"; + break; + } + } + + $php83Flags = $php82Flags; + + return [ + PHP_70_VERSION_ID => $php70Flags, + PHP_80_VERSION_ID => $php80Flags, + PHP_81_VERSION_ID => $php81Flags, + PHP_82_VERSION_ID => $php82Flags, + PHP_83_VERSION_ID => $php83Flags, + ]; + } + + /** + * @param array $classMap + * @param iterable $allConstInfos + * @param iterable $allConstInfo + */ + public function getClassSynopsisDocument(array $classMap, iterable $allConstInfos): ?string { + + $doc = new DOMDocument(); + $doc->formatOutput = true; + $classSynopsis = $this->getClassSynopsisElement($doc, $classMap, $allConstInfos); + if (!$classSynopsis) { + return null; + } + + $doc->appendChild($classSynopsis); + + return $doc->saveXML(); + } + + /** + * @param array $classMap + * @param iterable $allConstInfos + */ + public function getClassSynopsisElement(DOMDocument $doc, array $classMap, iterable $allConstInfos): ?DOMElement { + + $classSynopsis = $doc->createElement("classsynopsis"); + $classSynopsis->setAttribute("class", $this->type === "interface" ? "interface" : "class"); + + $exceptionOverride = $this->isException($classMap) ? "exception" : null; + $ooElement = self::createOoElement($doc, $this, $exceptionOverride, true, null, 4); + if (!$ooElement) { + return null; + } + $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild($ooElement); + + foreach ($this->extends as $k => $parent) { + $parentInfo = $classMap[$parent->toString()] ?? null; + if ($parentInfo === null) { + throw new Exception("Missing parent class " . $parent->toString()); + } + + $ooElement = self::createOoElement( + $doc, + $parentInfo, + null, + false, + $k === 0 ? "extends" : null, + 4 + ); + if (!$ooElement) { + return null; + } + + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsis->appendChild($ooElement); + } + + foreach ($this->implements as $k => $interface) { + $interfaceInfo = $classMap[$interface->toString()] ?? null; + if (!$interfaceInfo) { + throw new Exception("Missing implemented interface " . $interface->toString()); + } + + $ooElement = self::createOoElement($doc, $interfaceInfo, null, false, $k === 0 ? "implements" : null, 4); + if (!$ooElement) { + return null; + } + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsis->appendChild($ooElement); + } + + /** @var array $parentsWithInheritedConstants */ + $parentsWithInheritedConstants = []; + /** @var array $parentsWithInheritedProperties */ + $parentsWithInheritedProperties = []; + /** @var array $parentsWithInheritedMethods */ + $parentsWithInheritedMethods = []; + + $this->collectInheritedMembers( + $parentsWithInheritedConstants, + $parentsWithInheritedProperties, + $parentsWithInheritedMethods, + $this->hasConstructor(), + $classMap + ); + + $this->appendInheritedMemberSectionToClassSynopsis( + $doc, + $classSynopsis, + $parentsWithInheritedConstants, + "&Constants;", + "&InheritedConstants;" + ); + + if (!empty($this->constInfos)) { + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Constants;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + + foreach ($this->constInfos as $constInfo) { + $classSynopsis->appendChild(new DOMText("\n ")); + $fieldSynopsisElement = $constInfo->getFieldSynopsisElement($doc, $allConstInfos); + $classSynopsis->appendChild($fieldSynopsisElement); + } + } + + if (!empty($this->propertyInfos)) { + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + + foreach ($this->propertyInfos as $propertyInfo) { + $classSynopsis->appendChild(new DOMText("\n ")); + $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc, $allConstInfos); + $classSynopsis->appendChild($fieldSynopsisElement); + } + } + + $this->appendInheritedMemberSectionToClassSynopsis( + $doc, + $classSynopsis, + $parentsWithInheritedProperties, + "&Properties;", + "&InheritedProperties;" + ); + + if (!empty($this->funcInfos)) { + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Methods;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + + $classReference = self::getClassSynopsisReference($this->name); + $escapedName = addslashes($this->name->__toString()); + + if ($this->hasConstructor()) { + $classSynopsis->appendChild(new DOMText("\n ")); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[@role='$escapedName'])" + ); + $classSynopsis->appendChild($includeElement); + } + + if ($this->hasMethods()) { + $classSynopsis->appendChild(new DOMText("\n ")); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[@role='$escapedName'])" + ); + $classSynopsis->appendChild($includeElement); + } + + if ($this->hasDestructor()) { + $classSynopsis->appendChild(new DOMText("\n ")); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[@role='$escapedName'])" + ); + $classSynopsis->appendChild($includeElement); + } + } + + if (!empty($parentsWithInheritedMethods)) { + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedMethods;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + + foreach ($parentsWithInheritedMethods as $parent) { + $parentName = $parent["name"]; + $parentMethodsynopsisTypes = $parent["types"]; + + $parentReference = self::getClassSynopsisReference($parentName); + $escapedParentName = addslashes($parentName->__toString()); + + foreach ($parentMethodsynopsisTypes as $parentMethodsynopsisType) { + $classSynopsis->appendChild(new DOMText("\n ")); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:refentry/db:refsect1[@role='description']/descendant::db:{$parentMethodsynopsisType}[@role='$escapedParentName'])" + ); + + $classSynopsis->appendChild($includeElement); + } + } + } + + $classSynopsis->appendChild(new DOMText("\n ")); + + return $classSynopsis; + } + + private static function createOoElement( + DOMDocument $doc, + ClassInfo $classInfo, + ?string $typeOverride, + bool $withModifiers, + ?string $modifierOverride, + int $indentationLevel + ): ?DOMElement { + $indentation = str_repeat(" ", $indentationLevel); + + if ($classInfo->type !== "class" && $classInfo->type !== "interface") { + echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n"; + return null; + } + + $type = $typeOverride !== null ? $typeOverride : $classInfo->type; + + $ooElement = $doc->createElement("oo$type"); + $ooElement->appendChild(new DOMText("\n$indentation ")); + if ($modifierOverride !== null) { + $ooElement->appendChild($doc->createElement('modifier', $modifierOverride)); + $ooElement->appendChild(new DOMText("\n$indentation ")); + } elseif ($withModifiers) { + if ($classInfo->flags & Class_::MODIFIER_FINAL) { + $ooElement->appendChild($doc->createElement('modifier', 'final')); + $ooElement->appendChild(new DOMText("\n$indentation ")); + } + if ($classInfo->flags & Class_::MODIFIER_ABSTRACT) { + $ooElement->appendChild($doc->createElement('modifier', 'abstract')); + $ooElement->appendChild(new DOMText("\n$indentation ")); + } + if ($classInfo->flags & Class_::MODIFIER_READONLY) { + $ooElement->appendChild($doc->createElement('modifier', 'readonly')); + $ooElement->appendChild(new DOMText("\n$indentation ")); + } + } + + $nameElement = $doc->createElement("{$type}name", $classInfo->name->toString()); + $ooElement->appendChild($nameElement); + $ooElement->appendChild(new DOMText("\n$indentation")); + + return $ooElement; + } + + public static function getClassSynopsisFilename(Name $name): string { + return strtolower(str_replace("_", "-", implode('-', $name->getParts()))); + } + + public static function getClassSynopsisReference(Name $name): string { + return "class." . self::getClassSynopsisFilename($name); + } + + /** + * @param array $parentsWithInheritedConstants + * @param array $parentsWithInheritedProperties + * @param array $parentsWithInheritedMethods + * @param array $classMap + */ + private function collectInheritedMembers( + array &$parentsWithInheritedConstants, + array &$parentsWithInheritedProperties, + array &$parentsWithInheritedMethods, + bool $hasConstructor, + array $classMap + ): void { + foreach ($this->extends as $parent) { + $parentInfo = $classMap[$parent->toString()] ?? null; + $parentName = $parent->toString(); + + if (!$parentInfo) { + throw new Exception("Missing parent class $parentName"); + } + + if (!empty($parentInfo->constInfos) && !isset($parentsWithInheritedConstants[$parentName])) { + $parentsWithInheritedConstants[] = $parent; + } + + if (!empty($parentInfo->propertyInfos) && !isset($parentsWithInheritedProperties[$parentName])) { + $parentsWithInheritedProperties[$parentName] = $parent; + } + + if (!$hasConstructor && $parentInfo->hasNonPrivateConstructor()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "constructorsynopsis"; + } + + if ($parentInfo->hasMethods()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "methodsynopsis"; + } + + if ($parentInfo->hasDestructor()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "destructorsynopsis"; + } + + $parentInfo->collectInheritedMembers( + $parentsWithInheritedConstants, + $parentsWithInheritedProperties, + $parentsWithInheritedMethods, + $hasConstructor, + $classMap + ); + } + + foreach ($this->implements as $parent) { + $parentInfo = $classMap[$parent->toString()] ?? null; + if (!$parentInfo) { + throw new Exception("Missing parent interface " . $parent->toString()); + } + + if (!empty($parentInfo->constInfos) && !isset($parentsWithInheritedConstants[$parent->toString()])) { + $parentsWithInheritedConstants[$parent->toString()] = $parent; + } + + $unusedParentsWithInheritedProperties = []; + $unusedParentsWithInheritedMethods = []; + + $parentInfo->collectInheritedMembers( + $parentsWithInheritedConstants, + $unusedParentsWithInheritedProperties, + $unusedParentsWithInheritedMethods, + $hasConstructor, + $classMap + ); + } + } + + /** @param array $classMap */ + private function isException(array $classMap): bool + { + if ($this->name->toString() === "Throwable") { + return true; + } + + foreach ($this->extends as $parentName) { + $parent = $classMap[$parentName->toString()] ?? null; + if ($parent === null) { + throw new Exception("Missing parent class " . $parentName->toString()); + } + + if ($parent->isException($classMap)) { + return true; + } + } + + if ($this->type === "class") { + foreach ($this->implements as $interfaceName) { + $interface = $classMap[$interfaceName->toString()] ?? null; + if ($interface === null) { + throw new Exception("Missing implemented interface " . $interfaceName->toString()); + } + + if ($interface->isException($classMap)) { + return true; + } + } + } + + return false; + } + + private function hasConstructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isConstructor()) { + return true; + } + } + + return false; + } + + private function hasNonPrivateConstructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isConstructor() && !($funcInfo->flags & Class_::MODIFIER_PRIVATE)) { + return true; + } + } + + return false; + } + + private function hasDestructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isDestructor()) { + return true; + } + } + + return false; + } + + private function hasMethods(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if (!$funcInfo->name->isConstructor() && !$funcInfo->name->isDestructor()) { + return true; + } + } + + return false; + } + + private function createIncludeElement(DOMDocument $doc, string $query): DOMElement + { + $includeElement = $doc->createElement("xi:include"); + $attr = $doc->createAttribute("xpointer"); + $attr->value = $query; + $includeElement->appendChild($attr); + $fallbackElement = $doc->createElement("xi:fallback"); + $includeElement->appendChild(new DOMText("\n ")); + $includeElement->appendChild($fallbackElement); + $includeElement->appendChild(new DOMText("\n ")); + + return $includeElement; + } + + public function __clone() + { + foreach ($this->propertyInfos as $key => $propertyInfo) { + $this->propertyInfos[$key] = clone $propertyInfo; + } + + foreach ($this->funcInfos as $key => $funcInfo) { + $this->funcInfos[$key] = clone $funcInfo; + } + } + + /** + * @param Name[] $parents + */ + private function appendInheritedMemberSectionToClassSynopsis(DOMDocument $doc, DOMElement $classSynopsis, array $parents, string $label, string $inheritedLabel): void + { + if (empty($parents)) { + return; + } + + $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "$inheritedLabel"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + + foreach ($parents as $parent) { + $classSynopsis->appendChild(new DOMText("\n ")); + $parentReference = self::getClassSynopsisReference($parent); + + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:partintro/db:section/db:classsynopsis/db:fieldsynopsis[preceding-sibling::db:classsynopsisinfo[1][@role='comment' and text()='$label']]))" + ); + $classSynopsis->appendChild($includeElement); + } + } +} + +class FileInfo { + /** @var string[] */ + public array $dependencies = []; + /** @var ConstInfo[] */ + public array $constInfos = []; + /** @var FuncInfo[] */ + public array $funcInfos = []; + /** @var ClassInfo[] */ + public array $classInfos = []; + public bool $generateFunctionEntries = false; + public string $declarationPrefix = ""; + public ?int $generateLegacyArginfoForPhpVersionId = null; + public bool $generateClassEntries = false; + public bool $isUndocumentable = false; + + /** + * @return iterable + */ + public function getAllFuncInfos(): iterable { + yield from $this->funcInfos; + foreach ($this->classInfos as $classInfo) { + yield from $classInfo->funcInfos; + } + } + + /** + * @return iterable + */ + public function getAllConstInfos(): iterable { + $result = $this->constInfos; + + foreach ($this->classInfos as $classInfo) { + $result = array_merge($result, $classInfo->constInfos); + } + + return $result; + } + + /** + * @return iterable + */ + public function getAllPropertyInfos(): iterable { + foreach ($this->classInfos as $classInfo) { + yield from $classInfo->propertyInfos; + } + } + + public function __clone() + { + foreach ($this->funcInfos as $key => $funcInfo) { + $this->funcInfos[$key] = clone $funcInfo; + } + + foreach ($this->classInfos as $key => $classInfo) { + $this->classInfos[$key] = clone $classInfo; + } + } +} + +class DocCommentTag { + public string $name; + public ?string $value; + + public function __construct(string $name, ?string $value) { + $this->name = $name; + $this->value = $value; + } + + public function getValue(): string { + if ($this->value === null) { + throw new Exception("@$this->name does not have a value"); + } + + return $this->value; + } + + public function getType(): string { + $value = $this->getValue(); + + $matches = []; + + if ($this->name === "param") { + preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)\s*(?:[{(]|\$\w+).*$/', $value, $matches); + } elseif ($this->name === "return" || $this->name === "var") { + preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)/', $value, $matches); + } + + if (!isset($matches[1])) { + throw new Exception("@$this->name doesn't contain a type or has an invalid format \"$value\""); + } + + return trim($matches[1]); + } + + public function getVariableName(): string { + $value = $this->value; + if ($value === null || strlen($value) === 0) { + throw new Exception("@$this->name doesn't have any value"); + } + + $matches = []; + + if ($this->name === "param") { + // Allow for parsing extended types like callable(string):mixed in docblocks + preg_match('/^\s*(?[\w\|\\\\]+(?\((?(?:(?&parens)|[^(){}[\]]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\])*+(?::(?&type))?)\s*\$(?\w+).*$/', $value, $matches); + } elseif ($this->name === "prefer-ref") { + preg_match('/^\s*\$(?\w+).*$/', $value, $matches); + } + + if (!isset($matches["name"])) { + throw new Exception("@$this->name doesn't contain a variable name or has an invalid format \"$value\""); + } + + return $matches["name"]; + } +} + +/** @return DocCommentTag[] */ +function parseDocComment(DocComment $comment): array { + $commentText = substr($comment->getText(), 2, -2); + $tags = []; + foreach (explode("\n", $commentText) as $commentLine) { + $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/'; + if (preg_match($regex, trim($commentLine), $matches)) { + $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null); + } + } + + return $tags; +} + +function parseFunctionLike( + PrettyPrinterAbstract $prettyPrinter, + FunctionOrMethodName $name, + int $classFlags, + int $flags, + Node\FunctionLike $func, + ?string $cond, + bool $isUndocumentable +): FuncInfo { + try { + $comment = $func->getDocComment(); + $paramMeta = []; + $aliasType = null; + $alias = null; + $isDeprecated = false; + $supportsCompileTimeEval = false; + $verify = true; + $docReturnType = null; + $tentativeReturnType = false; + $docParamTypes = []; + $refcount = null; + + if ($comment) { + $tags = parseDocComment($comment); + foreach ($tags as $tag) { + switch ($tag->name) { + case 'alias': + case 'implementation-alias': + $aliasType = $tag->name; + $aliasParts = explode("::", $tag->getValue()); + if (count($aliasParts) === 1) { + $alias = new FunctionName(new Name($aliasParts[0])); + } else { + $alias = new MethodName(new Name($aliasParts[0]), $aliasParts[1]); + } + break; + + case 'deprecated': + $isDeprecated = true; + break; + + case 'no-verify': + $verify = false; + break; + + case 'tentative-return-type': + $tentativeReturnType = true; + break; + + case 'return': + $docReturnType = $tag->getType(); + break; + + case 'param': + $docParamTypes[$tag->getVariableName()] = $tag->getType(); + break; + + case 'refcount': + $refcount = $tag->getValue(); + break; + + case 'compile-time-eval': + $supportsCompileTimeEval = true; + break; + + case 'prefer-ref': + $varName = $tag->getVariableName(); + if (!isset($paramMeta[$varName])) { + $paramMeta[$varName] = []; + } + $paramMeta[$varName][$tag->name] = true; + break; + + case 'undocumentable': + $isUndocumentable = true; + break; + } + } + } + + $varNameSet = []; + $args = []; + $numRequiredArgs = 0; + $foundVariadic = false; + foreach ($func->getParams() as $i => $param) { + $varName = $param->var->name; + $preferRef = !empty($paramMeta[$varName]['prefer-ref']); + $attributes = []; + foreach ($param->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attributes[] = new AttributeInfo($attr->name->toString(), $attr->args); + } + } + unset($paramMeta[$varName]); + + if (isset($varNameSet[$varName])) { + throw new Exception("Duplicate parameter name $varName"); + } + $varNameSet[$varName] = true; + + if ($preferRef) { + $sendBy = ArgInfo::SEND_PREFER_REF; + } else if ($param->byRef) { + $sendBy = ArgInfo::SEND_BY_REF; + } else { + $sendBy = ArgInfo::SEND_BY_VAL; + } + + if ($foundVariadic) { + throw new Exception("Only the last parameter can be variadic"); + } + + $type = $param->type ? Type::fromNode($param->type) : null; + if ($type === null && !isset($docParamTypes[$varName])) { + throw new Exception("Missing parameter type"); + } + + if ($param->default instanceof Expr\ConstFetch && + $param->default->name->toLowerString() === "null" && + $type && !$type->isNullable() + ) { + $simpleType = $type->tryToSimpleType(); + if ($simpleType === null) { + throw new Exception("Parameter $varName has null default, but is not nullable"); + } + } + + if ($param->default instanceof Expr\ClassConstFetch && $param->default->class->toLowerString() === "self") { + throw new Exception('The exact class name must be used instead of "self"'); + } + + $foundVariadic = $param->variadic; + + $args[] = new ArgInfo( + $varName, + $sendBy, + $param->variadic, + $type, + isset($docParamTypes[$varName]) ? Type::fromString($docParamTypes[$varName]) : null, + $param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null, + $attributes + ); + if (!$param->default && !$param->variadic) { + $numRequiredArgs = $i + 1; + } + } + + foreach (array_keys($paramMeta) as $var) { + throw new Exception("Found metadata for invalid param $var"); + } + + $returnType = $func->getReturnType(); + if ($returnType === null && $docReturnType === null && !$name->isConstructor() && !$name->isDestructor()) { + throw new Exception("Missing return type"); + } + + $return = new ReturnInfo( + $func->returnsByRef(), + $returnType ? Type::fromNode($returnType) : null, + $docReturnType ? Type::fromString($docReturnType) : null, + $tentativeReturnType, + $refcount + ); + + return new FuncInfo( + $name, + $classFlags, + $flags, + $aliasType, + $alias, + $isDeprecated, + $supportsCompileTimeEval, + $verify, + $args, + $return, + $numRequiredArgs, + $cond, + $isUndocumentable + ); + } catch (Exception $e) { + throw new Exception($name . "(): " .$e->getMessage()); + } +} + +function parseConstLike( + PrettyPrinterAbstract $prettyPrinter, + ConstOrClassConstName $name, + Node\Const_ $const, + int $flags, + ?Node $type, + ?DocComment $docComment, + ?string $cond, + ?int $phpVersionIdMinimumCompatibility +): ConstInfo { + $phpDocType = null; + $deprecated = false; + $cValue = null; + $link = null; + if ($docComment) { + $tags = parseDocComment($docComment); + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); + } elseif ($tag->name === 'deprecated') { + $deprecated = true; + } elseif ($tag->name === 'cvalue') { + $cValue = $tag->value; + } elseif ($tag->name === 'link') { + $link = $tag->value; + } + } + } + + if ($type === null && $phpDocType === null) { + throw new Exception("Missing type for constant " . $name->__toString()); + } + + return new ConstInfo( + $name, + $flags, + $const->value, + $prettyPrinter->prettyPrintExpr($const->value), + $type ? Type::fromNode($type) : null, + $phpDocType ? Type::fromString($phpDocType) : null, + $deprecated, + $cond, + $cValue, + $link, + $phpVersionIdMinimumCompatibility + ); +} + +function parseProperty( + Name $class, + int $flags, + Stmt\PropertyProperty $property, + ?Node $type, + ?DocComment $comment, + PrettyPrinterAbstract $prettyPrinter, + ?int $phpVersionIdMinimumCompatibility +): PropertyInfo { + $phpDocType = null; + $isDocReadonly = false; + $link = null; + + if ($comment) { + $tags = parseDocComment($comment); + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); + } elseif ($tag->name === 'readonly') { + $isDocReadonly = true; + } elseif ($tag->name === 'link') { + $link = $tag->value; + } + } + } + + $propertyType = $type ? Type::fromNode($type) : null; + if ($propertyType === null && !$phpDocType) { + throw new Exception("Missing type for property $class::\$$property->name"); + } + + if ($property->default instanceof Expr\ConstFetch && + $property->default->name->toLowerString() === "null" && + $propertyType && !$propertyType->isNullable() + ) { + $simpleType = $propertyType->tryToSimpleType(); + if ($simpleType === null) { + throw new Exception( + "Property $class::\$$property->name has null default, but is not nullable"); + } + } + + return new PropertyInfo( + new PropertyName($class, $property->name->__toString()), + $flags, + $propertyType, + $phpDocType ? Type::fromString($phpDocType) : null, + $property->default, + $property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null, + $isDocReadonly, + $link, + $phpVersionIdMinimumCompatibility + ); +} + +/** + * @param ConstInfo[] $consts + * @param PropertyInfo[] $properties + * @param FuncInfo[] $methods + * @param EnumCaseInfo[] $enumCases + */ +function parseClass( + Name $name, + Stmt\ClassLike $class, + array $consts, + array $properties, + array $methods, + array $enumCases, + ?string $cond, + ?int $minimumPhpVersionIdCompatibility, + bool $isUndocumentable +): ClassInfo { + $flags = $class instanceof Class_ ? $class->flags : 0; + $comment = $class->getDocComment(); + $alias = null; + $isDeprecated = false; + $isStrictProperties = false; + $isNotSerializable = false; + $allowsDynamicProperties = false; + $attributes = []; + + if ($comment) { + $tags = parseDocComment($comment); + foreach ($tags as $tag) { + if ($tag->name === 'alias') { + $alias = $tag->getValue(); + } else if ($tag->name === 'deprecated') { + $isDeprecated = true; + } else if ($tag->name === 'strict-properties') { + $isStrictProperties = true; + } else if ($tag->name === 'not-serializable') { + $isNotSerializable = true; + } else if ($tag->name === 'undocumentable') { + $isUndocumentable = true; + } + } + } + + foreach ($class->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attributes[] = new AttributeInfo($attr->name->toString(), $attr->args); + switch ($attr->name->toString()) { + case 'AllowDynamicProperties': + $allowsDynamicProperties = true; + break; + } + } + } + + if ($isStrictProperties && $allowsDynamicProperties) { + throw new Exception("A class may not have '@strict-properties' and '#[\\AllowDynamicProperties]' at the same time."); + } + + $extends = []; + $implements = []; + + if ($class instanceof Class_) { + $classKind = "class"; + if ($class->extends) { + $extends[] = $class->extends; + } + $implements = $class->implements; + } elseif ($class instanceof Interface_) { + $classKind = "interface"; + $extends = $class->extends; + } else if ($class instanceof Trait_) { + $classKind = "trait"; + } else if ($class instanceof Enum_) { + $classKind = "enum"; + $implements = $class->implements; + } else { + throw new Exception("Unknown class kind " . get_class($class)); + } + + if ($isUndocumentable) { + foreach ($methods as $method) { + $method->isUndocumentable = true; + } + } + + return new ClassInfo( + $name, + $flags, + $classKind, + $alias, + $class instanceof Enum_ && $class->scalarType !== null + ? SimpleType::fromNode($class->scalarType) : null, + $isDeprecated, + $isStrictProperties, + $attributes, + $isNotSerializable, + $extends, + $implements, + $consts, + $properties, + $methods, + $enumCases, + $cond, + $minimumPhpVersionIdCompatibility, + $isUndocumentable + ); +} + +function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string { + foreach ($stmt->getComments() as $comment) { + $text = trim($comment->getText()); + if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { + $conds[] = $matches[1]; + } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { + $conds[] = "defined($matches[1])"; + } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { + $conds[] = "!defined($matches[1])"; + } else if (preg_match('/^#\s*else$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered else without corresponding #if"); + } + $cond = array_pop($conds); + $conds[] = "!($cond)"; + } else if (preg_match('/^#\s*endif$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered #endif without corresponding #if"); + } + array_pop($conds); + } else if ($text[0] === '#') { + throw new Exception("Unrecognized preprocessor directive \"$text\""); + } + } + + return empty($conds) ? null : implode(' && ', $conds); +} + +function getFileDocComment(array $stmts): ?DocComment { + if (empty($stmts)) { + return null; + } + + $comments = $stmts[0]->getComments(); + if (empty($comments)) { + return null; + } + + if ($comments[0] instanceof DocComment) { + return $comments[0]; + } + + return null; +} + +function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstract $prettyPrinter) { + $conds = []; + foreach ($stmts as $stmt) { + $cond = handlePreprocessorConditions($conds, $stmt); + + if ($stmt instanceof Stmt\Nop) { + continue; + } + + if ($stmt instanceof Stmt\Namespace_) { + handleStatements($fileInfo, $stmt->stmts, $prettyPrinter); + continue; + } + + if ($stmt instanceof Stmt\Const_) { + foreach ($stmt->consts as $const) { + $fileInfo->constInfos[] = parseConstLike( + $prettyPrinter, + new ConstName($const->namespacedName, $const->name->toString()), + $const, + 0, + null, + $stmt->getDocComment(), + $cond, + $fileInfo->generateLegacyArginfoForPhpVersionId + ); + } + continue; + } + + if ($stmt instanceof Stmt\Function_) { + $fileInfo->funcInfos[] = parseFunctionLike( + $prettyPrinter, + new FunctionName($stmt->namespacedName), + 0, + 0, + $stmt, + $cond, + $fileInfo->isUndocumentable + ); + continue; + } + + if ($stmt instanceof Stmt\ClassLike) { + $className = $stmt->namespacedName; + $constInfos = []; + $propertyInfos = []; + $methodInfos = []; + $enumCaseInfos = []; + foreach ($stmt->stmts as $classStmt) { + $cond = handlePreprocessorConditions($conds, $classStmt); + if ($classStmt instanceof Stmt\Nop) { + continue; + } + + $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; + $abstractFlag = $stmt instanceof Stmt\Interface_ ? Class_::MODIFIER_ABSTRACT : 0; + + if ($classStmt instanceof Stmt\ClassConst) { + foreach ($classStmt->consts as $const) { + $constInfos[] = parseConstLike( + $prettyPrinter, + new ClassConstName($className, $const->name->toString()), + $const, + $classStmt->flags, + $classStmt->type, + $classStmt->getDocComment(), + $cond, + $fileInfo->generateLegacyArginfoForPhpVersionId + ); + } + } else if ($classStmt instanceof Stmt\Property) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + foreach ($classStmt->props as $property) { + $propertyInfos[] = parseProperty( + $className, + $classStmt->flags, + $property, + $classStmt->type, + $classStmt->getDocComment(), + $prettyPrinter, + $fileInfo->generateLegacyArginfoForPhpVersionId + ); + } + } else if ($classStmt instanceof Stmt\ClassMethod) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + $methodInfos[] = parseFunctionLike( + $prettyPrinter, + new MethodName($className, $classStmt->name->toString()), + $classFlags, + $classStmt->flags | $abstractFlag, + $classStmt, + $cond, + $fileInfo->isUndocumentable + ); + } else if ($classStmt instanceof Stmt\EnumCase) { + $enumCaseInfos[] = new EnumCaseInfo( + $classStmt->name->toString(), $classStmt->expr); + } else { + throw new Exception("Not implemented {$classStmt->getType()}"); + } + } + + $fileInfo->classInfos[] = parseClass( + $className, $stmt, $constInfos, $propertyInfos, $methodInfos, $enumCaseInfos, $cond, $fileInfo->generateLegacyArginfoForPhpVersionId, $fileInfo->isUndocumentable + ); + continue; + } + + if ($stmt instanceof Stmt\Expression) { + $expr = $stmt->expr; + if ($expr instanceof Expr\Include_) { + $fileInfo->dependencies[] = (string)EvaluatedValue::createFromExpression($expr->expr, null, null, [])->value; + continue; + } + } + + throw new Exception("Unexpected node {$stmt->getType()}"); + } + if (!empty($conds)) { + throw new Exception("Unterminated preprocessor conditions"); + } +} + +function parseStubFile(string $code): FileInfo { + $lexer = new PhpParser\Lexer\Emulative(); + $parser = new PhpParser\Parser\Php7($lexer); + $nodeTraverser = new PhpParser\NodeTraverser; + $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + $prettyPrinter = new class extends Standard { + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return implode('\\', $node->getParts()); + } + }; + + $stmts = $parser->parse($code); + $nodeTraverser->traverse($stmts); + + $fileInfo = new FileInfo; + $fileDocComment = getFileDocComment($stmts); + if ($fileDocComment) { + $fileTags = parseDocComment($fileDocComment); + foreach ($fileTags as $tag) { + if ($tag->name === 'generate-function-entries') { + $fileInfo->generateFunctionEntries = true; + $fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : ""; + } else if ($tag->name === 'generate-legacy-arginfo') { + if ($tag->value && !in_array((int) $tag->value, ALL_PHP_VERSION_IDS, true)) { + throw new Exception( + "Legacy PHP version must be one of: \"" . PHP_70_VERSION_ID . "\" (PHP 7.0), \"" . PHP_80_VERSION_ID . "\" (PHP 8.0), " . + "\"" . PHP_81_VERSION_ID . "\" (PHP 8.1), \"" . PHP_82_VERSION_ID . "\" (PHP 8.2), \"" . PHP_83_VERSION_ID . "\" (PHP 8.3), " . + "\"" . $tag->value . "\" provided" + ); + } + + $fileInfo->generateLegacyArginfoForPhpVersionId = $tag->value ? (int) $tag->value : PHP_70_VERSION_ID; + } else if ($tag->name === 'generate-class-entries') { + $fileInfo->generateClassEntries = true; + $fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : ""; + } else if ($tag->name === 'undocumentable') { + $fileInfo->isUndocumentable = true; + } + } + } + + // Generating class entries require generating function/method entries + if ($fileInfo->generateClassEntries && !$fileInfo->generateFunctionEntries) { + $fileInfo->generateFunctionEntries = true; + } + + handleStatements($fileInfo, $stmts, $prettyPrinter); + return $fileInfo; +} + +function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { + $code = ''; + $returnType = $funcInfo->return->type; + $isTentativeReturnType = $funcInfo->return->tentativeReturnType; + $php81MinimumCompatibility = $fileInfo->generateLegacyArginfoForPhpVersionId === null || $fileInfo->generateLegacyArginfoForPhpVersionId >= PHP_81_VERSION_ID; + + if ($returnType !== null) { + if ($isTentativeReturnType && !$php81MinimumCompatibility) { + $code .= "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; + } + if (null !== $simpleReturnType = $returnType->tryToSimpleType()) { + if ($simpleReturnType->isBuiltin) { + $code .= sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, + $funcInfo->numRequiredArgs, + $simpleReturnType->toTypeCode(), $returnType->isNullable() + ); + } else { + $code .= sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, + $funcInfo->numRequiredArgs, + $simpleReturnType->toEscapedName(), $returnType->isNullable() + ); + } + } else { + $arginfoType = $returnType->toArginfoType(); + if ($arginfoType->hasClassType()) { + $code .= sprintf( + "%s(%s, %d, %d, %s, %s)\n", + $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, + $funcInfo->numRequiredArgs, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask() + ); + } else { + $code .= sprintf( + "%s(%s, %d, %d, %s)\n", + $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, + $funcInfo->numRequiredArgs, + $arginfoType->toTypeMask() + ); + } + } + if ($isTentativeReturnType && !$php81MinimumCompatibility) { + $code .= sprintf( + "#else\nZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n#endif\n", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs + ); + } + } else { + $code .= sprintf( + "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n", + $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs + ); + } + + foreach ($funcInfo->args as $argInfo) { + $argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG"; + $argDefaultKind = $argInfo->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : ""; + $argType = $argInfo->type; + if ($argType !== null) { + if (null !== $simpleArgType = $argType->tryToSimpleType()) { + if ($simpleArgType->isBuiltin) { + $code .= sprintf( + "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n", + $argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name, + $simpleArgType->toTypeCode(), $argType->isNullable(), + $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" + ); + } else { + $code .= sprintf( + "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n", + $argKind,$argDefaultKind, $argInfo->getSendByString(), $argInfo->name, + $simpleArgType->toEscapedName(), $argType->isNullable(), + $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" + ); + } + } else { + $arginfoType = $argType->toArginfoType(); + if ($arginfoType->hasClassType()) { + $code .= sprintf( + "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s%s)\n", + $argKind, $argInfo->getSendByString(), $argInfo->name, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(), + !$argInfo->isVariadic ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" + ); + } else { + $code .= sprintf( + "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", + $argKind, $argInfo->getSendByString(), $argInfo->name, + $arginfoType->toTypeMask(), + $argInfo->getDefaultValueAsArginfoString() + ); + } + } + } else { + $code .= sprintf( + "\tZEND_%s_INFO%s(%s, %s%s)\n", + $argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name, + $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" + ); + } + } + + $code .= "ZEND_END_ARG_INFO()"; + return $code . "\n"; +} + +/** @param FuncInfo[] $generatedFuncInfos */ +function findEquivalentFuncInfo(array $generatedFuncInfos, FuncInfo $funcInfo): ?FuncInfo { + foreach ($generatedFuncInfos as $generatedFuncInfo) { + if ($generatedFuncInfo->equalsApartFromNameAndRefcount($funcInfo)) { + return $generatedFuncInfo; + } + } + return null; +} + +/** + * @template T + * @param iterable $infos + * @param Closure(T): string|null $codeGenerator + * @param ?string $parentCond + */ +function generateCodeWithConditions( + iterable $infos, string $separator, Closure $codeGenerator, ?string $parentCond = null): string { + $code = ""; + foreach ($infos as $info) { + $infoCode = $codeGenerator($info); + if ($infoCode === null) { + continue; + } + + $code .= $separator; + if ($info->cond && $info->cond !== $parentCond) { + $code .= "#if {$info->cond}\n"; + $code .= $infoCode; + $code .= "#endif\n"; + } else { + $code .= $infoCode; + } + } + + return $code; +} + +/** + * @param iterable $allConstInfos + */ +function generateArgInfoCode( + string $stubFilenameWithoutExtension, + FileInfo $fileInfo, + iterable $allConstInfos, + string $stubHash +): string { + $code = "/* This is a generated file, edit the .stub.php file instead.\n" + . " * Stub hash: $stubHash */\n"; + + $generatedFuncInfos = []; + $code .= generateCodeWithConditions( + $fileInfo->getAllFuncInfos(), "\n", + static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { + /* If there already is an equivalent arginfo structure, only emit a #define */ + if ($generatedFuncInfo = findEquivalentFuncInfo($generatedFuncInfos, $funcInfo)) { + $code = sprintf( + "#define %s %s\n", + $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName() + ); + } else { + $code = funcInfoToCode($fileInfo, $funcInfo); + } + + $generatedFuncInfos[] = $funcInfo; + return $code; + } + ); + + if ($fileInfo->generateFunctionEntries) { + $code .= "\n\n"; + + $generatedFunctionDeclarations = []; + $code .= generateCodeWithConditions( + $fileInfo->getAllFuncInfos(), "", + static function (FuncInfo $funcInfo) use ($fileInfo, &$generatedFunctionDeclarations) { + $key = $funcInfo->getDeclarationKey(); + if (isset($generatedFunctionDeclarations[$key])) { + return null; + } + + $generatedFunctionDeclarations[$key] = true; + return $fileInfo->declarationPrefix . $funcInfo->getDeclaration(); + } + ); + + if (!empty($fileInfo->funcInfos)) { + $code .= generateFunctionEntries(null, $fileInfo->funcInfos); + } + + foreach ($fileInfo->classInfos as $classInfo) { + $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); + } + } + + $php80MinimumCompatibility = $fileInfo->generateLegacyArginfoForPhpVersionId === null || $fileInfo->generateLegacyArginfoForPhpVersionId >= PHP_80_VERSION_ID; + + if ($fileInfo->generateClassEntries) { + if ($attributeInitializationCode = generateAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->generateLegacyArginfoForPhpVersionId, null)) { + if (!$php80MinimumCompatibility) { + $attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n"; + } + } + + if ($attributeInitializationCode !== "" || !empty($fileInfo->constInfos)) { + $code .= "\nstatic void register_{$stubFilenameWithoutExtension}_symbols(int module_number)\n"; + $code .= "{\n"; + + foreach ($fileInfo->constInfos as $constInfo) { + $code .= $constInfo->getDeclaration($allConstInfos); + } + + if (!empty($attributeInitializationCode !== "" && $fileInfo->constInfos)) { + $code .= "\n"; + } + + $code .= $attributeInitializationCode; + $code .= "}\n"; + } + + $code .= generateClassEntryCode($fileInfo, $allConstInfos); + } + + return $code; +} + +/** + * @param iterable $allConstInfos + */ +function generateClassEntryCode(FileInfo $fileInfo, iterable $allConstInfos): string { + $code = ""; + + foreach ($fileInfo->classInfos as $class) { + $code .= "\n" . $class->getRegistration($allConstInfos); + } + + return $code; +} + +/** @param FuncInfo[] $funcInfos */ +function generateFunctionEntries(?Name $className, array $funcInfos, ?string $cond = null): string { + $code = "\n\n"; + + if ($cond) { + $code .= "#if {$cond}\n"; + } + + $functionEntryName = "ext_functions"; + if ($className) { + $underscoreName = implode("_", $className->getParts()); + $functionEntryName = "class_{$underscoreName}_methods"; + } + + $code .= "static const zend_function_entry {$functionEntryName}[] = {\n"; + $code .= generateCodeWithConditions($funcInfos, "", static function (FuncInfo $funcInfo) { + return $funcInfo->getFunctionEntry(); + }, $cond); + $code .= "\tZEND_FE_END\n"; + $code .= "};\n"; + + if ($cond) { + $code .= "#endif\n"; + } + + return $code; +} +/** + * @param iterable $funcInfos + */ +function generateAttributeInitialization(iterable $funcInfos, iterable $allConstInfos, ?int $phpVersionIdMinimumCompatibility, ?string $parentCond = null): string { + return generateCodeWithConditions( + $funcInfos, + "", + static function (FuncInfo $funcInfo) use ($allConstInfos, $phpVersionIdMinimumCompatibility) { + $code = null; + + foreach ($funcInfo->args as $index => $arg) { + if ($funcInfo->name instanceof MethodName) { + $functionTable = "&class_entry->function_table"; + } else { + $functionTable = "CG(function_table)"; + } + + foreach ($arg->attributes as $attribute) { + $code .= $attribute->generateCode("zend_add_parameter_attribute(zend_hash_str_find_ptr($functionTable, \"" . $funcInfo->name->getNameForAttributes() . "\", sizeof(\"" . $funcInfo->name->getNameForAttributes() . "\") - 1), $index", "{$funcInfo->name->getMethodSynopsisFilename()}_arg{$index}", $allConstInfos, $phpVersionIdMinimumCompatibility); + } + } + + return $code; + }, + $parentCond + ); +} + +/** @param array $funcMap */ +function generateOptimizerInfo(array $funcMap): string { + + $code = "/* This is a generated file, edit the .stub.php files instead. */\n\n"; + + $code .= "static const func_info_t func_infos[] = {\n"; + + $code .= generateCodeWithConditions($funcMap, "", static function (FuncInfo $funcInfo) { + return $funcInfo->getOptimizerInfo(); + }); + + $code .= "};\n"; + + return $code; +} + +/** + * @param array $flagsByPhpVersions + * @return string[] + */ +function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPhpVersions, ?int $phpVersionIdMinimumCompatibility): array +{ + $phpVersions = ALL_PHP_VERSION_IDS; + sort($phpVersions); + $currentPhpVersion = end($phpVersions); + + // No version compatibility is needed + if ($phpVersionIdMinimumCompatibility === null) { + if (empty($flagsByPhpVersions[$currentPhpVersion])) { + return []; + } + + return [sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion]))]; + } + + // Remove flags which depend on a PHP version below the minimally supported one + ksort($flagsByPhpVersions); + $index = array_search($phpVersionIdMinimumCompatibility, array_keys($flagsByPhpVersions)); + if ($index === false) { + throw new Exception("Missing version dependent flags for PHP version ID \"$phpVersionIdMinimumCompatibility\""); + } + $flagsByPhpVersions = array_slice($flagsByPhpVersions, $index, null, true); + + // Remove empty version-specific flags + $flagsByPhpVersions = array_filter( + $flagsByPhpVersions, + static function (array $value): bool { + return !empty($value); + }); + + // There are no version-specific flags + if (empty($flagsByPhpVersions)) { + return []; + } + + // Remove version-specific flags which don't differ from the previous one + $previousVersionId = null; + foreach ($flagsByPhpVersions as $versionId => $versionFlags) { + if ($previousVersionId !== null && $flagsByPhpVersions[$previousVersionId] === $versionFlags) { + unset($flagsByPhpVersions[$versionId]); + } else { + $previousVersionId = $versionId; + } + } + + $flagCount = count($flagsByPhpVersions); + + // Do not add a condition unnecessarily when the only version is the same as the minimally supported one + if ($flagCount === 1) { + reset($flagsByPhpVersions); + $firstVersion = key($flagsByPhpVersions); + if ($firstVersion === $phpVersionIdMinimumCompatibility) { + return [sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions)))]; + } + } + + // Add the necessary conditions around the code using the version-specific flags + $result = []; + $i = 0; + foreach (array_reverse($flagsByPhpVersions, true) as $version => $versionFlags) { + $code = ""; + + $if = $i === 0 ? "#if" : "#elif"; + $endif = $i === $flagCount - 1 ? "#endif\n" : ""; + + $code .= "$if (PHP_VERSION_ID >= $version)\n"; + + $code .= sprintf($codeTemplate, implode("|", $versionFlags)); + $code .= $endif; + + $result[] = $code; + $i++; + } + + return $result; +} + +/** + * @param array $classMap + * @param iterable $allConstInfos + * @return array + */ +function generateClassSynopses(array $classMap, iterable $allConstInfos): array { + $result = []; + + foreach ($classMap as $classInfo) { + $classSynopsis = $classInfo->getClassSynopsisDocument($classMap, $allConstInfos); + if ($classSynopsis !== null) { + $result[ClassInfo::getClassSynopsisFilename($classInfo->name) . ".xml"] = $classSynopsis; + } + } + + return $result; +} + +/** + * @param array $classMap + * $param iterable $allConstInfos + * @return array + */ +function replaceClassSynopses(string $targetDirectory, array $classMap, iterable $allConstInfos, bool $isVerify): array +{ + $existingClassSynopses = []; + + $classSynopses = []; + + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($targetDirectory), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($it as $file) { + $pathName = $file->getPathName(); + if (!preg_match('/\.xml$/i', $pathName)) { + continue; + } + + $xml = file_get_contents($pathName); + if ($xml === false) { + continue; + } + + if (stripos($xml, "formatOutput = false; + $doc->preserveWhiteSpace = true; + $doc->validateOnParse = true; + $success = $doc->loadXML($replacedXml); + if (!$success) { + echo "Failed opening $pathName\n"; + continue; + } + + $classSynopsisElements = []; + foreach ($doc->getElementsByTagName("classsynopsis") as $element) { + $classSynopsisElements[] = $element; + } + + foreach ($classSynopsisElements as $classSynopsis) { + if (!$classSynopsis instanceof DOMElement) { + continue; + } + + $firstChild = $classSynopsis->firstElementChild; + if ($firstChild === null) { + continue; + } + $firstChild = $firstChild->firstElementChild; + if ($firstChild === null) { + continue; + } + $className = $firstChild->textContent; + if (!isset($classMap[$className])) { + continue; + } + + $existingClassSynopses[$className] = $className; + + $classInfo = $classMap[$className]; + + $newClassSynopsis = $classInfo->getClassSynopsisElement($doc, $classMap, $allConstInfos); + if ($newClassSynopsis === null) { + continue; + } + + // Check if there is any change - short circuit if there is not any. + + if (replaceAndCompareXmls($doc, $classSynopsis, $newClassSynopsis)) { + continue; + } + + // Return the updated XML + + $replacedXml = $doc->saveXML(); + + $replacedXml = preg_replace( + [ + "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/", + '//i', + '//i', + '//i', + '//i', + '//i', + ], + [ + "&$1", + "", + "", + "", + "", + "", + ], + $replacedXml + ); + + $classSynopses[$pathName] = $replacedXml; + } + } + + if ($isVerify) { + $missingClassSynopses = array_diff_key($classMap, $existingClassSynopses); + foreach ($missingClassSynopses as $className => $info) { + /** @var ClassInfo $info */ + if (!$info->isUndocumentable) { + echo "Warning: Missing class synopsis for $className\n"; + } + } + } + + return $classSynopses; +} + +function getReplacedSynopsisXml(string $xml): string +{ + return preg_replace( + [ + "/&([A-Za-z0-9._{}%-]+?;)/", + "/<(\/)*xi:([A-Za-z]+?)/" + ], + [ + "REPLACED-ENTITY-$1", + "<$1XI$2", + ], + $xml + ); +} + +/** + * @param array $funcMap + * @param array $aliasMap + * @return array + */ +function generateMethodSynopses(array $funcMap, array $aliasMap): array { + $result = []; + + foreach ($funcMap as $funcInfo) { + $methodSynopsis = $funcInfo->getMethodSynopsisDocument($funcMap, $aliasMap); + if ($methodSynopsis !== null) { + $result[$funcInfo->name->getMethodSynopsisFilename() . ".xml"] = $methodSynopsis; + } + } + + return $result; +} + +/** + * @param array $funcMap + * @param array $aliasMap + * @return array + */ +function replaceMethodSynopses(string $targetDirectory, array $funcMap, array $aliasMap, bool $isVerify): array { + $existingMethodSynopses = []; + $methodSynopses = []; + + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($targetDirectory), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($it as $file) { + $pathName = $file->getPathName(); + if (!preg_match('/\.xml$/i', $pathName)) { + continue; + } + + $xml = file_get_contents($pathName); + if ($xml === false) { + continue; + } + + if ($isVerify) { + $matches = []; + preg_match("/\s*([\w:]+)\s*<\/refname>\s*\s*&Alias;\s*<(?:function|methodname)>\s*([\w:]+)\s*<\/(?:function|methodname)>\s*<\/refpurpose>/i", $xml, $matches); + $aliasName = $matches[1] ?? null; + $alias = $funcMap[$aliasName] ?? null; + $funcName = $matches[2] ?? null; + $func = $funcMap[$funcName] ?? null; + + if ($alias && + !$alias->isUndocumentable && + ($func === null || $func->alias === null || $func->alias->__toString() !== $aliasName) && + ($alias->alias === null || $alias->alias->__toString() !== $funcName) + ) { + echo "Warning: $aliasName()" . ($alias->alias ? " is an alias of " . $alias->alias->__toString() . "(), but it" : "") . " is incorrectly documented as an alias for $funcName()\n"; + } + + $matches = []; + preg_match("/<(?:para|simpara)>\s*(?:&info.function.alias;|&info.method.alias;|&Alias;)\s+<(?:function|methodname)>\s*([\w:]+)\s*<\/(?:function|methodname)>/i", $xml, $matches); + $descriptionFuncName = $matches[1] ?? null; + $descriptionFunc = $funcMap[$descriptionFuncName] ?? null; + if ($descriptionFunc && $funcName !== $descriptionFuncName) { + echo "Warning: Alias in the method synopsis description of $pathName doesn't match the alias in the \n"; + } + + if ($aliasName) { + $existingMethodSynopses[$aliasName] = $aliasName; + } + } + + if (stripos($xml, "formatOutput = false; + $doc->preserveWhiteSpace = true; + $doc->validateOnParse = true; + $success = $doc->loadXML($replacedXml); + if (!$success) { + echo "Failed opening $pathName\n"; + continue; + } + + $methodSynopsisElements = []; + foreach ($doc->getElementsByTagName("constructorsynopsis") as $element) { + $methodSynopsisElements[] = $element; + } + foreach ($doc->getElementsByTagName("destructorsynopsis") as $element) { + $methodSynopsisElements[] = $element; + } + foreach ($doc->getElementsByTagName("methodsynopsis") as $element) { + $methodSynopsisElements[] = $element; + } + + foreach ($methodSynopsisElements as $methodSynopsis) { + if (!$methodSynopsis instanceof DOMElement) { + continue; + } + + $list = $methodSynopsis->getElementsByTagName("methodname"); + $item = $list->item(0); + if (!$item instanceof DOMElement) { + continue; + } + $funcName = $item->textContent; + if (!isset($funcMap[$funcName])) { + continue; + } + + $funcInfo = $funcMap[$funcName]; + $existingMethodSynopses[$funcInfo->name->__toString()] = $funcInfo->name->__toString(); + + $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + if ($newMethodSynopsis === null) { + continue; + } + + // Retrieve current signature + + $params = []; + $list = $methodSynopsis->getElementsByTagName("methodparam"); + foreach ($list as $i => $item) { + if (!$item instanceof DOMElement) { + continue; + } + + $paramList = $item->getElementsByTagName("parameter"); + if ($paramList->count() !== 1) { + continue; + } + + $paramName = $paramList->item(0)->textContent; + $paramTypes = []; + + $paramList = $item->getElementsByTagName("type"); + foreach ($paramList as $type) { + if (!$type instanceof DOMElement) { + continue; + } + + $paramTypes[] = $type->textContent; + } + + $params[$paramName] = ["index" => $i, "type" => $paramTypes]; + } + + // Check if there is any change - short circuit if there is not any. + + if (replaceAndCompareXmls($doc, $methodSynopsis, $newMethodSynopsis)) { + continue; + } + + // Update parameter references + + $paramList = $doc->getElementsByTagName("parameter"); + /** @var DOMElement $paramElement */ + foreach ($paramList as $paramElement) { + if ($paramElement->parentNode && $paramElement->parentNode->nodeName === "methodparam") { + continue; + } + + $name = $paramElement->textContent; + if (!isset($params[$name])) { + continue; + } + + $index = $params[$name]["index"]; + if (!isset($funcInfo->args[$index])) { + continue; + } + + $paramElement->textContent = $funcInfo->args[$index]->name; + } + + // Return the updated XML + + $replacedXml = $doc->saveXML(); + + $replacedXml = preg_replace( + [ + "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/", + '//i', + '//i', + ], + [ + "&$1", + "", + "", + ], + $replacedXml + ); + + $methodSynopses[$pathName] = $replacedXml; + } + } + + if ($isVerify) { + $missingMethodSynopses = array_diff_key($funcMap, $existingMethodSynopses); + foreach ($missingMethodSynopses as $functionName => $info) { + /** @var FuncInfo $info */ + if (!$info->isUndocumentable) { + echo "Warning: Missing method synopsis for $functionName()\n"; + } + } + } + + return $methodSynopses; +} + +function replaceAndCompareXmls(DOMDocument $doc, DOMElement $originalSynopsis, DOMElement $newSynopsis): bool +{ + $docComparator = new DOMDocument(); + $docComparator->preserveWhiteSpace = false; + $docComparator->formatOutput = true; + + $xml1 = $doc->saveXML($originalSynopsis); + $xml1 = getReplacedSynopsisXml($xml1); + $docComparator->loadXML($xml1); + $xml1 = $docComparator->saveXML(); + + $originalSynopsis->parentNode->replaceChild($newSynopsis, $originalSynopsis); + + $xml2 = $doc->saveXML($newSynopsis); + $xml2 = getReplacedSynopsisXml($xml2); + + $docComparator->loadXML($xml2); + $xml2 = $docComparator->saveXML(); + + return $xml1 === $xml2; +} + +function installPhpParser(string $version, string $phpParserDir) { + $lockFile = __DIR__ . "/PHP-Parser-install-lock"; + $lockFd = fopen($lockFile, 'w+'); + if (!flock($lockFd, LOCK_EX)) { + throw new Exception("Failed to acquire installation lock"); + } + + try { + // Check whether a parallel process has already installed PHP-Parser. + if (is_dir($phpParserDir)) { + return; + } + + $cwd = getcwd(); + chdir(__DIR__); + + $tarName = "v$version.tar.gz"; + passthru("wget https://github.com/nikic/PHP-Parser/archive/$tarName", $exit); + if ($exit !== 0) { + passthru("curl -LO https://github.com/nikic/PHP-Parser/archive/$tarName", $exit); + } + if ($exit !== 0) { + throw new Exception("Failed to download PHP-Parser tarball"); + } + if (!mkdir($phpParserDir)) { + throw new Exception("Failed to create directory $phpParserDir"); + } + passthru("tar xvzf $tarName -C PHP-Parser-$version --strip-components 1", $exit); + if ($exit !== 0) { + throw new Exception("Failed to extract PHP-Parser tarball"); + } + unlink(__DIR__ . "/$tarName"); + chdir($cwd); + } finally { + flock($lockFd, LOCK_UN); + @unlink($lockFile); + } +} + +function initPhpParser() { + static $isInitialized = false; + if ($isInitialized) { + return; + } + + if (!extension_loaded("tokenizer")) { + throw new Exception("The \"tokenizer\" extension is not available"); + } + + $isInitialized = true; + $version = "5.0.0alpha3"; + $phpParserDir = __DIR__ . "/PHP-Parser-$version"; + if (!is_dir($phpParserDir)) { + installPhpParser($version, $phpParserDir); + } + + spl_autoload_register(static function(string $class) use ($phpParserDir) { + if (strpos($class, "PhpParser\\") === 0) { + $fileName = $phpParserDir . "/lib/" . str_replace("\\", "/", $class) . ".php"; + require $fileName; + } + }); +} + +$optind = null; +$options = getopt( + "fh", + [ + "force-regeneration", "parameter-stats", "help", "verify", "generate-classsynopses", "replace-classsynopses", + "generate-methodsynopses", "replace-methodsynopses", "generate-optimizer-info" + ], + $optind +); + +$context = new Context; +$printParameterStats = isset($options["parameter-stats"]); +$verify = isset($options["verify"]); +$generateClassSynopses = isset($options["generate-classsynopses"]); +$replaceClassSynopses = isset($options["replace-classsynopses"]); +$generateMethodSynopses = isset($options["generate-methodsynopses"]); +$replaceMethodSynopses = isset($options["replace-methodsynopses"]); +$generateOptimizerInfo = isset($options["generate-optimizer-info"]); +$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]); +$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses; + +$targetSynopses = $argv[$argc - 1] ?? null; +if ($replaceClassSynopses && $targetSynopses === null) { + die("A target class synopsis directory must be provided for.\n"); +} + +if ($replaceMethodSynopses && $targetSynopses === null) { + die("A target method synopsis directory must be provided.\n"); +} + +if (isset($options["h"]) || isset($options["help"])) { + die("\nusage: gen_stub.php [ -f | --force-regeneration ] [ --generate-classsynopses ] [ --replace-classsynopses ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ --generate-optimizer-info ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n"); +} + +$fileInfos = []; +$locations = array_slice($argv, $optind) ?: ['.']; +foreach (array_unique($locations) as $location) { + if (is_file($location)) { + // Generate single file. + $fileInfo = processStubFile($location, $context); + if ($fileInfo) { + $fileInfos[] = $fileInfo; + } + } else if (is_dir($location)) { + array_push($fileInfos, ...processDirectory($location, $context)); + } else { + echo "$location is neither a file nor a directory.\n"; + exit(1); + } +} + +if ($printParameterStats) { + $parameterStats = []; + + foreach ($fileInfos as $fileInfo) { + foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { + foreach ($funcInfo->args as $argInfo) { + if (!isset($parameterStats[$argInfo->name])) { + $parameterStats[$argInfo->name] = 0; + } + $parameterStats[$argInfo->name]++; + } + } + } + + arsort($parameterStats); + echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n"; +} + +/** @var array $classMap */ +$classMap = []; +/** @var array $funcMap */ +$funcMap = []; +/** @var array $aliasMap */ +$aliasMap = []; + +foreach ($fileInfos as $fileInfo) { + foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { + $funcMap[$funcInfo->name->__toString()] = $funcInfo; + + // TODO: Don't use aliasMap for methodsynopsis? + if ($funcInfo->aliasType === "alias") { + $aliasMap[$funcInfo->alias->__toString()] = $funcInfo; + } + } + + foreach ($fileInfo->classInfos as $classInfo) { + $classMap[$classInfo->name->__toString()] = $classInfo; + } +} + +if ($verify) { + $errors = []; + + foreach ($funcMap as $aliasFunc) { + if (!$aliasFunc->alias) { + continue; + } + + if (!isset($funcMap[$aliasFunc->alias->__toString()])) { + $errors[] = "Aliased function {$aliasFunc->alias}() cannot be found"; + continue; + } + + if (!$aliasFunc->verify) { + continue; + } + + $aliasedFunc = $funcMap[$aliasFunc->alias->__toString()]; + $aliasedArgs = $aliasedFunc->args; + $aliasArgs = $aliasFunc->args; + + if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) { + if ($aliasFunc->isInstanceMethod()) { + $aliasedArgs = array_slice($aliasedArgs, 1); + } + + if ($aliasedFunc->isInstanceMethod()) { + $aliasArgs = array_slice($aliasArgs, 1); + } + } + + array_map( + function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) { + if ($aliasArg === null) { + assert($aliasedArg !== null); + $errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing"; + return null; + } + + if ($aliasedArg === null) { + $errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing"; + return null; + } + + if ($aliasArg->name !== $aliasedArg->name) { + $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name"; + return null; + } + + if ($aliasArg->type != $aliasedArg->type) { + $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type"; + } + + if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) { + $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value"; + } + }, + $aliasArgs, $aliasedArgs + ); + + $aliasedReturn = $aliasedFunc->return; + $aliasReturn = $aliasFunc->return; + + if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) { + $aliasedReturnType = $aliasedReturn->type ?? $aliasedReturn->phpDocType; + $aliasReturnType = $aliasReturn->type ?? $aliasReturn->phpDocType; + if ($aliasReturnType != $aliasedReturnType) { + $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type"; + } + } + + $aliasedPhpDocReturnType = $aliasedReturn->phpDocType; + $aliasPhpDocReturnType = $aliasReturn->phpDocType; + if ($aliasedPhpDocReturnType != $aliasPhpDocReturnType && $aliasedPhpDocReturnType != $aliasReturn->type && $aliasPhpDocReturnType != $aliasedReturn->type) { + $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same PHPDoc return type"; + } + } + + echo implode("\n", $errors); + if (!empty($errors)) { + echo "\n"; + exit(1); + } +} + +if ($generateClassSynopses) { + $classSynopsesDirectory = getcwd() . "/classsynopses"; + + $classSynopses = generateClassSynopses($classMap, $context->allConstInfos); + if (!empty($classSynopses)) { + if (!file_exists($classSynopsesDirectory)) { + mkdir($classSynopsesDirectory); + } + + foreach ($classSynopses as $filename => $content) { + if (file_put_contents("$classSynopsesDirectory/$filename", $content)) { + echo "Saved $filename\n"; + } + } + } +} + +if ($replaceClassSynopses) { + $classSynopses = replaceClassSynopses($targetSynopses, $classMap, $context->allConstInfos, $verify); + + foreach ($classSynopses as $filename => $content) { + if (file_put_contents($filename, $content)) { + echo "Saved $filename\n"; + } + } +} + +if ($generateMethodSynopses) { + $methodSynopsesDirectory = getcwd() . "/methodsynopses"; + + $methodSynopses = generateMethodSynopses($funcMap, $aliasMap); + if (!empty($methodSynopses)) { + if (!file_exists($methodSynopsesDirectory)) { + mkdir($methodSynopsesDirectory); + } + + foreach ($methodSynopses as $filename => $content) { + if (file_put_contents("$methodSynopsesDirectory/$filename", $content)) { + echo "Saved $filename\n"; + } + } + } +} + +if ($replaceMethodSynopses) { + $methodSynopses = replaceMethodSynopses($targetSynopses, $funcMap, $aliasMap, $verify); + + foreach ($methodSynopses as $filename => $content) { + if (file_put_contents($filename, $content)) { + echo "Saved $filename\n"; + } + } +} + +if ($generateOptimizerInfo) { + $filename = dirname(__FILE__, 2) . "/Zend/Optimizer/zend_func_infos.h"; + $optimizerInfo = generateOptimizerInfo($funcMap); + + if (file_put_contents($filename, $optimizerInfo)) { + echo "Saved $filename\n"; + } +} \ No newline at end of file diff --git a/phper-build/src/lib.rs b/phper-build/src/lib.rs index 76b440bf..cf408fa0 100644 --- a/phper-build/src/lib.rs +++ b/phper-build/src/lib.rs @@ -12,7 +12,16 @@ #![warn(clippy::dbg_macro)] #![doc = include_str!("../README.md")] +use bindgen::Builder; +use cc::Build; use phper_sys::*; +use std::env; +use std::ffi::OsStr; +use std::path::Path; +use std::process::Command; +use walkdir::WalkDir; + +const GEN_STUB_PHP: &str = include_str!("../gen_stub.php"); /// Register all php build relative configure parameters, used in `build.rs`. pub fn register_all() { @@ -53,3 +62,218 @@ pub fn register_link_args() { println!("cargo:rustc-link-arg=dynamic_lookup"); } } + +fn execute_command>(argv: &[S]) -> Result> { + let mut command = Command::new(&argv[0]); + command.args(&argv[1..]); + let output = command.output()?.stdout; + Ok(String::from_utf8(output)?.trim().to_owned()) +} + +fn create_builder() -> Result<(Build, Builder), Box> { + let php_config = env::var("PHP_CONFIG").unwrap_or_else(|_| "php-config".to_string()); + + let includes = execute_command(&[php_config.as_str(), "--includes"])?; + let includes = includes.split(' ').collect::>(); + + let builder = Builder::default() + .derive_debug(true) + .clang_args(&includes) + .generate_inline_functions(true) + .generate_block(true) + .generate_comments(true) + .wrap_unsafe_ops(true) + .array_pointers_in_arguments(true) + .generate_cstr(true); + + let mut cc = Build::new(); + + for dir in includes.iter() { + cc.flag(dir); + } + + cc.cpp(false) + .debug(false) + .extra_warnings(false) + .warnings(false) + .flag("-std=c2x") // Replace with -std=c23 after CLANG 18 + .force_frame_pointer(false) + .opt_level(3) + .use_plt(false) + .static_flag(true) + .pic(true); + + Ok((cc, builder)) +} + +/// Includes php bindings for function/method arguments +pub fn generate_php_function_args, Q: AsRef>( + output_dir: P, + dirs: &[Q], + php_exec: Option<&str>, +) -> Result<(), Box> { + let output_dir = output_dir.as_ref(); + let gen_stub_php = output_dir.join("gen_stub.php"); + std::fs::write(&gen_stub_php, GEN_STUB_PHP)?; + + let gen_stub_php = gen_stub_php.as_os_str().to_str().unwrap(); + + for dir in dirs { + Command::new(php_exec.unwrap_or("php")) + .args([gen_stub_php, dir.as_ref().to_str().unwrap()]) + .output()?; + } + + let mut header = String::with_capacity(64 * 1024); + let mut c_file = String::with_capacity(64 * 1024); + header.push_str("#pragma once\n\n#include \n\n"); + c_file.push_str("#include \n\nBEGIN_EXTERN_C()\n#define static\n\n"); + + for dir in dirs { + let dir = dir.as_ref(); + + let files = WalkDir::new(dir).follow_links(false); + + for file in files { + let file = file?; + let path = file.path(); + + if path.is_file() && path.extension() == Some(OsStr::new("php")) { + println!("cargo:rerun-if-changed={}", path.display()); + } + + if file.file_type().is_dir() || path.extension() != Some(OsStr::new("h")) { + continue; + } + + let contents = std::fs::read_to_string(path)?; + + if extract_headers_and_c_file(&mut header, &mut c_file, contents).is_none() { + continue; + } + + std::fs::remove_file(path)?; + } + } + + c_file.push_str("#undef static\nEND_EXTERN_C()\n"); + + let php_args_binding_h_path = output_dir.join("php_args_bindings.h"); + std::fs::write(&php_args_binding_h_path, &header)?; + + let php_args_binding_c_path = output_dir.join("php_args_bindings.c"); + std::fs::write(&php_args_binding_c_path, c_file)?; + + let (mut cc, builder) = create_builder()?; + + cc.file(&php_args_binding_c_path) + .include(output_dir) + .compile("php_args_bindings"); + + builder + .header(php_args_binding_h_path.to_str().unwrap()) + .allowlist_file(php_args_binding_h_path.to_str().unwrap()) + .generate()? + .write_to_file(output_dir.join("php_args_bindings.rs"))?; + + Ok(()) +} + +fn extract_headers_and_c_file( + header: &mut String, + c_file: &mut String, + contents: String, +) -> Option<()> { + let mut result = Vec::new(); + + let mut name = ""; + let mut counter = 0; + + for line in contents.lines() { + let trimmed_line = line.trim(); + + if trimmed_line.starts_with("ZEND_FUNCTION") + || trimmed_line.starts_with("ZEND_METHOD") + || trimmed_line.starts_with("static const zend_function_entry ext_functions[]") + || trimmed_line.starts_with("static const zend_function_entry class_") + || trimmed_line.starts_with("ZEND_ME") + || trimmed_line.starts_with("ZEND_FE_END") + || trimmed_line.starts_with("ZEND_FE") + || trimmed_line.starts_with("ZEND_NS_") + || trimmed_line.starts_with("};") + { + continue; + } + + if trimmed_line.contains("zend_class_entry *register_") { + header.push_str("extern "); + header.push_str(&trimmed_line["static ".len()..]); + header.push(';'); + header.push('\n'); + } + + if trimmed_line.contains("_methods);") { + let last_comma = trimmed_line.rfind(',').unwrap(); + c_file.push_str(&trimmed_line[..last_comma]); + c_file.push_str(", NULL);"); + } else if trimmed_line.starts_with("static ") { + c_file.push_str(trimmed_line.strip_prefix("static ").unwrap()); + } else { + c_file.push_str(trimmed_line); + } + + c_file.push('\n'); + + if trimmed_line.starts_with("ZEND_BEGIN_") { + let start = line.find("arginfo_")?; + let end = line.find(',')?; + + name = &line[start..end]; + } + + if trimmed_line.starts_with("ZEND_ARG_") { + counter += 1; + } + + if trimmed_line.starts_with("ZEND_END_ARG_INFO") { + result.push((name, counter + 1)); + counter = 0; + name = ""; + } + } + + if !result.is_empty() { + result.iter().for_each(|(name, count)| { + header.push_str("extern const zend_internal_arg_info "); + header.push_str(name); + header.push('['); + header.push_str(count.to_string().as_str()); + header.push_str("];\n"); + }); + } + + Some(()) +} + +#[cfg(test)] + +mod tests { + use super::*; + + #[test] + fn test_extract_content_and_header() { + let mut header = String::new(); + let mut c_file = String::new(); + + const INPUT: &str = + include_str!("../tests/test_extract_content_and_header/say_hello_arginfo.h"); + const EXPECTED_HEADER: &str = + include_str!("../tests/test_extract_content_and_header/expected_header"); + const EXPECTED_C_FILE: &str = + include_str!("../tests/test_extract_content_and_header/expected_c_file"); + + assert!(extract_headers_and_c_file(&mut header, &mut c_file, INPUT.into()).is_some()); + assert_eq!(EXPECTED_HEADER, header.as_str()); + assert_eq!(EXPECTED_C_FILE, c_file.as_str()); + } +} diff --git a/phper-build/tests/integrations.rs b/phper-build/tests/integrations.rs new file mode 100644 index 00000000..c36f577e --- /dev/null +++ b/phper-build/tests/integrations.rs @@ -0,0 +1,27 @@ +use std::env::current_dir; + +use phper_build::generate_php_function_args; + +struct Files<'a>(&'a std::path::PathBuf); + +impl<'a> Drop for Files<'a> { + fn drop(&mut self) { + std::fs::remove_file(self.0.join("php_args_bindings.c")).unwrap(); + std::fs::remove_file(self.0.join("php_args_bindings.h")).unwrap(); + std::fs::remove_file(self.0.join("php_args_bindings.rs")).unwrap(); + } +} + +#[test] +pub fn test_generate_function_args() { + let current_dir = current_dir().unwrap(); + // let _files = Files(¤t_dir); + + let stubs_dir = current_dir.join("tests").join("stubs"); + + assert!(generate_php_function_args(¤t_dir, &[&stubs_dir], None).is_ok()); + // assert!(current_dir.join("php_args_bindings.h").exists()); + // assert!(current_dir.join("php_args_bindings.c").exists()); + // assert!(current_dir.join("php_args_bindings.rs").exists()); + // assert!(current_dir.join("php_args_bindings.a").exists()); +} diff --git a/phper-build/tests/stubs/test.stub.php b/phper-build/tests/stubs/test.stub.php new file mode 100644 index 00000000..2a11df2d --- /dev/null +++ b/phper-build/tests/stubs/test.stub.php @@ -0,0 +1,5 @@ +ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + +zval property_foo_default_value; +ZVAL_LONG(&property_foo_default_value, 100); +zend_string *property_foo_name = zend_string_init("foo", sizeof("foo") - 1, 1); +zend_string *property_foo_class_JsonSerializable = zend_string_init("JsonSerializable", sizeof("JsonSerializable") - 1, 1); +zend_string *property_foo_class_ArrayAccess = zend_string_init("ArrayAccess", sizeof("ArrayAccess") - 1, 1); +zend_type_list *property_foo_type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); +property_foo_type_list->num_types = 2; +property_foo_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(property_foo_class_JsonSerializable, 0, 0); +property_foo_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_CLASS(property_foo_class_ArrayAccess, 0, 0); +zend_type property_foo_type = ZEND_TYPE_INIT_UNION(property_foo_type_list, MAY_BE_LONG); +zend_declare_typed_property(class_entry, property_foo_name, &property_foo_default_value, ZEND_ACC_PRIVATE, NULL, property_foo_type); +zend_string_release(property_foo_name); + +return class_entry; +} diff --git a/phper-build/tests/test_extract_content_and_header/expected_header b/phper-build/tests/test_extract_content_and_header/expected_header new file mode 100644 index 00000000..41fbab38 --- /dev/null +++ b/phper-build/tests/test_extract_content_and_header/expected_header @@ -0,0 +1,2 @@ +extern zend_class_entry *register_class_Complex_Foo(void); +extern const zend_internal_arg_info arginfo_Complex_say_hello[2]; diff --git a/phper-build/tests/test_extract_content_and_header/say_hello_arginfo.h b/phper-build/tests/test_extract_content_and_header/say_hello_arginfo.h new file mode 100644 index 00000000..473ff57c --- /dev/null +++ b/phper-build/tests/test_extract_content_and_header/say_hello_arginfo.h @@ -0,0 +1,43 @@ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_Complex_say_hello, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_FUNCTION(Complex_say_hello); +ZEND_METHOD(Complex_Foo, setFoo); + +static const zend_function_entry ext_functions[] = { + ZEND_NS_FALIAS("Complex", say_hello, Complex_say_hello, arginfo_Complex_say_hello) + ZEND_NS_FALIAS("Complex", throw_exception, Complex_throw_exception, arginfo_Complex_throw_exception) + ZEND_NS_FALIAS("Complex", get_all_ini, Complex_get_all_ini, arginfo_Complex_get_all_ini) + ZEND_FE_END +}; + +static const zend_function_entry class_Complex_Foo_methods[] = { + ZEND_ME(Complex_Foo, getFoo, arginfo_class_Complex_Foo_getFoo, ZEND_ACC_PUBLIC) + ZEND_ME(Complex_Foo, setFoo, arginfo_class_Complex_Foo_setFoo, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_Complex_Foo(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Complex", "Foo", class_Complex_Foo_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + zval property_foo_default_value; + ZVAL_LONG(&property_foo_default_value, 100); + zend_string *property_foo_name = zend_string_init("foo", sizeof("foo") - 1, 1); + zend_string *property_foo_class_JsonSerializable = zend_string_init("JsonSerializable", sizeof("JsonSerializable") - 1, 1); + zend_string *property_foo_class_ArrayAccess = zend_string_init("ArrayAccess", sizeof("ArrayAccess") - 1, 1); + zend_type_list *property_foo_type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); + property_foo_type_list->num_types = 2; + property_foo_type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(property_foo_class_JsonSerializable, 0, 0); + property_foo_type_list->types[1] = (zend_type) ZEND_TYPE_INIT_CLASS(property_foo_class_ArrayAccess, 0, 0); + zend_type property_foo_type = ZEND_TYPE_INIT_UNION(property_foo_type_list, MAY_BE_LONG); + zend_declare_typed_property(class_entry, property_foo_name, &property_foo_default_value, ZEND_ACC_PRIVATE, NULL, property_foo_type); + zend_string_release(property_foo_name); + + return class_entry; +} diff --git a/phper-doc/doc/_02_quick_start/_01_write_your_first_extension/index.md b/phper-doc/doc/_02_quick_start/_01_write_your_first_extension/index.md index 10527ec1..515cd4e3 100644 --- a/phper-doc/doc/_02_quick_start/_01_write_your_first_extension/index.md +++ b/phper-doc/doc/_02_quick_start/_01_write_your_first_extension/index.md @@ -53,20 +53,20 @@ Full example is phper::Result<()> { // Get the first argument, expect the type `ZStr`, and convert to Rust utf-8 // str. let name = arguments[0].expect_z_str()?.to_str()?; - + // Macro which do php internal `echo`. echo!("Hello, {}!\n", name); - + Ok(()) } - + /// This is the entry of php extension, the attribute macro `php_get_module` /// will generate the `extern "C" fn`. #[php_get_module] @@ -77,10 +77,10 @@ Full example is - + # Build libhello.so. cargo build ``` diff --git a/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md b/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md index 0e903a6e..1b4450fd 100644 --- a/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md +++ b/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md @@ -94,35 +94,35 @@ Now let's begin to finish the logic. classes::{ClassEntry, ClassEntity}, errors::{exception_class, Throwable}, }; - + /// The exception class name of extension. const EXCEPTION_CLASS_NAME: &str = "HttpClient\\HttpClientException"; - + pub fn make_exception_class() -> ClassEntity<()> { let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); // The `extends` is same as the PHP class `extends`. class.extends(exception_class); class } - + #[derive(Debug, thiserror::Error)] pub enum HttpClientError { #[error(transparent)] Reqwest(reqwest::Error), - + #[error("should call '{method_name}()' before call 'body()'")] ResponseAfterRead { method_name: String }, - + #[error("should not call 'body()' multi time")] ResponseHadRead, } - + impl Throwable for HttpClientError { fn get_class(&self) -> &ClassEntry { ClassEntry::from_globals(EXCEPTION_CLASS_NAME).unwrap_or_else(|_| exception_class()) } } - + impl From for phper::Error { fn from(e: HttpClientError) -> Self { phper::Error::throw(e) @@ -186,11 +186,11 @@ Now let's begin to finish the logic. use phper::{ alloc::ToRefOwned, classes::{StaticStateClass, Visibility}, - functions::Argument, + arguments::Argument, }; use reqwest::blocking::{Client, ClientBuilder}; use std::{mem::take, time::Duration}; - + const HTTP_CLIENT_BUILDER_CLASS_NAME: &str = "HttpClient\\HttpClientBuilder"; const HTTP_CLIENT_CLASS_NAME: &str = "HttpClient\\HttpClient"; @@ -198,12 +198,12 @@ Now let's begin to finish the logic. // The static StaticStateClass is bind to ClassEntity of HttpClient, When the class registered, // the StaticStateClass will be initialized, so you can use it to new stateful object, etc. static HTTP_CLIENT_CLASS:StaticStateClass> =StaticStateClass::null(); - + pub fn make_client_builder_class() -> ClassEntity { // `new_with_default_state_constructor` means initialize the state of `ClientBuilder` as // `Default::default`. let mut class = ClassEntity::new_with_default_state_constructor(HTTP_CLIENT_BUILDER_CLASS_NAME); - + // Inner call the `ClientBuilder::timeout`. class .add_method("timeout", Visibility::Public, |this, arguments| { @@ -214,7 +214,7 @@ Now let's begin to finish the logic. Ok::<_, phper::Error>(this.to_ref_owned()) }) .argument(Argument::by_val("ms")); - + // Inner call the `ClientBuilder::cookie_store`. class .add_method("cookie_store", Visibility::Public, |this, arguments| { @@ -225,7 +225,7 @@ Now let's begin to finish the logic. Ok::<_, phper::Error>(this.to_ref_owned()) }) .argument(Argument::by_val("enable")); - + // Inner call the `ClientBuilder::build`, and wrap the result `Client` in // Object. class.add_method("build", Visibility::Public, |this, _arguments| { @@ -235,7 +235,7 @@ Now let's begin to finish the logic. *object.as_mut_state() = Some(client); Ok::<_, phper::Error>(object) }); - + class } ``` diff --git a/phper-doc/doc/_06_module/_02_register_functions/index.md b/phper-doc/doc/_06_module/_02_register_functions/index.md index fa9ecea5..2bf4500e 100644 --- a/phper-doc/doc/_06_module/_02_register_functions/index.md +++ b/phper-doc/doc/_06_module/_02_register_functions/index.md @@ -1,10 +1,10 @@ # Register functions -In `PHPER`, you can use [`add_function`](phper::modules::Module::add_function) to +In `PHPER`, you can use [`add_function`](phper::modules::Module::add_function) to register functions. ```rust,no_run -use phper::{modules::Module, php_get_module, functions::Argument, echo}; +use phper::{modules::Module, php_get_module, arguments::Argument, echo}; #[php_get_module] pub fn get_module() -> Module { @@ -24,7 +24,7 @@ pub fn get_module() -> Module { } ``` -This example registers a function called `say_hello` and accepts a parameter +This example registers a function called `say_hello` and accepts a parameter `name` passed by value, similarly in PHP: ```php @@ -51,13 +51,13 @@ Extension [ extension #56 hello version ] { } ``` -It is useful to register the parameters of the function, which can limit the +It is useful to register the parameters of the function, which can limit the number of parameters of the function. Especially when the parameters need to be passed by reference. ```rust,no_run -use phper::{modules::Module, php_get_module, functions::Argument}; +use phper::{modules::Module, php_get_module, arguments::Argument}; #[php_get_module] pub fn get_module() -> Module { @@ -78,5 +78,5 @@ pub fn get_module() -> Module { ``` Here, the argument is registered as -[`Argument::by_ref`](phper::functions::Argument::by_ref). Therefore, the type of +[`Argument::by_ref`](phper::arguments::Argument::by_ref). Therefore, the type of the `count` parameter is no longer long, but a reference. diff --git a/phper-doc/doc/_06_module/_06_register_class/index.md b/phper-doc/doc/_06_module/_06_register_class/index.md index ff206fbc..2de31ac5 100644 --- a/phper-doc/doc/_06_module/_06_register_class/index.md +++ b/phper-doc/doc/_06_module/_06_register_class/index.md @@ -88,7 +88,7 @@ Adding static class methods is more like adding module functions, because there ```rust,no_run use phper::classes::{ClassEntity, ClassEntry, Visibility}; -use phper::functions::Argument; +use phper::arguments::Argument; use phper::values::ZVal; let mut foo = ClassEntity::new("Foo"); @@ -117,7 +117,7 @@ to insert key value pair. ```rust,no_run use std::collections::HashMap; use phper::classes::{ClassEntity, ClassEntry, Visibility}; -use phper::functions::Argument; +use phper::arguments::Argument; let mut class = ClassEntity::>::new_with_state_constructor( diff --git a/phper-doc/doc/_06_module/_07_register_interface/index.md b/phper-doc/doc/_06_module/_07_register_interface/index.md index c71c36ba..244ede63 100644 --- a/phper-doc/doc/_06_module/_07_register_interface/index.md +++ b/phper-doc/doc/_06_module/_07_register_interface/index.md @@ -63,7 +63,7 @@ Interface can add public abstract methods. ```rust,no_run use phper::classes::{InterfaceEntity, ClassEntry, Visibility}; -use phper::functions::Argument; +use phper::arguments::Argument; use phper::objects::StateObj; use phper::values::ZVal; diff --git a/phper-macros/Cargo.toml b/phper-macros/Cargo.toml index a9cb6aff..7c4514c3 100644 --- a/phper-macros/Cargo.toml +++ b/phper-macros/Cargo.toml @@ -27,5 +27,7 @@ quote = "1.0.31" syn = { version = "2.0.26", features = ["full"] } proc-macro2 = "1.0.66" +phper-sys = { workspace = true } + [dev-dependencies] syn = { version = "2.0.26", features = ["full", "extra-traits"] } diff --git a/phper-macros/src/alloc.rs b/phper-macros/src/alloc.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/alloc.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/derives.rs b/phper-macros/src/derives.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/derives.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/globals.rs b/phper-macros/src/globals.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/globals.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/lib.rs b/phper-macros/src/lib.rs index f8387cfe..c48d73f3 100644 --- a/phper-macros/src/lib.rs +++ b/phper-macros/src/lib.rs @@ -8,19 +8,14 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -#![warn(rust_2018_idioms, missing_docs)] +#![warn(rust_2021_compatibility, rust_2018_idioms)] +#![allow(missing_docs)] #![warn(clippy::dbg_macro, clippy::print_stdout)] #![doc = include_str!("../README.md")] -// TODO Write a bridge macro for easy usage about register functions and -// classes, like `cxx`. - -mod alloc; -mod derives; -mod globals; mod inner; -mod log; mod utils; +mod zend_create_fn; use proc_macro::TokenStream; @@ -52,6 +47,11 @@ pub fn c_str_ptr(input: TokenStream) -> TokenStream { utils::c_str_ptr(input) } +#[proc_macro] +pub fn zend_create_fn(input: TokenStream) -> TokenStream { + zend_create_fn::zend_create_fn(input) +} + /// PHP module entry, wrap the `phper::modules::Module` write operation. /// /// # Examples diff --git a/phper-macros/src/log.rs b/phper-macros/src/log.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/log.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/zend_create_fn.rs b/phper-macros/src/zend_create_fn.rs new file mode 100644 index 00000000..8abb8e6a --- /dev/null +++ b/phper-macros/src/zend_create_fn.rs @@ -0,0 +1,44 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::parse::{Parse, ParseBuffer}; +use syn::Ident; +use syn::{parse_macro_input, Token}; + +struct ZendFn { + original_zend_fn: Ident, + const_name: Ident, +} + +impl Parse for ZendFn { + fn parse(input: &'_ ParseBuffer<'_>) -> syn::Result { + let original_zend_fn: Ident = input.parse()?; + input.parse::()?; + let const_name: Ident = input.parse()?; + Ok(ZendFn { + original_zend_fn, + const_name, + }) + } +} + +pub(crate) fn zend_create_fn(input: TokenStream) -> TokenStream { + let ZendFn { + original_zend_fn, + const_name, + } = parse_macro_input!(input as ZendFn); + + let fn_name = format!("zend_create_fn_{}", original_zend_fn); + let new_fn_name = Ident::new(&fn_name, Span::call_site()); + + let result = quote! { + unsafe extern "C" fn #new_fn_name() -> *mut phper::sys::zend_class_entry + { + unsafe { #original_zend_fn() as *mut phper::sys::zend_class_entry } + } + + pub(crate) const #const_name: unsafe extern "C" fn() -> *mut phper::sys::zend_class_entry = #new_fn_name; + }; + + result.into() +} diff --git a/phper-sys/.gitignore b/phper-sys/.gitignore new file mode 100644 index 00000000..ee4b54bc --- /dev/null +++ b/phper-sys/.gitignore @@ -0,0 +1,8 @@ +/target +**/*.rs.bk +/.cargo +/vendor +core +.vscode/ +.idea/ +php/ diff --git a/phper-sys/Cargo.toml b/phper-sys/Cargo.toml index ae66e254..f114a52f 100644 --- a/phper-sys/Cargo.toml +++ b/phper-sys/Cargo.toml @@ -20,5 +20,5 @@ repository = { workspace = true } license = { workspace = true } [build-dependencies] -bindgen = "0.66.1" +bindgen = "0.69.4" cc = "1.0.79" diff --git a/phper-sys/LICENSE b/phper-sys/LICENSE deleted file mode 120000 index ea5b6064..00000000 --- a/phper-sys/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE \ No newline at end of file diff --git a/phper-sys/README.md b/phper-sys/README.md deleted file mode 100644 index 2f54978c..00000000 --- a/phper-sys/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# phper-sys - -Low level PHP binding for Rust. - -The php-config is needed. You can set environment `PHP_CONFIG` to specify the path. - -## License - -[MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). diff --git a/phper-sys/build.rs b/phper-sys/build.rs index 7147503b..8a8cb761 100644 --- a/phper-sys/build.rs +++ b/phper-sys/build.rs @@ -12,10 +12,18 @@ use bindgen::Builder; use std::{env, ffi::OsStr, fmt::Debug, path::PathBuf, process::Command}; fn main() { + let current_dir = env::current_dir().unwrap(); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rerun-if-env-changed=PHP_CONFIG"); println!("cargo:rerun-if-changed=build.rs"); - let current_dir = std::env::current_dir().unwrap(); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_path.to_str().unwrap()); + println!("cargo:rustc-link-lib=static=phpwrapper"); + #[cfg(target_os = "macos")] + { + println!("cargo:rustc-link-arg=-undefined"); + println!("cargo:rustc-link-arg=dynamic_lookup"); + } let c_files = std::fs::read_dir(current_dir.join("c")) .unwrap() @@ -23,11 +31,14 @@ fn main() { .map(|file| file.path().to_string_lossy().to_string()) .collect::>(); + println!( + "cargo:rerun-if-changed={}", + current_dir.join("include").join("phper.h").display() + ); + c_files .iter() .for_each(|file| println!("cargo:rerun-if-changed={}", file)); - println!("cargo:rustc-link-search={}", out_path.to_str().unwrap()); - println!("cargo:rustc-link-lib=static=phpwrapper"); let php_config = env::var("PHP_CONFIG").unwrap_or_else(|_| "php-config".to_string()); @@ -58,7 +69,6 @@ fn main() { let mut builder = Builder::default() .header("include/phper.h") .allowlist_file("include/phper.h") - // .allowlist_recursively(true) // Block the `zend_ini_parse_quantity` because it's document causes the doc test to fail. .blocklist_function("zend_ini_parse_quantity") .derive_hash(true) @@ -80,7 +90,6 @@ fn main() { let bindings = builder.generate().expect("Unable to generate bindings"); - // print!("cargo:warning={}", bindings.to_string()); bindings .write_to_file(out_path.join("php_bindings.rs")) .expect("Unable to write output file"); diff --git a/phper-sys/c/php_alloc.c b/phper-sys/c/php_alloc.c index 7a3c7917..619ca35f 100644 --- a/phper-sys/c/php_alloc.c +++ b/phper-sys/c/php_alloc.c @@ -1,6 +1,6 @@ #include - void *phper_emalloc(size_t size) { +void *phper_emalloc(size_t size) { return emalloc(size); } diff --git a/phper-sys/c/php_args.c b/phper-sys/c/php_args.c new file mode 100644 index 00000000..adee1f08 --- /dev/null +++ b/phper-sys/c/php_args.c @@ -0,0 +1,14 @@ +#include + +ZEND_FASTCALL zend_internal_arg_info phper_zend_arg_info( + const char *name, int32_t type, bool return_reference, bool is_variadic, + bool is_tentative, uintptr_t required_num_args) { + zend_internal_arg_info info = { + .name = name, + .type = ZEND_TYPE_INIT_MASK( + type | + _ZEND_ARG_INFO_FLAGS(return_reference, is_variadic, is_tentative)), + }; + + return info; +} diff --git a/phper-sys/c/php_classes.c b/phper-sys/c/php_classes.c index cd92aedd..4f962c8c 100644 --- a/phper-sys/c/php_classes.c +++ b/phper-sys/c/php_classes.c @@ -1,21 +1,18 @@ #include -zend_class_entry phper_init_class_entry(const char *class_name, - size_t class_name_len) { +zend_class_entry * +phper_register_class_entry(zend_class_entry *(*create_ce)(void), + const zend_function_entry *functions, + zend_object *(*create_object)(zend_class_entry *)) { - zend_class_entry class_ce = {0}; - INIT_CLASS_ENTRY_EX(class_ce, class_name, class_name_len, NULL); - return class_ce; -} + zend_class_entry *ce = create_ce(); -zend_class_entry * -phper_register_class_entry(zend_class_entry *ce, zend_class_entry *parent, - const zend_function_entry *functions) { ce->info.internal.builtin_functions = functions; - if (parent == NULL) { - return zend_register_internal_class(ce); - } + zend_register_functions(ce, ce->info.internal.builtin_functions, + &ce->function_table, EG(current_module)->type); + + ce->create_object = create_object; - return zend_register_internal_class_ex(ce, parent); + return ce; } diff --git a/phper-sys/c/php_module.c b/phper-sys/c/php_module.c new file mode 100644 index 00000000..6359b9a4 --- /dev/null +++ b/phper-sys/c/php_module.c @@ -0,0 +1,5 @@ +#include + +const char *phper_get_zend_module_build_id() { + return ZEND_MODULE_BUILD_ID; +} diff --git a/phper-sys/c/php_objects.c b/phper-sys/c/php_object.c similarity index 67% rename from phper-sys/c/php_objects.c rename to phper-sys/c/php_object.c index 25fe2620..52e60f39 100644 --- a/phper-sys/c/php_objects.c +++ b/phper-sys/c/php_object.c @@ -1,7 +1,11 @@ #include -zval *phper_get_this(const zend_execute_data *execute_data) { - return getThis(); +const zval *phper_get_this(const zend_execute_data *execute_data) { + return ZEND_THIS; +} + +zval *phper_get_this_mut(zend_execute_data *execute_data) { + return ZEND_THIS; } size_t phper_zend_object_properties_size(const zend_class_entry *ce) { @@ -12,6 +16,11 @@ void *phper_zend_object_alloc(size_t obj_size, const zend_class_entry *ce) { return zend_object_alloc(obj_size, (zend_class_entry *)ce); } +zend_object *(**phper_get_create_object(zend_class_entry *ce))( + zend_class_entry *class_type) { + return &ce->create_object; +} + bool phper_object_init_ex(zval *arg, const zend_class_entry *class_type) { return object_init_ex(arg, (zend_class_entry *)class_type) == SUCCESS; } diff --git a/phper-sys/c/php_resource.c b/phper-sys/c/php_resource.c new file mode 100644 index 00000000..5c841a56 --- /dev/null +++ b/phper-sys/c/php_resource.c @@ -0,0 +1,44 @@ +#include + +zend_resource *phper_register_persistent_resource(const zend_string *id, + const void *ptr, int le_id) { + return zend_register_persistent_resource_ex((zend_string *)id, (void *)ptr, + le_id); +} + +int phper_zend_register_persistent_list_destructors(rsrc_dtor_func_t dtor, + const char *name, + int module_number) { + return zend_register_list_destructors_ex(NULL, dtor, name, module_number); +} + +int phper_zend_register_list_destructors(const rsrc_dtor_func_t dtor, + const char *name, int module_number) { + return zend_register_list_destructors_ex((rsrc_dtor_func_t)dtor, NULL, name, + module_number); +} + +int phper_zend_register_list_destructors_ex(const rsrc_dtor_func_t dtor, + const rsrc_dtor_func_t pdtor, + const char *name, + int module_number) { + + return zend_register_list_destructors_ex( + (rsrc_dtor_func_t)dtor, (rsrc_dtor_func_t)pdtor, name, module_number); +} + +int phper_zend_fetch_list_dtor_id(const char *name) { + return zend_fetch_list_dtor_id(name); +} + +const zend_resource *phper_register_persistent_find(const char *hash, + size_t len) { + zend_resource *zv = zend_hash_str_find_ptr(&EG(persistent_list), hash, len); + + if (zv == NULL) { + php_error_docref(0, E_WARNING, "Invalid persistent resource"); + return NULL; + } + + return zv; +} diff --git a/phper-sys/c/php_smart_string.c b/phper-sys/c/php_smart_string.c index 031bbc94..7bbaabb1 100644 --- a/phper-sys/c/php_smart_string.c +++ b/phper-sys/c/php_smart_string.c @@ -1,97 +1,87 @@ #include #include - void phper_smart_str_alloc(smart_str *str, size_t len, - bool persistent) { +void phper_smart_str_alloc(smart_str *str, size_t len, bool persistent) { smart_str_alloc(str, len, persistent); } - void phper_smart_str_extend_ex(smart_str *dest, size_t len, - bool persistent) { +void phper_smart_str_extend_ex(smart_str *dest, size_t len, bool persistent) { smart_str_extend_ex(dest, len, persistent); } - void phper_smart_str_erealloc(smart_str *str, size_t len) { +void phper_smart_str_erealloc(smart_str *str, size_t len) { smart_str_erealloc(str, len); } - void phper_smart_str_realloc(smart_str *str, size_t len) { +void phper_smart_str_realloc(smart_str *str, size_t len) { smart_str_realloc(str, len); } - void phper_smart_str_free_ex(smart_str *str, bool persistent) { +void phper_smart_str_free_ex(smart_str *str, bool persistent) { smart_str_free_ex(str, persistent); } - void phper_smart_str_append_escaped(smart_str *str, const char *s, - size_t l) { +void phper_smart_str_append_escaped(smart_str *str, const char *s, size_t l) { smart_str_append_escaped(str, s, l); } - void phper_smart_str_append_double(smart_str *str, double num, - int precision, - bool zero_fraction) { +void phper_smart_str_append_double(smart_str *str, double num, int precision, + bool zero_fraction) { smart_str_append_double(str, num, precision, zero_fraction); } - void phper_smart_str_append_escaped_truncated( - smart_str *str, const zend_string *value, size_t length) { +void phper_smart_str_append_escaped_truncated(smart_str *str, + const zend_string *value, + size_t length) { smart_str_append_escaped_truncated(str, value, length); } - void phper_smart_str_append_scalar(smart_str *str, - const zval *value, - size_t truncate) { +void phper_smart_str_append_scalar(smart_str *str, const zval *value, + size_t truncate) { smart_str_append_scalar(str, value, truncate); } - void phper_smart_str_0(smart_str *str) { +void phper_smart_str_0(smart_str *str) { smart_str_0(str); } - size_t phper_smart_str_get_len(const smart_str *str) { +size_t phper_smart_str_get_len(const smart_str *str) { return smart_str_get_len((smart_str *)str); } - zend_string *phper_smart_str_extract(smart_str *str) { +zend_string *phper_smart_str_extract(smart_str *str) { return smart_str_extract(str); } - void phper_smart_str_appendc_ex(smart_str *dest, char ch, - bool persistent) { +void phper_smart_str_appendc_ex(smart_str *dest, char ch, bool persistent) { smart_str_appendc_ex(dest, ch, persistent); } - void phper_smart_str_appendl_ex(smart_str *dest, const char *str, - size_t len, bool persistent) { +void phper_smart_str_appendl_ex(smart_str *dest, const char *str, size_t len, + bool persistent) { smart_str_appendl_ex(dest, str, len, persistent); } - void phper_smart_str_append_ex(smart_str *dest, - const zend_string *src, - bool persistent) { +void phper_smart_str_append_ex(smart_str *dest, const zend_string *src, + bool persistent) { smart_str_append_ex(dest, src, persistent); } - void phper_smart_str_append_smart_str_ex(smart_str *dest, - const smart_str *src, - bool persistent) { +void phper_smart_str_append_smart_str_ex(smart_str *dest, const smart_str *src, + bool persistent) { smart_str_append_smart_str_ex(dest, src, persistent); } - void phper_smart_str_append_long_ex(smart_str *dest, - zend_long num, - bool persistent) { +void phper_smart_str_append_long_ex(smart_str *dest, zend_long num, + bool persistent) { smart_str_append_long_ex(dest, num, persistent); } - void phper_smart_str_append_unsigned_ex(smart_str *dest, - zend_ulong num, - bool persistent) { +void phper_smart_str_append_unsigned_ex(smart_str *dest, zend_ulong num, + bool persistent) { smart_str_append_unsigned_ex(dest, num, persistent); } - void phper_smart_str_setl(smart_str *dest, const char *src, - size_t len) { +void phper_smart_str_setl(smart_str *dest, const char *src, size_t len) { smart_str_setl(dest, src, len); } \ No newline at end of file diff --git a/phper-sys/c/php_string.c b/phper-sys/c/php_string.c index 9247b767..b7b4504a 100644 --- a/phper-sys/c/php_string.c +++ b/phper-sys/c/php_string.c @@ -1,34 +1,34 @@ #include - zend_string *phper_zend_new_interned_string(zend_string *str) { +zend_string *phper_zend_new_interned_string(zend_string *str) { return zend_new_interned_string(str); } - zend_string *phper_zend_string_init(const char *str, size_t len, - int persistent) { +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) { +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) { +void phper_zend_string_release(zend_string *s) { zend_string_release(s); } - int phper_zstr_len(const zend_string *s) { +int phper_zstr_len(const zend_string *s) { return ZSTR_LEN(s); } - const char *phper_zstr_val(const zend_string *s) { +const char *phper_zstr_val(const zend_string *s) { return ZSTR_VAL(s); } - void phper_separate_string(zval *zv) { +void phper_separate_string(zval *zv) { SEPARATE_STRING(zv); } - zend_string *phper_zend_string_copy(zend_string *s) { +zend_string *phper_zend_string_copy(zend_string *s) { return zend_string_copy(s); } diff --git a/phper-sys/c/php_wrapper.c b/phper-sys/c/php_wrapper.c deleted file mode 100644 index 7e4264a2..00000000 --- a/phper-sys/c/php_wrapper.c +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -#include - -// ================================================== -// module apis: -// ================================================== - -const char *phper_get_zend_module_build_id() { - return ZEND_MODULE_BUILD_ID; -} - -zend_resource *phper_register_persistent_resource(const zend_string *id, - const void *ptr, int le_id) { - return zend_register_persistent_resource_ex((zend_string *)id, (void *)ptr, - le_id); -} - -int phper_zend_register_persistent_list_destructors(rsrc_dtor_func_t dtor, - const char *name, - int module_number) { - return zend_register_list_destructors_ex(NULL, dtor, name, module_number); -} - -int phper_zend_register_list_destructors(const rsrc_dtor_func_t dtor, - const char *name, int module_number) { - return zend_register_list_destructors_ex((rsrc_dtor_func_t)dtor, NULL, name, - module_number); -} - -int phper_zend_register_list_destructors_ex(const rsrc_dtor_func_t dtor, - const rsrc_dtor_func_t pdtor, - const char *name, - int module_number) { - - return zend_register_list_destructors_ex( - (rsrc_dtor_func_t)dtor, (rsrc_dtor_func_t)pdtor, name, module_number); -} - -int phper_zend_fetch_list_dtor_id(const char *name) { - return zend_fetch_list_dtor_id(name); -} - -const zend_resource *phper_register_persistent_find(const char *hash, - size_t len) { - zend_resource *zv = zend_hash_str_find_ptr(&EG(persistent_list), hash, len); - - if (zv == NULL) { - php_error_docref(0, E_WARNING, "Invalid persistent resource"); - return NULL; - } - - return zv; -} - -// ================================================== -// Argument API: -// ================================================== - -zend_internal_arg_info -phper_zend_begin_arg_info_ex(bool return_reference, - uintptr_t required_num_args) { -#define static -#define const - ZEND_BEGIN_ARG_INFO_EX(info, 0, return_reference, required_num_args) - ZEND_END_ARG_INFO() - return info[0]; -#undef static -#undef const -} - -zend_internal_arg_info phper_zend_arg_info(bool pass_by_ref, const char *name) { - zend_internal_arg_info info[] = {ZEND_ARG_INFO(pass_by_ref, )}; - info[0].name = name; - return info[0]; -} - -// zend_internal_arg_info phper_zend_arg_info( -// const char *name, int32_t type, bool return_reference, bool is_variadic, -// bool is_tentative, uintptr_t required_num_args) { -// zend_internal_arg_info info = { -// .name = name, -// .type = ZEND_TYPE_INIT_MASK( -// type | -// _ZEND_ARG_INFO_FLAGS(return_reference, is_variadic, -// is_tentative)), -// }; - -// return info; -// } diff --git a/phper-sys/c/php_zval.c b/phper-sys/c/php_zval.c index 948a0dbe..cad39018 100644 --- a/phper-sys/c/php_zval.c +++ b/phper-sys/c/php_zval.c @@ -1,9 +1,5 @@ #include -// ================================================== -// zval apis: -// ================================================== - const zend_long *phper_z_lval_p(const zval *zv) { return &(Z_LVAL_P(zv)); } diff --git a/phper-sys/include/phper.h b/phper-sys/include/phper.h index ac827061..0ed4624d 100644 --- a/phper-sys/include/phper.h +++ b/phper-sys/include/phper.h @@ -70,6 +70,15 @@ void phper_separate_array(zval *zv); void phper_separate_string(zval *zv); void phper_separate_zval(zval *zv); +// ================================================== +// constants apis: +// ================================================== +zend_result phper_register_constant(zend_constant *constant, int flags, + int module_number); + +zend_constant phper_create_constant(const char *name, size_t name_len, zval val, + int flags); + // ================================================== // string apis: // ================================================== @@ -192,7 +201,8 @@ bool phper_zend_str_exists(HashTable *ht, const char *str, size_t len); // object apis: // ================================================== -zval *phper_get_this(const zend_execute_data *execute_data); +const zval *phper_get_this(const zend_execute_data *execute_data); +zval *phper_get_this_mut(zend_execute_data *execute_data); size_t phper_zend_object_properties_size(const zend_class_entry *ce); void *phper_zend_object_alloc(size_t obj_size, const zend_class_entry *ce); bool phper_object_init_ex(zval *arg, const zend_class_entry *class_type); @@ -202,11 +212,10 @@ uint32_t phper_zend_object_gc_refcount(const zend_object *obj); // ================================================== // class apis: // ================================================== -zend_class_entry phper_init_class_entry(const char *class_name, - size_t class_name_len); zend_class_entry * -phper_register_class_entry(zend_class_entry *ce, zend_class_entry *parent, - const zend_function_entry *functions); +phper_register_class_entry(zend_class_entry *(*create_ce)(void), + const zend_function_entry *functions, + zend_object *(*create_object)(zend_class_entry *)); // ================================================== // interface apis: @@ -226,6 +235,10 @@ bool phper_call_user_function(zval *object, zval *function_name, zval *retval_ptr, const zval *params, uint32_t param_count, const HashTable *named_params); + +// ================================================== +// zend params api: +// ================================================== const zval *phper_zend_call_var_num(const zend_execute_data *execute_data, int index); const zval *phper_zend_call_arg(const zend_execute_data *execute_data, @@ -251,22 +264,4 @@ int phper_zend_register_list_destructors_ex(const rsrc_dtor_func_t dtor, int module_number); int phper_zend_fetch_list_dtor_id(const char *name); const zend_resource *phper_register_persistent_find(const char *hash, - size_t len); - -// ================================================== -// Argument API: -// ================================================== - -zend_internal_arg_info -phper_zend_begin_arg_info_ex(bool return_reference, - uintptr_t required_num_args); -zend_internal_arg_info phper_zend_arg_info(bool pass_by_ref, const char *name); - -// ================================================== -// Constants API: -// ================================================== -zend_constant phper_create_constant(const char *name, size_t name_len, zval val, - int flags); - -zend_result phper_register_constant(zend_constant *constant, int flags, - int module_number); \ No newline at end of file + size_t len); \ No newline at end of file diff --git a/phper-sys/src/lib.rs b/phper-sys/src/lib.rs index ff9297e2..69b1b948 100644 --- a/phper-sys/src/lib.rs +++ b/phper-sys/src/lib.rs @@ -15,6 +15,5 @@ #![allow(non_snake_case)] #![allow(deref_nullptr)] #![allow(clippy::all)] -#![doc = include_str!("../README.md")] include!(concat!(env!("OUT_DIR"), "/php_bindings.rs")); diff --git a/phper/Cargo.toml b/phper/Cargo.toml index cc6b9e46..6ec2ed2b 100644 --- a/phper/Cargo.toml +++ b/phper/Cargo.toml @@ -22,15 +22,17 @@ repository = { workspace = true } license = { workspace = true } [dependencies] -derive_more = "0.99.17" -indexmap = "2.0.0" -once_cell = "1.18.0" +derive_more = { version = "1.0.0-beta.6", features = ["from", "constructor"] } +once_cell = "1.19.0" +thiserror = "1.0.61" +bitflags = "2.5.0" +memoffset = "0.9.1" +smallvec = { version = "1.13.2", features = ["const_generics", "const_new", "union"] } +paste = "^1.0.15" + phper-alloc = { workspace = true } phper-macros = { workspace = true } phper-sys = { workspace = true } -thiserror = "1.0.43" -bitflags = { version = "2.4.1" } -memoffset = { version = "0.9" } [build-dependencies] phper-build = { workspace = true } diff --git a/phper/build.rs b/phper/build.rs index 5b379c71..a024aa00 100644 --- a/phper/build.rs +++ b/phper/build.rs @@ -8,14 +8,6 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper_sys::*; - fn main() { phper_build::register_all(); - - assert_eq!( - USING_ZTS, 0, - "PHPER not support ZTS mode now (php built with `--enable-maintainer-zts` or \ - `--enable-zts`)." - ); } diff --git a/phper/src/arrays.rs b/phper/src/arrays.rs deleted file mode 100644 index d167f298..00000000 --- a/phper/src/arrays.rs +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -//! Apis relate to [zend_array]. - -use crate::{alloc::ToRefOwned, strings::ZStr, sys::*, values::ZVal}; -use derive_more::From; -use std::{ - borrow::Borrow, - convert::TryInto, - fmt::{self, Debug}, - marker::PhantomData, - mem::ManuallyDrop, - ops::{Deref, DerefMut}, - ptr::null_mut, -}; - -/// Key for [ZArr]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum Key<'a> { - /// Index type key. - Index(u64), - /// String type key. - Str(&'a str), - /// String type key. - Bytes(&'a [u8]), - /// String type key. - ZStr(&'a ZStr), -} - -/// Insert key for [ZArr]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum InsertKey<'a> { - /// Insert with next index type key, like `$farr[] = "oo"` in PHP. - NextIndex, - /// Insert with index type key, like `$farr[0] = "oo"` in PHP. - Index(u64), - /// Insert with string type key, like `$farr["string"] = "oo"` in PHP. - Str(&'a str), - /// Insert with string type key. - Bytes(&'a [u8]), - /// Insert with string type key. - ZStr(&'a ZStr), -} - -impl<'a> From> for InsertKey<'a> { - fn from(k: Key<'a>) -> Self { - match k { - Key::Index(i) => InsertKey::Index(i), - Key::Str(s) => InsertKey::Str(s), - Key::Bytes(b) => InsertKey::Bytes(b), - Key::ZStr(s) => InsertKey::ZStr(s), - } - } -} - -/// Wrapper of [zend_array]. -#[repr(transparent)] -pub struct ZArr { - inner: zend_array, - _p: PhantomData<*mut ()>, -} - -impl ZArr { - /// Wraps a raw pointer. - /// - /// # Safety - /// - /// Create from raw pointer. - /// - /// # Panics - /// - /// Panics if pointer is null. - #[inline] - pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self { - (ptr as *const Self).as_ref().expect("ptr should't be null") - } - - /// Wraps a raw pointer, return None if pointer is null. - /// - /// # Safety - /// - /// Create from raw pointer. - #[inline] - pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> { - (ptr as *const Self).as_ref() - } - - /// Wraps a raw pointer. - /// - /// # Safety - /// - /// Create from raw pointer. - /// - /// # Panics - /// - /// Panics if pointer is null. - #[inline] - pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self { - (ptr as *mut Self).as_mut().expect("ptr should't be null") - } - - /// Wraps a raw pointer, return None if pointer is null. - /// - /// # Safety - /// - /// Create from raw pointer. - #[inline] - pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> { - (ptr as *mut Self).as_mut() - } - - /// Returns a raw pointer wrapped. - pub const fn as_ptr(&self) -> *const zend_array { - &self.inner - } - - /// Returns a raw pointer wrapped. - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut zend_array { - &mut self.inner - } - - /// Returns true if the array has a length of 0. - #[inline] - pub fn is_empty(&mut self) -> bool { - self.len() == 0 - } - - /// Get array items length. - #[inline] - pub fn len(&mut self) -> usize { - unsafe { zend_array_count(self.as_mut_ptr()).try_into().unwrap() } - } - - /// Add or update item by key. - #[allow(clippy::useless_conversion)] - pub fn insert<'a>(&mut self, key: impl Into>, value: impl Into) { - let key = key.into(); - let mut value = ManuallyDrop::new(value.into()); - let val = value.as_mut_ptr(); - - unsafe { - match key { - InsertKey::NextIndex => { - phper_zend_hash_next_index_insert(self.as_mut_ptr(), val); - } - InsertKey::Index(i) => { - phper_zend_hash_index_update(self.as_mut_ptr(), i, val); - } - InsertKey::Str(s) => { - phper_zend_str_update( - self.as_mut_ptr(), - s.as_ptr().cast(), - s.len().try_into().unwrap(), - val, - ); - } - InsertKey::Bytes(b) => { - phper_zend_str_update( - self.as_mut_ptr(), - b.as_ptr().cast(), - b.len().try_into().unwrap(), - val, - ); - } - InsertKey::ZStr(s) => { - phper_zend_str_update( - self.as_mut_ptr(), - s.as_c_str_ptr().cast(), - s.len().try_into().unwrap(), - val, - ); - } - } - } - } - - /// Get item by key. - pub fn get<'a>(&self, key: impl Into>) -> Option<&'a ZVal> { - self.inner_get(key).map(|v| &*v) - } - - /// Get item by key. - pub fn get_mut<'a>(&mut self, key: impl Into>) -> Option<&'a mut ZVal> { - self.inner_get(key) - } - - #[allow(clippy::useless_conversion)] - fn inner_get<'a>(&self, key: impl Into>) -> Option<&'a mut ZVal> { - let key = key.into(); - let ptr = self.as_ptr() as *mut _; - unsafe { - let value = match key { - Key::Index(i) => phper_zend_hash_index_find(ptr, i), - Key::Str(s) => { - phper_zend_str_find(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) - } - Key::Bytes(b) => { - phper_zend_str_find(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) - } - Key::ZStr(s) => { - phper_zend_str_find(ptr, s.as_c_str_ptr(), s.len().try_into().unwrap()) - } - }; - if value.is_null() { - None - } else { - Some(ZVal::from_mut_ptr(value)) - } - } - } - - /// Check if the key exists. - #[allow(clippy::useless_conversion)] - pub fn exists<'a>(&self, key: impl Into>) -> bool { - let key = key.into(); - let ptr = self.as_ptr() as *mut _; - unsafe { - match key { - Key::Index(i) => phper_zend_hash_index_exists(ptr, i), - Key::Str(s) => { - phper_zend_str_exists(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) - } - Key::Bytes(b) => { - phper_zend_str_exists(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) - } - Key::ZStr(s) => phper_zend_str_exists( - ptr, - s.to_bytes().as_ptr().cast(), - s.len().try_into().unwrap(), - ), - } - } - } - - /// Remove the item under the key - #[allow(clippy::useless_conversion)] - pub fn remove<'a>(&mut self, key: impl Into>) -> bool { - let key = key.into(); - unsafe { - match key { - Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i), - Key::Str(s) => phper_zend_str_del( - &mut self.inner, - s.as_ptr().cast(), - s.len().try_into().unwrap(), - ), - Key::Bytes(b) => phper_zend_str_del( - &mut self.inner, - b.as_ptr().cast(), - b.len().try_into().unwrap(), - ), - Key::ZStr(s) => phper_zend_str_del( - &mut self.inner, - s.as_c_str_ptr().cast(), - s.len().try_into().unwrap(), - ), - } - } - } - - /// Gets the given key’s corresponding entry in the array for in-place - /// manipulation. - /// - /// # Examples - /// - /// ```no_run - /// use phper::arrays::ZArray; - /// - /// let mut arr = ZArray::new(); - /// - /// // count the number of occurrences of letters in the vec - /// for x in ["a", "b", "a", "c", "a", "b"] { - /// arr.entry(x) - /// .and_modify(|cur| *cur.as_mut_long().unwrap() += 1) - /// .or_insert(1); - /// } - /// ``` - pub fn entry<'a>(&'a mut self, key: impl Into>) -> Entry<'a> { - let key = key.into(); - match self.get_mut(key.clone()) { - Some(val) => Entry::Occupied(OccupiedEntry(val)), - None => Entry::Vacant(VacantEntry { arr: self, key }), - } - } - - /// Provides a forward iterator. - #[inline] - pub fn iter(&self) -> Iter<'_> { - Iter::new(self) - } - - /// Provides a forward iterator with mutable references. - #[inline] - pub fn iter_mut(&mut self) -> IterMut<'_> { - IterMut::new(self) - } -} - -impl Debug for ZArr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - common_fmt(self, f, "ZArr") - } -} - -impl ToOwned for ZArr { - type Owned = ZArray; - - fn to_owned(&self) -> Self::Owned { - unsafe { - // TODO The source really immutable? - let dest = phper_zend_array_dup(self.as_ptr() as *mut _); - ZArray::from_raw(dest) - } - } -} - -impl ToRefOwned for ZArr { - type Owned = ZArray; - - fn to_ref_owned(&mut self) -> Self::Owned { - let mut val = ManuallyDrop::new(ZVal::default()); - unsafe { - phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr()); - phper_z_addref_p(val.as_mut_ptr()); - ZArray::from_raw(val.as_mut_z_arr().unwrap().as_mut_ptr()) - } - } -} - -/// Wrapper of [zend_array]. -#[repr(transparent)] -pub struct ZArray { - inner: *mut ZArr, -} - -impl ZArray { - /// Creates an empty `ZArray`. - #[inline] - pub fn new() -> Self { - Self::with_capacity(0) - } - - /// Creates an empty `ZArray` with at least the specified capacity. - /// - /// Note that the actual capacity is always a power of two, so if you have - /// 12 elements in a hashtable the actual table capacity will be 16. - pub fn with_capacity(n: usize) -> Self { - unsafe { - let ptr = phper_zend_new_array(n.try_into().unwrap()); - Self::from_raw(ptr) - } - } - - /// Create owned object From raw pointer, usually used in pairs with - /// `into_raw`. - /// - /// # Safety - /// - /// This function is unsafe because improper use may lead to memory - /// problems. For example, a double-free may occur if the function is called - /// twice on the same raw pointer. - #[inline] - pub unsafe fn from_raw(ptr: *mut zend_array) -> Self { - Self { - inner: ZArr::from_mut_ptr(ptr), - } - } - - /// Consumes the `ZArray` and transfers ownership to a raw pointer. - /// - /// Failure to call [`ZArray::from_raw`] will lead to a memory leak. - #[inline] - pub fn into_raw(self) -> *mut zend_array { - ManuallyDrop::new(self).as_mut_ptr() - } -} - -impl Debug for ZArray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - common_fmt(self, f, "ZArray") - } -} - -impl Default for ZArray { - fn default() -> Self { - Self::new() - } -} - -impl Deref for ZArray { - type Target = ZArr; - - fn deref(&self) -> &Self::Target { - unsafe { self.inner.as_ref().unwrap() } - } -} - -impl DerefMut for ZArray { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.inner.as_mut().unwrap() } - } -} - -impl Borrow for ZArray { - fn borrow(&self) -> &ZArr { - self.deref() - } -} - -impl Clone for ZArray { - fn clone(&self) -> Self { - self.deref().to_owned() - } -} - -impl Drop for ZArray { - fn drop(&mut self) { - unsafe { - zend_array_destroy(self.as_mut_ptr()); - } - } -} - -/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum IterKey<'a> { - /// Index type iterator key. - Index(u64), - /// String type iterator key. - ZStr(&'a ZStr), -} - -struct RawIter<'a> { - arr: *mut zend_array, - pos: HashPosition, - finished: bool, - _p: PhantomData<&'a ()>, -} - -impl<'a> RawIter<'a> { - fn new(arr: *mut zend_array) -> Self { - let mut pos: HashPosition = 0; - unsafe { - zend_hash_internal_pointer_reset_ex(arr, &mut pos); - } - Self { - arr, - pos, - finished: false, - _p: PhantomData, - } - } -} - -impl<'a> Iterator for RawIter<'a> { - type Item = (IterKey<'a>, *mut zval); - - fn next(&mut self) -> Option { - unsafe { - if self.finished { - return None; - } - - let mut str_index: *mut zend_string = null_mut(); - let mut num_index: zend_ulong = 0; - - #[allow(clippy::unnecessary_mut_passed)] - let result = zend_hash_get_current_key_ex( - self.arr, - &mut str_index, - &mut num_index, - &mut self.pos, - ) as u32; - - let iter_key = if result == HASH_KEY_IS_STRING { - IterKey::ZStr(ZStr::from_mut_ptr(str_index)) - } else if result == HASH_KEY_IS_LONG { - #[allow(clippy::unnecessary_cast)] - IterKey::Index(num_index as u64) - } else { - self.finished = true; - return None; - }; - - let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos); - if val.is_null() { - self.finished = true; - return None; - } - - if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE { - self.finished = true; - } - - Some((iter_key, val)) - } - } -} - -/// An iterator over the elements of a `ZArr`. -/// -/// This is created by [`iter`]. -/// -/// [`iter`]: ZArr::iter -pub struct Iter<'a>(RawIter<'a>); - -impl<'a> Iter<'a> { - fn new(arr: &'a ZArr) -> Self { - Self(RawIter::new(arr.as_ptr() as *mut _)) - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = (IterKey<'a>, &'a ZVal); - - fn next(&mut self) -> Option { - self.0 - .next() - .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) })) - } -} - -/// An mutable iterator over the elements of a `ZArr`. -/// -/// This is created by [`iter_mut`]. -/// -/// [`iter_mut`]: ZArr::iter_mut -pub struct IterMut<'a>(RawIter<'a>); - -impl<'a> IterMut<'a> { - fn new(arr: &'a mut ZArr) -> Self { - Self(RawIter::new(arr.as_mut_ptr())) - } -} - -impl<'a> Iterator for IterMut<'a> { - type Item = (IterKey<'a>, &'a mut ZVal); - - fn next(&mut self) -> Option { - self.0 - .next() - .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) })) - } -} - -/// A view into a single entry in an array, which may either be vacant or -/// occupied. -/// -/// This `enum` is constructed from the [`entry`] method on [`ZArr`]. -/// -/// [`entry`]: ZArr::entry -pub enum Entry<'a> { - /// An occupied entry. - Occupied(OccupiedEntry<'a>), - /// A vacant entry. - Vacant(VacantEntry<'a>), -} - -/// A view into an occupied entry in a `ZArr`. -/// It is part of the [`Entry`] enum. -pub struct OccupiedEntry<'a>(&'a mut ZVal); - -/// A view into a vacant entry in a `ZArr`. -/// It is part of the [`Entry`] enum. -pub struct VacantEntry<'a> { - arr: &'a mut ZArr, - key: Key<'a>, -} - -impl<'a> Entry<'a> { - /// Provides in-place mutable access to an occupied entry before any - /// potential inserts into the array. - pub fn and_modify(self, f: F) -> Self - where - F: FnOnce(&mut ZVal), - { - match self { - Entry::Occupied(entry) => { - f(entry.0); - Entry::Occupied(entry) - } - entry => entry, - } - } - - /// Ensures a value is in the entry by inserting the default if empty, and - /// returns a mutable reference to the value in the entry. - pub fn or_insert(self, val: impl Into) -> &'a mut ZVal { - match self { - Entry::Occupied(entry) => entry.0, - Entry::Vacant(entry) => { - let insert_key: InsertKey<'_> = entry.key.clone().into(); - entry.arr.insert(insert_key, val); - entry.arr.get_mut(entry.key).unwrap() - } - } - } -} - -fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result { - struct Debugger<'a>(&'a ZArr); - - impl Debug for Debugger<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entries(self.0.iter()).finish() - } - } - - let zd = Debugger(this); - - f.debug_tuple(name).field(&zd).finish() -} diff --git a/phper/src/arrays/entry.rs b/phper/src/arrays/entry.rs new file mode 100644 index 00000000..14e08ecb --- /dev/null +++ b/phper/src/arrays/entry.rs @@ -0,0 +1,56 @@ +use crate::arrays::{InsertKey, Key, ZArr}; +use crate::values::ZVal; + +/// A view into a single entry in an array, which may either be vacant or +/// occupied. +/// +/// This `enum` is constructed from the [`entry`] method on [`ZArr`]. +/// +/// [`entry`]: ZArr::entry +pub enum Entry<'a> { + /// An occupied entry. + Occupied(OccupiedEntry<'a>), + /// A vacant entry. + Vacant(VacantEntry<'a>), +} + +/// A view into an occupied entry in a `ZArr`. +/// It is part of the [`Entry`] enum. +pub struct OccupiedEntry<'a>(pub(super) &'a mut ZVal); + +/// A view into a vacant entry in a `ZArr`. +/// It is part of the [`Entry`] enum. +pub struct VacantEntry<'a> { + pub(super) arr: &'a mut ZArr, + pub(super) key: Key<'a>, +} + +impl<'a> Entry<'a> { + /// Provides in-place mutable access to an occupied entry before any + /// potential inserts into the array. + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(&mut ZVal), + { + match self { + Entry::Occupied(entry) => { + f(entry.0); + Entry::Occupied(entry) + } + entry => entry, + } + } + + /// Ensures a value is in the entry by inserting the default if empty, and + /// returns a mutable reference to the value in the entry. + pub fn or_insert(self, val: impl Into) -> &'a mut ZVal { + match self { + Entry::Occupied(entry) => entry.0, + Entry::Vacant(entry) => { + let insert_key: InsertKey<'_> = entry.key.clone().into(); + entry.arr.insert(insert_key, val); + entry.arr.get_mut(entry.key).unwrap() + } + } + } +} diff --git a/phper/src/arrays/iterators.rs b/phper/src/arrays/iterators.rs new file mode 100644 index 00000000..78333084 --- /dev/null +++ b/phper/src/arrays/iterators.rs @@ -0,0 +1,130 @@ +use derive_more::From; +use phper_sys::{ + zend_array, zend_hash_get_current_data_ex, zend_hash_get_current_key_ex, + zend_hash_internal_pointer_reset_ex, zend_hash_move_forward_ex, zend_string, zend_ulong, zval, + HashPosition, HASH_KEY_IS_LONG, HASH_KEY_IS_STRING, ZEND_RESULT_CODE_FAILURE, +}; +use std::marker::PhantomData; +use std::ptr::null_mut; + +use crate::arrays::ZArr; +use crate::strings::ZStr; +use crate::values::ZVal; + +/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum IterKey<'a> { + /// Index type iterator key. + Index(u64), + /// String type iterator key. + ZStr(&'a ZStr), +} + +/// An iterator over the elements of a `ZArr`. +/// +/// This is created by [`iter`]. +/// +/// [`iter`]: ZArr::iter +pub struct Iter<'a>(RawIter<'a>); + +impl<'a> Iter<'a> { + pub(super) fn new(arr: &'a ZArr) -> Self { + Self(RawIter::new(arr.as_ptr() as *mut _)) + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (IterKey<'a>, &'a ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) })) + } +} + +/// An mutable iterator over the elements of a `ZArr`. +/// +/// This is created by [`iter_mut`]. +/// +/// [`iter_mut`]: ZArr::iter_mut +pub struct IterMut<'a>(RawIter<'a>); + +impl<'a> IterMut<'a> { + pub(super) fn new(arr: &'a mut ZArr) -> Self { + Self(RawIter::new(arr.as_mut_ptr())) + } +} + +impl<'a> Iterator for IterMut<'a> { + type Item = (IterKey<'a>, &'a mut ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) })) + } +} + +struct RawIter<'a> { + arr: *mut zend_array, + pos: HashPosition, + finished: bool, + _p: PhantomData<&'a ()>, +} + +impl<'a> RawIter<'a> { + fn new(arr: *mut zend_array) -> Self { + let mut pos: HashPosition = 0; + unsafe { + zend_hash_internal_pointer_reset_ex(arr, &mut pos); + } + + Self { + arr, + pos, + finished: false, + _p: PhantomData, + } + } +} + +impl<'a> Iterator for RawIter<'a> { + type Item = (IterKey<'a>, *mut zval); + + fn next(&mut self) -> Option { + unsafe { + if self.finished { + return None; + } + + let mut str_index: *mut zend_string = null_mut(); + let mut num_index: zend_ulong = 0; + + let result = + zend_hash_get_current_key_ex(self.arr, &mut str_index, &mut num_index, &self.pos) + as u32; + + let iter_key = if result == HASH_KEY_IS_STRING { + IterKey::ZStr(ZStr::from_mut_ptr(str_index)) + } else if result == HASH_KEY_IS_LONG { + IterKey::Index(num_index as u64) + } else { + self.finished = true; + return None; + }; + + let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos); + if val.is_null() { + self.finished = true; + return None; + } + + if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE { + self.finished = true; + } + + Some((iter_key, val)) + } + } +} diff --git a/phper/src/arrays/keys.rs b/phper/src/arrays/keys.rs new file mode 100644 index 00000000..c6c00c9d --- /dev/null +++ b/phper/src/arrays/keys.rs @@ -0,0 +1,41 @@ +use crate::strings::ZStr; +use derive_more::From; + +/// Key for [ZArr]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum Key<'a> { + /// Index type key. + Index(u64), + /// String type key. + Str(&'a str), + /// String type key. + Bytes(&'a [u8]), + /// String type key. + ZStr(&'a ZStr), +} + +/// Insert key for [ZArr]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum InsertKey<'a> { + /// Insert with next index type key, like `$farr[] = "oo"` in PHP. + NextIndex, + /// Insert with index type key, like `$farr[0] = "oo"` in PHP. + Index(u64), + /// Insert with string type key, like `$farr["string"] = "oo"` in PHP. + Str(&'a str), + /// Insert with string type key. + Bytes(&'a [u8]), + /// Insert with zend string type key. + ZStr(&'a ZStr), +} + +impl<'a> From> for InsertKey<'a> { + fn from(k: Key<'a>) -> Self { + match k { + Key::Index(i) => InsertKey::Index(i), + Key::Str(s) => InsertKey::Str(s), + Key::Bytes(b) => InsertKey::Bytes(b), + Key::ZStr(s) => InsertKey::ZStr(s), + } + } +} diff --git a/phper/src/arrays/mod.rs b/phper/src/arrays/mod.rs new file mode 100644 index 00000000..22b85984 --- /dev/null +++ b/phper/src/arrays/mod.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 PHPER Framework Team +// PHPER is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! Apis relate to [zend_array]. + +mod entry; +mod iterators; +mod keys; +mod zarr; +mod zarray; + +use std::fmt::{self, Debug}; + +pub use iterators::{Iter, IterKey, IterMut}; +pub use keys::{InsertKey, Key}; +pub use zarr::ZArr; +pub use zarray::ZArray; + +fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result { + struct Debugger<'a>(&'a ZArr); + + impl Debug for Debugger<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.0.iter()).finish() + } + } + + let zd = Debugger(this); + + f.debug_tuple(name).field(&zd).finish() +} diff --git a/phper/src/arrays/zarr.rs b/phper/src/arrays/zarr.rs new file mode 100644 index 00000000..be08d260 --- /dev/null +++ b/phper/src/arrays/zarr.rs @@ -0,0 +1,278 @@ +use crate::arrays::entry::{Entry, OccupiedEntry, VacantEntry}; +use crate::arrays::zarray::ZArray; +use crate::arrays::{common_fmt, InsertKey, Iter, IterMut, Key}; +use crate::values::ZVal; +use phper_alloc::ToRefOwned; +use phper_sys::{ + phper_z_addref_p, phper_zend_array_dup, phper_zend_hash_index_del, + phper_zend_hash_index_exists, phper_zend_hash_index_find, phper_zend_hash_index_update, + phper_zend_hash_next_index_insert, phper_zend_str_del, phper_zend_str_exists, + phper_zend_str_find, phper_zend_str_update, phper_zval_arr, zend_array, zend_array_count, +}; +use std::fmt; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +/// Wrapper of [zend_array]. +#[repr(transparent)] +pub struct ZArr { + inner: zend_array, + _p: PhantomData<*mut ()>, +} + +impl ZArr { + /// Wraps a raw pointer. + /// + /// # Safety + /// + /// Create from raw pointer. + /// + /// # Panics + /// + /// Panics if pointer is null. + #[inline] + pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self { + (ptr as *const Self) + .as_ref() + .expect("ptr shouldn't be null") + } + + /// Wraps a raw pointer, return None if pointer is null. + /// + /// # Safety + /// + /// Create from raw pointer. + #[inline] + pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> { + (ptr as *const Self).as_ref() + } + + /// Wraps a raw pointer. + /// + /// # Safety + /// + /// Create from raw pointer. + /// + /// # Panics + /// + /// Panics if pointer is null. + #[inline] + pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self { + (ptr as *mut Self).as_mut().expect("ptr should't be null") + } + + /// Wraps a raw pointer, return None if pointer is null. + /// + /// # Safety + /// + /// Create from raw pointer. + #[inline] + pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> { + (ptr as *mut Self).as_mut() + } + + /// Returns a raw pointer wrapped. + #[inline] + pub const fn as_ptr(&self) -> *const zend_array { + &self.inner + } + + /// Returns a raw pointer wrapped. + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut zend_array { + &mut self.inner + } + + /// Returns true if the array has a length of 0. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get array items length. + #[inline] + pub fn len(&self) -> usize { + unsafe { zend_array_count(self.as_ptr() as *mut _) as usize } + } + + /// Add or update item by key. + pub fn insert<'a>(&mut self, key: impl Into>, value: impl Into) { + let key = key.into(); + let mut value = ManuallyDrop::new(value.into()); + let val = value.as_mut_ptr(); + + unsafe { + match key { + InsertKey::NextIndex => { + phper_zend_hash_next_index_insert(self.as_mut_ptr(), val); + } + InsertKey::Index(i) => { + phper_zend_hash_index_update(self.as_mut_ptr(), i, val); + } + InsertKey::Str(s) => { + phper_zend_str_update(self.as_mut_ptr(), s.as_ptr().cast(), s.len(), val); + } + InsertKey::Bytes(b) => { + phper_zend_str_update(self.as_mut_ptr(), b.as_ptr().cast(), b.len(), val); + } + InsertKey::ZStr(s) => { + phper_zend_str_update(self.as_mut_ptr(), s.as_c_str_ptr().cast(), s.len(), val); + } + } + } + } + + /// Get item by key. + #[inline] + pub fn get<'a>(&self, key: impl Into>) -> Option<&'a ZVal> { + self.inner_get(key).map(|v| &*v) + } + + /// Get item by key. + #[inline] + pub fn get_mut<'a>(&mut self, key: impl Into>) -> Option<&'a mut ZVal> { + self.inner_get(key) + } + + fn inner_get<'a>(&self, key: impl Into>) -> Option<&'a mut ZVal> { + let key = key.into(); + let ptr = self.as_ptr() as *mut _; + unsafe { + let value = match key { + Key::Index(i) => phper_zend_hash_index_find(ptr, i), + Key::Str(s) => phper_zend_str_find(ptr, s.as_ptr().cast(), s.len()), + Key::Bytes(b) => phper_zend_str_find(ptr, b.as_ptr().cast(), b.len()), + Key::ZStr(s) => phper_zend_str_find(ptr, s.as_c_str_ptr(), s.len()), + }; + if value.is_null() { + None + } else { + Some(ZVal::from_mut_ptr(value)) + } + } + } + + /// Check if the key exists. + pub fn exists<'a>(&self, key: impl Into>) -> bool { + let key = key.into(); + let ptr = self.as_ptr() as *mut _; + unsafe { + match key { + Key::Index(i) => phper_zend_hash_index_exists(ptr, i), + Key::Str(s) => phper_zend_str_exists(ptr, s.as_ptr().cast(), s.len()), + Key::Bytes(b) => phper_zend_str_exists(ptr, b.as_ptr().cast(), b.len()), + Key::ZStr(s) => phper_zend_str_exists(ptr, s.to_bytes().as_ptr().cast(), s.len()), + } + } + } + + /// Remove the item under the key + pub fn remove<'a>(&mut self, key: impl Into>) -> bool { + let key = key.into(); + unsafe { + match key { + Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i), + Key::Str(s) => phper_zend_str_del(&mut self.inner, s.as_ptr().cast(), s.len()), + Key::Bytes(b) => phper_zend_str_del(&mut self.inner, b.as_ptr().cast(), b.len()), + Key::ZStr(s) => { + phper_zend_str_del(&mut self.inner, s.as_c_str_ptr().cast(), s.len()) + } + } + } + } + + /// Gets the given key’s corresponding entry in the array for in-place + /// manipulation. + /// + /// # Examples + /// + /// ```no_run + /// use phper::arrays::ZArray; + /// + /// let mut arr = ZArray::new(); + /// + /// // count the number of occurrences of letters in the vec + /// for x in ["a", "b", "a", "c", "a", "b"] { + /// arr.entry(x) + /// .and_modify(|cur| *cur.as_mut_long().unwrap() += 1) + /// .or_insert(1); + /// } + /// ``` + #[inline] + pub fn entry<'a>(&'a mut self, key: impl Into>) -> Entry<'a> { + let key = key.into(); + match self.get_mut(key.clone()) { + Some(val) => Entry::Occupied(OccupiedEntry(val)), + None => Entry::Vacant(VacantEntry { arr: self, key }), + } + } + + /// Provides a forward iterator. + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter::new(self) + } + + /// Provides a forward iterator with mutable references. + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + IterMut::new(self) + } + + /// Gets Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + #[allow(clippy::missing_safety_doc)] + pub unsafe fn as_slice(&self) -> &[ZVal] { + std::slice::from_raw_parts( + self.inner.__bindgen_anon_1.arPacked as *const ZVal, + self.len(), + ) + } + + /// Gets Mutable Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + #[allow(clippy::missing_safety_doc)] + pub unsafe fn as_mut_slice(&mut self) -> &mut [ZVal] { + std::slice::from_raw_parts_mut( + self.inner.__bindgen_anon_1.arPacked as *mut ZVal, + self.len(), + ) + } +} + +impl Debug for ZArr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + common_fmt(self, f, "ZArr") + } +} + +impl ToOwned for ZArr { + type Owned = ZArray; + + fn to_owned(&self) -> Self::Owned { + unsafe { + let dest = phper_zend_array_dup(self.as_ptr() as *mut _); + ZArray::from_raw(dest) + } + } +} + +impl ToRefOwned for ZArr { + type Owned = ZArray; + + fn to_ref_owned(&mut self) -> Self::Owned { + let mut val = ManuallyDrop::new(ZVal::default()); + unsafe { + phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr()); + phper_z_addref_p(val.as_mut_ptr()); + ZArray::from_raw(val.as_mut_z_arr().unwrap().as_mut_ptr()) + } + } +} diff --git a/phper/src/arrays/zarray.rs b/phper/src/arrays/zarray.rs new file mode 100644 index 00000000..43e573e0 --- /dev/null +++ b/phper/src/arrays/zarray.rs @@ -0,0 +1,123 @@ +use crate::arrays::{common_fmt, ZArr}; +use crate::values::ZVal; +use phper_sys::{phper_z_arr_p, phper_zend_new_array, zend_array, zend_array_destroy}; +use std::borrow::Borrow; +use std::fmt; +use std::fmt::Debug; +use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; + +/// Wrapper of [zend_array]. +#[repr(transparent)] +pub struct ZArray(*mut ZArr); + +impl ZArray { + /// Creates an empty `ZArray`. + #[inline] + pub fn new() -> Self { + Self::with_capacity(0) + } + + /// Creates an empty `ZArray` with at least the specified capacity. + /// + /// Note that the actual capacity is always a power of two, so if you have + /// 12 elements in a hashtable the actual table capacity will be 16. + pub fn with_capacity(n: usize) -> Self { + unsafe { + let ptr = phper_zend_new_array(n.try_into().unwrap()); + Self::from_raw(ptr) + } + } + + /// Create owned object From raw pointer, usually used in pairs with + /// `into_raw`. + /// + /// # Safety + /// + /// This function is unsafe because improper use may lead to memory + /// problems. For example, a double-free may occur if the function is called + /// twice on the same raw pointer. + #[inline] + pub unsafe fn from_raw(ptr: *mut zend_array) -> Self { + Self(ZArr::from_mut_ptr(ptr)) + } + + /// Consumes the `ZArray` and transfers ownership to a raw pointer. + /// + /// Failure to call [`ZArray::from_raw`] will lead to a memory leak. + #[inline] + pub fn into_raw(self) -> *mut zend_array { + ManuallyDrop::new(self).as_mut_ptr() + } + + pub(crate) unsafe fn from_zval(value: ZVal) -> Self { + let value = ManuallyDrop::new(value); + ZArray::from_raw(phper_z_arr_p(value.as_ptr()) as *mut zend_array) + } + + /// Gets Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + #[allow(clippy::missing_safety_doc)] + pub unsafe fn as_slice(&self) -> &[ZVal] { + unsafe { &*self.0 }.as_slice() + } + + /// Gets Mutable Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + #[allow(clippy::missing_safety_doc)] + pub unsafe fn as_mut_slice(&mut self) -> &mut [ZVal] { + unsafe { &mut *self.0 }.as_mut_slice() + } +} + +impl Debug for ZArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + common_fmt(self, f, "ZArray") + } +} + +impl Default for ZArray { + fn default() -> Self { + Self::new() + } +} + +impl Deref for ZArray { + type Target = ZArr; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref().unwrap() } + } +} + +impl DerefMut for ZArray { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.0.as_mut().unwrap() } + } +} + +impl Borrow for ZArray { + fn borrow(&self) -> &ZArr { + self.deref() + } +} + +impl Clone for ZArray { + fn clone(&self) -> Self { + self.deref().to_owned() + } +} + +impl Drop for ZArray { + fn drop(&mut self) { + unsafe { + zend_array_destroy(self.as_mut_ptr()); + } + } +} diff --git a/phper/src/classes/entity.rs b/phper/src/classes/entity.rs index 654f4ca7..78aa7113 100644 --- a/phper/src/classes/entity.rs +++ b/phper/src/classes/entity.rs @@ -1,54 +1,46 @@ -use std::{any::Any, marker::PhantomData, mem::zeroed, ptr::null_mut, rc::Rc}; +use std::{any::Any, marker::PhantomData}; -use phper_sys::{ - phper_init_class_entry, phper_register_class_entry, zend_class_entry, zend_class_implements, - zend_function_entry, -}; +use smallvec::SmallVec; -use crate::{ - errors::Throwable, - functions::{Function, FunctionEntry, Method, MethodEntity}, - objects::StateObj, - types::Scalar, - values::ZVal, -}; +use phper_sys::{phper_register_class_entry, zend_class_entry, zend_function_entry}; -use super::{ - create_object, entry::ClassEntry, PropertyEntity, StateCloner, StateConstructor, - StaticStateClass, Visibility, -}; +use crate::classes::methods::MethodEntityBuilder; +use crate::errors::Throwable; +use crate::functions::{Function, Method}; +use crate::{functions::FunctionEntry, objects::StateObj, values::ZVal}; + +use super::{create_object, StateConstructor, StaticStateClass}; /// Builder for registering class. /// /// *It is a common practice for PHP extensions to use PHP objects to package /// third-party resources.* -pub struct ClassEntity { - class: zend_class_entry, - state_constructor: Rc, - method_entities: Vec, - property_entities: Vec, - parent: Option &'static ClassEntry>>, - interfaces: Vec &'static ClassEntry>>, - bind_class: Option<&'static StaticStateClass>, - state_cloner: Option>, - _p: PhantomData<*mut ()>, +pub struct ClassEntity { + state_constructor: Box, + method_entities: SmallVec<[FunctionEntry; 16]>, + bind_class: Option<&'static StaticStateClass<()>>, + class_create: unsafe extern "C" fn() -> *mut zend_class_entry, + // state_cloner: Option>, + _p: PhantomData<*mut T>, } -impl ClassEntity { +impl ClassEntity<()> { /// Construct a new `ClassEntity` with class name, do not own state. - pub fn new(class_name: impl AsRef) -> Self { - Self::new_with_state_constructor::<()>(class_name, || ()) + pub fn new(class_create: unsafe extern "C" fn() -> *mut zend_class_entry) -> Self { + Self::new_with_state_constructor(|| (), class_create) } } -impl ClassEntity { +impl ClassEntity { /// Construct a new `ClassEntity` with class name and default state /// constructor. - pub fn new_with_default_state_constructor(class_name: impl AsRef) -> Self + pub fn new_with_default_state_constructor( + class_create: unsafe extern "C" fn() -> *mut zend_class_entry, + ) -> Self where T: Default + 'static, { - Self::new_with_state_constructor(class_name, T::default) + Self::new_with_state_constructor(T::default, class_create) } } @@ -62,164 +54,61 @@ impl Handler for dyn Fn(&mut StateObj, &mut [ZVal]) -> Result } } -impl ClassEntity { +impl ClassEntity { /// Construct a new `ClassEntity` with class name and the constructor to /// build state. - pub fn new_with_state_constructor( - class_name: impl AsRef, + pub fn new_with_state_constructor( state_constructor: impl Fn() -> T + 'static, + class_create: unsafe extern "C" fn() -> *mut zend_class_entry, ) -> Self where T: 'static, { - let class_name = class_name.as_ref(); - let class_name_len = class_name.len(); - Self { - class: unsafe { phper_init_class_entry(class_name.as_ptr().cast(), class_name_len) }, - state_constructor: Rc::new(move || { - let state = state_constructor(); - let boxed = Box::new(state) as Box; - Box::into_raw(boxed) - }), - method_entities: Vec::new(), - property_entities: Vec::new(), - parent: None, - interfaces: Vec::new(), - state_cloner: None, + state_constructor: Box::new(move || Box::new(state_constructor()) as Box), + method_entities: SmallVec::default(), + class_create, + // state_cloner: None, bind_class: None, _p: Default::default(), } } /// Add member method to class, with visibility and method handler. - pub fn add_method( - &mut self, - name: impl AsRef, - vis: Visibility, - handler: F, - ) -> &mut MethodEntity + pub fn add_method(&mut self, handler: F, method_builder: MethodEntityBuilder) where F: Fn(&mut StateObj, &mut [ZVal]) -> Result + 'static, Z: Into + 'static, E: Throwable + 'static, { - self.method_entities.push(MethodEntity::new( - name, - Some(Rc::new(Method::::new(handler))), - vis, - )); - self.method_entities.last_mut().unwrap() + let entity = method_builder + .set_handler(Method::::new(handler)) + .build(); + self.method_entities.push(entity.into()); } /// Add static method to class, with visibility and method handler. - pub fn add_static_method( - &mut self, - name: impl AsRef, - vis: Visibility, - handler: F, - ) -> &mut MethodEntity + pub fn add_static_method(&mut self, handler: F, method_builder: MethodEntityBuilder) where F: Fn(&mut [ZVal]) -> Result + 'static, Z: Into + 'static, E: Throwable + 'static, { - let mut entity = MethodEntity::new(name, Some(Rc::new(Function::new(handler))), vis); - entity.set_vis_static(); - self.method_entities.push(entity); - self.method_entities.last_mut().unwrap() - } - - /// Add abstract method to class, with visibility (shouldn't be private). - pub fn add_abstract_method( - &mut self, - name: impl AsRef, - vis: Visibility, - ) -> &mut MethodEntity { - let mut entity = MethodEntity::new(name, None, vis); - entity.set_vis_abstract(); - self.method_entities.push(entity); - self.method_entities.last_mut().unwrap() - } - - /// Declare property. - /// - /// The argument `value` should be `Copy` because 'zend_declare_property' - /// receive only scalar zval , otherwise will report fatal error: - /// "Internal zvals cannot be refcounted". - pub fn add_property( - &mut self, - name: impl Into, - visibility: Visibility, - value: impl Into, - ) { - self.property_entities - .push(PropertyEntity::new(name, visibility, value)); - } - - /// Declare static property. - /// - /// The argument `value` should be `Copy` because 'zend_declare_property' - /// receive only scalar zval , otherwise will report fatal error: - /// "Internal zvals cannot be refcounted". - pub fn add_static_property( - &mut self, - name: impl Into, - visibility: Visibility, - value: impl Into, - ) { - let mut entity = PropertyEntity::new(name, visibility, value); - entity.set_vis_static(); - self.property_entities.push(entity); - } - - /// Register class to `extends` the parent class. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; - /// - /// let mut class = ClassEntity::new("MyException"); - /// class.extends(|| ClassEntry::from_globals("Exception").unwrap()); - /// ``` - pub fn extends(&mut self, parent: impl Fn() -> &'static ClassEntry + 'static) { - self.parent = Some(Box::new(parent)); - } - - /// Register class to `implements` the interface, due to the class can - /// implement multi interface, so this method can be called multi time. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; - /// - /// let mut class = ClassEntity::new("MyClass"); - /// class.implements(|| ClassEntry::from_globals("Stringable").unwrap()); - /// - /// // Here you have to the implement the method `__toString()` in `Stringable` - /// // for `MyClass`, otherwise `MyClass` will become abstract class. - /// // ... - /// ``` - pub fn implements(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.interfaces.push(Box::new(interface)); + let entity = method_builder + .set_abstract() + .set_handler(Function::new(handler)) + .build(); + self.method_entities.push(entity.into()); } /// Bind to static [StaticStateClass]. /// /// When the class registered, the [StaticStateClass] will be initialized, /// so you can use the [StaticStateClass] to new stateful object, etc. - pub fn bind(&mut self, cls: &'static StaticStateClass) { - self.bind_class = Some(cls); + pub fn bind(&mut self, cls: &'static StaticStateClass) { + self.bind_class = Some(unsafe { + std::mem::transmute::<&'static StaticStateClass, &'static StaticStateClass<()>>(cls) + }); } /// Add the state clone function, called when cloning PHP object. @@ -243,90 +132,71 @@ impl ClassEntity { /// ``` /// use phper::classes::ClassEntity; /// - /// fn make_foo_class() -> ClassEntity { + /// fn make_foo_class() -> ClassEntity { /// let mut class = ClassEntity::new_with_state_constructor("Foo", || 123456); /// class.state_cloner(Clone::clone); /// class /// } /// ``` - pub fn state_cloner(&mut self, clone_fn: impl Fn(&T) -> T + 'static) { - self.state_cloner = Some(Rc::new(move |src| { - let src = unsafe { - src.as_ref() - .unwrap() - .downcast_ref::() - .expect("cast Any to T failed") - }; - let dest = clone_fn(src); - let boxed = Box::new(dest) as Box; - Box::into_raw(boxed) - })); + pub fn state_cloner(&mut self, _clone_fn: impl Fn(&T) -> T + 'static) + where + T: 'static, + { + // self.state_cloner = Some(Rc::new(move |src| { + // let src = unsafe { + // src.as_ref() + // .unwrap() + // .downcast_ref::() + // .expect("cast Any to T failed") + // }; + // let dest = clone_fn(src); + // let boxed = Box::new(dest) as Box; + // Box::into_raw(boxed) + // })); } +} - unsafe fn function_entries(&self) -> *const zend_function_entry { - let mut methods = self - .method_entities - .iter() - .map(|method| FunctionEntry::from_method_entity(method)) - .collect::>(); - - methods.push(zeroed::()); +pub(super) union ToFunctionEntry { + pub(super) handler: *const StateConstructor, + pub(super) zend_function: zend_function_entry, +} - // Store the state constructor pointer to zend_class_entry. - methods.push(self.take_state_constructor_into_function_entry()); +impl crate::modules::Registerer for ClassEntity { + fn register(mut self, _: i32) -> Result<(), Box> { + unsafe { + let mut methods = std::mem::take(&mut self.method_entities); + methods.push(FunctionEntry::empty()); - // Store the state cloner pointer to zend_class_entry. - methods.push(self.take_state_cloner_into_function_entry()); + { + let val = ToFunctionEntry { + handler: Box::into_raw(self.state_constructor), + }; - Box::into_raw(methods.into_boxed_slice()).cast() - } + methods.insert(0, FunctionEntry(val.zend_function)); + } - unsafe fn take_state_constructor_into_function_entry(&self) -> zend_function_entry { - let mut entry = zeroed::(); - let ptr = &mut entry as *mut _ as *mut *const StateConstructor; - let state_constructor = Rc::into_raw(self.state_constructor.clone()); - ptr.write(state_constructor); - entry - } + // Store the state constructor pointer to zend_class_entry. - unsafe fn take_state_cloner_into_function_entry(&self) -> zend_function_entry { - let mut entry = zeroed::(); - let ptr = &mut entry as *mut _ as *mut *const StateCloner; - if let Some(state_cloner) = &self.state_cloner { - let state_constructor = Rc::into_raw(state_cloner.clone()); - ptr.write(state_constructor); - } - entry - } -} + // if let Some(state_cloner) = self.state_cloner { + // let mut entry = zeroed::(); + // let ptr = &mut entry as *mut _ as *mut *const StateCloner; + // let state_constructor = Rc::into_raw(state_cloner.clone()); + // ptr.write(state_constructor); + // methods.push(FunctionEntry(entry)); + // } -impl crate::modules::Registerer for ClassEntity { - fn register(&mut self, _: i32) -> Result<(), Box> { - unsafe { - let parent: *mut zend_class_entry = self - .parent - .as_ref() - .map(|parent| parent()) - .map(|entry| entry.as_ptr() as *mut _) - .unwrap_or(null_mut()); + let methods: *const zend_function_entry = + Box::into_raw(methods.into_boxed_slice()).cast(); - let class_ce = - phper_register_class_entry(&mut self.class, parent, self.function_entries()); + let class_ce = phper_register_class_entry( + Some(self.class_create), + methods.offset(1), + Some(create_object), + ); if let Some(bind_class) = self.bind_class { bind_class.bind(class_ce); } - - for interface in &self.interfaces { - let interface_ce = interface().as_ptr(); - zend_class_implements(class_ce, 1, interface_ce); - } - - (*class_ce).__bindgen_anon_2.create_object = Some(create_object); - - for property in &self.property_entities { - property.declare(class_ce); - } } Ok(()) diff --git a/phper/src/classes/entry.rs b/phper/src/classes/entry.rs index bd1bc530..e78512a1 100644 --- a/phper/src/classes/entry.rs +++ b/phper/src/classes/entry.rs @@ -21,7 +21,7 @@ use super::find_global_class_entry_ptr; #[repr(transparent)] pub struct ClassEntry { inner: zend_class_entry, - _p: PhantomData<*mut ()>, + _p: PhantomData<*mut zend_class_entry>, } impl ClassEntry { @@ -36,7 +36,9 @@ impl ClassEntry { /// Panics if pointer is null. #[inline] pub unsafe fn from_ptr<'a>(ptr: *const zend_class_entry) -> &'a Self { - (ptr as *const Self).as_ref().expect("ptr should't be null") + (ptr as *const Self) + .as_ref() + .expect("ptr shouldn't be null") } /// Wraps a raw pointer, return None if pointer is null. @@ -74,6 +76,7 @@ impl ClassEntry { } /// Returns a raw pointer wrapped. + #[inline] pub const fn as_ptr(&self) -> *const zend_class_entry { &self.inner } @@ -94,6 +97,7 @@ impl ClassEntry { /// let std_class = ClassEntry::from_globals("stdClass").unwrap(); /// let _obj = std_class.new_object([]).unwrap(); /// ``` + #[inline] pub fn from_globals(class_name: impl AsRef) -> crate::Result<&'static Self> { let name = class_name.as_ref(); let ptr: *mut Self = find_global_class_entry_ptr(name).cast(); @@ -108,6 +112,7 @@ impl ClassEntry { /// /// If the `__construct` is private, or protected and the called scope isn't /// parent class, it will throw PHP Error. + #[inline] pub fn new_object(&self, arguments: impl AsMut<[ZVal]>) -> crate::Result { let mut object = self.init_object()?; object.call_construct(arguments)?; @@ -117,6 +122,7 @@ impl ClassEntry { /// Create the object from class, without calling `__construct`. /// /// **Be careful when `__construct` is necessary.** + #[inline] pub fn init_object(&self) -> crate::Result { unsafe { let ptr = self.as_ptr() as *mut _; @@ -124,7 +130,7 @@ impl ClassEntry { if !phper_object_init_ex(val.as_mut_ptr(), ptr) { Err(InitializeObjectError::new(self.get_name().to_str()?.to_owned()).into()) } else { - // Can't drop val here! Otherwise the object will be dropped too (wasting me a + // Can't drop val here! Otherwise, the object will be dropped too (wasting me a // day of debugging time here). let mut val = ManuallyDrop::new(val); let ptr = phper_z_obj_p(val.as_mut_ptr()); @@ -134,11 +140,18 @@ impl ClassEntry { } /// Get the class name. + #[inline] pub fn get_name(&self) -> &ZStr { unsafe { ZStr::from_ptr(self.inner.name) } } + #[inline] + pub fn get_name_str(&self) -> &str { + unsafe { ZStr::from_ptr(self.inner.name).as_str() } + } + /// Detect if the method is exists in class. + #[inline] pub fn has_method(&self, method_name: &str) -> bool { unsafe { let function_table = ZArr::from_ptr(&self.inner.function_table); @@ -147,6 +160,7 @@ impl ClassEntry { } /// Detect if the class is instance of parent class. + #[inline] pub fn is_instance_of(&self, parent: &ClassEntry) -> bool { unsafe { phper_instanceof_function(self.as_ptr(), parent.as_ptr()) } } @@ -155,6 +169,7 @@ impl ClassEntry { /// /// Return None when static property hasn't register by /// [ClassEntity::add_static_property]. + #[inline] pub fn get_static_property(&self, name: impl AsRef) -> Option<&ZVal> { let ptr = self.as_ptr() as *mut _; let prop = Self::inner_get_static_property(ptr, name); @@ -166,6 +181,7 @@ impl ClassEntry { /// Return `Some(x)` where `x` is the previous value of static property, or /// return `None` when static property hasn't register by /// [ClassEntity::add_static_property]. + #[inline] pub fn set_static_property(&self, name: impl AsRef, val: impl Into) -> Option { let ptr = self.as_ptr() as *mut _; let prop = Self::inner_get_static_property(ptr, name); @@ -173,13 +189,11 @@ impl ClassEntry { prop.map(|prop| replace(prop, val.into())) } + #[inline] fn inner_get_static_property(scope: *mut zend_class_entry, name: impl AsRef) -> *mut zval { let name = name.as_ref(); - unsafe { - #[allow(clippy::useless_conversion)] - zend_read_static_property(scope, name.as_ptr().cast(), name.len(), true.into()) - } + unsafe { zend_read_static_property(scope, name.as_ptr().cast(), name.len(), true) } } } diff --git a/phper/src/classes/interfaces.rs b/phper/src/classes/interfaces.rs new file mode 100644 index 00000000..a616d9e7 --- /dev/null +++ b/phper/src/classes/interfaces.rs @@ -0,0 +1,112 @@ +use crate::classes::entry::ClassEntry; +use crate::classes::methods::MethodEntityBuilder; +use crate::functions::FunctionEntry; +use phper_sys::{ + phper_init_interface_entry, phper_register_interface_entry, zend_class_entry, + zend_class_implements, +}; +use smallvec::SmallVec; +use std::mem::zeroed; +use std::ptr::null_mut; +use std::sync::atomic::{AtomicPtr, Ordering}; + +#[repr(transparent)] +pub struct StaticInterface(AtomicPtr); + +impl StaticInterface { + /// Create empty [StaticInterface], with null + /// [zend_class_entry]. + pub const fn null() -> Self { + Self(AtomicPtr::new(null_mut())) + } + + fn bind(&'static self, ptr: *mut zend_class_entry) { + self.0.store(ptr, Ordering::Relaxed); + } + + /// Converts to class entry. + pub fn as_class_entry(&'static self) -> &'static ClassEntry { + unsafe { ClassEntry::from_mut_ptr(self.0.load(Ordering::Relaxed)) } + } +} + +/// Builder for registering interface. +pub struct InterfaceEntity { + interface: zend_class_entry, + methods: SmallVec<[FunctionEntry; 16]>, + extends: SmallVec<[Box &'static ClassEntry>; 1]>, + bind_interface: Option<&'static StaticInterface>, +} + +impl InterfaceEntity { + /// Construct a new `InterfaceEntity` with interface name. + pub fn new(interface_name: impl AsRef) -> Self { + let interface_name = interface_name.as_ref(); + let interface_name_len = interface_name.len(); + + Self { + interface: unsafe { + phper_init_interface_entry(interface_name.as_ptr().cast(), interface_name_len) + }, + methods: SmallVec::default(), + extends: SmallVec::default(), + bind_interface: None, + } + } + + /// Add member method to interface, with mandatory visibility public + /// abstract. + pub fn add_method(&mut self, builder: MethodEntityBuilder) { + self.methods.push(builder.set_abstract().build().into()); + } + + /// Register interface to `extends` the interfaces, due to the interface can + /// extend multi interface, so this method can be called multi time. + /// + /// *Because in the `MINIT` phase, the class starts to register, so the* + /// *closure is used to return the `ClassEntry` to delay the acquisition of* + /// *the class.* + /// + /// # Examples + /// + /// ```no_run + /// use phper::classes::{ClassEntry, InterfaceEntity}; + /// + /// let mut interface = InterfaceEntity::new("MyInterface"); + /// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap()); + /// ``` + pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { + self.extends.push(Box::new(interface)); + } + + /// Bind to static [StaticInterface]. + /// + /// When the interface registered, the [StaticInterface] will be + /// initialized, so you can use the [StaticInterface] to be implemented + /// by other class. + pub fn bind(&mut self, i: &'static StaticInterface) { + self.bind_interface = Some(i); + } +} + +impl crate::modules::Registerer for InterfaceEntity { + fn register(mut self, _: i32) -> Result<(), Box> { + unsafe { + self.methods.push(zeroed::()); + + let class_ce = + phper_register_interface_entry(&mut self.interface, self.methods.as_ptr().cast()); + + if let Some(bind_interface) = self.bind_interface { + bind_interface.bind(class_ce); + } + + for interface in &self.extends { + let interface_ce = interface().as_ptr(); + zend_class_implements(class_ce, 1, interface_ce); + } + }; + + Ok(()) + } +} diff --git a/phper/src/classes/methods.rs b/phper/src/classes/methods.rs new file mode 100644 index 00000000..7e635c2d --- /dev/null +++ b/phper/src/classes/methods.rs @@ -0,0 +1,89 @@ +use crate::classes::RawVisibility; +use crate::functions::{Callable, FunctionEntry}; +use crate::utils::ensure_end_with_zero; +use phper_sys::{ + zend_internal_arg_info, ZEND_ACC_ABSTRACT, ZEND_ACC_FINAL, ZEND_ACC_PRIVATE, + ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, +}; +use std::ffi::CString; + +/// Builder for registering class method. +pub struct MethodEntity { + pub(crate) name: CString, + pub(crate) handler: Option>, + pub(crate) arguments: &'static [zend_internal_arg_info], + pub(crate) visibility: RawVisibility, +} + +#[derive(Default)] +pub struct MethodEntityBuilder { + name: CString, + handler: Option>, + arguments: &'static [zend_internal_arg_info], + visibility: RawVisibility, +} +#[allow(clippy::from_over_into)] +impl Into for MethodEntity { + fn into(self) -> FunctionEntry { + unsafe { FunctionEntry::from_method_entity(self) } + } +} + +impl MethodEntityBuilder { + #[inline] + pub fn new(name: impl AsRef, arguments: &'static [zend_internal_arg_info]) -> Self { + Self { + name: ensure_end_with_zero(name), + handler: None, + arguments, + visibility: ZEND_ACC_PUBLIC, + } + } + + #[inline] + pub(crate) fn set_handler(mut self, handler: impl Callable + 'static) -> Self { + self.handler = Some(Box::new(handler)); + self + } + + #[inline] + pub fn set_final(mut self) -> Self { + self.visibility |= ZEND_ACC_FINAL; + self + } + + #[inline] + pub fn set_abstract(mut self) -> Self { + self.visibility |= ZEND_ACC_ABSTRACT; + self + } + + #[inline] + pub fn set_private(mut self) -> Self { + self.visibility |= ZEND_ACC_PRIVATE; + self + } + + #[inline] + pub fn set_protected(mut self) -> Self { + self.visibility |= ZEND_ACC_PROTECTED; + self + } + + #[inline] + pub fn set_public(mut self) -> Self { + self.visibility |= ZEND_ACC_PUBLIC; + self + } + + pub(crate) fn build(self) -> MethodEntity { + assert!(self.handler.is_some()); + + MethodEntity { + name: self.name, + handler: self.handler, + visibility: self.visibility, + arguments: self.arguments, + } + } +} diff --git a/phper/src/classes/mod.rs b/phper/src/classes/mod.rs index d22c5c88..e56181ae 100644 --- a/phper/src/classes/mod.rs +++ b/phper/src/classes/mod.rs @@ -13,28 +13,31 @@ pub mod entity; /// Zend Class Entry pub mod entry; +pub mod interfaces; +pub mod methods; pub mod zend_classes; +pub use entity::*; +pub use interfaces::*; +pub use methods::MethodEntity; +pub use zend_classes::*; + use crate::{ - functions::{FunctionEntry, MethodEntity}, objects::{StateObj, StateObject, ZObject}, sys::*, - types::Scalar, values::ZVal, }; use std::{ any::Any, - convert::TryInto, - mem::{size_of, zeroed}, + mem::size_of, os::raw::c_int, ptr::null_mut, - slice, sync::atomic::{AtomicPtr, Ordering}, }; use self::entry::ClassEntry; -#[allow(clippy::useless_conversion)] +#[inline] fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { let name = name.as_ref(); let name = name.to_lowercase(); @@ -42,7 +45,7 @@ fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { phper_zend_hash_str_find_ptr( compiler_globals.class_table, name.as_ptr().cast(), - name.len().try_into().unwrap(), + name.len(), ) .cast() } @@ -75,27 +78,24 @@ fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { /// class /// } /// ``` + #[repr(transparent)] -pub struct StaticStateClass { - inner: AtomicPtr, -} +pub struct StaticStateClass(AtomicPtr, std::marker::PhantomData); -impl StaticStateClass { +impl StaticStateClass { /// Create empty [StaticStateClass], with null /// [zend_class_entry]. pub const fn null() -> Self { - Self { - inner: AtomicPtr::new(null_mut()), - } + Self(AtomicPtr::new(null_mut()), std::marker::PhantomData) } fn bind(&'static self, ptr: *mut zend_class_entry) { - self.inner.store(ptr, Ordering::Relaxed); + self.0.store(ptr, Ordering::Relaxed); } /// Converts to class entry. pub fn as_class_entry(&'static self) -> &'static ClassEntry { - unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } + unsafe { ClassEntry::from_mut_ptr(self.0.load(Ordering::Relaxed)) } } /// Create the object from class and call `__construct` with arguments. @@ -120,7 +120,7 @@ impl StaticStateClass { } } -unsafe impl Sync for StaticStateClass {} +unsafe impl Sync for StaticStateClass {} /// The [StaticInterface] holds /// [zend_class_entry], always as the static @@ -145,294 +145,83 @@ unsafe impl Sync for StaticStateClass {} /// interface /// } /// ``` -#[repr(transparent)] -pub struct StaticInterface { - inner: AtomicPtr, -} -impl StaticInterface { - /// Create empty [StaticInterface], with null - /// [zend_class_entry]. - pub const fn null() -> Self { - Self { - inner: AtomicPtr::new(null_mut()), - } - } +pub(crate) type StateConstructor = dyn Fn() -> Box; - fn bind(&'static self, ptr: *mut zend_class_entry) { - self.inner.store(ptr, Ordering::Relaxed); - } - - /// Converts to class entry. - pub fn as_class_entry(&'static self) -> &'static ClassEntry { - unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } - } -} - -pub(crate) type StateConstructor = dyn Fn() -> *mut dyn Any; - -pub(crate) type StateCloner = dyn Fn(*const dyn Any) -> *mut dyn Any; - -/// Builder for registering interface. -pub struct InterfaceEntity { - interface: zend_class_entry, - method_entities: Vec, - extends: Vec &'static ClassEntry>>, - bind_interface: Option<&'static StaticInterface>, -} - -impl InterfaceEntity { - /// Construct a new `InterfaceEntity` with interface name. - pub fn new(interface_name: impl AsRef) -> Self { - let interface_name = interface_name.as_ref(); - let interface_name_len = interface_name.len(); - - Self { - interface: unsafe { - phper_init_interface_entry(interface_name.as_ptr().cast(), interface_name_len) - }, - method_entities: Vec::new(), - extends: Vec::new(), - bind_interface: None, - } - } - - /// Add member method to interface, with mandatory visibility public - /// abstract. - pub fn add_method(&mut self, name: impl AsRef) -> &mut MethodEntity { - let mut entity = MethodEntity::new(name, None, Visibility::Public); - entity.set_vis_abstract(); - self.method_entities.push(entity); - self.method_entities.last_mut().unwrap() - } - - /// Register interface to `extends` the interfaces, due to the interface can - /// extends multi interface, so this method can be called multi time. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntry, InterfaceEntity}; - /// - /// let mut interface = InterfaceEntity::new("MyInterface"); - /// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap()); - /// ``` - pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.extends.push(Box::new(interface)); - } - - /// Bind to static [StaticInterface]. - /// - /// When the interface registered, the [StaticInterface] will be - /// initialized, so you can use the [StaticInterface] to be implemented - /// by other class. - pub fn bind(&mut self, i: &'static StaticInterface) { - self.bind_interface = Some(i); - } - - unsafe fn function_entries(&self) -> *const zend_function_entry { - let mut methods = self - .method_entities - .iter() - .map(|method| FunctionEntry::from_method_entity(method)) - .collect::>(); - - methods.push(zeroed::()); - - Box::into_raw(methods.into_boxed_slice()).cast() - } -} - -impl crate::modules::Registerer for InterfaceEntity { - fn register(&mut self, _: i32) -> Result<(), Box> { - unsafe { - let class_ce = - phper_register_interface_entry(&mut self.interface, self.function_entries()); - - if let Some(bind_interface) = self.bind_interface { - bind_interface.bind(class_ce); - } - - for interface in &self.extends { - let interface_ce = interface().as_ptr(); - zend_class_implements(class_ce, 1, interface_ce); - } - }; - - Ok(()) - } -} - -/// Builder for declare class property. -struct PropertyEntity { - name: String, - visibility: RawVisibility, - value: Scalar, -} - -impl PropertyEntity { - fn new(name: impl Into, visibility: Visibility, value: impl Into) -> Self { - Self { - name: name.into(), - visibility: visibility as RawVisibility, - value: value.into(), - } - } - - #[inline] - pub(crate) fn set_vis_static(&mut self) -> &mut Self { - self.visibility |= ZEND_ACC_STATIC; - self - } - - pub(crate) fn declare(&self, ce: *mut zend_class_entry) { - let name = self.name.as_ptr().cast(); - let name_length = self.name.len(); - let access_type = self.visibility as i32; - - unsafe { - match &self.value { - Scalar::Null => { - zend_declare_property_null(ce, name, name_length, access_type); - } - Scalar::Bool(b) => { - zend_declare_property_bool(ce, name, name_length, *b as zend_long, access_type); - } - Scalar::I64(i) => { - zend_declare_property_long(ce, name, name_length, *i, access_type); - } - Scalar::F64(f) => { - zend_declare_property_double(ce, name, name_length, *f, access_type); - } - Scalar::String(s) => { - // If the `ce` is `ZEND_INTERNAL_CLASS`, then the `zend_string` is allocated - // as persistent. - zend_declare_property_stringl( - ce, - name, - name_length, - s.as_ptr().cast(), - s.len(), - access_type, - ); - } - Scalar::Bytes(b) => { - zend_declare_property_stringl( - ce, - name, - name_length, - b.as_ptr().cast(), - b.len(), - access_type, - ); - } - } - } - } -} - -/// Visibility of class properties and methods. -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum Visibility { - /// Public. - #[default] - Public = ZEND_ACC_PUBLIC, - - /// Protected. - Protected = ZEND_ACC_PROTECTED, - - /// Private. - Private = ZEND_ACC_PRIVATE, -} +// pub(crate) type StateCloner = dyn Fn(*const dyn Any) -> *mut dyn Any; /// Raw visibility flag. pub(crate) type RawVisibility = u32; unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_object { - // Alloc more memory size to store state data. let state_object = phper_zend_object_alloc(size_of::(), ce); let state_object = StateObj::from_mut_ptr(state_object); - // Find the hack elements hidden behind null builtin_function. - let mut func_ptr = (*ce).info.internal.builtin_functions; - while !(*func_ptr).fname.is_null() { - func_ptr = func_ptr.offset(1); - } + let func_ptr = (*ce).info.internal.builtin_functions.offset(-1); + let state_constructor = ToFunctionEntry { + zend_function: *func_ptr, + }; - // Get state constructor. - func_ptr = func_ptr.offset(1); - let state_constructor = func_ptr as *mut *const StateConstructor; - let state_constructor = state_constructor.read().as_ref().unwrap(); + let state_constructor = state_constructor.handler.as_ref().unwrap_unchecked(); // Get state cloner. - func_ptr = func_ptr.offset(1); - let has_state_cloner = - slice::from_raw_parts(func_ptr as *const u8, size_of::<*const StateCloner>()) - != [0u8; size_of::<*const StateCloner>()]; + // func_ptr = func_ptr.offset(1); + // let has_state_cloner = + // slice::from_raw_parts(func_ptr as *const u8, size_of::<*const StateCloner>()) + // != [0u8; size_of::<*const StateCloner>()]; - // Common initialize process. let object = state_object.as_mut_object().as_mut_ptr(); zend_object_std_init(object, ce); object_properties_init(object, ce); - rebuild_object_properties(object); - // Set handlers let mut handlers = Box::new(std_object_handlers); handlers.offset = StateObj::offset() as c_int; handlers.free_obj = Some(free_object); - handlers.clone_obj = has_state_cloner.then_some(clone_object); (*object).handlers = Box::into_raw(handlers); - - // Call the state constructor and store the state. - let data = (state_constructor)(); - *state_object.as_mut_any_state() = data; + state_object.state = Some(state_constructor()); object } -unsafe extern "C" fn clone_object(object: *mut zend_object) -> *mut zend_object { - clone_object_common(object) -} - -unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object { - let ce = (*object).ce; - - // Alloc more memory size to store state data. - let new_state_object = phper_zend_object_alloc(size_of::(), ce); - let new_state_object = StateObj::from_mut_ptr(new_state_object); - - // Find the hack elements hidden behind null builtin_function. - let mut func_ptr = (*(*object).ce).info.internal.builtin_functions; - while !(*func_ptr).fname.is_null() { - func_ptr = func_ptr.offset(1); - } - - // Get state cloner. - func_ptr = func_ptr.offset(2); - let state_cloner = func_ptr as *mut *const StateCloner; - let state_cloner = state_cloner.read().as_ref().unwrap(); - - // Initialize and clone members - let new_object = new_state_object.as_mut_object().as_mut_ptr(); - zend_object_std_init(new_object, ce); - object_properties_init(new_object, ce); - zend_objects_clone_members(new_object, object); - - // Set handlers - (*new_object).handlers = (*object).handlers; - - // Call the state cloner and store the state. - let state_object = StateObj::from_mut_object_ptr(object); - let data = (state_cloner)(*state_object.as_mut_any_state()); - *new_state_object.as_mut_any_state() = data; - - new_object -} +// unsafe extern "C" fn clone_object(object: *mut zend_object) -> *mut zend_object { +// // clone_object_common(object) +// object +// } + +// unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object { +// let ce = (*object).ce; +// +// // Alloc more memory size to store state data. +// let new_state_object = phper_zend_object_alloc(size_of::(), ce); +// let new_state_object = StateObj::from_mut_ptr(new_state_object); +// +// // Find the hack elements hidden behind null builtin_function. +// let mut func_ptr = (*(*object).ce).info.internal.builtin_functions; +// while !(*func_ptr).fname.is_null() { +// func_ptr = func_ptr.offset(1); +// } +// +// // Get state cloner. +// func_ptr = func_ptr.offset(2); +// let state_cloner = func_ptr as *mut *const StateCloner; +// let state_cloner = state_cloner.read().as_ref().unwrap(); +// +// // Initialize and clone members +// let new_object = new_state_object.as_mut_object().as_mut_ptr(); +// zend_object_std_init(new_object, ce); +// object_properties_init(new_object, ce); +// zend_objects_clone_members(new_object, object); +// +// // Set handlers +// (*new_object).handlers = (*object).handlers; +// +// // Call the state cloner and store the state. +// let state_object = StateObj::from_mut_object_ptr(object); +// let data = (state_cloner)(*state_object.as_mut_any_state()); +// *new_state_object.as_mut_any_state() = data; +// +// new_object +// } unsafe extern "C" fn free_object(object: *mut zend_object) { let state_object = StateObj::from_mut_object_ptr(object); diff --git a/phper/src/constants.rs b/phper/src/constants.rs index 7f85cbf1..1939e852 100644 --- a/phper/src/constants.rs +++ b/phper/src/constants.rs @@ -20,7 +20,6 @@ pub struct Constant { flags: i32, } -// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct Flags(u32); @@ -45,7 +44,7 @@ impl Constant { let name = name.as_ref(); let length = name.len(); let ptr = name.as_bytes().as_ptr() as *const i8; - let flags = flags.unwrap_or(Flags::Cs | Flags::Persistent).bits() as i32; + let flags = flags.unwrap_or_default().bits() as i32; Self { constant: unsafe { phper_create_constant(ptr, length, value.into().inner, flags) }, flags, @@ -54,7 +53,7 @@ impl Constant { } impl Registerer for Constant { - fn register(&mut self, module_number: i32) -> Result<(), Box> { + fn register(mut self, module_number: i32) -> Result<(), Box> { let result = unsafe { phper_register_constant(&mut self.constant, self.flags, module_number) }; diff --git a/phper/src/functions.rs b/phper/src/functions.rs deleted file mode 100644 index dd4e2e47..00000000 --- a/phper/src/functions.rs +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -//! Apis relate to [zend_function_entry]. -//! -//! TODO Add lambda. - -use crate::{ - classes::{entry::ClassEntry, RawVisibility, Visibility}, - errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable}, - objects::{StateObj, ZObj, ZObject}, - strings::{ZStr, ZString}, - sys::*, - utils::ensure_end_with_zero, - values::{ExecuteData, ZVal}, -}; -use phper_alloc::ToRefOwned; -use std::{ - ffi::{CStr, CString}, - marker::PhantomData, - mem::{transmute, zeroed}, - ptr::{self, null_mut}, - rc::Rc, -}; - -pub(crate) trait Callable { - fn call(&self, execute_data: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal); -} - -pub(crate) struct Function(F, PhantomData<(Z, E)>); - -impl Function { - pub fn new(f: F) -> Self { - Self(f, PhantomData) - } -} - -impl Callable for Function -where - F: Fn(&mut [ZVal]) -> Result, - Z: Into, - E: Throwable, -{ - fn call(&self, _: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal) { - match (self.0)(arguments) { - Ok(z) => { - *return_value = z.into(); - } - Err(e) => { - unsafe { - throw(e); - } - *return_value = ().into(); - } - } - } -} - -pub(crate) struct Method(F, PhantomData<(Z, E)>); - -impl Method { - pub(crate) fn new(f: F) -> Self { - Self(f, PhantomData) - } -} - -impl Callable for Method -where - F: Fn(&mut StateObj, &mut [ZVal]) -> Result, - Z: Into, - E: Throwable, -{ - fn call( - &self, - execute_data: &mut ExecuteData, - arguments: &mut [ZVal], - return_value: &mut ZVal, - ) { - let this = unsafe { execute_data.get_this_mut().unwrap().as_mut_state_obj() }; - match (self.0)(this, arguments) { - Ok(z) => { - *return_value = z.into(); - } - Err(e) => { - unsafe { - throw(e); - } - *return_value = ().into(); - } - } - } -} - -/// Wrapper of [`zend_function_entry`]. -#[repr(transparent)] -pub struct FunctionEntry { - #[allow(dead_code)] - inner: zend_function_entry, -} - -impl FunctionEntry { - pub(crate) unsafe fn from_function_entity(entity: &FunctionEntity) -> zend_function_entry { - Self::entry( - &entity.name, - &entity.arguments, - Some(entity.handler.clone()), - None, - ) - } - - pub(crate) unsafe fn from_method_entity(entity: &MethodEntity) -> zend_function_entry { - Self::entry( - &entity.name, - &entity.arguments, - entity.handler.clone(), - Some(entity.visibility), - ) - } - - /// Will leak memory - unsafe fn entry( - name: &CStr, - arguments: &[Argument], - handler: Option>, - visibility: Option, - ) -> zend_function_entry { - let mut infos = Vec::new(); - - let require_arg_count = arguments.iter().filter(|arg| arg.required).count(); - infos.push(phper_zend_begin_arg_info_ex(false, require_arg_count)); - - for arg in arguments { - infos.push(phper_zend_arg_info( - arg.pass_by_ref, - arg.name.as_ptr().cast(), - )); - } - - infos.push(zeroed::()); - - let raw_handler = handler.as_ref().map(|_| invoke as _); - - if let Some(handler) = handler { - let translator = CallableTranslator { - callable: Rc::into_raw(handler), - }; - let last_arg_info: zend_internal_arg_info = translator.internal_arg_info; - infos.push(last_arg_info); - } - - let flags = visibility.unwrap_or(Visibility::default() as u32); - - zend_function_entry { - fname: name.as_ptr().cast(), - handler: raw_handler, - arg_info: Box::into_raw(infos.into_boxed_slice()).cast(), - num_args: arguments.len() as u32, - flags, - } - } -} - -/// Builder for registering php function. -pub struct FunctionEntity { - name: CString, - handler: Rc, - arguments: Vec, -} - -impl FunctionEntity { - #[inline] - pub(crate) fn new(name: impl AsRef, handler: Rc) -> Self { - FunctionEntity { - name: ensure_end_with_zero(name), - handler, - arguments: Default::default(), - } - } - - /// Add single function argument info. - #[inline] - pub fn argument(&mut self, argument: Argument) -> &mut Self { - self.arguments.push(argument); - self - } - - /// Add many function argument infos. - #[inline] - pub fn arguments(&mut self, arguments: impl IntoIterator) -> &mut Self { - self.arguments.extend(arguments); - self - } -} - -/// Builder for registering class method. -pub struct MethodEntity { - name: CString, - handler: Option>, - arguments: Vec, - visibility: RawVisibility, -} - -impl MethodEntity { - #[inline] - pub(crate) fn new( - name: impl AsRef, - handler: Option>, - visibility: Visibility, - ) -> Self { - Self { - name: ensure_end_with_zero(name), - handler, - visibility: visibility as RawVisibility, - arguments: Default::default(), - } - } - - #[inline] - pub(crate) fn set_vis_static(&mut self) -> &mut Self { - self.visibility |= ZEND_ACC_STATIC; - self - } - - #[inline] - pub(crate) fn set_vis_abstract(&mut self) -> &mut Self { - self.visibility |= ZEND_ACC_ABSTRACT; - self - } - - /// Add single method argument info. - #[inline] - pub fn argument(&mut self, argument: Argument) -> &mut Self { - self.arguments.push(argument); - self - } - - /// Add many method argument infos. - #[inline] - pub fn arguments(&mut self, arguments: impl IntoIterator) -> &mut Self { - self.arguments.extend(arguments); - self - } -} - -/// Function or method argument info. -pub struct Argument { - name: CString, - pass_by_ref: bool, - required: bool, -} - -impl Argument { - /// Indicate the argument is pass by value. - pub fn by_val(name: impl AsRef) -> Self { - let name = ensure_end_with_zero(name); - Self { - name, - pass_by_ref: false, - required: true, - } - } - - /// Indicate the argument is pass by reference. - pub fn by_ref(name: impl AsRef) -> Self { - let name = ensure_end_with_zero(name); - Self { - name, - pass_by_ref: true, - required: true, - } - } - - /// Indicate the argument is pass by value and is optional. - pub fn by_val_optional(name: impl AsRef) -> Self { - let name = ensure_end_with_zero(name); - Self { - name, - pass_by_ref: false, - required: false, - } - } - - /// Indicate the argument is pass by reference nad is optional. - pub fn by_ref_optional(name: impl AsRef) -> Self { - let name = ensure_end_with_zero(name); - Self { - name, - pass_by_ref: true, - required: false, - } - } -} - -/// Wrapper of [`zend_function`]. -#[repr(transparent)] -pub struct ZFunc { - inner: zend_function, -} - -impl ZFunc { - /// Wraps a raw pointer. - /// - /// # Safety - /// - /// Create from raw pointer. - /// - /// # Panics - /// - /// Panics if pointer is null. - pub(crate) unsafe fn from_mut_ptr<'a>(ptr: *mut zend_function) -> &'a mut ZFunc { - let ptr = ptr as *mut Self; - ptr.as_mut().expect("ptr shouldn't be null") - } - - /// Returns a raw pointer wrapped. - pub const fn as_ptr(&self) -> *const zend_function { - &self.inner - } - - /// Returns a raw pointer wrapped. - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut zend_function { - &mut self.inner - } - - /// Get the function name if exists. - pub fn get_function_name(&self) -> Option<&ZStr> { - unsafe { - let s = phper_get_function_name(self.as_ptr()); - ZStr::try_from_ptr(s) - } - } - - /// Get the function or method fully-qualified name. - pub fn get_function_or_method_name(&self) -> ZString { - unsafe { - let s = phper_get_function_or_method_name(self.as_ptr()); - ZString::from_raw(s) - } - } - - /// Get the function related class if exists. - pub fn get_class(&self) -> Option<&ClassEntry> { - unsafe { - let ptr = self.inner.common.scope; - if ptr.is_null() { - None - } else { - Some(ClassEntry::from_ptr(self.inner.common.scope)) - } - } - } - - #[allow(clippy::useless_conversion)] - pub(crate) fn call( - &mut self, - mut object: Option<&mut ZObj>, - mut arguments: impl AsMut<[ZVal]>, - ) -> crate::Result { - let arguments = arguments.as_mut(); - let function_handler = self.as_mut_ptr(); - - let object_ptr = object - .as_mut() - .map(|o| o.as_mut_ptr()) - .unwrap_or(null_mut()); - - call_raw_common(|ret| unsafe { - let class_ptr = object - .as_mut() - .map(|o| o.get_mut_class().as_mut_ptr()) - .unwrap_or(null_mut()); - - zend_call_known_function( - function_handler, - object_ptr, - class_ptr, - ret.as_mut_ptr(), - arguments.len() as u32, - arguments.as_mut_ptr().cast(), - null_mut(), - ); - }) - } -} - -/// Just for type transmutation. -pub(crate) union CallableTranslator { - pub(crate) callable: *const dyn Callable, - pub(crate) internal_arg_info: zend_internal_arg_info, - pub(crate) arg_info: zend_arg_info, -} - -/// The entry for all registered PHP functions. -unsafe extern "C" fn invoke(execute_data: *mut zend_execute_data, return_value: *mut zval) { - let execute_data = ExecuteData::from_mut_ptr(execute_data); - let return_value = ZVal::from_mut_ptr(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 translator = CallableTranslator { - arg_info: *last_arg_info, - }; - let handler = translator.callable; - let handler = handler.as_ref().expect("handler is null"); - - // Check arguments count. - let num_args = execute_data.num_args(); - let required_num_args = execute_data.common_required_num_args(); - if num_args < required_num_args { - let func_name = execute_data.func().get_function_or_method_name(); - let err: crate::Error = match func_name.to_str() { - Ok(func_name) => { - ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() - } - Err(e) => e.into(), - }; - throw(err); - *return_value = ().into(); - return; - } - - let mut arguments = execute_data.get_parameters_array(); - let arguments = arguments.as_mut_slice(); - - handler.call(execute_data, transmute(arguments), return_value); -} - -/// Call user function by name. -/// -/// # Examples -/// -/// ```no_run -/// use phper::{arrays::ZArray, functions::call, values::ZVal}; -/// -/// fn json_encode() -> phper::Result<()> { -/// let mut arr = ZArray::new(); -/// arr.insert("a", ZVal::from(1)); -/// arr.insert("b", ZVal::from(2)); -/// let ret = call("json_encode", &mut [ZVal::from(arr)])?; -/// assert_eq!(ret.expect_z_str()?.to_str(), Ok(r#"{"a":1,"b":2}"#)); -/// Ok(()) -/// } -/// ``` -pub fn call(callable: impl Into, arguments: impl AsMut<[ZVal]>) -> crate::Result { - let mut func = callable.into(); - call_internal(&mut func, None, arguments) -} - -pub(crate) fn call_internal( - func: &mut ZVal, - mut object: Option<&mut ZObj>, - mut arguments: impl AsMut<[ZVal]>, -) -> crate::Result { - let func_ptr = func.as_mut_ptr(); - let arguments = arguments.as_mut(); - - let mut object_val = object.as_mut().map(|obj| ZVal::from(obj.to_ref_owned())); - - call_raw_common(|ret| unsafe { - phper_call_user_function( - object_val - .as_mut() - .map(|o| o.as_mut_ptr()) - .unwrap_or(null_mut()), - func_ptr, - ret.as_mut_ptr(), - arguments.as_mut_ptr().cast(), - arguments.len() as u32, - null_mut(), - ); - }) -} - -/// call function with raw pointer. -/// call_fn parameters: (return_value) -pub(crate) fn call_raw_common(call_fn: impl FnOnce(&mut ZVal)) -> crate::Result { - let _guard = ExceptionGuard::default(); - - let mut ret = ZVal::default(); - - call_fn(&mut ret); - if ret.get_type_info().is_undef() { - ret = ().into(); - } - - unsafe { - if !eg!(exception).is_null() { - let e = ptr::replace(&mut eg!(exception), null_mut()); - let obj = ZObject::from_raw(e); - match ThrowObject::new(obj) { - Ok(e) => return Err(e.into()), - Err(e) => return Err(e.into()), - } - } - } - - Ok(ret) -} diff --git a/phper/src/functions/invoke.rs b/phper/src/functions/invoke.rs new file mode 100644 index 00000000..42f0f872 --- /dev/null +++ b/phper/src/functions/invoke.rs @@ -0,0 +1,42 @@ +use crate::errors::{throw, ArgumentCountError}; +use crate::values::{ExecuteData, ZVal}; +use phper_sys::{zend_execute_data, zval}; + +/// The entry for all registered PHP functions. +pub(super) unsafe extern "C" fn call_function_handler( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + let execute_data = ExecuteData::from_mut_ptr(execute_data); + let return_value = ZVal::from_mut_ptr(return_value); + + // Check arguments count. + let num_args = execute_data.num_args(); + let required_num_args = execute_data.common_required_num_args(); + if num_args < required_num_args { + let func_name = execute_data.func().get_function_or_method_name(); + let err: crate::Error = match func_name.to_str() { + Ok(func_name) => { + ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() + } + Err(e) => e.into(), + }; + throw(err); + *return_value = ().into(); + return; + } + + let handler = execute_data.get_handler(); + if let Some(mut params) = execute_data.get_parameters_array() { + handler.call(execute_data, params.as_mut_slice(), return_value); + } else { + let func_name = execute_data.func().get_function_or_method_name(); + let err: crate::Error = match func_name.to_str() { + Ok(func_name) => { + ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() + } + Err(e) => e.into(), + }; + throw(err); + } +} diff --git a/phper/src/functions/mod.rs b/phper/src/functions/mod.rs new file mode 100644 index 00000000..6d084761 --- /dev/null +++ b/phper/src/functions/mod.rs @@ -0,0 +1,339 @@ +// Copyright (c) 2022 PHPER Framework Team +// PHPER is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! Apis relate to [zend_function_entry]. +//! +//! TODO Add lambda. + +mod invoke; + +use crate::classes::MethodEntity; +use crate::{ + classes::{entry::ClassEntry, RawVisibility}, + errors::{throw, ExceptionGuard, ThrowObject, Throwable}, + objects::{StateObj, ZObj, ZObject}, + strings::{ZStr, ZString}, + sys::*, + utils::ensure_end_with_zero, + values::{ExecuteData, ZVal}, +}; + +use phper_alloc::ToRefOwned; + +use std::{ffi::CString, marker::PhantomData, mem::zeroed, ptr::null_mut}; + +pub(crate) trait Callable { + fn call(&self, execute_data: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal); +} + +pub(crate) struct Function(F, PhantomData<(Z, E)>); + +impl Function { + pub fn new(f: F) -> Self { + Self(f, PhantomData) + } +} + +impl Callable for Function +where + F: Fn(&mut [ZVal]) -> Result, + Z: Into, + E: Throwable, +{ + fn call(&self, _: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal) { + match (self.0)(arguments) { + Ok(z) => { + *return_value = z.into(); + } + Err(e) => { + unsafe { + throw(e); + } + *return_value = ().into(); + } + } + } +} + +pub(crate) struct Method(F, PhantomData<(Z, E)>); + +impl Method { + pub(crate) fn new(f: F) -> Self { + Self(f, PhantomData) + } +} + +impl Callable for Method +where + F: Fn(&mut StateObj, &mut [ZVal]) -> Result, + Z: Into, + E: Throwable, +{ + fn call( + &self, + execute_data: &mut ExecuteData, + arguments: &mut [ZVal], + return_value: &mut ZVal, + ) { + let this = unsafe { execute_data.get_this_mut().unwrap().as_mut_state_obj() }; + match (self.0)(this, arguments) { + Ok(z) => { + *return_value = z.into(); + } + Err(e) => { + unsafe { + throw(e); + } + *return_value = ().into(); + } + } + } +} + +/// Wrapper of [`zend_function_entry`]. +#[repr(transparent)] +pub struct FunctionEntry(pub(crate) zend_function_entry); + +impl FunctionEntry { + pub(crate) fn empty() -> Self { + Self(unsafe { zeroed::() }) + } + + pub(crate) unsafe fn from_function_entity(entity: FunctionEntity) -> FunctionEntry { + Self::entry(entity.name, entity.arguments, entity.handler, None) + } + + pub(crate) unsafe fn from_method_entity(entity: MethodEntity) -> FunctionEntry { + Self::entry( + entity.name, + entity.arguments, + entity.handler.expect("Handler must be set on Method"), + Some(entity.visibility), + ) + } + + unsafe fn entry( + name: CString, + arguments: &'static [zend_internal_arg_info], + handler: Box, + visibility: Option, + ) -> FunctionEntry { + let (args, count) = ExecuteData::write_handler(handler, arguments); + + FunctionEntry(zend_function_entry { + fname: name.into_raw(), + handler: Some(invoke::call_function_handler), + arg_info: args, + num_args: count, + flags: visibility.unwrap_or(ZEND_ACC_PUBLIC), + }) + } +} + +impl Drop for FunctionEntry { + fn drop(&mut self) { + // let name = unsafe { CStr::from_ptr(self.0.fname) }.to_str().unwrap(); + // println!("Called drop for FunctionEntry {}", name); + // unsafe { + // + // drop(Vec::from_raw_parts( + // self.0.arg_info.offset(-1) as *mut zend_internal_arg_info, + // self.0.num_args as usize, + // self.0.num_args as usize, + // )); + // + // drop(CString::from_raw(self.0.fname as *mut c_char)) + // } + } +} + +/// Builder for registering php function. +pub struct FunctionEntity { + name: CString, + handler: Box, + arguments: &'static [zend_internal_arg_info], +} + +impl FunctionEntity { + #[inline] + pub(crate) fn new( + name: impl AsRef, + handler: Box, + arguments: &'static [zend_internal_arg_info], + ) -> Self { + FunctionEntity { + name: ensure_end_with_zero(name), + handler, + arguments, + } + } +} + +/// Wrapper of [`zend_function`]. +#[repr(transparent)] +pub struct ZFunc(zend_function); + +impl ZFunc { + /// Wraps a raw pointer. + /// + /// # Safety + /// + /// Create from raw pointer. + /// + /// # Panics + /// + /// Panics if pointer is null. + pub(crate) unsafe fn from_mut_ptr<'a>(ptr: *mut zend_function) -> &'a mut ZFunc { + let ptr = ptr as *mut Self; + ptr.as_mut().expect("ptr shouldn't be null") + } + + /// Returns a raw pointer wrapped. + pub const fn as_ptr(&self) -> *const zend_function { + &self.0 + } + + /// Returns a raw pointer wrapped. + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut zend_function { + &mut self.0 + } + + /// Get the function name if exists. + pub fn get_function_name(&self) -> Option<&ZStr> { + unsafe { + let s = phper_get_function_name(self.as_ptr()); + ZStr::try_from_ptr(s) + } + } + + /// Get the function or method fully-qualified name. + pub fn get_function_or_method_name(&self) -> ZString { + unsafe { + let s = phper_get_function_or_method_name(self.as_ptr()); + ZString::from_raw(s) + } + } + + /// Get the function related class if exists. + pub fn get_class(&self) -> Option<&ClassEntry> { + unsafe { + let ptr = self.0.common.scope; + if ptr.is_null() { + None + } else { + Some(ClassEntry::from_ptr(self.0.common.scope)) + } + } + } + + pub(crate) fn call( + &mut self, + mut object: Option<&mut ZObj>, + mut arguments: impl AsMut<[ZVal]>, + ) -> crate::Result { + let arguments = arguments.as_mut(); + let function_handler = self.as_mut_ptr(); + + let object_ptr = object + .as_mut() + .map(|o| o.as_mut_ptr()) + .unwrap_or(null_mut()); + + call_raw_common(|ret| unsafe { + let class_ptr = object + .as_mut() + .map(|o| o.get_mut_class().as_mut_ptr()) + .unwrap_or(null_mut()); + + zend_call_known_function( + function_handler, + object_ptr, + class_ptr, + ret.as_mut_ptr(), + arguments.len() as u32, + arguments.as_mut_ptr().cast(), + null_mut(), + ); + }) + } +} + +/// Call user function by name. +/// +/// # Examples +/// +/// ```no_run +/// use phper::{arrays::ZArray, functions::call, values::ZVal}; +/// +/// fn json_encode() -> phper::Result<()> { +/// let mut arr = ZArray::new(); +/// arr.insert("a", ZVal::from(1)); +/// arr.insert("b", ZVal::from(2)); +/// let ret = call("json_encode", &mut [ZVal::from(arr)])?; +/// assert_eq!(ret.expect_z_str()?.to_str(), Ok(r#"{"a":1,"b":2}"#)); +/// Ok(()) +/// } +/// ``` +pub fn call(callable: impl Into, arguments: impl AsMut<[ZVal]>) -> crate::Result { + let mut func = callable.into(); + call_internal(&mut func, None, arguments) +} + +pub(crate) fn call_internal( + func: &mut ZVal, + mut object: Option<&mut ZObj>, + mut arguments: impl AsMut<[ZVal]>, +) -> crate::Result { + let func_ptr = func.as_mut_ptr(); + let arguments = arguments.as_mut(); + + let mut object_val = object.as_mut().map(|obj| ZVal::from(obj.to_ref_owned())); + + call_raw_common(|ret| unsafe { + phper_call_user_function( + object_val + .as_mut() + .map(|o| o.as_mut_ptr()) + .unwrap_or(null_mut()), + func_ptr, + ret.as_mut_ptr(), + arguments.as_mut_ptr().cast(), + arguments.len() as u32, + null_mut(), + ); + }) +} + +/// call function with raw pointer. +/// call_fn parameters: (return_value) +pub(crate) fn call_raw_common(call_fn: impl FnOnce(&mut ZVal)) -> crate::Result { + let _guard = ExceptionGuard::default(); + + let mut ret = ZVal::default(); + + call_fn(&mut ret); + if ret.get_type_info().is_undef() { + ret = ().into(); + } + + unsafe { + if !eg!(exception).is_null() { + let e = std::ptr::replace(&mut eg!(exception), null_mut()); + let obj = ZObject::from_raw(e); + return match ThrowObject::new(obj) { + Ok(e) => Err(e.into()), + Err(e) => Err(e.into()), + }; + } + } + + Ok(ret) +} diff --git a/phper/src/ini/mod.rs b/phper/src/ini/mod.rs new file mode 100644 index 00000000..3b2d9066 --- /dev/null +++ b/phper/src/ini/mod.rs @@ -0,0 +1,21 @@ +mod policy; +mod stage; + +pub use policy::*; +pub use stage::Stage; + +/// Get the global registered configuration value. +/// +/// # Examples +/// +/// ```no_run +/// use phper::ini::ini_get; +/// use std::ffi::CStr; +/// +/// let _foo = ini_get::("FOO"); +/// let _bar = ini_get::>("BAR"); +/// ``` +#[inline] +pub fn ini_get(name: &str) -> T { + T::from_ini_value(name) +} diff --git a/phper/src/ini.rs b/phper/src/ini/policy.rs similarity index 76% rename from phper/src/ini.rs rename to phper/src/ini/policy.rs index 80b39da1..4f3b21cd 100644 --- a/phper/src/ini.rs +++ b/phper/src/ini/policy.rs @@ -11,6 +11,7 @@ //! Apis relate to [zend_ini_entry_def]. use crate::{c_str, strings::ZString}; +use std::ffi::CString; use std::{ ffi::{c_int, c_uchar, c_void, CStr}, mem::zeroed, @@ -19,23 +20,9 @@ use std::{ str, }; +use crate::ini::Stage; use phper_sys::*; -/// Get the global registered configuration value. -/// -/// # Examples -/// -/// ```no_run -/// use phper::ini::ini_get; -/// use std::ffi::CStr; -/// -/// let _foo = ini_get::("FOO"); -/// let _bar = ini_get::>("BAR"); -/// ``` -pub fn ini_get(name: &str) -> T { - T::from_ini_value(name) -} - /// Configuration changeable policy. #[repr(u32)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] @@ -46,29 +33,11 @@ pub enum Policy { /// Windows registry. Entry can be set in `.user.ini`. User = PHP_INI_USER, /// Entry can be set in `php.ini`, `.htaccess`, `httpd.conf` or `.user.ini`. - Perdir = PHP_INI_PERDIR, + PerDir = PHP_INI_PERDIR, /// Entry can be set in `php.ini` or `httpd.conf`. System = PHP_INI_SYSTEM, } -/// Configuration for INI Stage. -#[repr(i32)] -#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum Stage { - /// INI Load Event -> Startup -> PHP Started - Startup = ZEND_INI_STAGE_STARTUP as i32, - /// INI Event -> PHP Shutting down - Shutdown = ZEND_INI_STAGE_SHUTDOWN as i32, - /// INI Event -> PHP Module Activated - Activate = ZEND_INI_STAGE_ACTIVATE as i32, - /// INI Event -> PHP Module Deactivated - Deactivate = ZEND_INI_STAGE_DEACTIVATE as i32, - /// INI Event -> Value changed with ini_set from PHP - Runtime = ZEND_INI_STAGE_RUNTIME as i32, - /// INI Event -> Value changed from .htaccess file with php_ini directive - Htacces = ZEND_INI_STAGE_HTACCESS as i32, -} - /// The Type which can transform to an ini value. pub trait IntoIniValue { /// transform to an ini value. @@ -81,37 +50,6 @@ enum PHPIniFunction { DefaultValue(unsafe extern "C" fn(*const c_char, usize, c_int) -> T), } -macro_rules! try_from_stage_int { - ($arg:ty) => { - impl TryFrom<$arg> for Stage { - type Error = Box; - - fn try_from(value: $arg) -> Result { - match value as u32 { - ZEND_INI_STAGE_STARTUP => Ok(Stage::Startup), - ZEND_INI_STAGE_SHUTDOWN => Ok(Stage::Shutdown), - ZEND_INI_STAGE_ACTIVATE => Ok(Stage::Activate), - ZEND_INI_STAGE_DEACTIVATE => Ok(Self::Deactivate), - ZEND_INI_STAGE_RUNTIME => Ok(Stage::Runtime), - ZEND_INI_STAGE_HTACCESS => Ok(Stage::Htacces), - _ => Err("Invalid Zend Stage for INI values".into()), - } - } - } - }; -} - -try_from_stage_int!(i8); -try_from_stage_int!(i16); -try_from_stage_int!(i32); -try_from_stage_int!(i64); -try_from_stage_int!(isize); -try_from_stage_int!(u8); -try_from_stage_int!(u16); -try_from_stage_int!(u32); -try_from_stage_int!(u64); -try_from_stage_int!(usize); - impl IntoIniValue for bool { #[inline] fn into_ini_value(self) -> String { @@ -297,7 +235,7 @@ unsafe extern "C" fn on_modify( /// On INI Change Trait pub trait OnModify { - /// Called whenever INI has chaged + /// Called whenever INI has changed fn on_modify( &mut self, entry: Entry, @@ -335,16 +273,17 @@ type ZendOnModify = unsafe extern "C" fn( pub(crate) fn create_ini_entry_ex( name: impl AsRef, - default_value: impl AsRef, + default_value: String, modifiable: u32, on_modify_impl: Option, ) -> zend_ini_entry_def where T: OnModify, { - let name = name.as_ref(); - let default_value = default_value.as_ref(); - let (modifiable, name_length) = (modifiable as c_uchar, name.len() as u16); + let name_length = name.as_ref().len(); + let name = CString::new(name.as_ref()).unwrap().into_raw(); + let default_value_len = default_value.len(); + let default_value = CString::new(default_value).unwrap_or_default().into_raw(); let (callback, arg): (Option, *mut OnModifyCarry) = match on_modify_impl { Some(callback) => ( @@ -357,22 +296,21 @@ where }; zend_ini_entry_def { - name: name.as_ptr().cast(), - name_length, + name, + name_length: name_length as u16, on_modify: callback, mh_arg1: arg as *mut c_void, mh_arg2: null_mut(), mh_arg3: null_mut(), - value: default_value.as_ptr().cast(), - value_length: default_value.len() as u32, + value: default_value, + value_length: default_value_len as u32, displayer: None, - modifiable, + modifiable: modifiable as c_uchar, } } unsafe fn entries(mut ini_entries: Vec) -> *const zend_ini_entry_def { - ini_entries.push(zeroed::()); - + ini_entries.push(zeroed()); Box::into_raw(ini_entries.into_boxed_slice()).cast() } diff --git a/phper/src/ini/stage.rs b/phper/src/ini/stage.rs new file mode 100644 index 00000000..943cf8c7 --- /dev/null +++ b/phper/src/ini/stage.rs @@ -0,0 +1,53 @@ +use phper_sys::{ + ZEND_INI_STAGE_ACTIVATE, ZEND_INI_STAGE_DEACTIVATE, ZEND_INI_STAGE_HTACCESS, + ZEND_INI_STAGE_RUNTIME, ZEND_INI_STAGE_SHUTDOWN, ZEND_INI_STAGE_STARTUP, +}; + +/// Configuration for INI Stage. +#[repr(i32)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Stage { + /// INI Load Event -> Startup -> PHP Started + Startup = ZEND_INI_STAGE_STARTUP as i32, + /// INI Event -> PHP Shutting down + Shutdown = ZEND_INI_STAGE_SHUTDOWN as i32, + /// INI Event -> PHP Module Activated + Activate = ZEND_INI_STAGE_ACTIVATE as i32, + /// INI Event -> PHP Module Deactivated + Deactivate = ZEND_INI_STAGE_DEACTIVATE as i32, + /// INI Event -> Value changed with ini_set from PHP + Runtime = ZEND_INI_STAGE_RUNTIME as i32, + /// INI Event -> Value changed from .htaccess file with php_ini directive + HtAccess = ZEND_INI_STAGE_HTACCESS as i32, +} + +macro_rules! try_from_stage_int { + ($arg:ty) => { + impl TryFrom<$arg> for Stage { + type Error = String; + + fn try_from(value: $arg) -> Result { + match value as u32 { + ZEND_INI_STAGE_STARTUP => Ok(Stage::Startup), + ZEND_INI_STAGE_SHUTDOWN => Ok(Stage::Shutdown), + ZEND_INI_STAGE_ACTIVATE => Ok(Stage::Activate), + ZEND_INI_STAGE_DEACTIVATE => Ok(Self::Deactivate), + ZEND_INI_STAGE_RUNTIME => Ok(Stage::Runtime), + ZEND_INI_STAGE_HTACCESS => Ok(Stage::HtAccess), + _ => Err("Invalid Zend Stage for INI values".into()), + } + } + } + }; +} + +try_from_stage_int!(i8); +try_from_stage_int!(i16); +try_from_stage_int!(i32); +try_from_stage_int!(i64); +try_from_stage_int!(isize); +try_from_stage_int!(u8); +try_from_stage_int!(u16); +try_from_stage_int!(u32); +try_from_stage_int!(u64); +try_from_stage_int!(usize); diff --git a/phper/src/lib.rs b/phper/src/lib.rs index 474fa6d2..e1709c0e 100644 --- a/phper/src/lib.rs +++ b/phper/src/lib.rs @@ -11,7 +11,6 @@ #![warn(rust_2018_idioms)] #![warn(clippy::dbg_macro, clippy::print_stdout)] #![doc = include_str!("../README.md")] -// #![feature(unboxed_closures)] #[macro_use] mod macros; @@ -32,6 +31,7 @@ pub mod strings; pub mod types; mod utils; pub mod values; +mod zend_result; pub use crate::errors::{ok, Error, Result}; pub use phper_alloc as alloc; diff --git a/phper/src/macros.rs b/phper/src/macros.rs index 7afdf304..6fee2190 100644 --- a/phper/src/macros.rs +++ b/phper/src/macros.rs @@ -27,7 +27,7 @@ macro_rules! echo { /// # Examples /// /// ```no_test -/// phper::errro!("Hello, {}!", message) +/// phper::error!("Hello, {}!", message) /// ``` #[macro_export] macro_rules! error { @@ -133,3 +133,15 @@ macro_rules! sg { $crate::sys::sapi_globals.$x }; } + +#[macro_export] +macro_rules! zend_args { + ($x:expr) => { + unsafe { + let val = std::ptr::addr_of!($x); + let len = $x.len(); + let val = std::slice::from_raw_parts(val, len); + std::mem::transmute::<_, &'static [phper::sys::zend_internal_arg_info]>(val) + }; + }; +} diff --git a/phper/src/modules/extern_c.rs b/phper/src/modules/extern_c.rs new file mode 100644 index 00000000..8781cda8 --- /dev/null +++ b/phper/src/modules/extern_c.rs @@ -0,0 +1,102 @@ +use crate::ini; +use crate::modules::{get_module, Module, ModuleInfo, Registerer, GLOBAL_MODULE_NUMBER}; +use crate::zend_result::ZResult; +use phper_macros::c_str_ptr; +use phper_sys::{ + display_ini_entries, php_info_print_table_end, php_info_print_table_row, + php_info_print_table_start, zend_module_entry, +}; +use std::mem::take; +use std::os::raw::c_int; + +pub(super) unsafe extern "C" fn module_info(zend_module: *mut zend_module_entry) { + let module = get_module(); + + php_info_print_table_start(); + if !module.version.as_bytes().is_empty() { + php_info_print_table_row(2, c_str_ptr!("version"), module.version.as_ptr()); + } + if !module.author.as_bytes().is_empty() { + php_info_print_table_row(2, c_str_ptr!("authors"), module.author.as_ptr()); + } + for (key, value) in &module.infos { + php_info_print_table_row(2, key.as_ptr(), value.as_ptr()); + } + php_info_print_table_end(); + + display_ini_entries(zend_module); +} + +pub(super) unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int { + let module: &mut Module = get_module(); + GLOBAL_MODULE_NUMBER = module_number; + + ini::register(take(&mut module.ini_entities), module_number); + + for entity in take(&mut module.entities).into_iter() { + if let Err(err) = entity.register(module_number) { + crate::output::log( + crate::output::LogLevel::Error, + format!("Failed to register: {err:?}"), + ); + return ZResult::Failure.into(); + } + } + + if let Some(f) = take(&mut module.module_init) { + f(ModuleInfo { + ty: _type, + number: module_number, + }); + } + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn module_shutdown(_type: c_int, module_number: c_int) -> c_int { + ini::unregister(module_number); + + // let module = get_module(); + // + // if let Some(f) = take(&mut module.module_shutdown) { + // f(ModuleInfo { + // ty: _type, + // number: module_number, + // }); + // } + // + // if let Some(ref mut f) = take(&mut module.request_init) { + // let _b = Box::from_raw(f); + // } + // + // if let Some(ref mut f) = take(&mut module.request_shutdown) { + // let _b = Box::from_raw(f); + // } + // + // drop(Box::from_raw(GLOBAL_MODULE)); + // GLOBAL_MODULE = null_mut(); + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn request_startup(_type: c_int, module_number: c_int) -> c_int { + let f = get_module().request_init.unwrap_unchecked(); + + f(ModuleInfo { + ty: _type, + number: module_number, + }); + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn request_shutdown(_type: c_int, module_number: c_int) -> c_int { + let f = get_module().request_shutdown.unwrap_unchecked(); + + f(ModuleInfo { + ty: _type, + number: module_number, + }); + + ZResult::Success.into() +} diff --git a/phper/src/modules.rs b/phper/src/modules/mod.rs similarity index 57% rename from phper/src/modules.rs rename to phper/src/modules/mod.rs index 33d3c9da..744f2e54 100644 --- a/phper/src/modules.rs +++ b/phper/src/modules/mod.rs @@ -10,26 +10,29 @@ //! Apis relate to [zend_module_entry]. -use crate::constants; +mod extern_c; + +use smallvec::SmallVec; +use std::mem::ManuallyDrop; +use std::{ + collections::HashMap, + ffi::CString, + mem::{size_of, zeroed}, + os::raw::{c_uchar, c_uint, c_ushort}, + ptr::{null, null_mut}, +}; + use crate::{ - c_str_ptr, classes::{entity::ClassEntity, InterfaceEntity}, - constants::Constant, + constants::{Constant, Flags}, errors::Throwable, functions::{Function, FunctionEntity, FunctionEntry}, ini, sys::*, - utils::ensure_end_with_zero, values::ZVal, }; -use std::{ - collections::HashMap, - ffi::CString, - mem::{size_of, take, zeroed}, - os::raw::{c_int, c_uchar, c_uint, c_ushort}, - ptr::{null, null_mut}, - rc::Rc, -}; + +use extern_c::{module_info, module_shutdown, module_startup, request_shutdown, request_startup}; /// Global pointer hold the Module builder. /// Because PHP is single threaded, so there is no lock here. @@ -52,102 +55,10 @@ pub struct ModuleInfo { } pub(crate) trait Registerer { - fn register(&mut self, module_number: i32) -> Result<(), Box>; -} - -unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int { - let module: &mut Module = get_module(); - GLOBAL_MODULE_NUMBER = module_number; - - ini::register(take(&mut module.ini_entities), module_number); - - for mut entity in take(&mut module.entities).into_iter() { - if let Err(err) = entity.register(module_number) { - crate::output::log( - crate::output::LogLevel::Error, - format!("Failed to register: {err:?}"), - ); - return ZEND_RESULT_CODE_FAILURE; - } - } - - if let Some(f) = take(&mut module.module_init) { - f(ModuleInfo { - ty: _type, - number: module_number, - }); - } - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn module_shutdown(_type: c_int, module_number: c_int) -> c_int { - { - let module = get_module(); - - ini::unregister(module_number); - - if let Some(f) = take(&mut module.module_shutdown) { - f(ModuleInfo { - ty: _type, - number: module_number, - }); - } - - if let Some(ref mut f) = take(&mut module.request_init) { - let _b = Box::from_raw(f); - } - - if let Some(ref mut f) = take(&mut module.request_shutdown) { - let _b = Box::from_raw(f); - } - } - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn request_startup(_type: c_int, module_number: c_int) -> c_int { - let f = get_module().request_init.unwrap_unchecked(); - - f(ModuleInfo { - ty: _type, - number: module_number, - }); - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn request_shutdown(_type: c_int, module_number: c_int) -> c_int { - let f = get_module().request_shutdown.unwrap_unchecked(); - - f(ModuleInfo { - ty: _type, - number: module_number, - }); - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn module_info(zend_module: *mut zend_module_entry) { - let module = get_module(); - - php_info_print_table_start(); - if !module.version.as_bytes().is_empty() { - php_info_print_table_row(2, c_str_ptr!("version"), module.version.as_ptr()); - } - if !module.author.as_bytes().is_empty() { - php_info_print_table_row(2, c_str_ptr!("authors"), module.author.as_ptr()); - } - for (key, value) in &module.infos { - php_info_print_table_row(2, key.as_ptr(), value.as_ptr()); - } - php_info_print_table_end(); - - display_ini_entries(zend_module); + fn register(self, module_number: i32) -> Result<(), Box>; } /// Builder for registering PHP Module. -#[allow(clippy::type_complexity)] #[derive(Default)] pub struct Module { name: CString, @@ -157,20 +68,20 @@ pub struct Module { module_shutdown: Option>, request_init: Option<&'static dyn Fn(ModuleInfo)>, request_shutdown: Option<&'static dyn Fn(ModuleInfo)>, - function_entities: Vec, entities: Vec, ini_entities: Vec, infos: HashMap, + functions: SmallVec<[zend_function_entry; 64]>, } pub(crate) enum Entities { Constant(Constant), - Class(ClassEntity), + Class(ClassEntity<()>), Interface(InterfaceEntity), } impl Registerer for Entities { - fn register(&mut self, module_number: i32) -> Result<(), Box> { + fn register(self, module_number: i32) -> Result<(), Box> { match self { Entities::Constant(con) => con.register(module_number), Entities::Class(class) => class.register(module_number), @@ -183,17 +94,12 @@ impl Module { /// Construct the `Module` with base metadata. pub fn new(name: impl AsRef, version: impl AsRef, author: impl AsRef) -> Self { Self { - name: ensure_end_with_zero(name), - version: ensure_end_with_zero(version), - author: ensure_end_with_zero(author), - module_init: None, - module_shutdown: None, - request_init: None, - request_shutdown: None, - function_entities: vec![], - entities: Default::default(), - ini_entities: Default::default(), - infos: Default::default(), + name: CString::new(name.as_ref()).expect("Failed to allocate CString, param: name"), + version: CString::new(version.as_ref()) + .expect("Failed to allocate CString, param: version"), + author: CString::new(author.as_ref()) + .expect("Failed to allocate CString, param: author"), + ..Default::default() } } @@ -221,26 +127,39 @@ impl Module { pub fn add_function( &mut self, name: impl AsRef, + arguments: &'static [zend_internal_arg_info], handler: F, - ) -> &mut FunctionEntity + ) -> &mut Self where F: Fn(&mut [ZVal]) -> Result + 'static, Z: Into + 'static, E: Throwable + 'static, { - self.function_entities - .push(FunctionEntity::new(name, Rc::new(Function::new(handler)))); - self.function_entities.last_mut().unwrap() + let handler = Box::new(Function::new(handler)); + let entry = FunctionEntity::new(name, handler, arguments); + + unsafe { + let val = ManuallyDrop::new(FunctionEntry::from_function_entity(entry)); + self.functions.push(val.0); + } + + self } /// Register class to module. - pub fn add_class(&mut self, class: ClassEntity) { - self.entities.push(Entities::Class(class)); + pub fn add_class(&mut self, class: ClassEntity) -> &mut Self { + self.entities.push(Entities::Class(unsafe { + std::mem::transmute::, ClassEntity<()>>(class) + })); + + self } /// Register interface to module. - pub fn add_interface(&mut self, interface: InterfaceEntity) { + pub fn add_interface(&mut self, interface: InterfaceEntity) -> &mut Self { self.entities.push(Entities::Interface(interface)); + + self } /// Register constant to module. @@ -248,10 +167,12 @@ impl Module { &mut self, name: impl AsRef, value: impl Into, - flags: Option, - ) { + flags: Option, + ) -> &mut Self { self.entities .push(Entities::Constant(Constant::new(name, value, flags))); + + self } /// Register ini configuration to module. @@ -260,14 +181,16 @@ impl Module { name: impl AsRef, default_value: impl ini::IntoIniValue, policy: ini::Policy, - ) { + ) -> &mut Self { let ini = ini::create_ini_entry_ex( - name.as_ref(), + name, default_value.into_ini_value(), policy as u32, Option::<()>::None, ); self.ini_entities.push(ini); + + self } /// Register info item. @@ -275,23 +198,23 @@ impl Module { /// # Panics /// /// Panic if key or value contains '\0'. - pub fn add_info(&mut self, key: impl Into, value: impl Into) { + pub fn add_info(&mut self, key: impl Into, value: impl Into) -> &mut Self { let key = CString::new(key.into()).expect("key contains '\0'"); let value = CString::new(value.into()).expect("value contains '\0'"); self.infos.insert(key, value); + + self } #[doc(hidden)] - pub unsafe fn module_entry(self) -> *const zend_module_entry { + pub unsafe fn module_entry(mut self) -> *const zend_module_entry { if !GLOBAL_MODULE_ENTRY.is_null() { return GLOBAL_MODULE_ENTRY; } - assert!(!self.name.as_bytes().is_empty(), "module name must be set"); - assert!( - !self.version.as_bytes().is_empty(), - "module version must be set" - ); + self.functions.push(zeroed::()); + self.functions.shrink_to_fit(); + let fns = ManuallyDrop::new(std::mem::take(&mut self.functions).into_boxed_slice()); let module = Box::leak(Box::new(self)); @@ -303,7 +226,7 @@ impl Module { ini_entry: null(), deps: null(), name: module.name.as_ptr(), - functions: module.function_entries(), + functions: fns.as_ptr(), module_startup_func: Some(module_startup), module_shutdown_func: Some(module_shutdown), request_startup_func: if module.request_init.is_some() { @@ -320,9 +243,9 @@ impl Module { version: module.version.as_ptr(), globals_size: 0, #[cfg(phper_zts)] - globals_id_ptr: std::ptr::null_mut(), + globals_id_ptr: null_mut(), #[cfg(not(phper_zts))] - globals_ptr: std::ptr::null_mut(), + globals_ptr: null_mut(), globals_ctor: None, globals_dtor: None, post_deactivate_func: None, @@ -338,18 +261,4 @@ impl Module { GLOBAL_MODULE_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 { FunctionEntry::from_function_entity(f) }); - } - entries.push(unsafe { zeroed::() }); - - Box::into_raw(entries.into_boxed_slice()).cast() - } } diff --git a/phper/src/objects.rs b/phper/src/objects.rs index 71d74f03..6fc558bd 100644 --- a/phper/src/objects.rs +++ b/phper/src/objects.rs @@ -16,6 +16,7 @@ use crate::{ sys::*, values::ZVal, }; +use memoffset::offset_of; use phper_alloc::{RefClone, ToRefOwned}; use std::{ any::Any, @@ -24,11 +25,10 @@ use std::{ ffi::c_void, fmt::{self, Debug}, marker::PhantomData, - mem::{replace, ManuallyDrop}, + mem::ManuallyDrop, ops::{Deref, DerefMut}, ptr::null_mut, }; -use memoffset::offset_of; /// Wrapper of [zend_object]. #[repr(transparent)] @@ -112,7 +112,7 @@ impl ZObj { /// # Safety /// /// Should only call this method for the class of object defined by the - /// extension created by `phper`, otherwise, memory problems will caused. + /// extension created by `phper`, otherwise, memory problems will be caused. pub unsafe fn as_mut_state_obj(&mut self) -> &mut StateObj { StateObj::from_mut_object_ptr(self.as_mut_ptr()) } @@ -184,33 +184,17 @@ impl ZObj { } /// Set the property by name of object. - #[allow(clippy::useless_conversion)] pub fn set_property(&mut self, name: impl AsRef, val: impl Into) { let name = name.as_ref(); let mut val = val.into(); unsafe { - #[cfg(phper_major_version = "8")] - { - zend_update_property( - self.inner.ce, - &mut self.inner, - name.as_ptr().cast(), - name.len().try_into().unwrap(), - val.as_mut_ptr(), - ) - } - #[cfg(phper_major_version = "7")] - { - let mut zv = std::mem::zeroed::(); - phper_zval_obj(&mut zv, self.as_mut_ptr()); - zend_update_property( - self.inner.ce, - &mut zv, - name.as_ptr().cast(), - name.len().try_into().unwrap(), - val.as_mut_ptr(), - ) - } + zend_update_property( + self.inner.ce, + &mut self.inner, + name.as_ptr().cast(), + name.len(), + val.as_mut_ptr(), + ); } } @@ -267,8 +251,10 @@ impl ZObj { } } - pub(crate) unsafe fn gc_refcount(&self) -> u32 { - phper_zend_object_gc_refcount(self.as_ptr()) + #[allow(dead_code)] + #[inline] + pub(crate) fn gc_refcount(&self) -> u32 { + unsafe { phper_zend_object_gc_refcount(self.as_ptr()) } } } @@ -379,12 +365,10 @@ impl Debug for ZObject { } } -pub(crate) type AnyState = *mut dyn Any; - /// The object owned state, usually as the parameter of method handler. #[repr(C)] pub struct StateObj { - any_state: AnyState, + pub(crate) state: Option>, object: ZObj, } @@ -415,13 +399,13 @@ impl StateObj { } pub(crate) unsafe fn drop_state(&mut self) { - drop(Box::from_raw(self.any_state)); - } - - #[inline] - pub(crate) fn as_mut_any_state(&mut self) -> &mut AnyState { - &mut self.any_state + let _to_drop = std::mem::take(&mut self.state); } + // + // #[inline] + // pub(crate) fn as_mut_any_state(&mut self) -> &mut AnyState { + // &mut self.any_state + // } /// Gets object. #[inline] @@ -438,19 +422,15 @@ impl StateObj { impl StateObj { /// Gets inner state. - pub fn as_state(&self) -> &T { - unsafe { - let any_state = self.any_state.as_ref().unwrap(); - any_state.downcast_ref().unwrap() - } + #[inline] + pub fn as_state(&self) -> Option<&T> { + self.state.as_ref().and_then(|s| s.downcast_ref::()) } /// Gets inner mutable state. - pub fn as_mut_state(&mut self) -> &mut T { - unsafe { - let any_state = self.any_state.as_mut().unwrap(); - any_state.downcast_mut().unwrap() - } + #[inline] + pub fn as_mut_state(&mut self) -> Option<&mut T> { + self.state.as_mut().and_then(|s| s.downcast_mut::()) } } @@ -476,18 +456,12 @@ impl Debug for StateObj { /// The object owned state, usually crated by /// [StaticStateClass](crate::classes::StaticStateClass). -pub struct StateObject { - inner: *mut StateObj, -} +pub struct StateObject(*mut StateObj); impl StateObject { #[inline] pub(crate) fn from_raw_object(object: *mut zend_object) -> Self { - unsafe { - Self { - inner: StateObj::from_mut_object_ptr(object), - } - } + unsafe { Self(StateObj::from_mut_object_ptr(object)) } } #[inline] @@ -508,15 +482,13 @@ impl StateObject { /// therefore, you can only obtain state ownership when the refcount of the /// [zend_object] is `1`, otherwise, it will return /// `None`. - pub fn into_state(mut self) -> Option { - unsafe { - if self.gc_refcount() != 1 { - return None; - } - let null: AnyState = Box::into_raw(Box::new(())); - let ptr = replace(self.as_mut_any_state(), null); - Some(*Box::from_raw(ptr).downcast().unwrap()) - } + pub fn into_state(self) -> Option { + // if self.gc_refcount() == 1 { + // let val = unsafe { std::mem::zeroed::() }; + // Some(replace(&mut self.any_state, val)) + // } else { + None + // } } } @@ -532,13 +504,13 @@ impl Deref for StateObject { type Target = StateObj; fn deref(&self) -> &Self::Target { - unsafe { self.inner.as_ref().unwrap() } + unsafe { self.0.as_ref().unwrap() } } } impl DerefMut for StateObject { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.inner.as_mut().unwrap() } + unsafe { self.0.as_mut().unwrap() } } } diff --git a/phper/src/strings.rs b/phper/src/strings.rs index 96d05033..2a22ed1b 100644 --- a/phper/src/strings.rs +++ b/phper/src/strings.rs @@ -109,6 +109,18 @@ impl ZStr { self.inner.val.as_ptr() } + /// Get String from Zend String + /// + /// # Safety + /// Only valid UTF8 Strings should be transformed + /// Caller is responsible for checking string + #[inline] + #[allow(dead_code)] + pub unsafe fn as_str(&self) -> &str { + let val: &[u8] = from_raw_parts(self.inner.val.as_ptr() as *const u8, self.inner.len); + std::str::from_utf8_unchecked(val) + } + /// Gets the inner C string length. #[inline] pub fn len(&self) -> usize { diff --git a/phper/src/values.rs b/phper/src/values.rs index bd328457..36d4d3df 100644 --- a/phper/src/values.rs +++ b/phper/src/values.rs @@ -1,4 +1,5 @@ // Copyright (c) 2022 PHPER Framework Team +#![allow(clippy::ptr_offset_with_cast)] // PHPER is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. You may obtain a copy of Mulan PSL v2 at: @@ -10,6 +11,7 @@ //! Apis relate to [zval]. +use crate::functions::Callable; use crate::{ alloc::EBox, arrays::{ZArr, ZArray}, @@ -24,20 +26,18 @@ use crate::{ }; use phper_alloc::RefClone; use std::{ - convert::TryInto, - ffi::CStr, - fmt, - fmt::Debug, - marker::PhantomData, - mem::{transmute, zeroed, ManuallyDrop, MaybeUninit}, - str, + convert::TryInto, ffi::CStr, fmt, fmt::Debug, marker::PhantomData, mem::MaybeUninit, str, }; +#[repr(C)] +union ToZendInternalArg { + pub(crate) callable: *const dyn Callable, + pub(crate) internal_arg_info: zend_internal_arg_info, +} + /// Wrapper of [zend_execute_data]. #[repr(transparent)] -pub struct ExecuteData { - inner: zend_execute_data, -} +pub struct ExecuteData(zend_execute_data); impl ExecuteData { /// Wraps a raw pointer. @@ -93,49 +93,86 @@ impl ExecuteData { /// Returns a raw pointer wrapped. pub const fn as_ptr(&self) -> *const zend_execute_data { - &self.inner + &self.0 } /// Returns a raw pointer wrapped. #[inline] #[allow(dead_code)] pub fn as_mut_ptr(&mut self) -> *mut zend_execute_data { - &mut self.inner + &mut self.0 } /// Gets common arguments count. #[inline] pub fn common_num_args(&self) -> u32 { - unsafe { (*self.inner.func).common.num_args } + unsafe { (*self.0.func).common.num_args } } /// Gets common required arguments count. #[inline] pub fn common_required_num_args(&self) -> usize { - unsafe { (*self.inner.func).common.required_num_args as usize } + unsafe { (*self.0.func).common.required_num_args as usize } } /// Gets first common argument info. #[inline] pub fn common_arg_info(&self) -> *mut zend_arg_info { - unsafe { (*self.inner.func).common.arg_info } + unsafe { (*self.0.func).common.arg_info } + } + + #[inline] + pub(crate) unsafe fn get_handler(&self) -> &'static dyn Callable { + let len = self.common_num_args() as usize; + let arg_info = (*self.0.func) + .internal_function + .arg_info + .offset((len - 1) as isize) + .cast_const(); + + let items = std::slice::from_raw_parts(arg_info, 1); + + let val = ToZendInternalArg { + internal_arg_info: items[0], + }; + + val.callable.as_ref().unwrap_unchecked() + } + + pub(crate) unsafe fn write_handler( + handler: Box, + arguments: &'static [zend_internal_arg_info], + ) -> (*const zend_internal_arg_info, u32) { + let callable = Box::into_raw(handler); + + let val = ToZendInternalArg { callable }; + + let mut data = Vec::with_capacity(arguments.len()); + data.extend_from_slice(arguments); + data.push(val.internal_arg_info); + data.shrink_to_fit(); + + let val = Box::into_raw(data.into_boxed_slice()); + + (val as *const zend_internal_arg_info, arguments.len() as u32) } /// Gets arguments count. #[inline] pub fn num_args(&self) -> usize { - unsafe { phper_zend_num_args(self.as_ptr()).try_into().unwrap() } + unsafe { self.0.This.u2.num_args as usize } } /// Gets associated function. pub fn func(&self) -> &ZFunc { - unsafe { ZFunc::from_mut_ptr(self.inner.func) } + unsafe { ZFunc::from_mut_ptr(self.0.func) } } /// Gets associated `$this` object if exists. - pub fn get_this(&mut self) -> Option<&ZObj> { + #[inline] + pub fn get_this(&self) -> Option<&ZObj> { unsafe { - let val = ZVal::from_ptr(phper_get_this(&self.inner)); + let val = ZVal::from_ptr(&self.0.This); val.as_z_obj() } } @@ -143,21 +180,23 @@ impl ExecuteData { /// Gets associated mutable `$this` object if exists. pub fn get_this_mut(&mut self) -> Option<&mut ZObj> { unsafe { - let val = ZVal::from_mut_ptr(phper_get_this(&self.inner)); + let val = ZVal::from_mut_ptr(&mut self.0.This); val.as_mut_z_obj() } } - pub(crate) unsafe fn get_parameters_array(&mut self) -> Vec> { - let num_args = self.num_args(); - let mut arguments = vec![zeroed::(); num_args]; - if num_args > 0 { - phper_zend_get_parameters_array_ex( - num_args.try_into().unwrap(), - arguments.as_mut_ptr(), - ); + #[inline] + pub(crate) unsafe fn get_parameters_array(&mut self) -> Option { + let args_count = self.num_args(); + let mut arr = ZVal::from(ZArray::with_capacity(args_count)); + + if zend_copy_parameters_array(args_count as u32, arr.as_mut_ptr()) + == ZEND_RESULT_CODE_FAILURE + { + None + } else { + Some(ZArray::from_zval(arr)) } - transmute(arguments) } /// Gets parameter by index. @@ -556,8 +595,6 @@ impl ZVal { } /// Internally convert to long. - /// - /// TODO To fix assertion failed. pub fn convert_to_long(&mut self) { unsafe { phper_convert_to_long(self.as_mut_ptr()); @@ -565,8 +602,6 @@ impl ZVal { } /// Internally convert to string. - /// - /// TODO To fix assertion failed. pub fn convert_to_string(&mut self) { unsafe { phper_convert_to_string(self.as_mut_ptr()); diff --git a/phper/src/zend_result.rs b/phper/src/zend_result.rs new file mode 100644 index 00000000..4ed1d1cd --- /dev/null +++ b/phper/src/zend_result.rs @@ -0,0 +1,14 @@ +use crate::sys::{ZEND_RESULT_CODE_FAILURE, ZEND_RESULT_CODE_SUCCESS}; +use std::ffi::c_int; + +#[repr(i32)] +pub enum ZResult { + Success = ZEND_RESULT_CODE_SUCCESS, + Failure = ZEND_RESULT_CODE_FAILURE, +} + +impl From for c_int { + fn from(val: ZResult) -> Self { + val as c_int + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 924c8f41..f4f74be5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -9,5 +9,5 @@ # See the Mulan PSL v2 for more details. [toolchain] -channel = "1.75" +channel = "1.78" components = ["clippy", "rustfmt"] diff --git a/tests/integration/src/arguments.rs b/tests/integration/src/arguments.rs index f4346dc0..ef0a40a5 100644 --- a/tests/integration/src/arguments.rs +++ b/tests/integration/src/arguments.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - alloc::ToRefOwned, arrays::ZArray, functions::Argument, modules::Module, objects::ZObject, + alloc::ToRefOwned, arrays::ZArray, arguments::Argument, modules::Module, objects::ZObject, values::ZVal, }; diff --git a/tests/integration/src/classes.rs b/tests/integration/src/classes.rs index 46386caf..df9cf157 100644 --- a/tests/integration/src/classes.rs +++ b/tests/integration/src/classes.rs @@ -16,7 +16,7 @@ use phper::{ zend_classes::{array_access_interface, iterator_interface}, InterfaceEntity, StaticInterface, StaticStateClass, Visibility, }, - functions::Argument, + arguments::Argument, modules::Module, values::ZVal, }; diff --git a/tests/integration/src/functions.rs b/tests/integration/src/functions.rs index cb024cd0..b25dba35 100644 --- a/tests/integration/src/functions.rs +++ b/tests/integration/src/functions.rs @@ -11,7 +11,8 @@ use phper::{ arrays::ZArray, errors::throw, - functions::{call, Argument}, + functions::call, + arguments::Argument, modules::Module, values::ZVal, }; diff --git a/tests/integration/src/objects.rs b/tests/integration/src/objects.rs index ca390c82..2add2989 100644 --- a/tests/integration/src/objects.rs +++ b/tests/integration/src/objects.rs @@ -11,7 +11,7 @@ use phper::{ alloc::{RefClone, ToRefOwned}, classes::{entity::ClassEntity, entry::ClassEntry, Visibility}, - functions::Argument, + arguments::Argument, modules::Module, objects::ZObject, types::TypeInfo, diff --git a/tests/integration/src/references.rs b/tests/integration/src/references.rs index 2f74bcad..84d5f439 100644 --- a/tests/integration/src/references.rs +++ b/tests/integration/src/references.rs @@ -8,7 +8,7 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper::{functions::Argument, modules::Module}; +use phper::{arguments::Argument, modules::Module}; #[allow(clippy::disallowed_names)] pub fn integrate(module: &mut Module) {