sorbet-runtime 0.5.6338 → 0.5.6352

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: 3671ed176f9cf1875148797095dcda29672ad803d5ede745ec3aac0e8ddacd31
4
- data.tar.gz: '019c5545c3bdff3a7f1aad4c0eaefdc57f0172a13413aa59236b27da8609e89f'
3
+ metadata.gz: 749c737c99c9607d2b7c8dca07174fc9c29c7310022992388106585f071010e4
4
+ data.tar.gz: e9288fd72206f3ea8e51147ddc98976eb2cab320b191c36b83833dfcc416aee4
5
5
  SHA512:
6
- metadata.gz: 18c9836a43d62d5c73f8c7d4dd402496ed523e6beaa389eda774b207c4aaacb7499f83936f32a8d4efcf72559f0b2a06b341a89bd31aa41ca7168359ecf03462
7
- data.tar.gz: da19c72bf63324a8f09ef63b811edba84f84c26bc545025c1ca354e03626c774bbf1fc3ada5051c7393678d056ad10106f73ffdb24a790d14564b9b7ebf10797
6
+ metadata.gz: bfeba8bf0a258bab60cf9a86e924fea90e072ecc2c57963aeede61e0f40f2b260ded8d80916e672eb82310b89d7b3a4f02e2c0fb2ebf9e4c1efaa0bd888d0227
7
+ data.tar.gz: 0a38952b492811ac25b9b4aed0f91adb1dce7b6c2764350e3d4ed4164a2ba7bcb48ce63366c428106062b8f394c323c4c01698718ba89d4961ef45e286ee6284
@@ -433,6 +433,22 @@ module T::Configuration
433
433
  @sensitivity_and_pii_handler
434
434
  end
435
435
 
436
+ @redaction_handler = nil
437
+ # Set to a redaction handling function. This will be called when the
438
+ # `_redacted` version of a prop reader is used. By default this is set to
439
+ # `nil` and will raise an exception when the redacted version of a prop is
440
+ # accessed.
441
+ #
442
+ # @param [Lambda, Proc, nil] value Proc that converts a value into its
443
+ # redacted version according to the spec passed as the second argument.
444
+ def self.redaction_handler=(handler)
445
+ @redaction_handler = handler
446
+ end
447
+
448
+ def self.redaction_handler
449
+ @redaction_handler
450
+ end
451
+
436
452
  # Set to a function which can get the 'owner' of a class. This is
437
453
  # used in reporting deserialization errors
438
454
  #
@@ -37,7 +37,6 @@ module T::Private::Final
37
37
  mod.extend(mod.is_a?(Class) ? NoInherit : NoIncludeExtend)
38
38
  mark_as_final_module(mod)
39
39
  mark_as_final_module(mod.singleton_class)
40
- T::Private::Methods.add_module_with_final(mod)
41
40
  T::Private::Methods.install_hooks(mod)
42
41
  end
43
42
 
@@ -6,22 +6,21 @@ module T::Private::Methods
6
6
  @signatures_by_method = {}
7
7
  @sig_wrappers = {}
8
8
  @sigs_that_raised = {}
9
- # the info about whether a method is final is not stored in a DeclBuilder nor a Signature, but instead right here.
10
- # this is because final checks are special:
11
- # - they are done possibly before any sig block has run.
12
- # - they are done even if the method being defined doesn't have a sig.
13
- @final_methods = Set.new
14
9
  # stores method names that were declared final without regard for where.
15
10
  # enables early rejection of names that we know can't induce final method violations.
16
11
  @was_ever_final_names = Set.new
