diff --git a/Cargo.lock b/Cargo.lock index 4687a97fac..bd49be6919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "adler2" version = "2.0.0" @@ -68,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "approx" @@ -115,9 +109,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -133,9 +127,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "blake2" @@ -157,9 +151,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", @@ -174,9 +168,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -222,9 +216,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.21" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -249,9 +243,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -287,14 +281,14 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static 1.5.0", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -331,9 +325,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -501,9 +495,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -520,15 +514,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -542,9 +536,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -571,7 +565,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] @@ -632,9 +626,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "endian-type" @@ -661,12 +655,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -730,9 +724,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "libz-sys", @@ -813,9 +807,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "half" @@ -842,6 +836,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" @@ -877,11 +877,11 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -909,12 +909,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -925,26 +925,26 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "insta" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" dependencies = [ "console", - "lazy_static 1.5.0", "linked-hash-map", + "once_cell", "similar", ] [[package]] name = "is-macro" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2069faacbe981460232f880d26bf3c7634e322d49053aa48c27e3ae642f728f1" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" dependencies = [ - "Inflector", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] @@ -967,16 +967,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1049,9 +1050,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libffi" @@ -1072,11 +1073,21 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1084,7 +1095,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", ] @@ -1101,9 +1112,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "pkg-config", @@ -1118,9 +1129,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -1134,9 +1145,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lz4_flex" @@ -1183,7 +1194,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", "itertools 0.11.0", "libm", "ryu", @@ -1272,9 +1283,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -1303,7 +1314,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -1316,7 +1327,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -1377,14 +1388,14 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -1394,11 +1405,11 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -1415,29 +1426,29 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1480,7 +1491,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.5", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -1493,18 +1504,18 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", @@ -1512,9 +1523,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -1522,11 +1533,11 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] @@ -1571,14 +1582,14 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "portable-atomic" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" @@ -1591,9 +1602,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -1606,9 +1617,9 @@ checksum = "3804877ffeba468c806c2ad9057bbbae92e4b2c410c2f108baaa0042f241fa4c" [[package]] name = "pyo3" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e89ce2565d6044ca31a3eb79a334c3a79a841120a98f64eea9f579564cb691" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" dependencies = [ "cfg-if", "indoc", @@ -1624,9 +1635,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8afbaf3abd7325e08f35ffb8deb5892046fcb2608b703db6a583a5ba4cea01e" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", "target-lexicon", @@ -1634,9 +1645,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec15a5ba277339d04763f4c23d85987a5b08cbb494860be141e6a10a8eb88022" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" dependencies = [ "libc", "pyo3-build-config", @@ -1644,34 +1655,34 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e0f01b5364bcfbb686a52fc4181d412b708a68ed20c330db9fc8d2c2bf5a43" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "pyo3-macros-backend" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a09b550200e1e5ed9176976d0060cbc2ea82dc8515da07885e7b8153a85caacb" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1750,11 +1761,11 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62871f2d65009c0256aed1b9cfeeb8ac272833c404e13d53d400cd0dad7a2ac0" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -1782,9 +1793,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1794,9 +1805,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1805,9 +1816,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "region" @@ -1839,7 +1850,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] @@ -1859,15 +1870,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1909,7 +1920,7 @@ name = "rustpython-codegen" version = "0.4.0" dependencies = [ "ahash", - "bitflags 2.6.0", + "bitflags 2.8.0", "indexmap", "insta", "itertools 0.11.0", @@ -1927,7 +1938,7 @@ name = "rustpython-common" version = "0.4.0" dependencies = [ "ascii", - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "itertools 0.11.0", "libc", @@ -1942,7 +1953,7 @@ dependencies = [ "radium", "rand", "rustpython-format", - "siphasher", + "siphasher 0.3.11", "volatile", "widestring", "windows-sys 0.52.0", @@ -1961,7 +1972,7 @@ dependencies = [ name = "rustpython-compiler-core" version = "0.4.0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "itertools 0.11.0", "lz4_flex", "malachite-bigint", @@ -2009,7 +2020,7 @@ name = "rustpython-format" version = "0.4.0" source = "git+https://github.com/RustPython/Parser.git?rev=f07b97cef396d573364639904d767c9ff3e3e701#f07b97cef396d573364639904d767c9ff3e3e701" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "itertools 0.11.0", "malachite-bigint", "num-traits", @@ -2098,7 +2109,7 @@ dependencies = [ name = "rustpython-sre_engine" version = "0.4.0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "num_enum", "optional", ] @@ -2185,7 +2196,7 @@ version = "0.4.0" dependencies = [ "ahash", "ascii", - "bitflags 2.6.0", + "bitflags 2.8.0", "bstr", "caseless", "cfg-if", @@ -2203,6 +2214,8 @@ dependencies = [ "itertools 0.11.0", "junction", "libc", + "libffi", + "libloading", "log", "malachite-bigint", "memchr", @@ -2276,9 +2289,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rustyline" @@ -2286,7 +2299,7 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "clipboard-win", "fd-lock", @@ -2319,9 +2332,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -2334,15 +2347,15 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2371,20 +2384,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -2432,9 +2445,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" @@ -2442,6 +2455,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slice-group-by" version = "0.3.1" @@ -2456,9 +2475,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2492,7 +2511,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] @@ -2514,9 +2533,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2594,22 +2613,22 @@ checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] [[package]] @@ -2660,9 +2679,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -2828,9 +2847,9 @@ checksum = "623f59e6af2a98bdafeb93fa277ac8e1e40440973001ca15cf4ae1541cd16d56" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] name = "unicode-normalization" @@ -2889,9 +2908,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "atomic", "getrandom", @@ -2940,46 +2959,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2987,28 +3008,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3291,9 +3315,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xml-rs" -version = "0.8.22" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "zerocopy" @@ -3313,5 +3337,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index eadbdea4b7..f94625381a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ license.workspace = true [features] default = ["threading", "stdlib", "zlib", "importlib"] +ctypes = ["rustpython-vm/ctypes"] importlib = ["rustpython-vm/importlib"] encodings = ["rustpython-vm/encodings"] stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"] diff --git a/jit/Cargo.toml b/jit/Cargo.toml index cc26eb59a5..edb3ffd05c 100644 --- a/jit/Cargo.toml +++ b/jit/Cargo.toml @@ -21,7 +21,7 @@ cranelift-jit = "0.88.0" cranelift-module = "0.88.0" [dependencies.libffi] -version = "3.1.0" +version = "3.2.0" features = ["system"] [dev-dependencies] diff --git a/match_test.py b/match_test.py new file mode 100644 index 0000000000..ee1313e119 --- /dev/null +++ b/match_test.py @@ -0,0 +1,7 @@ +# Test match statement +x = 1 +match x: + case 1: + print("one") + case 2: + print("two") diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 6eaca281f8..5bb00afeb0 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -24,6 +24,7 @@ codegen = ["rustpython-codegen", "ast"] parser = ["rustpython-parser", "ast"] serde = ["dep:serde"] wasmbind = ["chrono/wasmbind", "getrandom/js", "wasm-bindgen"] +ctypes = [] [dependencies] rustpython-compiler = { workspace = true, optional = true } @@ -87,9 +88,14 @@ unicode_names2 = { workspace = true } # https://github.com/RustPython/RustPython/pull/832#discussion_r275428939 unicode-casing = "0.1.0" # update version all at the same time -unic-ucd-bidi = "0.9.0" -unic-ucd-category = "0.9.0" -unic-ucd-ident = "0.9.0" +unic-ucd-bidi = "0.9.0" +unic-ucd-category = "0.9.0" +unic-ucd-ident = "0.9.0" + +# ctypes +libloading = "0.8.6" +libffi = "3.2.0" +widestring = "1.1.0" [target.'cfg(unix)'.dependencies] rustix = { workspace = true } diff --git a/vm/src/stdlib/ctypes/_ctypes_test.c b/vm/src/stdlib/ctypes/_ctypes_test.c new file mode 100644 index 0000000000..17e10c64f4 --- /dev/null +++ b/vm/src/stdlib/ctypes/_ctypes_test.c @@ -0,0 +1,860 @@ +#if defined(_MSC_VER) || defined(__CYGWIN__) +#include +#define MS_WIN32 +#endif + +#ifdef _WIN32 +#define EXPORT(x) __declspec(dllexport) x +#else +#define EXPORT(x) extern x +#endif + +#include +#include +#include +#include +#include +#include + +#define HAVE_LONG_LONG +#define LONG_LONG long long +#define HAVE_WCHAR_H + +/* some functions handy for testing */ + +EXPORT(int) +_testfunc_cbk_reg_int(int a, int b, int c, int d, int e, + int (*func)(int, int, int, int, int)) +{ + return func(a*a, b*b, c*c, d*d, e*e); +} + +EXPORT(double) +_testfunc_cbk_reg_double(double a, double b, double c, double d, double e, + double (*func)(double, double, double, double, double)) +{ + return func(a*a, b*b, c*c, d*d, e*e); +} + +/* + * This structure should be the same as in test_callbacks.py and the + * method test_callback_large_struct. See issues 17310 and 20160: the + * structure must be larger than 8 bytes long. + */ + +typedef struct { + unsigned long first; + unsigned long second; + unsigned long third; +} Test; + +EXPORT(void) +_testfunc_cbk_large_struct(Test in, void (*func)(Test)) +{ + func(in); +} + +/* + * See issue 29565. Update a structure passed by value; + * the caller should not see any change. + */ + +EXPORT(void) +_testfunc_large_struct_update_value(Test in) +{ + ((volatile Test *)&in)->first = 0x0badf00d; + ((volatile Test *)&in)->second = 0x0badf00d; + ((volatile Test *)&in)->third = 0x0badf00d; +} + +typedef struct { + unsigned int first; + unsigned int second; +} TestReg; + + +EXPORT(TestReg) last_tfrsuv_arg = {0}; + + +EXPORT(void) +_testfunc_reg_struct_update_value(TestReg in) +{ + last_tfrsuv_arg = in; + ((volatile TestReg *)&in)->first = 0x0badf00d; + ((volatile TestReg *)&in)->second = 0x0badf00d; +} + +/* + * See bpo-22273. Structs containing arrays should work on Linux 64-bit. + */ + +typedef struct { + unsigned char data[16]; +} Test2; + +EXPORT(int) +_testfunc_array_in_struct1(Test2 in) +{ + int result = 0; + + for (unsigned i = 0; i < 16; i++) + result += in.data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +typedef struct { + double data[2]; +} Test3; + +typedef struct { + float data[2]; + float more_data[2]; +} Test3B; + +EXPORT(double) +_testfunc_array_in_struct2(Test3 in) +{ + double result = 0; + + for (unsigned i = 0; i < 2; i++) + result += in.data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +EXPORT(double) +_testfunc_array_in_struct2a(Test3B in) +{ + double result = 0; + + for (unsigned i = 0; i < 2; i++) + result += in.data[i]; + for (unsigned i = 0; i < 2; i++) + result += in.more_data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +typedef union { + long a_long; + struct { + int an_int; + int another_int; + } a_struct; +} Test4; + +typedef struct { + int an_int; + struct { + int an_int; + Test4 a_union; + } nested; + int another_int; +} Test5; + +EXPORT(long) +_testfunc_union_by_value1(Test4 in) { + long result = in.a_long + in.a_struct.an_int + in.a_struct.another_int; + + /* As the union/struct are passed by value, changes to them shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_union_by_value2(Test5 in) { + long result = in.an_int + in.nested.an_int; + + /* As the union/struct are passed by value, changes to them shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference1(Test4 *in) { + long result = in->a_long; + + memset(in, 0, sizeof(Test4)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference2(Test4 *in) { + long result = in->a_struct.an_int + in->a_struct.another_int; + + memset(in, 0, sizeof(Test4)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference3(Test5 *in) { + long result = in->an_int + in->nested.an_int + in->another_int; + + memset(in, 0, sizeof(Test5)); + return result; +} + +typedef struct { + signed int A: 1, B:2, C:3, D:2; +} Test6; + +EXPORT(long) +_testfunc_bitfield_by_value1(Test6 in) { + long result = in.A + in.B + in.C + in.D; + + /* As the struct is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_bitfield_by_reference1(Test6 *in) { + long result = in->A + in->B + in->C + in->D; + + memset(in, 0, sizeof(Test6)); + return result; +} + +typedef struct { + unsigned int A: 1, B:2, C:3, D:2; +} Test7; + +EXPORT(long) +_testfunc_bitfield_by_reference2(Test7 *in) { + long result = in->A + in->B + in->C + in->D; + + memset(in, 0, sizeof(Test7)); + return result; +} + +typedef union { + signed int A: 1, B:2, C:3, D:2; +} Test8; + +EXPORT(long) +_testfunc_bitfield_by_value2(Test8 in) { + long result = in.A + in.B + in.C + in.D; + + /* As the struct is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(void)testfunc_array(int values[4]) +{ + printf("testfunc_array %d %d %d %d\n", + values[0], + values[1], + values[2], + values[3]); +} + +EXPORT(long double)testfunc_Ddd(double a, double b) +{ + long double result = (long double)(a * b); + printf("testfunc_Ddd(%p, %p)\n", (void *)&a, (void *)&b); + printf("testfunc_Ddd(%g, %g)\n", a, b); + return result; +} + +EXPORT(long double)testfunc_DDD(long double a, long double b) +{ + long double result = a * b; + printf("testfunc_DDD(%p, %p)\n", (void *)&a, (void *)&b); + printf("testfunc_DDD(%Lg, %Lg)\n", a, b); + return result; +} + +EXPORT(int)testfunc_iii(int a, int b) +{ + int result = a * b; + printf("testfunc_iii(%p, %p)\n", (void *)&a, (void *)&b); + return result; +} + +EXPORT(int)myprintf(char *fmt, ...) +{ + int result; + va_list argptr; + va_start(argptr, fmt); + result = vprintf(fmt, argptr); + va_end(argptr); + return result; +} + +EXPORT(char *)my_strtok(char *token, const char *delim) +{ + return strtok(token, delim); +} + +EXPORT(char *)my_strchr(const char *s, int c) +{ + return strchr(s, c); +} + + +EXPORT(double) my_sqrt(double a) +{ + return sqrt(a); +} + +EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) +{ + qsort(base, num, width, compare); +} + +EXPORT(char) deref_LP_c_char_p(char** argv) +{ + char* s = *argv; + return s[0]; +} + +EXPORT(int *) _testfunc_ai8(int a[8]) +{ + return a; +} + +EXPORT(void) _testfunc_v(int a, int b, int *presult) +{ + *presult = a + b; +} + +EXPORT(int) _testfunc_i_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_i_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (int)(b + h + i + l + f + d); +} + +EXPORT(float) _testfunc_f_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_f_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (float)(b + h + i + l + f + d); +} + +EXPORT(double) _testfunc_d_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_d_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (double)(b + h + i + l + f + d); +} + +EXPORT(char *) _testfunc_p_p(void *s) +{ + return (char *)s; +} + +EXPORT(void *) _testfunc_c_p_p(int *argcp, char **argv) +{ + return argv[(*argcp)-1]; +} + +EXPORT(void *) get_strchr(void) +{ + return (void *)strchr; +} + +EXPORT(char *) my_strdup(char *src) +{ + char *dst = (char *)malloc(strlen(src)+1); + if (!dst) + return NULL; + strcpy(dst, src); + return dst; +} + +EXPORT(void)my_free(void *ptr) +{ + free(ptr); +} + +#ifdef HAVE_WCHAR_H +EXPORT(wchar_t *) my_wcsdup(wchar_t *src) +{ + size_t len = wcslen(src); + wchar_t *ptr = (wchar_t *)malloc((len + 1) * sizeof(wchar_t)); + if (ptr == NULL) + return NULL; + memcpy(ptr, src, (len+1) * sizeof(wchar_t)); + return ptr; +} + +EXPORT(size_t) my_wcslen(wchar_t *src) +{ + return wcslen(src); +} +#endif + +#ifndef MS_WIN32 +# ifndef __stdcall +# define __stdcall /* */ +# endif +#endif + +typedef struct { + int (*c)(int, int); + int (__stdcall *s)(int, int); +} FUNCS; + +EXPORT(int) _testfunc_callfuncp(FUNCS *fp) +{ + fp->c(1, 2); + fp->s(3, 4); + return 0; +} + +EXPORT(int) _testfunc_deref_pointer(int *pi) +{ + return *pi; +} + +#ifdef MS_WIN32 +EXPORT(int) _testfunc_piunk(IUnknown FAR *piunk) +{ + piunk->lpVtbl->AddRef(piunk); + return piunk->lpVtbl->Release(piunk); +} +#endif + +EXPORT(int) _testfunc_callback_with_pointer(int (*func)(int *)) +{ + int table[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + return (*func)(table); +} + +EXPORT(int) _testfunc_callback_opaque(int (*func)(void*), void* arg) +{ + return (*func)(arg); +} + +EXPORT(int) _testfunc_callback_void(void (*func)(void)) +{ + func(); + return 0; +} + +#ifdef HAVE_LONG_LONG +EXPORT(LONG_LONG) _testfunc_q_bhilfdq(signed char b, short h, int i, long l, float f, + double d, LONG_LONG q) +{ + return (LONG_LONG)(b + h + i + l + f + d + q); +} + +EXPORT(LONG_LONG) _testfunc_q_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ + return (LONG_LONG)(b + h + i + l + f + d); +} + +EXPORT(int) _testfunc_callback_i_if(int value, int (*func)(int)) +{ + int sum = 0; + while (value != 0) { + sum += func(value); + value /= 2; + } + return sum; +} + +EXPORT(LONG_LONG) _testfunc_callback_q_qf(LONG_LONG value, + LONG_LONG (*func)(LONG_LONG)) +{ + LONG_LONG sum = 0; + + while (value != 0) { + sum += func(value); + value /= 2; + } + return sum; +} + +#endif + +typedef struct { + char *name; + char *value; +} SPAM; + +typedef struct { + char *name; + int num_spams; + SPAM *spams; +} EGG; + +SPAM my_spams[2] = { + { "name1", "value1" }, + { "name2", "value2" }, +}; + +EGG my_eggs[1] = { + { "first egg", 1, my_spams } +}; + +EXPORT(int) getSPAMANDEGGS(EGG **eggs) +{ + *eggs = my_eggs; + return 1; +} + +typedef struct tagpoint { + int x; + int y; +} point; + +EXPORT(int) _testfunc_byval(point in, point *pout) +{ + if (pout) { + pout->x = in.x; + pout->y = in.y; + } + return in.x + in.y; +} + +EXPORT (int) an_integer = 42; + +EXPORT(int) get_an_integer(void) +{ + return an_integer; +} + +EXPORT(char) a_string[16] = "0123456789abcdef"; + +EXPORT(int) get_a_string_char(int index) +{ + return a_string[index]; +} + +EXPORT(double) +integrate(double a, double b, double (*f)(double), long nstep) +{ + double x, sum=0.0, dx=(b-a)/(double)nstep; + for(x=a+0.5*dx; (b-x)*(x-a)>0.0; x+=dx) + { + double y = f(x); + printf("f(x)=%.1f\n", y); + sum += f(x); + } + return sum/(double)nstep; +} + +typedef struct { + void (*initialize)(void *(*)(int), void(*)(void *)); +} xxx_library; + +static void _xxx_init(void *(*Xalloc)(int), void (*Xfree)(void *)) +{ + void *ptr; + + printf("_xxx_init got %p %p\n", Xalloc, Xfree); + printf("calling\n"); + ptr = Xalloc(32); + Xfree(ptr); + printf("calls done, ptr was %p\n", ptr); +} + +xxx_library _xxx_lib = { + _xxx_init +}; + +EXPORT(xxx_library) *library_get(void) +{ + return &_xxx_lib; +} + +#ifdef MS_WIN32 +/* See Don Box (german), pp 79ff. */ +EXPORT(void) GetString(BSTR *pbstr) +{ + *pbstr = SysAllocString(L"Goodbye!"); +} +#endif + +EXPORT(void) _py_func_si(char *s, int i) +{ +} + +EXPORT(void) _py_func(void) +{ +} + +EXPORT(LONG_LONG) last_tf_arg_s = 0; +EXPORT(unsigned LONG_LONG) last_tf_arg_u = 0; + +struct BITS { + int A: 1, B:2, C:3, D:4, E: 5, F: 6, G: 7, H: 8, I: 9; + short M: 1, N: 2, O: 3, P: 4, Q: 5, R: 6, S: 7; +}; + +EXPORT(void) set_bitfields(struct BITS *bits, char name, int value) +{ + switch (name) { + case 'A': bits->A = value; break; + case 'B': bits->B = value; break; + case 'C': bits->C = value; break; + case 'D': bits->D = value; break; + case 'E': bits->E = value; break; + case 'F': bits->F = value; break; + case 'G': bits->G = value; break; + case 'H': bits->H = value; break; + case 'I': bits->I = value; break; + + case 'M': bits->M = value; break; + case 'N': bits->N = value; break; + case 'O': bits->O = value; break; + case 'P': bits->P = value; break; + case 'Q': bits->Q = value; break; + case 'R': bits->R = value; break; + case 'S': bits->S = value; break; + } +} + +EXPORT(int) unpack_bitfields(struct BITS *bits, char name) +{ + switch (name) { + case 'A': return bits->A; + case 'B': return bits->B; + case 'C': return bits->C; + case 'D': return bits->D; + case 'E': return bits->E; + case 'F': return bits->F; + case 'G': return bits->G; + case 'H': return bits->H; + case 'I': return bits->I; + + case 'M': return bits->M; + case 'N': return bits->N; + case 'O': return bits->O; + case 'P': return bits->P; + case 'Q': return bits->Q; + case 'R': return bits->R; + case 'S': return bits->S; + } + return 0; +} + +#define S last_tf_arg_s = (LONG_LONG)c +#define U last_tf_arg_u = (unsigned LONG_LONG)c + +EXPORT(signed char) tf_b(signed char c) { S; return c/3; } +EXPORT(unsigned char) tf_B(unsigned char c) { U; return c/3; } +EXPORT(short) tf_h(short c) { S; return c/3; } +EXPORT(unsigned short) tf_H(unsigned short c) { U; return c/3; } +EXPORT(int) tf_i(int c) { S; return c/3; } +EXPORT(unsigned int) tf_I(unsigned int c) { U; return c/3; } +EXPORT(long) tf_l(long c) { S; return c/3; } +EXPORT(unsigned long) tf_L(unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) tf_q(LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) tf_Q(unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) tf_f(float c) { S; return c/3; } +EXPORT(double) tf_d(double c) { S; return c/3; } + +#ifdef MS_WIN32 +EXPORT(signed char) __stdcall s_tf_b(signed char c) { S; return c/3; } +EXPORT(unsigned char) __stdcall s_tf_B(unsigned char c) { U; return c/3; } +EXPORT(short) __stdcall s_tf_h(short c) { S; return c/3; } +EXPORT(unsigned short) __stdcall s_tf_H(unsigned short c) { U; return c/3; } +EXPORT(int) __stdcall s_tf_i(int c) { S; return c/3; } +EXPORT(unsigned int) __stdcall s_tf_I(unsigned int c) { U; return c/3; } +EXPORT(long) __stdcall s_tf_l(long c) { S; return c/3; } +EXPORT(unsigned long) __stdcall s_tf_L(unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) __stdcall s_tf_q(LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) __stdcall s_tf_Q(unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) __stdcall s_tf_f(float c) { S; return c/3; } +EXPORT(double) __stdcall s_tf_d(double c) { S; return c/3; } +#endif +/*******/ + +EXPORT(signed char) tf_bb(signed char x, signed char c) { S; return c/3; } +EXPORT(unsigned char) tf_bB(signed char x, unsigned char c) { U; return c/3; } +EXPORT(short) tf_bh(signed char x, short c) { S; return c/3; } +EXPORT(unsigned short) tf_bH(signed char x, unsigned short c) { U; return c/3; } +EXPORT(int) tf_bi(signed char x, int c) { S; return c/3; } +EXPORT(unsigned int) tf_bI(signed char x, unsigned int c) { U; return c/3; } +EXPORT(long) tf_bl(signed char x, long c) { S; return c/3; } +EXPORT(unsigned long) tf_bL(signed char x, unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) tf_bq(signed char x, LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) tf_bQ(signed char x, unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) tf_bf(signed char x, float c) { S; return c/3; } +EXPORT(double) tf_bd(signed char x, double c) { S; return c/3; } +EXPORT(void) tv_i(int c) { S; return; } + +#ifdef MS_WIN32 +EXPORT(signed char) __stdcall s_tf_bb(signed char x, signed char c) { S; return c/3; } +EXPORT(unsigned char) __stdcall s_tf_bB(signed char x, unsigned char c) { U; return c/3; } +EXPORT(short) __stdcall s_tf_bh(signed char x, short c) { S; return c/3; } +EXPORT(unsigned short) __stdcall s_tf_bH(signed char x, unsigned short c) { U; return c/3; } +EXPORT(int) __stdcall s_tf_bi(signed char x, int c) { S; return c/3; } +EXPORT(unsigned int) __stdcall s_tf_bI(signed char x, unsigned int c) { U; return c/3; } +EXPORT(long) __stdcall s_tf_bl(signed char x, long c) { S; return c/3; } +EXPORT(unsigned long) __stdcall s_tf_bL(signed char x, unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) __stdcall s_tf_bq(signed char x, LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) __stdcall s_tf_bQ(signed char x, unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) __stdcall s_tf_bf(signed char x, float c) { S; return c/3; } +EXPORT(double) __stdcall s_tf_bd(signed char x, double c) { S; return c/3; } +EXPORT(void) __stdcall s_tv_i(int c) { S; return; } +#endif + +/********/ + +#ifndef MS_WIN32 + +typedef struct { + long x; + long y; +} POINT; + +typedef struct { + long left; + long top; + long right; + long bottom; +} RECT; + +#endif + +EXPORT(int) PointInRect(RECT *prc, POINT pt) +{ + if (pt.x < prc->left) + return 0; + if (pt.x > prc->right) + return 0; + if (pt.y < prc->top) + return 0; + if (pt.y > prc->bottom) + return 0; + return 1; +} + +typedef struct { + short x; + short y; +} S2H; + +EXPORT(S2H) ret_2h_func(S2H inp) +{ + inp.x *= 2; + inp.y *= 3; + return inp; +} + +typedef struct { + int a, b, c, d, e, f, g, h; +} S8I; + + + +typedef int (*CALLBACK_RECT)(RECT rect); + +EXPORT(int) call_callback_with_rect(CALLBACK_RECT cb, RECT rect) +{ + return cb(rect); +} + + +EXPORT(S8I) ret_8i_func(S8I inp) +{ + inp.a *= 2; + inp.b *= 3; + inp.c *= 4; + inp.d *= 5; + inp.e *= 6; + inp.f *= 7; + inp.g *= 8; + inp.h *= 9; + return inp; +} + +EXPORT(int) GetRectangle(int flag, RECT *prect) +{ + if (flag == 0) + return 0; + prect->left = (int)flag; + prect->top = (int)flag + 1; + prect->right = (int)flag + 2; + prect->bottom = (int)flag + 3; + return 1; +} + +EXPORT(void) TwoOutArgs(int a, int *pi, int b, int *pj) +{ + *pi += a; + *pj += b; +} + +#ifdef MS_WIN32 +EXPORT(S2H) __stdcall s_ret_2h_func(S2H inp) { return ret_2h_func(inp); } +EXPORT(S8I) __stdcall s_ret_8i_func(S8I inp) { return ret_8i_func(inp); } +#endif + +#ifdef MS_WIN32 +/* Should port this */ +#include +#include + +EXPORT (HRESULT) KeepObject(IUnknown *punk) +{ + static IUnknown *pobj; + if (punk) + punk->lpVtbl->AddRef(punk); + if (pobj) + pobj->lpVtbl->Release(pobj); + pobj = punk; + return S_OK; +} + +#endif + +typedef union { + short x; + long y; +} UN; + +EXPORT(UN) ret_un_func(UN inp) +{ + inp.y = inp.x * 10000; + return inp; +} + +EXPORT(int) my_unused_function(void) +{ + return 42; +} + +EXPORT(int) test_errno(void) +{ + int result = errno; + errno = result + 1; + return result; +} + +EXPORT(int *) test_issue1655(char const *tag, int *len) +{ + static int data[] = { -1, -2, -3, -4 }; + *len = -42; + if (strcmp(tag, "testing!") != 0) + return NULL; + *len = sizeof(data) / sizeof(data[0]); + return data; +} diff --git a/vm/src/stdlib/ctypes/_ctypes_test.h b/vm/src/stdlib/ctypes/_ctypes_test.h new file mode 100644 index 0000000000..060d4d69b6 --- /dev/null +++ b/vm/src/stdlib/ctypes/_ctypes_test.h @@ -0,0 +1 @@ +extern int _testfunc_i_bhilfd(char b, short h, int i, long l, float f, double d); diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs new file mode 100644 index 0000000000..23b436fea4 --- /dev/null +++ b/vm/src/stdlib/ctypes/array.rs @@ -0,0 +1,694 @@ +use super::{ + pointer::PyCPointer, + primitive::{new_simple_type, PyCSimple}, +}; +use crate::builtins::{slice::PySlice, PyBytes, PyInt, PyList, PyRange, PyStr, PyType, PyTypeRef}; +use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; +use crate::function::{Either, OptionalArg}; +use crate::sliceable::SequenceIndex; +use crate::stdlib::ctypes::basics::{ + default_from_param, generic_get_buffer, get_size, BorrowValue as BorrowValueCData, + BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, RawBuffer, +}; +use crate::{AsObject, Context, Py, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine}; +use rustpython_vm::object::PyPayload; +use num_traits::Signed; +use std::convert::TryInto; +use std::fmt; +use widestring::WideCString; +use crate::class::StaticType; +use crate::convert::{IntoObject, ToPyObject}; +use crate::protocol::{PyBuffer, PyIter}; +use crate::types::AsBuffer; + +// TODO: make sure that this is correct wrt windows and unix wstr +fn slice_to_obj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { + if ty == "u" { + Ok(vm.new_pyobj(if cfg!(windows) { + u16::from_ne_bytes( + b.try_into().map_err(|_| { + vm.new_value_error("buffer does not fit widestring".to_string()) + })?, + ) as u32 + } else { + u32::from_ne_bytes( + b.try_into().map_err(|_| { + vm.new_value_error("buffer does not fit widestring".to_string()) + })?, + ) + })) + } else { + macro_rules! byte_match_type { + ( + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match ty { + $( + $( + t if t == $type => { + Ok(vm.new_pyobj($body::from_ne_bytes(b.try_into().map_err(|_| vm.new_value_error(format!("buffer does not fit type '{}'",ty)))?))) + } + )+ + )+ + _ => unreachable!() + } + } + } + byte_match_type!( + "c" | "b" => i8 + "h" => i16 + "H" => u16 + "i" => i32 + "I" => u32 + "l" | "q" => i64 + "L" | "Q" => u64 + "f" => f32 + "d" | "g" => f64 + "?" | "B" => u8 + "P" | "z" | "Z" => usize + ) + } +} + +pub fn make_array_with_length( + cls: PyTypeRef, + length: usize, + vm: &VirtualMachine, +) -> PyResult> { + let outer_type = cls.get_attr("_type_").ok_or_else(|| { + vm.new_attribute_error("class must define a '_type_' attribute".to_string()) + })?; + let _type_ = outer_type.get_attr("_type_", vm) + .map_err(|_| vm.new_type_error("_type_ must have storage info".to_string()))?; + let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); + let capacity = length + .checked_mul(itemsize) + .ok_or_else(|| vm.new_overflow_error("array too large".to_string()))?; + // FIXME change this initialization + Ok(PyCArray { + _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(&vm.ctx), + _length_: length, + _buffer: PyRwLock::new(RawBuffer { + inner: Vec::with_capacity(capacity).as_mut_ptr(), + size: capacity, + }), + } + .into_ref_with_type(vm, cls)?) +} +// TODO: finish implementation +fn set_array_value( + zelf: &PyObjectRef, + dst_buffer: &mut [u8], + idx: usize, + size: usize, + obj: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<()> { + let self_cls = zelf.class().clone(); + + // TODO: ensure fast_issubclass is the right thing to use + if !self_cls.fast_issubclass(PyCData::static_type()) { + return Err(vm.new_type_error("not a ctype instance".to_string())); + } + + let obj_clone = obj.clone(); + let obj_cls = obj_clone.class().clone(); + + // TODO: ensure fast_issubclass is the right thing to use + if !obj_cls.fast_issubclass(PyCData::static_type()) { + // TODO: Fill here + } + + if obj.is_instance(self_cls.as_ref(), vm)? { + let o_buffer = PyBuffer::try_from_object(vm, obj)?; + let src_buffer = o_buffer.obj_bytes(); + + assert!(dst_buffer.len() == size && src_buffer.len() >= size); + + dst_buffer.copy_from_slice(&src_buffer[..size]); + } + + if self_cls.is(PyCPointer::static_type()) && obj_cls.is(PyCArray::static_type()) { + //TODO: Fill here + } else { + return Err(vm.new_type_error(format!( + "incompatible types, {} instance instead of {} instance", + obj_cls.name(), self_cls.name() + ))); + } + + Ok(()) +} + +fn array_get_slice_params( + slice: PyRef, + length: &Option, + vm: &VirtualMachine, +) -> PyResult<(isize, isize, isize)> { + if let Some(ref len) = length { + let indices = vm.get_method(slice.to_pyobject(vm), "indices").unwrap()?; + let tuple = vm.invoke(&indices, (len.clone(),))?; + + let (start, stop, step) = ( + tuple.get_item(&0, vm)?, + tuple.get_item(&1, vm)?, + tuple.get_item(&2, vm)?, + ); + + Ok(( + isize::try_from_object(vm, step)?, + isize::try_from_object(vm, start)?, + isize::try_from_object(vm, stop)?, + )) + } else { + let step = slice.step.as_ref() + .map_or(Ok(1), |o| isize::try_from_object(vm, o.clone()))?; + + let start = slice.start.as_ref() + .map_or_else(|| { + if step > 0 { + Ok(0) + } else { + Err(vm.new_value_error("slice start is required for step < 0".to_string())) + } + }, + |o| isize::try_from_object(vm, o.clone()), + )?; + + let stop = isize::try_from_object(vm, slice.stop.clone())?; + + Ok((step, start, stop)) + } +} + +fn array_slice_getitem<'a>( + zelf: PyObjectRef, + buffer_bytes: &'a [u8], + slice: PyRef, + size: usize, + vm: &'a VirtualMachine, +) -> PyResult { + let length = zelf.get_attr("_length_", &vm) + .map(|c_l| usize::try_from_object(vm, c_l))??; + + let tp = zelf.get_attr("_type_", vm)?.downcast::().unwrap().to_string(); + let _type_ = tp.as_str(); + let (step, start, stop) = array_get_slice_params(slice, &Some(vm.ctx.new_int(length).to_pyobject(&vm)), vm)?; + + let _range = PyRange { + start: PyInt::from(start).into_ref(&vm.ctx), + stop: PyInt::from(stop).into_ref(&vm.ctx), + step: PyInt::from(step).into_ref(&vm.ctx), + }; + + let mut obj_vec = Vec::new(); + let mut offset; + + for curr in PyIter::try_from_object(vm,_range.into_object(vm))?.iter(vm)? { + let idx = fix_index(isize::try_from_object(vm, curr?)?, length, vm)? as usize; + offset = idx * size; + + obj_vec.push(slice_to_obj( + _type_, + buffer_bytes[offset..offset + size].as_ref(), + vm, + )?); + } + + Ok(vm.new_pyobj(PyList::from(obj_vec))) +} + +fn array_slice_setitem( + zelf: PyObjectRef, + slice: PyRef, + buffer_bytes: &mut [u8], + obj: PyObjectRef, + length: Option, + vm: &VirtualMachine, +) -> PyResult<()> { + let (step, start, stop) = array_get_slice_params(slice, &length, vm)?; + + let slice_length = if (step < 0 && stop >= start) || (step > 0 && start >= stop) { + 0 + } else if step < 0 { + (stop - start + 1) / step + 1 + } else { + (stop - start - 1) / step + 1 + }; + + if slice_length != obj.length(vm)? as isize { + return Err(vm.new_value_error("Can only assign sequence of same size".to_string())); + } + + let _range = PyRange { + start: PyInt::from(start).into_ref(&vm.ctx), + stop: PyInt::from(stop).into_ref(&vm.ctx), + step: PyInt::from(step).into_ref(&vm.ctx), + }; + + //FIXME: this function should be called for pointer too (length should be None), + //thus, needs to make sure the size. + //Right now I'm setting one + let size = length.map_or(Ok(1), |v| usize::try_from_object(vm, v))?; + + for (i, curr) in PyIter::try_from_object(vm,_range.into_object(vm))?.iter(vm)?.enumerate() { + let idx = fix_index(isize::try_from_object(vm, curr?)?, size, vm)? as usize; + let offset = idx * size; + let item = obj.get_item(&i, vm)?; + let buffer_slice = &mut buffer_bytes[offset..offset + size]; + + set_array_value(&zelf, buffer_slice, idx, size, item, vm)?; + } + + Ok(()) +} + +#[inline(always)] +fn fix_index(index: isize, length: usize, vm: &VirtualMachine) -> PyResult { + let index = if index < 0 { + index + length as isize + } else { + index + }; + + if 0 <= index && index <= length as isize { + Ok(index) + } else { + Err(vm.new_index_error("invalid index".to_string())) + } +} + +#[pyclass(module = "_ctypes", name = "PyCArrayType", base = "PyType")] +#[derive(PyPayload)] +pub struct PyCArrayMeta {} + +#[pyclass( + module = "_ctypes", + name = "Array", + base = "PyCData", + metaclass = "PyCArrayMeta" +)] +pub struct PyCArray { + _type_: PyRef, + _length_: usize, + _buffer: PyRwLock, +} + +impl fmt::Debug for PyCArrayMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCArrayMeta",) + } +} + +impl fmt::Debug for PyCArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PyCArray {{ {} {} }}", + self._type_._type_.as_str(), + self._length_ + ) + } +} + +impl<'a> BorrowValueCData<'a> for PyCArray { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer> { + self._buffer.read() + } +} + +impl<'a> BorrowValueMut<'a> for PyCArray { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer> { + self._buffer.write() + } +} + +impl PyPayload for PyCArray { + fn class(ctx: &Context) -> &'static Py { + todo!() + } +} + +impl AsBuffer for PyCArray { + fn as_buffer(zelf: &Py, vm: &VirtualMachine) -> PyResult { + generic_get_buffer::(zelf, vm) + } +} + +impl PyCDataMethods for PyCArrayMeta { + fn from_param( + zelf: PyRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let mut value = value; + let cls = zelf.class().clone(); + + if value.is_instance(cls.as_object(), vm)? { + return Ok(value); + } + + let length = zelf.as_object().get_attr("_length_", &vm) + .map(|c_l| usize::try_from_object(vm, c_l))??; + + let value_len = value.length(vm)?; + + if let Ok(tp) = zelf.as_object().get_attr("_type_", vm) { + let _type = tp.downcast::().unwrap(); + + if _type._type_.as_str() == "c" { + // TODO: replace with match statement + if value.is_instance(vm.ctx.types.bytes_type.as_ref(), vm).is_ok() { + if value_len > length { + return Err(vm.new_value_error("Invalid length".to_string())); + } + value = make_array_with_length(cls.clone().into_ref(&vm.ctx), length, vm)?.as_object().clone(); + } else if value.is_instance(cls.clone().into_ref(&vm.ctx).as_ref(), vm).is_err() { + return Err( + vm.new_type_error(format!("expected bytes, {} found", value.class().name())) + ); + } + } else if _type._type_.as_str() == "u" { + if value.is_instance(vm.ctx.types.str_type.as_ref(), vm).is_ok() { + if value_len > length { + return Err(vm.new_value_error("Invalid length".to_string())); + } + value = make_array_with_length(cls.clone(), length, vm)?.as_object().clone(); + } else if value.is_instance(cls.as_ref(), vm).is_err() { + return Err(vm.new_type_error(format!( + "expected unicode string, {} found", + value.class().name() + ))); + } + } + } + + if value.is_instance(vm.ctx.types.tuple_type.as_ref(), &vm).is_ok() { + if value_len > length { + return Err(vm.new_runtime_error("Invalid length".to_string())); + } + value = make_array_with_length(cls, length, vm)?.as_object().clone(); + } + + default_from_param(zelf, value, vm) + } +} + +#[pyclass(with(PyCDataMethods), flags(BASETYPE))] +impl PyCArrayMeta { + #[pyslot] + fn slot_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult { + let length_obj = cls.as_object().get_attr("_length_", &vm) + .map_err(|_| { + vm.new_attribute_error("class must define a '_length_' attribute".to_string()) + })?; + let length_int = length_obj.downcast_exact::(vm).map_err(|_| { + vm.new_type_error("The '_length_' attribute must be an integer".to_string()) + })?; + let length: usize = if length_int.as_bigint().is_negative() { + Err(vm.new_value_error("The '_length_' attribute must not be negative".to_string())) + } else { + Ok( + PyInt::try_to_primitive(&length_int, vm).map_err(|_| { + vm.new_overflow_error("The '_length_' attribute is too large".to_owned()) + })?, + ) + }?; + + Ok(make_array_with_length(cls, length, vm)?.as_object().clone()) + } +} + +#[pyclass(flags(BASETYPE), with(AsBuffer, PyCDataFunctions))] +impl PyCArray { + #[pymethod(magic)] + pub fn init(zelf: PyRef, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + value.map_or(Ok(()), |value| { + let value_length = value.length(vm)?; + + if value_length < zelf._length_ { + let value_vec: Vec = value.try_into_value(vm)?; + for (i, v) in value_vec.iter().enumerate() { + Self::setitem(zelf.clone(), SequenceIndex::Int(i as isize), v.clone(), vm)? + } + Ok(()) + } else if value_length == zelf._length_ { + let py_slice = SequenceIndex::Slice( + PySlice { + start: Some(vm.new_pyobj(0)), + stop: vm.new_pyobj(zelf._length_), + step: None, + } + .into_ref(&vm.ctx), + ); + + Self::setitem(zelf, py_slice, value, vm) + } else { + Err(vm.new_value_error("value has size greater than the array".to_string())) + } + }) + } + + #[pygetset(magic)] + pub fn value(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // TODO: make sure that this is correct + let obj = zelf.as_object(); + let buffer = PyBuffer::try_from_object(vm, obj)?; + + let res = if zelf._type_._type_ == "u" { + vm.new_pyobj( + unsafe { + if cfg!(windows) { + WideCString::from_vec_unchecked( + buffer + .obj_bytes() + .chunks_exact(2) + .map(|c| { + let chunk: [u8; 2] = c.try_into().unwrap(); + u16::from_ne_bytes(chunk) + }) + .collect::>(), + ) + } else { + WideCString::from_vec_unchecked( + buffer + .obj_bytes() + .chunks(4) + .map(|c| { + let chunk: [u8; 4] = c.try_into().unwrap(); + u32::from_ne_bytes(chunk) + }) + .collect::>(), + ) + } + } + .to_string() + .map_err(|e| vm.new_runtime_error(e.to_string()))?, + ) + } else { + // self._type_ == "c" + let bytes = buffer.obj_bytes(); + + let bytes_inner = if let Some((last, elements)) = bytes.split_last() { + if *last == 0 { + elements.to_vec() + } else { + bytes.to_vec() + } + } else { + Vec::new() + }; + + PyBytes::from(bytes_inner).into_object(vm) + }; + + Ok(res) + } + + #[pygetset(magic, setter)] + fn set_value(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let obj = zelf.as_object(); + let buffer = PyBuffer::try_from_object(vm, &obj)?; + let my_size = buffer.get_options().len; + let mut bytes = buffer.obj_bytes_mut(); + + if zelf._type_._type_ == "c" { + // bytes + let value = value.downcast_exact::(vm).map_err(|value| { + vm.new_value_error(format!( + "bytes expected instead of {} instance", + value.class().name() + )) + })?; + let wide_bytes = value.to_vec(); + + if wide_bytes.len() > my_size { + return Err(vm.new_value_error("byte string too long".to_string())); + } + + bytes[..wide_bytes.len()].copy_from_slice(wide_bytes.as_slice()); + if wide_bytes.len() < my_size { + bytes[my_size] = 0; + } + } else { + // TODO: make sure that this is correct + // unicode string zelf._type_ == "u" + let value = value.downcast_exact::(vm).map_err(|value| { + vm.new_value_error(format!( + "unicode string expected instead of {} instance", + value.class().name() + )) + })?; + let wide_str = unsafe { WideCString::from_str_unchecked(value.to_string()) }; + + let wide_str_len = wide_str.len(); + + if wide_str.len() > my_size { + return Err(vm.new_value_error("string too long".to_string())); + } + let res = if cfg!(windows) { + wide_str + .into_vec() + .iter_mut() + .map(|i| u16::to_ne_bytes(*i as u16).to_vec()) + .flatten() + .collect::>() + } else { + wide_str + .into_vec() + .iter_mut() + .map(|i| u32::to_ne_bytes(*i).to_vec()) + .flatten() + .collect::>() + }; + + bytes[..wide_str_len].copy_from_slice(res.as_slice()); + } + + Ok(()) + } + + #[pygetset(magic)] + fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // zelf._type_ == "c" + + let obj = zelf.as_object(); + let buffer = PyBuffer::try_from_object(vm, obj)?; + let buffer_vec = buffer.obj_bytes().to_vec(); + + Ok(PyBytes::from(buffer_vec)) + } + + #[pygetset(magic, setter)] + fn set_raw(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let my_buffer = PyBuffer::try_from_object(vm, zelf.into())?; + let my_size = my_buffer.get_options().len; + + let new_value = PyBuffer::try_from_object(vm, &value)?; + let new_size = new_value.get_options().len; + + // byte string zelf._type_ == "c" + if new_size > my_size { + Err(vm.new_value_error("byte string too long".to_string())) + } else { + let mut borrowed_buffer = my_buffer.obj_bytes_mut(); + let src = new_value.obj_bytes(); + borrowed_buffer[..new_size].copy_from_slice(&src); + Ok(()) + } + } + + #[pymethod(magic)] + fn len(&self) -> usize { + self._length_ + } + + #[pymethod(magic)] + fn getitem(zelf: PyRef, k_or_idx: SequenceIndex, vm: &VirtualMachine) -> PyResult { + let buffer = PyBuffer::try_from_object(vm, zelf.as_object())?; + let buffer_size = buffer.get_options().len; + let buffer_bytes = buffer.obj_bytes(); + let size = buffer_size / zelf._length_; + + match k_or_idx { + SequenceIndex::Int(idx) => { + let idx = fix_index(idx, zelf._length_, vm)? as usize; + let offset = idx * size; + let buffer_slice = buffer_bytes[offset..offset + size].as_ref(); + slice_to_obj(zelf._type_._type_.as_str(), buffer_slice, vm) + } + SequenceIndex::Slice(slice) => array_slice_getitem( + zelf.as_object().clone(), + &buffer_bytes[..], + slice, + size, + vm, + ), + } + } + + #[pymethod(magic)] + fn setitem( + zelf: PyRef, + k_or_idx: SequenceIndex, + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let buffer = PyBuffer::try_from_object(vm, zelf.as_object())?; + let buffer_size = buffer.get_options().len; + let mut buffer_bytes = buffer.obj_bytes_mut(); + + let size = buffer_size / zelf._length_; + + match k_or_idx { + SequenceIndex::Int(idx) => { + let idx = fix_index(idx, zelf._length_, vm)? as usize; + let offset = idx * size; + let buffer_slice = &mut buffer_bytes[offset..offset + size]; + set_array_value(&zelf.as_object().clone(), buffer_slice, idx, size, obj, vm) + } + SequenceIndex::Slice(slice) => array_slice_setitem( + zelf.as_object().clone(), + slice, + &mut buffer_bytes[..], + obj, + Some(vm.ctx.new_int(zelf._length_)), + vm, + ), + } + } +} + +impl PyCDataFunctions for PyCArray { + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(zelf._length_ * PyCDataFunctions::size_of_instances(zelf._type_.clone(), vm)?) + } + + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + PyCDataFunctions::alignment_of_instances(zelf._type_.clone(), vm) + } + + fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult { + let offset = offset + .into_option() + .map_or(Ok(0), |o| usize::try_from_object(vm, o))?; + + if offset > zelf._length_ * get_size(zelf._type_._type_.as_str()) { + Err(vm.new_index_error("offset out of bounds".to_string())) + } else { + let guard = zelf.borrow_value(); + let ref_at: *mut u8 = unsafe { guard.inner.add(offset) }; + + Ok(vm.new_pyobj(ref_at as *mut _ as *mut usize as usize)) + } + } + + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm + .new_pyobj(unsafe { &*zelf.borrow_value().inner } as *const _ as *const usize as usize)) + } +} + +impl PyCDataSequenceMethods for PyCArrayMeta {} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs new file mode 100644 index 0000000000..3a6e634d96 --- /dev/null +++ b/vm/src/stdlib/ctypes/basics.rs @@ -0,0 +1,456 @@ +use std::{fmt, os::raw::*, ptr, slice}; + +use widestring::WideChar; + +use crate::builtins::int::PyInt; +use crate::builtins::pystr::PyStrRef; +use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; +use crate::function::OptionalArg; +use crate::{AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; + +use crate::stdlib::ctypes::array::make_array_with_length; +use crate::stdlib::ctypes::dll::dlsym; +use crate::stdlib::ctypes::primitive::{new_simple_type, PyCSimple}; +use crate::function::Either; + +use crate::builtins::{PyType, PyTypeRef}; +use crate::protocol::PyBuffer; +use crossbeam_utils::atomic::AtomicCell; +use crate::class::StaticType; +use crate::convert::IntoObject; +use crate::object::{MaybeTraverse, TraverseFn}; +use crate::types::AsBuffer; + +pub fn get_size(ty: &str) -> usize { + match ty { + "u" => size_of::(), + "c" | "b" => size_of::(), + "h" => size_of::(), + "H" => size_of::(), + "i" => size_of::(), + "I" => size_of::(), + "l" => size_of::(), + "q" => size_of::(), + "L" => size_of::(), + "Q" => size_of::(), + "f" => size_of::(), + "d" | "g" => size_of::(), + "?" | "B" => size_of::(), + "P" | "z" | "Z" => size_of::(), + _ => unreachable!(), + } +} + +fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult { + match cls.as_object().get_attr("__abstract__", &vm)? { + Ok(attr) => match bool::try_from_object(vm, attr) { + Ok(false) => { + let len = vm + .get_attribute(cls.as_object().to_owned(), "_length_") + .map_or(Ok(1), |o: PyObjectRef| match i64::try_from_object(vm, o) { + Ok(v_int) => { + if v_int < 0 { + Err(vm.new_type_error("'_length_' must positive".to_string())) + } else { + Ok(v_int as usize) + } + } + _ => Err(vm.new_type_error("'_length_' must be an integer".to_string())), + })?; + + Ok(RawBuffer { + inner: buf as *const u8 as *mut _, + size: len, + }) + } + Ok(_) => Err(vm.new_type_error("abstract class".to_string())), + // FIXME: A sanity check + Err(_) => Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), + }, + // FIXME: I think it's unreachable + Err(_) => Err(vm.new_attribute_error("abstract class".to_string())), + } +} + +// FIXME: rework this function +fn buffer_copy( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + copy: bool, +) -> PyResult { + match cls.as_object().to_owned().get_attr("__abstract__", vm) { + Ok(attr) => { + match bool::try_from_object(vm, attr) { + Ok(b) if !b => { + let buffer = PyBuffer::try_from_object(vm, obj)?; + let opts = buffer.get_options().clone(); + + // TODO: Fix the way the size of stored + // Would this be the a proper replacement? + // vm.call_method(cls.as_object().to_owned(), "size", ())?. + let cls_size = vm + .get_attribute(cls.as_object().to_owned(), "_size") + .map(|c_s| usize::try_from_object(vm, c_s))??; + + let offset_int = offset + .into_option() + .map_or(Ok(0), |off| i64::try_from_object(vm, off))?; + + if opts.readonly { + Err(vm.new_type_error("underlying buffer is not writable".to_string())) + } else if !opts.contiguous { + Err(vm.new_type_error("underlying buffer is not C contiguous".to_string())) + } else if offset_int < 0 { + Err(vm.new_value_error("offset cannot be negative".to_string())) + } else if cls_size > opts.len - (offset_int as usize) { + Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + cls_size, + opts.len + (offset_int as usize) + ))) + } else if let Some(mut buffer) = buffer.as_contiguous_mut() { + // FIXME: Perform copy + let buffered = if copy { + unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr(), buffer.len()) } + .as_mut_ptr() + } else { + buffer.as_mut_ptr() + }; + + Ok(PyCData::new( + None, + Some(RawBuffer { + inner: buffered, + size: buffer.len(), + }), + )) + } else { + Err(vm.new_buffer_error("empty buffer".to_string())) + } + } + Ok(_) => Err(vm.new_type_error("abstract class".to_string())), + Err(_) => { + // TODO: A sanity check + Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())) + } + } + } + // TODO: I think this is unreachable... + Err(_) => Err(vm.new_type_error("abstract class".to_string())), + } +} + +pub fn default_from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult +where + T: PyCDataMethods + PyPayload, +{ + //TODO: check if this behaves like it should + let cls = zelf.as_object().clone_class(); + if vm.isinstance(&value, &cls)? { + Ok(value) + } else if let Ok(parameter) = value.get_attr("_as_parameter_", vm) { + T::from_param(zelf, parameter, vm) + } else { + Err(vm.new_attribute_error(format!( + "expected {} instance instead of {}", + cls.name, + value.class().name() + ))) + } +} +#[pyclass] +pub trait PyCDataFunctions: PyPayload { + #[pymethod] + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + + #[pymethod] + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + + #[pymethod] + fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult; + + #[pymethod] + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult; +} +#[pyclass] +pub trait PyCDataMethods: PyPayload { + // A lot of the logic goes in this trait + // There's also other traits that should have different implementations for some functions + // present here + + // The default methods (representing CDataType_methods) here are for: + // StructType_Type + // UnionType_Type + // PyCArrayType_Type + // PyCFuncPtrType_Type + + #[pymethod] + fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; + + #[pyclassmethod] + fn from_address( + cls: PyTypeRef, + address: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + if let Ok(obj) = address.downcast_exact::(vm) { + if let Ok(v) = usize::try_from_object(vm, obj.into_object()) { + let buffer = at_address(&cls, v, vm)?; + Ok(PyCData::new(None, Some(buffer))) + } else { + Err(vm.new_runtime_error("casting pointer failed".to_string())) + } + } else { + Err(vm.new_type_error("integer expected".to_string())) + } + } + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + buffer_copy(cls, obj, offset, vm, false) + } + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + buffer_copy(cls, obj, offset, vm, true) + } + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + if let Ok(h) = cls.as_object().to_owned().get_attr("_handle", vm) { + // This is something to be "CPython" like + let raw_ptr = if let Ok(h_int) = h.downcast_exact::(vm) { + dlsym(h_int, name, vm) + } else { + Err(vm.new_type_error(format!("_handle must be an int not {}", dll.class().name()))) + }?; + + let sym_ptr = usize::try_from_object(vm, raw_ptr)?; + + let buffer = at_address(&cls, sym_ptr, vm)?; + Ok(PyCData::new(None, Some(buffer))) + } else { + Err(vm.new_attribute_error("atribute '_handle' not found".to_string())) + } + } +} + +#[pyclass] +pub trait PyCDataSequenceMethods: PyPayload { + // CDataType_as_sequence methods are default for all *Type_Type + // Basically the sq_repeat slot is CDataType_repeat + // which transforms into a Array + + #[pymethod(name = "__mul__")] + #[pymethod(name = "__rmul__")] + fn mul(zelf: PyRef, length: isize, vm: &VirtualMachine) -> PyResult { + if length < 0 { + Err(vm.new_value_error(format!("Array length must be >= 0, not {} length", length))) + } else { + Ok( + make_array_with_length(zelf.clone_class(), length as usize, vm)? + .as_object() + .clone(), + ) + } + } +} + +pub fn generic_get_buffer(zelf: &Py, vm: &VirtualMachine) -> PyResult +where + for<'a> T: PyPayload + fmt::Debug + BorrowValue<'a> + BorrowValueMut<'a>, +{ + if let Ok(buffer) = zelf.as_object().clone().get_attr("_buffer", vm) { + if let Ok(_buffer) = buffer.downcast_exact::(vm) { + Ok(Box::new(PyCBuffer:: { + data: zelf.clone(), + })) + } else { + Err(vm.new_attribute_error("_buffer attribute should be RawBuffer".to_string())) + } + } else { + Err(vm.new_attribute_error("_buffer not found".to_string())) + } +} + +pub trait BorrowValueMut<'a> { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer>; +} + +pub trait BorrowValue<'a> { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer>; +} + +impl<'a> BorrowValue<'a> for PyCData { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer> { + self._buffer.read() + } +} + +impl<'a> BorrowValueMut<'a> for PyCData { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer> { + self._buffer.write() + } +} + +impl AsBuffer for PyCData { + fn as_buffer(zelf: &Py, vm: &VirtualMachine) -> PyResult { + generic_get_buffer::(zelf, vm) + } +} + +#[derive(Debug)] +pub struct PyCBuffer +where + for<'a> T: PyPayload + fmt::Debug + BorrowValue<'a> + BorrowValueMut<'a>, +{ + pub data: PyRef, +} + +// FIXME: Change this implementation +pub struct RawBuffer { + pub inner: *mut u8, + pub size: usize, +} + +impl fmt::Debug for RawBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RawBuffer {{ size: {} }}", self.size) + } +} + +impl MaybeTraverse for RawBuffer { + fn try_traverse(&self, _traverse_fn: &mut TraverseFn) { + todo!() + } +} + +impl PyPayload for RawBuffer { + fn class(_ctx: &Context) -> &'static Py { + todo!() + } +} + +unsafe impl Send for RawBuffer {} +unsafe impl Sync for RawBuffer {} + +// This Trait is the equivalent of PyCData_Type on tp_base for +// Struct_Type, Union_Type, PyCPointer_Type +// PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type +#[derive(PyPayload)] +#[pyclass(module = "_ctypes", name = "_CData")] +pub struct PyCData { + _objects: AtomicCell>, + _buffer: PyRwLock, +} + +impl fmt::Debug for PyCData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCData {{ _objects: {{}}, _buffer: {{}}}}",) + } +} + +impl PyCData { + pub fn new(objs: Option>, buffer: Option) -> Self { + PyCData { + _objects: AtomicCell::new(objs.unwrap_or_default()), + _buffer: PyRwLock::new(buffer.unwrap_or(RawBuffer { + inner: ptr::null::() as *mut _, + size: 0, + })), + } + } +} + +#[pyclass(flags(BASETYPE), with(AsBuffer))] +impl PyCData { + // PyCData_methods + #[pymethod(magic)] + pub fn ctypes_from_outparam(zelf: PyRef) {} + + #[pymethod(magic)] + pub fn reduce(zelf: PyRef) {} + + #[pymethod(magic)] + pub fn setstate(zelf: PyRef) {} +} + +// FIXME: this function is too hacky, work a better way of doing it +pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> PyResult { + match tp { + Either::A(type_) if type_.issubclass(PyCSimple::static_type()) => { + let zelf = new_simple_type(Either::B(&type_), vm)?; + PyCDataFunctions::size_of_instances(zelf.into_ref(&vm.ctx), vm) + } + Either::B(obj) if obj.has_class_attr("size_of_instances") => { + let size_of_method = obj.get_attr("size_of_instances", vm)?; + let size_of_return = vm.invoke(&size_of_method, ())?; + Ok(usize::try_from_object(vm, size_of_return)?) + } + _ => Err(vm.new_type_error("this type has no size".to_string())), + } +} + +// FIXME: this function is too hacky, work a better way of doing it +pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { + match tp { + Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => { + let zelf = new_simple_type(Either::B(&type_), vm)?; + PyCDataFunctions::alignment_of_instances(zelf.into_ref(&vm.ctx), vm) + } + Either::B(obj) if obj.has_class_attr("alignment_of_instances") => { + let alignment_of_m = obj.get_attr("alignment_of_instances", vm)?; + let alignment_of_r = vm.invoke(&alignment_of_m, ())?; + usize::try_from_object(vm, alignment_of_m) + } + _ => Err(vm.new_type_error("no alignment info".to_string())), + } +} + +pub fn byref(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + //TODO: Return a Pointer when Pointer implementation is ready + let class = tp.clone().class(); + + if class.issubclass(PyCData::static_type()) { + if let Some(ref_to) = vm.get_method(tp, "ref_to") { + return vm.invoke(&ref_to?, ()); + } + }; + + Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not '{}'", + class.name + ))) +} + +pub fn addressof(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let class = tp.clone().class(); + + if class.issubclass(PyCData::static_type()) { + if let Some(address_of) = vm.get_method(tp, "address_of") { + return vm.invoke(&address_of?, ()); + } + }; + + Err(vm.new_type_error(format!( + "addressof() argument must be a ctypes instance, not '{}'", + class.name + ))) +} diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs new file mode 100644 index 0000000000..3492ae6d8a --- /dev/null +++ b/vm/src/stdlib/ctypes/dll.rs @@ -0,0 +1,52 @@ +pub(crate) use _ctypes::*; + +#[pymodule] +pub(crate) mod _ctypes { + use crate::builtins::pystr::PyStrRef; + use crate::builtins::PyIntRef; + use crate::{TryFromObject, PyResult, VirtualMachine}; + + use super::super::shared_lib::libcache; + + #[pyfunction] + pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { + let mut data_cache = libcache().write(); + + let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); + + match result { + Ok(lib) => Ok(vm.new_pyobj(lib.get_pointer())), + Err(_) => Err(vm.new_os_error(format!( + "{} : cannot open shared object file: No such file or directory", + lib_path.to_string() + ))), + } + } + + #[pyfunction] + pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { + let func_name = str_ref.as_ref(); + let data_cache = libcache().read(); + + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => match l.get_sym(func_name) { + Ok(ptr) => Ok(vm.new_pyobj(ptr as *const _ as usize)), + Err(e) => Err(vm.new_runtime_error(e)), + }, + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), + } + } + + #[pyfunction] + pub fn dlclose(slib: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let data_cache = libcache().read(); + + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => { + l.close(); + Ok(()) + } + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), + } + } +} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs new file mode 100644 index 0000000000..9b5ff977ba --- /dev/null +++ b/vm/src/stdlib/ctypes/function.rs @@ -0,0 +1,440 @@ +use std::{fmt, os::raw::*}; + +use crossbeam_utils::atomic::AtomicCell; + +use libffi::middle::{arg, Arg, Cif, CodePtr, Type}; + +use crate::builtins::pystr::PyStrRef; +use crate::builtins::{PyInt, PyType, PyTypeRef}; +use crate::common::lock::PyRwLock; + +use crate::function::FuncArgs; +use crate::{AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; +use crate::class::StaticType; +use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::primitive::PyCSimple; + +use crate::stdlib::ctypes::dll::dlsym; +use crate::types::Callable; + +macro_rules! ffi_type { + ($name: ident) => { + Type::$name() + }; +} + +macro_rules! match_ffi_type { + ( + $pointer: expr, + + $( + $($type: ident)|+ => $body: expr + )+ + ) => { + match $pointer.as_raw_ptr() { + $( + $( + t if t == ffi_type!($type).as_raw_ptr() => { $body } + )+ + )+ + _ => unreachable!() + } + }; + ( + $kind: expr, + + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { ffi_type!($body) } + )? + )+ + _ => unreachable!() + } + } +} + +fn str_to_type(ty: &str) -> Type { + if ty == "u" { + if cfg!(windows) { + ffi_type!(c_ushort) + } else { + ffi_type!(c_uint) + } + } else { + match_ffi_type!( + ty, + "c" => c_schar + "b" => i8 + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => f32 + "d" => f64 + "g" => longdouble + "?" | "B" => c_uchar + "P" | "z" | "Z" => pointer + ) + } +} + +fn py_to_ffi(ty: &Type, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let res = match_ffi_type!( + ty , + c_schar => { + arg(&i8::try_from_object(vm, obj)?) + } + c_int => { + arg(&i32::try_from_object(vm, obj)?) + } + c_short => { + arg(&i16::try_from_object(vm, obj)?) + } + c_ushort => { + arg(&u16::try_from_object(vm, obj)?) + } + c_uint => { + arg(&u32::try_from_object(vm, obj)?) + } + c_long | c_longlong => { + arg(&i64::try_from_object(vm, obj)?) + } + c_ulong | c_ulonglong => { + arg(&u64::try_from_object(vm, obj)?) + } + f32 => { + arg(&f32::try_from_object(vm, obj)?) + } + f64 | longdouble=> { + arg(&f64::try_from_object(vm, obj)?) + } + c_uchar => { + arg(&u8::try_from_object(vm, obj)?) + } + pointer => { + arg(&(usize::try_from_object(vm, obj)? as *mut usize as *mut c_void)) + } + // void should not be here, once an argument cannot be pure void + ); + + Ok(res) +} + +#[derive(Debug)] +struct Function { + pointer: *mut c_void, + arguments: Vec, + return_type: Box, +} + +impl Function { + pub fn new(fn_ptr: usize, arguments: Vec, return_type: &str) -> Function { + Function { + pointer: fn_ptr as *mut _, + arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), + + return_type: Box::new(if return_type == "P" { + Type::void() + } else { + str_to_type(return_type) + }), + } + } + pub fn set_args(&mut self, args: Vec) { + self.arguments.clear(); + self.arguments + .extend(args.iter().map(|s| str_to_type(s.as_str()))); + } + + pub fn set_ret(&mut self, ret: &str) { + *self.return_type.as_mut() = if ret == "P" { + Type::void() + } else { + str_to_type(ret) + }; + } + + pub fn call(&mut self, arg_ptrs: Vec, vm: &VirtualMachine) -> PyResult { + let args_vec: Vec = arg_ptrs + .iter() + .zip(self.arguments.iter_mut()) + .map(|(o, t)| py_to_ffi(t, o.clone(), vm)) + .collect::>>()?; + + let args = args_vec.as_slice(); + + let cif = Cif::new( + self.arguments.clone().into_iter(), + self.return_type.as_ref().to_owned(), + ); + + let fun_ptr = CodePtr(self.pointer); + + let res = unsafe { + match_ffi_type!( + self.return_type.as_ref(), + c_schar => { + let r: c_schar = cif.call(fun_ptr, args); + vm.new_pyobj(r as i8) + } + c_int => { + let r: c_int = cif.call(fun_ptr, args); + vm.new_pyobj(r as i32) + } + c_short => { + let r: c_short = cif.call(fun_ptr, args); + vm.new_pyobj(r as i16) + } + c_ushort => { + let r: c_ushort = cif.call(fun_ptr, args); + vm.new_pyobj(r as u16) + } + c_uint => { + let r: c_uint = cif.call(fun_ptr, args); + vm.new_pyobj(r as u32) + } + c_long | c_longlong => { + let r: c_long = cif.call(fun_ptr, args); + vm.new_pyobj(r as i64) + } + c_ulong | c_ulonglong => { + let r: c_ulong = cif.call(fun_ptr, args); + vm.new_pyobj(r as u64) + } + f32 => { + let r: c_float = cif.call(fun_ptr, args); + vm.new_pyobj(r as f32) + } + f64 | longdouble=> { + let r: c_double = cif.call(fun_ptr, args); + vm.new_pyobj(r as f64) + } + c_uchar => { + let r: c_uchar = cif.call(fun_ptr, args); + vm.new_pyobj(r as u8) + } + pointer => { + let r: *mut c_void = cif.call(fun_ptr, args); + vm.new_pyobj(r as *const _ as usize) + } + void => { + vm.ctx.none() + } + ) + }; + + Ok(res) + } +} + +unsafe impl Send for Function {} +unsafe impl Sync for Function {} + +#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] +pub struct PyCFuncPtr { + pub _name_: String, + pub _argtypes_: AtomicCell>, + pub _restype_: AtomicCell, + _handle: PyObjectRef, + _f: PyRwLock, +} + +impl fmt::Debug for PyCFuncPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCFuncPtr {{ _name_, _argtypes_, _restype_}}") + } +} + +#[pyclass(with(Callable), flags(BASETYPE))] +impl PyCFuncPtr { + #[pygetset(name = "_argtypes_")] + fn argtypes(&self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx + .new_list(unsafe { &*self._argtypes_.as_ptr() }.clone()) + } + + #[pygetset(name = "_restype_")] + fn restype(&self, _vm: &VirtualMachine) -> PyObjectRef { + unsafe { &*self._restype_.as_ptr() }.clone() + } + + #[pygetset(name = "_argtypes_", setter)] + fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + if vm + .isinstance(&argtypes, &vm.ctx.types.list_type) + .and_then(|_| vm.isinstance(&argtypes, &vm.ctx.types.tuple_type)) + .map_err(|e| { + vm.new_type_error(format!( + "_argtypes_ must be a sequence of types, {} found.", + argtypes.to_string() + )) + })? + { + let args = vm.extract_elements(&argtypes)?; + let c_args_res: PyResult> = args + .iter() + .enumerate() + .map(|(idx, inner_obj)| { + match inner_obj.isinstance(PyCSimple::static_type(), &vm) { + // FIXME: checks related to _type_ are temporary + // it needs to check for from_param method, instead + Ok(_) => inner_obj.get_attr("_type_", vm), + _ => Err(vm.new_type_error(format!( + "item {} in _argtypes_ must be subclass of _SimpleType, but type {} found", + idx, + inner_obj.class().name() + ))), + } + }) + .collect(); + + let c_args = c_args_res?; + + self._argtypes_.store(c_args.clone()); + + let str_types: Vec = c_args + .iter() + .map(|obj| vm.to_str(&obj).unwrap().to_string()) + .collect(); + + let mut fn_ptr = self._f.write(); + fn_ptr.set_args(str_types); + } + + Ok(()) + } + + #[pygetset(name = "_restype_", setter)] + fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + match vm.isinstance(&restype, PyCSimple::static_type()) { + // TODO: checks related to _type_ are temporary + Ok(_) => match restype.get_attr("_type_", vm) { + Ok(_type_) => { + // TODO: restype must be a type, a callable, or None + self._restype_.store(restype.clone()); + let mut fn_ptr = self._f.write(); + fn_ptr.set_ret(vm.to_str(&_type_)?.as_ref()); + + Ok(()) + } + Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), + }, + + Err(_) => Err(vm.new_type_error(format!( + "value is not an instance of _CData, type {} found", + restype.class().name() + ))), + } + } + + // TODO: Needs to check and implement other forms of new + #[pyslot] + fn slot_new( + cls: PyTypeRef, + func_name: PyStrRef, + arg: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + match cls.as_object().to_owned().get_attr("_argtypes_", vm) { + Ok(_) => Self::from_dll(cls, func_name, arg, vm), + Err(_) => Err(vm.new_type_error( + "cannot construct instance of this class: no argtypes slot".to_string(), + )), + } + } + + /// Returns a PyCFuncPtr from a Python DLL object + /// # Arguments + /// + /// * `func_name` - A string that names the function symbol + /// * `arg` - A Python object with _handle attribute of type int + /// + fn from_dll( + cls: PyTypeRef, + func_name: PyStrRef, + arg: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + if let Ok(h) = arg.get_attr("_handle", vm) { + if let Ok(handle) = h.downcast::() { + let handle_obj = handle.clone().into_object(); + let ptr_fn = dlsym(handle, func_name.clone(), vm)?; + let fn_ptr = usize::try_from_object(vm, ptr_fn)?; + + PyCFuncPtr { + _name_: func_name.to_string(), + _argtypes_: AtomicCell::default(), + _restype_: AtomicCell::new(vm.ctx.none()), + _handle: handle_obj, + _f: PyRwLock::new(Function::new( + fn_ptr, + Vec::new(), + "i", // put a default here + )), + } + .into_ref_with_type(vm, cls) + } else { + Err(vm.new_type_error(format!("_handle must be an int not {}", arg.class().name()))) + } + } else { + Err(vm.new_attribute_error( + "positional argument 2 must have _handle attribute".to_string(), + )) + } + } +} + +impl PyPayload for PyCFuncPtr { + fn class(ctx: &Context) -> &'static Py { + // TODO: it's not really a function :P + ctx.types.function_type + } +} + +impl Callable for PyCFuncPtr { + type Args = FuncArgs; + + fn call(zelf: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let inner_args = unsafe { &*zelf._argtypes_.as_ptr() }; + + if args.args.len() != inner_args.len() { + return Err(vm.new_runtime_error(format!( + "this function takes at least {} argument{} ({} given)", + inner_args.len(), + if !inner_args.is_empty() { "s" } else { "" }, + args.args.len() + ))); + } + + let arg_res: Result, _> = args + .args + .iter() + .enumerate() + .map(|(idx, obj)| { + if vm + .issubclass(&obj.clone_class(), PyCSimple::static_type()) + .is_ok() + { + Ok(obj.get_attr("value", vm)?) + } else { + Err(vm.new_type_error(format!( + "positional argument {} must be subclass of _SimpleType, but type {} found", + idx, + obj.class().name() + ))) + } + }) + .collect(); + + (*zelf._f.write()).call(arg_res?, vm) + } +} diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs new file mode 100644 index 0000000000..9c63f2e296 --- /dev/null +++ b/vm/src/stdlib/ctypes/mod.rs @@ -0,0 +1,90 @@ +use crate::{PyObjectRef, VirtualMachine}; + +mod array; +mod basics; +mod dll; +mod function; +mod pointer; +mod primitive; +mod shared_lib; +mod structure; +mod union; + +use array::PyCArrayMeta; +use basics::PyCData; +use primitive::PySimpleMeta; +use crate::class::PyClassImpl; +use crate::convert::IntoObject; + +#[pymodule] +mod _ctypes { + use rustpython_vm::stdlib::ctypes::basics; + use crate::function::Either; + use crate::builtins::PyTypeRef; + use crate::{PyObjectRef, PyResult, VirtualMachine}; + use crate::stdlib::ctypes::pointer; + + #[pyattr(name="__version__")] + pub(crate) fn version(_vm: &VirtualMachine) -> &'static str { + "1.1.0" + } + + #[pyfunction] + pub(crate) fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { + basics::alignment(tp, vm) + } + + #[pyfunction] + pub(crate) fn sizeof(tp: Either, vm: &VirtualMachine) -> PyResult { + basics::sizeof_func(tp, vm) + } + + #[pyfunction] + pub(crate) fn byref(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + basics::byref(obj, vm) + } + + #[pyfunction] + pub(crate) fn addressof(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + basics::addressof(obj, vm) + } + + #[pyfunction] + pub(crate) fn POINTER(tp: PyTypeRef, vm: &VirtualMachine) -> PyResult { + pointer::POINTER(tp); + Ok(vm.ctx.none()) + } + + #[pyfunction] + pub(crate) fn pointer(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + pointer::pointer_fn(obj); + Ok(vm.ctx.none()) + } + + #[pyfunction] + pub(crate) fn _pointer_type_cache(vm: &VirtualMachine) -> PyResult { + Ok(PyObjectRef::from(vm.ctx.new_dict())) + } + + // TODO: add the classes +} + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + PyCData::make_class(ctx); + PySimpleMeta::make_class(ctx); + PyCArrayMeta::make_class(ctx); + + let module = _ctypes::make_module(vm); + // let module = py_module!(vm, "_ctypes", { + // "CFuncPtr" => PyCFuncPtr::make_class(ctx), + // "_SimpleCData" => PyCSimple::make_class(ctx), + // "_Pointer" => PyCPointer::make_class(ctx), + // "Array" => PyCArray::make_class(ctx), + // "Struct" => PyCStructure::make_class(ctx) + // }); + + dll::extend_module(vm, &module).unwrap(); + + module.into_object() +} diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs new file mode 100644 index 0000000000..700927f4ce --- /dev/null +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -0,0 +1,28 @@ +use std::fmt; + +use crate::PyObjectRef; +use crate::builtins::PyTypeRef; + +use crate::stdlib::ctypes::basics::PyCData; + +#[pyclass(module = "_ctypes", name = "_Pointer", base = "PyCData")] +pub struct PyCPointer {} + +impl fmt::Debug for PyCPointer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "_Pointer {{}}") + } +} + +// impl PyCDataMethods for PyCPointer { +// fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + +// } +// } + +#[pyclass(flags(BASETYPE))] +impl PyCPointer {} + +pub fn POINTER(cls: PyTypeRef) {} + +pub fn pointer_fn(inst: PyObjectRef) {} diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs new file mode 100644 index 0000000000..c70a7a4ed1 --- /dev/null +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -0,0 +1,436 @@ +use crossbeam_utils::atomic::AtomicCell; +use std::fmt; + +use crate::builtins::PyTypeRef; +use crate::builtins::{ + PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr, + PyType, +}; +use crate::function::OptionalArg; +use crate::stdlib::ctypes::array::PyCArray; +use crate::stdlib::ctypes::basics::{ + default_from_param, get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, + PyCDataSequenceMethods, +}; +use crate::stdlib::ctypes::function::PyCFuncPtr; +use crate::stdlib::ctypes::pointer::PyCPointer; +use crate::function::Either; +use crate::{AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine}; +use crate::class::StaticType; +use crate::convert::{IntoObject, ToPyObject}; +use crate::protocol::PyBuffer; + +const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?"; + +fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + match _type_ { + "c" => { + if value + .clone() + .downcast_exact::(vm) + .map_or(false, |v| v.len() == 1) + || value + .clone() + .downcast_exact::(vm) + .map_or(false, |v| v.borrow_buf().len() == 1) + || value + .clone() + .downcast_exact::(vm) + .map_or(Ok(false), |v| { + let n: i64 = PyInt::try_to_primitive(&v, vm)?; + Ok(0 <= n && n <= 255) + })? + { + Ok(value.clone()) + } else { + Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )) + } + } + "u" => { + if let Ok(b) = value + .clone() + .downcast_exact::(vm) + .map(|v| v.as_ref().chars().count() == 1) + { + if b { + Ok(value.clone()) + } else { + Err(vm.new_type_error("one character unicode string expected".to_string())) + } + } else { + Err(vm.new_type_error(format!( + "unicode string expected instead of {} instance", + value.class().name() + ))) + } + } + "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "an integer is required (got type {})", + value.class().name() + ))) + } + } + "f" | "d" | "g" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!("must be real number, not {}", value.class().name()))) + } + } + "?" => Ok(vm.ctx.new_bool(value.clone().try_to_bool(&vm)?).into()), + "B" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?)) + } else { + Err(vm.new_type_error(format!("int expected instead of {}", value.class().name()))) + } + } + "z" => { + if value.clone().downcast_exact::(vm).is_ok() + || value.clone().downcast_exact::(vm).is_ok() + { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "bytes or integer address expected instead of {} instance", + value.class().name() + ))) + } + } + "Z" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name() + ))) + } + } + _ => { + // "P" + if value.clone().downcast_exact::(vm).is_ok() + || value.clone().downcast_exact::(vm).is_ok() + { + Ok(value.clone()) + } else { + Err(vm.new_type_error("cannot be converted to pointer".to_string())) + } + } + } +} + +fn generic_xxx_p_from_param( + cls: &PyTypeRef, + value: &PyObjectRef, + type_str: &str, + vm: &VirtualMachine, +) -> PyResult { + if vm.is_none(value) { + return Ok(vm.ctx.none()); + } + + if value.is_instance(&vm.ctx.types.str_type.into(), vm)? + || value.is_instance(&vm.ctx.types.bytes_type.into(), vm)? + { + Ok(PyCSimple { + _type_: type_str.to_string(), + value: AtomicCell::new(value.clone()), + } + .into_object(vm)) + } else if value.is_instance(PyCSimple::static_type().as_ref(), vm)? + && (type_str == "z" || type_str == "Z" || type_str == "P") + { + Ok(value.clone()) + } else { + // TODO: better message + Err(vm.new_type_error("wrong type".to_string())) + } +} + +fn from_param_char_p( + cls: &PyTypeRef, + value: &PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let _type_ = value.get_attr("_type_", &vm)? + .downcast_exact::(vm) + .unwrap(); + let type_str = _type_.as_ref(); + + let res = generic_xxx_p_from_param(cls, value, type_str, vm)?; + + if !vm.is_none(&res) { + Ok(res) + } else if (value.is_instance(PyCArray::static_type().as_ref(), vm)? + || value.is_instance(PyCPointer::static_type().as_ref(), vm)?) + && (type_str == "z" || type_str == "Z" || type_str == "P") + { + Ok(value.clone()) + } else { + // TODO: Make sure of what goes here + Err(vm.new_type_error("some error".to_string())) + } +} + +fn from_param_void_p( + cls: &PyTypeRef, + value: &PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let _type_ = value.get_attr("_type_", &vm)? + .downcast_exact::(vm) + .unwrap(); + let type_str = _type_.as_ref(); + + let res = generic_xxx_p_from_param(cls, value, type_str, vm)?; + + if !vm.is_none(&res) { + Ok(res) + } else if value.is_instance(PyCArray::static_type().into(), vm)? { + Ok(value.clone()) + } else if value.is_instance(PyCFuncPtr::static_type().into(), vm)? + || value.is_instance(PyCPointer::static_type().into(), vm)? + { + // TODO: Is there a better way of doing this? + if let Some(from_address) = vm.get_method(cls.as_object().clone(), "from_address") { + if let Ok(cdata) = value.clone().downcast::() { + let buffer_guard = cdata.borrow_value_mut(); + let addr = buffer_guard.inner as usize; + + Ok(vm.invoke(&from_address?, (cls.clone_class(), addr))?) + } else { + // TODO: Make sure of what goes here + Err(vm.new_type_error("value should be an instance of _CData".to_string())) + } + } else { + // TODO: Make sure of what goes here + Err(vm.new_attribute_error("class has no from_address method".to_string())) + } + } else if value.is_instance(vm.ctx.types.int_type.as_ref(), vm)? { + Ok(PyCSimple { + _type_: type_str.to_string(), + value: AtomicCell::new(value.clone()), + } + .into_object(vm)) + } else { + // TODO: Make sure of what goes here + Err(vm.new_type_error("some error".to_string())) + } +} + +pub fn new_simple_type( + cls: Either<&PyObjectRef, &PyTypeRef>, + vm: &VirtualMachine, +) -> PyResult { + let cls = match cls { + Either::A(obj) => obj, + Either::B(typ) => typ.as_object(), + }; + + if let Ok(_type_) = cls.get_attr("_type_", vm) { + if _type_.is_instance(&vm.ctx.types.str_type.as_ref(), vm)? { + let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); + + if tp_str.len() != 1 { + Err(vm.new_value_error( + "class must define a '_type_' attribute which must be a string of length 1" + .to_string(), + )) + } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) + } else { + Ok(PyCSimple { + _type_: tp_str, + value: AtomicCell::new(vm.ctx.none()), + }) + } + } else { + Err(vm.new_type_error("class must define a '_type_' string attribute".to_string())) + } + } else { + Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) + } +} + +#[derive(PyPayload)] +#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")] +pub struct PySimpleMeta {} + +#[pyclass(with(PyCDataMethods), flags(BASETYPE))] +impl PySimpleMeta { + #[pyslot] + fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(new_simple_type(Either::B(&cls), vm)? + .into_ref_with_type(vm, cls)? + .as_object() + .clone() + .to_pyobject(vm)) + } +} + +#[derive(PyPayload)] +#[pyclass( + module = "_ctypes", + name = "_SimpleCData", + base = "PyCData", + metaclass = "PySimpleMeta" +)] +pub struct PyCSimple { + pub _type_: String, + value: AtomicCell, +} + +impl fmt::Debug for PyCSimple { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = unsafe { (*self.value.as_ptr()).to_string() }; + + write!( + f, + "PyCSimple {{ + _type_: {}, + value: {}, + }}", + self._type_.as_str(), + value + ) + } +} + +impl fmt::Debug for PySimpleMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PySimpleMeta") + } +} + +impl PyCDataMethods for PySimpleMeta { + // From PyCSimpleType_Type PyCSimpleType_methods + fn from_param( + zelf: PyRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let cls = zelf.class().clone(); + if cls.is(PyCSimple::static_type()) { + Err(vm.new_type_error("abstract class".to_string())) + } else if value.is_instance(cls.as_ref(), vm)? { + Ok(value) + } else { + let tp = zelf.as_object().to_pyobject(&vm).get_attr("_type_", &vm)?.downcast::().unwrap().to_string(); + let _type_ = tp.as_str(); + + match _type_ { + "z" | "Z" => from_param_char_p(&cls, &value, vm), + "P" => from_param_void_p(&cls, &value, vm), + _ => match new_simple_type(Either::B(&cls), vm) { + Ok(obj) => Ok(obj.into_object(vm).to_pyobject(vm)), + Err(e) => { + if e.clone().into_object().is_instance(vm.ctx.exceptions.type_error.into(), vm)? + || e.clone().into_object().is_instance( + vm.ctx.exceptions.value_error.into(), + &vm + )? + { + default_from_param(zelf, value.clone(), vm) + } else { + Err(e) + } + } + }, + } + } + } +} + +#[pyclass(with(PyCDataFunctions), flags(BASETYPE))] +impl PyCSimple { + #[pymethod(magic)] + pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + if let Some(ref v) = value.into_option() { + let content = set_primitive(self._type_.as_str(), v, vm)?; + self.value.store(content); + } else { + self.value.store(match self._type_.as_str() { + "c" | "u" => vm.ctx.new_bytes(vec![0]).into_object(), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0).into_object(), + "f" | "d" | "g" => vm.ctx.new_float(0.0).into_object(), + "?" => vm.ctx.new_bool(false).into_object(), + _ => vm.ctx.none(), // "z" | "Z" | "P" + }); + } + Ok(()) + } + + #[pygetset(name = "value", magic)] + pub fn value(&self) -> PyObjectRef { + unsafe { (*self.value.as_ptr()).clone() } + } + + #[pygetset(name = "value", magic, setter)] + fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let content = set_primitive(self._type_.as_str(), &value, vm)?; + self.value.store(content); + Ok(()) + } + + // From Simple_Type Simple_methods + #[pymethod(magic)] + pub fn ctypes_from_outparam(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + if let Some(base) = zelf.class().base.clone() { + if vm.bool_eq(&base.as_object(), PyCSimple::static_type().as_object())? { + return Ok(zelf.as_object().clone().to_pyobject(vm)); + } + } + Ok(zelf.value()) + } + + // Simple_repr + #[pymethod(magic)] + fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(format!( + "{}({})", + zelf.class().name(), + vm.to_repr(&zelf.value())?.to_string() + )) + } + + // Simple_as_number + #[pymethod(magic)] + fn bool(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let buffer = PyBuffer::try_from_object(vm, zelf.as_object().to_pyobject(vm))? + .obj_bytes() + .to_vec(); + + Ok(vm.new_pyobj(buffer != vec![0])) + } +} + +impl PyCDataFunctions for PyCSimple { + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(get_size(zelf._type_.as_str())) + } + + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Self::size_of_instances(zelf, vm) + } + + fn ref_to( + zelf: PyRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.new_pyobj(zelf.value.as_ptr() as *mut _ as *mut usize as usize)) + } + + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_pyobj(unsafe { &*zelf.value.as_ptr() } as *const _ as *const usize as usize)) + } +} + +impl PyCDataSequenceMethods for PySimpleMeta {} diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs new file mode 100644 index 0000000000..f5b0f86313 --- /dev/null +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -0,0 +1,122 @@ +use std::{collections::HashMap, fmt, os::raw::c_void, ptr::null}; + +use crossbeam_utils::atomic::AtomicCell; +use libloading::Library; + +use crate::common::lock::PyRwLock; +use crate::{Context, Py, PyPayload, PyRef, VirtualMachine}; +use crate::builtins::PyType; +use crate::object::{MaybeTraverse, TraverseFn}; + +pub struct SharedLibrary { + lib: AtomicCell>, +} + +impl MaybeTraverse for SharedLibrary { + fn try_traverse(&self, _traverse_fn: &mut TraverseFn) { + todo!() + } +} + +impl PyPayload for SharedLibrary { + fn class(_ctx: &Context) -> &'static Py { + todo!() + } +} + +impl fmt::Debug for SharedLibrary { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "SharedLibrary {{ + lib: {}, + }}", + self.get_pointer() + ) + } +} + +impl SharedLibrary { + pub fn new(name: &str) -> Result { + Ok(SharedLibrary { + lib: AtomicCell::new(Some(unsafe { Library::new(name.to_string())? })), + }) + } + + pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> { + if let Some(inner) = unsafe { &*self.lib.as_ptr() } { + unsafe { + inner + .get(name.as_bytes()) + .map(|f: libloading::Symbol<*mut c_void>| *f) + .map_err(|err| err.to_string()) + } + } else { + Err("The library has been closed".to_string()) + } + } + + pub fn get_pointer(&self) -> usize { + if let Some(l) = unsafe { &*self.lib.as_ptr() } { + l as *const Library as usize + } else { + null::() as usize + } + } + + pub fn is_closed(&self) -> bool { + unsafe { &*self.lib.as_ptr() }.is_none() + } + + pub fn close(&self) { + let old = self.lib.take(); + self.lib.store(None); + drop(old); + } +} + +pub struct ExternalLibs { + libraries: HashMap>, +} + +impl ExternalLibs { + pub fn new() -> Self { + Self { + libraries: HashMap::new(), + } + } + + pub fn get_lib(&self, key: usize) -> Option<&PyRef> { + self.libraries.get(&key) + } + + pub fn get_or_insert_lib( + &mut self, + library_path: &str, + vm: &VirtualMachine, + ) -> Result<&PyRef, libloading::Error> { + let nlib = SharedLibrary::new(library_path)?.into_ref(&vm.ctx); + let key = nlib.get_pointer(); + + match self.libraries.get(&key) { + Some(l) => { + if l.is_closed() { + self.libraries.insert(key, nlib); + } + } + _ => { + self.libraries.insert(key, nlib); + } + }; + + Ok(self.libraries.get(&key).unwrap()) + } +} + +rustpython_common::static_cell! { + static LIBCACHE: PyRwLock; +} + +pub fn libcache() -> &'static PyRwLock { + LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new())) +} diff --git a/vm/src/stdlib/ctypes/structure.rs b/vm/src/stdlib/ctypes/structure.rs new file mode 100644 index 0000000000..98af12a2f6 --- /dev/null +++ b/vm/src/stdlib/ctypes/structure.rs @@ -0,0 +1,15 @@ +use std::fmt; + +use crate::stdlib::ctypes::basics::PyCData; + +#[pyclass(module = "_ctypes", name = "Structure", base = "PyCData")] +pub struct PyCStructure {} + +impl fmt::Debug for PyCStructure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCStructure {{}}") + } +} + +#[pyclass(flags(BASETYPE))] +impl PyCStructure {} diff --git a/vm/src/stdlib/ctypes/union.rs b/vm/src/stdlib/ctypes/union.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 12baee11f7..a33e584bab 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -48,6 +48,9 @@ mod winapi; #[cfg(windows)] mod winreg; +#[cfg(feature = "ctypes")] +mod ctypes; + use crate::{builtins::PyModule, PyRef, VirtualMachine}; use std::{borrow::Cow, collections::HashMap}; @@ -102,6 +105,13 @@ pub fn get_module_inits() -> StdlibMap { { "symtable" => symtable::make_module, } + #[cfg(any(unix, windows))] + { + #[cfg(feature = "ctypes")] + { + "_ctypes" => ctypes::make_module, + } + } #[cfg(any(unix, target_os = "wasi"))] { "posix" => posix::make_module,