Module: Police::DataFlow::Proxying

Defined in:
lib/police/dataflow/proxying.rb

Class Method Summary collapse

Class Method Details

.add_instance_method(proxy_class, method_def, access) ⇒ Object

Adds a method to a proxy class.

Parameters:

  • proxy_class (Module)

    the class that will receive the proxy method

  • method_def (Method)

    the definition of the method to be proxied

  • access (Symbol)

    the proxied method’s access level (:public, :protected, or :private)



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/police/dataflow/proxying.rb', line 57

def self.add_instance_method(proxy_class, method_def, access)
  # Avoid redefining methods, because that blows up VM caches.
  if proxy_class.method_defined?(method_def.name) ||
      proxy_class.private_method_defined?(method_def.name)
    return
  end

  # Define the method.
  proxy_class.class_eval proxy_method_definition(
      proxy_class.__police_classes__, method_def, access)
  # Set its access level.
  proxy_class.__send__ access, method_def.name
end

.add_instance_methods(proxy_class, klass) ⇒ NilClass

Creates proxies for a class’ instance methods.

The proxy methods are defined as instance methods for the proxying class, because all the proxied objects that have the same class will need the same proxies.

Parameters:

  • proxy_class (Class)

    a Police::DataFlow::Proxy subclass that will receive the new proxy method definitions

  • klass (Class)

    the class whose instance methods will be proxied

Returns:

  • (NilClass)

    nil



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/police/dataflow/proxying.rb', line 35

def self.add_instance_methods(proxy_class, klass)
  # NOTE: this is thread-safe because, at worst, the effort of adding methods
  #       will be re-duplicated
  klass.public_instance_methods(true).each do |method|
    add_instance_method proxy_class, klass.instance_method(method), :public
  end
  klass.protected_instance_methods(true).each do |method|
    add_instance_method proxy_class, klass.instance_method(method),
                        :protected
  end
  klass.private_instance_methods(true).each do |method|
    add_instance_method proxy_class, klass.instance_method(method), :private
  end
  nil
end

.proxy(proxied, label_set) ⇒ Police::DataFlow::ProxyBase

Creates a label-holding proxy for an object.

Parameters:

  • proxied (Object)

    the object to be proxied

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

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

Returns:

  • (Police::DataFlow::ProxyBase)

    an object that can carry labels, and performs label-propagation as it redirects received messages to the proxied object



20
21
22
23
# File 'lib/police/dataflow/proxying.rb', line 20

def self.proxy(proxied, label_set)
  proxy_class = Police::DataFlow::Proxies.for proxied.class, label_set
  proxy_class.new proxied, proxy_class, label_set
end

.proxy_argument_list(method_def, capture_block) ⇒ String

The list of arguments used to define a proxy for the given method.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied

  • captue_block (Boolean)

    if true, the method captures the block that it receives; this should be true when the returned code is used in method definitions, and false when it is used in method calls

Returns:

  • (String)

    a chunk of Ruby that can be used as the argument list when defining a proxy for the given method



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/police/dataflow/proxying.rb', line 188

def self.proxy_argument_list(method_def, capture_block)
  arg_list = if method_def.arity >= 0
    # Fixed number of arguments.
    (1..method_def.arity).map { |i| "arg#{i}" }
  else
    # Variable number of arguments.
    ((1..(-method_def.arity - 1)).map { |i| "arg#{i}" } << '*args')
  end
  arg_list << '&block' if capture_block
  arg_list.join ', '
end

.proxy_call_argument_list(method_def) ⇒ String

The list of arguments used to call a proxied method.

This assumes that the proxy method definition uses the code retuned by proxy_argument_list.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied; should match the value passed to proxy_argument_list

Returns:

  • (String)

    a chunk of Ruby that can be used as the argument list when defining a proxy for the given method



209
210
211
212
213
214
215
216
# File 'lib/police/dataflow/proxying.rb', line 209

def self.proxy_call_argument_list(method_def)
  source = Police::VmInfo.method_source method_def
  if source == :native || source == :kernel
    proxy_low_level_call_argument_list method_def
  else
    proxy_argument_list method_def, false
  end
end

.proxy_low_level_call_argument_list(method_def) ⇒ String

The list of arguments used to call a proxied low-level method.

Low-level methods don’t use Ruby methods to manipulate their arguments, so they can’t work on proxies, and need to receive the proxied objects as arguments.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied; should match the value passed to #proxy_argument_list

Returns:

  • (String)

    a chunk of Ruby that can be used as the argument list when defining a proxy for the given method



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/police/dataflow/proxying.rb', line 228

