LibJS: Make Value() default-construct the undefined value

The special empty value (that we use for array holes, Optional<Value>
when empty and a few other other placeholder/sentinel tasks) still
exists, but you now create one via JS::js_special_empty_value() and
check for it with Value::is_special_empty_value().

The main idea here is to make it very unlikely to accidentally create an
unexpected special empty value.
This commit is contained in:
Andreas Kling 2025-04-04 23:16:34 +02:00 committed by Andreas Kling
parent 0d91363742
commit 3cf50539ec
Notes: github-actions[bot] 2025-04-05 09:21:31 +00:00
43 changed files with 165 additions and 122 deletions

View file

@ -686,6 +686,22 @@ public:
return {};
}
ErrorOr<void> try_resize_with_default_value(size_t new_size, T const& default_value, bool keep_capacity)
requires(!contains_reference)
{
if (new_size <= size()) {
shrink(new_size, keep_capacity);
return {};
}
TRY(try_ensure_capacity(new_size));
for (size_t i = size(); i < new_size; ++i)
new (slot(i)) StorageType { default_value };
m_size = new_size;
return {};
}
ErrorOr<void> try_resize_and_keep_capacity(size_t new_size)
requires(!contains_reference)
{
@ -733,6 +749,24 @@ public:
MUST(try_resize_and_keep_capacity(new_size));
}
void resize_with_default_value_and_keep_capacity(size_t new_size, T const& default_value)
requires(!contains_reference)
{
MUST(try_resize_with_default_value(new_size, default_value, true));
}
void resize_with_default_value(size_t new_size, T const& default_value, bool keep_capacity = false)
requires(!contains_reference)
{
MUST(try_resize_with_default_value(new_size, default_value, keep_capacity));
}
void fill(T const& value)
{
for (size_t i = 0; i < size(); ++i)
at(i) = value;
}
void shrink_to_fit()
{
if (size() == capacity())

View file

@ -137,7 +137,7 @@ static ThrowCompletionOr<ClassElementName> class_key_to_property_name(VM& vm, Ex
return ClassElementName { private_environment->resolve_private_identifier(private_identifier.string()) };
}
VERIFY(!prop_key.is_empty());
VERIFY(!prop_key.is_special_empty_value());
if (prop_key.is_object())
prop_key = TRY(prop_key.to_primitive(vm, Value::PreferredType::String));

View file

@ -1201,7 +1201,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
// If all elements are constant primitives, we can just emit a single instruction to initialize the array,
// instead of emitting instructions to manually evaluate them one-by-one
Vector<Value> values;
values.resize(m_elements.size());
values.resize_with_default_value(m_elements.size(), js_special_empty_value());
for (auto i = 0u; i < m_elements.size(); ++i) {
if (!m_elements[i])
continue;
@ -1221,7 +1221,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
auto value = TRY((*it)->generate_bytecode(generator)).value();
args.append(generator.copy_if_needed_to_preserve_evaluation_order(value));
} else {
args.append(generator.add_constant(Value()));
args.append(generator.add_constant(js_special_empty_value()));
}
}
@ -1235,7 +1235,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
if (first_spread != m_elements.end()) {
for (auto it = first_spread; it != m_elements.end(); ++it) {
if (!*it) {
generator.emit<Bytecode::Op::ArrayAppend>(dst, generator.add_constant(Value()), false);
generator.emit<Bytecode::Op::ArrayAppend>(dst, generator.add_constant(js_special_empty_value()), false);
} else {
auto value = TRY((*it)->generate_bytecode(generator)).value();
generator.emit<Bytecode::Op::ArrayAppend>(dst, value, *it && is<SpreadExpression>(**it));

View file

@ -1304,7 +1304,7 @@ ScopedOperand Generator::add_constant(Value value)
m_null_constant = append_new_constant();
return m_null_constant.value();
}
if (value.is_empty()) {
if (value.is_special_empty_value()) {
if (!m_empty_constant.has_value())
m_empty_constant = append_new_constant();
return m_empty_constant.value();

View file

@ -298,7 +298,7 @@ public:
emit<Bytecode::Op::PrepareYield>(Operand(Register::saved_return_value()), value);
else
emit<Bytecode::Op::Mov>(Operand(Register::saved_return_value()), value);
emit<Bytecode::Op::Mov>(Operand(Register::exception()), add_constant(Value {}));
emit<Bytecode::Op::Mov>(Operand(Register::exception()), add_constant(js_special_empty_value()));
emit<Bytecode::Op::Jump>(Label { *m_current_basic_block->finalizer() });
return;
}

View file

@ -86,7 +86,7 @@ static ByteString format_operand(StringView name, Operand operand, Bytecode::Exe
case Operand::Type::Constant: {
builder.append("\033[36m"sv);
auto value = executable.constants[operand.index() - executable.number_of_registers];
if (value.is_empty())
if (value.is_special_empty_value())
builder.append("<Empty>"sv);
else if (value.is_boolean())
builder.appendff("Bool({})", value.as_bool() ? "true"sv : "false"sv);
@ -268,12 +268,13 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, GC::Ptr<Environ
auto result_or_error = run_executable(*executable, {}, {});
if (result_or_error.value.is_error())
result = result_or_error.value.release_error();
else
result = result_or_error.return_register_value.value_or(js_undefined());
else {
result = result_or_error.return_register_value.is_special_empty_value() ? normal_completion(js_undefined()) : result_or_error.return_register_value;
}
}
// b. If result is a normal completion and result.[[Value]] is empty, then
if (result.type() == Completion::Type::Normal && result.value().is_empty()) {
if (result.type() == Completion::Type::Normal && result.value().is_special_empty_value()) {
// i. Set result to NormalCompletion(undefined).
result = normal_completion(js_undefined());
}
@ -300,9 +301,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, GC::Ptr<Environ
// 17. Return ? result.
if (result.is_abrupt()) {
VERIFY(result.type() == Completion::Type::Throw);
auto error = result.release_error();
VERIFY(!error.value().is_empty());
return error;
return result.release_error();
}
return result.value();
@ -515,12 +514,12 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
handle_ContinuePendingUnwind: {
auto& instruction = *reinterpret_cast<Op::ContinuePendingUnwind const*>(&bytecode[program_counter]);
if (auto exception = reg(Register::exception()); !exception.is_empty()) {
if (auto exception = reg(Register::exception()); !exception.is_special_empty_value()) {
if (handle_exception(program_counter, exception) == HandleExceptionResponse::ExitFromExecutable)
return;
goto start;
}
if (!saved_return_value().is_empty()) {
if (!saved_return_value().is_special_empty_value()) {
do_return(saved_return_value());
if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
if (auto finalizer = handlers.value().finalizer_offset; finalizer.has_value()) {
@ -528,7 +527,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
auto& unwind_context = running_execution_context.unwind_contexts.last();
VERIFY(unwind_context.executable == m_current_executable);
reg(Register::saved_return_value()) = reg(Register::return_value());
reg(Register::return_value()) = {};
reg(Register::return_value()) = js_special_empty_value();
program_counter = finalizer.value();
// the unwind_context will be pop'ed when entering the finally block
goto start;
@ -734,21 +733,21 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe
auto& running_execution_context = vm().running_execution_context();
u32 registers_and_constants_and_locals_count = executable.number_of_registers + executable.constants.size() + executable.local_variable_names.size();
if (running_execution_context.registers_and_constants_and_locals.size() < registers_and_constants_and_locals_count)
running_execution_context.registers_and_constants_and_locals.resize(registers_and_constants_and_locals_count);
running_execution_context.registers_and_constants_and_locals.resize_with_default_value(registers_and_constants_and_locals_count, js_special_empty_value());
TemporaryChange restore_running_execution_context { m_running_execution_context, &running_execution_context };
TemporaryChange restore_arguments { m_arguments, running_execution_context.arguments.span() };
TemporaryChange restore_registers_and_constants_and_locals { m_registers_and_constants_and_locals, running_execution_context.registers_and_constants_and_locals.span() };
reg(Register::accumulator()) = initial_accumulator_value;
reg(Register::return_value()) = {};
reg(Register::return_value()) = js_special_empty_value();
// NOTE: We only copy the `this` value from ExecutionContext if it's not already set.
// If we are re-entering an async/generator context, the `this` value
// may have already been cached by a ResolveThisBinding instruction,
// and subsequent instructions expect this value to be set.
if (reg(Register::this_value()).is_empty())
reg(Register::this_value()) = running_execution_context.this_value;
if (reg(Register::this_value()).is_special_empty_value())
reg(Register::this_value()) = running_execution_context.this_value.value_or(js_special_empty_value());
running_execution_context.executable = &executable;
@ -764,7 +763,7 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe
auto const& registers_and_constants_and_locals = running_execution_context.registers_and_constants_and_locals;
for (size_t i = 0; i < executable.number_of_registers; ++i) {
String value_string;
if (registers_and_constants_and_locals[i].is_empty())
if (registers_and_constants_and_locals[i].is_special_empty_value())
value_string = "(empty)"_string;
else
value_string = registers_and_constants_and_locals[i].to_string_without_side_effects();
@ -773,14 +772,14 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe
}
auto return_value = js_undefined();
if (!reg(Register::return_value()).is_empty())
if (!reg(Register::return_value()).is_special_empty_value())
return_value = reg(Register::return_value());
auto exception = reg(Register::exception());
vm().run_queued_promise_jobs();
vm().finish_execution_generation();
if (!exception.is_empty())
if (!exception.is_special_empty_value())
return { throw_completion(exception), running_execution_context.registers_and_constants_and_locals[0] };
return { return_value, running_execution_context.registers_and_constants_and_locals[0] };
}
@ -802,7 +801,7 @@ void Interpreter::leave_unwind_context()
void Interpreter::catch_exception(Operand dst)
{
set(dst, reg(Register::exception()));
reg(Register::exception()) = {};
reg(Register::exception()) = js_special_empty_value();
auto& context = running_execution_context().unwind_contexts.last();
VERIFY(!context.handler_called);
VERIFY(context.executable == &current_executable());
@ -817,7 +816,7 @@ void Interpreter::restore_scheduled_jump()
void Interpreter::leave_finally()
{
reg(Register::exception()) = {};
reg(Register::exception()) = js_special_empty_value();
m_scheduled_jump = running_execution_context().previously_scheduled_jumps.take_last();
}
@ -1265,7 +1264,7 @@ inline ThrowCompletionOr<Value> perform_call(Interpreter& interpreter, Value thi
Value return_value;
if (call_type == Op::CallType::DirectEval) {
if (callee == interpreter.realm().intrinsics().eval_function())
return_value = TRY(perform_eval(vm, !argument_values.is_empty() ? argument_values[0].value_or(JS::js_undefined()) : js_undefined(), vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct));
return_value = TRY(perform_eval(vm, !argument_values.is_empty() ? argument_values[0] : js_undefined(), vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct));
else
return_value = TRY(JS::call(vm, function, this_value, argument_values));
} else if (call_type == Op::CallType::Call)
@ -2539,13 +2538,13 @@ ThrowCompletionOr<void> DeleteByIdWithThis::execute_impl(Bytecode::Interpreter&
ThrowCompletionOr<void> ResolveThisBinding::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& cached_this_value = interpreter.reg(Register::this_value());
if (!cached_this_value.is_empty())
if (!cached_this_value.is_special_empty_value())
return {};
// OPTIMIZATION: Because the value of 'this' cannot be reassigned during a function execution, it's
// resolved once and then saved for subsequent use.
auto& running_execution_context = interpreter.running_execution_context();
if (auto function = running_execution_context.function; function && is<ECMAScriptFunctionObject>(*function) && !static_cast<ECMAScriptFunctionObject&>(*function).allocates_function_environment()) {
cached_this_value = running_execution_context.this_value;
cached_this_value = running_execution_context.this_value.value();
} else {
auto& vm = interpreter.vm();
cached_this_value = TRY(vm.resolve_this_binding());
@ -2801,7 +2800,7 @@ ThrowCompletionOr<void> ThrowIfTDZ::execute_impl(Bytecode::Interpreter& interpre
{
auto& vm = interpreter.vm();
auto value = interpreter.get(m_src);
if (value.is_empty())
if (value.is_special_empty_value())
return vm.throw_completion<ReferenceError>(ErrorType::BindingNotInitialized, value.to_string_without_side_effects());
return {};
}
@ -2825,20 +2824,20 @@ void LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
void Yield::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto yielded_value = interpreter.get(m_value).value_or(js_undefined());
auto yielded_value = interpreter.get(m_value).is_special_empty_value() ? js_undefined() : interpreter.get(m_value);
interpreter.do_return(
interpreter.do_yield(yielded_value, m_continuation_label));
}
void PrepareYield::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto value = interpreter.get(m_value).value_or(js_undefined());
auto value = interpreter.get(m_value).is_special_empty_value() ? js_undefined() : interpreter.get(m_value);
interpreter.set(m_dest, interpreter.do_yield(value, {}));
}
void Await::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto yielded_value = interpreter.get(m_argument).value_or(js_undefined());
auto yielded_value = interpreter.get(m_argument).is_special_empty_value() ? js_undefined() : interpreter.get(m_argument);
// FIXME: If we get a pointer, which is not accurately representable as a double
// will cause this to explode
auto continuation_value = Value(m_continuation_label.address());
@ -3832,7 +3831,7 @@ ByteString Dump::to_byte_string_impl(Bytecode::Executable const& executable) con
void GetCompletionFields::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto const& completion_cell = static_cast<CompletionCell const&>(interpreter.get(m_completion).as_cell());
interpreter.set(m_value_dst, completion_cell.completion().value().value_or(js_undefined()));
interpreter.set(m_value_dst, completion_cell.completion().value());
interpreter.set(m_type_dst, Value(to_underlying(completion_cell.completion().type())));
}

View file

@ -33,7 +33,7 @@ public:
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Optional<size_t> entry_point = {}, Value initial_accumulator_value = {})
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Optional<size_t> entry_point = {}, Value initial_accumulator_value = js_special_empty_value())
{
auto result_and_return_register = run_executable(executable, entry_point, initial_accumulator_value);
return move(result_and_return_register.value);
@ -43,7 +43,7 @@ public:
ThrowCompletionOr<Value> value;
Value return_register_value;
};
ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional<size_t> entry_point, Value initial_accumulator_value = {});
ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional<size_t> entry_point, Value initial_accumulator_value = js_special_empty_value());
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
@ -63,7 +63,7 @@ public:
void do_return(Value value)
{
reg(Register::return_value()) = value;
reg(Register::exception()) = {};
reg(Register::exception()) = js_special_empty_value();
}
void enter_unwind_context();

View file

@ -46,7 +46,7 @@ ErrorOr<String> MarkupGenerator::html_from_error(Error const& object, bool in_pr
ErrorOr<void> MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, HashTable<Object*>& seen_objects)
{
if (value.is_empty()) {
if (value.is_special_empty_value()) {
TRY(output_html.try_append("&lt;empty&gt;"sv));
return {};
}
@ -166,8 +166,8 @@ ErrorOr<void> MarkupGenerator::trace_to_html(TracebackFrame const& traceback_fra
ErrorOr<void> MarkupGenerator::error_to_html(Error const& error, StringBuilder& html_output, bool in_promise)
{
auto& vm = error.vm();
auto name = error.get_without_side_effects(vm.names.name).value_or(js_undefined());
auto message = error.get_without_side_effects(vm.names.message).value_or(js_undefined());
auto name = error.get_without_side_effects(vm.names.name);
auto message = error.get_without_side_effects(vm.names.message);
auto name_string = name.to_string_without_side_effects();
auto message_string = message.to_string_without_side_effects();
auto uncaught_message = TRY(String::formatted("Uncaught {}[{}]: ", in_promise ? "(in promise) " : "", name_string));

View file

@ -272,8 +272,8 @@ ErrorOr<void> print_date(JS::PrintContext& print_context, JS::Date const& date,
ErrorOr<void> print_error(JS::PrintContext& print_context, JS::Object const& object, HashTable<JS::Object*>& seen_objects)
{
auto name = object.get_without_side_effects(print_context.vm.names.name).value_or(JS::js_undefined());
auto message = object.get_without_side_effects(print_context.vm.names.message).value_or(JS::js_undefined());
auto name = object.get_without_side_effects(print_context.vm.names.name);
auto message = object.get_without_side_effects(print_context.vm.names.message);
if (name.is_accessor() || message.is_accessor()) {
TRY(print_value(print_context, &object, seen_objects));
} else {
@ -917,7 +917,7 @@ ErrorOr<void> print_string_object(JS::PrintContext& print_context, JS::StringObj
ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, HashTable<JS::Object*>& seen_objects)
{
if (value.is_empty()) {
if (value.is_special_empty_value()) {
TRY(js_out(print_context, "\033[34;1m<empty>\033[0m"));
return {};
}

View file

@ -699,9 +699,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
if (result_or_error.value.is_error())
return result_or_error.value.release_error();
auto& result = result_or_error.return_register_value;
if (!result.is_empty())
eval_result = result;
eval_result = result_or_error.return_register_value;
// 30. If result.[[Type]] is normal and result.[[Value]] is empty, then
// a. Set result to NormalCompletion(undefined).

View file

@ -602,7 +602,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_last_index)
// 23.1.3.13.1 FlattenIntoArray ( target, source, sourceLen, start, depth [ , mapperFunction [ , thisArg ] ] ), https://tc39.es/ecma262/#sec-flattenintoarray
static ThrowCompletionOr<size_t> flatten_into_array(VM& vm, Object& new_array, Object& array, size_t array_length, size_t target_index, double depth, FunctionObject* mapper_func = {}, Value this_arg = {})
{
VERIFY(!mapper_func || (!this_arg.is_empty() && depth == 1));
VERIFY(!mapper_func || (!this_arg.is_special_empty_value() && depth == 1));
for (size_t j = 0; j < array_length; ++j) {
auto value_exists = TRY(array.has_property(j));
@ -1258,7 +1258,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice)
double relative_end;
if (vm.argument(1).is_undefined() || vm.argument(1).is_empty())
if (vm.argument(1).is_undefined())
relative_end = (double)initial_length;
else
relative_end = TRY(vm.argument(1).to_integer_or_infinity(vm));

View file

@ -166,7 +166,7 @@ void AsyncFunctionDriverWrapper::continue_async_execution(VM& vm, Value value, b
}();
if (result.is_throw_completion()) {
m_top_level_promise->reject(result.throw_completion().value().value_or(js_undefined()));
m_top_level_promise->reject(result.throw_completion().value());
}
}

View file

@ -157,7 +157,7 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
auto generated_value = [](Value value) -> Value {
if (value.is_cell())
return static_cast<GeneratorResult const&>(value.as_cell()).result();
return value.is_empty() ? js_undefined() : value;
return value.is_special_empty_value() ? js_undefined() : value;
};
auto generated_continuation = [&](Value value) -> Optional<size_t> {

View file

@ -65,7 +65,7 @@ public:
, m_value(value)
{
VERIFY(type != Type::Empty);
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
}
Completion(ThrowCompletionOr<Value> const&);
@ -245,7 +245,7 @@ public:
: m_value_or_error(move(value))
{
if constexpr (IsSame<ValueType, Value>)
VERIFY(!m_value_or_error.template get<ValueType>().is_empty());
VERIFY(!m_value_or_error.template get<ValueType>().is_special_empty_value());
}
ALWAYS_INLINE ThrowCompletionOr(ThrowCompletionOr const&) = default;

View file

@ -793,7 +793,7 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro
return;
// 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
auto result = call(vm, *closure, async_context.this_value.is_empty() ? js_undefined() : async_context.this_value);
auto result = call(vm, *closure, *async_context.this_value);
// 6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
VERIFY(&vm.running_execution_context() == &running_context);
@ -828,7 +828,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
m_bytecode_executable = m_ecmascript_code->bytecode_executable();
}
vm.running_execution_context().registers_and_constants_and_locals.resize(m_local_variables_names.size() + m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size());
vm.running_execution_context().registers_and_constants_and_locals.resize_with_default_value(m_local_variables_names.size() + m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size(), js_special_empty_value());
auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, {});
@ -840,7 +840,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
// NOTE: Running the bytecode should eventually return a completion.
// Until it does, we assume "return" and include the undefined fallback from the call site.
if (m_kind == FunctionKind::Normal)
return { Completion::Type::Return, result.value_or(js_undefined()) };
return { Completion::Type::Return, result };
if (m_kind == FunctionKind::AsyncGenerator) {
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy()));

View file

@ -82,7 +82,8 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(lexical_environment);
visitor.visit(private_environment);
visitor.visit(context_owner);
visitor.visit(this_value);
if (this_value.has_value())
visitor.visit(*this_value);
visitor.visit(executable);
visitor.visit(function_name);
visitor.visit(arguments);

View file

@ -62,7 +62,7 @@ public:
mutable RefPtr<CachedSourceRange> cached_source_range;
GC::Ptr<PrimitiveString> function_name;
Value this_value;
Optional<Value> this_value;
GC::Ptr<Bytecode::Executable> executable;

View file

@ -21,7 +21,7 @@ FinalizationRegistry::FinalizationRegistry(Realm& realm, GC::Ref<JobCallback> cl
void FinalizationRegistry::add_finalization_record(Cell& target, Value held_value, Cell* unregister_token)
{
VERIFY(!held_value.is_empty());
VERIFY(!held_value.is_special_empty_value());
m_records.append({ &target, held_value, unregister_token });
}

View file

@ -79,7 +79,7 @@ ThrowCompletionOr<Value> FunctionEnvironment::get_this_binding(VM& vm) const
// 9.1.1.3.1 BindThisValue ( V ), https://tc39.es/ecma262/#sec-bindthisvalue
ThrowCompletionOr<Value> FunctionEnvironment::bind_this_value(VM& vm, Value this_value)
{
VERIFY(!this_value.is_empty());
VERIFY(!this_value.is_special_empty_value());
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
VERIFY(m_this_binding_status != ThisBindingStatus::Lexical);

View file

@ -34,7 +34,7 @@ public:
Value new_target() const { return m_new_target; }
void set_new_target(Value new_target)
{
VERIFY(!new_target.is_empty());
VERIFY(!new_target.is_special_empty_value());
m_new_target = new_target;
}

View file

@ -85,7 +85,7 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
auto generated_value = [](Value value) -> Value {
if (value.is_cell())
return static_cast<GeneratorResult const&>(value.as_cell()).result();
return value.is_empty() ? js_undefined() : value;
return value.is_special_empty_value() ? js_undefined() : value;
};
auto generated_continuation = [&](Value value) -> Optional<size_t> {

View file

@ -36,10 +36,10 @@ void SimpleIndexedPropertyStorage::grow_storage_if_needed()
return;
if (m_array_size <= m_packed_elements.capacity()) {
m_packed_elements.resize_and_keep_capacity(m_array_size);
m_packed_elements.resize_with_default_value_and_keep_capacity(m_array_size, js_special_empty_value());
} else {
// When the array is actually full grow storage by 25% at a time.
m_packed_elements.resize_and_keep_capacity(m_array_size + (m_array_size / 4));
m_packed_elements.resize_with_default_value_and_keep_capacity(m_array_size + (m_array_size / 4), js_special_empty_value());
}
}
@ -57,7 +57,7 @@ void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribute
void SimpleIndexedPropertyStorage::remove(u32 index)
{
VERIFY(index < m_array_size);
m_packed_elements[index] = {};
m_packed_elements[index] = js_special_empty_value();
}
ValueAndAttributes SimpleIndexedPropertyStorage::take_first()
@ -70,14 +70,14 @@ ValueAndAttributes SimpleIndexedPropertyStorage::take_last()
{
m_array_size--;
auto last_element = m_packed_elements[m_array_size];
m_packed_elements[m_array_size] = {};
m_packed_elements[m_array_size] = js_special_empty_value();
return { last_element, default_attributes };
}
bool SimpleIndexedPropertyStorage::set_array_like_size(size_t new_size)
{
m_array_size = new_size;
m_packed_elements.resize_and_keep_capacity(new_size);
m_packed_elements.resize_with_default_value_and_keep_capacity(new_size, js_special_empty_value());
return true;
}
@ -87,7 +87,7 @@ GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedProper
m_array_size = storage.array_like_size();
for (size_t i = 0; i < storage.m_packed_elements.size(); ++i) {
auto value = storage.m_packed_elements[i];
if (!value.is_empty())
if (!value.is_special_empty_value())
m_sparse_elements.set(i, { value, default_attributes });
}
}
@ -270,7 +270,7 @@ size_t IndexedProperties::real_size() const
auto& packed_elements = static_cast<SimpleIndexedPropertyStorage const&>(*m_storage).elements();
size_t size = 0;
for (auto& element : packed_elements) {
if (!element.is_empty())
if (!element.is_special_empty_value())
++size;
}
return size;
@ -288,7 +288,7 @@ Vector<u32> IndexedProperties::indices() const
Vector<u32> indices;
indices.ensure_capacity(storage.array_like_size());
for (size_t i = 0; i < elements.size(); ++i) {
if (!elements.at(i).is_empty())
if (!elements.at(i).is_special_empty_value())
indices.unchecked_append(i);
}
return indices;

View file

@ -80,7 +80,7 @@ public:
[[nodiscard]] bool inline_has_index(u32 index) const
{
return index < m_array_size && !m_packed_elements.data()[index].is_empty();
return index < m_array_size && !m_packed_elements.data()[index].is_special_empty_value();
}
[[nodiscard]] Optional<ValueAndAttributes> inline_get(u32 index) const

View file

@ -128,7 +128,7 @@ ThrowCompletionOr<void> Object::set(PropertyKey const& property_key, Value value
{
auto& vm = this->vm();
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
// 1. Let success be ? O.[[Set]](P, V, O).
auto success = TRY(internal_set(property_key, value, this));
@ -161,7 +161,7 @@ ThrowCompletionOr<bool> Object::create_data_property(PropertyKey const& property
// 7.3.6 CreateMethodProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createmethodproperty
void Object::create_method_property(PropertyKey const& property_key, Value value)
{
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
// 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
@ -184,7 +184,7 @@ ThrowCompletionOr<bool> Object::create_data_property_or_throw(PropertyKey const&
{
auto& vm = this->vm();
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
// 1. Let success be ? CreateDataProperty(O, P, V).
auto success = TRY(create_data_property(property_key, value));
@ -202,7 +202,7 @@ ThrowCompletionOr<bool> Object::create_data_property_or_throw(PropertyKey const&
// 7.3.8 CreateNonEnumerableDataPropertyOrThrow ( O, P, V ), https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyorthrow
void Object::create_non_enumerable_data_property_or_throw(PropertyKey const& property_key, Value value)
{
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
// 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
@ -812,7 +812,7 @@ ThrowCompletionOr<Optional<PropertyDescriptor>> Object::internal_get_own_propert
// 4. If X is a data property, then
if (!value.is_accessor()) {
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
descriptor.value = value.value_or(js_undefined());
descriptor.value = value;
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
descriptor.writable = attributes.is_writable();
@ -883,7 +883,7 @@ ThrowCompletionOr<bool> Object::internal_has_property(PropertyKey const& propert
// 10.1.8.1 OrdinaryGet ( O, P, Receiver ), https://tc39.es/ecma262/#sec-ordinaryget
ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
VERIFY(!receiver.is_empty());
VERIFY(!receiver.is_special_empty_value());
auto& vm = this->vm();
@ -950,8 +950,8 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
// 10.1.9.1 OrdinarySet ( O, P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinaryset
ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata)
{
VERIFY(!value.is_empty());
VERIFY(!receiver.is_empty());
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
auto own_descriptor = TRY(internal_get_own_property(property_key));
@ -963,8 +963,8 @@ ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Va
// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ), https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor
ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey const& property_key, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor, CacheablePropertyMetadata* cacheable_metadata)
{
VERIFY(!value.is_empty());
VERIFY(!receiver.is_empty());
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
auto& vm = this->vm();

View file

@ -204,7 +204,6 @@ void Promise::fulfill(Value value)
// 1. Assert: The value of promise.[[PromiseState]] is pending.
VERIFY(m_state == State::Pending);
VERIFY(!value.is_empty());
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
// NOTE: This is a noop, we do these steps in a slightly different order.
@ -235,7 +234,6 @@ void Promise::reject(Value reason)
// 1. Assert: The value of promise.[[PromiseState]] is pending.
VERIFY(m_state == State::Pending);
VERIFY(!reason.is_empty());
// 2. Let reactions be promise.[[PromiseRejectReactions]].
// NOTE: This is a noop, we do these steps in a slightly different order.

View file

@ -196,7 +196,7 @@ void PropertyDescriptor::complete()
{
if (is_generic_descriptor() || is_data_descriptor()) {
if (!value.has_value())
value = Value {};
value = js_undefined();
if (!writable.has_value())
writable = false;
} else {

View file

@ -25,7 +25,7 @@ public:
static ThrowCompletionOr<PropertyKey> from_value(VM& vm, Value value)
{
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
if (value.is_symbol())
return PropertyKey { value.as_symbol() };
if (value.is_integral_number() && value.as_double() >= 0 && value.as_double() < NumericLimits<u32>::max())

View file

@ -486,12 +486,10 @@ ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_k
// NOTE: We don't return any cacheable metadata for proxy lookups.
VERIFY(!receiver.is_empty());
VERIFY(!receiver.is_special_empty_value());
auto& vm = this->vm();
VERIFY(!receiver.is_empty());
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
@ -559,8 +557,8 @@ ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_ke
auto& vm = this->vm();
VERIFY(!value.is_empty());
VERIFY(!receiver.is_empty());
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
// 1. Let handler be O.[[ProxyHandler]].

View file

@ -30,7 +30,7 @@ public:
{
}
Reference(Value base, PropertyKey name, Value this_value, bool strict = false)
Reference(Value base, PropertyKey name, Optional<Value> this_value, bool strict = false)
: m_base_type(BaseType::Value)
, m_base_value(base)
, m_name(move(name))
@ -90,14 +90,14 @@ public:
{
VERIFY(is_property_reference());
if (is_super_reference())
return m_this_value;
return m_this_value.value();
return m_base_value;
}
// 6.2.4.3 IsSuperReference ( V ), https://tc39.es/ecma262/#sec-issuperreference
bool is_super_reference() const
{
return !m_this_value.is_empty();
return m_this_value.has_value();
}
// 6.2.4.4 IsPrivateReference ( V ), https://tc39.es/ecma262/#sec-isprivatereference
@ -131,7 +131,7 @@ private:
mutable Environment* m_base_environment;
};
Variant<PropertyKey, PrivateName> m_name;
Value m_this_value;
Optional<Value> m_this_value;
bool m_strict { false };
Optional<EnvironmentCoordinate> m_environment_coordinate;

View file

@ -158,13 +158,13 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, StringView source_tex
result = result_and_return_register.value.release_error();
} else {
// Resulting value is in the accumulator.
result = result_and_return_register.return_register_value.value_or(js_undefined());
result = result_and_return_register.return_register_value.is_special_empty_value() ? js_undefined() : result_and_return_register.return_register_value;
}
}
}
// 12. If result.[[Type]] is normal and result.[[Value]] is empty, then
if (result.type() == Completion::Type::Normal && result.value().is_empty()) {
if (result.type() == Completion::Type::Normal && result.value().is_special_empty_value()) {
// a. Set result to NormalCompletion(undefined).
result = normal_completion(js_undefined());
}

View file

@ -161,7 +161,7 @@ inline Value typed_array_get_element(TypedArrayBase const& typed_array, Canonica
template<typename T>
inline ThrowCompletionOr<void> typed_array_set_element(TypedArrayBase& typed_array, CanonicalIndex property_index, Value value)
{
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
auto& vm = typed_array.vm();
Value num_value;
@ -329,7 +329,7 @@ public:
// 10.4.5.5 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-typedarray-get
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const override
{
VERIFY(!receiver.is_empty());
VERIFY(!receiver.is_special_empty_value());
// NOTE: If the property name is a number type (An implementation-defined optimized
// property key type), it can be treated as a string property that will transparently be
@ -354,8 +354,8 @@ public:
// 10.4.5.6 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*) override
{
VERIFY(!value.is_empty());
VERIFY(!receiver.is_empty());
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
// NOTE: If the property name is a number type (An implementation-defined optimized
// property key type), it can be treated as a string property that will transparently be

View file

@ -175,7 +175,7 @@ public:
Value this_value() const
{
return running_execution_context().this_value;
return running_execution_context().this_value.value();
}
ThrowCompletionOr<Value> resolve_this_binding();

View file

@ -559,7 +559,7 @@ ThrowCompletionOr<Value> Value::to_primitive_slow_case(VM& vm, PreferredType pre
ThrowCompletionOr<GC::Ref<Object>> Value::to_object(VM& vm) const
{
auto& realm = *vm.current_realm();
VERIFY(!is_empty());
VERIFY(!is_special_empty_value());
// Number
if (is_number()) {
@ -712,7 +712,7 @@ double string_to_number(StringView string)
// 7.1.4 ToNumber ( argument ), https://tc39.es/ecma262/#sec-tonumber
ThrowCompletionOr<Value> Value::to_number_slow_case(VM& vm) const
{
VERIFY(!is_empty());
VERIFY(!is_special_empty_value());
// 1. If argument is a Number, return argument.
if (is_number())

View file

@ -95,7 +95,7 @@ public:
[[nodiscard]] u16 tag() const { return m_value.tag; }
bool is_empty() const { return m_value.tag == EMPTY_TAG; }
bool is_special_empty_value() const { return m_value.tag == EMPTY_TAG; }
bool is_undefined() const { return m_value.tag == UNDEFINED_TAG; }
bool is_null() const { return m_value.tag == NULL_TAG; }
bool is_number() const { return is_double() || is_int32(); }
@ -155,7 +155,7 @@ public:
}
Value()
: Value(EMPTY_TAG << GC::TAG_SHIFT, (u64)0)
: Value(UNDEFINED_TAG << GC::TAG_SHIFT, (u64)0)
{
}
@ -379,12 +379,14 @@ public:
[[nodiscard]] String to_string_without_side_effects() const;
#if 0
Value value_or(Value fallback) const
{
if (is_empty())
if (is_special_empty_value())
return fallback;
return *this;
}
#endif
[[nodiscard]] GC::Ref<PrimitiveString> typeof_(VM&) const;
@ -424,6 +426,13 @@ private:
ThrowCompletionOr<Value> to_numeric_slow_case(VM&) const;
ThrowCompletionOr<Value> to_primitive_slow_case(VM&, PreferredType) const;
enum class EmptyTag { Empty };
Value(EmptyTag)
: Value(EMPTY_TAG << GC::TAG_SHIFT, (u64)0)
{
}
Value(u64 tag, u64 val)
{
ASSERT(!(tag & val));
@ -460,6 +469,7 @@ private:
friend Value js_undefined();
friend Value js_null();
friend Value js_special_empty_value();
friend ThrowCompletionOr<Value> greater_than(VM&, Value lhs, Value rhs);
friend ThrowCompletionOr<Value> greater_than_equals(VM&, Value lhs, Value rhs);
friend ThrowCompletionOr<Value> less_than(VM&, Value lhs, Value rhs);
@ -478,6 +488,11 @@ inline Value js_null()
return Value(NULL_TAG << GC::TAG_SHIFT, (u64)0);
}
inline Value js_special_empty_value()
{
return Value(Value::EmptyTag::Empty);
}
inline Value js_nan()
{
return Value(NAN);
@ -600,12 +615,12 @@ public:
void clear()
{
m_value = {};
m_value = JS::js_special_empty_value();
}
[[nodiscard]] bool has_value() const
{
return !m_value.is_empty();
return !m_value.is_special_empty_value();
}
[[nodiscard]] JS::Value& value() &
@ -634,7 +649,7 @@ public:
}
private:
JS::Value m_value;
JS::Value m_value { JS::js_special_empty_value() };
};
}
@ -690,7 +705,7 @@ template<>
struct Formatter<JS::Value> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, JS::Value value)
{
if (value.is_empty())
if (value.is_special_empty_value())
return Formatter<StringView>::format(builder, "<empty>"sv);
return Formatter<StringView>::format(builder, value.to_string_without_side_effects());
}

View file

@ -16,7 +16,7 @@ namespace JS {
struct ValueTraits : public Traits<Value> {
static unsigned hash(Value value)
{
VERIFY(!value.is_empty());
VERIFY(!value.is_special_empty_value());
if (value.is_string())
return value.as_string().utf8_string().hash();

View file

@ -727,7 +727,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
result = result_and_return_register.value.release_error();
} else {
// Resulting value is in the accumulator.
result = result_and_return_register.return_register_value.value_or(js_undefined());
result = result_and_return_register.return_register_value.is_special_empty_value() ? js_undefined() : result_and_return_register.return_register_value;
}
}

View file

@ -436,8 +436,8 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
StringBuilder detail_builder;
auto& error_object = error.as_object();
auto name = error_object.get_without_side_effects(g_vm->names.name).value_or(JS::js_undefined());
auto message = error_object.get_without_side_effects(g_vm->names.message).value_or(JS::js_undefined());
auto name = error_object.get_without_side_effects(g_vm->names.name);
auto message = error_object.get_without_side_effects(g_vm->names.message);
if (name.is_accessor() || message.is_accessor()) {
detail_builder.append(error.to_string_without_side_effects());

View file

@ -22,8 +22,8 @@ void report_exception_to_console(JS::Value value, JS::Realm& realm, ErrorInPromi
if (value.is_object()) {
auto& object = value.as_object();
auto& vm = object.vm();
auto name = object.get_without_side_effects(vm.names.name).value_or(JS::js_undefined());
auto message = object.get_without_side_effects(vm.names.message).value_or(JS::js_undefined());
auto name = object.get_without_side_effects(vm.names.name);
auto message = object.get_without_side_effects(vm.names.message);
if (name.is_accessor() || message.is_accessor()) {
// The result is not going to be useful, let's just print the value. This affects DOMExceptions, for example.
if (is<WebIDL::DOMException>(object)) {

View file

@ -51,14 +51,14 @@ static JS::Value create_close_sentinel()
{
// The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers.
// Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers.
return {};
return JS::js_special_empty_value();
}
// https://streams.spec.whatwg.org/#close-sentinel
// Non-standard function that implements the "If value is a close sentinel" check.
static bool is_close_sentinel(JS::Value value)
{
return value.is_empty();
return value.is_special_empty_value();
}
// NON-STANDARD: Can be used instead of CreateReadableStream in cases where we need to set up a newly allocated

View file

@ -103,8 +103,8 @@ void DevToolsConsoleClient::report_exception(JS::Error const& exception, bool in
{
auto& vm = exception.vm();
auto name = exception.get_without_side_effects(vm.names.name).value_or(JS::js_undefined());
auto message = exception.get_without_side_effects(vm.names.message).value_or(JS::js_undefined());
auto name = exception.get_without_side_effects(vm.names.name);
auto message = exception.get_without_side_effects(vm.names.message);
Vector<WebView::StackFrame> trace;
trace.ensure_capacity(exception.traceback().size());

View file

@ -86,7 +86,7 @@ TEST_CASE(non_canon_nans)
EXPECT(!val.is_integral_number()); \
EXPECT(!val.is_finite_number()); \
EXPECT(!val.is_infinity()); \
EXPECT(!val.is_empty()); \
EXPECT(!val.is_special_empty_value()); \
EXPECT(!val.is_nullish()); \
}

View file

@ -91,7 +91,7 @@ static ErrorOr<void, TestError> run_program(InterpreterT& interpreter, ScriptOrM
auto& object = error_value.as_object();
auto name = object.get_without_side_effects("name"_fly_string);
if (!name.is_empty() && !name.is_accessor()) {
if (!name.is_undefined() && !name.is_accessor()) {
error.type = name.to_string_without_side_effects();
} else {
auto constructor = object.get_without_side_effects("constructor"_fly_string);
@ -103,7 +103,7 @@ static ErrorOr<void, TestError> run_program(InterpreterT& interpreter, ScriptOrM
}
auto message = object.get_without_side_effects("message"_fly_string);
if (!message.is_empty() && !message.is_accessor())
if (!message.is_undefined() && !message.is_accessor())
error.details = message.to_string_without_side_effects();
}
if (error.type.is_empty())

View file

@ -778,7 +778,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
if (value_or_error.is_error())
return {};
auto variable = value_or_error.value();
VERIFY(!variable.is_empty());
VERIFY(!variable.is_special_empty_value());
if (!variable.is_object())
break;