Browse Source

Change `HostEnsureCanCompileStrings` to the new spec (#3690)

* Change `HostEnsureCanCompileStrings` to the new spec

* Fix test
real_conformance
José Julián Espina 9 months ago committed by GitHub
parent
commit
c94e10ddd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      core/engine/src/builtins/eval/mod.rs
  2. 174
      core/engine/src/builtins/function/mod.rs
  3. 21
      core/engine/src/context/hooks.rs
  4. 5
      core/engine/src/object/builtins/jsarraybuffer.rs
  5. 3
      core/engine/src/vm/call_frame/mod.rs

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

@ -108,10 +108,12 @@ impl Eval {
// 3. Let evalRealm be the current Realm Record. // 3. Let evalRealm be the current Realm Record.
// 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval // 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval
// and of the eval function itself. // and of the eval function itself.
// 5. Perform ? HostEnsureCanCompileStrings(evalRealm). let eval_realm = context.realm().clone();
// 5. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct).
context context
.host_hooks() .host_hooks()
.ensure_can_compile_strings(context.realm().clone(), context)?; .ensure_can_compile_strings(eval_realm, &[], x, direct, context)?;
// 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: // 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
// a. Let script be ParseText(StringToCodePoints(x), Script). // a. Let script be ParseText(StringToCodePoints(x), Script).

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

@ -45,7 +45,6 @@ use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use boa_parser::{Parser, Source}; use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::io::Read;
use thin_vec::ThinVec; use thin_vec::ThinVec;
use super::Proxy; use super::Proxy;
@ -390,13 +389,7 @@ impl BuiltInFunctionObject {
generator: bool, generator: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
// 1. Let currentRealm be the current Realm Record. // 1. If newTarget is undefined, set newTarget to constructor.
// 2. Perform ? HostEnsureCanCompileStrings(currentRealm).
context
.host_hooks()
.ensure_can_compile_strings(context.realm().clone(), context)?;
// 3. If newTarget is undefined, set newTarget to constructor.
let new_target = if new_target.is_undefined() { let new_target = if new_target.is_undefined() {
constructor.into() constructor.into()
} else { } else {
@ -404,8 +397,8 @@ impl BuiltInFunctionObject {
}; };
let default = if r#async && generator { let default = if r#async && generator {
// 7. Else, // 5. Else,
// a. Assert: kind is asyncGenerator. // a. Assert: kind is async-generator.
// b. Let prefix be "async function*". // b. Let prefix be "async function*".
// c. Let exprSym be the grammar symbol AsyncGeneratorExpression. // c. Let exprSym be the grammar symbol AsyncGeneratorExpression.
// d. Let bodySym be the grammar symbol AsyncGeneratorBody. // d. Let bodySym be the grammar symbol AsyncGeneratorBody.
@ -413,7 +406,7 @@ impl BuiltInFunctionObject {
// f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%". // f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
StandardConstructors::async_generator_function StandardConstructors::async_generator_function
} else if r#async { } else if r#async {
// 6. Else if kind is async, then // 4. Else if kind is async, then
// a. Let prefix be "async function". // a. Let prefix be "async function".
// b. Let exprSym be the grammar symbol AsyncFunctionExpression. // b. Let exprSym be the grammar symbol AsyncFunctionExpression.
// c. Let bodySym be the grammar symbol AsyncFunctionBody. // c. Let bodySym be the grammar symbol AsyncFunctionBody.
@ -421,7 +414,7 @@ impl BuiltInFunctionObject {
// e. Let fallbackProto be "%AsyncFunction.prototype%". // e. Let fallbackProto be "%AsyncFunction.prototype%".
StandardConstructors::async_function StandardConstructors::async_function
} else if generator { } else if generator {
// 5. Else if kind is generator, then // 3. Else if kind is generator, then
// a. Let prefix be "function*". // a. Let prefix be "function*".
// b. Let exprSym be the grammar symbol GeneratorExpression. // b. Let exprSym be the grammar symbol GeneratorExpression.
// c. Let bodySym be the grammar symbol GeneratorBody. // c. Let bodySym be the grammar symbol GeneratorBody.
@ -429,7 +422,7 @@ impl BuiltInFunctionObject {
// e. Let fallbackProto be "%GeneratorFunction.prototype%". // e. Let fallbackProto be "%GeneratorFunction.prototype%".
StandardConstructors::generator_function StandardConstructors::generator_function
} else { } else {
// 4. If kind is normal, then // 2. If kind is normal, then
// a. Let prefix be "function". // a. Let prefix be "function".
// b. Let exprSym be the grammar symbol FunctionExpression. // b. Let exprSym be the grammar symbol FunctionExpression.
// c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await]. // c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
@ -441,74 +434,119 @@ impl BuiltInFunctionObject {
// 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). // 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
let prototype = get_prototype_from_constructor(&new_target, default, context)?; let prototype = get_prototype_from_constructor(&new_target, default, context)?;
let (parameters, body) = if let Some((body_arg, args)) = args.split_last() { // 6. Let argCount be the number of elements in parameterArgs.
let parameters = if args.is_empty() { let (body, param_list) = if let Some((body, params)) = args.split_last() {
FormalParameterList::default() // 7. Let bodyString be ? ToString(bodyArg).
} else { let body = body.to_string(context)?;
// 8. Let parameterStrings be a new empty List.
let mut parameters = Vec::with_capacity(args.len()); let mut parameters = Vec::with_capacity(args.len());
for arg in args { // 9. For each element arg of parameterArgs, do
parameters.push(arg.to_string(context)?); for param in params {
// a. Append ? ToString(arg) to parameterStrings.
parameters.push(param.to_string(context)?);
} }
let parameters = parameters.join(utf16!(",")); (body, parameters)
} else {
(js_string!(), Vec::new())
};
let current_realm = context.realm().clone();
context.host_hooks().ensure_can_compile_strings(
current_realm,
&param_list,
&body,
false,
context,
)?;
// TODO: make parser generic to u32 iterators let parameters = if param_list.is_empty() {
let parameters = String::from_utf16_lossy(&parameters); FormalParameterList::default()
let mut parser = Parser::new(Source::from_bytes(&parameters)); } else {
// 12. Let P be the empty String.
// 13. If argCount > 0, then
// a. Set P to parameterStrings[0].
// b. Let k be 1.
// c. Repeat, while k < argCount,
// i. Let nextArgString be parameterStrings[k].
// ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
// iii. Set k to k + 1.
let parameters = param_list.join(utf16!(","));
let mut parser = Parser::new(Source::from_utf16(&parameters));
parser.set_identifier(context.next_parser_identifier()); parser.set_identifier(context.next_parser_identifier());
let parameters = match parser.parse_formal_parameters( // 17. Let parameters be ParseText(StringToCodePoints(P), parameterSym).
context.interner_mut(), // 18. If parameters is a List of errors, throw a SyntaxError exception.
generator, let parameters = parser
r#async, .parse_formal_parameters(context.interner_mut(), generator, r#async)
) { .map_err(|e| {
Ok(parameters) => parameters, JsNativeError::syntax()
Err(e) => {
return Err(JsNativeError::syntax()
.with_message(format!("failed to parse function parameters: {e}")) .with_message(format!("failed to parse function parameters: {e}"))
.into()) })?;
}
};
// It is a Syntax Error if FormalParameters Contains YieldExpression is true.
if generator && contains(&parameters, ContainsSymbol::YieldExpression) { if generator && contains(&parameters, ContainsSymbol::YieldExpression) {
return Err(JsNativeError::syntax().with_message( return Err(JsNativeError::syntax().with_message(
"yield expression is not allowed in formal parameter list of generator function", if r#async {
).into()); "yield expression is not allowed in formal parameter list of async generator"
} else {
"yield expression is not allowed in formal parameter list of generator"
} }
).into());
parameters
};
// It is a Syntax Error if FormalParameters Contains YieldExpression is true.
if generator && r#async && contains(&parameters, ContainsSymbol::YieldExpression) {
return Err(JsNativeError::syntax()
.with_message("yield expression not allowed in async generator parameters")
.into());
} }
// It is a Syntax Error if FormalParameters Contains AwaitExpression is true. // It is a Syntax Error if FormalParameters Contains AwaitExpression is true.
if generator && r#async && contains(&parameters, ContainsSymbol::AwaitExpression) { if r#async && contains(&parameters, ContainsSymbol::AwaitExpression) {
return Err(JsNativeError::syntax() return Err(JsNativeError::syntax()
.with_message("await expression not allowed in async generator parameters") .with_message(
if generator {
"await expression is not allowed in formal parameter list of async function"
} else {
"await expression is not allowed in formal parameter list of async generator"
})
.into()); .into());
} }
// 11. Let bodyString be the string-concatenation of 0x000A (LINE FEED), ? ToString(bodyArg), and 0x000A (LINE FEED). parameters
let body_arg = body_arg.to_string(context)?.to_std_string_escaped(); };
let body = b"\n".chain(body_arg.as_bytes()).chain(b"\n".as_slice());
// TODO: make parser generic to u32 iterators let body = if body.is_empty() {
let mut parser = Parser::new(Source::from_reader(body, None)); FunctionBody::default()
} else {
// 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
let mut body_parse = Vec::with_capacity(body.len());
body_parse.push(u16::from(b'\n'));
body_parse.extend_from_slice(&body);
body_parse.push(u16::from(b'\n'));
// 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
// 20. If body is a List of errors, throw a SyntaxError exception.
let mut parser = Parser::new(Source::from_utf16(&body_parse));
parser.set_identifier(context.next_parser_identifier()); parser.set_identifier(context.next_parser_identifier());
let body = match parser.parse_function_body(context.interner_mut(), generator, r#async) // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym).
{ // 20. If body is a List of errors, throw a SyntaxError exception.
Ok(statement_list) => statement_list, let body = parser
Err(e) => { .parse_function_body(context.interner_mut(), generator, r#async)
return Err(JsNativeError::syntax() .map_err(|e| {
JsNativeError::syntax()
.with_message(format!("failed to parse function body: {e}")) .with_message(format!("failed to parse function body: {e}"))
.into()) })?;
// It is a Syntax Error if AllPrivateIdentifiersValid of StatementList with argument « »
// is false unless the source text containing ScriptBody is eval code that is being
// processed by a direct eval.
// https://tc39.es/ecma262/#sec-scripts-static-semantics-early-errors
if !all_private_identifiers_valid(&body, Vec::new()) {
return Err(JsNativeError::syntax()
.with_message("invalid private identifier usage")
.into());
} }
};
// 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.
// 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.
// 23. Let expr be ParseText(sourceText, exprSym).
// 24. If expr is a List of errors, throw a SyntaxError exception.
// Check for errors that apply for the combination of body and parameters.
// Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code, // Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
@ -516,7 +554,7 @@ impl BuiltInFunctionObject {
for name in bound_names(&parameters) { for name in bound_names(&parameters) {
if name == Sym::ARGUMENTS || name == Sym::EVAL { if name == Sym::ARGUMENTS || name == Sym::EVAL {
return Err(JsNativeError::syntax() return Err(JsNativeError::syntax()
.with_message(" Unexpected 'eval' or 'arguments' in strict mode") .with_message("Unexpected 'eval' or 'arguments' in strict mode")
.into()); .into());
} }
} }
@ -571,21 +609,7 @@ impl BuiltInFunctionObject {
} }
} }
if !all_private_identifiers_valid(&parameters, Vec::new()) { body
return Err(JsNativeError::syntax()
.with_message("invalid private identifier usage")
.into());
}
if !all_private_identifiers_valid(&body, Vec::new()) {
return Err(JsNativeError::syntax()
.with_message("invalid private identifier usage")
.into());
}
(parameters, body)
} else {
(FormalParameterList::default(), FunctionBody::default())
}; };
let code = FunctionCompiler::new() let code = FunctionCompiler::new()

21
core/engine/src/context/hooks.rs

@ -4,7 +4,7 @@ use crate::{
job::JobCallback, job::JobCallback,
object::{JsFunction, JsObject}, object::{JsFunction, JsObject},
realm::Realm, realm::Realm,
Context, JsResult, JsValue, Context, JsResult, JsString, JsValue,
}; };
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
@ -25,7 +25,7 @@ use time::util::local_offset;
/// use boa_engine::{ /// use boa_engine::{
/// context::{Context, ContextBuilder, HostHooks}, /// context::{Context, ContextBuilder, HostHooks},
/// realm::Realm, /// realm::Realm,
/// JsNativeError, JsResult, Source, /// JsNativeError, JsResult, JsString, Source,
/// }; /// };
/// ///
/// struct Hooks; /// struct Hooks;
@ -34,7 +34,10 @@ use time::util::local_offset;
/// fn ensure_can_compile_strings( /// fn ensure_can_compile_strings(
/// &self, /// &self,
/// _realm: Realm, /// _realm: Realm,
/// context: &mut Context, /// _parameters: &[JsString],
/// _body: &JsString,
/// _direct: bool,
/// _context: &mut Context,
/// ) -> JsResult<()> { /// ) -> JsResult<()> {
/// Err(JsNativeError::typ() /// Err(JsNativeError::typ()
/// .with_message("eval calls not available") /// .with_message("eval calls not available")
@ -106,7 +109,7 @@ pub trait HostHooks {
// The default implementation of HostPromiseRejectionTracker is to return unused. // The default implementation of HostPromiseRejectionTracker is to return unused.
} }
/// [`HostEnsureCanCompileStrings ( calleeRealm )`][spec] /// [`HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, direct )`][spec]
/// ///
/// # Requirements /// # Requirements
/// ///
@ -114,8 +117,14 @@ pub trait HostHooks {
/// containing unused. This is already ensured by the return type. /// containing unused. This is already ensured by the return type.
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-hostensurecancompilestrings /// [spec]: https://tc39.es/ecma262/#sec-hostensurecancompilestrings
// TODO: Track https://github.com/tc39/ecma262/issues/938 fn ensure_can_compile_strings(
fn ensure_can_compile_strings(&self, _realm: Realm, _context: &mut Context) -> JsResult<()> { &self,
_realm: Realm,
_parameters: &[JsString],
_body: &JsString,
_direct: bool,
_context: &mut Context,
) -> JsResult<()> {
// The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused). // The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused).
Ok(()) Ok(())
} }

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

