Skip to content

Stack overflow when resolving self-referential constant alias #3782

@pkondzior

Description

@pkondzior

Description

When a module defines a constant alias that references another constant of the same name in an outer namespace (for example, B = B::C inside A::D), the Ruby LSP indexer enters infinite recursion while resolving the alias.

This leads to a SystemStackError ("stack level too deep") during constant resolution.

Ruby LSP Information

Following code is going to cause SystemStackError exception during reference search:

  module A
    module B
      class C
      end
    end
  end

  module A
    module D
      B = B::C
    end
  end

Example log:

#<Thread:0x0000000125355e00 /Users/pkondzior/Projects/ruby-lsp/lib/ruby_lsp/base_server.rb:155 run> terminated with exception (report_on_exception is true):
/Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:427:in 'Integer#downto': stack level too deep (SystemStackError)
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:427:in 'RubyIndexer::Index#follow_aliased_namespace'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:1034:in 'RubyIndexer::Index#direct_or_aliased_constant'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:332:in 'RubyIndexer::Index#resolve'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:910:in 'RubyIndexer::Index#resolve_alias'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:436:in 'block in RubyIndexer::Index#follow_aliased_namespace'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:427:in 'Integer#downto'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:427:in 'RubyIndexer::Index#follow_aliased_namespace'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_indexer/lib/ruby_indexer/index.rb:1034:in 'RubyIndexer::Index#direct_or_aliased_constant'
	 ... 8542 levels...
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_lsp/requests/references.rb:63:in 'RubyLsp::Requests::References#perform'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_lsp/server.rb:783:in 'RubyLsp::Server#text_document_references'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_lsp/server.rb:78:in 'RubyLsp::Server#process_message'
	from /Users/pkondzior/Projects/ruby-lsp/lib/ruby_lsp/base_server.rb:167:in 'block in RubyLsp::BaseServer#new_worker'

Reproduction steps

  1. Run this code in irb console where ruby-lsp is available
require "uri"
require "ruby_lsp/internal"

index = RubyIndexer::Index.new

src = <<~RUBY
  module A
    module B
      class C; end
    end
  end

  module A
    module D
      B = B::C
    end
  end
RUBY

uri = URI.parse("file:///test.rb")
index.index_single(uri, src)

# Triggers stack overflow
index.resolve("A::D::B", [])

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions