From 7060e0e88dcd863317de07b99e2a7b7f90176ee1 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 11 May 2026 15:10:33 +1000 Subject: [PATCH] Expose AbstractInterpretation.getAEInstance() to Python The course-side Assignment-3 Python helper calls pysvf.AbstractInterpretation.getAEInstance() to obtain the SVF abstract interpreter singleton. Previously pysvf intentionally did NOT bind getAEInstance because a straightforward .def_static hit a static_assert deep inside : pybind11 instantiates make_(copy|move)_constructor whenever a binding returns the type, and AbstractInterpretation owns a vector> that makes the implicit copy/move ctors ill-formed, but the standard traits don't catch this until the body is generated. Two-part workaround: 1. pybind/AE.cpp: - Specialise pybind11::detail::is_copy_constructible and is_move_constructible for AbstractInterpretation, so pybind11 skips both factory paths when binding methods on the class. - Add the py::nodelete holder so pybind never tries to delete the SVF-owned singleton. - Bind a module-level shim _AbstractInterpretation_getAEInstance (the .def_static path turned out to take a different SFINAE route that the trait specialisations don't cover). 2. pysvf/__init__.py: import the shim and attach it as AbstractInterpretation.getAEInstance = staticmethod(...), so Python callers use the natural class-method form. 3. pysvf/pysvf.pyi: add the @staticmethod stub entry. Verified end-to-end in Docker against svftools/software-security-analysis image (after applying the SVF leak-singleton fix to libSvfCore.so): SSA Python Assignment-3 tests now reach the analysis logic instead of crashing at import; ctest pass rate 28/64 -> 62/64. --- pybind/AE.cpp | 32 ++++++++++++++++++++++++++++---- pysvf/__init__.py | 6 ++++++ pysvf/pysvf.pyi | 3 +++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/pybind/AE.cpp b/pybind/AE.cpp index 5b23f33..68bc809 100644 --- a/pybind/AE.cpp +++ b/pybind/AE.cpp @@ -11,6 +11,20 @@ #include "AE/Svfexe/AbstractInterpretation.h" #include +// Tell pybind11 that AbstractInterpretation is non-copyable and non-movable. +// The class owns a std::vector>, so its implicit +// copy/move ctors are ill-formed — but std::is_(copy|move)_constructible +// reports true until the body is instantiated, which it isn't unless we +// expose getAEInstance. Without these specialisations pybind11 instantiates +// make_(copy|move)_constructor in type_caster_base whenever a binding returns +// AbstractInterpretation* / AbstractInterpretation&, and the static_assert +// inside fires. +namespace pybind11 { namespace detail { +template <> +struct is_copy_constructible : std::false_type {}; +template <> +struct is_move_constructible : std::false_type {}; +}} namespace py = pybind11; using namespace SVF; @@ -684,10 +698,20 @@ void bind_abstract_state(py::module& m) { // (now-removed) AbstractStateManager: loadValue, storeValue, GEP // helpers, def/use-site queries, and direct trace access. // --------------------------------------------------------------- - py::class_(m, "AbstractInterpretation") - // No constructor / static factory bound here: the class has a - // protected ctor and is non-copyable. Users receive instances via - // other bound entry points. + // Module-level shim for AbstractInterpretation::getAEInstance. Exposing + // it via .def_static directly didn't work even with the trait + // specialisations above (pybind11's def_static path takes a different + // SFINAE route), so we bind a free function here and attach it as a + // staticmethod on the class from Python (pysvf/__init__.py). + m.def("_AbstractInterpretation_getAEInstance", []() -> AbstractInterpretation* { + return &AbstractInterpretation::getAEInstance(); + }, py::return_value_policy::reference); + + py::class_>(m, "AbstractInterpretation") + // getAEInstance is attached as a staticmethod from pysvf/__init__.py + // via the module-level shim above. The py::nodelete holder keeps + // pybind from trying to free the SVF-owned singleton. // State access (replaces old AbstractStateManager::getAbstractState etc.). .def("getAbsState", diff --git a/pysvf/__init__.py b/pysvf/__init__.py index 902b035..c9515e7 100644 --- a/pysvf/__init__.py +++ b/pysvf/__init__.py @@ -243,6 +243,12 @@ def main(): from .pysvf import AndersenWaveDiff_WPA as AndersenWaveDiff_WPA from .pysvf import Steensgaard_WPA as Steensgaard_WPA +# AbstractInterpretation.getAEInstance is bound at module level (see comment +# in pybind/AE.cpp); attach it as a staticmethod here so users can call +# pysvf.AbstractInterpretation.getAEInstance() the same way the C++ class does. +from .pysvf import _AbstractInterpretation_getAEInstance as _ae_instance_shim +AbstractInterpretation.getAEInstance = staticmethod(_ae_instance_shim) + # argument can be a string or a list of strings def buildSVFModule(args) -> None: if isinstance(args, str): diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index fb40d22..9b50ed9 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -2003,6 +2003,9 @@ class AbstractInterpretation: AbstractStateManager (folded back into AbstractInterpretation upstream when the AbstractStateManager.h header was removed).""" + @staticmethod + def getAEInstance() -> 'AbstractInterpretation': ... + # State access (replaces old getAbstractState / updateAbstractState). def getAbsState(self, node: 'ICFGNode') -> AbstractState: ... def updateAbsState(self, node: 'ICFGNode', state: AbstractState) -> None: ...