def self.proxy_low_level_call_argument_list(method_def)
  arg_list = if method_def.arity >= 0
    # Fixed number of arguments.
    (1..method_def.arity).map do |i|
      "(nil == arg#{i}.__police_labels__) ? arg#{i} : " +
          "arg#{i}.__police_proxied__"
    end
  else
    # Variable number of arguments.
    args_mapper =  '*(args.map { |a| (nil == a.__police_labels__) ? a : ' +
        'a.__police_proxied__ })'
    (1..(-method_def.arity - 1)).map { |i|
      "(nil == arg#{i}.__police_labels__) ? arg#{i} : " +
          "arg#{i}.__police_proxied__"
    } << args_mapper
  end
  arg_list.join ', '
end

.proxy_method_call(method_def, access) ⇒ String

The proxying call to a method.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied

  • access (Symbol)

    the proxied method’s access level (:public, :protected, or :private)

Returns:

  • (String)

    a chunk of Ruby that can be used to call the given method when defining a proxy for it



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/police/dataflow/proxying.rb', line 115

def self.proxy_method_call(method_def, access)
  arg_list = proxy_call_argument_list method_def

  if access == :public
    "@__police_proxied__.#{method_def.name}(#{arg_list})"
  else
    if arg_list.empty?
      "@__police_proxied__.__send__(:#{method_def.name})"
    else
      "@__police_proxied__.__send__(:#{method_def.name}, #{arg_list})"
    end
  end
end

.proxy_method_definition(label_classes, method_def, access) ⇒ String

The full definition of a proxy method.

Parameters:

  • label_classes (Array<Police::DataFlow::Label>)

    the label classes supported by the proxy class

  • method_def (Method)

    the definition of the method to be proxied

  • access (Symbol)

    the proxied method’s access level (:public, :protected, or :private)

Returns:

  • (String)

    a chunk of Ruby that can be eval’ed in the context of a proxy class to define a proxy for the given method



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
# File 'lib/police/dataflow/proxying.rb', line 80

def self.proxy_method_definition(label_classes, method_def, access)
  # NOTE: it might be tempting to attempt to pass a block to the proxied
  #       method at all times, and try to yield to the original block when
  #       our block is invoked; this would work most of the time, but it
  #       would break methods such as Enumerable#map and String#scan, whose
  #       behavior changes depending on whether or not a block is passed to
  #       them
  ["def #{method_def.name}(#{proxy_argument_list(method_def, true)})",
     proxy_sticky_fastpath_check(method_def),
     proxy_sticky_gathering(method_def),
     "return_value = if block",
       proxy_method_call(method_def, access) + " do |*yield_args|",
         proxy_yield_sticky_decorating(method_def),
         proxy_yield_args_decorating(label_classes, method_def),
         "block_return = yield(*yield_args)",
         # TODO(pwnall): consider adding a yield value filter
         "next block_return",
       "end",
     "else",
       proxy_method_call(method_def, access),
     "end",

     proxy_return_sticky_decorating(method_def),
     proxy_return_decorating(label_classes, method_def),
     "return return_value",
   "end"].join ';'
end

.proxy_return_decorating(label_classes, method_def) ⇒ String

Code that labels return value of a decorated method.

Parameters:

  • label_classes (Array<Police::DataFlow::Label>)

    the label classes supported by the proxy class

  • method_def (Method)

    the definition of the method to be proxied

Returns:

  • (String)

    a chunk of Ruby that can be used to invoke the return value decorators of the labels held by a labeled object’s proxy



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/police/dataflow/proxying.rb', line 162

def self.proxy_return_decorating(label_classes, method_def)
  method_name = method_def.name
  arg_list = proxy_argument_list method_def, false
  code_lines = ['labels = @__police_labels__']
  label_classes.each do |label_class|
    if hook = label_class.return_hook(method_name)
      label_key = label_class.__id__
      code_lines << "labels[#{label_key}].each { |label, _| " \
          "return_value = label.#{hook}(return_value, self, #{arg_list}) }"
    elsif label_class.sticky?
      label_key = label_class.__id__
      code_lines << "labels[#{label_key}].each { |label, _| " \
          "return_value = ::Police::DataFlow.label(return_value, label) }"
    end
  end
  (code_lines.length > 1) ? code_lines.join('; ') : ''
end

.proxy_return_sticky_decorating(method_def) ⇒ Object



341
342
343
344
345
346
347
348
349
# File 'lib/police/dataflow/proxying.rb', line 341

def self.proxy_return_sticky_decorating(method_def)
  # Don't generate anything for zero-argument methods.
  return '' if method_def.arity == 0

  'unless fast_sticky; ' +
    'return_value = ::Police::DataFlow::Labeling.bulk_sticky_label(' +
        'return_value, sticky_labels); ' +
  'end'
end

.proxy_sticky_fastpath_check(method_def) ⇒ String

Boolean expression deciding if a proxied method received labeled arguments.

If none of the method’s arguments is labeled, the sticky label propagation logic can be completely bypassed.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied; should match the value passed to #proxy_argument_list

Returns:

  • (String)

    a chunk of Ruby that sets the ‘fast_sticky’ local variable to a truthy value if none of the proxied method’s arguments is labeled, and to a falsey value if at least one argument has a label



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/police/dataflow/proxying.rb', line 257

