Permalink
Browse files

Fix potential problems with recursion if the module is included multi…

…ple times
  • Loading branch information...
1 parent 603c8d8 commit cf7a0c42525f262215beefa0226126df335df609 Darrick Wiebe committed Sep 22, 2011
Showing with 106 additions and 12 deletions.
  1. +63 −0 examples/example.rb
  2. +41 −10 lib/nested_exceptions.rb
  3. +2 −2 lib/nested_exceptions/define.rb
View
@@ -0,0 +1,63 @@
+require 'pp'
+
+$: << './lib'
+require 'nested_exceptions'
+
+module ErrorSpec
+
+ NestedExceptions.define_standard_exception_classes_in ErrorSpec
+
+ class Example
+ def bug
+ raise 'bug'
+ end
+
+ def nested_bug
+ bug
+ end
+
+ def problem
+ nested_bug
+ rescue
+ raise InternalError, 'problem'
+ end
+
+ def nested_problem
+ problem
+ end
+
+ def double_bug
+ nested_problem
+ rescue
+ raise 'oops'
+ end
+ end
+end
+
+example = ErrorSpec::Example.new
+
+if ARGV.first == 'original'
+
+ puts "Original exception:"
+ example.double_bug
+
+elsif ARGV.first == 'nested'
+
+ require 'nested_exceptions/global'
+ puts "Nested exception:"
+ begin
+ example.double_bug
+ rescue StandardError => e
+ puts "Note that JRuby does not display the modified backtrace:"
+ pp e.backtrace
+ raise
+ end
+
+else
+
+ puts "Usage:"
+ puts
+ puts "ruby #{ $0 } [original|nested]"
+
+end
+
View
@@ -6,18 +6,33 @@ module NestedExceptions
attr_reader :cause
def initialize(message = nil, cause = nil)
- @cause = cause || $!
- super(message)
+ # @cause could be defined if this module is included multiple
+ # times in a class heirarchy.
+ @illegal_nesting = defined? @cause
+ @recursing = false
+ if @illegal_nesting
+ warn "WARNING: NestedExceptions is included in the class heirarchy of #{ self.class } more than once."
+ warn "- Ensure if you require 'nested_exceptions/global' or manually add"
+ warn " NestedExceptions to a built-in Ruby exception class, you must do it at"
+ warn " the beginning of the program."
+ else
+ @cause = cause || $!
+ super(message)
+ end
end
- if Object.const_defined? :RUBY_ENGINE and RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx'
+ if Object.const_defined? :RUBY_ENGINE and (RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx')
def backtrace
- return @processed_backtrace if defined? @processed_backtrace and @processed_backtrace
- @processed_backtrace = process_backtrace(super)
+ prevent_recursion do
+ return @processed_backtrace if defined? @processed_backtrace and @processed_backtrace
+ @processed_backtrace = process_backtrace(super)
+ end
end
else
def set_backtrace(bt)
- super process_backtrace(bt)
+ prevent_recursion do
+ super process_backtrace(bt)
+ end
end
end
@@ -33,18 +48,34 @@ def root_cause
protected
+ # This shouldn't be necessary anymore
+ def prevent_recursion
+ if not @recursing and not @illegal_nesting
+ begin
+ @recursing = true
+ yield
+ ensure
+ @recursing = false
+ end
+ end
+ end
+
def process_backtrace(bt)
- if cause
- cause.backtrace.reverse.each do |line|
+ bt ||= []
+ if @cause and not @illegal_nesting
+ cause_backtrace = @cause.backtrace || []
+ cause_backtrace.reverse.each do |line|
if bt.last == line
bt.pop
else
break
end
end
- bt << "--- cause: #{cause.class.name}: #{cause}"
- bt.concat cause.backtrace
+ bt << "--- cause: #{@cause.class}: #{@cause}"
+ bt.concat cause_backtrace
end
bt
+ rescue Exception => e
+ warn "exception processing backtrace: #{ e.class }: #{ e.message }\n #{ caller(0).join("\n ") }"
end
end
@@ -5,9 +5,9 @@ class << self
#
# I actually recommend that you just define these manually, but this may be
# a handy shortcut in smaller projects.
- def define_standard_exception_classes_in(target_module)
+ def define_standard_exception_classes_in(target_module, add_module = false)
definitions = %{
- class Error < StandardError; include NestedExceptions; end
+ class Error < StandardError; #{ add_module ? 'include NestedExceptions;' : '' } end
class UserError < Error; end
class LogicError < Error; end
class ClientError < LogicError; end

0 comments on commit cf7a0c4

Please sign in to comment.