1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 04:48:14 +00:00

LibJS: Add side-effect-free version of Value::to_string()

There are now two API's on Value:

- Value::to_string(Interpreter&) -- may throw.
- Value::to_string_without_side_effects() -- will never throw.

These are some pretty big sweeping changes, so it's possible that I did
some part the wrong way. We'll work it out as we go. :^)

Fixes #2123.
This commit is contained in:
Andreas Kling 2020-05-15 13:39:24 +02:00
parent d8aa2a6997
commit c6ddbd1f3e
25 changed files with 285 additions and 112 deletions

View file

@ -123,9 +123,9 @@ Value CallExpression::execute(Interpreter& interpreter) const
expression_string = static_cast<const Identifier&>(*m_callee).string(); expression_string = static_cast<const Identifier&>(*m_callee).string();
else else
expression_string = static_cast<const MemberExpression&>(*m_callee).to_string_approximation(); expression_string = static_cast<const MemberExpression&>(*m_callee).to_string_approximation();
error_message = String::format("%s is not a %s (evaluated from '%s')", callee.to_string().characters(), call_type, expression_string.characters()); error_message = String::format("%s is not a %s (evaluated from '%s')", callee.to_string_without_side_effects().characters(), call_type, expression_string.characters());
} else { } else {
error_message = String::format("%s is not a %s", callee.to_string().characters(), call_type); error_message = String::format("%s is not a %s", callee.to_string_without_side_effects().characters(), call_type);
} }
return interpreter.throw_exception<TypeError>(error_message); return interpreter.throw_exception<TypeError>(error_message);
} }
@ -151,7 +151,7 @@ Value CallExpression::execute(Interpreter& interpreter) const
for (auto ch : static_cast<const StringObject&>(value.as_object()).primitive_string().string()) for (auto ch : static_cast<const StringObject&>(value.as_object()).primitive_string().string())
iterables.append(Value(js_string(interpreter, String::format("%c", ch)))); iterables.append(Value(js_string(interpreter, String::format("%c", ch))));
} else { } else {
interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string().characters())); interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string_without_side_effects().characters()));
} }
for (auto& value : iterables) for (auto& value : iterables)
arguments.append(value); arguments.append(value);
@ -1154,7 +1154,9 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
continue; continue;
} }
auto key = key_result.to_string(); auto key = key_result.to_string(interpreter);
if (interpreter.exception())
return {};
auto value = property.value().execute(interpreter); auto value = property.value().execute(interpreter);
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
@ -1184,14 +1186,13 @@ PropertyName MemberExpression::computed_property_name(Interpreter& interpreter)
ASSERT(!index.is_empty()); ASSERT(!index.is_empty());
if (!index.to_number().is_finite_number()) if (index.is_integer() && index.to_i32() >= 0)
return PropertyName(index.to_string()); return PropertyName(index.to_i32());
auto index_as_double = index.to_double(); auto index_string = index.to_string(interpreter);
if (index_as_double < 0 || (i32)index_as_double != index_as_double) if (interpreter.exception())
return PropertyName(index.to_string()); return {};
return PropertyName(index_string);
return PropertyName(index.to_i32());
} }
String MemberExpression::to_string_approximation() const String MemberExpression::to_string_approximation() const
@ -1283,7 +1284,7 @@ Value ArrayExpression::execute(Interpreter& interpreter) const
array->elements().append(js_string(interpreter, string_to_spread.substring(i, 1))); array->elements().append(js_string(interpreter, string_to_spread.substring(i, 1)));
continue; continue;
} }
interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string().characters())); interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string_without_side_effects().characters()));
return {}; return {};
} }
} }
@ -1307,7 +1308,10 @@ Value TemplateLiteral::execute(Interpreter& interpreter) const
auto expr = expression.execute(interpreter); auto expr = expression.execute(interpreter);
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
string_builder.append(expr.to_string()); auto string = expr.to_string(interpreter);
if (interpreter.exception())
return {};
string_builder.append(string);
} }
return js_string(interpreter, string_builder.build()); return js_string(interpreter, string_builder.build());
@ -1330,7 +1334,7 @@ Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
if (!tag.is_function()) { if (!tag.is_function()) {
interpreter.throw_exception<TypeError>(String::format("%s is not a function", tag.to_string().characters())); interpreter.throw_exception<TypeError>(String::format("%s is not a function", tag.to_string_without_side_effects().characters()));
return {}; return {};
} }
auto& tag_function = tag.as_function(); auto& tag_function = tag.as_function();

View file

@ -116,7 +116,7 @@ Value Console::count()
if (m_client) if (m_client)
return m_client->count(); return m_client->count();
auto label = m_interpreter.argument_count() ? m_interpreter.argument(0).to_string() : "default"; auto label = m_interpreter.argument_count() ? m_interpreter.argument(0).to_string_without_side_effects() : "default";
auto counter_value = counter_increment(label); auto counter_value = counter_increment(label);
dbg() << "log: " << label << ": " << counter_value; dbg() << "log: " << label << ": " << counter_value;
@ -129,7 +129,7 @@ Value Console::count_reset()
if (m_client) if (m_client)
return m_client->count_reset(); return m_client->count_reset();
auto label = m_interpreter.argument_count() ? m_interpreter.argument(0).to_string() : "default"; auto label = m_interpreter.argument_count() ? m_interpreter.argument(0).to_string_without_side_effects() : "default";
if (counter_reset(label)) if (counter_reset(label))
dbg() << "log: " << label << ": 0"; dbg() << "log: " << label << ": 0";

View file

@ -266,7 +266,7 @@ String Interpreter::join_arguments() const
{ {
StringBuilder joined_arguments; StringBuilder joined_arguments;
for (size_t i = 0; i < argument_count(); ++i) { for (size_t i = 0; i < argument_count(); ++i) {
joined_arguments.append(argument(i).to_string().characters()); joined_arguments.append(argument(i).to_string_without_side_effects().characters());
if (i != argument_count() - 1) if (i != argument_count() - 1)
joined_arguments.append(' '); joined_arguments.append(' ');
} }

View file

@ -77,7 +77,7 @@ static Function* callback_from_args(Interpreter& interpreter, const String& name
} }
auto callback = interpreter.argument(0); auto callback = interpreter.argument(0);
if (!callback.is_function()) { if (!callback.is_function()) {
interpreter.throw_exception<TypeError>(String::format("%s is not a function", callback.to_string().characters())); interpreter.throw_exception<TypeError>(String::format("%s is not a function", callback.to_string_without_side_effects().characters()));
return nullptr; return nullptr;
} }
return &callback.as_function(); return &callback.as_function();
@ -227,8 +227,12 @@ static Value join_array_with_separator(Interpreter& interpreter, const Array& ar
if (i != 0) if (i != 0)
builder.append(separator); builder.append(separator);
auto value = array.elements()[i]; auto value = array.elements()[i];
if (!value.is_empty() && !value.is_undefined() && !value.is_null()) if (!value.is_empty() && !value.is_undefined() && !value.is_null()) {
builder.append(value.to_string()); auto string = value.to_string(interpreter);
if (interpreter.exception())
return {};
builder.append(string);
}
} }
return js_string(interpreter, builder.to_string()); return js_string(interpreter, builder.to_string());
} }
@ -249,8 +253,11 @@ Value ArrayPrototype::join(Interpreter& interpreter)
return {}; return {};
String separator = ","; String separator = ",";
if (interpreter.argument_count() == 1) if (interpreter.argument_count() == 1) {
separator = interpreter.argument(0).to_string(); separator = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
}
return join_array_with_separator(interpreter, *array, separator); return join_array_with_separator(interpreter, *array, separator);
} }

View file

@ -50,29 +50,35 @@ Value ErrorConstructor::call(Interpreter& interpreter)
Value ErrorConstructor::construct(Interpreter& interpreter) Value ErrorConstructor::construct(Interpreter& interpreter)
{ {
String message = ""; String message = "";
if (!interpreter.call_frame().arguments.is_empty() && !interpreter.call_frame().arguments[0].is_undefined()) if (!interpreter.call_frame().arguments.is_empty() && !interpreter.call_frame().arguments[0].is_undefined()) {
message = interpreter.call_frame().arguments[0].to_string(); message = interpreter.call_frame().arguments[0].to_string(interpreter);
if (interpreter.exception())
return {};
}
return Error::create(interpreter.global_object(), "Error", message); return Error::create(interpreter.global_object(), "Error", message);
} }
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \
ConstructorName::ConstructorName() \ ConstructorName::ConstructorName() \
: NativeFunction(*interpreter().global_object().function_prototype()) \ : NativeFunction(*interpreter().global_object().function_prototype()) \
{ \ { \
put("prototype", interpreter().global_object().snake_name##_prototype(), 0); \ put("prototype", interpreter().global_object().snake_name##_prototype(), 0); \
put("length", Value(1), Attribute::Configurable); \ put("length", Value(1), Attribute::Configurable); \
} \ } \
ConstructorName::~ConstructorName() { } \ ConstructorName::~ConstructorName() { } \
Value ConstructorName::call(Interpreter& interpreter) \ Value ConstructorName::call(Interpreter& interpreter) \
{ \ { \
return construct(interpreter); \ return construct(interpreter); \
} \ } \
Value ConstructorName::construct(Interpreter& interpreter) \ Value ConstructorName::construct(Interpreter& interpreter) \
{ \ { \
String message = ""; \ String message = ""; \
if (!interpreter.call_frame().arguments.is_empty() && !interpreter.call_frame().arguments[0].is_undefined()) \ if (!interpreter.call_frame().arguments.is_empty() && !interpreter.call_frame().arguments[0].is_undefined()) { \
message = interpreter.call_frame().arguments[0].to_string(); \ message = interpreter.call_frame().arguments[0].to_string(interpreter); \
return ClassName::create(interpreter.global_object(), message); \ if (interpreter.exception()) \
return {}; \
} \
return ClassName::create(interpreter.global_object(), message); \
} }
JS_ENUMERATE_ERROR_SUBCLASSES JS_ENUMERATE_ERROR_SUBCLASSES

View file

@ -67,7 +67,9 @@ void ErrorPrototype::name_setter(Interpreter& interpreter, Value value)
interpreter.throw_exception<TypeError>("Not an Error object"); interpreter.throw_exception<TypeError>("Not an Error object");
return; return;
} }
auto name = FlyString(value.to_string()); auto name = value.to_string(interpreter);
if (interpreter.exception())
return;
static_cast<Error*>(this_object)->set_name(name); static_cast<Error*>(this_object)->set_name(name);
} }
@ -89,13 +91,19 @@ Value ErrorPrototype::to_string(Interpreter& interpreter)
String name = "Error"; String name = "Error";
auto object_name_property = this_object.get("name"); auto object_name_property = this_object.get("name");
if (!object_name_property.is_empty() && !object_name_property.is_undefined()) if (!object_name_property.is_empty() && !object_name_property.is_undefined()) {
name = object_name_property.to_string(); name = object_name_property.to_string(interpreter);
if (interpreter.exception())
return {};
}
String message = ""; String message = "";
auto object_message_property = this_object.get("message"); auto object_message_property = this_object.get("message");
if (!object_message_property.is_empty() && !object_message_property.is_undefined()) if (!object_message_property.is_empty() && !object_message_property.is_undefined()) {
message = object_message_property.to_string(); message = object_message_property.to_string(interpreter);
if (interpreter.exception())
return {};
}
if (name.length() == 0) if (name.length() == 0)
return js_string(interpreter, message); return js_string(interpreter, message);

