Commit graph

1101 commits

Author SHA1 Message Date
Linus Groh
7fb299fe46 LibJS: Clean up TypedArray constructors and prototypes
The current implementation is not entirely correct yet. Two classes have
been added:
- TypedArrayConstructor, which the various typed array constructors now
  inherit from. Calling or constructing this class (from JS, that is)
  directly is not possible, we might want to move this abstract class
  functionality to NativeFunction at a later point.
- TypedArrayPrototype, which the various typed array prototypes now have
  as their own prototype. This will be the place where most of the
  functionality is being shared.

Relevant parts from the spec:

22.2.1 The %TypedArray% Intrinsic Object
The %TypedArray% intrinsic object:
- is a constructor function object that all of the TypedArray
  constructor objects inherit from.
- along with its corresponding prototype object, provides common
  properties that are inherited by all TypedArray constructors and their
  instances.

22.2.2 Properties of the %TypedArray% Intrinsic Object
The %TypedArray% intrinsic object:
- has a [[Prototype]] internal slot whose value is %Function.prototype%.

22.2.2.3 %TypedArray%.prototype
The initial value of %TypedArray%.prototype is the %TypedArray%
prototype object.

22.2.6 Properties of the TypedArray Constructors
Each TypedArray constructor:
- has a [[Prototype]] internal slot whose value is %TypedArray%.

22.2.6.2 TypedArray.prototype
The initial value of TypedArray.prototype is the corresponding
TypedArray prototype intrinsic object (22.2.7).

22.2.7 Properties of the TypedArray Prototype Objects
Each TypedArray prototype object:
- has a [[Prototype]] internal slot whose value is %TypedArray.prototype%.

22.2.7.2 TypedArray.prototype.constructor
The initial value of a TypedArray.prototype.constructor is the
corresponding %TypedArray% intrinsic object.
2020-12-02 12:52:31 +01:00
Andreas Kling
3565d3c60c LibJS: Add six typed arrays (signed and unsigned 8/16/32-bit)
This patch adds six of the standard type arrays and tries to share as
much code as possible:

- Uint8Array
- Uint16Array
- Uint32Array
- Int8Array
- Int16Array
- Int32Array
2020-12-01 21:05:25 +01:00
Andreas Kling
93feb7a81f LibJS: Have Uint8ClampedArray delegate OOB accesses to JS::Object
Uint8ClampedArray itself only cares about legitimate in-bounds accesses
since that's what where the specialization happens.
2020-12-01 17:12:04 +01:00
Andreas Kling
f2c7caf2db LibJS: Zero out memory in newly allocated Uint8ClampedArray objects 2020-12-01 17:06:48 +01:00
Linus Groh
48c19cdb06 LibJS: Remove ProxyPrototype
Proxy is an "exotic object" and doesn't have its own prototype. Use the
regular object prototype instead, but most stuff is happening on the
target object anyway. :^)
2020-12-01 09:54:37 +01:00
Linus Groh
ea08bf5812 LibJS: Fix crash in Lexer on EOF in unterminated template literal
Fixes #4252.
2020-11-30 11:36:42 +01:00
Andreas Kling
2d9d883274 LibJS: Add a basic implementation of String.prototype.substr() 2020-11-29 20:29:49 +01:00
Andreas Kling
2e4832c3da LibJS: Constructor function's "prototype" property should be writable
This matches other engines.
2020-11-29 19:49:27 +01:00
Andreas Kling
01c8765519 LibJS+LibWeb: Log JavaScript exceptions raised by web content
Instead of hiding JS exceptions raised on the web, we now print them to
the debug log. This will make it a bit easier to work out why some web
pages aren't working right. :^)
2020-11-29 16:50:32 +01:00
Linus Groh
3ac7fb9f6c LibJS: Disallow 'with' statement in strict mode 2020-11-28 20:33:41 +01:00
Andreas Kling
42b1b36eb4 LibJS: Run clang-format on WithScope.h 2020-11-28 17:36:38 +01:00
Andreas Kling
1fad95fec5 LibJS: Add a basic unit test for the "with" statement 2020-11-28 17:16:48 +01:00
Andreas Kling
9de6443ab7 LibJS: Add basic support for "with" statements
with statements evaluate an expression and put the result of it at the
"front" of the scope chain. This is implemented by creating a WithScope
object and placing it in front of the VM's current call frame's scope.
2020-11-28 17:16:48 +01:00
Andreas Kling
c3fe9b4df8 LibJS: Add a scope object abstraction
Both GlobalObject and LexicalEnvironment now inherit from ScopeObject,
and the VM's call frames point to a ScopeObject chain rather than just
a LexicalEnvironment chain.

This gives us much more flexibility to implement things like "with",
and also unifies some of the code paths that previously required
special handling of the global object.

