police-dataflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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