17
- # a non-singleton is a module for which at least one of the following is true:
18
- # - is declared final
19
- # - defines a method that is declared final
20
- # - includes an non-singleton
21
- # - extends an non-singleton
22
- # a singleton is the singleton_class of a non-singleton.
23
- # modules_with_final is the set of singletons and non-singletons.
24
- @modules_with_final = Set.new
12
+ # maps from modules to the set of final methods declared in that module.
13
+ # we also overload entries slightly: if the value is nil, that means that the
14
+ # module has final methods somewhere along its ancestor chain, but does not itself
15
+ # have any final methods.
16
+ #
17
+ # we need the latter information to know whether we need to check along the ancestor
18
+ # chain for final method violations. we need the former information because we
19
+ # care about exactly where a final method is defined (e.g. including the same module
20
+ # twice is permitted). we could do this with two tables, but it seems slightly
21
+ # cleaner with a single table.
22
+ # Effectively T::Hash[Module, T.nilable(Set))]
23
+ @modules_with_final = Hash.new { |hash, key| hash[key] = nil }
25
24
  # this stores the old [included, extended] hooks for Module and inherited hook for Class that we override when
26
25
  # enabling final checks for when those hooks are called. the 'hooks' here don't have anything to do with the 'hooks'
27
26
  # in installed_hooks.
@@ -102,71 +101,83 @@ module T::Private::Methods
102
101
  #
103
102
  # we assume that source_method_names has already been filtered to only include method
104
103
  # names that were declared final at one point.
105
- def self._check_final_ancestors(target, target_ancestors, source_method_names)
106
- # use reverse_each to check farther-up ancestors first, for better error messages. we could avoid this if we were on
107
- # the version of ruby that adds the optional argument to method_defined? that allows you to exclude ancestors.
104
+ def self._check_final_ancestors(target, target_ancestors, source_method_names, source)
105
+ source_ancestors = nil
106
+ # use reverse_each to check farther-up ancestors first, for better error messages.
108
107
  target_ancestors.reverse_each do |ancestor|
109
- next if !module_with_final?(ancestor)
108
+ final_methods = @modules_with_final.fetch(ancestor, nil)
109
+ # In this case, either ancestor didn't have any final methods anywhere in its
110
+ # ancestor chain, or ancestor did have final methods somewhere in its ancestor
111
+ # chain, but no final methods defined in ancestor itself. Either way, there
112
+ # are no final methods to check here, so we can move on to the next ancestor.
113
+ next unless final_methods
110
114
  source_method_names.each do |method_name|
111
- # the usage of method_owner_and_name_to_key(ancestor, method_name) instead of
112
- # method_to_key(ancestor.instance_method(method_name)) is not (just) an optimization, but also required for
113
- # correctness, since ancestor.method_defined?(method_name) may return true even if method_name is not defined
114
- # directly on ancestor but instead an ancestor of ancestor.
115
- if final_method?(method_owner_and_name_to_key(ancestor, method_name))
116
- definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
117
- is_redefined = target == ancestor
118
- caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
119
- extra_info = "\n"
120
- if caller_loc
121
- extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
115
+ next unless final_methods.include?(method_name)
116
+
117
+ # If we get here, we are defining a method that some ancestor declared as
118
+ # final. however, we permit a final method to be defined multiple
119
+ # times if it is the same final method being defined each time.
120
+ if source
121
+ if !source_ancestors
122
+ source_ancestors = source.ancestors
123
+ # filter out things without actual final methods just to make sure that
124
+ # the below checks (which should be uncommon) go as quickly as possible.
125
+ source_ancestors.select! do |a|
126
+ @modules_with_final.fetch(a, nil)
127
+ end
122
128
  end
123
-
124
- error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
125
- (is_redefined ? "redefined" : "overridden in #{target}")
126
- pretty_message = "#{error_message}\n" \
127
- "Made final here: #{definition_file}:#{definition_line}\n" \
128
- "#{extra_info}"
129
-
130
- begin
131
- raise pretty_message
132
- rescue => e
133
- # sig_validation_error_handler raises by default; on the off chance that
134
- # it doesn't raise, we need to ensure that the rest of signature building
135
- # sees a consistent state. This sig failed to validate, so we should get
136
- # rid of it. If we don't do this, errors of the form "You called sig
137
- # twice without declaring a method in between" will non-deterministically
138
- # crop up in tests.
139
- T::Private::DeclState.current.reset!
140
- T::Configuration.sig_validation_error_handler(e, {})
129
+ # final-ness means that there should be no more than one index for which
130
+ # the below block returns true.
131
+ defining_ancestor_idx = source_ancestors.index do |a|
132
+ @modules_with_final.fetch(a).include?(method_name)
141
133
  end
