1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 04:27:43 +00:00

LibJS: Convert NativeFunction::{call,construct}() to ThrowCompletionOr

Both at the same time because many of them call construct() in call()
and I'm not keen on adding a bunch of temporary plumbing to turn
exceptions into throw completions.
Also changes the return value of construct() to Object* instead of Value
as it always needs to return an object; allowing an arbitrary Value is a
massive foot gun.
This commit is contained in:
Linus Groh 2021-10-20 21:16:30 +01:00
parent 0881f8160f
commit 5832de62fe
99 changed files with 597 additions and 669 deletions

View file

@ -940,8 +940,14 @@ static bool is_wrappable_type(IDL::Type const& type)
return false;
}
enum class ReturnType {
Completion,
Value,
Void,
};
template<typename ParameterType>
static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false)
static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, ReturnType return_type, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false)
{
auto scoped_generator = generator.fork();
scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
@ -957,10 +963,21 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (optional_default_value.has_value())
scoped_generator.set("parameter.optional_default_value", *optional_default_value);
if (return_void)
scoped_generator.set("return_statement", "return;");
else
switch (return_type) {
case ReturnType::Completion:
scoped_generator.set("return_statement", "return JS::throw_completion(vm.exception()->value());");
scoped_generator.set("try_macro", "TRY");
break;
case ReturnType::Value:
scoped_generator.set("return_statement", "return {};");
scoped_generator.set("try_macro", "TRY_OR_DISCARD");
break;
case ReturnType::Void:
scoped_generator.set("return_statement", "return;");
break;
default:
VERIFY_NOT_REACHED();
}
// FIXME: Add support for optional, variadic, nullable and default values to all types
if (parameter.type->is_string()) {
@ -1080,7 +1097,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
} else if (parameter.type->name == "double") {
if (!optional) {
scoped_generator.append(R"~~~(
double @cpp_name@ = TRY_OR_DISCARD(@js_name@@js_suffix@.to_double(global_object));
double @cpp_name@ = @try_macro@(@js_name@@js_suffix@.to_double(global_object));
)~~~");
} else {
if (optional_default_value.has_value()) {
@ -1094,7 +1111,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
}
scoped_generator.append(R"~~~(
if (!@js_name@@js_suffix@.is_undefined())
@cpp_name@ = TRY_OR_DISCARD(@js_name@@js_suffix@.to_double(global_object));
@cpp_name@ = @try_macro@(@js_name@@js_suffix@.to_double(global_object));
)~~~");
if (optional_default_value.has_value()) {
scoped_generator.append(R"~~~(
@ -1136,15 +1153,15 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
}
} else if (parameter.type->name == "unsigned long") {
scoped_generator.append(R"~~~(
auto @cpp_name@ = TRY_OR_DISCARD(@js_name@@js_suffix@.to_u32(global_object));
auto @cpp_name@ = @try_macro@(@js_name@@js_suffix@.to_u32(global_object));
)~~~");
} else if (parameter.type->name == "unsigned short") {
scoped_generator.append(R"~~~(
auto @cpp_name@ = TRY_OR_DISCARD(@js_name@@js_suffix@.to_u16(global_object));
auto @cpp_name@ = @try_macro@(@js_name@@js_suffix@.to_u16(global_object));
)~~~");
} else if (parameter.type->name == "long") {
scoped_generator.append(R"~~~(
auto @cpp_name@ = TRY_OR_DISCARD(@js_name@@js_suffix@.to_i32(global_object));
auto @cpp_name@ = @try_macro@(@js_name@@js_suffix@.to_i32(global_object));
)~~~");
} else if (parameter.type->name == "EventHandler") {
// x.onfoo = function() { ... }
@ -1236,7 +1253,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto member_value_name = String::formatted("{}_value", member_js_name);
dictionary_generator.set("member_value_name", member_value_name);
generate_to_cpp(dictionary_generator, member, member_js_name, "", member_value_name, dictionaries, return_void, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value);
generate_to_cpp(dictionary_generator, member, member_js_name, "", member_value_name, dictionaries, return_type, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value);
dictionary_generator.append(R"~~~(
@cpp_name@.@member_name@ = @member_value_name@;
)~~~");
@ -1253,7 +1270,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
}
template<typename FunctionType>
static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function)
static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function, ReturnType return_type)
{
auto argument_count_check_generator = generator.fork();
argument_count_check_generator.set("function.name", function.name);
@ -1269,15 +1286,27 @@ static void generate_argument_count_check(SourceGenerator& generator, FunctionTy
argument_count_check_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
}
switch (return_type) {
case ReturnType::Completion:
argument_count_check_generator.set("return_statement", "return JS::throw_completion(vm.exception()->value());");
break;
case ReturnType::Value:
argument_count_check_generator.set("return_statement", "return {};");
break;
default:
// 'Void' not used here.
VERIFY_NOT_REACHED();
}
argument_count_check_generator.append(R"~~~(
if (vm.argument_count() < @function.nargs@) {
vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
return {};
@return_statement@
}
)~~~");
}
static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter> const& parameters, StringBuilder& arguments_builder, HashMap<String, IDL::Dictionary> const& dictionaries, bool return_void = false)
static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter> const& parameters, StringBuilder& arguments_builder, HashMap<String, IDL::Dictionary> const& dictionaries, ReturnType return_type)
{
auto arguments_generator = generator.fork();
@ -1294,7 +1323,7 @@ static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter
}
bool legacy_null_to_empty_string = parameter.extended_attributes.contains("LegacyNullToEmptyString");
generate_to_cpp(generator, parameter, "arg", String::number(argument_index), parameter.name.to_snakecase(), dictionaries, return_void, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value, parameter.variadic);
generate_to_cpp(generator, parameter, "arg", String::number(argument_index), parameter.name.to_snakecase(), dictionaries, return_type, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value, parameter.variadic);
++argument_index;
}
@ -1447,10 +1476,10 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@)
)~~~");
}
generate_argument_count_check(generator, function);
generate_argument_count_check(generator, function, ReturnType::Value);
StringBuilder arguments_builder;
generate_arguments(generator, function.parameters, arguments_builder, dictionaries);
generate_arguments(generator, function.parameters, arguments_builder, dictionaries, ReturnType::Value);
function_generator.set(".arguments", arguments_builder.string_view());
if (is_static_function == StaticFunction::No) {
@ -2039,7 +2068,7 @@ static void invoke_named_property_setter(JS::GlobalObject& global_object, @fully
// 4. Let value be the result of converting V to an IDL value of type T.
// NOTE: This takes the last parameter as it's enforced that there's only two parameters.
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, true);
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, ReturnType::Void);
// 5. If operation was defined without an identifier, then:
if (interface.named_property_setter->name.is_empty()) {
@ -2097,7 +2126,7 @@ static void invoke_indexed_property_setter(JS::GlobalObject& global_object, @ful
// 5. Let value be the result of converting V to an IDL value of type T.
// NOTE: This takes the last parameter as it's enforced that there's only two parameters.
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, true);
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, ReturnType::Void);
// 6. If operation was defined without an identifier, then:
if (interface.indexed_property_setter->name.is_empty()) {
@ -2540,8 +2569,8 @@ public:
virtual void initialize(JS::GlobalObject&) override;
virtual ~@constructor_class@() override;
virtual JS::Value call() override;
virtual JS::Value construct(JS::FunctionObject& new_target) override;
virtual JS::ThrowCompletionOr<JS::Value> call() override;
virtual JS::ThrowCompletionOr<JS::Object*> construct(JS::FunctionObject& new_target) override;
private:
virtual bool has_constructor() const override { return true; }
@ -2644,13 +2673,12 @@ namespace Web::Bindings {
{
}
JS::Value @constructor_class@::call()
JS::ThrowCompletionOr<JS::Value> @constructor_class@::call()
{
vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "@name@");
return {};
return vm().throw_completion<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "@name@");
}
JS::Value @constructor_class@::construct(FunctionObject&)
JS::ThrowCompletionOr<JS::Object*> @constructor_class@::construct(FunctionObject&)
{
)~~~");
@ -2658,8 +2686,7 @@ JS::Value @constructor_class@::construct(FunctionObject&)
// No constructor
generator.set("constructor.length", "0");
generator.append(R"~~~(
vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::NotAConstructor, "@name@");
return {};
return vm().throw_completion<JS::TypeError>(global_object(), JS::ErrorType::NotAConstructor, "@name@");
)~~~");
} else if (interface.constructors.size() == 1) {
// Single constructor
@ -2675,10 +2702,10 @@ JS::Value @constructor_class@::construct(FunctionObject&)
)~~~");
if (!constructor.parameters.is_empty()) {
generate_argument_count_check(generator, constructor);
generate_argument_count_check(generator, constructor, ReturnType::Completion);
StringBuilder arguments_builder;
generate_arguments(generator, constructor.parameters, arguments_builder, interface.dictionaries);
generate_arguments(generator, constructor.parameters, arguments_builder, interface.dictionaries, ReturnType::Completion);
generator.set(".constructor_arguments", arguments_builder.string_view());
generator.append(R"~~~(
@ -2691,7 +2718,7 @@ JS::Value @constructor_class@::construct(FunctionObject&)
}
generator.append(R"~~~(
if (should_return_empty(impl))
return JS::Value();
return JS::throw_completion(vm.exception()->value());
return wrap(global_object, *impl.release_value());
)~~~");
} else {
@ -3236,7 +3263,7 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@)
auto value = vm.argument(0);
)~~~");
generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface.dictionaries, false, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface.dictionaries, ReturnType::Value, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
if (attribute.extended_attributes.contains("Reflect")) {
if (attribute.type->name != "boolean") {