There's a bunch of more cleanup that can be done in the wake of this
change, and there might be some oversights in the handling of the
"super" keyword, but this generally seems like a good architectural
improvement. :^)
2020-11-28 17:16:48 +01:00
Andreas Kling
e1bbc7c075 LibJS: Make JS::Value constructors take const cell pointers
Taking non-const cell pointers is asking for trouble, since passing e.g
a "const Object*" to Value(Object*) will actually call Value(bool),
which is most likely not what you want.
2020-11-28 17:16:48 +01:00
Andreas Kling
d617120499 LibJS: Parse "with" statements :^) 2020-11-28 17:16:48 +01:00
Andreas Kling
98f2da9834 LibJS: Rename Cell::visit_children() => Cell::visit_edges()
The GC heap is really a graph of cells, so "children" didn't quite feel
appropriate here.
2020-11-28 17:16:48 +01:00
Andreas Kling
97a05ac9ac LibJS: Allow Shape without a global object
It would be nice to be able to cache some shapes globally in the VM,
but then they can't be tied to a specific global object. So let's just
get rid of the requirement that shapes are tied to a global object.
2020-11-28 17:16:48 +01:00
Linus Groh
149b54e26f LibJS: Remove quotes from RegExpCompileError message
They look a bit out of place, especially for multi-line error messages.
2020-11-28 16:46:25 +01:00
Linus Groh
7dd77020ae LibJS: Use enumerator macros to define RegExp.prototype flag getters 2020-11-28 01:20:11 +01:00
Linus Groh
8a9a7f1677 LibJS: Make RegExp.prototype.source spec-compliant
Basically:
- And edge case for this object being RegExp.prototype.source
- Return "(?:)" for empty pattern
- Escape some things properly
2020-11-28 01:20:11 +01:00
Linus Groh
b6e5442d55 LibJS: Make RegExp.prototype.toString() spec-compliant
It should use the 'source' and 'flags' properties of the object, and
therefore work with non-RegExp objects as well.
2020-11-28 01:20:11 +01:00
Linus Groh
ee66eaa1b0 LibJS: Make RegExp.prototype.flags spec-compliant
This should be using the individual flag boolean properties rather than
the [[OriginalFlags]] internal slot.
Use an enumerator macro here for brevity, this will be useful for other
things as well. :^)
2020-11-28 01:20:11 +01:00
Linus Groh
5cb45e4feb LibJS: Make RegExp() constructor spec-compliant
- Default values should depend on arguments being undefined, not being
  missing
- "(?:)" for empty pattern happens in RegExp.prototype.source, not the
  constructor
2020-11-28 01:20:11 +01:00
AnotherTest
210a3db44d LibJS: Implement `RegExpPrototype::exec()'
This implements *only* the builtin exec() function.
2020-11-27 21:32:41 +01:00
AnotherTest
8ba273a2f3 LibJS: Hook up Regex<ECMA262> to RegExpObject and implement `test()'
This makes RegExpObject compile and store a Regex<ECMA262>, adds
all flag-related properties, and implements `RegExpPrototype.test()`
(complete with 'lastIndex' support) :^)
It should be noted that this only implements `test()' using the builtin
`exec()'.
2020-11-27 21:32:41 +01:00
AnotherTest
3200ff5f4f LibJS+js: Rename RegExp.{content => pattern}
The spec talks about it as 'pattern', so let's use that instead.
2020-11-27 21:32:41 +01:00
Linus Groh
922d0759b0 LibJS: Fix possible OOB read during Lexer construction
The Lexer constructor calls consume() once, which initializes m_position
to be > 0 and sets m_character. consume() calls is_line_terminator(),
which wasn't accounting for this state.
2020-11-25 22:43:30 +01:00
Linus Groh
f6f0d3cbae LibJS: Support receiver in ProxyObject::get/put()
If a receiver is given, e.g. via Reflect.get/set(), forward it to the
target object's get()/put() or use it as last argument of the trap
function. The default value is the Proxy object itself.
2020-11-24 21:35:03 +01:00
Linus Groh
48369194d2 LibJS: Forward receiver value to native property getters/setters
There's no reason why only (user-defined) accessors would have set the
receiver as this value for their getters/setters, this is an oversight.
2020-11-22 19:00:19 +01:00
Linus Groh
c52739ea4b LibJS: Make call_native_property_{g,s}etter() take a NativeProperty&
Passing in a plain Value and expecting it to be a native property is
error prone, let's use a more narrow type and pass a NativeProperty
reference directly.
2020-11-22 19:00:19 +01:00
Luke
bbc0487ced LibJS: Fix build with VM_DEBUG defined 2020-11-22 11:35:13 +01:00
Linus Groh
5a307836c1 LibJS: Handle symbol PropertyName in primitive assignment error
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.
2020-11-12 11:40:29 +01:00
Linus Groh
e77202fe0f LibJS/Tests: Use canParseSource() for toEval()
We can now enable the "new.target is syntax error outside of function"
test :^)
2020-11-12 10:14:57 +01:00
Linus Groh
8694d804c7 LibJS: Run prettier on test-common.js 2020-11-12 10:14:57 +01:00
Linus Groh
1b0c862f3a LibJS: Throw TypeError when calling class constructor without 'new' 2020-11-12 10:14:00 +01:00
Linus Groh
b07c7f589f LibJS: Refactor ScriptFunction::call() a bit
- Get VM reference once
- Less nesting
- Better variable names
2020-11-12 10:14:00 +01:00
Luke
bb22b04d44 LibWeb+LibJS: Add [LegacyNullToEmptyString] attribute
If specified, to_string() returns an empty string instead of "null" for
null values.
2020-11-11 12:15:05 +01:00
Andreas Kling
1745e503aa LibJS: Use a HashTable to identify potential cell pointers in GC scan
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.
2020-11-10 20:28:53 +01:00
Linus Groh
a02b9983f9 LibJS: Throw RuntimeError when reaching the end of the stack
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).
2020-11-08 16:51:54 +01:00
Linus Groh
9c3ead8f91 LibJS+AK: Move cross-platform stack bounds code from JS::Heap to AK::StackInfo
This will be useful for other things than the Heap, maybe even outside
of LibJS.
2020-11-08 16:51:54 +01:00
Andreas Kling
43ff2ea8d8 LibJS: Use regular stack for VM call frames instead of Vector storage
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.
2020-11-07 13:58:28 +01:00
Luke
f5aad71c15 LibJS: Remove unused variable m_has_property_table in Shape 2020-11-07 10:09:55 +01:00
Luke
020b782474 LibJS: Use pow instead of __bulitin_pow on clang
__bulitin_pow doesn't seem to exist on clang, at least
it didn't build with it.
2020-11-07 10:09:55 +01:00
Linus Groh
745ffca580 LibJS: Use element index as key for array spread in object
This fixes spreading of arrays with holes in object literals where the
inserted keys are not consecutive numbers.

