Skip to content

Fix out-of-bounds bitset access in Graph.add_vertex#41489

Merged
vbraun merged 9 commits intosagemath:developfrom
harrshita123:fix-add-vertex-bitset
Feb 11, 2026
Merged

Fix out-of-bounds bitset access in Graph.add_vertex#41489
vbraun merged 9 commits intosagemath:developfrom
harrshita123:fix-add-vertex-bitset

Conversation

@harrshita123
Copy link
Copy Markdown
Contributor

This PR fixes undefined behavior in Graph.add_vertex() caused by calling bitset_in() with an index outside the bitset bounds.

On some platforms (e.g. macOS) this happens to work, but on others—especially Linux—it can lead to incorrect vertex numbering, as seen around the 320/321 boundary in the linked issue.

The fix is deliberately minimal: a bounds check is added before calling bitset_in(), preventing out-of-bounds access without changing the existing logic. A small regression test is included to cover the boundary case and ensure consistent behavior across platforms.
Fixes #41472

📝 Checklist

  • The title is concise and informative.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

⌛ Dependencies

None

@dimpase
Copy link
Copy Markdown
Member

dimpase commented Jan 21, 2026

you should add me as a co-author on this, after all, besides the doctest, you basically took the code in my comment on #41472

@dimpase
Copy link
Copy Markdown
Member

dimpase commented Jan 21, 2026

see here
how to do this

@dcoudert
Copy link
Copy Markdown
Collaborator

I agree that @dimpase should at least be co-author.

I'm not sure about the test. Following discussions in #41472, I think that the bound test should only be related to bitset_in, so

            while (name in self.vertex_ints or
                   (name not in self.vertex_labels and
                    (name < self.cg().active_vertices.size and
                     bitset_in(self.cg().active_vertices, <mp_bitcnt_t> name)))):

@harrshita123
Copy link
Copy Markdown
Contributor Author

you should add me as a co-author on this, after all, besides the doctest, you basically took the code in my comment on #41472

Thanks for pointing this out — I have added you as a co-author.

@harrshita123 harrshita123 force-pushed the fix-add-vertex-bitset branch from 41bbe35 to b68d9f2 Compare January 21, 2026 19:22
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 21, 2026

