The old name is the result of the perhaps somewhat confusingly named
abstract operation OrdinaryFunctionCreate(), which creates an "ordinary
object" (https://tc39.es/ecma262/#ordinary-object) in contrast to an
"exotic object" (https://tc39.es/ecma262/#exotic-object).
However, the term "Ordinary Function" is not used anywhere in the spec,
instead the created object is referred to as an "ECMAScript Function
Object" (https://tc39.es/ecma262/#sec-ecmascript-function-objects), so
let's call it that.
The "ordinary" vs. "exotic" distinction is important because there are
also "Built-in Function Objects", which can be either implemented as
ordinary ECMAScript function objects, or as exotic objects (our
NativeFunction).
More work needs to be done to move a lot of infrastructure to
ECMAScriptFunctionObject in order to make FunctionObject nothing more
than an interface for objects that implement [[Call]] and optionally
[[Construct]].
In the spec, this happens in the EvaluateCall abstract operation
(https://tc39.es/ecma262/#sec-evaluatecall), and the order is defined
as:
3. Let argList be ? ArgumentListEvaluation of arguments.
4. If Type(func) is not Object, throw a TypeError exception.
5. If IsCallable(func) is false, throw a TypeError exception.
In LibJS this is handled by CallExpression::execute(), which had the
callee function check first and would therefore never evaluate the
arguments for a non-function callee.
The spec requires that invalid RegExp literals must cause a Syntax Error
before the JavaScript is executed. See:
https://tc39.es/ecma262/#sec-patterns-static-semantics-early-errors
This is explicitly tested in the RegExp/property-escapes test262 tests.
For example, see unsupported-property-Line_Break.js:
$DONOTEVALUATE();
/\p{Line_Break}/u;
That RegExp literal is invalid because Line_Break is not a supported
Unicode property. $DONOTEVALUATE() just throws an exception when it is
executed. The test expects that this file will fail to be parsed.
Note that RegExp patterns can still be parsed at execution time by way
of "new RegExp(...)".
This is not a functional change, the exposed (incorrect) behaviour is
the same as it was before, this simply removes the last user of
NativeProperties, allowing us to remove them completely from LibJS.
It's way too easy to get this wrong: for the IsArray abstract operation,
Value::is_array() needs to be called. Since we have RTTI, the virtual
Object::is_array() method is not needed anymore - if we need to know
whether something is *actually* a JS::Array (we currently check in more
cases than we should, I think) and not a Proxy with an Array target, we
should do that in a way that doesn't look like an abstract operation.
This removes all usages of the non-standard define_property helper
method and replaces all it's usages with the specification required
alternative or with define_direct_property where appropriate.
This is a huge patch, I know. In hindsight this perhaps could've been
done slightly more incremental, but I started and then fixed everything
until it worked, and here we are. I tried splitting of some completely
unrelated changes into separate commits, however. Anyway.
This is a rewrite of most of Object, and by extension large parts of
Array, Proxy, Reflect, String, TypedArray, and some other things.
What we already had worked fine for about 90% of things, but getting the
last 10% right proved to be increasingly difficult with the current code
that sort of grew organically and is only very loosely based on the
spec - this became especially obvious when we started fixing a large
number of test262 failures.
Key changes include:
- 1:1 matching function names and parameters of all object-related
functions, to avoid ambiguity. Previously we had things like put(),
which the spec doesn't have - as a result it wasn't always clear which
need to be used.
- Better separation between object abstract operations and internal
methods - the former are always the same, the latter can be overridden
(and are therefore virtual). The internal methods (i.e. [[Foo]] in the
spec) are now prefixed with 'internal_' for clarity - again, it was
previously not always clear which AO a certain method represents,
get() could've been both Get and [[Get]] (I don't know which one it
was closer to right now).
Note that some of the old names have been kept until all code relying
on them is updated, but they are now simple wrappers around the
closest matching standard abstract operation.
- Simplifications of the storage layer: functions that write values to
storage are now prefixed with 'storage_' to make their purpose clear,
and as they are not part of the spec they should not contain any steps
specified by it. Much functionality is now covered by the layers above
it and was removed (e.g. handling of accessors, attribute checks).
- PropertyAttributes has been greatly simplified, and is being replaced
by PropertyDescriptor - a concept similar to the current
implementation, but more aligned with the actual spec. See the commit
message of the previous commit where it was introduced for details.
- As a bonus, and since I had to look at the spec a whole lot anyway, I
introduced more inline comments with the exact steps from the spec -
this makes it super easy to verify correctness.
- East-const all the things.
As a result of all of this, things are much more correct but a bit
slower now. Retaining speed wasn't a consideration at all, I have done
no profiling of the new code - there might be low hanging fruits, which
we can then harvest separately.
Special thanks to Idan for helping me with this by tracking down bugs,
updating everything outside of LibJS to work with these changes (LibWeb,
Spreadsheet, HackStudio), as well as providing countless patches to fix
regressions I introduced - there still are very few (we got it down to
5), but we also get many new passing test262 tests in return. :^)
Co-authored-by: Idan Horowitz <idan.horowitz@gmail.com>
This was almost entirely up-to-spec already, just missing exception
checks, and we now leave the lexical environment in the modified state
if an exception occurs during statement evaluation.
Specifically, this now explicitly takes the length, adds missing
exceptions checks to calls with user-supplied lengths, takes and uses
the prototype argument, and fixes some spec non-conformance in
ArrayConstructor and its native functions around the use of ArrayCreate
This patch implements spec-compliant runtime semantics for the following
constructs:
- super.property
- super[property]
The MakeSuperPropertyReference AO is added to support this. :^)
ResolveBinding now matches the spec, while the non-conforming parts
are moved to GetIdentifierReference.
Implementing this properly requires variable bindings.
This patch adds an override for NewExpression::execute() in the AST
interpreter to separate the logic from CallExpression. As a result,
both evaluation functions are simplified.
Both expressions are still largely non-conforming, but this makes
it easier to work on improving that since we can now deal with them
separately. :^)
Our Reference class now has the same fields as the spec:
- Base (a non-nullish value, an environment record, or `unresolvable`)
- Referenced Name (the name of the binding)
- Strict (whether the reference originated in strict mode code)
- ThisValue (if non-empty, the reference represents a `super` keyword)
The main difference from before is that we now resolve the environment
record that a reference interacts with. Previously we simply resolved
to either "local variable" or "global variable".
The associated abstract operations are still largely non-conforming,
since we don't yet implement proper variable bindings. But this patch
should at least fix a handful of test262 cases. :^)
There's one minor regression: some TypeError message strings get
a little worse due to doing a RequireObjectCoercible earlier in the
evaluation of MemberExpression.
The parser doesn't always track lexical scopes correctly, so let's not
rely on that for direct argument loading.
This reverts the LoadArguments bytecode instruction as well. We can
bring these things back when the parser can reliably tell us that
a given Identifier is indeed a function argument.
To better follow the spec, we need to distinguish between the current
execution context's lexical environment and variable environment.
This patch moves us to having two record pointers, although both of
them point at the same environment records for now.
This patch adds FunctionEnvironmentRecord as a subclass of the existing
DeclarativeEnvironmentRecord. Things that are specific to function
environment records move into there, simplifying the base.
Most of the abstract operations related to function environment records
are rewritten to match the spec exactly. I also had to implement
GetThisEnvironment() and GetSuperConstructor() to keep tests working
after the changes, so that's nice as well. :^)
This patch makes the following renames:
- get_from_scope() => get_from_environment_record()
- put_to_scope() => put_into_environment_record()
- delete_from_scope() => delete_from_environment_record()
This patch makes the following name changes:
- ScopeObject => EnvironmentRecord
- LexicalEnvironment => DeclarativeEnvironmentRecord
- WithScope => ObjectEnvironmentRecord
This now matches the spec's OrdinaryObjectCreate() across the board:
instead of implicitly setting the created object's prototype to
%Object.prototype% and then in many cases setting it to a nullptr right
away, it now has an 'Object* prototype' parameter with _no default
value_. This makes the code easier to compare with the spec, very clear
in terms of what prototype is being used as well as avoiding unnecessary
shape transitions.
Also fixes a couple of cases were we weren't setting the correct
prototype.
There's no reason to assume that the object would not be empty (as in
having own properties), so let's follow our existing pattern of
Type::create(...) and simply call it 'create'.
Instead of doing a generic scoped variable lookup, function arguments
now go directly to the call frame arguments list.
This is a huge speedup on everything that uses arguments. :^)
This patch adds an "argument index" field to Identifier AST nodes.
If the Identifier refers to a function parameter in the currently
open function scope, we stash the index of the parameter here.
This will allow us to implement much faster direct access to function
argument variables.
The addition of an is_generator parameter broke this, as is_strict was
being passed in, causing an assertion.
This is being addressed by changing it to an enum in #7981, but in the
meantime let's just fix these two cases.
This was missing from Value::is_array(), which is equivalent to the
spec's IsArray() abstract operation - it treats a Proxy value with an
Array target object as being an Array.
It can throw, so needs both the global object and an exception check
now.
Previously SwitchStatement::execute() would return <empty> when hitting
break, continue or empty consequent block. This was not in line with
the standard.
- NewString (allocates a new PrimitiveString from the GC heap)
- GetVariable (retrieves a variable in the current scope)
- SetVariable (assigns a variable in the current scope)
This patch begins the work of implementing JavaScript execution in a
bytecode VM instead of an AST tree-walk interpreter.
It's probably quite naive, but we have to start somewhere.
The basic idea is that you call Bytecode::Generator::generate() on an
AST node and it hands you back a Bytecode::Block filled with
instructions that can then be interpreted by a Bytecode::Interpreter.
This first version only implements two instructions: Load and Add. :^)
Each bytecode block has infinity registers, and the interpreter resizes
its register file to fit the block being executed.
Two new `js` options are added in this patch as well:
`-d` will dump the generated bytecode
`-b` will execute the generated bytecode
Note that unless `-d` and/or `-b` are specified, none of the bytecode
related stuff in LibJS runs at all. This is implemented in parallel
with the existing AST interpreter. :^)
This fixes an issue where this would be bound to the global object
by default when operating in strict mode.
According to the specification, the expected value for |this| when
no binding is provided is undefined.
The TryStatement handler execution creates a new LexicalEnvironment
without a current function set, which we were not accounting for when
trying to get the super constructor while executing a SuperExpression.
This makes it work but isn't pretty - this needs some refactoring to be
close to the spec for that to happen.
Fixes#7045.
In HackStudio's Debugger a custom GlobalObject is used to reflect
debugger variables into the JS scope by overriding GlobalObject's
get method. However, when throwing a custom error during that lookup
it was replaced with the generic "not found" js exception. This patch
makes it instead pass along the custom error.
SPDX License Identifiers are a more compact / standardized
way of representing file license information.
See: https://spdx.dev/resources/use/#identifiers
This was done with the `ambr` search and replace tool.
ambr --no-parent-ignore --key-from-file --rep-from-file key.txt rep.txt *
This has the nice side effect of giving us a decent error message for
something like undefined.foo() - another useless "ToObject on null or
undefined" gone. :^)
Also turn the various ternary operators into two separate if branches,
they don't really share that much.
Sometimes we just want to set m_exception to some value we stored
previously, without really "throwing" it again - that's what
set_exception() does now. Since we have clear_exception(), it does take
a reference, i.e. you don't set_exception(nullptr). For consistency I
updated throw_exception() to do the same.
Previously we would always return the result of executing the finalizer,
however the spec dictates the finalizer result must only be returned for
a non-normal completion.
I added some more comments along the way, which should make it more
clear what's going on - the unwinding and exception flow isn't super
straightforward here.
This implements the missing step 6a of 14.7.5.6 ForIn/OfHeadEvaluation:
a. If exprValue is undefined or null, then
i. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }.
In other words, this should just do nothing instead of throwing during
the undefined to object coercion:
for (const x in undefined);
This replaces the current 'function plus boolean indicating the type'
API, which makes it easier to set both getter and setter at once.
This was already possible before but required two calls of this
function, which wasn't intuitive:
define_accessor(name, getter, true, ...);
define_accessor(name, setter, false, ...);
Which now becomes:
define_accessor(name, getter, setter, ...);
Letting these create and return a JS::Array directly is pretty awkward
since we then need to go through the indexed properties for iteration.
Just use a MarkedValueList (i.e. Vector<Value>) for this and add a new
Array::create_from() function to turn the Vector into a returnable
Array as we did before.
This brings it a lot closer to the spec as well, which uses the
CreateArrayFromList abstract operation to do exactly this.
There's an optimization opportunity for the future here, since we know
the Vector's size we could prepare the newly created Array accordingly,
e.g. by switching to generic storage upfront if needed.
Object::get_own_properties() is a bit unwieldy to use - especially as
StringOnly is about to no longer be the default value. The spec has an
abstract operation specifically for this (EnumerateObjectProperties),
so let's use that. No functionality change.
Specifically:
- Object::get_own_properties()
- Object::put_own_property()
- Object::put_own_property_by_index()
These APIs make no sense (and are inconsistent, get_own_property()
didn't have this parameter, for example) - and as expected we were
always passing in the same object we were calling the method on anyway.
Not sure if this regressed at some point or just never worked, it
definitely wasn't tested at all. We would always return undefined when
returning from a try statement block, handler, or finalizer.
We should be able to get the 'typeof' string for any value directly, so
this is now a standalone Value::typeof() method instead of being part of
UnaryExpression::execute().
The auto naming of function expressions is a purely syntactic
decision, so shouldn't be decided based on the dynamic type of
an assignment. This moves the decision making into the parser.
One icky hack is that we add a field to FunctionExpression to
indicate whether we can autoname. The real solution is to actually
generate a CompoundExpression node so that the parser can make
the correct decision, however this would have a potentially
significant run time cost.
This does not correct the behaviour for class expressions.
Patch from Anonymous.
We now store 32-bit integers as 32-bit integers directly which avoids
having to convert them from doubles when they're only used as 32-bit
integers anyway. :^)
This patch feels a bit incomplete and there's a lot of opportunities
to take advantage of this information. We'll have to find and exploit
them eventually.
Previously we would generate function names for anonymous functions
on every AssignmentExpression, even if we weren't assigning a function.
We were also setting names of anonymous functions in arrays, which is
apparently a SpiderMonkey specific behavior not supported by V8, JSC
or required by ECMA262. This patch removes that behavior.
This is a huge performance improvement on the CanvasCycle demo! :^)
For various statements the spec states:
Return NormalCompletion(empty).
In those cases we have been returning undefined so far, which is
incorrect.
In other cases it states:
Return Completion(UpdateEmpty(stmtCompletion, undefined)).
Which essentially means a statement is evaluated and its completion
value returned if non-empty, and undefined otherwise.
While not actually noticeable in normal scripts as the VM's "last value"
can't be accessed from JS code directly (with the exception of eval(),
see below), it provided an inconsistent experience in the REPL:
> if (true) 42;
42
> if (true) { 42; }
undefined
This also fixes the case where eval() would return undefined if the last
executed statement is not a value-producing one:
eval("1;;;;;")
eval("1;{}")
eval("1;var a;")
As a consequence of the changes outlined above, these now all correctly
return 1.
See https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation,
"NOTE 2".
Fixes#3609.
(...and ASSERT_NOT_REACHED => VERIFY_NOT_REACHED)
Since all of these checks are done in release builds as well,
let's rename them to VERIFY to prevent confusion, as everyone is
used to assertions being compiled out in release.
We can introduce a new ASSERT macro that is specifically for debug
checks, but I'm doing this wholesale conversion first since we've
accumulated thousands of these already, and it's not immediately
obvious which ones are suitable for ASSERT.
If it's missing we get an empty value, but we can't use that with
to_string_without_side_effects() so we have to use undefined as the
default.
Fixes#5142.
If we have a function as class extends value, we still cannot assume
that it has a prototype property and that property has a function or
null as its value - blindly calling to_object() on it may fail.
Fixes#5075.
Without this, the oss-fuzz build says:
../Userland/Libraries/LibJS/AST.cpp:58:34: error: member access into incomplete type 'const std::type_info'
return demangle(typeid(*this).name()).substring(4);
^