@ -230,7 +230,10 @@ impl JsArrayBuffer {
/// // Get a reference to the data. /// // Get a reference to the data.
/// let internal_buffer = array_buffer.data(); /// let internal_buffer = array_buffer.data();
/// ///
/// assert_eq!(internal_buffer.as_deref(), Some((0..5).collect::<Vec<u8>>().as_slice())); /// assert_eq!(
/// internal_buffer.as_deref(),
/// Some((0..5).collect::<Vec<u8>>().as_slice())
/// );
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```

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

@ -40,7 +40,6 @@ pub struct CallFrame {
pub(crate) code_block: Gc<CodeBlock>, pub(crate) code_block: Gc<CodeBlock>,
pub(crate) pc: u32, pub(crate) pc: u32,
/// The register pointer, points to the first register in the stack. /// The register pointer, points to the first register in the stack.
///
// TODO: Check if storing the frame pointer instead of argument count and computing the // TODO: Check if storing the frame pointer instead of argument count and computing the
// argument count based on the pointers would be better for accessing the arguments // argument count based on the pointers would be better for accessing the arguments
// and the elements before the register pointer. // and the elements before the register pointer.
@ -124,13 +123,11 @@ impl CallFrame {
/// │ callee frame pointer /// │ callee frame pointer
/// │ /// │
/// └───── caller frame pointer /// └───── caller frame pointer
///
/// ``` /// ```
/// ///
/// Some questions: /// Some questions:
/// ///
/// - Who is responsible for cleaning up the stack after a call? The rust caller. /// - Who is responsible for cleaning up the stack after a call? The rust caller.
///
pub(crate) const FUNCTION_PROLOGUE: u32 = 2; pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
pub(crate) const THIS_POSITION: u32 = 2; pub(crate) const THIS_POSITION: u32 = 2;
pub(crate) const FUNCTION_POSITION: u32 = 1; pub(crate) const FUNCTION_POSITION: u32 = 1;

Loading…
Cancel
Save