1use std::cell::Cell;
12use std::collections::HashMap;
13use std::rc::Rc;
14
15use hermes_support::location::SMLoc;
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum ParserPass {
21 PreParse,
23 LazyParse,
25 FullParse,
27}
28
29#[derive(Clone)]
33pub struct PreParsedFunctionInfo {
34 pub end: SMLoc,
36
37 pub strict_mode: bool,
39
40 pub directives: Vec<Vec<u8>>,
45
46 pub contains_arrow_functions: bool,
48
49 pub may_contain_arrow_functions_using_arguments: bool,
52}
53
54#[derive(Clone)]
57pub struct PreParsedBufferInfo {
58 pub function_info: HashMap<u32, PreParsedFunctionInfo>,
62}
63
64pub(super) struct SaveFunctionState {
74 is_arrow: Rc<Cell<bool>>,
75 contains: Rc<Cell<bool>>,
76 may_contain: Rc<Cell<bool>>,
77 old_is_arrow: bool,
78 old_contains: bool,
79 old_may_contain: bool,
80}
81
82impl Drop for SaveFunctionState {
83 fn drop(&mut self) {
84 if !self.is_arrow.get() {
86 self.contains.set(self.old_contains);
87 self.may_contain.set(self.old_may_contain);
88 }
89 self.is_arrow.set(self.old_is_arrow);
90 }
91}
92
93use hermes_ast::node::{Node, NodeKind};
94
95use crate::lexer::{GrammarContext, JSLexer};
96
97use super::flow::{AllowTypedArrowFunction, CoverTypedParameters};
98use super::{JSParserImpl, PARAM_IN, PARAM_RETURN};
99
100impl<'gc, 'ast, 'ctx, 'a> JSParserImpl<'gc, 'ast, 'ctx, 'a> {
101 pub fn pre_parse_buffer(
109 gc: &'gc hermes_ast::context::GCLock<'ast, 'ctx>,
110 lexer: JSLexer<'a>,
111 strict: bool,
112 ) -> Option<JSParserImpl<'gc, 'ast, 'ctx, 'a>> {
113 let mut p = JSParserImpl::new_with_pass(gc, lexer, ParserPass::PreParse);
114 p.lexer.set_strict_mode(strict);
115 #[allow(unsafe_code)] let scope = unsafe { gc.alloc_scope() };
119 let ok = p.parse().is_some();
120 drop(scope);
121 if !ok {
122 return None;
123 }
124 Some(p)
125 }
126
127 pub(super) fn save_function_state(&self, is_arrow: bool) -> SaveFunctionState {
132 let g = SaveFunctionState {
133 is_arrow: Rc::clone(&self.is_arrow_function),
134 contains: Rc::clone(&self.contains_arrow_functions),
135 may_contain: Rc::clone(
136 &self.may_contain_arrow_functions_using_arguments,
137 ),
138 old_is_arrow: self.is_arrow_function.get(),
139 old_contains: self.contains_arrow_functions.get(),
140 old_may_contain: self
141 .may_contain_arrow_functions_using_arguments
142 .get(),
143 };
144 self.is_arrow_function.set(is_arrow);
146 if is_arrow {
147 self.contains_arrow_functions.set(true);
148 } else {
149 self.contains_arrow_functions.set(false);
150 self.may_contain_arrow_functions_using_arguments.set(false);
151 }
152 g
153 }
154
155 #[allow(dead_code)]
158 pub(super) fn copy_seen_directives(&self) -> Vec<Vec<u8>> {
159 self.seen_directives.clone()
160 }
161
162 pub(super) fn seek(&mut self, loc: SMLoc) {
168 self.lexer.seek(loc);
169 self.advance(GrammarContext::AllowRegExp);
170 }
171
172 pub fn parse_lazy_function(
185 &mut self,
186 kind: NodeKind,
187 param_yield: bool,
188 param_await: bool,
189 start: SMLoc,
190 ) -> Option<&'gc Node<'gc>> {
191 self.seek(start);
197 self.param_yield.set(param_yield);
198 self.param_await.set(param_await);
199
200 match kind {
201 NodeKind::FunctionExpression => {
203 self.parse_function_expression(true)
204 }
205
206 NodeKind::FunctionDeclaration => {
208 self.parse_function_declaration(
209 PARAM_RETURN,
210 true,
211 )
212 }
213
214 NodeKind::ArrowFunctionExpression => self.parse_assignment_expression(
217 PARAM_IN,
218 true,
219 AllowTypedArrowFunction::Yes,
220 CoverTypedParameters::Yes,
221 None,
222 ),
223
224 NodeKind::Property => {
228 let node = self.parse_property_assignment(true)?;
229 match node {
230 Node::Property(prop) => Some(prop.value),
231 _ => {
232 debug_assert!(
233 false,
234 "Expected a getter/setter function"
235 );
236 None
237 }
238 }
239 }
240
241 NodeKind::MethodDefinition => {
246 let mut body: Vec<&'gc Node<'gc>> = Vec::new();
247 let mut constructor: Option<&'gc Node<'gc>> = None;
248 let success = self.parse_class_body_impl(
249 &mut body,
250 &mut constructor,
251 true,
252 );
253 if !success || body.len() != 1 {
254 debug_assert!(false, "Unexpected parse_class_body_impl result");
255 None
256 } else {
257 match body[0] {
258 Node::MethodDefinition(method) => Some(method.value),
259 _ => {
260 debug_assert!(false, "Expected MethodDefinitionNode");
261 None
262 }
263 }
264 }
265 }
266
267 _ => unreachable!("Asked to parse unexpected node type"),
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 #[test]
277 fn parser_pass_defaults_and_override() {
278 use hermes_ast::context::Context;
279 use hermes_support::manager::SourceErrorManager;
280 use crate::lexer::{GrammarContext, JSLexer};
281 use crate::js::{JSParserImpl, ParserPass};
282
283 let mut sm = SourceErrorManager::new();
284 let id = sm.add_buffer_bytes("t", b"1;");
285 let mut ctx = Context::new();
286 let gc = ctx.lock();
287 let atoms = &gc.ctx().atom_table;
288 let lexer = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
289 let p = JSParserImpl::new_with_pass(&gc, lexer, ParserPass::PreParse);
290 assert_eq!(p.pass, ParserPass::PreParse);
291 }
292
293 #[test]
295 fn pre_parsed_table_and_threshold() {
296 use hermes_ast::context::Context;
297 let mut ctx = Context::new();
298 assert_eq!(ctx.preemptive_function_compilation_threshold(), 0);
299 ctx.set_preemptive_function_compilation_threshold(64);
300 assert_eq!(ctx.preemptive_function_compilation_threshold(), 64);
301 }
302
303 #[test]
307 fn save_function_state_restores_on_drop() {
308 use hermes_ast::context::Context;
309 use hermes_support::manager::SourceErrorManager;
310 use crate::lexer::{GrammarContext, JSLexer};
311 use crate::js::{JSParserImpl, ParserPass};
312
313 let mut sm = SourceErrorManager::new();
314 let id = sm.add_buffer_bytes("t", b"0");
315 let mut ctx = Context::new();
316 let gc = ctx.lock();
317 let atoms = &gc.ctx().atom_table;
318 let lexer = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
319 let mut p = JSParserImpl::new_with_pass(&gc, lexer, ParserPass::PreParse);
320
321 p.lexer.set_strict_mode(false);
322 p.contains_arrow_functions.set(false);
323 {
324 let _g = p.save_function_state(false);
326 p.contains_arrow_functions.set(true);
329 }
330 assert!(!p.contains_arrow_functions.get(), "contains_arrow restored");
333 assert!(!p.lexer.is_strict_mode(), "strict was never changed by guard");
335 }
336
337 #[test]
340 fn preparse_records_functions() {
341 use hermes_ast::context::Context;
342 use hermes_support::manager::SourceErrorManager;
343 use crate::lexer::{GrammarContext, JSLexer};
344 use crate::js::{JSParserImpl, ParserPass};
345
346 let src = b"function a(){ 'use strict'; return 1; }\nvar b = () => 2;\n";
347 let mut sm = SourceErrorManager::new();
348 let id = sm.add_buffer_bytes("t", src);
349 let mut ctx = Context::new();
350 let gc = ctx.lock();
351 let atoms = &gc.ctx().atom_table;
352 let lexer = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
353 let mut p = JSParserImpl::new_with_pass(&gc, lexer, ParserPass::PreParse);
354 assert!(p.parse().is_some());
355 let t = p.take_pre_parsed();
356 assert_eq!(t.function_info.len(), 2);
358 let strict_count =
360 t.function_info.values().filter(|i| i.strict_mode).count();
361 assert_eq!(strict_count, 1);
362 let with_dir = t
363 .function_info
364 .values()
365 .filter(|i| !i.directives.is_empty())
366 .count();
367 assert_eq!(with_dir, 1);
368 }
369
370 fn has_lazy_stub<'gc>(node: &'gc hermes_ast::node::Node<'gc>) -> bool {
373 use hermes_ast::node::Node;
374 use hermes_ast::visitor::Visitor;
375
376 struct LazyFinder(bool);
377 impl<'gc> Visitor<'gc> for LazyFinder {
378 fn visit_node(&mut self, node: &'gc Node<'gc>) {
379 if self.0 {
380 return;
381 }
382 if let Node::BlockStatement(b) = node {
383 if b.is_lazy_function_body.get() {
384 self.0 = true;
385 return;
386 }
387 }
388 node.visit_children(self);
389 }
390 }
391
392 let mut finder = LazyFinder(false);
393 finder.visit_node(node);
394 finder.0
395 }
396
397 fn find_function_decl<'gc>(
399 node: &'gc hermes_ast::node::Node<'gc>,
400 ) -> Option<&'gc hermes_ast::node::Node<'gc>> {
401 use hermes_ast::node::Node;
402 use hermes_ast::visitor::Visitor;
403
404 struct FnFinder<'gc>(Option<&'gc Node<'gc>>);
405 impl<'gc> Visitor<'gc> for FnFinder<'gc> {
406 fn visit_node(&mut self, node: &'gc Node<'gc>) {
407 if self.0.is_some() {
408 return;
409 }
410 if let Node::FunctionDeclaration(_) = node {
411 self.0 = Some(node);
412 return;
413 }
414 node.visit_children(self);
415 }
416 }
417
418 let mut finder = FnFinder(None);
419 finder.visit_node(node);
420 finder.0
421 }
422
423 #[test]
428 fn parse_lazy_function_reparses_body() {
429 use hermes_ast::context::Context;
430 use hermes_ast::node::{Node, NodeKind};
431 use hermes_support::manager::SourceErrorManager;
432 use crate::lexer::{GrammarContext, JSLexer};
433 use crate::js::{JSParserImpl, ParserPass};
434
435 let src = b"function a(){ return 1 + 2; }\n";
436 let mut sm = SourceErrorManager::new();
437 let id = sm.add_buffer_bytes("t", src);
438 let mut ctx = Context::new();
439 ctx.set_preemptive_function_compilation_threshold(0); let gc = ctx.lock();
441 let atoms = &gc.ctx().atom_table;
442 let table = {
444 let l = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
445 let mut pp =
446 JSParserImpl::new_with_pass(&gc, l, ParserPass::PreParse);
447 pp.parse().unwrap();
448 pp.take_pre_parsed()
449 };
450 let l = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
452 let mut lp =
453 JSParserImpl::new_with_pass(&gc, l, ParserPass::LazyParse);
454 lp.set_pre_parsed(table);
455 let prog = lp.parse().unwrap();
456 assert!(has_lazy_stub(prog), "skeleton body should be a lazy stub");
457
458 let func = find_function_decl(prog).expect("FunctionDeclaration");
460 let start = func.range().start;
461
462 let body = lp
464 .parse_lazy_function(NodeKind::FunctionDeclaration, false, false, start)
465 .expect("parse_lazy_function should succeed");
466
467 let Node::FunctionDeclaration(fd) = body else {
470 panic!("expected a FunctionDeclaration node");
471 };
472 let Node::BlockStatement(block) = fd.body else {
473 panic!("expected a BlockStatement body");
474 };
475 assert!(
476 !block.is_lazy_function_body.get(),
477 "re-parsed body must NOT be a lazy stub"
478 );
479 assert!(
480 !block.body.is_empty(),
481 "re-parsed body must contain statements"
482 );
483 assert!(!has_lazy_stub(body), "re-parsed function has no lazy stub");
485 }
486
487 #[test]
493 fn preparse_reclaims_function_bodies() {
494 use hermes_ast::context::Context;
495 use hermes_support::manager::SourceErrorManager;
496 use crate::lexer::{GrammarContext, JSLexer};
497 use crate::js::{JSParserImpl, ParserPass};
498
499 let mut src: Vec<u8> = Vec::new();
501 for f in 0..50 {
502 src.extend_from_slice(format!("function f{f}(a, b) {{\n").as_bytes());
503 for i in 0..20 {
504 src.extend_from_slice(
505 format!(" var x{i} = a + b * {i};\n").as_bytes(),
506 );
507 }
508 src.extend_from_slice(b" return a;\n}\n");
509 }
510
511 let count_nodes = |pass: ParserPass| -> usize {
512 let mut sm = SourceErrorManager::new();
513 let id = sm.add_buffer_bytes("t", &src);
514 let mut ctx = Context::new();
515 let gc = ctx.lock();
516 let atoms = &gc.ctx().atom_table;
517 let lexer =
518 JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
519 let mut p = JSParserImpl::new_with_pass(&gc, lexer, pass);
520 assert!(p.parse().is_some(), "parse failed");
521 gc.ctx().num_nodes()
522 };
523
524 let eager = count_nodes(ParserPass::FullParse);
525 let pre = count_nodes(ParserPass::PreParse);
526 assert!(
529 pre * 5 < eager,
530 "PreParse retained O(file) AST: pre={pre} eager={eager}"
531 );
532 }
533
534 #[test]
537 fn lazyparse_defers_body() {
538 use hermes_ast::context::Context;
539 use hermes_support::manager::SourceErrorManager;
540 use crate::lexer::{GrammarContext, JSLexer};
541 use crate::js::{JSParserImpl, ParserPass};
542
543 let src = b"function a(){ return 1 + 2; }\n";
544 let mut sm = SourceErrorManager::new();
545 let id = sm.add_buffer_bytes("t", src);
546 let mut ctx = Context::new();
547 ctx.set_preemptive_function_compilation_threshold(0); let gc = ctx.lock();
549 let atoms = &gc.ctx().atom_table;
550 let table = {
552 let l = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
553 let mut pp =
554 JSParserImpl::new_with_pass(&gc, l, ParserPass::PreParse);
555 pp.parse().unwrap();
556 pp.take_pre_parsed()
557 };
558 let l = JSLexer::new(id, &mut sm, atoms, GrammarContext::AllowRegExp);
559 let mut lp =
560 JSParserImpl::new_with_pass(&gc, l, ParserPass::LazyParse);
561 lp.set_pre_parsed(table);
562 let prog = lp.parse().unwrap();
563 assert!(
565 has_lazy_stub(prog),
566 "expected a lazy function body stub"
567 );
568 }
569}
570