rubocop-thread_safety 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="