1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00
ladybird/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp
Aliaksandr Kalenik 4b04e97feb LibWeb: Send IPC messages exceeding socket buffer through shared memory
It turned out that some web applications want to send fairly large
messages to WebWorker through IPC (for example, MapLibre GL sends
~1200KiB), which led to failures (at least on macOS) because buffer size
of TransportSocket is limited to 128KiB. This change solves the problem
by wrapping messages that exceed socket buffer size into another message
that holds wrapped message content in shared memory.

Co-Authored-By: Luke Wilde <luke@ladybird.org>
2025-04-03 13:55:41 +02:00

963 lines
35 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Enumerate.h>
#include <AK/Function.h>
#include <AK/GenericLexer.h>
#include <AK/HashMap.h>
#include <AK/SourceGenerator.h>
#include <AK/StringBuilder.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibMain/Main.h>
#include <ctype.h>
#include <stdio.h>
namespace {
struct Parameter {
Vector<ByteString> attributes;
ByteString type;
ByteString type_for_encoding;
ByteString name;
};
static ByteString pascal_case(ByteString const& identifier)
{
StringBuilder builder;
bool was_new_word = true;
for (auto ch : identifier) {
if (ch == '_') {
was_new_word = true;
continue;
}
if (was_new_word) {
builder.append(toupper(ch));
was_new_word = false;
} else
builder.append(ch);
}
return builder.to_byte_string();
}
struct Message {
ByteString name;
bool is_synchronous { false };
Vector<Parameter> inputs;
Vector<Parameter> outputs;
ByteString response_name() const
{
StringBuilder builder;
builder.append(pascal_case(name));
builder.append("Response"sv);
return builder.to_byte_string();
}
};
struct Endpoint {
Vector<ByteString> includes;
ByteString name;
u32 magic;
Vector<Message> messages;
};
static bool is_primitive_type(ByteString const& type)
{
return type.is_one_of("u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "size_t", "bool", "double", "float", "int", "unsigned", "unsigned int");
}
static bool is_simple_type(ByteString const& type)
{
// Small types that it makes sense just to pass by value.
if (type.starts_with("ReadonlySpan<"sv) && type.ends_with(">"sv))
return true;
return type.is_one_of("AK::CaseSensitivity", "AK::Duration", "Gfx::Color", "ReadonlyBytes", "StringView", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Web::DevicePixelRect", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior", "Web::HTML::VisibilityState", "WebView::PageInfoType");
}
static bool is_primitive_or_simple_type(ByteString const& type)
{
return is_primitive_type(type) || is_simple_type(type);
}
static ByteString message_name(ByteString const& endpoint, ByteString const& message, bool is_response)
{
StringBuilder builder;
builder.append("Messages::"sv);
builder.append(endpoint);
builder.append("::"sv);
builder.append(pascal_case(message));
if (is_response)
builder.append("Response"sv);
return builder.to_byte_string();
}
static ByteString make_argument_type(ByteString const& type)
{
StringBuilder builder;
bool const_ref = !is_primitive_or_simple_type(type);
builder.append(type);
if (const_ref)
builder.append(" const&"sv);
return builder.to_byte_string();
}
Vector<Endpoint> parse(ByteBuffer const& file_contents)
{
GenericLexer lexer(file_contents);
Vector<Endpoint> endpoints;
auto assert_specific = [&lexer](char ch) {
if (lexer.peek() != ch)
warnln("assert_specific: wanted '{}', but got '{}' at index {}", ch, lexer.peek(), lexer.tell());
bool saw_expected = lexer.consume_specific(ch);
VERIFY(saw_expected);
};
auto consume_whitespace = [&lexer] {
lexer.ignore_while([](char ch) { return isspace(ch); });
if (lexer.peek() == '/' && lexer.peek(1) == '/')
lexer.ignore_until('\n');
};
auto parse_parameter_type = [&]() {
ByteString parameter_type = lexer.consume_until([](char ch) { return ch == '<' || isspace(ch); });
if (lexer.peek() == '<') {
lexer.consume();
StringBuilder builder;
builder.append(parameter_type);
builder.append('<');
auto nesting_level = 1;
while (nesting_level > 0) {
auto inner_type = lexer.consume_until([](char ch) { return ch == '<' || ch == '>'; });
if (lexer.is_eof()) {
warnln("Unexpected EOF when parsing parameter type");
VERIFY_NOT_REACHED();
}
builder.append(inner_type);
if (lexer.peek() == '<') {
nesting_level++;
} else if (lexer.peek() == '>') {
nesting_level--;
}
builder.append(lexer.consume());
}
parameter_type = builder.to_byte_string();
}
return parameter_type;
};
auto parse_parameter = [&](Vector<Parameter>& storage, StringView message_name) {
for (auto parameter_index = 1;; ++parameter_index) {
Parameter parameter;
if (lexer.is_eof()) {
warnln("EOF when parsing parameter");
VERIFY_NOT_REACHED();
}
consume_whitespace();
if (lexer.peek() == ')')
break;
if (lexer.consume_specific('[')) {
for (;;) {
if (lexer.consume_specific(']')) {
consume_whitespace();
break;
}
if (lexer.consume_specific(',')) {
consume_whitespace();
}
auto attribute = lexer.consume_until([](char ch) { return ch == ']' || ch == ','; });
parameter.attributes.append(attribute);
consume_whitespace();
}
}
parameter.type = parse_parameter_type();
if (parameter.type.ends_with(',') || parameter.type.ends_with(')')) {
warnln("Parameter {} of method: {} must be named", parameter_index, message_name);
VERIFY_NOT_REACHED();
}
if (parameter.type.starts_with("Vector<"sv) && parameter.type.ends_with(">"sv)) {
parameter.type_for_encoding = parameter.type.replace("Vector"sv, "ReadonlySpan"sv, ReplaceMode::FirstOnly);
} else if (parameter.type.is_one_of("String"sv, "ByteString"sv)) {
parameter.type_for_encoding = "StringView"sv;
} else if (parameter.type == "ByteBuffer"sv) {
parameter.type_for_encoding = "ReadonlyBytes"sv;
} else {
parameter.type_for_encoding = parameter.type;
}
VERIFY(!lexer.is_eof());
consume_whitespace();
parameter.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == ',' || ch == ')'; });
consume_whitespace();
storage.append(move(parameter));
if (lexer.consume_specific(','))
continue;
if (lexer.peek() == ')')
break;
}
};
auto parse_parameters = [&](Vector<Parameter>& storage, StringView message_name) {
for (;;) {
consume_whitespace();
parse_parameter(storage, message_name);
consume_whitespace();
if (lexer.consume_specific(','))
continue;
if (lexer.peek() == ')')
break;
}
};
auto parse_message = [&] {
Message message;
consume_whitespace();
message.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == '('; });
consume_whitespace();
assert_specific('(');
parse_parameters(message.inputs, message.name);
assert_specific(')');
consume_whitespace();
assert_specific('=');
auto type = lexer.consume();
if (type == '>')
message.is_synchronous = true;
else if (type == '|')
message.is_synchronous = false;
else
VERIFY_NOT_REACHED();
consume_whitespace();
if (message.is_synchronous) {
assert_specific('(');
parse_parameters(message.outputs, message.name);
assert_specific(')');
}
consume_whitespace();
endpoints.last().messages.append(move(message));
};
auto parse_messages = [&] {
for (;;) {
consume_whitespace();
if (lexer.peek() == '}')
break;
parse_message();
consume_whitespace();
}
};
auto parse_include = [&] {
ByteString include;
consume_whitespace();
include = lexer.consume_while([](char ch) { return ch != '\n'; });
consume_whitespace();
endpoints.last().includes.append(move(include));
};
auto parse_includes = [&] {
for (;;) {
consume_whitespace();
if (lexer.peek() != '#')
break;
parse_include();
consume_whitespace();
}
};
auto parse_endpoint = [&] {
endpoints.empend();
consume_whitespace();
parse_includes();
consume_whitespace();
lexer.consume_specific("endpoint"sv);
consume_whitespace();
endpoints.last().name = lexer.consume_while([](char ch) { return !isspace(ch); });
endpoints.last().magic = Traits<ByteString>::hash(endpoints.last().name);
consume_whitespace();
assert_specific('{');
parse_messages();
assert_specific('}');
consume_whitespace();
};
while (lexer.tell() < file_contents.size())
parse_endpoint();
return endpoints;
}
HashMap<ByteString, int> build_message_ids_for_endpoint(SourceGenerator generator, Endpoint const& endpoint)
{
HashMap<ByteString, int> message_ids;
generator.appendln("\nenum class MessageID : i32 {");
for (auto const& message : endpoint.messages) {
message_ids.set(message.name, message_ids.size() + 1);
generator.set("message.pascal_name", pascal_case(message.name));
generator.set("message.id", ByteString::number(message_ids.size()));
generator.appendln(" @message.pascal_name@ = @message.id@,");
if (message.is_synchronous) {
message_ids.set(message.response_name(), message_ids.size() + 1);
generator.set("message.pascal_name", pascal_case(message.response_name()));
generator.set("message.id", ByteString::number(message_ids.size()));
generator.appendln(" @message.pascal_name@ = @message.id@,");
}
}
generator.appendln("};");
return message_ids;
}
ByteString constructor_for_message(ByteString const& name, Vector<Parameter> const& parameters)
{
StringBuilder builder;
builder.append(name);
if (parameters.is_empty()) {
builder.append("() {}"sv);
return builder.to_byte_string();
}
builder.append('(');
for (size_t i = 0; i < parameters.size(); ++i) {
auto const& parameter = parameters[i];
builder.appendff("{} {}", parameter.type, parameter.name);
if (i != parameters.size() - 1)
builder.append(", "sv);
}
builder.append(") : "sv);
for (size_t i = 0; i < parameters.size(); ++i) {
auto const& parameter = parameters[i];
builder.appendff("m_{}(move({}))", parameter.name, parameter.name);
if (i != parameters.size() - 1)
builder.append(", "sv);
}
builder.append(" {}"sv);
return builder.to_byte_string();
}
void do_message(SourceGenerator message_generator, ByteString const& name, Vector<Parameter> const& parameters, ByteString const& response_type = {})
{
auto pascal_name = pascal_case(name);
message_generator.set("message.name", name);
message_generator.set("message.pascal_name", pascal_name);
message_generator.set("message.response_type", response_type);
message_generator.set("message.constructor", constructor_for_message(pascal_name, parameters));
message_generator.append(R"~~~(
class @message.pascal_name@ final : public IPC::Message {
public:)~~~");
if (!response_type.is_empty())
message_generator.appendln(R"~~~(
typedef class @message.response_type@ ResponseType;)~~~");
message_generator.appendln(R"~~~(
@message.pascal_name@(@message.pascal_name@ const&) = default;
@message.pascal_name@(@message.pascal_name@&&) = default;
@message.pascal_name@& operator=(@message.pascal_name@ const&) = default;
@message.constructor@)~~~");
if (parameters.size() == 1) {
auto const& parameter = parameters[0];
message_generator.set("parameter.type"sv, parameter.type);
message_generator.set("parameter.name"sv, parameter.name);
message_generator.appendln(R"~~~(
template <typename WrappedReturnType>
requires(!SameAs<WrappedReturnType, @parameter.type@>)
@message.pascal_name@(WrappedReturnType&& value)
: m_@parameter.name@(forward<WrappedReturnType>(value))
{
})~~~");
}
message_generator.append(R"~~~(
virtual ~@message.pascal_name@() override = default;
static constexpr u32 ENDPOINT_MAGIC = @endpoint.magic@;
virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
virtual i32 message_id() const override { return (int)MessageID::@message.pascal_name@; }
static i32 static_message_id() { return (int)MessageID::@message.pascal_name@; }
virtual const char* message_name() const override { return "@endpoint.name@::@message.pascal_name@"; }
static ErrorOr<NonnullOwnPtr<@message.pascal_name@>> decode(Stream& stream, IPC::UnprocessedFileDescriptors& files)
{
IPC::Decoder decoder { stream, files };)~~~");
for (auto const& parameter : parameters) {
auto parameter_generator = message_generator.fork();
parameter_generator.set("parameter.type", parameter.type);
parameter_generator.set("parameter.name", parameter.name);
if (parameter.type == "bool")
parameter_generator.set("parameter.initial_value", "false");
else
parameter_generator.set("parameter.initial_value", "{}");
parameter_generator.append(R"~~~(
auto @parameter.name@ = TRY((decoder.decode<@parameter.type@>()));)~~~");
if (parameter.attributes.contains_slow("UTF8")) {
parameter_generator.appendln(R"~~~(
if (!Utf8View(@parameter.name@).validate())
return Error::from_string_literal("Decoded @parameter.name@ is invalid UTF-8");)~~~");
}
}
StringBuilder builder;
for (size_t i = 0; i < parameters.size(); ++i) {
auto const& parameter = parameters[i];
builder.appendff("move({})", parameter.name);
if (i != parameters.size() - 1)
builder.append(", "sv);
}
message_generator.set("message.constructor_call_parameters", builder.to_byte_string());
message_generator.appendln(R"~~~(
return make<@message.pascal_name@>(@message.constructor_call_parameters@);
})~~~");
message_generator.append(R"~~~(
static ErrorOr<IPC::MessageBuffer> static_encode()~~~");
for (auto const& [i, parameter] : enumerate(parameters)) {
auto argument_generator = message_generator.fork();
argument_generator.set("argument.type", make_argument_type(parameter.type_for_encoding));
argument_generator.set("argument.name", parameter.name);
argument_generator.append("@argument.type@ @argument.name@");
if (i != parameters.size() - 1)
argument_generator.append(", ");
}
message_generator.append(R"~~~()
{
IPC::MessageBuffer buffer;
IPC::Encoder stream(buffer);
TRY(stream.encode(ENDPOINT_MAGIC));
TRY(stream.encode((int)MessageID::@message.pascal_name@));)~~~");
for (auto const& parameter : parameters) {
auto parameter_generator = message_generator.fork();
parameter_generator.set("parameter.name", parameter.name);
parameter_generator.append(R"~~~(
TRY(stream.encode(@parameter.name@));)~~~");
}
message_generator.appendln(R"~~~(
return buffer;
})~~~");
message_generator.append(R"~~~(
virtual ErrorOr<IPC::MessageBuffer> encode() const override
{
return static_encode()~~~");
for (auto const& [i, parameter] : enumerate(parameters)) {
auto parameter_generator = message_generator.fork();
parameter_generator.set("parameter.name", parameter.name);
parameter_generator.append("m_@parameter.name@");
if (i != parameters.size() - 1)
parameter_generator.append(", ");
}
message_generator.appendln(R"~~~();
})~~~");
for (auto const& parameter : parameters) {
auto parameter_generator = message_generator.fork();
parameter_generator.set("parameter.type", parameter.type);
parameter_generator.set("parameter.name", parameter.name);
if (is_primitive_or_simple_type(parameter.type)) {
parameter_generator.appendln(R"~~~(
@parameter.type@ @parameter.name@() const { return m_@parameter.name@; })~~~");
} else {
parameter_generator.appendln(R"~~~(
const @parameter.type@& @parameter.name@() const { return m_@parameter.name@; }
@parameter.type@ take_@parameter.name@() { return move(m_@parameter.name@); })~~~");
}
}
message_generator.append(R"~~~(
private:)~~~");
for (auto const& parameter : parameters) {
auto parameter_generator = message_generator.fork();
parameter_generator.set("parameter.type", parameter.type);
parameter_generator.set("parameter.name", parameter.name);
parameter_generator.append(R"~~~(
@parameter.type@ m_@parameter.name@;)~~~");
}
message_generator.appendln("\n};");
}
void generate_proxy_method(SourceGenerator& message_generator, Endpoint const& endpoint, Message const& message, ByteString const& name, Vector<Parameter> const& parameters, bool is_synchronous, bool is_try, bool is_utf8_string_overload = false)
{
// FIXME: For String parameters, we want to retain the property that all tranferred String objects are strictly UTF-8.
// So instead of generating a single proxy method that accepts StringView parameters, we generate two overloads.
// The first accepts StringView parameters, but validates the view is UTF-8. The second accepts String parameters,
// for callers that already have a UTF-8 String object.
//
// Ideally, we will eventually have separate StringView types for each of String and ByteString, where String's
// view internally provides UTF-8 guarantees. Then we won't need these overloads.
bool generate_utf8_string_overload = false;
ByteString return_type = "void";
if (is_synchronous) {
if (message.outputs.size() == 1)
return_type = message.outputs[0].type;
else if (!message.outputs.is_empty())
return_type = message_name(endpoint.name, message.name, true);
}
ByteString inner_return_type = return_type;
if (is_try)
return_type = ByteString::formatted("IPC::IPCErrorOr<{}>", return_type);
message_generator.set("message.name", message.name);
message_generator.set("message.pascal_name", pascal_case(message.name));
message_generator.set("message.complex_return_type", return_type);
message_generator.set("async_prefix_maybe", is_synchronous ? "" : "async_");
message_generator.set("try_prefix_maybe", is_try ? "try_" : "");
message_generator.set("handler_name", name);
message_generator.append(R"~~~(
@message.complex_return_type@ @try_prefix_maybe@@async_prefix_maybe@@handler_name@()~~~");
for (auto const& [i, parameter] : enumerate(parameters)) {
ByteString type;
if (is_synchronous || is_try)
type = parameter.type;
else if (is_utf8_string_overload)
type = make_argument_type(parameter.type);
else
type = make_argument_type(parameter.type_for_encoding);
auto argument_generator = message_generator.fork();
argument_generator.set("argument.type", type);
argument_generator.set("argument.name", parameter.name);
argument_generator.append("@argument.type@ @argument.name@");
if (i != parameters.size() - 1)
argument_generator.append(", ");
}
message_generator.append(") {");
if (!is_synchronous && !is_try && !is_utf8_string_overload) {
for (auto const& parameter : parameters) {
auto const& type = is_synchronous || is_try ? parameter.type : parameter.type_for_encoding;
if (parameter.type == "String"sv && type == "StringView"sv) {
auto argument_generator = message_generator.fork();
argument_generator.set("argument.name", parameter.name);
argument_generator.append(R"~~~(
VERIFY(Utf8View { @argument.name@ }.validate());)~~~");
generate_utf8_string_overload = true;
}
}
}
if (is_synchronous && !is_try) {
if (return_type != "void") {
message_generator.append(R"~~~(
return )~~~");
if (message.outputs.size() != 1)
message_generator.append("move(*");
} else {
message_generator.append(R"~~~(
(void) )~~~");
}
message_generator.append("m_connection.template send_sync<Messages::@endpoint.name@::@message.pascal_name@>(");
} else if (is_try) {
message_generator.append(R"~~~(
auto result = m_connection.template send_sync_but_allow_failure<Messages::@endpoint.name@::@message.pascal_name@>()~~~");
} else {
message_generator.append(R"~~~(
auto message_buffer = MUST(Messages::@endpoint.name@::@message.pascal_name@::static_encode()~~~");
}
for (auto const& [i, parameter] : enumerate(parameters)) {
auto const& type = is_synchronous || is_try ? parameter.type : parameter.type_for_encoding;
auto argument_generator = message_generator.fork();
argument_generator.set("argument.name", parameter.name);
if (is_primitive_or_simple_type(type))
argument_generator.append("@argument.name@");
else
argument_generator.append("move(@argument.name@)");
if (i != parameters.size() - 1)
argument_generator.append(", ");
}
if (is_synchronous && !is_try) {
if (return_type != "void") {
message_generator.append(")");
}
if (message.outputs.size() == 1) {
if (is_primitive_or_simple_type(message.outputs[0].type))
message_generator.append("->");
else
message_generator.append("->take_");
message_generator.append(message.outputs[0].name);
message_generator.append("()");
} else
message_generator.append(")");
message_generator.append(";");
} else if (is_try) {
message_generator.append(R"~~~();
if (!result) {
m_connection.shutdown();
return IPC::ErrorCode::PeerDisconnected;
})~~~");
if (inner_return_type != "void") {
message_generator.appendln(R"~~~(
return move(*result);)~~~");
} else {
message_generator.appendln(R"~~~(
return { };)~~~");
}
} else {
message_generator.append(R"~~~());
MUST(m_connection.post_message(@endpoint.magic@, move(message_buffer))); )~~~");
}
message_generator.appendln(R"~~~(
})~~~");
if (generate_utf8_string_overload)
generate_proxy_method(message_generator, endpoint, message, message.name, message.inputs, is_synchronous, is_try, generate_utf8_string_overload);
}
void do_message_for_proxy(SourceGenerator message_generator, Endpoint const& endpoint, Message const& message)
{
generate_proxy_method(message_generator, endpoint, message, message.name, message.inputs, message.is_synchronous, false);
if (message.is_synchronous) {
generate_proxy_method(message_generator, endpoint, message, message.name, message.inputs, false, false);
generate_proxy_method(message_generator, endpoint, message, message.name, message.inputs, true, true);
}
}
void build_endpoint(SourceGenerator generator, Endpoint const& endpoint)
{
generator.set("endpoint.name", endpoint.name);
generator.set("endpoint.magic", ByteString::number(endpoint.magic));
generator.appendln("\nnamespace Messages::@endpoint.name@ {");
HashMap<ByteString, int> message_ids = build_message_ids_for_endpoint(generator.fork(), endpoint);
for (auto const& message : endpoint.messages) {
ByteString response_name;
if (message.is_synchronous) {
response_name = message.response_name();
do_message(generator.fork(), response_name, message.outputs);
}
do_message(generator.fork(), message.name, message.inputs, response_name);
}
generator.appendln(R"~~~(
} // namespace Messages::@endpoint.name@
template<typename LocalEndpoint, typename PeerEndpoint>
class @endpoint.name@Proxy {
public:
// Used to disambiguate the constructor call.
struct Tag { };
@endpoint.name@Proxy(IPC::Connection<LocalEndpoint, PeerEndpoint>& connection, Tag)
: m_connection(connection)
{ })~~~");
for (auto const& message : endpoint.messages)
do_message_for_proxy(generator.fork(), endpoint, message);
generator.appendln(R"~~~(
private:
IPC::Connection<LocalEndpoint, PeerEndpoint>& m_connection;
};)~~~");
generator.append(R"~~~(
template<typename LocalEndpoint, typename PeerEndpoint>
class @endpoint.name@Proxy;
class @endpoint.name@Stub;
class @endpoint.name@Endpoint {
public:
template<typename LocalEndpoint>
using Proxy = @endpoint.name@Proxy<LocalEndpoint, @endpoint.name@Endpoint>;
using Stub = @endpoint.name@Stub;
static u32 static_magic() { return @endpoint.magic@; }
static ErrorOr<NonnullOwnPtr<IPC::Message>> decode_message(ReadonlyBytes buffer, [[maybe_unused]] IPC::UnprocessedFileDescriptors& files)
{
FixedMemoryStream stream { buffer };
auto message_endpoint_magic = TRY(stream.read_value<u32>());)~~~");
generator.append(R"~~~(
if (message_endpoint_magic != @endpoint.magic@) {)~~~");
if constexpr (GENERATE_DEBUG) {
generator.append(R"~~~(
dbgln("@endpoint.name@: Endpoint magic number message_endpoint_magic != @endpoint.magic@, not my message! (the other endpoint may have handled it)");)~~~");
}
generator.appendln(R"~~~(
return Error::from_string_literal("Endpoint magic number mismatch, not my message!");
}
auto message_id = TRY(stream.read_value<i32>());)~~~");
generator.appendln(R"~~~(
switch (message_id) {)~~~");
for (auto const& message : endpoint.messages) {
auto do_decode_message = [&](ByteString const& name) {
auto message_generator = generator.fork();
message_generator.set("message.name", name);
message_generator.set("message.pascal_name", pascal_case(name));
message_generator.append(R"~~~(
case (int)Messages::@endpoint.name@::MessageID::@message.pascal_name@:
return TRY(Messages::@endpoint.name@::@message.pascal_name@::decode(stream, files));)~~~");
};
do_decode_message(message.name);
if (message.is_synchronous)
do_decode_message(message.response_name());
}
generator.append(R"~~~(
case (int)IPC::LargeMessageWrapper::MESSAGE_ID:
return TRY(IPC::LargeMessageWrapper::decode(message_endpoint_magic, stream, files));
)~~~");
generator.append(R"~~~(
default:)~~~");
if constexpr (GENERATE_DEBUG) {
generator.append(R"~~~(
dbgln("Failed to decode @endpoint.name@.({})", message_id);)~~~");
}
generator.appendln(R"~~~(
return Error::from_string_literal("Failed to decode @endpoint.name@ message");
})~~~");
generator.appendln(R"~~~(
VERIFY_NOT_REACHED();
}
};
class @endpoint.name@Stub : public IPC::Stub {
public:
@endpoint.name@Stub() { }
virtual ~@endpoint.name@Stub() override { }
virtual u32 magic() const override { return @endpoint.magic@; }
virtual ByteString name() const override { return "@endpoint.name@"; }
virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
{
switch (message->message_id()) {)~~~");
for (auto const& message : endpoint.messages) {
auto do_handle_message = [&](ByteString const& name, Vector<Parameter> const& parameters, bool returns_something) {
auto message_generator = generator.fork();
StringBuilder argument_generator;
for (auto const& [i, parameter] : enumerate(parameters)) {
if (is_primitive_or_simple_type(parameter.type))
argument_generator.append("request."sv);
else
argument_generator.append("request.take_"sv);
argument_generator.append(parameter.name);
argument_generator.append("()"sv);
if (i != parameters.size() - 1)
argument_generator.append(", "sv);
}
message_generator.set("message.pascal_name", pascal_case(name));
message_generator.set("message.response_type", pascal_case(message.response_name()));
message_generator.set("handler_name", name);
message_generator.set("arguments", argument_generator.to_byte_string());
message_generator.append(R"~~~(
case (int)Messages::@endpoint.name@::MessageID::@message.pascal_name@: {)~~~");
if (returns_something) {
if (message.outputs.is_empty()) {
message_generator.append(R"~~~(
[[maybe_unused]] auto& request = static_cast<Messages::@endpoint.name@::@message.pascal_name@&>(*message);
@handler_name@(@arguments@);
auto response = Messages::@endpoint.name@::@message.response_type@ { };
return make<IPC::MessageBuffer>(TRY(response.encode()));)~~~");
} else {
message_generator.append(R"~~~(
[[maybe_unused]] auto& request = static_cast<Messages::@endpoint.name@::@message.pascal_name@&>(*message);
auto response = @handler_name@(@arguments@);
return make<IPC::MessageBuffer>(TRY(response.encode()));)~~~");
}
} else {
message_generator.append(R"~~~(
[[maybe_unused]] auto& request = static_cast<Messages::@endpoint.name@::@message.pascal_name@&>(*message);
@handler_name@(@arguments@);
return nullptr;)~~~");
}
message_generator.append(R"~~~(
})~~~");
};
do_handle_message(message.name, message.inputs, message.is_synchronous);
}
generator.appendln(R"~~~(
default:
return Error::from_string_literal("Unknown message ID for @endpoint.name@ endpoint");
}
})~~~");
for (auto const& message : endpoint.messages) {
auto message_generator = generator.fork();
auto do_handle_message_decl = [&](ByteString const& name, Vector<Parameter> const& parameters) {
ByteString return_type = "void";
if (message.is_synchronous && !message.outputs.is_empty())
return_type = message_name(endpoint.name, message.name, true);
message_generator.set("message.complex_return_type", return_type);
message_generator.set("handler_name", name);
message_generator.append(R"~~~(
virtual @message.complex_return_type@ @handler_name@()~~~");
for (size_t i = 0; i < parameters.size(); ++i) {
auto const& parameter = parameters[i];
auto argument_generator = message_generator.fork();
argument_generator.set("argument.type", parameter.type);
argument_generator.set("argument.name", parameter.name);
argument_generator.append("@argument.type@ @argument.name@");
if (i != parameters.size() - 1)
argument_generator.append(", ");
}
message_generator.append(") = 0;");
};
do_handle_message_decl(message.name, message.inputs);
}
generator.appendln(R"~~~(
};
#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic pop
#endif)~~~");
}
void build(StringBuilder& builder, Vector<Endpoint> const& endpoints)
{
SourceGenerator generator { builder };
generator.appendln("#pragma once\n");
// This must occur before LibIPC/Decoder.h
for (auto const& endpoint : endpoints) {
for (auto const& include : endpoint.includes) {
generator.appendln(include);
}
}
generator.appendln(R"~~~(#include <AK/Error.h>
#include <AK/MemoryStream.h>
#include <AK/OwnPtr.h>
#include <AK/Result.h>
#include <AK/Utf8View.h>
#include <LibIPC/Connection.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibIPC/File.h>
#include <LibIPC/Message.h>
#include <LibIPC/Stub.h>
#include <LibIPC/UnprocessedFileDescriptors.h>
#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
#endif)~~~");
for (auto const& endpoint : endpoints)
build_endpoint(generator.fork(), endpoint);
}
} // end anonymous namespace
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView ipc_file;
StringView output_file = "-"sv;
Core::ArgsParser parser;
parser.add_positional_argument(ipc_file, "IPC endpoint definition file", "input");
parser.add_option(output_file, "Place to write file", "output", 'o', "output-file");
parser.parse(arguments);
auto output = TRY(Core::File::open_file_or_standard_stream(output_file, Core::File::OpenMode::Write));
auto file = TRY(Core::File::open(ipc_file, Core::File::OpenMode::Read));
auto file_contents = TRY(file->read_until_eof());
auto endpoints = parse(file_contents);
StringBuilder builder;
build(builder, endpoints);
TRY(output->write_until_depleted(builder.string_view().bytes()));
if constexpr (GENERATE_DEBUG) {
for (auto& endpoint : endpoints) {
warnln("Endpoint '{}' (magic: {})", endpoint.name, endpoint.magic);
for (auto& message : endpoint.messages) {
warnln(" Message: '{}'", message.name);
warnln(" Sync: {}", message.is_synchronous);
warnln(" Inputs:");
for (auto& parameter : message.inputs)
warnln(" Parameter: {} ({})", parameter.name, parameter.type);
if (message.inputs.is_empty())
warnln(" (none)");
if (message.is_synchronous) {
warnln(" Outputs:");
for (auto& parameter : message.outputs)
warnln(" Parameter: {} ({})", parameter.name, parameter.type);
if (message.outputs.is_empty())
warnln(" (none)");
}
}
}
}
return 0;
}