class Orbacle::TypingService
Constants
- TypingError
Attributes
logger[R]
stats[R]
Public Class Methods
new(logger, stats)
click to toggle source
# File lib/orbacle/typing_service.rb, line 13 def initialize(logger, stats) @logger = logger @stats = stats end
Public Instance Methods
call(graph, worklist, state)
click to toggle source
# File lib/orbacle/typing_service.rb, line 18 def call(graph, worklist, state) @worklist = worklist @graph = graph @state = state stats.set_value(:initial_nodes, @graph.vertices.size) stats.set_value(:initial_message_sends, @worklist.message_sends.size) @graph.vertices.to_a.each {|v| @worklist.enqueue_node(v) } while !@worklist.nodes.empty? while !@worklist.nodes.empty? while !@worklist.nodes.empty? node = @worklist.pop_node @worklist.count_node(node) if !@worklist.limit_exceeded?(node) current_result = @state.type_of(node) new_result = compute_result(node, @graph.parent_vertices(node)) raise ArgumentError.new(node) if new_result.nil? @state.set_type_of(node, new_result) stats.inc(:processed_nodes) logger.debug("Processed nodes: #{stats.counter(:processed_nodes)} remaining nodes #{@worklist.nodes.size} msends #{@worklist.handled_message_sends.size} / #{@worklist.message_sends.size}") if stats.counter(:processed_nodes) % 1000 == 0 if current_result != @state.type_of(node) @graph.adjacent_vertices(node).each do |adjacent_node| @worklist.enqueue_node(adjacent_node) end end end end @worklist.message_sends.each do |message_send| case message_send when Worklist::MessageSend if satisfied_message_send?(message_send) handle_message_send(message_send) end when Worklist::SuperSend if satisfied_super_send?(message_send) && !@worklist.message_send_handled?(message_send) handle_super_send(message_send) @worklist.mark_message_send_as_handled(message_send, nil) end else raise "Not handled message send" end end end @worklist.message_sends.each do |message_send| case message_send when Worklist::MessageSend handle_message_send(message_send) when Worklist::SuperSend if !@worklist.message_send_handled?(message_send) handle_super_send(message_send) @worklist.mark_message_send_as_handled(message_send, nil) end else raise "Not handled message send" end end end end
Private Instance Methods
build_union(sources_types)
click to toggle source
# File lib/orbacle/typing_service.rb, line 343 def build_union(sources_types) sources_types_without_unknowns = sources_types.compact.reject(&:bottom?).uniq if sources_types_without_unknowns.size == 0 BottomType.new elsif sources_types_without_unknowns.size == 1 sources_types_without_unknowns.first else UnionType.new(sources_types_without_unknowns.flat_map {|t| get_possible_types(t) }.uniq) end end
compute_result(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 82 def compute_result(node, sources) case node.type when :int then handle_int(node, sources) when :float then handle_float(node, sources) when :nil then handle_nil(node, sources) when :bool then handle_bool(node, sources) when :str then handle_just_string(node, sources) when :dstr then handle_just_string(node, sources) when :xstr then handle_just_string(node, sources) when :sym then handle_just_symbol(node, sources) when :dsym then handle_just_symbol(node, sources) when :regexp then handle_regexp(node, sources) when :array then handle_wrap_array(node, sources) when :splat_array then handle_unwrap_array(node, sources) when :hash_keys then handle_group(node, sources) when :hash_values then handle_group(node, sources) when :hash then handle_hash(node, sources) when :range_from then handle_group(node, sources) when :range_to then handle_group(node, sources) when :range then handle_range(node, sources) when :lvar then handle_group(node, sources) when :lvasgn then handle_pass_lte1(node, sources) when :ivasgn then handle_group(node, sources) when :ivar_definition then handle_group(node, sources) when :clivar_definition then handle_group(node, sources) when :ivar then handle_pass1(node, sources) when :cvasgn then handle_group(node, sources) when :cvar_definition then handle_group(node, sources) when :cvar then handle_pass1(node, sources) when :gvasgn then handle_group(node, sources) when :gvar_definition then handle_group(node, sources) when :gvar then handle_pass1(node, sources) when :backref then handle_just_string(node, sources) when :nthref then handle_just_string(node, sources) when :defined then handle_maybe_string(node, sources) when :casgn then handle_group(node, sources) when :const then handle_const(node, sources) when :self then handle_self(node, sources) when :call_obj then handle_pass1(node, sources) when :call_result then handle_group(node, sources) when :call_arg then handle_group(node, sources) when :call_splatarg then handle_group(node, sources) when :formal_arg then handle_group(node, sources) when :formal_optarg then handle_group(node, sources) when :formal_restarg then handle_wrap_array(node, sources) when :formal_kwarg then handle_group(node, sources) when :formal_kwoptarg then handle_group(node, sources) when :formal_kwrestarg then handle_group(node, sources) when :formal_blockarg then handle_group(node, sources) when :block_result then handle_pass_lte1(node, sources) when :caller then handle_group(node, sources) when :loop_operator then handle_bottom(node, sources) when :rescue then handle_group(node, sources) when :ensure then handle_group(node, sources) when :retry then handle_bottom(node, sources) when :unwrap_error_array then handle_unwrap_error_array(node, sources) when :if_result then handle_group(node, sources) when :and then handle_and(node, sources) when :or then handle_or(node, sources) when :unwrap_array then handle_unwrap_array(node, sources) when :wrap_array then handle_wrap_array(node, sources) when :case_result then handle_group(node, sources) when :for then handle_pass1(node, sources) # not really tested when :dynamic_const then handle_bottom(node, sources) when :unwrap_hash_values then handle_unwrap_hash_values(node, sources) when :unwrap_hash_keys then handle_unwrap_hash_keys(node, sources) when :const_definition then handle_group(node, sources) when :constructor then handle_constructor(node, sources) when :method_result then handle_group(node, sources) when :extract_class then handle_extract_class(node, sources) when :lambda then handle_lambda(node, sources) when :definition_by_id then handle_definition_by_id(node, sources) when :yield_result then handle_group(node, sources) else raise UnknownNodeKindError.new(node) end rescue UnknownNodeKindError => e logger.error("Unknown node kind '#{e.node.type}' at #{e.node.location}") raise rescue => e logger.error("Typing failed with error '#{e.inspect}' at node #{node.type} at #{node.location}") raise end
connect_actual_args_to_formal_args(found_method_argtree, found_method_nodes, send_args)
click to toggle source
# File lib/orbacle/typing_service.rb, line 466 def connect_actual_args_to_formal_args(found_method_argtree, found_method_nodes, send_args) if send_args.last if found_method_argtree.kwargs.empty? regular_args = send_args connect_regular_args(found_method_argtree.args, found_method_nodes, regular_args) else @state.type_of(send_args.last).each_possible_type do |type| if type.name == "Hash" regular_args = send_args[0..-2] kwarg_arg = send_args.last connect_regular_args(found_method_argtree.args, found_method_nodes, regular_args) connect_keyword_args(found_method_argtree.kwargs, found_method_nodes, kwarg_arg) else regular_args = send_args connect_regular_args(found_method_argtree.args, found_method_nodes, regular_args) end end end end end
connect_constructor_to_node(class_name, node)
click to toggle source
# File lib/orbacle/typing_service.rb, line 441 def connect_constructor_to_node(class_name, node) constructor_node = Node.new(:constructor, { name: class_name }, nil) @graph.add_vertex(constructor_node) @graph.add_edge(constructor_node, node) @worklist.enqueue_node(constructor_node) end
connect_keyword_args(found_method_kwargs, found_method_nodes, kwarg_arg)
click to toggle source
# File lib/orbacle/typing_service.rb, line 592 def connect_keyword_args(found_method_kwargs, found_method_nodes, kwarg_arg) unwrapping_node = Node.new(:unwrap_hash_values, {}, nil) @graph.add_vertex(unwrapping_node) @graph.add_edge(kwarg_arg, unwrapping_node) @worklist.enqueue_node(unwrapping_node) found_method_kwargs.each do |formal_kwarg| next if formal_kwarg.name.nil? node_formal_kwarg = found_method_nodes[formal_kwarg.name] case formal_kwarg when GlobalTree::ArgumentsTree::Regular @graph.add_edge(unwrapping_node, node_formal_kwarg) @worklist.enqueue_node(node_formal_kwarg) when GlobalTree::ArgumentsTree::Optional @graph.add_edge(unwrapping_node, node_formal_kwarg) @worklist.enqueue_node(node_formal_kwarg) when GlobalTree::ArgumentsTree::Splat @graph.add_edge(kwarg_arg, node_formal_kwarg) @worklist.enqueue_node(node_formal_kwarg) else raise end end end
connect_method_result_to_node(method_nodes, node)
click to toggle source
# File lib/orbacle/typing_service.rb, line 628 def connect_method_result_to_node(method_nodes, node) method_result_node = method_nodes.result if !@graph.has_edge?(method_result_node, node) @graph.add_edge(method_result_node, node) @worklist.enqueue_node(node) end end
connect_regular_args(found_method_args, found_method_nodes, basic_send_args)
click to toggle source
# File lib/orbacle/typing_service.rb, line 566 def connect_regular_args(found_method_args, found_method_nodes, basic_send_args) possible_send_args = generate_possible_args(found_method_args.size, basic_send_args) possible_send_args.each do |send_args| found_method_args.each_with_index do |formal_arg, i| next if formal_arg.name.nil? node_formal_arg = found_method_nodes[formal_arg.name] next if send_args[i].nil? case formal_arg when GlobalTree::ArgumentsTree::Regular @graph.add_edge(send_args[i], node_formal_arg) @worklist.enqueue_node(node_formal_arg) when GlobalTree::ArgumentsTree::Optional @graph.add_edge(send_args[i], node_formal_arg) @worklist.enqueue_node(node_formal_arg) when GlobalTree::ArgumentsTree::Splat send_args[i..-1].each do |send_arg| @graph.add_edge(send_arg, node_formal_arg) @worklist.enqueue_node(node_formal_arg) end else raise end end end end
connect_yields_to_block_lambda(yields_nodes, block_node)
click to toggle source
# File lib/orbacle/typing_service.rb, line 529 def connect_yields_to_block_lambda(yields_nodes, block_node) yields_nodes.each do |yield_nodes| lambda_ids = lambda_ids_of_block(block_node) lambda_ids.each do |lambda_id| block_lambda = @state.get_lambda(lambda_id) block_lambda_nodes = @graph.get_lambda_nodes(lambda_id) connect_actual_args_to_formal_args(block_lambda.args, block_lambda_nodes.args, yield_nodes.send_args) @graph.add_edge(block_lambda_nodes.result, yield_nodes.send_result) @worklist.enqueue_node(yield_nodes.send_result) end end end
constructor_send?(type, message_name)
click to toggle source
# File lib/orbacle/typing_service.rb, line 636 def constructor_send?(type, message_name) type.is_a?(ClassType) && message_name == :new end
defined_type?(t)
click to toggle source
# File lib/orbacle/typing_service.rb, line 393 def defined_type?(t) t.class != BottomType end
extract_class(type)
click to toggle source
# File lib/orbacle/typing_service.rb, line 383 def extract_class(type) case type when NominalType then ClassType.new(type.name) when GenericType then ClassType.new(type.name) when ClassType then ClassType.new("Class") when UnionType then build_union(type.types.map {|t| extract_class(t) }) when MainType then ClassType.new("Object") end end
generate_possible_args(splatsize, send_args)
click to toggle source
# File lib/orbacle/typing_service.rb, line 543 def generate_possible_args(splatsize, send_args) if send_args.empty? [[]] else head, *rest = send_args tails = generate_possible_args(splatsize, rest) if head.type == :call_arg tails.map {|tail| [head, *tail] } elsif head.type == :call_splatarg unwrap_node = Node.new(:unwrap_array, {}) @graph.add_edge(head, unwrap_node) @worklist.enqueue_node(unwrap_node) (splatsize + 1).times.flat_map do |splat_array_possible_size| unwraps = [unwrap_node] * splat_array_possible_size tails.map do |tail| [*unwraps, *tail] end end else raise end end end
get_possible_types(type)
click to toggle source
# File lib/orbacle/typing_service.rb, line 354 def get_possible_types(type) case type when UnionType then type.types.flat_map {|t| get_possible_types(t) } else [type] end end
handle_and(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 210 def handle_and(node, sources) build_union([ handle_group(node, sources), NominalType.new("Boolean"), ]) end
handle_bool(*args)
click to toggle source
# File lib/orbacle/typing_service.rb, line 198 def handle_bool(*args) NominalType.new("Boolean") end
handle_bottom(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 232 def handle_bottom(node, sources) BottomType.new end
handle_class_send(class_name, message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 457 def handle_class_send(class_name, message_send) found_method = @state.find_deep_class_method_from_class_name(class_name, message_send.message_send) if found_method found_method_nodes = @graph.get_metod_nodes(found_method.id) handle_custom_message_send(found_method, message_send, found_method_nodes) connect_method_result_to_node(found_method_nodes, message_send.send_result) end end
handle_const(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 361 def handle_const(node, sources) const_ref = node.params.fetch(:const_ref) ref_result = @state.solve_reference(const_ref) if ref_result && @graph.constants[ref_result.full_name] const_definition_node = @graph.constants[ref_result.full_name] @graph.add_edge(const_definition_node, node) @worklist.enqueue_node(const_definition_node) @state.type_of(const_definition_node) elsif ref_result ClassType.new(ref_result.full_name) else ClassType.new(const_ref.relative_name) end end
handle_constructor(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 640 def handle_constructor(node, sources) name = node.params.fetch(:name) type_from_class_name(name) end
handle_constructor_send(class_name, message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 430 def handle_constructor_send(class_name, message_send) found_method = @state.find_deep_instance_method_from_class_name(class_name, :initialize) if found_method.nil? connect_constructor_to_node(class_name, message_send.send_result) else found_method_nodes = @graph.get_metod_nodes(found_method.id) handle_custom_message_send(found_method, message_send, found_method_nodes) connect_constructor_to_node(class_name, message_send.send_result) end end
handle_custom_message_send(found_method, message_send, found_method_nodes)
click to toggle source
# File lib/orbacle/typing_service.rb, line 488 def handle_custom_message_send(found_method, message_send, found_method_nodes) if found_method_nodes.caller_node @graph.add_edge(message_send.send_obj, found_method_nodes.caller_node) @worklist.enqueue_node(found_method_nodes.caller_node) end connect_actual_args_to_formal_args(found_method.args, found_method_nodes.args, message_send.send_args) found_method_nodes.zsupers.each do |zsuper_call| super_method = @state.find_super_method(found_method.id) next if super_method.nil? super_method_nodes = @graph.get_metod_nodes(super_method.id) connect_actual_args_to_formal_args(super_method.args, super_method_nodes.args, message_send.send_args) if zsuper_call.block.nil? connect_yields_to_block_lambda(@graph.get_metod_nodes(super_method.id).yields, message_send.block) else connect_yields_to_block_lambda(@graph.get_metod_nodes(super_method.id).yields, zsuper_call.block) end connect_method_result_to_node(super_method_nodes, zsuper_call.send_result) end connect_yields_to_block_lambda(found_method_nodes.yields, message_send.block) end
handle_definition_by_id(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 645 def handle_definition_by_id(node, sources) definition_id = node.params.fetch(:id) const = @state.find_constant_for_definition(definition_id) const ? ClassType.new(const.full_name) : BottomType.new end
handle_extract_class(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 376 def handle_extract_class(node, sources) res = sources.map do |source| extract_class(@state.type_of(sources.first)) end build_union(res) end
handle_float(*args)
click to toggle source
# File lib/orbacle/typing_service.rb, line 202 def handle_float(*args) NominalType.new("Float") end
handle_group(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 260 def handle_group(node, sources) sources_types = sources.map {|source_node| @state.type_of(source_node) } build_union(sources_types) end
handle_hash(_node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 271 def handle_hash(_node, sources) hash_keys_node = sources.find {|s| s.type == :hash_keys } hash_values_node = sources.find {|s| s.type == :hash_values } GenericType.new("Hash", [@state.type_of(hash_keys_node), @state.type_of(hash_values_node)]) end
handle_instance_send(class_name, message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 448 def handle_instance_send(class_name, message_send) found_method = @state.find_deep_instance_method_from_class_name(class_name, message_send.message_send) if found_method found_method_nodes = @graph.get_metod_nodes(found_method.id) handle_custom_message_send(found_method, message_send, found_method_nodes) connect_method_result_to_node(found_method_nodes, message_send.send_result) end end
handle_int(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 186 def handle_int(node, sources) NominalType.new("Integer") end
handle_just_string(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 206 def handle_just_string(node, sources) NominalType.new("String") end
handle_just_symbol(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 228 def handle_just_symbol(node, sources) NominalType.new("Symbol") end
handle_lambda(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 236 def handle_lambda(node, sources) ProcType.new(node.params.fetch(:id)) end
handle_maybe_string(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 224 def handle_maybe_string(node, sources) build_union([NominalType.new("String"), NominalType.new("Nil")]) end
handle_message_send(message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 406 def handle_message_send(message_send) @state.type_of(message_send.send_obj).each_possible_type do |possible_type| next if @worklist.message_send_handled_by_type?(message_send, possible_type) if constructor_send?(possible_type, message_send.message_send) handle_constructor_send(possible_type.name, message_send) elsif possible_type.instance_of?(ProcType) && message_send.message_send == :call handle_proc_call(possible_type, message_send) elsif possible_type.is_a?(ClassType) handle_class_send(possible_type.name, message_send) else handle_instance_send(possible_type.name, message_send) end @worklist.mark_message_send_as_handled(message_send, possible_type) end end
handle_nil(_node, _sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 194 def handle_nil(_node, _sources) NominalType.new("Nil") end
handle_or(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 217 def handle_or(node, sources) build_union([ handle_group(node, sources), NominalType.new("Boolean"), ]) end
handle_pass1(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 265 def handle_pass1(node, sources) raise if sources.size != 1 source = sources.first @state.type_of(source) end
handle_pass_lte1(_node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 338 def handle_pass_lte1(_node, sources) raise if sources.size > 1 @state.type_of(sources.first) end
handle_proc_call(lambda_type, message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 422 def handle_proc_call(lambda_type, message_send) found_lambda = @state.get_lambda(lambda_type.lambda_id) found_lambda_nodes = @graph.get_lambda_nodes(found_lambda.id) connect_actual_args_to_formal_args(found_lambda.args, found_lambda_nodes.args, message_send.send_args) @graph.add_edge(found_lambda_nodes.result, message_send.send_result) @worklist.enqueue_node(message_send.send_result) end
handle_range(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 277 def handle_range(node, sources) sources_types = sources.map {|source_node| @state.type_of(source_node) } GenericType.new("Range", [build_union(sources_types)]) end
handle_regexp(_node, _sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 190 def handle_regexp(_node, _sources) NominalType.new("Regexp") end
handle_self(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 282 def handle_self(node, sources) selfie = node.params.fetch(:selfie) if selfie.klass? if selfie.scope.empty? BottomType.new else ClassType.new(selfie.scope.absolute_str) end elsif selfie.instance? if selfie.scope.empty? BottomType.new else type_from_class_name(selfie.scope.absolute_str) end elsif selfie.main? MainType.new else raise end end
handle_super_send(super_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 617 def handle_super_send(super_send) return if super_send.method_id.nil? super_method = @state.find_super_method(super_send.method_id) return if super_method.nil? super_method_nodes = @graph.get_metod_nodes(super_method.id) handle_custom_message_send(super_method, super_send, super_method_nodes) connect_method_result_to_node(super_method_nodes, super_send.send_result) end
handle_unwrap_array(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 311 def handle_unwrap_array(node, sources) types_inside_arrays = [] sources .each do |s| @state.type_of(s).each_possible_type do |t| if t.name == "Array" types_inside_arrays << t.parameters.first end end end build_union(types_inside_arrays) end
handle_unwrap_error_array(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 324 def handle_unwrap_error_array(node, sources) result = [] handle_unwrap_array(node, sources).each_possible_type do |t| if t.is_a?(ClassType) result << NominalType.new(t.name) end end build_union(result) end
handle_unwrap_hash_keys(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 240 def handle_unwrap_hash_keys(node, sources) raise if sources.size != 1 source = sources.first if @state.type_of(source).is_a?(GenericType) @state.type_of(source).parameters.at(0) else BottomType.new end end
handle_unwrap_hash_values(node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 250 def handle_unwrap_hash_values(node, sources) raise if sources.size != 1 source = sources.first if @state.type_of(source).is_a?(GenericType) @state.type_of(source).parameters.at(1) else BottomType.new end end
handle_wrap_array(_node, sources)
click to toggle source
# File lib/orbacle/typing_service.rb, line 334 def handle_wrap_array(_node, sources) GenericType.new("Array", [build_union(sources.map {|s| @state.type_of(s) })]) end
lambda_ids_of_block(block)
click to toggle source
# File lib/orbacle/typing_service.rb, line 513 def lambda_ids_of_block(block) case block when Worklist::BlockLambda [block.lambda_id] when Worklist::BlockNode @state.type_of(block.node).enum_for(:each_possible_type).map do |possible_type| if possible_type.instance_of?(ProcType) possible_type.lambda_id end end.compact when NilClass [] else raise end end
satisfied_message_send?(message_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 397 def satisfied_message_send?(message_send) defined_type?(@state.type_of(message_send.send_obj)) && message_send.send_args.all? {|a| defined_type?(@state.type_of(a)) } end
satisfied_super_send?(super_send)
click to toggle source
# File lib/orbacle/typing_service.rb, line 402 def satisfied_super_send?(super_send) super_send.send_args.all? {|a| defined_type?(@state.type_of(a)) } end
type_from_class_name(name)
click to toggle source
# File lib/orbacle/typing_service.rb, line 303 def type_from_class_name(name) if ["Array", "Hash", "Range"].include?(name) GenericType.new(name, []) else NominalType.new(name) end end