From 101d9be1812790a4dff191e7b864592f3d2ece8e Mon Sep 17 00:00:00 2001 From: Kevin Phoenix Date: Wed, 30 Oct 2024 13:31:59 -0700 Subject: [PATCH] Update python bindings for op split (#9) --- Cargo.lock | 257 +++++++++++++++++- Cargo.toml | 3 +- crates/claripy/src/ast/base.rs | 37 --- crates/claripy/src/ast/bits.rs | 26 -- crates/claripy/src/ast/bool.rs | 65 ----- crates/claripy/src/ast/bv.rs | 137 ---------- crates/claripy/src/ast/mod.rs | 43 --- crates/claripy/src/ast/py_factory.rs | 54 ---- crates/claripy/src/ast/shared_ops.rs | 47 ---- crates/claripy/src/ast/string.rs | 115 -------- crates/claripy/src/macros.rs | 108 -------- crates/claripy/src/prelude.rs | 9 - crates/claripy/src/solver.rs | 71 ----- crates/claripy/src/weakref.rs | 29 -- .../clarirs_core/src/algorithms/simplify.rs | 2 + crates/clarirs_core/src/ast/bool.rs | 8 +- crates/clarirs_core/src/ast/factory.rs | 32 +-- .../clarirs_core/src/ast/factory_support.rs | 98 ++++++- crates/clarirs_core/src/solver/solver.rs | 8 +- crates/{claripy => clarirs_py}/Cargo.toml | 15 +- .../benchmarks/perf_ast.py | 19 +- .../{claripy => clarirs_py}/src/annotation.rs | 6 +- crates/clarirs_py/src/ast/base.rs | 19 ++ crates/clarirs_py/src/ast/bits.rs | 16 ++ crates/clarirs_py/src/ast/bool.rs | 101 +++++++ crates/clarirs_py/src/ast/bv.rs | 168 ++++++++++++ crates/{claripy => clarirs_py}/src/ast/fp.rs | 189 +++++++------ crates/clarirs_py/src/ast/mod.rs | 188 +++++++++++++ crates/clarirs_py/src/ast/string.rs | 205 ++++++++++++++ crates/{claripy => clarirs_py}/src/error.rs | 0 crates/{claripy => clarirs_py}/src/lib.rs | 90 ++++-- crates/clarirs_py/src/macros.rs | 8 + crates/clarirs_py/src/prelude.rs | 7 + crates/{claripy => clarirs_py}/src/py_err.rs | 12 +- crates/clarirs_py/src/solver.rs | 125 +++++++++ pyproject.toml | 4 +- 36 files changed, 1436 insertions(+), 885 deletions(-) delete mode 100644 crates/claripy/src/ast/base.rs delete mode 100644 crates/claripy/src/ast/bits.rs delete mode 100644 crates/claripy/src/ast/bool.rs delete mode 100644 crates/claripy/src/ast/bv.rs delete mode 100644 crates/claripy/src/ast/mod.rs delete mode 100644 crates/claripy/src/ast/py_factory.rs delete mode 100644 crates/claripy/src/ast/shared_ops.rs delete mode 100644 crates/claripy/src/ast/string.rs delete mode 100644 crates/claripy/src/macros.rs delete mode 100644 crates/claripy/src/prelude.rs delete mode 100644 crates/claripy/src/solver.rs delete mode 100644 crates/claripy/src/weakref.rs rename crates/{claripy => clarirs_py}/Cargo.toml (56%) rename crates/{claripy => clarirs_py}/benchmarks/perf_ast.py (59%) rename crates/{claripy => clarirs_py}/src/annotation.rs (66%) create mode 100644 crates/clarirs_py/src/ast/base.rs create mode 100644 crates/clarirs_py/src/ast/bits.rs create mode 100644 crates/clarirs_py/src/ast/bool.rs create mode 100644 crates/clarirs_py/src/ast/bv.rs rename crates/{claripy => clarirs_py}/src/ast/fp.rs (50%) create mode 100644 crates/clarirs_py/src/ast/mod.rs create mode 100644 crates/clarirs_py/src/ast/string.rs rename crates/{claripy => clarirs_py}/src/error.rs (100%) rename crates/{claripy => clarirs_py}/src/lib.rs (54%) create mode 100644 crates/clarirs_py/src/macros.rs create mode 100644 crates/clarirs_py/src/prelude.rs rename crates/{claripy => clarirs_py}/src/py_err.rs (51%) create mode 100644 crates/clarirs_py/src/solver.rs diff --git a/Cargo.lock b/Cargo.lock index 96bd64d..89ec3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "byteorder" version = "1.5.0" @@ -66,6 +72,38 @@ dependencies = [ "thiserror", ] +[[package]] +name = "clarirs_py" +version = "0.1.0" +dependencies = [ + "clarirs_core", + "dashmap", + "log", + "num-bigint", + "pyo3", + "thiserror", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -89,12 +127,24 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "indexmap" version = "2.6.0" @@ -102,15 +152,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -146,6 +227,19 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" @@ -162,6 +256,12 @@ dependencies = [ "indexmap", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -180,6 +280,70 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "num-bigint", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.37" @@ -219,6 +383,21 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.209" @@ -259,6 +438,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "thiserror" version = "1.0.63" @@ -285,6 +470,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "version_check" version = "0.9.5" @@ -297,6 +488,70 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 60a7fad..b9234d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/clarirs_num", "crates/clarirs_core", + "crates/clarirs_py", ] resolver = "2" @@ -14,4 +15,4 @@ repository = "https://github.com/twizmwazin/clarirs.git" [workspace.lints.clippy] all = "warn" -cargo = "deny" +cargo = "warn" diff --git a/crates/claripy/src/ast/base.rs b/crates/claripy/src/ast/base.rs deleted file mode 100644 index f3ff9b9..0000000 --- a/crates/claripy/src/ast/base.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::prelude::*; - -#[pyclass(subclass, frozen, weakref, module = "claripy.ast.base")] -#[derive(Clone)] -pub struct Base { - pub ast: AstRef<'static>, -} - -#[pymethods] -impl Base {} - -impl Base { - pub fn new(ast: &AstRef<'static>) -> Self { - Base { ast: ast.clone() } - } -} - -impl From for AstRef<'static> { - fn from(base: Base) -> Self { - base.ast - } -} - -impl PyAst for Base { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - PyClassInitializer::from(Base::new(ast_ref)) - } - - fn as_base(self_: PyRef) -> PyRef { - self_ - } -} - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - m.add_class::()?; - Ok(()) -} diff --git a/crates/claripy/src/ast/bits.rs b/crates/claripy/src/ast/bits.rs deleted file mode 100644 index ff3ec82..0000000 --- a/crates/claripy/src/ast/bits.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::prelude::*; - -#[pyclass(extends=Base, subclass, frozen, weakref, module="claripy.ast.bits")] -#[derive(Default)] -pub struct Bits; - -impl Bits { - pub fn new() -> Self { - Bits {} - } -} - -impl PyAst for Bits { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - Base::new_from_astref(ast_ref).add_subclass(Bits::new()) - } - - fn as_base(self_: PyRef) -> PyRef { - self_.into_super() - } -} - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - m.add_class::()?; - Ok(()) -} diff --git a/crates/claripy/src/ast/bool.rs b/crates/claripy/src/ast/bool.rs deleted file mode 100644 index 8aceed1..0000000 --- a/crates/claripy/src/ast/bool.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![allow(non_snake_case)] - -use pyo3::prelude::*; - -use crate::prelude::*; -use crate::{ast::py_factory::GLOBAL_CONTEXT, error::ClaripyError}; - -use super::shared_ops; -use super::{base::Base, py_factory::py_ast_from_astref, PyAst}; - -#[pyclass(extends=Base, subclass, frozen, weakref, module="claripy.ast.bool")] -pub struct Bool; - -#[pymethods] -impl Bool { - fn is_true(self_: PyRef) -> bool { - self_.as_super().ast.is_true() - } - - fn is_false(self_: PyRef) -> bool { - self_.as_super().ast.is_false() - } -} - -impl PyAst for Bool { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - Base::new_from_astref(ast_ref).add_subclass(Bool {}) - } - - fn as_base(self_: PyRef) -> PyRef { - self_.into_super() - } -} - -pyop!(BoolS, bools, Bool, name: String); -pyop!(BoolV, boolv, Bool, value: bool); - -#[pyfunction(name = "true")] -pub fn true_op(py: Python) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.true_()?) -} -#[pyfunction(name = "false")] -pub fn false_op(py: Python) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.false_()?) -} - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - m.add_class::()?; - - add_pyfunctions!( - m, - BoolS, - BoolV, - shared_ops::Not, - shared_ops::And, - shared_ops::Or, - shared_ops::Xor, - shared_ops::Eq_, - shared_ops::If, - true_op, - false_op, - ); - - Ok(()) -} diff --git a/crates/claripy/src/ast/bv.rs b/crates/claripy/src/ast/bv.rs deleted file mode 100644 index 35617b6..0000000 --- a/crates/claripy/src/ast/bv.rs +++ /dev/null @@ -1,137 +0,0 @@ -#![allow(non_snake_case)] - -use num_bigint::BigUint; -use pyo3::exceptions::{PyTypeError, PyValueError}; - -use super::shared_ops; -use super::{bits::Bits, bool::Bool}; -use crate::prelude::*; - -#[pyclass(extends=Bits, subclass, frozen, weakref, module="claripy.ast.bv")] -pub struct BV; - -impl PyAst for BV { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - Bits::new_from_astref(ast_ref).add_subclass(BV {}) - } - - fn as_base(self_: PyRef) -> PyRef { - self_.into_super().into_super() - } -} - -#[pymethods] -impl BV {} - -pyop!(BVS, bvs, BV, name: String, size: u32); - -#[allow(non_snake_case)] -#[pyfunction(signature = (value, size = None))] -pub fn BVV(py: Python, value: Bound, size: Option) -> Result, PyErr> { - if let Ok(int_val) = value.extract::() { - if let Some(size) = size { - return Ok(py_ast_from_astref( - py, - GLOBAL_CONTEXT - .bvv_from_biguint_with_size(&int_val, size) - .map_err(ClaripyError::from)?, - )?); - } else { - return Err(PyErr::new::("size must be specified")); - } - } - // TODO: deduplicate bytes/str - if let Ok(bytes_val) = value.extract::>() { - let int_val = BigUint::from_bytes_le(&bytes_val); - log::warn!("bytes value passed to BVV, assuming little-endian"); - if size.is_some() { - log::warn!("BVV size specified with bytes, value will be ignored"); - } - return Ok(py_ast_from_astref( - py, - GLOBAL_CONTEXT - .bvv_from_biguint_with_size(&int_val, bytes_val.len() as u32 * 8) - .map_err(ClaripyError::from)?, - )?); - } - if let Ok(str_val) = value.extract::() { - log::warn!("string value passed to BVV, assuming utf-8"); - let bytes_val = str_val.as_bytes(); - let int_val = BigUint::from_bytes_le(bytes_val); - log::warn!("bytes value passed to BVV, assuming little-endian"); - if size.is_some() { - log::warn!("BVV size specified with bytes, value will be ignored"); - } - return Ok(py_ast_from_astref( - py, - GLOBAL_CONTEXT - .bvv_from_biguint_with_size(&int_val, bytes_val.len() as u32 * 8) - .map_err(ClaripyError::from)?, - )?); - } - Err(PyErr::new::( - "BVV value must be a int, bytes, or str", - )) -} - -pyop!(Add, add, BV, BV, BV); -pyop!(Sub, sub, BV, BV, BV); -pyop!(Mul, mul, BV, BV, BV); -pyop!(UDiv, udiv, BV, BV, BV); -pyop!(SDiv, sdiv, BV, BV, BV); -pyop!(UMod, urem, BV, BV, BV); -pyop!(SMod, srem, BV, BV, BV); -pyop!(Pow, pow, BV, BV, BV); -pyop!(LShL, lshl, BV, BV, BV); -pyop!(LShR, lshr, BV, BV, BV); -pyop!(AShR, ashr, BV, BV, BV); -pyop!(AShL, ashl, BV, BV, BV); -pyop!(Concat, concat, BV, BV, BV); -pyop!(Extract, extract, BV, BV, start: u32, end: u32); - -pyop!(ULT, ult, Bool, BV, BV); -pyop!(ULE, ule, Bool, BV, BV); -pyop!(UGT, ugt, Bool, BV, BV); -pyop!(UGE, uge, Bool, BV, BV); -pyop!(SLT, slt, Bool, BV, BV); -pyop!(SLE, sle, Bool, BV, BV); -pyop!(SGT, sgt, Bool, BV, BV); -pyop!(SGE, sge, Bool, BV, BV); - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - m.add_class::()?; - - add_pyfunctions!( - m, - BVS, - BVV, - shared_ops::Not, - shared_ops::And, - Add, - Sub, - Mul, - UDiv, - SDiv, - UMod, - SMod, - Pow, - LShL, - LShR, - AShR, - AShL, - Concat, - Extract, - ULT, - ULE, - UGT, - UGE, - SLT, - SLE, - SGT, - SGE, - shared_ops::Eq_, - shared_ops::If, - ); - - Ok(()) -} diff --git a/crates/claripy/src/ast/mod.rs b/crates/claripy/src/ast/mod.rs deleted file mode 100644 index 8d5d935..0000000 --- a/crates/claripy/src/ast/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub mod base; -pub mod bits; -pub mod bool; -pub mod bv; -pub mod fp; -pub mod py_factory; -pub mod shared_ops; -pub mod string; - -use pyo3::{prelude::*, PyClass}; - -use clarirs_core::ast::AstRef; - -use super::import_submodule; - -pub trait PyAst: PyClass { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer; - fn as_base(self_: PyRef) -> PyRef; -} - -pub fn get_astref(self_: PyRef) -> AstRef<'static> -where - T: PyAst, -{ - T::as_base(self_).ast.clone() -} - -pub(crate) fn import(py: Python, m: &Bound) -> PyResult<()> { - import_submodule(py, m, "claripy.ast", "base", base::import)?; - import_submodule(py, m, "claripy.ast", "bits", bits::import)?; - import_submodule(py, m, "claripy.ast", "bool", bool::import)?; - import_submodule(py, m, "claripy.ast", "bv", bv::import)?; - import_submodule(py, m, "claripy.ast", "fp", fp::import)?; - import_submodule(py, m, "claripy.ast", "strings", string::import)?; - - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - Ok(()) -} diff --git a/crates/claripy/src/ast/py_factory.rs b/crates/claripy/src/ast/py_factory.rs deleted file mode 100644 index 28a026b..0000000 --- a/crates/claripy/src/ast/py_factory.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{ - collections::HashMap, - sync::{LazyLock, RwLock}, -}; - -use pyo3::pyclass_init::PyObjectInit; - -use crate::prelude::*; - -pub static GLOBAL_CONTEXT: LazyLock> = LazyLock::new(Context::new); -pub static PY_OBJECT_CACHE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); - -pub fn extract_arg<'py, T>( - py: Python<'py>, - args: &[PyObject], - index: usize, -) -> Result -where - T: FromPyObject<'py>, -{ - args.get(index) - .ok_or(ClaripyError::MissingArgIndex(index)) - .and_then(|arg| { - arg.extract::(py) - .map_err(|_| ClaripyError::FailedToExtractArg(arg.clone_ref(py))) - }) -} - -pub fn py_ast_from_astref(py: Python, native_ast: AstRef<'static>) -> Result, ClaripyError> -where - T: PyAst, -{ - let cached_obj = PY_OBJECT_CACHE - .read() - .unwrap() - .get(&native_ast.hash()) - .and_then(|weak_ptr| weak_ptr.upgrade(py)); - match cached_obj { - Some(py_obj) => Ok(py_obj), - None => { - let py_init = T::new_from_astref(&native_ast); - let py_obj = unsafe { - Py::from_owned_ptr(py, py_init.into_new_object(py, T::type_object_raw(py))?) - }; - let weakref = WeakRef::from(&py_obj); - PY_OBJECT_CACHE - .write() - .unwrap() - .insert(native_ast.hash(), weakref); - Ok(py_obj) - } - } -} diff --git a/crates/claripy/src/ast/shared_ops.rs b/crates/claripy/src/ast/shared_ops.rs deleted file mode 100644 index b91506d..0000000 --- a/crates/claripy/src/ast/shared_ops.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![allow(non_snake_case)] - -use crate::ast::{base::Base, bool::Bool}; -use crate::prelude::*; - -#[pyfunction] -pub fn Not(py: Python, b: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.not(&get_astref(b))?) -} - -#[pyfunction] -pub fn And(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.and(&get_astref(lhs), &get_astref(rhs))?) -} - -#[pyfunction] -pub fn Or(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.or(&get_astref(lhs), &get_astref(rhs))?) -} - -#[pyfunction] -pub fn Xor(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.xor(&get_astref(lhs), &get_astref(rhs))?) -} - -#[allow(non_snake_case)] -#[pyfunction(name = "Eq")] -pub fn Eq_(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( - py, - crate::ast::py_factory::GLOBAL_CONTEXT - .eq_(&crate::ast::get_astref(lhs), &crate::ast::get_astref(rhs))?, - ) -} - -#[pyfunction] -pub fn If( - py: Python, - cond: PyRef, - then_: PyRef, - else_: PyRef, -) -> Result, ClaripyError> { - py_ast_from_astref( - py, - GLOBAL_CONTEXT.if_(&get_astref(cond), &get_astref(then_), &get_astref(else_))?, - ) -} diff --git a/crates/claripy/src/ast/string.rs b/crates/claripy/src/ast/string.rs deleted file mode 100644 index f1d43bc..0000000 --- a/crates/claripy/src/ast/string.rs +++ /dev/null @@ -1,115 +0,0 @@ -#![allow(non_snake_case)] - -use ast::bv::BV; - -use crate::ast::bits::Bits; -use crate::prelude::*; - -#[pyclass(name="String", extends=Bits, subclass, frozen, module="claripy.ast.string")] -pub struct AstString; - -impl PyAst for AstString { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - Bits::new_from_astref(ast_ref).add_subclass(AstString {}) - } - - fn as_base(self_: PyRef) -> PyRef { - self_.into_super().into_super() - } -} - -pyop!(StringS, strings, AstString, name: String, size: u32); -pyop!(StringV, stringv, AstString, value: String); -pyop!(StrLen, strlen, AstString, AstString); -pyop!(StrConcat, strconcat, AstString, AstString, AstString); - -#[pyfunction] -pub fn StrSubstr( - py: Python, - base: PyRef, - start: PyRef, - end: PyRef, -) -> Result, ClaripyError> { - py_ast_from_astref( - py, - GLOBAL_CONTEXT.strsubstr(&get_astref(base), &get_astref(start), &get_astref(end))?, - ) -} - -pyop!(StrContains, strcontains, AstString, AstString, AstString); - -#[pyfunction] -pub fn StrIndexOf( - py: Python, - haystack: PyRef, - needle: PyRef, - start: PyRef, -) -> Result, ClaripyError> { - py_ast_from_astref( - py, - GLOBAL_CONTEXT.strindexof( - &get_astref(haystack), - &get_astref(needle), - &get_astref(start), - )?, - ) -} - -#[pyfunction] -pub fn StrReplace( - py: Python, - haystack: PyRef, - needle: PyRef, - replacement: PyRef, -) -> Result, ClaripyError> { - py_ast_from_astref( - py, - GLOBAL_CONTEXT.strreplace( - &get_astref(haystack), - &get_astref(needle), - &get_astref(replacement), - )?, - ) -} - -pyop!(StrPrefixOf, strprefixof, AstString, AstString, AstString); -pyop!(StrSuffixOf, strsuffixof, AstString, AstString, AstString); - -#[pyfunction] -pub fn StrToBV(py: Python, s: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.strtobv(&get_astref(s))?) -} - -#[pyfunction] -pub fn BVToStr(py: Python, bv: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.bvtostr(&get_astref(bv))?) -} - -pyop!(StrIsDigit, strisdigit, AstString, AstString); -pyop!(StrEq, streq, AstString, AstString, AstString); -pyop!(StrNeq, strneq, AstString, AstString, AstString); - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - m.add_class::()?; - - add_pyfunctions!( - m, - StringS, - StringV, - StrLen, - StrConcat, - StrSubstr, - StrContains, - StrIndexOf, - StrReplace, - StrPrefixOf, - StrSuffixOf, - StrToBV, - BVToStr, - StrIsDigit, - StrEq, - StrNeq, - ); - - Ok(()) -} diff --git a/crates/claripy/src/macros.rs b/crates/claripy/src/macros.rs deleted file mode 100644 index 20736bf..0000000 --- a/crates/claripy/src/macros.rs +++ /dev/null @@ -1,108 +0,0 @@ -#[macro_export] -macro_rules! add_pyfunctions { - ($m:ident, $($fn_name:path),*,) => { - $( - $m.add_function(wrap_pyfunction!($fn_name, $m)?)?; - )* - }; -} - -#[macro_export] -macro_rules! pyop { - // Match one argument - ($fn_name:ident, $context_method:ident, $return_type:ty, $at:tt) => { - #[allow(non_snake_case)] - #[pyfunction] - pub fn $fn_name(py: Python, ast: pyop!(@py? $at)) -> Result, ClaripyError> { - py_ast_from_astref(py, $crate::ast::py_factory::GLOBAL_CONTEXT.$context_method(&$crate::ast::get_astref(ast))?) - } - }; - - // Match two arguments - ($fn_name:ident, $context_method:ident, $return_type:ty, $at1:tt, $at2:tt) => { - #[allow(non_snake_case)] - #[pyfunction] - pub fn $fn_name(py: Python, lhs: pyop!(@py? $at1), rhs: pyop!(@py? $at2)) -> Result, ClaripyError> { - py_ast_from_astref(py, $crate::ast::py_factory::GLOBAL_CONTEXT.$context_method(&$crate::ast::get_astref(lhs), &$crate::ast::get_astref(rhs))?) - } - }; - - // Extract-shaped - // TODO: Why can't the named arguments pattern cover this? - ($fn_name:ident, $context_method:ident, $return_type:ty, $at:ty, $a1:ident: $a1_t:tt, $a2:ident: $a2_t:tt) => { - #[allow(non_snake_case)] - #[pyfunction] - pub fn $fn_name(py: Python, ast: PyRef<$at>, $a1: $a1_t, $a2: $a2_t) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.$context_method(&get_astref(ast), $a1, $a2)?) - } - }; - - // If-shaped - // TODO: Why can't the named arguments pattern cover this? - ($fn_name:ident, $context_method:ident, $return_type:ty, $a1:ident: $a1_t:tt, $a2:ident: $a2_t:tt, $a3:ident, $a3_t:tt) => { - #[allow(non_snake_case)] - #[pyfunction] - pub fn $fn_name(py: Python, ast: PyRef<$at>, $a1: pyop!(@py? $a1_t), $a2: pyop!(@py? $a2_t), $a3: pyop!(@py? $a3_t)) -> Result, ClaripyError> { - py_ast_from_astref(py, GLOBAL_CONTEXT.$context_method(&get_astref($a1), &get_astref($a2), &get_astref($a3))?) - } - }; - - // Named arguments - ($fn_name:ident, $context_method:ident, $return_type:ty, $($arg_name:ident: $arg_type:tt),*) => { - #[pyfunction] - #[allow(non_snake_case)] - pub fn $fn_name(py: Python, $($arg_name: pyop!(@py? $arg_type)),*) -> Result, ClaripyError> { - // $(let $arg_name = define_pyop!(@convert_arg $arg_name, $arg_type);)* - py_ast_from_astref(py, $crate::ast::py_factory::GLOBAL_CONTEXT.$context_method($(pyop!(@astref? $arg_name, $arg_type)),*)?) - } - }; - - // Helper to convert PyRef arguments to AstRef using get_astref - (@astref? $arg_name:ident, PyRef<$inner_type:tt>) => { - &$crate::ast::py_factory::get_astref($arg_name), - }; - - (@astref? $arg_name:ident, String) => { - $arg_name - }; - - (@astref? $arg_name:ident, BigUint) => { - &$arg_name - }; - - (@astref? $arg_name:ident, u32) => { - $arg_name - }; - - (@astref? $arg_name:ident, $arg_type:ty) => { - $arg_name - }; - - (@py? Bool) => { - PyRef - }; - - (@py? BV) => { - PyRef - }; - - (@py? FP) => { - PyRef - }; - - (@py? AstString) => { - PyRef - }; - - (@py? FSort) => { - PyRef - }; - - (@py? PyRef<$arg_type:tt>) => { - PyRef<$arg_type> - }; - - (@py? $arg_type:ty) => { - $arg_type - }; -} diff --git a/crates/claripy/src/prelude.rs b/crates/claripy/src/prelude.rs deleted file mode 100644 index 7755a5a..0000000 --- a/crates/claripy/src/prelude.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub use crate::ast; -pub use crate::ast::{ - base::Base, get_astref, py_factory::extract_arg, py_factory::py_ast_from_astref, - py_factory::GLOBAL_CONTEXT, PyAst, -}; -pub use crate::error::ClaripyError; -pub use crate::weakref::WeakRef; -pub use clarirs_core::prelude::*; -pub use pyo3::prelude::*; diff --git a/crates/claripy/src/solver.rs b/crates/claripy/src/solver.rs deleted file mode 100644 index 659d522..0000000 --- a/crates/claripy/src/solver.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::prelude::*; - -macro_rules! pysolver { - ($m:ident, $pystruct:ident, $struct_:ident, $pyname:literal) => { - #[pyclass(name = $pyname, module = "claripy.solver")] - pub struct $pystruct { - solver: $struct_<'static>, - } - - #[pymethods] - impl $pystruct { - #[new] - fn new() -> Result, ClaripyError> { - Ok(PyClassInitializer::from(Self { - solver: $struct_::new(&GLOBAL_CONTEXT)?, - })) - } - - fn add(&mut self, expr: Bound) -> Result<(), ClaripyError> { - Ok(self.solver.add(&expr.get().ast.clone())?) - } - - fn eval(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { - py_ast_from_astref(py, self.solver.eval(&expr.get().ast)?) - } - - fn batch_eval( - &mut self, - py: Python, - exprs: Vec>, - max_solutions: u32, - ) -> Result>, ClaripyError> { - self.solver - .batch_eval(exprs.iter().map(|e| e.get().ast.clone()), max_solutions)? - .into_iter() - .map(|ast| py_ast_from_astref::(py, ast)) - .collect::>, ClaripyError>>() - } - - fn is_solution( - &mut self, - expr: Bound, - value: Bound, - ) -> Result { - Ok(self.solver.is_solution(&expr.get().ast, &value.get().ast)?) - } - - fn is_true(&mut self, expr: Bound) -> Result { - Ok(self.solver.is_true(&expr.get().ast).unwrap()) - } - - fn is_false(&mut self, expr: Bound) -> Result { - Ok(self.solver.is_false(&expr.get().ast).unwrap()) - } - - fn min(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { - py_ast_from_astref(py, self.solver.min(expr.get().ast.clone()).unwrap()) - } - - fn max(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { - py_ast_from_astref(py, self.solver.max(expr.get().ast.clone()).unwrap()) - } - } - $m.add_class::()?; - }; -} - -pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { - pysolver!(m, PyConcreteSolver, ConcreteSolver, "ConcreteSolver"); - Ok(()) -} diff --git a/crates/claripy/src/weakref.rs b/crates/claripy/src/weakref.rs deleted file mode 100644 index 5afbcbe..0000000 --- a/crates/claripy/src/weakref.rs +++ /dev/null @@ -1,29 +0,0 @@ -use pyo3::{ - ffi::{self}, - Py, Python, -}; - -pub struct WeakRef(*mut ffi::PyObject); - -unsafe impl Send for WeakRef {} -unsafe impl Sync for WeakRef {} - -impl WeakRef { - pub fn is_valid(&self) -> bool { - unsafe { ffi::PyWeakref_CheckRef(self.0) != 0 } - } - - pub fn upgrade(&self, py: Python<'_>) -> Option> { - if self.is_valid() { - Some(unsafe { Py::from_borrowed_ptr(py, ffi::PyWeakref_GetObject(self.0)) }) - } else { - None - } - } -} - -impl From<&Py> for WeakRef { - fn from(obj: &Py) -> Self { - WeakRef(unsafe { ffi::PyWeakref_NewRef(obj.as_ptr(), std::ptr::null_mut()) }) - } -} diff --git a/crates/clarirs_core/src/algorithms/simplify.rs b/crates/clarirs_core/src/algorithms/simplify.rs index d4acace..a6d65ef 100644 --- a/crates/clarirs_core/src/algorithms/simplify.rs +++ b/crates/clarirs_core/src/algorithms/simplify.rs @@ -24,6 +24,8 @@ impl<'c> Simplify<'c> for BoolAst<'c> { BooleanOp::And(arc, arc1) => todo!(), BooleanOp::Or(arc, arc1) => todo!(), BooleanOp::Xor(arc, arc1) => todo!(), + BooleanOp::BoolEq(arc, arc1) => todo!(), + BooleanOp::BoolNeq(arc, arc1) => todo!(), BooleanOp::Eq(arc, arc1) => todo!(), BooleanOp::Neq(arc, arc1) => todo!(), BooleanOp::ULT(arc, arc1) => todo!(), diff --git a/crates/clarirs_core/src/ast/bool.rs b/crates/clarirs_core/src/ast/bool.rs index e30fa04..c3f859e 100644 --- a/crates/clarirs_core/src/ast/bool.rs +++ b/crates/clarirs_core/src/ast/bool.rs @@ -13,6 +13,8 @@ pub enum BooleanOp<'c> { And(BoolAst<'c>, BoolAst<'c>), Or(BoolAst<'c>, BoolAst<'c>), Xor(BoolAst<'c>, BoolAst<'c>), + BoolEq(BoolAst<'c>, BoolAst<'c>), + BoolNeq(BoolAst<'c>, BoolAst<'c>), Eq(BitVecAst<'c>, BitVecAst<'c>), Neq(BitVecAst<'c>, BitVecAst<'c>), ULT(BitVecAst<'c>, BitVecAst<'c>), @@ -56,7 +58,11 @@ impl<'c> Op<'c> for BooleanOp<'c> { BooleanOp::StrIsDigit(a) => vec![a.into()], // Cases with two children - BooleanOp::And(a, b) | BooleanOp::Or(a, b) | BooleanOp::Xor(a, b) => { + BooleanOp::And(a, b) + | BooleanOp::Or(a, b) + | BooleanOp::Xor(a, b) + | BooleanOp::BoolEq(a, b) + | BooleanOp::BoolNeq(a, b) => { vec![a.into(), b.into()] } BooleanOp::Eq(a, b) diff --git a/crates/clarirs_core/src/ast/factory.rs b/crates/clarirs_core/src/ast/factory.rs index 15514e0..0f3670b 100644 --- a/crates/clarirs_core/src/ast/factory.rs +++ b/crates/clarirs_core/src/ast/factory.rs @@ -84,6 +84,22 @@ pub trait AstFactory<'c>: Sized { Op::xor(self, lhs, rhs) } + fn eq_>( + &'c self, + lhs: &AstRef<'c, Op>, + rhs: &AstRef<'c, Op>, + ) -> Result, ClarirsError> { + Op::eq_(self, lhs, rhs) + } + + fn neq>( + &'c self, + lhs: &AstRef<'c, Op>, + rhs: &AstRef<'c, Op>, + ) -> Result, ClarirsError> { + Op::neq(self, lhs, rhs) + } + fn add>( &'c self, lhs: &AstRef<'c, Op>, @@ -217,22 +233,6 @@ pub trait AstFactory<'c>: Sized { self.make_bitvec(BitVecOp::Reverse(lhs.clone())) } - fn eq_( - &'c self, - lhs: &BitVecAst<'c>, - rhs: &BitVecAst<'c>, - ) -> Result, ClarirsError> { - self.make_bool(BooleanOp::Eq(lhs.clone(), rhs.clone())) - } - - fn neq( - &'c self, - lhs: &BitVecAst<'c>, - rhs: &BitVecAst<'c>, - ) -> Result, ClarirsError> { - self.make_bool(BooleanOp::Neq(lhs.clone(), rhs.clone())) - } - fn ult( &'c self, lhs: &BitVecAst<'c>, diff --git a/crates/clarirs_core/src/ast/factory_support.rs b/crates/clarirs_core/src/ast/factory_support.rs index 17a0ca5..d0ebf9a 100644 --- a/crates/clarirs_core/src/ast/factory_support.rs +++ b/crates/clarirs_core/src/ast/factory_support.rs @@ -3,7 +3,7 @@ use crate::prelude::*; macro_rules! uniop_support_trait { ($name:ident, $($impler:ty, $factory_func:ident),*) => { paste::paste! { - pub(crate) trait []<'c>: Op<'c> + Sized { + pub trait []<'c>: Op<'c> + Sized { fn [< $name:lower >](factory: &'c impl AstFactory<'c>, ast: &AstRef<'c, Self>) -> Result, ClarirsError>; } } @@ -99,3 +99,99 @@ impl_supports_if_and_annotated!( StringOp<'c>, make_string ); + +pub trait SupportsEq<'c>: Op<'c> + Sized { + fn eq_( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError>; +} + +impl<'c> SupportsEq<'c> for BooleanOp<'c> { + fn eq_( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::BoolEq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsEq<'c> for BitVecOp<'c> { + fn eq_( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::Eq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsEq<'c> for FloatOp<'c> { + fn eq_( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::FpEq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsEq<'c> for StringOp<'c> { + fn eq_( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::StrEq(lhs.clone(), rhs.clone())) + } +} + +pub trait SupportsNeq<'c>: Op<'c> + Sized { + fn neq( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError>; +} + +impl<'c> SupportsNeq<'c> for BooleanOp<'c> { + fn neq( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::BoolNeq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsNeq<'c> for BitVecOp<'c> { + fn neq( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::Neq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsNeq<'c> for FloatOp<'c> { + fn neq( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::FpNeq(lhs.clone(), rhs.clone())) + } +} + +impl<'c> SupportsNeq<'c> for StringOp<'c> { + fn neq( + factory: &'c impl AstFactory<'c>, + lhs: &AstRef<'c, Self>, + rhs: &AstRef<'c, Self>, + ) -> Result, ClarirsError> { + factory.make_bool(BooleanOp::StrNeq(lhs.clone(), rhs.clone())) + } +} diff --git a/crates/clarirs_core/src/solver/solver.rs b/crates/clarirs_core/src/solver/solver.rs index 5049615..a93b6d5 100644 --- a/crates/clarirs_core/src/solver/solver.rs +++ b/crates/clarirs_core/src/solver/solver.rs @@ -26,13 +26,13 @@ pub trait Model<'c> { /// Get the minimum value of an expression in the current model. If the constraints are /// unsatisfiable, an error is returned. - fn min(&self, expr: BitVecAst<'c>) -> Result, ClarirsError> { + fn min(&self, expr: &BitVecAst<'c>) -> Result, ClarirsError> { todo!() } /// Get the maximum value of an expression in the current model. If the constraints are /// unsatisfiable, an error is returned. - fn max(&self, expr: BitVecAst<'c>) -> Result, ClarirsError> { + fn max(&self, expr: &BitVecAst<'c>) -> Result, ClarirsError> { todo!() } } @@ -104,13 +104,13 @@ pub trait Solver<'c>: Clone + HasContext<'c> { /// Get the minimum value of an expression in the current model. If the constraints are /// unsatisfiable, an error is returned. - fn min(&mut self, expr: BitVecAst<'c>) -> Result, ClarirsError> { + fn min(&mut self, expr: &BitVecAst<'c>) -> Result, ClarirsError> { self.model()?.min(expr) } /// Get the maximum value of an expression in the current model. If the constraints are /// unsatisfiable, an error is returned. - fn max(&mut self, expr: BitVecAst<'c>) -> Result, ClarirsError> { + fn max(&mut self, expr: &BitVecAst<'c>) -> Result, ClarirsError> { self.model()?.max(expr) } } diff --git a/crates/claripy/Cargo.toml b/crates/clarirs_py/Cargo.toml similarity index 56% rename from crates/claripy/Cargo.toml rename to crates/clarirs_py/Cargo.toml index 9a48a69..8de3bd5 100644 --- a/crates/claripy/Cargo.toml +++ b/crates/clarirs_py/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "claripy" +name = "clarirs_py" version = "0.1.0" edition = "2021" description = "Python bindings for clarirs" @@ -10,14 +10,19 @@ categories = { workspace = true } repository = { workspace = true } [dependencies] -# pyo3 0.22.2 has an issue with raising exceptions on Python 3.12, so we use the -# latest version from the master branch until a new release is made. -# See https://github.com/PyO3/pyo3/pull/3306 -pyo3 = { git = "https://github.com/PyO3/pyo3.git", features = ["extension-module", "py-clone", "num-bigint"] } +pyo3 = { version = "0.22.5", features = [ + "extension-module", + "py-clone", + "num-bigint", +] } clarirs_core = { path = "../clarirs_core", features = ["runtime-checks"] } thiserror = "1.0.63" num-bigint = "0.4.6" log = "0.4.22" +dashmap = "6.1.0" + +[lib] +name = "clarirs" [lints] workspace = true diff --git a/crates/claripy/benchmarks/perf_ast.py b/crates/clarirs_py/benchmarks/perf_ast.py similarity index 59% rename from crates/claripy/benchmarks/perf_ast.py rename to crates/clarirs_py/benchmarks/perf_ast.py index 6dfd9b4..34819cb 100644 --- a/crates/claripy/benchmarks/perf_ast.py +++ b/crates/clarirs_py/benchmarks/perf_ast.py @@ -1,10 +1,10 @@ from __future__ import annotations -import claripy -from claripy.ast.base import Base +import clarirs +from clarirs.ast.base import Base -class MyAnnotation(claripy.Annotation): +class MyAnnotation(clarirs.Annotation): def __init__(self, value): self.value = value @@ -16,23 +16,22 @@ def make_asts() -> list[Base]: results: list[Base] = [] # Make a lot of BVS - results.extend([claripy.BVS(str(i), 32) for i in range(1000)]) + results.extend([clarirs.BVS(str(i), 32) for i in range(1000)]) # Make a lot of BVV - results.extend([claripy.BVV(i, 32) for i in range(1000)]) + results.extend([clarirs.BVV(i, 32) for i in range(1000)]) # Make a lot of And - results.extend([claripy.And(claripy.BVS(str(i), 32), claripy.BVV(i, 32)) for i in range(1000)]) - # results.extend(res) + results.extend([clarirs.And(clarirs.BVS(str(i), 32), clarirs.BVV(i, 32)) for i in range(1000)]) # Make a lot of Or - results.extend([claripy.Or(claripy.BVS(str(i), 32), claripy.BVV(i, 32)) for i in range(1000)]) + results.extend([clarirs.Or(clarirs.BVS(str(i), 32), clarirs.BVV(i, 32)) for i in range(1000)]) # Make a lot of FPS - results.extend([claripy.FPS(str(i), claripy.FSORT_DOUBLE) for i in range(1000)]) + results.extend([clarirs.FPS(str(i), clarirs.FSORT_DOUBLE) for i in range(1000)]) # Make a lot of FPV - results.extend([claripy.FPV(i, claripy.FSORT_DOUBLE) for i in range(1000)]) + results.extend([clarirs.FPV(i, clarirs.FSORT_DOUBLE) for i in range(1000)]) # Annotate! # for i in range(100): diff --git a/crates/claripy/src/annotation.rs b/crates/clarirs_py/src/annotation.rs similarity index 66% rename from crates/claripy/src/annotation.rs rename to crates/clarirs_py/src/annotation.rs index a3e8087..44df10a 100644 --- a/crates/claripy/src/annotation.rs +++ b/crates/clarirs_py/src/annotation.rs @@ -1,12 +1,12 @@ use crate::prelude::*; -#[pyclass(name = "Annotation", module = "claripy.annotation", subclass)] +#[pyclass(name = "Annotation", module = "clarirs.annotation", subclass)] pub struct PyAnnotation {} -#[pyclass(extends = PyAnnotation, module = "claripy.annotation", subclass)] +#[pyclass(extends = PyAnnotation, module = "clarirs.annotation", subclass)] pub struct SimplificationAvoidanceAnnotation {} -#[pyclass(extends = PyAnnotation, module = "claripy.annotation", subclass)] +#[pyclass(extends = PyAnnotation, module = "clarirs.annotation", subclass)] pub struct RegionAnnotation {} pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { diff --git a/crates/clarirs_py/src/ast/base.rs b/crates/clarirs_py/src/ast/base.rs new file mode 100644 index 0000000..f55b103 --- /dev/null +++ b/crates/clarirs_py/src/ast/base.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +#[pyclass(subclass, frozen, weakref, module = "clarirs.ast.base")] +#[derive(Clone, Default)] +pub struct Base {} + +#[pymethods] +impl Base {} + +impl Base { + pub fn new() -> Self { + Base {} + } +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/crates/clarirs_py/src/ast/bits.rs b/crates/clarirs_py/src/ast/bits.rs new file mode 100644 index 0000000..80e8ff0 --- /dev/null +++ b/crates/clarirs_py/src/ast/bits.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; + +#[pyclass(extends=Base, subclass, frozen, weakref, module="clarirs.ast.bits")] +#[derive(Clone, Default)] +pub struct Bits; + +impl Bits { + pub fn new() -> Self { + Bits {} + } +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/crates/clarirs_py/src/ast/bool.rs b/crates/clarirs_py/src/ast/bool.rs new file mode 100644 index 0000000..03fc15e --- /dev/null +++ b/crates/clarirs_py/src/ast/bool.rs @@ -0,0 +1,101 @@ +#![allow(non_snake_case)] + +use std::sync::LazyLock; + +use dashmap::DashMap; +use pyo3::types::PyWeakrefMethods; +use pyo3::types::PyWeakrefReference; + +use crate::ast::{And, Not, Or, Xor}; +use crate::prelude::*; + +static PY_BOOL_CACHE: LazyLock>> = LazyLock::new(DashMap::new); + +#[pyclass(extends=Base, subclass, frozen, weakref, module="clarirs.ast.bool")] +pub struct Bool { + pub(crate) inner: BoolAst<'static>, +} + +impl Bool { + pub fn new(py: Python, inner: BoolAst<'static>) -> Result, ClaripyError> { + if let Some(cache_hit) = PY_BOOL_CACHE.get(&inner.hash()).and_then(|cache_hit| { + cache_hit + .bind(py) + .upgrade_as::() + .expect("bool cache poisoned") + }) { + Ok(cache_hit.unbind()) + } else { + let this = Py::new( + py, + PyClassInitializer::from(Base::new()).add_subclass(Bool { + inner: inner.clone(), + }), + )?; + let weakref = PyWeakrefReference::new_bound(this.bind(py))?; + PY_BOOL_CACHE.insert(inner.hash(), weakref.unbind()); + + Ok(this) + } + } +} + +#[pymethods] +impl Bool { + fn is_true(self_: PyRef) -> bool { + self_.inner.is_true() + } + + fn is_false(self_: PyRef) -> bool { + self_.inner.is_false() + } +} + +#[pyfunction] +pub fn BoolS(py: Python, name: &str) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.bools(name)?) +} +#[pyfunction] +pub fn BoolV(py: Python, value: bool) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.boolv(value)?) +} + +#[pyfunction] +pub fn Eq_(py: Python, a: Bound, b: Bound) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.eq_(&a.get().inner, &b.get().inner)?) +} + +#[pyfunction] +pub fn Neq(py: Python, a: Bound, b: Bound) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.neq(&a.get().inner, &b.get().inner)?) +} + +#[pyfunction] +pub fn If( + py: Python, + cond: Bound, + then_: Bound, + else_: Bound, +) -> Result, ClaripyError> { + Bool::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_.get().inner, &else_.get().inner)?, + ) +} + +#[pyfunction(name = "true")] +pub fn true_op(py: Python) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.true_()?) +} +#[pyfunction(name = "false")] +pub fn false_op(py: Python) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.false_()?) +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + + add_pyfunctions!(m, BoolS, BoolV, Not, And, Or, Xor, Eq_, If, true_op, false_op,); + + Ok(()) +} diff --git a/crates/clarirs_py/src/ast/bv.rs b/crates/clarirs_py/src/ast/bv.rs new file mode 100644 index 0000000..a3ebc7f --- /dev/null +++ b/crates/clarirs_py/src/ast/bv.rs @@ -0,0 +1,168 @@ +#![allow(non_snake_case)] + +use std::sync::LazyLock; + +use dashmap::DashMap; +use num_bigint::BigUint; +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::types::PyWeakrefReference; + +use crate::ast::{And, Not, Or, Xor}; +use crate::prelude::*; + +static PY_BV_CACHE: LazyLock>> = LazyLock::new(DashMap::new); + +#[pyclass(extends=Bits, subclass, frozen, weakref, module="clarirs.ast.bv")] +pub struct BV { + pub(crate) inner: BitVecAst<'static>, +} + +impl BV { + pub fn new(py: Python, inner: BitVecAst<'static>) -> Result, ClaripyError> { + if let Some(cache_hit) = PY_BV_CACHE.get(&inner.hash()).and_then(|cache_hit| { + cache_hit + .bind(py) + .upgrade_as::() + .expect("bool cache poisoned") + }) { + Ok(cache_hit.unbind()) + } else { + let this = Py::new( + py, + PyClassInitializer::from(Base::new()) + .add_subclass(Bits::new()) + .add_subclass(BV { + inner: inner.clone(), + }), + )?; + let weakref = PyWeakrefReference::new_bound(this.bind(py))?; + PY_BV_CACHE.insert(inner.hash(), weakref.unbind()); + + Ok(this) + } + } +} + +#[pymethods] +impl BV {} + +#[pyfunction] +pub fn BVS(py: Python, name: String, size: u32) -> Result, ClaripyError> { + BV::new(py, GLOBAL_CONTEXT.bvs(&name, size)?) +} + +#[allow(non_snake_case)] +#[pyfunction(signature = (value, size = None))] +pub fn BVV(py: Python, value: Bound, size: Option) -> Result, PyErr> { + if let Ok(int_val) = value.extract::() { + if let Some(size) = size { + let a = GLOBAL_CONTEXT + .bvv_from_biguint_with_size(&int_val, size) + .map_err(ClaripyError::from)?; + return Ok(BV::new(py, a)?); + } else { + return Err(PyErr::new::("size must be specified")); + } + } + // TODO: deduplicate bytes/str + if let Ok(bytes_val) = value.extract::>() { + let int_val = BigUint::from_bytes_le(&bytes_val); + log::warn!("bytes value passed to BVV, assuming little-endian"); + if size.is_some() { + log::warn!("BVV size specified with bytes, value will be ignored"); + } + return Ok(BV::new( + py, + GLOBAL_CONTEXT + .bvv_from_biguint_with_size(&int_val, bytes_val.len() as u32 * 8) + .map_err(ClaripyError::from)?, + )?); + } + if let Ok(str_val) = value.extract::() { + log::warn!("string value passed to BVV, assuming utf-8"); + let bytes_val = str_val.as_bytes(); + let int_val = BigUint::from_bytes_le(bytes_val); + log::warn!("bytes value passed to BVV, assuming little-endian"); + if size.is_some() { + log::warn!("BVV size specified with bytes, value will be ignored"); + } + return Ok(BV::new( + py, + GLOBAL_CONTEXT + .bvv_from_biguint_with_size(&int_val, bytes_val.len() as u32 * 8) + .map_err(ClaripyError::from)?, + )?); + } + Err(PyErr::new::( + "BVV value must be a int, bytes, or str", + )) +} + +macro_rules! binop { + ($name:ident, $context_method:ident, $return_type:ty, $lhs:ty, $rhs:ty) => { + #[pyfunction] + pub fn $name( + py: Python, + lhs: Bound<$lhs>, + rhs: Bound<$rhs>, + ) -> Result, ClaripyError> { + <$return_type>::new( + py, + GLOBAL_CONTEXT.$context_method(&lhs.get().inner, &rhs.get().inner)?, + ) + } + }; +} + +binop!(Add, add, BV, BV, BV); +binop!(Sub, sub, BV, BV, BV); +binop!(Mul, mul, BV, BV, BV); +binop!(UDiv, udiv, BV, BV, BV); +binop!(SDiv, sdiv, BV, BV, BV); +binop!(UMod, urem, BV, BV, BV); +binop!(SMod, srem, BV, BV, BV); +binop!(Pow, pow, BV, BV, BV); +binop!(ShL, ashl, BV, BV, BV); +binop!(LShR, lshr, BV, BV, BV); +binop!(AShR, ashr, BV, BV, BV); +binop!(Concat, concat, BV, BV, BV); + +binop!(ULT, ult, Bool, BV, BV); +binop!(ULE, ule, Bool, BV, BV); +binop!(UGT, ugt, Bool, BV, BV); +binop!(UGE, uge, Bool, BV, BV); +binop!(SLT, slt, Bool, BV, BV); +binop!(SLE, sle, Bool, BV, BV); +binop!(SGT, sgt, Bool, BV, BV); +binop!(SGE, sge, Bool, BV, BV); +binop!(Eq_, eq_, Bool, BV, BV); +binop!(Neq, neq, Bool, BV, BV); + +#[pyfunction] +pub fn Extract(py: Python, base: Bound, start: u32, end: u32) -> Result, ClaripyError> { + BV::new(py, GLOBAL_CONTEXT.extract(&base.get().inner, start, end)?) +} + +#[pyfunction] +pub fn If( + py: Python, + cond: Bound, + then_: Bound, + else_: Bound, +) -> Result, ClaripyError> { + BV::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_.get().inner, &else_.get().inner)?, + ) +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + + add_pyfunctions!( + m, BVS, BVV, Not, And, Or, Xor, Add, Sub, Mul, UDiv, SDiv, UMod, SMod, Pow, ShL, LShR, + AShR, Concat, Extract, ULT, ULE, UGT, UGE, SLT, SLE, SGT, SGE, Eq_, If, + ); + + Ok(()) +} diff --git a/crates/claripy/src/ast/fp.rs b/crates/clarirs_py/src/ast/fp.rs similarity index 50% rename from crates/claripy/src/ast/fp.rs rename to crates/clarirs_py/src/ast/fp.rs index 939cb52..e82ddc8 100644 --- a/crates/claripy/src/ast/fp.rs +++ b/crates/clarirs_py/src/ast/fp.rs @@ -1,11 +1,15 @@ #![allow(non_snake_case)] -use ast::bv::BV; +use std::sync::LazyLock; + +use dashmap::DashMap; +use pyo3::types::PyWeakrefReference; -use crate::ast::bits::Bits; use crate::prelude::*; -#[pyclass(name = "RM", module = "claripy.ast.fp", eq)] +static PY_FP_CACHE: LazyLock>> = LazyLock::new(DashMap::new); + +#[pyclass(name = "RM", module = "clarirs.ast.fp", eq)] #[derive(Clone, PartialEq, Eq, Default)] #[allow(non_camel_case_types)] pub enum PyRM { @@ -29,7 +33,7 @@ impl From for FPRM { } } -#[pyclass(name = "FSort", module = "claripy.ast.fp")] +#[pyclass(name = "FSort", module = "clarirs.ast.fp")] #[derive(Clone)] pub struct PyFSort(FSort); @@ -53,31 +57,45 @@ pub fn fsort_double() -> PyFSort { PyFSort(FSort::f64()) } -#[pyclass(extends=Bits, subclass, frozen, weakref, module="claripy.ast.bits")] -#[derive(Default)] -pub struct FP; +#[pyclass(extends=Bits, subclass, frozen, weakref, module="clarirs.ast.bits")] +pub struct FP { + pub(crate) inner: FloatAst<'static>, +} impl FP { - pub fn new() -> Self { - FP {} + pub fn new(py: Python, inner: FloatAst<'static>) -> Result, ClaripyError> { + if let Some(cache_hit) = PY_FP_CACHE.get(&inner.hash()).and_then(|cache_hit| { + cache_hit + .bind(py) + .upgrade_as::() + .expect("bool cache poisoned") + }) { + Ok(cache_hit.unbind()) + } else { + let this = Py::new( + py, + PyClassInitializer::from(Base::new()) + .add_subclass(Bits::new()) + .add_subclass(FP { + inner: inner.clone(), + }), + )?; + let weakref = PyWeakrefReference::new_bound(this.bind(py))?; + PY_FP_CACHE.insert(inner.hash(), weakref.unbind()); + + Ok(this) + } } } -impl PyAst for FP { - fn new_from_astref(ast_ref: &AstRef<'static>) -> PyClassInitializer { - Bits::new_from_astref(ast_ref).add_subclass(FP::new()) - } - - fn as_base(self_: PyRef) -> PyRef { - self_.into_super().into_super() - } +#[pyfunction] +pub fn FPS(py: Python, name: &str, sort: PyFSort) -> Result, ClaripyError> { + FP::new(py, GLOBAL_CONTEXT.fps(name, sort)?) } -pyop!(FPS, fps, FP, name: String, sort: PyFSort); - #[pyfunction] pub fn FPV(py: Python, value: f64, sort: PyFSort) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, GLOBAL_CONTEXT .fpv(>::into(value).to_fsort(sort.into(), FPRM::default()))?, @@ -91,171 +109,189 @@ pub fn FpToFP( sort: PyFSort, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.fp_to_fp(&get_astref(fp), sort, rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_to_fp(&fp.inner, sort, rm.unwrap_or_default())?, ) } #[pyfunction(name = "bvToFpUnsigned", signature = (bv, sort, rm = None))] pub fn BvToFpUnsigned( py: Python, - bv: PyRef, + bv: Bound, sort: PyFSort, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.bv_to_fp_unsigned(&get_astref(bv), sort, rm.unwrap_or_default())?, + GLOBAL_CONTEXT.bv_to_fp_unsigned(&bv.get().inner, sort, rm.unwrap_or_default())?, ) } -pyop!(fpToIEEEBV, fp_to_ieeebv, BV, FP); +#[pyfunction(name = "fpToIEEEBV", signature = (fp))] +pub fn fpToIEEEBV(py: Python, fp: Bound) -> Result, ClaripyError> { + BV::new(py, GLOBAL_CONTEXT.fp_to_ieeebv(&fp.get().inner)?) +} #[pyfunction(name = "fpToUBV", signature = (fp, len, rm = None))] pub fn FpToUbv( py: Python, - fp: PyRef, + fp: Bound, len: u32, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + BV::new( py, - GLOBAL_CONTEXT.fp_to_ubv(&get_astref(fp), len, rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_to_ubv(&fp.get().inner, len, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpToSBV", signature = (fp, len, rm = None))] pub fn FpToBv( py: Python, - fp: PyRef, + fp: Bound, len: u32, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + BV::new( py, - GLOBAL_CONTEXT.fp_to_sbv(&get_astref(fp), len, rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_to_sbv(&fp.get().inner, len, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpNeg", signature = (lhs, rm = None))] -pub fn FpNeg(py: Python, lhs: PyRef, rm: Option) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpNeg(py: Python, lhs: Bound, rm: Option) -> Result, ClaripyError> { + FP::new( py, - GLOBAL_CONTEXT.fp_neg(&get_astref(lhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_neg(&lhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpAbs", signature = (lhs, rm = None))] -pub fn FpAbs(py: Python, lhs: PyRef, rm: Option) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpAbs(py: Python, lhs: Bound, rm: Option) -> Result, ClaripyError> { + FP::new( py, - GLOBAL_CONTEXT.fp_abs(&get_astref(lhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_abs(&lhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpAdd", signature = (lhs, rhs, rm = None))] pub fn FpAdd( py: Python, - lhs: PyRef, - rhs: PyRef, + lhs: Bound, + rhs: Bound, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.fp_add(&get_astref(lhs), &get_astref(rhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_add(&lhs.get().inner, &rhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpSub", signature = (lhs, rhs, rm = None))] pub fn FpSub( py: Python, - lhs: PyRef, - rhs: PyRef, + lhs: Bound, + rhs: Bound, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.fp_sub(&get_astref(lhs), &get_astref(rhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_sub(&lhs.get().inner, &rhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpMul", signature = (lhs, rhs, rm = None))] pub fn FpMul( py: Python, - lhs: PyRef, - rhs: PyRef, + lhs: Bound, + rhs: Bound, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.fp_mul(&get_astref(lhs), &get_astref(rhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_mul(&lhs.get().inner, &rhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpDiv", signature = (lhs, rhs, rm = None))] pub fn FpDiv( py: Python, - lhs: PyRef, - rhs: PyRef, + lhs: Bound, + rhs: Bound, rm: Option, ) -> Result, ClaripyError> { - py_ast_from_astref( + FP::new( py, - GLOBAL_CONTEXT.fp_div(&get_astref(lhs), &get_astref(rhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_div(&lhs.get().inner, &rhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "fpSqrt", signature = (lhs, rm = None))] -pub fn FpSqrt(py: Python, lhs: PyRef, rm: Option) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpSqrt(py: Python, lhs: Bound, rm: Option) -> Result, ClaripyError> { + FP::new( py, - GLOBAL_CONTEXT.fp_sqrt(&get_astref(lhs), rm.unwrap_or_default())?, + GLOBAL_CONTEXT.fp_sqrt(&lhs.get().inner, rm.unwrap_or_default())?, ) } #[pyfunction(name = "FpEq", signature = (lhs, rhs))] -pub fn FpEq(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpEq(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( py, - GLOBAL_CONTEXT.fp_eq(&get_astref(lhs), &get_astref(rhs))?, + GLOBAL_CONTEXT.fp_eq(&lhs.get().inner, &rhs.get().inner)?, ) } #[pyfunction(name = "fpNeq", signature = (lhs, rhs))] -pub fn FpNeq(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpNeq(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( py, - GLOBAL_CONTEXT.fp_neq(&get_astref(lhs), &get_astref(rhs))?, + GLOBAL_CONTEXT.fp_neq(&lhs.get().inner, &rhs.get().inner)?, ) } #[pyfunction(name = "fpLt", signature = (lhs, rhs))] -pub fn FpLt(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpLt(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( py, - GLOBAL_CONTEXT.fp_lt(&get_astref(lhs), &get_astref(rhs))?, + GLOBAL_CONTEXT.fp_lt(&lhs.get().inner, &rhs.get().inner)?, ) } #[pyfunction(name = "fpLeq", signature = (lhs, rhs))] -pub fn FpLeq(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpLeq(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( py, - GLOBAL_CONTEXT.fp_leq(&get_astref(lhs), &get_astref(rhs))?, + GLOBAL_CONTEXT.fp_leq(&lhs.get().inner, &rhs.get().inner)?, ) } #[pyfunction(name = "fpGt", signature = (lhs, rhs))] -pub fn FpGt(py: Python, lhs: PyRef, rhs: PyRef) -> Result, ClaripyError> { - py_ast_from_astref( +pub fn FpGt(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( py, - GLOBAL_CONTEXT.fp_gt(&get_astref(lhs), &get_astref(rhs))?, + GLOBAL_CONTEXT.fp_gt(&lhs.get().inner, &rhs.get().inner)?, ) } -pyop!(fpIsNan, fp_is_nan, FP, FP); -pyop!(fpIsInf, fp_is_inf, FP, FP); +#[pyfunction(name = "fpGeq", signature = (lhs, rhs))] +pub fn FpGeq(py: Python, lhs: Bound, rhs: Bound) -> Result, ClaripyError> { + Bool::new( + py, + GLOBAL_CONTEXT.fp_geq(&lhs.get().inner, &rhs.get().inner)?, + ) +} + +#[pyfunction(name = "fpIsNan", signature = (fp))] +pub fn FpIsNan(py: Python, fp: Bound) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.fp_is_nan(&fp.get().inner)?) +} + +#[pyfunction(name = "fpIsInf", signature = (fp))] +pub fn FpIsInf(py: Python, fp: Bound) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.fp_is_inf(&fp.get().inner)?) +} pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { m.add_class::()?; @@ -282,8 +318,9 @@ pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { FpLt, FpLeq, FpGt, - fpIsNan, - fpIsInf, + FpGeq, + FpIsNan, + FpIsInf, ); m.add("FSORT_FLOAT", fsort_float())?; diff --git a/crates/clarirs_py/src/ast/mod.rs b/crates/clarirs_py/src/ast/mod.rs new file mode 100644 index 0000000..b5c6cc4 --- /dev/null +++ b/crates/clarirs_py/src/ast/mod.rs @@ -0,0 +1,188 @@ +pub mod base; +pub mod bits; +pub mod bool; +pub mod bv; +pub mod fp; +pub mod string; + +use std::sync::LazyLock; + +use crate::prelude::*; + +use super::import_submodule; + +pub static GLOBAL_CONTEXT: LazyLock> = LazyLock::new(Context::new); + +#[pyfunction] +#[allow(non_snake_case)] +pub fn Not(py: Python, b: Bound) -> Result, ClaripyError> { + if let Ok(b_bool) = b.clone().into_any().downcast::() { + return Bool::new(py, GLOBAL_CONTEXT.not(&b_bool.get().inner)?).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }); + } else if let Ok(b_bv) = b.clone().into_any().downcast::() { + return BV::new(py, GLOBAL_CONTEXT.not(&b_bv.get().inner)?).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }); + } else { + panic!("unsupported type") + } +} + +macro_rules! define_binop { + ($name:ident, $op:ident) => { + #[pyfunction] + #[allow(non_snake_case)] + pub fn $name(py: Python, a: Bound, b: Bound) -> Result, ClaripyError> { + if let Ok(a_bool) = a.clone().into_any().downcast::() { + if let Ok(b_bool) = b.clone().into_any().downcast::() { + return Bool::new( + py, + GLOBAL_CONTEXT.$op(&a_bool.get().inner, &b_bool.get().inner)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }); + } else { + panic!("mismatched types") + } + } else if let Ok(a_bv) = a.clone().into_any().downcast::() { + if let Ok(b_bv) = b.clone().into_any().downcast::() { + return BV::new( + py, + GLOBAL_CONTEXT.$op(&a_bv.get().inner, &b_bv.get().inner)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }); + } else { + panic!("mismatched types") + } + } else { + panic!("unsupported type") + } + } + }; +} + +define_binop!(And, and); +define_binop!(Or, or); +define_binop!(Xor, xor); + +#[pyfunction] +#[allow(non_snake_case)] +pub fn If( + py: Python, + cond: Bound, + then_: Bound, + else_: Bound, +) -> Result, ClaripyError> { + if let Ok(then_bv) = then_.clone().into_any().downcast::() { + if let Ok(else_bv) = else_.clone().into_any().downcast::() { + let then_bv = then_bv.get().inner.clone(); + let else_bv = else_bv.get().inner.clone(); + BV::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_bv, &else_bv)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("mismatched types") + } + } else if let Ok(then_bool) = then_.clone().into_any().downcast::() { + if let Ok(else_bv) = else_.clone().into_any().downcast::() { + let then_bv = then_bool.get().inner.clone(); + let else_bv = else_bv.get().inner.clone(); + Bool::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_bv, &else_bv)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("mismatched types") + } + } else if let Ok(then_fp) = then_.clone().into_any().downcast::() { + if let Ok(else_bv) = else_.clone().into_any().downcast::() { + let then_bv = then_fp.get().inner.clone(); + let else_bv = else_bv.get().inner.clone(); + FP::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_bv, &else_bv)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("mismatched types") + } + } else if let Ok(then_string) = then_.clone().into_any().downcast::() { + if let Ok(else_bv) = else_.clone().into_any().downcast::() { + let then_bv = then_string.get().inner.clone(); + let else_bv = else_bv.get().inner.clone(); + PyAstString::new( + py, + GLOBAL_CONTEXT.if_(&cond.get().inner, &then_bv, &else_bv)?, + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("mismatched types") + } + } else { + panic!("unsupported type") + } +} + +pub(crate) fn import(py: Python, m: &Bound) -> PyResult<()> { + import_submodule(py, m, "clarirs.ast", "base", base::import)?; + import_submodule(py, m, "clarirs.ast", "bits", bits::import)?; + import_submodule(py, m, "clarirs.ast", "bool", bool::import)?; + import_submodule(py, m, "clarirs.ast", "bv", bv::import)?; + import_submodule(py, m, "clarirs.ast", "fp", fp::import)?; + import_submodule(py, m, "clarirs.ast", "strings", string::import)?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/crates/clarirs_py/src/ast/string.rs b/crates/clarirs_py/src/ast/string.rs new file mode 100644 index 0000000..3c7b367 --- /dev/null +++ b/crates/clarirs_py/src/ast/string.rs @@ -0,0 +1,205 @@ +#![allow(non_snake_case)] + +use std::sync::LazyLock; + +use dashmap::DashMap; +use pyo3::types::PyWeakrefReference; + +use crate::prelude::*; + +static PY_STRING_CACHE: LazyLock>> = + LazyLock::new(DashMap::new); + +#[pyclass(name="String", extends=Base, subclass, frozen, module="clarirs.ast.string")] +pub struct PyAstString { + pub(crate) inner: StringAst<'static>, +} + +impl PyAstString { + pub fn new(py: Python, inner: StringAst<'static>) -> Result, ClaripyError> { + if let Some(cache_hit) = PY_STRING_CACHE.get(&inner.hash()).and_then(|cache_hit| { + cache_hit + .bind(py) + .upgrade_as::() + .expect("bool cache poisoned") + }) { + Ok(cache_hit.unbind()) + } else { + let this = Py::new( + py, + PyClassInitializer::from(Base::new()).add_subclass(PyAstString { + inner: inner.clone(), + }), + )?; + let weakref = PyWeakrefReference::new_bound(this.bind(py))?; + PY_STRING_CACHE.insert(inner.hash(), weakref.unbind()); + + Ok(this) + } + } +} + +#[pyfunction] +pub fn StringS(py: Python, name: &str, size: u32) -> Result, ClaripyError> { + PyAstString::new(py, GLOBAL_CONTEXT.strings(name, size)?) +} + +#[pyfunction] +pub fn StringV(py: Python, value: &str) -> Result, ClaripyError> { + PyAstString::new(py, GLOBAL_CONTEXT.stringv(value)?) +} + +#[pyfunction] +pub fn StrLen(py: Python, s: Bound) -> Result, ClaripyError> { + BV::new(py, GLOBAL_CONTEXT.strlen(&s.get().inner)?) +} + +#[pyfunction] +pub fn StrConcat( + py: Python, + s1: Bound, + s2: Bound, +) -> Result, ClaripyError> { + PyAstString::new( + py, + GLOBAL_CONTEXT.strconcat(&s1.get().inner, &s2.get().inner)?, + ) +} + +#[pyfunction] +pub fn StrSubstr( + py: Python, + base: Bound, + start: Bound, + end: Bound, +) -> Result, ClaripyError> { + PyAstString::new( + py, + GLOBAL_CONTEXT.strsubstr(&base.get().inner, &start.get().inner, &end.get().inner)?, + ) +} + +#[pyfunction] +pub fn StrContains( + py: Python, + haystack: Bound, + needle: Bound, +) -> Result, ClaripyError> { + Bool::new( + py, + GLOBAL_CONTEXT.strcontains(&haystack.get().inner, &needle.get().inner)?, + ) +} + +#[pyfunction] +pub fn StrIndexOf( + py: Python, + haystack: Bound, + needle: Bound, + start: Bound, +) -> Result, ClaripyError> { + BV::new( + py, + GLOBAL_CONTEXT.strindexof( + &haystack.get().inner, + &needle.get().inner, + &start.get().inner, + )?, + ) +} + +#[pyfunction] +pub fn StrReplace( + py: Python, + haystack: Bound, + needle: Bound, + replacement: Bound, +) -> Result, ClaripyError> { + PyAstString::new( + py, + GLOBAL_CONTEXT.strreplace( + &haystack.get().inner, + &needle.get().inner, + &replacement.get().inner, + )?, + ) +} + +#[pyfunction] +pub fn StrPrefixOf( + py: Python, + needle: Bound, + haystack: Bound, +) -> Result, ClaripyError> { + Bool::new( + py, + GLOBAL_CONTEXT.strprefixof(&needle.get().inner, &haystack.get().inner)?, + ) +} +#[pyfunction] +pub fn StrSuffixOf( + py: Python, + needle: Bound, + haystack: Bound, +) -> Result, ClaripyError> { + Bool::new( + py, + GLOBAL_CONTEXT.strsuffixof(&needle.get().inner, &haystack.get().inner)?, + ) +} + +#[pyfunction] +pub fn StrToBV(py: Python, s: Bound) -> Result, ClaripyError> { + BV::new(py, GLOBAL_CONTEXT.strtobv(&s.get().inner)?) +} + +#[pyfunction] +pub fn BVToStr(py: Python, bv: Bound) -> Result, ClaripyError> { + PyAstString::new(py, GLOBAL_CONTEXT.bvtostr(&bv.get().inner)?) +} + +#[pyfunction] +pub fn StrIsDigit(py: Python, s: Bound) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.strisdigit(&s.get().inner)?) +} +#[pyfunction] +pub fn StrEq( + py: Python, + s1: Bound, + s2: Bound, +) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.streq(&s1.get().inner, &s2.get().inner)?) +} +#[pyfunction] +pub fn StrNeq( + py: Python, + s1: Bound, + s2: Bound, +) -> Result, ClaripyError> { + Bool::new(py, GLOBAL_CONTEXT.strneq(&s1.get().inner, &s2.get().inner)?) +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + + add_pyfunctions!( + m, + StringS, + StringV, + StrLen, + StrConcat, + StrSubstr, + StrContains, + StrIndexOf, + StrReplace, + StrPrefixOf, + StrSuffixOf, + StrToBV, + BVToStr, + StrIsDigit, + StrEq, + StrNeq, + ); + + Ok(()) +} diff --git a/crates/claripy/src/error.rs b/crates/clarirs_py/src/error.rs similarity index 100% rename from crates/claripy/src/error.rs rename to crates/clarirs_py/src/error.rs diff --git a/crates/claripy/src/lib.rs b/crates/clarirs_py/src/lib.rs similarity index 54% rename from crates/claripy/src/lib.rs rename to crates/clarirs_py/src/lib.rs index a4b695d..b6a44ed 100644 --- a/crates/claripy/src/lib.rs +++ b/crates/clarirs_py/src/lib.rs @@ -1,3 +1,4 @@ +#[allow(clippy::multiple_crate_versions)] #[macro_use] mod macros; @@ -7,7 +8,6 @@ pub mod error; pub mod prelude; pub mod py_err; pub mod solver; -pub mod weakref; use prelude::*; @@ -18,7 +18,7 @@ fn import_submodule<'py>( name: &str, import_func: fn(Python<'py>, &Bound<'py, PyModule>) -> PyResult<()>, ) -> PyResult<()> { - let submodule = PyModule::new(py, name)?; + let submodule = PyModule::new_bound(py, name)?; import_func(py, &submodule)?; pyo3::py_run!( py, @@ -33,15 +33,49 @@ fn import_submodule<'py>( } #[pyfunction(name = "simplify")] -fn py_simplify(py: Python, ast: PyRef) -> Result, ClaripyError> { - py_ast_from_astref(py, get_astref(ast).simplify()?) +fn py_simplify(py: Python, expr: Bound) -> Result, ClaripyError> { + if let Ok(bv_value) = expr.clone().into_any().downcast::() { + BV::new(py, bv_value.get().inner.simplify().unwrap()).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(bool_value) = expr.clone().into_any().downcast::() { + Bool::new(py, bool_value.get().inner.simplify().unwrap()).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(fp_value) = expr.clone().into_any().downcast::() { + FP::new(py, fp_value.get().inner.simplify().unwrap()).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(string_value) = expr.clone().into_any().downcast::() { + PyAstString::new(py, string_value.get().inner.simplify().unwrap()).map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("Unsupported type"); + } } #[pymodule] -pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - import_submodule(py, m, "claripy", "annotation", annotation::import)?; - import_submodule(py, m, "claripy", "ast", ast::import)?; - import_submodule(py, m, "claripy", "solver", solver::import)?; +pub fn clarirs(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + import_submodule(py, m, "clarirs", "annotation", annotation::import)?; + import_submodule(py, m, "clarirs", "ast", ast::import)?; + import_submodule(py, m, "clarirs", "solver", solver::import)?; add_pyfunctions!( m, @@ -61,12 +95,13 @@ pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { ast::bv::UMod, ast::bv::SMod, ast::bv::Pow, - ast::bv::LShL, + ast::bv::ShL, ast::bv::LShR, ast::bv::AShR, - ast::bv::AShL, ast::bv::Concat, ast::bv::Extract, + ast::bv::Eq_, + ast::bv::Neq, ast::bv::ULT, ast::bv::ULE, ast::bv::UGT, @@ -78,6 +113,26 @@ pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { // FP ast::fp::FPS, ast::fp::FPV, + ast::fp::FpToFP, + ast::fp::BvToFpUnsigned, + ast::fp::fpToIEEEBV, + ast::fp::FpToUbv, + ast::fp::FpToBv, + ast::fp::FpNeg, + ast::fp::FpAbs, + ast::fp::FpAdd, + ast::fp::FpSub, + ast::fp::FpMul, + ast::fp::FpDiv, + ast::fp::FpSqrt, + ast::fp::FpEq, + ast::fp::FpNeq, + ast::fp::FpLt, + ast::fp::FpLeq, + ast::fp::FpGt, + ast::fp::FpGeq, + ast::fp::FpIsNan, + ast::fp::FpIsInf, // String ast::string::StringS, ast::string::StringV, @@ -97,12 +152,11 @@ pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { ast::string::StrEq, ast::string::StrNeq, // Shared - ast::shared_ops::Not, - ast::shared_ops::And, - ast::shared_ops::Or, - ast::shared_ops::Xor, - ast::shared_ops::Eq_, - ast::shared_ops::If, + ast::If, + ast::Not, + ast::And, + ast::Or, + ast::Xor, ); m.add_class::()?; @@ -110,7 +164,7 @@ pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -130,7 +184,7 @@ pub fn claripy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { // Compat // fp - let fp = PyModule::new(py, "fp")?; + let fp = PyModule::new_bound(py, "fp")?; fp.add_class::()?; fp.add_class::()?; fp.add("FSORT_FLOAT", ast::fp::fsort_float())?; diff --git a/crates/clarirs_py/src/macros.rs b/crates/clarirs_py/src/macros.rs new file mode 100644 index 0000000..bee95b1 --- /dev/null +++ b/crates/clarirs_py/src/macros.rs @@ -0,0 +1,8 @@ +#[macro_export] +macro_rules! add_pyfunctions { + ($m:ident, $($fn_name:path),*,) => { + $( + $m.add_function(wrap_pyfunction!($fn_name, $m)?)?; + )* + }; +} diff --git a/crates/clarirs_py/src/prelude.rs b/crates/clarirs_py/src/prelude.rs new file mode 100644 index 0000000..1b9ac24 --- /dev/null +++ b/crates/clarirs_py/src/prelude.rs @@ -0,0 +1,7 @@ +pub use crate::ast; +pub use crate::ast::{ + base::Base, bits::Bits, bool::Bool, bv::BV, fp::FP, string::PyAstString, GLOBAL_CONTEXT, +}; +pub use crate::error::ClaripyError; +pub use clarirs_core::prelude::*; +pub use pyo3::prelude::*; diff --git a/crates/claripy/src/py_err.rs b/crates/clarirs_py/src/py_err.rs similarity index 51% rename from crates/claripy/src/py_err.rs rename to crates/clarirs_py/src/py_err.rs index 3f8b5e9..c39b276 100644 --- a/crates/claripy/src/py_err.rs +++ b/crates/clarirs_py/src/py_err.rs @@ -2,16 +2,16 @@ use pyo3::exceptions::PyException; use crate::prelude::*; -#[pyclass(name = "ClaripyError", extends = PyException, module = "claripy.error", subclass)] +#[pyclass(name = "ClaripyError", extends = PyException, module = "clarirs.error", subclass)] pub struct PyClaripyError; -#[pyclass(extends = PyClaripyError, module = "claripy.error", subclass)] +#[pyclass(extends = PyClaripyError, module = "clarirs.error", subclass)] pub struct UnsatError; -#[pyclass(extends = PyClaripyError, module = "claripy.error", subclass)] +#[pyclass(extends = PyClaripyError, module = "clarirs.error", subclass)] pub struct ClaripyFrontendError; -#[pyclass(extends = PyClaripyError, module = "claripy.error", subclass)] +#[pyclass(extends = PyClaripyError, module = "clarirs.error", subclass)] pub struct ClaripySolverInterruptError; -#[pyclass(extends = PyClaripyError, module = "claripy.error", subclass)] +#[pyclass(extends = PyClaripyError, module = "clarirs.error", subclass)] pub struct ClaripyOperationError; -#[pyclass(extends = PyClaripyError, module = "claripy.error", subclass)] +#[pyclass(extends = PyClaripyError, module = "clarirs.error", subclass)] pub struct ClaripyZeroDivisionError; diff --git a/crates/clarirs_py/src/solver.rs b/crates/clarirs_py/src/solver.rs new file mode 100644 index 0000000..461410d --- /dev/null +++ b/crates/clarirs_py/src/solver.rs @@ -0,0 +1,125 @@ +use crate::prelude::*; + +#[pyclass(name = "ConcreteSolver", module = "clarirs.solver")] +pub struct PyConcreteSolver { + inner: ConcreteSolver<'static>, +} + +#[pymethods] +impl PyConcreteSolver { + #[new] + fn new() -> Result, ClaripyError> { + Ok(PyClassInitializer::from(Self { + inner: ConcreteSolver::new(&GLOBAL_CONTEXT)?, + })) + } + + fn add(&mut self, expr: Bound) -> Result<(), ClaripyError> { + Ok(self.inner.add(&expr.get().inner)?) + } + + fn eval(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { + if let Ok(bv_value) = expr.clone().into_any().downcast::() { + BV::new( + py, + self.inner + .model()? + .eval_bitvec(&bv_value.get().inner) + .unwrap(), + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(bool_value) = expr.clone().into_any().downcast::() { + Bool::new( + py, + self.inner + .model()? + .eval_bool(&bool_value.get().inner) + .unwrap(), + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(fp_value) = expr.clone().into_any().downcast::() { + FP::new( + py, + self.inner + .model()? + .eval_float(&fp_value.get().inner) + .unwrap(), + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else if let Ok(string_value) = expr.clone().into_any().downcast::() { + PyAstString::new( + py, + self.inner + .model()? + .eval_string(&string_value.get().inner) + .unwrap(), + ) + .map(|b| { + b.into_any() + .downcast_bound::(py) + .unwrap() + .clone() + .unbind() + }) + } else { + panic!("Unsupported type"); + } + } + + fn batch_eval( + &mut self, + py: Python, + exprs: Vec>, + max_solutions: u32, + ) -> Result>, ClaripyError> { + exprs.into_iter().map(|expr| self.eval(py, expr)).collect() + } + + // TODO: See corresponding function in clarirs_core/src/solver/solver.rs + // fn is_solution( + // &mut self, + // expr: Bound, + // value: Bound, + // ) -> Result { + // Ok(self.solver.is_solution(&expr.get().ast, &value.get().ast)?) + // } + + fn is_true(&mut self, expr: Bound) -> Result { + Ok(self.inner.is_true(&expr.get().inner).unwrap()) + } + + fn is_false(&mut self, expr: Bound) -> Result { + Ok(self.inner.is_false(&expr.get().inner).unwrap()) + } + + fn min(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { + BV::new(py, self.inner.min(&expr.get().inner).unwrap()) + } + + fn max(&mut self, py: Python, expr: Bound) -> Result, ClaripyError> { + BV::new(py, self.inner.max(&expr.get().inner).unwrap()) + } +} + +pub(crate) fn import(_: Python, m: &Bound) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/pyproject.toml b/pyproject.toml index 033b475..a2b3804 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["maturin>=1,<2"] build-backend = "maturin" [project] -name = "claripy" +name = "clarirs" requires-python = ">=3.10" [tool.maturin] -manifest-path = "crates/claripy/Cargo.toml" +manifest-path = "crates/clarirs_py/Cargo.toml"