class SyntaxTree::Index::ISeqBackend
runtimes.
faster than using the Syntax Tree parser, but is not available on all
This backend creates the index using RubyVM::InstructionSequence, which is
def find_attr_arguments(insns, index)
def find_attr_arguments(insns, index) orig_argc = insns[index][1][:orig_argc] names = [] current = index - 1 while current >= 0 && names.length < orig_argc if insns[current].is_a?(Array) && insns[current][0] == :putobject names.unshift(insns[current][1]) end current -= 1 end names if insns[current] == [:putself] && names.length == orig_argc end
def find_constant_path(insns, index)
def find_constant_path(insns, index) index -= 1 while index >= 0 && ( insns[index].is_a?(Integer) || ( insns[index].is_a?(Array) && %i[swap topn].include?(insns[index][0]) ) ) insn = insns[index] if insn.is_a?(Array) && insn[0] == :opt_getconstant_path # In this case we're on Ruby 3.2+ and we have an opt_getconstant_path # instruction, so we already know all of the symbols in the nesting. [index - 1, insn[1]] elsif insn.is_a?(Symbol) && insn.match?(/\Alabel_\d+/) # Otherwise, if we have a label then this is very likely the # destination of an opt_getinlinecache instruction, in which case # we'll walk backwards to grab up all of the constants. names = [] index -= 1 until insns[index].is_a?(Array) && insns[index][0] == :opt_getinlinecache if insns[index].is_a?(Array) && insns[index][0] == :getconstant names.unshift(insns[index][1]) end index -= 1 end [index - 1, names] else [index, []] end end
def index(source)
def index(source) index_iseq( RubyVM::InstructionSequence.compile(source).to_a, FileComments.new(FileComments::StringSource.new(source)) ) end
def index_file(filepath)
def index_file(filepath) index_iseq( RubyVM::InstructionSequence.compile_file(filepath).to_a, FileComments.new(FileComments::FileSource.new(filepath)) ) end
def index_iseq(iseq, file_comments)
def index_iseq(iseq, file_comments) results = [] queue = [[iseq, []]] while (current_iseq, current_nesting = queue.shift) file = current_iseq[5] line = current_iseq[8] insns = current_iseq[13] insns.each_with_index do |insn, index| case insn when Integer line = insn next when Array # continue on else # skip everything else next end case insn[0] when :defineclass _, name, class_iseq, flags = insn next_nesting = current_nesting.dup # This is the index we're going to search for the nested constant # path within the declaration name. constant_index = index - 2 # This is the superclass of the class being defined. superclass = [] # If there is a superclass, then we're going to find it here and # then update the constant_index as necessary. if flags & VM_DEFINECLASS_FLAG_HAS_SUPERCLASS > 0 constant_index, superclass = find_constant_path(insns, index - 1) if superclass.empty? warn("#{file}:#{line}: superclass with non constant path") next end end if (_, nesting = find_constant_path(insns, constant_index)) # If there is a constant path in the class name, then we need to # handle that by updating the nesting. next_nesting << (nesting << name) else # Otherwise we'll add the class name to the nesting. next_nesting << [name] end if flags == VM_DEFINECLASS_TYPE_SINGLETON_CLASS # At the moment, we don't support singletons that aren't # defined on self. We could, but it would require more # emulation. if insns[index - 2] != [:putself] warn( "#{file}:#{line}: singleton class with non-self receiver" ) next end elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0 location = location_for(class_iseq) results << ModuleDefinition.new( next_nesting, name, location, EntryComments.new(file_comments, location) ) else location = location_for(class_iseq) results << ClassDefinition.new( next_nesting, name, superclass, location, EntryComments.new(file_comments, location) ) end queue << [class_iseq, next_nesting] when :definemethod location = location_for(insn[2]) results << method_definition( current_nesting, insn[1], location, file_comments ) when :definesmethod if insns[index - 1] != [:putself] warn("#{file}:#{line}: singleton method with non-self receiver") next end location = location_for(insn[2]) results << SingletonMethodDefinition.new( current_nesting, insn[1], location, EntryComments.new(file_comments, location) ) when :setconstant next_nesting = current_nesting.dup name = insn[1] _, nesting = find_constant_path(insns, index - 1) next_nesting << nesting if nesting.any? location = Location.new(line, :unknown) results << ConstantDefinition.new( next_nesting, name, location, EntryComments.new(file_comments, location) ) when :opt_send_without_block, :send case insn[1][:mid] when :attr_reader, :attr_writer, :attr_accessor attr_names = find_attr_arguments(insns, index) next unless attr_names location = Location.new(line, :unknown) attr_names.each do |attr_name| if insn[1][:mid] != :attr_writer results << method_definition( current_nesting, attr_name, location, file_comments ) end if insn[1][:mid] != :attr_reader results << method_definition( current_nesting, :"#{attr_name}=", location, file_comments ) end end when :"core#set_method_alias" # Now we have to validate that the alias is happening with a # non-interpolated value. To do this we'll match the specific # pattern we're expecting. values = insns[(index - 4)...index].map do |previous| previous.is_a?(Array) ? previous[0] : previous end if values != %i[putspecialobject putspecialobject putobject putobject] next end # Now that we know it's in the structure we want it, we can use # the values of the putobject to determine the alias. location = Location.new(line, :unknown) results << AliasMethodDefinition.new( current_nesting, insns[index - 2][1], location, EntryComments.new(file_comments, location) ) end end end end results end
def location_for(iseq)
def location_for(iseq) code_location = iseq[4][:code_location] Location.new(code_location[0], code_location[1]) end
def method_definition(nesting, name, location, file_comments)
def method_definition(nesting, name, location, file_comments) comments = EntryComments.new(file_comments, location) if nesting.last == [:singletonclass] SingletonMethodDefinition.new( nesting[0...-1], name, location, comments ) else MethodDefinition.new(nesting, name, location, comments) end end