Class: Police::DataFlow::ProxyBase

Inherits:
BasicObject
Defined in:
lib/police/dataflow/proxy_base.rb

Overview

Base class for labeled objects replacements.

Direct Known Subclasses

ProxyNumeric

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(proxied, proxy_class, label_set) ⇒ ProxyBase

Creates a proxied object.

Parameters:

  • proxied (Object)

    the object that will receive messages sent to the newly created proxy

  • proxy_class (Class<Police::DataFlow::ProxyBase>)

    the Police::DataFlow::ProxyBase subclass being instantiated; Object instances can call Object#class to get to their class, but BasicObject instances don’t have this luxury

  • label_set (Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>)

    the set of all labels that will be held by the object’s proxy



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/police/dataflow/proxy_base.rb', line 49

def initialize(proxied, proxy_class, label_set)
  @__police_proxied__ = proxied
  @__police_labels__ = label_set

  # Cache the sticky labels to speed up certain computations.
  @__police_stickies__ = {}
  @__police_labels__.each do |class_id, instance_set|
    label_class = instance_set.first.first.class
    next unless label_class.sticky?
    # NOTE: the set of instances is intentionally shared with
    #       @__police_labels__ so updates to the former are automatically
    #       reflected in the stickies cache
    @__police_stickies__[class_id] = instance_set
  end

  # Holds the object's class, because Object#class is not available.
  @__police_class__ = proxy_class
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Handles method calls to the proxied object.

Whenever possible, proxy methods are created on-the-fly, so that future calls to the same method will be faster.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/police/dataflow/proxy_base.rb', line 72

def method_missing(name, *args, &block)
  # Build a fast path for future method calls, if possible.
  respond_to_missing? name, true

  # NOTE: going out of our way to use the fast path methods, because they
  #       handle native method argument unwrapping correctly
  if @__police_class__.method_defined?(name) ||
      @__police_class__.private_method_defined?(name)
    return __send__(name, *args, &block)
  end

  if block
    return_value = @__police_proxied__.__send__ name, *args do |*yield_args|
      # Yielded values filtering.
      @__police_labels__.each do |_, label_hash|
        label_class = label_hash.first.first.class
        if hook = label_class.yield_args_hook(name)
          label_hash.each do |label, _|
            yield_args = label.__send__ hook, self, yield_args, *args
          end
        elsif label_class.sticky?
          label_hash.each do |label, _|
            yield_args.map! { |arg| ::Police::DataFlow.label arg, label }
          end
        end
      end

      yield_return = yield(*yield_args)
      # TODO(pwnall): consider adding a yield value filter
      next yield_return
    end
  else
    return_value = @__police_proxied__.__send__ name, *args
  end

  # Return value filtering.
  @__police_labels__.each do |_, label_hash|
    label_class = label_hash.first.first.class
    if hook = label_hash.first.first.class.return_hook(name)
      label_hash.each do |label, _|
        return_value = label.__send__ hook, return_value, self, *args
      end
    elsif label_class.sticky?
      label_hash.each do |label, _|
        return_value = ::Police::DataFlow.label return_value, label
      end
    end
  end
  return return_value
end

Class Attribute Details

.__police_classes__Object

The classes of the labels supported by the proxy class.

This is a Police::DataFlow implementation detail. Do not read it directly.



166
167
168
# File 'lib/police/dataflow/proxy_base.rb', line 166

def __police_classes__
  @__police_classes__
end

Instance Attribute Details

#__police_class__Object (readonly)

The actual class of a proxy object.

This is necessary because the class method for a proxy object must return the proxied object’s class.

Use the Police::DataFlow API instead of reading this attribute directly.



37
38
39
# File 'lib/police/dataflow/proxy_base.rb', line 37

def __police_class__
  @__police_class__
end

#__police_labels__Object (readonly)

The Label instances attached to the proxied object.

This is a Hash whose keys are the __id__s of the Class objects for the labels’ classes. The values are sets of labels that are instances of the corresponding label classes. Sets are implemented as Hashes mapping the label instances to boolean true values.

Use the Police::DataFlow API instead of reading this attribute directly.



22
23
24
# File 'lib/police/dataflow/proxy_base.rb', line 22

def __police_labels__
  @__police_labels__
end

#__police_proxied__Object (readonly)

The object being proxied by this object.

Use the Police::DataFlow API instead of reading this attribute directly.



11
12
13
# File 'lib/police/dataflow/proxy_base.rb', line 11

def __police_proxied__
  @__police_proxied__
end

#__police_stickies__Object (readonly)

Attached Label instances whose classes are sticky.

This is a proxying implementation detail and should not be relied on.



28
29
30
# File 'lib/police/dataflow/proxy_base.rb', line 28

def __police_stickies__
  @__police_stickies__
end

Instance Method Details

#respond_to_missing?(name, include_private) ⇒ Boolean

Called when Object#respond_to? returns false.

Returns:

  • (Boolean)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/police/dataflow/proxy_base.rb', line 124

def respond_to_missing?(name, include_private)
  return false unless @__police_proxied__.respond_to? name, include_private

  # A method on the proxied object doesn't have a corresponding proxy.
  # Fix this by creating all possible proxies.

  # NOTE: this approach is cheaper than creating proxies one by one, because
  #       it plays nice with method caches

  ::Police::DataFlow::Proxying.add_instance_methods @__police_class__,
                                                    @__police_proxied__.class

  # NOTE: we don't want to create unnecessary singleton classes
  # target_methods = @__police_proxied__.singleton_methods true
  # unless target_methods.empty?
  #  ::Police::DataFlow::Proxying.add_singleton_methods self,
  #      @__police_proxied__, target_methods
  # end

  true
end