Fix out-of-bounds bitset access in Graph.add_vertex#41489
Fix out-of-bounds bitset access in Graph.add_vertex#41489vbraun merged 9 commits intosagemath:developfrom
Conversation
|
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 |
|
see here |
|
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 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)))): |
Thanks for pointing this out — I have added you as a co-author. |
41bbe35 to
b68d9f2
Compare
|
Documentation preview for this PR (built with commit ebfeec7; changes) is ready! 🎉 |
|
@harrshita123 - please use this version of the fix.
|
|
you might also want to rebase over the latest |
|
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. |
|
@vbraun |
| 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 ( |
There was a problem hiding this comment.
please follow the standard coding style of Sagemath.
There was a problem hiding this comment.
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(): | |||
There was a problem hiding this comment.
this test could be added directly in a TESTS: block in method add_vertex. This is the standard way to do it in Sagemath.
There was a problem hiding this comment.
I think this is meant to be used with pytest, which is also OK, I think
There was a problem hiding this comment.
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.
|
on the way, could you check all calls to |
Thanks for pointing this out. |
| """ | ||
| cdef int u_int = self.get_vertex(u) | ||
| if u_int != -1 and bitset_in(self.cg().active_vertices, u_int): | ||
| if ( |
There was a problem hiding this comment.
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)):|
@dcoudert |
|
See remark above about alignment and coding style. |
|
|
@vbraun |
There was a problem hiding this comment.
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")| if u_int in vertex_labels: | ||
| return vertex_labels[u_int] | ||
| elif bitset_in(self.cg().active_vertices, u_int): | ||
| elif ( |
There was a problem hiding this comment.
Use
elif (u_int != -1 and u_int < self.cg().active_vertices.size
and bitset_in(self.cg().active_vertices, u_int)):There was a problem hiding this comment.
same style in other places. Thanks.
|
Ensure also that you are not introducing errors. This is currently the case with a typo |
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. |
|
|
I don't see the revert. The extra commit is still there. |
Co-authored-by: Dima Pasechnik <dimpase@users.noreply.github.com>
8ffe5d3 to
1567935
Compare
Thanks for checking — you were right. I have now dropped the extra commit and force-pushed a clean history. |
|
except that |
Thanks for pointing that out. |
|
still 2 files here |
@dcoudert |
|
I regenerated the branch to ensure the PR only contains the intended change. 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 Please let me know if you still see any unexpected files on your side. |
|
@vbraun |
|
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. |
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
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
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
⌛ Dependencies
None