Skip to content

Conversation

@pvcresin
Copy link
Contributor

@pvcresin pvcresin commented Nov 22, 2025

This PR fixes a crash that occurs when Hash#merge! is called multiple times on the same object.

The issue happens because TypeProf attempts to add an edge to the type graph that already exists, causing a TypeProf::Core::Set#<< exception.

Reproduction

def option
  { a: 1 }
end

def foo1
  option.merge!(bar)
end

def foo2
  option.merge!(bar)
end

def bar = {}

Fix

I added guard clauses to Vertex#add_edge and remove_edge to safely ignore duplicate additions or missing deletions.

def add_edge(genv, nvtx)
  return if @next_vtxs.include?(nvtx) # Added guard
  @next_vtxs << nvtx
  # ...
end

Added regression test: scenario/regressions/hash-merge-bang.rb

@mame
Copy link
Member

mame commented Dec 3, 2025

Thank you!

In the current design, we avoid duplicate edges between the same vertices. This is because if we have two merge! calls and the second one skips adding an edge (because it's a duplicate of the first), removing the first merge! call later (e.g., during editing) might result in losing the edge between vertex A and B entirely. Since the second merge! is still present, the edge must not be simply deleted.

There are two possible solutions:

  • Insert a redundant intermediate vertex C every time an edge is added (creating two paths: A -> C1 -> B and A -> C2 -> B). This ensures that if one merge! call is deleted, the other path remains.
  • Use a multigraph instead of a standard graph and track edge multiplicity.

TypeProf currently adopts the first approach, so I've added a commit to implement this.

@mame mame merged commit dc5cd3c into ruby:master Dec 3, 2025
6 checks passed
@pvcresin pvcresin deleted the fix-hash-merge-bug branch December 4, 2025 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants