Bug #22179
openCoverage: redefined methods silently disappear from method Coverage.result after GC
Description
When method coverage is enabled, a method that has been redefined vanishes from Coverage.result once GC runs. It is not reported as uncovered (count 0), it is missing altogether, so the set of methods reported for a file changes nondeterministically depending on GC timing.
Reproduction¶
target.rb:
run.rb:
require "coverage"
Coverage.start(methods: true)
require_relative "./target"
GC.start if ENV["FORCE_GC"]
pp Coverage.result.find { |f, _| f.end_with?("target.rb") }.last[:methods]
Without GC, both definitions are reported:
With a GC before Coverage.result, the first definition is gone:
The explicit GC.start is only for determinism. In real programs any GC that happens to run between the redefinition and Coverage.result has the same effect, which is how this was noticed in the wild.
I’ve looked into why this is happening and I believe it's because method coverage is not stored per file the way line and branch coverage are, it's reconstructed at result time by walking the heap for method entries (method_coverage_i via rb_objspace_each_objects in ext/coverage/coverage.c). A shadowed definition that was never called has no entry in me2counter and exists only as a garbage method entry after redefinition, so once GC collects it, the heap walk cannot see it and the definition is silently forgotten.
This issue was discovered when it was reported as a bug in SimpleCov, of which I am a maintainer.
Reproduced on 3.2.11, 3.3.11, 3.4.10, and 4.0.5.
No data to display