Fixes #3967.
2020-11-07 10:08:28 +01:00
Linus Groh
06a3625545 LibJS: Set prototype of GlobalObject to ObjectPrototype
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.
2020-11-07 10:08:05 +01:00
Linus Groh
965050796f LibJS: Don't create StringOrSymbol(String) if from_value() fails
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).
2020-11-07 10:08:05 +01:00
Linus Groh
021c8dea1f LibJS: Skip trailing empty values in IndexedPropertyIterator
When we reach the end of the pre-computed indices vector we can just
skip to the end (array-like size) as only empty values will follow.

Fixes #3970.
2020-11-07 10:03:58 +01:00
Linus Groh
82b42cefbd LibJS: Handle circular references in Array.prototype.toLocaleString()
Also use ArmedScopeGuard for removing seen objects to account for early
returns.

Fixes #3963.
2020-11-06 15:50:18 +01:00
Linus Groh
dec6c0a207 LibJS: Use array-like size for IndexedProperties::is_empty()
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.
2020-11-05 20:01:30 +01:00
Linus Groh
0bb66890c8 LibJS: Fix Object::delete_property() with numeric string property
- 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.
2020-11-05 19:15:00 +01:00
Linus Groh
8d96f428ef LibJS: ASSERT(property_name.is_valid()) in more Object methods 2020-11-05 19:15:00 +01:00
Linus Groh
2cf8649d0e LibJS: Fix ProxyObject get/set with symbol property name
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]
2020-11-04 23:06:44 +01:00
Linus Groh
44e38b8457 LibJS: Replace a bunch of vm() calls in ProxyObject with reference
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.
2020-11-04 23:06:44 +01:00
Linus Groh
2645dfafcf LibJS: Implement Object(value) constructor
Not sure why we didn't have this yet, it's super simple :^)
2020-11-04 23:06:44 +01:00
Linus Groh
0603402c80 LibJS: Handle circular references in Array.prototype.join()
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.
2020-11-04 19:35:43 +01:00
Linus Groh
e5845ba3a0 LibJS: Use "," separator in Array.prototype.join() if first arg is undefined
This is how the spec describes it, not "if the first arg is missing".
Also swap length & separator steps to match spec.
2020-11-04 19:35:43 +01:00
Linus Groh
fb89c324c5 LibJS: Implement spec-compliant OrdinaryToPrimitive
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.
2020-11-04 19:33:49 +01:00
Linus Groh
e163db248d LibJS: Implement RegExp.prototype.toString() as standalone function
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'.
2020-11-04 19:33:49 +01:00
Linus Groh
41837f548d LibJS: Don't create "valid" PropertyName from null string
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.
2020-11-04 15:31:39 +01:00
Linus Groh
8afe1c8165 LibJS: Fix incorrect exception checks in ProxyObject
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.
2020-11-04 14:21:06 +01:00
Linus Groh
565a26808d LibJS: Fix crashing exception in Value::ordinary_has_instance()
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.
2020-11-03 19:14:13 +01:00
Linus Groh
39a1c9d827 LibJS: Implement 'new.target'
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.
2020-11-02 22:40:59 +01:00
Linus Groh
e07a39c816 LibJS: Replace 'size_t line, size_t column' with 'Optional<Position>'
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 :^)
2020-11-02 22:40:59 +01:00
Linus Groh
9e80c67608 LibJS: Fix "use strict" directive false positives
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.
2020-11-02 13:13:54 +01:00
Linus Groh
d2a2d19a86 LibJS: Handle multi-line source code in MarkupGenerator
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.
2020-10-31 20:52:54 +01:00
Linus Groh
a598a2c19d LibJS: Function declarations in if statement clauses
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.
2020-10-31 15:25:12 +01:00
Linus Groh
563d3c8055 LibJS: Require initializer for 'const' variable declaration 2020-10-30 23:43:38 +01:00
Linus Groh
69845ae460 LibJS: "-->" preceded by token on same line isn't start of HTML-like comment
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.
2020-10-29 22:28:15 +01:00
Linus Groh
a10d09faba LibJS: Tweak generated source in 'new Function()' to match ES 2015 spec
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.
2020-10-29 22:27:55 +01:00
Linus Groh
3dbf4c62b0 LibJS: Use GenericLexer for Token::string_value()
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.
2020-10-29 11:52:31 +01:00
Linus Groh
b5bd05b717 LibJS: Don't parse numeric literal containing 8 or 9 as octal
If the value has a leading zero (allowed in non-strict mode) but
contains the digits 8 or 9 it can't be an octal number.
2020-10-28 21:11:32 +01:00
Linus Groh
b4e51249e9 LibJS: Always insert semicolon after do-while statement if missing
https://tc39.es/ecma262/#sec-additions-and-changes-that-introduce-incompatibilities-with-prior-editions