def self.proxy_sticky_fastpath_check(method_def)
  # Don't generate anything for zero-argument methods.
  return '' if method_def.arity == 0

  boolean_list = if method_def.arity > 0
    # Fixed number of arguments.
    (1..method_def.arity).map do |i|
      "(nil == arg#{i}.__police_stickies__)"
    end
  else
    # Variable number of arguments.
    args_boolean = '(args.all? { |a| nil == a.__police_stickies__ })'
    (1..(-method_def.arity - 1)).map { |i|
      "(nil == arg#{i}.__police_stickies__)"
    } << args_boolean
  end
  'fast_sticky = ' + boolean_list.join(' && ')
end

.proxy_sticky_gathering(method_def) ⇒ String

Code for computing the union of the arguments’ sticky labels.

The code is wrapped in a check that assumes the code returned by #proxy_sticky_fastpath_check was already executed.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied; should match the value passed to #proxy_argument_list and #proxy_sticky_fastpath_check

Returns:

  • (String)

    a chunk of Ruby that sets the ‘sticky_labels’ local variable to a label set that contains all the sticky labels in the method’s arguments



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/police/dataflow/proxying.rb', line 287

def self.proxy_sticky_gathering(method_def)
  # Don't generate anything for zero-argument methods.
  return '' if method_def.arity == 0

  code_lines = ['unless fast_sticky', 'sticky_labels = {}']
  if method_def.arity > 0
    # Fixed number of arguments.
    1.upto method_def.arity do |i|
      code_lines << "unless nil == arg#{i}.__police_stickies__"
      code_lines <<   '::Police::DataFlow::Labeling.merge_sets!(' +
                          "sticky_labels, arg#{i}.__police_stickies__)"
      code_lines << 'end'
    end
  else
    # Variable number of arguments.
    1.upto(-method_def.arity - 1) do |i|
      code_lines << "unless nil == arg#{i}.__police_stickies__"
      code_lines <<   '::Police::DataFlow::Labeling.merge_sets!(' +
                          "sticky_labels, arg#{i}.__police_stickies__)"
      code_lines << 'end'
    end
    code_lines << 'args.each do |a|'
    code_lines <<   'unless nil == a.__police_stickies__'
    code_lines <<     '::Police::DataFlow::Labeling.merge_sets!(' +
                          'sticky_labels, a.__police_stickies__)'
    code_lines <<   'end'
    code_lines << 'end'
  end
  code_lines << 'end'
  code_lines.join '; '
end

.proxy_yield_args_decorating(label_classes, method_def) ⇒ String

Code that labels the values yielded by a decorated method to its block.

Parameters:

  • label_classes (Array<Police::DataFlow::Label>)

    the label classes supported by the proxy class

  • method_def (Method)

    the definition of the decorated method

Returns:

  • (String)

    a chunk of Ruby that can be used to invoke the yield args decorators of the labels held by a labeled object’s proxy



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/police/dataflow/proxying.rb', line 136

def self.proxy_yield_args_decorating(label_classes, method_def)
  method_name = method_def.name
  arg_list = proxy_argument_list method_def, false
  code_lines = ['labels = @__police_labels__']
  label_classes.each do |label_class|
    if hook = label_class.yield_args_hook(method_name)
      label_key = label_class.__id__
      code_lines << "labels[#{label_key}].each { |label, _| " \
          "label.#{hook}(self, yield_args, #{arg_list}) }"
    elsif label_class.sticky?
      label_key = label_class.__id__
      code_lines << "labels[#{label_key}].each { |label, _| " \
          "yield_args.map! { |arg| ::Police::DataFlow.label(arg, label) } " \
          "}"
    end
  end
  (code_lines.length > 1) ? code_lines.join('; ') : ''
end

.proxy_yield_sticky_decorating(method_def) ⇒ String

Code for applying argument sticky labels to a method’s yielded arguments.

This code assumes that the code returned by #proxy_sticky_fastpath_check and #proxy_sticky_gathering was already executed.

Parameters:

  • method_def (Method)

    the definition of the method to be proxied; should match the value passed to #proxy_argument_list and #proxy_sticky_fastpath_check

Returns:

  • (String)

    a chunk of Ruby that sets the ‘sticky_labels’ local variable to a label set that contains all the sticky labels in the method’s arguments



330
331
332
333
334
335
336
337
338
339
# File 'lib/police/dataflow/proxying.rb', line 330

def self.proxy_yield_sticky_decorating(method_def)
  # Don't generate anything for zero-argument methods.
  return '' if method_def.arity == 0

  'unless fast_sticky; ' +
    'yield_args.map! do |a|; ' +
      '::Police::DataFlow::Labeling.bulk_sticky_label(a, sticky_labels); ' +
    'end; ' +
  'end'
end