134
+ next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor
142
135
  end
143
- end
144
- end
145
- end
146
136
 
147
- private_class_method def self.add_final_method(method_key)
148
- @final_methods.add(method_key)
149
- end
150
-
151
- private_class_method def self.final_method?(method_key)
152
- @final_methods.include?(method_key)
153
- end
154
-
155
- private_class_method def self.add_was_ever_final(method_name)
156
- @was_ever_final_names.add(method_name)
157
- end
137
+ definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
138
+ is_redefined = target == ancestor
139
+ caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
140
+ extra_info = "\n"
141
+ if caller_loc
142
+ extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
143
+ end
158
144
 
159
- private_class_method def self.was_ever_final?(method_name)
160
- @was_ever_final_names.include?(method_name)
145
+ error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
146
+ (is_redefined ? "redefined" : "overridden in #{target}")
147
+ pretty_message = "#{error_message}\n" \
148
+ "Made final here: #{definition_file}:#{definition_line}\n" \
149
+ "#{extra_info}"
150
+
151
+ begin
152
+ raise pretty_message
153
+ rescue => e
154
+ # sig_validation_error_handler raises by default; on the off chance that
155
+ # it doesn't raise, we need to ensure that the rest of signature building
156
+ # sees a consistent state. This sig failed to validate, so we should get
157
+ # rid of it. If we don't do this, errors of the form "You called sig
158
+ # twice without declaring a method in between" will non-deterministically
159
+ # crop up in tests.
160
+ T::Private::DeclState.current.reset!
161
+ T::Configuration.sig_validation_error_handler(e, {})
162
+ end
163
+ end
164
+ end
161
165
  end
162
166
 
163
- def self.add_module_with_final(mod)
164
- @modules_with_final.add(mod)
165
- @modules_with_final.add(mod.singleton_class)
167
+ def self.add_module_with_final_method(mod, method_name, is_singleton_method)
168
+ m = is_singleton_method ? mod.singleton_class : mod
169
+ methods = @modules_with_final[m]
170
+ if methods.nil?
171
+ methods = Set.new
172
+ @modules_with_final[m] = methods
173
+ end
174
+ methods.add(method_name)
166
175
  end
167
176
 
168
- private_class_method def self.module_with_final?(mod)
169
- @modules_with_final.include?(mod)
177
+ def self.note_module_deals_with_final(mod)
178
+ # Side-effectfully initialize the value if it's not already there
179
+ @modules_with_final[mod]
180
+ @modules_with_final[mod.singleton_class]
170
181
  end
171
182
 
172
183
  # Only public because it needs to get called below inside the replace_method blocks below.
@@ -182,13 +193,13 @@ module T::Private::Methods
182
193
  raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
183
194
  end
184
195
  # Don't compute mod.ancestors if we don't need to bother checking final-ness.
185
- if was_ever_final?(method_name) && module_with_final?(mod)
186
- _check_final_ancestors(mod, mod.ancestors, [method_name])
196
+ if @was_ever_final_names.include?(method_name) && @modules_with_final.include?(mod)
197
+ _check_final_ancestors(mod, mod.ancestors, [method_name], nil)
198
+ # We need to fetch the active declaration again, as _check_final_ancestors
199
+ # may have reset it (see the comment in that method for details).
200
+ current_declaration = T::Private::DeclState.current.active_declaration
187
201
  end
188
202
 
189
- # We need to fetch the active declaration again, as _check_final_ancestors
190
- # may have reset it (see the comment in that method for details).
191
- current_declaration = T::Private::DeclState.current.active_declaration
192
203
  if current_declaration.nil?
193
204
  return
194
205
  end
@@ -210,9 +221,9 @@ module T::Private::Methods
210
221
  # which is called only on the *first* invocation.