11.9.1: In ECMAScript 2015, Automatic Semicolon Insertion adds a
semicolon at the end of a do-while statement if the semicolon is
missing. This change aligns the specification with the actual behaviour
of most existing implementations.
2020-10-28 21:11:32 +01:00
Linus Groh
d278f61f4c LibJS: Restrict toEval() failures to SyntaxError
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.
2020-10-28 21:11:32 +01:00
Linus Groh
7112031bfb LibJS: Use message from invalid token in syntax error 2020-10-26 21:38:34 +01:00
Linus Groh
6a3389cec6 LibJS: Emit token message for invalid numeric literals 2020-10-26 21:38:34 +01:00
Linus Groh
19edcbd79c LibJS: Emit TokenType::Invalid for unterminated multi-line comments 2020-10-26 21:38:34 +01:00
Linus Groh
03c1d43f6e LibJS: Add message string to Token
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.
2020-10-26 21:38:34 +01:00
Linus Groh
66e315959d LibJS: Allow all line terminators to be used for line continuations 2020-10-25 19:45:47 +01:00
Marcin Gasperowicz
e5ddcadd3c LibJS: Parse line continuations in string literals properly
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".
2020-10-25 15:16:47 +01:00
Linus Groh
dca9e4ec10 LibJS: Implement rules for duplicate function parameters
- 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)
2020-10-25 12:56:02 +01:00
Linus Groh
2adcabb6b3 LibJS: Disallow escape sequence/line continuation in use strict directive
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.
2020-10-24 16:34:01 +02:00
Linus Groh
4fb96afafc LibJS: Support LegacyOctalEscapeSequence in string literals
https://tc39.es/ecma262/#sec-additional-syntax-string-literals

The syntax and semantics of 11.8.4 is extended as follows except that
this extension is not allowed for strict mode code:

Syntax

    EscapeSequence::
        CharacterEscapeSequence
        LegacyOctalEscapeSequence
        NonOctalDecimalEscapeSequence
        HexEscapeSequence
        UnicodeEscapeSequence

    LegacyOctalEscapeSequence::
        OctalDigit [lookahead ∉ OctalDigit]
        ZeroToThree OctalDigit [lookahead ∉ OctalDigit]
        FourToSeven OctalDigit
        ZeroToThree OctalDigit OctalDigit

    ZeroToThree :: one of
        0 1 2 3

    FourToSeven :: one of
        4 5 6 7

    NonOctalDecimalEscapeSequence :: one of
        8 9

This definition of EscapeSequence is not used in strict mode or when
parsing TemplateCharacter.

Note

It is possible for string literals to precede a Use Strict Directive
that places the enclosing code in strict mode, and implementations must
take care to not use this extended definition of EscapeSequence with
such literals. For example, attempting to parse the following source
text must fail:

function invalid() { "\7"; "use strict"; }
2020-10-24 16:34:01 +02:00
Linus Groh
9f036959e8 LibJS: Report correct line/column for string literal syntax errors
We're passing a token to this function, so m_current_token is actually
the next token - which leads to incorrect line/column numbers for string
literal syntax errors:

    "\u"
        ^
    Uncaught exception: [SyntaxError]: Malformed unicode escape sequence (line: 1, column: 5)

Rather than:

    "\u"
    ^
    Uncaught exception: [SyntaxError]: Malformed unicode escape sequence (line: 1, column: 1)
