Skip to content

filtered_graph: num_vertices is an index bound, not a count #690

Open

Description

@Becheler

num_vertices(g) is used for two different things in BGL:

  1. as a vertex count (emptiness checks, loop bounds, sampling)
    if (num_vertices(g) == 0)
    return 0;
  2. and as an upper bound on vertex indices to size property arrays.
    auto index_map = get(vertex_index, g);
    auto color_map = ::boost::make_two_bit_color_map(num_vertices(g), index_map);
    depth_first_search(g, vis, color_map);

Note

This works today because filtering doesn't renumber vertices: a kept vertex keeps its index from the base graph, so indices still go up to the base graph's size. filtered_graph forwards num_vertices and num_edges to the base graph, so any array sized with num_vertices(fg) is large enough for every index still in use. The price is that num_vertices no longer means "how many vertices vertices(fg) yields".

// base graph has vertices 0 1 2 3 4, the filter keeps 0 2 4
std::vector<color> color_map(num_vertices(fg));

// num_vertices(fg) is 5, so color_map[4] is valid
// if it returned 3, color_map[4] would be out of bounds
color_map[get(vertex_index, fg, 4)] = ...;

Bug: Since num_vertices(fg) doesn't match what vertices(fg) yields, every algorithm that uses it as a count can go wrong once vertices are filtered out:

Users hit this too: Rosetta, Hyperscan and IfcOpenShell all carry comments and workarounds for it, and BGL has its own count_vertices in isomorphism.hpp.

Proposal

  1. Add vertex_index_bound(g) / edge_index_bound(g) (names TBD), defaulting to num_vertices(g) / num_edges(g). Nothing changes for existing graphs.
  2. Move algorithms to the bound when sizing property storage, and keep num_vertices for counting.
  3. Then make filtered_graph's num_vertices / num_edges return the real count (O(V) or cached, O(1) when the predicate is keep_all).

Step 3 silently breaks user code that does std::vector<T> v(num_vertices(fg)) on a vertex-filtered graph and indexes it by vertex index. It compiles and then writes out of bounds. A search of public code found only a handful of such cases (bark, two OpenCV books, a BGL test), but it should still go with a release that already breaks things, plus a loud release note.

For #231 itself, fixing the call sites is enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions