Class: RuboCop::Socketry::Style::GlobalExceptionVariables

Inherits:
Cop::Base
  • Object
show all
Defined in:
lib/rubocop/socketry/style/global_exception_variables.rb

Overview

A RuboCop cop that warns against using global exception variables in unsafe contexts.

This cop discourages the use of:

  • $! (last exception)
  • $@ (backtrace of last exception)
  • $ERROR_INFO (English name for $!)
  • $ERROR_POSITION (English name for $@)

These global variables are implicit and can make code harder to understand.

However, this cop allows their use in safe contexts where the scope is well-defined:

  • Inside rescue blocks (well-defined scope)
  • In rescue modifiers (expression rescue $!)
  • In method parameter defaults (def foo(error = $!), def bar(error: $!))

This cop specifically flags their use in unsafe contexts:

  • Inside ensure blocks (extremely unsafe - exception state is unpredictable)
  • Outside of exception handling contexts

Examples:

# bad - unsafe in ensure block
begin
  risky_operation
ensure
  log($!.message) if $!  # unsafe!
end

# bad - outside exception handling
def process
  puts $!.message
end

# good - explicit exception handling
begin
  risky_operation
rescue => error
  puts error.message
end

# allowed - inside rescue block (well-defined scope)
begin
  risky_operation
rescue
  puts $!.message
end

# allowed - rescue modifier
result = risky_operation rescue $!

# allowed - parameter defaults
def foo(error = $!)
def bar(error: $!)

Constant Summary collapse

MSG =
"Avoid using global exception variable `%<variable>s` in %<context>s. Use explicit exception handling with `rescue => error` instead."
ENSURE_MSG =
"Using global exception variable `%<variable>s` in an ensure block is extremely unsafe."
EXCEPTION_VARIABLES =
%i[$! $@ $ERROR_INFO $ERROR_POSITION].freeze

Instance Method Summary collapse

Instance Method Details

#on_gvar(node) ⇒ Object

Check each global variable reference for unsafe exception state access.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rubocop/socketry/style/global_exception_variables.rb', line 71

def on_gvar(node)
	variable_name = node.children.first
	
	return unless EXCEPTION_VARIABLES.include?(variable_name)
	
	# Allow in parameter defaults (explicitly opting in)
	return if in_parameter_default?(node)
	
	# Allow in rescue modifier (well-defined scope)
	return if in_rescue_modifier?(node)
	
	# Allow in rescue block (well-defined scope)
	return if in_rescue_block?(node)
	
	# Flag if in ensure block (extremely unsafe)
	if in_ensure_block?(node)
		add_offense(
			node,
			message: format(ENSURE_MSG, variable: variable_name)
		)
		return
	end
	
	# Flag in all other contexts
	add_offense(
		node,
		message: format(MSG, variable: variable_name, context: "this context")
	)
end