rubocop-thread_safety 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -16
- data/README.md +4 -0
- data/config/default.yml +22 -0
- data/lib/rubocop/cop/thread_safety/active_support_callbacks.rb +52 -0
- data/lib/rubocop/cop/thread_safety/class_instance_variable.rb +3 -3
- data/lib/rubocop/cop/thread_safety/dir_chdir.rb +2 -2
- data/lib/rubocop/cop/thread_safety/env_mutation.rb +73 -0
- data/lib/rubocop/cop/thread_safety/lazy_synchronization_primitive.rb +65 -0
- data/lib/rubocop/cop/thread_safety/method_redefinition.rb +41 -0
- data/lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb +15 -1
- data/lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb +1 -1
- data/lib/rubocop/cop/thread_safety.rb +24 -0
- data/lib/rubocop/thread_safety/plugin.rb +2 -2
- data/lib/rubocop/thread_safety/version.rb +1 -1
- data/lib/rubocop-thread_safety.rb +1 -8
- metadata +28 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c093be2fec5fa73fc90a6af37b1aece06ae43e02d079aef4ffb15948db8b3799
|
|
4
|
+
data.tar.gz: 640f785bc8cf61a88f87eda3fd806f775499bd517950b6f2c3e6dc79f7321d75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12f68444eaca92647816c292715a6a54af6d989b77c38619637e018817ad7dd990033defbf3ab64f7d9f51a85a1904096ac6ff6817befc77a5e2f73470061a2d
|
|
7
|
+
data.tar.gz: b9492879e05682caab54066a728d1eb386ff1ce83c713beb17cbb69f51cab0df8d561d5e594baa32204c426627be39674867f0b0574bbef015f8baac69886b3c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
* [#112](https://github.com/rubocop/rubocop-thread_safety/pull/112): Speed up loading rubocop-thread_safety by lazily loading only the cops needed for a run. This requires RuboCop 1.89.0+. ([@koic][])
|
|
6
|
+
* [#111](https://github.com/rubocop/rubocop-thread_safety/pull/111): Add new `ThreadSafety/EnvMutation` cop. ([@mikegee][])
|
|
7
|
+
* [#109](https://github.com/rubocop/rubocop-thread_safety/pull/109): Add new `ThreadSafety/LazySynchronizationPrimitive` cop. ([@viralpraxis][])
|
|
8
|
+
* [#107](https://github.com/rubocop/rubocop-thread_safety/pull/107): Add new `ThreadSafety/ActiveSupportCallbacks` cop. ([@mikegee][])
|
|
9
|
+
* [#104](https://github.com/rubocop/rubocop-thread_safety/pull/104): Set minimum required RuboCop version to `1.81`. ([@viralpraxis][])
|
|
10
|
+
* [#95](https://github.com/rubocop/rubocop-thread_safety/pull/95): Add new `ThreadSafety/MethodRedefinition` cop. ([@viralpraxis][])
|
|
11
|
+
* [#91](https://github.com/rubocop/rubocop-thread_safety/pull/91): Make `ThreadSafety/MutableClassInstanceVariable` cop aware of `TestCase`'s `setup`/`teardown` DSL. ([@viralpraxis][])
|
|
12
|
+
|
|
13
|
+
## 0.7.3
|
|
14
|
+
|
|
15
|
+
* [#93](https://github.com/rubocop/rubocop-thread_safety/pull/93): Fix an error for `ThreadSafety/MutableClassInstanceVariable` cop. ([@viralpraxis][])
|
|
4
16
|
|
|
5
17
|
## 0.7.2
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
* [#88](https://github.com/rubocop/rubocop-thread_safety/pull/88): Fix incorrect plugin metadata version. ([@viralpraxis][])
|
|
8
20
|
|
|
9
21
|
## 0.7.1
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
* [#84](https://github.com/rubocop/rubocop-thread_safety/pull/84): Rename `InstanceVariableInClassMethod` in default config ([@sambostock][])
|
|
12
24
|
|
|
13
25
|
## 0.7.0
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
* [#80](https://github.com/rubocop/rubocop-thread_safety/pull/80) Make RuboCop ThreadSafety work as a RuboCop plugin. ([@bquorning][])
|
|
28
|
+
* [#76](https://github.com/rubocop/rubocop-thread_safety/pull/76): Detect offenses when using safe navigation for `ThreadSafety/DirChdir`, `ThreadSafety/NewThread` and `ThreadSafety/RackMiddlewareInstanceVariable` cops. ([@viralpraxis][])
|
|
29
|
+
* [#73](https://github.com/rubocop/rubocop-thread_safety/pull/73): Add `AllowCallWithBlock` option to `ThreadSafety/DirChdir` cop. ([@viralpraxis][])
|
|
18
30
|
|
|
19
31
|
## 0.6.0
|
|
20
32
|
|
|
21
|
-
* [#59](https://github.com/rubocop/rubocop-thread_safety/pull/59): Rename `ThreadSafety::InstanceVariableInClassMethod` cop to `ThreadSafety::ClassInstanceVariable` to better reflect its purpose. ([@viralpraxis]
|
|
22
|
-
* [#55](https://github.com/rubocop/rubocop-thread_safety/pull/55): Enhance `ThreadSafety::InstanceVariableInClassMethod` cop to detect offenses within `class_eval/exec` blocks. ([@viralpraxis]
|
|
23
|
-
* [#54](https://github.com/rubocop/rubocop-thread_safety/pull/54): Drop support for RuboCop older than 1.48. ([@viralpraxis]
|
|
24
|
-
* [#52](https://github.com/rubocop/rubocop-thread_safety/pull/52): Add new `RackMiddlewareInstanceVariable` cop to detect instance variables in Rack middleware. ([@viralpraxis]
|
|
25
|
-
* [#48](https://github.com/rubocop/rubocop-thread_safety/pull/48): Do not report instance variables in `ActionDispatch` callbacks in singleton methods. ([@viralpraxis]
|
|
26
|
-
* [#43](https://github.com/rubocop/rubocop-thread_safety/pull/43): Make detection of ActiveSupport's `class_attribute` configurable. ([@viralpraxis]
|
|
27
|
-
* [#42](https://github.com/rubocop/rubocop-thread_safety/pull/42): Fix some `InstanceVariableInClassMethod` cop false positive offenses. ([@viralpraxis]
|
|
28
|
-
* [#41](https://github.com/rubocop/rubocop-thread_safety/pull/41): Drop support for MRI older than 2.7. ([@viralpraxis]
|
|
29
|
-
* [#38](https://github.com/rubocop/rubocop-thread_safety/pull/38): Fix `NewThread` cop detection is case of `Thread.start`, `Thread.fork`, or `Thread.new` with arguments. ([@viralpraxis]
|
|
30
|
-
* [#36](https://github.com/rubocop/rubocop-thread_safety/pull/36): Add new `DirChdir` cop to detect `Dir.chdir` calls. ([@viralpraxis]
|
|
33
|
+
* [#59](https://github.com/rubocop/rubocop-thread_safety/pull/59): Rename `ThreadSafety::InstanceVariableInClassMethod` cop to `ThreadSafety::ClassInstanceVariable` to better reflect its purpose. ([@viralpraxis][])
|
|
34
|
+
* [#55](https://github.com/rubocop/rubocop-thread_safety/pull/55): Enhance `ThreadSafety::InstanceVariableInClassMethod` cop to detect offenses within `class_eval/exec` blocks. ([@viralpraxis][])
|
|
35
|
+
* [#54](https://github.com/rubocop/rubocop-thread_safety/pull/54): Drop support for RuboCop older than 1.48. ([@viralpraxis][])
|
|
36
|
+
* [#52](https://github.com/rubocop/rubocop-thread_safety/pull/52): Add new `RackMiddlewareInstanceVariable` cop to detect instance variables in Rack middleware. ([@viralpraxis][])
|
|
37
|
+
* [#48](https://github.com/rubocop/rubocop-thread_safety/pull/48): Do not report instance variables in `ActionDispatch` callbacks in singleton methods. ([@viralpraxis][])
|
|
38
|
+
* [#43](https://github.com/rubocop/rubocop-thread_safety/pull/43): Make detection of ActiveSupport's `class_attribute` configurable. ([@viralpraxis][])
|
|
39
|
+
* [#42](https://github.com/rubocop/rubocop-thread_safety/pull/42): Fix some `InstanceVariableInClassMethod` cop false positive offenses. ([@viralpraxis][])
|
|
40
|
+
* [#41](https://github.com/rubocop/rubocop-thread_safety/pull/41): Drop support for MRI older than 2.7. ([@viralpraxis][])
|
|
41
|
+
* [#38](https://github.com/rubocop/rubocop-thread_safety/pull/38): Fix `NewThread` cop detection is case of `Thread.start`, `Thread.fork`, or `Thread.new` with arguments. ([@viralpraxis][])
|
|
42
|
+
* [#36](https://github.com/rubocop/rubocop-thread_safety/pull/36): Add new `DirChdir` cop to detect `Dir.chdir` calls. ([@viralpraxis][])
|
|
43
|
+
|
|
44
|
+
[@bquorning]: https://github.com/bquorning
|
|
45
|
+
[@koic]: https://github.com/koic
|
|
46
|
+
[@mikegee]: https://github.com/mikegee
|
|
47
|
+
[@sambostock]: https://github.com/sambostock
|
|
48
|
+
[@viralpraxis]: https://github.com/viralpraxis
|
data/README.md
CHANGED
|
@@ -65,6 +65,10 @@ Improvements that would make shared state thread-safe include:
|
|
|
65
65
|
|
|
66
66
|
Certain system calls, such as `chdir`, affect the entire process. To avoid potential thread-safety issues, it's preferable to use (if possible) the `chdir` option in methods like `Kernel.system` and `IO.popen` rather than relying on `Dir.chdir`.
|
|
67
67
|
|
|
68
|
+
## Useful resources
|
|
69
|
+
|
|
70
|
+
* Intro to thread-safety in Ruby by Socketry: https://socketry.github.io/async/guides/thread-safety/index.html
|
|
71
|
+
|
|
68
72
|
## Development
|
|
69
73
|
|
|
70
74
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/config/default.yml
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Without adding these to your rubocop config, these values will be the default.
|
|
4
4
|
|
|
5
|
+
ThreadSafety/ActiveSupportCallbacks:
|
|
6
|
+
Description: 'Avoid mutating ActiveSupport callback chains at runtime.'
|
|
7
|
+
Enabled: pending
|
|
8
|
+
VersionAdded: '0.8'
|
|
9
|
+
|
|
5
10
|
ThreadSafety/ClassAndModuleAttributes:
|
|
6
11
|
Description: 'Avoid mutating class and module attributes.'
|
|
7
12
|
Enabled: true
|
|
@@ -11,6 +16,18 @@ ThreadSafety/ClassInstanceVariable:
|
|
|
11
16
|
Description: 'Avoid class instance variables.'
|
|
12
17
|
Enabled: true
|
|
13
18
|
|
|
19
|
+
ThreadSafety/MethodRedefinition:
|
|
20
|
+
Description: 'Do not use `remove_method` followed by method definition.'
|
|
21
|
+
Enabled: pending
|
|
22
|
+
VersionAdded: '0.8'
|
|
23
|
+
References:
|
|
24
|
+
- 'https://bugs.ruby-lang.org/issues/13574'
|
|
25
|
+
|
|
26
|
+
ThreadSafety/LazySynchronizationPrimitive:
|
|
27
|
+
Description: 'Do not lazily initialize synchronization primitives with `||=`.'
|
|
28
|
+
Enabled: pending
|
|
29
|
+
VersionAdded: '0.8'
|
|
30
|
+
|
|
14
31
|
ThreadSafety/MutableClassInstanceVariable:
|
|
15
32
|
Description: 'Do not assign mutable objects to class instance variables.'
|
|
16
33
|
Enabled: true
|
|
@@ -37,6 +54,11 @@ ThreadSafety/DirChdir:
|
|
|
37
54
|
Enabled: true
|
|
38
55
|
AllowCallWithBlock: false
|
|
39
56
|
|
|
57
|
+
ThreadSafety/EnvMutation:
|
|
58
|
+
Description: Avoid mutating `ENV` due to its process-wide effect.
|
|
59
|
+
Enabled: false
|
|
60
|
+
VersionAdded: '0.8'
|
|
61
|
+
|
|
40
62
|
ThreadSafety/RackMiddlewareInstanceVariable:
|
|
41
63
|
Description: Avoid instance variables in Rack middleware.
|
|
42
64
|
Enabled: true
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ThreadSafety
|
|
6
|
+
# Avoid mutating ActiveSupport callback chains at runtime.
|
|
7
|
+
#
|
|
8
|
+
# Calls such as `User.skip_callback` and `User.set_callback` mutate callback
|
|
9
|
+
# chains at process scope.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# Site.skip_callback(:commit, :after, :after_owner_change)
|
|
14
|
+
#
|
|
15
|
+
# # bad
|
|
16
|
+
# Site.set_callback(
|
|
17
|
+
# :commit, :after, :after_owner_change,
|
|
18
|
+
# if: :saved_change_to_owner?
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# class User < ApplicationRecord
|
|
23
|
+
# skip_callback :commit, :after, :after_owner_change
|
|
24
|
+
# end
|
|
25
|
+
class ActiveSupportCallbacks < Base
|
|
26
|
+
requires_gem 'activesupport'
|
|
27
|
+
|
|
28
|
+
MSG = 'Avoid process-wide ActiveSupport callback mutation with `%<expression>s`.'
|
|
29
|
+
RESTRICT_ON_SEND = %i[set_callback skip_callback].freeze
|
|
30
|
+
|
|
31
|
+
def on_send(node)
|
|
32
|
+
return unless constant_receiver?(node.receiver)
|
|
33
|
+
|
|
34
|
+
add_offense(node.loc.selector, message: format(MSG, expression: callback_call(node)))
|
|
35
|
+
end
|
|
36
|
+
alias on_csend on_send
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def constant_receiver?(receiver)
|
|
41
|
+
receiver = receiver.children.first while receiver&.begin_type? && receiver.children.one?
|
|
42
|
+
|
|
43
|
+
receiver&.const_type?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def callback_call(node)
|
|
47
|
+
"#{node.receiver.source}#{node.loc.dot.source}#{node.method_name}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -227,9 +227,9 @@ module RuboCop
|
|
|
227
227
|
# @!method new_lexical_scope?(node)
|
|
228
228
|
def_node_matcher :new_lexical_scope?, <<~PATTERN
|
|
229
229
|
{
|
|
230
|
-
(block (send (const nil? :Struct) :new ...) _ (
|
|
231
|
-
(block (send (const nil? :Class) :new ...) _ (
|
|
232
|
-
(block (send (const nil? :Data) :define ...) _ (
|
|
230
|
+
(block (send (const nil? :Struct) :new ...) _ (any_def ...))
|
|
231
|
+
(block (send (const nil? :Class) :new ...) _ (any_def ...))
|
|
232
|
+
(block (send (const nil? :Data) :define ...) _ (any_def ...))
|
|
233
233
|
(block
|
|
234
234
|
(send nil?
|
|
235
235
|
{
|
|
@@ -15,13 +15,13 @@ module RuboCop
|
|
|
15
15
|
# FileUtils.chdir("/var/run")
|
|
16
16
|
#
|
|
17
17
|
# @example AllowCallWithBlock: false (default)
|
|
18
|
-
# #
|
|
18
|
+
# # bad
|
|
19
19
|
# Dir.chdir("/var/run") do
|
|
20
20
|
# puts Dir.pwd
|
|
21
21
|
# end
|
|
22
22
|
#
|
|
23
23
|
# @example AllowCallWithBlock: true
|
|
24
|
-
# #
|
|
24
|
+
# # good
|
|
25
25
|
# Dir.chdir("/var/run") do
|
|
26
26
|
# puts Dir.pwd
|
|
27
27
|
# end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ThreadSafety
|
|
6
|
+
# Avoid mutating `ENV`.
|
|
7
|
+
#
|
|
8
|
+
# Environment variables are process-wide. Mutating them can affect other
|
|
9
|
+
# threads, requests, jobs, or subprocesses running in the same Ruby
|
|
10
|
+
# process.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# ENV['TZ'] = 'UTC'
|
|
15
|
+
#
|
|
16
|
+
# # bad
|
|
17
|
+
# ENV.update('FOO' => 'bar')
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# system({ 'TZ' => 'UTC' }, 'date')
|
|
21
|
+
#
|
|
22
|
+
# # good
|
|
23
|
+
# ENV.fetch('TZ', 'UTC')
|
|
24
|
+
class EnvMutation < Base
|
|
25
|
+
MSG = 'Avoid mutating `ENV` due to its process-wide effect.'
|
|
26
|
+
RESTRICT_ON_SEND = %i[
|
|
27
|
+
[]=
|
|
28
|
+
store
|
|
29
|
+
update
|
|
30
|
+
merge!
|
|
31
|
+
replace
|
|
32
|
+
delete
|
|
33
|
+
delete_if
|
|
34
|
+
reject!
|
|
35
|
+
keep_if
|
|
36
|
+
select!
|
|
37
|
+
filter!
|
|
38
|
+
shift
|
|
39
|
+
clear
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
# @!method env_mutation?(node)
|
|
43
|
+
def_node_matcher :env_mutation?, <<~PATTERN
|
|
44
|
+
(call
|
|
45
|
+
(const {nil? cbase} :ENV)
|
|
46
|
+
{
|
|
47
|
+
:[]=
|
|
48
|
+
:store
|
|
49
|
+
:update
|
|
50
|
+
:merge!
|
|
51
|
+
:replace
|
|
52
|
+
:delete
|
|
53
|
+
:delete_if
|
|
54
|
+
:reject!
|
|
55
|
+
:keep_if
|
|
56
|
+
:select!
|
|
57
|
+
:filter!
|
|
58
|
+
:shift
|
|
59
|
+
:clear
|
|
60
|
+
}
|
|
61
|
+
...)
|
|
62
|
+
PATTERN
|
|
63
|
+
|
|
64
|
+
def on_send(node)
|
|
65
|
+
return unless env_mutation?(node)
|
|
66
|
+
|
|
67
|
+
add_offense(node)
|
|
68
|
+
end
|
|
69
|
+
alias on_csend on_send
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ThreadSafety
|
|
6
|
+
# Avoid lazily initializing synchronization primitives with `||=`.
|
|
7
|
+
#
|
|
8
|
+
# The check-then-set performed by `||=` is not atomic, so concurrent threads
|
|
9
|
+
# can observe an uninitialized primitive or create more than one instance.
|
|
10
|
+
# Eagerly assign the primitive (for example in `initialize`) or use a constant.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# def mutex
|
|
15
|
+
# @mutex ||= Mutex.new
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # bad
|
|
19
|
+
# def mutex = @mutex ||= Mutex.new
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# def initialize
|
|
23
|
+
# @mutex = Mutex.new
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
class LazySynchronizationPrimitive < Base
|
|
27
|
+
MSG = 'Do not lazily initialize synchronization primitives with `||=`.'
|
|
28
|
+
|
|
29
|
+
# @!method lazy_shared_variable_assignment?(node)
|
|
30
|
+
def_node_matcher :lazy_shared_variable_assignment?, <<~PATTERN
|
|
31
|
+
(or_asgn ${ivasgn cvasgn} _)
|
|
32
|
+
PATTERN
|
|
33
|
+
|
|
34
|
+
# @!method synchronization_primitive?(node)
|
|
35
|
+
def_node_matcher :synchronization_primitive?, <<~PATTERN
|
|
36
|
+
{
|
|
37
|
+
(send (const {nil? cbase} {:Mutex :Monitor}) :new ...)
|
|
38
|
+
(send (const (const nil? :Thread) :Mutex) :new ...)
|
|
39
|
+
}
|
|
40
|
+
PATTERN
|
|
41
|
+
|
|
42
|
+
def on_or_asgn(node)
|
|
43
|
+
return unless lazy_shared_variable_assignment?(node)
|
|
44
|
+
return unless synchronization_primitive?(node.rhs)
|
|
45
|
+
return unless method_definition?(node)
|
|
46
|
+
return if synchronized?(node)
|
|
47
|
+
|
|
48
|
+
add_offense(node)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def method_definition?(node)
|
|
54
|
+
node.each_ancestor(:any_def).any?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def synchronized?(node)
|
|
58
|
+
node.each_ancestor(:block).any? do |ancestor|
|
|
59
|
+
ancestor.method?(:synchronize)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ThreadSafety
|
|
6
|
+
# Avoid the thread-unsafe combination of remove_method followed by defining a method with the same name.
|
|
7
|
+
# This can lead to a race condition, as these two actions are not atomic.
|
|
8
|
+
# As a safer alternative, consider aliasing the method to itself instead.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad
|
|
12
|
+
# remove_method :foo
|
|
13
|
+
# def foo; end
|
|
14
|
+
#
|
|
15
|
+
# # good
|
|
16
|
+
# alias_method :foo, :foo
|
|
17
|
+
# def foo; end
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# alias foo foo
|
|
21
|
+
# def foo; end
|
|
22
|
+
#
|
|
23
|
+
class MethodRedefinition < Base
|
|
24
|
+
MSG = 'Do not use `remove_method` followed by method definition.'
|
|
25
|
+
|
|
26
|
+
RESTRICT_ON_SEND = %i[remove_method].freeze
|
|
27
|
+
|
|
28
|
+
def on_send(node)
|
|
29
|
+
return unless (def_node = node.right_sibling)
|
|
30
|
+
return unless def_node.def_type?
|
|
31
|
+
return unless node.arguments.one?
|
|
32
|
+
return unless (method_name_node = node.first_argument).type?(:str, :sym)
|
|
33
|
+
return unless def_node.method?(method_name_node.value)
|
|
34
|
+
|
|
35
|
+
add_offense(node)
|
|
36
|
+
end
|
|
37
|
+
alias on_csend on_send
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -138,6 +138,8 @@ module RuboCop
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def on_assignment(value)
|
|
141
|
+
return if within_dsl_with_threadsafe_semantics?(value)
|
|
142
|
+
|
|
141
143
|
if style == :strict
|
|
142
144
|
strict_check(value)
|
|
143
145
|
else
|
|
@@ -194,7 +196,7 @@ module RuboCop
|
|
|
194
196
|
|
|
195
197
|
def requires_parentheses?(node)
|
|
196
198
|
range_type?(node) ||
|
|
197
|
-
(node.send_type? && node.loc.dot.nil?)
|
|
199
|
+
(node.send_type? && (node.loc.dot.nil? || (node.arguments.any? && !node.parenthesized_call?)))
|
|
198
200
|
end
|
|
199
201
|
|
|
200
202
|
def range_type?(node)
|
|
@@ -247,6 +249,18 @@ module RuboCop
|
|
|
247
249
|
def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
|
|
248
250
|
(begin (range _ _))
|
|
249
251
|
PATTERN
|
|
252
|
+
|
|
253
|
+
# rubocop:disable-next Metrics
|
|
254
|
+
def within_dsl_with_threadsafe_semantics?(node)
|
|
255
|
+
return false unless node
|
|
256
|
+
return false unless (block_node = node.ancestors.find(&:block_type?))
|
|
257
|
+
return false unless block_node.method?(:setup) || block_node.method?(:teardown)
|
|
258
|
+
|
|
259
|
+
class_node = block_node.ancestors.find(&:class_type?)
|
|
260
|
+
return false unless class_node
|
|
261
|
+
|
|
262
|
+
class_node.parent_class&.const_name == 'ActiveSupport::TestCase'
|
|
263
|
+
end
|
|
250
264
|
end
|
|
251
265
|
end
|
|
252
266
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'mixin/operation_with_threadsafe_result'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
# Cops for the `ThreadSafety` department. The department's cops are
|
|
8
|
+
# registered for lazy loading and their files are loaded on demand.
|
|
9
|
+
module ThreadSafety
|
|
10
|
+
extend LazyLoader
|
|
11
|
+
|
|
12
|
+
register_cop :ActiveSupportCallbacks, "#{__dir__}/thread_safety/active_support_callbacks"
|
|
13
|
+
register_cop :ClassInstanceVariable, "#{__dir__}/thread_safety/class_instance_variable"
|
|
14
|
+
register_cop :ClassAndModuleAttributes, "#{__dir__}/thread_safety/class_and_module_attributes"
|
|
15
|
+
register_cop :MutableClassInstanceVariable, "#{__dir__}/thread_safety/mutable_class_instance_variable"
|
|
16
|
+
register_cop :NewThread, "#{__dir__}/thread_safety/new_thread"
|
|
17
|
+
register_cop :DirChdir, "#{__dir__}/thread_safety/dir_chdir"
|
|
18
|
+
register_cop :EnvMutation, "#{__dir__}/thread_safety/env_mutation"
|
|
19
|
+
register_cop :RackMiddlewareInstanceVariable, "#{__dir__}/thread_safety/rack_middleware_instance_variable"
|
|
20
|
+
register_cop :MethodRedefinition, "#{__dir__}/thread_safety/method_redefinition"
|
|
21
|
+
register_cop :LazySynchronizationPrimitive, "#{__dir__}/thread_safety/lazy_synchronization_primitive"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -6,7 +6,7 @@ module RuboCop
|
|
|
6
6
|
module ThreadSafety
|
|
7
7
|
# A plugin that integrates RuboCop ThreadSafety with RuboCop's plugin system.
|
|
8
8
|
class Plugin < LintRoller::Plugin
|
|
9
|
-
# :
|
|
9
|
+
# simplecov:disable
|
|
10
10
|
def about
|
|
11
11
|
LintRoller::About.new(
|
|
12
12
|
name: 'rubocop-thread_safety',
|
|
@@ -15,7 +15,7 @@ module RuboCop
|
|
|
15
15
|
description: 'Thread-safety checks via static analysis.'
|
|
16
16
|
)
|
|
17
17
|
end
|
|
18
|
-
# :
|
|
18
|
+
# simplecov:enable
|
|
19
19
|
|
|
20
20
|
def supported?(context)
|
|
21
21
|
context.engine == :rubocop
|
|
@@ -6,11 +6,4 @@ require 'rubocop/thread_safety'
|
|
|
6
6
|
require 'rubocop/thread_safety/version'
|
|
7
7
|
require 'rubocop/thread_safety/plugin'
|
|
8
8
|
|
|
9
|
-
require 'rubocop/cop/
|
|
10
|
-
|
|
11
|
-
require 'rubocop/cop/thread_safety/class_instance_variable'
|
|
12
|
-
require 'rubocop/cop/thread_safety/class_and_module_attributes'
|
|
13
|
-
require 'rubocop/cop/thread_safety/mutable_class_instance_variable'
|
|
14
|
-
require 'rubocop/cop/thread_safety/new_thread'
|
|
15
|
-
require 'rubocop/cop/thread_safety/dir_chdir'
|
|
16
|
-
require 'rubocop/cop/thread_safety/rack_middleware_instance_variable'
|
|
9
|
+
require 'rubocop/cop/thread_safety'
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-thread_safety
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Gee
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: lint_roller
|
|
@@ -29,20 +29,34 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.
|
|
33
|
-
- - ">="
|
|
34
|
-
- !ruby/object:Gem::Version
|
|
35
|
-
version: 1.72.1
|
|
32
|
+
version: '1.89'
|
|
36
33
|
type: :runtime
|
|
37
34
|
prerelease: false
|
|
38
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
39
36
|
requirements:
|
|
40
37
|
- - "~>"
|
|
41
38
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: '1.
|
|
39
|
+
version: '1.89'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop-ast
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
43
44
|
- - ">="
|
|
44
45
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: 1.
|
|
46
|
+
version: 1.44.0
|
|
47
|
+
- - "<"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '2.0'
|
|
50
|
+
type: :runtime
|
|
51
|
+
prerelease: false
|
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: 1.44.0
|
|
57
|
+
- - "<"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '2.0'
|
|
46
60
|
description: |2
|
|
47
61
|
Thread-safety checks via static analysis.
|
|
48
62
|
A plugin for the RuboCop code style enforcing & linting tool.
|
|
@@ -59,9 +73,14 @@ files:
|
|
|
59
73
|
- config/obsoletion.yml
|
|
60
74
|
- lib/rubocop-thread_safety.rb
|
|
61
75
|
- lib/rubocop/cop/mixin/operation_with_threadsafe_result.rb
|
|
76
|
+
- lib/rubocop/cop/thread_safety.rb
|
|
77
|
+
- lib/rubocop/cop/thread_safety/active_support_callbacks.rb
|
|
62
78
|
- lib/rubocop/cop/thread_safety/class_and_module_attributes.rb
|
|
63
79
|
- lib/rubocop/cop/thread_safety/class_instance_variable.rb
|
|
64
80
|
- lib/rubocop/cop/thread_safety/dir_chdir.rb
|
|
81
|
+
- lib/rubocop/cop/thread_safety/env_mutation.rb
|
|
82
|
+
- lib/rubocop/cop/thread_safety/lazy_synchronization_primitive.rb
|
|
83
|
+
- lib/rubocop/cop/thread_safety/method_redefinition.rb
|
|
65
84
|
- lib/rubocop/cop/thread_safety/mutable_class_instance_variable.rb
|
|
66
85
|
- lib/rubocop/cop/thread_safety/new_thread.rb
|
|
67
86
|
- lib/rubocop/cop/thread_safety/rack_middleware_instance_variable.rb
|
|
@@ -91,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
91
110
|
- !ruby/object:Gem::Version
|
|
92
111
|
version: '0'
|
|
93
112
|
requirements: []
|
|
94
|
-
rubygems_version:
|
|
113
|
+
rubygems_version: 4.0.16
|
|
95
114
|
specification_version: 4
|
|
96
115
|
summary: Thread-safety checks via static analysis
|
|
97
116
|
test_files: []
|