diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/mojo | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/mojo')
169 files changed, 6702 insertions, 808 deletions
diff --git a/chromium/mojo/core/BUILD.gn b/chromium/mojo/core/BUILD.gn index e6fcb96256f..c42daf24d62 100644 --- a/chromium/mojo/core/BUILD.gn +++ b/chromium/mojo/core/BUILD.gn @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/chromeos/ui_mode.gni") import("//build/config/compiler/compiler.gni") import("//build/config/nacl/config.gni") import("//testing/libfuzzer/fuzzer_test.gni") @@ -18,6 +19,7 @@ component("embedder_internal") { ":test_sources", "//mojo:*", "//mojo/core/embedder", + "//mojo/core/test:test_support", ] } @@ -57,6 +59,7 @@ template("core_impl_source_set") { "handle_table.h", "invitation_dispatcher.h", "message_pipe_dispatcher.h", + "node_channel.h", "node_controller.h", "options_validation.h", "platform_handle_dispatcher.h", @@ -86,7 +89,6 @@ template("core_impl_source_set") { "invitation_dispatcher.cc", "message_pipe_dispatcher.cc", "node_channel.cc", - "node_channel.h", "node_controller.cc", "platform_handle_dispatcher.cc", "platform_handle_in_transit.cc", @@ -160,7 +162,8 @@ template("core_impl_source_set") { # Use target_os == "chromeos" instead of is_chromeos because we need to # build NaCl targets (i.e. IRT) for ChromeOS the same as the rest of ChromeOS. - if (is_android || target_os == "chromeos") { + if (is_android || target_os == "chromeos" || + (target_os == "linux" && chromeos_is_browser_only)) { defines += [ "MOJO_CORE_LEGACY_PROTOCOL" ] } @@ -187,6 +190,7 @@ if (is_chromeos || is_linux || is_android || is_win) { defines = [ "MOJO_CORE_SHARED_LIBRARY" ] deps = [ ":impl_for_shared_library", + "//base:base_static", "//mojo/public/c/system:headers", ] if (is_win) { @@ -240,6 +244,7 @@ if (is_chromeos || is_linux || is_android || is_win) { test("mojo_core_unittests") { sources = [ "mojo_core_unittest.cc", + "mojo_core_unittest.h", "run_all_core_unittests.cc", ] @@ -289,6 +294,7 @@ source_set("test_sources") { "handle_table_unittest.cc", "message_pipe_unittest.cc", "message_unittest.cc", + "node_channel_unittest.cc", "options_validation_unittest.cc", "platform_handle_dispatcher_unittest.cc", "quota_unittest.cc", @@ -387,6 +393,7 @@ fuzzer_test("mojo_core_node_channel_fuzzer") { deps = [ ":core_impl_for_fuzzers", "//base", + "//mojo/core/test:test_support", "//mojo/public/cpp/platform", ] } diff --git a/chromium/mojo/core/atomic_flag.h b/chromium/mojo/core/atomic_flag.h index 075b837ab1b..aec7f4ee65b 100644 --- a/chromium/mojo/core/atomic_flag.h +++ b/chromium/mojo/core/atomic_flag.h @@ -33,7 +33,7 @@ namespace core { class AtomicFlag { public: AtomicFlag() : flag_(0) {} - ~AtomicFlag() {} + ~AtomicFlag() = default; void Set(bool value) { base::subtle::Release_Store(&flag_, value ? 1 : 0); } diff --git a/chromium/mojo/core/broker_win.cc b/chromium/mojo/core/broker_win.cc index e81bfd301cd..f734b6b6f55 100644 --- a/chromium/mojo/core/broker_win.cc +++ b/chromium/mojo/core/broker_win.cc @@ -8,6 +8,7 @@ #include <utility> #include "base/debug/alias.h" +#include "base/logging.h" #include "base/memory/platform_shared_memory_region.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_piece.h" @@ -118,7 +119,7 @@ Broker::Broker(PlatformHandle handle, bool wait_for_channel_handle) } } -Broker::~Broker() {} +Broker::~Broker() = default; PlatformChannelEndpoint Broker::GetInviterEndpoint() { return std::move(inviter_endpoint_); diff --git a/chromium/mojo/core/channel.cc b/chromium/mojo/core/channel.cc index 38cbaea5c99..5d855891e2e 100644 --- a/chromium/mojo/core/channel.cc +++ b/chromium/mojo/core/channel.cc @@ -11,6 +11,7 @@ #include <limits> #include <utility> +#include "base/logging.h" #include "base/macros.h" #include "base/memory/aligned_memory.h" #include "base/memory/ptr_util.h" @@ -577,7 +578,7 @@ Channel::Channel(Delegate* delegate, ? new ReadBuffer : nullptr) {} -Channel::~Channel() {} +Channel::~Channel() = default; void Channel::ShutDown() { ShutDownImpl(); diff --git a/chromium/mojo/core/channel.h b/chromium/mojo/core/channel.h index 62447f8756d..a639c53c452 100644 --- a/chromium/mojo/core/channel.h +++ b/chromium/mojo/core/channel.h @@ -252,7 +252,7 @@ class MOJO_SYSTEM_IMPL_EXPORT Channel // was created (see Channel::Create). class Delegate { public: - virtual ~Delegate() {} + virtual ~Delegate() = default; // Notify of a received message. |payload| is not owned and must not be // retained; it will be null if |payload_size| is 0. |handles| are diff --git a/chromium/mojo/core/channel_fuchsia.cc b/chromium/mojo/core/channel_fuchsia.cc index 8ca10557d8d..2d818ac6804 100644 --- a/chromium/mojo/core/channel_fuchsia.cc +++ b/chromium/mojo/core/channel_fuchsia.cc @@ -99,16 +99,11 @@ class MessageView { DCHECK_GT(message_->data_num_bytes(), offset_); } - MessageView(MessageView&& other) { *this = std::move(other); } + MessageView(MessageView&& other) = default; - MessageView& operator=(MessageView&& other) { - message_ = std::move(other.message_); - offset_ = other.offset_; - handles_ = std::move(other.handles_); - return *this; - } + MessageView& operator=(MessageView&& other) = default; - ~MessageView() {} + ~MessageView() = default; const void* data() const { return static_cast<const char*>(message_->data()) + offset_; diff --git a/chromium/mojo/core/channel_posix.cc b/chromium/mojo/core/channel_posix.cc index 7b52b42f230..fb7c190b50a 100644 --- a/chromium/mojo/core/channel_posix.cc +++ b/chromium/mojo/core/channel_posix.cc @@ -47,11 +47,11 @@ class MessageView { DCHECK(!message_->data_num_bytes() || message_->data_num_bytes() > offset_); } - MessageView(MessageView&& other) { *this = std::move(other); } + MessageView(MessageView&& other) = default; MessageView& operator=(MessageView&& other) = default; - ~MessageView() {} + ~MessageView() = default; const void* data() const { return static_cast<const char*>(message_->data()) + offset_; diff --git a/chromium/mojo/core/channel_unittest.cc b/chromium/mojo/core/channel_unittest.cc index a57f2a4924d..6013ce22219 100644 --- a/chromium/mojo/core/channel_unittest.cc +++ b/chromium/mojo/core/channel_unittest.cc @@ -56,13 +56,13 @@ class TestChannel : public Channel { void Write(MessagePtr message) override {} protected: - ~TestChannel() override {} + ~TestChannel() override = default; }; // Not using GMock as I don't think it supports movable types. class MockChannelDelegate : public Channel::Delegate { public: - MockChannelDelegate() {} + MockChannelDelegate() = default; size_t GetReceivedPayloadSize() const { return payload_size_; } diff --git a/chromium/mojo/core/channel_win.cc b/chromium/mojo/core/channel_win.cc index 4ea0aeb6810..3fee0f2b78c 100644 --- a/chromium/mojo/core/channel_win.cc +++ b/chromium/mojo/core/channel_win.cc @@ -15,6 +15,7 @@ #include "base/containers/queue.h" #include "base/debug/activity_tracker.h" #include "base/location.h" +#include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop_current.h" @@ -186,7 +187,7 @@ class ChannelWin : public Channel, private: // May run on any thread. - ~ChannelWin() override {} + ~ChannelWin() override = default; void StartOnIOThread() { base::MessageLoopCurrent::Get()->AddDestructionObserver(this); diff --git a/chromium/mojo/core/core.cc b/chromium/mojo/core/core.cc index d6092658b75..6d06ab1fdee 100644 --- a/chromium/mojo/core/core.cc +++ b/chromium/mojo/core/core.cc @@ -153,10 +153,6 @@ scoped_refptr<Dispatcher> Core::GetAndRemoveDispatcher(MojoHandle handle) { return dispatcher; } -void Core::SetDefaultProcessErrorCallback(ProcessErrorCallback callback) { - default_process_error_callback_ = std::move(callback); -} - MojoHandle Core::CreatePartialMessagePipe(ports::PortRef* peer) { RequestContext request_context; ports::PortRef local_port; @@ -1477,6 +1473,29 @@ MojoResult Core::QueryQuota(MojoHandle handle, return dispatcher->QueryQuota(type, limit, usage); } +MojoResult Core::SetDefaultProcessErrorHandler( + MojoDefaultProcessErrorHandler handler, + const MojoSetDefaultProcessErrorHandlerOptions* options) { + if (default_process_error_callback_ && handler) + return MOJO_RESULT_ALREADY_EXISTS; + + if (!handler) { + default_process_error_callback_.Reset(); + return MOJO_RESULT_OK; + } + + default_process_error_callback_ = base::BindRepeating( + [](MojoDefaultProcessErrorHandler handler, const std::string& error) { + MojoProcessErrorDetails details = {0}; + details.struct_size = sizeof(details); + details.error_message_length = static_cast<uint32_t>(error.size()); + details.error_message = error.c_str(); + handler(&details); + }, + handler); + return MOJO_RESULT_OK; +} + void Core::GetActiveHandlesForTest(std::vector<MojoHandle>* handles) { base::AutoLock lock(handles_->GetLock()); handles_->GetActiveHandlesForTest(handles); diff --git a/chromium/mojo/core/core.h b/chromium/mojo/core/core.h index eaed40c8650..37c8a229fcf 100644 --- a/chromium/mojo/core/core.h +++ b/chromium/mojo/core/core.h @@ -54,8 +54,6 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle); scoped_refptr<Dispatcher> GetAndRemoveDispatcher(MojoHandle handle); - void SetDefaultProcessErrorCallback(ProcessErrorCallback callback); - // Creates a message pipe endpoint with an unbound peer port returned in // |*peer|. Useful for setting up cross-process bootstrap message pipes. The // returned message pipe handle is usable immediately by the caller. @@ -325,6 +323,10 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { uint64_t* limit, uint64_t* usage); + MojoResult SetDefaultProcessErrorHandler( + MojoDefaultProcessErrorHandler handler, + const MojoSetDefaultProcessErrorHandlerOptions* options); + void GetActiveHandlesForTest(std::vector<MojoHandle>* handles); private: diff --git a/chromium/mojo/core/core_test_base.cc b/chromium/mojo/core/core_test_base.cc index 88645237567..e173d853d1a 100644 --- a/chromium/mojo/core/core_test_base.cc +++ b/chromium/mojo/core/core_test_base.cc @@ -105,9 +105,9 @@ class MockDispatcher : public Dispatcher { // CoreTestBase ---------------------------------------------------------------- -CoreTestBase::CoreTestBase() {} +CoreTestBase::CoreTestBase() = default; -CoreTestBase::~CoreTestBase() {} +CoreTestBase::~CoreTestBase() = default; MojoHandle CoreTestBase::CreateMockHandle(CoreTestBase::MockHandleInfo* info) { scoped_refptr<MockDispatcher> dispatcher = MockDispatcher::Create(info); @@ -133,7 +133,7 @@ CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo() begin_read_data_call_count_(0), end_read_data_call_count_(0) {} -CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() {} +CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() = default; unsigned CoreTestBase_MockHandleInfo::GetCtorCallCount() const { base::AutoLock locker(lock_); diff --git a/chromium/mojo/core/data_pipe_consumer_dispatcher.cc b/chromium/mojo/core/data_pipe_consumer_dispatcher.cc index 979a222c104..4cf36093574 100644 --- a/chromium/mojo/core/data_pipe_consumer_dispatcher.cc +++ b/chromium/mojo/core/data_pipe_consumer_dispatcher.cc @@ -59,7 +59,7 @@ class DataPipeConsumerDispatcher::PortObserverThunk : dispatcher_(dispatcher) {} private: - ~PortObserverThunk() override {} + ~PortObserverThunk() override = default; // NodeController::PortObserver: void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } diff --git a/chromium/mojo/core/data_pipe_control_message.cc b/chromium/mojo/core/data_pipe_control_message.cc index cd782e5bc12..d59676ff0ef 100644 --- a/chromium/mojo/core/data_pipe_control_message.cc +++ b/chromium/mojo/core/data_pipe_control_message.cc @@ -4,6 +4,7 @@ #include "mojo/core/data_pipe_control_message.h" +#include "base/logging.h" #include "mojo/core/node_controller.h" #include "mojo/core/ports/event.h" #include "mojo/core/user_message_impl.h" diff --git a/chromium/mojo/core/data_pipe_producer_dispatcher.cc b/chromium/mojo/core/data_pipe_producer_dispatcher.cc index 4c0473d0b2f..cebe8dda57d 100644 --- a/chromium/mojo/core/data_pipe_producer_dispatcher.cc +++ b/chromium/mojo/core/data_pipe_producer_dispatcher.cc @@ -58,7 +58,7 @@ class DataPipeProducerDispatcher::PortObserverThunk : dispatcher_(dispatcher) {} private: - ~PortObserverThunk() override {} + ~PortObserverThunk() override = default; // NodeController::PortObserver: void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } diff --git a/chromium/mojo/core/dispatcher.cc b/chromium/mojo/core/dispatcher.cc index a110dbdc8e4..25959441808 100644 --- a/chromium/mojo/core/dispatcher.cc +++ b/chromium/mojo/core/dispatcher.cc @@ -16,12 +16,12 @@ namespace mojo { namespace core { -Dispatcher::DispatcherInTransit::DispatcherInTransit() {} +Dispatcher::DispatcherInTransit::DispatcherInTransit() = default; Dispatcher::DispatcherInTransit::DispatcherInTransit( const DispatcherInTransit& other) = default; -Dispatcher::DispatcherInTransit::~DispatcherInTransit() {} +Dispatcher::DispatcherInTransit::~DispatcherInTransit() = default; MojoResult Dispatcher::WatchDispatcher(scoped_refptr<Dispatcher> dispatcher, MojoHandleSignals signals, @@ -190,9 +190,9 @@ scoped_refptr<Dispatcher> Dispatcher::Deserialize( } } -Dispatcher::Dispatcher() {} +Dispatcher::Dispatcher() = default; -Dispatcher::~Dispatcher() {} +Dispatcher::~Dispatcher() = default; } // namespace core } // namespace mojo diff --git a/chromium/mojo/core/embedder/embedder.cc b/chromium/mojo/core/embedder/embedder.cc index 8b9d331ad5b..3a4e3172938 100644 --- a/chromium/mojo/core/embedder/embedder.cc +++ b/chromium/mojo/core/embedder/embedder.cc @@ -7,10 +7,8 @@ #include <stdint.h> #include <utility> -#include "base/bind.h" #include "base/memory/ref_counted.h" #include "base/task_runner.h" -#include "build/build_config.h" #include "mojo/core/configuration.h" #include "mojo/core/core.h" #include "mojo/core/entrypoints.h" @@ -30,11 +28,7 @@ void Init() { Init(Configuration()); } -void SetDefaultProcessErrorCallback(ProcessErrorCallback callback) { - Core::Get()->SetDefaultProcessErrorCallback(std::move(callback)); -} - -scoped_refptr<base::TaskRunner> GetIOTaskRunner() { +scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() { return Core::Get()->GetNodeController()->io_task_runner(); } diff --git a/chromium/mojo/core/embedder/embedder.h b/chromium/mojo/core/embedder/embedder.h index 5d654009877..575b301468d 100644 --- a/chromium/mojo/core/embedder/embedder.h +++ b/chromium/mojo/core/embedder/embedder.h @@ -9,20 +9,16 @@ #include <string> -#include "base/callback.h" #include "base/component_export.h" #include "base/memory/ref_counted.h" #include "base/process/process_handle.h" -#include "base/task_runner.h" +#include "base/single_thread_task_runner.h" #include "build/build_config.h" #include "mojo/core/embedder/configuration.h" namespace mojo { namespace core { -using ProcessErrorCallback = - base::RepeatingCallback<void(const std::string& error)>; - // Basic configuration/initialization ------------------------------------------ // Must be called first, or just after setting configuration parameters, to @@ -35,16 +31,12 @@ void Init(const Configuration& configuration); // Like above but uses a default Configuration. COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) void Init(); -// Sets a default callback to invoke when an internal error is reported but -// cannot be associated with a specific child process. Calling this is optional. -COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) -void SetDefaultProcessErrorCallback(ProcessErrorCallback callback); - // Initialialization/shutdown for interprocess communication (IPC) ------------- -// Retrieves the TaskRunner used for IPC I/O, as set by ScopedIPCSupport. +// Retrieves the SequencedTaskRunner used for IPC I/O, as set by +// ScopedIPCSupport. COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) -scoped_refptr<base::TaskRunner> GetIOTaskRunner(); +scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner(); } // namespace core } // namespace mojo diff --git a/chromium/mojo/core/embedder/scoped_ipc_support.h b/chromium/mojo/core/embedder/scoped_ipc_support.h index aba6e712ad8..ff603294702 100644 --- a/chromium/mojo/core/embedder/scoped_ipc_support.h +++ b/chromium/mojo/core/embedder/scoped_ipc_support.h @@ -86,7 +86,7 @@ class COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) ScopedIPCSupport { // // There are other practical scenarios where fast shutdown is safe even if // the process may have live proxies. For example, content's browser process - // is treated as a sort of master process in the system, in the sense that if + // is treated as a sort of root process in the system, in the sense that if // the browser is terminated, no other part of the system is expected to // continue normal operation anyway. In this case the side-effects of fast // shutdown are irrelevant, so fast shutdown is preferred. diff --git a/chromium/mojo/core/entrypoints.cc b/chromium/mojo/core/entrypoints.cc index 466da7e08a5..7b342a516f5 100644 --- a/chromium/mojo/core/entrypoints.cc +++ b/chromium/mojo/core/entrypoints.cc @@ -350,6 +350,12 @@ MojoResult MojoShutdownImpl(const MojoShutdownOptions* options) { return MOJO_RESULT_UNIMPLEMENTED; } +MojoResult MojoSetDefaultProcessErrorHandlerImpl( + MojoDefaultProcessErrorHandler handler, + const MojoSetDefaultProcessErrorHandlerOptions* options) { + return g_core->SetDefaultProcessErrorHandler(handler, options); +} + } // extern "C" MojoSystemThunks g_thunks = {sizeof(MojoSystemThunks), @@ -396,7 +402,8 @@ MojoSystemThunks g_thunks = {sizeof(MojoSystemThunks), MojoAcceptInvitationImpl, MojoSetQuotaImpl, MojoQueryQuotaImpl, - MojoShutdownImpl}; + MojoShutdownImpl, + MojoSetDefaultProcessErrorHandlerImpl}; } // namespace diff --git a/chromium/mojo/core/handle_table.cc b/chromium/mojo/core/handle_table.cc index 62419a92363..9426281d73f 100644 --- a/chromium/mojo/core/handle_table.cc +++ b/chromium/mojo/core/handle_table.cc @@ -40,9 +40,9 @@ const char* GetNameForDispatcherType(Dispatcher::Type type) { } // namespace -HandleTable::HandleTable() {} +HandleTable::HandleTable() = default; -HandleTable::~HandleTable() {} +HandleTable::~HandleTable() = default; base::Lock& HandleTable::GetLock() { return lock_; @@ -191,14 +191,14 @@ bool HandleTable::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, return true; } -HandleTable::Entry::Entry() {} +HandleTable::Entry::Entry() = default; HandleTable::Entry::Entry(scoped_refptr<Dispatcher> dispatcher) : dispatcher(std::move(dispatcher)) {} HandleTable::Entry::Entry(const Entry& other) = default; -HandleTable::Entry::~Entry() {} +HandleTable::Entry::~Entry() = default; } // namespace core } // namespace mojo diff --git a/chromium/mojo/core/handle_table_unittest.cc b/chromium/mojo/core/handle_table_unittest.cc index 910b14e76cf..0724fb09474 100644 --- a/chromium/mojo/core/handle_table_unittest.cc +++ b/chromium/mojo/core/handle_table_unittest.cc @@ -27,14 +27,14 @@ namespace { class FakeMessagePipeDispatcher : public Dispatcher { public: - FakeMessagePipeDispatcher() {} + FakeMessagePipeDispatcher() = default; Type GetType() const override { return Type::MESSAGE_PIPE; } MojoResult Close() override { return MOJO_RESULT_OK; } private: - ~FakeMessagePipeDispatcher() override {} + ~FakeMessagePipeDispatcher() override = default; DISALLOW_COPY_AND_ASSIGN(FakeMessagePipeDispatcher); }; diff --git a/chromium/mojo/core/message_pipe_dispatcher.cc b/chromium/mojo/core/message_pipe_dispatcher.cc index 22ac04acc5f..e17fdabc2d3 100644 --- a/chromium/mojo/core/message_pipe_dispatcher.cc +++ b/chromium/mojo/core/message_pipe_dispatcher.cc @@ -47,7 +47,7 @@ class MessagePipeDispatcher::PortObserverThunk : dispatcher_(dispatcher) {} private: - ~PortObserverThunk() override {} + ~PortObserverThunk() override = default; // NodeController::PortObserver: void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } @@ -63,8 +63,8 @@ class MessagePipeDispatcher::PortObserverThunk // the next available message on a port, for debug logging only. class PeekSizeMessageFilter : public ports::MessageFilter { public: - PeekSizeMessageFilter() {} - ~PeekSizeMessageFilter() override {} + PeekSizeMessageFilter() = default; + ~PeekSizeMessageFilter() override = default; // ports::MessageFilter: bool Match(const ports::UserMessageEvent& message_event) override { @@ -380,9 +380,8 @@ MojoResult MessagePipeDispatcher::CloseNoLock() { base::AutoUnlock unlock(signal_lock_); node_controller_->ClosePort(port_); - TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), - "MessagePipe closing", pipe_id_ + endpoint_, - TRACE_EVENT_FLAG_FLOW_OUT); + TRACE_EVENT_WITH_FLOW0("toplevel.flow", "MessagePipe closing", + pipe_id_ + endpoint_, TRACE_EVENT_FLAG_FLOW_OUT); } return MOJO_RESULT_OK; @@ -433,9 +432,9 @@ HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateNoLock() const { rv.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED; last_known_satisfied_signals_ = rv.satisfied_signals; if (is_peer_closed && !was_peer_closed) { - TRACE_EVENT_WITH_FLOW0( - TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), "MessagePipe peer closed", - pipe_id_ + (1 - endpoint_), TRACE_EVENT_FLAG_FLOW_IN); + TRACE_EVENT_WITH_FLOW0("toplevel.flow", "MessagePipe peer closed", + pipe_id_ + (1 - endpoint_), + TRACE_EVENT_FLAG_FLOW_IN); } return rv; diff --git a/chromium/mojo/core/message_unittest.cc b/chromium/mojo/core/message_unittest.cc index c079e1106c6..cb0747c09ea 100644 --- a/chromium/mojo/core/message_unittest.cc +++ b/chromium/mojo/core/message_unittest.cc @@ -30,7 +30,7 @@ using MessageTest = test::MojoTestBase; // handles. class TestMessageBase { public: - virtual ~TestMessageBase() {} + virtual ~TestMessageBase() = default; static MojoMessageHandle MakeMessageHandle( std::unique_ptr<TestMessageBase> message) { diff --git a/chromium/mojo/core/mojo_core.cc b/chromium/mojo/core/mojo_core.cc index 7d28bcfdddd..2261a8a9adb 100644 --- a/chromium/mojo/core/mojo_core.cc +++ b/chromium/mojo/core/mojo_core.cc @@ -5,17 +5,25 @@ #include <stddef.h> #include "base/at_exit.h" +#include "base/base_switches.h" #include "base/bind.h" +#include "base/command_line.h" +#include "base/debug/stack_trace.h" +#include "base/feature_list.h" +#include "base/logging.h" #include "base/macros.h" #include "base/message_loop/message_pump_type.h" #include "base/no_destructor.h" +#include "base/rand_util.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/time/time.h" +#include "build/build_config.h" #include "mojo/core/configuration.h" #include "mojo/core/core.h" #include "mojo/core/entrypoints.h" #include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/macros.h" #include "mojo/public/c/system/thunks.h" namespace { @@ -56,6 +64,62 @@ std::unique_ptr<IPCSupport>& GetIPCSupport() { return *state; } +// This helper is only called from within the context of a newly loaded Mojo +// Core shared library, where various bits of static state (e.g. //base globals) +// will not yet be initialized. Base library initialization steps are thus +// consolidated here so that base APIs work as expected from within the loaded +// Mojo Core implementation. +// +// NOTE: This is a no-op in component builds, as we expect both the client +// application and the Mojo Core library to have been linked against the same +// base component library, and we furthermore expect that the client application +// has already initialized base globals by this point. +class GlobalStateInitializer { + public: + GlobalStateInitializer() = default; + + bool Initialize(int argc, const char* const* argv) { + if (initialized_) + return false; + initialized_ = true; +#if !defined(COMPONENT_BUILD) + base::CommandLine::Init(argc, argv); + + logging::LoggingSettings settings; + settings.logging_dest = + logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR; + logging::InitLogging(settings); + logging::SetLogItems(true, // Process ID + true, // Thread ID + true, // Timestamp + true); // Tick count + +#if !defined(OFFICIAL_BUILD) && !defined(OS_WIN) + // Correct stack dumping behavior requires symbol names in all loaded + // libraries to be cached. We do this here in case the calling process will + // imminently enter a sandbox. + base::debug::EnableInProcessStackDumping(); +#endif + +#if defined(OS_POSIX) + // Tickle base's PRNG. This lazily opens a static handle to /dev/urandom. + // Mojo Core uses the API internally, so it's important to warm the handle + // before potentially entering a sandbox. + base::RandUint64(); +#endif + + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::FeatureList::InitializeInstance( + command_line->GetSwitchValueASCII(switches::kEnableFeatures), + command_line->GetSwitchValueASCII(switches::kDisableFeatures)); +#endif // !defined(COMPONENT_BUILD) + return true; + } + + private: + bool initialized_ = false; +}; + } // namespace extern "C" { @@ -63,13 +127,47 @@ extern "C" { namespace { MojoResult InitializeImpl(const struct MojoInitializeOptions* options) { + std::unique_ptr<IPCSupport>& ipc_support = GetIPCSupport(); + if (ipc_support) { + // Already fully initialized, so there's nothing to do. + return MOJO_RESULT_FAILED_PRECONDITION; + } + + // NOTE: |MojoInitialize()| may be called more than once if the caller wishes + // to separate basic initialization from IPC support initialization. We only + // do basic initialization the first time this is called. + const bool should_initialize_ipc_support = + !options || ((options->flags & MOJO_INITIALIZE_FLAG_LOAD_ONLY) == 0); + + int argc = 0; + const char* const* argv = nullptr; + if (options && MOJO_IS_STRUCT_FIELD_PRESENT(options, argv)) { + argc = options->argc; + argv = options->argv; + } + + static base::NoDestructor<GlobalStateInitializer> global_state_initializer; + const bool was_global_state_already_initialized = + !global_state_initializer->Initialize(argc, argv); + + if (!should_initialize_ipc_support) { + if (was_global_state_already_initialized) + return MOJO_RESULT_ALREADY_EXISTS; + else + return MOJO_RESULT_OK; + } + + DCHECK(!mojo::core::Core::Get()); mojo::core::Configuration config; config.is_broker_process = options && options->flags & MOJO_INITIALIZE_FLAG_AS_BROKER; + config.force_direct_shared_memory_allocation = + options && options->flags & + MOJO_INITIALIZE_FLAG_FORCE_DIRECT_SHARED_MEMORY_ALLOCATION; mojo::core::internal::g_configuration = config; - mojo::core::InitializeCore(); - GetIPCSupport() = std::make_unique<IPCSupport>(); + ipc_support = std::make_unique<IPCSupport>(); + return MOJO_RESULT_OK; } diff --git a/chromium/mojo/core/mojo_core_unittest.cc b/chromium/mojo/core/mojo_core_unittest.cc index a6f07022b75..e97172c71f9 100644 --- a/chromium/mojo/core/mojo_core_unittest.cc +++ b/chromium/mojo/core/mojo_core_unittest.cc @@ -4,12 +4,14 @@ #include <stdint.h> +#include <tuple> #include <vector> #include "base/command_line.h" #include "base/process/launch.h" #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" +#include "mojo/core/mojo_core_unittest.h" #include "mojo/public/c/system/core.h" #include "mojo/public/cpp/platform/platform_channel.h" #include "mojo/public/cpp/platform/platform_handle.h" @@ -19,6 +21,11 @@ #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" +namespace switches { +const char kMojoLoadBeforeInit[] = "mojo-load-before-init"; +const char kMojoUseExplicitLibraryPath[] = "mojo-use-explicit-library-path"; +} // namespace switches + namespace { // TODO(https://crbug.com/902135): Re-enable this on MSAN. Currently hangs @@ -66,10 +73,27 @@ TEST(MojoCoreTest, SanityCheck) { EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); } -TEST(MojoCoreTest, BasicMultiprocess) { +enum class InitializationMode { kCombinedLoadAndInit, kLoadBeforeInit }; + +enum class LoadPathSpec { kImplicit, kExplicit }; + +class MojoCoreMultiprocessTest + : public ::testing::TestWithParam< + std::tuple<InitializationMode, LoadPathSpec>> { + public: + void SetChildCommandLineForTestParams(base::CommandLine* command_line) { + if (std::get<0>(GetParam()) == InitializationMode::kLoadBeforeInit) + command_line->AppendSwitch(switches::kMojoLoadBeforeInit); + if (std::get<1>(GetParam()) == LoadPathSpec::kExplicit) + command_line->AppendSwitch(switches::kMojoUseExplicitLibraryPath); + } +}; + +TEST_P(MojoCoreMultiprocessTest, BasicMultiprocess) { base::CommandLine child_cmd(base::GetMultiProcessTestChildBaseCommandLine()); - base::LaunchOptions options; + SetChildCommandLineForTestParams(&child_cmd); + base::LaunchOptions options; mojo::PlatformChannel channel; channel.PrepareToPassRemoteEndpoint(&options, &child_cmd); base::Process child_process = base::SpawnMultiProcessTestChild( @@ -108,6 +132,15 @@ MULTIPROCESS_TEST_MAIN(BasicMultiprocessClientMain) { return 0; } + +INSTANTIATE_TEST_SUITE_P( + , + MojoCoreMultiprocessTest, + ::testing::Combine( + ::testing::Values(InitializationMode::kCombinedLoadAndInit, + InitializationMode::kLoadBeforeInit), + ::testing::Values(LoadPathSpec::kImplicit, LoadPathSpec::kExplicit))); + #endif // !defined(MEMORY_SANITIZER) } // namespace diff --git a/chromium/mojo/core/mojo_core_unittest.h b/chromium/mojo/core/mojo_core_unittest.h new file mode 100644 index 00000000000..ee25f11da9c --- /dev/null +++ b/chromium/mojo/core/mojo_core_unittest.h @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_CORE_MOJO_CORE_UNITTEST_H_ +#define MOJO_CORE_MOJO_CORE_UNITTEST_H_ + +namespace switches { + +// Instructs the test runner to initialize the Mojo Core library in separate +// phases. Used by a unit test when launching a subprocess, to verify the +// correctness of phased initialization. +extern const char kMojoLoadBeforeInit[]; + +// Instructs the test runner to provide an explicit path to the Mojo Core shared +// library, rather than assuming it's present in the current working directory. +extern const char kMojoUseExplicitLibraryPath[]; + +} // namespace switches + +#endif // MOJO_CORE_MOJO_CORE_UNITTEST_H_ diff --git a/chromium/mojo/core/node_channel.cc b/chromium/mojo/core/node_channel.cc index e898b044286..061ea1026e9 100644 --- a/chromium/mojo/core/node_channel.cc +++ b/chromium/mojo/core/node_channel.cc @@ -228,7 +228,7 @@ void NodeChannel::NotifyBadMessage(const std::string& error) { } void NodeChannel::SetRemoteProcessHandle(ScopedProcessHandle process_handle) { - DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence()); { base::AutoLock lock(channel_lock_); if (channel_) @@ -253,7 +253,7 @@ ScopedProcessHandle NodeChannel::CloneRemoteProcessHandle() { } void NodeChannel::SetRemoteNodeName(const ports::NodeName& name) { - DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence()); remote_node_name_ = name; } @@ -468,15 +468,15 @@ NodeChannel::NodeChannel( Channel::HandlePolicy channel_handle_policy, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const ProcessErrorCallback& process_error_callback) - : delegate_(delegate), - io_task_runner_(io_task_runner), + : base::RefCountedDeleteOnSequence<NodeChannel>(io_task_runner), + delegate_(delegate), process_error_callback_(process_error_callback) #if !defined(OS_NACL_SFI) , channel_(Channel::Create(this, std::move(connection_params), channel_handle_policy, - io_task_runner_)) + std::move(io_task_runner))) #endif { } @@ -499,15 +499,10 @@ void NodeChannel::CreateAndBindLocalBrokerHost( void NodeChannel::OnChannelMessage(const void* payload, size_t payload_size, std::vector<PlatformHandle> handles) { - DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence()); RequestContext request_context(RequestContext::Source::SYSTEM); - // Ensure this NodeChannel stays alive through the extent of this method. The - // delegate may have the only other reference to this object and it may choose - // to drop it here in response to, e.g., a malformed message. - scoped_refptr<NodeChannel> keepalive = this; - if (payload_size <= sizeof(Header)) { delegate_->OnChannelError(remote_node_name_, this); return; @@ -739,7 +734,7 @@ void NodeChannel::OnChannelMessage(const void* payload, } void NodeChannel::OnChannelError(Channel::Error error) { - DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence()); RequestContext request_context(RequestContext::Source::SYSTEM); diff --git a/chromium/mojo/core/node_channel.h b/chromium/mojo/core/node_channel.h index ea91f927049..58ab42bd01f 100644 --- a/chromium/mojo/core/node_channel.h +++ b/chromium/mojo/core/node_channel.h @@ -11,7 +11,7 @@ #include "base/callback.h" #include "base/containers/queue.h" #include "base/macros.h" -#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_delete_on_sequence.h" #include "base/process/process_handle.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" @@ -21,17 +21,19 @@ #include "mojo/core/embedder/process_error_callback.h" #include "mojo/core/ports/name.h" #include "mojo/core/scoped_process_handle.h" +#include "mojo/core/system_impl_export.h" namespace mojo { namespace core { // Wraps a Channel to send and receive Node control messages. -class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, - public Channel::Delegate { +class MOJO_SYSTEM_IMPL_EXPORT NodeChannel + : public base::RefCountedDeleteOnSequence<NodeChannel>, + public Channel::Delegate { public: class Delegate { public: - virtual ~Delegate() {} + virtual ~Delegate() = default; virtual void OnAcceptInvitee(const ports::NodeName& from_node, const ports::NodeName& inviter_name, const ports::NodeName& token) = 0; @@ -92,8 +94,6 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, void** data, size_t* num_data_bytes); - Channel* channel() const { return channel_.get(); } - // Start receiving messages. void Start(); @@ -155,7 +155,8 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, #endif private: - friend class base::RefCountedThreadSafe<NodeChannel>; + friend class base::RefCountedDeleteOnSequence<NodeChannel>; + friend class base::DeleteHelper<NodeChannel>; using PendingMessageQueue = base::queue<Channel::MessagePtr>; using PendingRelayMessageQueue = @@ -181,13 +182,12 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, void WriteChannelMessage(Channel::MessagePtr message); Delegate* const delegate_; - const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; const ProcessErrorCallback process_error_callback_; base::Lock channel_lock_; - scoped_refptr<Channel> channel_; + scoped_refptr<Channel> channel_ GUARDED_BY(channel_lock_); - // Must only be accessed from |io_task_runner_|'s thread. + // Must only be accessed from the owning task runner's thread. ports::NodeName remote_node_name_; base::Lock remote_process_handle_lock_; diff --git a/chromium/mojo/core/node_channel_fuzzer.cc b/chromium/mojo/core/node_channel_fuzzer.cc index 99047c000db..54fe757e0de 100644 --- a/chromium/mojo/core/node_channel_fuzzer.cc +++ b/chromium/mojo/core/node_channel_fuzzer.cc @@ -14,6 +14,7 @@ #include "mojo/core/connection_params.h" #include "mojo/core/entrypoints.h" #include "mojo/core/node_channel.h" // nogncheck +#include "mojo/core/test/mock_node_channel_delegate.h" #include "mojo/public/cpp/platform/platform_channel.h" #if defined(OS_WIN) @@ -24,60 +25,6 @@ using mojo::core::Channel; using mojo::core::ConnectionParams; using mojo::core::ports::NodeName; -// Implementation of NodeChannel::Delegate which does nothing. All of the -// interesting NodeChannel control message message parsing is done by -// NodeChannel by the time any of the delegate methods are invoked, so there's -// no need for this to do any work. -class FakeNodeChannelDelegate : public mojo::core::NodeChannel::Delegate { - public: - FakeNodeChannelDelegate() = default; - ~FakeNodeChannelDelegate() override = default; - - void OnAcceptInvitee(const NodeName& from_node, - const NodeName& inviter_name, - const NodeName& token) override {} - void OnAcceptInvitation(const NodeName& from_node, - const NodeName& token, - const NodeName& invitee_name) override {} - void OnAddBrokerClient(const NodeName& from_node, - const NodeName& client_name, - base::ProcessHandle process_handle) override {} - void OnBrokerClientAdded(const NodeName& from_node, - const NodeName& client_name, - mojo::PlatformHandle broker_channel) override {} - void OnAcceptBrokerClient(const NodeName& from_node, - const NodeName& broker_name, - mojo::PlatformHandle broker_channel) override {} - void OnEventMessage(const NodeName& from_node, - Channel::MessagePtr message) override {} - void OnRequestPortMerge( - const NodeName& from_node, - const mojo::core::ports::PortName& connector_port_name, - const std::string& token) override {} - void OnRequestIntroduction(const NodeName& from_node, - const NodeName& name) override {} - void OnIntroduce(const NodeName& from_node, - const NodeName& name, - mojo::PlatformHandle channel_handle) override {} - void OnBroadcast(const NodeName& from_node, - Channel::MessagePtr message) override {} -#if defined(OS_WIN) - void OnRelayEventMessage(const NodeName& from_node, - base::ProcessHandle from_process, - const NodeName& destination, - Channel::MessagePtr message) override {} - void OnEventMessageFromRelay(const NodeName& from_node, - const NodeName& source_node, - Channel::MessagePtr message) override {} -#endif - void OnAcceptPeer(const NodeName& from_node, - const NodeName& token, - const NodeName& peer_name, - const mojo::core::ports::PortName& port_name) override {} - void OnChannelError(const NodeName& node, - mojo::core::NodeChannel* channel) override {} -}; - // A fake delegate for the sending Channel endpoint. The sending Channel is not // being fuzzed and won't receive any interesting messages, so this doesn't need // to do anything. @@ -109,7 +56,7 @@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) { // used to carry messages between processes. mojo::PlatformChannel channel; - FakeNodeChannelDelegate receiver_delegate; + mojo::core::MockNodeChannelDelegate receiver_delegate; auto receiver = mojo::core::NodeChannel::Create( &receiver_delegate, ConnectionParams(channel.TakeLocalEndpoint()), Channel::HandlePolicy::kRejectHandles, diff --git a/chromium/mojo/core/node_channel_unittest.cc b/chromium/mojo/core/node_channel_unittest.cc new file mode 100644 index 00000000000..13c46f13fea --- /dev/null +++ b/chromium/mojo/core/node_channel_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/core/node_channel.h" + +#include "base/bind_helpers.h" +#include "base/memory/scoped_refptr.h" +#include "base/message_loop/message_pump_type.h" +#include "base/test/task_environment.h" +#include "base/threading/thread.h" +#include "mojo/core/embedder/embedder.h" +#include "mojo/core/test/mock_node_channel_delegate.h" +#include "mojo/public/cpp/platform/platform_channel.h" +#include "mojo/public/cpp/platform/platform_channel_endpoint.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace core { +namespace { + +using NodeChannelTest = testing::Test; +using ports::NodeName; + +scoped_refptr<NodeChannel> CreateNodeChannel(NodeChannel::Delegate* delegate, + PlatformChannelEndpoint endpoint) { + return NodeChannel::Create(delegate, ConnectionParams(std::move(endpoint)), + Channel::HandlePolicy::kAcceptHandles, + GetIOTaskRunner(), base::NullCallback()); +} + +TEST_F(NodeChannelTest, DestructionIsSafe) { + // Regression test for https://crbug.com/1081874. + base::test::TaskEnvironment task_environment; + + PlatformChannel channel; + MockNodeChannelDelegate local_delegate; + auto local_channel = + CreateNodeChannel(&local_delegate, channel.TakeLocalEndpoint()); + local_channel->Start(); + MockNodeChannelDelegate remote_delegate; + auto remote_channel = + CreateNodeChannel(&remote_delegate, channel.TakeRemoteEndpoint()); + remote_channel->Start(); + + // Verify end-to-end operation + const NodeName kRemoteNodeName{123, 456}; + const NodeName kToken{987, 654}; + base::RunLoop loop; + EXPECT_CALL(local_delegate, + OnAcceptInvitee(ports::kInvalidNodeName, kRemoteNodeName, kToken)) + .WillRepeatedly([&] { loop.Quit(); }); + remote_channel->AcceptInvitee(kRemoteNodeName, kToken); + loop.Run(); + + // Now send another message to the local endpoint but tear it down + // immediately. This will race with the message being received on the IO + // thread, and although the corresponding delegate call may or may not + // dispatch as a result, the race should still be memory-safe. + remote_channel->AcceptInvitee(kRemoteNodeName, kToken); + + base::RunLoop error_loop; + EXPECT_CALL(remote_delegate, OnChannelError).WillOnce([&] { + error_loop.Quit(); + }); + local_channel.reset(); + error_loop.Run(); +} + +} // namespace +} // namespace core +} // namespace mojo diff --git a/chromium/mojo/core/node_controller.cc b/chromium/mojo/core/node_controller.cc index 345d22ae06b..ff93f0c3a6b 100644 --- a/chromium/mojo/core/node_controller.cc +++ b/chromium/mojo/core/node_controller.cc @@ -144,7 +144,7 @@ class ThreadDestructionObserver } // namespace -NodeController::~NodeController() {} +NodeController::~NodeController() = default; NodeController::NodeController(Core* core) : core_(core), diff --git a/chromium/mojo/core/node_controller.h b/chromium/mojo/core/node_controller.h index e9645084037..9494de5b809 100644 --- a/chromium/mojo/core/node_controller.h +++ b/chromium/mojo/core/node_controller.h @@ -48,7 +48,7 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, virtual void OnPortStatusChanged() = 0; protected: - ~PortObserver() override {} + ~PortObserver() override = default; }; // |core| owns and out-lives us. diff --git a/chromium/mojo/core/options_validation.h b/chromium/mojo/core/options_validation.h index ae4120800ae..89c12d0c887 100644 --- a/chromium/mojo/core/options_validation.h +++ b/chromium/mojo/core/options_validation.h @@ -16,7 +16,7 @@ #include <algorithm> -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "mojo/core/system_impl_export.h" #include "mojo/public/c/system/types.h" diff --git a/chromium/mojo/core/platform_shared_memory_mapping.h b/chromium/mojo/core/platform_shared_memory_mapping.h index b7c43ace9e0..305f5e4ef15 100644 --- a/chromium/mojo/core/platform_shared_memory_mapping.h +++ b/chromium/mojo/core/platform_shared_memory_mapping.h @@ -9,7 +9,6 @@ #include <memory> -#include "base/logging.h" #include "base/macros.h" #include "base/memory/platform_shared_memory_region.h" #include "base/memory/shared_memory_mapping.h" diff --git a/chromium/mojo/core/ports/message_filter.h b/chromium/mojo/core/ports/message_filter.h index f09903ffca4..21f8c1c8697 100644 --- a/chromium/mojo/core/ports/message_filter.h +++ b/chromium/mojo/core/ports/message_filter.h @@ -15,7 +15,7 @@ class UserMessageEvent; // arbitrary policy. class MessageFilter { public: - virtual ~MessageFilter() {} + virtual ~MessageFilter() = default; // Returns true if |message| should be accepted by whomever is applying this // filter. See MessageQueue::GetNextMessage(), for example. diff --git a/chromium/mojo/core/ports/node.cc b/chromium/mojo/core/ports/node.cc index 476594db0cb..5b2558a0465 100644 --- a/chromium/mojo/core/ports/node.cc +++ b/chromium/mojo/core/ports/node.cc @@ -15,6 +15,7 @@ #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/notreached.h" #include "base/optional.h" #include "base/synchronization/lock.h" #include "base/threading/thread_local.h" @@ -1726,7 +1727,7 @@ Node::DelegateHolder::DelegateHolder(Node* node, NodeDelegate* delegate) DCHECK(node_); } -Node::DelegateHolder::~DelegateHolder() {} +Node::DelegateHolder::~DelegateHolder() = default; #if DCHECK_IS_ON() void Node::DelegateHolder::EnsureSafeDelegateAccess() const { diff --git a/chromium/mojo/core/ports/node_delegate.h b/chromium/mojo/core/ports/node_delegate.h index afe1c4cd975..3172779c06b 100644 --- a/chromium/mojo/core/ports/node_delegate.h +++ b/chromium/mojo/core/ports/node_delegate.h @@ -17,7 +17,7 @@ namespace ports { class NodeDelegate { public: - virtual ~NodeDelegate() {} + virtual ~NodeDelegate() = default; // Forward an event (possibly asynchronously) to the specified node. virtual void ForwardEvent(const NodeName& node, ScopedEvent event) = 0; diff --git a/chromium/mojo/core/ports/port.cc b/chromium/mojo/core/ports/port.cc index 0d2aa74650e..c46dc9ed25b 100644 --- a/chromium/mojo/core/ports/port.cc +++ b/chromium/mojo/core/ports/port.cc @@ -21,7 +21,7 @@ Port::Port(uint64_t next_sequence_num_to_send, peer_closed(false), peer_lost_unexpectedly(false) {} -Port::~Port() {} +Port::~Port() = default; } // namespace ports } // namespace core diff --git a/chromium/mojo/core/ports/port_ref.cc b/chromium/mojo/core/ports/port_ref.cc index a3d312bc399..7cbc53674c7 100644 --- a/chromium/mojo/core/ports/port_ref.cc +++ b/chromium/mojo/core/ports/port_ref.cc @@ -10,9 +10,9 @@ namespace mojo { namespace core { namespace ports { -PortRef::~PortRef() {} +PortRef::~PortRef() = default; -PortRef::PortRef() {} +PortRef::PortRef() = default; PortRef::PortRef(const PortName& name, scoped_refptr<Port> port) : name_(name), port_(std::move(port)) {} diff --git a/chromium/mojo/core/ports/port_ref.h b/chromium/mojo/core/ports/port_ref.h index b63d6cfcd00..afa75e54c17 100644 --- a/chromium/mojo/core/ports/port_ref.h +++ b/chromium/mojo/core/ports/port_ref.h @@ -6,7 +6,6 @@ #define MOJO_CORE_PORTS_PORT_REF_H_ #include "base/component_export.h" -#include "base/logging.h" #include "base/memory/ref_counted.h" #include "mojo/core/ports/name.h" diff --git a/chromium/mojo/core/ports/ports_unittest.cc b/chromium/mojo/core/ports/ports_unittest.cc index 07ee1155fd3..3353aa42e2e 100644 --- a/chromium/mojo/core/ports/ports_unittest.cc +++ b/chromium/mojo/core/ports/ports_unittest.cc @@ -44,7 +44,7 @@ class TestMessage : public UserMessage { TestMessage(const base::StringPiece& payload) : UserMessage(&kUserMessageTypeInfo), payload_(payload) {} - ~TestMessage() override {} + ~TestMessage() override = default; const std::string& payload() const { return payload_; } @@ -69,7 +69,7 @@ class TestNode; class MessageRouter { public: - virtual ~MessageRouter() {} + virtual ~MessageRouter() = default; virtual void ForwardEvent(TestNode* from_node, const NodeName& node_name, diff --git a/chromium/mojo/core/ports/user_data.h b/chromium/mojo/core/ports/user_data.h index e18d9b79d5f..2d2b25fc01b 100644 --- a/chromium/mojo/core/ports/user_data.h +++ b/chromium/mojo/core/ports/user_data.h @@ -15,7 +15,7 @@ class UserData : public base::RefCountedThreadSafe<UserData> { protected: friend class base::RefCountedThreadSafe<UserData>; - virtual ~UserData() {} + virtual ~UserData() = default; }; } // namespace ports diff --git a/chromium/mojo/core/request_context.cc b/chromium/mojo/core/request_context.cc index 582ff6fdf53..4ede95c4146 100644 --- a/chromium/mojo/core/request_context.cc +++ b/chromium/mojo/core/request_context.cc @@ -110,7 +110,7 @@ RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( const WatchNotifyFinalizer& other) = default; -RequestContext::WatchNotifyFinalizer::~WatchNotifyFinalizer() {} +RequestContext::WatchNotifyFinalizer::~WatchNotifyFinalizer() = default; } // namespace core } // namespace mojo diff --git a/chromium/mojo/core/run_all_core_unittests.cc b/chromium/mojo/core/run_all_core_unittests.cc index 18b119ef4af..b8b1f199add 100644 --- a/chromium/mojo/core/run_all_core_unittests.cc +++ b/chromium/mojo/core/run_all_core_unittests.cc @@ -4,25 +4,54 @@ #include "base/base_switches.h" #include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/optional.h" #include "base/test/launcher/unit_test_launcher.h" #include "base/test/test_suite.h" #include "build/build_config.h" +#include "mojo/core/mojo_core_unittest.h" #include "mojo/public/c/system/core.h" +#include "mojo/public/cpp/system/dynamic_library_support.h" + +base::FilePath GetMojoCoreLibraryPath() { +#if defined(OS_WIN) + const char kLibraryFilename[] = "mojo_core.dll"; +#else + const char kLibraryFilename[] = "libmojo_core.so"; +#endif + base::FilePath executable_dir = + base::CommandLine::ForCurrentProcess()->GetProgram().DirName(); + if (executable_dir.IsAbsolute()) + return executable_dir.AppendASCII(kLibraryFilename); + + base::FilePath current_directory; + CHECK(base::GetCurrentDirectory(¤t_directory)); + return current_directory.Append(executable_dir).AppendASCII(kLibraryFilename); +} int main(int argc, char** argv) { base::TestSuite test_suite(argc, argv); - MojoInitializeOptions options; - options.struct_size = sizeof(options); - options.flags = MOJO_INITIALIZE_FLAG_NONE; - options.mojo_core_path = NULL; - options.mojo_core_path_length = 0; - if (!base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kTestChildProcess)) { - options.flags = MOJO_INITIALIZE_FLAG_AS_BROKER; + MojoInitializeFlags flags = MOJO_INITIALIZE_FLAG_NONE; + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kTestChildProcess)) + flags |= MOJO_INITIALIZE_FLAG_AS_BROKER; + + base::Optional<base::FilePath> library_path; + if (command_line.HasSwitch(switches::kMojoUseExplicitLibraryPath)) + library_path = GetMojoCoreLibraryPath(); + + if (command_line.HasSwitch(switches::kMojoLoadBeforeInit)) { + CHECK_EQ(MOJO_RESULT_OK, mojo::LoadCoreLibrary(library_path)); + CHECK_EQ(MOJO_RESULT_OK, mojo::InitializeCoreLibrary(flags)); + } else { + CHECK_EQ(MOJO_RESULT_OK, + mojo::LoadAndInitializeCoreLibrary(library_path, flags)); } - CHECK_EQ(MOJO_RESULT_OK, MojoInitialize(&options)); int result = base::LaunchUnitTests( argc, argv, base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); diff --git a/chromium/mojo/core/shared_buffer_dispatcher_unittest.cc b/chromium/mojo/core/shared_buffer_dispatcher_unittest.cc index b245b78330d..fe206b63146 100644 --- a/chromium/mojo/core/shared_buffer_dispatcher_unittest.cc +++ b/chromium/mojo/core/shared_buffer_dispatcher_unittest.cc @@ -45,8 +45,8 @@ void RevalidateCreateOptions( class SharedBufferDispatcherTest : public testing::Test { public: - SharedBufferDispatcherTest() {} - ~SharedBufferDispatcherTest() override {} + SharedBufferDispatcherTest() = default; + ~SharedBufferDispatcherTest() override = default; private: DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcherTest); diff --git a/chromium/mojo/core/test/BUILD.gn b/chromium/mojo/core/test/BUILD.gn index 1abadfc503d..9429c618530 100644 --- a/chromium/mojo/core/test/BUILD.gn +++ b/chromium/mojo/core/test/BUILD.gn @@ -7,6 +7,8 @@ import("//third_party/protobuf/proto_library.gni") static_library("test_support") { testonly = true sources = [ + "mock_node_channel_delegate.cc", + "mock_node_channel_delegate.h", "mojo_test_base.cc", "mojo_test_base.h", "test_utils.h", @@ -27,8 +29,10 @@ static_library("test_support") { public_deps = [ "//base", "//base/test:test_support", + "//mojo/core:embedder_internal", "//mojo/core/embedder", "//mojo/public/cpp/system", + "//testing/gmock", "//testing/gtest", ] } diff --git a/chromium/mojo/core/test_utils.cc b/chromium/mojo/core/test_utils.cc index eb025b4a3c4..acbfca018af 100644 --- a/chromium/mojo/core/test_utils.cc +++ b/chromium/mojo/core/test_utils.cc @@ -54,9 +54,9 @@ void Sleep(MojoDeadline deadline) { base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline))); } -Stopwatch::Stopwatch() {} +Stopwatch::Stopwatch() = default; -Stopwatch::~Stopwatch() {} +Stopwatch::~Stopwatch() = default; void Stopwatch::Start() { start_time_ = base::TimeTicks::Now(); diff --git a/chromium/mojo/core/trap_unittest.cc b/chromium/mojo/core/trap_unittest.cc index f543b2f0ec3..8ff1f74a6ad 100644 --- a/chromium/mojo/core/trap_unittest.cc +++ b/chromium/mojo/core/trap_unittest.cc @@ -34,8 +34,8 @@ class TriggerHelper { public: using ContextCallback = base::RepeatingCallback<void(const MojoTrapEvent&)>; - TriggerHelper() {} - ~TriggerHelper() {} + TriggerHelper() = default; + ~TriggerHelper() = default; MojoResult CreateTrap(MojoHandle* handle) { return MojoCreateTrap(&Notify, nullptr, handle); @@ -65,7 +65,7 @@ class TriggerHelper { explicit NotificationContext(const ContextCallback& callback) : callback_(callback) {} - ~NotificationContext() {} + ~NotificationContext() = default; void SetCancelCallback(base::OnceClosure cancel_callback) { cancel_callback_ = std::move(cancel_callback); @@ -97,7 +97,7 @@ class ThreadedRunner : public base::SimpleThread { public: explicit ThreadedRunner(base::OnceClosure callback) : SimpleThread("ThreadedRunner"), callback_(std::move(callback)) {} - ~ThreadedRunner() override {} + ~ThreadedRunner() override = default; void Run() override { std::move(callback_).Run(); } diff --git a/chromium/mojo/core/watch.cc b/chromium/mojo/core/watch.cc index 996f0a7ca24..0d0429da097 100644 --- a/chromium/mojo/core/watch.cc +++ b/chromium/mojo/core/watch.cc @@ -78,7 +78,7 @@ void Watch::InvokeCallback(MojoResult result, watcher_->InvokeWatchCallback(context_, result, state, flags); } -Watch::~Watch() {} +Watch::~Watch() = default; #if DCHECK_IS_ON() void Watch::AssertWatcherLockAcquired() const { diff --git a/chromium/mojo/docs/mojolpm.md b/chromium/mojo/docs/mojolpm.md new file mode 100644 index 00000000000..a3b1c8e254b --- /dev/null +++ b/chromium/mojo/docs/mojolpm.md @@ -0,0 +1,471 @@ +# Getting started with MojoLPM + +*** note +**Note:** Using MojoLPM to fuzz your Mojo interfaces is intended to be simple, +but there are edge-cases that may require a very detailed understanding of the +Mojo implementation to fix. If you run into problems that you can't understand +readily, send an email to [markbrand@google.com] and cc `fuzzing@chromium.org` +and we'll try and help. + +**Prerequisites:** Knowledge of [libfuzzer] and basic understanding +of [Protocol Buffers] and [libprotobuf-mutator]. Basic understanding of +[testing in Chromium]. +*** + +This document will walk you through: +* An overview of MojoLPM and what it's used for. +* Adding a fuzzer to an existing Mojo interface using MojoLPM. + +[TOC] + +## Overview of MojoLPM + +MojoLPM is a toolchain for automatically generating structure-aware fuzzers for +Mojo interfaces using libprotobuf-mutator as the fuzzing engine. + +This tool works by using the existing "grammar" for the interface provided by +the .mojom files, and translating that into a Protocol Buffer format that can be +fuzzed by libprotobuf-mutator. These protocol buffers are then interpreted by +a generated runtime as a sequence of mojo method calls on the targeted +interface. + +The intention is that using these should be as simple as plugging the generated +code in to the existing unittests for those interfaces - so if you've already +implemented the necessary mocks to unittest your code, the majority of the work +needed to get quite effective fuzzing of your interfaces is already complete! + +## Choose the Mojo interface(s) to fuzz + +If you're a developer looking to add fuzzing support for an interface that +you're developing, then this should be very easy for you! + +If not, then a good starting point is to search for [interfaces] in codesearch. +The most interesting interfaces from a security perspective are those which are +implemented in the browser process and exposed to the renderer process, but +there isn't a very simple way to enumerate these, so you may need to look +through some of the source code to find an interesting one. + +For the rest of this guide, we'll write a new fuzzer for +`blink.mojom.CodeCacheHost`, which is defined in +`third_party/blink/public/mojom/loader/code_cache.mojom`. + +We then need to find the relevant GN build target for this mojo interface so +that we know how to refer to it later - in this case that is +`//third_party/blink/public/mojom:mojom_platform`. + +## Find the implementations of the interfaces + +If you are developing these interfaces, then you already know where to find the +implementations. + +Otherwise a good starting point is to search for references to +"public blink::mojom::CodeCacheHost". Usually there is only a single +implementation of a given Mojo interface (there are a few exceptions where the +interface abstracts platform specific details, but this is less common). This +leads us to `content/browser/renderer_host/code_cache_host_impl.h` and +`CodeCacheHostImpl`. + +## Find the unittest for the implementation + +Unfortunately, it doesn't look like `CodeCacheHostImpl` has a unittest, so we'll +have to go through the process of understanding how to create a valid instance +ourselves in order to fuzz this interface. + +Since this interface runs in the Browser process, and is part of `/content`, +we're going to create our new fuzzer in `/content/test/fuzzer`. + +## Add our testcase proto + +First we'll add a proto source file, `code_cache_host_mojolpm_fuzzer.proto`, +which is going to define the structure of our testcases. This is basically +boilerplate, but it allows creating fuzzers which interact with multiple Mojo +interfaces to uncover more complex issues. For our case, this will be a simple +file: + +``` +syntax = "proto2"; + +package content.fuzzing.code_cache_host.proto; + +import "third_party/blink/public/mojom/loader/code_cache.mojom.mojolpm.proto"; + +message NewCodeCacheHost { + required uint32 id = 1; +} + +message RunUntilIdle { + enum ThreadId { + IO = 0; + UI = 1; + } + + required ThreadId id = 1; +} + +message Action { + oneof action { + NewCodeCacheHost new_code_cache_host = 1; + RunUntilIdle run_until_idle = 2; + mojolpm.blink.mojom.CodeCacheHost.RemoteMethodCall code_cache_host_call = 3; + } +} + +message Sequence { + repeated uint32 action_indexes = 1 [packed=true]; +} + +message Testcase { + repeated Action actions = 1; + repeated Sequence sequences = 2; + repeated uint32 sequence_indexes = 3 [packed=true]; +} +``` + +This specifies all of the actions that the fuzzer will be able to take - it +will be able to create a new `CodeCacheHost` instance, perform sequences of +interface calls on those instances, and wait for various threads to be idle. + +In order to build this proto file, we'll need to copy it into the out/ directory +so that it can reference the proto files generated by MojoLPM - this will be +handled for us by the `mojolpm_fuzzer_test` build rule. + +## Add our fuzzer source + +Now we're ready to create the fuzzer c++ source file, +`code_cache_host_mojolpm_fuzzer.cc` and the fuzzer build target. This +target is going to depend on both our proto file, and on the c++ source file. +Most of the necessary dependencies will be handled for us, but we do still need +to add some directly. + +Note especially the dependency on `mojom_platform_mojolpm` in blink, this is an +autogenerated target where the target containing the generated fuzzer protocol +buffer descriptions will be the name of the mojom target with `_mojolpm` +appended. + +``` +mojolpm_fuzzer_test("code_cache_host_mojolpm_fuzzer") { + sources = [ + "code_cache_host_mojolpm_fuzzer.cc" + ] + + proto_source = "code_cache_host_mojolpm_fuzzer.proto" + + deps = [ + "//base/test:test_support", + "//content/browser:for_content_tests", + "//content/public/browser:browser_sources", + "//content/test:test_support", + "//services/network:test_support", + "//storage/browser:test_support", + ] + + proto_deps = [ + "//third_party/blink/public/mojom:mojom_platform_mojolpm", + ] +} +``` + +Now, the minimal source code to do load our testcases: + +```c++ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> +#include <utility> + +#include "code_cache_host_mojolpm_fuzzer.pb.h" +#include "mojo/core/embedder/embedder.h" +#include "third_party/blink/public/mojom/loader/code_cache.mojom-mojolpm.h" +#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h" + +DEFINE_BINARY_PROTO_FUZZER( + const content::fuzzing::code_cache_host::proto::Testcase& testcase) { +} +``` + +You should now be able to build and run this fuzzer (it, of course, won't do +very much) to check that everything is lined up right so far. + +## Handle global process setup + +Now we need to add some basic setup code so that our process has something that +mostly resembles a normal Browser process; if you look in the file this is +`CodeCacheHostFuzzerEnvironment`, which adds a global environment instance that +will handle setting up this basic environment, which will be reused for all of +our testcases, since starting threads is expensive and slow. + +## Handle per-testcase setup + +We next need to handle the necessary setup to instantiate `CodeCacheHostImpl`, +so that we can actually run the testcases. At this point, we realise that it's +likely that we want to be able to have multiple `CodeCacheHostImpl`'s with +different render_process_ids and different backing origins, so we need to modify +our proto file to reflect this: + +``` +message NewCodeCacheHost { + enum OriginId { + ORIGIN_A = 0; + ORIGIN_B = 1; + ORIGIN_OPAQUE = 2; + ORIGIN_EMPTY = 3; + } + + required uint32 id = 1; + required uint32 render_process_id = 2; + required OriginId origin_id = 3; +} +``` + +Note that we're using an enum to represent the origin, rather than a string; +it's unlikely that the true value of the origin is going to be important, so +we've instead chosen a few select values based on the cases mentioned in the +source. + +The first thing that we need to do is set-up the basic Browser process +environment; this is what `ContentFuzzerEnvironment` is doing - this has a basic +setup suitable for fuzzing interfaces in `/content`. A few things to be careful +of are that we need to make sure that `mojo::core::Init()` is called (only once) +and we probably want as much freedom as possible in terms of scheduling, so we +want to use slightly different threading options than the average unittest. This +is a singleton type that will live for the entire duration of the fuzzer process +so we don't want to be holding any testcase-specific data here. + +The next thing that we need to do is to figure out the basic setup needed to +instantiate the interface we're interested in. Looking at the constructor for +`CodeCacheHostImpl` we need three things; a valid `render_process_id`, an +instance of `CacheStorageContextImpl` and an instance of +`GeneratedCodeCacheContext`. `CodeCacheHostFuzzerContext` is our container for +these per-testcase instances; and will handle creating and binding the instances +of the Mojo interfaces that we're going to fuzz. The most important thing to be +careful of here is that everything happens on the correct thread/sequence. Many +Browser-process objects have specific expectations, and will end up with very +different behaviour if they are created or used from the wrong context. + +## Integrate with the generated MojoLPM fuzzer code + +Finally, we need to do a little bit more plumbing, to rig up this infrastructure +that we've built together with the autogenerated code that MojoLPM gives us to +interpret and run our testcases. This is the `CodeCacheHostTestcase`, and the +part where the magic happens is here: + +```c++ +void CodeCacheHostTestcase::NextAction() { + if (next_idx_ < testcase_.sequence_indexes_size()) { + auto sequence_idx = testcase_.sequence_indexes(next_idx_++); + const auto& sequence = + testcase_.sequences(sequence_idx % testcase_.sequences_size()); + for (auto action_idx : sequence.action_indexes()) { + if (!testcase_.actions_size() || ++action_count_ > MAX_ACTION_COUNT) { + return; + } + const auto& action = + testcase_.actions(action_idx % testcase_.actions_size()); + switch (action.action_case()) { + case content::fuzzing::code_cache_host::proto::Action::kNewCodeCacheHost: { + cch_context_.AddCodeCacheHost( + action.new_code_cache_host().id(), + action.new_code_cache_host().render_process_id(), + action.new_code_cache_host().origin_id()); + } break; + + case content::fuzzing::code_cache_host::proto::Action::kRunUntilIdle: { + if (action.run_until_idle().id()) { + content::RunUIThreadUntilIdle(); + } else { + content::RunIOThreadUntilIdle(); + } + } break; + + case content::fuzzing::code_cache_host::proto::Action::kCodeCacheHostCall: { + mojolpm::HandleRemoteMethodCall(action.code_cache_host_call()); + } break; + + case content::fuzzing::code_cache_host::proto::Action::ACTION_NOT_SET: + break; + } + } + } +} +``` + +The key line here in integration with MojoLPM is the last case, +`kCodeCacheHostCall`, where we're asking MojoLPM to treat this incoming proto +entry as a call to a method on the `CodeCacheHost` interface. + +There's just a little bit more boilerplate in the bottom of the file to tidy up +concurrency loose ends, making sure that the fuzzer components are all running +on the correct threads; those are more-or-less common to any fuzzer using +MojoLPM. + +## Test it! + +Make a corpus directory and fire up your shiny new fuzzer! + +``` + ~/chromium/src% out/Default/code_cache_host_mojolpm_fuzzer /dev/shm/corpus +INFO: Seed: 3273881842 +INFO: Loaded 1 modules (1121912 inline 8-bit counters): 1121912 [0x559151a1aea8, 0x559151b2cd20), +INFO: Loaded 1 PC tables (1121912 PCs): 1121912 [0x559151b2cd20,0x559152c4b4a0), +INFO: 146 files found in /dev/shm/corpus +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes +INFO: seed corpus: files: 146 min: 2b max: 268b total: 8548b rss: 88Mb +#147 INITED cov: 4633 ft: 10500 corp: 138/8041b exec/s: 0 rss: 91Mb +#152 NEW cov: 4633 ft: 10501 corp: 139/8139b lim: 4096 exec/s: 0 rss: 91Mb L: 98/268 MS: 8 Custom-ChangeByte-Custom-EraseBytes-Custom-ShuffleBytes-Custom-Custom- +#154 NEW cov: 4634 ft: 10510 corp: 140/8262b lim: 4096 exec/s: 0 rss: 91Mb L: 123/268 MS: 3 CustomCrossOver-ChangeBit-Custom- +#157 NEW cov: 4634 ft: 10512 corp: 141/8384b lim: 4096 exec/s: 0 rss: 91Mb L: 122/268 MS: 3 CustomCrossOver-Custom-CustomCrossOver- +#158 NEW cov: 4634 ft: 10514 corp: 142/8498b lim: 4096 exec/s: 0 rss: 91Mb L: 114/268 MS: 1 CustomCrossOver- +#159 NEW cov: 4634 ft: 10517 corp: 143/8601b lim: 4096 exec/s: 0 rss: 91Mb L: 103/268 MS: 1 Custom- +#160 NEW cov: 4634 ft: 10526 corp: 144/8633b lim: 4096 exec/s: 0 rss: 91Mb L: 32/268 MS: 1 Custom- +#164 NEW cov: 4634 ft: 10528 corp: 145/8851b lim: 4096 exec/s: 0 rss: 91Mb L: 218/268 MS: 4 CustomCrossOver-Custom-CustomCrossOver-Custom- +``` + +## Wait for it... + +Let the fuzzer run for a while, and keep periodically checking in in case it's +fallen over. It's likely you'll have made a few mistakes somewhere along the way +but hopefully soon you'll have the fuzzer running 'clean' for a few hours. + +If your coverage isn't going up at all, then you've probably made a mistake and +it likely isn't managing to actually interact with the interface you're trying +to fuzz - try using the code coverage output from the next step to debug what's +going wrong. + +## (Optional) Run coverage + +In many cases it's useful to check the code coverage to see if we can benefit +from adding some manual testcases to get deeper coverage. For this example I +used the following command: + +``` +python tools/code_coverage/coverage.py code_cache_host_mojolpm_fuzzer -b out/Coverage -o ManualReport -c "out/Coverage/code_cache_host_mojolpm_fuzzer -ignore_timeouts=1 -timeout=4 -runs=0 /dev/shm/corpus" -f content +``` + +With the CodeCacheHost, looking at the coverage after a few hours we could see +that there's definitely some room for improvement: + +```c++ +/* 55 */ base::Optional<GURL> GetSecondaryKeyForCodeCache(const GURL& resource_url, +/* 56 53.6k */ int render_process_id) { +/* 57 53.6k */ if (!resource_url.is_valid() || !resource_url.SchemeIsHTTPOrHTTPS()) +/* 58 53.6k */ return base::nullopt; +/* 59 0 */ +/* 60 0 */ GURL origin_lock = +/* 61 0 */ ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock( +/* 62 0 */ render_process_id); +``` + +## (Optional) Improve corpus manually + +It's fairly easy to improve the corpus manually, since our corpus files are just +protobuf files that describe the sequence of interface calls to make. + +There are a couple of approaches that we can take here - we'll try building a +small manual seed corpus that we'll use to kick-start our fuzzer. Since it's +easier to edit text protos, MojoLPM can automatically convert our seed corpus +from text protos to binary protos during the build, making this slightly less +painful for us, and letting us store our corpus in-tree in a readable format. + +So, we'll create a new folder to hold this seed corpus, and craft our first +file: + +``` +actions { + new_code_cache_host { + id: 1 + render_process_id: 0 + origin_id: ORIGIN_A + } +} +actions { + code_cache_host_call { + remote { + id: 1 + } + m_did_generate_cacheable_metadata { + m_cache_type: CodeCacheType_kJavascript + m_url { + new { + id: 1 + m_url: "http://aaa.com/test" + } + } + m_data { + new { + id: 1 + m_bytes { + } + } + m_expected_response_time { + } + } + } +} +sequences { + action_indexes: 0 + action_indexes: 1 +} +sequence_indexes: 0 +``` + +We can then add some new entries to our build target to have the corpus +converted to binary proto directly during build. + +``` + testcase_proto_kind = "content.fuzzing.code_cache_host.proto.Testcase" + + seed_corpus_sources = [ + "code_cache_host_mojolpm_fuzzer_corpus/did_generate_cacheable_metadata.textproto", + ] +``` + +If we now run a new coverage report using this single file seed corpus: +(note that the binary corpus files will be output in your output directory, in +this case code_cache_host_mojolpm_fuzzer_seed_corpus.zip): + +``` +autoninja -C out/Coverage chrome +rm -rf /tmp/corpus; mkdir /tmp/corpus; unzip out/Coverage/code_cache_host_mojolpm_fuzzer_seed_corpus.zip -d /tmp/corpus +python tools/code_coverage/coverage.py code_cache_host_mojolpm_fuzzer -b out/Coverage -o ManualReport -c "out/Coverage/code_cache_host_mojolpm_fuzzer -ignore_timeouts=1 -timeout=4 -runs=0 /tmp/corpus" -f content +``` + +We can see that we're now getting some more coverage: + +```c++ +/* 118 */ void CodeCacheHostImpl::DidGenerateCacheableMetadata( +/* 119 */ blink::mojom::CodeCacheType cache_type, +/* 120 */ const GURL& url, +/* 121 */ base::Time expected_response_time, +/* 122 2 */ mojo_base::BigBuffer data) { +/* 123 2 */ if (!url.SchemeIsHTTPOrHTTPS()) { +/* 124 0 */ mojo::ReportBadMessage("Invalid URL scheme for code cache."); +/* 125 0 */ return; +/* 126 0 */ } +/* 127 2 */ +/* 128 2 */ DCHECK_CURRENTLY_ON(BrowserThread::UI); +/* 129 2 */ +/* 130 2 */ GeneratedCodeCache* code_cache = GetCodeCache(cache_type); +/* 131 2 */ if (!code_cache) +/* 132 0 */ return; +/* 133 2 */ +/* 134 2 */ base::Optional<GURL> origin_lock = +/* 135 2 */ GetSecondaryKeyForCodeCache(url, render_process_id_); +/* 136 2 */ if (!origin_lock) +/* 137 0 */ return; +/* 138 2 */ +/* 139 2 */ code_cache->WriteEntry(url, *origin_lock, expected_response_time, +/* 140 2 */ std::move(data)); +/* 141 2 */ } +``` + +Much better! + +[markbrand@google.com]: mailto:markbrand@google.com?subject=[MojoLPM%20Help]:%20&cc=fuzzing@chromium.org +[libfuzzer]: https://source.chromium.org/chromium/chromium/src/+/master:testing/libfuzzer/getting_started.md +[Protocol Buffers]: https://developers.google.com/protocol-buffers/docs/cpptutorial +[libprotobuf-mutator]: https://source.chromium.org/chromium/chromium/src/+/master:testing/libfuzzer/libprotobuf-mutator.md +[testing in Chromium]: https://source.chromium.org/chromium/chromium/src/+/master:docs/testing/testing_in_chromium.md +[interfaces]: https://source.chromium.org/search?q=interface%5Cs%2B%5Cw%2B%5Cs%2B%7B%20f:%5C.mojom$%20-f:test + diff --git a/chromium/mojo/public/c/system/functions.h b/chromium/mojo/public/c/system/functions.h index 6f47028f480..257b6012c74 100644 --- a/chromium/mojo/public/c/system/functions.h +++ b/chromium/mojo/public/c/system/functions.h @@ -12,6 +12,7 @@ #include <stddef.h> #include <stdint.h> +#include "mojo/public/c/system/invitation.h" #include "mojo/public/c/system/system_export.h" #include "mojo/public/c/system/types.h" @@ -26,12 +27,23 @@ extern "C" { // // |options| may be null. // +// If the |MOJO_INITIALIZE_FLAG_LOAD_ONLY| flag is given in |options|, this only +// partially initializes the library. Other Mojo APIs will remain unavailable +// until the library is fully initialized by a subsequent call to +// |MojoInitialize()| WITHOUT the flag. See documentation on +// |MOJO_INITIALIZE_FLAG_LOAD_ONLY| in types.h for details. +// // Returns: // |MOJO_RESULT_OK| if Mojo initialization was successful. +// |MOJO_RESULT_NOT_FOUND| if the Mojo Core library could not be loaded or +// appears to be malformed. +// |MOJO_RESULT_FAILED_PRECONDITION| if the Mojo Core library AND full IPC +// support has already been initialized by some prior call(s) to +// |MojoInitialize()|. +// |MOJO_RESULT_ALREADY_EXISTS| if |MOJO_INITIALIZE_FLAG_LOAD_ONLY| was +// specified for this call but the library has already been successfully +// loaded and partially initialized by a previous call with the same flag. // |MOJO_RESULT_INVALID_ARGUMENT| if |options| was non-null and invalid. -// |MOJO_RESULT_FAILED_PRECONDITION| if |MojoInitialize()| was already called -// once or if the application already explicitly initialized a Mojo Core -// environment as an embedder. MOJO_SYSTEM_EXPORT MojoResult MojoInitialize(const struct MojoInitializeOptions* options); diff --git a/chromium/mojo/public/c/system/invitation.h b/chromium/mojo/public/c/system/invitation.h index e63001eb0be..f95bcc4e096 100644 --- a/chromium/mojo/public/c/system/invitation.h +++ b/chromium/mojo/public/c/system/invitation.h @@ -251,6 +251,25 @@ struct MOJO_ALIGNAS(8) MojoAcceptInvitationOptions { MOJO_STATIC_ASSERT(sizeof(struct MojoAcceptInvitationOptions) == 8, "MojoAcceptInvitationOptions has wrong size"); +// Flags passed to |MojoSetDefaultProcessErrorHandler()| via +// |MojoSetDefaultProcessErrorHandlerOptions|. +typedef uint32_t MojoSetDefaultProcessErrorHandlerFlags; + +// No flags. Default behavior. +#define MOJO_SET_DEFAULT_PROCESS_ERROR_HANDLER_FLAG_NONE \ + ((MojoSetDefaultProcessErrorHandlerFlags)0) + +// Options passed to |MojoSetDefaultProcessErrorHandler()|. +struct MOJO_ALIGNAS(8) MojoSetDefaultProcessErrorHandlerOptions { + // The size of this structure, used for versioning. + uint32_t struct_size; + + // See |MojoSetDefaultProcessErrorHandlerFlags|. + MojoSetDefaultProcessErrorHandlerFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(struct MojoSetDefaultProcessErrorHandlerOptions) == 8, + "MojoSetDefaultProcessErrorHandlerOptions has wrong size"); + #ifdef __cplusplus extern "C" { #endif @@ -266,6 +285,12 @@ typedef void (*MojoProcessErrorHandler)( uintptr_t context, const struct MojoProcessErrorDetails* details); +// Similar to above, but registered globally via +// |MojoSetDefaultProcessErrorHandler()| and invoked only for communication +// errors regarding processes NOT invited by the calling process. +typedef void (*MojoDefaultProcessErrorHandler)( + const struct MojoProcessErrorDetails* details); + // Creates a new invitation to be sent to another process. // // An invitation is used to invite another process to join this process's @@ -474,6 +499,31 @@ MOJO_SYSTEM_EXPORT MojoResult MojoAcceptInvitation( const struct MojoAcceptInvitationOptions* options, MojoHandle* invitation_handle); +// Registers a process-wide handler to be invoked when an error is raised on a +// connection to a peer process. Such errors can be raised if the peer process +// sends malformed data to this process. +// +// Note that this handler is only invoked for connections to processes NOT +// explicitly invited by this process. To handle errors concerning processes +// invited by this process, see the MojoProcessErrorHandler argument to +// |MojoSendInvitation()|. +// +// The general use case for this API is to be able to log or report instances of +// bad IPCs received by a client process which no real ability or authority to +// identify the source. +// +// Returns: +// |MOJO_RESULT_OK| if |handler| is successfully registered as the global +// default process error handler within the calling process. If |handler| +// is null, any registered default process error handler is removed. +// |MOJO_RESULT_ALREADY_EXISTS| if |handler| is non-null and there is already +// a registered error handler. Callers wishing to replace an existing +// handler must first call |MojoSetDefaultProcessErrorHandler()| with null +// in order to do so. +MOJO_SYSTEM_EXPORT MojoResult MojoSetDefaultProcessErrorHandler( + MojoDefaultProcessErrorHandler handler, + const struct MojoSetDefaultProcessErrorHandlerOptions* options); + #ifdef __cplusplus } // extern "C" #endif diff --git a/chromium/mojo/public/c/system/macros.h b/chromium/mojo/public/c/system/macros.h index 1023e1c111a..ebacb7482ac 100644 --- a/chromium/mojo/public/c/system/macros.h +++ b/chromium/mojo/public/c/system/macros.h @@ -6,6 +6,7 @@ #define MOJO_PUBLIC_C_SYSTEM_MACROS_H_ #include <stddef.h> +#include <stdint.h> #if !defined(__cplusplus) #include <assert.h> // Defines static_assert() in C11. @@ -29,6 +30,18 @@ // Like the C++11 |alignof| operator. #define MOJO_ALIGNOF(type) alignof(type) +// Provides a convenient test for the presence of a field in a user-provided +// structure from a potentially older version of the ABI. Presence is determined +// by comparing the struct's provided |struct_size| value against the known +// offset and size of the field in this version of the ABI. Because fields are +// never reordered or removed, this is a sufficient test for the field's +// presence within whatever version of the ABI the client is programmed against. +#define MOJO_IS_STRUCT_FIELD_PRESENT(struct_pointer, field) \ + ((size_t)(uintptr_t)((const char*)&(struct_pointer)->field - \ + (const char*)(struct_pointer)) + \ + sizeof((struct_pointer)->field) <= \ + (struct_pointer)->struct_size) + // Specify the alignment of a |struct|, etc. // Use like: // struct MOJO_ALIGNAS(8) Foo { ... }; diff --git a/chromium/mojo/public/c/system/thunks.cc b/chromium/mojo/public/c/system/thunks.cc index 1bf1a2673d7..6a67df5e710 100644 --- a/chromium/mojo/public/c/system/thunks.cc +++ b/chromium/mojo/public/c/system/thunks.cc @@ -8,12 +8,17 @@ #include <cstdint> #include <cstring> +#include "base/check_op.h" #include "base/compiler_specific.h" +#include "base/files/file_path.h" #include "base/logging.h" #include "base/macros.h" #include "base/no_destructor.h" +#include "base/notreached.h" +#include "base/strings/string_piece.h" #include "build/build_config.h" #include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/macros.h" #if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN) #include "base/environment.h" @@ -57,17 +62,17 @@ namespace mojo { // enabled. class CoreLibraryInitializer { public: - CoreLibraryInitializer(const MojoInitializeOptions* options) { + CoreLibraryInitializer() = default; + CoreLibraryInitializer(const CoreLibraryInitializer&) = delete; + CoreLibraryInitializer& operator=(const CoreLibraryInitializer&) = delete; + ~CoreLibraryInitializer() = default; + + MojoResult LoadLibrary(base::FilePath library_path) { #if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN) - bool application_provided_path = false; - base::Optional<base::FilePath> library_path; - if (options && options->struct_size >= sizeof(*options) && - options->mojo_core_path) { - base::StringPiece utf8_path(options->mojo_core_path, - options->mojo_core_path_length); - library_path.emplace(base::FilePath::FromUTF8Unsafe(utf8_path)); - application_provided_path = true; - } else { + if (library_ && library_->is_valid()) + return MOJO_RESULT_OK; + + if (library_path.empty()) { auto environment = base::Environment::Create(); std::string library_path_value; const char kLibraryPathEnvironmentVar[] = "MOJO_CORE_LIBRARY_PATH"; @@ -75,7 +80,7 @@ class CoreLibraryInitializer { library_path = base::FilePath::FromUTF8Unsafe(library_path_value); } - if (!library_path) { + if (library_path.empty()) { // Default to looking for the library in the current working directory. #if defined(OS_CHROMEOS) || defined(OS_LINUX) const base::FilePath::CharType kDefaultLibraryPathValue[] = @@ -84,7 +89,7 @@ class CoreLibraryInitializer { const base::FilePath::CharType kDefaultLibraryPathValue[] = FILE_PATH_LITERAL("mojo_core.dll"); #endif - library_path.emplace(kDefaultLibraryPathValue); + library_path = base::FilePath(kDefaultLibraryPathValue); } // NOTE: |prefer_own_symbols| on POSIX implies that the library is loaded @@ -100,47 +105,36 @@ class CoreLibraryInitializer { // allocator shims, so it's unnecessary there. library_options.prefer_own_symbols = true; #endif - library_.emplace(base::LoadNativeLibraryWithOptions( - *library_path, library_options, nullptr)); - if (!application_provided_path) { - CHECK(library_->is_valid()) - << "Unable to load the mojo_core library. Make sure the library is " - << "in the working directory or is correctly pointed to by the " - << "MOJO_CORE_LIBRARY_PATH environment variable."; - } else { - CHECK(library_->is_valid()) - << "Unable to locate mojo_core library. This application expects to " - << "find it at " << library_path->value(); - } + base::ScopedNativeLibrary library(base::LoadNativeLibraryWithOptions( + library_path, library_options, nullptr)); + if (!library.is_valid()) + return MOJO_RESULT_NOT_FOUND; const char kGetThunksFunctionName[] = "MojoGetSystemThunks"; MojoGetSystemThunksFunction g_get_thunks = reinterpret_cast<MojoGetSystemThunksFunction>( - library_->GetFunctionPointer(kGetThunksFunctionName)); - CHECK(g_get_thunks) << "Invalid mojo_core library: " - << library_path->value(); + library.GetFunctionPointer(kGetThunksFunctionName)); + if (!g_get_thunks) + return MOJO_RESULT_NOT_FOUND; DCHECK_EQ(g_thunks.size, 0u); g_thunks.size = sizeof(g_thunks); g_get_thunks(&g_thunks); + if (g_thunks.size == 0) + return MOJO_RESULT_NOT_FOUND; - CHECK_GT(g_thunks.size, 0u) - << "Invalid mojo_core library: " << library_path->value(); -#else // defined(OS_CHROMEOS) || defined(OS_LINUX) - NOTREACHED() - << "Dynamic mojo_core loading is not supported on this platform."; -#endif // defined(OS_CHROMEOS) || defined(OS_LINUX) + library_ = std::move(library); + return MOJO_RESULT_OK; +#else // defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN) + return MOJO_RESULT_UNIMPLEMENTED; +#endif // defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN) } - ~CoreLibraryInitializer() = default; - private: #if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN) base::Optional<base::ScopedNativeLibrary> library_; #endif - - DISALLOW_COPY_AND_ASSIGN(CoreLibraryInitializer); }; } // namespace mojo @@ -148,10 +142,22 @@ class CoreLibraryInitializer { extern "C" { MojoResult MojoInitialize(const struct MojoInitializeOptions* options) { - static base::NoDestructor<mojo::CoreLibraryInitializer> initializer(options); - ALLOW_UNUSED_LOCAL(initializer); - DCHECK(g_thunks.Initialize); + static base::NoDestructor<mojo::CoreLibraryInitializer> initializer; + + base::StringPiece library_path_utf8; + if (options) { + if (!MOJO_IS_STRUCT_FIELD_PRESENT(options, mojo_core_path_length)) + return MOJO_RESULT_INVALID_ARGUMENT; + library_path_utf8 = base::StringPiece(options->mojo_core_path, + options->mojo_core_path_length); + } + + MojoResult load_result = initializer->LoadLibrary( + base::FilePath::FromUTF8Unsafe(library_path_utf8)); + if (load_result != MOJO_RESULT_OK) + return load_result; + DCHECK(g_thunks.Initialize); return INVOKE_THUNK(Initialize, options); } @@ -476,6 +482,12 @@ MojoResult MojoShutdown(const MojoShutdownOptions* options) { return INVOKE_THUNK(Shutdown, options); } +MojoResult MojoSetDefaultProcessErrorHandler( + MojoDefaultProcessErrorHandler handler, + const struct MojoSetDefaultProcessErrorHandlerOptions* options) { + return INVOKE_THUNK(SetDefaultProcessErrorHandler, handler, options); +} + } // extern "C" void MojoEmbedderSetSystemThunks(const MojoSystemThunks* thunks) { diff --git a/chromium/mojo/public/c/system/thunks.h b/chromium/mojo/public/c/system/thunks.h index f3f9742a8bf..4b41b939e24 100644 --- a/chromium/mojo/public/c/system/thunks.h +++ b/chromium/mojo/public/c/system/thunks.h @@ -228,6 +228,11 @@ struct MojoSystemThunks { // Core ABI version 2 additions begin here. MojoResult (*Shutdown)(const struct MojoShutdownOptions* options); + + // Core ABI version 3 additions begin here. + MojoResult (*SetDefaultProcessErrorHandler)( + MojoDefaultProcessErrorHandler handler, + const struct MojoSetDefaultProcessErrorHandlerOptions* options); }; #pragma pack(pop) diff --git a/chromium/mojo/public/c/system/types.h b/chromium/mojo/public/c/system/types.h index cee529f1dac..bb6504343a7 100644 --- a/chromium/mojo/public/c/system/types.h +++ b/chromium/mojo/public/c/system/types.h @@ -147,8 +147,38 @@ typedef uint32_t MojoInitializeFlags; // process. That process is always the first member of the network and it should // set this flag during initialization. Attempts to invite a broker process into // an existing network will always fail. +// +// This flag is ignored when |MOJO_INITIALIZE_FLAG_LOAD_ONLY| is set. #define MOJO_INITIALIZE_FLAG_AS_BROKER ((MojoInitializeFlags)1) +// Even if not initialized as the broker process, the calling process will be +// configured for direct shared memory allocation. This can be used for +// non-broker processes which are still sufficiently privileged to allocate +// their own shared memory. +// +// This flag is ignored when |MOJO_INITIALIZE_FLAG_LOAD_ONLY| is set. +#define MOJO_INITIALIZE_FLAG_FORCE_DIRECT_SHARED_MEMORY_ALLOCATION \ + ((MojoInitializeFlags)2) + +// This call to |MojoInitialize()| should NOT fully initialize Mojo's internal +// IPC support engine. Initialization is essentially a two-phase operation: +// first the library is loaded and its global state is initialized, and then +// full IPC support is initialized. The latter phase may spawn a background +// thread, thus making it hostile to certain scenarios (e.g. prior to a fork() +// on Linux et al); meanwhile the former phase may still need to be completed +// early, e.g. prior to some sandbox configuration which may precede a fork(). +// +// Applications wishing to separate initialization into two phases can set +// this flag during an initial call to |MojoInitialize()|. To subsequently +// enable use of all Mojo APIs, |MojoInitialize()| must be called another time, +// without this flag set. +// +// Note that various MojoInitializeOptions may be ignored on the second call +// to |MojoInitialize()|, while others may actually override options passed to +// the first call. Documentation on individual option fields and flags clarifies +// this behavior. +#define MOJO_INITIALIZE_FLAG_LOAD_ONLY ((MojoInitializeFlags)4) + // Options passed to |MojoInitialize()|. struct MOJO_ALIGNAS(8) MojoInitializeOptions { // The size of this structure, used for versioning. @@ -161,10 +191,22 @@ struct MOJO_ALIGNAS(8) MojoInitializeOptions { // to load. If the |mojo_core_path| is null then |mojo_core_path_length| is // ignored and Mojo will fall back first onto the |MOJO_CORE_LIBRARY_PATH| // environment variable, and then onto the current working directory. + // + // NOTE: These fields are only observed during the first successful call to + // |MojoInitialize()| in a process. MOJO_POINTER_FIELD(const char*, mojo_core_path); uint32_t mojo_core_path_length; + + // For POSIX and Fuchsia systems only, this is the |argc| and |argv| from + // the calling process's main() entry point. These fields are ignored on + // Windows, but we define them anyway for the sake of ABI consistency. + // + // NOTE: These fields are only observed during the first successful call to + // |MojoInitialize()| within a process. + int32_t argc; + MOJO_POINTER_FIELD(const char* const*, argv); }; -MOJO_STATIC_ASSERT(sizeof(struct MojoInitializeOptions) == 24, +MOJO_STATIC_ASSERT(sizeof(struct MojoInitializeOptions) == 32, "MojoInitializeOptions has wrong size"); // Flags passed to |MojoShutdown()| via |MojoShutdownOptions|. diff --git a/chromium/mojo/public/cpp/base/big_buffer.cc b/chromium/mojo/public/cpp/base/big_buffer.cc index ecd5c0dfe55..59e3039ec29 100644 --- a/chromium/mojo/public/cpp/base/big_buffer.cc +++ b/chromium/mojo/public/cpp/base/big_buffer.cc @@ -67,6 +67,10 @@ void TryCreateSharedMemory( // instead produce an invalid buffer. This will always fail validation on // the receiving end. *storage_type = BigBuffer::StorageType::kInvalidBuffer; + + // TODO(crbug.com/1076341): Remove this temporary CHECK to investigate + // some bad IPC reports likely caused by this path. + CHECK(false); return; } } diff --git a/chromium/mojo/public/cpp/bindings/README.md b/chromium/mojo/public/cpp/bindings/README.md index 2c64ec743d8..db9b37d2a64 100644 --- a/chromium/mojo/public/cpp/bindings/README.md +++ b/chromium/mojo/public/cpp/bindings/README.md @@ -1139,23 +1139,23 @@ foo->SetBar(std::move(bar)); ### Performance considerations -When using associated interfaces on different sequences than the master sequence -(where the master interface lives): +When using associated interfaces on different sequences than the primary +sequence (where the primary interface lives): * Sending messages: send happens directly on the calling sequence. So there isn't sequence hopping. * Receiving messages: associated interfaces bound on a different sequence from - the master interface incur an extra sequence hop during dispatch. + the primary interface incur an extra sequence hop during dispatch. Therefore, performance-wise associated interfaces are better suited for -scenarios where message receiving happens on the master sequence. +scenarios where message receiving happens on the primary sequence. ### Testing -Associated interfaces need to be associated with a master interface before +Associated interfaces need to be associated with a primary interface before they can be used. This means one end of the associated interface must be sent -over one end of the master interface, or over one end of another associated -interface which itself already has a master interface. +over one end of the primary interface, or over one end of another associated +interface which itself already has a primary interface. If you want to test an associated interface endpoint without first associating it, you can use `AssociatedRemote::BindNewEndpointAndPassDedicatedReceiverForTesting`. diff --git a/chromium/mojo/public/cpp/bindings/associated_binding.h b/chromium/mojo/public/cpp/bindings/associated_binding.h index 2c7f8a3f9f7..a0b6eeadc21 100644 --- a/chromium/mojo/public/cpp/bindings/associated_binding.h +++ b/chromium/mojo/public/cpp/bindings/associated_binding.h @@ -11,8 +11,8 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" diff --git a/chromium/mojo/public/cpp/bindings/associated_group_controller.h b/chromium/mojo/public/cpp/bindings/associated_group_controller.h index 386ebdf8609..6de041783f5 100644 --- a/chromium/mojo/public/cpp/bindings/associated_group_controller.h +++ b/chromium/mojo/public/cpp/bindings/associated_group_controller.h @@ -38,7 +38,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) AssociatedGroupController // Creates an interface endpoint handle from a given interface ID. The handle // joins this associated group. // Typically, this method is used to (1) create an endpoint handle for the - // master interface; or (2) create an endpoint handle on receiving an + // primary interface; or (2) create an endpoint handle on receiving an // interface ID from the message pipe. // // On failure, the method returns an invalid handle. Usually that is because diff --git a/chromium/mojo/public/cpp/bindings/associated_interface_ptr.h b/chromium/mojo/public/cpp/bindings/associated_interface_ptr.h index f027f9ea2b1..956138c4277 100644 --- a/chromium/mojo/public/cpp/bindings/associated_interface_ptr.h +++ b/chromium/mojo/public/cpp/bindings/associated_interface_ptr.h @@ -12,8 +12,8 @@ #include <utility> #include "base/callback.h" +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/sequenced_task_runner.h" diff --git a/chromium/mojo/public/cpp/bindings/associated_receiver.h b/chromium/mojo/public/cpp/bindings/associated_receiver.h index 2087f4e4cde..f2857dcaa44 100644 --- a/chromium/mojo/public/cpp/bindings/associated_receiver.h +++ b/chromium/mojo/public/cpp/bindings/associated_receiver.h @@ -8,8 +8,8 @@ #include <memory> #include <utility> +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "base/sequenced_task_runner.h" diff --git a/chromium/mojo/public/cpp/bindings/associated_remote.h b/chromium/mojo/public/cpp/bindings/associated_remote.h index 9aa834c11fd..32b010c61c9 100644 --- a/chromium/mojo/public/cpp/bindings/associated_remote.h +++ b/chromium/mojo/public/cpp/bindings/associated_remote.h @@ -9,8 +9,8 @@ #include <utility> #include "base/callback_forward.h" +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "base/sequenced_task_runner.h" diff --git a/chromium/mojo/public/cpp/bindings/binding.h b/chromium/mojo/public/cpp/bindings/binding.h index bd650c79a40..4993de731f6 100644 --- a/chromium/mojo/public/cpp/bindings/binding.h +++ b/chromium/mojo/public/cpp/bindings/binding.h @@ -138,11 +138,10 @@ class Binding { // true if a method was successfully read and dispatched. // // This method may only be called if the object has been bound to a message - // pipe. This returns once a message is received either on the master + // pipe. This returns once a message is received either on the primary // interface or any associated interfaces. - bool WaitForIncomingMethodCall( - MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) { - return internal_state_.WaitForIncomingMethodCall(deadline); + bool WaitForIncomingMethodCall() { + return internal_state_.WaitForIncomingMethodCall(); } // Closes the message pipe that was previously bound. Put this object into a diff --git a/chromium/mojo/public/cpp/bindings/connector.h b/chromium/mojo/public/cpp/bindings/connector.h index 91dd4655b6a..16e478c9ebd 100644 --- a/chromium/mojo/public/cpp/bindings/connector.h +++ b/chromium/mojo/public/cpp/bindings/connector.h @@ -161,10 +161,10 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) Connector : public MessageReceiver { // be added to the same group. void SetConnectionGroup(ConnectionGroup::Ref ref); - // Waits for the next message on the pipe, blocking until one arrives, - // |deadline| elapses, or an error happens. Returns |true| if a message has - // been delivered, |false| otherwise. - bool WaitForIncomingMessage(MojoDeadline deadline); + // Waits for the next message on the pipe, blocking until one arrives or an + // error happens. Returns |true| if a message has been delivered, |false| + // otherwise. + bool WaitForIncomingMessage(); // See Binding for details of pause/resume. void PauseIncomingMethodCallProcessing(); diff --git a/chromium/mojo/public/cpp/bindings/interface_endpoint_client.h b/chromium/mojo/public/cpp/bindings/interface_endpoint_client.h index d2e1d0574f5..c8065924b2e 100644 --- a/chromium/mojo/public/cpp/bindings/interface_endpoint_client.h +++ b/chromium/mojo/public/cpp/bindings/interface_endpoint_client.h @@ -12,10 +12,10 @@ #include <utility> #include "base/callback.h" +#include "base/check_op.h" #include "base/compiler_specific.h" #include "base/component_export.h" #include "base/location.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" diff --git a/chromium/mojo/public/cpp/bindings/interface_id.h b/chromium/mojo/public/cpp/bindings/interface_id.h index d6128537d20..8760fea068f 100644 --- a/chromium/mojo/public/cpp/bindings/interface_id.h +++ b/chromium/mojo/public/cpp/bindings/interface_id.h @@ -14,16 +14,16 @@ using InterfaceId = uint32_t; // IDs of associated interface can be generated at both sides of the message // pipe. In order to avoid collision, the highest bit is used as namespace bit: -// at the side where the client-side of the master interface lives, IDs are +// at the side where the client-side of the primary interface lives, IDs are // generated with the namespace bit set to 1; at the opposite side IDs are // generated with the namespace bit set to 0. const uint32_t kInterfaceIdNamespaceMask = 0x80000000; -const InterfaceId kMasterInterfaceId = 0x00000000; +const InterfaceId kPrimaryInterfaceId = 0x00000000; const InterfaceId kInvalidInterfaceId = 0xFFFFFFFF; -inline bool IsMasterInterfaceId(InterfaceId id) { - return id == kMasterInterfaceId; +inline bool IsPrimaryInterfaceId(InterfaceId id) { + return id == kPrimaryInterfaceId; } inline bool IsValidInterfaceId(InterfaceId id) { diff --git a/chromium/mojo/public/cpp/bindings/interface_ptr.h b/chromium/mojo/public/cpp/bindings/interface_ptr.h index 4f10be5acc5..6745a32b530 100644 --- a/chromium/mojo/public/cpp/bindings/interface_ptr.h +++ b/chromium/mojo/public/cpp/bindings/interface_ptr.h @@ -12,7 +12,7 @@ #include <utility> #include "base/callback_forward.h" -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/sequenced_task_runner.h" diff --git a/chromium/mojo/public/cpp/bindings/interface_request.h b/chromium/mojo/public/cpp/bindings/interface_request.h index 0b9fd64b193..3e44bfba2a7 100644 --- a/chromium/mojo/public/cpp/bindings/interface_request.h +++ b/chromium/mojo/public/cpp/bindings/interface_request.h @@ -80,7 +80,7 @@ class InterfaceRequest { Message message = PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( - kMasterInterfaceId, DisconnectReason(custom_reason, description)); + kPrimaryInterfaceId, DisconnectReason(custom_reason, description)); MojoResult result = WriteMessageNew(state_.pipe.get(), message.TakeMojoMessage(), MOJO_WRITE_MESSAGE_FLAG_NONE); diff --git a/chromium/mojo/public/cpp/bindings/lib/array_internal.h b/chromium/mojo/public/cpp/bindings/lib/array_internal.h index e34349f2a72..cb8c5b30907 100644 --- a/chromium/mojo/public/cpp/bindings/lib/array_internal.h +++ b/chromium/mojo/public/cpp/bindings/lib/array_internal.h @@ -11,8 +11,8 @@ #include <limits> #include <new> +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "mojo/public/c/system/macros.h" #include "mojo/public/cpp/bindings/lib/bindings_internal.h" diff --git a/chromium/mojo/public/cpp/bindings/lib/binding_state.cc b/chromium/mojo/public/cpp/bindings/lib/binding_state.cc index f0eab656f1e..ecfe41c4652 100644 --- a/chromium/mojo/public/cpp/bindings/lib/binding_state.cc +++ b/chromium/mojo/public/cpp/bindings/lib/binding_state.cc @@ -36,9 +36,9 @@ void BindingStateBase::ResumeIncomingMethodCallProcessing() { router_->ResumeIncomingMethodCallProcessing(); } -bool BindingStateBase::WaitForIncomingMethodCall(MojoDeadline deadline) { +bool BindingStateBase::WaitForIncomingMethodCall() { DCHECK(router_); - return router_->WaitForIncomingMessage(deadline); + return router_->WaitForIncomingMessage(); } void BindingStateBase::PauseRemoteCallbacksUntilFlushCompletes( @@ -121,11 +121,11 @@ void BindingStateBase::BindInternal( : MultiplexRouter::SINGLE_INTERFACE); router_ = new MultiplexRouter(std::move(receiver_state->pipe), config, false, sequenced_runner); - router_->SetMasterInterfaceName(interface_name); + router_->SetPrimaryInterfaceName(interface_name); router_->SetConnectionGroup(std::move(receiver_state->connection_group)); endpoint_client_.reset(new InterfaceEndpointClient( - router_->CreateLocalEndpointHandle(kMasterInterfaceId), stub, + router_->CreateLocalEndpointHandle(kPrimaryInterfaceId), stub, std::move(request_validator), has_sync_methods, std::move(sequenced_runner), interface_version, interface_name)); endpoint_client_->SetIdleTrackingEnabledCallback( diff --git a/chromium/mojo/public/cpp/bindings/lib/binding_state.h b/chromium/mojo/public/cpp/bindings/lib/binding_state.h index cadb81a7fa6..598668482ff 100644 --- a/chromium/mojo/public/cpp/bindings/lib/binding_state.h +++ b/chromium/mojo/public/cpp/bindings/lib/binding_state.h @@ -11,8 +11,8 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" @@ -48,8 +48,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) BindingStateBase { void PauseIncomingMethodCallProcessing(); void ResumeIncomingMethodCallProcessing(); - bool WaitForIncomingMethodCall( - MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE); + bool WaitForIncomingMethodCall(); void PauseRemoteCallbacksUntilFlushCompletes(PendingFlush flush); void FlushAsync(AsyncFlusher flusher); diff --git a/chromium/mojo/public/cpp/bindings/lib/connector.cc b/chromium/mojo/public/cpp/bindings/lib/connector.cc index 5c92e2d65ba..c342d655d29 100644 --- a/chromium/mojo/public/cpp/bindings/lib/connector.cc +++ b/chromium/mojo/public/cpp/bindings/lib/connector.cc @@ -229,7 +229,7 @@ void Connector::SetConnectionGroup(ConnectionGroup::Ref ref) { connection_group_ = std::move(ref); } -bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { +bool Connector::WaitForIncomingMessage() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (error_) @@ -237,23 +237,13 @@ bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { ResumeIncomingMethodCallProcessing(); - // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or - // INDEFINITE deadlines at present, so we only support those. - DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE); - - MojoResult rv = MOJO_RESULT_UNKNOWN; - if (deadline == 0 && !message_pipe_->QuerySignalsState().readable()) + MojoResult rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (rv != MOJO_RESULT_OK) { + // Users that call WaitForIncomingMessage() should expect their code to be + // re-entered, so we call the error handler synchronously. + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION /* force_pipe_reset */, + false /* force_async_handler */); return false; - - if (deadline == MOJO_DEADLINE_INDEFINITE) { - rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE); - if (rv != MOJO_RESULT_OK) { - // Users that call WaitForIncomingMessage() should expect their code to be - // re-entered, so we call the error handler synchronously. - HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION /* force_pipe_reset */, - false /* force_async_handler */); - return false; - } } Message message; @@ -505,9 +495,9 @@ bool Connector::DispatchMessage(Message message) { incoming_serialization_mode_); } - TRACE_EVENT_WITH_FLOW0( - TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), "mojo::Message Receive", - MANGLE_MESSAGE_ID(message.header()->trace_id), TRACE_EVENT_FLAG_FLOW_IN); + TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Receive", + MANGLE_MESSAGE_ID(message.header()->trace_id), + TRACE_EVENT_FLAG_FLOW_IN); #if !BUILDFLAG(MOJO_TRACE_ENABLED) // This emits just full class name, and is inferior to mojo tracing. TRACE_EVENT0("mojom", heap_profiler_tag_); diff --git a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc index 348c63d7c16..bd363911483 100644 --- a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc +++ b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc @@ -92,7 +92,7 @@ bool InterfacePtrStateBase::InitializeEndpointClient( DCHECK(runner_->RunsTasksInCurrentSequence()); router_ = new MultiplexRouter(std::move(handle_), config, true, runner_); endpoint_client_.reset(new InterfaceEndpointClient( - router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr, + router_->CreateLocalEndpointHandle(kPrimaryInterfaceId), nullptr, std::move(payload_validator), false, std::move(runner_), // The version is only queried from the client so the value passed here // will not be used. diff --git a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.h index 0d259f37a2f..d1e37115f90 100644 --- a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.h +++ b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.h @@ -14,9 +14,9 @@ #include "base/bind.h" #include "base/callback_forward.h" +#include "base/check_op.h" #include "base/component_export.h" #include "base/location.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" @@ -260,7 +260,7 @@ class InterfacePtrState : public InterfacePtrStateBase { Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, std::make_unique<typename Interface::ResponseValidator_>(), Interface::Name_)) { - router()->SetMasterInterfaceName(Interface::Name_); + router()->SetPrimaryInterfaceName(Interface::Name_); proxy_ = std::make_unique<Proxy>(endpoint_client()); } } diff --git a/chromium/mojo/public/cpp/bindings/lib/message.cc b/chromium/mojo/public/cpp/bindings/lib/message.cc index 7b8e92ecbf7..893ec949fbc 100644 --- a/chromium/mojo/public/cpp/bindings/lib/message.cc +++ b/chromium/mojo/public/cpp/bindings/lib/message.cc @@ -102,8 +102,8 @@ void CreateSerializedMessageObject(uint32_t name, std::vector<ScopedHandle>* handles, ScopedMessageHandle* out_handle, internal::Buffer* out_buffer) { - TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), - "mojo::Message Send", MANGLE_MESSAGE_ID(trace_id), + TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send", + MANGLE_MESSAGE_ID(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); ScopedMessageHandle handle; @@ -148,8 +148,8 @@ void SerializeUnserializedContext(MojoMessageHandle message, reinterpret_cast<internal::UnserializedMessageContext*>(context_value); uint32_t trace_id = GetTraceId(context); - TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), - "mojo::Message Send", MANGLE_MESSAGE_ID(trace_id), + TRACE_EVENT_WITH_FLOW0("toplevel.flow", "mojo::Message Send", + MANGLE_MESSAGE_ID(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); void* buffer; diff --git a/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc index 25a651e4b45..e4f8a4905cd 100644 --- a/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc +++ b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc @@ -90,7 +90,7 @@ bool IsValidMessageHeader(const internal::MessageHeader* header, size_t num_ids = header_v2->payload_interface_ids.Get()->size(); const uint32_t* ids = header_v2->payload_interface_ids.Get()->storage(); for (size_t i = 0; i < num_ids; ++i) { - if (!IsValidInterfaceId(ids[i]) || IsMasterInterfaceId(ids[i])) { + if (!IsValidInterfaceId(ids[i]) || IsPrimaryInterfaceId(ids[i])) { internal::ReportValidationError( validation_context, internal::VALIDATION_ERROR_ILLEGAL_INTERFACE_ID); diff --git a/chromium/mojo/public/cpp/bindings/lib/message_quota_checker.h b/chromium/mojo/public/cpp/bindings/lib/message_quota_checker.h index 071853640a6..37955350905 100644 --- a/chromium/mojo/public/cpp/bindings/lib/message_quota_checker.h +++ b/chromium/mojo/public/cpp/bindings/lib/message_quota_checker.h @@ -31,10 +31,10 @@ namespace internal { // outgoing queue. Additionally, |BeforeWrite()| should be called immediately // before writing each message to the corresponding message pipe. // -// Also note that messages posted to a different sequence with -// |base::PostTask()| and the like, need to be treated as locally queued. Task -// queues can grow arbitrarily long, and it's ideal to perform unread quota -// checks before posting. +// Also note that messages posted to a different sequence with base::ThreadPool +// and the like, need to be treated as locally queued. Task queues can grow +// arbitrarily long, and it's ideal to perform unread quota checks before +// posting. // // Either |BeforeMessagesEnqueued()| or |BeforeWrite()| may cause the quota // to be exceeded, thus invoking the |maybe_crash_function| set in this diff --git a/chromium/mojo/public/cpp/bindings/lib/multiplex_router.cc b/chromium/mojo/public/cpp/bindings/lib/multiplex_router.cc index 4a825fd6a71..08da6c26ded 100644 --- a/chromium/mojo/public/cpp/bindings/lib/multiplex_router.cc +++ b/chromium/mojo/public/cpp/bindings/lib/multiplex_router.cc @@ -369,12 +369,12 @@ void MultiplexRouter::SetIncomingMessageFilter( dispatcher_.SetFilter(std::move(filter)); } -void MultiplexRouter::SetMasterInterfaceName(const char* name) { +void MultiplexRouter::SetPrimaryInterfaceName(const char* name) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); header_validator_->SetDescription(std::string(name) + - " [master] MessageHeaderValidator"); + " [primary] MessageHeaderValidator"); control_message_handler_.SetDescription( - std::string(name) + " [master] PipeControlMessageHandler"); + std::string(name) + " [primary] PipeControlMessageHandler"); connector_.SetWatcherHeapProfilerTag(name); } @@ -454,7 +454,7 @@ void MultiplexRouter::CloseEndpointHandle( DCHECK(!endpoint->closed()); UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); - if (!IsMasterInterfaceId(id) || reason) { + if (!IsPrimaryInterfaceId(id) || reason) { MayAutoUnlock unlocker(&lock_); control_message_proxy_.NotifyPeerEndpointClosed(id, reason); } @@ -575,7 +575,7 @@ bool MultiplexRouter::HasAssociatedEndpoints() const { if (endpoints_.size() == 0) return false; - return !base::Contains(endpoints_, kMasterInterfaceId); + return !base::Contains(endpoints_, kPrimaryInterfaceId); } void MultiplexRouter::EnableBatchDispatch() { @@ -674,7 +674,7 @@ bool MultiplexRouter::OnPeerAssociatedEndpointClosed( bool MultiplexRouter::WaitForFlushToComplete(ScopedMessagePipeHandle pipe) { // If this MultiplexRouter has an associated interface on some task runner - // other than the master interface's task runner, it is possible to process + // other than the primary interface's task runner, it is possible to process // incoming control messages on that task runner. We don't support this // control message on anything but the main interface though. if (!task_runner_->RunsTasksInCurrentSequence()) @@ -1048,7 +1048,7 @@ bool MultiplexRouter::InsertEndpointsForMessage(const Message& message) { MayAutoLock locker(&lock_); for (uint32_t i = 0; i < num_ids; ++i) { // Message header validation already ensures that the IDs are valid and not - // the master ID. + // the primary ID. // The IDs are from the remote side and therefore their namespace bit is // supposed to be different than the value that this router would use. if (set_interface_id_namespace_bit_ == diff --git a/chromium/mojo/public/cpp/bindings/lib/multiplex_router.h b/chromium/mojo/public/cpp/bindings/lib/multiplex_router.h index 3063ec49703..16128e880b0 100644 --- a/chromium/mojo/public/cpp/bindings/lib/multiplex_router.h +++ b/chromium/mojo/public/cpp/bindings/lib/multiplex_router.h @@ -11,11 +11,11 @@ #include <memory> #include <string> +#include "base/check_op.h" #include "base/compiler_specific.h" #include "base/component_export.h" #include "base/containers/queue.h" #include "base/containers/small_map.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" @@ -47,7 +47,7 @@ namespace internal { // MultiplexRouter supports routing messages for multiple interfaces over a // single message pipe. // -// It is created on the sequence where the master interface of the message pipe +// It is created on the sequence where the primary interface of the message pipe // lives. // Some public methods are only allowed to be called on the creating sequence; // while the others are safe to call from any sequence. Please see the method @@ -61,14 +61,14 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) MultiplexRouter public PipeControlMessageHandlerDelegate { public: enum Config { - // There is only the master interface running on this router. Please note + // There is only the primary interface running on this router. Please note // that because of interface versioning, the other side of the message pipe - // may use a newer master interface definition which passes associated + // may use a newer primary interface definition which passes associated // interfaces. In that case, this router may still receive pipe control // messages or messages targetting associated interfaces. SINGLE_INTERFACE, - // Similar to the mode above, there is only the master interface running on - // this router. Besides, the master interface has sync methods. + // Similar to the mode above, there is only the primary interface running on + // this router. Besides, the primary interface has sync methods. SINGLE_INTERFACE_WITH_SYNC_METHODS, // There may be associated interfaces running on this router. MULTI_INTERFACE @@ -85,10 +85,10 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) MultiplexRouter // before dispatch. void SetIncomingMessageFilter(std::unique_ptr<MessageFilter> filter); - // Sets the master interface name for this router. Only used when reporting + // Sets the primary interface name for this router. Only used when reporting // message header or control message validation errors. // |name| must be a string literal. - void SetMasterInterfaceName(const char* name); + void SetPrimaryInterfaceName(const char* name); // Adds this object to a ConnectionGroup identified by |ref|. All receiving // pipe endpoints decoded from inbound messages on this MultiplexRouter will @@ -130,11 +130,10 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) MultiplexRouter return connector_.PassMessagePipe(); } - // Blocks the current sequence until the first incoming message, or - // |deadline|. - bool WaitForIncomingMessage(MojoDeadline deadline) { + // Blocks the current sequence until the first incoming message. + bool WaitForIncomingMessage() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return connector_.WaitForIncomingMessage(deadline); + return connector_.WaitForIncomingMessage(); } // See Binding for details of pause/resume. diff --git a/chromium/mojo/public/cpp/bindings/lib/native_enum_serialization.h b/chromium/mojo/public/cpp/bindings/lib/native_enum_serialization.h index 4faf957c58e..6cd8c7f90fb 100644 --- a/chromium/mojo/public/cpp/bindings/lib/native_enum_serialization.h +++ b/chromium/mojo/public/cpp/bindings/lib/native_enum_serialization.h @@ -10,7 +10,7 @@ #include <type_traits> -#include "base/logging.h" +#include "base/check_op.h" #include "base/pickle.h" #include "ipc/ipc_param_traits.h" #include "mojo/public/cpp/bindings/lib/serialization_forward.h" diff --git a/chromium/mojo/public/cpp/bindings/lib/native_struct_serialization.h b/chromium/mojo/public/cpp/bindings/lib/native_struct_serialization.h index 5e2be21c981..41abb228c4f 100644 --- a/chromium/mojo/public/cpp/bindings/lib/native_struct_serialization.h +++ b/chromium/mojo/public/cpp/bindings/lib/native_struct_serialization.h @@ -10,8 +10,8 @@ #include <limits> +#include "base/check_op.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/pickle.h" #include "ipc/ipc_message.h" #include "ipc/ipc_param_traits.h" diff --git a/chromium/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc b/chromium/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc index c443c65cf55..1d9178799df 100644 --- a/chromium/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc +++ b/chromium/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc @@ -99,6 +99,8 @@ class SequenceLocalSyncEventWatcher::SequenceLocalState { { base::AutoLock lock(ready_watchers_lock_); ready_watchers_.erase(iter->first); + if (ready_watchers_.empty()) + event_.Reset(); } registered_watchers_.erase(iter); @@ -206,8 +208,10 @@ void SequenceLocalSyncEventWatcher::SequenceLocalState::OnEventSignaled() { base::AutoLock lock(ready_watchers_lock_); std::swap(ready_watchers_, ready_watchers); } - if (ready_watchers.empty()) + if (ready_watchers.empty()) { + event_.Reset(); return; + } auto weak_self = weak_ptr_factory_.GetWeakPtr(); for (auto* watcher : ready_watchers) { diff --git a/chromium/mojo/public/cpp/bindings/map_data_view.h b/chromium/mojo/public/cpp/bindings/map_data_view.h index a65bb9eca14..5743829eeec 100644 --- a/chromium/mojo/public/cpp/bindings/map_data_view.h +++ b/chromium/mojo/public/cpp/bindings/map_data_view.h @@ -5,7 +5,7 @@ #ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_ #define MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_ -#include "base/logging.h" +#include "base/check_op.h" #include "mojo/public/cpp/bindings/array_data_view.h" #include "mojo/public/cpp/bindings/lib/bindings_internal.h" #include "mojo/public/cpp/bindings/lib/map_data_internal.h" diff --git a/chromium/mojo/public/cpp/bindings/message.h b/chromium/mojo/public/cpp/bindings/message.h index 8dbb3df8875..8099cd4305b 100644 --- a/chromium/mojo/public/cpp/bindings/message.h +++ b/chromium/mojo/public/cpp/bindings/message.h @@ -14,10 +14,10 @@ #include <vector> #include "base/callback.h" +#include "base/check_op.h" #include "base/compiler_specific.h" #include "base/component_export.h" #include "base/containers/span.h" -#include "base/logging.h" #include "base/memory/ptr_util.h" #include "mojo/public/cpp/bindings/connection_group.h" #include "mojo/public/cpp/bindings/lib/buffer.h" diff --git a/chromium/mojo/public/cpp/bindings/pending_remote.h b/chromium/mojo/public/cpp/bindings/pending_remote.h index e67dd1a6691..64c9ab90653 100644 --- a/chromium/mojo/public/cpp/bindings/pending_remote.h +++ b/chromium/mojo/public/cpp/bindings/pending_remote.h @@ -9,8 +9,8 @@ #include <type_traits> #include <utility> +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "build/build_config.h" #include "mojo/public/cpp/bindings/interface_ptr_info.h" diff --git a/chromium/mojo/public/cpp/bindings/receiver.h b/chromium/mojo/public/cpp/bindings/receiver.h index 045bd3e5dbc..6f8b9976581 100644 --- a/chromium/mojo/public/cpp/bindings/receiver.h +++ b/chromium/mojo/public/cpp/bindings/receiver.h @@ -8,8 +8,8 @@ #include <memory> #include <utility> +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "base/sequenced_task_runner.h" @@ -204,7 +204,7 @@ class Receiver { // Blocks the calling thread until a new message arrives and is dispatched // to the bound implementation. bool WaitForIncomingCall() { - return internal_state_.WaitForIncomingMethodCall(MOJO_DEADLINE_INDEFINITE); + return internal_state_.WaitForIncomingMethodCall(); } // Pauses the Remote endpoint, stopping dispatch of callbacks on that end. Any diff --git a/chromium/mojo/public/cpp/bindings/remote.h b/chromium/mojo/public/cpp/bindings/remote.h index 45615bfb930..d71d2037141 100644 --- a/chromium/mojo/public/cpp/bindings/remote.h +++ b/chromium/mojo/public/cpp/bindings/remote.h @@ -9,8 +9,8 @@ #include <utility> #include "base/callback_forward.h" +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "base/sequenced_task_runner.h" diff --git a/chromium/mojo/public/cpp/bindings/strong_associated_binding.h b/chromium/mojo/public/cpp/bindings/strong_associated_binding.h index 0bb340c7d11..7425e87f2e4 100644 --- a/chromium/mojo/public/cpp/bindings/strong_associated_binding.h +++ b/chromium/mojo/public/cpp/bindings/strong_associated_binding.h @@ -11,7 +11,7 @@ #include "base/bind.h" #include "base/callback.h" -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "mojo/public/cpp/bindings/associated_binding.h" diff --git a/chromium/mojo/public/cpp/bindings/strong_associated_binding_set.h b/chromium/mojo/public/cpp/bindings/strong_associated_binding_set.h deleted file mode 100644 index 8c769698ba3..00000000000 --- a/chromium/mojo/public/cpp/bindings/strong_associated_binding_set.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_SET_H_ -#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_SET_H_ - -#include "mojo/public/cpp/bindings/associated_binding.h" -#include "mojo/public/cpp/bindings/associated_binding_set.h" -#include "mojo/public/cpp/bindings/associated_interface_ptr.h" -#include "mojo/public/cpp/bindings/associated_interface_request.h" -#include "mojo/public/cpp/bindings/binding_set.h" -#include "mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h" - -namespace mojo { - -template <typename Interface, typename ContextType = void> -using StrongAssociatedBindingSet = BindingSetBase< - Interface, - AssociatedBinding<Interface, UniquePtrImplRefTraits<Interface>>, - ContextType>; - -} // namespace mojo - -#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_SET_H_ diff --git a/chromium/mojo/public/cpp/bindings/strong_binding.h b/chromium/mojo/public/cpp/bindings/strong_binding.h index b1160453a96..e3940fba4b5 100644 --- a/chromium/mojo/public/cpp/bindings/strong_binding.h +++ b/chromium/mojo/public/cpp/bindings/strong_binding.h @@ -11,7 +11,7 @@ #include "base/bind.h" #include "base/callback.h" -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "mojo/public/cpp/bindings/binding.h" diff --git a/chromium/mojo/public/cpp/bindings/struct_ptr.h b/chromium/mojo/public/cpp/bindings/struct_ptr.h index 93431740434..ea2f52ea878 100644 --- a/chromium/mojo/public/cpp/bindings/struct_ptr.h +++ b/chromium/mojo/public/cpp/bindings/struct_ptr.h @@ -10,7 +10,7 @@ #include <memory> #include <new> -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "base/optional.h" #include "mojo/public/cpp/bindings/lib/hash_util.h" diff --git a/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h b/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h index 60e11492057..599d24e260a 100644 --- a/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h +++ b/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h @@ -15,6 +15,10 @@ #define ENABLE_SYNC_CALL_RESTRICTIONS 0 #endif +namespace chromecast { +class CastCdmOriginProvider; +} // namespace chromecast + namespace sync_preferences { class PrefServiceSyncable; } @@ -82,6 +86,11 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) SyncCallRestrictions { // For preventing frame swaps of wrong size during resize on Windows. // (https://crbug.com/811945) friend class ui::Compositor; + // For calling sync mojo API to get cdm origin. The service and the client are + // running in the same process, so it won't block anything. + // TODO(159346933) Remove once the origin isolation logic is moved outside of + // cast media service. + friend class chromecast::CastCdmOriginProvider; // END ALLOWED USAGE. #if ENABLE_SYNC_CALL_RESTRICTIONS diff --git a/chromium/mojo/public/cpp/bindings/sync_handle_watcher.h b/chromium/mojo/public/cpp/bindings/sync_handle_watcher.h index f75e1e232af..5ed7e7b827e 100644 --- a/chromium/mojo/public/cpp/bindings/sync_handle_watcher.h +++ b/chromium/mojo/public/cpp/bindings/sync_handle_watcher.h @@ -22,7 +22,7 @@ namespace mojo { // SyncHandleWatcher is used for sync methods. While a sync call is waiting for // response, we would like to block the sequence. On the other hand, we need // incoming sync method requests on the same sequence to be able to reenter. We -// also need master interface endpoints to continue dispatching messages for +// also need primary interface endpoints to continue dispatching messages for // associated endpoints on different sequence. // // This class is not thread safe. diff --git a/chromium/mojo/public/cpp/bindings/tests/BUILD.gn b/chromium/mojo/public/cpp/bindings/tests/BUILD.gn index 6fb6542728a..16674cc3366 100644 --- a/chromium/mojo/public/cpp/bindings/tests/BUILD.gn +++ b/chromium/mojo/public/cpp/bindings/tests/BUILD.gn @@ -67,7 +67,6 @@ source_set("tests") { ":test_extra_cpp_template_mojom", ":test_mojom", "//base/test:test_support", - "//mojo/core/embedder", "//mojo/public/cpp/bindings", "//mojo/public/cpp/system", "//mojo/public/cpp/test_support:test_utils", @@ -197,7 +196,10 @@ source_set("mojo_public_bindings_test_utils") { "validation_test_input_parser.h", ] - deps = [ "//mojo/public/c/system" ] + deps = [ + "//base", + "//mojo/public/c/system", + ] } action("generate_test_mojom") { diff --git a/chromium/mojo/public/cpp/platform/platform_channel.cc b/chromium/mojo/public/cpp/platform/platform_channel.cc index 3320bcce7eb..140fb8bf965 100644 --- a/chromium/mojo/public/cpp/platform/platform_channel.cc +++ b/chromium/mojo/public/cpp/platform/platform_channel.cc @@ -337,4 +337,10 @@ PlatformChannelEndpoint PlatformChannel::RecoverPassedEndpointFromCommandLine( command_line.GetSwitchValueASCII(kHandleSwitch)); } +// static +bool PlatformChannel::CommandLineHasPassedEndpoint( + const base::CommandLine& command_line) { + return command_line.HasSwitch(kHandleSwitch); +} + } // namespace mojo diff --git a/chromium/mojo/public/cpp/platform/platform_channel.h b/chromium/mojo/public/cpp/platform/platform_channel.h index 01d3ae19390..c7ef7badb8a 100644 --- a/chromium/mojo/public/cpp/platform/platform_channel.h +++ b/chromium/mojo/public/cpp/platform/platform_channel.h @@ -107,6 +107,10 @@ class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformChannel { static PlatformChannelEndpoint RecoverPassedEndpointFromCommandLine( const base::CommandLine& command_line) WARN_UNUSED_RESULT; + // Indicates whether |RecoverPassedEndpointFromCommandLine()| would succeed. + static bool CommandLineHasPassedEndpoint( + const base::CommandLine& command_line); + private: PlatformChannelEndpoint local_endpoint_; PlatformChannelEndpoint remote_endpoint_; diff --git a/chromium/mojo/public/cpp/platform/platform_handle.h b/chromium/mojo/public/cpp/platform/platform_handle.h index 0e01be2cfa7..338b992a2d0 100644 --- a/chromium/mojo/public/cpp/platform/platform_handle.h +++ b/chromium/mojo/public/cpp/platform/platform_handle.h @@ -5,9 +5,9 @@ #ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_ #define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_ +#include "base/check_op.h" #include "base/component_export.h" #include "base/files/platform_file.h" -#include "base/logging.h" #include "base/macros.h" #include "build/build_config.h" #include "mojo/public/c/system/platform_handle.h" diff --git a/chromium/mojo/public/cpp/platform/socket_utils_posix.cc b/chromium/mojo/public/cpp/platform/socket_utils_posix.cc index 6199a36a69e..b135da1a4e8 100644 --- a/chromium/mojo/public/cpp/platform/socket_utils_posix.cc +++ b/chromium/mojo/public/cpp/platform/socket_utils_posix.cc @@ -14,6 +14,7 @@ #include "base/files/file_util.h" #include "base/logging.h" +#include "base/notreached.h" #include "base/posix/eintr_wrapper.h" #include "build/build_config.h" diff --git a/chromium/mojo/public/cpp/platform/socket_utils_posix.h b/chromium/mojo/public/cpp/platform/socket_utils_posix.h index 9e53d058127..3da31cc77e7 100644 --- a/chromium/mojo/public/cpp/platform/socket_utils_posix.h +++ b/chromium/mojo/public/cpp/platform/socket_utils_posix.h @@ -13,7 +13,6 @@ #include "base/component_export.h" #include "base/files/platform_file.h" #include "base/files/scoped_file.h" -#include "base/logging.h" #include "base/macros.h" struct iovec; // Declared in <sys/uio.h> diff --git a/chromium/mojo/public/cpp/system/BUILD.gn b/chromium/mojo/public/cpp/system/BUILD.gn index 8608e7b8f0d..7f8eaefbb1f 100644 --- a/chromium/mojo/public/cpp/system/BUILD.gn +++ b/chromium/mojo/public/cpp/system/BUILD.gn @@ -17,10 +17,13 @@ component("system") { "data_pipe_producer.h", "data_pipe_utils.cc", "data_pipe_utils.h", + "dynamic_library_support.cc", + "dynamic_library_support.h", "file_data_source.cc", "file_data_source.h", "filtered_data_source.cc", "filtered_data_source.h", + "functions.cc", "functions.h", "handle.h", "handle_signal_tracker.cc", diff --git a/chromium/mojo/public/cpp/system/buffer.h b/chromium/mojo/public/cpp/system/buffer.h index e20a8fe14d9..9b8700badf8 100644 --- a/chromium/mojo/public/cpp/system/buffer.h +++ b/chromium/mojo/public/cpp/system/buffer.h @@ -16,8 +16,8 @@ #include <memory> +#include "base/check_op.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "mojo/public/c/system/buffer.h" #include "mojo/public/cpp/system/handle.h" #include "mojo/public/cpp/system/system_export.h" diff --git a/chromium/mojo/public/cpp/system/data_pipe.h b/chromium/mojo/public/cpp/system/data_pipe.h index eb7d6e279ad..6cd2eb35428 100644 --- a/chromium/mojo/public/cpp/system/data_pipe.h +++ b/chromium/mojo/public/cpp/system/data_pipe.h @@ -14,8 +14,8 @@ #include <stdint.h> +#include "base/check.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "mojo/public/c/system/data_pipe.h" #include "mojo/public/cpp/system/handle.h" diff --git a/chromium/mojo/public/cpp/system/data_pipe_producer.cc b/chromium/mojo/public/cpp/system/data_pipe_producer.cc index 056620a794d..bb474ca29df 100644 --- a/chromium/mojo/public/cpp/system/data_pipe_producer.cc +++ b/chromium/mojo/public/cpp/system/data_pipe_producer.cc @@ -53,6 +53,8 @@ class DataPipeProducer::SequenceState void Cancel() { base::AutoLock lock(lock_); is_cancelled_ = true; + owning_task_runner()->PostTask( + FROM_HERE, base::BindOnce(&SequenceState::CancelOnSequence, this)); } void Start(std::unique_ptr<DataSource> data_source) { @@ -151,6 +153,13 @@ class DataPipeProducer::SequenceState std::move(producer_handle_), result)); } + void CancelOnSequence() { + if (!data_source_) + return; + data_source_->Abort(); + Finish(MOJO_RESULT_CANCELLED); + } + const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; // State which is effectively owned and used only on the file sequence. diff --git a/chromium/mojo/public/cpp/system/dynamic_library_support.cc b/chromium/mojo/public/cpp/system/dynamic_library_support.cc new file mode 100644 index 00000000000..c106778e69c --- /dev/null +++ b/chromium/mojo/public/cpp/system/dynamic_library_support.cc @@ -0,0 +1,64 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/dynamic_library_support.h" + +#include <stdint.h> + +#include "base/command_line.h" +#include "build/build_config.h" +#include "mojo/public/c/system/functions.h" + +namespace mojo { + +namespace { + +// Helper for temporary storage related to |MojoInitialize()| calls. +struct InitializationState { + InitializationState(const base::Optional<base::FilePath>& path, + MojoInitializeFlags flags) { + options.flags = flags; + + if (path) { + utf8_path = path->AsUTF8Unsafe(); + options.mojo_core_path = utf8_path.c_str(); + options.mojo_core_path_length = static_cast<uint32_t>(utf8_path.size()); + } + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) + // Build a temporary reconstructed argv to pass into the library so it can + // inspect the application command line if needed. + for (const std::string& s : base::CommandLine::ForCurrentProcess()->argv()) + argv.push_back(s.c_str()); + options.argc = static_cast<uint32_t>(argv.size()); + options.argv = argv.data(); +#endif + } + + MojoInitializeOptions options = {sizeof(MojoInitializeOptions)}; + std::string utf8_path; + std::vector<const char*> argv; +}; + +} // namespace + +MojoResult LoadCoreLibrary(base::Optional<base::FilePath> path) { + InitializationState state(path, MOJO_INITIALIZE_FLAG_LOAD_ONLY); + return MojoInitialize(&state.options); +} + +MojoResult InitializeCoreLibrary(MojoInitializeFlags flags) { + DCHECK_EQ(flags & MOJO_INITIALIZE_FLAG_LOAD_ONLY, 0u); + InitializationState state(base::nullopt, flags); + return MojoInitialize(&state.options); +} + +MojoResult LoadAndInitializeCoreLibrary(base::Optional<base::FilePath> path, + MojoInitializeFlags flags) { + DCHECK_EQ(flags & MOJO_INITIALIZE_FLAG_LOAD_ONLY, 0u); + InitializationState state(path, flags); + return MojoInitialize(&state.options); +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/system/dynamic_library_support.h b/chromium/mojo/public/cpp/system/dynamic_library_support.h new file mode 100644 index 00000000000..97c30427cb6 --- /dev/null +++ b/chromium/mojo/public/cpp/system/dynamic_library_support.h @@ -0,0 +1,45 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_DYNAMIC_LIBRARY_SUPPORT_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_DYNAMIC_LIBRARY_SUPPORT_H_ + +#include "base/files/file_path.h" +#include "base/optional.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// Helper to load Mojo Core dynamically from a shared library. If |path| is +// not given, the library path is assumed to be set in the +// MOJO_CORE_LIBRARY_PATH environment variable, or the library is searched for +// in the current working directory. +// +// This may only be called in a process that hasn't already initialized Mojo. +// Mojo is still not fully initialized or usable until |InitializeCoreLibrary()| +// is also called. These two functions are kept distinct to facilitate use +// cases where the client application must perform some work (e.g. sandbox +// configuration, forking, etc) between the loading and initialization steps. +MOJO_CPP_SYSTEM_EXPORT MojoResult +LoadCoreLibrary(base::Optional<base::FilePath> path); + +// Initializes the dynamic Mojo Core library previously loaded by +// |LoadCoreLibrary()| above. +// +// This may only be called in a process that hasn't already initialized Mojo. +MOJO_CPP_SYSTEM_EXPORT MojoResult +InitializeCoreLibrary(MojoInitializeFlags flags); + +// Loads and initializes Mojo Core from a shared library. This combines +// |LoadCoreLibrary()| and |InitializeCoreLibrary()| for convenience in cases +// where they don't need to be performed at different times by the client +// application. +MOJO_CPP_SYSTEM_EXPORT MojoResult +LoadAndInitializeCoreLibrary(base::Optional<base::FilePath> path, + MojoInitializeFlags flags); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_DYNAMIC_LIBRARY_SUPPORT_H_ diff --git a/chromium/mojo/public/cpp/system/functions.cc b/chromium/mojo/public/cpp/system/functions.cc new file mode 100644 index 00000000000..74aee1a0973 --- /dev/null +++ b/chromium/mojo/public/cpp/system/functions.cc @@ -0,0 +1,36 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/system/functions.h" + +#include "base/callback.h" +#include "base/no_destructor.h" + +namespace mojo { + +namespace { + +DefaultProcessErrorHandler& GetDefaultProcessErrorHandler() { + static base::NoDestructor<DefaultProcessErrorHandler> handler; + return *handler; +} + +void HandleError(const MojoProcessErrorDetails* details) { + const DefaultProcessErrorHandler& handler = GetDefaultProcessErrorHandler(); + handler.Run( + std::string(details->error_message, details->error_message_length)); +} + +} // namespace + +void SetDefaultProcessErrorHandler(DefaultProcessErrorHandler handler) { + // Ensure any previously set handler is wiped out. + MojoSetDefaultProcessErrorHandler(nullptr, nullptr); + auto& global_handler = GetDefaultProcessErrorHandler(); + global_handler = std::move(handler); + if (global_handler) + MojoSetDefaultProcessErrorHandler(&HandleError, nullptr); +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/system/functions.h b/chromium/mojo/public/cpp/system/functions.h index 31edf57ab54..4f45ecef20c 100644 --- a/chromium/mojo/public/cpp/system/functions.h +++ b/chromium/mojo/public/cpp/system/functions.h @@ -11,7 +11,11 @@ #ifndef MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ #define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ +#include <string> + +#include "base/callback_forward.h" #include "mojo/public/c/system/functions.h" +#include "mojo/public/cpp/system/system_export.h" namespace mojo { @@ -21,6 +25,17 @@ inline MojoTimeTicks GetTimeTicksNow() { return MojoGetTimeTicksNow(); } +// Sets a callback to handle communication errors regarding peer processes whose +// identity is not explicitly known by this process, i.e. processes that are +// part of the same Mojo process network but which were not invited by this +// process. +// +// This can be used to globally listen for reports of bad incoming IPCs. +using DefaultProcessErrorHandler = + base::RepeatingCallback<void(const std::string& error)>; +void MOJO_CPP_SYSTEM_EXPORT +SetDefaultProcessErrorHandler(DefaultProcessErrorHandler handler); + } // namespace mojo #endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ diff --git a/chromium/mojo/public/cpp/system/handle.h b/chromium/mojo/public/cpp/system/handle.h index 61e1e570a2b..a0bd46ba2aa 100644 --- a/chromium/mojo/public/cpp/system/handle.h +++ b/chromium/mojo/public/cpp/system/handle.h @@ -8,8 +8,8 @@ #include <stdint.h> #include <limits> +#include "base/check_op.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "base/macros.h" #include "mojo/public/c/system/functions.h" #include "mojo/public/c/system/types.h" diff --git a/chromium/mojo/public/cpp/system/message_pipe.h b/chromium/mojo/public/cpp/system/message_pipe.h index 3803a20dee5..92ac7a5fcba 100644 --- a/chromium/mojo/public/cpp/system/message_pipe.h +++ b/chromium/mojo/public/cpp/system/message_pipe.h @@ -16,8 +16,8 @@ #include <vector> +#include "base/check_op.h" #include "base/compiler_specific.h" -#include "base/logging.h" #include "mojo/public/c/system/message_pipe.h" #include "mojo/public/cpp/system/handle.h" #include "mojo/public/cpp/system/message.h" diff --git a/chromium/mojo/public/cpp/system/platform_handle.h b/chromium/mojo/public/cpp/system/platform_handle.h index 297be2263e7..eb69b3e661a 100644 --- a/chromium/mojo/public/cpp/system/platform_handle.h +++ b/chromium/mojo/public/cpp/system/platform_handle.h @@ -15,7 +15,6 @@ #include "base/compiler_specific.h" #include "base/files/platform_file.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/read_only_shared_memory_region.h" #include "base/memory/unsafe_shared_memory_region.h" diff --git a/chromium/mojo/public/cpp/test_support/BUILD.gn b/chromium/mojo/public/cpp/test_support/BUILD.gn index 8bde2beb334..3312a371ba8 100644 --- a/chromium/mojo/public/cpp/test_support/BUILD.gn +++ b/chromium/mojo/public/cpp/test_support/BUILD.gn @@ -12,7 +12,6 @@ static_library("test_utils") { ] deps = [ - "//mojo/core/embedder", "//mojo/public/c/test_support", "//mojo/public/cpp/bindings", "//mojo/public/cpp/system", diff --git a/chromium/mojo/public/cpp/test_support/lib/test_utils.cc b/chromium/mojo/public/cpp/test_support/lib/test_utils.cc index 99e7f708bf2..062106db864 100644 --- a/chromium/mojo/public/cpp/test_support/lib/test_utils.cc +++ b/chromium/mojo/public/cpp/test_support/lib/test_utils.cc @@ -10,7 +10,7 @@ #include <vector> #include "base/bind.h" -#include "mojo/core/embedder/embedder.h" +#include "base/bind_helpers.h" #include "mojo/public/cpp/system/core.h" #include "mojo/public/cpp/system/wait.h" #include "mojo/public/cpp/test_support/test_support.h" @@ -80,13 +80,12 @@ void IterateAndReportPerf(const char* test_name, } BadMessageObserver::BadMessageObserver() : got_bad_message_(false) { - mojo::core::SetDefaultProcessErrorCallback(base::BindRepeating( + mojo::SetDefaultProcessErrorHandler(base::BindRepeating( &BadMessageObserver::OnReportBadMessage, base::Unretained(this))); } BadMessageObserver::~BadMessageObserver() { - mojo::core::SetDefaultProcessErrorCallback( - mojo::core::ProcessErrorCallback()); + mojo::SetDefaultProcessErrorHandler(base::NullCallback()); } std::string BadMessageObserver::WaitForBadMessage() { diff --git a/chromium/mojo/public/interfaces/bindings/pipe_control_messages.mojom b/chromium/mojo/public/interfaces/bindings/pipe_control_messages.mojom index 052e2e6df33..d735567bfc3 100644 --- a/chromium/mojo/public/interfaces/bindings/pipe_control_messages.mojom +++ b/chromium/mojo/public/interfaces/bindings/pipe_control_messages.mojom @@ -38,7 +38,7 @@ struct DisconnectReason { // An event to notify that an interface endpoint set up at the message sender // side has been closed. // -// This event is omitted if the endpoint belongs to the master interface and +// This event is omitted if the endpoint belongs to the primary interface and // there is no disconnect reason specified. struct PeerAssociatedEndpointClosedEvent { // The interface ID. diff --git a/chromium/mojo/public/interfaces/bindings/tests/BUILD.gn b/chromium/mojo/public/interfaces/bindings/tests/BUILD.gn index 1c48f55567d..404d9cf94f4 100644 --- a/chromium/mojo/public/interfaces/bindings/tests/BUILD.gn +++ b/chromium/mojo/public/interfaces/bindings/tests/BUILD.gn @@ -21,8 +21,8 @@ copy("validation_test_data") { "data/validation/associated_conformance_mthd0_good.expected", "data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data", "data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected", - "data/validation/associated_conformance_mthd0_illegal_master_interface_id.data", - "data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected", + "data/validation/associated_conformance_mthd0_illegal_primary_interface_id.data", + "data/validation/associated_conformance_mthd0_illegal_primary_interface_id.expected", "data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data", "data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected", "data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data", diff --git a/chromium/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom b/chromium/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom index b5a7911fdee..b859b33d3e0 100644 --- a/chromium/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom +++ b/chromium/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom @@ -29,7 +29,7 @@ interface TestSync { }; // Test sync method support with associated interfaces. -interface TestSyncMaster { +interface TestSyncPrimary { [Sync] Ping() => (); diff --git a/chromium/mojo/public/js/BUILD.gn b/chromium/mojo/public/js/BUILD.gn index c842406970e..47ee8536397 100644 --- a/chromium/mojo/public/js/BUILD.gn +++ b/chromium/mojo/public/js/BUILD.gn @@ -61,7 +61,7 @@ js_library("bindings_lite_sources") { deps = [ "//mojo/public/interfaces/bindings:bindings_js_library_for_compile" ] } -if (enable_mojom_closure_compile || closure_compile) { +if (enable_mojom_closure_compile || enable_js_type_check) { js_binary("bindings_lite") { outputs = [ bindings_lite_compiled_file ] deps = [ ":bindings_lite_sources" ] diff --git a/chromium/mojo/public/js/bindings.js b/chromium/mojo/public/js/bindings.js index a82f43dddac..30a76104695 100644 --- a/chromium/mojo/public/js/bindings.js +++ b/chromium/mojo/public/js/bindings.js @@ -136,7 +136,7 @@ this.handle_ = null; this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( - this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId)); + this.router_.createLocalEndpointHandle(internal.kPrimaryInterfaceId)); this.interfaceEndpointClient_ .setPayloadValidators([ this.interfaceType_.validateResponse]); @@ -214,7 +214,7 @@ this.stub_ = new this.interfaceType_.stubClass(this.impl_); this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( - this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId), + this.router_.createLocalEndpointHandle(internal.kPrimaryInterfaceId), this.stub_, this.interfaceType_.kVersion); this.interfaceEndpointClient_ .setPayloadValidators([ @@ -345,7 +345,7 @@ // // // A locally-created associated interface pointer can only be used to // // make calls when the corresponding associated request is sent over - // // another interface (either the master interface or another + // // another interface (either the primary interface or another // // associated interface). // var associatedInterfacePtrInfo = new AssociatedInterfacePtrInfo(); // var associatedRequest = makeRequest(interfacePtrInfo); diff --git a/chromium/mojo/public/js/interface_types.js b/chromium/mojo/public/js/interface_types.js index b7085e5a90b..83448a450e9 100644 --- a/chromium/mojo/public/js/interface_types.js +++ b/chromium/mojo/public/js/interface_types.js @@ -7,7 +7,7 @@ // Constants ---------------------------------------------------------------- var kInterfaceIdNamespaceMask = 0x80000000; - var kMasterInterfaceId = 0x00000000; + var kPrimaryInterfaceId = 0x00000000; var kInvalidInterfaceId = 0xFFFFFFFF; // --------------------------------------------------------------------------- @@ -69,8 +69,8 @@ this.interfaceEndpointHandle.reset(reason); }; - function isMasterInterfaceId(interfaceId) { - return interfaceId === kMasterInterfaceId; + function isPrimaryInterfaceId(interfaceId) { + return interfaceId === kPrimaryInterfaceId; } function isValidInterfaceId(interfaceId) { @@ -88,10 +88,10 @@ mojo.InterfaceRequest = InterfaceRequest; mojo.AssociatedInterfacePtrInfo = AssociatedInterfacePtrInfo; mojo.AssociatedInterfaceRequest = AssociatedInterfaceRequest; - internal.isMasterInterfaceId = isMasterInterfaceId; + internal.isPrimaryInterfaceId = isPrimaryInterfaceId; internal.isValidInterfaceId = isValidInterfaceId; internal.hasInterfaceIdNamespaceBitSet = hasInterfaceIdNamespaceBitSet; internal.kInvalidInterfaceId = kInvalidInterfaceId; - internal.kMasterInterfaceId = kMasterInterfaceId; + internal.kPrimaryInterfaceId = kPrimaryInterfaceId; internal.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask; })(); diff --git a/chromium/mojo/public/js/lib/router.js b/chromium/mojo/public/js/lib/router.js index 251f3a97fef..1fa309f731c 100644 --- a/chromium/mojo/public/js/lib/router.js +++ b/chromium/mojo/public/js/lib/router.js @@ -165,10 +165,10 @@ return new internal.InterfaceEndpointHandle(); } - // Unless it is the master ID, |interfaceId| is from the remote side and + // Unless it is the primary ID, |interfaceId| is from the remote side and // therefore its namespace bit is supposed to be different than the value // that this router would use. - if (!internal.isMasterInterfaceId(interfaceId) && + if (!internal.isPrimaryInterfaceId(interfaceId) && this.setInterfaceIdNamespaceBit_ === internal.hasInterfaceIdNamespaceBitSet(interfaceId)) { return new internal.InterfaceEndpointHandle(); @@ -290,7 +290,7 @@ this.updateEndpointStateMayRemove(endpoint, EndpointStateUpdateType.ENDPOINT_CLOSED); - if (!internal.isMasterInterfaceId(interfaceId) || reason) { + if (!internal.isPrimaryInterfaceId(interfaceId) || reason) { this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason); } diff --git a/chromium/mojo/public/js/lib/validator.js b/chromium/mojo/public/js/lib/validator.js index 577dcf00339..f32419e6b5e 100644 --- a/chromium/mojo/public/js/lib/validator.js +++ b/chromium/mojo/public/js/lib/validator.js @@ -323,7 +323,7 @@ if (this.payloadInterfaceIds) { for (var interfaceId of this.payloadInterfaceIds) { if (!internal.isValidInterfaceId(interfaceId) || - internal.isMasterInterfaceId(interfaceId)) { + internal.isPrimaryInterfaceId(interfaceId)) { return validationError.ILLEGAL_INTERFACE_ID; } } diff --git a/chromium/mojo/public/js/test/BUILD.gn b/chromium/mojo/public/js/test/BUILD.gn index 68397db1435..8c0a853223a 100644 --- a/chromium/mojo/public/js/test/BUILD.gn +++ b/chromium/mojo/public/js/test/BUILD.gn @@ -15,7 +15,7 @@ mojom("test_mojom") { ] } -if (enable_mojom_closure_compile || closure_compile) { +if (enable_mojom_closure_compile || enable_js_type_check) { js_binary("compile_test") { outputs = [ "$target_gen_dir/compile_test.js" ] deps = [ ":test_mojom_js_library_for_compile" ] diff --git a/chromium/mojo/public/js/ts/bindings/tests/BUILD.gn b/chromium/mojo/public/js/ts/bindings/tests/BUILD.gn index bafb2d6808a..81873c95bb4 100644 --- a/chromium/mojo/public/js/ts/bindings/tests/BUILD.gn +++ b/chromium/mojo/public/js/ts/bindings/tests/BUILD.gn @@ -11,7 +11,14 @@ mojom("test_interfaces") { sources = [ "constants.test-mojom", "enums.test-mojom", + "export1.test-mojom", + "export2.test-mojom", + "export3.test-mojom", + "export4.test-mojom", + "import.test-mojom", "module.test-mojom", + "other_dir/other_dir.test-mojom", + "structs.test-mojom", ] use_typescript_sources = true } diff --git a/chromium/mojo/public/mojom/base/BUILD.gn b/chromium/mojo/public/mojom/base/BUILD.gn index 7734ccba97e..1e42e12c612 100644 --- a/chromium/mojo/public/mojom/base/BUILD.gn +++ b/chromium/mojo/public/mojom/base/BUILD.gn @@ -10,6 +10,7 @@ mojom_component("base") { "application_state.mojom", "big_buffer.mojom", "big_string.mojom", + "binder.mojom", "file.mojom", "file_error.mojom", "file_info.mojom", @@ -264,6 +265,20 @@ mojom_component("base") { "//mojo/public/cpp/base:shared_typemap_traits", ] }, + { + types = [ + { + mojom = "mojo_base.mojom.TextDirection" + cpp = "::base::i18n::TextDirection" + }, + ] + traits_headers = + [ "//mojo/public/cpp/base/text_direction_mojom_traits.h" ] + traits_public_deps = [ + "//base:i18n", + "//mojo/public/cpp/base:typemap_traits", + ] + }, ] cpp_typemaps = common_typemaps @@ -372,20 +387,6 @@ mojom_component("base") { { types = [ { - mojom = "mojo_base.mojom.TextDirection" - cpp = "::base::i18n::TextDirection" - }, - ] - traits_headers = - [ "//mojo/public/cpp/base/text_direction_mojom_traits.h" ] - traits_public_deps = [ - "//base:i18n", - "//mojo/public/cpp/base:typemap_traits", - ] - }, - { - types = [ - { mojom = "mojo_base.mojom.ThreadPriority" cpp = "::base::ThreadPriority" }, diff --git a/chromium/mojo/public/mojom/base/binder.mojom b/chromium/mojo/public/mojom/base/binder.mojom new file mode 100644 index 00000000000..a8e90e09ad6 --- /dev/null +++ b/chromium/mojo/public/mojom/base/binder.mojom @@ -0,0 +1,18 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo_base.mojom; + +import "mojo/public/mojom/base/generic_pending_receiver.mojom"; + +// A generic interface for anything which can bind arbitrary other interface +// receivers filtered at runtime. +// +// NOTE: This interface may be exposed to external binaries, so all changes MUST +// preserve backward-compatibility. +[Stable] +interface Binder { + // Requests that |receiver| be bound to an appropriate endpoint. + Bind@0(GenericPendingReceiver receiver); +}; diff --git a/chromium/mojo/public/mojom/base/generic_pending_receiver.mojom b/chromium/mojo/public/mojom/base/generic_pending_receiver.mojom index 67dda272651..9008b469bcc 100644 --- a/chromium/mojo/public/mojom/base/generic_pending_receiver.mojom +++ b/chromium/mojo/public/mojom/base/generic_pending_receiver.mojom @@ -10,7 +10,17 @@ module mojo_base.mojom; // This should be used sparingly, in cases where APIs need to dynamically pass // different types of receivers that cannot or should not be known at compile // time. +// +// NOTE: This type may be exposed to external binaries, so all changes MUST +// preserve backward-compatibility. +[Stable] struct GenericPendingReceiver { - string interface_name; - handle<message_pipe> receiving_pipe; + // The name of the interface which defines the messages to be received by + // |receiving_pipe|. + string interface_name@0; + + // A message pipe endpoint which is expected to receive messages defined by + // the interface named by |interface_name| above. This should be bound to + // an implementation of the named interface. + handle<message_pipe> receiving_pipe@1; }; diff --git a/chromium/mojo/public/mojom/base/string16.mojom b/chromium/mojo/public/mojom/base/string16.mojom index dd672baa169..2a3808436b5 100644 --- a/chromium/mojo/public/mojom/base/string16.mojom +++ b/chromium/mojo/public/mojom/base/string16.mojom @@ -9,10 +9,7 @@ import "mojo/public/mojom/base/big_buffer.mojom"; // Corresponds to |base::string16| in base/strings/string16.h // Corresponds to |WTF::String| in // third_party/WebKit/Source/platform/wtf/text/WTFString.h. -// Don't make backwards-incompatible changes to this definition! -// It's used in PageState serialization, so backwards incompatible changes -// would cause stored PageState objects to be un-parseable. Please contact the -// page state serialization owners before making such a change. +[Stable] struct String16 { array<uint16> data; }; diff --git a/chromium/mojo/public/mojom/base/time.mojom b/chromium/mojo/public/mojom/base/time.mojom index 78cde1a1671..64777e56754 100644 --- a/chromium/mojo/public/mojom/base/time.mojom +++ b/chromium/mojo/public/mojom/base/time.mojom @@ -4,6 +4,7 @@ module mojo_base.mojom; +[Stable] struct Time { // The internal value is expressed in terms of microseconds since a fixed but // intentionally unspecified epoch. diff --git a/chromium/mojo/public/tools/bindings/README.md b/chromium/mojo/public/tools/bindings/README.md index 56ee4cc3b39..39c26c0998d 100644 --- a/chromium/mojo/public/tools/bindings/README.md +++ b/chromium/mojo/public/tools/bindings/README.md @@ -407,6 +407,17 @@ interesting attributes supported today. field, enum value, interface method, or method parameter was introduced. See [Versioning](#Versioning) for more details. +**`[Stable]`** +: The `Stable` attribute specifies that a given mojom type or interface + definition can be considered stable over time, meaning it is safe to use for + things like persistent storage or communication between independent + version-skewed binaries. Stable definitions may only depend on builtin mojom + types or other stable definitions, and changes to such definitions MUST + preserve backward-compatibility through appropriate use of versioning. + Backward-compatibility of changes is enforced in the Chromium tree using a + strict presubmit check. See [Versioning](#Versioning) for more details on + backward-compatibility constraints. + **`[EnableIf=value]`** : The `EnableIf` attribute is used to conditionally enable definitions when the mojom is parsed. If the `mojom` target in the GN file does not include diff --git a/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni index d585c48f963..e40fa41f5b9 100644 --- a/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni +++ b/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni @@ -21,7 +21,6 @@ _typemap_imports = [ "//components/typemaps.gni", "//content/browser/typemaps.gni", "//content/public/common/typemaps.gni", - "//fuchsia/mojom/test_typemaps.gni", "//media/capture/mojom/typemaps.gni", "//media/fuchsia/mojom/typemaps.gni", "//media/learning/mojo/public/cpp/typemaps.gni", diff --git a/chromium/mojo/public/tools/bindings/compile_typescript.py b/chromium/mojo/public/tools/bindings/compile_typescript.py index dc41fb4122d..086d5dcfa8e 100644 --- a/chromium/mojo/public/tools/bindings/compile_typescript.py +++ b/chromium/mojo/public/tools/bindings/compile_typescript.py @@ -15,26 +15,11 @@ import node_modules def main(argv): parser = argparse.ArgumentParser() - parser.add_argument('--filelist', required=True) + parser.add_argument('--tsconfig_path', required=True) args = parser.parse_args(argv) - files = [] - with open(args.filelist) as filelist_file: - for line in filelist_file: - for f in line.split(): - files.append(os.path.join(os.getcwd(), f)) - - file_paths = ' '.join(files) - - result = node.RunNode( - [node_modules.PathToTypescript()] + - [ - "--target 'es6'", - "--module 'es6'", - "--lib 'es6, esnext.bigint'", - "--strict", - file_paths - ]) + result = node.RunNode([node_modules.PathToTypescript()] + + ['--project %s' % args.tsconfig_path]) if len(result) != 0: raise RuntimeError('Failed to compile Typescript: \n%s' % result) diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl index 1093c7ff5fe..9ffd65d3460 100644 --- a/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl @@ -20,13 +20,8 @@ enum class {{enum_name}} : int32_t; {{ kythe_annotation(full_enum_name) }} enum class {{enum_name}} : int32_t { {%- for field in enum.fields %} -{%- if field.value %} {{ kythe_annotation("%s.%s"|format(full_enum_name, field.name)) }} - {{field.name}} = {{field.value|expression_to_text}}, -{%- else %} - {{ kythe_annotation("%s.%s"|format(full_enum_name, field.name)) }} - {{field.name}}, -{%- endif %} + {{field.name}} = {{field.numeric_value}}, {%- endfor %} {%- if enum.min_value is not none %} kMinValue = {{enum.min_value}}, diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl index b3525b75ef5..5fab70e5aaa 100644 --- a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl @@ -9,7 +9,6 @@ #ifndef {{header_guard}} #define {{header_guard}} -#include "base/logging.h" #include "base/macros.h" #include "mojo/public/cpp/bindings/lib/bindings_internal.h" #include "mojo/public/cpp/bindings/lib/buffer.h" diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl index f51f9bd81cd..2aeb29fca9b 100644 --- a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl @@ -6,7 +6,6 @@ #include <utility> -#include "base/logging.h" #include "base/stl_util.h" // for base::size() #include "mojo/public/cpp/bindings/lib/validate_params.h" #include "mojo/public/cpp/bindings/lib/validation_context.h" diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl index 71ad8790ee4..8e65847a9c5 100644 --- a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl @@ -24,7 +24,6 @@ #include <utility> #include "base/hash/md5_constexpr.h" -#include "base/logging.h" #include "base/run_loop.h" #include "base/task/common/task_annotator.h" #include "mojo/public/cpp/bindings/lib/generated_code_util.h" diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl index 0daf77b3e6d..e09f00aa569 100644 --- a/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl @@ -1,18 +1,8 @@ -{%- macro enum_value(enum, field, index) -%} -{%- if field.value -%} -{{field.value|expression_to_text('i32')}}; -{%- elif index == 0 -%} -{{field.numeric_value}}; -{%- else -%} -{{field.numeric_value}}; // {{enum.fields[index - 1]|name}} + 1 -{%- endif -%} -{%- endmacro -%} - {%- macro enum_def(enum, top_level) -%} public {{ 'static ' if not top_level }}final class {{enum|name}} { private static final boolean IS_EXTENSIBLE = {% if enum.extensible %}true{% else %}false{% endif %}; {% for field in enum.fields %} - public static final int {{field|name}} = {{enum_value(enum, field, loop.index0)}} + public static final int {{field|name}} = {{field.numeric_value}}; {%- endfor %} {%- if enum|covers_continuous_range %} diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl index 26961956fe7..50341269275 100644 --- a/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl @@ -2,13 +2,7 @@ {{enum_name}} = {}; {%- set prev_enum = 0 %} {%- for field in enum.fields %} -{%- if field.value %} - {{enum_name}}.{{field.name}} = {{field.value|expression_to_text}}; -{%- elif loop.first %} - {{enum_name}}.{{field.name}} = 0; -{%- else %} - {{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1; -{%- endif %} + {{enum_name}}.{{field.name}} = {{field.numeric_value}}; {%- endfor %} {%- if enum.min_value is not none %} {{enum_name}}.MIN_VALUE = {{enum.min_value}}, diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/lite/enum_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/lite/enum_definition.tmpl index affa4d09650..d6561f3b7c8 100644 --- a/chromium/mojo/public/tools/bindings/generators/js_templates/lite/enum_definition.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/lite/enum_definition.tmpl @@ -21,7 +21,7 @@ goog.provide('{{enum_spec_parent}}.{{enum.name}}Spec'); {{enum_name}} = { {# Set up the enum here, but fill out the values later. #} {%- for field in enum.fields %} - {{field.name}}: 0, + {{field.name}}: {{field.numeric_value}}, {%- endfor %} {%- if enum.min_value is not none %} MIN_VALUE: {{enum.min_value}}, @@ -30,16 +30,5 @@ goog.provide('{{enum_spec_parent}}.{{enum.name}}Spec'); MAX_VALUE: {{enum.max_value}}, {%- endif %} }; -{%- for field in enum.fields %} -{# Suppress type checks since we're assigning number into an enum. #} -/** @suppress {checkTypes} */ -{%- if field.value %} -{{enum_name}}.{{field.name}} = {{field.value|expression_to_text_lite}}; -{%- elif loop.first %} -{{enum_name}}.{{field.name}} = 0; -{%- else %} -{{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1; -{%- endif %} -{%- endfor %} {%- endmacro %} diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl new file mode 100644 index 00000000000..e9462281808 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl @@ -0,0 +1,841 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "{{module.path}}-mojolpm.h" + +#include <functional> + +#include "base/no_destructor.h" +#include "base/task/post_task.h" +#include "mojo/public/cpp/bindings/associated_binding_set.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +{% for extra_traits_header in all_extra_traits_headers %} +#include "{{extra_traits_header}}" +{%- endfor %} + +{%- import "mojolpm_macros.tmpl" as util %} +{%- import "mojolpm_from_proto_macros.tmpl" as from_proto %} +{%- import "mojolpm_to_proto_macros.tmpl" as to_proto %} +{%- import "mojolpm_traits_specialization_macros.tmpl" as traits_specialization %} + +namespace mojo { +{%- for struct in structs %} +{{- traits_specialization.define_struct(struct) }} +{%- endfor %} + +{%- for union in unions %} +{{ traits_specialization.define_union(union) }} +{%- endfor %} +} // namespace mojo + +namespace mojolpm { +{%- for enum in all_enums %} +{{- from_proto.define_enum(enum) }} +{{- to_proto.define_enum(enum) }} +{%- endfor %} + +{%- for struct in structs %} +{%- set proto_type = "::mojolpm" ~ (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set struct_type = proto_type ~ "_ProtoStruct" %} +{%- for field in struct.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} +{{- from_proto.define(struct_type, kind, name) }} +{{- to_proto.define(struct_type, kind, name) }} +{%- endif %} +{%- endfor %} +{{- from_proto.define_struct(struct) }} +{{- to_proto.define_struct(struct) }} +{%- endfor %} + +{%- for union in unions %} +{%- set proto_type = "::mojolpm" ~ (union|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set union_type = proto_type ~ "_ProtoUnion" %} +{%- for field in union.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} +{{- from_proto.define(union_type, kind, name)}} +{{- to_proto.define(union_type, kind, name)}} +{%- endif %} +{%- endfor %} +{{- from_proto.define_union(union) }} +{{- to_proto.define_union(union) }} +{%- endfor %} + +{%- for interface in interfaces %} +{%- set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %} +{%- set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +class {{interface.name}}Impl : public {{mojom_type}} { + ::mojo::BindingSet<{{mojom_type}}> bindings_; + ::mojo::AssociatedBindingSet<{{mojom_type}}> associated_bindings_; + + public: + {{interface.name}}Impl() { + } + + void Bind({{mojom_type}}Request&& request) { + DCHECK(mojolpm::GetContext()->task_runner()->RunsTasksInCurrentSequence()); + bindings_.AddBinding(this, std::move(request)); + } + + void Bind({{mojom_type}}AssociatedRequest&& request) { + DCHECK(mojolpm::GetContext()->task_runner()->RunsTasksInCurrentSequence()); + associated_bindings_.AddBinding(this, std::move(request)); + } + +{%- for method in interface.methods -%}{{"\n"}} + void {{method.name}}({{ "\n" }} +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under -%} +{%- set kind = param.kind -%} +{%- set param_mojom_type = kind|cpp_wrapper_param_type(add_same_module_namespaces=true) -%} +{{ ",\n" if not loop.first }} {{param_mojom_type}} {{name}} +{%- endfor -%} +{%- if method.response_parameters != None -%} +{{ ",\n" if method.parameters }} {{mojom_type}}::{{method.name}}Callback callback +{%- endif -%} +) override { +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under -%} +{%- set kind = param.kind -%} +{{ util.add_instance(kind, name, False)|indent(2, True) }} +{%- endfor %} + mojolpmdbg("{{interface.name}}Impl.{{method.name}}\n"); + mojolpm::GetContext()->NextAction(); +{%- if method.response_parameters != None %} + auto mojolpm_response = mojolpm::GetContext()->GetInstance< + {{proto_type}}::{{interface.name}}_{{method.name}}Response>( + mojolpm::GetContext()->NextResponseIndex( + mojolpm::type_id<{{mojom_type}}>())); + + if (!mojolpm_response) { + return; + } + + bool mojolpm_result = true; +{%- for param in method.response_parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %} + {{param_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{param_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for param in method.response_parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(mojolpm_response->m_{{name}}(), local_{{name}}); + mojolpmdbg("{{name}} %i\n", mojolpm_result); +{%- else %} + if (FromProto(mojolpm_response->m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + std::move(callback).Run( +{%- for param in method.response_parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_interface_kind or kind|is_associated_kind %} + {{kind|cpp_wrapper_param_type(add_same_module_namespaces=true)}}(std::move(local_{{name}})){{ ',' if not loop.last }} +{%- else %} + std::move(local_{{name}}){{ ',' if not loop.last }} +{%- endif %} +{%- endfor -%} +); + } +{%- endif %} + } +{%- endfor %} +}; + +bool FromProto(const {{proto_type}}::Ptr& input, + {{mojom_type}}PtrInfo& output) { + bool result = false; + std::unique_ptr<{{mojom_type}}Ptr> output_ptr = nullptr; + + if (input.id()) { + output_ptr = mojolpm::GetContext()->GetAndRemoveInstance<{{mojom_type}}Ptr>(input.id()); + } else { + output_ptr = NewInstance<{{mojom_type}}>(); + } + + if (output_ptr) { + // NB: PassInterface is allowed, since output_ptr is bound on this sequence + // (the fuzzer sequence) + output = output_ptr.release()->PassInterface(); + result = true; + } else { + // we otherwise create a local instance + ::mojo::InterfacePtr<{{mojom_type}}> ptr; + ::mojo::InterfaceRequest<{{mojom_type}}> request = ::mojo::MakeRequest(&ptr); + auto impl = std::make_unique<{{interface.name}}Impl>(); + impl->Bind(std::move(request)); + mojolpm::GetContext()->AddInstance(std::move(impl)); + output = ptr.PassInterface(); + result = true; + } + + return result; +} + +bool FromProto(const {{proto_type}}::AssociatedPtr& input, + {{mojom_type}}AssociatedPtrInfo& output) { + bool result = false; + std::unique_ptr<{{mojom_type}}AssociatedPtr> output_ptr; + + if (input.id()) { + output_ptr = mojolpm::GetContext()->GetAndRemoveInstance<{{mojom_type}}AssociatedPtr>(input.id()); + } else { + output_ptr = NewAssociatedInstance<{{mojom_type}}>(); + } + + if (output_ptr) { + // NB: PassInterface is allowed, since output_ptr is bound on this sequence + // (the fuzzer sequence) + output = output_ptr.release()->PassInterface(); + result = true; + } else { + // we otherwise create a local instance + ::mojo::AssociatedInterfacePtr<{{mojom_type}}> ptr; + ::mojo::AssociatedInterfaceRequest<{{mojom_type}}> request = ::mojo::MakeRequest(&ptr); + auto impl = std::make_unique<{{interface.name}}Impl>(); + impl->Bind(std::move(request)); + mojolpm::GetContext()->AddInstance(std::move(impl)); + output = ptr.PassInterface(); + result = true; + } + + return result; +} + +bool FromProto(const {{proto_type}}::Request& input, + {{mojom_type}}Request& output) { + {{mojom_type}}Ptr ptr; + + output = ::mojo::MakeRequest(&ptr); + mojolpm::GetContext()->AddInstance(input.id(), std::move(ptr)); + + return true; +} + +bool FromProto(const {{proto_type}}::AssociatedRequest& input, + {{mojom_type}}AssociatedRequest& output) { + {{mojom_type}}AssociatedPtr ptr; + + output = ::mojo::MakeRequest(&ptr); + mojolpm::GetContext()->AddInstance(input.id(), std::move(ptr)); + + return true; +} + +bool ToProto({{mojom_type}}PtrInfo&& input, + {{proto_type}}::Ptr& output) { + bool result = false; + + // NB: Not implementing this at present as it only has limited applicability, + // and the corresponding types are being deprecated. If your target needs this + // to fuzz effectively, consider porting to the new mojo types. + NOTREACHED(); + + return result; +} + +bool ToProto({{mojom_type}}Ptr&& input, + {{proto_type}}::Ptr& output) { + bool result = false; + + // NB: Not implementing this at present as it only has limited applicability, + // and the corresponding types are being deprecated. If your target needs this + // to fuzz effectively, consider porting to the new mojo types. + CHECK(false); + + return result; +} + +bool ToProto({{mojom_type}}AssociatedPtrInfo&& input, + {{proto_type}}::AssociatedPtr& output) { + bool result = false; + + // NB: Not implementing this at present as it only has limited applicability, + // and the corresponding types are being deprecated. If your target needs this + // to fuzz effectively, consider porting to the new mojo types. + CHECK(false); + + return result; +} + +bool ToProto({{mojom_type}}Request&& input, + {{proto_type}}::Request& output) { + bool result = false; + + // NB: Not implementing this at present as it only has limited applicability, + // and the corresponding types are being deprecated. If your target needs this + // to fuzz effectively, consider porting to the new mojo types. + CHECK(false); + + return result; +} + +bool ToProto({{mojom_type}}AssociatedRequest&& input, + {{proto_type}}::AssociatedRequest& output) { + bool result = false; + + // NB: Not implementing this at present as it only has limited applicability, + // and the corresponding types are being deprecated. If your target needs this + // to fuzz effectively, consider porting to the new mojo types. + CHECK(false); + + return result; +} + +bool FromProto(const {{proto_type}}::PendingRemote& input, + ::mojo::PendingRemote<{{mojom_type}}>& output) { + bool result = false; + ::mojo::Remote<{{mojom_type}}>* output_ptr = nullptr; + + if (input.id()) { + output_ptr = mojolpm::GetContext()->GetInstance<::mojo::Remote<{{mojom_type}}>>(input.id()); + if (output_ptr) { + // TODO(markbrand): look for a cleaner way to handle this check. + if (output_ptr->internal_state() + && output_ptr->internal_state()->has_pending_callbacks()) { + // not safe to Unbind, so fail instead. + output_ptr = nullptr; + } else { + output = output_ptr->Unbind(); + result = true; + } + } + } else { + auto impl = std::make_unique<{{interface.name}}Impl>(); + auto receiver_ptr = std::make_unique<::mojo::Receiver<{{mojom_type}}>>(impl.get()); + output = receiver_ptr->BindNewPipeAndPassRemote(); + mojolpm::GetContext()->AddInstance(std::move(impl)); + mojolpm::GetContext()->AddInstance(input.id(), std::move(receiver_ptr)); + result = true; + } + + return result; +} + +bool ToProto(::mojo::PendingRemote<{{mojom_type}}>&& input, + {{proto_type}}::PendingRemote& output) { + bool result = false; + + ::mojo::Remote<{{mojom_type}}> remote(std::move(input)); + int next_id = NextId<{{mojom_type}}>(); + output.set_id(AddRemote<{{mojom_type}}>(next_id, std::move(remote))); + + return result; +} + +bool FromProto(const {{proto_type}}::PendingReceiver& input, + ::mojo::PendingReceiver<{{mojom_type}}>& output) { + ::mojo::Remote<{{mojom_type}}> remote = ::mojo::Remote<{{mojom_type}}>(); + + output = remote.BindNewPipeAndPassReceiver(); + + mojolpm::GetContext()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + base::IgnoreResult(&AddRemote<{{mojom_type}}>), + input.id(), + std::move(remote))); + + return true; +} + +bool ToProto(::mojo::PendingReceiver<{{mojom_type}}>&& input, + {{proto_type}}::PendingReceiver& output) { + bool result = true; + + // This should only get called from callbacks into the fuzzer, ie from one of + // the XxxImpls or from a return callback. Since that is the case, we want to + // bind the receiver and store it. + + auto impl = std::make_unique<{{interface.name}}Impl>(); + auto receiver = std::make_unique<::mojo::Receiver<{{mojom_type}}>>( + impl.get(), std::move(input)); + mojolpm::GetContext()->AddInstance(std::move(impl)); + output.set_id(mojolpm::GetContext()->AddInstance(std::move(receiver))); + + return result; +} + +bool FromProto(const {{proto_type}}::PendingAssociatedRemote& input, + ::mojo::PendingAssociatedRemote<{{mojom_type}}>& output) { + mojolpmdbg("PendingAssociatedRemote {{interface.name}}\n"); + bool result = false; + ::mojo::AssociatedRemote<{{mojom_type}}>* output_ptr; + + if (input.id()) { + output_ptr = mojolpm::GetContext()->GetInstance<::mojo::AssociatedRemote<{{mojom_type}}>>(input.id()); + if (output_ptr) { + // TODO(markbrand): look for a cleaner way to handle this check. + if (output_ptr->internal_state() + && output_ptr->internal_state()->has_pending_callbacks()) { + // not safe to Unbind, so fail instead. + output_ptr = nullptr; + } else { + output = output_ptr->Unbind(); + result = true; + } + } + } else { + auto impl = std::make_unique<{{interface.name}}Impl>(); + auto receiver = std::make_unique<::mojo::AssociatedReceiver<{{mojom_type}}>>(impl.get()); + output = receiver->BindNewEndpointAndPassRemote(); + mojolpm::GetContext()->AddInstance(std::move(impl)); + mojolpm::GetContext()->AddInstance(input.id(), std::move(receiver)); + result = true; + } + + return result; +} + +bool ToProto(::mojo::PendingAssociatedRemote<{{mojom_type}}>&& input, + {{proto_type}}::PendingAssociatedRemote& output) { + bool result = true; + + ::mojo::AssociatedRemote<{{mojom_type}}> remote(std::move(input)); + int next_id = NextId<{{mojom_type}}>(); + output.set_id(AddAssociatedRemote(next_id, std::move(remote))); + + return result; +} + +bool FromProto(const {{proto_type}}::PendingAssociatedReceiver& input, + ::mojo::PendingAssociatedReceiver<{{mojom_type}}>& output) { + mojolpmdbg("PendingAssociatedReceiver {{interface.name}}\n"); + ::mojo::AssociatedRemote<{{mojom_type}}> remote = ::mojo::AssociatedRemote<{{mojom_type}}>(); + output = remote.BindNewEndpointAndPassReceiver(); + + mojolpm::GetContext()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + base::IgnoreResult(&AddAssociatedRemote<{{mojom_type}}>), + input.id(), + std::move(remote))); + + return true; +} + +bool ToProto(::mojo::PendingAssociatedReceiver<{{mojom_type}}>&& input, + {{proto_type}}::PendingAssociatedReceiver& output) { + bool result = true; + + // This should only get called from callbacks into the fuzzer, ie from one of + // the XxxImpls or from a return callback. Since that is the case, we want to + // bind the receiver and store it. + + auto impl = std::make_unique<{{interface.name}}Impl>(); + auto receiver = std::make_unique<::mojo::AssociatedReceiver<{{mojom_type}}>>( + impl.get(), std::move(input)); + mojolpm::GetContext()->AddInstance(std::move(impl)); + output.set_id(mojolpm::GetContext()->AddInstance(std::move(receiver))); + + return result; +}{{"\n"-}} + +{%- for method in interface.methods %} +{%- set method_type = proto_type ~ "::" ~ interface.name ~ "_" ~ method.name %} +{%- for param in method.parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_array_kind or kind|is_map_kind -%} +{{ from_proto.define(method_type, kind, name) }} +{{ to_proto.define(method_type, kind, name) }} +{%- endif %} +{%- endfor %} +{%- endfor %} +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} +{%- set method_type = proto_type ~ "::" ~ interface.name ~ "_" ~ method.name ~ "Response" %} +{%- for param in method.response_parameters %} +{%- set name = param.name %} +{%- set kind = param.kind %} +{%- if kind|is_array_kind or kind|is_map_kind -%} +{{- from_proto.define(method_type, kind, name)}} +{{- to_proto.define(method_type, kind, name)}} +{%- endif %} +{%- endfor %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for interface in interfaces %} +{%- set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %} +{%- set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- if interface.methods %} +bool HandleMethodCall(const {{proto_type}}::MethodCall& input) { + bool result = false; + + //mojolpmdbg("HandleMethodCall({{interface.name}})\n"); + + {{mojom_type}}Ptr* instance_ptr; + instance_ptr = mojolpm::GetContext()->GetInstance<{{mojom_type}}Ptr>(input.ptr().id()); + + if (instance_ptr && *instance_ptr) { + result = true; + } + + if (result) { + switch (input.method_case()) { +{%- for method in interface.methods %} + case {{proto_type}}::MethodCall::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: { + result = HandleMethodCall(*instance_ptr, input.{{("m" ~ method.name)|camel_to_under}}()); + } break; +{%- endfor %} +case {{proto_type}}::MethodCall::kReset: { + mojolpm::GetContext()->GetAndRemoveInstance<{{mojom_type}}Ptr>(input.ptr().id()); + } break; + default: { + result = false; + } + } + } + + return result; +} + +bool HandleMethodCallA(const {{proto_type}}::MethodCallA& input) { + bool result = false; + + //mojolpmdbg("HandleMethodCall({{interface.name}})\n"); + + {{mojom_type}}AssociatedPtr* instance_ptr; + instance_ptr = mojolpm::GetContext()->GetInstance<{{mojom_type}}AssociatedPtr>(input.ptr().id()); + + if (instance_ptr && instance_ptr->is_bound() && *instance_ptr) { + result = true; + } + + if (result) { + switch (input.method_case()) { +{%- for method in interface.methods %} + case {{proto_type}}::MethodCallA::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: { + result = HandleMethodCallA(*instance_ptr, input.{{("m" ~ method.name)|camel_to_under}}()); + } break; +{%- endfor %} + case {{proto_type}}::MethodCallA::kReset: { + mojolpm::GetContext()->GetAndRemoveInstance<{{mojom_type}}AssociatedPtr>(input.ptr().id()); + } break; + + default: { + result = false; + } + } + } + + return result; +} + +bool HandleRemoteMethodCall(const {{proto_type}}::RemoteMethodCall& input) { + bool result = false; + + ::mojo::Remote<{{mojom_type}}>* instance_ptr; + instance_ptr = mojolpm::GetContext()->GetInstance<::mojo::Remote<{{mojom_type}}>>(input.remote().id()); + + if (instance_ptr && *instance_ptr && instance_ptr->is_bound()) { + result = true; + } + + if (result) { + switch (input.method_case()) { +{%- for method in interface.methods %} + case {{proto_type}}::RemoteMethodCall::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: { + result = HandleRemoteMethodCall(*instance_ptr, input.{{("m" ~ method.name)|camel_to_under}}()); + } break; +{%- endfor %} +case {{proto_type}}::RemoteMethodCall::kReset: { + mojolpm::GetContext()->GetAndRemoveInstance<::mojo::Remote<{{mojom_type}}>>(input.remote().id()); + } break; + default: { + result = false; + } + } + } + + return result; +} + +bool HandleAssociatedRemoteMethodCall(const {{proto_type}}::AssociatedRemoteMethodCall& input) { + bool result = false; + + ::mojo::AssociatedRemote<{{mojom_type}}>* instance_ptr; + instance_ptr = mojolpm::GetContext()->GetInstance<::mojo::AssociatedRemote<{{mojom_type}}>>(input.remote().id()); + + if (instance_ptr && *instance_ptr && instance_ptr->is_bound()) { + result = true; + } + + if (result) { + switch (input.method_case()) { +{%- for method in interface.methods %} + case {{proto_type}}::AssociatedRemoteMethodCall::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: { + result = HandleAssociatedRemoteMethodCall(*instance_ptr, input.{{("m" ~ method.name)|camel_to_under}}()); + } break; +{%- endfor %} + case {{proto_type}}::AssociatedRemoteMethodCall::kReset: { + mojolpm::GetContext()->GetAndRemoveInstance<::mojo::AssociatedRemote<{{mojom_type}}>>(input.remote().id()); + } break; + + default: { + result = false; + } + } + } + + return result; +} + +bool AddResponse( + const {{proto_type}}::ReceiverResponse& input) { + bool result = true; + switch (input.response_case()) { +{%- for method in interface.methods %} + case {{proto_type}}::ReceiverResponse::k{{("m_" ~ method.name)|under_to_camel(digits_split=True) ~ "Response"}}: { + {{proto_type}}::{{interface.name}}_{{method.name}}Response response_copy; + response_copy.CopyFrom(input.m_{{method.name|camel_to_under}}_response()); + mojolpm::GetContext()->AddInstance< + {{proto_type}}::{{interface.name}}_{{method.name}}Response>( + std::move(response_copy)); + } break; +{%- endfor %} + + default: { + result = false; + } + } + + return result; +}{{"\n"-}} +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} +static void {{interface.name}}_{{method.name}}Callback( +{%- for param in method.response_parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_param_type(add_same_module_namespaces=true) %}{{ ',' if not loop.first }} + {{param_mojom_type}} param_{{name}} +{%- endfor -%} +) { +{%- for param in method.response_parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{{ util.add_instance(kind, 'param_' ~ name, False) }} +{%- endfor %} + mojolpmdbg("{{interface.name}}.{{method.name}}Callback\n"); + mojolpm::GetContext()->NextAction(); +}{{"\n"-}} +{%- endif %} +bool HandleMethodCall({{mojom_type}}Ptr& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input) { + bool mojolpm_result = true; + mojolpmdbg("HandleMethodCall({{interface.name}}::{{method.name}})\n"); +{%- for param in method.parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %} + {{param_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{param_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(input.m_{{name}}(), local_{{name}}); + mojolpmdbg("{{name}} %i\n", mojolpm_result); +{%- else %} + if (FromProto(input.m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + instance->{{method.name}}( +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_interface_kind or kind|is_associated_kind %} + {{kind|cpp_wrapper_param_type(add_same_module_namespaces=true)}}(std::move(local_{{name}})){{ ',' if not loop.last }} +{%- else %} + std::move(local_{{name}}){{ ',' if not loop.last }} +{%- endif %} +{%- endfor -%} +{%- if method.response_parameters != None -%} +{{ ',' if method.parameters }} + base::BindOnce(&{{interface.name}}_{{method.name}}Callback)); + } +{%- else -%} +); + } +{%- endif %} + return mojolpm_result; +} + +bool HandleMethodCallA({{mojom_type}}AssociatedPtr& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input) { + bool mojolpm_result = true; + mojolpmdbg("HandleMethodCallA({{interface.name}}::{{method.name}})\n"); +{%- for param in method.parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %} + {{param_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{param_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(input.m_{{name}}(), local_{{name}}); +{%- else %} + if (FromProto(input.m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + instance->{{method.name}}( +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_interface_kind or kind|is_associated_kind %} + {{kind|cpp_wrapper_param_type(add_same_module_namespaces=true)}}(std::move(local_{{name}})){{ ',' if not loop.last }} +{%- else %} + std::move(local_{{name}}){{ ',' if not loop.last }} +{%- endif %} +{%- endfor -%} +{%- if method.response_parameters != None -%} +{{ ',' if method.parameters }} + base::BindOnce(&{{interface.name}}_{{method.name}}Callback)); + } +{%- else -%} +); + } +{%- endif %} + return mojolpm_result; +} +bool HandleRemoteMethodCall(::mojo::Remote<{{mojom_type}}>& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input) { + bool mojolpm_result = true; + mojolpmdbg("HandleRemoteMethodCall({{interface.name}}::{{method.name}})\n"); +{%- for param in method.parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %} + {{param_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{param_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(input.m_{{name}}(), local_{{name}}); +{%- else %} + if (FromProto(input.m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + instance->{{method.name}}( +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_interface_kind or kind|is_associated_kind %} + {{kind|cpp_wrapper_param_type(add_same_module_namespaces=true)}}(std::move(local_{{name}})){{ ',' if not loop.last }} +{%- else %} + std::move(local_{{name}}){{ ',' if not loop.last }} +{%- endif %} +{%- endfor -%} +{%- if method.response_parameters != None -%} +{{ ',' if method.parameters }} + base::BindOnce(&{{interface.name}}_{{method.name}}Callback)); + } else { + mojolpmdbg("call failed\n"); + } +{%- else -%} +); + } +{%- endif %} + return mojolpm_result; +} + +bool HandleAssociatedRemoteMethodCall( + ::mojo::AssociatedRemote<{{mojom_type}}>& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input) { + bool mojolpm_result = true; + mojolpmdbg("HandleAssociatedRemoteMethodCall({{interface.name}}::{{method.name}})\n"); +{%- for param in method.parameters %} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %} + {{param_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{param_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(input.m_{{name}}(), local_{{name}}); +{%- else %} + if (FromProto(input.m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + instance->{{method.name}}( +{%- for param in method.parameters -%} +{%- set name = param.name|camel_to_under %} +{%- set kind = param.kind %} +{%- if kind|is_interface_kind or kind|is_associated_kind %} + {{kind|cpp_wrapper_param_type(add_same_module_namespaces=true)}}(std::move(local_{{name}})){{ ',' if not loop.last }} +{%- else %} + std::move(local_{{name}}){{ ',' if not loop.last }} +{%- endif %} +{%- endfor -%} +{%- if method.response_parameters != None -%} +{{ ',' if method.parameters }} + base::BindOnce(&{{interface.name}}_{{method.name}}Callback)); + } +{%- else -%} +); + } else { + mojolpmdbg("call failed\n"); + } +{%- endif %} + return mojolpm_result; +}{{"\n"-}} +{%- endfor %} +{%- endif %} +{%- endfor -%} +} // namespace mojolpm diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl new file mode 100644 index 00000000000..16af070194f --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl @@ -0,0 +1,251 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- set header_guard = "%s_MOJOLPM_H_"|format( + module.path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +{%- macro namespace_begin() %} +namespace mojolpm { +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- endmacro %} + +{%- macro namespace_end() %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} +} // namespace mojolpm +{%- endmacro %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include "mojo/public/cpp/bindings/associated_receiver.h" +#include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/tools/fuzzers/mojolpm.h" + +{% for extra_public_header in extra_public_headers %} +#include "{{extra_public_header}}" +{%- endfor %} + +{% for import in imports %} +#include "{{import.path}}-mojolpm.h" +#include "{{import.path}}.h" +{%- endfor %} + +#include "{{module.path}}.mojolpm.pb.h" +#include "{{module.path}}.h" + +{%- import "mojolpm_macros.tmpl" as util %} +{%- import "mojolpm_from_proto_macros.tmpl" as from_proto %} +{%- import "mojolpm_to_proto_macros.tmpl" as to_proto %} + +namespace mojolpm { + +{%- for enum in all_enums %} +{%- set mojom_type = enum|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (enum|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +// enum {{enum.name}} +bool FromProto( + const {{proto_type}}& input, + {{mojom_type}}& output); + +bool ToProto( + const {{mojom_type}}& input, + {{proto_type}}& output); +{% endfor %} + +{%- for struct in structs %} +{%- set mojom_in_type = struct|cpp_wrapper_param_type(add_same_module_namespaces=true) %} +{%- set mojom_out_type = struct|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set maybe_const = "const " if not struct|contains_handles_or_interfaces else "" %} +{%- set proto_type = "::mojolpm" ~ (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set struct_type = proto_type ~ "_ProtoStruct" %} +// struct {{struct.name}} +bool FromProto( + const {{proto_type}}& input, + {{mojom_out_type}}& output); + +bool ToProto( + {{mojom_in_type}} input, + {{proto_type}}& output);{{"\n"-}} +{%- for field in struct.fields %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} +{{- from_proto.declare(struct_type, kind, name) }} +{{- to_proto.declare(struct_type, kind, name) }} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for union in unions %} +{%- set mojom_in_type = union|cpp_wrapper_param_type(add_same_module_namespaces=true) %} +{%- set mojom_out_type = union|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set maybe_const = "const " if not union|contains_handles_or_interfaces else "" %} +{%- set proto_type = "::mojolpm" ~ (union|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set union_type = proto_type ~ "_ProtoUnion" %} +{%- if union|is_native_only_kind %} +#error "Mojo native-only union {{union.name}} - don't think this is possible" +{%- else %} +// union {{union.name}} +bool FromProto( + const {{proto_type}}& input, + {{mojom_out_type}}& output); + +bool ToProto( + {{mojom_in_type}} input, + {{proto_type}}& output);{{"\n"-}} +{%- endif %} +{%- for field in union.fields %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} +{{- from_proto.declare(union_type, kind, name) }} +{{- to_proto.declare(union_type, kind, name) }} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for interface in interfaces %} +{%- set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %} +{%- set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +// interface {{interface.name}} +bool FromProto( + const {{proto_type}}::Ptr& input, + {{mojom_type}}PtrInfo& output); + +bool ToProto( + {{mojom_type}}Ptr&& input, + {{proto_type}}::Ptr& output); + +bool ToProto( + {{mojom_type}}PtrInfo&& input, + {{proto_type}}::Ptr& output); + +bool FromProto( + const {{proto_type}}::AssociatedPtr& input, + {{mojom_type}}AssociatedPtrInfo& output); + +bool ToProto( + {{mojom_type}}AssociatedPtrInfo&& input, + {{proto_type}}::AssociatedPtr& output); + +bool FromProto( + const {{proto_type}}::Request& input, + {{mojom_type}}Request& output); + +bool ToProto( + {{mojom_type}}Request&& input, + {{proto_type}}::Request& output); + +bool FromProto( + const {{proto_type}}::AssociatedRequest& input, + {{mojom_type}}AssociatedRequest& output); + +bool ToProto( + {{mojom_type}}AssociatedRequest&& input, + {{proto_type}}::AssociatedRequest& output); + +bool FromProto( + const {{proto_type}}::PendingRemote& input, + ::mojo::PendingRemote<{{mojom_type}}>& output); + +bool ToProto( + ::mojo::PendingRemote<{{mojom_type}}>&& input, + {{proto_type}}::PendingRemote& output); + +bool FromProto( + const {{proto_type}}::PendingReceiver& input, + ::mojo::PendingReceiver<{{mojom_type}}>& output); + +bool ToProto( + ::mojo::PendingReceiver<{{mojom_type}}>&& input, + {{proto_type}}::PendingReceiver& output); + +bool FromProto( + const {{proto_type}}::PendingAssociatedRemote& input, + ::mojo::PendingAssociatedRemote<{{mojom_type}}>& output); + +bool ToProto( + ::mojo::PendingAssociatedRemote<{{mojom_type}}>&& input, + {{proto_type}}::PendingAssociatedRemote& output); + +bool FromProto( + const {{proto_type}}::PendingAssociatedReceiver& input, + ::mojo::PendingAssociatedReceiver<{{mojom_type}}>& output); + +bool ToProto( + ::mojo::PendingAssociatedReceiver<{{mojom_type}}>&& input, + {{proto_type}}::PendingAssociatedReceiver& output);{{"\n"-}} +{%- for method in interface.methods %} +{%- set method_type = proto_type ~ "::" ~ interface.name ~ "_" ~ method.name %} +{%- for param in method.parameters %} +{%- set name = param.name %} +{%- set kind = param.kind %} +{%- if kind|is_array_kind or kind|is_map_kind -%} +{{- from_proto.declare(method_type, kind, name)}} +{{- to_proto.declare(method_type, kind, name)}} +{%- endif %} +{%- endfor %} +{%- endfor %} +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} +{%- set method_type = proto_type ~ "::" ~ interface.name ~ "_" ~ method.name ~ "Response" %} +{%- for param in method.response_parameters %} +{%- set name = param.name %} +{%- set kind = param.kind %} +{%- if kind|is_array_kind or kind|is_map_kind -%} +{{- from_proto.declare(method_type, kind, name)}} +{{- to_proto.declare(method_type, kind, name)}} +{%- endif %} +{%- endfor %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for interface in interfaces %} +{%- set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %} +{%- set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- if interface.methods %} +bool HandleMethodCall( + const {{proto_type}}::MethodCall& input); + +bool HandleMethodCallA( + const {{proto_type}}::MethodCallA& input); + +bool HandleRemoteMethodCall( + const {{proto_type}}::RemoteMethodCall& input); + +bool HandleAssociatedRemoteMethodCall( + const {{proto_type}}::AssociatedRemoteMethodCall& input); + +bool AddResponse( + const {{proto_type}}::ReceiverResponse& response);{{"\n"-}} +{%- for method in interface.methods %} +bool HandleMethodCall( + {{mojom_type}}Ptr& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input); + +bool HandleMethodCallA( + {{mojom_type}}AssociatedPtr& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input); + +bool HandleRemoteMethodCall( + ::mojo::Remote<{{mojom_type}}>& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input); + +bool HandleAssociatedRemoteMethodCall( + ::mojo::AssociatedRemote<{{mojom_type}}>& instance, + const {{proto_type}}::{{interface.name}}_{{method.name}}& input);{{"\n"-}} +{%- endfor %} +{%- endif %} +{%- endfor -%} +} // namespace mojolpm + +#endif // {{header_guard}} diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl new file mode 100644 index 00000000000..521cdf4e6e8 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl @@ -0,0 +1,406 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +package mojolpm.{{module.mojom_namespace|replace("/",".")}}; + +{% for proto_import in proto_imports %} +import "{{proto_import}}"; +{%- endfor %} + +{%- set module_prefix = "%s"|format(namespaces_as_array|join(".")) %} + +{%- macro optional_or_required(kind) %} +{%- if kind|is_nullable_kind %}optional +{%- else -%}required +{%- endif %} +{%- endmacro -%} + +{%- for enum in enums %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +{%- if enum|is_native_only_kind %} +// WARNING Native only enum support {{enum_name}} is very basic. +enum {{enum.name}} { + {{enum.name}}_0 = 0; + {{enum.name}}_1 = 1; + {{enum.name}}_2 = 2; + {{enum.name}}_3 = 3; + {{enum.name}}_4 = 4; + {{enum.name}}_5 = 5; + {{enum.name}}_6 = 6; + {{enum.name}}_7 = 7; + {{enum.name}}_8 = 8; + {{enum.name}}_9 = 9; + {{enum.name}}_10 = 10; + {{enum.name}}_11 = 11; + {{enum.name}}_12 = 12; + {{enum.name}}_13 = 13; + {{enum.name}}_14 = 14; + {{enum.name}}_15 = 15; +} +{%- else %} + +enum {{enum_name}} { +{%- if enum|has_duplicate_values %} + option allow_alias = true; +{%- endif %} +{%- set i = 0 %} +{%- for field in enum.fields %} +{%- if field.name != "MAX" %} + {{enum.name}}_{{field.name|enum_field_name(enum)}} = {{field.numeric_value}}; +{%- endif %} +{%- endfor %} +} +{%- endif %} +{%- endfor %} + +{%- macro forward_declare_field_types_inner(kind, name) %} +{%- if kind|is_array_kind %} +{%- set entry_name = name ~ "Entry" -%} +{%- if (kind.kind|is_array_kind or kind.kind|is_map_kind) %} +{{forward_declare_field_types_inner(kind.kind, entry_name)}} +{%- else %} + message {{entry_name}} { + {{kind.kind|proto_field_type}} value = 1; + } +{%- endif %} + + message {{name}} { + repeated {{entry_name}} values = 1; + } + +{%- elif kind|is_map_kind %} +{%- set entry_name = name ~ "Entry" %} +{%- set key_name = name ~ "Key" %} +{%- set value_name = name ~ "Value" -%} +{%- if (kind.key_kind|is_array_kind or kind.key_kind|is_map_kind) -%} +{{forward_declare_field_types_inner(kind.key_kind, key_name)}} +{%- else %} + message {{key_name}} { + {{kind.key_kind|proto_field_type}} value = 1; + } +{% endif %} +{%- if (kind.value_kind|is_array_kind or kind.value_kind|is_map_kind) -%} +{{forward_declare_field_types_inner(kind.value_kind, value_name)}} +{%- else %} + message {{value_name}} { + {{kind.value_kind|proto_field_type}} value = 1; + } +{% endif %} + message {{entry_name}} { + required {{key_name}} key = 1; + required {{value_name}} value = 2; + } + + message {{name}} { + repeated {{entry_name}} values = 1; + } +{%- endif %} +{% endmacro %} + +{%- macro forward_declare_field_types(kind, name) %} +{%- if kind|is_array_kind %} +{%- set array_name = (name|under_to_camel) ~ "_Array" -%} +{{ forward_declare_field_types_inner(kind, array_name) }} +{%- elif kind|is_map_kind %} +{%- set map_name = (name|under_to_camel) ~ "_Map" %} +{{ forward_declare_field_types_inner(kind, map_name) }} +{%- endif %} +{% endmacro %} + +{%- for struct in structs %} +{%- set struct_name = struct|get_name_for_kind(flatten_nested_kind=True) %} +{%- if struct|is_native_only_kind %} +message {{struct_name}}_ProtoStruct { + // native-only struct + required uint32 id = 1; + required bytes native_bytes = 2; +} +{%- else %} + +message {{struct_name}}_ProtoStruct { + required uint32 id = 1; +{%- for pf in struct.packed.packed_fields_in_ordinal_order -%} +{%- set name = pf.field.name|camel_to_under %} +{%- set kind = pf.field.kind %} +{%- if (kind|is_array_kind or kind|is_map_kind) -%} +{{forward_declare_field_types(kind, name)}} +{%- endif %} +{%- endfor %} +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set name = pf.field.name|camel_to_under %} +{%- set kind = pf.field.kind %} +{%- if kind|is_array_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Array m_{{name}} = {{name|proto_id(kind)}}; +{%- elif kind|is_map_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Map m_{{name}} = {{name|proto_id(kind)}}; +{%- else %} + {{kind|proto_field_type}} m_{{name}} = {{name|proto_id(kind)}}; +{%- endif %} +{%- endfor %} +} +{%- endif %} + +message {{struct_name}} { +{%- for enum in struct.enums %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +{%- if enum|is_native_only_kind %} + // WARNING Native only enum support {{enum_name}} is very basic. + enum {{enum.name}} { + {{enum.name}}_0 = 0; + {{enum.name}}_1 = 1; + {{enum.name}}_2 = 2; + {{enum.name}}_3 = 3; + {{enum.name}}_4 = 4; + {{enum.name}}_5 = 5; + {{enum.name}}_6 = 6; + {{enum.name}}_7 = 7; + {{enum.name}}_8 = 8; + {{enum.name}}_9 = 9; + {{enum.name}}_10 = 10; + {{enum.name}}_11 = 11; + {{enum.name}}_12 = 12; + {{enum.name}}_13 = 13; + {{enum.name}}_14 = 14; + {{enum.name}}_15 = 15; + } +{%- else %} + + enum {{enum.name}} { +{%- if enum|has_duplicate_values %} + option allow_alias = true; +{%- endif %} +{%- set i = 0 %} +{%- for field in enum.fields %} + {{enum_name}}_{{field.name}} = {{field.numeric_value}}; +{%- endfor %} + } +{%- endif %} +{%- endfor %} + + oneof instance { + uint32 old = 1; + {{struct_name}}_ProtoStruct new = 2; + } +} +{%- endfor %} + +{%- for union in unions %} +{%- set union_name = union|get_name_for_kind(flatten_nested_kind=True) %} +{%- if union|is_native_only_kind %} +// ERROR native-only union kind +{%- else %} + +message {{union_name}}_ProtoUnion { + required uint32 id = 1; +{%- for field in union.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if (kind|is_array_kind or kind|is_map_kind) -%} +{{forward_declare_field_types(kind, name)}} +{%- endif %} +{%- endfor %} + oneof union_member { +{%- for field in union.fields %} +{%- set name = field.name|lower %} +{%- set kind = field.kind %} +{%- if kind|is_array_kind %} + {{name|under_to_camel}}_Array m_{{name}} = {{name|proto_id(kind)}}; +{%- elif kind|is_map_kind %} + {{name|under_to_camel}}_Map m_{{name}} = {{name|proto_id(kind)}}; +{%- else %} + {{kind|proto_field_type(quantified=False)}} m_{{name}} = {{name|proto_id(kind)}}; +{%- endif %} +{%- endfor %} + } +} + +message {{union_name}} { + oneof instance { + uint32 old = 1; + {{union_name}}_ProtoUnion new = 2; + } +} +{%- endif %} +{%- endfor %} + +{%- for interface in interfaces %} + +message {{interface.name}} { + // TODO(markbrand): remove the old types once crbug/955171 is landed. + message Ptr { + required uint32 id = 1; + } + + message AssociatedPtr { + required uint32 id = 1; + } + + message Request { + required uint32 id = 1; + } + + message AssociatedRequest { + required uint32 id = 1; + } + + message PendingRemote { + required uint32 id = 1; + } + + message PendingReceiver { + required uint32 id = 1; + } + + message PendingAssociatedRemote { + required uint32 id = 1; + } + + message PendingAssociatedReceiver { + required uint32 id = 1; + } + + message Reset { + } + +{%- for enum in interface.enums %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +{%- if enum|is_native_only_kind %} + // WARNING Native only enum support {{enum_name}} is very basic. + enum {{enum.name}} { + {{enum.name}}_0 = 0; + {{enum.name}}_1 = 1; + {{enum.name}}_2 = 2; + {{enum.name}}_3 = 3; + {{enum.name}}_4 = 4; + {{enum.name}}_5 = 5; + {{enum.name}}_6 = 6; + {{enum.name}}_7 = 7; + {{enum.name}}_8 = 8; + {{enum.name}}_9 = 9; + {{enum.name}}_10 = 10; + {{enum.name}}_11 = 11; + {{enum.name}}_12 = 12; + {{enum.name}}_13 = 13; + {{enum.name}}_14 = 14; + {{enum.name}}_15 = 15; + } +{%- else %} + + enum {{enum.name}} { +{%- if enum|has_duplicate_values %} + option allow_alias = true; +{%- endif %} +{%- set i = 0 %} +{%- for field in enum.fields %} + {{enum.name}}_{{field.name}} = {{field.numeric_value}}; +{%- endfor %} + } +{%- endif %} +{%- endfor %} + +{%- for method in interface.methods %} + + message {{interface.name}}_{{method.name}} { +{%- for parameter in method.parameters %} +{%- set name = parameter.name|camel_to_under %} +{%- set kind = parameter.kind %} +{%- if (kind|is_array_kind or kind|is_map_kind) -%} +{{forward_declare_field_types(kind, name)|indent(width=2)}} +{%- endif %} +{%- endfor %} +{%- for parameter in method.parameters %} +{%- set name = parameter.name|camel_to_under %} +{%- set kind = parameter.kind %} +{%- if kind|is_array_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Array m_{{name}} = {{name|proto_id(kind)}}; +{%- elif kind|is_map_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Map m_{{name}} = {{name|proto_id(kind)}}; +{%- else %} + {{kind|proto_field_type}} m_{{name}} = {{name|proto_id(kind)}}; +{%- endif %} +{%- endfor %} + } + + message {{interface.name}}_{{method.name}}Response { +{%- if method.response_parameters %} +{%- for parameter in method.response_parameters %} +{%- set name = parameter.name|camel_to_under %} +{%- set kind = parameter.kind %} +{%- if (kind|is_array_kind or kind|is_map_kind) -%} +{{forward_declare_field_types(kind, name)|indent(width=2)}} +{%- endif %} +{%- endfor %} +{%- for parameter in method.response_parameters %} +{%- set name = parameter.name|camel_to_under %} +{%- set kind = parameter.kind %} +{%- if kind|is_array_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Array m_{{name}} = {{name|proto_id(kind)}}; +{%- elif kind|is_map_kind %} + {{optional_or_required(kind)}} {{name|under_to_camel}}_Map m_{{name}} = {{name|proto_id(kind)}}; +{%- else %} + {{kind|proto_field_type}} m_{{name}} = {{name|proto_id(kind)}}; +{%- endif %} +{%- endfor %} +{%- endif %} + } +{%- endfor%} + +{%- if interface.methods|length %} + message MethodCall { + required {{interface.name}}.Ptr ptr = 1; + + oneof method { + Reset reset = 2; +{%- for method in interface.methods %} + {{interface.name}}_{{method.name}} m_{{method.name|camel_to_under}} = {{loop.index + 2}}; +{%- endfor %} + } + } + + message MethodCallA { + required {{interface.name}}.AssociatedPtr ptr = 1; + + oneof method { + Reset reset = 2; +{%- for method in interface.methods %} + {{interface.name}}_{{method.name}} m_{{method.name|camel_to_under}} = {{loop.index + 2}}; +{%- endfor %} + } + } + + message RemoteMethodCall { + required {{interface.name}}.PendingRemote remote = 1; + + oneof method { + Reset reset = 2; +{%- for method in interface.methods %} + {{interface.name}}_{{method.name}} m_{{method.name|camel_to_under}} = {{loop.index + 2}}; +{%- endfor %} + } + } + + message AssociatedRemoteMethodCall { + required {{interface.name}}.PendingAssociatedRemote remote = 1; + + oneof method { + Reset reset = 2; +{%- for method in interface.methods %} + {{interface.name}}_{{method.name}} m_{{method.name|camel_to_under}} = {{loop.index + 2}}; +{%- endfor %} + } + } + + message ReceiverResponse { + oneof response { +{%- for method in interface.methods %} + {{interface.name}}_{{method.name}}Response m_{{method.name|camel_to_under}}_response = {{loop.index + 2}}; +{%- endfor %} + } + } +{%- endif %} +} + +{%- endfor %} diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl new file mode 100644 index 00000000000..6a7c3989e3d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl @@ -0,0 +1,383 @@ +{% import "mojolpm_macros.tmpl" as util %} + + +{%- macro declare_array(type, kind) %} +{%- set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_maybe_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool FromProto( + const {{type}}& input, + std::vector<{{mojom_type}}>& output);{{"\n"-}} +{%- if kind.kind|is_array_kind %} +{{declare_array(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_map_kind %} +{{declare_map(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_nullable_kind %} +bool FromProto( + const {{type}}Entry& input, + {{mojom_maybe_type}}& output);{{"\n"-}} +{%- else %} +bool FromProto( + const {{type}}Entry& input, + {{mojom_type}}& output);{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro declare_map(type, kind) %} +{%- set mojom_key_type = kind.key_kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_maybe_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool FromProto( + const {{type}}& input, + base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>& output);{{"\n"-}} +{%- if kind.key_kind|is_array_kind %} +{{- declare_array(type ~ "Key", kind.key_kind)}} +{%- elif kind.key_kind|is_map_kind %} +{{- declare_map(type ~ "Key", kind.key_kind)}} +{%- else %} +bool FromProto( + const {{type}}Key& input, + {{mojom_key_type}}& output);{{"\n"-}} +{%- endif %} +{%- if kind.value_kind|is_array_kind %} +{{- declare_array(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_map_kind %} +{{- declare_map(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_nullable_kind %} +bool FromProto( + const {{type}}Value& input, + {{mojom_maybe_value_type}}& output);{{"\n"-}} +{%- else %} +bool FromProto( + const {{type}}Value& input, + {{mojom_value_type}}& output);{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro declare(parent_name, kind, name) %} +{%- if kind|is_array_kind %} +{%- set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %} +{{- declare_array(array_type, kind)}} +{%- elif kind|is_map_kind %} +{%- set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %} +{{- declare_map(map_type, kind)}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_array(type, kind) %} +{%- set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_maybe_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool FromProto( + const {{type}}& input, + std::vector<{{mojom_type}}>& output) { + bool result = true; + size_t i = 0; + + output.resize(input.values_size()); + + for (const auto& entry : input.values()) { +{%- if kind.kind|is_nullable_kind %} + {{mojom_type}} value; + {{mojom_maybe_type}} maybe_value; + if (FromProto(entry.value(), maybe_value)) { + value = std::move(maybe_value); + } + output[i++] = std::move(value); +{%- elif kind.kind|is_map_kind or kind.kind|is_array_kind %} + {{mojom_type}} values; + result = FromProto(entry, values); + if (!result) { + break; + } + output[i++] = std::move(values); +{%- else %} + {{mojom_type}} value; + result = FromProto(entry.value(), value); + if (!result) { + break; + } + output[i++] = std::move(value); +{%- endif %} + } + + return result; +}{{"\n"-}} +{%- if kind.kind|is_array_kind %} +{{- define_array(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_map_kind %} +{{- define_map(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_nullable_kind %} +bool FromProto( + const {{type}}Entry& input, + {{mojom_maybe_type}}& output) { + return FromProto(input.value(), output); +}{{"\n"-}} +{%- else %} +bool FromProto( + const {{type}}Entry& input, + {{mojom_type}}& output) { + return FromProto(input.value(), output); +}{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_map(type, kind) %} +{%- set mojom_key_type = kind.key_kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set mojom_maybe_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool FromProto( + const {{type}}& input, + base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>& output) { + bool result = true; + for (const auto& entry : input.values()) { +{%- if kind.value_kind|is_nullable_kind %} + {{mojom_key_type}} key; + {{mojom_value_type}} value; + {{mojom_maybe_value_type}} maybe_value; + + if (FromProto(entry.key(), key)) { + if (FromProto(entry.value(), maybe_value)) { + value = std::move(maybe_value); + } + output.emplace(std::move(key), std::move(value)); + } else { + result = false; + break; + } +{%- else %} + {{mojom_key_type}} key; + {{mojom_value_type}} value; + + if (FromProto(entry.key(), key) + && FromProto(entry.value(), value)) { + output.emplace(std::move(key), std::move(value)); + } else { + result = false; + break; + } +{%- endif %} + } + + return result; +}{{"\n"-}} +{%- if kind.key_kind|is_array_kind %} +{{define_array(type ~ "Key", kind.key_kind)}} +{%- elif kind.key_kind|is_map_kind %} +{{define_map(type ~ "Key", kind.key_kind)}} +{%- else %} +bool FromProto( + const {{type}}Key& input, + {{mojom_key_type}}& output) { + return FromProto(input.value(), output); +}{{"\n"-}} +{%- endif %} +{%- if kind.value_kind|is_array_kind %} +{{- define_array(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_map_kind %} +{{- define_map(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_nullable_kind %} +bool FromProto( + const {{type}}Value& input, + {{mojom_maybe_value_type}}& output) { + return FromProto(input.value(), output); +}{{"\n"-}} +{%- else %} +bool FromProto( + const {{type}}Value& input, + {{mojom_value_type}}& output) { + return FromProto(input.value(), output); +}{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro define(parent_name, kind, name) %} +{%- if kind|is_array_kind %} +{%- set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %} +{{- define_array(array_type, kind)}} +{%- elif kind|is_map_kind %} +{%- set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %} +{{- define_map(map_type, kind)}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_enum(enum) -%} +{%- set mojom_type = enum|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (enum|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set enum_type = enum|get_qualified_name_for_kind(flatten_nested_kind=True) %} +bool FromProto( + const {{proto_type}}& input, + {{mojom_type}}& output) { +{%- if enum|is_native_only_kind or not enum|is_typemapped_kind %} + // This ignores IPC_PARAM_TRAITS for native IPC enums, but internal to the + // fuzzer we don't want the overhead of the serialization layer if we don't + // need it. This doesn't change the actual checks on the receiving end. + output = static_cast<{{mojom_type}}>(input); + return true; +{%- else %} + return mojo::EnumTraits<{{enum_type}}, {{mojom_type}}>::FromMojom( + static_cast<{{enum_type}}>(input), &output); +{%- endif %} +} +{%- endmacro %} + + +{%- macro define_struct(struct) -%} +{%- set mojom_type = struct|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set struct_type = proto_type ~ "_ProtoStruct" %} +bool FromProto( + const {{struct_type}}& input, + {{mojom_type}}& output) { +{%- if struct|is_native_only_kind %} + memset((void*)&output, 0, sizeof(output)); + if (input.native_bytes().size() < sizeof(output)) { + memcpy((void*)&output, input.native_bytes().data(), input.native_bytes().size()); + } else { + memcpy((void*)&output, input.native_bytes().data(), sizeof(output)); + } + return true; +{%- elif struct|is_typemapped_kind %} +{%- set dataview_type = (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) ~ "DataView" %} +{%- set data_type = (struct|get_qualified_name_for_kind(flatten_nested_kind=True, internal=True)) %} + ::mojo::internal::SerializationContext mojolpm_serialization_context; + auto mojolpm_buffer = mojolpm::GetContext()->message().payload_buffer(); + {{data_type}}::BufferWriter mojolpm_writer; + bool result = false; + + ::mojo::internal::Serializer<{{dataview_type}}, const {{struct_type}}>::Serialize( + input, mojolpm_buffer, &mojolpm_writer, &mojolpm_serialization_context); + result = ::mojo::internal::Serializer<{{dataview_type}}, {{mojom_type}}>::Deserialize( + mojolpm_writer.data(), &output, &mojolpm_serialization_context); + + return result; + +{%- elif struct.fields %} + bool mojolpm_result = true; +{%- for field in struct.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set field_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} + {{field_mojom_type}} local_{{name}}; +{%- if kind|is_nullable_kind %} + {{field_maybe_mojom_type}} local_maybe_{{name}}; +{%- endif %} +{%- endfor %} + +{%- for field in struct.fields -%} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if not kind|is_nullable_kind %} + mojolpm_result &= FromProto(input.m_{{name}}(), local_{{name}}); +{%- else %} + if (input.has_m_{{name}}() && FromProto(input.m_{{name}}(), local_maybe_{{name}})) { + local_{{name}} = std::move(local_maybe_{{name}}); + } +{%- endif %} +{%- endfor %} + if (mojolpm_result) { + output = {{struct|get_qualified_name_for_kind(flatten_nested_kind=true)}}::New( +{%- for field in struct.fields -%} +{%- set name = field.name|camel_to_under %} + std::move(local_{{name}}){{ ', ' if not loop.last }} +{%- endfor -%}); + } + + return mojolpm_result; +{%- else %} + output = {{struct|get_qualified_name_for_kind(flatten_nested_kind=true)}}::New(); + return true; +{%- endif %} +} + +bool FromProto( + const {{proto_type}}& input, + {{mojom_type}}& output) { + if (input.instance_case() == {{proto_type}}::kOld) { + {{struct_type}}* old = mojolpm::GetContext()->GetInstance<{{struct_type}}>(input.old()); + if (old) { + return FromProto(*old, output); + } + } else { + return FromProto(input.new_(), output); + } + + return false; +} +{%- endmacro %} + + +{%- macro define_union(union) -%} +{%- set mojom_type = union|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (union|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set union_type = proto_type ~ "_ProtoUnion" %} +bool FromProto( + const {{union_type}}& input, + {{mojom_type}}& output) { +{%- if union|is_typemapped_kind %} +{%- set dataview_type = (union|get_qualified_name_for_kind(flatten_nested_kind=True)) ~ "DataView" %} +{%- set data_type = (union|get_qualified_name_for_kind(flatten_nested_kind=True, internal=True)) %} + ::mojo::internal::SerializationContext mojolpm_serialization_context; + auto mojolpm_buffer = mojolpm::GetContext()->message().payload_buffer(); + {{data_type}}::BufferWriter mojolpm_writer; + + ::mojo::internal::Serializer<{{dataview_type}}, const {{union_type}}>::Serialize( + input, mojolpm_buffer, &mojolpm_writer, false, &mojolpm_serialization_context); + return ::mojo::internal::Serializer<{{dataview_type}}, {{mojom_type}}>::Deserialize( + mojolpm_writer.data(), &output, &mojolpm_serialization_context); +{%- else %} + switch (input.union_member_case()) { +{%- for field in union.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} + case {{union_type}}::k{{("m_" ~ name)|under_to_camel}}: { +{%- if kind|is_nullable_kind %} +{%- set field_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} + {{field_mojom_type}} local_{{name}}; + {{field_maybe_mojom_type}} maybe_local_{{name}}; + if (FromProto(input.m_{{name}}(), maybe_local_{{name}})) { + local_{{name}} = std::move(maybe_local_{{name}}); + } + output = {{union|get_qualified_name_for_kind(flatten_nested_kind=true)}}::New{{name|under_to_camel}}(std::move(local_{{name}})); + return true; +{%- else %} + {{field_mojom_type}} local_{{name}}; + if (FromProto(input.m_{{name}}(), local_{{name}})) { + output = {{union|get_qualified_name_for_kind(flatten_nested_kind=true)}}::New{{name|under_to_camel}}(std::move(local_{{name}})); + return true; + } +{%- endif %} + } break; +{% endfor %} + default: { + return false; + } + } + + return false; +{%- endif %} +} + +bool FromProto( + const {{proto_type}}& input, + {{mojom_type}}& output) { + if (input.instance_case() == {{proto_type}}::kOld) { + {{union_type}}* old = mojolpm::GetContext()->GetInstance<{{union_type}}>(input.old()); + if (old) { + return FromProto(*old, output); + } + } else { + return FromProto(input.new_(), output); + } + + return false; +} +{%- endmacro -%}
\ No newline at end of file diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_macros.tmpl new file mode 100644 index 00000000000..c34ddb32dc3 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_macros.tmpl @@ -0,0 +1,82 @@ +{%- macro not_null(kind, name) %} +{%- if kind|is_typemapped_kind and kind|is_struct_kind and kind|nullable_is_same_kind %} +{%- set data_view = kind|get_qualified_name_for_kind ~ "DataView" %} +{%- set data_type = kind|cpp_wrapper_type(ignore_nullable=True) %} +{%- if data_type|truncate(16, true, '', 0) == '::scoped_refptr<' %} +{{name}} +{%- else %} +!::mojo::internal::CallIsNullIfExists<::mojo::StructTraits<{{data_view}}, {{data_type}}>>({{name}}) +{%- endif %} +{%- elif kind|is_platform_handle_kind -%}{{name}}.is_valid() +{%- else -%}{{name}} +{%- endif %} +{%- endmacro -%} + +{%- macro value(kind, name) %} +{%- if kind|is_nullable_kind and (not kind|nullable_is_same_kind) -%}*{{name}} +{%- else -%}{{name}} +{%- endif %} +{%- endmacro -%} + +{%- macro add_instance(kind, name, nested) %} +{%- if kind|is_array_kind %} + for (auto& {{name}}_iter : {{ value(kind, name) }}) { +{{ add_instance(kind.kind, name ~ '_iter', True)|indent(2, True) }} + } +{%- elif kind|is_map_kind %} + for (auto& {{name}}_iter : {{ value(kind, name) }}) { + auto& {{name}}_key = {{name}}_iter.first; + auto& {{name}}_value = {{name}}_iter.second; +{{ add_instance(kind.key_kind, name ~ "_key", True)|indent(2, True) }} +{{ add_instance(kind.value_kind, name ~ "_value", True)|indent(2, True) }} + } +{%- elif kind|is_interface_kind %} +{%- set mojom_type = kind|get_qualified_name_for_kind(flatten_nested_kind=True) %} + if ({{name}}) { + {{mojom_type}}Ptr tmp_{{name}}(std::move({{name}})); + mojolpm::GetContext()->AddInstance(std::move(tmp_{{name}})); + } +{%- elif kind|is_associated_interface_kind %} +{%- set mojom_type = kind.kind|get_qualified_name_for_kind(flatten_nested_kind=True) %} + if ({{name}}) { + {{mojom_type}}AssociatedPtr tmp_{{name}}(std::move({{name}})); + mojolpm::GetContext()->AddInstance(std::move(tmp_{{name}})); + } +{%- elif kind|is_pending_remote_kind %} +{%- set mojom_type = kind.kind|get_qualified_name_for_kind(flatten_nested_kind=True) %} + if ({{name}}) { + ::mojo::Remote<{{mojom_type}}> tmp_{{name}}(std::move({{name}})); + mojolpm::GetContext()->AddInstance(std::move(tmp_{{name}})); + } +{%- elif kind|is_pending_associated_remote_kind %} +{%- set mojom_type = kind.kind|get_qualified_name_for_kind(flatten_nested_kind=True) %} + if ({{name}}) { + ::mojo::AssociatedRemote<{{mojom_type}}> tmp_{{name}}(std::move({{name}})); + mojolpm::GetContext()->AddInstance(std::move(tmp_{{name}})); + } +{%- else %} +{%- if kind|is_nullable_kind %} +{%- set proto_type = kind|cpp_wrapper_proto_type(add_same_module_namespaces=true) %} + {{proto_type}} tmp_{{name}}; + if ({{ not_null(kind, name) }}) { +{%- if kind|is_move_only_kind %} + if (ToProto(std::move({{ value(kind, name) }}), tmp_{{name}})) { +{%- else %} + if (ToProto({{ value(kind, name) }}, tmp_{{name}})) { +{%- endif %} + mojolpm::GetContext()->AddInstance(tmp_{{name}}); + } + } +{%- else %} +{%- set proto_type = kind|cpp_wrapper_proto_type(add_same_module_namespaces=true) %} + {{proto_type}} tmp_{{name}}; +{%- if kind|is_move_only_kind %} + if (ToProto(std::move({{name}}), tmp_{{name}})) { +{%- else %} + if (ToProto({{name}}, tmp_{{name}})) { +{%- endif %} + mojolpm::GetContext()->AddInstance(tmp_{{name}}); + } +{%- endif %} +{%- endif %} +{%- endmacro %}
\ No newline at end of file diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl new file mode 100644 index 00000000000..e7fe4f52f13 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl @@ -0,0 +1,360 @@ +{% import "mojolpm_macros.tmpl" as util %} + + +{%- macro declare_array(type, kind) %} +{%- set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set maybe_mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool ToProto( +{%- if kind.kind|is_move_only_kind %} + std::vector<{{mojom_type}}>&& input, +{%- else %} + const std::vector<{{mojom_type}}>& input, +{%- endif %} + {{type}}& output);{{"\n"-}} +{%- if kind.kind|is_array_kind %} +{{declare_array(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_map_kind %} +{{declare_map(type ~ "Entry", kind.kind)}} +{%- else %} +bool ToProto( + {{maybe_mojom_type}} input, + {{type}}Entry& output);{{"\n"-}} +{%- endif %} +{%- endmacro %} + +{%- macro declare_map(type, kind) %} +{%- set mojom_key_type = kind.key_kind|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set mojom_value_type = kind.value_kind|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +bool ToProto( +{%- if kind.key_kind|is_move_only_kind or kind.value_kind|is_move_only_kind %} + base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>&& input, +{%- else %} + const base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>& input, +{%- endif %} + {{type}}& output);{{"\n"-}} +{%- if kind.key_kind|is_array_kind %} +{{- declare_array(type ~ "Key", kind.key_kind)}} +{%- elif kind.key_kind|is_map_kind %} +{{- declare_map(type ~ "Key", kind.key_kind)}} +{%- else %} +bool ToProto( + {{mojom_key_type}} input, + {{type}}Key& output);{{"\n"-}} +{%- endif %} +{%- if kind.value_kind|is_array_kind %} +{{- declare_array(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_map_kind %} +{{- declare_map(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_nullable_kind %} +bool ToProto( + {{mojom_value_type}} input, + {{type}}Value& output);{{"\n"-}} +{%- else %} +bool ToProto( + {{mojom_value_type}} input, + {{type}}Value& output);{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro declare(parent_name, kind, name) %} +{%- if kind|is_array_kind %} +{%- set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %} +{{- declare_array(array_type, kind)}} +{%- elif kind|is_map_kind %} +{%- set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %} +{{- declare_map(map_type, kind)}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_array(type, kind) %} +{%- set maybe_const = "const " if not kind.kind|is_move_only_kind else "" %} +{%- set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set maybe_mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} +bool ToProto( +{%- if kind.kind|is_move_only_kind %} + std::vector<{{mojom_type}}>&& input, +{%- else %} + const std::vector<{{mojom_type}}>& input, +{%- endif %} + {{type}}& output) { + bool result = true; + + for ({{maybe_const}}auto& in_value : input) { +{%- if kind.kind|is_nullable_kind %} + {{type}}Entry* out_value = output.mutable_values()->Add(); + if ({{util.not_null(kind.kind, 'in_value')}}) { +{%- if kind.kind|is_move_only_kind %} + ToProto(std::move({{util.value(kind.kind, 'in_value')}}), *out_value); +{%- else %} + ToProto({{util.value(kind.kind, 'in_value')}}, *out_value); +{%- endif %} + } +{%- else %} + {{type}}Entry* out_value = output.mutable_values()->Add(); +{%- if kind.kind|is_move_only_kind %} + ToProto(std::move({{util.value(kind.kind, 'in_value')}}), *out_value); +{%- else %} + ToProto({{util.value(kind.kind, 'in_value')}}, *out_value); +{%- endif %} +{%- endif %} + } + + return result; +}{{"\n"-}} +{%- if kind.kind|is_array_kind %} +{{- define_array(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_map_kind %} +{{- define_map(type ~ "Entry", kind.kind)}} +{%- elif kind.kind|is_nullable_kind %} +bool ToProto( + {{maybe_const}}{{maybe_mojom_type}} input, + {{type}}Entry& output) { +{%- if kind.kind|is_move_only_kind %} + return ToProto(std::move(input), *output.mutable_value()); +{%- else %} + return ToProto(input, *output.mutable_value()); +{%- endif %} +}{{"\n"-}} +{%- else %} +bool ToProto( + {{maybe_const}}{{mojom_type}} input, + {{type}}Entry& output) { +{%- if kind.kind|is_integral_kind or kind.kind|is_enum_kind or kind.kind|is_float_kind or kind.kind|is_double_kind %} + bool mojolpm_result; + {{kind.kind|cpp_wrapper_proto_type(add_same_module_namespaces=true)}} value; + mojolpm_result = ToProto(input, value); + output.set_value(value); + return mojolpm_result; +{%- elif kind.kind|is_move_only_kind %} + return ToProto(std::move(input), *output.mutable_value()); +{%- else %} + return ToProto(input, *output.mutable_value()); +{%- endif %} +}{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_map(type, kind) %} +{%- set maybe_const_key = "const " if not kind.key_kind|is_move_only_kind else "" %} +{%- set mojom_key_type = kind.key_kind|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set maybe_const_value = "const " if not kind.key_kind|is_move_only_kind else "" %} +{%- set mojom_value_type = kind.value_kind|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +bool ToProto( +{%- if kind.key_kind|is_move_only_kind or kind.value_kind|is_move_only_kind %} + base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>&& input, +{%- else %} + const base::flat_map<{{mojom_key_type}}, + {{mojom_value_type}}>& input, +{%- endif %} + {{type}}& output) { + bool result = true; + + for (auto& in_entry : input) { + auto out_entry = output.mutable_values()->Add(); +{%- if kind.key_kind|is_move_only_kind %} + result = ToProto(std::move(in_entry.first), *out_entry->mutable_key()); +{%- else %} + result = ToProto(in_entry.first, *out_entry->mutable_key()); +{%- endif %} + if (!result) { + break; + } +{%- if kind.value_kind|is_nullable_kind %} + if ({{util.not_null(kind.value_kind, 'in_entry.second')}}) { +{%- if kind.value_kind|is_move_only_kind %} + result = ToProto(std::move(*in_entry.second), *out_entry->mutable_value()); +{%- else %} + result = ToProto(*in_entry.second, *out_entry->mutable_value()); +{%- endif %} + } +{%- elif kind.value_kind|is_move_only_kind %} + result = ToProto(std::move(in_entry.second), *out_entry->mutable_value()); +{%- else %} + result = ToProto(in_entry.second, *out_entry->mutable_value()); +{%- endif %} + if (!result) { + break; + } + } + + return result; +}{{"\n"-}} +{%- if kind.key_kind|is_array_kind %} +{{define_array(type ~ "Key", kind.key_kind)}} +{%- elif kind.key_kind|is_map_kind %} +{{define_map(type ~ "Key", kind.key_kind)}} +{%- else %} +bool ToProto( + {{mojom_key_type}} input, + {{type}}Key& output) { +{%- if kind.key_kind|is_integral_kind or kind.key_kind|is_enum_kind or kind.key_kind|is_float_kind or kind.key_kind|is_double_kind %} + bool mojolpm_result; + {{kind.key_kind|cpp_wrapper_proto_type(add_same_module_namespaces=true)}} value; + mojolpm_result = ToProto(input, value); + output.set_value(value); + return mojolpm_result; +{%- elif kind.key_kind|is_move_only_kind %} + return ToProto(std::move(input), *output.mutable_value()); +{%- else %} + return ToProto(input, *output.mutable_value()); +{%- endif %} +}{{"\n"-}} +{%- endif %} +{%- if kind.value_kind|is_array_kind %} +{{- define_array(type ~ "Value", kind.value_kind)}} +{%- elif kind.value_kind|is_map_kind %} +{{- define_map(type ~ "Value", kind.value_kind)}} +{%- else %} +bool ToProto( + {{mojom_value_type}} input, + {{type}}Value& output) { +{%- if kind.value_kind|is_integral_kind or kind.value_kind|is_enum_kind or kind.value_kind|is_float_kind or kind.value_kind|is_double_kind %} + bool mojolpm_result; + {{kind.value_kind|cpp_wrapper_proto_type(add_same_module_namespaces=true)}} value; + mojolpm_result = ToProto(input, value); + output.set_value(value); + return mojolpm_result; +{%- elif kind.value_kind|is_move_only_kind %} + return ToProto(std::move(input), *output.mutable_value()); +{%- else %} + return ToProto(input, *output.mutable_value()); +{%- endif %} +}{{"\n"-}} +{%- endif %} +{%- endmacro %} + + +{%- macro define(parent_name, kind, name) %} +{%- if kind|is_array_kind %} +{%- set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %} +{{- define_array(array_type, kind)}} +{%- elif kind|is_map_kind %} +{%- set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %} +{{- define_map(map_type, kind)}} +{%- endif %} +{%- endmacro %} + + +{%- macro define_enum(enum) -%} +{%- set mojom_type = enum|cpp_wrapper_call_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (enum|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set enum_type = enum|get_qualified_name_for_kind(flatten_nested_kind=True) %} +bool ToProto( + const {{mojom_type}}& input, + {{proto_type}}& output) { +{%- if enum|is_native_only_kind or not enum|is_typemapped_kind %} + // This ignores IPC_PARAM_TRAITS for native IPC enums, but internal to the + // fuzzer we don't want the overhead of the serialization layer if we don't + // need it. This doesn't change the actual checks on the receiving end. + output = static_cast<{{proto_type}}>(input); + return true; +{%- else %} + output = static_cast<{{proto_type}}>( + mojo::EnumTraits<{{enum_type}}, {{mojom_type}}>::ToMojom(input)); + return true; +{%- endif %} +}{{"\n"-}} +{%- endmacro %} + + +{%- macro define_struct(struct) -%} +{%- set mojom_type = struct|cpp_wrapper_param_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set struct_type = proto_type ~ "_ProtoStruct" %} +bool ToProto( + {{mojom_type}} input, + {{proto_type}}& output) { +{%- if struct|is_native_only_kind %} + {{struct_type}}* new_instance = output.mutable_new_(); + new_instance->mutable_native_bytes()->resize(sizeof({{mojom_type}}), 0); + memcpy(&new_instance->mutable_native_bytes()[0], (void*)&input, sizeof({{mojom_type}})); + return true; +{%- elif struct|is_typemapped_kind %} + // TODO(markbrand): ToProto for typemapped struct kind + return false; +{%- elif struct.fields %} + {{struct_type}}* new_instance = output.mutable_new_(); + bool mojolpm_result = true; +{%- for field in struct.fields -%} +{%- set raw_name = field.name %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if kind|is_integral_kind or kind|is_enum_kind or kind|is_float_kind or kind|is_double_kind %} + {{kind|cpp_wrapper_proto_type(add_same_module_namespaces=true)}} tmp_{{name}}; + mojolpm_result &= ToProto(input->{{raw_name}}, tmp_{{name}}); + new_instance->set_m_{{name}}(tmp_{{name}}); +{%- elif kind|is_nullable_kind %} + if ({{util.not_null(kind, 'input->' ~ raw_name)}}) { +{%- if kind|is_move_only_kind %} + mojolpm_result &= ToProto(std::move({{util.value(kind, 'input->' ~ raw_name)}}), *new_instance->mutable_m_{{name}}()); +{%- else %} + mojolpm_result &= ToProto({{util.value(kind, 'input->' ~ raw_name)}}, *new_instance->mutable_m_{{name}}()); +{%- endif %} + } +{%- elif kind|is_move_only_kind %} + mojolpm_result &= ToProto(std::move(input->{{raw_name}}), *new_instance->mutable_m_{{name}}()); +{%- else %} + mojolpm_result &= ToProto(input->{{raw_name}}, *new_instance->mutable_m_{{name}}()); +{%- endif %} +{%- endfor %} + return mojolpm_result; +{%- else %} + output.new_(); + return true; +{%- endif %} +}{{"\n"-}} +{%- endmacro %} + + +{%- macro define_union(union) %} +{%- set mojom_type = union|cpp_wrapper_param_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (union|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set union_type = proto_type ~ "_ProtoUnion" %} +bool ToProto( + {{mojom_type}} input, + {{proto_type}}& output) { +{%- if union|is_typemapped_kind %} + // TODO(markbrand): ToProto for typemapped union kind. + return false; +{%- else %} +{%- set enum_name = (union|get_qualified_name_for_kind(flatten_nested_kind=True, internal=True)) ~ "::" ~ union.name ~ "_Tag" %} + {{union_type}}* new_instance = output.mutable_new_(); + bool mojolpm_result = true; + switch (input->which()) { +{%- for field in union.fields %} +{%- set raw_name = field.name %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} + case {{enum_name}}::{{field.name|upper}}: { +{%- if kind|is_move_only_kind and kind|is_nullable_kind %} + if ({{util.not_null(kind, 'input->get_' ~ raw_name ~ '()')}}) { + mojolpm_result &= ToProto(std::move({{util.value(kind, 'input->get_' ~ raw_name ~ '()')}}), *new_instance->mutable_m_{{name}}()); + } +{%- elif kind|is_move_only_kind %} + mojolpm_result = ToProto(std::move(input->get_{{raw_name}}()), + *(new_instance->mutable_m_{{name}}())); +{%- elif kind|is_nullable_kind %} + if ({{util.not_null(kind, 'input->get_' ~ raw_name ~ '()')}}) { + mojolpm_result &= ToProto({{util.value(kind, 'input->get_' ~ raw_name ~ '()')}}, *new_instance->mutable_m_{{name}}()); + } +{%- elif kind|is_integral_kind or kind|is_enum_kind or kind|is_float_kind or kind|is_double_kind %} + {{kind|cpp_wrapper_proto_type(add_same_module_namespaces=true)}} tmp_{{name}}; + mojolpm_result &= ToProto(input->get_{{raw_name}}(), tmp_{{name}}); + new_instance->set_m_{{name}}(tmp_{{name}}); +{%- else %} + mojolpm_result = ToProto(input->get_{{name}}(), + *(new_instance->mutable_m_{{name}}())); +{%- endif %} + } break; +{%- endfor %} + } + return mojolpm_result; +{%- endif %} +}{{"\n"-}} +{%- endmacro %} diff --git a/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl new file mode 100644 index 00000000000..7eeddd7ada4 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl @@ -0,0 +1,96 @@ +{% import "mojolpm_macros.tmpl" as util %} + +{%- macro define_struct(struct) %} +{%- set mojom_type = struct|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set struct_type = proto_type ~ "_ProtoStruct" %} +{%- set dataview_type = (struct|get_qualified_name_for_kind(flatten_nested_kind=True)) ~ "DataView" %} +template <> +struct StructTraits<{{dataview_type}}, {{struct_type}}> { +{%- for field in struct.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if field.kind|is_nullable_kind and not field.kind|nullable_is_same_kind %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set field_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} + static {{field_mojom_type}} {{field.name}}( + const {{struct_type}}& input) { + {{field_mojom_type}} maybe_local_{{name}}; + {{field_maybe_mojom_type}} local_{{name}}; + if (input.has_m_{{name}}() && mojolpm::FromProto( + input.m_{{name}}(), + local_{{name}})) { + maybe_local_{{name}} = std::move(local_{{name}}); + } + return maybe_local_{{name}}; + }{{ "\n" }} +{%- else %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} + static {{field_mojom_type}} {{field.name}}( + const {{struct_type}}& input) { + {{field_mojom_type}} local_{{name}}; + (void) mojolpm::FromProto( + input.m_{{name}}(), + local_{{name}}); + return local_{{name}}; + }{{ "\n" }} +{%- endif %} +{%- endfor -%} +}; +{% endmacro -%} + +{%- macro define_union(union) -%} +{%- set mojom_type = union|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set proto_type = "::mojolpm" ~ (union|get_qualified_name_for_kind(flatten_nested_kind=True)) %} +{%- set union_type = proto_type ~ "_ProtoUnion" %} +{%- if union|is_typemapped_kind %} +{%- set dataview_type = (union|get_qualified_name_for_kind(flatten_nested_kind=True)) ~ "DataView" %} +template<> +struct UnionTraits<{{dataview_type}}, {{union_type}}> { + static {{dataview_type}}::Tag GetTag( + const {{union_type}}& input) { + switch (input.union_member_case()) { +{%- for field in union.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} + case {{union_type}}::k{{("m_" ~ name)|under_to_camel}}: + return {{dataview_type}}::Tag::{{name|upper}}; +{%- endfor %} + default: + NOTREACHED(); + return static_cast<{{dataview_type}}::Tag>(0); + } + }{{"\n"}} +{%- for field in union.fields %} +{%- set name = field.name|camel_to_under %} +{%- set kind = field.kind %} +{%- if field.kind|is_nullable_kind and not field.kind|nullable_is_same_kind %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} +{%- set field_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=true) %} + static {{field_mojom_type}} {{field.name}}( + const {{union_type}}& input) { + {{field_mojom_type}} maybe_local_{{name}}; + {{field_maybe_mojom_type}} local_{{name}}; + if (mojolpm::FromProto( + input.m_{{name}}(), + local_{{name}})) { + maybe_local_{{name}} = std::move(local_{{name}}); + } + return maybe_local_{{name}}; + }{{ "\n" }} +{%- else %} +{%- set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %} + static {{field_mojom_type}} {{field.name}}( + const {{union_type}}& input) { + {{field_mojom_type}} local_{{name}}; + (void) mojolpm::FromProto( + input.m_{{name}}(), + local_{{name}}); + return local_{{name}}; + }{{ "\n" }} +{%- endif %} +{%- endfor -%} +}; +{% endif %} +{% endmacro -%}
\ No newline at end of file diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py index 90e559eba13..a0ce5540841 100644 --- a/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py +++ b/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py @@ -33,6 +33,8 @@ _kind_to_cpp_literal_suffix = { } + + class _NameFormatter(object): """A formatter for the names of kinds or values.""" @@ -558,12 +560,20 @@ class Generator(generator.Generator): for cpp_template_path in self.extra_cpp_template_paths: path_to_template, filename = os.path.split(cpp_template_path) filename_without_tmpl_suffix = filename.rstrip(".tmpl") - self.WriteWithComment(self._GenerateModuleFromImportedTemplate(path_to_template, filename), - "%s%s-%s" % (self.module.path, suffix, filename_without_tmpl_suffix)) + self.WriteWithComment( + self._GenerateModuleFromImportedTemplate(path_to_template, + filename), "%s%s-%s" % + (self.module.path, suffix, filename_without_tmpl_suffix)) def _ConstantValue(self, constant): return self._ExpressionToText(constant.value, kind=constant.kind) + def _UnderToCamel(self, value, digits_split=False): + # There are some mojom files that don't use snake_cased names, so we try to + # fix that to get more consistent output. + return generator.ToCamel(generator.ToLowerSnakeCase(value), + digits_split=digits_split) + def _DefaultValue(self, field): if not field.default: return "" @@ -658,8 +668,13 @@ class Generator(generator.Generator): GetCppPodType(constant.kind), constant.name, self._ConstantValue(constant)) - def _GetCppWrapperType(self, kind, add_same_module_namespaces=False): + def _GetCppWrapperType(self, + kind, + add_same_module_namespaces=False, + ignore_nullable=False): def _AddOptional(type_name): + if ignore_nullable: + return type_name return "base::Optional<%s>" % type_name if self._IsTypemappedKind(kind): @@ -700,16 +715,16 @@ class Generator(generator.Generator): return "%sRequest" % self._GetNameForKind( kind.kind, add_same_module_namespaces=add_same_module_namespaces) if mojom.IsPendingRemoteKind(kind): - return "mojo::PendingRemote<%s>" % self._GetNameForKind( + return "::mojo::PendingRemote<%s>" % self._GetNameForKind( kind.kind, add_same_module_namespaces=add_same_module_namespaces) if mojom.IsPendingReceiverKind(kind): - return "mojo::PendingReceiver<%s>" % self._GetNameForKind( + return "::mojo::PendingReceiver<%s>" % self._GetNameForKind( kind.kind, add_same_module_namespaces=add_same_module_namespaces) if mojom.IsPendingAssociatedRemoteKind(kind): - return "mojo::PendingAssociatedRemote<%s>" % self._GetNameForKind( + return "::mojo::PendingAssociatedRemote<%s>" % self._GetNameForKind( kind.kind, add_same_module_namespaces=add_same_module_namespaces) if mojom.IsPendingAssociatedReceiverKind(kind): - return "mojo::PendingAssociatedReceiver<%s>" % self._GetNameForKind( + return "::mojo::PendingAssociatedReceiver<%s>" % self._GetNameForKind( kind.kind, add_same_module_namespaces=add_same_module_namespaces) if mojom.IsAssociatedInterfaceKind(kind): return "%sAssociatedPtrInfo" % self._GetNameForKind( @@ -724,17 +739,17 @@ class Generator(generator.Generator): return (_AddOptional(type_name) if mojom.IsNullableKind(kind) else type_name) if mojom.IsGenericHandleKind(kind): - return "mojo::ScopedHandle" + return "::mojo::ScopedHandle" if mojom.IsDataPipeConsumerKind(kind): - return "mojo::ScopedDataPipeConsumerHandle" + return "::mojo::ScopedDataPipeConsumerHandle" if mojom.IsDataPipeProducerKind(kind): - return "mojo::ScopedDataPipeProducerHandle" + return "::mojo::ScopedDataPipeProducerHandle" if mojom.IsMessagePipeKind(kind): - return "mojo::ScopedMessagePipeHandle" + return "::mojo::ScopedMessagePipeHandle" if mojom.IsSharedBufferKind(kind): - return "mojo::ScopedSharedBufferHandle" + return "::mojo::ScopedSharedBufferHandle" if mojom.IsPlatformHandleKind(kind): - return "mojo::PlatformHandle" + return "::mojo::PlatformHandle" if not kind in _kind_to_cpp_type: raise Exception("Unrecognized kind %s" % kind.spec) return _kind_to_cpp_type[kind] @@ -769,19 +784,23 @@ class Generator(generator.Generator): return ((not mojom.IsReferenceKind(kind)) or self._IsMoveOnlyKind(kind) or self._IsCopyablePassByValue(kind)) - def _GetCppWrapperCallType(self, kind): + def _GetCppWrapperCallType(self, kind, add_same_module_namespaces=False): # TODO: Remove this once interfaces are always passed as PtrInfo. if mojom.IsInterfaceKind(kind): - return "%sPtr" % self._GetNameForKind(kind) - return self._GetCppWrapperType(kind) + return "%sPtr" % self._GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + return self._GetCppWrapperType( + kind, add_same_module_namespaces=add_same_module_namespaces) - def _GetCppWrapperParamType(self, kind): + def _GetCppWrapperParamType(self, kind, add_same_module_namespaces=False): # TODO: Remove all usage of this method in favor of # _GetCppWrapperParamTypeNew. This requires all generated code which passes # interface handles to use PtrInfo instead of Ptr. if mojom.IsInterfaceKind(kind): - return "%sPtr" % self._GetNameForKind(kind) - cpp_wrapper_type = self._GetCppWrapperType(kind) + return "%sPtr" % self._GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + cpp_wrapper_type = self._GetCppWrapperType( + kind, add_same_module_namespaces=add_same_module_namespaces) return (cpp_wrapper_type if self._ShouldPassParamByValue(kind) else "const %s&" % cpp_wrapper_type) @@ -937,8 +956,7 @@ class Generator(generator.Generator): if param_counts[-1] != version.num_fields: param_counts.append(version.num_fields) - ordinal_fields = sorted( - struct.fields, key=lambda field: field.ordinal or "") + ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal) return (StructConstructor(struct.fields, ordinal_fields[:param_count]) for param_count in param_counts) diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_mojolpm_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_mojolpm_generator.py new file mode 100644 index 00000000000..6b7271114a4 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojom_mojolpm_generator.py @@ -0,0 +1,517 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generates C++ source files from a mojom.Module.""" +import os +import sys +from mojom_cpp_generator import _NameFormatter as CppNameFormatter +from mojom_cpp_generator import Generator as CppGenerator +from mojom_cpp_generator import IsNativeOnlyKind, NamespaceToArray +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +from mojom.generate.template_expander import UseJinja, UseJinjaForImportedTemplate + +_kind_to_proto_type = { + mojom.BOOL: "bool", + mojom.INT8: "int32", + mojom.UINT8: "uint32", + mojom.INT16: "int32", + mojom.UINT16: "uint32", + mojom.INT32: "int32", + mojom.UINT32: "uint32", + mojom.FLOAT: "float", + mojom.INT64: "int64", + mojom.UINT64: "uint64", + mojom.DOUBLE: "double", +} + +_kind_to_cpp_proto_type = { + mojom.BOOL: "bool", + mojom.INT8: "::google::protobuf::int32", + mojom.UINT8: "::google::protobuf::uint32", + mojom.INT16: "::google::protobuf::int32", + mojom.UINT16: "::google::protobuf::uint32", + mojom.INT32: "::google::protobuf::int32", + mojom.UINT32: "::google::protobuf::uint32", + mojom.FLOAT: "float", + mojom.INT64: "::google::protobuf::int64", + mojom.UINT64: "::google::protobuf::int64", + mojom.DOUBLE: "double", +} + + +def _IsStrOrUnicode(x): + if sys.version_info[0] < 3: + return isinstance(x, (unicode, str)) + return isinstance(x, str) + + +class _NameFormatter(CppNameFormatter): + """A formatter for the names of kinds or values.""" + + def __init__(self, *args, **kwargs): + super(_NameFormatter, self).__init__(*args, **kwargs) + + def FormatForProto(self, + omit_namespace_for_module=None, + flatten_nested_kind=False): + return self.Format(".", + prefixed=True, + omit_namespace_for_module=omit_namespace_for_module, + flatten_nested_kind=flatten_nested_kind) + + +class Generator(CppGenerator): + def __init__(self, *args, **kwargs): + super(Generator, self).__init__(*args, **kwargs) + self.needs_mojolpm_proto = False + self.enum_name_cache = dict() + + def _GetAllExtraTraitsHeaders(self): + extra_headers = set() + for typemap in self._GetAllTypemaps(): + extra_headers.update(typemap.get("traits_headers", [])) + extra_headers.update(self._GetExtraTraitsHeaders()) + return sorted(extra_headers) + + def _GetAllTypemaps(self): + """Returns the typemaps for types needed in this module. + """ + all_typemaps = [] + seen_types = set() + + def AddKind(kind): + if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) + or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) + or mojom.IsAnyHandleKind(kind) or mojom.IsInterfaceKind(kind) + or mojom.IsInterfaceRequestKind(kind) or mojom.IsAssociatedKind(kind) + or mojom.IsPendingRemoteKind(kind) + or mojom.IsPendingReceiverKind(kind)): + pass + elif mojom.IsArrayKind(kind): + AddKind(kind.kind) + elif mojom.IsMapKind(kind): + AddKind(kind.key_kind) + AddKind(kind.value_kind) + else: + name = self._GetFullMojomNameForKind(kind) + if name in seen_types: + return + seen_types.add(name) + + typemap = self.typemap.get(name, None) + if typemap: + all_typemaps.append(typemap) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + for field in kind.fields: + AddKind(field.kind) + + for kind in self.module.enums + self.module.structs + self.module.unions: + AddKind(kind) + + return all_typemaps + + def _ProtoImports(self): + """Scans all of the types used in this module to check which of the imports + are needed for the generated proto files. This is somewhat different to the + general case, since the generated proto files don't reference response + parameters. + """ + all_imports = self.module.imports + seen_imports = set() + seen_types = set() + + def AddKind(kind): + if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) + or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind)): + pass + elif (mojom.IsAnyHandleKind(kind)): + self.needs_mojolpm_proto = True + elif mojom.IsArrayKind(kind): + AddKind(kind.kind) + elif mojom.IsMapKind(kind): + AddKind(kind.key_kind) + AddKind(kind.value_kind) + elif (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind) + or mojom.IsEnumKind(kind) or mojom.IsInterfaceKind(kind)): + name = self._GetFullMojomNameForKind(kind) + if name in seen_types: + return + seen_types.add(name) + if kind.module in all_imports: + seen_imports.add(kind.module) + elif (mojom.IsInterfaceRequestKind(kind) + or mojom.IsAssociatedInterfaceKind(kind) + or mojom.IsAssociatedInterfaceRequestKind(kind) + or mojom.IsPendingRemoteKind(kind) + or mojom.IsPendingReceiverKind(kind) + or mojom.IsPendingAssociatedRemoteKind(kind) + or mojom.IsPendingAssociatedReceiverKind(kind)): + AddKind(kind.kind) + + for kind in self.module.structs + self.module.unions: + for field in kind.fields: + AddKind(field.kind) + + for interface in self.module.interfaces: + for method in interface.methods: + for parameter in method.parameters: + AddKind(parameter.kind) + if method.response_parameters: + for parameter in method.response_parameters: + AddKind(parameter.kind) + + import_files = list( + map(lambda x: '{}.mojolpm.proto'.format(x.path), seen_imports)) + if self.needs_mojolpm_proto: + import_files.append('mojolpm.proto') + import_files.sort() + + return import_files + + def _GetJinjaExports(self): + all_enums = list(self.module.enums) + for struct in self.module.structs: + all_enums.extend(struct.enums) + for interface in self.module.interfaces: + all_enums.extend(interface.enums) + + return { + "all_enums": all_enums, + "all_extra_traits_headers": self._GetAllExtraTraitsHeaders(), + "enums": self.module.enums, + "extra_public_headers": self._GetExtraPublicHeaders(), + "extra_traits_headers": self._GetExtraTraitsHeaders(), + "imports": self.module.imports, + "interfaces": self.module.interfaces, + "module": self.module, + "module_namespace": self.module.namespace, + "namespaces_as_array": NamespaceToArray(self.module.namespace), + "proto_imports": self._ProtoImports(), + "structs": self.module.structs, + "unions": self.module.unions, + } + + @staticmethod + def GetTemplatePrefix(): + return "mojolpm_templates" + + def GetFilters(self): + cpp_filters = { + "camel_to_under": generator.ToLowerSnakeCase, + "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces, + "cpp_wrapper_call_type": self._GetCppWrapperCallType, + "cpp_wrapper_param_type": self._GetCppWrapperParamType, + "cpp_wrapper_proto_type": self._GetCppWrapperProtoType, + "cpp_wrapper_type": self._GetCppWrapperType, + "enum_field_name": self._EnumFieldName, + "get_name_for_kind": self._GetNameForKind, + "get_qualified_name_for_kind": self._GetQualifiedNameForKind, + "has_duplicate_values": self._EnumHasDuplicateValues, + "nullable_is_same_kind": self._NullableIsSameKind, + "proto_field_type": self._GetProtoFieldType, + "proto_id": self._GetProtoId, + "is_array_kind": mojom.IsArrayKind, + "is_enum_kind": mojom.IsEnumKind, + "is_double_kind": mojom.IsDoubleKind, + "is_float_kind": mojom.IsFloatKind, + "is_integral_kind": mojom.IsIntegralKind, + "is_interface_kind": mojom.IsInterfaceKind, + "is_receiver_kind": self._IsReceiverKind, + "is_pending_associated_receiver_kind": + mojom.IsPendingAssociatedReceiverKind, + "is_pending_receiver_kind": mojom.IsPendingReceiverKind, + "is_pending_associated_remote_kind": + mojom.IsPendingAssociatedRemoteKind, + "is_pending_remote_kind": mojom.IsPendingRemoteKind, + "is_platform_handle_kind": mojom.IsPlatformHandleKind, + "is_associated_interface_kind": mojom.IsAssociatedInterfaceKind, + "is_native_only_kind": IsNativeOnlyKind, + "is_any_handle_kind": mojom.IsAnyHandleKind, + "is_any_interface_kind": mojom.IsAnyInterfaceKind, + "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind, + "is_associated_kind": mojom.IsAssociatedKind, + "is_float_kind": mojom.IsFloatKind, + "is_hashable": self._IsHashableKind, + "is_map_kind": mojom.IsMapKind, + "is_move_only_kind": self._IsMoveOnlyKind, + "is_nullable_kind": mojom.IsNullableKind, + "is_object_kind": mojom.IsObjectKind, + "is_reference_kind": mojom.IsReferenceKind, + "is_string_kind": mojom.IsStringKind, + "is_struct_kind": mojom.IsStructKind, + "is_typemapped_kind": self._IsTypemappedKind, + "is_union_kind": mojom.IsUnionKind, + "under_to_camel": self._UnderToCamel, + } + return cpp_filters + + @UseJinja("mojolpm.proto.tmpl") + def _GenerateMojolpmProto(self): + return self._GetJinjaExports() + + @UseJinja("mojolpm.h.tmpl") + def _GenerateMojolpmHeader(self): + return self._GetJinjaExports() + + @UseJinja("mojolpm.cc.tmpl") + def _GenerateMojolpmSource(self): + return self._GetJinjaExports() + + def GenerateFiles(self, args): + self.module.Stylize(generator.Stylizer()) + + if self.generate_non_variant_code: + self.WriteWithComment(self._GenerateMojolpmProto(), + "%s.mojolpm.proto" % self.module.path) + else: + self.WriteWithComment(self._GenerateMojolpmHeader(), + "%s-mojolpm.h" % self.module.path) + self.WriteWithComment(self._GenerateMojolpmSource(), + "%s-mojolpm.cc" % self.module.path) + + def _GetCppProtoNameForKind(self, + kind, + flatten_nested_kind=False, + add_same_module_namespaces=False): + name = _NameFormatter(kind, self.variant).FormatForCpp( + flatten_nested_kind=flatten_nested_kind, + omit_namespace_for_module=(None if add_same_module_namespaces else + self.module)) + if name.startswith('::'): + name = 'mojolpm' + name + return name + + def _GetProtoNameForKind(self, + kind, + flatten_nested_kind=False, + add_same_module_namespaces=False): + name = _NameFormatter(kind, self.variant).FormatForProto( + flatten_nested_kind=flatten_nested_kind, + omit_namespace_for_module=(None if add_same_module_namespaces else + self.module)) + if name.startswith('.'): + name = 'mojolpm' + name + return name + + def _IsMoveOnlyKind(self, kind): + if self._IsTypemappedKind(kind): + if mojom.IsEnumKind(kind): + return False + return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"] + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return True + if mojom.IsArrayKind(kind): + return self._IsMoveOnlyKind(kind.kind) + if mojom.IsMapKind(kind): + return (self._IsMoveOnlyKind(kind.value_kind) + or self._IsMoveOnlyKind(kind.key_kind)) + if mojom.IsAnyHandleOrInterfaceKind(kind): + return True + return False + + def _GetNativeTypeName(self, typemapped_kind): + return self.typemap[self._GetFullMojomNameForKind( + typemapped_kind)]["typename"] + + def _FormatConstantDeclaration(self, constant, nested=False): + if mojom.IsStringKind(constant.kind): + if nested: + return "const char %s[]" % constant.name + return "%sextern const char %s[]" % \ + ((self.export_attribute + " ") if self.export_attribute else "", + constant.name) + return "constexpr %s %s = %s" % (GetCppPodType( + constant.kind), constant.name, self._ConstantValue(constant)) + + def _IsMoveOnlyKind(self, kind): + if self._IsTypemappedKind(kind): + if mojom.IsEnumKind(kind): + return False + return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"] + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return True + if mojom.IsArrayKind(kind): + return self._IsMoveOnlyKind(kind.kind) + if mojom.IsMapKind(kind): + return (self._IsMoveOnlyKind(kind.value_kind) + or self._IsMoveOnlyKind(kind.key_kind)) + if mojom.IsAnyHandleOrInterfaceKind(kind): + return True + return False + + def _GetCppWrapperProtoType(self, kind, add_same_module_namespaces=False): + if (mojom.IsEnumKind(kind) or mojom.IsStructKind(kind) + or mojom.IsUnionKind(kind)): + return self._GetCppProtoNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsInterfaceKind(kind): + return '%s::Ptr' % self._GetCppProtoNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsInterfaceRequestKind(kind): + return '%s::Request' % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsAssociatedInterfaceKind(kind): + return '%s::AssociatedPtr' % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsAssociatedInterfaceRequestKind(kind): + return '%s::AssociatedRequest' % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsPendingRemoteKind(kind): + return "%s::PendingRemote" % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsPendingReceiverKind(kind): + return "%s::PendingReceiver" % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsPendingAssociatedRemoteKind(kind): + return "%s::PendingAssociatedRemote" % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsPendingAssociatedReceiverKind(kind): + return "%s::PendingAssociatedReceiver" % self._GetCppProtoNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + elif mojom.IsStringKind(kind): + return "std::string" + elif mojom.IsGenericHandleKind(kind): + return "mojolpm::Handle" + elif mojom.IsDataPipeConsumerKind(kind): + return "mojolpm::DataPipeConsumerHandle" + elif mojom.IsDataPipeProducerKind(kind): + return "mojolpm::DataPipeProducerHandle" + elif mojom.IsMessagePipeKind(kind): + return "mojolpm::MessagePipeHandle" + elif mojom.IsSharedBufferKind(kind): + return "mojolpm::SharedBufferHandle" + elif mojom.IsPlatformHandleKind(kind): + return "mojolpm::PlatformHandle" + + if not kind in _kind_to_cpp_proto_type: + raise Exception("Unrecognized kind %s" % kind.spec) + return _kind_to_cpp_proto_type[kind] + + def _GetProtoFieldType(self, kind, quantified=True): + # TODO(markbrand): This will not handle array<array> or array<map> + # TODO(markbrand): This also will not handle array<x, 10> + unquantified = '' + if (mojom.IsEnumKind(kind) or mojom.IsStructKind(kind) + or mojom.IsUnionKind(kind)): + unquantified = self._GetProtoNameForKind(kind) + elif mojom.IsArrayKind(kind): + return "repeated %sEntry" % self._GetProtoFieldType(kind.kind, + quantified=False) + elif mojom.IsMapKind(kind): + return ("map<%sKey, %sValue>" % + (self._GetProtoFieldType(kind.key_kind, quantified=False), + self._GetProtoFieldType(kind.value_kind, quantified=False))) + elif mojom.IsInterfaceKind(kind): + unquantified = "%s.Ptr" % self._GetProtoNameForKind(kind) + elif mojom.IsInterfaceRequestKind(kind): + unquantified = "%s.Request" % self._GetProtoNameForKind(kind.kind) + elif mojom.IsAssociatedInterfaceKind(kind): + unquantified = "%s.AssociatedPtr" % self._GetProtoNameForKind(kind.kind) + elif mojom.IsAssociatedInterfaceRequestKind(kind): + unquantified = ("%s.AssociatedRequest" % + self._GetProtoNameForKind(kind.kind)) + elif mojom.IsPendingRemoteKind(kind): + unquantified = "%s.PendingRemote" % self._GetProtoNameForKind(kind.kind) + elif mojom.IsPendingReceiverKind(kind): + unquantified = "%s.PendingReceiver" % self._GetProtoNameForKind(kind.kind) + elif mojom.IsPendingAssociatedRemoteKind(kind): + unquantified = ("%s.PendingAssociatedRemote" % + self._GetProtoNameForKind(kind.kind)) + elif mojom.IsPendingAssociatedReceiverKind(kind): + unquantified = ("%s.PendingAssociatedReceiver" % + self._GetProtoNameForKind(kind.kind)) + elif mojom.IsStringKind(kind): + unquantified = "string" + elif mojom.IsGenericHandleKind(kind): + unquantified = "mojolpm.Handle" + elif mojom.IsDataPipeConsumerKind(kind): + unquantified = "mojolpm.DataPipeConsumerHandle" + elif mojom.IsDataPipeProducerKind(kind): + unquantified = "mojolpm.DataPipeProducerHandle" + elif mojom.IsMessagePipeKind(kind): + unquantified = "mojolpm.MessagePipeHandle" + elif mojom.IsSharedBufferKind(kind): + unquantified = "mojolpm.SharedBufferHandle" + elif mojom.IsPlatformHandleKind(kind): + unquantified = "mojolpm.PlatformHandle" + else: + unquantified = _kind_to_proto_type[kind] + + if quantified and mojom.IsNullableKind(kind): + return 'optional %s' % unquantified + elif quantified: + return 'required %s' % unquantified + else: + return unquantified + + def _GetProtoId(self, name, kind=''): + # We reserve ids [0,15] + # Protobuf implementation reserves [19000,19999] + # Max proto id is 2^29-1 + string = '{}@{}'.format(name, self._GetProtoFieldType(kind, False)).lower() + # 32-bit fnv-1a + fnv = 2166136261 + for c in string: + fnv = fnv ^ ord(c) + fnv = (fnv * 16777619) & 0xffffffff + # xor-fold to 29-bits + fnv = (fnv >> 29) ^ (fnv & 0x1fffffff) + # now use a modulo to reduce to [0,2^29-1 - 1016] + fnv = fnv % 536869895 + # now we move out the disallowed ranges + fnv = fnv + 15 + if fnv >= 19000: + fnv += 1000 + return fnv + + def _NullableIsSameKind(self, kind): + if self._IsTypemappedKind(kind): + if not self.typemap[self._GetFullMojomNameForKind( + kind)]["nullable_is_same_type"]: + return False + if mojom.IsArrayKind(kind): + return False + if mojom.IsMapKind(kind): + return False + if mojom.IsStringKind(kind): + return False + return True + + def _EnumHasDuplicateValues(self, kind): + values = set() + i = 0 + for field in kind.fields: + if field.name == 'MAX': + continue + if field.value: + if _IsStrOrUnicode(field.value): + if field.value in values: + return True + values.add(field.value) + i = int(field.value, 0) + 1 + else: + return True + else: + if str(i) in values: + return True + values.add(str(i)) + i += 1 + return False + + def _EnumFieldName(self, name, kind): + # The WebFeature enum has entries that differ only by the casing of the + # names. Protobuf doesn't support this, so we add the value to the end of + # the name in these cases to disambiguate. + if kind not in self.enum_name_cache: + field_names = dict() + lower_field_names = set() + for field in kind.fields: + new_field_name = field.name + if new_field_name.lower() in lower_field_names: + new_field_name = '{}_{}'.format(new_field_name, field.numeric_value) + lower_field_names.add(new_field_name.lower()) + field_names[field.name] = new_field_name + self.enum_name_cache[kind] = field_names + return self.enum_name_cache[kind][name] diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_ts_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_ts_generator.py index c9cfd51ee96..2b85a8a0858 100644 --- a/chromium/mojo/public/tools/bindings/generators/mojom_ts_generator.py +++ b/chromium/mojo/public/tools/bindings/generators/mojom_ts_generator.py @@ -4,9 +4,13 @@ """Generates Typescript source files from a mojom.Module.""" +import argparse import mojom.generate.generator as generator import mojom.generate.module as mojom from mojom.generate.template_expander import UseJinja +import os + +GENERATOR_PREFIX = 'ts' _kind_to_typescript_type = { mojom.BOOL: "boolean", @@ -39,16 +43,19 @@ class TypescriptStylizer(generator.Stylizer): return '.'.join(generator.ToCamel(word, lower_initial=True) for word in mojom_namespace.split('.')) + def StylizeField(self, mojom_name): + return generator.ToCamel(mojom_name, lower_initial=True) + def StylizeConstant(self, mojom_name): return generator.ToUpperSnakeCase(mojom_name) - class Generator(generator.Generator): def _GetParameters(self, use_es_modules=False): return { - "module": self.module, - "use_es_modules": use_es_modules, - "enums": self.module.enums, + "module": self.module, + "use_es_modules": use_es_modules, + "enums": self.module.enums, + "structs": self.module.structs, } @staticmethod @@ -57,8 +64,9 @@ class Generator(generator.Generator): def GetFilters(self): ts_filters = { - "typescript_type_with_nullability": self._TypescriptTypeWithNullability, - "constant_value": self._ConstantValue, + "typescript_type_with_nullability": self._TypescriptTypeWithNullability, + "constant_value": self._ConstantValue, + "relative_path": self._RelativePath, } return ts_filters @@ -70,24 +78,61 @@ class Generator(generator.Generator): def _GenerateESModulesBindings(self): return self._GetParameters(use_es_modules=True) - def GenerateFiles(self, args): + def GenerateFiles(self, unparsed_args): if self.variant: raise Exception("Variants not supported in JavaScript bindings.") - self.module.Stylize(TypescriptStylizer()) - - self.Write(self._GenerateBindings(), "%s-lite.ts" % self.module.path) - self.Write(self._GenerateESModulesBindings(), - "%s-lite.m.ts" % self.module.path) + parser = argparse.ArgumentParser() + parser.add_argument('--ts_use_es_modules', + action="store_true", + help="Generate bindings that use ES Modules.") + args = parser.parse_args(unparsed_args) + self.module.Stylize(TypescriptStylizer()) - def _TypescriptType(self, kind): + self._SetUniqueAliasesForImports() + + if args.ts_use_es_modules == True: + self.Write(self._GenerateESModulesBindings(), + "%s-lite.m.ts" % self.module.path) + else: + self.Write(self._GenerateBindings(), "%s-lite.ts" % self.module.path) + + # Function that sets unique alias names for all imported modules based on + # their namespace. For example, for two imported modules with namespace + # "mojo.foo", we would generate two aliases "mojoFoo" and "mojoFoo$0". + def _SetUniqueAliasesForImports(self): + used_aliases = set() + for each_import in self.module.imports: + # Per the style guide module aliases start with lower case. + simple_alias = generator.ToCamel(identifier=each_import.namespace, + lower_initial=True, + delimiter='.') + unique_alias = simple_alias + counter = 0 + while unique_alias in used_aliases: + unique_alias = '%s$%d' % (simple_alias, counter) + counter += 1 + + used_aliases.add(unique_alias) + each_import.unique_alias = unique_alias + + def _TypescriptType(self, kind, use_es_modules): if kind in mojom.PRIMITIVES: return _kind_to_typescript_type[kind] + + if mojom.IsStructKind(kind): + if kind.module and kind.module.path != self.module.path: + namespace = (kind.module.unique_alias + if use_es_modules else kind.module.namespace) + return '.'.join([namespace, kind.name]) + + return kind.name + raise Exception("Type is not supported yet.") - def _TypescriptTypeWithNullability(self, kind): - return (self._TypescriptType(kind) + + def _TypescriptTypeWithNullability(self, kind, use_es_modules): + return (self._TypescriptType(kind, use_es_modules) + (" | null" if mojom.IsNullableKind(kind) else "")) def _ConstantValue(self, constant): @@ -110,3 +155,6 @@ class Generator(generator.Generator): return "BigInt('%s')" % value return value + + def _RelativePath(self, path): + return os.path.relpath(path, os.path.dirname(self.module.path)) diff --git a/chromium/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl index dc6d54dff30..a00f9a03de5 100644 --- a/chromium/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl @@ -1,21 +1,17 @@ -{%- if not use_es_modules %} +{%- if not use_es_modules -%} namespace {{module.namespace}} { {% endif %} {#--- Constants #} {%- for constant in module.constants %} -export const {{constant.name}}: {{constant.kind|typescript_type_with_nullability}} = +export const {{constant.name}}: {{constant.kind|typescript_type_with_nullability(use_es_modules)}} = {{constant|constant_value}}; {%- endfor %} {#--- Enums #} -{% for enum in enums %} +{%- for enum in enums %} export enum {{enum.name}} { {%- for field in enum.fields %} -{%- if field.value %} - {{field.name}} = {{field.value}}, -{%- else %} - {{field.name}}, -{%- endif %} + {{field.name}} = {{field.numeric_value}}, {%- endfor %} {%- if enum.min_value is not none %} MIN_VALUE = {{enum.min_value}}, @@ -26,6 +22,15 @@ export enum {{enum.name}} { } {% endfor %} +{#--- Structs #} +{%- for struct in structs %} +export interface {{struct.name}} { +{%- for packed_field in struct.packed.packed_fields %} + {{packed_field.field.name}}: {{packed_field.field.kind|typescript_type_with_nullability(use_es_modules)}}; +{%- endfor %} +} +{% endfor %} + {%- if not use_es_modules %} } // namespace {{module.namespace}} {% endif %} diff --git a/chromium/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl b/chromium/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl index 1dd6cf0afae..4d358eb9f08 100644 --- a/chromium/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl +++ b/chromium/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl @@ -2,4 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +{%- if use_es_modules %} +{%- for import in module.imports %} +{# + # We add a ".js" file extension because otherwise the compiled JS would import + # "-lite.m" which doesn't work on browsers unless served with a + # "text/javascript" MIME type. + #} +import * as {{import.unique_alias}} from '{{import.path|relative_path}}-lite.m.js'; +{%- endfor %} +{% endif %} + {% include "module_definition.tmpl" %} diff --git a/chromium/mojo/public/tools/bindings/mojom.gni b/chromium/mojo/public/tools/bindings/mojom.gni index f8c595f49f7..0d2080c566e 100644 --- a/chromium/mojo/public/tools/bindings/mojom.gni +++ b/chromium/mojo/public/tools/bindings/mojom.gni @@ -5,6 +5,7 @@ import("//build/config/jumbo.gni") import("//third_party/closure_compiler/closure_args.gni") import("//third_party/closure_compiler/compile_js.gni") +import("//third_party/protobuf/proto_library.gni") import("//ui/webui/webui_features.gni") # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're @@ -14,6 +15,7 @@ import("//ui/webui/webui_features.gni") # flipped elsewhere though. import("//build/config/chrome_build.gni") import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/ui_mode.gni") import("//build/config/nacl/config.gni") import("//build/toolchain/kythe.gni") import("//components/nacl/features.gni") @@ -39,7 +41,7 @@ declare_args() { # Enables Closure compilation of generated JS lite bindings. In environments # where compilation is supported, any mojom target "foo" will also have a # corresponding "foo_js_library_for_compile" target generated. - enable_mojom_closure_compile = closure_compile && optimize_webui + enable_mojom_closure_compile = enable_js_type_check && optimize_webui # Enables generating Typescript bindings and compiling them to JS bindings. enable_typescript_bindings = false @@ -60,8 +62,10 @@ declare_args() { # check |target_os| explicitly, as it's consistent across all toolchains. enable_scrambled_message_ids = enable_mojom_message_id_scrambling && - (is_mac || is_win || (is_linux && !is_chromeos && !is_chromecast) || - ((enable_nacl || is_nacl || is_nacl_nonsfi) && target_os != "chromeos")) + (is_mac || is_win || (is_linux && !is_chromeos && !is_chromecast && + !chromeos_is_browser_only) || + ((enable_nacl || is_nacl || is_nacl_nonsfi) && + (target_os != "chromeos" && !chromeos_is_browser_only))) _mojom_tools_root = "//mojo/public/tools" _mojom_library_root = "$_mojom_tools_root/mojom/mojom" @@ -529,6 +533,12 @@ template("mojom") { sources_list = invoker.sources } + # Reset sources_assignment_filter for the BUILD.gn file to prevent + # regression during the migration of Chromium away from the feature. + # See docs/no_sources_assignment_filter.md for more information. + # TODO(crbug.com/1018739): remove this when migration is done. + set_sources_assignment_filter([]) + # Listed sources may be relative to the current target dir, or they may be # absolute paths, including paths to generated mojom files. While those are # fine as-is for input references, deriving output paths can be more subtle. @@ -904,6 +914,83 @@ template("mojom") { } } + if (generate_fuzzing) { + # This block generates the proto files used for the MojoLPM fuzzer, + # and the corresponding proto targets that will be linked in the fuzzer + # targets. These are independent of the typemappings, and can be done + # separately here. + + generator_mojolpm_proto_target_name = + "${target_name}_mojolpm_proto_generator" + action(generator_mojolpm_proto_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = invoker.sources + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + + outputs = [] + args = common_generator_args + filelist = [] + foreach(source, invoker.sources) { + filelist += [ rebase_path("$source", root_build_dir) ] + outputs += [ "$target_gen_dir/$source.mojolpm.proto" ] + } + + response_file_contents = filelist + + args += [ + "--filelist={{response_file_name}}", + "--generate_non_variant_code", + "-g", + "mojolpm", + ] + } + + mojolpm_proto_target_name = "${target_name}_mojolpm_proto" + if (defined(invoker.sources)) { + proto_library(mojolpm_proto_target_name) { + testonly = true + generate_python = false + sources = process_file_template( + invoker.sources, + [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ]) + import_dirs = [ "${root_gen_dir}" ] + proto_in_dir = "${root_gen_dir}" + proto_out_dir = "." + proto_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto_copy" ] + proto_deps += [ ":$generator_mojolpm_proto_target_name" ] + link_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append mojolpm_proto_suffix + # to get the proto dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + proto_deps += [ "${full_name}_mojolpm_proto" ] + link_deps += [ "${full_name}_mojolpm_proto" ] + } + } + } else { + group(mojolpm_proto_target_name) { + testonly = true + public_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ] + if (defined(generator_shared_target_name)) { + public_deps += [ ":$generator_shared_target_name" ] + } + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append #mojolpm_proto_suffix + # to get the proto dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_mojolpm_proto" ] + } + } + } + } + # Generate code for variants. if (!defined(invoker.disable_variants) || !invoker.disable_variants) { enabled_configurations = _bindings_configurations @@ -1021,33 +1108,7 @@ template("mojom") { } _typemap_config = typemap.config if (get_path_info(source, "abspath") == _typemap_config.mojom) { - enabled = false - if (!defined(_typemap_config.os_whitelist)) { - enabled = true - } else { - foreach(os, _typemap_config.os_whitelist) { - if (os == "android" && is_android) { - enabled = true - } else if (os == "chromeos" && is_chromeos) { - enabled = true - } else if (os == "fuchsia" && is_fuchsia) { - enabled = true - } else if (os == "ios" && is_ios) { - enabled = true - } else if (os == "linux" && is_linux) { - enabled = true - } else if (os == "mac" && is_mac) { - enabled = true - } else if (os == "posix" && is_posix) { - enabled = true - } else if (os == "win" && is_win) { - enabled = true - } - } - } - if (enabled) { - active_typemaps += [ typemap ] - } + active_typemaps += [ typemap ] } } } @@ -1081,6 +1142,12 @@ template("mojom") { "$root_gen_dir/${base_path}${variant_dash_suffix}.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}.h", ] + if (generate_fuzzing && !defined(bindings_configuration.variant)) { + outputs += [ + "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc", + "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h", + ] + } } response_file_contents = filelist @@ -1088,9 +1155,14 @@ template("mojom") { args += [ "--filelist={{response_file_name}}", "-g", - "c++", ] + if (generate_fuzzing && !defined(bindings_configuration.variant)) { + args += [ "c++,mojolpm" ] + } else { + args += [ "c++" ] + } + if (defined(bindings_configuration.variant)) { args += [ "--variant", @@ -1141,6 +1213,73 @@ template("mojom") { } } + if (generate_fuzzing && !defined(variant)) { + # This block contains the C++ targets for the MojoLPM fuzzer, we need to + # do this here so that we can use the typemap configuration for the + # empty-variant Mojo target. + + mojolpm_target_name = "${target_name}_mojolpm" + mojolpm_generator_target_name = "${target_name}__generator" + source_set(mojolpm_target_name) { + # There are still a few missing header dependencies between mojo targets + # with typemaps and the dependencies of their typemap headers. It would + # be good to enable include checking for these in the future though. + check_includes = false + testonly = true + if (defined(invoker.sources)) { + sources = process_file_template( + invoker.sources, + [ + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc", + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h", + ]) + deps = [] + } else { + sources = [] + deps = [] + } + + public_deps = [ + ":$generator_shared_target_name", + + # NB: hardcoded dependency on the no-variant variant generator, since + # mojolpm only uses the no-variant type. + ":$mojolpm_generator_target_name", + ":$mojolpm_proto_target_name", + "//mojo/public/tools/fuzzers:mojolpm", + ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix to + # get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_mojolpm" ] + } + + foreach(typemap, active_typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + + if (defined(_typemap_config.deps)) { + deps += _typemap_config.deps + } + if (defined(_typemap_config.public_deps)) { + public_deps += _typemap_config.public_deps + } + } + foreach(config, cpp_typemap_configs) { + if (defined(config.traits_deps)) { + deps += config.traits_deps + } + if (defined(config.traits_public_deps)) { + public_deps += config.traits_public_deps + } + } + } + } + # Write the typemapping configuration for this target out to a file to be # validated by a Python script. This helps catch mistakes that can't # be caught by logic in GN. @@ -1458,6 +1597,7 @@ template("mojom") { java_target_name = target_name + "_java" android_library(java_target_name) { + forward_variables_from(invoker, [ "enable_bytecode_checks" ]) deps = [ "//base:base_java", "//mojo/public/java:bindings_java", @@ -1630,69 +1770,111 @@ template("mojom") { } if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) && use_typescript_for_target) { - generator_ts_target_name = "${target_name}_ts__generator" - + generator_js_target_names = [] source_filelist = [] - ts_filelist = [] - ts_outputs = [] - js_outputs = [] foreach(source, sources_list) { source_filelist += [ rebase_path("$source", root_build_dir) ] } - foreach(base_path, output_file_base_paths) { - ts_outputs += [ - "$root_gen_dir/$base_path-lite.ts", - "$root_gen_dir/$base_path-lite.m.ts", - ] - ts_filelist += [ - rebase_path("$root_gen_dir/$base_path-lite.ts", root_build_dir), - rebase_path("$root_gen_dir/$base_path-lite.m.ts", root_build_dir), - ] - js_outputs += [ - "$root_gen_dir/$base_path-lite.js", - "$root_gen_dir/$base_path-lite.m.js", - ] - } + dependency_types = [ + { + name = "regular" + ts_extension = ".ts" + js_extension = ".js" + }, + { + name = "es_modules" + ts_extension = ".m.ts" + js_extension = ".m.js" + }, + ] - generator_ts_target_name = "${target_name}_ts__generator" - action(generator_ts_target_name) { - script = mojom_generator_script - inputs = mojom_generator_sources + jinja2_sources - sources = sources_list - deps = [ - ":$parser_target_name", - "//mojo/public/tools/bindings:precompile_templates", - ] - outputs = ts_outputs - args = common_generator_args - response_file_contents = source_filelist + foreach(dependency_type, dependency_types) { + ts_outputs = [] + js_outputs = [] - args += [ - "--filelist={{response_file_name}}", - "-g", - "typescript", - ] + foreach(base_path, output_file_base_paths) { + ts_outputs += + [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ] + js_outputs += + [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ] + } - # TODO(crbug.com/1007587): Support scramble_message_ids. - # TODO(crbug.com/1007591): Support generate_fuzzing. - } + # Generate Typescript bindings. + generator_ts_target_name = + "${target_name}_${dependency_type.name}__ts__generator" + action(generator_ts_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = ts_outputs + args = common_generator_args + response_file_contents = source_filelist - generator_js_target_name = "${target_name}_js__generator" - action(generator_js_target_name) { - script = "$mojom_generator_root/compile_typescript.py" - sources = ts_outputs - outputs = js_outputs - public_deps = [ ":$generator_ts_target_name" ] - response_file_contents = ts_filelist - args = [ "--filelist={{response_file_name}}" ] + args += [ + "--filelist={{response_file_name}}", + "-g", + "typescript", + ] + + if (dependency_type.name == "es_modules") { + args += [ "--ts_use_es_modules" ] + } + + # TODO(crbug.com/1007587): Support scramble_message_ids. + # TODO(crbug.com/1007591): Support generate_fuzzing. + } + + # Create tsconfig.json for the generated Typescript. + tsconfig_filename = + "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json" + tsconfig = { + } + tsconfig.compilerOptions = { + target = "es6" + module = "es6" + lib = [ + "es6", + "esnext.bigint", + ] + strict = true + } + tsconfig.files = [] + foreach(base_path, output_file_base_paths) { + tsconfig.files += [ rebase_path( + "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}", + target_gen_dir, + root_gen_dir) ] + } + write_file(tsconfig_filename, tsconfig, "json") + + # Compile previously generated Typescript to Javascript. + generator_js_target_name = + "${target_name}_${dependency_type.name}__js__generator" + generator_js_target_names += [ generator_js_target_name ] + + action(generator_js_target_name) { + script = "$mojom_generator_root/compile_typescript.py" + sources = ts_outputs + outputs = js_outputs + public_deps = [ ":$generator_ts_target_name" ] + absolute_tsconfig_path = + rebase_path(tsconfig_filename, "", target_gen_dir) + args = [ "--tsconfig_path=$absolute_tsconfig_path" ] + } } js_target_name = target_name + "_js" group(js_target_name) { public_deps = [] if (sources_list != []) { - public_deps += [ ":$generator_js_target_name" ] + foreach(generator_js_target_name, generator_js_target_names) { + public_deps += [ ":$generator_js_target_name" ] + } } foreach(d, all_deps) { diff --git a/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py index edcb982957a..da9efc71cef 100755 --- a/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py +++ b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -42,7 +42,7 @@ import mojom.fileutil as fileutil from mojom.generate.module import Module from mojom.generate import template_expander from mojom.generate import translate -from mojom.generate.generator import AddComputedData, WriteFile +from mojom.generate.generator import WriteFile sys.path.append( os.path.join(_GetDirAbove("mojo"), "tools", "diagnosis")) @@ -50,10 +50,11 @@ import crbug_1001171 _BUILTIN_GENERATORS = { - "c++": "mojom_cpp_generator", - "javascript": "mojom_js_generator", - "java": "mojom_java_generator", - "typescript": "mojom_ts_generator", + "c++": "mojom_cpp_generator", + "javascript": "mojom_js_generator", + "java": "mojom_java_generator", + "mojolpm": "mojom_mojolpm_generator", + "typescript": "mojom_ts_generator", } @@ -108,6 +109,8 @@ def ScrambleMethodOrdinals(interfaces, salt): i = 0 already_generated.clear() for method in interface.methods: + if method.explicit_ordinal is not None: + continue while True: i = i + 1 if i == 1000000: @@ -163,6 +166,8 @@ class MojomProcessor(object): language_map = self._typemap.get(language, {}) language_map.update(typemap) self._typemap[language] = language_map + if 'c++' in self._typemap: + self._typemap['mojolpm'] = self._typemap['c++'] def _GenerateModule(self, args, remaining_args, generator_modules, rel_filename, imported_filename_stack): @@ -185,7 +190,6 @@ class MojomProcessor(object): ScrambleMethodOrdinals(module.interfaces, salt) if self._should_generate(rel_filename.path): - AddComputedData(module) for language, generator_module in generator_modules.items(): generator = generator_module.Generator( module, args.output_dir, typemap=self._typemap.get(language, {}), @@ -276,10 +280,11 @@ def main(): generate_parser.add_argument("--filelist", help="mojom input file list") generate_parser.add_argument("-d", "--depth", dest="depth", default=".", help="depth from source root") - generate_parser.add_argument("-g", "--generators", + generate_parser.add_argument("-g", + "--generators", dest="generators_string", metavar="GENERATORS", - default="c++,javascript,java", + default="c++,javascript,java,mojolpm", help="comma-separated list of generators") generate_parser.add_argument( "--gen_dir", dest="gen_directories", action="append", metavar="directory", diff --git a/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py index bcffbbb116e..bddbe3f4c58 100644 --- a/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py +++ b/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -9,14 +9,15 @@ from mojom_bindings_generator import ScrambleMethodOrdinals class FakeIface(object): - def __init__( self ): - self.name = None + def __init__(self): + self.mojom_name = None self.methods = None class FakeMethod(object): - def __init__( self ): - self.ordinal = None + def __init__(self, explicit_ordinal=None): + self.explicit_ordinal = explicit_ordinal + self.ordinal = explicit_ordinal self.ordinal_comment = None @@ -25,18 +26,23 @@ class MojoBindingsGeneratorTest(unittest.TestCase): def testMakeImportStackMessage(self): """Tests MakeImportStackMessage().""" - self.assertEquals(MakeImportStackMessage(["x"]), "") - self.assertEquals(MakeImportStackMessage(["x", "y"]), - "\n y was imported by x") - self.assertEquals(MakeImportStackMessage(["x", "y", "z"]), - "\n z was imported by y\n y was imported by x") + self.assertEqual(MakeImportStackMessage(["x"]), "") + self.assertEqual(MakeImportStackMessage(["x", "y"]), + "\n y was imported by x") + self.assertEqual(MakeImportStackMessage(["x", "y", "z"]), + "\n z was imported by y\n y was imported by x") def testScrambleMethodOrdinals(self): """Tests ScrambleMethodOrdinals().""" interface = FakeIface() - interface.name = 'RendererConfiguration' - interface.methods = [FakeMethod(), FakeMethod(), FakeMethod()] - ScrambleMethodOrdinals([interface], "foo") + interface.mojom_name = 'RendererConfiguration' + interface.methods = [ + FakeMethod(), + FakeMethod(), + FakeMethod(), + FakeMethod(explicit_ordinal=42) + ] + ScrambleMethodOrdinals([interface], "foo".encode('utf-8')) # These next three values are hard-coded. If the generation algorithm # changes from being based on sha256(seed + interface.name + str(i)) then # these numbers will obviously need to change too. @@ -44,9 +50,12 @@ class MojoBindingsGeneratorTest(unittest.TestCase): # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is # '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The # difference in 0x4a vs 0xca is because we only take 31 bits. - self.assertEquals(interface.methods[0].ordinal, 1257880741) - self.assertEquals(interface.methods[1].ordinal, 631133653) - self.assertEquals(interface.methods[2].ordinal, 549336076) + self.assertEqual(interface.methods[0].ordinal, 1257880741) + self.assertEqual(interface.methods[1].ordinal, 631133653) + self.assertEqual(interface.methods[2].ordinal, 549336076) + + # Explicit method ordinals should not be scrambled. + self.assertEqual(interface.methods[3].ordinal, 42) if __name__ == "__main__": diff --git a/chromium/mojo/public/tools/fuzzers/mojolpm.gni b/chromium/mojo/public/tools/fuzzers/mojolpm.gni new file mode 100644 index 00000000000..82f66c1e3c3 --- /dev/null +++ b/chromium/mojo/public/tools/fuzzers/mojolpm.gni @@ -0,0 +1,144 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//testing/libfuzzer/fuzzer_test.gni") +import("//third_party/protobuf/proto_library.gni") +import("//tools/ipc_fuzzer/ipc_fuzzer.gni") + +# Generate a MojoLPM-based fuzzer test. +# +# This rule will copy the proto file defining the fuzzer testcases into the +# output directory so that it can be compiled against the generated MojoLPM +# protos. It then adds a rule to compile that proto, and finally a fuzzer +# test target which uses the compiled proto. +# +# Optionally it can also handle converting a seed corpus of text protos into +# a binary corpus as part of the build. +# +# Parameters: +# sources +# List of source .cc files to compile. +# +# deps +# List of dependencies to compile this target. +# +# proto_source +# Single source .proto file defining the structure of a testcase. +# +# proto_deps +# List of additional dependencies for compiling proto_source. +# +# testcase_proto_kind (optional, required if seed_corpus_sources provided) +# Name of proto message type representing a testcase. +# +# seed_corpus_sources (optional) +# List of source .textproto files used to build a seed corpus. +# +# Example: +# mojolpm_fuzzer_test("foo_mojolpm_fuzzer") { +# sources = [ "foo_mojolpm_fuzzer.cc" ] +# +# deps = [ +# "//content/browser/foo:foo_mojolpm_fuzzer_proto", +# "//content/browser:for_content_tests", +# "//content/public/browser:browser_sources", +# "//content/test:test_support", +# "//mojo/core/embedder", +# "//mojo/public/tools/fuzzers:mojolpm", +# "//third_party/libprotobuf-mutator", +# ] +# +# proto_deps = [ +# "//content/browser/bar/mojom:mojom_mojolpm"," +# ] +# +# testcase_proto = "foo_mojolpm_fuzzer.proto" +# testcase_proto_kind = "foo.mojolpm.proto.Testcase" +# +# seed_corpus_sources = [ +# "foo_mojolpm_fuzzer_corpus/seed_one.textproto", +# "foo_mojolpm_fuzzer_corpus/seed_two.textproto", +# ] +# } +template("mojolpm_fuzzer_test") { + assert(defined(invoker.sources) && defined(invoker.proto_source), + "\"sources\" and \"proto_source\" must be defined for $target_name") + + assert( + !defined(invoker.seed_corpus_sources) || + defined(invoker.testcase_proto_kind), + "\"testcase_proto_kind\" must be defined for $target_name since \"seed_corpus_sources\" is defined.") + + if (enable_ipc_fuzzer) { + proto_target_name = "${target_name}_proto" + + proto_library(proto_target_name) { + sources = [ invoker.proto_source ] + generate_python = false + + proto_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto_copy" ] + + import_dirs = [ root_gen_dir ] + + link_deps = [] + + if (defined(invoker.proto_deps)) { + proto_deps += invoker.proto_deps + link_deps += invoker.proto_deps + } + + testonly = true + } + + if (defined(invoker.seed_corpus_sources)) { + protoc_convert_target_name = "${target_name}_protoc_convert" + seed_corpus_path = "${target_gen_dir}/${target_name}_seed_corpus" + + protoc_convert(protoc_convert_target_name) { + sources = invoker.seed_corpus_sources + + inputs = [ invoker.proto_source ] + + output_pattern = "${seed_corpus_path}/{{source_name_part}}.binarypb" + + args = [ + "--encode=${invoker.testcase_proto_kind}", + "-I", + rebase_path(root_gen_dir), + "-I", + get_path_info(rebase_path(inputs[0]), "dir"), + rebase_path(inputs[0]), + ] + + deps = [] + + if (defined(invoker.proto_deps)) { + deps += invoker.proto_deps + } + + testonly = true + } + } + + fuzzer_test(target_name) { + sources = invoker.sources + deps = [ + ":${proto_target_name}", + "//mojo/core/embedder", + "//mojo/public/tools/fuzzers:mojolpm", + "//third_party/libprotobuf-mutator", + ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + + if (defined(invoker.seed_corpus_sources)) { + seed_corpus = seed_corpus_path + seed_corpus_deps = [ ":${protoc_convert_target_name}" ] + } + } + } else { + not_needed(invoker, "*") + } +} diff --git a/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility.py new file mode 100755 index 00000000000..7e746112228 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Verifies backward-compatibility of mojom type changes. + +Given a set of pre- and post-diff mojom file contents, and a root directory +for a project, this tool verifies that any changes to [Stable] mojom types are +backward-compatible with the previous version. + +This can be used e.g. by a presubmit check to prevent developers from making +breaking changes to stable mojoms.""" + +import argparse +import errno +import io +import json +import os +import os.path +import shutil +import six +import sys +import tempfile + +from mojom.generate import module +from mojom.generate import translate +from mojom.parse import parser + + +class ParseError(Exception): + pass + + +def _ValidateDelta(root, delta): + """Parses all modified mojoms (including all transitive mojom dependencies, + even if unmodified) to perform backward-compatibility checks on any types + marked with the [Stable] attribute. + + Note that unlike the normal build-time parser in mojom_parser.py, this does + not produce or rely on cached module translations, but instead parses the full + transitive closure of a mojom's input dependencies all at once. + """ + + # First build a map of all files covered by the delta + affected_files = set() + old_files = {} + new_files = {} + for change in delta: + # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3. + filename = change['filename'].replace('\\', '/') + affected_files.add(filename) + if change['old']: + old_files[filename] = change['old'] + if change['new']: + new_files[filename] = change['new'] + + # Parse and translate all mojoms relevant to the delta, including transitive + # imports that weren't modified. + unmodified_modules = {} + + def parseMojom(mojom, file_overrides, override_modules): + if mojom in unmodified_modules or mojom in override_modules: + return + + contents = file_overrides.get(mojom) + if contents: + modules = override_modules + else: + modules = unmodified_modules + with io.open(os.path.join(root, mojom), encoding='utf-8') as f: + contents = f.read() + + try: + ast = parser.Parse(contents, mojom) + except Exception as e: + six.reraise( + ParseError, + 'encountered exception {0} while parsing {1}'.format(e, mojom), + sys.exc_info()[2]) + for imp in ast.import_list: + parseMojom(imp.import_filename, file_overrides, override_modules) + + # Now that the transitive set of dependencies has been imported and parsed + # above, translate each mojom AST into a Module so that all types are fully + # defined and can be inspected. + all_modules = {} + all_modules.update(unmodified_modules) + all_modules.update(override_modules) + modules[mojom] = translate.OrderedModule(ast, mojom, all_modules) + + old_modules = {} + for mojom in old_files.keys(): + parseMojom(mojom, old_files, old_modules) + new_modules = {} + for mojom in new_files.keys(): + parseMojom(mojom, new_files, new_modules) + + # At this point we have a complete set of translated Modules from both the + # pre- and post-diff mojom contents. Now we can analyze backward-compatibility + # of the deltas. + # + # Note that for backward-compatibility checks we only care about types which + # were marked [Stable] before the diff. Types newly marked as [Stable] are not + # checked. + def collectTypes(modules): + types = {} + for m in modules.values(): + for kinds in (m.enums, m.structs, m.unions, m.interfaces): + for kind in kinds: + types[kind.qualified_name] = kind + return types + + old_types = collectTypes(old_modules) + new_types = collectTypes(new_modules) + + # Collect any renamed types so they can be compared accordingly. + renamed_types = {} + for name, kind in new_types.items(): + old_name = kind.attributes and kind.attributes.get('RenamedFrom') + if old_name: + renamed_types[old_name] = name + + for qualified_name, kind in old_types.items(): + if not kind.stable: + continue + + new_name = renamed_types.get(qualified_name, qualified_name) + if new_name not in new_types: + raise Exception( + 'Stable type %s appears to be deleted by this change. If it was ' + 'renamed, please add a [RenamedFrom] attribute to the new type. This ' + 'can be deleted by a subsequent change.' % qualified_name) + + if not new_types[new_name].IsBackwardCompatible(kind): + raise Exception('Stable type %s appears to have changed in a way which ' + 'breaks backward-compatibility. Please fix!\n\nIf you ' + 'believe this assessment to be incorrect, please file a ' + 'Chromium bug against the "Internals>Mojo>Bindings" ' + 'component.' % qualified_name) + + +def Run(command_line, delta=None): + """Runs the tool with the given command_line. Normally this will read the + change description from stdin as a JSON-encoded list, but tests may pass a + delta directly for convenience.""" + arg_parser = argparse.ArgumentParser( + description='Verifies backward-compatibility of mojom type changes.', + epilog=""" +This tool reads a change description from stdin and verifies that all modified +[Stable] mojom types will retain backward-compatibility. The change description +must be a JSON-encoded list of objects, each with a "filename" key (path to a +changed mojom file, relative to ROOT); an "old" key whose value is a string of +the full file contents before the change, or null if the file is being added; +and a "new" key whose value is a string of the full file contents after the +change, or null if the file is being deleted.""") + arg_parser.add_argument( + '--src-root', + required=True, + action='store', + metavar='ROOT', + help='The root of the source tree in which the checked mojoms live.') + + args, _ = arg_parser.parse_known_args(command_line) + if not delta: + delta = json.load(sys.stdin) + _ValidateDelta(args.src_root, delta) + + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py new file mode 100755 index 00000000000..9f51ea7751f --- /dev/null +++ b/chromium/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import os +import os.path +import shutil +import tempfile +import unittest + +import check_stable_mojom_compatibility + +from mojom.generate import module + + +class Change(object): + """Helper to clearly define a mojom file delta to be analyzed.""" + + def __init__(self, filename, old=None, new=None): + """If old is None, this is a file addition. If new is None, this is a file + deletion. Otherwise it's a file change.""" + self.filename = filename + self.old = old + self.new = new + + +class UnchangedFile(Change): + def __init__(self, filename, contents): + super(UnchangedFile, self).__init__(filename, old=contents, new=contents) + + +class CheckStableMojomCompatibilityTest(unittest.TestCase): + """Tests covering the behavior of the compatibility checking tool. Note that + details of different compatibility checks and relevant failure modes are NOT + covered by these tests. Those are instead covered by unittests in + version_compatibility_unittest.py. Additionally, the tests which ensure a + given set of [Stable] mojom definitions are indeed plausibly stable (i.e. they + have no unstable dependencies) are covered by stable_attribute_unittest.py. + + These tests cover higher-level concerns of the compatibility checking tool, + like file or symbol, renames, changes spread over multiple files, etc.""" + + def verifyBackwardCompatibility(self, changes): + """Helper for implementing assertBackwardCompatible and + assertNotBackwardCompatible""" + + temp_dir = tempfile.mkdtemp() + for change in changes: + if change.old: + # Populate the old file on disk in our temporary fake source root + file_path = os.path.join(temp_dir, change.filename) + dir_path = os.path.dirname(file_path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + with open(file_path, 'w') as f: + f.write(change.old) + + delta = [] + for change in changes: + if change.old != change.new: + delta.append({ + 'filename': change.filename, + 'old': change.old, + 'new': change.new + }) + + try: + check_stable_mojom_compatibility.Run(['--src-root', temp_dir], + delta=delta) + finally: + shutil.rmtree(temp_dir) + + def assertBackwardCompatible(self, changes): + self.verifyBackwardCompatibility(changes) + + def assertNotBackwardCompatible(self, changes): + try: + self.verifyBackwardCompatibility(changes) + except Exception: + return + + raise Exception('Change unexpectedly passed a backward-compatibility check') + + def testBasicCompatibility(self): + """Minimal smoke test to verify acceptance of a simple valid change.""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Stable] struct S {};', + new='[Stable] struct S { [MinVersion=1] int32 x; };') + ]) + + def testBasicIncompatibility(self): + """Minimal smoke test to verify rejection of a simple invalid change.""" + self.assertNotBackwardCompatible([ + Change('foo/foo.mojom', + old='[Stable] struct S {};', + new='[Stable] struct S { int32 x; };') + ]) + + def testIgnoreIfNotStable(self): + """We don't care about types not marked [Stable]""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='struct S {};', + new='struct S { int32 x; };') + ]) + + def testRename(self): + """We can do checks for renamed types.""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Stable] struct S {};', + new='[Stable, RenamedFrom="S"] struct T {};') + ]) + self.assertNotBackwardCompatible([ + Change('foo/foo.mojom', + old='[Stable] struct S {};', + new='[Stable, RenamedFrom="S"] struct T { int32 x; };') + ]) + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Stable] struct S {};', + new="""\ + [Stable, RenamedFrom="S"] + struct T { [MinVersion=1] int32 x; }; + """) + ]) + + def testNewlyStable(self): + """We don't care about types newly marked as [Stable].""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='struct S {};', + new='[Stable] struct S { int32 x; };') + ]) + + def testFileRename(self): + """Make sure we can still do compatibility checks after a file rename.""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', old='[Stable] struct S {};', new=None), + Change('bar/bar.mojom', + old=None, + new='[Stable] struct S { [MinVersion=1] int32 x; };') + ]) + self.assertNotBackwardCompatible([ + Change('foo/foo.mojom', old='[Stable] struct S {};', new=None), + Change('bar/bar.mojom', old=None, new='[Stable] struct S { int32 x; };') + ]) + + def testWithImport(self): + """Ensure that cross-module dependencies do not break the compatibility + checking tool.""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old="""\ + module foo; + [Stable] struct S {}; + """, + new="""\ + module foo; + [Stable] struct S { [MinVersion=2] int32 x; }; + """), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; [MinVersion=1] int32 y; }; + """) + ]) + + def testWithMovedDefinition(self): + """If a definition moves from one file to another, we should still be able + to check compatibility accurately.""" + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old="""\ + module foo; + [Stable] struct S {}; + """, + new="""\ + module foo; + """), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable, RenamedFrom="foo.S"] struct S { + [MinVersion=2] int32 x; + }; + [Stable] struct T { S s; [MinVersion=1] int32 y; }; + """) + ]) + + self.assertNotBackwardCompatible([ + Change('foo/foo.mojom', + old="""\ + module foo; + [Stable] struct S {}; + """, + new="""\ + module foo; + """), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable, RenamedFrom="foo.S"] struct S { int32 x; }; + [Stable] struct T { S s; [MinVersion=1] int32 y; }; + """) + ]) + + def testWithUnmodifiedImport(self): + """Unchanged files in the filesystem are still parsed by the compatibility + checking tool if they're imported by a changed file.""" + self.assertBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; [MinVersion=1] int32 x; }; + """) + ]) + + self.assertNotBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; int32 x; }; + """) + ]) diff --git a/chromium/mojo/public/tools/mojom/const_unittest.py b/chromium/mojo/public/tools/mojom/const_unittest.py new file mode 100644 index 00000000000..cb42dfac355 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/const_unittest.py @@ -0,0 +1,90 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase +from mojom.generate import module as mojom + + +class ConstTest(MojomParserTestCase): + """Tests constant parsing behavior.""" + + def testLiteralInt(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'const int32 k = 42;') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(1, len(a.constants)) + self.assertEqual('k', a.constants[0].mojom_name) + self.assertEqual('42', a.constants[0].value) + + def testLiteralFloat(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'const float k = 42.5;') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(1, len(a.constants)) + self.assertEqual('k', a.constants[0].mojom_name) + self.assertEqual('42.5', a.constants[0].value) + + def testLiteralString(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'const string k = "woot";') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(1, len(a.constants)) + self.assertEqual('k', a.constants[0].mojom_name) + self.assertEqual('"woot"', a.constants[0].value) + + def testEnumConstant(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'module a; enum E { kA = 41, kB };') + b_mojom = 'b.mojom' + self.WriteFile( + b_mojom, """\ + import "a.mojom"; + const a.E kE1 = a.E.kB; + + // We also allow value names to be unqualified, implying scope from the + // constant's type. + const a.E kE2 = kB; + """) + self.ParseMojoms([a_mojom, b_mojom]) + a = self.LoadModule(a_mojom) + b = self.LoadModule(b_mojom) + self.assertEqual(1, len(a.enums)) + self.assertEqual('E', a.enums[0].mojom_name) + self.assertEqual(2, len(b.constants)) + self.assertEqual('kE1', b.constants[0].mojom_name) + self.assertEqual(a.enums[0], b.constants[0].kind) + self.assertEqual(a.enums[0].fields[1], b.constants[0].value.field) + self.assertEqual(42, b.constants[0].value.field.numeric_value) + self.assertEqual('kE2', b.constants[1].mojom_name) + self.assertEqual(a.enums[0].fields[1], b.constants[1].value.field) + self.assertEqual(42, b.constants[1].value.field.numeric_value) + + def testConstantReference(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'const int32 kA = 42; const int32 kB = kA;') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(2, len(a.constants)) + self.assertEqual('kA', a.constants[0].mojom_name) + self.assertEqual('42', a.constants[0].value) + self.assertEqual('kB', a.constants[1].mojom_name) + self.assertEqual('42', a.constants[1].value) + + def testImportedConstantReference(self): + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'const int32 kA = 42;') + b_mojom = 'b.mojom' + self.WriteFile(b_mojom, 'import "a.mojom"; const int32 kB = kA;') + self.ParseMojoms([a_mojom, b_mojom]) + a = self.LoadModule(a_mojom) + b = self.LoadModule(b_mojom) + self.assertEqual(1, len(a.constants)) + self.assertEqual(1, len(b.constants)) + self.assertEqual('kA', a.constants[0].mojom_name) + self.assertEqual('42', a.constants[0].value) + self.assertEqual('kB', b.constants[0].mojom_name) + self.assertEqual('42', b.constants[0].value) diff --git a/chromium/mojo/public/tools/mojom/enum_unittest.py b/chromium/mojo/public/tools/mojom/enum_unittest.py new file mode 100644 index 00000000000..d9005078671 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/enum_unittest.py @@ -0,0 +1,92 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + + +class EnumTest(MojomParserTestCase): + """Tests enum parsing behavior.""" + + def testExplicitValues(self): + """Verifies basic parsing of assigned integral values.""" + types = self.ExtractTypes('enum E { kFoo=0, kBar=2, kBaz };') + self.assertEqual('kFoo', types['E'].fields[0].mojom_name) + self.assertEqual(0, types['E'].fields[0].numeric_value) + self.assertEqual('kBar', types['E'].fields[1].mojom_name) + self.assertEqual(2, types['E'].fields[1].numeric_value) + self.assertEqual('kBaz', types['E'].fields[2].mojom_name) + self.assertEqual(3, types['E'].fields[2].numeric_value) + + def testImplicitValues(self): + """Verifies basic automatic assignment of integral values at parse time.""" + types = self.ExtractTypes('enum E { kFoo, kBar, kBaz };') + self.assertEqual('kFoo', types['E'].fields[0].mojom_name) + self.assertEqual(0, types['E'].fields[0].numeric_value) + self.assertEqual('kBar', types['E'].fields[1].mojom_name) + self.assertEqual(1, types['E'].fields[1].numeric_value) + self.assertEqual('kBaz', types['E'].fields[2].mojom_name) + self.assertEqual(2, types['E'].fields[2].numeric_value) + + def testSameEnumReference(self): + """Verifies that an enum value can be assigned from the value of another + field within the same enum.""" + types = self.ExtractTypes('enum E { kA, kB, kFirst=kA };') + self.assertEqual('kA', types['E'].fields[0].mojom_name) + self.assertEqual(0, types['E'].fields[0].numeric_value) + self.assertEqual('kB', types['E'].fields[1].mojom_name) + self.assertEqual(1, types['E'].fields[1].numeric_value) + self.assertEqual('kFirst', types['E'].fields[2].mojom_name) + self.assertEqual(0, types['E'].fields[2].numeric_value) + + def testSameModuleOtherEnumReference(self): + """Verifies that an enum value can be assigned from the value of a field + in another enum within the same module.""" + types = self.ExtractTypes('enum E { kA, kB }; enum F { kA = E.kB };') + self.assertEqual(1, types['F'].fields[0].numeric_value) + + def testImportedEnumReference(self): + """Verifies that an enum value can be assigned from the value of a field + in another enum within a different module.""" + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'module a; enum E { kFoo=42, kBar };') + b_mojom = 'b.mojom' + self.WriteFile(b_mojom, + 'module b; import "a.mojom"; enum F { kFoo = a.E.kBar };') + self.ParseMojoms([a_mojom, b_mojom]) + b = self.LoadModule(b_mojom) + + self.assertEqual('F', b.enums[0].mojom_name) + self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name) + self.assertEqual(43, b.enums[0].fields[0].numeric_value) + + def testConstantReference(self): + """Verifies that an enum value can be assigned from the value of an + integral constant within the same module.""" + types = self.ExtractTypes('const int32 kFoo = 42; enum E { kA = kFoo };') + self.assertEqual(42, types['E'].fields[0].numeric_value) + + def testInvalidConstantReference(self): + """Verifies that enum values cannot be assigned from the value of + non-integral constants.""" + with self.assertRaisesRegexp(ValueError, 'not an integer'): + self.ExtractTypes('const float kFoo = 1.0; enum E { kA = kFoo };') + with self.assertRaisesRegexp(ValueError, 'not an integer'): + self.ExtractTypes('const double kFoo = 1.0; enum E { kA = kFoo };') + with self.assertRaisesRegexp(ValueError, 'not an integer'): + self.ExtractTypes('const string kFoo = "lol"; enum E { kA = kFoo };') + + def testImportedConstantReference(self): + """Verifies that an enum value can be assigned from the value of an integral + constant within an imported module.""" + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'module a; const int32 kFoo = 37;') + b_mojom = 'b.mojom' + self.WriteFile(b_mojom, + 'module b; import "a.mojom"; enum F { kFoo = a.kFoo };') + self.ParseMojoms([a_mojom, b_mojom]) + b = self.LoadModule(b_mojom) + + self.assertEqual('F', b.enums[0].mojom_name) + self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name) + self.assertEqual(37, b.enums[0].fields[0].numeric_value) diff --git a/chromium/mojo/public/tools/mojom/mojom/generate/generator.py b/chromium/mojo/public/tools/mojom/mojom/generate/generator.py index 3471e6799a8..de62260a5c9 100644 --- a/chromium/mojo/public/tools/mojom/mojom/generate/generator.py +++ b/chromium/mojo/public/tools/mojom/mojom/generate/generator.py @@ -163,33 +163,30 @@ def AddComputedData(module): struct.versions = pack.GetVersionInfo(struct.packed) struct.exported = exported - def _AddUnionComputedData(union): - ordinal = 0 - for field in union.fields: - if field.ordinal is not None: - ordinal = field.ordinal - field.ordinal = ordinal - ordinal += 1 - def _AddInterfaceComputedData(interface): - next_ordinal = 0 interface.version = 0 for method in interface.methods: - if method.ordinal is None: - method.ordinal = next_ordinal # this field is never scrambled - method.sequential_ordinal = next_ordinal - next_ordinal = method.ordinal + 1 + method.sequential_ordinal = method.ordinal if method.min_version is not None: interface.version = max(interface.version, method.min_version) method.param_struct = _GetStructFromMethod(method) + if interface.stable: + method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True + if method.explicit_ordinal is None: + raise Exception( + 'Stable interfaces must declare explicit method ordinals. The ' + 'method %s on stable interface %s does not declare an explicit ' + 'ordinal.' % (method.mojom_name, interface.qualified_name)) interface.version = max(interface.version, method.param_struct.versions[-1].version) if method.response_parameters is not None: method.response_param_struct = _GetResponseStructFromMethod(method) + if interface.stable: + method.response_param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True interface.version = max( interface.version, method.response_param_struct.versions[-1].version) @@ -200,7 +197,9 @@ def AddComputedData(module): """Converts a method's parameters into the fields of a struct.""" params_class = "%s_%s_Params" % (method.interface.mojom_name, method.mojom_name) - struct = mojom.Struct(params_class, module=method.interface.module) + struct = mojom.Struct(params_class, + module=method.interface.module, + attributes={}) for param in method.parameters: struct.AddField( param.mojom_name, @@ -214,7 +213,9 @@ def AddComputedData(module): """Converts a method's response_parameters into the fields of a struct.""" params_class = "%s_%s_ResponseParams" % (method.interface.mojom_name, method.mojom_name) - struct = mojom.Struct(params_class, module=method.interface.module) + struct = mojom.Struct(params_class, + module=method.interface.module, + attributes={}) for param in method.response_parameters: struct.AddField( param.mojom_name, @@ -226,8 +227,6 @@ def AddComputedData(module): for struct in module.structs: _AddStructComputedData(True, struct) - for union in module.unions: - _AddUnionComputedData(union) for interface in module.interfaces: _AddInterfaceComputedData(interface) diff --git a/chromium/mojo/public/tools/mojom/mojom/generate/module.py b/chromium/mojo/public/tools/mojom/mojom/generate/module.py index f77cbe227b9..f8eef7c8fe7 100644 --- a/chromium/mojo/public/tools/mojom/mojom/generate/module.py +++ b/chromium/mojo/public/tools/mojom/mojom/generate/module.py @@ -254,6 +254,7 @@ PRIMITIVES = ( ATTRIBUTE_MIN_VERSION = 'MinVersion' ATTRIBUTE_EXTENSIBLE = 'Extensible' +ATTRIBUTE_STABLE = 'Stable' ATTRIBUTE_SYNC = 'Sync' @@ -264,7 +265,7 @@ class NamedValue(object): self.mojom_name = mojom_name def GetSpec(self): - return (self.module.mojom_namespace + '.' + + return (self.module.GetNamespacePrefix() + (self.parent_kind and (self.parent_kind.mojom_name + '.') or "") + self.mojom_name) @@ -299,9 +300,9 @@ class EnumValue(NamedValue): self.enum = enum def GetSpec(self): - return (self.module.mojom_namespace + '.' + - (self.parent_kind and (self.parent_kind.mojom_name + '.') - or "") + self.enum.mojom_name + '.' + self.mojom_name) + return (self.module.GetNamespacePrefix() + + (self.parent_kind and (self.parent_kind.mojom_name + '.') or "") + + self.enum.mojom_name + '.' + self.mojom_name) @property def name(self): @@ -371,6 +372,16 @@ class UnionField(Field): pass +def _IsFieldBackwardCompatible(new_field, old_field): + if (new_field.min_version or 0) != (old_field.min_version or 0): + return False + + if isinstance(new_field.kind, (Enum, Struct, Union)): + return new_field.kind.IsBackwardCompatible(old_field.kind) + + return new_field.kind == old_field.kind + + class Struct(ReferenceKind): """A struct with typed fields. @@ -443,6 +454,84 @@ class Struct(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) + def IsBackwardCompatible(self, older_struct): + """This struct is backward-compatible with older_struct if and only if all + of the following conditions hold: + - Any newly added field is tagged with a [MinVersion] attribute specifying + a version number greater than all previously used [MinVersion] + attributes within the struct. + - All fields present in older_struct remain present in the new struct, + with the same ordinal position, same optional or non-optional status, + same (or backward-compatible) type and where applicable, the same + [MinVersion] attribute value. + - All [MinVersion] attributes must be non-decreasing in ordinal order. + - All reference-typed (string, array, map, struct, or union) fields tagged + with a [MinVersion] greater than zero must be optional. + """ + + def buildOrdinalFieldMap(struct): + fields_by_ordinal = {} + for field in struct.fields: + if field.ordinal in fields_by_ordinal: + raise Exception('Multiple fields with ordinal %s in struct %s.' % + (field.ordinal, struct.mojom_name)) + fields_by_ordinal[field.ordinal] = field + return fields_by_ordinal + + new_fields = buildOrdinalFieldMap(self) + old_fields = buildOrdinalFieldMap(older_struct) + if len(new_fields) < len(old_fields): + # At least one field was removed, which is not OK. + return False + + # If there are N fields, existing ordinal values must exactly cover the + # range from 0 to N-1. + num_old_ordinals = len(old_fields) + max_old_min_version = 0 + for ordinal in range(num_old_ordinals): + new_field = new_fields[ordinal] + old_field = old_fields[ordinal] + if (old_field.min_version or 0) > max_old_min_version: + max_old_min_version = old_field.min_version + if not _IsFieldBackwardCompatible(new_field, old_field): + # Type or min-version mismatch between old and new versions of the same + # ordinal field. + return False + + # At this point we know all old fields are intact in the new struct + # definition. Now verify that all new fields have a high enough min version + # and are appropriately optional where required. + num_new_ordinals = len(new_fields) + last_min_version = max_old_min_version + for ordinal in range(num_old_ordinals, num_new_ordinals): + new_field = new_fields[ordinal] + min_version = new_field.min_version or 0 + if min_version <= max_old_min_version: + # A new field is being added to an existing version, which is not OK. + return False + if min_version < last_min_version: + # The [MinVersion] of a field cannot be lower than the [MinVersion] of + # a field with lower ordinal value. + return False + if IsReferenceKind(new_field.kind) and not IsNullableKind(new_field.kind): + # New fields whose type can be nullable MUST be nullable. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + def __eq__(self, rhs): return (isinstance(rhs, Struct) and (self.mojom_name, self.native_only, self.fields, self.constants, @@ -498,6 +587,67 @@ class Union(ReferenceKind): for field in self.fields: field.Stylize(stylizer) + def IsBackwardCompatible(self, older_union): + """This union is backward-compatible with older_union if and only if all + of the following conditions hold: + - Any newly added field is tagged with a [MinVersion] attribute specifying + a version number greater than all previously used [MinVersion] + attributes within the union. + - All fields present in older_union remain present in the new union, + with the same ordinal value, same optional or non-optional status, + same (or backward-compatible) type, and where applicable, the same + [MinVersion] attribute value. + """ + + def buildOrdinalFieldMap(union): + fields_by_ordinal = {} + for field in union.fields: + if field.ordinal in fields_by_ordinal: + raise Exception('Multiple fields with ordinal %s in union %s.' % + (field.ordinal, union.mojom_name)) + fields_by_ordinal[field.ordinal] = field + return fields_by_ordinal + + new_fields = buildOrdinalFieldMap(self) + old_fields = buildOrdinalFieldMap(older_union) + if len(new_fields) < len(old_fields): + # At least one field was removed, which is not OK. + return False + + max_old_min_version = 0 + for ordinal, old_field in old_fields.items(): + new_field = new_fields.get(ordinal) + if not new_field: + # A field was removed, which is not OK. + return False + if not _IsFieldBackwardCompatible(new_field, old_field): + # An field changed its type or MinVersion, which is not OK. + return False + old_min_version = old_field.min_version or 0 + if old_min_version > max_old_min_version: + max_old_min_version = old_min_version + + new_ordinals = set(new_fields.keys()) - set(old_fields.keys()) + for ordinal in new_ordinals: + if (new_fields[ordinal].min_version or 0) <= max_old_min_version: + # New fields must use a MinVersion greater than any old fields. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + def __eq__(self, rhs): return (isinstance(rhs, Union) and (self.mojom_name, self.fields, @@ -760,6 +910,7 @@ class Method(object): self.interface = interface self.mojom_name = mojom_name self.name = None + self.explicit_ordinal = ordinal self.ordinal = ordinal self.parameters = [] self.param_struct = None @@ -875,6 +1026,91 @@ class Interface(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) + def IsBackwardCompatible(self, older_interface): + """This interface is backward-compatible with older_interface if and only + if all of the following conditions hold: + - All defined methods in older_interface (when identified by ordinal) have + backward-compatible definitions in this interface. For each method this + means: + - The parameter list is backward-compatible, according to backward- + compatibility rules for structs, where each parameter is essentially + a struct field. + - If the old method definition does not specify a reply message, the + new method definition must not specify a reply message. + - If the old method definition specifies a reply message, the new + method definition must also specify a reply message with a parameter + list that is backward-compatible according to backward-compatibility + rules for structs. + - All newly introduced methods in this interface have a [MinVersion] + attribute specifying a version greater than any method in + older_interface. + """ + + def buildOrdinalMethodMap(interface): + methods_by_ordinal = {} + for method in interface.methods: + if method.ordinal in methods_by_ordinal: + raise Exception('Multiple methods with ordinal %s in interface %s.' % + (method.ordinal, interface.mojom_name)) + methods_by_ordinal[method.ordinal] = method + return methods_by_ordinal + + new_methods = buildOrdinalMethodMap(self) + old_methods = buildOrdinalMethodMap(older_interface) + max_old_min_version = 0 + for ordinal, old_method in old_methods.items(): + new_method = new_methods.get(ordinal) + if not new_method: + # A method was removed, which is not OK. + return False + + if not new_method.param_struct.IsBackwardCompatible( + old_method.param_struct): + # The parameter list is not backward-compatible, which is not OK. + return False + + if old_method.response_param_struct is None: + if new_method.response_param_struct is not None: + # A reply was added to a message which didn't have one before, and + # this is not OK. + return False + else: + if new_method.response_param_struct is None: + # A reply was removed from a message, which is not OK. + return False + if not new_method.response_param_struct.IsBackwardCompatible( + old_method.response_param_struct): + # The new message's reply is not backward-compatible with the old + # message's reply, which is not OK. + return False + + if (old_method.min_version or 0) > max_old_min_version: + max_old_min_version = old_method.min_version + + # All the old methods are compatible with their new counterparts. Now verify + # that newly added methods are properly versioned. + new_ordinals = set(new_methods.keys()) - set(old_methods.keys()) + for ordinal in new_ordinals: + new_method = new_methods[ordinal] + if (new_method.min_version or 0) <= max_old_min_version: + # A method was added to an existing version, which is not OK. + return False + + return True + + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + def __eq__(self, rhs): return (isinstance(rhs, Interface) and (self.mojom_name, self.methods, self.enums, self.constants, @@ -964,6 +1200,50 @@ class Enum(Kind): return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ if self.attributes else False + @property + def stable(self): + return self.attributes.get(ATTRIBUTE_STABLE, False) \ + if self.attributes else False + + @property + def qualified_name(self): + if self.parent_kind: + prefix = self.parent_kind.qualified_name + '.' + else: + prefix = self.module.GetNamespacePrefix() + return '%s%s' % (prefix, self.mojom_name) + + def IsBackwardCompatible(self, older_enum): + """This enum is backward-compatible with older_enum if and only if one of + the following conditions holds: + - Neither enum is [Extensible] and both have the exact same set of valid + numeric values. Field names and aliases for the same numeric value do + not affect compatibility. + - older_enum is [Extensible], and for every version defined by + older_enum, this enum has the exact same set of valid numeric values. + """ + + def buildVersionFieldMap(enum): + fields_by_min_version = {} + for field in enum.fields: + if field.min_version not in fields_by_min_version: + fields_by_min_version[field.min_version] = set() + fields_by_min_version[field.min_version].add(field.numeric_value) + return fields_by_min_version + + old_fields = buildVersionFieldMap(older_enum) + new_fields = buildVersionFieldMap(self) + + if new_fields.keys() != old_fields.keys() and not older_enum.extensible: + return False + + for min_version, valid_values in old_fields.items(): + if (min_version not in new_fields + or new_fields[min_version] != valid_values): + return False + + return True + def __eq__(self, rhs): return (isinstance(rhs, Enum) and (self.mojom_name, self.native_only, self.fields, self.attributes, @@ -1017,6 +1297,9 @@ class Module(object): 'unions': False }) + def GetNamespacePrefix(self): + return '%s.' % self.mojom_namespace if self.mojom_namespace else '' + def AddInterface(self, mojom_name, attributes=None): interface = Interface(mojom_name, self, attributes) self.interfaces.append(interface) diff --git a/chromium/mojo/public/tools/mojom/mojom/generate/translate.py b/chromium/mojo/public/tools/mojom/mojom/generate/translate.py index 6854b8bfed9..d6df3ca6da8 100644 --- a/chromium/mojo/public/tools/mojom/mojom/generate/translate.py +++ b/chromium/mojo/public/tools/mojom/mojom/generate/translate.py @@ -12,11 +12,19 @@ already been parsed and converted to ASTs before. import itertools import os import re +import sys +from mojom.generate import generator from mojom.generate import module as mojom from mojom.parse import ast +def _IsStrOrUnicode(x): + if sys.version_info[0] < 3: + return isinstance(x, (unicode, str)) + return isinstance(x, str) + + def _DuplicateName(values): """Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name' has already been encountered. If there are no duplicates, returns None.""" @@ -50,6 +58,23 @@ def _ElemsOfType(elems, elem_type, scope): return result +def _ProcessElements(scope, elements, operations_by_type): + """Iterates over the given elements, running a function from + operations_by_type for any element that matches a key in that dict. The scope + is the name of the surrounding scope, such as a filename or struct name, used + only in error messages.""" + names_in_this_scope = set() + for element in elements: + # pylint: disable=unidiomatic-typecheck + element_type = type(element) + if element_type in operations_by_type: + if element.mojom_name in names_in_this_scope: + raise Exception('Names must be unique within a scope. The name "%s" is ' + 'used more than once within the scope "%s".' % + (duplicate_name, scope)) + operations_by_type[element_type](element) + + def _MapKind(kind): map_to_kind = { 'bool': 'b', @@ -153,39 +178,62 @@ def _LookupKind(kinds, spec, scope): return kinds.get(spec) -def _LookupValue(values, mojom_name, scope, kind): - """Like LookupKind, but for constant values.""" - # If the type is an enum, the value can be specified as a qualified name, in - # which case the form EnumName.ENUM_VALUE must be used. We use the presence - # of a '.' in the requested name to identify this. Otherwise, we prepend the - # enum name. - if isinstance(kind, mojom.Enum) and '.' not in mojom_name: - mojom_name = '%s.%s' % (kind.spec.split(':', 1)[1], mojom_name) +def _GetScopeForKind(module, kind): + """For a given kind, returns a tuple of progressively more specific names + used to qualify the kind. For example if kind is an enum named Bar nested in a + struct Foo within module 'foo', this would return ('foo', 'Foo', 'Bar')""" + if isinstance(kind, mojom.Enum) and kind.parent_kind: + # Enums may be nested in other kinds. + return _GetScopeForKind(module, kind.parent_kind) + (kind.mojom_name, ) + + module_fragment = (module.mojom_namespace, ) if module.mojom_namespace else () + kind_fragment = (kind.mojom_name, ) if kind else () + return module_fragment + kind_fragment + + +def _LookupValueInScope(module, kind, identifier): + """Given a kind and an identifier, this attempts to resolve the given + identifier to a concrete NamedValue within the scope of the given kind.""" + scope = _GetScopeForKind(module, kind) for i in reversed(range(len(scope) + 1)): - test_spec = '.'.join(scope[:i]) - if test_spec: - test_spec += '.' - test_spec += mojom_name - value = values.get(test_spec) + qualified_name = '.'.join(scope[:i] + (identifier, )) + value = module.values.get(qualified_name) if value: return value + return None + + +def _LookupValue(module, parent_kind, implied_kind, ast_leaf_node): + """Resolves a leaf node in the form ('IDENTIFIER', 'x') to a constant value + identified by 'x' in some mojom definition. parent_kind is used as context + when resolving the identifier. If the given leaf node is not an IDENTIFIER + (e.g. already a constant value), it is returned as-is. + + If implied_kind is provided, the parsed identifier may also be resolved within + its scope as fallback. This can be useful for more concise value references + when assigning enum-typed constants or field values.""" + if not isinstance(ast_leaf_node, tuple) or ast_leaf_node[0] != 'IDENTIFIER': + return ast_leaf_node - return values.get(mojom_name) + # First look for a known user-defined identifier to resolve this within the + # enclosing scope. + identifier = ast_leaf_node[1] + value = _LookupValueInScope(module, parent_kind, identifier) + if value: + return value -def _FixupExpression(module, value, scope, kind): - """Translates an IDENTIFIER into a built-in value or structured NamedValue - object.""" - if isinstance(value, tuple) and value[0] == 'IDENTIFIER': - # Allow user defined values to shadow builtins. - result = _LookupValue(module.values, value[1], scope, kind) - if result: - if isinstance(result, tuple): - raise Exception('Unable to resolve expression: %r' % value[1]) - return result - if _IsBuiltinValue(value[1]): - return mojom.BuiltinValue(value[1]) - return value + # Next look in the scope of implied_kind, if provided. + value = (implied_kind and implied_kind.module and _LookupValueInScope( + implied_kind.module, implied_kind, identifier)) + if value: + return value + + # Fall back on defined builtin symbols + if _IsBuiltinValue(identifier): + return mojom.BuiltinValue(identifier) + + raise ValueError('Unknown identifier %s' % identifier) def _Kind(kinds, spec, scope): @@ -280,26 +328,22 @@ def _Struct(module, parsed_struct): struct = mojom.Struct(module=module) struct.mojom_name = parsed_struct.mojom_name struct.native_only = parsed_struct.body is None - struct.spec = 'x:' + module.mojom_namespace + '.' + struct.mojom_name + struct.spec = 'x:' + module.GetNamespacePrefix() + struct.mojom_name module.kinds[struct.spec] = struct - if struct.native_only: - struct.enums = [] - struct.constants = [] - struct.fields_data = [] - else: - struct.enums = list( - map( - lambda enum: _Enum(module, enum, struct), - _ElemsOfType(parsed_struct.body, ast.Enum, - parsed_struct.mojom_name))) - struct.constants = list( - map( - lambda constant: _Constant(module, constant, struct), - _ElemsOfType(parsed_struct.body, ast.Const, - parsed_struct.mojom_name))) - # Stash fields parsed_struct here temporarily. - struct.fields_data = _ElemsOfType(parsed_struct.body, ast.StructField, - parsed_struct.mojom_name) + struct.enums = [] + struct.constants = [] + struct.fields_data = [] + if not struct.native_only: + _ProcessElements( + parsed_struct.mojom_name, parsed_struct.body, { + ast.Enum: + lambda enum: struct.enums.append(_Enum(module, enum, struct)), + ast.Const: + lambda const: struct.constants.append( + _Constant(module, const, struct)), + ast.StructField: + struct.fields_data.append, + }) struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) @@ -327,11 +371,12 @@ def _Union(module, parsed_union): """ union = mojom.Union(module=module) union.mojom_name = parsed_union.mojom_name - union.spec = 'x:' + module.mojom_namespace + '.' + union.mojom_name + union.spec = 'x:' + module.GetNamespacePrefix() + union.mojom_name module.kinds[union.spec] = union # Stash fields parsed_union here temporarily. - union.fields_data = _ElemsOfType(parsed_union.body, ast.UnionField, - parsed_union.mojom_name) + union.fields_data = [] + _ProcessElements(parsed_union.mojom_name, parsed_union.body, + {ast.UnionField: union.fields_data.append}) union.attributes = _AttributeListToDict(parsed_union.attribute_list) return union @@ -351,9 +396,8 @@ def _StructField(module, parsed_field, struct): field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename), (module.mojom_namespace, struct.mojom_name)) field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None - field.default = _FixupExpression(module, parsed_field.default_value, - (module.mojom_namespace, struct.mojom_name), - field.kind) + field.default = _LookupValue(module, struct, field.kind, + parsed_field.default_value) field.attributes = _AttributeListToDict(parsed_field.attribute_list) return field @@ -373,8 +417,7 @@ def _UnionField(module, parsed_field, union): field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename), (module.mojom_namespace, union.mojom_name)) field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None - field.default = _FixupExpression( - module, None, (module.mojom_namespace, union.mojom_name), field.kind) + field.default = None field.attributes = _AttributeListToDict(parsed_field.attribute_list) return field @@ -444,89 +487,85 @@ def _Interface(module, parsed_iface): """ interface = mojom.Interface(module=module) interface.mojom_name = parsed_iface.mojom_name - interface.spec = 'x:' + module.mojom_namespace + '.' + interface.mojom_name + interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name module.kinds[interface.spec] = interface - interface.enums = list( - map(lambda enum: _Enum(module, enum, interface), - _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.mojom_name))) - interface.constants = list( - map(lambda constant: _Constant(module, constant, interface), - _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.mojom_name))) - # Stash methods parsed_iface here temporarily. - interface.methods_data = _ElemsOfType(parsed_iface.body, ast.Method, - parsed_iface.mojom_name) interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) + interface.enums = [] + interface.constants = [] + interface.methods_data = [] + _ProcessElements( + parsed_iface.mojom_name, parsed_iface.body, { + ast.Enum: + lambda enum: interface.enums.append(_Enum(module, enum, interface)), + ast.Const: + lambda const: interface.constants.append( + _Constant(module, const, interface)), + ast.Method: + interface.methods_data.append, + }) return interface -def _EnumField(module, enum, parsed_field, parent_kind): +def _EnumField(module, enum, parsed_field): """ Args: module: {mojom.Module} Module currently being constructed. enum: {mojom.Enum} Enum this field belongs to. parsed_field: {ast.EnumValue} Parsed enum value. - parent_kind: {mojom.Kind} The enclosing type. Returns: {mojom.EnumField} AST enum field. """ field = mojom.EnumField() field.mojom_name = parsed_field.mojom_name - # TODO(mpcomplete): FixupExpression should be done in the second pass, - # so constants and enums can refer to each other. - # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or - # vice versa? - if parent_kind: - field.value = _FixupExpression( - module, parsed_field.value, - (module.mojom_namespace, parent_kind.mojom_name), enum) - else: - field.value = _FixupExpression(module, parsed_field.value, - (module.mojom_namespace, ), enum) + field.value = _LookupValue(module, enum, None, parsed_field.value) field.attributes = _AttributeListToDict(parsed_field.attribute_list) value = mojom.EnumValue(module, enum, field) module.values[value.GetSpec()] = value return field -def _ResolveNumericEnumValues(enum_fields): +def _ResolveNumericEnumValues(enum): """ - Given a reference to a list of mojom.EnumField, resolves and assigns their - values to EnumField.numeric_value. - - Returns: - A tuple of the lowest and highest assigned enumerator value or None, None - if no enumerator values were assigned. + Given a reference to a mojom.Enum, resolves and assigns the numeric value of + each field, and also computes the min_value and max_value of the enum. """ # map of <mojom_name> -> integral value - resolved_enum_values = {} prev_value = -1 min_value = None max_value = None - for field in enum_fields: + for field in enum.fields: # This enum value is +1 the previous enum value (e.g: BEGIN). if field.value is None: prev_value += 1 # Integral value (e.g: BEGIN = -0x1). - elif isinstance(field.value, str): + elif _IsStrOrUnicode(field.value): prev_value = int(field.value, 0) # Reference to a previous enum value (e.g: INIT = BEGIN). elif isinstance(field.value, mojom.EnumValue): - prev_value = resolved_enum_values[field.value.mojom_name] + prev_value = field.value.field.numeric_value + elif isinstance(field.value, mojom.ConstantValue): + constant = field.value.constant + kind = constant.kind + if not mojom.IsIntegralKind(kind) or mojom.IsBoolKind(kind): + raise ValueError('Enum values must be integers. %s is not an integer.' % + constant.mojom_name) + prev_value = int(constant.value, 0) else: - raise Exception("Unresolved enum value.") + raise Exception('Unresolved enum value for %s' % field.value.GetSpec()) - resolved_enum_values[field.mojom_name] = prev_value + #resolved_enum_values[field.mojom_name] = prev_value field.numeric_value = prev_value if min_value is None or prev_value < min_value: min_value = prev_value if max_value is None or prev_value > max_value: max_value = prev_value - return min_value, max_value + enum.min_value = min_value + enum.max_value = max_value def _Enum(module, parsed_enum, parent_kind): @@ -547,11 +586,12 @@ def _Enum(module, parsed_enum, parent_kind): enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name) enum.parent_kind = parent_kind enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) + if not enum.native_only: enum.fields = list( - map(lambda field: _EnumField(module, enum, field, parent_kind), + map(lambda field: _EnumField(module, enum, field), parsed_enum.enum_value_list)) - enum.min_value, enum.max_value = _ResolveNumericEnumValues(enum.fields) + _ResolveNumericEnumValues(enum) module.kinds[enum.spec] = enum @@ -583,7 +623,12 @@ def _Constant(module, parsed_const, parent_kind): # TODO(mpcomplete): maybe we should only support POD kinds. constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope) constant.parent_kind = parent_kind - constant.value = _FixupExpression(module, parsed_const.value, scope, None) + constant.value = _LookupValue(module, parent_kind, constant.kind, + parsed_const.value) + + # Iteratively resolve this constant reference to a concrete value + while isinstance(constant.value, mojom.ConstantValue): + constant.value = constant.value.constant.value value = mojom.ConstantValue(module, parent_kind, constant) module.values[value.GetSpec()] = value @@ -643,6 +688,47 @@ def _CollectReferencedKinds(module, all_defined_kinds): return referenced_user_kinds +def _AssignDefaultOrdinals(items): + """Assigns default ordinal values to a sequence of items if necessary.""" + next_ordinal = 0 + for item in items: + if item.ordinal is not None: + next_ordinal = item.ordinal + 1 + else: + item.ordinal = next_ordinal + next_ordinal += 1 + + +def _AssertTypeIsStable(kind): + """Raises an error if a type is not stable, meaning it is composed of at least + one type that is not marked [Stable].""" + + def assertDependencyIsStable(dependency): + if (mojom.IsEnumKind(dependency) or mojom.IsStructKind(dependency) + or mojom.IsUnionKind(dependency) or mojom.IsInterfaceKind(dependency)): + if not dependency.stable: + raise Exception( + '%s is marked [Stable] but cannot be stable because it depends on ' + '%s, which is not marked [Stable].' % + (kind.mojom_name, dependency.mojom_name)) + elif mojom.IsArrayKind(dependency) or mojom.IsAnyInterfaceKind(dependency): + assertDependencyIsStable(dependency.kind) + elif mojom.IsMapKind(dependency): + assertDependencyIsStable(dependency.key_kind) + assertDependencyIsStable(dependency.value_kind) + + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + for field in kind.fields: + assertDependencyIsStable(field.kind) + elif mojom.IsInterfaceKind(kind): + for method in kind.methods: + for param in method.param_struct.fields: + assertDependencyIsStable(param.kind) + if method.response_param_struct: + for response_param in method.response_param_struct.fields: + assertDependencyIsStable(response_param.kind) + + def _Module(tree, path, imports): """ Args: @@ -675,21 +761,25 @@ def _Module(tree, path, imports): filename = os.path.basename(path) # First pass collects kinds. - module.enums = list( - map(lambda enum: _Enum(module, enum, None), - _ElemsOfType(tree.definition_list, ast.Enum, filename))) - module.structs = list( - map(lambda struct: _Struct(module, struct), - _ElemsOfType(tree.definition_list, ast.Struct, filename))) - module.unions = list( - map(lambda union: _Union(module, union), - _ElemsOfType(tree.definition_list, ast.Union, filename))) - module.interfaces = list( - map(lambda interface: _Interface(module, interface), - _ElemsOfType(tree.definition_list, ast.Interface, filename))) - module.constants = list( - map(lambda constant: _Constant(module, constant, None), - _ElemsOfType(tree.definition_list, ast.Const, filename))) + module.constants = [] + module.enums = [] + module.structs = [] + module.unions = [] + module.interfaces = [] + _ProcessElements( + filename, tree.definition_list, { + ast.Const: + lambda const: module.constants.append(_Constant(module, const, None)), + ast.Enum: + lambda enum: module.enums.append(_Enum(module, enum, None)), + ast.Struct: + lambda struct: module.structs.append(_Struct(module, struct)), + ast.Union: + lambda union: module.unions.append(_Union(module, union)), + ast.Interface: + lambda interface: module.interfaces.append( + _Interface(module, interface)), + }) # Second pass expands fields and methods. This allows fields and parameters # to refer to kinds defined anywhere in the mojom. @@ -698,19 +788,24 @@ def _Module(tree, path, imports): struct.fields = list( map(lambda field: _StructField(module, field, struct), struct.fields_data)) + _AssignDefaultOrdinals(struct.fields) del struct.fields_data all_defined_kinds[struct.spec] = struct for enum in struct.enums: all_defined_kinds[enum.spec] = enum + for union in module.unions: union.fields = list( map(lambda field: _UnionField(module, field, union), union.fields_data)) + _AssignDefaultOrdinals(union.fields) del union.fields_data all_defined_kinds[union.spec] = union + for interface in module.interfaces: interface.methods = list( map(lambda method: _Method(module, method, interface), interface.methods_data)) + _AssignDefaultOrdinals(interface.methods) del interface.methods_data all_defined_kinds[interface.spec] = interface for enum in interface.enums: @@ -725,6 +820,21 @@ def _Module(tree, path, imports): module.imported_kinds = dict( (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs) + generator.AddComputedData(module) + for iface in module.interfaces: + for method in iface.methods: + if method.param_struct: + _AssignDefaultOrdinals(method.param_struct.fields) + if method.response_param_struct: + _AssignDefaultOrdinals(method.response_param_struct.fields) + + # Ensure that all types marked [Stable] are actually stable. Enums are + # automatically OK since they don't depend on other definitions. + for kinds in (module.structs, module.unions, module.interfaces): + for kind in kinds: + if kind.stable: + _AssertTypeIsStable(kind) + return module diff --git a/chromium/mojo/public/tools/mojom/mojom/parse/ast.py b/chromium/mojo/public/tools/mojom/mojom/parse/ast.py index c9b6605cf43..1f0db200549 100644 --- a/chromium/mojo/public/tools/mojom/mojom/parse/ast.py +++ b/chromium/mojo/public/tools/mojom/mojom/parse/ast.py @@ -9,6 +9,15 @@ # failures, especially for more complex types. +import sys + + +def _IsStrOrUnicode(x): + if sys.version_info[0] < 3: + return isinstance(x, (unicode, str)) + return isinstance(x, str) + + class NodeBase(object): """Base class for nodes in the AST.""" @@ -87,7 +96,7 @@ class Definition(NodeBase): include parameter definitions.) This class is meant to be subclassed.""" def __init__(self, mojom_name, **kwargs): - assert isinstance(mojom_name, str) + assert _IsStrOrUnicode(mojom_name) NodeBase.__init__(self, **kwargs) self.mojom_name = mojom_name @@ -99,7 +108,7 @@ class Attribute(NodeBase): """Represents an attribute.""" def __init__(self, key, value, **kwargs): - assert isinstance(key, str) + assert _IsStrOrUnicode(key) super(Attribute, self).__init__(**kwargs) self.key = key self.value = value @@ -122,10 +131,10 @@ class Const(Definition): def __init__(self, mojom_name, attribute_list, typename, value, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) # The typename is currently passed through as a string. - assert isinstance(typename, str) + assert _IsStrOrUnicode(typename) # The value is either a literal (currently passed through as a string) or a # "wrapped identifier". - assert isinstance(value, str) or isinstance(value, tuple) + assert _IsStrOrUnicode or isinstance(value, tuple) super(Const, self).__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.typename = typename @@ -161,7 +170,7 @@ class EnumValue(Definition): # The optional value is either an int (which is current a string) or a # "wrapped identifier". assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert value is None or isinstance(value, (str, tuple)) + assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple) super(EnumValue, self).__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.value = value @@ -184,7 +193,7 @@ class Import(NodeBase): def __init__(self, attribute_list, import_filename, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert isinstance(import_filename, str) + assert _IsStrOrUnicode(import_filename) super(Import, self).__init__(**kwargs) self.attribute_list = attribute_list self.import_filename = import_filename @@ -305,10 +314,10 @@ class Parameter(NodeBase): """Represents a method request or response parameter.""" def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert isinstance(mojom_name, str) + assert _IsStrOrUnicode(mojom_name) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert isinstance(typename, str) + assert _IsStrOrUnicode(typename) super(Parameter, self).__init__(**kwargs) self.mojom_name = mojom_name self.attribute_list = attribute_list @@ -350,13 +359,14 @@ class StructField(Definition): def __init__(self, mojom_name, attribute_list, ordinal, typename, default_value, **kwargs): - assert isinstance(mojom_name, str) + assert _IsStrOrUnicode(mojom_name) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert isinstance(typename, str) + assert _IsStrOrUnicode(typename) # The optional default value is currently either a value as a string or a # "wrapped identifier". - assert default_value is None or isinstance(default_value, (str, tuple)) + assert default_value is None or _IsStrOrUnicode(default_value) or \ + isinstance(default_value, tuple) super(StructField, self).__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal @@ -396,10 +406,10 @@ class Union(Definition): class UnionField(Definition): def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert isinstance(mojom_name, str) + assert _IsStrOrUnicode(mojom_name) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert isinstance(typename, str) + assert _IsStrOrUnicode(typename) super(UnionField, self).__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal diff --git a/chromium/mojo/public/tools/mojom/mojom/parse/parser.py b/chromium/mojo/public/tools/mojom/mojom/parse/parser.py index fbde9889df7..b3b803d6f33 100644 --- a/chromium/mojo/public/tools/mojom/mojom/parse/parser.py +++ b/chromium/mojo/public/tools/mojom/mojom/parse/parser.py @@ -472,7 +472,7 @@ def Parse(source, filename): """Parse source file to AST. Args: - source: The source text as a str. + source: The source text as a str (Python 2 or 3) or unicode (Python 2). filename: The filename that |source| originates from. Returns: diff --git a/chromium/mojo/public/tools/mojom/mojom_parser.py b/chromium/mojo/public/tools/mojom/mojom_parser.py index 0d63a4ad933..12adbfb9d3f 100755 --- a/chromium/mojo/public/tools/mojom/mojom_parser.py +++ b/chromium/mojo/public/tools/mojom/mojom_parser.py @@ -11,6 +11,7 @@ generate usable language bindings. """ import argparse +import codecs import errno import json import os @@ -193,7 +194,7 @@ def _ParseMojoms(mojom_files, abs_paths = dict( (path, abs_path) for abs_path, path in mojom_files_to_parse.items()) for mojom_abspath, _ in mojom_files_to_parse.items(): - with open(mojom_abspath) as f: + with codecs.open(mojom_abspath, encoding='utf-8') as f: ast = parser.Parse(''.join(f.readlines()), mojom_abspath) conditional_features.RemoveDisabledDefinitions(ast, enabled_features) loaded_mojom_asts[mojom_abspath] = ast diff --git a/chromium/mojo/public/tools/mojom/mojom_parser_test_case.py b/chromium/mojo/public/tools/mojom/mojom_parser_test_case.py new file mode 100644 index 00000000000..e213fbfa760 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/mojom_parser_test_case.py @@ -0,0 +1,73 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import json +import os +import os.path +import shutil +import tempfile +import unittest + +import mojom_parser + +from mojom.generate import module + + +class MojomParserTestCase(unittest.TestCase): + """Tests covering the behavior defined by the main mojom_parser.py script. + This includes behavior around input and output path manipulation, dependency + resolution, and module serialization and deserialization.""" + + def __init__(self, method_name): + super(MojomParserTestCase, self).__init__(method_name) + self._temp_dir = None + + def setUp(self): + self._temp_dir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self._temp_dir) + self._temp_dir = None + + def GetPath(self, path): + assert not os.path.isabs(path) + return os.path.join(self._temp_dir, path) + + def GetModulePath(self, path): + assert not os.path.isabs(path) + return os.path.join(self.GetPath('out'), path) + '-module' + + def WriteFile(self, path, contents): + full_path = self.GetPath(path) + dirname = os.path.dirname(full_path) + if not os.path.exists(dirname): + os.makedirs(dirname) + with open(full_path, 'w') as f: + f.write(contents) + + def LoadModule(self, mojom_path): + with open(self.GetModulePath(mojom_path), 'rb') as f: + return module.Module.Load(f) + + def ParseMojoms(self, mojoms, metadata=None): + """Parse all input mojoms relative the temp dir.""" + out_dir = self.GetPath('out') + args = [ + '--input-root', self._temp_dir, '--input-root', out_dir, + '--output-root', out_dir, '--mojoms' + ] + list(map(lambda mojom: os.path.join(self._temp_dir, mojom), mojoms)) + if metadata: + args.extend(['--check-imports', self.GetPath(metadata)]) + mojom_parser.Run(args) + + def ExtractTypes(self, mojom): + filename = 'test.mojom' + self.WriteFile(filename, mojom) + self.ParseMojoms([filename]) + m = self.LoadModule(filename) + definitions = {} + for kinds in (m.enums, m.structs, m.unions, m.interfaces): + for kind in kinds: + definitions[kind.mojom_name] = kind + return definitions diff --git a/chromium/mojo/public/tools/mojom/mojom_parser_unittest.py b/chromium/mojo/public/tools/mojom/mojom_parser_unittest.py index 9aa655dbed2..a93f34bacb4 100755..100644 --- a/chromium/mojo/public/tools/mojom/mojom_parser_unittest.py +++ b/chromium/mojo/public/tools/mojom/mojom_parser_unittest.py @@ -1,67 +1,15 @@ -#!/usr/bin/env python # Copyright 2020 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import json -import os -import os.path -import shutil -import tempfile -import unittest +from mojom_parser_test_case import MojomParserTestCase -import mojom_parser -from mojom.generate import module - - -class MojomParserTest(unittest.TestCase): +class MojomParserTest(MojomParserTestCase): """Tests covering the behavior defined by the main mojom_parser.py script. This includes behavior around input and output path manipulation, dependency resolution, and module serialization and deserialization.""" - def __init__(self, method_name): - super(MojomParserTest, self).__init__(method_name) - self._temp_dir = None - - def setUp(self): - self._temp_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self._temp_dir) - self._temp_dir = None - - def GetPath(self, path): - assert not os.path.isabs(path) - return os.path.join(self._temp_dir, path) - - def GetModulePath(self, path): - assert not os.path.isabs(path) - return os.path.join(self.GetPath('out'), path) + '-module' - - def WriteFile(self, path, contents): - full_path = self.GetPath(path) - dirname = os.path.dirname(full_path) - if not os.path.exists(dirname): - os.makedirs(dirname) - with open(full_path, 'w') as f: - f.write(contents) - - def LoadModule(self, mojom_path): - with open(self.GetModulePath(mojom_path), 'rb') as f: - return module.Module.Load(f) - - def ParseMojoms(self, mojoms, metadata=None): - """Parse all input mojoms relative the temp dir.""" - out_dir = self.GetPath('out') - args = [ - '--input-root', self._temp_dir, '--input-root', out_dir, - '--output-root', out_dir, '--mojoms' - ] + list(map(lambda mojom: os.path.join(self._temp_dir, mojom), mojoms)) - if metadata: - args.extend(['--check-imports', self.GetPath(metadata)]) - mojom_parser.Run(args) - def testBasicParse(self): """Basic test to verify that we can parse a mojom file and get a module.""" mojom = 'foo/bar.mojom' diff --git a/chromium/mojo/public/tools/mojom/stable_attribute_unittest.py b/chromium/mojo/public/tools/mojom/stable_attribute_unittest.py new file mode 100644 index 00000000000..d45ec586299 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/stable_attribute_unittest.py @@ -0,0 +1,127 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + +from mojom.generate import module + + +class StableAttributeTest(MojomParserTestCase): + """Tests covering usage of the [Stable] attribute.""" + + def testStableAttributeTagging(self): + """Verify that we recognize the [Stable] attribute on relevant definitions + and the resulting parser outputs are tagged accordingly.""" + mojom = 'test.mojom' + self.WriteFile( + mojom, """\ + [Stable] enum TestEnum { kFoo }; + enum UnstableEnum { kBar }; + [Stable] struct TestStruct { TestEnum a; }; + struct UnstableStruct { UnstableEnum a; }; + [Stable] union TestUnion { TestEnum a; TestStruct b; }; + union UnstableUnion { UnstableEnum a; UnstableStruct b; }; + [Stable] interface TestInterface { Foo@0(TestUnion x) => (); }; + interface UnstableInterface { Foo(UnstableUnion x) => (); }; + """) + self.ParseMojoms([mojom]) + + m = self.LoadModule(mojom) + self.assertEqual(2, len(m.enums)) + self.assertTrue(m.enums[0].stable) + self.assertFalse(m.enums[1].stable) + self.assertEqual(2, len(m.structs)) + self.assertTrue(m.structs[0].stable) + self.assertFalse(m.structs[1].stable) + self.assertEqual(2, len(m.unions)) + self.assertTrue(m.unions[0].stable) + self.assertFalse(m.unions[1].stable) + self.assertEqual(2, len(m.interfaces)) + self.assertTrue(m.interfaces[0].stable) + self.assertFalse(m.interfaces[1].stable) + + def testStableStruct(self): + """A [Stable] struct is valid if all its fields are also stable.""" + self.ExtractTypes('[Stable] struct S {};') + self.ExtractTypes('[Stable] struct S { int32 x; bool b; };') + self.ExtractTypes('[Stable] enum E { A }; [Stable] struct S { E e; };') + self.ExtractTypes('[Stable] struct S {}; [Stable] struct T { S s; };') + self.ExtractTypes( + '[Stable] struct S {}; [Stable] struct T { array<S> ss; };') + self.ExtractTypes( + '[Stable] interface F {}; [Stable] struct T { pending_remote<F> f; };') + + with self.assertRaisesRegexp(Exception, 'because it depends on E'): + self.ExtractTypes('enum E { A }; [Stable] struct S { E e; };') + with self.assertRaisesRegexp(Exception, 'because it depends on X'): + self.ExtractTypes('struct X {}; [Stable] struct S { X x; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] struct S { array<T> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] struct S { map<int32, T> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] struct S { map<T, int32> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on F'): + self.ExtractTypes( + 'interface F {}; [Stable] struct S { pending_remote<F> f; };') + with self.assertRaisesRegexp(Exception, 'because it depends on F'): + self.ExtractTypes( + 'interface F {}; [Stable] struct S { pending_receiver<F> f; };') + + def testStableUnion(self): + """A [Stable] union is valid if all its fields' types are also stable.""" + self.ExtractTypes('[Stable] union U {};') + self.ExtractTypes('[Stable] union U { int32 x; bool b; };') + self.ExtractTypes('[Stable] enum E { A }; [Stable] union U { E e; };') + self.ExtractTypes('[Stable] struct S {}; [Stable] union U { S s; };') + self.ExtractTypes( + '[Stable] struct S {}; [Stable] union U { array<S> ss; };') + self.ExtractTypes( + '[Stable] interface F {}; [Stable] union U { pending_remote<F> f; };') + + with self.assertRaisesRegexp(Exception, 'because it depends on E'): + self.ExtractTypes('enum E { A }; [Stable] union U { E e; };') + with self.assertRaisesRegexp(Exception, 'because it depends on X'): + self.ExtractTypes('struct X {}; [Stable] union U { X x; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] union U { array<T> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] union U { map<int32, T> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on T'): + self.ExtractTypes('struct T {}; [Stable] union U { map<T, int32> xs; };') + with self.assertRaisesRegexp(Exception, 'because it depends on F'): + self.ExtractTypes( + 'interface F {}; [Stable] union U { pending_remote<F> f; };') + with self.assertRaisesRegexp(Exception, 'because it depends on F'): + self.ExtractTypes( + 'interface F {}; [Stable] union U { pending_receiver<F> f; };') + + def testStableInterface(self): + """A [Stable] interface is valid if all its methods' parameter types are + stable, including response parameters where applicable.""" + self.ExtractTypes('[Stable] interface F {};') + self.ExtractTypes('[Stable] interface F { A@0(int32 x); };') + self.ExtractTypes('[Stable] interface F { A@0(int32 x) => (bool b); };') + self.ExtractTypes("""\ + [Stable] enum E { A, B, C }; + [Stable] struct S {}; + [Stable] interface F { A@0(E e, S s) => (bool b, array<S> s); }; + """) + + with self.assertRaisesRegexp(Exception, 'because it depends on E'): + self.ExtractTypes( + 'enum E { A, B, C }; [Stable] interface F { A@0(E e); };') + with self.assertRaisesRegexp(Exception, 'because it depends on E'): + self.ExtractTypes( + 'enum E { A, B, C }; [Stable] interface F { A@0(int32 x) => (E e); };' + ) + with self.assertRaisesRegexp(Exception, 'because it depends on S'): + self.ExtractTypes( + 'struct S {}; [Stable] interface F { A@0(int32 x) => (S s); };') + with self.assertRaisesRegexp(Exception, 'because it depends on S'): + self.ExtractTypes( + 'struct S {}; [Stable] interface F { A@0(S s) => (bool b); };') + + with self.assertRaisesRegexp(Exception, 'explicit method ordinals'): + self.ExtractTypes('[Stable] interface F { A() => (); };') diff --git a/chromium/mojo/public/tools/mojom/version_compatibility_unittest.py b/chromium/mojo/public/tools/mojom/version_compatibility_unittest.py new file mode 100644 index 00000000000..a0ee150ec10 --- /dev/null +++ b/chromium/mojo/public/tools/mojom/version_compatibility_unittest.py @@ -0,0 +1,397 @@ +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + + +class VersionCompatibilityTest(MojomParserTestCase): + """Tests covering compatibility between two versions of the same mojom type + definition. This coverage ensures that we can reliably detect unsafe changes + to definitions that are expected to tolerate version skew in production + environments.""" + + def _GetTypeCompatibilityMap(self, old_mojom, new_mojom): + """Helper to support the implementation of assertBackwardCompatible and + assertNotBackwardCompatible.""" + + old = self.ExtractTypes(old_mojom) + new = self.ExtractTypes(new_mojom) + self.assertEqual(set(old.keys()), set(new.keys()), + 'Old and new test mojoms should use the same type names.') + + compatibility_map = {} + for name in old.keys(): + compatibility_map[name] = new[name].IsBackwardCompatible(old[name]) + return compatibility_map + + def assertBackwardCompatible(self, old_mojom, new_mojom): + compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom) + for name, compatible in compatibility_map.items(): + if not compatible: + raise AssertionError( + 'Given the old mojom:\n\n %s\n\nand the new mojom:\n\n %s\n\n' + 'The new definition of %s should pass a backward-compatibiity ' + 'check, but it does not.' % (old_mojom, new_mojom, name)) + + def assertNotBackwardCompatible(self, old_mojom, new_mojom): + compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom) + if all(compatibility_map.values()): + raise AssertionError( + 'Given the old mojom:\n\n %s\n\nand the new mojom:\n\n %s\n\n' + 'The new mojom should fail a backward-compatibility check, but it ' + 'does not.' % (old_mojom, new_mojom)) + + def testNewNonExtensibleEnumValue(self): + """Adding a value to a non-extensible enum breaks backward-compatibility.""" + self.assertNotBackwardCompatible('enum E { kFoo, kBar };', + 'enum E { kFoo, kBar, kBaz };') + + def testNewNonExtensibleEnumValueWithMinVersion(self): + """Adding a value to a non-extensible enum breaks backward-compatibility, + even with a new [MinVersion] specified for the value.""" + self.assertNotBackwardCompatible( + 'enum E { kFoo, kBar };', 'enum E { kFoo, kBar, [MinVersion=1] kBaz };') + + def testNewValueInExistingVersion(self): + """Adding a value to an existing version is not allowed, even if the old + enum was marked [Extensible]. Note that it is irrelevant whether or not the + new enum is marked [Extensible].""" + self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', + 'enum E { kFoo, kBar, kBaz };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kFoo, kBar };', + '[Extensible] enum E { kFoo, kBar, kBaz };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kFoo, [MinVersion=1] kBar };', + 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };') + + def testEnumValueRemoval(self): + """Removal of an enum value is never valid even for [Extensible] enums.""" + self.assertNotBackwardCompatible('enum E { kFoo, kBar };', + 'enum E { kFoo };') + self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', + '[Extensible] enum E { kFoo };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kA, [MinVersion=1] kB };', + '[Extensible] enum E { kA, };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };', + '[Extensible] enum E { kA, [MinVersion=1] kB };') + + def testNewExtensibleEnumValueWithMinVersion(self): + """Adding a new and properly [MinVersion]'d value to an [Extensible] enum + is a backward-compatible change. Note that it is irrelevant whether or not + the new enum is marked [Extensible].""" + self.assertBackwardCompatible('[Extensible] enum E { kA, kB };', + 'enum E { kA, kB, [MinVersion=1] kC };') + self.assertBackwardCompatible( + '[Extensible] enum E { kA, kB };', + '[Extensible] enum E { kA, kB, [MinVersion=1] kC };') + self.assertBackwardCompatible( + '[Extensible] enum E { kA, [MinVersion=1] kB };', + '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };') + + def testRenameEnumValue(self): + """Renaming an enum value does not affect backward-compatibility. Only + numeric value is relevant.""" + self.assertBackwardCompatible('enum E { kA, kB };', 'enum E { kX, kY };') + + def testAddEnumValueAlias(self): + """Adding new enum fields does not affect backward-compatibility if it does + not introduce any new numeric values.""" + self.assertBackwardCompatible( + 'enum E { kA, kB };', 'enum E { kA, kB, kC = kA, kD = 1, kE = kD };') + + def testEnumIdentity(self): + """An unchanged enum is obviously backward-compatible.""" + self.assertBackwardCompatible('enum E { kA, kB, kC };', + 'enum E { kA, kB, kC };') + + def testNewStructFieldUnversioned(self): + """Adding a new field to a struct without a new (i.e. higher than any + existing version) [MinVersion] tag breaks backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string a; };', + 'struct S { string a; string b; };') + + def testStructFieldRemoval(self): + """Removing a field from a struct breaks backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string a; string b; };', + 'struct S { string a; };') + + def testStructFieldTypeChange(self): + """Changing the type of an existing field always breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string a; };', + 'struct S { array<int32> a; };') + + def testStructFieldBecomingOptional(self): + """Changing a field from non-optional to optional breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string a; };', + 'struct S { string? a; };') + + def testStructFieldBecomingNonOptional(self): + """Changing a field from optional to non-optional breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string? a; };', + 'struct S { string a; };') + + def testStructFieldOrderChange(self): + """Changing the order of fields breaks backward-compatibility.""" + self.assertNotBackwardCompatible('struct S { string a; bool b; };', + 'struct S { bool b; string a; };') + self.assertNotBackwardCompatible('struct S { string a@0; bool b@1; };', + 'struct S { string a@1; bool b@0; };') + + def testStructFieldMinVersionChange(self): + """Changing the MinVersion of a field breaks backward-compatibility.""" + self.assertNotBackwardCompatible( + 'struct S { string a; [MinVersion=1] string? b; };', + 'struct S { string a; [MinVersion=2] string? b; };') + + def testStructFieldTypeChange(self): + """If a struct field's own type definition changes, the containing struct + is backward-compatible if and only if the field type's change is + backward-compatible.""" + self.assertBackwardCompatible( + 'struct S {}; struct T { S s; };', + 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };') + self.assertBackwardCompatible( + '[Extensible] enum E { kA }; struct S { E e; };', + '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };') + self.assertNotBackwardCompatible( + 'struct S {}; struct T { S s; };', + 'struct S { int32 x; }; struct T { S s; };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kA }; struct S { E e; };', + '[Extensible] enum E { kA, kB }; struct S { E e; };') + + def testNewStructFieldWithInvalidMinVersion(self): + """Adding a new field using an existing MinVersion breaks backward- + compatibility.""" + self.assertNotBackwardCompatible( + """\ + struct S { + string a; + [MinVersion=1] string? b; + }; + """, """\ + struct S { + string a; + [MinVersion=1] string? b; + [MinVersion=1] string? c; + };""") + + def testNewStructFieldWithValidMinVersion(self): + """Adding a new field is safe if tagged with a MinVersion greater than any + previously used MinVersion in the struct.""" + self.assertBackwardCompatible( + 'struct S { int32 a; };', + 'struct S { int32 a; [MinVersion=1] int32 b; };') + self.assertBackwardCompatible( + 'struct S { int32 a; [MinVersion=1] int32 b; };', + 'struct S { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };') + + def testNewStructFieldNullableReference(self): + """Adding a new nullable reference-typed field is fine if versioned + properly.""" + self.assertBackwardCompatible( + 'struct S { int32 a; };', + 'struct S { int32 a; [MinVersion=1] string? b; };') + + def testStructFieldRename(self): + """Renaming a field has no effect on backward-compatibility.""" + self.assertBackwardCompatible('struct S { int32 x; bool b; };', + 'struct S { int32 a; bool b; };') + + def testStructFieldReorderWithExplicitOrdinals(self): + """Reordering fields has no effect on backward-compatibility when field + ordinals are explicitly labeled and remain unchanged.""" + self.assertBackwardCompatible('struct S { bool b@1; int32 a@0; };', + 'struct S { int32 a@0; bool b@1; };') + + def testNewUnionFieldUnversioned(self): + """Adding a new field to a union without a new (i.e. higher than any + existing version) [MinVersion] tag breaks backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string a; };', + 'union U { string a; string b; };') + + def testUnionFieldRemoval(self): + """Removing a field from a union breaks backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string a; string b; };', + 'union U { string a; };') + + def testUnionFieldTypeChange(self): + """Changing the type of an existing field always breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string a; };', + 'union U { array<int32> a; };') + + def testUnionFieldBecomingOptional(self): + """Changing a field from non-optional to optional breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string a; };', + 'union U { string? a; };') + + def testUnionFieldBecomingNonOptional(self): + """Changing a field from optional to non-optional breaks + backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string? a; };', + 'union U { string a; };') + + def testUnionFieldOrderChange(self): + """Changing the order of fields breaks backward-compatibility.""" + self.assertNotBackwardCompatible('union U { string a; bool b; };', + 'union U { bool b; string a; };') + self.assertNotBackwardCompatible('union U { string a@0; bool b@1; };', + 'union U { string a@1; bool b@0; };') + + def testUnionFieldMinVersionChange(self): + """Changing the MinVersion of a field breaks backward-compatibility.""" + self.assertNotBackwardCompatible( + 'union U { string a; [MinVersion=1] string b; };', + 'union U { string a; [MinVersion=2] string b; };') + + def testUnionFieldTypeChange(self): + """If a union field's own type definition changes, the containing union + is backward-compatible if and only if the field type's change is + backward-compatible.""" + self.assertBackwardCompatible( + 'struct S {}; union U { S s; };', + 'struct S { [MinVersion=1] int32 x; }; union U { S s; };') + self.assertBackwardCompatible( + '[Extensible] enum E { kA }; union U { E e; };', + '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };') + self.assertNotBackwardCompatible( + 'struct S {}; union U { S s; };', + 'struct S { int32 x; }; union U { S s; };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { kA }; union U { E e; };', + '[Extensible] enum E { kA, kB }; union U { E e; };') + + def testNewUnionFieldWithInvalidMinVersion(self): + """Adding a new field using an existing MinVersion breaks backward- + compatibility.""" + self.assertNotBackwardCompatible( + """\ + union U { + string a; + [MinVersion=1] string b; + }; + """, """\ + union U { + string a; + [MinVersion=1] string b; + [MinVersion=1] string c; + };""") + + def testNewUnionFieldWithValidMinVersion(self): + """Adding a new field is safe if tagged with a MinVersion greater than any + previously used MinVersion in the union.""" + self.assertBackwardCompatible( + 'union U { int32 a; };', + 'union U { int32 a; [MinVersion=1] int32 b; };') + self.assertBackwardCompatible( + 'union U { int32 a; [MinVersion=1] int32 b; };', + 'union U { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };') + + def testUnionFieldRename(self): + """Renaming a field has no effect on backward-compatibility.""" + self.assertBackwardCompatible('union U { int32 x; bool b; };', + 'union U { int32 a; bool b; };') + + def testUnionFieldReorderWithExplicitOrdinals(self): + """Reordering fields has no effect on backward-compatibility when field + ordinals are explicitly labeled and remain unchanged.""" + self.assertBackwardCompatible('union U { bool b@1; int32 a@0; };', + 'union U { int32 a@0; bool b@1; };') + + def testNewInterfaceMethodUnversioned(self): + """Adding a new method to an interface without a new (i.e. higher than any + existing version) [MinVersion] tag breaks backward-compatibility.""" + self.assertNotBackwardCompatible('interface F { A(); };', + 'interface F { A(); B(); };') + + def testInterfaceMethodRemoval(self): + """Removing a method from an interface breaks backward-compatibility.""" + self.assertNotBackwardCompatible('interface F { A(); B(); };', + 'interface F { A(); };') + + def testInterfaceMethodParamsChanged(self): + """Changes to the parameter list are only backward-compatible if they meet + backward-compatibility requirements of an equivalent struct definition.""" + self.assertNotBackwardCompatible('interface F { A(); };', + 'interface F { A(int32 x); };') + self.assertNotBackwardCompatible('interface F { A(int32 x); };', + 'interface F { A(bool x); };') + self.assertNotBackwardCompatible( + 'interface F { A(int32 x, [MinVersion=1] string? s); };', """\ + interface F { + A(int32 x, [MinVersion=1] string? s, [MinVersion=1] int32 y); + };""") + + self.assertBackwardCompatible('interface F { A(int32 x); };', + 'interface F { A(int32 a); };') + self.assertBackwardCompatible( + 'interface F { A(int32 x); };', + 'interface F { A(int32 x, [MinVersion=1] string? s); };') + + self.assertBackwardCompatible( + 'struct S {}; interface F { A(S s); };', + 'struct S { [MinVersion=1] int32 x; }; interface F { A(S s); };') + self.assertBackwardCompatible( + 'struct S {}; struct T {}; interface F { A(S s); };', + 'struct S {}; struct T {}; interface F { A(T s); };') + self.assertNotBackwardCompatible( + 'struct S {}; struct T { int32 x; }; interface F { A(S s); };', + 'struct S {}; struct T { int32 x; }; interface F { A(T t); };') + + def testInterfaceMethodReplyAdded(self): + """Adding a reply to a message breaks backward-compatibilty.""" + self.assertNotBackwardCompatible('interface F { A(); };', + 'interface F { A() => (); };') + + def testInterfaceMethodReplyRemoved(self): + """Removing a reply from a message breaks backward-compatibility.""" + self.assertNotBackwardCompatible('interface F { A() => (); };', + 'interface F { A(); };') + + def testInterfaceMethodReplyParamsChanged(self): + """Similar to request parameters, a change to reply parameters is considered + backward-compatible if it meets the same backward-compatibility + requirements imposed on equivalent struct changes.""" + self.assertNotBackwardCompatible('interface F { A() => (); };', + 'interface F { A() => (int32 x); };') + self.assertNotBackwardCompatible('interface F { A() => (int32 x); };', + 'interface F { A() => (); };') + self.assertNotBackwardCompatible('interface F { A() => (bool x); };', + 'interface F { A() => (int32 x); };') + + self.assertBackwardCompatible('interface F { A() => (int32 a); };', + 'interface F { A() => (int32 x); };') + self.assertBackwardCompatible( + 'interface F { A() => (int32 x); };', + 'interface F { A() => (int32 x, [MinVersion] string? s); };') + + def testNewInterfaceMethodWithInvalidMinVersion(self): + """Adding a new method to an existing version is not backward-compatible.""" + self.assertNotBackwardCompatible( + """\ + interface F { + A(); + [MinVersion=1] B(); + }; + """, """\ + interface F { + A(); + [MinVersion=1] B(); + [MinVersion=1] C(); + }; + """) + + def testNewInterfaceMethodWithValidMinVersion(self): + """Adding a new method is fine as long as its MinVersion exceeds that of any + method on the old interface definition.""" + self.assertBackwardCompatible('interface F { A(); };', + 'interface F { A(); [MinVersion=1] B(); };') |