2020-10-24 16:34:01 +02:00
Linus Groh
d6f8c52245 LibJS: Allow try statement with only finally clause
This was a regression introduced by 9ffe45b - a TryStatement without
'catch' clause *is* allowed, if it has a 'finally' clause. It is now
checked properly that at least one of both is present.
2020-10-24 16:34:01 +02:00
Linus Groh
80bb62b9cc LibJS: Distinguish between statement and declaration
This separates matching/parsing of statements and declarations and
fixes a few edge cases where the parser would incorrectly accept a
declaration where only a statement is allowed - for example:

    if (foo) const a = 1;
    for (var bar;;) function b() {}
    while (baz) class c {}
2020-10-23 19:13:06 +02:00
Linus Groh
f8ae6fa713 LibJS: Disallow NumericLiteral immediately followed by Identifier
From the spec: https://tc39.es/ecma262/#sec-literals-numeric-literals

The SourceCharacter immediately following a NumericLiteral must not be
an IdentifierStart or DecimalDigit.

For example: 3in is an error and not the two input elements 3 and in.
2020-10-23 19:13:06 +02:00
Linus Groh
80bb22788f LibJS: Don't allow TryStatement without catch clause 2020-10-23 19:13:06 +02:00
Linus Groh
82ac936a9d LibJS: Check for exception after executing (do)while test expression
Otherwise we crash the interpreter when an exception is thrown during
evaluation of the while or do/while test expression - which is easily
caused by a ReferenceError - e.g.:

    while (someUndefinedVariable) {
        // ...
    }
2020-10-23 19:06:57 +02:00
Andreas Kling
619cd613d0 LibJS: Give VM a cache of single-ASCII-character PrimitiveString
A large number of JS strings are a single ASCII character. This patch
adds a 128-entry cache for those strings to the VM. The cost of the
cache is 1536 byte of GC heap (all in same block) + 2304 bytes malloc.

This avoids a lot of GC heap allocations, and packing all of these
in the same heap block is nice for fragmentation as well.
2020-10-22 17:48:12 +02:00
Andreas Kling
5c2520e6b2 LibJS: Simplify environment access a little bit in VM::construct() 2020-10-22 17:23:40 +02:00
Andreas Kling
07f76cd980 LibJS: Shrink sizeof(LexicalEnvironment) by reorganizing members 2020-10-22 17:03:40 +02:00
Linus Groh
15642874f3 LibJS: Support all line terminators (LF, CR, LS, PS)
https://tc39.es/ecma262/#sec-line-terminators
2020-10-22 10:06:30 +02:00
Linus Groh
1e86379327 LibJS: Rest parameter in setter functions is a syntax error 2020-10-20 20:27:58 +02:00
Linus Groh
6331d45a6f LibJS: Move checks for invalid getter/setter params to parse_function_node
This allows us to provide better error messages as we can point the
syntax error location to the exact first invalid parameter instead of
always the end of the function within a object literal or class
definition.

Before this change:

    const Foo = { set bar() {} }
                               ^
    Uncaught exception: [SyntaxError]: Object setter property must have one argument (line: 1, column: 28)

    class Foo { set bar() {} }
                             ^
    Uncaught exception: [SyntaxError]: Class setter method must have one argument (line: 1, column: 26)

After this change:

    const Foo = { set bar() {} }
                          ^
    Uncaught exception: [SyntaxError]: Setter function must have one argument (line: 1, column: 23)

    class Foo { set bar() {} }
                        ^
    Uncaught exception: [SyntaxError]: Setter function must have one argument (line: 1, column: 21)

The only possible downside of this change is that class getters/setters
and functions in objects are not distinguished in the message anymore -
I don't think that's important though, and classes are (mostly) just
syntactic sugar anyway.
2020-10-20 20:27:58 +02:00
Linus Groh
db75be1119 LibJS: Refactor parse_function_node() bool parameters into bit flags
I'm about to add even more options and a bunch of unnamed true/false
arguments is really not helpful. Let's make this a single parse options
parameter using bit flags.
2020-10-20 20:27:58 +02:00
Linus Groh
a82c56f9f7 LibJS: Speed up IndexedPropertyIterator by computing non-empty indices
This provides a huge speed-up for objects with large numbers as property
keys in some situation. Previously we would simply iterate from 0-<max>
and check if there's a non-empty value at each index - now we're being
smarter and compute a list of non-empty indices upfront, by checking
each value in the packed elements vector and appending the sparse
elements hashmap keys (for GenericIndexedPropertyStorage).

Consider this example, an object with a single own property, which is a
number increasing by a factor of 10 each iteration:

    for (let i = 0; i < 10; ++i) {
        const o = {[10 ** i]: "foo"};
        const start = Date.now();
        Object.getOwnPropertyNames(o);  // <-- IndexedPropertyIterator
        const end = Date.now();
        console.log(`${10 ** i} -> ${(end - start) / 1000}s`);
    }

