Class: RuboCop::Cop::Lint::NonAtomicFileOperation

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Defined in:
lib/rubocop/cop/lint/non_atomic_file_operation.rb

Overview

Checks for non-atomic file operation. And then replace it with a nearly equivalent and atomic method.

These can cause problems that are difficult to reproduce, especially in cases of frequent file operations in parallel, such as test runs with parallel_rspec.

For examples: creating a directory if there is none, has the following problems

An exception occurs when the directory didn’t exist at the time of exist?, but someone else created it before mkdir was executed.

Subsequent processes are executed without the directory that should be there when the directory existed at the time of exist?, but someone else deleted it shortly afterwards.

Examples:

# bad - race condition with another process may result in an error in `mkdir`
unless Dir.exist?(path)
  FileUtils.mkdir(path)
end

# good - atomic and idempotent creation
FileUtils.mkdir_p(path)

# bad - race condition with another process may result in an error in `remove`
if File.exist?(path)
  FileUtils.remove(path)
end

# good - atomic and idempotent removal
FileUtils.rm_f(path)

Cop Safety Information:

  • This cop is unsafe, because autocorrection change to atomic processing. The atomic processing of the replacement destination is not guaranteed to be strictly equivalent to that before the replacement.

Constant Summary collapse

MSG_REMOVE_FILE_EXIST_CHECK =
'Remove unnecessary existence check ' \
'`%<receiver>s.%<method_name>s`.'
MSG_CHANGE_FORCE_METHOD =
'Use atomic file operation method `FileUtils.%<method_name>s`.'
MAKE_FORCE_METHODS =
%i[makedirs mkdir_p mkpath].freeze
MAKE_METHODS =
%i[mkdir].freeze
REMOVE_FORCE_METHODS =
%i[rm_f rm_rf].freeze
REMOVE_METHODS =
%i[remove delete unlink remove_file rm rmdir safe_unlink].freeze
RECURSIVE_REMOVE_METHODS =
%i[remove_dir remove_entry remove_entry_secure].freeze
RESTRICT_ON_SEND =
(
  MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS + RECURSIVE_REMOVE_METHODS +
  REMOVE_FORCE_METHODS
).freeze

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from AutoCorrector

support_autocorrect?

Methods inherited from Base

#active_support_extensions_enabled?, #add_global_offense, #add_offense, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, #inspect, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #parse, #ready, #relevant_file?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from ExcludeLimit

#exclude_limit

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

silence_warnings

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#explicit_not_force?(node) ⇒ Object



76
77
78
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 76

def_node_search :explicit_not_force?, <<~PATTERN
  (pair (sym :force) (:false))
PATTERN

#force?(node) ⇒ Object



71
72
73
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 71

def_node_search :force?, <<~PATTERN
  (pair (sym :force) (:true))
PATTERN

#on_send(node) ⇒ Object



80
81
82
83
84
85
86
87
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 80

def on_send(node)
  return unless if_node_child?(node)
  return if explicit_not_force?(node)
  return unless (exist_node = send_exist_node(node.parent).first)
  return unless exist_node.first_argument == node.first_argument

  register_offense(node, exist_node)
end

#receiver_and_method_name(node) ⇒ Object



66
67
68
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 66

def_node_matcher :receiver_and_method_name, <<~PATTERN
  (send (const nil? $_) $_ ...)
PATTERN

#send_exist_node(node) ⇒ Object



61
62
63
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 61

def_node_search :send_exist_node, <<~PATTERN
  $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
PATTERN