cfsolve 0.1.1 → 0.1.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -9
  4. data/UPSTREAM.md +6 -6
  5. data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  6. data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  7. data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
  8. data/lib/cfsolve/version.rb +2 -2
  9. data/vendor/cfsolve/README.md +0 -35
  10. data/vendor/cfsolve/examples/build_profile.rs +17 -15
  11. data/vendor/cfsolve/examples/capture_session.rs +37 -0
  12. data/vendor/cfsolve/examples/check_answers.rs +66 -0
  13. data/vendor/cfsolve/examples/compare_session.rs +190 -0
  14. data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
  15. data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  16. data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  17. data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
  18. data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
  19. data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
  20. data/vendor/cfsolve/src/capture/profile.rs +7 -18
  21. data/vendor/cfsolve/src/capture/recorded.rs +42 -7
  22. data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
  23. data/vendor/cfsolve/src/challenge/installed.rs +122 -271
  24. data/vendor/cfsolve/src/challenge/payload.rs +107 -5
  25. data/vendor/cfsolve/src/challenge/state.rs +98 -16
  26. data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
  27. data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
  28. data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
  29. data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
  30. data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
  31. data/vendor/cfsolve/src/http/client.rs +11 -0
  32. data/vendor/cfsolve/src/js/ast.rs +12 -3
  33. data/vendor/cfsolve/src/js/objects.rs +9 -2
  34. data/vendor/cfsolve/src/listing/fold.rs +0 -83
  35. data/vendor/cfsolve/src/listing/query.rs +25 -244
  36. data/vendor/cfsolve/src/listing.rs +0 -1
  37. data/vendor/cfsolve/src/session/initial.rs +14 -5
  38. data/vendor/cfsolve/src/session/probes.rs +160 -125
  39. data/vendor/cfsolve/src/session/timing.rs +58 -53
  40. data/vendor/cfsolve/src/session/widget.rs +5 -6
  41. data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
  42. data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
  43. data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
  44. data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
  45. data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
  46. data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
  47. data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
  48. data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
  49. data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
  50. data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
  51. data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
  52. data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
  53. data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
  54. data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
  55. data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
  56. data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
  57. data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
  58. data/vendor/cfsolve/src/stage2/probes.rs +0 -1
  59. data/vendor/cfsolve/src/stage2/records.rs +4 -13
  60. data/vendor/cfsolve/src/stage2/reference.json +1 -1
  61. data/vendor/cfsolve/src/stage2/reference.rs +98 -100
  62. data/vendor/cfsolve/src/stage2/registry.rs +41 -34
  63. data/vendor/cfsolve/src/stage2/replay.rs +182 -254
  64. data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
  65. data/vendor/cfsolve/src/stage2/trace.rs +18 -0
  66. data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
  67. data/vendor/cfsolve/src/stage2.rs +2 -1
  68. data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
  69. data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
  70. data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
  71. data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
  72. data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
  73. data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
  74. data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
  75. data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
  76. data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
  77. data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
  78. data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
  79. data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
  80. data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
  81. data/vendor/cfsolve/src/vm/interpret.rs +7 -2
  82. data/vendor/cfsolve/src/vm/machine.rs +6 -2
  83. data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
  84. data/vendor/cfsolve/src/vm/session.rs +36 -14
  85. data/vendor/cfsolve/src/vm/value.rs +13 -0
  86. data/vendor/cfsolve/src/vm.rs +1 -0
  87. data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
  88. metadata +13 -6
  89. data/vendor/cfsolve/src/listing/normalize.rs +0 -264
  90. data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
  91. data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
  92. data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
  93. data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
@@ -21,7 +21,7 @@ use super::interpret::Way;
21
21
  use super::machine::{Loaded, Machine};
22
22
  use super::output::{Constant, Effect, Exit, Hidden, Instruction, Listing, Path, Position, Term};
23
23
  use super::value::Value;
24
- use super::{HashMap, convert, dataflow};
24
+ use super::{HashMap, convert, dataflow, packed_names};
25
25
  use crate::candidates;
26
26
 
27
27
  impl Machine<'_> {
@@ -57,7 +57,7 @@ impl Machine<'_> {
57
57
  Err(failure) => walk.listing.failures.push(failure),
58
58
  }
59
59
  }
