ladybird/Userland/Libraries/LibJS/Bytecode/Op.cpp

1380 lines
57 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/HashTable.h>
#include <LibJS/AST.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Op.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Environment.h>
#include <LibJS/Runtime/FunctionEnvironment.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Bytecode {
DeprecatedString Instruction::to_deprecated_string(Bytecode::Executable const& executable) const
{
#define __BYTECODE_OP(op) \
case Instruction::Type::op: \
return static_cast<Bytecode::Op::op const&>(*this).to_deprecated_string_impl(executable);
switch (type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
}
namespace JS::Bytecode::Op {
static ThrowCompletionOr<void> put_by_property_key(Object* object, Value value, PropertyKey name, Bytecode::Interpreter& interpreter, PropertyKind kind)
{
auto& vm = interpreter.vm();
if (kind == PropertyKind::Getter || kind == PropertyKind::Setter) {
// The generator should only pass us functions for getters and setters.
VERIFY(value.is_function());
}
switch (kind) {
case PropertyKind::Getter: {
auto& function = value.as_function();
if (function.name().is_empty() && is<ECMAScriptFunctionObject>(function))
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(DeprecatedString::formatted("get {}", name));
object->define_direct_accessor(name, &function, nullptr, Attribute::Configurable | Attribute::Enumerable);
break;
}
case PropertyKind::Setter: {
auto& function = value.as_function();
if (function.name().is_empty() && is<ECMAScriptFunctionObject>(function))
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(DeprecatedString::formatted("set {}", name));
object->define_direct_accessor(name, nullptr, &function, Attribute::Configurable | Attribute::Enumerable);
break;
}
case PropertyKind::KeyValue: {
bool succeeded = TRY(object->internal_set(name, interpreter.accumulator(), object));
if (!succeeded && vm.in_strict_mode())
return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, interpreter.accumulator().to_string_without_side_effects());
break;
}
case PropertyKind::Spread:
TRY(object->copy_data_properties(vm, value, {}));
break;
case PropertyKind::ProtoSetter:
if (value.is_object() || value.is_null())
MUST(object->internal_set_prototype_of(value.is_object() ? &value.as_object() : nullptr));
break;
}
return {};
}
ThrowCompletionOr<void> Load::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.accumulator() = interpreter.reg(m_src);
return {};
}
ThrowCompletionOr<void> LoadImmediate::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.accumulator() = m_value;
return {};
}
ThrowCompletionOr<void> Store::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.reg(m_dst) = interpreter.accumulator();
return {};
}
static ThrowCompletionOr<Value> abstract_inequals(VM& vm, Value src1, Value src2)
{
return Value(!TRY(is_loosely_equal(vm, src1, src2)));
}
static ThrowCompletionOr<Value> abstract_equals(VM& vm, Value src1, Value src2)
{
return Value(TRY(is_loosely_equal(vm, src1, src2)));
}
static ThrowCompletionOr<Value> typed_inequals(VM&, Value src1, Value src2)
{
return Value(!is_strictly_equal(src1, src2));
}
static ThrowCompletionOr<Value> typed_equals(VM&, Value src1, Value src2)
{
return Value(is_strictly_equal(src1, src2));
}
#define JS_DEFINE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \
ThrowCompletionOr<void> OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \
{ \
auto& vm = interpreter.vm(); \
auto lhs = interpreter.reg(m_lhs_reg); \
auto rhs = interpreter.accumulator(); \
interpreter.accumulator() = TRY(op_snake_case(vm, lhs, rhs)); \
return {}; \
} \
DeprecatedString OpTitleCase::to_deprecated_string_impl(Bytecode::Executable const&) const \
{ \
return DeprecatedString::formatted(#OpTitleCase " {}", m_lhs_reg); \
}
JS_ENUMERATE_COMMON_BINARY_OPS(JS_DEFINE_COMMON_BINARY_OP)
static ThrowCompletionOr<Value> not_(VM&, Value value)
{
return Value(!value.to_boolean());
}
static ThrowCompletionOr<Value> typeof_(VM& vm, Value value)
{
return Value(PrimitiveString::create(vm, value.typeof()));
}
#define JS_DEFINE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \
ThrowCompletionOr<void> OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \
{ \
auto& vm = interpreter.vm(); \
interpreter.accumulator() = TRY(op_snake_case(vm, interpreter.accumulator())); \
return {}; \
} \
DeprecatedString OpTitleCase::to_deprecated_string_impl(Bytecode::Executable const&) const \
{ \
return #OpTitleCase; \
}
JS_ENUMERATE_COMMON_UNARY_OPS(JS_DEFINE_COMMON_UNARY_OP)
ThrowCompletionOr<void> NewBigInt::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
interpreter.accumulator() = BigInt::create(vm, m_bigint);
return {};
}
ThrowCompletionOr<void> NewArray::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto array = MUST(Array::create(interpreter.realm(), 0));
for (size_t i = 0; i < m_element_count; i++) {
auto& value = interpreter.reg(Register(m_elements[0].index() + i));
array->indexed_properties().put(i, value, default_attributes);
}
interpreter.accumulator() = array;
return {};
}
ThrowCompletionOr<void> Append::execute_impl(Bytecode::Interpreter& interpreter) const
{
// Note: This OpCode is used to construct array literals and argument arrays for calls,
// containing at least one spread element,
// Iterating over such a spread element to unpack it has to be visible by
// the user courtesy of
// (1) https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation
// SpreadElement : ... AssignmentExpression
// 1. Let spreadRef be ? Evaluation of AssignmentExpression.
// 2. Let spreadObj be ? GetValue(spreadRef).
// 3. Let iteratorRecord be ? GetIterator(spreadObj).
// 4. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
// b. If next is false, return nextIndex.
// c. Let nextValue be ? IteratorValue(next).
// d. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(nextIndex)), nextValue).
// e. Set nextIndex to nextIndex + 1.
// (2) https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
// ArgumentList : ... AssignmentExpression
// 1. Let list be a new empty List.
// 2. Let spreadRef be ? Evaluation of AssignmentExpression.
// 3. Let spreadObj be ? GetValue(spreadRef).
// 4. Let iteratorRecord be ? GetIterator(spreadObj).
// 5. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
// b. If next is false, return list.
// c. Let nextArg be ? IteratorValue(next).
// d. Append nextArg to list.
// ArgumentList : ArgumentList , ... AssignmentExpression
// 1. Let precedingArgs be ? ArgumentListEvaluation of ArgumentList.
// 2. Let spreadRef be ? Evaluation of AssignmentExpression.
// 3. Let iteratorRecord be ? GetIterator(? GetValue(spreadRef)).
// 4. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
// b. If next is false, return precedingArgs.
// c. Let nextArg be ? IteratorValue(next).
// d. Append nextArg to precedingArgs.
auto& vm = interpreter.vm();
// Note: We know from codegen, that lhs is a plain array with only indexed properties
auto& lhs = interpreter.reg(m_lhs).as_array();
auto lhs_size = lhs.indexed_properties().array_like_size();
auto rhs = interpreter.accumulator();
if (m_is_spread) {
// ...rhs
size_t i = lhs_size;
TRY(get_iterator_values(vm, rhs, [&i, &lhs](Value iterator_value) -> Optional<Completion> {
lhs.indexed_properties().put(i, iterator_value, default_attributes);
++i;
return {};
}));
} else {
lhs.indexed_properties().put(lhs_size, rhs, default_attributes);
}
return {};
}
// FIXME: Since the accumulator is a Value, we store an object there and have to convert back and forth between that an Iterator records. Not great.
// Make sure to put this into the accumulator before the iterator object disappears from the stack to prevent the members from being GC'd.
static Object* iterator_to_object(VM& vm, Iterator iterator)
{
auto& realm = *vm.current_realm();
auto object = Object::create(realm, nullptr);
object->define_direct_property(vm.names.iterator, iterator.iterator, 0);
object->define_direct_property(vm.names.next, iterator.next_method, 0);
object->define_direct_property(vm.names.done, Value(iterator.done), 0);
return object;
}
static Iterator object_to_iterator(VM& vm, Object& object)
{
return Iterator {
.iterator = &MUST(object.get(vm.names.iterator)).as_object(),
.next_method = MUST(object.get(vm.names.next)),
.done = MUST(object.get(vm.names.done)).as_bool()
};
}
ThrowCompletionOr<void> IteratorToArray::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto iterator_object = TRY(interpreter.accumulator().to_object(vm));
auto iterator = object_to_iterator(vm, *iterator_object);
auto array = MUST(Array::create(interpreter.realm(), 0));
size_t index = 0;
while (true) {
auto* iterator_result = TRY(iterator_next(vm, iterator));
auto complete = TRY(iterator_complete(vm, *iterator_result));
if (complete) {
interpreter.accumulator() = array;
return {};
}
auto value = TRY(iterator_value(vm, *iterator_result));
MUST(array->create_data_property_or_throw(index, value));
index++;
}
return {};
}
ThrowCompletionOr<void> NewString::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.accumulator() = PrimitiveString::create(interpreter.vm(), interpreter.current_executable().get_string(m_string));
return {};
}
ThrowCompletionOr<void> NewObject::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& realm = *vm.current_realm();
interpreter.accumulator() = Object::create(realm, realm.intrinsics().object_prototype());
return {};
}
ThrowCompletionOr<void> NewRegExp::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto source = interpreter.current_executable().get_string(m_source_index);
auto flags = interpreter.current_executable().get_string(m_flags_index);
interpreter.accumulator() = TRY(regexp_create(vm, PrimitiveString::create(vm, source), PrimitiveString::create(vm, flags)));
return {};
}
2022-12-09 18:48:57 +00:00
#define JS_DEFINE_NEW_BUILTIN_ERROR_OP(ErrorName) \
ThrowCompletionOr<void> New##ErrorName::execute_impl(Bytecode::Interpreter& interpreter) const \
{ \
auto& vm = interpreter.vm(); \
auto& realm = *vm.current_realm(); \
interpreter.accumulator() = ErrorName::create(realm, interpreter.current_executable().get_string(m_error_string)); \
return {}; \
} \
DeprecatedString New##ErrorName::to_deprecated_string_impl(Bytecode::Executable const& executable) const \
{ \
return DeprecatedString::formatted("New" #ErrorName " {} (\"{}\")", m_error_string, executable.string_table->get(m_error_string)); \
}
JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DEFINE_NEW_BUILTIN_ERROR_OP)
ThrowCompletionOr<void> CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& realm = *vm.current_realm();
auto* from_object = TRY(interpreter.reg(m_from_object).to_object(vm));
auto to_object = Object::create(realm, realm.intrinsics().object_prototype());
HashTable<Value, ValueTraits> excluded_names;
for (size_t i = 0; i < m_excluded_names_count; ++i)
excluded_names.set(interpreter.reg(m_excluded_names[i]));
auto own_keys = TRY(from_object->internal_own_property_keys());
for (auto& key : own_keys) {
if (!excluded_names.contains(key)) {
auto property_key = TRY(key.to_property_key(vm));
auto property_value = TRY(from_object->get(property_key));
to_object->define_direct_property(property_key, property_value, JS::default_attributes);
}
}
interpreter.accumulator() = to_object;
return {};
}
ThrowCompletionOr<void> ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
interpreter.reg(m_lhs) = TRY(add(vm, interpreter.reg(m_lhs), interpreter.accumulator()));
return {};
}
ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto get_reference = [&]() -> ThrowCompletionOr<Reference> {
auto const& string = interpreter.current_executable().get_identifier(m_identifier);
if (m_cached_environment_coordinate.has_value()) {
Environment* environment = nullptr;
if (m_cached_environment_coordinate->index == EnvironmentCoordinate::global_marker) {
environment = &interpreter.vm().current_realm()->global_environment();
} else {
environment = vm.running_execution_context().lexical_environment;
for (size_t i = 0; i < m_cached_environment_coordinate->hops; ++i)
environment = environment->outer_environment();
VERIFY(environment);
VERIFY(environment->is_declarative_environment());
}
if (!environment->is_permanently_screwed_by_eval()) {
return Reference { *environment, string, vm.in_strict_mode(), m_cached_environment_coordinate };
}
m_cached_environment_coordinate = {};
}
auto reference = TRY(vm.resolve_binding(string));
if (reference.environment_coordinate().has_value())
m_cached_environment_coordinate = reference.environment_coordinate();
return reference;
};
auto reference = TRY(get_reference());
interpreter.accumulator() = TRY(reference.get_value(vm));
return {};
}
ThrowCompletionOr<void> DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto const& string = interpreter.current_executable().get_identifier(m_identifier);
auto reference = TRY(vm.resolve_binding(string));
interpreter.accumulator() = Value(TRY(reference.delete_(vm)));
return {};
}
ThrowCompletionOr<void> CreateEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto make_and_swap_envs = [&](auto*& old_environment) {
Environment* environment = new_declarative_environment(*old_environment).ptr();
swap(old_environment, environment);
return environment;
};
if (m_mode == EnvironmentMode::Lexical)
interpreter.saved_lexical_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().lexical_environment));
else if (m_mode == EnvironmentMode::Var)
interpreter.saved_variable_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().variable_environment));
return {};
}
ThrowCompletionOr<void> EnterObjectEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto& old_environment = vm.running_execution_context().lexical_environment;
interpreter.saved_lexical_environment_stack().append(old_environment);
auto object = TRY(interpreter.accumulator().to_object(vm));
vm.running_execution_context().lexical_environment = new_object_environment(*object, true, old_environment);
return {};
}
ThrowCompletionOr<void> CreateVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 12:44:56 +02:00
auto& vm = interpreter.vm();
auto const& name = interpreter.current_executable().get_identifier(m_identifier);
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 12:44:56 +02:00
if (m_mode == EnvironmentMode::Lexical) {
VERIFY(!m_is_global);
// Note: This is papering over an issue where "FunctionDeclarationInstantiation" creates these bindings for us.
// Instead of crashing in there, we'll just raise an exception here.
if (TRY(vm.lexical_environment()->has_binding(name)))
return vm.throw_completion<InternalError>(DeprecatedString::formatted("Lexical environment already has binding '{}'", name));
if (m_is_immutable)
vm.lexical_environment()->create_immutable_binding(vm, name, vm.in_strict_mode());
else
vm.lexical_environment()->create_mutable_binding(vm, name, vm.in_strict_mode());
} else {
if (!m_is_global) {
if (m_is_immutable)
vm.variable_environment()->create_immutable_binding(vm, name, vm.in_strict_mode());
else
vm.variable_environment()->create_mutable_binding(vm, name, vm.in_strict_mode());
} else {
// NOTE: CreateVariable with m_is_global set to true is expected to only be used in GlobalDeclarationInstantiation currently, which only uses "false" for "can_be_deleted".
// The only area that sets "can_be_deleted" to true is EvalDeclarationInstantiation, which is currently fully implemented in C++ and not in Bytecode.
verify_cast<GlobalEnvironment>(vm.variable_environment())->create_global_var_binding(name, false);
}
}
return {};
}
ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto const& name = interpreter.current_executable().get_identifier(m_identifier);
auto environment = m_mode == EnvironmentMode::Lexical ? vm.running_execution_context().lexical_environment : vm.running_execution_context().variable_environment;
auto reference = TRY(vm.resolve_binding(name, environment));
switch (m_initialization_mode) {
case InitializationMode::Initialize:
TRY(reference.initialize_referenced_binding(vm, interpreter.accumulator()));
break;
case InitializationMode::Set:
TRY(reference.put_value(vm, interpreter.accumulator()));
break;
case InitializationMode::InitializeOrSet:
VERIFY(reference.is_environment_reference());
VERIFY(reference.base_environment().is_declarative_environment());
TRY(static_cast<DeclarativeEnvironment&>(reference.base_environment()).initialize_or_set_mutable_binding(vm, name, interpreter.accumulator()));
break;
}
return {};
}
ThrowCompletionOr<void> GetById::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.accumulator().to_object(vm));
interpreter.accumulator() = TRY(object->get(interpreter.current_executable().get_identifier(m_property)));
return {};
}
ThrowCompletionOr<void> PutById::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.reg(m_base).to_object(vm));
PropertyKey name = interpreter.current_executable().get_identifier(m_property);
auto value = interpreter.accumulator();
return put_by_property_key(object, value, name, interpreter, m_kind);
}
ThrowCompletionOr<void> DeleteById::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.accumulator().to_object(vm));
auto const& identifier = interpreter.current_executable().get_identifier(m_property);
bool strict = vm.in_strict_mode();
auto reference = Reference { object, identifier, {}, strict };
interpreter.accumulator() = Value(TRY(reference.delete_(vm)));
return {};
};
ThrowCompletionOr<void> Jump::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.jump(*m_true_target);
return {};
}
ThrowCompletionOr<void> ResolveThisBinding::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
interpreter.accumulator() = TRY(vm.resolve_this_binding());
return {};
}
ThrowCompletionOr<void> GetNewTarget::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.accumulator() = interpreter.vm().get_new_target();
return {};
}
void Jump::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (m_true_target.has_value() && &m_true_target->block() == &from)
m_true_target = Label { to };
if (m_false_target.has_value() && &m_false_target->block() == &from)
m_false_target = Label { to };
}
ThrowCompletionOr<void> JumpConditional::execute_impl(Bytecode::Interpreter& interpreter) const
{
VERIFY(m_true_target.has_value());
VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator();
if (result.to_boolean())
interpreter.jump(m_true_target.value());
else
interpreter.jump(m_false_target.value());
return {};
}
ThrowCompletionOr<void> JumpNullish::execute_impl(Bytecode::Interpreter& interpreter) const
{
VERIFY(m_true_target.has_value());
VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator();
if (result.is_nullish())
interpreter.jump(m_true_target.value());
else
interpreter.jump(m_false_target.value());
return {};
}
ThrowCompletionOr<void> JumpUndefined::execute_impl(Bytecode::Interpreter& interpreter) const
2021-06-13 12:24:40 -07:00
{
VERIFY(m_true_target.has_value());
VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator();
if (result.is_undefined())
interpreter.jump(m_true_target.value());
else
interpreter.jump(m_false_target.value());
return {};
2021-06-13 12:24:40 -07:00
}
// 13.3.8.1 https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
static MarkedVector<Value> argument_list_evaluation(Bytecode::Interpreter& interpreter)
{
// Note: Any spreading and actual evaluation is handled in preceding opcodes
// Note: The spec uses the concept of a list, while we create a temporary array
// in the preceding opcodes, so we have to convert in a manner that is not
// visible to the user
auto& vm = interpreter.vm();
MarkedVector<Value> argument_values { vm.heap() };
auto arguments = interpreter.accumulator();
if (!(arguments.is_object() && is<Array>(arguments.as_object()))) {
dbgln("[{}] Call arguments are not an array, but: {}", interpreter.debug_position(), arguments.to_string_without_side_effects());
interpreter.current_executable().dump();
VERIFY_NOT_REACHED();
}
auto& argument_array = arguments.as_array();
auto array_length = argument_array.indexed_properties().array_like_size();
argument_values.ensure_capacity(array_length);
for (size_t i = 0; i < array_length; ++i) {
if (auto maybe_value = argument_array.indexed_properties().get(i); maybe_value.has_value())
argument_values.append(maybe_value.release_value().value);
else
argument_values.append(js_undefined());
}
return argument_values;
}
Completion Call::throw_type_error_for_callee(Bytecode::Interpreter& interpreter, StringView callee_type) const
{
auto callee = interpreter.reg(m_callee);
if (m_expression_string.has_value())
return interpreter.vm().throw_completion<TypeError>(ErrorType::IsNotAEvaluatedFrom, callee.to_string_without_side_effects(), callee_type, interpreter.current_executable().get_string(m_expression_string->value()));
return interpreter.vm().throw_completion<TypeError>(ErrorType::IsNotA, callee.to_string_without_side_effects(), callee_type);
}
ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto callee = interpreter.reg(m_callee);
if (m_type == CallType::Call && !callee.is_function())
return throw_type_error_for_callee(interpreter, "function"sv);
if (m_type == CallType::Construct && !callee.is_constructor())
return throw_type_error_for_callee(interpreter, "constructor"sv);
auto& function = callee.as_function();
auto this_value = interpreter.reg(m_this_value);
auto argument_values = argument_list_evaluation(interpreter);
Value return_value;
if (m_type == CallType::Call)
return_value = TRY(call(vm, function, this_value, move(argument_values)));
else
return_value = TRY(construct(vm, function, move(argument_values)));
interpreter.accumulator() = return_value;
return {};
}
// 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
ThrowCompletionOr<void> SuperCall::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
// 1. Let newTarget be GetNewTarget().
auto new_target = vm.get_new_target();
// 2. Assert: Type(newTarget) is Object.
VERIFY(new_target.is_object());
// 3. Let func be GetSuperConstructor().
auto* func = get_super_constructor(vm);
// 4. Let argList be ? ArgumentListEvaluation of Arguments.
MarkedVector<Value> arg_list { vm.heap() };
if (m_is_synthetic) {
auto const& value = interpreter.accumulator();
VERIFY(value.is_object() && is<Array>(value.as_object()));
auto const& array_value = static_cast<Array const&>(value.as_object());
auto length = MUST(length_of_array_like(vm, array_value));
for (size_t i = 0; i < length; ++i)
arg_list.append(array_value.get_without_side_effects(PropertyKey { i }));
} else {
arg_list = argument_list_evaluation(interpreter);
}
// 5. If IsConstructor(func) is false, throw a TypeError exception.
if (!Value(func).is_constructor())
return vm.throw_completion<TypeError>(ErrorType::NotAConstructor, "Super constructor");
// 6. Let result be ? Construct(func, argList, newTarget).
auto result = TRY(construct(vm, static_cast<FunctionObject&>(*func), move(arg_list), &new_target.as_function()));
// 7. Let thisER be GetThisEnvironment().
auto& this_environment = verify_cast<FunctionEnvironment>(*get_this_environment(vm));
// 8. Perform ? thisER.BindThisValue(result).
TRY(this_environment.bind_this_value(vm, result));
// 9. Let F be thisER.[[FunctionObject]].
auto& f = this_environment.function_object();
// 10. Assert: F is an ECMAScript function object.
// NOTE: This is implied by the strong C++ type.
// 11. Perform ? InitializeInstanceElements(result, F).
TRY(result->initialize_instance_elements(f));
// 12. Return result.
interpreter.accumulator() = result;
return {};
}
ThrowCompletionOr<void> NewFunction::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
interpreter.accumulator() = ECMAScriptFunctionObject::create(interpreter.realm(), m_function_node.name(), m_function_node.source_text(), m_function_node.body(), m_function_node.parameters(), m_function_node.function_length(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function_node.kind(), m_function_node.is_strict_mode(), m_function_node.might_need_arguments_object(), m_function_node.contains_direct_call_to_eval(), m_function_node.is_arrow_function());
return {};
}
ThrowCompletionOr<void> Return::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.do_return(interpreter.accumulator().value_or(js_undefined()));
return {};
}
ThrowCompletionOr<void> Increment::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto old_value = TRY(interpreter.accumulator().to_numeric(vm));
if (old_value.is_number())
interpreter.accumulator() = Value(old_value.as_double() + 1);
else
interpreter.accumulator() = BigInt::create(vm, old_value.as_bigint().big_integer().plus(Crypto::SignedBigInteger { 1 }));
return {};
}
ThrowCompletionOr<void> Decrement::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto old_value = TRY(interpreter.accumulator().to_numeric(vm));
if (old_value.is_number())
interpreter.accumulator() = Value(old_value.as_double() - 1);
else
interpreter.accumulator() = BigInt::create(vm, old_value.as_bigint().big_integer().minus(Crypto::SignedBigInteger { 1 }));
return {};
}
ThrowCompletionOr<void> Throw::execute_impl(Bytecode::Interpreter& interpreter) const
{
return throw_completion(interpreter.accumulator());
}
2022-12-09 18:48:57 +00:00
ThrowCompletionOr<void> ThrowIfNotObject::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
if (!interpreter.accumulator().is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, interpreter.accumulator().to_string_without_side_effects());
return {};
}
ThrowCompletionOr<void> EnterUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
interpreter.enter_unwind_context(m_handler_target, m_finalizer_target);
interpreter.jump(m_entry_point);
return {};
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
}
void EnterUnwindContext::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (&m_entry_point.block() == &from)
m_entry_point = Label { to };
if (m_handler_target.has_value() && &m_handler_target->block() == &from)
m_handler_target = Label { to };
if (m_finalizer_target.has_value() && &m_finalizer_target->block() == &from)
m_finalizer_target = Label { to };
}
void CopyObjectExcludingProperties::replace_references_impl(Register from, Register to)
{
if (m_from_object == from)
m_from_object = to;
for (size_t i = 0; i < m_excluded_names_count; ++i) {
if (m_excluded_names[i] == from)
m_excluded_names[i] = to;
}
}
void Call::replace_references_impl(Register from, Register to)
{
if (m_callee == from)
m_callee = to;
if (m_this_value == from)
m_this_value = to;
}
ThrowCompletionOr<void> LeaveEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
if (m_mode == EnvironmentMode::Lexical)
interpreter.vm().running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last();
if (m_mode == EnvironmentMode::Var)
interpreter.vm().running_execution_context().variable_environment = interpreter.saved_variable_environment_stack().take_last();
return {};
}
ThrowCompletionOr<void> LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
interpreter.leave_unwind_context();
return {};
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
}
ThrowCompletionOr<void> ContinuePendingUnwind::execute_impl(Bytecode::Interpreter& interpreter) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
return interpreter.continue_pending_unwind(m_resume_target);
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
}
void ContinuePendingUnwind::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (&m_resume_target.block() == &from)
m_resume_target = Label { to };
}
ThrowCompletionOr<void> PushDeclarativeEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto environment = interpreter.vm().heap().allocate_without_realm<DeclarativeEnvironment>(interpreter.vm().lexical_environment());
interpreter.vm().running_execution_context().lexical_environment = environment;
interpreter.vm().running_execution_context().variable_environment = environment;
return {};
}
ThrowCompletionOr<void> Yield::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto yielded_value = interpreter.accumulator().value_or(js_undefined());
auto object = Object::create(interpreter.realm(), nullptr);
object->define_direct_property("result", yielded_value, JS::default_attributes);
if (m_continuation_label.has_value())
object->define_direct_property("continuation", Value(static_cast<double>(reinterpret_cast<u64>(&m_continuation_label->block()))), JS::default_attributes);
else
object->define_direct_property("continuation", Value(0), JS::default_attributes);
interpreter.do_return(object);
return {};
}
void Yield::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (m_continuation_label.has_value() && &m_continuation_label->block() == &from)
m_continuation_label = Label { to };
}
ThrowCompletionOr<void> GetByValue::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.reg(m_base).to_object(vm));
auto property_key = TRY(interpreter.accumulator().to_property_key(vm));
interpreter.accumulator() = TRY(object->get(property_key));
return {};
}
ThrowCompletionOr<void> PutByValue::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.reg(m_base).to_object(vm));
auto property_key = TRY(interpreter.reg(m_property).to_property_key(vm));
return put_by_property_key(object, interpreter.accumulator(), property_key, interpreter, m_kind);
}
ThrowCompletionOr<void> DeleteByValue::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.reg(m_base).to_object(vm));
auto property_key = TRY(interpreter.accumulator().to_property_key(vm));
bool strict = vm.in_strict_mode();
auto reference = Reference { object, property_key, {}, strict };
interpreter.accumulator() = Value(TRY(reference.delete_(vm)));
return {};
}
ThrowCompletionOr<void> GetIterator::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto iterator = TRY(get_iterator(vm, interpreter.accumulator()));
interpreter.accumulator() = iterator_to_object(vm, iterator);
return {};
}
2022-12-09 18:48:57 +00:00
ThrowCompletionOr<void> GetMethod::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto identifier = interpreter.current_executable().get_identifier(m_property);
auto* method = TRY(interpreter.accumulator().get_method(vm, identifier));
interpreter.accumulator() = method ?: js_undefined();
return {};
}
// 14.7.5.9 EnumerateObjectProperties ( O ), https://tc39.es/ecma262/#sec-enumerate-object-properties
ThrowCompletionOr<void> GetObjectPropertyIterator::execute_impl(Bytecode::Interpreter& interpreter) const
{
// While the spec does provide an algorithm, it allows us to implement it ourselves so long as we meet the following invariants:
// 1- Returned property keys do not include keys that are Symbols
// 2- Properties of the target object may be deleted during enumeration. A property that is deleted before it is processed by the iterator's next method is ignored
// 3- If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration
// 4- A property name will be returned by the iterator's next method at most once in any enumeration.
// 5- Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively;
// but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's next method.
// 6- The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed.
// 7- The enumerable property names of prototype objects must be obtained by invoking EnumerateObjectProperties passing the prototype object as the argument.
// 8- EnumerateObjectProperties must obtain the own property keys of the target object by calling its [[OwnPropertyKeys]] internal method.
// 9- Property attributes of the target object must be obtained by calling its [[GetOwnProperty]] internal method
// Invariant 3 effectively allows the implementation to ignore newly added keys, and we do so (similar to other implementations).
// Invariants 1 and 6 through 9 are implemented in `enumerable_own_property_names`, which implements the EnumerableOwnPropertyNames AO.
auto& vm = interpreter.vm();
auto* object = TRY(interpreter.accumulator().to_object(vm));
// Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys,
// so we just keep the order consistent anyway.
OrderedHashTable<PropertyKey> properties;
HashTable<Object*> seen_objects;
// Collect all keys immediately (invariant no. 5)
for (auto* object_to_check = object; object_to_check && !seen_objects.contains(object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) {
seen_objects.set(object_to_check);
for (auto& key : TRY(object_to_check->enumerable_own_property_names(Object::PropertyKind::Key))) {
properties.set(TRY(PropertyKey::from_value(vm, key)));
}
}
Iterator iterator {
.iterator = object,
.next_method = NativeFunction::create(
interpreter.realm(),
[seen_items = HashTable<PropertyKey>(), items = move(properties)](VM& vm) mutable -> ThrowCompletionOr<Value> {
auto& realm = *vm.current_realm();
auto iterated_object_value = vm.this_value();
if (!iterated_object_value.is_object())
return vm.throw_completion<InternalError>("Invalid state for GetObjectPropertyIterator.next");
auto& iterated_object = iterated_object_value.as_object();
auto result_object = Object::create(realm, nullptr);
while (true) {
if (items.is_empty()) {
result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
return result_object;
}
auto it = items.begin();
auto key = *it;
items.remove(it);
// If the key was already seen, skip over it (invariant no. 4)
auto result = seen_items.set(key);
if (result != AK::HashSetResult::InsertedNewEntry)
continue;
// If the property is deleted, don't include it (invariant no. 2)
if (!TRY(iterated_object.has_property(key)))
continue;
result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes);
if (key.is_number())
result_object->define_direct_property(vm.names.value, JS::Value(key.as_number()), default_attributes);
else if (key.is_string())
result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, key.as_string()), default_attributes);
else
VERIFY_NOT_REACHED(); // We should not have non-string/number keys.
return result_object;
}
},
1,
vm.names.next),
.done = false,
};
interpreter.accumulator() = iterator_to_object(vm, move(iterator));
return {};
}
2022-12-09 18:48:57 +00:00
ThrowCompletionOr<void> IteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* iterator_object = TRY(interpreter.accumulator().to_object(vm));
auto iterator = object_to_iterator(vm, *iterator_object);
// FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!)
TRY(iterator_close(vm, iterator, Completion { m_completion_type, m_completion_value, {} }));
return {};
}
ThrowCompletionOr<void> IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* iterator_object = TRY(interpreter.accumulator().to_object(vm));
auto iterator = object_to_iterator(vm, *iterator_object);
interpreter.accumulator() = TRY(iterator_next(vm, iterator));
return {};
}
ThrowCompletionOr<void> IteratorResultDone::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* iterator_result = TRY(interpreter.accumulator().to_object(vm));
auto complete = TRY(iterator_complete(vm, *iterator_result));
interpreter.accumulator() = Value(complete);
return {};
}
ThrowCompletionOr<void> IteratorResultValue::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto* iterator_result = TRY(interpreter.accumulator().to_object(vm));
interpreter.accumulator() = TRY(iterator_value(vm, *iterator_result));
return {};
}
2022-02-11 22:38:21 +03:30
ThrowCompletionOr<void> NewClass::execute_impl(Bytecode::Interpreter& interpreter) const
{
2022-02-11 22:38:21 +03:30
auto name = m_class_expression.name();
auto scope = interpreter.ast_interpreter_scope();
auto& ast_interpreter = scope.interpreter();
auto* class_object = TRY(m_class_expression.class_definition_evaluation(ast_interpreter, name, name.is_null() ? ""sv : name));
class_object->set_source_text(m_class_expression.source_text());
2022-02-11 22:38:21 +03:30
interpreter.accumulator() = class_object;
return {};
}
// 13.5.3.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-typeof-operator-runtime-semantics-evaluation
ThrowCompletionOr<void> TypeofVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
// 1. Let val be the result of evaluating UnaryExpression.
auto const& string = interpreter.current_executable().get_identifier(m_identifier);
auto reference = TRY(vm.resolve_binding(string));
// 2. If val is a Reference Record, then
// a. If IsUnresolvableReference(val) is true, return "undefined".
if (reference.is_unresolvable()) {
interpreter.accumulator() = PrimitiveString::create(vm, "undefined"sv);
return {};
}
// 3. Set val to ? GetValue(val).
auto value = TRY(reference.get_value(vm));
// 4. NOTE: This step is replaced in section B.3.6.3.
// 5. Return a String according to Table 41.
interpreter.accumulator() = PrimitiveString::create(vm, value.typeof());
return {};
}
DeprecatedString Load::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("Load {}", m_src);
}
DeprecatedString LoadImmediate::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("LoadImmediate {}", m_value);
}
DeprecatedString Store::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("Store {}", m_dst);
}
DeprecatedString NewBigInt::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("NewBigInt \"{}\"", m_bigint.to_base_deprecated(10));
}
DeprecatedString NewArray::to_deprecated_string_impl(Bytecode::Executable const&) const
{
StringBuilder builder;
builder.append("NewArray"sv);
if (m_element_count != 0) {
builder.appendff(" [{}-{}]", m_elements[0], m_elements[1]);
}
return builder.to_deprecated_string();
}
DeprecatedString Append::to_deprecated_string_impl(Bytecode::Executable const&) const
{
if (m_is_spread)
return DeprecatedString::formatted("Append lhs: **{}", m_lhs);
return DeprecatedString::formatted("Append lhs: {}", m_lhs);
}
DeprecatedString IteratorToArray::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "IteratorToArray";
}
DeprecatedString NewString::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("NewString {} (\"{}\")", m_string, executable.string_table->get(m_string));
}
DeprecatedString NewObject::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "NewObject";
}
DeprecatedString NewRegExp::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("NewRegExp source:{} (\"{}\") flags:{} (\"{}\")", m_source_index, executable.get_string(m_source_index), m_flags_index, executable.get_string(m_flags_index));
}
DeprecatedString CopyObjectExcludingProperties::to_deprecated_string_impl(Bytecode::Executable const&) const
{
StringBuilder builder;
builder.appendff("CopyObjectExcludingProperties from:{}", m_from_object);
if (m_excluded_names_count != 0) {
builder.append(" excluding:["sv);
builder.join(", "sv, ReadonlySpan<Register>(m_excluded_names, m_excluded_names_count));
builder.append(']');
}
return builder.to_deprecated_string();
}
DeprecatedString ConcatString::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("ConcatString {}", m_lhs);
}
DeprecatedString GetVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString DeleteVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("DeleteVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString CreateEnvironment::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto mode_string = m_mode == EnvironmentMode::Lexical
? "Lexical"
: "Variable";
return DeprecatedString::formatted("CreateEnvironment mode:{}", mode_string);
}
DeprecatedString CreateVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
return DeprecatedString::formatted("CreateVariable env:{} immutable:{} global:{} {} ({})", mode_string, m_is_immutable, m_is_global, m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString EnterObjectEnvironment::to_deprecated_string_impl(Executable const&) const
{
return DeprecatedString::formatted("EnterObjectEnvironment");
}
DeprecatedString SetVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
auto initialization_mode_name = m_initialization_mode == InitializationMode ::Initialize ? "Initialize"
: m_initialization_mode == InitializationMode::Set ? "Set"
: "InitializeOrSet";
auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
return DeprecatedString::formatted("SetVariable env:{} init:{} {} ({})", mode_string, initialization_mode_name, m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString PutById::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
auto kind = m_kind == PropertyKind::Getter
? "getter"
: m_kind == PropertyKind::Setter
? "setter"
: "property";
return DeprecatedString::formatted("PutById kind:{} base:{}, property:{} ({})", kind, m_base, m_property, executable.identifier_table->get(m_property));
}
DeprecatedString GetById::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("GetById {} ({})", m_property, executable.identifier_table->get(m_property));
}
DeprecatedString DeleteById::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("DeleteById {} ({})", m_property, executable.identifier_table->get(m_property));
}
DeprecatedString Jump::to_deprecated_string_impl(Bytecode::Executable const&) const
{
if (m_true_target.has_value())
return DeprecatedString::formatted("Jump {}", *m_true_target);
return DeprecatedString::formatted("Jump <empty>");
}
DeprecatedString JumpConditional::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto true_string = m_true_target.has_value() ? DeprecatedString::formatted("{}", *m_true_target) : "<empty>";
auto false_string = m_false_target.has_value() ? DeprecatedString::formatted("{}", *m_false_target) : "<empty>";
return DeprecatedString::formatted("JumpConditional true:{} false:{}", true_string, false_string);
}
DeprecatedString JumpNullish::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto true_string = m_true_target.has_value() ? DeprecatedString::formatted("{}", *m_true_target) : "<empty>";
auto false_string = m_false_target.has_value() ? DeprecatedString::formatted("{}", *m_false_target) : "<empty>";
return DeprecatedString::formatted("JumpNullish null:{} nonnull:{}", true_string, false_string);
}
DeprecatedString JumpUndefined::to_deprecated_string_impl(Bytecode::Executable const&) const
2021-06-13 12:24:40 -07:00
{
auto true_string = m_true_target.has_value() ? DeprecatedString::formatted("{}", *m_true_target) : "<empty>";
auto false_string = m_false_target.has_value() ? DeprecatedString::formatted("{}", *m_false_target) : "<empty>";
return DeprecatedString::formatted("JumpUndefined undefined:{} not undefined:{}", true_string, false_string);
2021-06-13 12:24:40 -07:00
}
DeprecatedString Call::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
if (m_expression_string.has_value())
return DeprecatedString::formatted("Call callee:{}, this:{}, arguments:[...acc] ({})", m_callee, m_this_value, executable.get_string(m_expression_string.value()));
return DeprecatedString::formatted("Call callee:{}, this:{}, arguments:[...acc]", m_callee, m_this_value);
}
DeprecatedString SuperCall::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "SuperCall arguments:[...acc]"sv;
}
DeprecatedString NewFunction::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "NewFunction";
}
DeprecatedString NewClass::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto name = m_class_expression.name();
return DeprecatedString::formatted("NewClass '{}'", name.is_null() ? ""sv : name);
}
DeprecatedString Return::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "Return";
}
DeprecatedString Increment::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "Increment";
}
DeprecatedString Decrement::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "Decrement";
}
DeprecatedString Throw::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "Throw";
}
2022-12-09 18:48:57 +00:00
DeprecatedString ThrowIfNotObject::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "ThrowIfNotObject";
}
DeprecatedString EnterUnwindContext::to_deprecated_string_impl(Bytecode::Executable const&) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
auto handler_string = m_handler_target.has_value() ? DeprecatedString::formatted("{}", *m_handler_target) : "<empty>";
auto finalizer_string = m_finalizer_target.has_value() ? DeprecatedString::formatted("{}", *m_finalizer_target) : "<empty>";
return DeprecatedString::formatted("EnterUnwindContext handler:{} finalizer:{} entry:{}", handler_string, finalizer_string, m_entry_point);
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
}
DeprecatedString LeaveEnvironment::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto mode_string = m_mode == EnvironmentMode::Lexical
? "Lexical"
: "Variable";
return DeprecatedString::formatted("LeaveEnvironment env:{}", mode_string);
}
DeprecatedString LeaveUnwindContext::to_deprecated_string_impl(Bytecode::Executable const&) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
return "LeaveUnwindContext";
}
DeprecatedString ContinuePendingUnwind::to_deprecated_string_impl(Bytecode::Executable const&) const
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
{
return DeprecatedString::formatted("ContinuePendingUnwind resume:{}", m_resume_target);
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 15:04:38 +02:00
}
DeprecatedString PushDeclarativeEnvironment::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
StringBuilder builder;
builder.append("PushDeclarativeEnvironment"sv);
if (!m_variables.is_empty()) {
builder.append(" {"sv);
Vector<DeprecatedString> names;
for (auto& it : m_variables)
names.append(executable.get_string(it.key));
builder.append('}');
builder.join(", "sv, names);
}
return builder.to_deprecated_string();
}
DeprecatedString Yield::to_deprecated_string_impl(Bytecode::Executable const&) const
{
if (m_continuation_label.has_value())
return DeprecatedString::formatted("Yield continuation:@{}", m_continuation_label->block().name());
return DeprecatedString::formatted("Yield return");
}
DeprecatedString GetByValue::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("GetByValue base:{}", m_base);
}
DeprecatedString PutByValue::to_deprecated_string_impl(Bytecode::Executable const&) const
{
auto kind = m_kind == PropertyKind::Getter
? "getter"
: m_kind == PropertyKind::Setter
? "setter"
: "property";
return DeprecatedString::formatted("PutByValue kind:{} base:{}, property:{}", kind, m_base, m_property);
}
DeprecatedString DeleteByValue::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("DeleteByValue base:{}", m_base);
}
DeprecatedString GetIterator::to_deprecated_string_impl(Executable const&) const
{
return "GetIterator";
}
2022-12-09 18:48:57 +00:00
DeprecatedString GetMethod::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("GetMethod {} ({})", m_property, executable.identifier_table->get(m_property));
}
DeprecatedString GetObjectPropertyIterator::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "GetObjectPropertyIterator";
}
2022-12-09 18:48:57 +00:00
DeprecatedString IteratorClose::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("IteratorClose completion_type={} completion_value={}", to_underlying(m_completion_type), m_completion_value.has_value() ? m_completion_value.value().to_string_without_side_effects() : "<empty>");
}
DeprecatedString IteratorNext::to_deprecated_string_impl(Executable const&) const
{
return "IteratorNext";
}
DeprecatedString IteratorResultDone::to_deprecated_string_impl(Executable const&) const
{
return "IteratorResultDone";
}
DeprecatedString IteratorResultValue::to_deprecated_string_impl(Executable const&) const
{
return "IteratorResultValue";
}
DeprecatedString ResolveThisBinding::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "ResolveThisBinding"sv;
}
DeprecatedString GetNewTarget::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "GetNewTarget"sv;
}
DeprecatedString TypeofVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("TypeofVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
}
}