Before this change:

    1 -> 0.0000s
    10 -> 0.0000s
    100 -> 0.0000s
    1000 -> 0.0000s
    10000 -> 0.0005s
    100000 -> 0.0039s
    1000000 -> 0.0295s
    10000000 -> 0.2489s
    100000000 -> 2.4758s
    1000000000 -> 25.5669s

After this change:

    1 -> 0.0000s
    10 -> 0.0000s
    100 -> 0.0000s
    1000 -> 0.0000s
    10000 -> 0.0000s
    100000 -> 0.0000s
    1000000 -> 0.0000s
    10000000 -> 0.0000s
    100000000 -> 0.0000s
    1000000000 -> 0.0000s

Fixes #3805.
2020-10-20 08:51:41 +02:00
Linus Groh
46cc1f718e LibJS: Unprefixed octal numbers are a syntax error in strict mode 2020-10-19 20:08:22 +02:00
Linus Groh
e898c98873 LibJS: Don't parse arrow function with newline between ) and =>
If there's a newline between the closing paren and arrow it's not a
valid arrow function, ASI should kick in instead (it'll then fail with
"Unexpected token Arrow")
2020-10-19 11:31:55 +02:00
Linus Groh
965d952ff3 LibJS: Share parameter parsing between regular and arrow functions
This simplifies try_parse_arrow_function_expression() and fixes a few
cases that should not produce an arrow function AST but did:

    (a,,) => {}
    (a b) => {}
    (a ...b) => {}
    (...b a) => {}

The new parsing logic checks whether parens are expected and uses
parse_function_parameters() if so, rolling back if a new syntax error
occurs during that. Otherwise it's just an identifier in which case we
parse the single parameter ourselves.
2020-10-19 11:31:55 +02:00
Linus Groh
aa68de3530 LibJS: Fix dump() indentation of UpdateExpression with suffix operator 2020-10-19 11:31:55 +02:00
Linus Groh
2dbea60fe2 LibJS: Multiple 'default' clauses in switch statement are a syntax error 2020-10-19 11:30:14 +02:00
Linus Groh
f8886ef5ba LibJS: Handle continue in switch statement unwinding 2020-10-18 19:08:52 +02:00
Linus Groh
8f54edb7a0 LibJS: Handle return value in switch statement unwinding
Fixes #3790.
2020-10-18 19:08:52 +02:00
Stephan Unverwerth
2c888b3c6e LibJS: Fix parsing of invalid numeric literals
i.e. "1e" "0x" "0b" "0o" used to be parsed as valid literals.
They now produce invalid tokens. Fixes #3716
2020-10-18 15:38:57 +02:00
Andreas Kling
77c1957961 LibJS: Use allocate_without_global_object for allocating Shapes 2020-10-17 23:47:07 +02:00
Andreas Kling
d8269c343c LibJS: Avoid creating temporary Strings to look up tokens while lexing
It would be cool to solve this in a general way so that looking up
a string literal or StringView in a HashMap with String keys avoids
creating a temp string.

For now, this patch simply addresses the issue in JS::Lexer.
This is a 2-3% speed-up on test-js.
2020-10-17 23:44:41 +02:00
Andreas Kling
d3dfd55472 LibJS: Prebake the empty object ({}) with a prototype
Instead of performing a prototype transition for every new object we
create via {}, prebake the object returned by Object::create_empty()
with a shape with ObjectPrototype as the prototype.

We also prebake the shape for the object assigned to the "prototype"
property of new ScriptFunction objects, since those are extremely
common and that code broke from this change anyway.

This avoid a large number of transitions and is a small speed-up on
test-js.
2020-10-17 23:23:53 +02:00
Linus Groh
b98b83712f LibJS: constexpr some Number object constant values 2020-10-16 17:06:57 +02:00
Andreas Kling
2c956ac132 LibJS: Reorganize Shape members to reduce sizeof(Shape) a bit 2020-10-16 16:46:27 +02:00
Andreas Kling
2c0e153396 LibJS: Don't bother deferring GC during ensure_property_table()
This is not actually necessary, since no GC allocations are made during
this process. If we ever make property tables into heap cells, we'd
have to rethink this.
2020-10-16 08:59:51 +02:00
Andreas Kling
4387590e65 LibJS: Support move semantics for StringOrSymbol
This allows us to rehash property tables without a bunch of ref count
churn happening.
2020-10-15 23:49:53 +02:00
Andreas Kling
1d96ecf148 Everywhere: Add missing <AK/TemporaryChange.h> includes
Don't rely on HashTable.h pulling this in.
2020-10-15 23:49:53 +02:00
Linus Groh
e07490ce13 LibJS: Don't assume value for index < size in IndexedPropertyIterator
This assumption only works for the m_packed_elements Vector where a
missing value at a certain index still returns an empty value, but not
for the m_sparse_elements HashMap, which is being used for indices
>= 200 - in that case the Optional<ValueAndAttributes> result will not
have a value.

