rubocop-thread_safety 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2f9c0ded508d65e482cd339fc6bafe4a843f19982884ea8c03aa0f6ceb1f4cd
4
- data.tar.gz: bcfb88db93ee4c0bacdb1df6e60e72bdd044209ecd1c9c27fc8b8e8597f5eff9
3
+ metadata.gz: ca09c8c0f8772fca865f55df6d350d28c0d3b6ab11a81f2837b76b74ce29b8bb
4
+ data.tar.gz: 16ac028cb86f4853370d6863974e2ac6cacd64253fbe9b93d2f4084ec0e89125
5
5
  SHA512:
6
- metadata.gz: 81a037e33675b2aa5cf77bad8732b43e4b09bcbb0fc1127fe64eda0481516a20068e70f3afd32d4a69673729c8ce68422374b81ad7788a9de6bbe0ab9d4ae4bd
7
- data.tar.gz: e6c0c3e2626e371eaf47b7fc144eb7659661d98184c47e2002d49a3f276e744f6ebc67c932fa361ff4379e536322d775579303b51e0dcf806907e9e712e61532
6
+ metadata.gz: 5c303f9423fe7dccfc38f47ce3a22bcc2dbb573b6754c46c3216bf49787e0bf4c5ab162af9cd1471021b5b6eea1a57ec5358d4cb699bd5a369d2116fb0d4b25b
7
+ data.tar.gz: 57ca57201e7dbc8e36458d1d97d95aae84a6b7ec7cd7d4282fd632b8f29caf54f98402315d678dbbf98f9a83d2cfecd411cb345907dead297d19f56ad1d94ee3
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
+ /gemfiles/*.lock
4
5
  /_yardoc/
5
6
  /coverage/
6
7
  /doc/
@@ -1,5 +1,12 @@
1
1
  AllCops:
2
2
  DisplayCopNames: true
3
+ TargetRubyVersion: 2.3
4
+
5
+ Lint/RaiseException:
6
+ Enabled: true
7
+
8
+ Lint/StructNewOverride:
9
+ Enabled: true
3
10
 
4
11
  Metrics/BlockLength:
5
12
  Exclude:
@@ -24,6 +31,19 @@ Style/AutoResourceCleanup:
24
31
  Style/CollectionMethods:
25
32
  Enabled: true
26
33
 
34
+ Style/FrozenStringLiteralComment:
35
+ Exclude:
36
+ - "gemfiles/*.gemfile"
37
+
38
+ Style/HashEachMethods:
39
+ Enabled: true
40
+
41
+ Style/HashTransformKeys:
42
+ Enabled: false
43
+
44
+ Style/HashTransformValues:
45
+ Enabled: false
46
+
27
47
  Style/MethodCalledOnDoEndBlock:
28
48
  Enabled: true
29
49
  Exclude:
@@ -39,6 +59,10 @@ Style/OptionHash:
39
59
  Style/Send:
40
60
  Enabled: true
41
61
 
62
+ Style/StringLiterals:
63
+ Exclude:
64
+ - "gemfiles/*.gemfile"
65
+
42
66
  Style/StringMethods:
43
67
  Enabled: true
44
68
 
@@ -1,20 +1,55 @@
1
- sudo: false
2
1
  cache: bundler
3
2
  language: ruby
4
3
  rvm:
5
- - jruby-9.1.14.0
4
+ - jruby-9.2.9.0
6
5
  - 2.3.0
7
6
  - 2.4
8
7
  - 2.5
9
8
  - 2.6
10
9
  - 2.7
11
10
 
12
- matrix:
11
+ gemfile:
12
+ - gemfiles/rubocop_0.53.gemfile
13
+ - gemfiles/rubocop_0.81.gemfile
14
+ - gemfiles/rubocop_0.86.gemfile
15
+
16
+ script_rubocop: &script_rubocop
17
+ - bundle exec rspec
18
+ - bundle exec rubocop
19
+
20
+ jobs:
13
21
  fast_finish: true
22
+ exclude:
23
+ - rvm: 2.3.0
24
+ gemfile: gemfiles/rubocop_0.86.gemfile
25
+ - rvm: 2.5
26
+ gemfile: gemfiles/rubocop_0.53.gemfile
27
+ - rvm: 2.6
28
+ gemfile: gemfiles/rubocop_0.53.gemfile
29
+ - rvm: 2.7
30
+ gemfile: gemfiles/rubocop_0.53.gemfile
31
+ include:
32
+ - rvm: jruby-9.2.9.0
33
+ gemfile: gemfiles/rubocop_0.81.gemfile
34
+ script: *script_rubocop
35
+ - rvm: 2.3.0
36
+ gemfile: gemfiles/rubocop_0.81.gemfile
37
+ script: *script_rubocop
38
+ - rvm: 2.4
39
+ gemfile: gemfiles/rubocop_0.81.gemfile
40
+ script: *script_rubocop
41
+ - rvm: 2.5
42
+ gemfile: gemfiles/rubocop_0.81.gemfile
43
+ script: *script_rubocop
44
+ - rvm: 2.6
45
+ gemfile: gemfiles/rubocop_0.81.gemfile
46
+ script: *script_rubocop
47
+ - rvm: 2.7
48
+ gemfile: gemfiles/rubocop_0.81.gemfile
49
+ script: *script_rubocop
14
50
 
15
51
  before_install: gem install --remote bundler
16
52
  install:
17
53
  - bundle install --retry=3
18
54
  script:
19
55
  - bundle exec rspec
20
- - bundle exec rubocop
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise 'rubocop-0.53' do
4
+ gem 'rubocop', '~> 0.53.0'
5
+ end
6
+
7
+ appraise 'rubocop-0.81' do
8
+ gem 'rubocop', '~> 0.81.0'
9
+ end
10
+
11
+ if Gem::Requirement.new('>= 2.4.0')
12
+ .satisfied_by?(Gem::Version.new(RUBY_VERSION))
13
+ appraise 'rubocop-0.86' do
14
+ gem 'rubocop', '~> 0.86.0'
15
+ end
16
+ end
data/README.md CHANGED
@@ -38,6 +38,27 @@ Scan the application for just thread-safety issues:
38
38
 
39
39
  There are some added [configuration options](https://github.com/covermymeds/rubocop-thread_safety/blob/master/config/default.yml) that can be tweaked to modify the behaviour of these thread-safety cops.
40
40
 
41
+ ### Correcting code for thread-safety
42
+
43
+ There are a few ways to improve thread-safety that stem around avoiding
44
+ unsynchronized mutation of state that is shared between multiple threads.
45
+
46
+ State shared between threads may take various forms, including:
47
+
48
+ * Class variables (`@@name`). Note: these affect child classes too.
49
+ * Class instance variables (`@name` in class context or class methods)
50
+ * Constants (`NAME`). Ruby will warn if a constant is re-assigned to a new value but will allow it. Mutable objects can still be mutated (e.g. push to an array) even if they are assigned to a constant.
51
+ * Globals (`$name`), with the possible exception of some special globals provided by ruby that are documented as thread-local like regular expression results.
52
+ * Variables in the scope of created threads (where `Thread.new` is called).
53
+
54
+ Improvements that would make shared state thread-safe include:
55
+
56
+ * `freeze` objects to protect against mutation. Note: `freeze` is shallow, i.e. freezing an array will not also freeze its elements.
57
+ * Use data structures or concurrency abstractions from [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby), e.g. `Concurrent::Map`
58
+ * Use a `Mutex` or similar to `synchronize` access.
59
+ * Use [`RequestStore`](https://github.com/steveklabnik/request_store)
60
+ * Use `Thread.current[:name]`
61
+
41
62
  ## Development
42
63
 
43
64
  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.
@@ -28,3 +28,4 @@ ThreadSafety/NewThread:
28
28
  Description: >-
29
29
  Avoid starting new threads.
30
30
  Let a framework like Sidekiq handle the threads.
31
+ Enabled: true
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.53.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.81.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop", "~> 0.86.0"
6
+
7
+ gemspec path: "../"
@@ -15,25 +15,25 @@ module RuboCop
15
15
  class ClassAndModuleAttributes < Cop
16
16
  MSG = 'Avoid mutating class and module attributes.'
17
17
 
18
- def_node_matcher :mattr?, <<-MATCHER
18
+ def_node_matcher :mattr?, <<~MATCHER
19
19
  (send nil?
20
20
  {:mattr_writer :mattr_accessor :cattr_writer :cattr_accessor}
21
21
  ...)
22
22
  MATCHER
23
23
 
24
- def_node_matcher :attr?, <<-MATCHER
24
+ def_node_matcher :attr?, <<~MATCHER
25
25
  (send nil?
26
26
  {:attr :attr_accessor :attr_writer}
27
27
  ...)
28
28
  MATCHER
29
29
 
30
- def_node_matcher :attr_internal?, <<-MATCHER
30
+ def_node_matcher :attr_internal?, <<~MATCHER
31
31
  (send nil?
32
32
  {:attr_internal :attr_internal_accessor :attr_internal_writer}
33
33
  ...)
34
34
  MATCHER
35
35
 
36
- def_node_matcher :class_attr?, <<-MATCHER
36
+ def_node_matcher :class_attr?, <<~MATCHER
37
37
  (send nil?
38
38
  :class_attribute
39
39
  ...)
@@ -50,7 +50,19 @@ module RuboCop
50
50
 
51
51
  def singleton_attr?(node)
52
52
  (attr?(node) || attr_internal?(node)) &&
53
- node.ancestors.map(&:type).include?(:sclass)
53
+ defined_in_singleton_class?(node)
54
+ end
55
+
56
+ def defined_in_singleton_class?(node)
57
+ node.ancestors.each do |ancestor|
58
+ case ancestor.type
59
+ when :def then return false
60
+ when :sclass then return true
61
+ else next
62
+ end
63
+ end
64
+
65
+ false
54
66
  end
55
67
  end
56
68
  end
@@ -29,14 +29,22 @@ module RuboCop
29
29
  # end
30
30
  # end
31
31
  # end
32
+ #
33
+ # module Example
34
+ # module ClassMethods
35
+ # def test(params)
36
+ # @params = params
37
+ # end
38
+ # end
39
+ # end
32
40
  class InstanceVariableInClassMethod < Cop
33
41
  MSG = 'Avoid instance variables in class methods.'
34
42
 
35
- def_node_matcher :instance_variable_set_call?, <<-MATCHER
43
+ def_node_matcher :instance_variable_set_call?, <<~MATCHER
36
44
  (send nil? :instance_variable_set (...) (...))
37
45
  MATCHER
38
46
 
39
- def_node_matcher :instance_variable_get_call?, <<-MATCHER
47
+ def_node_matcher :instance_variable_get_call?, <<~MATCHER
40
48
  (send nil? :instance_variable_get (...))
41
49
  MATCHER
42
50
 
@@ -61,6 +69,7 @@ module RuboCop
61
69
  def class_method_definition?(node)
62
70
  in_defs?(node) ||
63
71
  in_def_sclass?(node) ||
72
+ in_def_class_methods?(node) ||
64
73
  singleton_method_definition?(node)
65
74
  end
66
75
 
@@ -80,6 +89,18 @@ module RuboCop
80
89
  end
81
90
  end
82
91
 
92
+ def in_def_class_methods?(node)
93
+ defn = node.ancestors.find(&:def_type?)
94
+ return unless defn
95
+
96
+ mod = defn.ancestors.find do |ancestor|
97
+ %i[class module].include?(ancestor.type)
98
+ end
99
+ return unless mod
100
+
101
+ class_methods_module?(mod)
102
+ end
103
+
83
104
  def singleton_method_definition?(node)
84
105
  node.ancestors.any? do |ancestor|
85
106
  next unless ancestor.children.first.is_a? AST::SendNode
@@ -100,6 +121,10 @@ module RuboCop
100
121
  def instance_variable_call?(node)
101
122
  instance_variable_set_call?(node) || instance_variable_get_call?(node)
102
123
  end
124
+
125
+ def_node_matcher :class_methods_module?, <<~PATTERN
126
+ (module (const _ :ClassMethods) ...)
127
+ PATTERN
103
128
  end
104
129
  end
105
130
  end
@@ -34,7 +34,7 @@ module RuboCop
34
34
  #
35
35
  # # good
36
36
  # class Model
37
- # @var = <<-TESTING.freeze
37
+ # @var = <<~TESTING.freeze
38
38
  # This is a heredoc
39
39
  # TESTING
40
40
  # end
@@ -138,6 +138,7 @@ module RuboCop
138
138
  def strict_check(value)
139
139
  return if immutable_literal?(value)
140
140
  return if operation_produces_immutable_object?(value)
141
+ return if operation_produces_threadsafe_object?(value)
141
142
  return if frozen_string_literal?(value)
142
143
 
143
144
  add_offense(value)
@@ -167,7 +168,9 @@ module RuboCop
167
168
  end
168
169
 
169
170
  def mutable_literal?(node)
170
- node&.mutable_literal?
171
+ return if node.nil?
172
+
173
+ node.mutable_literal? || range_type?(node)
171
174
  end
172
175
 
173
176
  def immutable_literal?(node)
@@ -180,10 +183,14 @@ module RuboCop
180
183
  end
181
184
 
182
185
  def requires_parentheses?(node)
183
- node.range_type? ||
186
+ range_type?(node) ||
184
187
  (node.send_type? && node.loc.dot.nil?)
185
188
  end
186
189
 
190
+ def range_type?(node)
191
+ node.erange_type? || node.irange_type?
192
+ end
193
+
187
194
  def correct_splat_expansion(corrector, expr, splat_value)
188
195
  if range_enclosed_in_parentheses?(splat_value)
189
196
  corrector.replace(expr, "#{splat_value.source}.to_a")
@@ -192,33 +199,65 @@ module RuboCop
192
199
  end
193
200
  end
194
201
 
195
- def_node_matcher :define_singleton_method?, <<-PATTERN
202
+ def_node_matcher :define_singleton_method?, <<~PATTERN
196
203
  (block (send nil? :define_singleton_method ...) ...)
197
204
  PATTERN
198
205
 
199
- def_node_matcher :splat_value, <<-PATTERN
206
+ def_node_matcher :splat_value, <<~PATTERN
200
207
  (array (splat $_))
201
208
  PATTERN
202
209
 
203
210
  # NOTE: Some of these patterns may not actually return an immutable
204
211
  # object but we will consider them immutable for this cop.
205
- def_node_matcher :operation_produces_immutable_object?, <<-PATTERN
212
+ def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
206
213
  {
207
214
  (const _ _)
208
- (send (const nil? :Struct) :new ...)
209
- (block (send (const nil? :Struct) :new ...) ...)
215
+ (send (const {nil? cbase} :Struct) :new ...)
216
+ (block (send (const {nil? cbase} :Struct) :new ...) ...)
210
217
  (send _ :freeze)
211
218
  (send {float int} {:+ :- :* :** :/ :% :<<} _)
212
219
  (send _ {:+ :- :* :** :/ :%} {float int})
213
220
  (send _ {:== :=== :!= :<= :>= :< :>} _)
214
- (send (const nil? :ENV) :[] _)
215
- (or (send (const nil? :ENV) :[] _) _)
221
+ (send (const {nil? cbase} :ENV) :[] _)
222
+ (or (send (const {nil? cbase} :ENV) :[] _) _)
216
223
  (send _ {:count :length :size} ...)
217
224
  (block (send _ {:count :length :size} ...) ...)
218
225
  }
219
226
  PATTERN
220
227
 
221
- def_node_matcher :range_enclosed_in_parentheses?, <<-PATTERN
228
+ def_node_matcher :operation_produces_threadsafe_object?, <<~PATTERN
229
+ {
230
+ (send (const {nil? cbase} :Queue) :new ...)
231
+ (send
232
+ (const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
233
+ :new ...)
234
+ (block
235
+ (send
236
+ (const (const {nil? cbase} :ThreadSafe) {:Hash :Array})
237
+ :new ...)
238
+ ...)
239
+ (send (const (const {nil? cbase} :Concurrent) _) :new ...)
240
+ (block
241
+ (send (const (const {nil? cbase} :Concurrent) _) :new ...)
242
+ ...)
243
+ (send (const (const (const {nil? cbase} :Concurrent) _) _) :new ...)
244
+ (block
245
+ (send
246
+ (const (const (const {nil? cbase} :Concurrent) _) _)
247
+ :new ...)
248
+ ...)
249
+ (send
250
+ (const (const (const (const {nil? cbase} :Concurrent) _) _) _)
251
+ :new ...)
252
+ (block
253
+ (send
254
+ (const (const (const (const {nil? cbase} :Concurrent) _) _) _)
255
+ :new ...)
256
+ ...)
257
+ }
258
+ PATTERN
259
+
260
+ def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
222
261
  (begin ({irange erange} _ _))
223
262
  PATTERN
224
263
  end
@@ -13,8 +13,8 @@ module RuboCop
13
13
  class NewThread < Cop
14
14
  MSG = 'Avoid starting new threads.'
15
15
 
16
- def_node_matcher :new_thread?, <<-MATCHER
17
- (send (const nil? :Thread) :new)
16
+ def_node_matcher :new_thread?, <<~MATCHER
17
+ (send (const {nil? cbase} :Thread) :new)
18
18
  MATCHER
19
19
 
20
20
  def on_send(node)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module ThreadSafety
5
- VERSION = '0.4.1'
5
+ VERSION = '0.4.2'
6
6
  end
7
7
  end
@@ -26,10 +26,12 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ['lib']
28
28
 
29
- spec.add_runtime_dependency 'rubocop', '>= 0.51.0'
29
+ spec.required_ruby_version = '>= 2.3.0'
30
30
 
31
+ spec.add_runtime_dependency 'rubocop', '>= 0.53.0'
32
+
33
+ spec.add_development_dependency 'appraisal'
31
34
  spec.add_development_dependency 'bundler', '>= 1.10', '< 3'
32
- spec.add_development_dependency 'powerpack', '~> 0.1'
33
35
  spec.add_development_dependency 'pry' unless ENV['CI']
34
36
  spec.add_development_dependency 'rake', '>= 10.0'
35
37
  spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-thread_safety
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Gee
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-26 00:00:00.000000000 Z
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -16,48 +16,48 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.51.0
19
+ version: 0.53.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.51.0
26
+ version: 0.53.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: appraisal
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.10'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '3'
33
+ version: '0'
37
34
  type: :development
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
- version: '1.10'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '3'
40
+ version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
- name: powerpack
42
+ name: bundler
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
- - - "~>"
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ - - "<"
52
49
  - !ruby/object:Gem::Version
53
- version: '0.1'
50
+ version: '3'
54
51
  type: :development
55
52
  prerelease: false
56
53
  version_requirements: !ruby/object:Gem::Requirement
57
54
  requirements:
58
- - - "~>"
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '1.10'
58
+ - - "<"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.1'
60
+ version: '3'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: pry
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +113,7 @@ files:
113
113
  - ".rspec"
114
114
  - ".rubocop.yml"
115
115
  - ".travis.yml"
116
+ - Appraisals
116
117
  - Gemfile
117
118
  - LICENSE.txt
118
119
  - README.md
@@ -120,6 +121,9 @@ files:
120
121
  - bin/console
121
122
  - bin/setup
122
123
  - config/default.yml
124
+ - gemfiles/rubocop_0.53.gemfile
125
+ - gemfiles/rubocop_0.81.gemfile
126
+ - gemfiles/rubocop_0.86.gemfile
123
127
  - lib/rubocop-thread_safety.rb
124
128
  - lib/rubocop/cop/thread_safety/class_and_module_attributes.rb
125
129
  - lib/rubocop/cop/thread_safety/instance_variable_in_class_method.rb
@@ -141,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
145
  requirements:
142
146
  - - ">="
143
147
  - !ruby/object:Gem::Version
144
- version: '0'
148
+ version: 2.3.0
145
149
  required_rubygems_version: !ruby/object:Gem::Requirement
146
150
  requirements:
147
151
  - - ">="