safer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/History.txt +4 -0
- data/Manifest.txt +10 -0
- data/README.txt +69 -0
- data/Rakefile +8 -0
- data/lib/safer/ivar.rb +127 -0
- data/lib/safer/protocol.rb +335 -0
- data/lib/safer.rb +8 -0
- data/test/test_safer_ivar.rb +85 -0
- data/test/test_safer_protocol.rb +144 -0
- metadata +122 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
= safer
|
2
|
+
|
3
|
+
http://safer.rubyforge.org/
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Safer is an umbrella library, with components designed to make it simple to
|
8
|
+
verify and improve the safety of your ruby code. There are at present two
|
9
|
+
modules under the safer umbrella:
|
10
|
+
|
11
|
+
[<tt>Safer::IVar</tt>] generates specially-named accessor functions
|
12
|
+
for class instance variables.
|
13
|
+
[<tt>Safer::ClassProtocol</tt>] is used to provide a ruby analogue to
|
14
|
+
Objective-C Protocols (which are similar to
|
15
|
+
Java interfaces, but do not require
|
16
|
+
inheritance).
|
17
|
+
|
18
|
+
== FEATURES/PROBLEMS:
|
19
|
+
|
20
|
+
* Name instance variables after the class in which they are defined. Generate
|
21
|
+
accessor functions for these instance variables.
|
22
|
+
* Define an "Protocol" class, and derive a signature from it. Verify that
|
23
|
+
another class (or an instance of that class) implements the protocol
|
24
|
+
signature.
|
25
|
+
|
26
|
+
== SYNOPSIS:
|
27
|
+
|
28
|
+
require 'safer/ivar'
|
29
|
+
require 'safer/protocol'
|
30
|
+
|
31
|
+
module MyModule
|
32
|
+
class MyProtocolClass
|
33
|
+
def self.class_method_foo(arg1, arg2)
|
34
|
+
end
|
35
|
+
def instance_method(arg3)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
PROTOCOL = Safer::Protocol.create_from_class(MyProtocolClass)
|
39
|
+
class MyClass
|
40
|
+
Safer::IVar.instance_variable(self, :var1, :var2)
|
41
|
+
def initialize(var1, var2)
|
42
|
+
PROTOCOL.check_instance_conforms(var1)
|
43
|
+
PROTOCOL.check_instance_conforms(var2)
|
44
|
+
self.mymodule_myclass__var1 = var1
|
45
|
+
self.mymodule_myclass__var2 = var2
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
== REQUIREMENTS:
|
51
|
+
|
52
|
+
* None
|
53
|
+
|
54
|
+
== INSTALL:
|
55
|
+
|
56
|
+
* sudo gem install
|
57
|
+
|
58
|
+
== DEVELOPERS:
|
59
|
+
|
60
|
+
After checking out the source, run:
|
61
|
+
|
62
|
+
$ rake newb
|
63
|
+
|
64
|
+
This task will install any missing dependencies, run the tests/specs,
|
65
|
+
and generate the RDoc.
|
66
|
+
|
67
|
+
== LICENSE:
|
68
|
+
|
69
|
+
This software is placed into the public domain.
|
data/Rakefile
ADDED
data/lib/safer/ivar.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Safer
|
2
|
+
##
|
3
|
+
# Create accessor functions for instance variables, in which the accessor
|
4
|
+
# function is prefixed with the name of the class in which the instance
|
5
|
+
# variable is defined.
|
6
|
+
module IVar
|
7
|
+
|
8
|
+
##
|
9
|
+
# Given a class name, derive the prefix string for the accessor functions
|
10
|
+
# that instance_variable will create.
|
11
|
+
def self.class_symbol_prefix(klass)
|
12
|
+
klass.to_s.split('::').inject(nil) do |memo, el|
|
13
|
+
# reset the symbol name when an anonymous class is encountered.
|
14
|
+
if /^\#<.*>$/.match(el)
|
15
|
+
nil
|
16
|
+
elsif memo
|
17
|
+
memo + '_' + el.downcase
|
18
|
+
else
|
19
|
+
el.downcase
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# compute accessor routine symbol names, so that the names include the
|
26
|
+
# entire class namespace.
|
27
|
+
# [+klass+] Class object for which to generate a symbol name.
|
28
|
+
# [+nmlist+] Array of +Symbol+ objects.
|
29
|
+
# [_return_] Array of +Symbol+ objects generated from +klass+ and each
|
30
|
+
# element of +nmlist+.
|
31
|
+
# For example, given the following listing:
|
32
|
+
# class MyClass
|
33
|
+
# class SubClass
|
34
|
+
# SYMNM = Safer::IVar::symbol_names(self, :foo)
|
35
|
+
# puts(SYMNM)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# the following output would be produced:
|
39
|
+
# myclass_subclass__foo
|
40
|
+
def self.symbol_names(klass, nmlist)
|
41
|
+
kname = self.class_symbol_prefix(klass)
|
42
|
+
|
43
|
+
nmlist.map do |nm|
|
44
|
+
(kname + '__' + nm.to_s).to_sym
|
45
|
+
end
|
46
|
+
end # Safer::IVar::symbol_names
|
47
|
+
|
48
|
+
##
|
49
|
+
# create accessor routines for an instance variable, with variable name
|
50
|
+
# determined by the class in which the instance variable was declared.
|
51
|
+
# [+klass+] Class object into which to generate the variable accessor
|
52
|
+
# functions.
|
53
|
+
# [+nmlist+] List of symbols for which to create accessor routines.
|
54
|
+
# Uses Safer::IVar::symbol_names to determine the symbol names of the
|
55
|
+
# accessor routines.
|
56
|
+
# For example, the listing:
|
57
|
+
# class MyClass
|
58
|
+
# class SubClass
|
59
|
+
# Safer::IVar::instance_variable(self, :foo, :bar)
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# is equivalent to the following code:
|
63
|
+
# class MyClass
|
64
|
+
# class SubClass
|
65
|
+
# attr_accessor :myclass_subclass__foo
|
66
|
+
# attr_accessor :myclass_subclass__bar
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
# The name-mangling signals that these instance variables are, for all
|
70
|
+
# intents and purposes, private to +klass+.
|
71
|
+
def self.instance_variable(klass, *nmlist)
|
72
|
+
self.symbol_names(klass, nmlist).each do |symnm|
|
73
|
+
klass.class_eval do
|
74
|
+
attr_accessor symnm
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end # Safer::IVar::instance_variable
|
78
|
+
|
79
|
+
##
|
80
|
+
# export the reader routines for instance variables in a nicer way.
|
81
|
+
# [+klass+] Class object for which to define reader accessors for
|
82
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
83
|
+
# [+nmlist+] List of symbols for which to define reader accessors.
|
84
|
+
# Each symbol in +nmlist+ should have previously been given as
|
85
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
86
|
+
def self.export_reader(klass, *nmlist)
|
87
|
+
symlist = self.symbol_names(klass, nmlist)
|
88
|
+
nmlist.size.times do |index|
|
89
|
+
thisnm = nmlist[index]
|
90
|
+
thissym = symlist[index]
|
91
|
+
klass.class_eval("def #{thisnm} ; self.#{thissym} ; end")
|
92
|
+
end
|
93
|
+
end # Safer::IVar::export_reader
|
94
|
+
|
95
|
+
##
|
96
|
+
# export the writer routines for instance variables in a nicer way.
|
97
|
+
# [+klass+] Class object for which to define writer accessors for
|
98
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
99
|
+
# [+nmlist+] List of symbols for which to define writer accessors.
|
100
|
+
# Each symbol in +nmlist+ should have previously been given as
|
101
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
102
|
+
def self.export_writer(klass, *nmlist)
|
103
|
+
symlist = self.symbol_names(klass, nmlist)
|
104
|
+
nmlist.size.times do |index|
|
105
|
+
thisnm = nmlist[index]
|
106
|
+
thissym = symlist[index]
|
107
|
+
klass.class_eval(
|
108
|
+
"def #{thisnm}=(value) ; self.#{thissym} = value ; end"
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end # Safer::IVar::export_writer
|
112
|
+
|
113
|
+
##
|
114
|
+
# export both reader and writer routines for instance variables in a
|
115
|
+
# nicer way.
|
116
|
+
# [+klass+] Class object for which to define accessors for
|
117
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
118
|
+
# [+nmlist+] List of symbols for which to define accessors.
|
119
|
+
# Each symbol in +nmlist+ should have previously been given as
|
120
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
121
|
+
def self.export_accessor(klass, *nmlist)
|
122
|
+
self.export_reader(klass, *nmlist)
|
123
|
+
self.export_writer(klass, *nmlist)
|
124
|
+
end # Safer::IVar::export_accessor
|
125
|
+
|
126
|
+
end # Safer::IVar
|
127
|
+
end # Safer
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'safer/ivar'
|
2
|
+
|
3
|
+
module Safer
|
4
|
+
## Safer::ClassProtocol
|
5
|
+
# Named set of class- and instance- methods, with associated numbers of
|
6
|
+
# arguments. Call +create_from_class+ or +create_from_instance+ to create a
|
7
|
+
# new +ClassProtocol+ object. Call +class_conforms?+ or +instance_conforms?+
|
8
|
+
# to verify that a Class or instance of the class (respectively) conforms to
|
9
|
+
# a protocol.
|
10
|
+
class ClassProtocol
|
11
|
+
|
12
|
+
##
|
13
|
+
# Utility function used during ClassProtocol operations. Given an
|
14
|
+
# +Array+, construct a +Hash+ with the values in the +Array+ as keys.
|
15
|
+
# If no block is provided, then the values for all keys in the +Hash+ will
|
16
|
+
# be +true+. If a block is provided, the block is called for each value
|
17
|
+
# in the +Array+, and its return value will be stored as the value for
|
18
|
+
# that element in the +Hash+. If the block returns +nil+, then the element
|
19
|
+
# will not be stored in the resulting +Hash+.
|
20
|
+
def self._array_to_table(array, &block)
|
21
|
+
if ! block
|
22
|
+
block = proc do |el| true ; end
|
23
|
+
end
|
24
|
+
array.inject(Hash::new) do |h, el|
|
25
|
+
newval = block.call(h, el)
|
26
|
+
if newval
|
27
|
+
h[el] = newval
|
28
|
+
end
|
29
|
+
h
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Describes a set of methods that should be implemented by an object, but
|
35
|
+
# are not implemented. The ClassProtocol::Error object will contain two
|
36
|
+
# ClassProtocol::Violation objects: one for class-method protocol
|
37
|
+
# violations, and one for instance-method protocol violations.
|
38
|
+
class Violation
|
39
|
+
Safer::IVar::instance_variable(self, :table)
|
40
|
+
Safer::IVar::instance_variable(self, :report)
|
41
|
+
|
42
|
+
##
|
43
|
+
# :attr_reader: table
|
44
|
+
# Hash describing method violations. Keys are the names of methods
|
45
|
+
# containing errors. If a method was unimplemented in the class, the
|
46
|
+
# value for that method will be +true+. If a method was implemented, but
|
47
|
+
# had a different arity (number of required / optional arguments) than
|
48
|
+
# the protocol, the value for that method will be the arity discovered in
|
49
|
+
# the object. (The desired arity can be accessed through the
|
50
|
+
# ClassProtocol::Signature for that method.)
|
51
|
+
Safer::IVar::export_reader(self, :table)
|
52
|
+
##
|
53
|
+
# :attr_reader: report
|
54
|
+
# String describing the errors from +table+ in a more human-readable
|
55
|
+
# fashion.
|
56
|
+
Safer::IVar::export_reader(self, :report)
|
57
|
+
|
58
|
+
##
|
59
|
+
# Create a new ClassProtocol::Violation object.
|
60
|
+
def initialize(table, report)
|
61
|
+
self.safer_classprotocol_violation__table = table
|
62
|
+
self.safer_classprotocol_violation__report = report
|
63
|
+
end
|
64
|
+
##
|
65
|
+
# Compare two ClassProtocol::Violation objects for content equality.
|
66
|
+
def ==(other_object)
|
67
|
+
self.class == other_object.class &&
|
68
|
+
self.safer_classprotocol_violation__table == other_object.safer_classprotocol_violation__table
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Hash of method_name => method.arity, describing a set of methods that
|
74
|
+
# we expect to see implemented in another class. ClassProtocol will contain
|
75
|
+
# two of these objects - one describing class methods, and one describing
|
76
|
+
# instance methods.
|
77
|
+
class Signature
|
78
|
+
Safer::IVar::instance_variable(self, :table)
|
79
|
+
##
|
80
|
+
# :attr_reader: table
|
81
|
+
# +Hash+ object, in which the keys are the names of methods, and the
|
82
|
+
# value for a key is the required arity of that method.
|
83
|
+
Safer::IVar::export_reader(self, :table)
|
84
|
+
|
85
|
+
##
|
86
|
+
# Create a ClassProtocol::Signature object.
|
87
|
+
def initialize(table)
|
88
|
+
self.safer_classprotocol_signature__table = table
|
89
|
+
end
|
90
|
+
##
|
91
|
+
# Compare two ClassProtocol::Signature objects for content equality.
|
92
|
+
def ==(other_object)
|
93
|
+
self.class == other_object.class &&
|
94
|
+
self.safer_classprotocol_signature__table == other_object.safer_classprotocol_signature__table
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Given an object, and the name of the method for getting a named method
|
99
|
+
# from that object, check that the object implements this function
|
100
|
+
# signature.
|
101
|
+
def find_violations(object, get_method)
|
102
|
+
report = ''
|
103
|
+
have_error = false
|
104
|
+
error_table = Hash::new
|
105
|
+
self.safer_classprotocol_signature__table.each_pair do |name, arity|
|
106
|
+
begin
|
107
|
+
m = object.send(get_method, name)
|
108
|
+
if m.arity != arity
|
109
|
+
report += "#{get_method}(#{name}) arity #{m.arity} != desired #{arity}.\n"
|
110
|
+
error_table[name] = m.arity
|
111
|
+
have_error = true
|
112
|
+
end
|
113
|
+
rescue NameError => e
|
114
|
+
report += "#{get_method}(#{name}) unimplemented.\n"
|
115
|
+
error_table[name] = true
|
116
|
+
have_error = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if have_error
|
121
|
+
Safer::ClassProtocol::Violation.new(error_table, report)
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Derive a Signature from an object.
|
129
|
+
def self.create(object, get_methods, get_method, &filter)
|
130
|
+
method_names = object.send(get_methods)
|
131
|
+
method_table = Safer::ClassProtocol._array_to_table(method_names) do
|
132
|
+
|h, method_name|
|
133
|
+
if filter.call(method_name)
|
134
|
+
m = object.send(get_method, method_name)
|
135
|
+
m.arity
|
136
|
+
end
|
137
|
+
end
|
138
|
+
self.new(method_table)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
## Safer::ClassProtocol::Error
|
143
|
+
# Error generated when a class does not conform to a protocol signature.
|
144
|
+
class Error < StandardError
|
145
|
+
Safer::IVar::instance_variable(self, :error_object)
|
146
|
+
Safer::IVar::instance_variable(self, :protocol)
|
147
|
+
Safer::IVar::instance_variable(self, :class_violations)
|
148
|
+
Safer::IVar::instance_variable(self, :instance_violations)
|
149
|
+
Safer::IVar::instance_variable(self, :report)
|
150
|
+
|
151
|
+
##
|
152
|
+
# :attr_reader: error_object
|
153
|
+
# Object that does not properly implement a desired method protocol.
|
154
|
+
Safer::IVar::export_reader(self, :error_object)
|
155
|
+
##
|
156
|
+
# :attr_reader: protocol
|
157
|
+
# ClassProtocol object describing the signature that error_object failed
|
158
|
+
# to properly implement.
|
159
|
+
Safer::IVar::export_reader(self, :protocol)
|
160
|
+
##
|
161
|
+
# :attr_reader: class_violations
|
162
|
+
# ClassProtocol::Violation describing class-method protocol violations.
|
163
|
+
Safer::IVar::export_reader(self, :class_violations)
|
164
|
+
##
|
165
|
+
# :attr_reader: instance_violations
|
166
|
+
# ClassProtocol::Violation describing instance-method protocol violations.
|
167
|
+
Safer::IVar::export_reader(self, :instance_violations)
|
168
|
+
##
|
169
|
+
# :attr_reader: report
|
170
|
+
# String for displaying human-readable error message describing protocol
|
171
|
+
# violations.
|
172
|
+
Safer::IVar::export_reader(self, :report)
|
173
|
+
|
174
|
+
|
175
|
+
##
|
176
|
+
# Create a Safer::ClassProtocol::Error object.
|
177
|
+
# Used internally.
|
178
|
+
def initialize(message, error_object, protocol, class_violations, instance_violations)
|
179
|
+
super(message)
|
180
|
+
self.safer_classprotocol_error__error_object = error_object
|
181
|
+
self.safer_classprotocol_error__protocol = protocol
|
182
|
+
self.safer_classprotocol_error__class_violations = class_violations
|
183
|
+
self.safer_classprotocol_error__instance_violations = instance_violations
|
184
|
+
report = ''
|
185
|
+
if class_violations
|
186
|
+
report +=
|
187
|
+
"Class method errors:\n" +
|
188
|
+
self.safer_classprotocol_error__class_violations.report
|
189
|
+
if instance_violations
|
190
|
+
report += "\n\n"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
if instance_violations
|
194
|
+
report +=
|
195
|
+
"Instance method errors:\n" +
|
196
|
+
self.safer_classprotocol_error__instance_violations.report
|
197
|
+
end
|
198
|
+
self.safer_classprotocol_error__report = report
|
199
|
+
end # Safer::ClassProtocol::Error#initialize
|
200
|
+
|
201
|
+
##
|
202
|
+
# Compare two Safer::ClassProtocol::Error objects for content equality.
|
203
|
+
def ==(other_object)
|
204
|
+
self.class == other_object.class &&
|
205
|
+
self.error_object == other_object.error_object &&
|
206
|
+
self.protocol == other_object.protocol &&
|
207
|
+
self.class_violations == other_object.class_violations &&
|
208
|
+
self.instance_violations == other_object.instance_violations
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Error generated when a Class object does not conform to a desired
|
213
|
+
# protocol.
|
214
|
+
class ClassError < Safer::ClassProtocol::Error
|
215
|
+
##
|
216
|
+
# Create a Safer::ClassProtocol::Error::ClassError object.
|
217
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
218
|
+
super(
|
219
|
+
"Class #{error_object} does not implement protocol" +
|
220
|
+
"#{protocol.name}.",
|
221
|
+
error_object, protocol, class_violations, instance_violations)
|
222
|
+
end
|
223
|
+
end # Safer::ClassProtocol::Error::ClassError
|
224
|
+
|
225
|
+
##
|
226
|
+
# Error generated when an instance object does not conform to a desired
|
227
|
+
# protocol.
|
228
|
+
class InstanceError < Safer::ClassProtocol::Error
|
229
|
+
##
|
230
|
+
# Create a Safer::ClassProtocol::Error::InstanceError object.
|
231
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
232
|
+
super(
|
233
|
+
"Instance of #{error_object.class} does not implement protocol" +
|
234
|
+
"#{protocol.name}.",
|
235
|
+
error_object, protocol, class_violations, instance_violations)
|
236
|
+
end
|
237
|
+
end # Safer::ClassProtocol::Error::InstanceError
|
238
|
+
end # Safer::ClassProtocol::Error
|
239
|
+
|
240
|
+
|
241
|
+
Safer::IVar::instance_variable(self, :name)
|
242
|
+
Safer::IVar::instance_variable(self, :class_signature)
|
243
|
+
Safer::IVar::instance_variable(self, :instance_signature)
|
244
|
+
|
245
|
+
##
|
246
|
+
# :attr_reader: name
|
247
|
+
# Name of this protocol signature. Typically derived from the class name
|
248
|
+
# from which this signature is derived.
|
249
|
+
Safer::IVar::export_reader(self, :name)
|
250
|
+
##
|
251
|
+
# :attr_reader: class_signature
|
252
|
+
# Signatures of required class methods for objects implementing this
|
253
|
+
# protocol.
|
254
|
+
Safer::IVar::export_reader(self, :class_signature)
|
255
|
+
##
|
256
|
+
# :attr_reader: instance_signature
|
257
|
+
# Signatures of required instance methods for objects implementing this
|
258
|
+
# protocol.
|
259
|
+
Safer::IVar::export_reader(self, :instance_signature)
|
260
|
+
|
261
|
+
##
|
262
|
+
# Create a +ClassProtocol+ object.
|
263
|
+
# Used internally.
|
264
|
+
# [+name+] Value for +name+ field object.
|
265
|
+
# [+class_signature+] Value for +class_signature+ field.
|
266
|
+
# [+instance_signature+] Value for +instance_signature+ field.
|
267
|
+
def initialize(name, class_signature, instance_signature)
|
268
|
+
self.safer_classprotocol__name = name
|
269
|
+
self.safer_classprotocol__class_signature = class_signature
|
270
|
+
self.safer_classprotocol__instance_signature = instance_signature
|
271
|
+
end # Safer::ClassProtocol#initialize
|
272
|
+
|
273
|
+
##
|
274
|
+
# Check that a Class object conforms to this protocol.
|
275
|
+
def class_conforms?(klass)
|
276
|
+
class_violations = self.safer_classprotocol__class_signature.find_violations(klass, :method)
|
277
|
+
instance_violations = self.safer_classprotocol__instance_signature.find_violations(klass, :instance_method)
|
278
|
+
if class_violations || instance_violations
|
279
|
+
raise(Error::ClassError.new(klass, self, class_violations, instance_violations))
|
280
|
+
end
|
281
|
+
true
|
282
|
+
end # Safer::ClassProtocol#class_conforms?
|
283
|
+
##
|
284
|
+
# Check that an instance object conforms to this protocol.
|
285
|
+
def instance_conforms?(instance)
|
286
|
+
class_violations = self.safer_classprotocol__class_signature.find_violations(instance.class, :method)
|
287
|
+
instance_violations = self.safer_classprotocol__instance_signature.find_violations(instance, :method)
|
288
|
+
if class_violations || instance_violations
|
289
|
+
raise(Error::InstanceError.new(instance, self, class_violations, instance_violations))
|
290
|
+
end
|
291
|
+
true
|
292
|
+
end # Safer::ClassProtocol#instance_conforms?
|
293
|
+
|
294
|
+
##
|
295
|
+
# Given a +Class+ object +klass+ and a +Class+ object +base+, create a
|
296
|
+
# +ClassProtocol+ object representing the methods implemented in +klass+
|
297
|
+
# that are not present in +base+.
|
298
|
+
# [+klass+] +Class+ object from which to derive a +ClassProtocol+.
|
299
|
+
# [+base+] +Class+ object describing routines that will be implemented by
|
300
|
+
# +klass+, but which should not be included in the returned
|
301
|
+
# +ClassProtocol+.
|
302
|
+
def self.create_from_class(klass, base = Object)
|
303
|
+
class_signature = Signature.create(klass, :methods, :method) do
|
304
|
+
|method_name|
|
305
|
+
! Class.respond_to?(method_name)
|
306
|
+
end
|
307
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
308
|
+
instance_signature = Signature.create(klass, :instance_methods, :instance_method) do
|
309
|
+
|method_name|
|
310
|
+
! baseinstance_table.has_key?(method_name)
|
311
|
+
end
|
312
|
+
|
313
|
+
self.new(klass.to_s, class_signature, instance_signature)
|
314
|
+
end # Safer::ClassProtocol.create_from_class
|
315
|
+
|
316
|
+
##
|
317
|
+
# Given an instance object +instance+ and a +Class+ object +baseclass+,
|
318
|
+
# create a +ClassProtocol+ object representing the methods implemented in
|
319
|
+
# +klass+ that are not present in +base+.
|
320
|
+
def self.create_from_instance(instance, baseclass = Object)
|
321
|
+
class_signature = Signature.create(instance.class, :methods, :method) do
|
322
|
+
|method_name|
|
323
|
+
! Class.method_defined?(method_name)
|
324
|
+
end
|
325
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
326
|
+
instance_signature = Signature.create(instance, :methods, :method) do
|
327
|
+
|method_name|
|
328
|
+
! baseinstance_table.has_key?(method_name)
|
329
|
+
end
|
330
|
+
|
331
|
+
self.new(instance.class.to_s, class_signature, instance_signature)
|
332
|
+
end # Safer::ClassProtocol.create_from_instance
|
333
|
+
|
334
|
+
end # Safer::ClassProtocol
|
335
|
+
end # Safer
|
data/lib/safer.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'safer/ivar'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module Root
|
5
|
+
class Test
|
6
|
+
class Instance
|
7
|
+
Safer::IVar::instance_variable(self, :instance)
|
8
|
+
end
|
9
|
+
class Reader
|
10
|
+
Safer::IVar::instance_variable(self, :instance)
|
11
|
+
Safer::IVar::export_reader(self, :instance)
|
12
|
+
end
|
13
|
+
class Writer
|
14
|
+
Safer::IVar::instance_variable(self, :instance)
|
15
|
+
Safer::IVar::export_writer(self, :instance)
|
16
|
+
end
|
17
|
+
class Accessor
|
18
|
+
Safer::IVar::instance_variable(self, :instance)
|
19
|
+
Safer::IVar::export_accessor(self, :instance)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TC_SaferIVar < Test::Unit::TestCase
|
25
|
+
def setup
|
26
|
+
end
|
27
|
+
def teardown
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_no_respond_to(object, method, message="")
|
31
|
+
_wrap_assertion do
|
32
|
+
full_message =
|
33
|
+
build_message(
|
34
|
+
nil,
|
35
|
+
"<?>\ngiven as the method name argument to #assert_no_respond_to " +
|
36
|
+
"must be a Symbol or #respond_to\\?(:to_str).",
|
37
|
+
method
|
38
|
+
)
|
39
|
+
|
40
|
+
assert_block(full_message) do
|
41
|
+
method.kind_of?(Symbol) || method.respond_to?(:to_str)
|
42
|
+
end
|
43
|
+
full_message =
|
44
|
+
build_message(
|
45
|
+
message, "<?>\nof type <?>\nnot expected to respond_to\\\\?<?>.\n",
|
46
|
+
object, object.class, method
|
47
|
+
)
|
48
|
+
assert_block(full_message) { not object.respond_to?(method) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_class_symbol_prefix
|
53
|
+
assert_equal("root", Safer::IVar::class_symbol_prefix(Root))
|
54
|
+
assert_equal("root_test", Safer::IVar::class_symbol_prefix(Root::Test))
|
55
|
+
assert_equal("root_test_instance", Safer::IVar::class_symbol_prefix(Root::Test::Instance))
|
56
|
+
k = Root.class_eval("$k = Class::new(Root::Test::Instance)")
|
57
|
+
assert_same(nil, Safer::IVar::class_symbol_prefix(k))
|
58
|
+
b = k.class_eval("class Blah ; end ; Blah")
|
59
|
+
assert_equal("blah", Safer::IVar::class_symbol_prefix(b))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_instance
|
63
|
+
test_accessors = proc do |obj, expectRead, expectWrite|
|
64
|
+
if expectRead
|
65
|
+
assert_respond_to(obj, :instance)
|
66
|
+
else
|
67
|
+
assert_no_respond_to(obj, :instance)
|
68
|
+
end
|
69
|
+
if expectWrite
|
70
|
+
assert_respond_to(obj, :instance=)
|
71
|
+
else
|
72
|
+
assert_no_respond_to(obj, :instance=)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ti = Root::Test::Instance::new
|
76
|
+
assert_respond_to(ti, :root_test_instance__instance)
|
77
|
+
test_accessors.call(ti, false, false)
|
78
|
+
tr = Root::Test::Reader::new
|
79
|
+
test_accessors.call(tr, true, false)
|
80
|
+
tw = Root::Test::Writer::new
|
81
|
+
test_accessors.call(tw, false, true)
|
82
|
+
ta = Root::Test::Accessor::new
|
83
|
+
test_accessors.call(ta, true, true)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'safer/protocol'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module ArityModule
|
5
|
+
def arity_empty
|
6
|
+
end
|
7
|
+
def arity_1(one)
|
8
|
+
end
|
9
|
+
def arity_2(one, two)
|
10
|
+
end
|
11
|
+
def arity_any(*args)
|
12
|
+
end
|
13
|
+
def arity_2_any(one, two, *args)
|
14
|
+
end
|
15
|
+
METHODS = %w(empty 1 2 any 2_any).map do |suffix| "arity_#{suffix}".to_sym end
|
16
|
+
end
|
17
|
+
|
18
|
+
class TestArityClass
|
19
|
+
extend ArityModule
|
20
|
+
end
|
21
|
+
class TestArityInstance
|
22
|
+
include ArityModule
|
23
|
+
end
|
24
|
+
class TestArityBoth
|
25
|
+
extend ArityModule
|
26
|
+
include ArityModule
|
27
|
+
end
|
28
|
+
|
29
|
+
class TestArityError
|
30
|
+
include ArityModule
|
31
|
+
def arity_empty(blah)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TC_SaferProtocol < Test::Unit::TestCase
|
36
|
+
def setup
|
37
|
+
@class_protocol = Safer::ClassProtocol.create_from_class(TestArityClass)
|
38
|
+
@instance_protocol = Safer::ClassProtocol.create_from_class(TestArityInstance)
|
39
|
+
@both_protocol = Safer::ClassProtocol.create_from_class(TestArityBoth)
|
40
|
+
|
41
|
+
@class_instance = TestArityClass::new
|
42
|
+
@instance_instance = TestArityInstance::new
|
43
|
+
@both_instance = TestArityBoth::new
|
44
|
+
@error_instance = TestArityError::new
|
45
|
+
end
|
46
|
+
def teardown
|
47
|
+
@class_protocol = nil
|
48
|
+
@instance_protocol = nil
|
49
|
+
@both_protocol = nil
|
50
|
+
|
51
|
+
@class_instance = nil
|
52
|
+
@instance_instance = nil
|
53
|
+
@both_instance = nil
|
54
|
+
@error_instance = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_methods(useinstance)
|
58
|
+
if useinstance
|
59
|
+
[ :instance_conforms?, proc do |obj| obj ; end, Safer::ClassProtocol::Error::InstanceError ]
|
60
|
+
else
|
61
|
+
[ :class_conforms?, proc do |obj| obj.class ; end, Safer::ClassProtocol::Error::ClassError ]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_signaturesCorrect
|
66
|
+
assert_equal(@both_protocol.class_signature, @class_protocol.class_signature)
|
67
|
+
assert_equal(@both_protocol.instance_signature, @instance_protocol.instance_signature)
|
68
|
+
assert_equal(@class_protocol.class_signature, @instance_protocol.instance_signature)
|
69
|
+
arity_methods = ArityModule::METHODS.map do |meth| meth.to_s end.sort
|
70
|
+
assert_equal(@class_protocol.class_signature.table.keys.sort, arity_methods)
|
71
|
+
end
|
72
|
+
|
73
|
+
def run_shouldConform(useinstance)
|
74
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
75
|
+
assert_nothing_thrown do
|
76
|
+
@class_protocol.send(conforms_method, object_get.call(@class_instance))
|
77
|
+
@instance_protocol.send(conforms_method, object_get.call(@instance_instance))
|
78
|
+
@both_protocol.send(conforms_method, object_get.call(@both_instance))
|
79
|
+
@class_protocol.send(conforms_method, object_get.call(@both_instance))
|
80
|
+
@instance_protocol.send(conforms_method, object_get.call(@both_instance))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
def test_shouldConform
|
84
|
+
run_shouldConform(false)
|
85
|
+
run_shouldConform(true)
|
86
|
+
end
|
87
|
+
|
88
|
+
def run_arity(useinstance)
|
89
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
90
|
+
begin
|
91
|
+
@instance_protocol.send(conforms_method, object_get.call(@error_instance))
|
92
|
+
flunk("Expected exception.")
|
93
|
+
rescue Safer::ClassProtocol::Error => e
|
94
|
+
assert_same(e.class, except_type)
|
95
|
+
assert_same(object_get.call(@error_instance), e.error_object)
|
96
|
+
assert_same(@instance_protocol, e.protocol)
|
97
|
+
assert_same(nil, e.class_violations)
|
98
|
+
assert_equal({ 'arity_empty' => 1 }, e.instance_violations.table)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
def test_arity
|
102
|
+
run_arity(false)
|
103
|
+
run_arity(true)
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_classInstanceError(useinstance)
|
107
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
108
|
+
|
109
|
+
class_instance_error = nil
|
110
|
+
begin
|
111
|
+
@instance_protocol.send(conforms_method, object_get.call(@class_instance))
|
112
|
+
flunk("Expected exception.")
|
113
|
+
rescue Safer::ClassProtocol::Error => e
|
114
|
+
assert_same(e.class, except_type)
|
115
|
+
assert_same(e.error_object, object_get.call(@class_instance))
|
116
|
+
assert_same(e.protocol, @instance_protocol)
|
117
|
+
assert_same(nil, e.class_violations)
|
118
|
+
assert_equal(e.instance_violations.table.keys.size, @instance_protocol.instance_signature.table.keys.size)
|
119
|
+
@instance_protocol.instance_signature.table.each_pair do |key, arity|
|
120
|
+
assert(e.instance_violations.table.has_key?(key))
|
121
|
+
assert_same(e.instance_violations.table[key], true)
|
122
|
+
end
|
123
|
+
class_instance_error = e
|
124
|
+
end
|
125
|
+
|
126
|
+
begin
|
127
|
+
@both_protocol.send(conforms_method, object_get.call(@class_instance))
|
128
|
+
flunk("Expected exception.")
|
129
|
+
rescue Safer::ClassProtocol::Error => e
|
130
|
+
assert_same(e.class, except_type)
|
131
|
+
assert_same(e.error_object, object_get.call(@class_instance))
|
132
|
+
assert_same(e.protocol, @both_protocol)
|
133
|
+
assert_equal(
|
134
|
+
e.instance_violations, class_instance_error.instance_violations)
|
135
|
+
assert_same(
|
136
|
+
e.class_violations, class_instance_error.class_violations)
|
137
|
+
assert_equal(e.report, class_instance_error.report)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
def test_classInstanceError
|
141
|
+
run_classInstanceError(false)
|
142
|
+
run_classInstanceError(true)
|
143
|
+
end
|
144
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Aidan Cully
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-23 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rubyforge
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 19
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 2
|
50
|
+
version: 2.6.2
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: |-
|
54
|
+
Safer is an umbrella library, with components designed to make it simple to
|
55
|
+
verify and improve the safety of your ruby code. There are at present two
|
56
|
+
modules under the safer umbrella:
|
57
|
+
|
58
|
+
[<tt>Safer::IVar</tt>] generates specially-named accessor functions
|
59
|
+
for class instance variables.
|
60
|
+
[<tt>Safer::ClassProtocol</tt>] is used to provide a ruby analogue to
|
61
|
+
Objective-C Protocols (which are similar to
|
62
|
+
Java interfaces, but do not require
|
63
|
+
inheritance).
|
64
|
+
email:
|
65
|
+
- aidan@panix.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- History.txt
|
72
|
+
- Manifest.txt
|
73
|
+
- README.txt
|
74
|
+
files:
|
75
|
+
- .autotest
|
76
|
+
- History.txt
|
77
|
+
- Manifest.txt
|
78
|
+
- README.txt
|
79
|
+
- Rakefile
|
80
|
+
- lib/safer.rb
|
81
|
+
- lib/safer/ivar.rb
|
82
|
+
- lib/safer/protocol.rb
|
83
|
+
- test/test_safer_ivar.rb
|
84
|
+
- test/test_safer_protocol.rb
|
85
|
+
has_rdoc: true
|
86
|
+
homepage: http://safer.rubyforge.org/
|
87
|
+
licenses: []
|
88
|
+
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options:
|
91
|
+
- --main
|
92
|
+
- README.txt
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
requirements: []
|
114
|
+
|
115
|
+
rubyforge_project: aidancully
|
116
|
+
rubygems_version: 1.3.7
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Safer is an umbrella library, with components designed to make it simple to verify and improve the safety of your ruby code
|
120
|
+
test_files:
|
121
|
+
- test/test_safer_ivar.rb
|
122
|
+
- test/test_safer_protocol.rb
|