60
- if !walk.retry(self, &loaded)? && !walk.drop_shortcuts() {
60
+ if !(walk.retry(self, &loaded)? || walk.drop_shortcuts()) {
61
61
  break;
62
62
  }
63
63
  }
@@ -69,6 +69,7 @@ impl Machine<'_> {
69
69
  });
70
70
  listing.failures.sort_unstable_by_key(|failure| failure.pc);
71
71
  fuse_method_calls(&mut listing);
72
+ packed_names::fold(&mut listing);
72
73
  Ok(listing)
73
74
  }
74
75
  }
@@ -387,6 +388,23 @@ pub fn catch_frame(elements: &[Term]) -> Option<Position> {
387
388
  }
388
389
  }
389
390
 
391
+ /// Exception continuations enter without the registers known on the normal path.
392
+ pub(super) fn exception_entries(listing: &Listing) -> BTreeSet<u32> {
393
+ let mut entries = BTreeSet::new();
394
+ for instruction in listing.instructions.values() {
395
+ for effect in instruction.effects() {
396
+ effect.visit_terms(&mut |term| {
397
+ if let Term::Array(frame) = term
398
+ && let Some(position) = catch_frame(frame)
399
+ {
400
+ entries.insert(position.pc);
401
+ }
402
+ });
403
+ }
404
+ }
405
+ entries
406
+ }
407
+
390
408
  /// Rewrites method calls to the form listing readers expect. Some builds call `object[key]`
391
409
  /// directly; others load the method into a register and later call that register with `object`
392
410
  /// as receiver.
