From 5075347b195e559b5c7249367aaa5f2b52209282 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:06:05 +1000 Subject: [PATCH 1/6] Update CallPE binding: AssignStmt -> MultiOpndStmt (phi-like refactor) CallPE was refactored in SVF from AssignStmt to MultiOpndStmt to support phi-like semantics where a formal parameter merges actual parameters from multiple call sites. Update the pybind11 binding and type stubs accordingly: - Change CallPE parent class from AssignStmt to MultiOpndStmt - Replace getCallSite() with getOpCallICFGNode(idx) and getOpCallICFGNodes() - Keep getFunEntryICFGNode() unchanged Co-Authored-By: Claude Opus 4.6 (1M context) --- pybind/SVFIR.cpp | 7 +++++-- pysvf/pysvf.pyi | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pybind/SVFIR.cpp b/pybind/SVFIR.cpp index 6ef2b46..54f0e27 100644 --- a/pybind/SVFIR.cpp +++ b/pybind/SVFIR.cpp @@ -120,8 +120,11 @@ void bind_svf_stmt(py::module& m) { py::class_(m, "LoadStmt"); - py::class_(m, "CallPE") - .def("getCallSite", &CallPE::getCallSite, "Get the call site") + py::class_(m, "CallPE") + .def("getOpCallICFGNode", &CallPE::getOpCallICFGNode, py::return_value_policy::reference, + "Get the CallICFGNode of the i-th operand") + .def("getOpCallICFGNodes", &CallPE::getOpCallICFGNodes, py::return_value_policy::reference, + "Get all call site ICFGNodes") .def("getFunEntryICFGNode", &CallPE::getFunEntryICFGNode, py::return_value_policy::reference, "Get the function entry ICFG node"); diff --git a/pysvf/pysvf.pyi b/pysvf/pysvf.pyi index a1447ad..bd0191a 100644 --- a/pysvf/pysvf.pyi +++ b/pysvf/pysvf.pyi @@ -486,12 +486,15 @@ class StoreStmt(AssignStmt): class LoadStmt(AssignStmt): ... -class CallPE(AssignStmt): +class CallPE(MultiOpndStmt): def __init__(self, *args, **kwargs) -> None: ... """Not intended for direct instantiation.""" - - def getCallSite(self) -> "CallICFGNode": ... - """Get the call site""" + + def getOpCallICFGNode(self, op_idx: int) -> "CallICFGNode": ... + """Get the CallICFGNode of the i-th operand""" + + def getOpCallICFGNodes(self) -> List["CallICFGNode"]: ... + """Get all call site ICFGNodes""" def getFunEntryICFGNode(self) -> "FunEntryICFGNode": ... """Get the function entry ICFG node""" From b867d4d2a3a27d63d84cd606cb81cce71aa0c4b0 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:37:46 +1000 Subject: [PATCH 2/6] Add PyPI publishing to release workflow Publish wheels to official PyPI in addition to TestPyPI. Version calculation now queries both PyPI and TestPyPI to avoid version conflicts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/release.yml | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 889d9a4..dbd505a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,26 +21,30 @@ jobs: - name: Install jq run: sudo apt-get update && sudo apt-get install -y jq - - name: Get next version from TestPyPI + - name: Get next version from PyPI and TestPyPI id: get_next_version run: | PACKAGE="pysvf" - INDEX_URL="https://test.pypi.org/pypi/$PACKAGE/json" - - echo "Querying $INDEX_URL ..." - VERSIONS=$(curl -s "$INDEX_URL" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$') - - if [ -z "$VERSIONS" ]; then + + echo "Querying TestPyPI..." + TEST_VERSIONS=$(curl -s "https://test.pypi.org/pypi/$PACKAGE/json" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$' || true) + + echo "Querying PyPI..." + PROD_VERSIONS=$(curl -s "https://pypi.org/pypi/$PACKAGE/json" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$' || true) + + ALL_VERSIONS=$(echo -e "${TEST_VERSIONS}\n${PROD_VERSIONS}" | grep -v '^$' | sort -V | uniq) + + if [ -z "$ALL_VERSIONS" ]; then LAST_VERSION="1.0.0.0" else - LAST_VERSION=$(echo "$VERSIONS" | sort -V | tail -n 1) + LAST_VERSION=$(echo "$ALL_VERSIONS" | tail -n 1) fi - + echo "Last version: $LAST_VERSION" - + NEXT_VERSION=$(echo "$LAST_VERSION" | awk -F. '{$NF+=1; print $1 "." $2 "." $3 "." $4}') echo "Next version: $NEXT_VERSION" - + echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT stubtest: @@ -209,6 +213,14 @@ jobs: pip install twine twine upload --repository testpypi dist/* --verbose + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + cd SVF-Python + twine upload dist/* --verbose + - name: Upload Python wheels uses: actions/upload-artifact@v4 with: From 787446341b933d45a2c1a7e70c1abae02973de38 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:40:50 +1000 Subject: [PATCH 3/6] Remove TestPyPI, publish only to official PyPI starting from 1.0.0.0 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/release.yml | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbd505a..20a3ae8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,30 +21,24 @@ jobs: - name: Install jq run: sudo apt-get update && sudo apt-get install -y jq - - name: Get next version from PyPI and TestPyPI + - name: Get next version from PyPI id: get_next_version run: | PACKAGE="pysvf" + INDEX_URL="https://pypi.org/pypi/$PACKAGE/json" - echo "Querying TestPyPI..." - TEST_VERSIONS=$(curl -s "https://test.pypi.org/pypi/$PACKAGE/json" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$' || true) + echo "Querying $INDEX_URL ..." + VERSIONS=$(curl -s "$INDEX_URL" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$' || true) - echo "Querying PyPI..." - PROD_VERSIONS=$(curl -s "https://pypi.org/pypi/$PACKAGE/json" | jq -r '.releases | keys[]' | grep -E '^1\.0\.0\.[0-9]+$' || true) - - ALL_VERSIONS=$(echo -e "${TEST_VERSIONS}\n${PROD_VERSIONS}" | grep -v '^$' | sort -V | uniq) - - if [ -z "$ALL_VERSIONS" ]; then - LAST_VERSION="1.0.0.0" + if [ -z "$VERSIONS" ]; then + NEXT_VERSION="1.0.0.0" else - LAST_VERSION=$(echo "$ALL_VERSIONS" | tail -n 1) + LAST_VERSION=$(echo "$VERSIONS" | sort -V | tail -n 1) + echo "Last version: $LAST_VERSION" + NEXT_VERSION=$(echo "$LAST_VERSION" | awk -F. '{$NF+=1; print $1 "." $2 "." $3 "." $4}') fi - echo "Last version: $LAST_VERSION" - - NEXT_VERSION=$(echo "$LAST_VERSION" | awk -F. '{$NF+=1; print $1 "." $2 "." $3 "." $4}') echo "Next version: $NEXT_VERSION" - echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT stubtest: @@ -204,15 +198,6 @@ jobs: echo "Built wheel files:" ls -lh dist/ - - name: Publish to TestPyPI - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - run: | - cd SVF-Python - pip install twine - twine upload --repository testpypi dist/* --verbose - - name: Publish to PyPI env: TWINE_USERNAME: __token__ From 4477140901c103ade79d6bc665f2771e57354691 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:44:34 +1000 Subject: [PATCH 4/6] Update docs: migrate from TestPyPI to PyPI, acknowledge contributors - Update README: add news about PyPI migration and credit mgree for #46 - Update pip install command to use official PyPI - Update Python version support to 3.8-3.12 - Remove TestPyPI references from pag.ipynb demo notebook Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 8 +++++--- demo/pag.ipynb | 21 ++++----------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3650cb4..1b147cd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## News +* Pysvf is now available on [PyPI](https://pypi.org/project/pysvf/). The previous Test PyPI distribution is no longer maintained. +* Thanks to [mgree](https://github.com/mgree) for contributing the `FunObjVar::getSourceLoc` binding ([#46](https://github.com/SVF-tools/SVF-Python/pull/46)). * SVF-Python now supports MTA bindings and more pointer analysis strategies(AndersenWaveDiff, AndersenBase, and Steensgaard bindings). (Thank [JoelYYoung](https://github.com/JoelYYoung) for his help!). ## 1. Introduction @@ -12,17 +14,17 @@ Pysvf can be installed in two ways: -### Method 1: Install via pip (Test PyPI) +### Method 1: Install via pip #### Requirements -- Python 3.8 - 3.11 +- Python 3.8 - 3.12 - OS: Linux X86-64, Linux Arm64, MacOS #### Install Command ```bash -python3 -m pip install -i https://test.pypi.org/simple/ pysvf +pip install pysvf ```` ### Method 2: Build from Source diff --git a/demo/pag.ipynb b/demo/pag.ipynb index 8077696..fddf2a8 100644 --- a/demo/pag.ipynb +++ b/demo/pag.ipynb @@ -36,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2025-03-28T11:45:40.163290Z", @@ -44,21 +44,8 @@ }, "collapsed": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Looking in indexes: https://test.pypi.org/simple/\r\n", - "Requirement already satisfied: pysvf in /Users/z5489735/PycharmProjects/Amei/.venv/lib/python3.9/site-packages (0.0.0)\r\n" - ] - } - ], - "source": [ - "# bash install pip install pysvf from testpypi\n", - "\n", - "!pip install pysvf --index-url https://test.pypi.org/simple/" - ] + "outputs": [], + "source": "# Install pysvf from PyPI\n\n!pip install pysvf" }, { "cell_type": "code", @@ -656,4 +643,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 039ad1206e6f1ee583a10e71fa0be8c1f97bf9e3 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:48:24 +1000 Subject: [PATCH 5/6] Update icfg.ipynb for CallPE API change (MultiOpndStmt) - Replace CallPE.getCallSite()/getLHSVar()/getRHSVar() with getFunEntryICFGNode()/getRes()/getOpVarNum() in code cell - Update CallPE API documentation tables in markdown cells - Clear stale output from affected code cell Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/icfg.ipynb | 246 ++++-------------------------------------------- 1 file changed, 20 insertions(+), 226 deletions(-) diff --git a/demo/icfg.ipynb b/demo/icfg.ipynb index 08da047..191642f 100644 --- a/demo/icfg.ipynb +++ b/demo/icfg.ipynb @@ -693,12 +693,12 @@ "| | `getLHSVarID` | Get the ID of the LHS variable of the load statement |\n", "| | `getRHSVar` | Get the RHS variable of the load statement |\n", "| | `getRHSVarID` | Get the ID of the RHS variable of the load statement |\n", - "| `CallPE` | `getCallSite` | Get the call site |\n", - "| | `getLHSVar` | Get the LHS variable of the call PE |\n", - "| | `getLHSVarID` | Get the ID of the LHS variable of the call PE |\n", - "| | `getRHSVar` | Get the RHS variable of the call PE |\n", - "| | `getRHSVarID` | Get the ID of the RHS variable of the call PE |\n", - "| | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| `CallPE` | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| (MultiOpndStmt) | `getOpCallICFGNode(idx)` | Get the CallICFGNode of the i-th operand |\n", + "| | `getOpCallICFGNodes` | Get all call site ICFGNodes |\n", + "| | `getRes` | Get the result variable (formal parameter) |\n", + "| | `getOpVar(idx)` | Get the i-th operand variable (actual parameter) |\n", + "| | `getOpVarNum` | Get the number of operands |\n", "| `RetPE` | `getCallSite` | Get the call site |\n", "| | `getLHSVar` | Get the LHS variable of the return PE |\n", "| | `getLHSVarID` | Get the ID of the LHS variable of the return PE |\n", @@ -809,12 +809,12 @@ "| | `getLHSVarID` | Get the ID of the LHS variable of the load statement |\n", "| | `getRHSVar` | Get the RHS variable of the load statement |\n", "| | `getRHSVarID` | Get the ID of the RHS variable of the load statement |\n", - "| `CallPE` | `getCallSite` | Get the call site |\n", - "| | `getLHSVar` | Get the LHS variable of the call PE |\n", - "| | `getLHSVarID` | Get the ID of the LHS variable of the call PE |\n", - "| | `getRHSVar` | Get the RHS variable of the call PE |\n", - "| | `getRHSVarID` | Get the ID of the RHS variable of the call PE |\n", - "| | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| `CallPE` | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| (MultiOpndStmt) | `getOpCallICFGNode(idx)` | Get the CallICFGNode of the i-th operand |\n", + "| | `getOpCallICFGNodes` | Get all call site ICFGNodes |\n", + "| | `getRes` | Get the result variable (formal parameter) |\n", + "| | `getOpVar(idx)` | Get the i-th operand variable (actual parameter) |\n", + "| | `getOpVarNum` | Get the number of operands |\n", "| `RetPE` | `getCallSite` | Get the call site |\n", "| | `getLHSVar` | Get the LHS variable of the return PE |\n", "| | `getLHSVarID` | Get the ID of the LHS variable of the return PE |\n", @@ -980,12 +980,12 @@ "| | `getLHSVarID` | Get the ID of the LHS variable of the load statement |\n", "| | `getRHSVar` | Get the RHS variable of the load statement |\n", "| | `getRHSVarID` | Get the ID of the RHS variable of the load statement |\n", - "| `CallPE` | `getCallSite` | Get the call site |\n", - "| | `getLHSVar` | Get the LHS variable of the call PE |\n", - "| | `getLHSVarID` | Get the ID of the LHS variable of the call PE |\n", - "| | `getRHSVar` | Get the RHS variable of the call PE |\n", - "| | `getRHSVarID` | Get the ID of the RHS variable of the call PE |\n", - "| | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| `CallPE` | `getFunEntryICFGNode` | Get the function entry ICFG node |\n", + "| (MultiOpndStmt) | `getOpCallICFGNode(idx)` | Get the CallICFGNode of the i-th operand |\n", + "| | `getOpCallICFGNodes` | Get all call site ICFGNodes |\n", + "| | `getRes` | Get the result variable (formal parameter) |\n", + "| | `getOpVar(idx)` | Get the i-th operand variable (actual parameter) |\n", + "| | `getOpVarNum` | Get the number of operands |\n", "| `RetPE` | `getCallSite` | Get the call site |\n", "| | `getLHSVar` | Get the LHS variable of the return PE |\n", "| | `getLHSVarID` | Get the ID of the LHS variable of the return PE |\n", @@ -1044,213 +1044,7 @@ }, "collapsed": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CopyStmt: lhs_var=DummyValVar ID: 1, rhs_var=ConstNullPtrValVar ID: 0\n", - " ptr null { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 5\n", - " i8 37 { constant data }, rhs_var=ConstIntObjVar ID: 6\n", - " i8 37 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 7\n", - " i8 100 { constant data }, rhs_var=ConstIntObjVar ID: 8\n", - " i8 100 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 9\n", - " i8 10 { constant data }, rhs_var=ConstIntObjVar ID: 10\n", - " i8 10 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 11\n", - " i8 0 { constant data }, rhs_var=ConstIntObjVar ID: 12\n", - " i8 0 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 48\n", - " i32 3 { constant data }, rhs_var=ConstIntObjVar ID: 49\n", - " i32 3 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 45\n", - " i64 1 { constant data }, rhs_var=ConstIntObjVar ID: 46\n", - " i64 1 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 42\n", - " i32 5 { constant data }, rhs_var=ConstIntObjVar ID: 43\n", - " i32 5 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 39\n", - " i64 0 { constant data }, rhs_var=ConstIntObjVar ID: 40\n", - " i64 0 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 55\n", - " i32 1 { constant data }, rhs_var=ConstIntObjVar ID: 56\n", - " i32 1 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 21\n", - " i32 0 { constant data }, rhs_var=ConstIntObjVar ID: 22\n", - " i32 0 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 60\n", - " i1 false { constant data }, rhs_var=ConstIntObjVar ID: 61\n", - " i1 false { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 36\n", - " i64 8 { constant data }, rhs_var=ConstIntObjVar ID: 37\n", - " i64 8 { constant data }\n", - "AddrStmt: lhs_var=ConstIntValVar ID: 62\n", - " i1 true { constant data }, rhs_var=ConstIntObjVar ID: 63\n", - " i1 true { constant data }\n", - "AddrStmt: lhs_var=GlobalValVar ID: 4\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=GlobalObjVar ID: 13\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }\n", - "GepStmt: lhs_var=GepValVar ID: 86 with offset_0\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=GlobalValVar ID: 4\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }\n", - "StoreStmt: lhs_var=GepValVar ID: 86 with offset_0\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=ConstIntValVar ID: 5\n", - " i8 37 { constant data }\n", - "GepStmt: lhs_var=GepValVar ID: 87 with offset_1\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=GlobalValVar ID: 4\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }\n", - "StoreStmt: lhs_var=GepValVar ID: 87 with offset_1\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=ConstIntValVar ID: 7\n", - " i8 100 { constant data }\n", - "GepStmt: lhs_var=GepValVar ID: 88 with offset_2\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=GlobalValVar ID: 4\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }\n", - "StoreStmt: lhs_var=GepValVar ID: 88 with offset_2\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=ConstIntValVar ID: 9\n", - " i8 10 { constant data }\n", - "GepStmt: lhs_var=GepValVar ID: 89 with offset_3\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=GlobalValVar ID: 4\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }\n", - "StoreStmt: lhs_var=GepValVar ID: 89 with offset_3\n", - " @.str = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\", align 1 { Glob }, rhs_var=ConstIntValVar ID: 11\n", - " i8 0 { constant data }\n", - "AddrStmt: lhs_var=FunValVar ID: 14\n", - "add_or_sub, rhs_var=FunObjVar ID: 15 (base object)\n", - "add_or_sub\n", - "AddrStmt: lhs_var=FunValVar ID: 31\n", - "main, rhs_var=FunObjVar ID: 32 (base object)\n", - "main\n", - "AddrStmt: lhs_var=FunValVar ID: 67\n", - "__memcpy_chk, rhs_var=FunObjVar ID: 68 (base object)\n", - "__memcpy_chk\n", - "AddrStmt: lhs_var=FunValVar ID: 64\n", - "llvm.objectsize.i64.p0, rhs_var=FunObjVar ID: 65 (base object)\n", - "llvm.objectsize.i64.p0\n", - "AddrStmt: lhs_var=FunValVar ID: 70\n", - "printf, rhs_var=FunObjVar ID: 71 (base object)\n", - "printf\n", - "PhiStmt: res_var=RetValPN ID: 16 unique return node for function add_or_sub, op_var_num=1\n", - "PhiStmt: res_var=RetValPN ID: 33 unique return node for function main, op_var_num=1\n", - "CmpStmt: predicate=33, res=ValVar ID: 20\n", - " %tobool = icmp ne i32 %flag, 0 , op_var0=ArgValVar ID: 19\n", - " i32 %flag { 2nd arg add_or_sub }, op_var1=ConstIntValVar ID: 21\n", - " i32 0 { constant data }\n", - "BranchStmt: successors=[(, 1), (, 0)], condition=ValVar ID: 20\n", - " %tobool = icmp ne i32 %flag, 0 \n", - "BinaryOPStmt: opcode=13, res=ValVar ID: 24\n", - " %add = add nsw i32 %a, %b , op_var_0=ArgValVar ID: 17\n", - " i32 %a { 0th arg add_or_sub }, op_var_1=ArgValVar ID: 18\n", - " i32 %b { 1st arg add_or_sub }\n", - "BinaryOPStmt: opcode=15, res=ValVar ID: 27\n", - " %sub = sub nsw i32 %a, %b , op_var_0=ArgValVar ID: 17\n", - " i32 %a { 0th arg add_or_sub }, op_var_1=ArgValVar ID: 18\n", - " i32 %b { 1st arg add_or_sub }\n", - "BranchStmt: successors=[(, 1)], condition=ConstNullPtrValVar ID: 0\n", - " ptr null { constant data }\n", - "BranchStmt: successors=[(, 1)], condition=ConstNullPtrValVar ID: 0\n", - " ptr null { constant data }\n", - "PhiStmt: res_var=ValVar ID: 29\n", - " %result.0 = phi i32 [ %add, %if.then ], [ %sub, %if.else ] , op_var_num=2\n", - "AddrStmt: lhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 , rhs_var=StackObjVar ID: 35\n", - " %0 = alloca i8, i64 8, align 8 \n", - "GepStmt: lhs_var=ValVar ID: 38\n", - " %arrayidx = getelementptr inbounds i32, ptr %0, i64 0 , rhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 \n", - "StoreStmt: lhs_var=ValVar ID: 38\n", - " %arrayidx = getelementptr inbounds i32, ptr %0, i64 0 , rhs_var=ConstIntValVar ID: 42\n", - " i32 5 { constant data }\n", - "GepStmt: lhs_var=ValVar ID: 44\n", - " %arrayidx1 = getelementptr inbounds i32, ptr %0, i64 1 , rhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 \n", - "StoreStmt: lhs_var=ValVar ID: 44\n", - " %arrayidx1 = getelementptr inbounds i32, ptr %0, i64 1 , rhs_var=ConstIntValVar ID: 48\n", - " i32 3 { constant data }\n", - "GepStmt: lhs_var=ValVar ID: 50\n", - " %arrayidx2 = getelementptr inbounds i32, ptr %0, i64 0 , rhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 \n", - "LoadStmt: lhs_var=ValVar ID: 51\n", - " %1 = load i32, ptr %arrayidx2, align 4 , rhs_var=ValVar ID: 50\n", - " %arrayidx2 = getelementptr inbounds i32, ptr %0, i64 0 \n", - "GepStmt: lhs_var=ValVar ID: 52\n", - " %arrayidx3 = getelementptr inbounds i32, ptr %0, i64 1 , rhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 \n", - "LoadStmt: lhs_var=ValVar ID: 53\n", - " %2 = load i32, ptr %arrayidx3, align 4 , rhs_var=ValVar ID: 52\n", - " %arrayidx3 = getelementptr inbounds i32, ptr %0, i64 1 \n", - "CallPE: callsite=CallICFGNode22 {fun: main}\n", - "CallPE: [Var17 <-- Var51]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var18 <-- Var53]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var19 <-- Var55]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - ", lhs_var=ArgValVar ID: 17\n", - " i32 %a { 0th arg add_or_sub }, rhs_var=ValVar ID: 51\n", - " %1 = load i32, ptr %arrayidx2, align 4 \n", - "CallPE: callsite=CallICFGNode22 {fun: main}\n", - "CallPE: [Var17 <-- Var51]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var18 <-- Var53]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var19 <-- Var55]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - ", lhs_var=ArgValVar ID: 18\n", - " i32 %b { 1st arg add_or_sub }, rhs_var=ValVar ID: 53\n", - " %2 = load i32, ptr %arrayidx3, align 4 \n", - "CallPE: callsite=CallICFGNode22 {fun: main}\n", - "CallPE: [Var17 <-- Var51]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var18 <-- Var53]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var19 <-- Var55]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - ", lhs_var=ArgValVar ID: 19\n", - " i32 %flag { 2nd arg add_or_sub }, rhs_var=ConstIntValVar ID: 55\n", - " i32 1 { constant data }\n", - "RetPE: callsite=CallICFGNode22 {fun: main}\n", - "CallPE: [Var17 <-- Var51]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var18 <-- Var53]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - "CallPE: [Var19 <-- Var55]\t\n", - "ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) \n", - ", lhs_var=ValVar ID: 54\n", - " %call = call i32 @add_or_sub(i32 noundef %1, i32 noundef %2, i32 noundef 1) , rhs_var=RetValPN ID: 16 unique return node for function add_or_sub\n", - "AddrStmt: lhs_var=ValVar ID: 57\n", - " %3 = alloca i8, i64 8, align 8 , rhs_var=StackObjVar ID: 58\n", - " %3 = alloca i8, i64 8, align 8 \n", - "GepStmt: lhs_var=GepValVar ID: 90 with offset_0\n", - " %3 = alloca i8, i64 8, align 8 , rhs_var=ValVar ID: 57\n", - " %3 = alloca i8, i64 8, align 8 \n", - "GepStmt: lhs_var=GepValVar ID: 91 with offset_0\n", - " %0 = alloca i8, i64 8, align 8 , rhs_var=ValVar ID: 34\n", - " %0 = alloca i8, i64 8, align 8 \n", - "LoadStmt: lhs_var=DummyValVar ID: 92, rhs_var=GepValVar ID: 91 with offset_0\n", - " %0 = alloca i8, i64 8, align 8 \n", - "StoreStmt: lhs_var=GepValVar ID: 90 with offset_0\n", - " %3 = alloca i8, i64 8, align 8 , rhs_var=DummyValVar ID: 92\n", - "CopyStmt: lhs_var=ValVar ID: 66\n", - " %call4 = call ptr @__memcpy_chk(ptr noundef %3, ptr noundef %0, i64 noundef 8, i64 noundef %4) #4 , rhs_var=ValVar ID: 57\n", - " %3 = alloca i8, i64 8, align 8 \n" - ] - } - ], + "outputs": [], "source": [ "for node in cfg.getNodes():\n", " for stmt in node.getSVFStmts():\n", @@ -1274,7 +1068,7 @@ " print(\"StoreStmt: lhs_var={}, rhs_var={}\".format(store_stmt.getLHSVar(), store_stmt.getRHSVar()))\n", " elif isinstance(stmt, pysvf.CallPE):\n", " call_pe = stmt.asCallPE()\n", - " print(\"CallPE: callsite={}, lhs_var={}, rhs_var={}\".format(call_pe.getCallSite(), call_pe.getLHSVar(), call_pe.getRHSVar()))\n", + " print(\"CallPE: entry={}, res={}, num_opnds={}\".format(call_pe.getFunEntryICFGNode(), call_pe.getRes(), call_pe.getOpVarNum()))\n", " elif isinstance(stmt, pysvf.RetPE):\n", " ret_pe = stmt.asRetPE()\n", " print(\"RetPE: callsite={}, lhs_var={}, rhs_var={}\".format(ret_pe.getCallSite(), ret_pe.getLHSVar(), ret_pe.getRHSVar()))\n", From e35af5d95685fbe84f4a86bf6338b02e66193214 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 11 Apr 2026 23:56:51 +1000 Subject: [PATCH 6/6] Fix CallPE registration order: move after MultiOpndStmt pybind11 requires base classes to be registered before derived classes. CallPE (now inheriting MultiOpndStmt) was registered before MultiOpndStmt, causing "referenced unknown base type" ImportError at runtime. Co-Authored-By: Claude Opus 4.6 (1M context) --- pybind/SVFIR.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pybind/SVFIR.cpp b/pybind/SVFIR.cpp index 54f0e27..84bf9af 100644 --- a/pybind/SVFIR.cpp +++ b/pybind/SVFIR.cpp @@ -120,14 +120,6 @@ void bind_svf_stmt(py::module& m) { py::class_(m, "LoadStmt"); - py::class_(m, "CallPE") - .def("getOpCallICFGNode", &CallPE::getOpCallICFGNode, py::return_value_policy::reference, - "Get the CallICFGNode of the i-th operand") - .def("getOpCallICFGNodes", &CallPE::getOpCallICFGNodes, py::return_value_policy::reference, - "Get all call site ICFGNodes") - .def("getFunEntryICFGNode", &CallPE::getFunEntryICFGNode, py::return_value_policy::reference, - "Get the function entry ICFG node"); - py::class_(m, "RetPE") .def("getCallSite", &RetPE::getCallSite, "Get the call site") .def("getFunExitICFGNode", &RetPE::getFunExitICFGNode, py::return_value_policy::reference, @@ -158,6 +150,14 @@ void bind_svf_stmt(py::module& m) { return py::make_iterator(stmt.opVarBegin(), stmt.opVerEnd()); }, py::keep_alive<0, 1>()); // Keep the iterator alive while iterating + py::class_(m, "CallPE") + .def("getOpCallICFGNode", &CallPE::getOpCallICFGNode, py::return_value_policy::reference, + "Get the CallICFGNode of the i-th operand") + .def("getOpCallICFGNodes", &CallPE::getOpCallICFGNodes, py::return_value_policy::reference, + "Get all call site ICFGNodes") + .def("getFunEntryICFGNode", &CallPE::getFunEntryICFGNode, py::return_value_policy::reference, + "Get the function entry ICFG node"); + py::class_(m, "PhiStmt") // TODO: may implement get_op_var and get_op_var_id .def("getOpICFGNode", [](PhiStmt& stmt, int idx) { return stmt.getOpICFGNode(idx); },