211
222
  # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
212
223
  # (or unwrap back to the original method).
213
- new_method = nil
224
+ key = method_owner_and_name_to_key(mod, method_name)
214
225
  T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
215
- method_sig = T::Private::Methods.maybe_run_sig_block_for_method(new_method)
226
+ method_sig = T::Private::Methods.maybe_run_sig_block_for_key(key)
216
227
  method_sig ||= T::Private::Methods._handle_missing_method_signature(
217
228
  self,
218
229
  original_method,
@@ -239,15 +250,13 @@ module T::Private::Methods
239
250
  end
240
251
  end
241
252
 
242
- new_method = mod.instance_method(method_name)
243
- key = method_to_key(new_method)
244
253
  @sig_wrappers[key] = sig_block
245
254
  if current_declaration.final
246
- add_final_method(key)
247
- add_was_ever_final(method_name)
255
+ @was_ever_final_names.add(method_name)
248
256
  # use hook_mod, not mod, because for example, we want class C to be marked as having final if we def C.foo as
249
257
  # final. change this to mod to see some final_method tests fail.
250
- add_module_with_final(hook_mod)
258
+ note_module_deals_with_final(hook_mod)
259
+ add_module_with_final_method(hook_mod, method_name, is_singleton_method)
251
260
  end
252
261
  end
253
262
 
@@ -379,7 +388,8 @@ module T::Private::Methods
379
388
  maybe_run_sig_block_for_key(method_to_key(method))
380
389
  end
381
390
 
382
- private_class_method def self.maybe_run_sig_block_for_key(key)
391
+ # Only public so that it can be accessed in the closure for _on_method_added
392
+ def self.maybe_run_sig_block_for_key(key)
383
393
  run_sig_block_for_key(key) if has_sig_block_for_key(key)
384
394
  end
385
395
 
@@ -424,29 +434,27 @@ module T::Private::Methods
424
434
  # the module target is adding the methods from the module source to itself. we need to check that for all instance
425
435
  # methods M on source, M is not defined on any of target's ancestors.
426
436
  def self._hook_impl(target, singleton_class, source)
427
- target_was_final = module_with_final?(target)
428
- if !target_was_final && !module_with_final?(source)
429
- return
430
- end
431
437
  # we do not need to call add_was_ever_final here, because we have already marked
432
438
  # any such methods when source was originally defined.
433
- add_module_with_final(target)
434
- install_hooks(target)
435
-
436
- if !target_was_final
439
+ if !@modules_with_final.include?(target)
440
+ if !@modules_with_final.include?(source)
441
+ return
442
+ end
443
+ note_module_deals_with_final(target)
444
+ install_hooks(target)
437
445
  return
438
446
  end
439
447
 
440
448
  methods = source.instance_methods
441
449
  methods.select! do |method_name|
442
- was_ever_final?(method_name)
450
+ @was_ever_final_names.include?(method_name)
443
451
  end
444
452
  if methods.empty?
445
453
  return
446
454
  end
447
455
 
448
456
  target_ancestors = singleton_class ? target.singleton_class.ancestors : target.ancestors
449
- _check_final_ancestors(target, target_ancestors - source.ancestors, methods)
457
+ _check_final_ancestors(target, target_ancestors, methods, source)
450
458
  end
451
459
 
452
460
  def self.set_final_checks_on_hooks(enable)
@@ -449,8 +449,11 @@ class T::Props::Decorator
449
449
 
450
450
  @class.send(:define_method, redacted_method) do
451
451
  value = self.public_send(prop_name)
452
- Chalk::Tools::RedactionUtils.redact_with_directive(
453
- value, redaction)
452
+ handler = T::Configuration.redaction_handler
453
+ if !handler
454
+ raise "Using `redaction:` on a prop requires specifying `T::Configuration.redaction_handler`"
455
+ end
456
+ handler.call(value, redaction)
454
457
  end
455
458
  end
456
459
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6338
4
+ version: 0.5.6352
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-12 00:00:00.000000000 Z
11
+ date: 2021-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest