police-dataflow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ module Police
2
+
3
+ module DataFlow
4
+
5
+ # The complex method dispatching logic used by ProxyBase.
6
+ #
7
+ # ProxyBase is the superclass for all proxy classes, which makes it visible to
8
+ # application code. For this reason, we avoid defining any methods there.
9
+ module Proxying
10
+ # Creates a label-holding proxy for an object.
11
+ #
12
+ # @param [Object] proxied the object to be proxied
13
+ # @param [Array<Integer>] label_keys
14
+ # @return [Police::DataFlow::ProxyBase] an object that can carry labels, and
15
+ # performs label-propagation as it redirects received messages to the
16
+ # proxied object
17
+ def self.proxy(proxied, label_set, autoflow_set)
18
+ proxy_class = Police::DataFlow::Proxies.for proxied.class, label_set
19
+ proxy_class.new proxied, proxy_class, label_set, autoflow_set
20
+ end
21
+
22
+ # Creates proxies for a class' instance methods.
23
+ #
24
+ # The proxy methods are defined as instance methods for the proxying class,
25
+ # because all the proxied objects that have the same class will need the same
26
+ # proxies.
27
+ #
28
+ # @param [Class] proxy_class a Police::DataFlow::Proxy subclass that will
29
+ # receive the new proxy method definitions
30
+ # @param [Class] klass the class whose instance methods will be proxied
31
+ # @return [NilClass] nil
32
+ def self.add_class_methods(proxy_class, klass)
33
+ # NOTE: this is thread-safe because, at worst, the effort of adding methods
34
+ # will be re-duplicated
35
+ klass.public_instance_methods(true).each do |method|
36
+ add_class_method proxy_class, klass.instance_method(method), :public
37
+ end
38
+ klass.protected_instance_methods(true).each do |method|
39
+ add_class_method proxy_class, klass.instance_method(method), :protected
40
+ end
41
+ klass.private_instance_methods(true).each do |method|
42
+ add_class_method proxy_class, klass.instance_method(method), :private
43
+ end
44
+ nil
45
+ end
46
+
47
+ # Adds a method to a proxy class.
48
+ #
49
+ # @param [Module] proxy_class the class that will receive the proxy method
50
+ # @param [Method] method_def the definition of the method to be proxied
51
+ # @param [Symbol] access the proxied method's access level (:public,
52
+ # :protected, or :private)
53
+ def self.add_class_method(proxy_class, method_def, access)
54
+ # Avoid redefining methods, because that blows up VM caches.
55
+ if proxy_class.method_defined?(method_def.name) ||
56
+ proxy_class.private_method_defined?(method_def.name)
57
+ return
58
+ end
59
+
60
+ # Define the method.
61
+ proxy_class.class_eval proxy_method_definition(
62
+ proxy_class.__police_classes__, method_def, access)
63
+ # Set its access level.
64
+ proxy_class.__send__ access, method_def.name
65
+ end
66
+
67
+ # The full definition of a proxy method.
68
+ #
69
+ # @param [Array<Police::DataFlow::Label>] label_classes the label classes
70
+ # supported by the proxy class
71
+ # @param [Method] method_def the definition of the method to be proxied
72
+ # @param [Symbol] access the proxied method's access level (:public,
73
+ # :protected, or :private)
74
+ # @return [String] a chunk of Ruby that can be eval'ed in the context of a
75
+ # proxy class to define a proxy for the given method
76
+ def self.proxy_method_definition(label_classes, method_def, access)
77
+ # NOTE: it might be tempting to attempt to pass a block to the proxied
78
+ # method at all times, and try to yield to the original block when our
79
+ # block is invoked; this would work most of the time, but it would
80
+ # break methods such as Enumerable#map and String#scan, whose behavior
81
+ # changes depending on whether or not a block is passed to them
82
+ ["def #{method_def.name}(#{proxy_argument_list(method_def, true)})",
83
+ "return_value = if block",
84
+ proxy_method_call(method_def, access, false) + " do |*yield_args|",
85
+ proxy_yield_args_decorating(label_classes, method_def),
86
+ "block_return = yield(*yield_args)",
87
+ # TODO(pwnall): consider adding a yield value filter
88
+ "next block_return",
89
+ "end",
90
+ "else",
91
+ proxy_method_call(method_def, access, false),
92
+ "end",
93
+ proxy_return_decorating(label_classes, method_def),
94
+ "return return_value",
95
+ "end"].join ';'
96
+ end
97
+
98
+ # The proxying call to a method.
99
+ #
100
+ # @param [Method] method_def the definition of the method to be proxied
101
+ # @param [Symbol] access the proxied method's access level (:public,
102
+ # :protected, or :private)
103
+ # @param [Boolean] include_block if true, the method call passes the block
104
+ # that the proxy has received; if false, the block is ignored
105
+ # @return [String] a chunk of Ruby that can be used to call the given method
106
+ # when defining a proxy for it
107
+ def self.proxy_method_call(method_def, access, include_block)
108
+ arg_list = proxy_argument_list method_def, include_block
109
+
110
+ if access == :public
111
+ "@__police_proxied__.#{method_def.name}(#{arg_list})"
112
+ else
113
+ "@__police_proxied__.__send__(:#{method_def.name}, #{arg_list})"
114
+ end
115
+ end
116
+
117
+ # Code that labels the values yielded by a decorated method to its block.
118
+ #
119
+ # @param [Array<Police::DataFlow::Label>] label_classes the label classes
120
+ # supported by the proxy class
121
+ # @param [Method] method_def the definition of the decorated method
122
+ # @return [String] a chunk of Ruby that can be used to invoke the yield args
123
+ # decorators of the labels held by a labeled object's proxy
124
+ def self.proxy_yield_args_decorating(label_classes, method_def)
125
+ method_name = method_def.name
126
+ arg_list = proxy_argument_list method_def, false
127
+ code_lines = ['labels = @__police_labels__']
128
+ label_classes.each do |label_class|
129
+ next unless hook = label_class.yield_args_hook(method_name)
130
+ label_key = label_class.__id__
131
+ code_lines << "labels[#{label_key}].each { |label, _| " \
132
+ "label.#{hook}(self, yield_args, #{arg_list}) }"
133
+ end
134
+ (code_lines.length > 1) ? code_lines.join('; ') : ''
135
+ end
136
+
137
+ # Code that labels return value of a decorated method.
138
+ #
139
+ # @param [Array<Police::DataFlow::Label>] label_classes the label classes
140
+ # supported by the proxy class
141
+ # @param [Method] method_def the definition of the method to be proxied
142
+ # @return [String] a chunk of Ruby that can be used to invoke the return value
143
+ # decorators of the labels held by a labeled object's proxy
144
+ def self.proxy_return_decorating(label_classes, method_def)
145
+ method_name = method_def.name
146
+ arg_list = proxy_argument_list method_def, false
147
+ code_lines = ['labels = @__police_labels__']
148
+ label_classes.each do |label_class|
149
+ next unless hook = label_class.return_hook(method_name)
150
+ label_key = label_class.__id__
151
+ code_lines << "labels[#{label_key}].each { |label, _| " \
152
+ "return_value = label.#{hook}(return_value, self, #{arg_list}) }"
153
+ end
154
+ (code_lines.length > 1) ? code_lines.join('; ') : ''
155
+ end
156
+
157
+ # The list of arguments used to define a proxy for the given method.
158
+ #
159
+ # @param [Method] method_def the definition of the method to be proxied
160
+ # @param [Boolean] captue_block if true, the method captures the block that it
161
+ # receives
162
+ # @return [String] a chunk of Ruby that can be used as the argument list when
163
+ # defining a proxy for the given method
164
+ def self.proxy_argument_list(method_def, capture_block)
165
+ arg_list = if method_def.arity >= 0
166
+ # Fixed number of arguments.
167
+ (1..method_def.arity).map { |i| "arg#{i}" }
168
+ else
169
+ # Variable number of arguments.
170
+ ((1..(-method_def.arity - 1)).map { |i| "arg#{i}" } << '*args')
171
+ end
172
+ arg_list << '&block' if capture_block
173
+ arg_list.join ', '
174
+ end
175
+ end # namespace Police::DataFlow::Proxying
176
+
177
+ end # namespace Police::DataFlow
178
+
179
+ end # namespace Police
@@ -0,0 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "police-dataflow"
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Victor Costan"]
12
+ s.date = "2012-03-28"
13
+ s.description = "Pure Ruby implementtion of data flow label propagation."
14
+ s.email = "victor@costan.us"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/police-dataflow.rb",
28
+ "lib/police/dataflow.rb",
29
+ "lib/police/dataflow/core_extensions.rb",
30
+ "lib/police/dataflow/guarding.rb",
31
+ "lib/police/dataflow/label.rb",
32
+ "lib/police/dataflow/labeling.rb",
33
+ "lib/police/dataflow/proxies.rb",
34
+ "lib/police/dataflow/proxy_base.rb",
35
+ "lib/police/dataflow/proxying.rb",
36
+ "police-dataflow.gemspec",
37
+ "test/dataflow/core_extensions_test.rb",
38
+ "test/dataflow/labeling_test.rb",
39
+ "test/dataflow/proxies_test.rb",
40
+ "test/dataflow/proxy_base_test.rb",
41
+ "test/dataflow/proxying_test.rb",
42
+ "test/helper.rb",
43
+ "test/helpers/auto_flow_fixture.rb",
44
+ "test/helpers/no_flow_fixture.rb",
45
+ "test/helpers/proxying_fixture.rb"
46
+ ]
47
+ s.homepage = "http://github.com/csail/police"
48
+ s.licenses = ["MIT"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.21"
51
+ s.summary = "Data flow label propagation"
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ else
58
+ end
59
+ else
60
+ end
61
+ end
62
+
@@ -0,0 +1,51 @@
1
+ require File.expand_path('../helper.rb', File.dirname(__FILE__))
2
+
3
+ describe Police::DataFlow do
4
+ before do
5
+ @object = 'Hello'
6
+ @class = @object.class
7
+ @label = AutoFlowFixture.new
8
+ end
9
+
10
+ describe '#label' do
11
+ describe 'with a single label' do
12
+ before do
13
+ @object = Police::DataFlow.label @object, @label
14
+ end
15
+
16
+ it 'labels the object correctly' do
17
+ Police::DataFlow.labels(@object).must_equal [@label]
18
+ end
19
+
20
+ it 'is idempotent' do
21
+ same_object = Police::DataFlow.label @object, @label
22
+ same_object.must_equal @object
23
+ Police::DataFlow.labels(@object).must_equal [@label]
24
+ end
25
+
26
+ it 'returns a working proxy' do
27
+ @object.length.must_equal 5
28
+ @object.class.must_equal @class
29
+ end
30
+
31
+ it 'returns a label-preserving proxy' do
32
+ @object << ' world'
33
+ @object.length.must_equal 11
34
+ Police::DataFlow.labels(@object).must_equal [@label]
35
+ end
36
+ end
37
+
38
+ describe 'with two labels' do
39
+ before do
40
+ @label2 = NoFlowFixture.new
41
+ @object = Police::DataFlow.label @object, @label
42
+ @object = Police::DataFlow.label @object, @label2
43
+ end
44
+
45
+ it 'labels the object correctly' do
46
+ Set.new(Police::DataFlow.labels(@object)).
47
+ must_equal Set.new([@label, @label2])
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path('../helper.rb', File.dirname(__FILE__))
2
+
3
+ describe Police::DataFlow do
4
+ before do
5
+ @object = 'Hello'
6
+ @class = @object.class
7
+ @p_label = AutoFlowFixture.new
8
+ end
9
+
10
+ describe '#label' do
11
+ describe 'with a single label' do
12
+ before do
13
+ @object = Police::DataFlow.label @object, @p_label
14
+ end
15
+
16
+ it 'labels the object correctly' do
17
+ Police::DataFlow.labels(@object).must_equal [@p_label]
18
+ end
19
+
20
+ it 'is idempotent' do
21
+ same_object = Police::DataFlow.label @object, @p_label
22
+ same_object.must_equal @object
23
+ Police::DataFlow.labels(@object).must_equal [@p_label]
24
+ end
25
+
26
+ it 'is idempotent vs label type' do
27
+
28
+ end
29
+
30
+ it 'returns a working proxy' do
31
+ @object.length.must_equal 5
32
+ @object.class.must_equal @class
33
+ end
34
+
35
+ it 'returns a label-preserving proxy' do
36
+ @object << ' world'
37
+ @object.length.must_equal 11
38
+ Police::DataFlow.labels(@object).must_equal [@p_label]
39
+ end
40
+
41
+ it 'returns a label-propagating proxy' do
42
+ Police::DataFlow.labels(@object[2..5]).must_equal [@p_label]
43
+ end
44
+ end
45
+
46
+ describe 'with two labels' do
47
+ before do
48
+ @n_label = NoFlowFixture.new
49
+ @object = Police::DataFlow.label @object, @p_label
50
+ @object = Police::DataFlow.label @object, @n_label
51
+ end
52
+
53
+ it 'labels the object correctly' do
54
+ Set.new(Police::DataFlow.labels(@object)).
55
+ must_equal Set.new([@p_label, @n_label])
56
+ end
57
+
58
+ it 'propagates the labels correctly' do
59
+ Police::DataFlow.labels(@object[2..5]).must_equal [@p_label]
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#labels' do
65
+ it 'returns an empty array for un-labeled objects' do
66
+ Police::DataFlow.labels(@object).must_equal []
67
+ end
68
+
69
+ it 'returns a populated array for labeled objects' do
70
+ labeled = Police::DataFlow.label @object, @p_label
71
+ Police::DataFlow.labels(labeled).must_equal [@p_label]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../helper.rb', File.dirname(__FILE__))
2
+
3
+ describe Police::DataFlow::Proxies do
4
+ describe '#for' do
5
+ before do
6
+ @label_set = {}
7
+ @autoflow_set = {}
8
+ Police::DataFlow::Labeling.add_label_to_set NoFlowFixture.new,
9
+ @label_set, @autoflow_set
10
+ end
11
+
12
+ let(:result) do
13
+ Police::DataFlow::Proxies.for ProxyingFixture, @label_set
14
+ end
15
+ after { Police::DataFlow::Proxies.clear_cache }
16
+
17
+ it 'creates a Police::DataFlow::ProxyBase subclass' do
18
+ result.superclass.must_equal Police::DataFlow::ProxyBase
19
+ end
20
+
21
+ it 'caches classes' do
22
+ Police::DataFlow::Proxies.for(ProxyingFixture, @label_set).
23
+ must_equal result
24
+ end
25
+
26
+ it 'creates different proxies for different classes' do
27
+ Police::DataFlow::Proxies.for(Class.new(ProxyingFixture), @label_set).
28
+ wont_equal result
29
+ end
30
+
31
+ it 'creates different proxies for sets of different label classes' do
32
+ label_set = @label_set.clone
33
+ autoflow_set = @autoflow_set.clone
34
+ Police::DataFlow::Labeling.add_label_to_set AutoFlowFixture.new,
35
+ label_set, autoflow_set
36
+ Police::DataFlow::Proxies.for(ProxyingFixture, label_set).
37
+ wont_equal result
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,145 @@
1
+ require File.expand_path('../helper.rb', File.dirname(__FILE__))
2
+
3
+ describe Police::DataFlow::ProxyBase do
4
+ before do
5
+ @label = AutoFlowFixture.new
6
+ @label_set = {}
7
+ @autoflow_set = {}
8
+ Police::DataFlow::Labeling.add_label_to_set @label, @label_set,
9
+ @autoflow_set
10
+ @proxied = ProxyingFixture.new
11
+ @proxy_class = Police::DataFlow::Proxies.for ProxyingFixture, @label_set
12
+ @proxy = @proxy_class.new @proxied, @proxy_class, @label_set, @autoflow_set
13
+ end
14
+ after { Police::DataFlow::Proxies.clear_cache }
15
+
16
+ it 'proxies public methods' do
17
+ # NOTE: this test exercises the slow path in method_missing
18
+ @proxy.route('One', 'Two').must_equal ['One', 'Two']
19
+ end
20
+
21
+ it 'allows labels to filter the return value of public methods' do
22
+ # NOTE: this test exercises the slow path in method_missing
23
+ Police::DataFlow.labels(@proxy.route('One', 'Two')).must_equal [@label]
24
+ end
25
+
26
+ describe 'after proxying public methods' do
27
+ before { @proxy.route 'One', 'Two' }
28
+
29
+ it 'defines proxied methods on the fly' do
30
+ @proxy_class.public_method_defined?(:route).must_equal true
31
+ @proxy_class.instance_method(:route).owner.must_equal @proxy_class
32
+ end
33
+
34
+ it 'still proxies public methods' do
35
+ # NOTE: this test exercises the auto-generated proxy method's fast path
36
+ @proxy.route('One', 'Two').must_equal ['One', 'Two']
37
+ end
38
+
39
+ it 'still allows labels to filter the return value of public methods' do
40
+ # NOTE: this test exercises the auto-generated proxy method's fast path
41
+ Police::DataFlow.labels(@proxy.route('One', 'Two')).must_equal [@label]
42
+ end
43
+
44
+ it 'can still build proxies' do
45
+ other_proxy = @proxy_class.new ProxyingFixture.new, @proxy_class,
46
+ @label_set, @autoflow_set
47
+ other_proxy.route('One', 'Two').must_equal ['One', 'Two']
48
+ end
49
+ end
50
+
51
+ it 'proxies public methods with blocks' do
52
+ # NOTE: this test exercises the slow path in method_missing
53
+ result = []
54
+ @proxy.route('One', 'Two') { |*args| result << args }
55
+ result.must_equal [['One', 'Two']]
56
+ end
57
+
58
+ it 'allows labels to filter the yielded values of public methods' do
59
+ # NOTE: this test exercises the slow path in method_missing
60
+ @proxy.route('One', 'Two') do |*args|
61
+ args.each { |arg| Police::DataFlow.labels(arg).must_equal [@label] }
62
+ end
63
+ end
64
+
65
+ describe 'after proxying public methods with blocks' do
66
+ before { @proxy.route('One', 'Two') { |*args| } }
67
+
68
+ it 'defines proxied methods on the fly' do
69
+ @proxy_class.public_method_defined?(:route).must_equal true
70
+ @proxy_class.instance_method(:route).owner.must_equal @proxy_class
71
+ end
72
+
73
+ it 'still proxies public methods with blocks' do
74
+ # NOTE: this test exercises the auto-generated proxy method's fast path
75
+ result = []
76
+ @proxy.route('One', 'Two') { |*args| result << args }
77
+ result.must_equal [['One', 'Two']]
78
+ end
79
+
80
+ it 'still allows labels to filter the yielded values of public methods' do
81
+ # NOTE: this test exercises the auto-generated proxy method's fast path
82
+ @proxy.route('One', 'Two') do |*args|
83
+ args.each { |arg| Police::DataFlow.labels(arg).must_equal [@label] }
84
+ end
85
+ end
86
+ end
87
+
88
+ it 'proxies protected methods' do
89
+ # NOTE: this test exercises the slow path in method_missing
90
+ @proxy.__send__(:add, 'One', 'Two').must_equal 'One, Two'
91
+ end
92
+
93
+ it 'allows labels to filter the return value of protected methods' do
94
+ # NOTE: this test exercises the slow path in method_missing
95
+ Police::DataFlow.labels(@proxy.__send__(:add, 'One', 'Two')).
96
+ must_equal [@label]
97
+ end
98
+
99
+
100
+ describe 'after proxying protected methods' do
101
+ before { @proxy.__send__ :add, 'One', 'Two' }
102
+
103
+ it 'defines proxied methods on the fly' do
104
+ @proxy_class.protected_method_defined?(:add).must_equal true
105
+ @proxy_class.instance_method(:add).owner.must_equal @proxy_class
106
+ end
107
+
108
+ it 'still proxies protected methods' do
109
+ # NOTE: this test exercises the auto-generated proxy method's fast path
110
+ @proxy.__send__(:add, 'One', 'Two').must_equal 'One, Two'
111
+ end
112
+
113
+ it 'still allows labels to filter the return value of protected methods' do
114
+ # NOTE: this test exercises the auto-generated proxy method's fast path
115
+ Police::DataFlow.labels(@proxy.__send__(:add, 'One', 'Two')).
116
+ must_equal [@label]
117
+ end
118
+ end
119
+
120
+ it 'proxies magic methods' do
121
+ @proxy.magic_meth('One', 'Two').must_equal ['meth', 'One', 'Two']
122
+ end
123
+
124
+ it 'allows labels to filter the return value of magic methods' do
125
+ Police::DataFlow.labels(@proxy.magic_meth('One', 'Two')).must_equal [@label]
126
+ end
127
+
128
+ describe 'after proxying magic methods' do
129
+ before do
130
+ @proxy.magic_meth 'One', 'Two'
131
+ end
132
+
133
+ it 'does not define magic proxied methods' do
134
+ @proxy_class.public_method_defined?(:magic_meth).must_equal false
135
+ end
136
+ end
137
+
138
+ it 'proxies ==' do
139
+ (@proxy == nil).must_equal '== proxied'
140
+ end
141
+
142
+ it 'proxies !=' do
143
+ (@proxy != nil).must_equal '!= proxied'
144
+ end
145
+ end