From bb03d796e8a68231e290f3befd4ff3a132a32478 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Thu, 30 Apr 2026 18:00:36 +1000 Subject: [PATCH] Fix stubtest + bind AbstractStateManager dict-protocol The previous sync (51543e8) added pysvf.AbstractStateManager, AbstractInterpretation and Options to the binary but left the .pyi stub declaring the methods that the same commit removed from AbstractState. The release.yml `stubtest` job catches that drift and was failing on the sync-llvm-21 PR. This commit: pybind/AE.cpp: - Add __setitem__ (delegates to updateAbstractState) and __contains__ (delegates to hasAbstractState) on AbstractStateManager so Python code can use it as a drop-in replacement for the old `post_abs_trace` dict (Software-Security-Analysis Assignment-3 relies on this to wire the stateMgr in as the post-trace backing store). pysvf/pysvf.pyi: - Drop the seven AbstractState methods that the upstream Semi-Sparse refactor moved off the class: getElementIndex, getByteOffset, getPointeeElement, storeValue, loadValue, getAllocaInstByteSize, getGepObjAddrs. - Add type stubs for AbstractStateManager (full surface, including the dict-protocol additions), AbstractInterpretation, and Options so they are visible to mypy/pyright users. - Add GepStmt.getStructFieldOffset matching the new lambda binding. stubtest_allowlist.txt: - Whitelist the new pybind classes (AbstractStateManager, AbstractInterpretation, Options) using the same `pysvf.` shape that already exempts AbstractState/AbstractValue/etc. The pybind11 metaclass + auto-generated `__init__(*args, **kwargs)` always trip stubtest unless the class is allow-listed. Verified locally: stubtest reports `Success: no issues found in 4 modules`. Co-Authored-By: Claude Opus 4.7 (1M context) --- pybind/AE.cpp | 4 +++ pysvf/pysvf.pyi | 62 ++++++++++++++++++++++++++++++++++++------ stubtest_allowlist.txt | 5 ++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/pybind/AE.cpp b/pybind/AE.cpp index 71b0f1c..abe174f 100644 --- a/pybind/AE.cpp +++ b/pybind/AE.cpp @@ -750,6 +750,10 @@ void bind_abstract_state(py::module& m) { .def("__getitem__", [](AbstractStateManager& self, const ICFGNode* node) -> AbstractState& { return self[node]; }, py::arg("node"), py::return_value_policy::reference) + .def("__setitem__", [](AbstractStateManager& self, const ICFGNode* node, const AbstractState& state) { + self.updateAbstractState(node, state); + }, py::arg("node"), py::arg("state")) + .def("__contains__", &AbstractStateManager::hasAbstractState, py::arg("node")) // Def/Use site queries .def("getUseSitesOfObjVar", &AbstractStateManager::getUseSitesOfObjVar, diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index bd0191a..22ad98c 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -531,6 +531,9 @@ class GepStmt(AssignStmt): def getSrcPointeeType(self) -> "SVFType": ... """Get the source pointee type""" + def getStructFieldOffset(self, idx_var: "ValVar", struct_type: "SVFStructType") -> int: ... + """Compute the byte offset for a struct field index""" + class MultiOpndStmt(SVFStmt): def getOpVar(self, ID: int) -> "SVFVar": ... """Get the operand variable""" @@ -1964,16 +1967,10 @@ class AbstractState: def bottom(self) -> None: ... def getIDFromAddr(self, addr: int) -> int: ... def top(self) -> None: ... - def getElementIndex(self, gep: 'GepStmt') -> IntervalValue: ... - def getByteOffset(self, gep: 'GepStmt') -> IntervalValue: ... - def getPointeeElement(self, var_id: int) -> SVFType: ... def isVirtualMemAddress(self, val: int) -> bool: ... def getVirtualMemAddress(self, idx: int) -> int: ... def isCmpBranchFeasible(self, cmp: 'CmpStmt', succ: int, abstract_state: AbstractState) -> bool: ... def isSwitchBranchFeasible(self, switch_var: SVFVar, succ: int, abstract_state: AbstractState) -> bool: ... - def storeValue(self, varId: int, val: AbstractValue) -> None: ... - def loadValue(self, varId: int) -> AbstractValue: ... - def getAllocaInstByteSize(self, addr: AddrStmt) -> int: ... def inVarToValTable(self, var_id: int) -> bool: ... def inVarToAddrsTable(self, var_id: int) -> bool: ... def inAddrToAddrsTable(self, id: int) -> bool: ... @@ -1986,7 +1983,6 @@ class AbstractState: """Check if an address is freed memory""" def hash(self) -> int: ... """Get the hash of this abstract state""" - def getGepObjAddrs(self, var_id: int, offset: IntervalValue) -> AddressValue: ... def clear(self) -> None: ... def clone(self) -> 'AbstractState': ... @@ -2001,7 +1997,57 @@ class AbstractState: def isBlackHoleObjAddr(self, addr: int) -> bool: ... -class PointsTo(Set[int]): +class AbstractStateManager: + """Manages abstract states across ICFG nodes and provides the + sparsity-aware GEP / load / store / size helpers that used to live on + AbstractState (moved upstream by the Semi-Sparse refactor).""" + + def __init__(self, svfir: 'SVFIR', pta: 'AndersenWaveDiff') -> None: ... + + def getAbstractValue(self, var: 'SVFVar', node: 'ICFGNode') -> AbstractValue: ... + def hasAbstractValue(self, var: 'SVFVar', node: 'ICFGNode') -> bool: ... + def updateAbstractValue(self, var: 'SVFVar', val: AbstractValue, node: 'ICFGNode') -> None: ... + + def getAbstractState(self, node: 'ICFGNode') -> AbstractState: ... + def hasAbstractState(self, node: 'ICFGNode') -> bool: ... + def updateAbstractState(self, node: 'ICFGNode', state: AbstractState) -> None: ... + + def getGepElementIndex(self, gep: 'GepStmt') -> IntervalValue: ... + def getGepByteOffset(self, gep: 'GepStmt') -> IntervalValue: ... + def getGepObjAddrs(self, pointer: 'ValVar', offset: IntervalValue) -> AddressValue: ... + + def loadValue(self, pointer: 'ValVar', node: 'ICFGNode') -> AbstractValue: ... + def storeValue(self, pointer: 'ValVar', val: AbstractValue, node: 'ICFGNode') -> None: ... + + def getPointeeElement(self, var: 'ObjVar', node: 'ICFGNode') -> 'SVFType': ... + def getAllocaInstByteSize(self, addr: 'AddrStmt') -> int: ... + + def getTrace(self) -> dict: ... + def __getitem__(self, node: 'ICFGNode') -> AbstractState: ... + def __setitem__(self, node: 'ICFGNode', state: AbstractState) -> None: ... + def __contains__(self, node: 'ICFGNode') -> bool: ... + + def getUseSitesOfObjVar(self, obj: 'ObjVar', node: 'ICFGNode') -> set: ... + def getUseSitesOfValVar(self, var: 'ValVar') -> set: ... + def getDefSiteOfValVar(self, var: 'ValVar') -> 'ICFGNode': ... + def getDefSiteOfObjVar(self, obj: 'ObjVar', node: 'ICFGNode') -> 'ICFGNode': ... + + +class AbstractInterpretation: + """Minimal binding -- exposes getStateMgr() so Python can grab the + AbstractStateManager from a running analysis.""" + + def getStateMgr(self) -> AbstractStateManager: ... + + +class Options: + """A small subset of SVF Options exposed for downstream Python code.""" + + @staticmethod + def max_field_limit() -> int: ... + + +class PointsTo(Set[int]): def set(self, value: int) -> None: ... diff --git a/stubtest_allowlist.txt b/stubtest_allowlist.txt index 612b767..2a2f460 100644 --- a/stubtest_allowlist.txt +++ b/stubtest_allowlist.txt @@ -1,5 +1,10 @@ pysvf.AbstractState +pysvf.AbstractStateManager +pysvf.AbstractInterpretation +pysvf.AbstractInterpretation.__init__ pysvf.AbstractValue +pysvf.Options +pysvf.Options.__init__ pysvf.ActualINSVFGNode pysvf.ActualINSVFGNode.__init__ pysvf.ActualOUTSVFGNode