View file

@ -55,16 +55,24 @@ Value FunctionConstructor::construct(Interpreter& interpreter)
{ {
String parameters_source = ""; String parameters_source = "";
String body_source = ""; String body_source = "";
if (interpreter.argument_count() == 1) if (interpreter.argument_count() == 1) {
body_source = interpreter.argument(0).to_string(); body_source = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
}
if (interpreter.argument_count() > 1) { if (interpreter.argument_count() > 1) {
Vector<String> parameters; Vector<String> parameters;
for (size_t i = 0; i < interpreter.argument_count() - 1; ++i) for (size_t i = 0; i < interpreter.argument_count() - 1; ++i) {
parameters.append(interpreter.argument(i).to_string()); parameters.append(interpreter.argument(i).to_string(interpreter));
if (interpreter.exception())
return {};
}
StringBuilder parameters_builder; StringBuilder parameters_builder;
parameters_builder.join(',', parameters); parameters_builder.join(',', parameters);
parameters_source = parameters_builder.build(); parameters_source = parameters_builder.build();
body_source = interpreter.argument(interpreter.argument_count() - 1).to_string(); body_source = interpreter.argument(interpreter.argument_count() - 1).to_string(interpreter);
if (interpreter.exception())
return {};
} }
auto source = String::format("function anonymous(%s) { %s }", parameters_source.characters(), body_source.characters()); auto source = String::format("function anonymous(%s) { %s }", parameters_source.characters(), body_source.characters());
auto parser = Parser(Lexer(source)); auto parser = Parser(Lexer(source));

View file

@ -485,7 +485,10 @@ Value Object::to_string() const
interpreter.throw_exception<TypeError>("Cannot convert object to string"); interpreter.throw_exception<TypeError>("Cannot convert object to string");
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
return js_string(heap(), to_string_result.to_string()); auto* string = to_string_result.to_primitive_string(interpreter);
if (interpreter.exception())
return {};
return string;
} }
return js_string(heap(), String::format("[object %s]", class_name())); return js_string(heap(), String::format("[object %s]", class_name()));
} }

View file

@ -112,7 +112,9 @@ Value ObjectConstructor::get_own_property_descriptor(Interpreter& interpreter)
auto* object = interpreter.argument(0).to_object(interpreter.heap()); auto* object = interpreter.argument(0).to_object(interpreter.heap());
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
return object->get_own_property_descriptor(property_key); return object->get_own_property_descriptor(property_key);
} }
@ -123,7 +125,9 @@ Value ObjectConstructor::define_property(Interpreter& interpreter)
if (!interpreter.argument(2).is_object()) if (!interpreter.argument(2).is_object())
return interpreter.throw_exception<TypeError>("Descriptor argument is not an object"); return interpreter.throw_exception<TypeError>("Descriptor argument is not an object");
auto& object = interpreter.argument(0).as_object(); auto& object = interpreter.argument(0).as_object();
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
auto& descriptor = interpreter.argument(2).as_object(); auto& descriptor = interpreter.argument(2).as_object();
object.define_property(property_key, descriptor); object.define_property(property_key, descriptor);
return &object; return &object;

View file

@ -57,7 +57,10 @@ Value ObjectPrototype::has_own_property(Interpreter& interpreter)
auto* this_object = interpreter.this_value().to_object(interpreter.heap()); auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object) if (!this_object)
return {}; return {};
return Value(this_object->has_own_property(interpreter.argument(0).to_string())); auto name = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
return Value(this_object->has_own_property(name));
} }
Value ObjectPrototype::to_string(Interpreter& interpreter) Value ObjectPrototype::to_string(Interpreter& interpreter)

View file

@ -138,7 +138,9 @@ Value ReflectObject::define_property(Interpreter& interpreter)
return {}; return {};
if (!interpreter.argument(2).is_object()) if (!interpreter.argument(2).is_object())
return interpreter.throw_exception<TypeError>("Descriptor argument is not an object"); return interpreter.throw_exception<TypeError>("Descriptor argument is not an object");
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
auto& descriptor = interpreter.argument(2).as_object(); auto& descriptor = interpreter.argument(2).as_object();
auto success = target->define_property(property_key, descriptor, false); auto success = target->define_property(property_key, descriptor, false);
return Value(success); return Value(success);
@ -151,13 +153,15 @@ Value ReflectObject::delete_property(Interpreter& interpreter)
return {}; return {};
auto property_key = interpreter.argument(1); auto property_key = interpreter.argument(1);
PropertyName property = PropertyName(property_key.to_string()); auto property_name = PropertyName(property_key.to_string(interpreter));
if (interpreter.exception())
return {};
if (property_key.to_number().is_finite_number()) { if (property_key.to_number().is_finite_number()) {
auto property_key_as_double = property_key.to_double(); auto property_key_as_double = property_key.to_double();
if (property_key_as_double >= 0 && (i32)property_key_as_double == property_key_as_double) if (property_key_as_double >= 0 && (i32)property_key_as_double == property_key_as_double)
property = PropertyName(property_key_as_double); property_name = PropertyName(property_key_as_double);
} }
return target->delete_property(property); return target->delete_property(property_name);
} }
Value ReflectObject::get(Interpreter& interpreter) Value ReflectObject::get(Interpreter& interpreter)
@ -166,7 +170,9 @@ Value ReflectObject::get(Interpreter& interpreter)
auto* target = get_target_object_from(interpreter, "get"); auto* target = get_target_object_from(interpreter, "get");
if (!target) if (!target)
return {}; return {};
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
return target->get(property_key).value_or(js_undefined()); return target->get(property_key).value_or(js_undefined());
} }
@ -175,7 +181,9 @@ Value ReflectObject::get_own_property_descriptor(Interpreter& interpreter)
auto* target = get_target_object_from(interpreter, "getOwnPropertyDescriptor"); auto* target = get_target_object_from(interpreter, "getOwnPropertyDescriptor");
if (!target) if (!target)
return {}; return {};
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
return target->get_own_property_descriptor(property_key); return target->get_own_property_descriptor(property_key);
} }
@ -192,7 +200,9 @@ Value ReflectObject::has(Interpreter& interpreter)
auto* target = get_target_object_from(interpreter, "has"); auto* target = get_target_object_from(interpreter, "has");
if (!target) if (!target)
return {}; return {};
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
return Value(target->has_property(property_key)); return Value(target->has_property(property_key));
} }
@ -224,7 +234,9 @@ Value ReflectObject::set(Interpreter& interpreter)
auto* target = get_target_object_from(interpreter, "set"); auto* target = get_target_object_from(interpreter, "set");
if (!target) if (!target)
return {}; return {};
auto property_key = interpreter.argument(1).to_string(); auto property_key = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
auto value = interpreter.argument(2); auto value = interpreter.argument(2);
return Value(target->put(property_key, value)); return Value(target->put(property_key, value));
} }

View file

@ -51,7 +51,10 @@ Value StringConstructor::call(Interpreter& interpreter)
{ {
if (!interpreter.argument_count()) if (!interpreter.argument_count())
return js_string(interpreter, ""); return js_string(interpreter, "");
return js_string(interpreter, interpreter.argument(0).to_string()); auto* string = interpreter.argument(0).to_primitive_string(interpreter);
if (interpreter.exception())
return {};
return string;
} }
Value StringConstructor::construct(Interpreter& interpreter) Value StringConstructor::construct(Interpreter& interpreter)
@ -60,7 +63,7 @@ Value StringConstructor::construct(Interpreter& interpreter)
if (!interpreter.argument_count()) if (!interpreter.argument_count())
primitive_string = js_string(interpreter, ""); primitive_string = js_string(interpreter, "");
else else
primitive_string = js_string(interpreter, interpreter.argument(0).to_string()); primitive_string = interpreter.argument(0).to_primitive_string(interpreter);
if (!primitive_string) if (!primitive_string)
return {}; return {};
return StringObject::create(interpreter.global_object(), *primitive_string); return StringObject::create(interpreter.global_object(), *primitive_string);
@ -84,9 +87,14 @@ Value StringConstructor::raw(Interpreter& interpreter)
StringBuilder builder; StringBuilder builder;
for (size_t i = 0; i < raw_array_elements.size(); ++i) { for (size_t i = 0; i < raw_array_elements.size(); ++i) {
builder.append(raw_array_elements.at(i).to_string()); builder.append(raw_array_elements.at(i).to_string(interpreter));
if (i + 1 < interpreter.argument_count() && i < raw_array_elements.size() - 1) if (interpreter.exception())
builder.append(interpreter.argument(i + 1).to_string()); return {};
if (i + 1 < interpreter.argument_count() && i < raw_array_elements.size() - 1) {
builder.append(interpreter.argument(i + 1).to_string(interpreter));
if (interpreter.exception())
return {};
}
} }
return js_string(interpreter, builder.build()); return js_string(interpreter, builder.build());

View file

@ -56,7 +56,7 @@ static String string_from(Interpreter& interpreter)
auto* this_object = interpreter.this_value().to_object(interpreter.heap()); auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object) if (!this_object)
return {}; return {};
return Value(this_object).to_string(); return Value(this_object).to_string(interpreter);
} }
StringPrototype::StringPrototype() StringPrototype::StringPrototype()
@ -127,7 +127,9 @@ Value StringPrototype::starts_with(Interpreter& interpreter)
return {}; return {};
if (!interpreter.argument_count()) if (!interpreter.argument_count())
return Value(false); return Value(false);
auto search_string = interpreter.argument(0).to_string(); auto search_string = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
auto search_string_length = static_cast<i32>(search_string.length()); auto search_string_length = static_cast<i32>(search_string.length());
i32 position = 0; i32 position = 0;
if (interpreter.argument_count() > 1) { if (interpreter.argument_count() > 1) {
@ -152,7 +154,9 @@ Value StringPrototype::index_of(Interpreter& interpreter)
Value needle_value = js_undefined(); Value needle_value = js_undefined();
if (interpreter.argument_count() >= 1) if (interpreter.argument_count() >= 1)
needle_value = interpreter.argument(0); needle_value = interpreter.argument(0);
auto needle = needle_value.to_string(); auto needle = needle_value.to_string(interpreter);
if (interpreter.exception())
return {};
return Value((i32)string.index_of(needle).value_or(-1)); return Value((i32)string.index_of(needle).value_or(-1));
} }
@ -204,8 +208,11 @@ static Value pad_string(Interpreter& interpreter, const String& string, PadPlace
return js_string(interpreter, string); return js_string(interpreter, string);
String fill_string = " "; String fill_string = " ";
if (!interpreter.argument(1).is_undefined()) if (!interpreter.argument(1).is_undefined()) {
fill_string = interpreter.argument(1).to_string(); fill_string = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
}
if (fill_string.is_empty()) if (fill_string.is_empty())
return js_string(interpreter, string); return js_string(interpreter, string);
@ -269,7 +276,9 @@ Value StringPrototype::concat(Interpreter& interpreter)
StringBuilder builder; StringBuilder builder;
builder.append(string); builder.append(string);
for (size_t i = 0; i < interpreter.argument_count(); ++i) { for (size_t i = 0; i < interpreter.argument_count(); ++i) {
auto string_argument = interpreter.argument(i).to_string(); auto string_argument = interpreter.argument(i).to_string(interpreter);
if (interpreter.exception())
return {};
builder.append(string_argument); builder.append(string_argument);
} }
return js_string(interpreter, builder.to_string()); return js_string(interpreter, builder.to_string());
@ -324,7 +333,9 @@ Value StringPrototype::includes(Interpreter& interpreter)
auto string = string_from(interpreter); auto string = string_from(interpreter);
if (string.is_null()) if (string.is_null())
return {}; return {};
auto search_string = interpreter.argument(0).to_string(); auto search_string = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
i32 position = 0; i32 position = 0;
if (interpreter.argument_count() >= 2) { if (interpreter.argument_count() >= 2) {
@ -393,7 +404,9 @@ Value StringPrototype::last_index_of(Interpreter& interpreter)
if (interpreter.argument_count() == 0) if (interpreter.argument_count() == 0)
return Value(-1); return Value(-1);
auto search_string = interpreter.argument(0).to_string(); auto search_string = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
if (search_string.length() > string.length()) if (search_string.length() > string.length())
return Value(-1); return Value(-1);

View file

@ -26,6 +26,7 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibJS/Heap/Heap.h> #include <LibJS/Heap/Heap.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
@ -60,7 +61,7 @@ Function& Value::as_function()
return static_cast<Function&>(as_object()); return static_cast<Function&>(as_object());
} }
String Value::to_string() const String Value::to_string_without_side_effects() const
{ {
if (is_boolean()) if (is_boolean())
return as_bool() ? "true" : "false"; return as_bool() ? "true" : "false";
@ -78,8 +79,47 @@ String Value::to_string() const
if (is_infinity()) if (is_infinity())
return as_double() < 0 ? "-Infinity" : "Infinity"; return as_double() < 0 ? "-Infinity" : "Infinity";
// FIXME: This needs improvement. if (is_integer())
if ((double)to_i32() == as_double()) return String::number(to_i32());
return String::format("%.4f", as_double());
}
if (is_string())
return m_value.as_string->string();
ASSERT(is_object());
return String::format("[object %s]", as_object().class_name());
}
PrimitiveString* Value::to_primitive_string(Interpreter & interpreter)
{
if (is_string())
return &as_string();
auto string = to_string(interpreter);
if (interpreter.exception())
return nullptr;
return js_string(interpreter, string);
}
String Value::to_string(Interpreter& interpreter) const
{
if (is_boolean())
return as_bool() ? "true" : "false";
if (is_null())
return "null";
if (is_undefined())
return "undefined";
if (is_number()) {
if (is_nan())
return "NaN";
if (is_infinity())
return as_double() < 0 ? "-Infinity" : "Infinity";
if (is_integer())
return String::number(to_i32()); return String::number(to_i32());
return String::format("%.4f", as_double()); return String::format("%.4f", as_double());
} }
@ -89,13 +129,11 @@ String Value::to_string() const
// FIXME: Maybe we should pass in the Interpreter& and call interpreter.exception() instead? // FIXME: Maybe we should pass in the Interpreter& and call interpreter.exception() instead?
if (primitive_value.is_empty()) if (primitive_value.is_empty())
return {}; return {};
return primitive_value.to_string(); return primitive_value.to_string(interpreter);
} }
if (is_string()) ASSERT(is_string());
return m_value.as_string->string(); return m_value.as_string->string();
ASSERT_NOT_REACHED();
} }
bool Value::to_boolean() const bool Value::to_boolean() const
@ -305,10 +343,24 @@ Value unsigned_right_shift(Interpreter&, Value lhs, Value rhs)
Value add(Interpreter& interpreter, Value lhs, Value rhs) Value add(Interpreter& interpreter, Value lhs, Value rhs)
{ {
auto lhs_primitive = lhs.to_primitive(interpreter); auto lhs_primitive = lhs.to_primitive(interpreter);
if (interpreter.exception())
return {};
auto rhs_primitive = rhs.to_primitive(interpreter); auto rhs_primitive = rhs.to_primitive(interpreter);
if (interpreter.exception())
return {};
if (lhs_primitive.is_string() || rhs_primitive.is_string()) if (lhs_primitive.is_string() || rhs_primitive.is_string()) {
return js_string(interpreter.heap(), String::format("%s%s", lhs_primitive.to_string().characters(), rhs_primitive.to_string().characters())); auto lhs_string = lhs_primitive.to_string(interpreter);
if (interpreter.exception())
return {};
auto rhs_string = rhs_primitive.to_string(interpreter);
if (interpreter.exception())
return {};
StringBuilder builder(lhs_string.length() + rhs_string.length());
builder.append(lhs_string);
builder.append(rhs_string);
return js_string(interpreter, builder.to_string());
}
return Value(lhs_primitive.to_number().as_double() + rhs_primitive.to_number().as_double()); return Value(lhs_primitive.to_number().as_double() + rhs_primitive.to_number().as_double());
} }
@ -350,7 +402,11 @@ Value in(Interpreter& interpreter, Value lhs, Value rhs)
if (!rhs.is_object()) if (!rhs.is_object())
return interpreter.throw_exception<TypeError>("'in' operator must be used on object"); return interpreter.throw_exception<TypeError>("'in' operator must be used on object");
return Value(!rhs.as_object().get(lhs.to_string()).is_empty()); auto lhs_string = lhs.to_string(interpreter);
if (interpreter.exception())
return {};
return Value(!rhs.as_object().get(lhs_string).is_empty());
} }
Value instance_of(Interpreter&, Value lhs, Value rhs) Value instance_of(Interpreter&, Value lhs, Value rhs)
@ -367,7 +423,7 @@ Value instance_of(Interpreter&, Value lhs, Value rhs)
const LogStream& operator<<(const LogStream& stream, const Value& value) const LogStream& operator<<(const LogStream& stream, const Value& value)
{ {
return stream << (value.is_empty() ? "<empty>" : value.to_string()); return stream << (value.is_empty() ? "<empty>" : value.to_string_without_side_effects());
} }
bool same_value(Interpreter& interpreter, Value lhs, Value rhs) bool same_value(Interpreter& interpreter, Value lhs, Value rhs)

View file

@ -159,9 +159,12 @@ public:
return m_value.as_cell; return m_value.as_cell;
} }
String to_string_without_side_effects() const;
Function& as_function(); Function& as_function();
String to_string() const; String to_string(Interpreter&) const;
PrimitiveString* to_primitive_string(Interpreter&);
bool to_boolean() const; bool to_boolean() const;
Value to_number() const; Value to_number() const;
i32 to_i32() const; i32 to_i32() const;

View file

@ -30,6 +30,7 @@ try {
message: "1 is not iterable", message: "1 is not iterable",
}); });
assertThrowsError(() => { assertThrowsError(() => {
[...{}]; [...{}];
}, { }, {

View file

@ -43,7 +43,7 @@ try {
new isNaN(); new isNaN();
}, { }, {
error: TypeError, error: TypeError,
message: "function isNaN() {\n [NativeFunction]\n} is not a constructor (evaluated from 'isNaN')" message: "[object NativeFunction] is not a constructor (evaluated from 'isNaN')"
}); });
console.log("PASS"); console.log("PASS");

View file

@ -161,8 +161,12 @@ JS::Value CanvasRenderingContext2DWrapper::fill_style_getter(JS::Interpreter& in
void CanvasRenderingContext2DWrapper::fill_style_setter(JS::Interpreter& interpreter, JS::Value value) void CanvasRenderingContext2DWrapper::fill_style_setter(JS::Interpreter& interpreter, JS::Value value)
{ {
if (auto* impl = impl_from(interpreter)) if (auto* impl = impl_from(interpreter)) {
impl->set_fill_style(value.to_string()); auto string = value.to_string(interpreter);
if (interpreter.exception())
return;
impl->set_fill_style(string);
}
} }
JS::Value CanvasRenderingContext2DWrapper::stroke_style_getter(JS::Interpreter& interpreter) JS::Value CanvasRenderingContext2DWrapper::stroke_style_getter(JS::Interpreter& interpreter)
@ -175,8 +179,12 @@ JS::Value CanvasRenderingContext2DWrapper::stroke_style_getter(JS::Interpreter&
void CanvasRenderingContext2DWrapper::stroke_style_setter(JS::Interpreter& interpreter, JS::Value value) void CanvasRenderingContext2DWrapper::stroke_style_setter(JS::Interpreter& interpreter, JS::Value value)
{ {
if (auto* impl = impl_from(interpreter)) if (auto* impl = impl_from(interpreter)){
impl->set_stroke_style(value.to_string()); auto string = value.to_string(interpreter);
if (interpreter.exception())
return;
impl->set_stroke_style(string);
}
} }
JS::Value CanvasRenderingContext2DWrapper::line_width_getter(JS::Interpreter& interpreter) JS::Value CanvasRenderingContext2DWrapper::line_width_getter(JS::Interpreter& interpreter)

View file

@ -78,7 +78,9 @@ JS::Value DocumentWrapper::get_element_by_id(JS::Interpreter& interpreter)
auto& arguments = interpreter.call_frame().arguments; auto& arguments = interpreter.call_frame().arguments;
if (arguments.is_empty()) if (arguments.is_empty())
return JS::js_null(); return JS::js_null();
auto id = arguments[0].to_string(); auto id = arguments[0].to_string(interpreter);
if (interpreter.exception())
return {};
auto* element = document->get_element_by_id(id); auto* element = document->get_element_by_id(id);
if (!element) if (!element)
return JS::js_null(); return JS::js_null();
@ -93,7 +95,9 @@ JS::Value DocumentWrapper::query_selector_all(JS::Interpreter& interpreter)
auto& arguments = interpreter.call_frame().arguments; auto& arguments = interpreter.call_frame().arguments;
if (arguments.is_empty()) if (arguments.is_empty())
return JS::js_null(); return JS::js_null();
auto selector = arguments[0].to_string(); auto selector = arguments[0].to_string(interpreter);
if (interpreter.exception())
return {};
auto elements = document->query_selector_all(selector); auto elements = document->query_selector_all(selector);
// FIXME: This should be a static NodeList, not a plain JS::Array. // FIXME: This should be a static NodeList, not a plain JS::Array.
auto* node_list = JS::Array::create(interpreter.global_object()); auto* node_list = JS::Array::create(interpreter.global_object());

View file

@ -74,8 +74,12 @@ JS::Value ElementWrapper::inner_html_getter(JS::Interpreter& interpreter)
void ElementWrapper::inner_html_setter(JS::Interpreter& interpreter, JS::Value value) void ElementWrapper::inner_html_setter(JS::Interpreter& interpreter, JS::Value value)
{ {
if (auto* impl = impl_from(interpreter)) if (auto* impl = impl_from(interpreter)) {
impl->set_inner_html(value.to_string()); auto string = value.to_string(interpreter);
if (interpreter.exception())
return;
impl->set_inner_html(string);
}
} }
JS::Value ElementWrapper::id_getter(JS::Interpreter& interpreter) JS::Value ElementWrapper::id_getter(JS::Interpreter& interpreter)
@ -87,8 +91,12 @@ JS::Value ElementWrapper::id_getter(JS::Interpreter& interpreter)
void ElementWrapper::id_setter(JS::Interpreter& interpreter, JS::Value value) void ElementWrapper::id_setter(JS::Interpreter& interpreter, JS::Value value)
{ {
if (auto* impl = impl_from(interpreter)) if (auto* impl = impl_from(interpreter)) {
impl->set_attribute("id", value.to_string()); auto string = value.to_string(interpreter);
if (interpreter.exception())
return;
impl->set_attribute("id", string);
}
} }
} }

View file

@ -56,7 +56,9 @@ JS::Value EventTargetWrapper::add_event_listener(JS::Interpreter& interpreter)
auto& arguments = interpreter.call_frame().arguments; auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 2) if (arguments.size() < 2)
return JS::js_undefined(); return JS::js_undefined();
auto event_name = arguments[0].to_string(); auto event_name = arguments[0].to_string(interpreter);
if (interpreter.exception())
return {};
ASSERT(arguments[1].is_object()); ASSERT(arguments[1].is_object());
ASSERT(arguments[1].as_object().is_function()); ASSERT(arguments[1].as_object().is_function());
auto& function = static_cast<JS::Function&>(const_cast<Object&>(arguments[1].as_object())); auto& function = static_cast<JS::Function&>(const_cast<Object&>(arguments[1].as_object()));

