Issue #22299 has been updated by Eregon (Benoit Daloze). I bisected it to https://github.com/ruby/ruby/commit/f4813a34c274a764b99d6e706dd016514b66cf45 / https://github.com/ruby/ruby/pull/18538, which added a skip for `ruby/parameters_signature_test.rb`. cc @byroot ---------------------------------------- Bug #22299: opt_new emits a duplicate :line trace point for Const.new following a branch https://bugs.ruby-lang.org/issues/22299#change-118822 * Author: Eregon (Benoit Daloze) * Status: Open * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ## Summary On Ruby master (`4.1.0dev`), compiling an expression of the form `Constant.new(...)` that is the first statement of a basic block reached after a branch (e.g. immediately after a `return unless`/`next unless` guard) produces **two** `:line` trace points for that one line instead of one. The extra event only appears when the `opt_new` specialized instruction is generated, so it depends on: - a **constant** receiver (`Foo.new`, `A::B.new` — not `foo.new`), - the method being `new` with `opt_new` applicable (no block; `Foo.new { }` is fine), - the call sitting at the head of a basic block after a control-flow merge, and - `RubyVM::InstructionSequence.compile_option[:specialized_instruction]` being `true` (the default). ## Reproduction ```ruby def line_events(iseq) ev = iseq.trace_points.select { |_, e| e == :line }.map(&:first) iseq.each_child { |c| ev.concat(line_events(c)) } ev end src = <<~RUBY return unless x Foo.new RUBY [true, false].each do |spec| RubyVM::InstructionSequence.compile_option = { specialized_instruction: spec } events = line_events(RubyVM::InstructionSequence.compile(src)) puts "specialized_instruction: #{spec.inspect}\t=> line events #{events.inspect}" end ``` ### Output Ruby `4.1.0dev` (master): ``` specialized_instruction: true => line events [1, 2, 2] specialized_instruction: false => line events [1, 2] ``` Ruby `4.0.6` (and earlier): ``` specialized_instruction: true => line events [1, 2] specialized_instruction: false => line events [1, 2] ``` Line 2 (`Foo.new`) is reported twice on master with the default compile options. ## Analysis With `specialized_instruction: true`, the `opt_new` codepath prepends a `putnil` (reserving the slot for the object) that carries the statement's line, and the following `opt_getconstant_path` *also* carries a `:line` trace point — so line 2 is emitted twice: ``` 0007 putnil ( 2)[Li] # statement start 0008 opt_getconstant_path <Foo>[Li] # duplicate :line on the same line 0010 opt_new <calldata!mid:new, argc:0, ARGS_SIMPLE>, 17 ``` For comparison, the non-`opt_new` path (`Foo.bar`, or `Foo.new` under `specialized_instruction: false`) emits the line once, on `opt_getconstant_path`: ``` 0007 opt_getconstant_path <Foo>( 2)[Li] 0009 opt_send_without_block <calldata!mid:bar, ...> ``` The `[Li]` on `opt_getconstant_path` in the `opt_new` path appears to be redundant: the `putnil` before it already carries the statement's line event. ## Impact This surfaced as a failure of `test/prism/newline_test.rb` in `make test-all` on ruby-head in [this Prism PR](https://github.com/ruby/prism/pull/4223). That test asserts that prism's newline flags match RubyVM's `:line` trace points exactly. Two fixtures hit the pattern: - `test/prism/ruby/parameters_signature_test.rb:95` — `object = Object.new` after `return unless compare` - `test/prism/locals_test.rb:101` — `yield ISeq.new(opnd)` after `next unless ...` It is not observed when running the same test in the prism repository, because there `require "test/unit"` (the test-unit gem) sets `specialized_instruction: false` globally, which suppresses `opt_new` and therefore the duplicate event. -- https://bugs.ruby-lang.org/