This fixes a crash in the js REPL where printing an array with a hole at
any index >= 200 would crash.
2020-10-14 00:52:47 +02:00
Andreas Kling
a1029738fd LibJS: Add some more items to CommonPropertyNames that I missed 2020-10-14 00:10:49 +02:00
Andreas Kling
8f535435dc LibJS: Avoid property lookups during object initialization
When we're initializing objects, we're just adding a bunch of new
properties, without transition, and without overlap (we never add
the same property twice.)

Take advantage of this by skipping lookups entirely (no need to see
if we're overwriting an existing property) during initialization.

Another nice test-js speedup :^)
2020-10-13 23:57:45 +02:00
Andreas Kling
7b863330dc LibJS: Cache commonly used FlyStrings in the VM
Roughly 7% of test-js runtime was spent creating FlyStrings from string
literals. This patch frontloads that work and caches all the commonly
used names in LibJS on a CommonPropertyNames struct that hangs off VM.
2020-10-13 23:57:45 +02:00
Andreas Kling
9f6c5f68b6 LibJS: Tidy up CallExpression::execute() a little bit 2020-10-13 19:13:37 +02:00
Linus Groh
a5bf6cfff9 LibJS: Don't change offset when reconfiguring property in unique shape
When changing the attributes of an existing property of an object with
unique shape we must not change the PropertyMetadata offset.
Doing so without resizing the underlying storage vector caused an OOB
write crash.

Fixes #3735.
2020-10-10 23:25:00 +02:00
Matthew Olsson
e8da5f99b1 LibJS: break or continue with nonexistent label is a syntax error 2020-10-08 23:27:16 +02:00
Matthew Olsson
6e05685ad4 LibJS: Fix return statements not working properly in loops
Previously, when a loop detected an unwind of type ScopeType::Function
(which means a return statement was executed inside of the loop), it
would just return undefined. This set the VM's last_value to undefined,
when it should have been the returned value. This patch makes all loop
statements return the appropriate value in the above case.
2020-10-08 23:23:55 +02:00
Matthew Olsson
d980073122 LibJS: Handle unwinding in while and do-while statements
For some reason, this was never added. So something like "while (true)
{ return }" would loop infinitely.
2020-10-08 23:23:55 +02:00
Matthew Olsson
e49ea1b520 LibJS: Disallow 'continue' & 'break' outside of their respective scopes
'continue' is no longer allowed outside of a loop, and an unlabeled
'break' is not longer allowed outside of a loop or switch statement.
Labeled 'break' statements are still allowed everywhere, even if the
label does not exist.
2020-10-08 10:20:49 +02:00
Matthew Olsson
9a82c22a85 LibJS: Disallow 'return' outside of a function 2020-10-08 10:03:21 +02:00
Linus Groh
5feb7e8d28 LibJS: Use PropertyName::from_value() in MemberExpression::computed_property_name()
No need for duplicating this logic.
2020-10-08 10:02:47 +02:00
Linus Groh
bc78e4b7da LibJS: Fix PropertyName::from_value() for negative and non-int numbers
It was converting *any* number to an i32 index, which obviously is not
correct for negative ints, doubles, infinity and nan.

Fixes #3712.
2020-10-08 10:02:47 +02:00
Andreas Kling
c541310e19 LibJS: Use IntrusiveList for Allocator's block lists
This way we don't need to deal with shifting vector storage, and most
operations are upgraded from O(n) to O(1) :^)
2020-10-07 14:07:31 +02:00
Andreas Kling
d1592643a6 LibJS: Make sure the HeapBlock cell storage is alignas(Cell) 2020-10-07 13:09:59 +02:00
Andreas Kling
48f13b7c3f LibJS: Split Heap into per-cell-size allocators
Instead of keeping all the HeapBlocks in one big list, we now split it
into two levels:

- Heap has a set of Allocators, each with a specific cell size.
- Allocators have two lists of blocks, "full" and "usable".

Allocating a new cell no longer has to scan the entire set of blocks,
but instead just needs to find the right allocator and then pop a cell
from its freelist. If all the blocks in the allocator are full, a new
block will be created.

Blocks are moved from the "full" to "usable" list after sweeping has
determined that they are not completely empty and not completely full.

There are certainly many ways we can improve on this. This patch is
mostly about getting the new allocator architecture in place. :^)
2020-10-06 18:50:47 +02:00
Andreas Kling
8baacda03d LibJS: Fix weird self-including header 2020-10-06 18:37:58 +02:00
Andreas Kling
4c33209011 LibJS: Add Object::define_property_without_transition() helper
This allows us to avoid transitioning in two common cases, saving some
time during object construction.
2020-10-06 17:43:51 +02:00
Andreas Kling
148c4161d9 LibJS: Avoid work in Shape::lookup() if there are no properties 2020-10-05 20:53:00 +02:00
Andreas Kling
69bae3fd9a LibJS: Prevent object shape transitions during runtime object buildup
While initialization common runtime objects like functions, prototypes,
etc, we don't really care about tracking transitions for each and every
property added to them.