View file

@ -76,7 +76,10 @@ JS::Value HTMLCanvasElementWrapper::get_context(JS::Interpreter& interpreter)
return {}; return {};
auto& arguments = interpreter.call_frame().arguments; auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() >= 1) { if (arguments.size() >= 1) {
auto* context = impl->get_context(arguments[0].to_string()); auto string = arguments[0].to_string(interpreter);
if (interpreter.exception())
return {};
auto* context = impl->get_context(string);
return wrap(interpreter.heap(), *context); return wrap(interpreter.heap(), *context);
} }
return JS::js_undefined(); return JS::js_undefined();

View file

@ -98,8 +98,11 @@ JS::Value WindowObject::alert(JS::Interpreter& interpreter)
if (!impl) if (!impl)
return {}; return {};
String message = ""; String message = "";
if (interpreter.argument_count()) if (interpreter.argument_count()) {
message = interpreter.argument(0).to_string(); message = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
}
impl->alert(message); impl->alert(message);
return JS::js_undefined(); return JS::js_undefined();
} }
@ -110,8 +113,11 @@ JS::Value WindowObject::confirm(JS::Interpreter& interpreter)
if (!impl) if (!impl)
return {}; return {};
String message = ""; String message = "";
if (interpreter.argument_count()) if (interpreter.argument_count()) {
message = interpreter.argument(0).to_string(); message = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
}
return JS::Value(impl->confirm(message)); return JS::Value(impl->confirm(message));
} }

