class RuboCop::Cop::ThreadSafety::MutableClassInstanceVariable
This cop checks whether some class instance variable isn't a mutable literal (e.g. array or hash).
It is based on Style/MutableConstant from RuboCop
. See github.com/rubocop-hq/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb
Class instance variables are a risk to threaded code as they are shared between threads. A mutable object such as an array or hash may be updated via an attr_reader so would not be detected by the ThreadSafety/ClassAndModuleAttributes cop.
Strict mode can be used to freeze all class instance variables, rather than just literals. Strict mode is considered an experimental feature. It has not been updated with an exhaustive list of all methods that will produce frozen objects so there is a decent chance of getting some false positives. Luckily, there is no harm in freezing an already frozen object.
@example EnforcedStyle: literals (default)
# bad class Model @list = [1, 2, 3] end # good class Model @list = [1, 2, 3].freeze end # good class Model @var = <<~TESTING.freeze This is a heredoc TESTING end # good class Model @var = Something.new end
@example EnforcedStyle: strict
# bad class Model @var = Something.new end # bad class Model @var = Struct.new do def foo puts 1 end end end # good class Model @var = Something.new.freeze end # good class Model @var = Struct.new do def foo puts 1 end end.freeze end
Constants
- FROZEN_STRING_LITERAL_TYPES_RUBY27
- FROZEN_STRING_LITERAL_TYPES_RUBY30
- MSG
Public Instance Methods
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 111 def autocorrect(node) expr = node.source_range lambda do |corrector| splat_value = splat_value(node) if splat_value correct_splat_expansion(corrector, expr, splat_value) elsif node.array_type? && !node.bracketed? corrector.insert_before(expr, '[') corrector.insert_after(expr, ']') elsif requires_parentheses?(node) corrector.insert_before(expr, '(') corrector.insert_after(expr, ')') end corrector.insert_after(expr, '.freeze') end end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 83 def on_ivasgn(node) return unless in_class?(node) _, value = *node on_assignment(value) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 98 def on_masgn(node) return unless in_class?(node) mlhs, values = *node return unless values.array_type? mlhs.to_a.zip(values.to_a).each do |lhs, value| next unless lhs.ivasgn_type? on_assignment(value) end end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 90 def on_or_asgn(node) lhs, value = *node return unless lhs&.ivasgn_type? return unless in_class?(node) on_assignment(value) end
Private Instance Methods
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 158 def check(value) return unless mutable_literal?(value) || range_enclosed_in_parentheses?(value) return if frozen_string_literal?(value) add_offense(value) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 175 def container?(node) return true if define_singleton_method?(node) %i[def defs class module].include?(node.type) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 200 def correct_splat_expansion(corrector, expr, splat_value) if range_enclosed_in_parentheses?(splat_value) corrector.replace(expr, "#{splat_value.source}.to_a") else corrector.replace(expr, "(#{splat_value.source}).to_a") end end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 132 def frozen_string_literal?(node) literal_types = if target_ruby_version >= 3.0 FROZEN_STRING_LITERAL_TYPES_RUBY30 else FROZEN_STRING_LITERAL_TYPES_RUBY27 end literal_types.include?(node.type) && frozen_string_literals_enabled? end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 187 def immutable_literal?(node) node.nil? || node.immutable_literal? end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 166 def in_class?(node) container = node.ancestors.find do |ancestor| container?(ancestor) end return false if container.nil? %i[class module].include?(container.type) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 181 def mutable_literal?(node) return if node.nil? node.mutable_literal? || range_type?(node) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 141 def on_assignment(value) if style == :strict strict_check(value) else check(value) end end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 196 def range_type?(node) node.erange_type? || node.irange_type? end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 191 def requires_parentheses?(node) range_type?(node) || (node.send_type? && node.loc.dot.nil?) end
# File lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb, line 149 def strict_check(value) return if immutable_literal?(value) return if operation_produces_immutable_object?(value) return if operation_produces_threadsafe_object?(value) return if frozen_string_literal?(value) add_offense(value) end