This patch puts objects into a "disable transitions" mode while we call
initialize() on them. After that, adding more properties will cause new
transitions to be generated and added to the chain.

This gives a ~10% speed-up on test-js. :^)
2020-10-05 20:53:00 +02:00
Andreas Kling
50ab87f651 LibJS: Make use of existing property tables when reifying new ones
When reifying a shape transition chain, look for the nearest previous
shape in the transition chain that has a property table already, and
use that as the starting point.

This achieves two things:

1. We do less work when reifying property tables that already have
   partial property tables earlier in the chain.

2. This enables adding properties to a shape without performing a
   transition. This will be useful for initializing runtime objects
   with way fewer allocations. See next patch. :^)
2020-10-05 20:53:00 +02:00
Linus Groh
aa71dae03c LibJS: Implement logical assignment operators (&&=, ||=, ??=)
TC39 proposal, stage 4 as of 2020-07.
https://tc39.es/proposal-logical-assignment/
2020-10-05 17:57:26 +02:00
Nico Weber
d8d00d3ac7 LibJS: Add StringOrSymbol::as_string_impl() helper 2020-10-05 17:35:27 +02:00
Nico Weber
cc765e14ca AK: Move StringImpl::operator== implementation into StringImpl 2020-10-05 17:35:27 +02:00
Linus Groh
2d4cd5b49b LibJS: Evaluate AssignmentExpression LHS before RHS according to the spec
Fixes #3689.
2020-10-05 14:34:37 +02:00
Linus Groh
f4d0babd5d LibJS: Make assignment to CallExpression a syntax error in strict mode 2020-10-05 09:25:04 +02:00
Linus Groh
283ee678f7 LibJS: Validate all assignment expressions, not just "="
The check for invalid lhs and assignment to eval/arguments in strict
mode should happen for all kinds of assignment expressions, not just
AssignmentOp::Assignment.
2020-10-05 09:25:04 +02:00
Linus Groh
e80217a746 LibJS: Unify syntax highlighting
So far we have three different syntax highlighters for LibJS:

- js's Line::Editor stylization
- JS::MarkupGenerator
- GUI::JSSyntaxHighlighter

This not only caused repetition of most token types in each highlighter
but also a lot of inconsistency regarding the styling of certain tokens:

- JSSyntaxHighlighter was considering TokenType::Period to be an
  operator whereas MarkupGenerator categorized it as punctuation.
- MarkupGenerator was considering TokenType::{Break,Case,Continue,
  Default,Switch,With} control keywords whereas JSSyntaxHighlighter just
  disregarded them
- MarkupGenerator considered some future reserved keywords invalid and
  others not. JSSyntaxHighlighter and js disregarded most

Adding a new token type meant adding it to ENUMERATE_JS_TOKENS as well
as each individual highlighter's switch/case construct.

I added a TokenCategory enum, and each TokenType is now associated to a
certain category, which the syntax highlighters then can use for styling
rather than operating on the token type directly. This also makes
changing a token's category everywhere easier, should we need to do that
(e.g. I decided to make TokenType::{Period,QuestionMarkPeriod}
TokenCategory::Operator for now, but we might want to change them to
Punctuation.
2020-10-04 23:41:31 +02:00
Andreas Kling
fdb0ac7c1e LibJS: Remove some unused Interpreter member functions 2020-10-04 23:10:07 +02:00
Andreas Kling
94b95a4924 LibJS: Remove Interpreter::call()
Just use VM::call() directly everywhere.
2020-10-04 23:08:49 +02:00
Andreas Kling
ec55490198 LibJS: Make global objects have unique shape from the start
There's no point in trying to achieve shape sharing for global objects,
so we can simply make the shape unique from the start and avoid making
a transition chain.
2020-10-04 22:56:45 +02:00
Andreas Kling
2864cb66c0 LibJS: Avoid an unnecessary MarkedValueList copy in VM::call_internal() 2020-10-04 22:42:24 +02:00
Andreas Kling
2852ce4954 LibJS: Always inline HeapBlock::allocate()
This thing is so simple and sits on the hot path so just inline it.
2020-10-04 19:25:49 +02:00
Andreas Kling
ad0d377e4c LibJS: Pre-size the hash map and vector used in ensure_property_table() 2020-10-04 19:25:49 +02:00
Andreas Kling
b7975abef8 LibJS: Don't force property table reification on Shape::property_count()
Previously whenever you would ask a Shape how many properties it had,
it would reify the property table into a HashMap and use HashMap::size()
to answer the question.

This can be a huge waste of time if we don't need the property table for
anything else, so this patch implements property count tracking in a
separate integer member of Shape. :^)
2020-10-04 19:25:49 +02:00
Andreas Kling
d01b746d88 LibJS: Add StringOrSymbol constructor that takes a FlyString
This avoids refcount churn from implicit conversion in some places.
2020-10-04 19:25:49 +02:00
Andreas Kling
3d053f244f LibJS: Avoid creating a temporary String in StringOrSymbol::operator== 2020-10-04 19:25:49 +02:00