class RuboCop::Cop::Lint::ShadowedException

Checks for a rescued exception that get shadowed by a less specific exception being rescued before a more specific exception is rescued.

An exception is considered shadowed if it is rescued after its ancestor is, or if it and its ancestor are both rescued in the same ‘rescue` statement. In both cases, the more specific rescue is unnecessary because it is covered by rescuing the less specific exception. (ie. `rescue Exception, StandardError` has the same behavior whether `StandardError` is included or not, because all “StandardError“s are rescued by `rescue Exception`).

@example

# bad

begin
  something
rescue Exception
  handle_exception
rescue StandardError
  handle_standard_error
end

# bad
begin
  something
rescue Exception, StandardError
  handle_error
end

# good

begin
  something
rescue StandardError
  handle_standard_error
rescue Exception
  handle_exception
end

# good, however depending on runtime environment.
#
# This is a special case for system call errors.
# System dependent error code depends on runtime environment.
# For example, whether `Errno::EAGAIN` and `Errno::EWOULDBLOCK` are
# the same error code or different error code depends on environment.
# This good case is for `Errno::EAGAIN` and `Errno::EWOULDBLOCK` with
# the same error code.
begin
  something
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
  handle_standard_error
end

Constants

MSG

Public Instance Methods

on_rescue(node) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 67
def on_rescue(node)
  return if rescue_modifier?(node)

  _body, *rescues, _else = *node
  rescued_groups = rescued_groups_for(rescues)

  rescue_group_rescues_multiple_levels = rescued_groups.any? do |group|
    contains_multiple_levels_of_exceptions?(group)
  end

  return if !rescue_group_rescues_multiple_levels && sorted?(rescued_groups)

  add_offense(offense_range(rescues))
end

Private Instance Methods

compare_exceptions(exception, other_exception) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 101
def compare_exceptions(exception, other_exception)
  if system_call_err?(exception) && system_call_err?(other_exception)
    # This condition logic is for special case.
    # System dependent error code depends on runtime environment.
    # For example, whether `Errno::EAGAIN` and `Errno::EWOULDBLOCK` are
    # the same error code or different error code depends on runtime
    # environment. This checks the error code for that.
    exception.const_get(:Errno) != other_exception.const_get(:Errno) &&
      exception <=> other_exception
  else
    exception && other_exception && exception <=> other_exception
  end
end
contains_multiple_levels_of_exceptions?(group) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 94
def contains_multiple_levels_of_exceptions?(group)
  # Always treat `Exception` as the highest level exception.
  return true if group.size > 1 && group.include?(Exception)

  group.combination(2).any? { |a, b| compare_exceptions(a, b) }
end
evaluate_exceptions(group) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 119
def evaluate_exceptions(group)
  rescued_exceptions = group.exceptions

  if rescued_exceptions.any?
    rescued_exceptions.each_with_object([]) do |exception, converted|
      # FIXME: Workaround `rubocop:disable` comment for JRuby.
      #        https://github.com/jruby/jruby/issues/6642
      # rubocop:disable Style/RedundantBegin
      begin
        RuboCop::Util.silence_warnings do
          # Avoid printing deprecation warnings about constants
          converted << Kernel.const_get(exception.source)
        end
      rescue NameError
        converted << nil
      end
      # rubocop:enable Style/RedundantBegin
    end
  else
    # treat an empty `rescue` as `rescue StandardError`
    [StandardError]
  end
end
find_shadowing_rescue(rescues) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 158
def find_shadowing_rescue(rescues)
  rescued_groups = rescued_groups_for(rescues)
  rescued_groups.zip(rescues).each do |group, res|
    return res if contains_multiple_levels_of_exceptions?(group)
  end

  rescued_groups.each_cons(2).with_index do |group_pair, i|
    return rescues[i] unless sorted?(group_pair)
  end
end
offense_range(rescues) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 84
def offense_range(rescues)
  shadowing_rescue = find_shadowing_rescue(rescues)
  expression = shadowing_rescue.loc.expression
  range_between(expression.begin_pos, expression.end_pos)
end
rescued_groups_for(rescues) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 90
def rescued_groups_for(rescues)
  rescues.map { |group| evaluate_exceptions(group) }
end
sorted?(rescued_groups) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 143
def sorted?(rescued_groups)
  rescued_groups.each_cons(2).all? do |x, y|
    if x.include?(Exception)
      false
    elsif y.include?(Exception) ||
          # consider sorted if a group is empty or only contains
          # `nil`s
          x.none? || y.none?
      true
    else
      (x <=> y || 0) <= 0
    end
  end
end
system_call_err?(error) click to toggle source
# File lib/rubocop/cop/lint/shadowed_exception.rb, line 115
def system_call_err?(error)
  error && error.ancestors[1] == SystemCallError
end