Hello,
I ran a code review tool I develop on igraph and got interesting results from it. Below’s an overview, and the full report is available at python-igraph C Extension Analysis Report · GitHub. I hope it helps.
Please let me know if you’d like me to open an umbrella issue, individual issues, PRs or if there’s any other kind of way to help.
python-igraph Analysis
48 FIX, 30 CONSIDER, 8 POLICY from 13 agents + clang-tidy/cppcheck.
Key Statistics
Category Count Most Severe Crash bugs (use-after-free, segfault, NULL deref) 13 _enter_safelocalereturns dangling > pointer; 3 functions use freedweights__new__segfaults6 types Vertex (22 methods crash), EdgeSeq ( len()crashes)Reference leaks 12 Per-iteration leak in adjacency matrix (1 int per edge per call) igraph resource leaks 8 BFS wrong-variable + triple vector leak Re-init safety 5 types All leak gref/destructor/selectors PyErr_Clear 6 findings Preemptive clear in hot Py_mp_subscriptpathWhat’s Good
- Exemplary stable ABI implementation — zero production-code violations at Python 3.9
- All 8 types use
PyType_FromSpecwith correct heap-type dealloc- Graph type has explicit
__new__safety — developer comment shows awareness- RNG callbacks correctly use
PyErr_WriteUnraisable— best practice for error-constrained > callbacksDominant Bug Patterns
Error-path resource cleanup — The most common class. igraph vectors/matrices allocated before an igraph call are not freed when the call fails. The
graphobject.cmonster (19K lines) has hundreds of such allocation/call/cleanup sequences.Code cloning — VertexSeq/EdgeSeq duplication already caused a proven divergent refcount bug (EdgeSeq has
Py_DECREF(result)at line 725, VertexSeq doesn’t at line 703).
__new__-without-__init__— 6 of 8 types crash becausegref(graph back-pointer) is NULL after zero-fill allocation. TheCHECK_GRAPH(self)macro approach would fix most with minimal code changes.python-igraph Reproduction
13 findings reproduced, 35 code-confirmed.
Reproduction Highlights
Category Count Most Dramatic Segfaults from __new__7 Vertex.graph,Edge.graph,EdgeSeq len(),VertexSeq.graph,EdgeSeq.graph,Vertex.degree(),Edge.is_loop()Segfaults via OOM injection 3 feedback_arc_set(weights=...)atset_nomemory(1,0),feedback_vertex_setsame,contract_verticesatset_nomemory(3,0)MemoryError swallowed 2 g.vs[BadIndex()]andg.get_shortest_paths(BadVid())— both replace MemoryError with generic TypeErrorReference leaks 2 Vertex re-init (1.7KB/200), Graph re-init (6.8KB/200) SystemError 1 len(VertexSeq.__new__(VertexSeq))— returns NULL without exception