LibJS: Validate regex literals during parsing
Now that LibRegex is safe to use (for parsing) off the main thread, we can validate regex literals directly while parsing JavaScript. This allows us to remove the deferred regex compilation pass that we previously ran on the main thread after parsing JS in the background.
This commit is contained in:
parent
d7bf9d3898
commit
c8a0a960b5
4 changed files with 9 additions and 123 deletions
|
|
@ -113,7 +113,6 @@ pub struct ParsedProgram {
|
|||
has_top_level_await: bool,
|
||||
errors: Vec<ParseError>,
|
||||
ast_dump: Option<Vec<u8>>,
|
||||
deferred_regexes: Vec<parser::DeferredRegex>,
|
||||
}
|
||||
|
||||
// SAFETY: Full ownership transfer between threads, never concurrent access.
|
||||
|
|
@ -358,12 +357,6 @@ pub unsafe extern "C" fn rust_compile_program(
|
|||
|
||||
let program = parser.parse_program(starts_in_strict_mode);
|
||||
|
||||
// Compile deferred regex literals.
|
||||
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
|
||||
if !regex_errors.is_empty() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
if check_errors(&mut parser) {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
|
@ -464,8 +457,6 @@ pub unsafe extern "C" fn rust_parse_program(
|
|||
(scope, false, false)
|
||||
};
|
||||
|
||||
let deferred_regexes = parser.take_deferred_regexes();
|
||||
|
||||
let parsed = ParsedProgram {
|
||||
program,
|
||||
function_table: std::mem::take(&mut parser.function_table),
|
||||
|
|
@ -474,7 +465,6 @@ pub unsafe extern "C" fn rust_parse_program(
|
|||
has_top_level_await: has_tla,
|
||||
errors,
|
||||
ast_dump: None,
|
||||
deferred_regexes,
|
||||
};
|
||||
|
||||
Box::into_raw(Box::new(parsed))
|
||||
|
|
@ -514,25 +504,6 @@ pub unsafe extern "C" fn rust_parsed_program_take_errors(
|
|||
}
|
||||
}
|
||||
|
||||
/// Compile deferred regex literals in a ParsedProgram.
|
||||
///
|
||||
/// Must be called on the main thread (LibRegex is not thread-safe).
|
||||
/// Any regex compilation errors are added to the ParsedProgram's error list,
|
||||
/// so the caller should check `rust_parsed_program_has_errors()` afterwards.
|
||||
///
|
||||
/// # Safety
|
||||
/// `parsed` must be a valid pointer from `rust_parse_program()`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_parsed_program_compile_regexes(parsed: *mut ParsedProgram) {
|
||||
unsafe {
|
||||
let parsed = &mut *parsed;
|
||||
let deferred = std::mem::take(&mut parsed.deferred_regexes);
|
||||
parsed
|
||||
.errors
|
||||
.extend(Parser::compile_deferred_regexes(deferred));
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a ParsedProgram without compiling it.
|
||||
///
|
||||
/// # Safety
|
||||
|
|
@ -667,18 +638,6 @@ pub unsafe extern "C" fn rust_compile_eval(
|
|||
|
||||
let program = parser.parse_program(starts_in_strict_mode);
|
||||
|
||||
// Compile deferred regex literals.
|
||||
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
|
||||
if !regex_errors.is_empty() {
|
||||
if let Some(cb) = error_callback {
|
||||
for err in ®ex_errors {
|
||||
let msg = err.message.as_bytes();
|
||||
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
if check_errors_with_callback(&mut parser, error_context, error_callback) {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
|
@ -864,18 +823,6 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
|
|||
let mut parser = Parser::new(full_slice, ProgramType::Script);
|
||||
let program = parser.parse_program(false);
|
||||
|
||||
// Compile deferred regex literals.
|
||||
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
|
||||
if !regex_errors.is_empty() {
|
||||
if let Some(cb) = error_callback {
|
||||
for err in ®ex_errors {
|
||||
let msg = err.message.as_bytes();
|
||||
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
|
||||
}
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
if check_errors_with_callback(&mut parser, error_context, error_callback) {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
|
@ -988,16 +935,6 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
|
|||
let mut parser = Parser::new(source_slice, ProgramType::Script);
|
||||
let program = parser.parse_program(true); // strict mode
|
||||
|
||||
// Compile deferred regex literals.
|
||||
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
|
||||
if !regex_errors.is_empty() {
|
||||
let errors: Vec<String> = regex_errors
|
||||
.iter()
|
||||
.map(|e| format!("{}:{}: {}", e.line, e.column, e.message))
|
||||
.collect();
|
||||
panic!("Regex errors in builtin file: {}", errors.join("; "));
|
||||
}
|
||||
|
||||
if parser.has_errors() {
|
||||
let errors: Vec<String> = parser
|
||||
.errors()
|
||||
|
|
@ -1313,9 +1250,6 @@ pub unsafe extern "C" fn rust_compile_module(
|
|||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
// Compile deferred regex literals before checking for errors.
|
||||
rust_parsed_program_compile_regexes(parsed);
|
||||
|
||||
if rust_parsed_program_has_errors(parsed) {
|
||||
if let Some(cb) = error_callback {
|
||||
rust_parsed_program_take_errors(parsed, error_context, Some(cb));
|
||||
|
|
|
|||
|
|
@ -37,9 +37,8 @@ use std::collections::{HashMap, HashSet};
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::ast::{
|
||||
BindingPattern, CompiledRegex, Expression, ExpressionKind, FunctionParameter, FunctionTable,
|
||||
Identifier, PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind,
|
||||
Utf16String,
|
||||
BindingPattern, Expression, ExpressionKind, FunctionParameter, FunctionTable, Identifier,
|
||||
PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind, Utf16String,
|
||||
};
|
||||
use crate::lexer::{Lexer, ch};
|
||||
use crate::scope_collector::{ScopeCollector, ScopeCollectorState};
|
||||
|
|
@ -207,22 +206,12 @@ pub(crate) struct ParserFlags {
|
|||
pub in_property_key_context: bool,
|
||||
}
|
||||
|
||||
/// A regex literal whose compilation is deferred until after parsing.
|
||||
pub struct DeferredRegex {
|
||||
pub compiled_regex: Rc<CompiledRegex>,
|
||||
pub pattern: Vec<u16>,
|
||||
pub flags: Vec<u16>,
|
||||
pub line: u32,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
/// Snapshot of parser state for speculative parsing (backtracking).
|
||||
struct SavedState {
|
||||
token: Token,
|
||||
errors_len: usize,
|
||||
flags: ParserFlags,
|
||||
scope_collector_state: ScopeCollectorState,
|
||||
deferred_regexes_len: usize,
|
||||
}
|
||||
|
||||
/// The main JavaScript parser.
|
||||
|
|
@ -310,9 +299,6 @@ pub struct Parser<'a> {
|
|||
/// `(a=(b=(c=0)))` where each failed arrow attempt would otherwise
|
||||
/// re-attempt inner positions during grouping expression re-parse.
|
||||
arrow_function_failed_positions: HashSet<usize>,
|
||||
|
||||
/// Regex literals whose compilation is deferred until after parsing.
|
||||
deferred_regexes: Vec<DeferredRegex>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
|
|
@ -361,7 +347,6 @@ impl<'a> Parser<'a> {
|
|||
exported_names: HashSet::new(),
|
||||
function_table: FunctionTable::new(),
|
||||
arrow_function_failed_positions: HashSet::new(),
|
||||
deferred_regexes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,30 +670,6 @@ impl<'a> Parser<'a> {
|
|||
self.syntax_error(&msg);
|
||||
}
|
||||
|
||||
/// Take the deferred regex literals collected during parsing.
|
||||
/// The caller is responsible for compiling them (on the main thread).
|
||||
pub(crate) fn take_deferred_regexes(&mut self) -> Vec<DeferredRegex> {
|
||||
std::mem::take(&mut self.deferred_regexes)
|
||||
}
|
||||
|
||||
/// Batch-compile deferred regex literals. On error, returns the errors.
|
||||
pub(crate) fn compile_deferred_regexes(deferred: Vec<DeferredRegex>) -> Vec<ParseError> {
|
||||
let mut errors = Vec::new();
|
||||
for d in deferred {
|
||||
match crate::bytecode::ffi::compile_regex(&d.pattern, &d.flags) {
|
||||
Ok(handle) => d.compiled_regex.set(handle),
|
||||
Err(msg) => {
|
||||
errors.push(ParseError {
|
||||
message: msg,
|
||||
line: d.line,
|
||||
column: d.column,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
||||
pub(crate) fn validate_regex_flags(&mut self, flags: &[u16]) {
|
||||
let valid_flags: &[u16] = &[
|
||||
ch(b'd'),
|
||||
|
|
@ -768,7 +729,6 @@ impl<'a> Parser<'a> {
|
|||
errors_len: self.errors.len(),
|
||||
flags: self.flags,
|
||||
scope_collector_state: self.scope_collector.save_state(),
|
||||
deferred_regexes_len: self.deferred_regexes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -776,7 +736,6 @@ impl<'a> Parser<'a> {
|
|||
let state = self.saved_states.pop().expect("No saved state to restore");
|
||||
self.current_token = state.token;
|
||||
self.errors.truncate(state.errors_len);
|
||||
self.deferred_regexes.truncate(state.deferred_regexes_len);
|
||||
self.flags = state.flags;
|
||||
self.scope_collector.load_state(state.scope_collector_state);
|
||||
self.lexer.load_state();
|
||||
|
|
|
|||
|
|
@ -690,14 +690,13 @@ impl Parser<'_> {
|
|||
Vec::new()
|
||||
};
|
||||
self.validate_regex_flags(&flags);
|
||||
let compiled_regex = Rc::new(CompiledRegex::new(std::ptr::null_mut()));
|
||||
self.deferred_regexes.push(super::DeferredRegex {
|
||||
compiled_regex: compiled_regex.clone(),
|
||||
pattern: pattern.clone(),
|
||||
flags: flags.clone(),
|
||||
line: start.line,
|
||||
column: start.column,
|
||||
});
|
||||
let compiled_regex = match crate::bytecode::ffi::compile_regex(&pattern, &flags) {
|
||||
Ok(handle) => Rc::new(CompiledRegex::new(handle)),
|
||||
Err(msg) => {
|
||||
self.syntax_error_at_position(&msg, start);
|
||||
Rc::new(CompiledRegex::new(std::ptr::null_mut()))
|
||||
}
|
||||
};
|
||||
self.expression(
|
||||
start,
|
||||
ExpressionKind::RegExpLiteral(RegExpLiteralData {
|
||||
|
|
|
|||
|
|
@ -366,9 +366,6 @@ Optional<Result<ScriptResult, Vector<ParserError>>> compile_parsed_script(Parsed
|
|||
if (!parsed)
|
||||
return {};
|
||||
|
||||
// Compile deferred regex literals (must happen on the main thread).
|
||||
rust_parsed_program_compile_regexes(parsed);
|
||||
|
||||
if (rust_parsed_program_has_errors(parsed)) {
|
||||
Vector<ParserError> parse_errors;
|
||||
rust_parsed_program_take_errors(parsed, &parse_errors, collect_parse_errors);
|
||||
|
|
@ -470,9 +467,6 @@ Optional<Result<ModuleResult, Vector<ParserError>>> compile_parsed_module(Parsed
|
|||
if (!parsed)
|
||||
return {};
|
||||
|
||||
// Compile deferred regex literals (must happen on the main thread).
|
||||
rust_parsed_program_compile_regexes(parsed);
|
||||
|
||||
if (rust_parsed_program_has_errors(parsed)) {
|
||||
Vector<ParserError> parse_errors;
|
||||
rust_parsed_program_take_errors(parsed, &parse_errors, collect_parse_errors);
|
||||
|
|
|
|||
Loading…
Reference in a new issue