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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +10 -9
- data/UPSTREAM.md +6 -6
- data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
- data/lib/cfsolve/version.rb +2 -2
- data/vendor/cfsolve/README.md +0 -35
- data/vendor/cfsolve/examples/build_profile.rs +17 -15
- data/vendor/cfsolve/examples/capture_session.rs +37 -0
- data/vendor/cfsolve/examples/check_answers.rs +66 -0
- data/vendor/cfsolve/examples/compare_session.rs +190 -0
- data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
- data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
- data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
- data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
- data/vendor/cfsolve/src/capture/profile.rs +7 -18
- data/vendor/cfsolve/src/capture/recorded.rs +42 -7
- data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
- data/vendor/cfsolve/src/challenge/installed.rs +122 -271
- data/vendor/cfsolve/src/challenge/payload.rs +107 -5
- data/vendor/cfsolve/src/challenge/state.rs +98 -16
- data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
- data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
- data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
- data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
- data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
- data/vendor/cfsolve/src/http/client.rs +11 -0
- data/vendor/cfsolve/src/js/ast.rs +12 -3
- data/vendor/cfsolve/src/js/objects.rs +9 -2
- data/vendor/cfsolve/src/listing/fold.rs +0 -83
- data/vendor/cfsolve/src/listing/query.rs +25 -244
- data/vendor/cfsolve/src/listing.rs +0 -1
- data/vendor/cfsolve/src/session/initial.rs +14 -5
- data/vendor/cfsolve/src/session/probes.rs +160 -125
- data/vendor/cfsolve/src/session/timing.rs +58 -53
- data/vendor/cfsolve/src/session/widget.rs +5 -6
- data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
- data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
- data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
- data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
- data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
- data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
- data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
- data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
- data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
- data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
- data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
- data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
- data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
- data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
- data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
- data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
- data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
- data/vendor/cfsolve/src/stage2/probes.rs +0 -1
- data/vendor/cfsolve/src/stage2/records.rs +4 -13
- data/vendor/cfsolve/src/stage2/reference.json +1 -1
- data/vendor/cfsolve/src/stage2/reference.rs +98 -100
- data/vendor/cfsolve/src/stage2/registry.rs +41 -34
- data/vendor/cfsolve/src/stage2/replay.rs +182 -254
- data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
- data/vendor/cfsolve/src/stage2/trace.rs +18 -0
- data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
- data/vendor/cfsolve/src/stage2.rs +2 -1
- data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
- data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
- data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
- data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
- data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
- data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
- data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
- data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
- data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
- data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
- data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
- data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
- data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
- data/vendor/cfsolve/src/vm/interpret.rs +7 -2
- data/vendor/cfsolve/src/vm/machine.rs +6 -2
- data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
- data/vendor/cfsolve/src/vm/session.rs +36 -14
- data/vendor/cfsolve/src/vm/value.rs +13 -0
- data/vendor/cfsolve/src/vm.rs +1 -0
- data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
- metadata +13 -6
- data/vendor/cfsolve/src/listing/normalize.rs +0 -264
- data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
- data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
- data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
- data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
use std::cell::RefCell;
|
|
4
4
|
use std::rc::Rc;
|
|
5
5
|
|
|
6
|
-
use
|
|
7
|
-
|
|
6
|
+
use super::frozen::{self, Frozen};
|
|
8
7
|
use crate::vm::HashMap;
|
|
9
8
|
use crate::vm::value::{Callable, Env, Scope, Value};
|
|
10
9
|
|
|
@@ -17,135 +16,248 @@ pub(super) struct Copies<'ast> {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
/// Deep-copies mutable state in `value`. Uses original addresses in `copies` to preserve shared
|
|
20
|
-
/// references and avoid copying twice.
|
|
21
|
-
pub(super) fn copy<'a>(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
19
|
+
/// references and avoid copying twice. Objects in `frozen` stay shared.
|
|
20
|
+
pub(super) fn copy<'a>(
|
|
21
|
+
value: &Value<'a>,
|
|
22
|
+
copies: &mut Copies<'a>,
|
|
23
|
+
frozen: Option<&Frozen>,
|
|
24
|
+
) -> Value<'a> {
|
|
25
|
+
Isolation { copies, frozen }.copy(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// The deep copy shared by [`copy`] and [`Fork`], which differ in the scopes they copy.
|
|
29
|
+
trait Copier<'ast>: Sized {
|
|
30
|
+
/// Shared session objects, which are not copied.
|
|
31
|
+
fn frozen(&self) -> Option<&Frozen>;
|
|
32
|
+
/// Copies by original address.
|
|
33
|
+
fn values(&mut self) -> &mut HashMap<usize, Value<'ast>>;
|
|
34
|
+
/// Copies a function's captured scope, or returns `None` if it needs no copy.
|
|
35
|
+
fn captured(&mut self, scope: &Scope<'ast>) -> Option<Scope<'ast>>;
|
|
36
|
+
|
|
37
|
+
fn copy(&mut self, value: &Value<'ast>) -> Value<'ast> {
|
|
38
|
+
if let Some(frozen) = self.frozen()
|
|
39
|
+
&& let Some(address) = frozen::address(value)
|
|
40
|
+
&& frozen.contains(address)
|
|
41
|
+
{
|
|
42
|
+
return value.clone();
|
|
43
|
+
}
|
|
44
|
+
let address = match (value, frozen::address(value)) {
|
|
45
|
+
(Value::Function(callable), _) => Rc::as_ptr(callable).addr(),
|
|
46
|
+
(_, Some(address)) => address,
|
|
47
|
+
(_, None) => return value.clone(),
|
|
48
|
+
};
|
|
49
|
+
if let Some(copy) = self.values().get(&address) {
|
|
50
|
+
return copy.clone();
|
|
51
|
+
}
|
|
52
|
+
match value {
|
|
53
|
+
Value::Array(elements) => self.elements(elements, address, Value::Array, Self::copy),
|
|
54
|
+
Value::Set(members) => self.elements(members, address, Value::Set, Self::copy),
|
|
55
|
+
Value::Map(entries) => {
|
|
56
|
+
self.elements(entries, address, Value::Map, |copier, (key, value)| {
|
|
57
|
+
(copier.copy(key), copier.copy(value))
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
Value::Object(properties) => self.elements(
|
|
61
|
+
properties,
|
|
62
|
+
address,
|
|
63
|
+
Value::Object,
|
|
64
|
+
|copier, (name, value)| (name.clone(), copier.copy(value)),
|
|
65
|
+
),
|
|
66
|
+
Value::Function(callable) => {
|
|
67
|
+
let copied = match &**callable {
|
|
68
|
+
Callable::Script {
|
|
69
|
+
name,
|
|
61
70
|
function,
|
|
62
|
-
scope,
|
|
71
|
+
scope: Some(scope),
|
|
72
|
+
} => match self.captured(scope) {
|
|
73
|
+
// The function captures no mutable state.
|
|
74
|
+
None => return value.clone(),
|
|
75
|
+
scope => Callable::Script {
|
|
76
|
+
name: name.clone(),
|
|
77
|
+
function,
|
|
78
|
+
scope,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
Callable::Script { scope: None, .. }
|
|
82
|
+
| Callable::Builtin(_)
|
|
83
|
+
| Callable::Standard(_) => return value.clone(),
|
|
84
|
+
Callable::Arrow { arrow, this, scope } => Callable::Arrow {
|
|
85
|
+
arrow,
|
|
86
|
+
this: self.copy(this),
|
|
87
|
+
scope: scope.as_ref().and_then(|scope| self.captured(scope)),
|
|
88
|
+
},
|
|
89
|
+
Callable::Call(target) => Callable::Call(self.copy(target)),
|
|
90
|
+
Callable::Apply(target) => Callable::Apply(self.copy(target)),
|
|
91
|
+
Callable::Bind(target) => Callable::Bind(self.copy(target)),
|
|
92
|
+
Callable::Bound {
|
|
93
|
+
target,
|
|
94
|
+
this,
|
|
95
|
+
arguments,
|
|
96
|
+
} => Callable::Bound {
|
|
97
|
+
target: self.copy(target),
|
|
98
|
+
this: self.copy(this),
|
|
99
|
+
arguments: arguments
|
|
100
|
+
.iter()
|
|
101
|
+
.map(|argument| self.copy(argument))
|
|
102
|
+
.collect(),
|
|
63
103
|
},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
},
|
|
73
|
-
Callable::Call(target) => Callable::Call(copy(target, copies)),
|
|
74
|
-
Callable::Apply(target) => Callable::Apply(copy(target, copies)),
|
|
75
|
-
Callable::Bind(target) => Callable::Bind(copy(target, copies)),
|
|
76
|
-
Callable::Bound {
|
|
77
|
-
target,
|
|
78
|
-
this,
|
|
79
|
-
arguments,
|
|
80
|
-
} => Callable::Bound {
|
|
81
|
-
target: copy(target, copies),
|
|
82
|
-
this: copy(this, copies),
|
|
83
|
-
arguments: arguments
|
|
84
|
-
.iter()
|
|
85
|
-
.map(|argument| copy(argument, copies))
|
|
86
|
-
.collect(),
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
// A captured value may refer back to this function and copy it first.
|
|
90
|
-
if let Some(copy) = copies.values.get(&address) {
|
|
91
|
-
return copy.clone();
|
|
104
|
+
};
|
|
105
|
+
// A captured value may refer back to this function and copy it first.
|
|
106
|
+
if let Some(copy) = self.values().get(&address) {
|
|
107
|
+
return copy.clone();
|
|
108
|
+
}
|
|
109
|
+
let copied = Value::function(copied);
|
|
110
|
+
self.values().insert(address, copied.clone());
|
|
111
|
+
copied
|
|
92
112
|
}
|
|
93
|
-
|
|
94
|
-
copies.values.insert(address, copied.clone());
|
|
95
|
-
copied
|
|
113
|
+
_ => unreachable!("only objects have addresses"),
|
|
96
114
|
}
|
|
97
|
-
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Copies the elements of an array, set, map, or object with `copy_element`.
|
|
118
|
+
fn elements<T>(
|
|
119
|
+
&mut self,
|
|
120
|
+
elements: &RefCell<Vec<T>>,
|
|
121
|
+
address: usize,
|
|
122
|
+
wrap: fn(Rc<RefCell<Vec<T>>>) -> Value<'ast>,
|
|
123
|
+
copy_element: impl Fn(&mut Self, &T) -> T,
|
|
124
|
+
) -> Value<'ast> {
|
|
125
|
+
// Cache before copying elements because they may refer back to this object.
|
|
126
|
+
let copied = Rc::new(RefCell::new(Vec::new()));
|
|
127
|
+
self.values().insert(address, wrap(copied.clone()));
|
|
128
|
+
let elements: Vec<T> = elements
|
|
129
|
+
.borrow()
|
|
130
|
+
.iter()
|
|
131
|
+
.map(|element| copy_element(self, element))
|
|
132
|
+
.collect();
|
|
133
|
+
*copied.borrow_mut() = elements;
|
|
134
|
+
wrap(copied)
|
|
98
135
|
}
|
|
99
136
|
}
|
|
100
137
|
|
|
101
|
-
///
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
copies: &mut Copies<'a>,
|
|
106
|
-
wrap: fn(Rc<RefCell<Vec<T>>>) -> Value<'a>,
|
|
107
|
-
copy_element: impl Fn(&T, &mut Copies<'a>) -> T,
|
|
108
|
-
) -> Value<'a> {
|
|
109
|
-
// Cache before copying elements because they may refer back to this object.
|
|
110
|
-
let copied = Rc::new(RefCell::new(Vec::new()));
|
|
111
|
-
copies.values.insert(address, wrap(copied.clone()));
|
|
112
|
-
let elements: Vec<T> = elements
|
|
113
|
-
.borrow()
|
|
114
|
-
.iter()
|
|
115
|
-
.map(|element| copy_element(element, copies))
|
|
116
|
-
.collect();
|
|
117
|
-
*copied.borrow_mut() = elements;
|
|
118
|
-
wrap(copied)
|
|
138
|
+
/// The state of a [`copy`].
|
|
139
|
+
struct Isolation<'copies, 'frozen, 'ast> {
|
|
140
|
+
copies: &'copies mut Copies<'ast>,
|
|
141
|
+
frozen: Option<&'frozen Frozen>,
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
144
|
+
impl<'ast> Copier<'ast> for Isolation<'_, '_, 'ast> {
|
|
145
|
+
fn frozen(&self) -> Option<&Frozen> {
|
|
146
|
+
self.frozen
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn values(&mut self) -> &mut HashMap<usize, Value<'ast>> {
|
|
150
|
+
&mut self.copies.values
|
|
127
151
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
|
|
153
|
+
/// Copies `scope` and its parents. Omits chains with no variables: completed session calls
|
|
154
|
+
/// cannot add variables to those scopes.
|
|
155
|
+
fn captured(&mut self, scope: &Scope<'ast>) -> Option<Scope<'ast>> {
|
|
156
|
+
let address = Rc::as_ptr(scope).addr();
|
|
157
|
+
if let Some(copy) = self.copies.scopes.get(&address) {
|
|
158
|
+
return copy.clone();
|
|
159
|
+
}
|
|
160
|
+
let original = scope.borrow();
|
|
161
|
+
if original.variables.is_empty() {
|
|
162
|
+
let copied = original
|
|
163
|
+
.outer
|
|
164
|
+
.as_ref()
|
|
165
|
+
.and_then(|outer| self.captured(outer));
|
|
166
|
+
self.copies.scopes.insert(address, copied.clone());
|
|
167
|
+
return copied;
|
|
168
|
+
}
|
|
169
|
+
// Cache before copying variables because they may hold functions capturing this scope.
|
|
170
|
+
let copied = Env::scope(HashMap::default(), None);
|
|
171
|
+
self.copies.scopes.insert(address, Some(copied.clone()));
|
|
172
|
+
let outer = original
|
|
131
173
|
.outer
|
|
132
174
|
.as_ref()
|
|
133
|
-
.and_then(|outer|
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
.and_then(|outer| self.captured(outer));
|
|
176
|
+
let variables = original
|
|
177
|
+
.variables
|
|
178
|
+
.iter()
|
|
179
|
+
.map(|(name, value)| (name.clone(), self.copy(value)))
|
|
180
|
+
.collect();
|
|
181
|
+
*copied.borrow_mut() = Env { variables, outer };
|
|
182
|
+
Some(copied)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Deep-copies a run's state for another path to resume from (see
|
|
187
|
+
/// [`super::run::Snapshot`]). Unlike [`copy`], it copies every scope, since the run may still
|
|
188
|
+
/// add variables to them, and it clones maps before copying their values so they keep the
|
|
189
|
+
/// original's layout and iteration order.
|
|
190
|
+
pub(super) struct Fork<'frozen, 'ast> {
|
|
191
|
+
frozen: Option<&'frozen Frozen>,
|
|
192
|
+
values: HashMap<usize, Value<'ast>>,
|
|
193
|
+
scopes: HashMap<usize, Scope<'ast>>,
|
|
194
|
+
/// Every scope copied, for breaking cycles when the copy is dropped.
|
|
195
|
+
pub(super) copied: Vec<Scope<'ast>>,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
impl<'ast> Copier<'ast> for Fork<'_, 'ast> {
|
|
199
|
+
fn frozen(&self) -> Option<&Frozen> {
|
|
200
|
+
self.frozen
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fn values(&mut self) -> &mut HashMap<usize, Value<'ast>> {
|
|
204
|
+
&mut self.values
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn captured(&mut self, scope: &Scope<'ast>) -> Option<Scope<'ast>> {
|
|
208
|
+
Some(self.scope(scope))
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
impl<'frozen, 'ast> Fork<'frozen, 'ast> {
|
|
213
|
+
pub(super) fn new(frozen: Option<&'frozen Frozen>) -> Self {
|
|
214
|
+
Self {
|
|
215
|
+
frozen,
|
|
216
|
+
values: HashMap::default(),
|
|
217
|
+
scopes: HashMap::default(),
|
|
218
|
+
copied: Vec::new(),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pub(super) fn value(&mut self, value: &Value<'ast>) -> Value<'ast> {
|
|
223
|
+
self.copy(value)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
pub(super) fn scope(&mut self, scope: &Scope<'ast>) -> Scope<'ast> {
|
|
227
|
+
let address = Rc::as_ptr(scope).addr();
|
|
228
|
+
if let Some(copy) = self.scopes.get(&address) {
|
|
229
|
+
return copy.clone();
|
|
230
|
+
}
|
|
231
|
+
// Cache before copying variables because they may hold functions capturing this scope.
|
|
232
|
+
let copied = Env::scope(HashMap::default(), None);
|
|
233
|
+
self.scopes.insert(address, copied.clone());
|
|
234
|
+
self.copied.push(copied.clone());
|
|
235
|
+
let original = scope.borrow();
|
|
236
|
+
let outer = original.outer.as_ref().map(|outer| self.scope(outer));
|
|
237
|
+
let variables = self.map(&original.variables);
|
|
238
|
+
*copied.borrow_mut() = Env { variables, outer };
|
|
239
|
+
copied
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Copies a map's values, keeping its layout.
|
|
243
|
+
pub(super) fn map<K: Clone>(
|
|
244
|
+
&mut self,
|
|
245
|
+
map: &HashMap<K, Value<'ast>>,
|
|
246
|
+
) -> HashMap<K, Value<'ast>> {
|
|
247
|
+
let mut copied = map.clone();
|
|
248
|
+
for value in copied.values_mut() {
|
|
249
|
+
*value = self.copy(value);
|
|
250
|
+
}
|
|
251
|
+
copied
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// Copies the run's [`Copies`], mapping session objects to copies of their run copies.
|
|
255
|
+
pub(super) fn copies(&mut self, copies: &Copies<'ast>) -> Copies<'ast> {
|
|
256
|
+
let values = self.map(&copies.values);
|
|
257
|
+
let mut scopes = copies.scopes.clone();
|
|
258
|
+
for scope in scopes.values_mut().flatten() {
|
|
259
|
+
*scope = self.scope(scope);
|
|
260
|
+
}
|
|
261
|
+
Copies { values, scopes }
|
|
136
262
|
}
|
|
137
|
-
// Cache before copying variables because they may hold functions capturing this scope.
|
|
138
|
-
let copied = Env::scope(HashMap::default(), None);
|
|
139
|
-
copies.scopes.insert(address, Some(copied.clone()));
|
|
140
|
-
let outer = original
|
|
141
|
-
.outer
|
|
142
|
-
.as_ref()
|
|
143
|
-
.and_then(|outer| copy_scope(outer, copies));
|
|
144
|
-
let variables: HashMap<Atom, Value<'a>> = original
|
|
145
|
-
.variables
|
|
146
|
-
.iter()
|
|
147
|
-
.map(|(name, value)| (name.clone(), copy(value, copies)))
|
|
148
|
-
.collect();
|
|
149
|
-
*copied.borrow_mut() = Env { variables, outer };
|
|
150
|
-
Some(copied)
|
|
151
263
|
}
|
|
@@ -4,17 +4,58 @@ use swc_core::atoms::Atom;
|
|
|
4
4
|
use swc_core::ecma::ast::BinaryOp;
|
|
5
5
|
|
|
6
6
|
use crate::js::ast;
|
|
7
|
-
use crate::vm::extract;
|
|
8
7
|
use crate::vm::interpret::plan::FunctionPlan;
|
|
9
8
|
use crate::vm::interpret::run::Stop;
|
|
10
9
|
use crate::vm::interpret::{Run, semantics};
|
|
11
|
-
use crate::vm::output::Term;
|
|
12
|
-
use crate::vm::session::Session;
|
|
10
|
+
use crate::vm::output::{Effect, Position, Term};
|
|
11
|
+
use crate::vm::session::{Layout, Session};
|
|
13
12
|
use crate::vm::value::{Callable, Value};
|
|
13
|
+
use crate::vm::{disassemble, extract};
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn concrete_register_stack_keeps_its_exception_continuation() {
|
|
17
|
+
let parsed = ast::parse_iframe_javascript("function f(stack) { stack.push([42, 9, [], 0]); }")
|
|
18
|
+
.expect("the source parses");
|
|
19
|
+
let mut session = Session::empty();
|
|
20
|
+
session.functions = extract::functions(&parsed.script.body);
|
|
21
|
+
session.layout = Layout {
|
|
22
|
+
pc: 240,
|
|
23
|
+
key: 241,
|
|
24
|
+
program: 242,
|
|
25
|
+
cells: 243,
|
|
26
|
+
};
|
|
27
|
+
session.initial = vec![(7, Value::array(Vec::new()))];
|
|
28
|
+
let mut run = Run::method(&session);
|
|
29
|
+
run.start_at(0, 0);
|
|
30
|
+
let stack = Value::array(Vec::new());
|
|
31
|
+
run.registers.insert(7, stack.clone());
|
|
32
|
+
let function = Callable::Script {
|
|
33
|
+
name: None,
|
|
34
|
+
function: session.functions[&Atom::from("f")],
|
|
35
|
+
scope: None,
|
|
36
|
+
};
|
|
37
|
+
let result = run.invoke(&function, Value::Undefined, vec![stack]);
|
|
38
|
+
let path = run.finish(result).expect("the call completes");
|
|
39
|
+
let catches = |frame: &Term| {
|
|
40
|
+
matches!(frame, Term::Array(values)
|
|
41
|
+
if disassemble::catch_frame(values) == Some(Position::new(42, 9)))
|
|
42
|
+
};
|
|
43
|
+
assert!(
|
|
44
|
+
path.effects.iter().any(|effect| matches!(effect,
|
|
45
|
+
Effect::Set(7, Term::Array(frames)) if frames.iter().any(catches))),
|
|
46
|
+
"the pushed catch frame is kept as a register effect: {:?}",
|
|
47
|
+
path.effects
|
|
48
|
+
);
|
|
49
|
+
}
|
|
14
50
|
|
|
15
51
|
/// Compares results, exceptions, symbolic effects, branches, and global writes. Each execution
|
|
16
|
-
/// has independent mutable state.
|
|
17
|
-
fn compare(
|
|
52
|
+
/// has independent mutable state. Returns the shared outcome, starting with the result.
|
|
53
|
+
fn compare(
|
|
54
|
+
source: &str,
|
|
55
|
+
arguments: Vec<Value<'static>>,
|
|
56
|
+
decisions: Vec<bool>,
|
|
57
|
+
slots: bool,
|
|
58
|
+
) -> String {
|
|
18
59
|
// Leak the script text so arguments can be prepared before parsing it.
|
|
19
60
|
let parsed: &_ = Box::leak(Box::new(ast::parse_iframe_javascript(source).unwrap()));
|
|
20
61
|
let mut session = Session::empty();
|
|
@@ -56,7 +97,9 @@ fn compare(source: &str, arguments: Vec<Value<'static>>, decisions: Vec<bool>, s
|
|
|
56
97
|
run.effects, run.branches.decisions
|
|
57
98
|
)
|
|
58
99
|
};
|
|
59
|
-
|
|
100
|
+
let outcome = execute(false);
|
|
101
|
+
assert_eq!(outcome, execute(true), "{source}");
|
|
102
|
+
outcome
|
|
60
103
|
}
|
|
61
104
|
|
|
62
105
|
#[test]
|
|
@@ -122,13 +165,37 @@ fn branches_and_loops_match() {
|
|
|
122
165
|
}
|
|
123
166
|
}
|
|
124
167
|
|
|
168
|
+
#[test]
|
|
169
|
+
fn functions_declared_in_blocks_are_bound_on_entry() {
|
|
170
|
+
// A function declared after a block's `return` is callable from the start of the block,
|
|
171
|
+
// including one that reads the enclosing function's locals.
|
|
172
|
+
for (source, slots, result) in [
|
|
173
|
+
(
|
|
174
|
+
"function f(a) { { return g(a) + 1; function g(x) { return x * 3; } } }",
|
|
175
|
+
true,
|
|
176
|
+
"return Const(Number(7.0))",
|
|
177
|
+
),
|
|
178
|
+
(
|
|
179
|
+
"function f(a) { { var b = 4; return g(1); function g(x) { return a + b + x; } } }",
|
|
180
|
+
true,
|
|
181
|
+
"return Const(Number(7.0))",
|
|
182
|
+
),
|
|
183
|
+
(
|
|
184
|
+
"function f(a) { try { return g(a); function g(x) { return x + 1; } } finally {} }",
|
|
185
|
+
true,
|
|
186
|
+
"return Const(Number(3.0))",
|
|
187
|
+
),
|
|
188
|
+
] {
|
|
189
|
+
let outcome = compare(source, vec![Value::Number(2.0)], vec![], slots);
|
|
190
|
+
assert!(outcome.starts_with(result), "{source}: {outcome}");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
125
194
|
#[test]
|
|
126
195
|
fn rejected_functions_use_scoped_plans() {
|
|
127
196
|
for source in [
|
|
128
197
|
"function f(a) { return (function() { return a; })(); }",
|
|
129
|
-
"function f(a) { var g = () => a; return g(); }",
|
|
130
198
|
"function f(a) { var g = () => arguments[0]; return g(); }",
|
|
131
|
-
"function f(a) { var g = function() { return a; }; return g(); }",
|
|
132
199
|
"function f(arguments) { return arguments; }",
|
|
133
200
|
"function f(a) { return {[a]: 1}; }",
|
|
134
201
|
"function f(a) { try { return a; } catch ({ e }) { return 0; } }",
|
|
@@ -137,6 +204,27 @@ fn rejected_functions_use_scoped_plans() {
|
|
|
137
204
|
}
|
|
138
205
|
}
|
|
139
206
|
|
|
207
|
+
/// Locals that nested functions mention stay in the call's scope, and both the outer
|
|
208
|
+
/// function and its closures keep slot plans.
|
|
209
|
+
#[test]
|
|
210
|
+
fn closures_share_captured_locals() {
|
|
211
|
+
for source in [
|
|
212
|
+
"function f(a) { var g = () => a; return g(); }",
|
|
213
|
+
"function f(a) { var g = function() { return a; }; return g(); }",
|
|
214
|
+
"function f(a) { var s = 0; function g(x) { return k + x; } var k = a * 3; s = g(1) + g(2); return s; }",
|
|
215
|
+
"function f(a) { var n = 0; var inc = function() { n += a; return n; }; inc(); inc(); return n; }",
|
|
216
|
+
"function f(a) { var mk = function(b) { return function(c) { return a + b + c; }; }; return mk(1)(2); }",
|
|
217
|
+
"function f(a) { a = a + 1; var g = function() { return a; }; a = a * 10; return g(); }",
|
|
218
|
+
"function f(a) { var t = [a]; var g = function(i) { return t[i] + outer; }; return g(0); }",
|
|
219
|
+
"function f(a) { function r(n) { return n ? r(n - 1) + a : 0; } return r(3); }",
|
|
220
|
+
"function f(a) { var s = ''; for (var i = 0; i < a; i++) { s += c(i); } return s; function c(j) { var x = j * 2; return k[x]; } var k; }",
|
|
221
|
+
"function f(a) { var k = 'abcdef', s = ''; for (var i = 0; i < a; i++) { s += c(i); } return s; function c(j) { var x = j * 2; return k[x]; } }",
|
|
222
|
+
"function f(a) { var o = {}; o.h = function(x, y) { return x ^ y; }; var h = o.h; return h(a, 6); }",
|
|
223
|
+
] {
|
|
224
|
+
compare(source, vec![Value::Number(2.0)], vec![], true);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
140
228
|
#[test]
|
|
141
229
|
fn numeric_operands_and_effects_match() {
|
|
142
230
|
for source in [
|