Documentation preview for this PR (built with commit ebfeec7; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@dimpase
Copy link
Copy Markdown
Member

dimpase commented Jan 21, 2026

@harrshita123 - please use this version of the fix.
This avoids potential shenanigans related to the bitset not set up for all the vertices.

I'm not sure about the test. Following discussions in #41472, I think that the bound test should only be related to bitset_in, so

            while (name in self.vertex_ints or
                   (name not in self.vertex_labels and
                    (name < self.cg().active_vertices.size and
                     bitset_in(self.cg().active_vertices, <mp_bitcnt_t> name)))):

@dimpase
Copy link
Copy Markdown
Member

dimpase commented Jan 21, 2026

you might also want to rebase over the latest develop branch

@harrshita123
Copy link
Copy Markdown
Contributor Author

I attempted to rebase on the latest develop, but it results in multiple conflicts in unrelated modules (number fields, modular forms, elliptic curves). Since these are outside the scope of this PR, I have aborted the rebase and left it unchanged.

@harrshita123
Copy link
Copy Markdown
Contributor Author

harrshita123 commented Jan 22, 2026

@vbraun
This PR is limited to fixing out-of-bounds access in Graph.add_vertex and adding a small, targeted regression test in src/sage/graphs.
The remaining CI failures seem unrelated (long tests / Conda builds). Please let me know if anything needs further updates — I will be glad to take care of it. When you have time, could you please take a look?
Thanks!

Comment thread src/sage/graphs/base/c_graph.pyx Outdated
while (name in self.vertex_ints or
(name not in self.vertex_labels and
bitset_in(self.cg().active_vertices, <mp_bitcnt_t> name))):
while (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please follow the standard coding style of Sagemath.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments.
I will reformat the while condition in Graph.add_vertex to follow the standard SageMath coding style.

@@ -0,0 +1,4 @@
def test_add_vertex_boundary_320():
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test could be added directly in a TESTS: block in method add_vertex. This is the standard way to do it in Sagemath.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is meant to be used with pytest, which is also OK, I think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the test: I added it as a pytest regression test, but I am happy to move it into a TESTS block in add_vertex if that is preferred.

@dcoudert
Copy link
Copy Markdown
Collaborator

on the way, could you check all calls to bitset_in in this file ? we may have similar issues.

@harrshita123
Copy link
Copy Markdown
Contributor Author

on the way, could you check all calls to bitset_in in this file ? we may have similar issues.

Thanks for pointing this out.
I reviewed all bitset_in calls in src/sage/graphs/base/c_graph.pyx and added missing bounds checks where needed to avoid out-of-bounds access. The remaining calls were already guarded by size or capacity checks.

Comment thread src/sage/graphs/base/c_graph.pyx Outdated
"""
cdef int u_int = self.get_vertex(u)
if u_int != -1 and bitset_in(self.cg().active_vertices, u_int):
if (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please comply with our coding style. Something like that to avoid too long lines.

        if (u_int != -1 and u_int < self.cg().active_vertices.size
            and bitset_in(self.cg().active_vertices, u_int)):

@harrshita123
Copy link
Copy Markdown
Contributor Author

@dcoudert
let me know if anything needs further updates — I will be glad to take care of it. When you have time, could you please take a look?
Thanks!

@dcoudert
Copy link
Copy Markdown
Collaborator

See remark above about alignment and coding style.

@harrshita123
Copy link
Copy Markdown
Contributor Author

harrshita123 commented Jan 25, 2026

See remark above about alignment and coding style.
@dcoudert
Thanks for pointing this out.
I have updated the condition to include an explicit bounds check and reformatted it to follow SageMath’s standard alignment and line-length conventions, as suggested.

@harrshita123
Copy link
Copy Markdown
Contributor Author

@vbraun
This PR fixes out-of-bounds bitset_in access in Graph.add_vertex and adds a small regression test.
The remaining CI failures appear unrelated (long tests / docs / Conda builds across platforms). Please let me know if you think any failure might be connected to this change — I will be happy to follow up.
Thanks for taking a look when you have time!

Copy link
Copy Markdown
Collaborator

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what you have to do to respect the coding style.

diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx
index 3ba7fd22894..ef3be3da432 100644
--- a/src/sage/graphs/base/c_graph.pyx
+++ b/src/sage/graphs/base/c_graph.pyx
@@ -1617,14 +1617,10 @@ cdef class CGraphBackend(GenericGraphBackend):
         if ``u`` is not a vertex of the graph.
         """
         cdef int u_int = self.get_vertex(u)
-        if (
-            u_int != -1
-            and u_int < self.cg().active_vertices.size
-            and bitset_in(self.cg().active_vertices, u_int)
-        ):
+        if (u_int != -1 and u_int < self.cg().active_vertices.size
+                and bitset_in(self.cg().active_vertices, u_int)):
             return u_int
-        else:
-            return -1
+        return -1
 
     cdef vertex_label(self, int u_int):
         """
@@ -1635,14 +1631,10 @@ cdef class CGraphBackend(GenericGraphBackend):
 
         if u_int in vertex_labels:
             return vertex_labels[u_int]
-        elif (
-            u_int != -1 
-            and u_int < self.cg().active_vertices.size
-            and bitset_in(self.cg().active_vertices, u_int)
-        ):
+        if (u_int != -1 and u_int < self.cg().active_vertices.size
+                and bitset_in(self.cg().active_vertices, u_int)):
             return u_int
-        else:
-            return None
+        return None
 
     cdef inline int check_labelled_vertex(self, u, bint reverse) except ? -1:
         """
@@ -1654,12 +1646,8 @@ cdef class CGraphBackend(GenericGraphBackend):
 
         cdef int u_int = self.get_vertex(u)
         if u_int != -1:
-            if (
-                u_int < 0 
-                or u_int >= G.active_vertices.size 
-                or not bitset_in(G.active_vertices, u_int)
-            ):
-
+            if (u_int < 0 or u_int >= G.active_vertices.size
+                    or not bitset_in(G.active_vertices, u_int)):
                 bitset_add(G.active_vertices, u_int)
                 G.num_verts += 1
             return u_int
@@ -1746,10 +1734,10 @@ cdef class CGraphBackend(GenericGraphBackend):
         retval = None
         if name is None:
             name = 0
-            while name in self.vertex_ints or (
-                    name not in self.vertex_labels and
-                    name < self.cg().active_vertices.size and bitset_in(self.cg().active_vertices,
-                    <mp_bitcnt_t> name)):
+            while (name in self.vertex_ints or
+                   (name not in self.vertex_labels and
+                    name < self.cg().active_vertices.size and
+                    bitset_in(self.cg().active_vertices, <mp_bitcnt_t> name)):
                 name += 1
             retval = name
 
@@ -2221,10 +2209,8 @@ cdef class CGraphBackend(GenericGraphBackend):
 
         cdef int u_int
         cdef int v_int = self.get_vertex(v)
-        if (
-            v_int == -1 or v_int >= self.cg().active_vertices.size
-            or not bitset_in(self.cg().active_vertices, v_int)
-        ):
+        if (v_int == -1 or v_int >= self.cg().active_vertices.size
+                or not bitset_in(self.cg().active_vertices, v_int)):
             raise LookupError("vertex ({0}) is not a vertex of the graph".format(v))
 
         cdef set seen = set()
@@ -2273,11 +2259,8 @@ cdef class CGraphBackend(GenericGraphBackend):
 
         cdef int u_int
         cdef int v_int = self.get_vertex(v)
-        if (
-            v_int == -1 or
-            v_int >= self.cg().active_vertices.size 
-            or not bitset_in(self.cg().active_vertices,v_int)
-        ):
+        if (v_int == -1 or v_int >= self.cg().active_vertices.size
+                or not bitset_in(self.cg().active_vertices,v_int)):
             raise LookupError("vertex ({0}) is not a vertex of the graph".format(v))
 
         for u_int in self.cg().in_neighbors(v_int):
@@ -2317,11 +2300,8 @@ cdef class CGraphBackend(GenericGraphBackend):
         """
         cdef int u_int
         cdef int v_int = self.get_vertex(v)
-        if (
-            v_int == -1 or
-            v_int >= self.cg().active_vertices.size 
-            or not bitset_in(self.cg().active_vertices,v_int)
-        ):
+        if (v_int == -1 or v_int >= self.cg().active_vertices.size
+                or not bitset_in(self.cg().active_vertices,v_int)):
             raise LookupError("vertex ({0}) is not a vertex of the graph".format(v))
 
         for u_int in self.cg().out_neighbors(v_int):
@@ -2904,13 +2884,8 @@ cdef class CGraphBackend(GenericGraphBackend):
                                 yield (v, u)
                     v = v_copy
 
-                if (
-                    v_int < 0 or v_int >= self.cg().active_vertices.size 
-                    or unlikely(
-                        not bitset_in(self.cg()active_vertices, v_int)
-                    )
-                ):
-
+                if (v_int < 0 or v_int >= self.cg().active_vertices.size
+                        or unlikely(not bitset_in(self.cg().active_vertices, v_int))):
                     raise IndexError("the vertices were modified while iterating the edges")

Comment thread src/sage/graphs/base/c_graph.pyx Outdated
if u_int in vertex_labels:
return vertex_labels[u_int]
elif bitset_in(self.cg().active_vertices, u_int):
elif (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use

        elif (u_int != -1  and u_int < self.cg().active_vertices.size
              and bitset_in(self.cg().active_vertices, u_int)):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same style in other places. Thanks.

@dcoudert
Copy link
Copy Markdown
Collaborator

Ensure also that you are not introducing errors. This is currently the case with a typo self.cg()active_vertices.

@harrshita123
Copy link
Copy Markdown
Contributor Author

Ensure also that you are not introducing errors. This is currently the case with a typo self.cg()active_vertices.

You are absolutely right — I mistakenly changed elif to if, which alters the control flow, and I also introduced a typo (self.cg()active_vertices) in one place.I will fix both issues and update the patch to follow the suggested style . Thanks for catching that.

@harrshita123
Copy link
Copy Markdown
Contributor Author

Note: commit 8ffe5d3 was added accidentally from another branch and has been reverted. The final PR diff only affects Graph.add_vertex as intended.

@dcoudert
Copy link
Copy Markdown
Collaborator

dcoudert commented Feb 3, 2026

I don't see the revert. The extra commit is still there.

@harrshita123 harrshita123 force-pushed the fix-add-vertex-bitset branch from 8ffe5d3 to 1567935 Compare February 3, 2026 12:33
@harrshita123
Copy link
Copy Markdown
Contributor Author

I don't see the revert. The extra commit is still there.

Thanks for checking — you were right. I have now dropped the extra commit and force-pushed a clean history.

@dcoudert
Copy link
Copy Markdown
Collaborator

dcoudert commented Feb 3, 2026

except that meson.build is back...

@harrshita123
Copy link
Copy Markdown
Contributor Author

except that meson.build is back...

Thanks for pointing that out.
I have rebased and force-pushed again — meson.build is no longer modified and the PR now only touches c_graph.pyx. Please let me know if you still see it on your side.

@dcoudert
Copy link
Copy Markdown
Collaborator

dcoudert commented Feb 3, 2026

still 2 files here https://github.com/sagemath/sage/pull/41489/files
I don't see the last push...

@harrshita123
Copy link
Copy Markdown
Contributor Author

still 2 files here https://github.com/sagemath/sage/pull/41489/files I don't see the last push...

@dcoudert
Thanks for your patience.
I have confirmed that the branch is now clean and the PR only modifies c_graph.pyx.
Please let me know if you spot anything else that needs adjustment.
Thanks!

@harrshita123
Copy link
Copy Markdown
Contributor Author

I regenerated the branch to ensure the PR only contains the intended change.
The workflow I followed was:

synced my branch with the remote fix-add-vertex-bitset

restored src/sage/graphs/tests/meson.build from upstream/develop to remove the accidental test-registration change

committed this cleanup separately

force-pushed the branch

As a result, the current PR diff now only modifies src/sage/graphs/base/c_graph.pyx, which contains the bounds checks and
formatting fixes discussed above.

Please let me know if you still see any unexpected files on your side.

Copy link
Copy Markdown
Collaborator

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@harrshita123
Copy link
Copy Markdown
Contributor Author

@vbraun
This PR fixes out-of-bounds bitset_in access in Graph.add_vertex and adds a small regression test.
Please let me know if you think any failure might be connected to this change .I will be happy to follow up.
Thanks for taking a look when you have time!

@dcoudert
Copy link
Copy Markdown
Collaborator

dcoudert commented Feb 4, 2026

There is no need for asking Volker Braun, our release manager, here. He will use his scripts to check whether this PR can be properly merged without introducing new issues and then decide if it can be merged or if it needs more work.

@harrshita123
Copy link
Copy Markdown
Contributor Author

There is no need for asking Volker Braun, our release manager, here. He will use his scripts to check whether this PR can be properly merged without introducing new issues and then decide if it can be merged or if it needs more work.

Thanks for the clarification I understood. I will wait for the automated checks and follow up if anything needs adjustment.

vbraun pushed a commit to vbraun/sage that referenced this pull request Feb 5, 2026
sagemathgh-41489: Fix out-of-bounds bitset access in Graph.add_vertex
    
This PR fixes undefined behavior in Graph.add_vertex() caused by calling
bitset_in() with an index outside the bitset bounds.

On some platforms (e.g. macOS) this happens to work, but on
others—especially Linux—it can lead to incorrect vertex numbering, as
seen around the 320/321 boundary in the linked issue.

The fix is deliberately minimal: a bounds check is added before calling
bitset_in(), preventing out-of-bounds access without changing the
existing logic. A small regression test is included to cover the
boundary case and ensure consistent behavior across platforms.
Fixes sagemath#41472

### 📝 Checklist
- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies
None
    
URL: sagemath#41489
Reported by: Harshita Yadav
Reviewer(s): David Coudert, Dima Pasechnik, Harshita Yadav
vbraun pushed a commit to vbraun/sage that referenced this pull request Feb 7, 2026
sagemathgh-41489: Fix out-of-bounds bitset access in Graph.add_vertex
    
This PR fixes undefined behavior in Graph.add_vertex() caused by calling
bitset_in() with an index outside the bitset bounds.

On some platforms (e.g. macOS) this happens to work, but on
others—especially Linux—it can lead to incorrect vertex numbering, as
seen around the 320/321 boundary in the linked issue.

The fix is deliberately minimal: a bounds check is added before calling
bitset_in(), preventing out-of-bounds access without changing the
existing logic. A small regression test is included to cover the
boundary case and ensure consistent behavior across platforms.
Fixes sagemath#41472

### 📝 Checklist
- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies
None
    
URL: sagemath#41489
Reported by: Harshita Yadav
Reviewer(s): David Coudert, Dima Pasechnik, Harshita Yadav
@vbraun vbraun merged commit cf5c82b into sagemath:develop Feb 11, 2026
16 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Graph.add_vertex() uses 321 before 320

4 participants