View file

@ -71,7 +71,13 @@ JS::Value XMLHttpRequestPrototype::open(JS::Interpreter& interpreter)
auto* impl = impl_from(interpreter); auto* impl = impl_from(interpreter);
if (!impl) if (!impl)
return {}; return {};
impl->open(interpreter.argument(0).to_string(), interpreter.argument(1).to_string()); auto arg0 = interpreter.argument(0).to_string(interpreter);
if (interpreter.exception())
return {};
auto arg1 = interpreter.argument(1).to_string(interpreter);
if (interpreter.exception())
return {};
impl->open(arg0, arg1);
return JS::js_undefined(); return JS::js_undefined();
} }

View file

@ -213,7 +213,7 @@ void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
printf("\033[34;1m"); printf("\033[34;1m");
if (value.is_string()) if (value.is_string())
putchar('"'); putchar('"');
printf("%s", value.to_string().characters()); printf("%s", value.to_string_without_side_effects().characters());
if (value.is_string()) if (value.is_string())
putchar('"'); putchar('"');
printf("\033[0m"); printf("\033[0m");
@ -316,7 +316,7 @@ JS::Value ReplObject::save_to_file(JS::Interpreter& interpreter)
{ {
if (!interpreter.argument_count()) if (!interpreter.argument_count())
return JS::Value(false); return JS::Value(false);
String save_path = interpreter.argument(0).to_string(); String save_path = interpreter.argument(0).to_string_without_side_effects();
StringView path = StringView(save_path.characters()); StringView path = StringView(save_path.characters());
if (write_to_file(path)) { if (write_to_file(path)) {
return JS::Value(true); return JS::Value(true);
@ -445,14 +445,14 @@ public:
} }
virtual JS::Value count() override virtual JS::Value count() override
{ {
auto label = interpreter().argument_count() ? interpreter().argument(0).to_string() : "default"; auto label = interpreter().argument_count() ? interpreter().argument(0).to_string_without_side_effects() : "default";
auto counter_value = m_console.counter_increment(label); auto counter_value = m_console.counter_increment(label);
printf("%s: %u\n", label.characters(), counter_value); printf("%s: %u\n", label.characters(), counter_value);
return JS::js_undefined(); return JS::js_undefined();
} }
virtual JS::Value count_reset() override virtual JS::Value count_reset() override
{ {
auto label = interpreter().argument_count() ? interpreter().argument(0).to_string() : "default"; auto label = interpreter().argument_count() ? interpreter().argument(0).to_string_without_side_effects() : "default";
if (m_console.counter_reset(label)) { if (m_console.counter_reset(label)) {
printf("%s: 0\n", label.characters()); printf("%s: 0\n", label.characters());
} else { } else {