bitwalker / libgraph

A graph data structure library for Elixir projects
MIT License
524 stars 75 forks source link

Infinite loop when nil passed in as graph #73

Closed aymanosman closed 5 months ago

aymanosman commented 9 months ago

The following code results in an infinite loop:

Graph.add_vertex(nil, "A")
vegabook commented 5 months ago

As of libgraph 1.16 this isn't fixed; Also btw it's not only an infinite loop but a rapid memory leak. https://asciinema.org/a/HKCGZkPMeTxQcxu4gn083Pse7

stevensonmt commented 5 months ago

This appears to be due to this bit of code:

  @spec add_vertex(t, vertex, label) :: t
  def add_vertex(g, v, labels \\ [])
  def add_vertex(
        %__MODULE__{vertices: vs, vertex_labels: vl, vertex_identifier: vertex_identifier} = g,
        v,
        labels
      )
      when is_list(labels) do
    id = vertex_identifier.(v)

    case Map.get(vs, id) do
      nil ->
        %__MODULE__{g | vertices: Map.put(vs, id, v), vertex_labels: Map.put(vl, id, labels)}

      _ ->
        g
    end
  end

  def add_vertex(g, v, label) do
    add_vertex(g, v, [label])
  end

When you call add_vertex(nil, vertex) a default empty list gets supplied as the label. Because nil does not match the function head that requires g to be %__MODULE__{} it tries to match the next def which is add_vertex(g, v, label) with no guards. So the empty list gets wrapped in another list and supplied to another call for add_vertex(nil, v, [label]). This will still only match the same function head and keep wrapping label in lists for eternity. The easiest fix is likely to just make the last function head also match g on a %__MODULE__{} or to make label match on not is_list.

  def add_vertex(%__MODULE__{} = g, v, label) do
    add_vertex(g, v, [label])
  end

  def add_vertex(g, v, label) when not is_list(label) do
    add_vertex(g, v, [label])
  end

PR here