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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
//! Native versions of pure functions the script defines, such as the BLAKE3 compression its
|
|
2
|
+
//! string decryption runs. A script function is replaced only after it returns the native
|
|
3
|
+
//! result on test inputs. Calls with arguments a native version does not accept are
|
|
4
|
+
//! interpreted.
|
|
5
|
+
|
|
6
|
+
use std::ptr;
|
|
7
|
+
|
|
8
|
+
use swc_core::ecma::ast::{Function, Lit};
|
|
9
|
+
use swc_core::ecma::visit::{Visit, VisitWith};
|
|
10
|
+
|
|
11
|
+
use super::copies::Copies;
|
|
12
|
+
use super::run::Run;
|
|
13
|
+
use crate::js;
|
|
14
|
+
use crate::vm::HashMap;
|
|
15
|
+
use crate::vm::session::Session;
|
|
16
|
+
use crate::vm::value::{Callable, Value};
|
|
17
|
+
|
|
18
|
+
/// A native function equivalent to a script function.
|
|
19
|
+
#[derive(Clone, Copy)]
|
|
20
|
+
pub(crate) enum Intrinsic {
|
|
21
|
+
/// `compress(cv, block, counter, length, flags)`: overwrites the 8 words of `cv` with the
|
|
22
|
+
/// BLAKE3 compression of the 16-word `block`.
|
|
23
|
+
Blake3Compress,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Script functions with native equivalents, by function address.
|
|
27
|
+
pub(crate) type Intrinsics = HashMap<usize, Intrinsic>;
|
|
28
|
+
|
|
29
|
+
/// The address identifying `function` in [`Intrinsics`].
|
|
30
|
+
pub(super) fn address(function: &Function) -> usize {
|
|
31
|
+
ptr::from_ref(function).addr()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Finds the script functions `session` can run natively. Tests functions that take five
|
|
35
|
+
/// parameters and mention the BLAKE3 initialization vector.
|
|
36
|
+
pub(crate) fn intrinsics(session: &Session<'_>) -> Intrinsics {
|
|
37
|
+
session
|
|
38
|
+
.functions
|
|
39
|
+
.values()
|
|
40
|
+
.filter(|function| function.params.len() == 5 && mentions(function, IV[0]))
|
|
41
|
+
.filter(|function| compresses_like_native(session, function))
|
|
42
|
+
.map(|function| (address(function), Intrinsic::Blake3Compress))
|
|
43
|
+
.collect()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl Intrinsic {
|
|
47
|
+
/// Runs the native version. Returns `None` if the arguments are outside what it reproduces
|
|
48
|
+
/// exactly, leaving the call to the interpreter.
|
|
49
|
+
pub(super) fn call<'ast>(self, arguments: &[Value<'ast>]) -> Option<Value<'ast>> {
|
|
50
|
+
match self {
|
|
51
|
+
Self::Blake3Compress => {
|
|
52
|
+
let [
|
|
53
|
+
Value::Array(cv),
|
|
54
|
+
Value::Array(block),
|
|
55
|
+
counter,
|
|
56
|
+
length,
|
|
57
|
+
flags,
|
|
58
|
+
] = arguments
|
|
59
|
+
else {
|
|
60
|
+
return None;
|
|
61
|
+
};
|
|
62
|
+
let output = {
|
|
63
|
+
let (cv, block) = (cv.borrow(), block.borrow());
|
|
64
|
+
compress(
|
|
65
|
+
&words(cv.get(..8)?)?,
|
|
66
|
+
&words(block.get(..16)?)?,
|
|
67
|
+
Counter::of(counter.as_number()?),
|
|
68
|
+
js::int32(length.as_number()?) as u32,
|
|
69
|
+
js::int32(flags.as_number()?) as u32,
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
let mut cv = cv.borrow_mut();
|
|
73
|
+
for (slot, word) in cv.iter_mut().zip(output) {
|
|
74
|
+
// The script stores the results of `^`, which are signed.
|
|
75
|
+
*slot = Value::Number(f64::from(word as i32));
|
|
76
|
+
}
|
|
77
|
+
Some(Value::Undefined)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
impl Value<'_> {
|
|
84
|
+
fn as_number(&self) -> Option<f64> {
|
|
85
|
+
match self {
|
|
86
|
+
Self::Number(number) => Some(*number),
|
|
87
|
+
_ => None,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Converts state or message words. The script first adds three words before truncating, which
|
|
93
|
+
/// matches wrapping addition only while the sum is exact, so larger or fractional words are
|
|
94
|
+
/// rejected.
|
|
95
|
+
fn words<const N: usize>(values: &[Value<'_>]) -> Option<[u32; N]> {
|
|
96
|
+
let mut words = [0; N];
|
|
97
|
+
for (word, value) in words.iter_mut().zip(values) {
|
|
98
|
+
let number = value.as_number()?;
|
|
99
|
+
if number.fract() != 0.0 || number.abs() >= 2f64.powi(51) {
|
|
100
|
+
return None;
|
|
101
|
+
}
|
|
102
|
+
*word = js::int32(number) as u32;
|
|
103
|
+
}
|
|
104
|
+
Some(words)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// A block counter split as the script splits it: `counter | 0` and
|
|
108
|
+
/// `counter / 4294967296 | 0`.
|
|
109
|
+
#[derive(Clone, Copy)]
|
|
110
|
+
struct Counter {
|
|
111
|
+
low: u32,
|
|
112
|
+
high: u32,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl Counter {
|
|
116
|
+
fn of(counter: f64) -> Self {
|
|
117
|
+
Self {
|
|
118
|
+
low: js::int32(counter) as u32,
|
|
119
|
+
high: js::int32(counter / 4_294_967_296.0) as u32,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const IV: [u32; 8] = [
|
|
125
|
+
0x6a09_e667,
|
|
126
|
+
0xbb67_ae85,
|
|
127
|
+
0x3c6e_f372,
|
|
128
|
+
0xa54f_f53a,
|
|
129
|
+
0x510e_527f,
|
|
130
|
+
0x9b05_688c,
|
|
131
|
+
0x1f83_d9ab,
|
|
132
|
+
0x5be0_cd19,
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
/// Message word order of each round after the first, relative to the previous round.
|
|
136
|
+
const PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
|
|
137
|
+
|
|
138
|
+
/// The BLAKE3 compression function, truncated to the chaining value.
|
|
139
|
+
fn compress(
|
|
140
|
+
cv: &[u32; 8],
|
|
141
|
+
block: &[u32; 16],
|
|
142
|
+
counter: Counter,
|
|
143
|
+
length: u32,
|
|
144
|
+
flags: u32,
|
|
145
|
+
) -> [u32; 8] {
|
|
146
|
+
let mut state = [0; 16];
|
|
147
|
+
state[..8].copy_from_slice(cv);
|
|
148
|
+
state[8..12].copy_from_slice(&IV[..4]);
|
|
149
|
+
state[12..].copy_from_slice(&[counter.low, counter.high, length, flags]);
|
|
150
|
+
let mut message = *block;
|
|
151
|
+
for round in 0..7 {
|
|
152
|
+
if round > 0 {
|
|
153
|
+
message = PERMUTATION.map(|index| message[index]);
|
|
154
|
+
}
|
|
155
|
+
mix(&mut state, [0, 4, 8, 12], message[0], message[1]);
|
|
156
|
+
mix(&mut state, [1, 5, 9, 13], message[2], message[3]);
|
|
157
|
+
mix(&mut state, [2, 6, 10, 14], message[4], message[5]);
|
|
158
|
+
mix(&mut state, [3, 7, 11, 15], message[6], message[7]);
|
|
159
|
+
mix(&mut state, [0, 5, 10, 15], message[8], message[9]);
|
|
160
|
+
mix(&mut state, [1, 6, 11, 12], message[10], message[11]);
|
|
161
|
+
mix(&mut state, [2, 7, 8, 13], message[12], message[13]);
|
|
162
|
+
mix(&mut state, [3, 4, 9, 14], message[14], message[15]);
|
|
163
|
+
}
|
|
164
|
+
std::array::from_fn(|index| state[index] ^ state[index + 8])
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// The BLAKE3 quarter-round `G` on state words `a`, `b`, `c` and `d`.
|
|
168
|
+
fn mix(state: &mut [u32; 16], [a, b, c, d]: [usize; 4], x: u32, y: u32) {
|
|
169
|
+
state[a] = state[a].wrapping_add(state[b]).wrapping_add(x);
|
|
170
|
+
state[d] = (state[d] ^ state[a]).rotate_right(16);
|
|
171
|
+
state[c] = state[c].wrapping_add(state[d]);
|
|
172
|
+
state[b] = (state[b] ^ state[c]).rotate_right(12);
|
|
173
|
+
state[a] = state[a].wrapping_add(state[b]).wrapping_add(y);
|
|
174
|
+
state[d] = (state[d] ^ state[a]).rotate_right(8);
|
|
175
|
+
state[c] = state[c].wrapping_add(state[d]);
|
|
176
|
+
state[b] = (state[b] ^ state[c]).rotate_right(7);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Whether `function` contains the numeric literal `number`.
|
|
180
|
+
fn mentions(function: &Function, number: u32) -> bool {
|
|
181
|
+
struct Finder {
|
|
182
|
+
number: f64,
|
|
183
|
+
found: bool,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
impl Visit for Finder {
|
|
187
|
+
fn visit_lit(&mut self, literal: &Lit) {
|
|
188
|
+
self.found |= matches!(literal, Lit::Num(found) if found.value == self.number);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let mut finder = Finder {
|
|
193
|
+
number: f64::from(number),
|
|
194
|
+
found: false,
|
|
195
|
+
};
|
|
196
|
+
function.visit_with(&mut finder);
|
|
197
|
+
finder.found
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Whether interpreting `function` gives the result of [`Intrinsic::Blake3Compress`] on every
|
|
201
|
+
/// test input, returning nothing and recording no effects.
|
|
202
|
+
fn compresses_like_native<'ast>(session: &Session<'ast>, function: &'ast Function) -> bool {
|
|
203
|
+
TEST_INPUTS.iter().all(|input| {
|
|
204
|
+
let native = input.arguments();
|
|
205
|
+
if Intrinsic::Blake3Compress.call(&native).is_none() {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
// Clones share the arrays, so `interpreted` shows what the script wrote.
|
|
209
|
+
let interpreted = input.arguments();
|
|
210
|
+
let mut run = Run::new(session, &[], Vec::new(), Some(Copies::default()));
|
|
211
|
+
let callable = Callable::Script {
|
|
212
|
+
name: None,
|
|
213
|
+
function,
|
|
214
|
+
scope: None,
|
|
215
|
+
};
|
|
216
|
+
let outcome = run.invoke(&callable, Value::Undefined, interpreted.clone());
|
|
217
|
+
let same = matches!(outcome, Ok(Value::Undefined))
|
|
218
|
+
&& run.effects.is_empty()
|
|
219
|
+
&& numbers(&interpreted[0]) == numbers(&native[0]);
|
|
220
|
+
for scope in run.scopes() {
|
|
221
|
+
scope.borrow_mut().variables.clear();
|
|
222
|
+
}
|
|
223
|
+
same
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Arguments for [`Intrinsic::Blake3Compress`], covering signed and unsigned words and a counter
|
|
228
|
+
/// above 32 bits.
|
|
229
|
+
struct TestInput {
|
|
230
|
+
cv: [f64; 8],
|
|
231
|
+
block: [f64; 16],
|
|
232
|
+
counter: f64,
|
|
233
|
+
length: f64,
|
|
234
|
+
flags: f64,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const TEST_INPUTS: [TestInput; 2] = [
|
|
238
|
+
TestInput {
|
|
239
|
+
cv: [
|
|
240
|
+
3_144_134_277.0,
|
|
241
|
+
-1_521_486_534.0,
|
|
242
|
+
7.0,
|
|
243
|
+
4_294_967_295.0,
|
|
244
|
+
1_359_893_119.0,
|
|
245
|
+
0.0,
|
|
246
|
+
-1.0,
|
|
247
|
+
2_147_483_648.0,
|
|
248
|
+
],
|
|
249
|
+
block: [
|
|
250
|
+
1.0,
|
|
251
|
+
2.0,
|
|
252
|
+
3.0,
|
|
253
|
+
255.0,
|
|
254
|
+
65_536.0,
|
|
255
|
+
-2.0,
|
|
256
|
+
1_779_033_703.0,
|
|
257
|
+
3_000_000_000.0,
|
|
258
|
+
12.0,
|
|
259
|
+
-2_147_483_648.0,
|
|
260
|
+
99.0,
|
|
261
|
+
100_000.0,
|
|
262
|
+
31.0,
|
|
263
|
+
4_000_000_000.0,
|
|
264
|
+
5.0,
|
|
265
|
+
6.0,
|
|
266
|
+
],
|
|
267
|
+
counter: 4_294_967_301.0,
|
|
268
|
+
length: 64.0,
|
|
269
|
+
flags: 11.0,
|
|
270
|
+
},
|
|
271
|
+
TestInput {
|
|
272
|
+
cv: [
|
|
273
|
+
1_779_033_703.0,
|
|
274
|
+
3_144_134_277.0,
|
|
275
|
+
1_013_904_242.0,
|
|
276
|
+
2_773_480_762.0,
|
|
277
|
+
1_359_893_119.0,
|
|
278
|
+
2_600_822_924.0,
|
|
279
|
+
528_734_635.0,
|
|
280
|
+
1_541_459_225.0,
|
|
281
|
+
],
|
|
282
|
+
block: [
|
|
283
|
+
827_605_328.0,
|
|
284
|
+
17.0,
|
|
285
|
+
0.0,
|
|
286
|
+
0.0,
|
|
287
|
+
-5.0,
|
|
288
|
+
0.0,
|
|
289
|
+
0.0,
|
|
290
|
+
1_234.0,
|
|
291
|
+
0.0,
|
|
292
|
+
0.0,
|
|
293
|
+
0.0,
|
|
294
|
+
0.0,
|
|
295
|
+
0.0,
|
|
296
|
+
0.0,
|
|
297
|
+
0.0,
|
|
298
|
+
0.0,
|
|
299
|
+
],
|
|
300
|
+
counter: 3.0,
|
|
301
|
+
length: 13.0,
|
|
302
|
+
flags: 1.0,
|
|
303
|
+
},
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
impl TestInput {
|
|
307
|
+
fn arguments<'ast>(&self) -> Vec<Value<'ast>> {
|
|
308
|
+
let array =
|
|
309
|
+
|numbers: &[f64]| Value::array(numbers.iter().copied().map(Value::Number).collect());
|
|
310
|
+
vec![
|
|
311
|
+
array(&self.cv),
|
|
312
|
+
array(&self.block),
|
|
313
|
+
Value::Number(self.counter),
|
|
314
|
+
Value::Number(self.length),
|
|
315
|
+
Value::Number(self.flags),
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fn numbers(value: &Value<'_>) -> Option<Vec<f64>> {
|
|
321
|
+
let Value::Array(elements) = value else {
|
|
322
|
+
return None;
|
|
323
|
+
};
|
|
324
|
+
elements.borrow().iter().map(Value::as_number).collect()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[cfg(test)]
|
|
328
|
+
mod tests {
|
|
329
|
+
use super::*;
|
|
330
|
+
|
|
331
|
+
/// BLAKE3 of the empty input is one compression of an empty block with the chunk-start,
|
|
332
|
+
/// chunk-end and root flags.
|
|
333
|
+
#[test]
|
|
334
|
+
fn compresses_like_blake3() {
|
|
335
|
+
let digest = compress(&IV, &[0; 16], Counter::of(0.0), 0, 1 | 2 | 8);
|
|
336
|
+
let bytes: Vec<u8> = digest.iter().flat_map(|word| word.to_le_bytes()).collect();
|
|
337
|
+
let hex: String = bytes.iter().map(|byte| format!("{byte:02x}")).collect();
|
|
338
|
+
assert_eq!(
|
|
339
|
+
hex,
|
|
340
|
+
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[test]
|
|
345
|
+
fn rejects_inexact_words() {
|
|
346
|
+
let mut arguments = TEST_INPUTS[0].arguments();
|
|
347
|
+
arguments[1] = Value::array(vec![Value::Number(0.5); 16]);
|
|
348
|
+
assert!(Intrinsic::Blake3Compress.call(&arguments).is_none());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! Compiles JavaScript into plans run by [`super::evaluate`]. Slot plans store function locals
|
|
2
|
-
//! in numbered slots
|
|
3
|
-
//!
|
|
2
|
+
//! in numbered slots, except locals that nested functions mention, which stay in the call's
|
|
3
|
+
//! scope for those functions to capture. Scoped plans resolve all names through scopes.
|
|
4
|
+
//! Unsupported syntax becomes a node that stops evaluation if reached.
|
|
4
5
|
|
|
5
6
|
#[cfg(test)]
|
|
6
7
|
use std::cell::Cell;
|
|
@@ -8,15 +9,17 @@ use std::cell::RefCell;
|
|
|
8
9
|
use std::ptr;
|
|
9
10
|
use std::rc::Rc;
|
|
10
11
|
|
|
12
|
+
use rustc_hash::FxHashSet as HashSet;
|
|
11
13
|
use swc_core::atoms::{Atom, atom};
|
|
12
14
|
use swc_core::ecma::ast::{
|
|
13
|
-
ArrowExpr, AssignTarget, BinaryOp, BlockStmtOrExpr, Callee, CatchClause, Class,
|
|
14
|
-
DoWhileStmt, Expr, ExprOrSpread, FnDecl, Function, Ident, Lit, MemberExpr, MemberProp,
|
|
15
|
-
Prop, PropName, PropOrSpread, SimpleAssignTarget, Stmt, UnaryOp, UpdateOp, VarDecl,
|
|
15
|
+
ArrowExpr, AssignTarget, BinaryOp, BlockStmt, BlockStmtOrExpr, Callee, CatchClause, Class,
|
|
16
|
+
Decl, DoWhileStmt, Expr, ExprOrSpread, FnDecl, Function, Ident, Lit, MemberExpr, MemberProp,
|
|
17
|
+
Pat, Prop, PropName, PropOrSpread, SimpleAssignTarget, Stmt, UnaryOp, UpdateOp, VarDecl,
|
|
16
18
|
VarDeclKind, VarDeclOrExpr, VarDeclarator, WhileStmt,
|
|
17
19
|
};
|
|
18
20
|
use swc_core::ecma::visit::{Visit, VisitWith};
|
|
19
21
|
|
|
22
|
+
use super::compile::{self, Code};
|
|
20
23
|
use super::semantics;
|
|
21
24
|
use crate::vm::HashMap;
|
|
22
25
|
use crate::vm::value::Value;
|
|
@@ -65,28 +68,35 @@ impl<'ast> Plans<'ast> {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
pub(super) struct FunctionPlan<'ast> {
|
|
68
|
-
|
|
71
|
+
/// Where each parameter is bound: a slot, or the call's scope if a nested function
|
|
72
|
+
/// mentions it.
|
|
73
|
+
pub(super) parameters: Vec<Name<'ast>>,
|
|
69
74
|
/// The slot of `arguments`, if the body reads it.
|
|
70
75
|
pub(super) arguments: Option<usize>,
|
|
71
76
|
pub(super) locals: usize,
|
|
72
|
-
|
|
77
|
+
/// The body, compiled into closures.
|
|
78
|
+
pub(super) code: Vec<Code<'ast>>,
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
impl<'ast> FunctionPlan<'ast> {
|
|
76
82
|
/// Attempts to compile a slot plan. Returns `None` to request a scoped plan for unsupported
|
|
77
83
|
/// syntax, parenthesized callees, computed object keys, logical assignments, parameter
|
|
78
|
-
/// patterns, variables named `arguments`, or
|
|
84
|
+
/// patterns, variables named `arguments`, or arrows reading `arguments`.
|
|
79
85
|
pub(super) fn compile(function: &'ast Function) -> Option<Self> {
|
|
80
|
-
let
|
|
86
|
+
let body = function.body.as_ref()?;
|
|
87
|
+
let mut compiler = PlanCompiler {
|
|
88
|
+
captured: captured(body),
|
|
89
|
+
..PlanCompiler::default()
|
|
90
|
+
};
|
|
81
91
|
let mut parameters = Vec::with_capacity(function.params.len());
|
|
82
92
|
for parameter in &function.params {
|
|
83
93
|
let name = ¶meter.pat.as_ident()?.sym;
|
|
84
94
|
if name == "arguments" {
|
|
85
95
|
return None;
|
|
86
96
|
}
|
|
87
|
-
|
|
97
|
+
compiler.local(name);
|
|
98
|
+
parameters.push(compiler.name(name));
|
|
88
99
|
}
|
|
89
|
-
let body = function.body.as_ref()?;
|
|
90
100
|
body.visit_with(&mut compiler);
|
|
91
101
|
let body = compiler.body(&body.stmts);
|
|
92
102
|
if compiler.rejected {
|
|
@@ -96,7 +106,7 @@ impl<'ast> FunctionPlan<'ast> {
|
|
|
96
106
|
parameters,
|
|
97
107
|
arguments: compiler.locals.get(&atom!("arguments")).copied(),
|
|
98
108
|
locals: compiler.locals.len(),
|
|
99
|
-
body,
|
|
109
|
+
code: compile::statements(&body),
|
|
100
110
|
})
|
|
101
111
|
}
|
|
102
112
|
}
|
|
@@ -126,6 +136,8 @@ pub(super) fn scoped_arrow(arrow: &ArrowExpr) -> Vec<Statement<'_>> {
|
|
|
126
136
|
struct PlanCompiler {
|
|
127
137
|
/// Slots of a slot plan's locals; empty for scoped plans.
|
|
128
138
|
locals: HashMap<Atom, usize>,
|
|
139
|
+
/// Names nested functions mention. Locals with these names stay in the call's scope.
|
|
140
|
+
captured: HashSet<Atom>,
|
|
129
141
|
/// Whether compilation needs a scoped plan. Ignored when compiling scoped plans.
|
|
130
142
|
rejected: bool,
|
|
131
143
|
}
|
|
@@ -133,32 +145,31 @@ struct PlanCompiler {
|
|
|
133
145
|
impl Visit for PlanCompiler {
|
|
134
146
|
fn visit_var_declarator(&mut self, declaration: &VarDeclarator) {
|
|
135
147
|
if let Pat::Ident(name) = &declaration.name {
|
|
136
|
-
self.
|
|
148
|
+
self.local(&name.sym);
|
|
137
149
|
}
|
|
138
150
|
declaration.visit_children_with(self);
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
fn visit_ident(&mut self, name: &Ident) {
|
|
142
154
|
if name.sym == "arguments" {
|
|
143
|
-
self.
|
|
155
|
+
self.local(&name.sym);
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
|
|
147
159
|
/// Treat a nested function declaration as a local binding in the enclosing function.
|
|
148
160
|
fn visit_fn_decl(&mut self, declaration: &FnDecl) {
|
|
149
|
-
self.
|
|
161
|
+
self.local(&declaration.ident.sym);
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
/// Treat a `catch` parameter as a function local in the execution plan.
|
|
153
165
|
fn visit_catch_clause(&mut self, clause: &CatchClause) {
|
|
154
166
|
if let Some(Pat::Ident(name)) = &clause.param {
|
|
155
|
-
self.
|
|
167
|
+
self.local(&name.sym);
|
|
156
168
|
}
|
|
157
169
|
clause.visit_children_with(self);
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
//
|
|
161
|
-
// [`PlanCompiler::closes_over_nothing`]). Its own locals remain separate.
|
|
172
|
+
// A nested function's own locals remain separate.
|
|
162
173
|
fn visit_function(&mut self, _: &Function) {}
|
|
163
174
|
fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
|
|
164
175
|
fn visit_class(&mut self, _: &Class) {}
|
|
@@ -166,12 +177,36 @@ impl Visit for PlanCompiler {
|
|
|
166
177
|
|
|
167
178
|
/// Every name a nested function mentions, at any depth.
|
|
168
179
|
#[derive(Default)]
|
|
169
|
-
struct Names(
|
|
180
|
+
struct Names(HashSet<Atom>);
|
|
170
181
|
|
|
171
182
|
impl Visit for Names {
|
|
172
183
|
fn visit_ident(&mut self, name: &Ident) {
|
|
173
|
-
self.0.
|
|
184
|
+
self.0.insert(name.sym.clone());
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// The names that functions, arrows, and classes nested in `body` mention, except `arguments`,
|
|
189
|
+
/// which functions shadow and arrows reading it are rejected for.
|
|
190
|
+
fn captured(body: &BlockStmt) -> HashSet<Atom> {
|
|
191
|
+
struct Nested(Names);
|
|
192
|
+
|
|
193
|
+
impl Visit for Nested {
|
|
194
|
+
fn visit_function(&mut self, function: &Function) {
|
|
195
|
+
function.visit_with(&mut self.0);
|
|
196
|
+
}
|
|
197
|
+
fn visit_arrow_expr(&mut self, arrow: &ArrowExpr) {
|
|
198
|
+
arrow.visit_with(&mut self.0);
|
|
199
|
+
}
|
|
200
|
+
fn visit_class(&mut self, class: &Class) {
|
|
201
|
+
class.visit_with(&mut self.0);
|
|
202
|
+
}
|
|
174
203
|
}
|
|
204
|
+
|
|
205
|
+
let mut nested = Nested(Names::default());
|
|
206
|
+
body.visit_with(&mut nested);
|
|
207
|
+
let Nested(Names(mut names)) = nested;
|
|
208
|
+
names.remove(&atom!("arguments"));
|
|
209
|
+
names
|
|
175
210
|
}
|
|
176
211
|
|
|
177
212
|
pub(super) struct Case<'ast> {
|
|
@@ -291,31 +326,22 @@ impl<'ast> Name<'ast> {
|
|
|
291
326
|
}
|
|
292
327
|
|
|
293
328
|
impl PlanCompiler {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
/// Slot plans do not put these locals in the scope chain. Ordinary functions shadow
|
|
301
|
-
/// `arguments`; arrows inherit it and must not read it.
|
|
302
|
-
fn closes_over_nothing(&self, nested: &impl VisitWith<Names>, arrow: bool) -> bool {
|
|
303
|
-
let mut names = Names::default();
|
|
304
|
-
nested.visit_with(&mut names);
|
|
305
|
-
names
|
|
306
|
-
.0
|
|
307
|
-
.iter()
|
|
308
|
-
.all(|name| !self.locals.contains_key(name) && !(arrow && name == "arguments"))
|
|
329
|
+
/// Gives local `name` a slot unless a nested function mentions it.
|
|
330
|
+
fn local(&mut self, name: &Atom) {
|
|
331
|
+
if !self.captured.contains(name) {
|
|
332
|
+
let next = self.locals.len();
|
|
333
|
+
self.locals.entry(name.clone()).or_insert(next);
|
|
334
|
+
}
|
|
309
335
|
}
|
|
310
336
|
|
|
311
|
-
/// Plans a function body, binding function declarations before executing
|
|
312
|
-
///
|
|
337
|
+
/// Plans a function body or block, binding its function declarations before executing
|
|
338
|
+
/// statements, as JavaScript hoists them to the start of the body or block. Declarations
|
|
339
|
+
/// beside the VM class are planned as statements and resolved as globals.
|
|
313
340
|
fn body<'a>(&mut self, statements: &'a [Stmt]) -> Vec<Statement<'a>> {
|
|
314
341
|
let mut plan = Vec::with_capacity(statements.len());
|
|
315
342
|
for statement in statements {
|
|
316
343
|
if let Stmt::Decl(Decl::Fn(declaration)) = statement {
|
|
317
344
|
let name = &declaration.ident.sym;
|
|
318
|
-
self.rejected |= !self.closes_over_nothing(&*declaration.function, false);
|
|
319
345
|
plan.push(Statement::Declare(vec![Declaration {
|
|
320
346
|
name: self.name(name),
|
|
321
347
|
init: Some(Expression::Function(Some(name), &declaration.function)),
|
|
@@ -341,7 +367,6 @@ impl PlanCompiler {
|
|
|
341
367
|
// Declarations do not bind their names; the names resolve as globals.
|
|
342
368
|
Stmt::Decl(Decl::Fn(_) | Decl::Class(_)) | Stmt::Empty(_) => Statement::Empty,
|
|
343
369
|
Stmt::Decl(_) => Statement::Expression(self.unsupported("a declaration")),
|
|
344
|
-
// A block shares its function's scope, so its function declarations bind at its start.
|
|
345
370
|
Stmt::Block(block) => Statement::Block(self.body(&block.stmts)),
|
|
346
371
|
Stmt::If(branch) => Statement::If(
|
|
347
372
|
self.expression(&branch.test),
|
|
@@ -406,13 +431,13 @@ impl PlanCompiler {
|
|
|
406
431
|
}
|
|
407
432
|
};
|
|
408
433
|
Statement::Try {
|
|
409
|
-
block: self.
|
|
434
|
+
block: self.body(&attempt.block.stmts),
|
|
410
435
|
parameter,
|
|
411
|
-
handler: handler.map(|handler| self.
|
|
436
|
+
handler: handler.map(|handler| self.body(&handler.body.stmts)),
|
|
412
437
|
finalizer: attempt
|
|
413
438
|
.finalizer
|
|
414
439
|
.as_ref()
|
|
415
|
-
.map(|body| self.
|
|
440
|
+
.map(|body| self.body(&body.stmts)),
|
|
416
441
|
}
|
|
417
442
|
}
|
|
418
443
|
Stmt::ForIn(_) | Stmt::ForOf(_) => {
|
|
@@ -665,12 +690,14 @@ impl PlanCompiler {
|
|
|
665
690
|
.collect(),
|
|
666
691
|
),
|
|
667
692
|
Expr::Fn(function) => {
|
|
668
|
-
self.rejected |= !self.closes_over_nothing(&function.function, false);
|
|
669
693
|
let name = function.ident.as_ref().map(|ident| &ident.sym);
|
|
670
694
|
Expression::Function(name, &function.function)
|
|
671
695
|
}
|
|
696
|
+
// Arrows inherit `arguments`, which slot plans keep out of scopes.
|
|
672
697
|
Expr::Arrow(arrow) => {
|
|
673
|
-
|
|
698
|
+
let mut names = Names::default();
|
|
699
|
+
arrow.visit_with(&mut names);
|
|
700
|
+
self.rejected |= names.0.contains(&atom!("arguments"));
|
|
674
701
|
Expression::Arrow(arrow)
|
|
675
702
|
}
|
|
676
703
|
expression => self.unsupported(match expression {
|