Skip to content

Commit c94e10d

Browse files
authored
Change HostEnsureCanCompileStrings to the new spec (#3690)
* Change `HostEnsureCanCompileStrings` to the new spec * Fix test
1 parent 6559683 commit c94e10d

File tree

5 files changed

+129
-94
lines changed

5 files changed

+129
-94
lines changed

core/engine/src/builtins/eval/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ impl Eval {
108108
// 3. Let evalRealm be the current Realm Record.
109109
// 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval
110110
// and of the eval function itself.
111-
// 5. Perform ? HostEnsureCanCompileStrings(evalRealm).
111+
let eval_realm = context.realm().clone();
112+
113+
// 5. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct).
112114
context
113115
.host_hooks()
114-
.ensure_can_compile_strings(context.realm().clone(), context)?;
116+
.ensure_can_compile_strings(eval_realm, &[], x, direct, context)?;
115117

116118
// 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
117119
// a. Let script be ParseText(StringToCodePoints(x), Script).

core/engine/src/builtins/function/mod.rs

Lines changed: 106 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
4545
use boa_interner::Sym;
4646
use boa_parser::{Parser, Source};
4747
use boa_profiler::Profiler;
48-
use std::io::Read;
4948
use thin_vec::ThinVec;
5049

5150
use super::Proxy;
@@ -390,46 +389,40 @@ impl BuiltInFunctionObject {
390389
generator: bool,
391390
context: &mut Context,
392391
) -> JsResult<JsObject> {
393-
// 1. Let currentRealm be the current Realm Record.
394-
// 2. Perform ? HostEnsureCanCompileStrings(currentRealm).
395-
context
396-
.host_hooks()
397-
.ensure_can_compile_strings(context.realm().clone(), context)?;
398-
399-
// 3. If newTarget is undefined, set newTarget to constructor.
392+
// 1. If newTarget is undefined, set newTarget to constructor.
400393
let new_target = if new_target.is_undefined() {
401394
constructor.into()
402395
} else {
403396
new_target.clone()
404397
};
405398

406399
let default = if r#async && generator {
407-
// 7. Else,
408-
// a. Assert: kind is asyncGenerator.
400+
// 5. Else,
401+
// a. Assert: kind is async-generator.
409402
// b. Let prefix be "async function*".
410403
// c. Let exprSym be the grammar symbol AsyncGeneratorExpression.
411404
// d. Let bodySym be the grammar symbol AsyncGeneratorBody.
412405
// e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await].
413406
// f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
414407
StandardConstructors::async_generator_function
415408
} else if r#async {
416-
// 6. Else if kind is async, then
409+
// 4. Else if kind is async, then
417410
// a. Let prefix be "async function".
418411
// b. Let exprSym be the grammar symbol AsyncFunctionExpression.
419412
// c. Let bodySym be the grammar symbol AsyncFunctionBody.
420413
// d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await].
421414
// e. Let fallbackProto be "%AsyncFunction.prototype%".
422415
StandardConstructors::async_function
423416
} else if generator {
424-
// 5. Else if kind is generator, then
417+
// 3. Else if kind is generator, then
425418
// a. Let prefix be "function*".
426419
// b. Let exprSym be the grammar symbol GeneratorExpression.
427420
// c. Let bodySym be the grammar symbol GeneratorBody.
428421
// d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await].
429422
// e. Let fallbackProto be "%GeneratorFunction.prototype%".
430423
StandardConstructors::generator_function
431424
} else {
432-
// 4. If kind is normal, then
425+
// 2. If kind is normal, then
433426
// a. Let prefix be "function".
434427
// b. Let exprSym be the grammar symbol FunctionExpression.
435428
// c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
@@ -441,82 +434,127 @@ impl BuiltInFunctionObject {
441434
// 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
442435
let prototype = get_prototype_from_constructor(&new_target, default, context)?;
443436

444-
let (parameters, body) = if let Some((body_arg, args)) = args.split_last() {
445-
let parameters = if args.is_empty() {
446-
FormalParameterList::default()
447-
} else {
448-
let mut parameters = Vec::with_capacity(args.len());
449-
for arg in args {
450-
parameters.push(arg.to_string(context)?);
451-
}
452-
let parameters = parameters.join(utf16!(","));
453-
454-
// TODO: make parser generic to u32 iterators
455-
let parameters = String::from_utf16_lossy(&parameters);
456-
let mut parser = Parser::new(Source::from_bytes(&parameters));
457-
parser.set_identifier(context.next_parser_identifier());
458-
459-
let parameters = match parser.parse_formal_parameters(
460-
context.interner_mut(),
461-
generator,
462-
r#async,
463-
) {
464-
Ok(parameters) => parameters,
465-
Err(e) => {
466-
return Err(JsNativeError::syntax()
467-
.with_message(format!("failed to parse function parameters: {e}"))
468-
.into())
469-
}
470-
};
437+
// 6. Let argCount be the number of elements in parameterArgs.
438+
let (body, param_list) = if let Some((body, params)) = args.split_last() {
439+
// 7. Let bodyString be ? ToString(bodyArg).
440+
let body = body.to_string(context)?;
441+
// 8. Let parameterStrings be a new empty List.
442+
let mut parameters = Vec::with_capacity(args.len());
443+
// 9. For each element arg of parameterArgs, do
444+
for param in params {
445+
// a. Append ? ToString(arg) to parameterStrings.
446+
parameters.push(param.to_string(context)?);
447+
}
448+
(body, parameters)
449+
} else {
450+
(js_string!(), Vec::new())
451+
};
452+
let current_realm = context.realm().clone();
471453

472-
if generator && contains(&parameters, ContainsSymbol::YieldExpression) {
473-
return Err(JsNativeError::syntax().with_message(
474-
"yield expression is not allowed in formal parameter list of generator function",
475-
).into());
476-
}
454+
context.host_hooks().ensure_can_compile_strings(
455+
current_realm,
456+
&param_list,
457+
&body,
458+
false,
459+
context,
460+
)?;
477461

478-
parameters
479-
};
462+
let parameters = if param_list.is_empty() {
463+
FormalParameterList::default()
464+
} else {
465+
// 12. Let P be the empty String.
466+
// 13. If argCount > 0, then
467+
// a. Set P to parameterStrings[0].
468+
// b. Let k be 1.
469+
// c. Repeat, while k < argCount,
470+
// i. Let nextArgString be parameterStrings[k].
471+
// ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
472+
// iii. Set k to k + 1.
473+
let parameters = param_list.join(utf16!(","));
474+
let mut parser = Parser::new(Source::from_utf16(&parameters));
475+
parser.set_identifier(context.next_parser_identifier());
476+
477+
// 17. Let parameters be ParseText(StringToCodePoints(P), parameterSym).
478+
// 18. If parameters is a List of errors, throw a SyntaxError exception.
479+
let parameters = parser
480+
.parse_formal_parameters(context.interner_mut(), generator, r#async)
481+
.map_err(|e| {
482+
JsNativeError::syntax()
483+
.with_message(format!("failed to parse function parameters: {e}"))
484+
})?;
480485

481486
// It is a Syntax Error if FormalParameters Contains YieldExpression is true.
482-
if generator && r#async && contains(&parameters, ContainsSymbol::YieldExpression) {
483-
return Err(JsNativeError::syntax()
484-
.with_message("yield expression not allowed in async generator parameters")
485-
.into());
487+
if generator && contains(&parameters, ContainsSymbol::YieldExpression) {
488+
return Err(JsNativeError::syntax().with_message(
489+
if r#async {
490+
"yield expression is not allowed in formal parameter list of async generator"
491+
} else {
492+
"yield expression is not allowed in formal parameter list of generator"
493+
}
494+
).into());
486495
}
487496

488497
// It is a Syntax Error if FormalParameters Contains AwaitExpression is true.
489-
if generator && r#async && contains(&parameters, ContainsSymbol::AwaitExpression) {
498+
if r#async && contains(&parameters, ContainsSymbol::AwaitExpression) {
490499
return Err(JsNativeError::syntax()
491-
.with_message("await expression not allowed in async generator parameters")
500+
.with_message(
501+
if generator {
502+
"await expression is not allowed in formal parameter list of async function"
503+
} else {
504+
"await expression is not allowed in formal parameter list of async generator"
505+
})
492506
.into());
493507
}
494508

495-
// 11. Let bodyString be the string-concatenation of 0x000A (LINE FEED), ? ToString(bodyArg), and 0x000A (LINE FEED).
496-
let body_arg = body_arg.to_string(context)?.to_std_string_escaped();
497-
let body = b"\n".chain(body_arg.as_bytes()).chain(b"\n".as_slice());
509+
parameters
510+
};
498511

499-
// TODO: make parser generic to u32 iterators
500-
let mut parser = Parser::new(Source::from_reader(body, None));
512+
let body = if body.is_empty() {
513+
FunctionBody::default()
514+
} else {
515+
// 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
516+
let mut body_parse = Vec::with_capacity(body.len());
517+
body_parse.push(u16::from(b'\n'));
518+
body_parse.extend_from_slice(&body);
519+
body_parse.push(u16::from(b'\n'));
520+
521+
// 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
522+
// 20. If body is a List of errors, throw a SyntaxError exception.
523+
let mut parser = Parser::new(Source::from_utf16(&body_parse));
501524
parser.set_identifier(context.next_parser_identifier());
502525

503-
let body = match parser.parse_function_body(context.interner_mut(), generator, r#async)
504-
{
505-
Ok(statement_list) => statement_list,
506-
Err(e) => {
507-
return Err(JsNativeError::syntax()
526+
// 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
527+
// 20. If body is a List of errors, throw a SyntaxError exception.
528+
let body = parser
529+
.parse_function_body(context.interner_mut(), generator, r#async)
530+
.map_err(|e| {
531+
JsNativeError::syntax()
508532
.with_message(format!("failed to parse function body: {e}"))
509-
.into())
510-
}
511-
};
533+
})?;
534+
535+
// It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with argument « »
536+
// is false unless the source text containing ScriptBody is eval code that is being
537+
// processed by a direct eval.
538+
// https://tc39.es/ecma262/#sec-scripts-static-semantics-early-errors
539+
if !all_private_identifiers_valid(&body, Vec::new()) {
540+
return Err(JsNativeError::syntax()
541+
.with_message("invalid private identifier usage")
542+
.into());
543+
}
544+
545+
// 21. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") does not evaluate to a function.
546+
// 22. NOTE: If this step is reached, sourceText must have the syntax of exprSym (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to exprSym directly.
547+
// 23. Let expr be ParseText(sourceText, exprSym).
548+
// 24. If expr is a List of errors, throw a SyntaxError exception.
549+
// Check for errors that apply for the combination of body and parameters.
512550

513551
// Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
514552
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
515553
if body.strict() {
516554
for name in bound_names(&parameters) {
517555
if name == Sym::ARGUMENTS || name == Sym::EVAL {
518556
return Err(JsNativeError::syntax()
519-
.with_message(" Unexpected 'eval' or 'arguments' in strict mode")
557+
.with_message("Unexpected 'eval' or 'arguments' in strict mode")
520558
.into());
521559
}
522560
}
@@ -571,21 +609,7 @@ impl BuiltInFunctionObject {
571609
}
572610
}
573611

574-
if !all_private_identifiers_valid(&parameters, Vec::new()) {
575-
return Err(JsNativeError::syntax()
576-
.with_message("invalid private identifier usage")
577-
.into());
578-
}
579-
580-
if !all_private_identifiers_valid(&body, Vec::new()) {
581-
return Err(JsNativeError::syntax()
582-
.with_message("invalid private identifier usage")
583-
.into());
584-
}
585-
586-
(parameters, body)
587-
} else {
588-
(FormalParameterList::default(), FunctionBody::default())
612+
body
589613
};
590614

591615
let code = FunctionCompiler::new()

core/engine/src/context/hooks.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
job::JobCallback,
55
object::{JsFunction, JsObject},
66
realm::Realm,
7-
Context, JsResult, JsValue,
7+
Context, JsResult, JsString, JsValue,
88
};
99
use time::{OffsetDateTime, UtcOffset};
1010

@@ -25,7 +25,7 @@ use time::util::local_offset;
2525
/// use boa_engine::{
2626
/// context::{Context, ContextBuilder, HostHooks},
2727
/// realm::Realm,
28-
/// JsNativeError, JsResult, Source,
28+
/// JsNativeError, JsResult, JsString, Source,
2929
/// };
3030
///
3131
/// struct Hooks;
@@ -34,7 +34,10 @@ use time::util::local_offset;
3434
/// fn ensure_can_compile_strings(
3535
/// &self,
3636
/// _realm: Realm,
37-
/// context: &mut Context,
37+
/// _parameters: &[JsString],
38+
/// _body: &JsString,
39+
/// _direct: bool,
40+
/// _context: &mut Context,
3841
/// ) -> JsResult<()> {
3942
/// Err(JsNativeError::typ()
4043
/// .with_message("eval calls not available")
@@ -106,16 +109,22 @@ pub trait HostHooks {
106109
// The default implementation of HostPromiseRejectionTracker is to return unused.
107110
}
108111

109-
/// [`HostEnsureCanCompileStrings ( calleeRealm )`][spec]
112+
/// [`HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, direct )`][spec]
110113
///
111114
/// # Requirements
112115
///
113116
/// - If the returned Completion Record is a normal completion, it must be a normal completion
114117
/// containing unused. This is already ensured by the return type.
115118
///
116119
/// [spec]: https://tc39.es/ecma262/#sec-hostensurecancompilestrings
117-
// TODO: Track https://github.com/tc39/ecma262/issues/938
118-
fn ensure_can_compile_strings(&self, _realm: Realm, _context: &mut Context) -> JsResult<()> {
120+
fn ensure_can_compile_strings(
121+
&self,
122+
_realm: Realm,
123+
_parameters: &[JsString],
124+
_body: &JsString,
125+
_direct: bool,
126+
_context: &mut Context,
127+
) -> JsResult<()> {
119128
// The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused).
120129
Ok(())
121130
}

core/engine/src/object/builtins/jsarraybuffer.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,10 @@ impl JsArrayBuffer {
230230
/// // Get a reference to the data.
231231
/// let internal_buffer = array_buffer.data();
232232
///
233-
/// assert_eq!(internal_buffer.as_deref(), Some((0..5).collect::<Vec<u8>>().as_slice()));
233+
/// assert_eq!(
234+
/// internal_buffer.as_deref(),
235+
/// Some((0..5).collect::<Vec<u8>>().as_slice())
236+
/// );
234237
/// # Ok(())
235238
/// # }
236239
/// ```

core/engine/src/vm/call_frame/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ pub struct CallFrame {
4040
pub(crate) code_block: Gc<CodeBlock>,
4141
pub(crate) pc: u32,
4242
/// The register pointer, points to the first register in the stack.
43-
///
4443
// TODO: Check if storing the frame pointer instead of argument count and computing the
4544
// argument count based on the pointers would be better for accessing the arguments
4645
// and the elements before the register pointer.
@@ -124,13 +123,11 @@ impl CallFrame {
124123
/// │ callee frame pointer
125124
/// │
126125
/// └───── caller frame pointer
127-
///
128126
/// ```
129127
///
130128
/// Some questions:
131129
///
132130
/// - Who is responsible for cleaning up the stack after a call? The rust caller.
133-
///
134131
pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
135132
pub(crate) const THIS_POSITION: u32 = 2;
136133
pub(crate) const FUNCTION_POSITION: u32 = 1;

0 commit comments

Comments
 (0)