Otherwise these will get their name/default message from the Error
prototype, and as a result would always just say "Error" in error
messages, not the specific type.
Something I missed in da177c6, now with tests. :^)
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.
The previous handling of the name and message properties specifically
was breaking websites that created their own error types and relied on
the error prototype working correctly - not assuming an JS::Error this
object, that is.
The way it works now, and it is supposed to work, is:
- Error.prototype.name and Error.prototype.message just have initial
string values and are no longer getters/setters
- When constructing an error with a message, we create a regular
property on the newly created object, so a lookup of the message
property will either get it from the object directly or go though the
prototype chain
- Internal m_name/m_message properties are no longer needed and removed
This makes printing errors slightly more complicated, as we can no
longer rely on the (safe) internal properties, and cannot trust a
property lookup either - get_without_side_effects() is used to solve
this, it's not perfect but something we can revisit later.
I did some refactoring along the way, there was some really old stuff in
there - accessing vm.call_frame().arguments[0] is not something we (have
to) do anymore :^)
Fixes#6245.
Similar to Value::to_string_without_side_effects() this is mostly a
regular object property lookup, but with the guarantee that it will be
side-effect free, i.e. no accessors or native property functions will
be called. This is needed when we want to access user-controlled object
properties for debug logging, for example. The specific use case will be
error objects which will soon no longer have internal name/message
properties, so we need to guarantee that printing an error, which may
already be the result of an exception, won't blow up in our face :^)
This fixes two cases of indexed access (array holes, out-of-bounds
string object access) where we would not follow the prototype chain and
incorrectly return undefined:
// Should be "a", returned undefined
Object.setPrototypeOf([,], ["a"])[0]
// Should be "a", returned undefined
Object.setPrototypeOf(new String(""), new String("a"))[0]
The actual fix is simple, instead of returning early if the requested
index is past the string's length or within the indexed properties size
but has no value, we just continue the prototype chain traversal and get
correct behaviour from that.
Otherwise the GC won't be able to keep track of the stored values
and they may suddenly disappear from underneath us, thinking
they're not in use.
Fixes a crash when going to the Bootstrap website, where the first
replace call in jQuery caused a jump to a bogus address.
This commit makes the user-facing StdLibExtras templates and utilities
arguably more nice-looking by removing the need to reach into the
wrapper structs generated by them to get the value/type needed.
The C++ standard library had to invent `_v` and `_t` variants (likely
because of backwards compat), but we don't need to cater to any codebase
except our own, so might as well have good things for free. :^)
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.
The new default return_type argument is GetOwnPropertyReturnType::All,
which returns properties with both string and symbol keys (which is also
the default for [[OwnPropertyKeys]]). This means that in some cases we
need to iterate the ordered property table twice, as we don't store
string and symbol properties separately but symbols must - there's
certainly room for (performance) improvements here. On the other hand
this makes Reflect.ownKeys() return symbol properties now :^)
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.
This was super confusing as we would check if the exception's value is a
JS::Error and not log it otherwise, even with m_should_log_exceptions
set.
As a result, things like DOM exceptions were invisible to us with
execution just silently stopping, for example.
By using regex::AllFlags::SkipTrimEmptyMatches we get a null string for
unmatched capture groups, which we then turn into an undefined entry in
the result array instead of putting all matches first and appending
undefined for the remaining number of capture groups - e.g. for
/foo(ba((r)|(z)))/.exec("foobaz")
we now return
["foobaz", "baz", "z", undefined, "z"]
and not [
["foobaz", "baz", "z", "z", undefined]
Fixes part of #6042.
Also happens to fix selecting an element by ID using jQuery's $("#foo").
This fixes an issue where `undefined.foo = "bar"` would throw a
ReferenceError instead of a TypeError as undefined was also used for
truly unresolvable references (e.g. `foo() = "bar"`). I also made the
various error messages here a bit nicer, just "primitive value" is not
very helpful.
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().
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
1. Allow Value(size_t) and use it for array length properties.
If an array length can't fit in an Int32 value, we shouldn't go out of
or way to force it into one. Instead, for values above INT32_MAX,
we simply store them as Double values.
2. Switch to generic indexed property storage for large arrays.
Previously we would always allocate array storage eagerly when the
length property was set. This meant that "a.length = 0x80000000" would
trivially DOS the engine on 32-bit since we don't have that much VM.
We now switch to generic storage when changing the length moves us over
the 4M entry mark.
Fixes#5986.
Just a sanity check, this must never happen. Manual invocations of
call() e.g. in LibWeb bindings must pass js_undefined() if no specific
this value is desired, which OrdinaryCallBindThis will then use as-is or
turn into the global this value in non-strict mode.
This commit replaces the usage of String::starts_with with
Utf8View::starts_with, which first decodes the utf8 encoded
string, and as such can take things like overlong encoded
sequences into account (which could otherwise cause the prefix
check to be inconsistent with the following code points check).
Since we have Value::Type::Int32 now, let's use the Value(i32)
constructor here directly by not casting these i32 values to doubles.
The Value(double) would also figure out that these can be stored as
integers, but needs to do extra work which is not needed here. :^)
Just like to_size_t() - which was already removed in f369229 - this is
non-standard, use to_length() instead. One remaining use was removed,
and I'm glad it's gone. :^)
toGMTString() is deprecated but is kept for compatibility's sake, but because
HTTP Dates are always expressed in GMT, it should be safe to call toUTCString()
in toGMTString().
As @nico pointed out, 0.0 == -0.0 in C++, even though they are not
bitwise identical. Use the same trick as Value::is_negative_zero() to
really check for it.
This allows JS::Value(0.0) to correctly become an Int32-backed 0 value.
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.