to_robust 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +39 -0
- data/lib/global/fix.rb +72 -0
- data/lib/global/fix/add_atom.rb +28 -0
- data/lib/global/fix/atom.rb +51 -0
- data/lib/global/fix/init.rb +4 -0
- data/lib/global/fix/remove_atom.rb +16 -0
- data/lib/global/fix/swap_atom.rb +26 -0
- data/lib/global/global.rb +155 -0
- data/lib/global/init.rb +10 -0
- data/lib/global/kernel/argument_error.rb +22 -0
- data/lib/global/kernel/exception.rb +9 -0
- data/lib/global/kernel/init.rb +4 -0
- data/lib/global/kernel/no_method_error.rb +15 -0
- data/lib/global/kernel/zero_division_error.rb +21 -0
- data/lib/global/report.rb +42 -0
- data/lib/global/robust_proc.rb +48 -0
- data/lib/global/strategies/divide_by_zero_error_strategy.rb +277 -0
- data/lib/global/strategies/init.rb +5 -0
- data/lib/global/strategies/no_method_error_strategy.rb +92 -0
- data/lib/global/strategies/wrong_arguments_error_strategy.rb +174 -0
- data/lib/global/strategy.rb +67 -0
- data/lib/local/init.rb +6 -0
- data/lib/local/kernel/init.rb +1 -0
- data/lib/local/kernel/module.rb +43 -0
- data/lib/local/local.rb +43 -0
- data/lib/local/strategies/bignum_division_strategy.rb +11 -0
- data/lib/local/strategies/bignum_modulus_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_coerce_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_division_strategy.rb +12 -0
- data/lib/local/strategies/fixnum_modulus_strategy.rb +12 -0
- data/lib/local/strategies/float_division_strategy.rb +11 -0
- data/lib/local/strategies/float_modulus_strategy.rb +12 -0
- data/lib/local/strategies/init.rb +37 -0
- data/lib/local/strategies/numeric_division_strategy.rb +11 -0
- data/lib/local/strategies/numeric_modulus_strategy.rb +12 -0
- data/lib/local/strategies/soft_binding_strategy.rb +108 -0
- data/lib/local/strategies/swap_method_strategy.rb +32 -0
- data/lib/local/strategy.rb +18 -0
- data/lib/to_robust.rb +4 -0
- data/test/local/float_division.rb +28 -0
- data/test/local/integer_division.rb +28 -0
- data/test/local/method_binding.rb +45 -0
- metadata +109 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Chris Timperley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
ruby_to_robust
|
2
|
+
==============
|
3
|
+
|
4
|
+
RubyToRobust allows you to implement soft semantics in your programs so that they
|
5
|
+
may automatically recover from a range of supported exceptions in some sensible manner,
|
6
|
+
allowing the program to continue running.
|
7
|
+
|
8
|
+
The soft semantics provided by this project were originally designed to be exploited by
|
9
|
+
Wallace.rb to provide robustness and support for soft grammars when using Grammatical Evolution.
|
10
|
+
|
11
|
+
Two different soft semantics solutions are provided by RubyToRobust, both based on the
|
12
|
+
Local and Global robustness schemes outlined in Chris Timperley's Masters Thesis.
|
13
|
+
|
14
|
+
* **Global:** The Global robustness module implements soft semantics by intercepting
|
15
|
+
all exceptions thrown by a monitored method and using line information and other information
|
16
|
+
provided by the exception to determine the root of the problem in the method source code
|
17
|
+
which it then proceeds to "repair".
|
18
|
+
* **Local:** Rather than manipulating the source code of the monitored function, the Local
|
19
|
+
robustness measure operates by implementing a series of monkey patches which ensure that
|
20
|
+
a set of supported exceptions are never (or rarely) thrown within the context of the monitored
|
21
|
+
function.
|
22
|
+
|
23
|
+
Please note that the definition of a "repaired method" used by this project is a method which
|
24
|
+
no longer produces errors (which would otherwise crash the program). This does not mean that the
|
25
|
+
program does what its programmer wanted (although sometimes it might).
|
26
|
+
|
27
|
+
The implementation and behaviour of this robustness scheme is very different to the implementation
|
28
|
+
used in Chris Timperley's Master Thesis.
|
29
|
+
* In the original version Global robustness was either enabled or disabled, and was constantly active
|
30
|
+
except in certain classes and modules. The new implementation allows Global robustness to be constrained
|
31
|
+
to a given block or method, so that the program may operate as it otherwise would outside the "protected"
|
32
|
+
block.
|
33
|
+
* Exceptions are treated as soon as they are thrown outside the context of the monitored method.
|
34
|
+
The original Global robustness would allow exceptions to propagate through the program (allowing
|
35
|
+
them to be caught by a parent context) up till the point they would crash Ruby.
|
36
|
+
* Performance is hugely improved! By providing the optional file name and line number parameters to
|
37
|
+
dynamically evaluated procedures there is no need to use external files to extract detailed error information.
|
38
|
+
Additionally, since the soft semantics are constrained to a given block or method, very few method calls
|
39
|
+
have to be wrapped (and therefore slowed down).
|
data/lib/global/fix.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Instances of this class are used to represent candidate fixes / solutions to given errors that
|
2
|
+
# have occurred during the execution of a method.
|
3
|
+
#
|
4
|
+
# A fix operates on the source code of a method, adding, removing and replacing specific lines of
|
5
|
+
# code (like a Git patch/change) in an attempt to remove the source of the error.
|
6
|
+
#
|
7
|
+
# As mentioned elsewhere in the Global module, strategies may return an ordered sequence of fixes
|
8
|
+
# according to the likelihood that they will solve the problem (or by their performance penalty to
|
9
|
+
# the method), but as this ordering is carried out by the strategies and all ordering information is
|
10
|
+
# implict, candidate fixes proposed by different solutions cannot be compared and ordered.
|
11
|
+
#
|
12
|
+
# The ability to compare arbitrary fixes could be introduced by adding a notion of cost to each fix;
|
13
|
+
# a net quantification of both the performance penalty incurred to the method *after* applying the fix
|
14
|
+
# and the probability that the fix will be a good one (a good fix would have a lower cost than a worse
|
15
|
+
# fix). Candidate fixes could then be processed in ascending order of cost.
|
16
|
+
class ToRobust::Global::Fix
|
17
|
+
|
18
|
+
# Creates a new candidate fix.
|
19
|
+
#
|
20
|
+
# *Parameters:*
|
21
|
+
# * changes, an array of changes that the fix should make to the source code.
|
22
|
+
# * validator, a lambda function that verifies a given error is not the same as the original error.
|
23
|
+
def initialize(changes,validator)
|
24
|
+
@changes = changes
|
25
|
+
@validator = validator
|
26
|
+
end
|
27
|
+
|
28
|
+
# Calculates the source code of the fixed method using the atomic fixes described by this object.
|
29
|
+
#
|
30
|
+
# *Parameters:*
|
31
|
+
# * src, the original source code (as lines) of the affected method.
|
32
|
+
#
|
33
|
+
# *Returns:*
|
34
|
+
# The fixed source code for the method.
|
35
|
+
def fixed_source(src)
|
36
|
+
history = []
|
37
|
+
@changes.each do |change|
|
38
|
+
change.apply!(src, history)
|
39
|
+
history << change
|
40
|
+
end
|
41
|
+
return src
|
42
|
+
end
|
43
|
+
|
44
|
+
# Applies the proposed changes to the source code of the affected method.
|
45
|
+
#
|
46
|
+
# *Parameters:*
|
47
|
+
# * method, the affected method.
|
48
|
+
#
|
49
|
+
# *Returns*
|
50
|
+
# A variant of the affected method modified according to the changes given by this fix.
|
51
|
+
def apply(method)
|
52
|
+
ToRobust::Global::RobustProc.new(method.headers, fixed_source(method.source.dup[1...-1]).join('/\n/)'))
|
53
|
+
end
|
54
|
+
|
55
|
+
# In the event that the method resulting from the fix encounters an error, this method *attempts*
|
56
|
+
# to determine whether the fix has been successful in removing the source of the original error.
|
57
|
+
# A "fixed" method in this sense is not necessarily one which removes all errors from a given method,
|
58
|
+
# rather it removes a particular identifiable error.
|
59
|
+
#
|
60
|
+
# *Parameters:*
|
61
|
+
# * method, the affected method.
|
62
|
+
# * original_error, the original error that was encountered by the method.
|
63
|
+
# * new_error, the error encountered after applying this fix and executing the method again.
|
64
|
+
#
|
65
|
+
# *Returns:*
|
66
|
+
# * True if the fix was successful in removing the cause of the original error, false if not.
|
67
|
+
def successful?(method, original_error, new_error)
|
68
|
+
@validator[method, original_error, new_error]
|
69
|
+
end
|
70
|
+
alias_method :validate, :successful?
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# A add atom is used to insert a sequence of lines at a given position in the affected source code.
|
2
|
+
class ToRobust::Global::Fix::AddAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Constructs a new add atom.
|
5
|
+
#
|
6
|
+
# *Parameters:*
|
7
|
+
# * line_no, the line in the source code which the atom should operate on.
|
8
|
+
# * additions, the sequence of lines to insert at the given line number (as an ordered array).
|
9
|
+
def initialize(line_no, additions)
|
10
|
+
super(line_no)
|
11
|
+
@additions = additions
|
12
|
+
end
|
13
|
+
|
14
|
+
# Calculates and returns the number of lines added by the atom.
|
15
|
+
def lines_added
|
16
|
+
@additions.length
|
17
|
+
end
|
18
|
+
|
19
|
+
# Inserts a sequence of lines at specific line in the affected source code.
|
20
|
+
#
|
21
|
+
# *Parameters:*
|
22
|
+
# * source, the (partially fixed) affected source.
|
23
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
24
|
+
def apply!(source, changes)
|
25
|
+
source.insert(adjusted_line_no(changes), *@additions)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# A fix atom is used to store and represent a single "atomic" change made by a candidate fix which
|
2
|
+
# may be represented as a collection of such atomic fixes.
|
3
|
+
class ToRobust::Global::Fix::Atom
|
4
|
+
|
5
|
+
# The line which this atomic fix changes.
|
6
|
+
attr_reader :line_no
|
7
|
+
|
8
|
+
# Constructs a new fix atom.
|
9
|
+
#
|
10
|
+
# *Parameters:*
|
11
|
+
# * line_no, the line in the source code which the atom should operate on.
|
12
|
+
def initialize(line_no)
|
13
|
+
@line_no = line_no
|
14
|
+
end
|
15
|
+
|
16
|
+
# Calculates the number of lines that will be added by this fix.
|
17
|
+
def lines_added
|
18
|
+
raise NotImplementedError, 'No "lines_added" method was implemented by this Atom.'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calculate the line number in the adjusted source code that corresponds to the specified
|
22
|
+
# line in the body of the original source code.
|
23
|
+
#
|
24
|
+
# *Parameters:*
|
25
|
+
# * changes, an array of the atomic changes already applied to this source.
|
26
|
+
def adjusted_line_no(changes)
|
27
|
+
|
28
|
+
adj_n = @line_no
|
29
|
+
changes.each do |c|
|
30
|
+
if c.line_no < @line_no and lines_added < 0
|
31
|
+
adj_n += c.lines_added
|
32
|
+
elsif c.line_no >= line_no and lines_added > 0
|
33
|
+
adj_n += c.lines_added
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Subtract one to return the line number in the body (header takes up the first line).
|
38
|
+
return adj_n - 1
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Destructively applies the change described by this atomic fix to the given source code.
|
43
|
+
#
|
44
|
+
# *Parameters:*
|
45
|
+
# * source, the source code of the affected method (represented as a sequence of lines).
|
46
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
47
|
+
def apply!(source, changes)
|
48
|
+
raise NotImplementedError, 'No "apply!" method was implemented by this Atom.'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# A remove atom is used to remove a line from the affected source code.
|
2
|
+
class ToRobust::Global::Fix::RemoveAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Removes a single line from the source code.
|
5
|
+
def lines_added; -1; end
|
6
|
+
|
7
|
+
# Removes a specific line from the affected source code.
|
8
|
+
#
|
9
|
+
# *Parameters:*
|
10
|
+
# * source, the (partially fixed) affected source.
|
11
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
12
|
+
def apply!(source, changes)
|
13
|
+
source.delete_at(adjusted_line_no(changes))
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# A remove atom is used to swap a line from the affected source code with another.
|
2
|
+
class ToRobust::Global::Fix::SwapAtom < ToRobust::Global::Fix::Atom
|
3
|
+
|
4
|
+
# Constructs a new swap atom.
|
5
|
+
#
|
6
|
+
# *Parameters:*
|
7
|
+
# * line_no, the line in the source code which the atom should operate on.
|
8
|
+
# * replacement, the contents of the replacement line.
|
9
|
+
def initialize(line_no, replacement)
|
10
|
+
super(line_no)
|
11
|
+
@replacement = replacement
|
12
|
+
end
|
13
|
+
|
14
|
+
# No lines are added.
|
15
|
+
def lines_added; 0; end
|
16
|
+
|
17
|
+
# Swaps the contents at specific line with an altered form stored by this object.
|
18
|
+
#
|
19
|
+
# *Parameters:*
|
20
|
+
# * source, the (partially fixed) affected source.
|
21
|
+
# * changes, an array of the atomic changes already applied to this source (in order of application).
|
22
|
+
def apply!(source, changes)
|
23
|
+
source[adjusted_line_no(changes)] = @replacement
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# The Global robustness module implements a simplified (and faster) variant of the global
|
2
|
+
# robustness measure outlined in Chris Timperley's Master's Thesis.
|
3
|
+
#
|
4
|
+
# Methods executed under global robustness protection are executed within an exception
|
5
|
+
# catching block which intercept any errors which cause the method to fail
|
6
|
+
#
|
7
|
+
# For now this measure only works with simple methods that do not change the state of
|
8
|
+
# objects or interfere with the global state.
|
9
|
+
#
|
10
|
+
# Author: Chris Timperley
|
11
|
+
module ToRobust::Global
|
12
|
+
|
13
|
+
@strategies = []
|
14
|
+
@max_attempts = 10
|
15
|
+
@save_repairs = true
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# A flag to control whether repairs to a method should be permanent (true) or if they
|
20
|
+
# should last only for a given call (false).
|
21
|
+
# True by default.
|
22
|
+
attr_accessor :save_repairs
|
23
|
+
|
24
|
+
# The maximum number of (different) errors that will be tolerated before
|
25
|
+
# all attempts to repair the method are ceased.
|
26
|
+
# 10 by default.
|
27
|
+
attr_accessor :max_attempts
|
28
|
+
|
29
|
+
# Allow strategies to be added and removed via:
|
30
|
+
# * Global.strategies << Strategy.new
|
31
|
+
# * Global.strategies.remove(strategy)
|
32
|
+
attr_reader :strategies
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Executes a given method (or proc) under global robustness protection.
|
37
|
+
#
|
38
|
+
# *Parameters:*
|
39
|
+
# * method, the (robust proc) method to execute using global robustness protection.
|
40
|
+
# * params, the parameters to the method call.
|
41
|
+
#
|
42
|
+
# *Returns:*
|
43
|
+
# The result of the method call.
|
44
|
+
def self.execute(method, params)
|
45
|
+
|
46
|
+
# Attempt to execute the method. If an error occurs then attempt to repair the method
|
47
|
+
# using the error information.
|
48
|
+
attempts = 0
|
49
|
+
begin
|
50
|
+
return method.call(*params.clone)
|
51
|
+
rescue => original_error
|
52
|
+
report = repair(method, params, original_error)
|
53
|
+
attempts += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
# Once repaired attempt to call the method again, repeating the cycle,
|
57
|
+
# until either the method successfully returns a result or a maximum number of errors occur.
|
58
|
+
until attempts >= @max_attempts or report.complete?
|
59
|
+
attempts += 1
|
60
|
+
report = repair(report.fixed_method, params, report.error)
|
61
|
+
end
|
62
|
+
|
63
|
+
# If the repair limit has been hit then return the original error and make no permanent
|
64
|
+
# changes to the supplied method (or should we keep the changes?).
|
65
|
+
raise original_error if report.partial?
|
66
|
+
|
67
|
+
# If the method was completely repaired then swap the original method with the fixed form
|
68
|
+
# (so long as that functionality is enabled) and return the result of the method call.
|
69
|
+
method.become(report.fixed_method) if @save_repairs
|
70
|
+
return report.result
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Calculates all the possible candidate fixes to a given error within a provided method.
|
75
|
+
# Passes the details of the error along with the method to each associated error handling
|
76
|
+
# strategy and queries all possible candidate solutions.
|
77
|
+
#
|
78
|
+
# *TODO:*
|
79
|
+
# At the moment each strategy may return an ordered set of candidate fixes to the problem,
|
80
|
+
# but this ordering information is implicit (and usually not universally comparable) so there
|
81
|
+
# can be no ordering of candidate fixes from ALL given strategies (i.e. strategy B may produce
|
82
|
+
# a strategy that is more likely to solve the error than the best candidate given by strategy A).
|
83
|
+
#
|
84
|
+
# *Parameters:*
|
85
|
+
# * method, the method to be fixed.
|
86
|
+
# * error, the error to be addressed.
|
87
|
+
#
|
88
|
+
# *Returns:*
|
89
|
+
# An array of candidate fixes to the error.
|
90
|
+
def self.generate_candidates(method, error)
|
91
|
+
@strategies.map { |s| s.generate_candidates(method, error) }.flatten
|
92
|
+
end
|
93
|
+
|
94
|
+
# Repairs a given (statically defined) method using the details of an encountered error.
|
95
|
+
#
|
96
|
+
# Special care must be taken to define repair. In this context, a repaired method
|
97
|
+
# is one which does not suffer from the same error that it did originally. This means
|
98
|
+
# that the repaired form of the provided method may still contain errors.
|
99
|
+
#
|
100
|
+
# This definition of repair is necessary to fix methods which contain more than a single
|
101
|
+
# error.
|
102
|
+
#
|
103
|
+
# *Parameters:*
|
104
|
+
# * method, the broken method to be repaired.
|
105
|
+
# * params, the parameters to the original method call.
|
106
|
+
# * error, the error that occurred when the method was called.
|
107
|
+
#
|
108
|
+
# *Returns:*
|
109
|
+
# A repaired form of the method.
|
110
|
+
def self.repair(method, params, error)
|
111
|
+
|
112
|
+
# Produce a series of candidate fixes to the method.
|
113
|
+
candidates = generate_candidates(method, error)
|
114
|
+
|
115
|
+
# Attempt each of the candidate fixes until the error is prevented or there are no
|
116
|
+
# further candidates to try.
|
117
|
+
until candidates.empty?
|
118
|
+
|
119
|
+
fix = candidates.shift
|
120
|
+
fixed_method = fix.apply(method)
|
121
|
+
|
122
|
+
begin
|
123
|
+
result = fixed_method.call(*params.clone)
|
124
|
+
return Report.new(fixed_method, result: result)
|
125
|
+
rescue => fix_error
|
126
|
+
return Report.new(fixed_method, error: fix_error) if fix.successful?(fixed_method, error, fix_error)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# If all attempts to repair the method against the given error fail then re-throw
|
132
|
+
# the exception.
|
133
|
+
raise error
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
# Executes a given block whilst preventing a ZeroDivisionError occurring.
|
138
|
+
# If a zero division error does occur during the execution of the block, this safety
|
139
|
+
# wrapper will catch the exception and will return a zero instead, allowing the program
|
140
|
+
# to continue.
|
141
|
+
#
|
142
|
+
# *Parameters:*
|
143
|
+
# * &block, the block to execute under ZeroDivisionError protection.
|
144
|
+
#
|
145
|
+
# *Returns:*
|
146
|
+
# The result of the block execution, or zero if the block execution yields a ZeroDivisionError.
|
147
|
+
def self.prevent_dbz(&block)
|
148
|
+
begin
|
149
|
+
return block.call
|
150
|
+
rescue ZeroDivisionError
|
151
|
+
return 0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/global/init.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module ToRobust; end
|
2
|
+
|
3
|
+
require_relative 'kernel/init.rb'
|
4
|
+
require_relative 'global.rb'
|
5
|
+
require_relative 'robust_proc.rb'
|
6
|
+
require_relative 'strategy.rb'
|
7
|
+
require_relative 'strategies/init.rb'
|
8
|
+
require_relative 'fix.rb'
|
9
|
+
require_relative 'fix/init.rb'
|
10
|
+
require_relative 'report.rb'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Augments the functionality of ArgumentError instances so that the affected method and expected number
|
2
|
+
# of arguments can be quickly inspected. Also adjusts the line number calculation so that the correct
|
3
|
+
# line number is returned for the error.
|
4
|
+
class ArgumentError
|
5
|
+
|
6
|
+
# Returns the name of the method which was provided with poorly formed arguments.
|
7
|
+
def affected_method
|
8
|
+
backtrace[0].match(/`(([a-zA-Z]|_)+)'/)[0][1...-1]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the number of arguments that were expected by this method.
|
12
|
+
def args_expected
|
13
|
+
message.match(/\s(\d+)\)/)[0][1...-1].to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the line number that the method call was made from.
|
17
|
+
# The line that the method call was made is given by the second line of the backtrace.
|
18
|
+
def line_no
|
19
|
+
backtrace[1][/:(\d+):/][1...-1].to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|