sorbet-runtime 0.5.6346 → 0.5.6357

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: ccd35b0362921e31eb5c9a7bfbab5b33f337eca5e785175db007ce503deaf418
4
- data.tar.gz: c44f1a563ea2a4bb6be35068957fcb0d08116745dc26f7ade284bf2dc66d2720
3
+ metadata.gz: '043596fdb160f2d17988b48b8eda6c7275d7d1fd6384f097bcbdf87c4d4defe6'
4
+ data.tar.gz: 43cb4fc1d2acc6eef38b351a64431748817352454e6cd355e1e997e5839689c0
5
5
  SHA512:
6
- metadata.gz: f8b8cc96bc8ec2a866dd726a7828f5cab96251dbc391d03f29c6ffb5d4555a966a28c216df849d93c539993e19b37d4f73f04bc5244968dc4559705eff9f9a8f
7
- data.tar.gz: 84d5da8c69776de610a6103166a8a756885c36fb2095c65d262f495696b06b71b7854dcba4662f50066f62d68c33bfc4ebcb82c15913e6559160740d56a315a1
6
+ metadata.gz: 2c71a09ca70498f152a37c40c567f61e13067d6f0635a544578d329d84e8013650a553d83cd77a336a6b3ff9b7fbfd31b1675f3d83c8a4a22d6d67f30ca0fa10
7
+ data.tar.gz: 157ad03691136f5262da24cae305b481a1f018692944b9ec7028a625176e5710c39499c4ccc210d222c7465cca237d78269d0a7d969bfda7f0ac6da191c59e51
@@ -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.6346
4
+ version: 0.5.6357
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-16 00:00:00.000000000 Z
11
+ date: 2021-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest