police-dataflow 0.0.1 → 0.0.2
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.
- checksums.yaml +15 -0
- data/Gemfile +8 -7
- data/Gemfile.lock +57 -18
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/lib/police/dataflow.rb +2 -1
- data/lib/police/dataflow/core_extensions.rb +18 -4
- data/lib/police/dataflow/gate_profiles/ruby1.9.3 +167 -0
- data/lib/police/dataflow/gating.rb +70 -0
- data/lib/police/dataflow/label.rb +19 -14
- data/lib/police/dataflow/labeling.rb +103 -22
- data/lib/police/dataflow/proxies.rb +33 -5
- data/lib/police/dataflow/proxy_base.rb +80 -40
- data/lib/police/dataflow/proxy_numeric.rb +22 -0
- data/lib/police/dataflow/proxying.rb +215 -40
- data/police-dataflow.gemspec +11 -15
- data/tasks/info.rake +43 -0
- data/test/dataflow/core_extensions_test.rb +8 -8
- data/test/dataflow/labeling_test.rb +324 -15
- data/test/dataflow/proxies_test.rb +7 -11
- data/test/dataflow/proxy_base_test.rb +199 -72
- data/test/dataflow/proxy_numeric_test.rb +45 -0
- data/test/dataflow/proxying_test.rb +333 -80
- data/test/helper.rb +2 -2
- data/test/helpers/hooks_flow_fixture.rb +22 -0
- data/test/helpers/no_flow_fixture.rb +4 -4
- data/test/helpers/proxying_fixture.rb +11 -7
- data/test/helpers/sticky_fixture.rb +14 -0
- metadata +12 -12
- data/lib/police/dataflow/guarding.rb +0 -16
- data/test/helpers/auto_flow_fixture.rb +0 -6
@@ -3,37 +3,35 @@ module Police
|
|
3
3
|
module DataFlow
|
4
4
|
# Attaches a label to a piece of data.
|
5
5
|
#
|
6
|
-
# @param [
|
7
|
-
# @param [Police::DataFlow::Label] label the label to be applied to the
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# @param [BasicObject] data the data that will be labeled
|
7
|
+
# @param [Police::DataFlow::Label] label the label to be applied to the
|
8
|
+
# object
|
9
|
+
# @return [BasicObject] either the given piece of data, or a proxy that
|
10
|
+
# should be used instead of it
|
10
11
|
def self.label(data, label)
|
11
12
|
label_set = data.__police_labels__
|
12
|
-
if label_set
|
13
|
+
if nil == label_set
|
13
14
|
proxied = data
|
14
15
|
label_set = {}
|
15
|
-
autoflow_set = {}
|
16
16
|
else
|
17
17
|
proxied = data.__police_proxied__
|
18
|
-
autoflow_set = data.__police_autoflows__
|
19
18
|
end
|
20
|
-
|
21
|
-
if Police::DataFlow::Labeling.add_label_to_set label, label_set
|
22
|
-
|
23
|
-
data = Police::DataFlow::Proxying.proxy proxied, label_set, autoflow_set
|
24
|
-
data
|
19
|
+
|
20
|
+
if Police::DataFlow::Labeling.add_label_to_set label, label_set
|
21
|
+
data = Police::DataFlow::Proxying.proxy proxied, label_set
|
25
22
|
end
|
26
23
|
data
|
27
24
|
end
|
28
|
-
|
25
|
+
|
29
26
|
# All the labels attached to a piece of data.
|
30
27
|
#
|
31
28
|
# @param [Object] data the data whose labels are queried
|
32
|
-
# @return [Array<Police::DataFlow::Label>] all the labels attached to the
|
29
|
+
# @return [Array<Police::DataFlow::Label>] all the labels attached to the
|
30
|
+
# data
|
33
31
|
def self.labels(data)
|
34
32
|
return [] unless label_set = data.__police_labels__
|
35
33
|
return label_set.first.last.keys if label_set.length == 1
|
36
|
-
|
34
|
+
|
37
35
|
labels = []
|
38
36
|
label_set.each { |label_key, label_hash| labels.concat label_hash.keys }
|
39
37
|
labels
|
@@ -41,31 +39,114 @@ module DataFlow
|
|
41
39
|
|
42
40
|
# Label algebra.
|
43
41
|
module Labeling
|
42
|
+
# The actual class of a proxy object.
|
43
|
+
#
|
44
|
+
# This is necessary because the class method for a proxy object must return
|
45
|
+
# the proxied object's class.
|
46
|
+
#
|
47
|
+
# @private
|
48
|
+
# This is intended to help testing the proxying code. It should not be used
|
49
|
+
# by client code.
|
50
|
+
#
|
51
|
+
# @param [BasicObject] data a proxy returned by {Police::DataFlow#label}
|
52
|
+
# @return [Class] a subclass of {Police::DataFlow::ProxyBase}, or nil if the
|
53
|
+
# given object is not a proxy
|
54
|
+
def self.proxy_class(data)
|
55
|
+
if data.__police_labels__.nil?
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
data.__police_class__
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
44
62
|
# Adds a label to the set of labels held by an object's proxy.
|
45
63
|
#
|
64
|
+
# @param [Police::DataFlow::Label] label the label to be added to the set
|
46
65
|
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
|
47
66
|
# set of all labels that will be held by the object's proxy
|
48
|
-
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] autoflow_set
|
49
|
-
# the set of labels whose autoflow? method returned true
|
50
67
|
# @return [Boolean] false if the set already had a label of the same type, so
|
51
68
|
# the proxy holding the set can still be used; true if the set had to be
|
52
69
|
# expanded, so a new proxy is needed
|
53
|
-
def self.add_label_to_set(label, label_set
|
70
|
+
def self.add_label_to_set(label, label_set)
|
54
71
|
label_class = label.class
|
55
72
|
label_key = label_class.__id__
|
56
73
|
if label_set.has_key? label_key
|
57
74
|
label_set[label_key][label] = true
|
58
|
-
# NOTE: autoflow_set uses use the same hash, so no work is necessary
|
59
75
|
return false
|
60
76
|
end
|
61
|
-
|
77
|
+
|
62
78
|
label_entry = { label => true }
|
63
79
|
label_set[label_key] = label_entry
|
64
|
-
autoflow_set[label_key] = label_entry if label_class.autoflow?
|
65
80
|
true
|
66
81
|
end
|
82
|
+
|
83
|
+
# Merges a set of labels into another set.
|
84
|
+
#
|
85
|
+
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] target_set one
|
86
|
+
# of the label sets to be merged; this set will be modified by the method
|
87
|
+
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] source_set the
|
88
|
+
# other label set that will be merged; this set will not be modified
|
89
|
+
# @return [Boolean] false if the target set already had labels of all the
|
90
|
+
# types in the source set, so the proxy holding the target object can
|
91
|
+
# still be used; true if the target set had to be expanded, so a new
|
92
|
+
# proxy is needed
|
93
|
+
def self.merge_sets!(target_set, source_set)
|
94
|
+
expanded = false
|
95
|
+
source_set.each do |class_id, label_classes|
|
96
|
+
target_classes = target_set[class_id]
|
97
|
+
if nil == target_classes
|
98
|
+
target_set[class_id] = label_classes.dup
|
99
|
+
expanded = true
|
100
|
+
else
|
101
|
+
target_classes.merge! label_classes
|
102
|
+
end
|
103
|
+
end
|
104
|
+
expanded
|
105
|
+
end
|
106
|
+
|
107
|
+
# Applies sticky labels to a piece of data.
|
108
|
+
#
|
109
|
+
# @private
|
110
|
+
# This is an version of {Police::DataFlow.label} optimized for sticky label
|
111
|
+
# propagation. User code should not depend on it.
|
112
|
+
#
|
113
|
+
# @param [BasicObject] data the data that will be labeled
|
114
|
+
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] sticky_set a
|
115
|
+
# set of sticky labels that
|
116
|
+
# @param [Police::DataFlow::Label] label the label to be applied to the
|
117
|
+
# object
|
118
|
+
# @return [BasicObject] either the given piece of data, or a proxy that
|
119
|
+
# should be used instead of it
|
120
|
+
def self.bulk_sticky_label(data, sticky_set)
|
121
|
+
label_set = data.__police_labels__
|
122
|
+
if nil == label_set
|
123
|
+
# Unlabeled data.
|
124
|
+
return Police::DataFlow::Proxying.proxy(data, dup_set(sticky_set))
|
125
|
+
end
|
126
|
+
|
127
|
+
# TODO(pwnall): implement copy-on-write to waste less memory on gated ops
|
128
|
+
if merge_sets! label_set, sticky_set
|
129
|
+
Police::DataFlow::Proxying.proxy data.__police_proxied__, label_set
|
130
|
+
else
|
131
|
+
data
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Creates a shallow copy of a label set.
|
136
|
+
#
|
137
|
+
# The resulting copy is slightly deeper than what {Hash#clone} would produce,
|
138
|
+
# because the groups are copied as well.
|
139
|
+
#
|
140
|
+
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
|
141
|
+
# set of labels that will be copied
|
142
|
+
# @return [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set a
|
143
|
+
# copy of the given label set that can be used with another object's
|
144
|
+
# proxy
|
145
|
+
def self.dup_set(label_set)
|
146
|
+
Hash[label_set.map { |k, v| [k, v.dup] } ]
|
147
|
+
end
|
67
148
|
end # namespace Police::DataFlow::Labeling
|
68
|
-
|
149
|
+
|
69
150
|
end # namespace Police::DataFlow
|
70
151
|
|
71
152
|
end # namespace Police
|
@@ -9,7 +9,7 @@ module Proxies
|
|
9
9
|
# @param [Class] proxied_class the class whose instances will be proxied by
|
10
10
|
# instances of the returned class
|
11
11
|
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
|
12
|
-
# set of all labels that will be
|
12
|
+
# set of all labels that will be held by the proxy
|
13
13
|
# @return [Class] a Police::DataFlow::ProxyBase subclass that can proxy
|
14
14
|
# instances of the given class
|
15
15
|
def self.for(proxied_class, label_set)
|
@@ -17,20 +17,48 @@ module Proxies
|
|
17
17
|
class_cache = {}
|
18
18
|
@classes[proxied_class] = class_cache
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
cache_key = label_set.keys.sort!.freeze
|
22
22
|
return class_cache[cache_key] if class_cache.has_key? cache_key
|
23
|
-
|
23
|
+
|
24
24
|
label_classes = label_set.map { |label_key, label_hash|
|
25
25
|
label_hash.first.first.class
|
26
26
|
}.sort_by!(&:__id__).freeze
|
27
27
|
|
28
|
-
proxy_class =
|
29
|
-
proxy_class.__police_classes__ = label_classes
|
28
|
+
proxy_class = for! proxied_class, label_classes
|
30
29
|
class_cache[cache_key] = proxy_class
|
31
30
|
proxy_class
|
32
31
|
end
|
33
32
|
|
33
|
+
# A class whose instances proxy instances of a Ruby class.
|
34
|
+
#
|
35
|
+
# @private
|
36
|
+
# Use for instead of calling this directly.
|
37
|
+
#
|
38
|
+
# @param [Class] proxied_class the class whose instances will be proxied by
|
39
|
+
# instances of the returned class
|
40
|
+
# @param [Array<Class>] label_classes classes for all the labels that will be
|
41
|
+
# held by proxies instantiated from this class; the array should not have
|
42
|
+
# duplicates
|
43
|
+
# @return [Class] a Police::DataFlow::ProxyBase subclass that can proxy
|
44
|
+
# instances of the given class
|
45
|
+
def self.for!(proxied_class, label_classes)
|
46
|
+
proxy_class = nil
|
47
|
+
klass = proxied_class
|
48
|
+
until klass == nil
|
49
|
+
if klass == String
|
50
|
+
# TODO(pwnall): String-specific proxying
|
51
|
+
break
|
52
|
+
elsif klass == Numeric
|
53
|
+
proxy_class = Class.new Police::DataFlow::ProxyNumeric
|
54
|
+
end
|
55
|
+
klass = klass.superclass
|
56
|
+
end
|
57
|
+
proxy_class ||= Class.new Police::DataFlow::ProxyBase
|
58
|
+
proxy_class.__police_classes__ = label_classes
|
59
|
+
proxy_class
|
60
|
+
end
|
61
|
+
|
34
62
|
# Clears the cache of proxy classes associated with Ruby classes.
|
35
63
|
#
|
36
64
|
# This method has a terrible impact on VM performance, and is only intended
|
@@ -7,43 +7,62 @@ class ProxyBase < BasicObject
|
|
7
7
|
# The object being proxied by this object.
|
8
8
|
#
|
9
9
|
# @private
|
10
|
-
# Use the Police::DataFlow API instead of reading this attribute directly.
|
10
|
+
# Use the {Police::DataFlow} API instead of reading this attribute directly.
|
11
11
|
attr_reader :__police_proxied__
|
12
|
-
|
13
|
-
# The Label instances attached to the proxied object.
|
12
|
+
|
13
|
+
# The {Police::DataFlow::Label} instances attached to the proxied object.
|
14
|
+
#
|
15
|
+
# This is a Hash whose keys are the __id__s of the Class objects for the
|
16
|
+
# labels' classes. The values are sets of labels that are instances of the
|
17
|
+
# corresponding label classes. Sets are implemented as Hashes mapping the
|
18
|
+
# label instances to boolean true values.
|
14
19
|
#
|
15
20
|
# @private
|
16
|
-
# Use the Police::DataFlow API instead of reading this attribute directly.
|
21
|
+
# Use the {Police::DataFlow} API instead of reading this attribute directly.
|
17
22
|
attr_reader :__police_labels__
|
18
|
-
|
19
|
-
#
|
23
|
+
|
24
|
+
# Attached {Police::DataFlow::Label} instances whose classes are sticky.
|
25
|
+
#
|
26
|
+
# @private
|
27
|
+
# This is a proxying implementation detail and should not be relied on.
|
28
|
+
attr_reader :__police_stickies__
|
29
|
+
|
30
|
+
# The actual class of a proxy object.
|
31
|
+
#
|
32
|
+
# This is necessary because the class method for a proxy object must return
|
33
|
+
# the proxied object's class.
|
20
34
|
#
|
21
35
|
# @private
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
36
|
+
# Use the {Police::DataFlow} API instead of reading this attribute directly.
|
37
|
+
attr_reader :__police_class__
|
38
|
+
|
26
39
|
# Creates a proxied object.
|
27
40
|
#
|
28
41
|
# @param [Object] proxied the object that will receive messages sent to the
|
29
42
|
# newly created proxy
|
30
43
|
# @param [Class<Police::DataFlow::ProxyBase>] proxy_class the
|
31
|
-
# Police::DataFlow::ProxyBase subclass being instantiated; Object
|
44
|
+
# {Police::DataFlow::ProxyBase} subclass being instantiated; Object
|
32
45
|
# instances can call Object#class to get to their class, but BasicObject
|
33
46
|
# instances don't have this luxury
|
34
47
|
# @param [Hash<Integer,Hash<Police::DataFlow::Label,Boolean>>] label_set the
|
35
48
|
# set of all labels that will be held by the object's proxy
|
36
|
-
|
37
|
-
# the set of labels whose autoflow? method returned true
|
38
|
-
def initialize(proxied, proxy_class, label_set, autoflow_set)
|
49
|
+
def initialize(proxied, proxy_class, label_set)
|
39
50
|
@__police_proxied__ = proxied
|
40
51
|
@__police_labels__ = label_set
|
41
52
|
|
53
|
+
# Cache the sticky labels to speed up certain computations.
|
54
|
+
@__police_stickies__ = {}
|
55
|
+
@__police_labels__.each do |class_id, instance_set|
|
56
|
+
label_class = instance_set.first.first.class
|
57
|
+
next unless label_class.sticky?
|
58
|
+
# NOTE: the set of instances is intentionally shared with
|
59
|
+
# @__police_labels__ so updates to the former are automatically
|
60
|
+
# reflected in the stickies cache
|
61
|
+
@__police_stickies__[class_id] = instance_set
|
62
|
+
end
|
63
|
+
|
42
64
|
# Holds the object's class, because Object#class is not available.
|
43
65
|
@__police_class__ = proxy_class
|
44
|
-
|
45
|
-
# Labels that flow automatically across method calls.
|
46
|
-
@__police_autoflows__ = autoflow_set
|
47
66
|
end
|
48
67
|
|
49
68
|
# Handles method calls to the proxied object.
|
@@ -54,77 +73,98 @@ class ProxyBase < BasicObject
|
|
54
73
|
# Build a fast path for future method calls, if possible.
|
55
74
|
respond_to_missing? name, true
|
56
75
|
|
76
|
+
# NOTE: going out of our way to use the fast path methods, because they
|
77
|
+
# handle native method argument unwrapping correctly
|
78
|
+
if @__police_class__.method_defined?(name) ||
|
79
|
+
@__police_class__.private_method_defined?(name)
|
80
|
+
return __send__(name, *args, &block)
|
81
|
+
end
|
82
|
+
|
57
83
|
if block
|
58
84
|
return_value = @__police_proxied__.__send__ name, *args do |*yield_args|
|
59
85
|
# Yielded values filtering.
|
60
86
|
@__police_labels__.each do |_, label_hash|
|
61
|
-
|
62
|
-
|
63
|
-
|
87
|
+
label_class = label_hash.first.first.class
|
88
|
+
if hook = label_class.yield_args_hook(name)
|
89
|
+
label_hash.each do |label, _|
|
90
|
+
yield_args = label.__send__ hook, self, yield_args, *args
|
91
|
+
end
|
92
|
+
elsif label_class.sticky?
|
93
|
+
label_hash.each do |label, _|
|
94
|
+
yield_args.map! { |arg| ::Police::DataFlow.label arg, label }
|
95
|
+
end
|
64
96
|
end
|
65
97
|
end
|
66
|
-
|
98
|
+
|
67
99
|
yield_return = yield(*yield_args)
|
68
100
|
# TODO(pwnall): consider adding a yield value filter
|
69
101
|
next yield_return
|
70
102
|
end
|
71
103
|
else
|
72
|
-
return_value = @__police_proxied__.__send__ name, *args
|
104
|
+
return_value = @__police_proxied__.__send__ name, *args
|
73
105
|
end
|
74
|
-
|
106
|
+
|
75
107
|
# Return value filtering.
|
76
108
|
@__police_labels__.each do |_, label_hash|
|
77
|
-
|
78
|
-
|
79
|
-
|
109
|
+
label_class = label_hash.first.first.class
|
110
|
+
if hook = label_hash.first.first.class.return_hook(name)
|
111
|
+
label_hash.each do |label, _|
|
112
|
+
return_value = label.__send__ hook, return_value, self, *args
|
113
|
+
end
|
114
|
+
elsif label_class.sticky?
|
115
|
+
label_hash.each do |label, _|
|
116
|
+
return_value = ::Police::DataFlow.label return_value, label
|
117
|
+
end
|
80
118
|
end
|
81
119
|
end
|
82
120
|
return return_value
|
83
121
|
end
|
84
|
-
|
122
|
+
|
85
123
|
# Called when Object#respond_to? returns false.
|
86
124
|
def respond_to_missing?(name, include_private)
|
87
125
|
return false unless @__police_proxied__.respond_to? name, include_private
|
88
|
-
|
126
|
+
|
89
127
|
# A method on the proxied object doesn't have a corresponding proxy.
|
90
128
|
# Fix this by creating all possible proxies.
|
91
|
-
|
129
|
+
|
92
130
|
# NOTE: this approach is cheaper than creating proxies one by one, because
|
93
131
|
# it plays nice with method caches
|
94
|
-
|
95
|
-
::Police::DataFlow::Proxying.
|
96
|
-
|
97
|
-
|
132
|
+
|
133
|
+
::Police::DataFlow::Proxying.add_instance_methods @__police_class__,
|
134
|
+
@__police_proxied__.class
|
135
|
+
|
98
136
|
# NOTE: we don't want to create unnecessary singleton classes
|
99
137
|
# target_methods = @__police_proxied__.singleton_methods true
|
100
138
|
# unless target_methods.empty?
|
101
139
|
# ::Police::DataFlow::Proxying.add_singleton_methods self,
|
102
140
|
# @__police_proxied__, target_methods
|
103
141
|
# end
|
104
|
-
|
142
|
+
|
105
143
|
true
|
106
144
|
end
|
107
|
-
|
145
|
+
|
108
146
|
# Ruby 1.9 throws scary warnings if proxies define object_id.
|
109
147
|
# In either case, it's probably best to have it match __id__.
|
110
148
|
alias object_id __id__
|
111
|
-
|
149
|
+
|
112
150
|
# Remove the == and != implementations from BasicObject, so that we can proxy
|
113
151
|
# them. This is particularly important for String.
|
114
152
|
#
|
115
153
|
# NOTE: We don't remove equal?, because Object's documentation says that
|
116
154
|
# BasicObject subclasses should really not override it. We also don't
|
117
|
-
# remove instance_eval and instance_exec, so the code that gets
|
118
|
-
# using them will still have its method calls proxied
|
155
|
+
# remove instance_eval and instance_exec, so the code that gets
|
156
|
+
# executed using them will still have its method calls proxied
|
157
|
+
# correctly.
|
119
158
|
undef ==, !=
|
120
159
|
|
121
160
|
class <<self
|
122
161
|
# The classes of the labels supported by the proxy class.
|
123
162
|
#
|
124
163
|
# @private
|
125
|
-
# This is a Police::DataFlow implementation detail. Do not read it
|
164
|
+
# This is a Police::DataFlow implementation detail. Do not read it
|
165
|
+
# directly.
|
126
166
|
attr_accessor :__police_classes__
|
127
|
-
end
|
167
|
+
end
|
128
168
|
end # class Police::DataFlow::ProxyBase
|
129
169
|
|
130
170
|
end # namespace Police::DataFlow
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Police
|
2
|
+
|
3
|
+
module DataFlow
|
4
|
+
|
5
|
+
# Base class for labeled Numeric replacements.
|
6
|
+
class ProxyNumeric < ProxyBase
|
7
|
+
# Called when a regular Numeric is added, multiplied, etc to a proxied one.
|
8
|
+
#
|
9
|
+
# Wraps the regular Numeric instance with a proxy, so that call dispatch can
|
10
|
+
# take place.
|
11
|
+
def coerce(numeric)
|
12
|
+
if numeric.__police_labels__
|
13
|
+
return [numeric, self]
|
14
|
+
end
|
15
|
+
proxied_numeric = ::Police::DataFlow::Proxying.proxy numeric, {}
|
16
|
+
[proxied_numeric, self]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end # namespace DataFlow
|
21
|
+
|
22
|
+
end # namespace Police
|