@@ -394,15 +412,11 @@ fn fuse_method_calls(listing: &mut Listing) {
394
412
  // Unrecorded entries, such as functions and hidden targets, start with no known loads.
395
413
  let functions = listing.functions.iter().map(|function| function.pc);
396
414
  let hidden = listing.hidden.iter().map(|hidden| hidden.target.pc);
397
- let entries = functions.chain(hidden).map(|pc| (pc, Loads::default()));
398
- let ControlFlow::Continue(incoming) = dataflow::forward(listing, entries, |_, path, loads| {
399
- for effect in &path.effects {
400
- update_loads(loads, effect);
401
- }
402
- ControlFlow::<Infallible, bool>::Continue(true)
403
- });
415
+ let incoming = incoming_loads(listing, functions.chain(hidden));
416
+ // Exception continuations also start with no known loads, without erasing the normal path's.
417
+ let exceptional = incoming_loads(listing, exception_entries(listing));
404
418
  for (pc, instruction) in &mut listing.instructions {
405
- let Some(entry) = incoming.get(pc) else {
419
+ let Some(entry) = incoming.get(pc).or_else(|| exceptional.get(pc)) else {
406
420
  continue;
407
421
  };
408
422
  for path in &mut instruction.paths {
@@ -417,6 +431,21 @@ fn fuse_method_calls(listing: &mut Listing) {
417
431
  }
418
432
  }
419
433
 
434
+ /// The loads known on entry to each instruction reachable from `entries`.
435
+ fn incoming_loads(
436
+ listing: &Listing,
437
+ entries: impl IntoIterator<Item = u32>,
438
+ ) -> BTreeMap<u32, Loads> {
439
+ let entries = entries.into_iter().map(|pc| (pc, Loads::default()));
440
+ let ControlFlow::Continue(incoming) = dataflow::forward(listing, entries, |_, path, loads| {
441
+ for effect in &path.effects {
442
+ update_loads(loads, effect);
443
+ }
444
+ ControlFlow::<Infallible, bool>::Continue(true)
445
+ });
446
+ incoming
447
+ }
448
+
420
449
  /// Tracks registers holding `object[key]` while the object and key registers remain unchanged.
421
450
  /// Saves facts at each instruction's entry. Uses a vector because these sets are small.
422
451
  #[derive(Clone, Default)]
@@ -271,6 +271,7 @@ impl<'ast> Run<'ast, '_> {
271
271
  self.set_field(name.to_owned(), value);
272
272
  }
273
273
  Value::Array(elements) if key.text() == Some("length") => {
274
+ self.writable(object)?;
274
275
  match value.number() {
275
276
  Some(length) => {
276
277
  let length = convert::length(length)
@@ -294,6 +295,7 @@ impl<'ast> Run<'ast, '_> {
294
295
  .and_then(convert::length)
295
296
  .filter(|&index| index < MAXIMUM_LENGTH)
296
297
  .ok_or_else(|| unsupported("array index", key))?;
298
+ self.writable(object)?;
297
299
  let mut elements = elements.borrow_mut();
298
300
  if elements.len() <= index {
299
301
  elements.resize(index + 1, Value::Undefined);
@@ -304,6 +306,7 @@ impl<'ast> Run<'ast, '_> {
304
306
  let name = key
305
307
  .property()
306
308
  .ok_or_else(|| unsupported("object key", key))?;
309
+ self.writable(object)?;
307
310
  let mut properties = properties.borrow_mut();
308
311
  match properties
309
312
  .iter_mut()
@@ -348,33 +351,14 @@ impl<'ast> Run<'ast, '_> {
348
351
 
349
352
  /// Decrypts a string record using the same logic as the register file's `read` method.
350
353
  fn decrypted(&mut self, record: Value<'ast>) -> Evaluation<'ast, Value<'ast>> {
351
- let Value::Array(elements) = &record else {
354
+ if !matches!(record, Value::Array(_)) {
352
355
  return Ok(record);
353
- };
354
- let field = |index: usize| elements.borrow().get(index).and_then(Value::number);
355
- let key = match (field(1), field(2), field(3)) {
356
- (Some(pc), Some(length), Some(key)) => Some((pc as u32, length as u32, key as u32)),
357
- _ => None,
358
- };
359
- if let Some(key) = key
360
- && let Some(text) = self.session.decrypted.borrow().get(&key)
361
- {
362
- return Ok(text.clone());
363
356
  }
364
357
  let decoder = self
365
358
  .session
366
359
  .record_decoder
367
- .as_ref()
368
360
  .ok_or_else(|| Stop::unsupported("an encrypted string with no decoder"))?;
369
- let decoder = self.lookup(decoder)?;
370
- let text = self.apply(decoder, Value::Undefined, vec![record])?;
371
- if let (Some(key), Value::String(_)) = (key, &text) {
372
- self.session
373
- .decrypted
374
- .borrow_mut()
375
- .insert(key, text.clone());
376
- }
377
- Ok(text)
361
+ self.script(decoder, None, Value::Undefined, vec![record])
378
362
  }
379
363
 
380
364
  /// A member of the register file (see [`Member`]).
@@ -216,17 +216,7 @@ impl<'ast> Run<'ast, '_> {
216
216
  let Value::Array(elements) = &this else {
217
217
  return self.apply(Value::symbolic(global()), this, arguments);
218
218
  };
219
- // Preserve [pc, key, cells, depth] frames for disassembly even when the
220
- // constructor-created stack is concrete. Other pushes need no symbolic effect.
221
- for value in &arguments {
222
- if let Value::Array(frame) = value
223
- && frame.borrow().len() == 4
224
- && let Term::Array(frame) = value.expr()
225
- && crate::vm::catch_frame(&frame).is_some()
226
- {
227
- self.temp(Term::Array(frame));
228
- }
229
- }
219
+ self.writable(&this)?;
230
220
  let mut elements = elements.borrow_mut();
231
221
  elements.extend(arguments);
232
222
  Ok(Value::Number(elements.len() as f64))
@@ -238,6 +228,7 @@ impl<'ast> Run<'ast, '_> {
238
228
  let Value::Array(elements) = &this else {
239
229
  return self.apply(Value::symbolic(global()), this, arguments);
240
230
  };
231
+ self.writable(&this)?;
241
232
  let popped = elements.borrow_mut().pop();
242
233
  match popped {
243
234
  Some(value) => Ok(value),
@@ -377,11 +368,13 @@ impl<'ast> Run<'ast, '_> {
377
368
  },
378
369
  Builtin::Add => {
379
370
  if contains(&members.borrow(), &key) == Some(false) {
371
+ self.writable(&this)?;
380
372
  members.borrow_mut().push(key);
381
373
  }
382
374
  Ok(this.clone())
383
375
  }
384
376
  _ => {
377
+ self.writable(&this)?;
385
378
  let mut members = members.borrow_mut();
386
379
  let before = members.len();
387
380
  members.retain(|member| {
@@ -414,6 +407,7 @@ impl<'ast> Run<'ast, '_> {
414
407
  Builtin::MapHas => Ok(Value::Bool(position.is_some())),
415
408
  Builtin::MapSet => {
416
409
  let value = argument(1);
410
+ self.writable(&this)?;
417
411
  let mut entries = entries.borrow_mut();
418
412
  match position {
419
413
  Some(position) => entries[position].1 = value,
@@ -423,6 +417,7 @@ impl<'ast> Run<'ast, '_> {
423
417
  }
424
418
  _ => {
425
419
  if let Some(position) = position {
420
+ self.writable(&this)?;
426
421
  entries.borrow_mut().remove(position);
427
422
  }
428
423
  Ok(Value::Bool(position.is_some()))
@@ -608,48 +603,6 @@ mod tests {
608
603
  assert!(call(Builtin::Floor, &[]).is_nan());
609
604
  }
610
605
 
611
- #[test]
612
- fn concrete_stack_pushes_expose_exception_frames_to_disassembly() {
613
- let session = Session::empty();
614
- let mut run = Run::method(&session);
615
- let stack = Value::array(vec![]);
616
- let frame = Value::array(vec![
617
- Value::Number(42.0),
618
- Value::Number(7.0),
619
- Value::Cells,
620
- Value::Number(0.0),
621
- ]);
622
- let expected = frame.expr();
623
- assert_eq!(
624
- number(run.builtin(Builtin::Push, stack.clone(), vec![frame])),
625
- 1.0
626
- );
627
- assert_eq!(
628
- run.effects,
629
- vec![crate::vm::Effect::Let(0, expected.clone())]
630
- );
631
- assert_eq!(stack.expr(), Term::Array(vec![expected]));
632
- for (length, value) in [
633
- (2.0, Value::Number(9.0)),
634
- (3.0, Value::array(vec![Value::Number(42.0)])),
635
- (
636
- 4.0,
637
- Value::array(vec![
638
- Value::Number(42.0),
639
- Value::Number(256.0),
640
- Value::Cells,
641
- Value::Number(0.0),
642
- ]),
643
- ),
644
- ] {
645
- assert_eq!(
646
- number(run.builtin(Builtin::Push, stack.clone(), vec![value])),
647
- length
648
- );
649
- assert_eq!(run.effects.len(), 1);
650
- }
651
- }
652
-
653
606
  /// Checks `SameValueZero` membership: the set contains 1, "1", NaN, and 0 after duplicate
654
607
  /// removal.
655
608
  #[test]
@@ -1,11 +1,13 @@
1
1
  //! Calls and constructions of script functions, arrows, builtins, and bound functions.
2
2
 
3
+ use std::ptr;
4
+
3
5
  use swc_core::atoms::{Atom, atom};
4
6
  use swc_core::ecma::ast::{ArrowExpr, Decl, Function, Pat, Stmt};
5
7
 
6
8
  use super::access::Key;
7
- use super::plan;
8
- use super::run::{Evaluation, Frame, Run, Stop};
9
+ use super::run::{Evaluation, Frame, RecordKey, RecordPart, Run, Stop};
10
+ use super::{intrinsic, plan};
9
11
  use crate::vm::HashMap;
10
12
  use crate::vm::output::Term;
11
13
  use crate::vm::value::{Callable, Env, Scope, Value};
@@ -112,15 +114,24 @@ impl<'ast> Run<'ast, '_> {
112
114
  function,
113
115
  scope,
114
116
  } if arguments.iter().any(Value::is_symbolic) => {
115
- let call = Term::Call {
117
+ // The call records the arguments as they were before the attempt. Only
118
+ // containers can change during it, so calls without them are kept (`Err`) and
119
+ // recorded only when the attempt fails.
120
+ let call = |arguments: &[Value<'ast>]| Term::Call {
116
121
  callee: Box::new(Term::Global(name.to_string())),
117
122
  this: None,
118
123
  arguments: arguments.iter().map(Value::expr).collect(),
119
124
  };
125
+ let recorded = if arguments.iter().all(Value::is_immutable) {
126
+ Err(arguments.clone())
127
+ } else {
128
+ Ok(call(&arguments))
129
+ };
120
130
  let checkpoint = self.checkpoint();
121
131
  match self.script(function, scope.as_ref(), this, arguments) {
122
132
  Err(stop) if matches!(*stop, Stop::Unsupported(_)) => {
123
133
  self.restore(checkpoint);
134
+ let call = recorded.unwrap_or_else(|kept| call(&kept));
124
135
  Ok(self.temp(call))
125
136
  }
126
137
  outcome => {
@@ -191,20 +202,59 @@ impl<'ast> Run<'ast, '_> {
191
202
  }
192
203
  }
193
204
 
194
- /// Calls a function through its slot plan if it has one and is not a closure, or else
195
- /// through its scoped plan.
196
- fn script(
205
+ /// Calls a script function: natively if it has an intrinsic that accepts the arguments,
206
+ /// from the program's cache if it decrypts a string record decrypted before, or else
207
+ /// interpreted.
208
+ pub(super) fn script(
209
+ &mut self,
210
+ function: &'ast Function,
211
+ scope: Option<&Scope<'ast>>,
212
+ this: Value<'ast>,
213
+ arguments: Vec<Value<'ast>>,
214
+ ) -> Evaluation<'ast, Value<'ast>> {
215
+ if let Some(intrinsic) = self.session.intrinsics.get(&intrinsic::address(function)) {
216
+ // Intrinsics may write to their arguments.
217
+ for argument in &arguments {
218
+ self.writable(argument)?;
219
+ }
220
+ if let Some(value) = intrinsic.call(&arguments) {
221
+ return Ok(value);
222
+ }
223
+ }
224
+ // Handlers decrypt strings both by reading registers and by calling the decoder.
225
+ if self
226
+ .session
227
+ .record_decoder
228
+ .is_some_and(|decoder| ptr::eq(decoder, function))
229
+ && let Some(decrypted) = self.decrypted
230
+ && let [record] = &arguments[..]
231
+ && let Some(key) = record_key(record)
232
+ {
233
+ if let Some(text) = decrypted.borrow().get(&key) {
234
+ return Ok(text.clone());
235
+ }
236
+ let text = self.interpret(function, scope, this, arguments)?;
237
+ // A run that thawed a shared object may have computed the text differently.
238
+ if matches!(text, Value::String(_)) && !self.thawed {
239
+ decrypted.borrow_mut().insert(key, text.clone());
240
+ }
241
+ return Ok(text);
242
+ }
243
+ self.interpret(function, scope, this, arguments)
244
+ }
245
+
246
+ /// Runs a function through its slot plan if it has one, or else through its scoped plan.
247
+ fn interpret(
197
248
  &mut self,
198
249
  function: &'ast Function,
199
250
  scope: Option<&Scope<'ast>>,
200
251
  this: Value<'ast>,
201
252
  arguments: Vec<Value<'ast>>,
202
253
  ) -> Evaluation<'ast, Value<'ast>> {
203
- if scope.is_none()
204
- && self.setup.is_none()
254
+ if self.setup.is_none()
205
255
  && let Some(plan) = self.session.compiled.slots(function)
206
256
  {
207
- return self.call_plan(&plan, this, arguments);
257
+ return self.call_plan(&plan, scope, this, arguments);
208
258
  }
209
259
  let body = function
210
260
  .body
@@ -252,6 +302,33 @@ impl<'ast> Run<'ast, '_> {
252
302
  }
253
303
  }
254
304
 
305
+ /// Identifies a string record: the program, then numbers and arrays of numbers. A program's
306
+ /// records with the same contents decrypt to the same string.
307
+ fn record_key(record: &Value<'_>) -> Option<RecordKey> {
308
+ let Value::Array(elements) = record else {
309
+ return None;
310
+ };
311
+ let elements = elements.borrow();
312
+ let (Value::Program, rest) = elements.split_first()? else {
313
+ return None;
314
+ };
315
+ let bits = |value: &Value<'_>| match value {
316
+ Value::Number(number) => Some(number.to_bits()),
317
+ _ => None,
318
+ };
319
+ rest.iter()
320
+ .map(|element| match element {
321
+ Value::Array(numbers) => numbers
322
+ .borrow()
323
+ .iter()
324
+ .map(bits)
325
+ .collect::<Option<_>>()
326
+ .map(RecordPart::Numbers),
327
+ element => bits(element).map(RecordPart::Number),
328
+ })
329
+ .collect()
330
+ }
331
+
255
332
  /// Binds call arguments to parameters and reserves `room` additional local slots.
256
333
  fn parameters<'ast, 'pattern>(
257
334
  patterns: impl Iterator<Item = &'pattern Pat>,