We can't just to_string() the PropertyName, it might be a symbol.
Instead to_value() it and then use to_string_without_side_effects() as
usual.
Fixes#4062.
Previously we would iterate over all the live HeapBlocks in order to
learn if an arbitrary pointer-sized value was a pointer into a live
HeapBlock. This was quite time-consuming.
Instead of that, just put all the live HeapBlock*'s in a HashTable
and identify pointers by doing a bit-masked lookup into the table.
This prevents stack overflows when calling infinite/deep recursive
functions, e.g.:
const f = () => f(); f();
JSON.stringify({}, () => ({ foo: "bar" }));
new Proxy({}, { get: (_, __, p) => p.foo }).foo;
The VM caches a StackInfo object to not slow down function calls
considerably. VM::push_call_frame() will throw an exception if
necessary (plain Error with "RuntimeError" as its .name).
Keeping the VM call frames in a Vector could cause them to move around
underneath us due to Vector resizing. Avoid this issue by allocating
CallFrame objects on the stack and having the VM simply keep a list
of pointers to each CallFrame, instead of the CallFrames themselves.
Fixes#3830.
Fixes#3951.
As the global object is constructed and initialized in a different way
than most other objects we were not setting its prototype! This made
things like "globalThis.toString()" fail unexpectedly.
If value.to_string() throws an exception and returns a null string we
must create an invalid StringOrSymbol, not one from the null string
(which ASSERT()s).
Some things, like (the non-generic version of) Array.prototype.pop(),
check is_empty() to determine whether an action, like removing elements,
can be performed. We need to know the array-like size for that, not the
size of the underlying storage, which can be different - and is not
something IndexedProperties should expose so I removed its size().
Fixes#3948.
- We have to check if the property name is a string before calling
as_string() on it
- We can't as_number() the same property name but have to use the parsed
index number
Fixes#3950.
We can't assume that property names can be converted to strings anymore,
as we have symbols. Use name.to_value() instead.
This makes something like this possible:
new Proxy(Object, { get(t, p) { return t[p] } })[Symbol.hasInstance]
This was probably a result of search & replace, it's quite ridiculous in
some places. Let use the existing pattern of getting a reference to the
VM once at each function start consistently.
This fixes Array.prototype.{join,toString}() crashing with arrays
containing themselves, i.e. circular references.
The spec is suspiciously silent about this, and indeed engine262, a
"100% spec compliant" ECMA-262 implementation, can't handle these cases.
I had a look at some major engines instead and they all seem to keep
track or check for circular references and return an empty string for
already seen objects.
- SpiderMonkey: "AutoCycleDetector detector(cx, obj)"
- V8: "CycleProtectedArrayJoin<JSArray>(...)"
- JavaScriptCore: "StringRecursionChecker checker(globalObject, thisObject)"
- ChakraCore: "scriptContext->CheckObject(thisArg)"
To keep things simple & consistent this uses the same pattern as
JSONObject, MarkupGenerator and js: simply putting each seen object in a
HashTable<Object*>.
Fixes#3929.
This renames Object::to_primitive() to Object::ordinary_to_primitive()
for two reasons:
- No confusion with Value::to_primitive()
- To match the spec's name
Also change existing uses of Object::to_primitive() to
Value::to_primitive() when the spec uses the latter (which will still
call Object::ordinary_to_primitive()). Object::to_string() has been
removed as it's not needed anymore (and nothing the spec uses).
This makes it possible to overwrite an object's toString and valueOf and
have them provide results for anything that uses to_primitive() - e.g.:
const o = { toString: undefined, valueOf: () => 42 };
Number(o) // 42, previously NaN
["foo", o].toString(); // "foo,42", previously "foo,[object Object]"
++o // 43, previously NaN
etc.
This should not just inherit Object.prototype.toString() (and override
Object::to_string()) but be its own function, i.e.
'RegExp.prototype.toString !== Object.prototype.toString'.
When value.to_string() throws an exception it returns a null string in
which case we must not construct a valid PropertyName.
Also ASSERT in PropertyName(String) and PropertyName(FlyString) to
prevent this from happening in the future.
Fixes#3941.
We must *never* call some method that expects a non-empty value on the
result of a function call without checking for exceptions first. It
won't work reliably.
Fixes#3939.
Two issues:
- throw_exception() with ErrorType::InstanceOfOperatorBadPrototype would
receive rhs_prototype.to_string_without_side_effects(), which would
ASSERT_NOT_REACHED() as to_string_without_side_effects() must not be
called on an empty value. It should (and now does) receive the RHS
value instead as the message is "'prototype' property of {} is not an
object".
- Value::instance_of() was missing an exception check after calling
has_instance_method, to_boolean() on an empty value result would crash
as well.
Fixes#3930.
This adds a new MetaProperty AST node which will be used for
'new.target' and 'import.meta' meta properties. The parser now
distinguishes between "in function context" and "in arrow function
context" (which is required for this).
When encountering TokenType::New we will attempt to parse it as meta
property and resort to regular new expression parsing if that fails,
much like the parsing of labelled statements.
This is a bit nicer for two reasons:
- The absence of line number/column information isn't based on 'values
are zero' anymore but on Optional's value
- When reporting syntax errors with position information other than the
current token's position we had to store line and column ourselves,
like this:
auto foo_start_line = m_parser_state.m_current_token.line_number();
auto foo_start_column = m_parser_state.m_current_token.line_column();
...
syntax_error("...", foo_start_line, foo_start_column);
Which now becomes:
auto foo_start= position();
...
syntax_error("...", foo_start);
This makes it easier to report correct positions for syntax errors
that only emerge a few tokens later :^)
By having the "is this a use strict directive?" logic in
parse_string_literal() we would apply it to *any* string literal, which
is incorrect and would lead to false positives - e.g.:
"use strict" + 1
`"use strict"`
"\123"; ({"use strict": ...})
Relevant part from the spec which is now implemented properly:
[...] and where each ExpressionStatement in the sequence consists
entirely of a StringLiteral token [...]
I also got rid of UseStrictDirectiveState which is not needed anymore.
Fixes#3903.
The previous approach (keeping track of the current source position
manually) was only working for single line sources (which is fair
considering this was developed for Browser's JS console).
The new approach is much simpler: append token trivia (all whitespace
and comments since the last token), then append styled token value.
https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses
B.3.4 FunctionDeclarations in IfStatement Statement Clauses
The following augments the IfStatement production in 13.6:
IfStatement[Yield, Await, Return] :
if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default] else Statement[?Yield, ?Await, ?Return]
if ( Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] else FunctionDeclaration[?Yield, ?Await, ~Default]
if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default] else FunctionDeclaration[?Yield, ?Await, ~Default]
if ( Expression[+In, ?Yield, ?Await] ) FunctionDeclaration[?Yield, ?Await, ~Default]
This production only applies when parsing non-strict code. Code matching
this production is processed as if each matching occurrence of
FunctionDeclaration[?Yield, ?Await, ~Default] was the sole
StatementListItem of a BlockStatement occupying that position in the
source code. The semantics of such a synthetic BlockStatement includes
the web legacy compatibility semantics specified in B.3.3.
B.1.3 HTML-like Comments
The syntax and semantics of 11.4 is extended as follows except that this
extension is not allowed when parsing source code using the goal symbol
Module:
Syntax (only relevant part included)
SingleLineHTMLCloseComment ::
LineTerminatorSequence HTMLCloseComment
HTMLCloseComment ::
WhiteSpaceSequence[opt] SingleLineDelimitedCommentSequence[opt] --> SingleLineCommentChars[opt]
Fixes#3810.
ES 5(.1) described parsing of the function body string as:
https://www.ecma-international.org/ecma-262/5.1/#sec-15.3.2.1
7. If P is not parsable as a FormalParameterList[opt] then throw a SyntaxError exception.
8. If body is not parsable as FunctionBody then throw a SyntaxError exception.
We implemented it as building the source string of a complete function
and feeding that to the parser, with the same outcome. ES 2015+ does
exactly that, but with newlines at certain positions:
https://tc39.es/ecma262/#sec-createdynamicfunction
16. Let bodyString be the string-concatenation of 0x000A (LINE FEED), ? ToString(bodyArg), and 0x000A (LINE FEED).
17. Let prefix be the prefix associated with kind in Table 49.
18. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyString, and "}".
This patch updates the generated source string to match these
requirements. This will make certain edge cases work, e.g.
'new Function("-->")', where the user supplied input must be placed on
its own line to be valid syntax.
This is, and I can't stress this enough, a lot better than all the
manual bounds checking and indexing that was going on before.
Also fixes a small bug where "\u{}" wouldn't get rejected as invalid
unicode escape sequence.
We only use expect(...).toEval() / not.toEval() for checking syntax
errors, where we obviously can't put the code in a regular function. For
runtime errors we do exactly that, so toEval() should not fail - this
allows us to use undefined identifiers in syntax tests.
This allows us to communicate details about invalid tokens to the parser
without having to invent a bunch of specific invalid tokens like
TokenType::InvalidNumericLiteral.
Newlines after line continuation were inserted into the string
literals. This patch makes the parser ignore the newlines after \ and
also makes it so that "use strict" containing a line continuation is
not a valid "use strict".
- A regular function can have duplicate parameters except in strict mode
or if its parameter list is not "simple" (has a default or rest
parameter)
- An arrow function can never have duplicate parameters
Compared to other engines I opted for more useful syntax error messages
than a generic "duplicate parameter name not allowed in this context":
"use strict"; function test(foo, foo) {}
^
Uncaught exception: [SyntaxError]: Duplicate parameter 'foo' not allowed in strict mode (line: 1, column: 34)
function test(foo, foo = 1) {}
^
Uncaught exception: [SyntaxError]: Duplicate parameter 'foo' not allowed in function with default parameter (line: 1, column: 20)
function test(foo, ...foo) {}
^
Uncaught exception: [SyntaxError]: Duplicate parameter 'foo' not allowed in function with rest parameter (line: 1, column: 23)
(foo, foo) => {}
^
Uncaught exception: [SyntaxError]: Duplicate parameter 'foo' not allowed in arrow function (line: 1, column: 7)
https://tc39.es/ecma262/#sec-directive-prologues-and-the-use-strict-directive
A Use Strict Directive is an ExpressionStatement in a Directive Prologue
whose StringLiteral is either of the exact code point sequences
"use strict" or 'use strict'. A Use Strict Directive may not contain an
EscapeSequence or LineContinuation.