Browse Source

chore: cherry-pick 0d4862e31aca and c4e7fe2f2e1b from chromium (#36587)

* chore: [20-x-y] cherry-pick c4e7fe2f2e1b from chromium

* chore: update patches

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: electron-patch-conflict-fixer[bot] <83340002+electron-patch-conflict-fixer[bot]@users.noreply.github.com>
Pedro Pontes 2 years ago
parent
commit
4c27ab0a48

+ 2 - 0
patches/chromium/.patches

@@ -152,4 +152,6 @@ cherry-pick-2ef09109c0ec.patch
 cherry-pick-f98adc846aad.patch
 cherry-pick-eed5a4de2c40.patch
 cherry-pick-d1d654d73222.patch
+mojo_disable_sync_call_interrupts_in_the_browser.patch
+mojo_validate_that_a_message_is_allowed_to_use_the_sync_flag.patch
 cherry-pick-819d876e1bb8.patch

+ 536 - 0
patches/chromium/mojo_disable_sync_call_interrupts_in_the_browser.patch

@@ -0,0 +1,536 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ken Rockot <[email protected]>
+Date: Thu, 10 Nov 2022 03:24:17 +0000
+Subject: Mojo: Disable sync call interrupts in the browser
+
+This changes the default Mojo sync call behavior in the browser process
+to prevent any blocking sync calls from being interrupted by other
+incoming sync IPC dispatches.
+
+(cherry picked from commit b6f921260e0e763db7a72de9c7a3f0f78a99f21f)
+
+Bug: 1376099
+Change-Id: I53681ef379fdd3c2bfc37d7e16b3de17acad5d20
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3989408
+Commit-Queue: Ken Rockot <[email protected]>
+Reviewed-by: Daniel Cheng <[email protected]>
+Cr-Original-Commit-Position: refs/heads/main@{#1065369}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4018257
+Commit-Queue: Daniel Cheng <[email protected]>
+Auto-Submit: Daniel Cheng <[email protected]>
+Reviewed-by: Ken Rockot <[email protected]>
+Commit-Queue: Rubber Stamper <[email protected]>
+Bot-Commit: Rubber Stamper <[email protected]>
+Cr-Commit-Position: refs/branch-heads/5359@{#719}
+Cr-Branched-From: 27d3765d341b09369006d030f83f582a29eb57ae-refs/heads/main@{#1058933}
+
+diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
+index 2f973efcf3e38edcc74060929339fc21bdf0aa3b..f8f72678fd8bbcf857bf09176b8b6388d35e3605 100644
+--- a/content/app/content_main_runner_impl.cc
++++ b/content/app/content_main_runner_impl.cc
+@@ -92,6 +92,7 @@
+ #include "media/media_buildflags.h"
+ #include "mojo/core/embedder/embedder.h"
+ #include "mojo/public/cpp/bindings/self_owned_receiver.h"
++#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+ #include "mojo/public/cpp/platform/platform_channel.h"
+ #include "mojo/public/cpp/system/dynamic_library_support.h"
+ #include "mojo/public/cpp/system/invitation.h"
+@@ -1073,6 +1074,11 @@ int ContentMainRunnerImpl::RunBrowser(MainFunctionParams main_params,
+   if (is_browser_main_loop_started_)
+     return -1;
+ 
++  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
++          switches::kSingleProcess)) {
++    mojo::SyncCallRestrictions::DisableSyncCallInterrupts();
++  }
++
+   if (!mojo_ipc_support_) {
+     const auto invoked_in = main_params.ui_task
+                                 ? InvokedIn::kBrowserProcessUnderTest
+diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc
+index eb8fa358b0a72eea2e294c531549da5fc81f394c..8c13ef236ba6f7cf05584cffba522a3cb54536c9 100644
+--- a/ipc/ipc_mojo_bootstrap.cc
++++ b/ipc/ipc_mojo_bootstrap.cc
+@@ -16,13 +16,15 @@
+ #include "base/bind.h"
+ #include "base/callback.h"
+ #include "base/check_op.h"
++#include "base/containers/circular_deque.h"
+ #include "base/containers/contains.h"
+-#include "base/containers/queue.h"
+ #include "base/memory/ptr_util.h"
+ #include "base/memory/raw_ptr.h"
+ #include "base/no_destructor.h"
++#include "base/ranges/algorithm.h"
+ #include "base/strings/stringprintf.h"
+ #include "base/synchronization/lock.h"
++#include "base/synchronization/waitable_event.h"
+ #include "base/task/common/task_annotator.h"
+ #include "base/task/sequenced_task_runner.h"
+ #include "base/task/single_thread_task_runner.h"
+@@ -48,6 +50,7 @@
+ #include "mojo/public/cpp/bindings/pipe_control_message_proxy.h"
+ #include "mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h"
+ #include "mojo/public/cpp/bindings/tracing_helpers.h"
++#include "third_party/abseil-cpp/absl/types/optional.h"
+ 
+ namespace IPC {
+ 
+@@ -466,6 +469,11 @@ class ChannelAssociatedGroupController
+       return *this;
+     }
+ 
++    bool HasRequestId(uint64_t request_id) {
++      return !value_.IsNull() && value_.version() >= 1 &&
++             value_.header_v1()->request_id == request_id;
++    }
++
+     mojo::Message& value() { return value_; }
+ 
+    private:
+@@ -557,10 +565,15 @@ class ChannelAssociatedGroupController
+       sync_watcher_.reset();
+     }
+ 
+-    uint32_t EnqueueSyncMessage(MessageWrapper message) {
++    absl::optional<uint32_t> EnqueueSyncMessage(MessageWrapper message) {
+       controller_->lock_.AssertAcquired();
++      if (exclusive_wait_ && exclusive_wait_->TryFulfillingWith(message)) {
++        exclusive_wait_ = nullptr;
++        return absl::nullopt;
++      }
++
+       uint32_t id = GenerateSyncMessageId();
+-      sync_messages_.emplace(id, std::move(message));
++      sync_messages_.emplace_back(id, std::move(message));
+       SignalSyncMessageEvent();
+       return id;
+     }
+@@ -577,7 +590,7 @@ class ChannelAssociatedGroupController
+       if (sync_messages_.empty() || sync_messages_.front().first != id)
+         return MessageWrapper();
+       MessageWrapper message = std::move(sync_messages_.front().second);
+-      sync_messages_.pop();
++      sync_messages_.pop_front();
+       return message;
+     }
+ 
+@@ -607,10 +620,38 @@ class ChannelAssociatedGroupController
+       return sync_watcher_->SyncWatch(&should_stop);
+     }
+ 
++    MessageWrapper WaitForIncomingSyncReply(uint64_t request_id) {
++      absl::optional<ExclusiveSyncWait> wait;
++      {
++        base::AutoLock lock(controller_->lock_);
++        for (auto& [id, message] : sync_messages_) {
++          if (message.HasRequestId(request_id)) {
++            return std::move(message);
++          }
++        }
++
++        DCHECK(!exclusive_wait_);
++        wait.emplace(request_id);
++        exclusive_wait_ = &wait.value();
++      }
++
++      wait->event.Wait();
++      return std::move(wait->message);
++    }
++
+     bool SyncWatchExclusive(uint64_t request_id) override {
+-      // We don't support exclusive waits on Channel-associated interfaces.
+-      NOTREACHED();
+-      return false;
++      MessageWrapper message = WaitForIncomingSyncReply(request_id);
++      if (message.value().IsNull() || !client_) {
++        return false;
++      }
++
++      if (!client_->HandleIncomingMessage(&message.value())) {
++        base::AutoLock locker(controller_->lock_);
++        controller_->RaiseError();
++        return false;
++      }
++
++      return true;
+     }
+ 
+     void RegisterExternalSyncWaiter(uint64_t request_id) override {}
+@@ -624,6 +665,9 @@ class ChannelAssociatedGroupController
+       DCHECK(closed_);
+       DCHECK(peer_closed_);
+       DCHECK(!sync_watcher_);
++      if (exclusive_wait_) {
++        exclusive_wait_->event.Signal();
++      }
+     }
+ 
+     void OnSyncMessageEventReady() {
+@@ -640,7 +684,7 @@ class ChannelAssociatedGroupController
+       if (!sync_messages_.empty()) {
+         MessageWrapper message_wrapper =
+             std::move(sync_messages_.front().second);
+-        sync_messages_.pop();
++        sync_messages_.pop_front();
+ 
+         bool dispatch_succeeded;
+         mojo::InterfaceEndpointClient* client = client_;
+@@ -688,6 +732,28 @@ class ChannelAssociatedGroupController
+       return id;
+     }
+ 
++    // Tracks the state of a pending sync wait which excludes all other incoming
++    // IPC on the waiting thread.
++    struct ExclusiveSyncWait {
++      explicit ExclusiveSyncWait(uint64_t request_id)
++          : request_id(request_id) {}
++      ~ExclusiveSyncWait() = default;
++
++      bool TryFulfillingWith(MessageWrapper& wrapper) {
++        if (!wrapper.HasRequestId(request_id)) {
++          return false;
++        }
++
++        message = std::move(wrapper);
++        event.Signal();
++        return true;
++      }
++
++      uint64_t request_id;
++      base::WaitableEvent event;
++      MessageWrapper message;
++    };
++
+     const raw_ptr<ChannelAssociatedGroupController> controller_;
+     const mojo::InterfaceId id_;
+ 
+@@ -699,7 +765,8 @@ class ChannelAssociatedGroupController
+     raw_ptr<mojo::InterfaceEndpointClient> client_ = nullptr;
+     scoped_refptr<base::SequencedTaskRunner> task_runner_;
+     std::unique_ptr<mojo::SequenceLocalSyncEventWatcher> sync_watcher_;
+-    base::queue<std::pair<uint32_t, MessageWrapper>> sync_messages_;
++    base::circular_deque<std::pair<uint32_t, MessageWrapper>> sync_messages_;
++    ExclusiveSyncWait* exclusive_wait_ = nullptr;
+     uint32_t next_sync_message_id_ = 0;
+   };
+ 
+@@ -932,12 +999,15 @@ class ChannelAssociatedGroupController
+         // sync message queue. If the endpoint was blocking, it will dequeue the
+         // message and dispatch it. Otherwise the posted |AcceptSyncMessage()|
+         // call will dequeue the message and dispatch it.
+-        uint32_t message_id =
++        absl::optional<uint32_t> message_id =
+             endpoint->EnqueueSyncMessage(std::move(message_wrapper));
+-        task_runner->PostTask(
+-            FROM_HERE,
+-            base::BindOnce(&ChannelAssociatedGroupController::AcceptSyncMessage,
+-                           this, id, message_id));
++        if (message_id) {
++          task_runner->PostTask(
++              FROM_HERE,
++              base::BindOnce(
++                  &ChannelAssociatedGroupController::AcceptSyncMessage, this,
++                  id, *message_id));
++        }
+         return true;
+       }
+ 
+diff --git a/mojo/public/cpp/bindings/interface_endpoint_controller.h b/mojo/public/cpp/bindings/interface_endpoint_controller.h
+index 89dbe39994620148e0ef33910b7cc7baacd7cc2e..8649abe1ac9c4b964c2bf833b850aa6f898b7103 100644
+--- a/mojo/public/cpp/bindings/interface_endpoint_controller.h
++++ b/mojo/public/cpp/bindings/interface_endpoint_controller.h
+@@ -36,6 +36,10 @@ class InterfaceEndpointController {
+   // Watches the endpoint for a specific incoming sync reply. This method only
+   // returns true once the reply is received, or false if the endpoint is
+   // detached or destroyed beforehand.
++  //
++  // Unlike with SyncWatch(), no other IPCs (not even other sync IPCs) can be
++  // dispatched to the calling thread while SyncWatchExclusive() is waiting on
++  // the reply for `request_id`.
+   virtual bool SyncWatchExclusive(uint64_t request_id) = 0;
+ 
+   // Notifies the controller that a specific in-flight sync message identified
+diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+index 6e87db197c603d8ac44b591f2cd70023217dcbe2..1a786923f6d66c0b8d17765ea7b629add2db104c 100644
+--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
++++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+@@ -387,7 +387,9 @@ void ThreadSafeInterfaceEndpointClientProxy::SendMessageWithResponder(
+   }
+ 
+   // If the Remote is bound on another sequence, post the call.
+-  const bool allow_interrupt = !message.has_flag(Message::kFlagNoInterrupt);
++  const bool allow_interrupt =
++      SyncCallRestrictions::AreSyncCallInterruptsEnabled() &&
++      !message.has_flag(Message::kFlagNoInterrupt);
+   auto response = base::MakeRefCounted<SyncResponseInfo>();
+   auto response_signaler = std::make_unique<SyncResponseSignaler>(response);
+   task_runner_->PostTask(
+@@ -625,7 +627,9 @@ bool InterfaceEndpointClient::SendMessageWithResponder(
+ 
+   const uint32_t message_name = message->name();
+   const bool is_sync = message->has_flag(Message::kFlagIsSync);
+-  const bool exclusive_wait = message->has_flag(Message::kFlagNoInterrupt);
++  const bool exclusive_wait =
++      message->has_flag(Message::kFlagNoInterrupt) ||
++      !SyncCallRestrictions::AreSyncCallInterruptsEnabled();
+   if (!controller_->SendMessage(message))
+     return false;
+ 
+diff --git a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
+index 329901dec12572e8d8833eba33ad1cc793919084..6242391074ee6279cfea29cf1e73ac4ef874445a 100644
+--- a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
++++ b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
+@@ -4,8 +4,6 @@
+ 
+ #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+ 
+-#if ENABLE_SYNC_CALL_RESTRICTIONS
+-
+ #include "base/check_op.h"
+ #include "base/debug/leak_annotations.h"
+ #include "base/logging.h"
+@@ -19,6 +17,11 @@ namespace mojo {
+ 
+ namespace {
+ 
++// Sync call interrupts are enabled by default.
++bool g_enable_sync_call_interrupts = true;
++
++#if ENABLE_SYNC_CALL_RESTRICTIONS
++
+ class GlobalSyncCallSettings {
+  public:
+   GlobalSyncCallSettings() = default;
+@@ -61,8 +64,12 @@ bool SyncCallRestrictionsEnforceable() {
+   return base::internal::SequenceLocalStorageMap::IsSetForCurrentThread();
+ }
+ 
++#endif  // ENABLE_SYNC_CALL_RESTRICTIONS
++
+ }  // namespace
+ 
++#if ENABLE_SYNC_CALL_RESTRICTIONS
++
+ // static
+ void SyncCallRestrictions::AssertSyncCallAllowed() {
+   if (GetGlobalSettings().sync_call_allowed_by_default() ||
+@@ -102,6 +109,21 @@ void SyncCallRestrictions::DecreaseScopedAllowCount() {
+   --GetSequenceLocalScopedAllowCount();
+ }
+ 
+-}  // namespace mojo
+-
+ #endif  // ENABLE_SYNC_CALL_RESTRICTIONS
++
++// static
++void SyncCallRestrictions::DisableSyncCallInterrupts() {
++  g_enable_sync_call_interrupts = false;
++}
++
++// static
++void SyncCallRestrictions::EnableSyncCallInterruptsForTesting() {
++  g_enable_sync_call_interrupts = true;
++}
++
++// static
++bool SyncCallRestrictions::AreSyncCallInterruptsEnabled() {
++  return g_enable_sync_call_interrupts;
++}
++
++}  // namespace mojo
+diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h
+index e7e67ee824b2a87eb14b45a3f2d76d471ff864fb..1653fd63033383b40b643c03500b26bdc65a44a6 100644
+--- a/mojo/public/cpp/bindings/sync_call_restrictions.h
++++ b/mojo/public/cpp/bindings/sync_call_restrictions.h
+@@ -86,6 +86,20 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) SyncCallRestrictions {
+   static void DisallowSyncCall() {}
+ #endif
+ 
++  // Globally disables sync call interrupts. This means that all sync calls in
++  // the current process will be strictly blocking until a reply is received,
++  // and no incoming sync calls can dispatch on the blocking thread in interim.
++  static void DisableSyncCallInterrupts();
++
++  // Used only in tests to re-enable sync call interrupts after disabling them.
++  static void EnableSyncCallInterruptsForTesting();
++
++  // Indicates whether sync call interrupts are enabled in the calling process.
++  // They're enabled by default, so any sync message that isn't marked [Sync]
++  // may have its blocking call interrupted to dispatch other incoming sync
++  // IPCs which target the blocking thread.
++  static bool AreSyncCallInterruptsEnabled();
++
+  private:
+   // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to mojo/OWNERS first.
+   // BEGIN ALLOWED USAGE.
+diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+index cfac737af29e653d788eb1ce7669f73ae7b320d4..d794d29df07d289d34b3b1aae9f8574fc914c050 100644
+--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
++++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+@@ -25,6 +25,7 @@
+ #include "mojo/public/cpp/bindings/self_owned_receiver.h"
+ #include "mojo/public/cpp/bindings/shared_associated_remote.h"
+ #include "mojo/public/cpp/bindings/shared_remote.h"
++#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+ #include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
+ #include "mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom.h"
+ #include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h"
+@@ -1563,7 +1564,144 @@ TEST_P(SyncInterruptTest, SharedAssociatedRemoteNoInterrupt) {
+   EXPECT_EQ(0, same_pipe_ponger().num_sync_pongs());
+ }
+ 
++class SyncService : public mojom::SyncService {
++ public:
++  explicit SyncService(PendingReceiver<mojom::SyncService> receiver)
++      : receiver_(this, std::move(receiver)) {}
++
++  void SetCallHandler(base::OnceClosure call_handler) {
++    call_handler_ = std::move(call_handler);
++  }
++
++  // mojom::SyncService:
++  void SyncCall(SyncCallCallback callback) override {
++    std::move(callback).Run();
++    if (call_handler_) {
++      std::move(call_handler_).Run();
++    }
++  }
++
++ private:
++  Receiver<mojom::SyncService> receiver_;
++  base::OnceClosure call_handler_;
++};
++
++class DisableSyncInterruptTest : public BindingsTestBase {
++ public:
++  void SetUp() override {
++    mojo::SyncCallRestrictions::DisableSyncCallInterrupts();
++  }
++
++  void TearDown() override {
++    mojo::SyncCallRestrictions::EnableSyncCallInterruptsForTesting();
++  }
++};
++
++TEST_P(DisableSyncInterruptTest, NoInterruptWhenDisabled) {
++  PendingRemote<mojom::SyncService> interrupter;
++  SyncService service(interrupter.InitWithNewPipeAndPassReceiver());
++
++  base::RunLoop wait_for_main_thread_service_call;
++  bool main_thread_service_called = false;
++  service.SetCallHandler(base::BindLambdaForTesting([&] {
++    main_thread_service_called = true;
++    wait_for_main_thread_service_call.Quit();
++  }));
++
++  Remote<mojom::SyncService> caller;
++  base::Thread background_service_thread("SyncService");
++  background_service_thread.Start();
++  base::SequenceBound<SyncService> background_service{
++      background_service_thread.task_runner(),
++      caller.BindNewPipeAndPassReceiver()};
++
++  base::Thread interrupter_thread("Interrupter");
++  interrupter_thread.Start();
++  interrupter_thread.task_runner()->PostTask(
++      FROM_HERE, base::BindLambdaForTesting([&interrupter] {
++        // Issue a sync call to the SyncService on the main thread. This should
++        // never be dispatched until *after* the sync call *from* the main
++        // thread completes below.
++        Remote<mojom::SyncService>(std::move(interrupter))->SyncCall();
++      }));
++
++  // The key test expectation here is that `main_thread_service_called` cannot
++  // be set to true until after SyncCall() returns and we can pump the thread's
++  // message loop. If sync interrupts are not properly disabled, this
++  // expectation can fail flakily (and often.)
++  caller->SyncCall();
++  EXPECT_FALSE(main_thread_service_called);
++
++  // Now the incoming sync call can be dispatched.
++  wait_for_main_thread_service_call.Run();
++  EXPECT_TRUE(main_thread_service_called);
++
++  background_service.SynchronouslyResetForTest();
++  interrupter_thread.Stop();
++  background_service_thread.Stop();
++}
++
++TEST_P(DisableSyncInterruptTest, SharedRemoteNoInterruptWhenDisabled) {
++  PendingRemote<mojom::SyncService> interrupter;
++  SyncService service(interrupter.InitWithNewPipeAndPassReceiver());
++
++  base::RunLoop wait_for_main_thread_service_call;
++  bool main_thread_service_called = false;
++  service.SetCallHandler(base::BindLambdaForTesting([&] {
++    main_thread_service_called = true;
++    wait_for_main_thread_service_call.Quit();
++  }));
++
++  // Bind a SharedRemote to another background thread so that we exercise
++  // SharedRemote's own sync wait codepath when called into from the main
++  // thread.
++  base::Thread background_client_thread("Client");
++  background_client_thread.Start();
++
++  base::Thread background_service_thread("Service");
++  background_service_thread.Start();
++
++  SharedRemote<mojom::SyncService> caller;
++  base::SequenceBound<SyncService> background_service{
++      background_service_thread.task_runner(),
++      caller.BindNewPipeAndPassReceiver(
++          background_client_thread.task_runner())};
++
++  base::Thread interrupter_thread("Interrupter");
++  interrupter_thread.Start();
++  interrupter_thread.task_runner()->PostTask(
++      FROM_HERE, base::BindLambdaForTesting([&interrupter] {
++        // Issue a sync call to the SyncService on the main thread. This should
++        // never be dispatched until *after* the sync call *from* the main
++        // thread completes below.
++        Remote<mojom::SyncService>(std::move(interrupter))->SyncCall();
++      }));
++
++  // The key test expectation here is that `main_thread_service_called` cannot
++  // be set to true until after SyncCall() returns and we can pump the thread's
++  // message loop. If sync interrupts are not properly disabled, this
++  // expectation can fail flakily (and often.)
++  caller->SyncCall();
++  EXPECT_FALSE(main_thread_service_called);
++
++  // Now the incoming sync call can be dispatched.
++  wait_for_main_thread_service_call.Run();
++  EXPECT_TRUE(main_thread_service_called);
++
++  background_service.SynchronouslyResetForTest();
++
++  // We need to reset the SharedRemote before the client thread is stopped, to
++  // ensure the necessary teardown work is executed on that thread. Otherwise
++  // the underlying pipe and related state will leak, and ASan will complain.
++  caller.reset();
++
++  interrupter_thread.Stop();
++  background_service_thread.Stop();
++  background_client_thread.Stop();
++}
++
+ INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(SyncInterruptTest);
++INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(DisableSyncInterruptTest);
+ 
+ }  // namespace
+ }  // namespace sync_method_unittest
+diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom b/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
+index 383b54f3ab654d664192522c061058b29fd0509a..951442b3585ad22f936568e211ad41f8ae358705 100644
+--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
++++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
+@@ -45,3 +45,7 @@ interface Ponger {
+   [Sync] Pong() => ();
+   PongAsync();
+ };
++
++interface SyncService {
++  [Sync] SyncCall() => ();
++};

+ 953 - 0
patches/chromium/mojo_validate_that_a_message_is_allowed_to_use_the_sync_flag.patch

@@ -0,0 +1,953 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Cheng <[email protected]>
+Date: Sat, 12 Nov 2022 00:27:56 +0000
+Subject: Validate that a message is allowed to use the sync flag.
+
+This changes consists of several coordinated changes:
+- The C++ bindings generator now emits an array of method ordinals that
+  are allowed to use sync calls, but only if any method has a [Sync]
+  annotation. This is intended to minimize the code cost to interfaces
+  that do not have any sync methods (i.e. most of them).
+- The C++ binding endpoints (mojo::Receiver, et cetera) now plumb the
+  array of sync-allowed ordinals to the InterfaceEndpointClient.
+- Processing an incoming message checks if the incoming message is
+  allowed to use the sync flag by filtering it against the array of
+  sync-allowed ordinals that was previously passed to the
+  InterfaceEndpointClient.
+
+This also fixes an incorrect forward declaration of ValidationContext in
+the generated bindings that discovered in the process of writing the
+test.
+
+(cherry picked from commit 4365dddb49847a422bce674383b4aa4f38ff9e89)
+
+Bug: 1376099
+Change-Id: Icb5864dcab96ccd18c98b4cc6ade7cdef39e209f
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3994146
+Reviewed-by: Ken Rockot <[email protected]>
+Commit-Queue: Daniel Cheng <[email protected]>
+Cr-Original-Commit-Position: refs/heads/main@{#1067894}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4018151
+Auto-Submit: Daniel Cheng <[email protected]>
+Commit-Queue: Ken Rockot <[email protected]>
+Cr-Commit-Position: refs/branch-heads/5359@{#774}
+Cr-Branched-From: 27d3765d341b09369006d030f83f582a29eb57ae-refs/heads/main@{#1058933}
+
+diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
+index 2c27474d1200f80ff7abc773eaafdc9d30494f58..de587d8759f2e850ef9de355551c4be12f3ca6e7 100644
+--- a/mojo/public/cpp/bindings/BUILD.gn
++++ b/mojo/public/cpp/bindings/BUILD.gn
+@@ -183,6 +183,7 @@ component("bindings") {
+     "lib/sync_event_watcher.cc",
+     "lib/sync_handle_registry.cc",
+     "lib/sync_handle_watcher.cc",
++    "lib/sync_method_traits.h",
+     "lib/task_runner_helper.cc",
+     "lib/task_runner_helper.h",
+     "lib/thread_safe_forwarder_base.cc",
+diff --git a/mojo/public/cpp/bindings/associated_receiver.h b/mojo/public/cpp/bindings/associated_receiver.h
+index 78c53543ed01cafe627d0559c31be1d792750685..59b86367122074e8c59fcbd9877bf59d669cc436 100644
+--- a/mojo/public/cpp/bindings/associated_receiver.h
++++ b/mojo/public/cpp/bindings/associated_receiver.h
+@@ -5,15 +5,19 @@
+ #ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_RECEIVER_H_
+ #define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_RECEIVER_H_
+ 
++#include <stdint.h>
++
+ #include <memory>
+ #include <utility>
+ 
+ #include "base/check.h"
++#include "base/containers/span.h"
+ #include "base/memory/ptr_util.h"
+ #include "base/memory/scoped_refptr.h"
+ #include "base/memory/weak_ptr.h"
+ #include "base/strings/string_piece.h"
+ #include "base/task/sequenced_task_runner.h"
++#include "mojo/public/cpp/bindings/lib/sync_method_traits.h"
+ #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+ #include "mojo/public/cpp/bindings/pending_associated_remote.h"
+ #include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+@@ -60,7 +64,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) AssociatedReceiverBase {
+   void BindImpl(ScopedInterfaceEndpointHandle handle,
+                 MessageReceiverWithResponderStatus* receiver,
+                 std::unique_ptr<MessageReceiver> payload_validator,
+-                bool expect_sync_requests,
++                base::span<const uint32_t> sync_method_ordinals,
+                 scoped_refptr<base::SequencedTaskRunner> runner,
+                 uint32_t interface_version,
+                 const char* interface_name,
+@@ -201,8 +205,8 @@ class AssociatedReceiver : public internal::AssociatedReceiverBase {
+     if (pending_receiver) {
+       BindImpl(pending_receiver.PassHandle(), &stub_,
+                base::WrapUnique(new typename Interface::RequestValidator_()),
+-               Interface::HasSyncMethods_, std::move(task_runner),
+-               Interface::Version_, Interface::Name_,
++               internal::SyncMethodTraits<Interface>::GetOrdinals(),
++               std::move(task_runner), Interface::Version_, Interface::Name_,
+                Interface::MessageToStableIPCHash_,
+                Interface::MessageToMethodName_);
+     } else {
+diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
+index dcc6e2aa8d9e97ff716d6ab1de02a83eba95eec2..93aea2bd29c5171f63401bc5d88fb7d6dc302e71 100644
+--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
++++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
+@@ -13,6 +13,7 @@
+ 
+ #include "base/callback.h"
+ #include "base/component_export.h"
++#include "base/containers/span.h"
+ #include "base/dcheck_is_on.h"
+ #include "base/location.h"
+ #include "base/memory/raw_ptr.h"
+@@ -56,7 +57,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) InterfaceEndpointClient
+   InterfaceEndpointClient(ScopedInterfaceEndpointHandle handle,
+                           MessageReceiverWithResponderStatus* receiver,
+                           std::unique_ptr<MessageReceiver> payload_validator,
+-                          bool expect_sync_requests,
++                          base::span<const uint32_t> sync_method_ordinals,
+                           scoped_refptr<base::SequencedTaskRunner> task_runner,
+                           uint32_t interface_version,
+                           const char* interface_name,
+@@ -220,6 +221,10 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) InterfaceEndpointClient
+   // The router lock must be held when calling this.
+   void ForgetAsyncRequest(uint64_t request_id);
+ 
++  base::span<const uint32_t> sync_method_ordinals() const {
++    return sync_method_ordinals_;
++  }
++
+  private:
+   struct PendingAsyncResponse {
+    public:
+@@ -281,7 +286,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) InterfaceEndpointClient
+ 
+   bool HandleValidatedMessage(Message* message);
+ 
+-  const bool expect_sync_requests_ = false;
++  const base::span<const uint32_t> sync_method_ordinals_;
+ 
+   // The callback to invoke when our peer endpoint sends us NotifyIdle and we
+   // have no outstanding unacked messages. If null, no callback has been set and
+diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc
+index 957ce42c4fa8e69ce63ee876653ef8f8a2ae5b28..a8b86f529329c8358172e4d0d6c3fe61fbede31b 100644
+--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc
++++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc
+@@ -4,6 +4,11 @@
+ 
+ #include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h"
+ 
++#include <stdint.h>
++
++#include <utility>
++
++#include "base/containers/span.h"
+ #include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+ 
+ namespace mojo {
+@@ -70,7 +75,8 @@ void AssociatedInterfacePtrStateBase::Bind(
+   // The version is only queried from the client so the value passed here
+   // will not be used.
+   endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
+-      std::move(handle), nullptr, std::move(validator), false,
++      std::move(handle), nullptr, std::move(validator),
++      /*sync_method_ordinals=*/base::span<const uint32_t>(),
+       GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(runner)), 0u,
+       interface_name, ipc_hash_callback, method_name_callback);
+ }
+diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
+index d9a4ef1fd6c8141a69de15895ae411a77a03462a..81806976b9c5ccc3d001344a55633d447ac2df24 100644
+--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
++++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
+@@ -141,6 +141,10 @@ class AssociatedInterfacePtrState : public AssociatedInterfacePtrStateBase {
+     return info;
+   }
+ 
++  InterfaceEndpointClient* endpoint_client_for_test() {
++    return endpoint_client();
++  }
++
+  private:
+   std::unique_ptr<Proxy> proxy_;
+ };
+diff --git a/mojo/public/cpp/bindings/lib/associated_receiver.cc b/mojo/public/cpp/bindings/lib/associated_receiver.cc
+index be3d8689cbdeaaa685f470f39fef3650d19f0aa0..413104e622a879db15ab6d6fa631462104a409e4 100644
+--- a/mojo/public/cpp/bindings/lib/associated_receiver.cc
++++ b/mojo/public/cpp/bindings/lib/associated_receiver.cc
+@@ -64,7 +64,7 @@ void AssociatedReceiverBase::BindImpl(
+     ScopedInterfaceEndpointHandle handle,
+     MessageReceiverWithResponderStatus* receiver,
+     std::unique_ptr<MessageReceiver> payload_validator,
+-    bool expect_sync_requests,
++    base::span<const uint32_t> sync_method_ordinals,
+     scoped_refptr<base::SequencedTaskRunner> runner,
+     uint32_t interface_version,
+     const char* interface_name,
+@@ -74,7 +74,7 @@ void AssociatedReceiverBase::BindImpl(
+ 
+   endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
+       std::move(handle), receiver, std::move(payload_validator),
+-      expect_sync_requests,
++      sync_method_ordinals,
+       internal::GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(runner)),
+       interface_version, interface_name, ipc_hash_callback,
+       method_name_callback);
+diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc
+index c40b244184601a6fc661a623f6ae45430925b789..a07943d50add0f840752eceb5d7c56f11ce958e6 100644
+--- a/mojo/public/cpp/bindings/lib/binding_state.cc
++++ b/mojo/public/cpp/bindings/lib/binding_state.cc
+@@ -107,7 +107,7 @@ void BindingStateBase::BindInternal(
+     const char* interface_name,
+     std::unique_ptr<MessageReceiver> request_validator,
+     bool passes_associated_kinds,
+-    bool has_sync_methods,
++    base::span<const uint32_t> sync_method_ordinals,
+     MessageReceiverWithResponderStatus* stub,
+     uint32_t interface_version,
+     MessageToStableIPCHashCallback ipc_hash_callback,
+@@ -121,7 +121,7 @@ void BindingStateBase::BindInternal(
+   MultiplexRouter::Config config =
+       passes_associated_kinds
+           ? MultiplexRouter::MULTI_INTERFACE
+-          : (has_sync_methods
++          : (!sync_method_ordinals.empty()
+                  ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS
+                  : MultiplexRouter::SINGLE_INTERFACE);
+   router_ = MultiplexRouter::CreateAndStartReceiving(
+@@ -131,7 +131,7 @@ void BindingStateBase::BindInternal(
+ 
+   endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
+       router_->CreateLocalEndpointHandle(kPrimaryInterfaceId), stub,
+-      std::move(request_validator), has_sync_methods,
++      std::move(request_validator), sync_method_ordinals,
+       std::move(sequenced_runner), interface_version, interface_name,
+       ipc_hash_callback, method_name_callback);
+   endpoint_client_->SetIdleTrackingEnabledCallback(
+diff --git a/mojo/public/cpp/bindings/lib/binding_state.h b/mojo/public/cpp/bindings/lib/binding_state.h
+index 64e3fb139fcd8afcf66a85f68e5264dbb47ca997..949b6d8a6eb5ae7d19e26e1b0afe63f1640cff76 100644
+--- a/mojo/public/cpp/bindings/lib/binding_state.h
++++ b/mojo/public/cpp/bindings/lib/binding_state.h
+@@ -5,6 +5,8 @@
+ #ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_
+ #define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_
+ 
++#include <stdint.h>
++
+ #include <memory>
+ #include <utility>
+ 
+@@ -12,6 +14,7 @@
+ #include "base/callback.h"
+ #include "base/check.h"
+ #include "base/component_export.h"
++#include "base/containers/span.h"
+ #include "base/memory/ptr_util.h"
+ #include "base/memory/ref_counted.h"
+ #include "base/strings/string_piece.h"
+@@ -23,6 +26,7 @@
+ #include "mojo/public/cpp/bindings/interface_id.h"
+ #include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+ #include "mojo/public/cpp/bindings/lib/pending_receiver_state.h"
++#include "mojo/public/cpp/bindings/lib/sync_method_traits.h"
+ #include "mojo/public/cpp/bindings/message_header_validator.h"
+ #include "mojo/public/cpp/bindings/pending_flush.h"
+ #include "mojo/public/cpp/bindings/pending_receiver.h"
+@@ -90,7 +94,7 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) BindingStateBase {
+                     const char* interface_name,
+                     std::unique_ptr<MessageReceiver> request_validator,
+                     bool passes_associated_kinds,
+-                    bool has_sync_methods,
++                    base::span<const uint32_t> sync_method_ordinals,
+                     MessageReceiverWithResponderStatus* stub,
+                     uint32_t interface_version,
+                     MessageToStableIPCHashCallback ipc_hash_callback,
+@@ -121,9 +125,9 @@ class BindingState : public BindingStateBase {
+     BindingStateBase::BindInternal(
+         std::move(receiver_state), runner, Interface::Name_,
+         std::make_unique<typename Interface::RequestValidator_>(),
+-        Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_,
+-        Interface::Version_, Interface::MessageToStableIPCHash_,
+-        Interface::MessageToMethodName_);
++        Interface::PassesAssociatedKinds_,
++        SyncMethodTraits<Interface>::GetOrdinals(), &stub_, Interface::Version_,
++        Interface::MessageToStableIPCHash_, Interface::MessageToMethodName_);
+   }
+ 
+   PendingReceiver<Interface> Unbind() {
+diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+index 1a786923f6d66c0b8d17765ea7b629add2db104c..c1e7efe6954ae3dc9186d33067bc324b05273e65 100644
+--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
++++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+@@ -439,13 +439,13 @@ InterfaceEndpointClient::InterfaceEndpointClient(
+     ScopedInterfaceEndpointHandle handle,
+     MessageReceiverWithResponderStatus* receiver,
+     std::unique_ptr<MessageReceiver> payload_validator,
+-    bool expect_sync_requests,
++    base::span<const uint32_t> sync_method_ordinals,
+     scoped_refptr<base::SequencedTaskRunner> task_runner,
+     uint32_t interface_version,
+     const char* interface_name,
+     MessageToStableIPCHashCallback ipc_hash_callback,
+     MessageToMethodNameCallback method_name_callback)
+-    : expect_sync_requests_(expect_sync_requests),
++    : sync_method_ordinals_(sync_method_ordinals),
+       handle_(std::move(handle)),
+       incoming_receiver_(receiver),
+       dispatcher_(&thunk_),
+@@ -849,7 +849,8 @@ void InterfaceEndpointClient::InitControllerIfNecessary() {
+ 
+   controller_ = handle_.group_controller()->AttachEndpointClient(handle_, this,
+                                                                  task_runner_);
+-  if (expect_sync_requests_ && task_runner_->RunsTasksInCurrentSequence())
++  if (!sync_method_ordinals_.empty() &&
++      task_runner_->RunsTasksInCurrentSequence())
+     controller_->AllowWokenUpBySyncWatchOnSameThread();
+ }
+ 
+diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.cc b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
+index 8607d5fba2fd59f4f0ca154a9085c47a26ff2c21..aa12189148f79a742be39f3cfaeba9cf54c2dc91 100644
+--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
++++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
+@@ -4,6 +4,11 @@
+ 
+ #include "mojo/public/cpp/bindings/lib/interface_ptr_state.h"
+ 
++#include <stdint.h>
++
++#include <utility>
++
++#include "base/containers/span.h"
+ #include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+ 
+ namespace mojo {
+@@ -100,7 +105,9 @@ bool InterfacePtrStateBase::InitializeEndpointClient(
+                                     interface_name);
+   endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
+       router_->CreateLocalEndpointHandle(kPrimaryInterfaceId), nullptr,
+-      std::move(payload_validator), false, std::move(runner_),
++      std::move(payload_validator),
++      /* sync_method_ordinals= */ base::span<const uint32_t>(),
++      std::move(runner_),
+       // The version is only queried from the client so the value passed here
+       // will not be used.
+       0u, interface_name, ipc_hash_callback, method_name_callback);
+diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+index 2d379a081e6731db5cd2182365529da2200e3d7b..5b7bb34ac5c9a4764ee9a41c1d3ce324c6a93b13 100644
+--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h
++++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+@@ -25,6 +25,7 @@
+ #include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+ #include "mojo/public/cpp/bindings/lib/multiplex_router.h"
+ #include "mojo/public/cpp/bindings/lib/pending_remote_state.h"
++#include "mojo/public/cpp/bindings/lib/sync_method_traits.h"
+ #include "mojo/public/cpp/bindings/pending_flush.h"
+ #include "mojo/public/cpp/bindings/thread_safe_proxy.h"
+ #include "mojo/public/cpp/system/message_pipe.h"
+@@ -249,6 +250,10 @@ class InterfacePtrState : public InterfacePtrStateBase {
+     endpoint_client()->RaiseError();
+   }
+ 
++  InterfaceEndpointClient* endpoint_client_for_test() {
++    return endpoint_client();
++  }
++
+  private:
+   void ConfigureProxyIfNecessary() {
+     // The proxy has been configured.
+@@ -259,7 +264,8 @@ class InterfacePtrState : public InterfacePtrStateBase {
+     }
+ 
+     if (InitializeEndpointClient(
+-            Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_,
++            Interface::PassesAssociatedKinds_,
++            !SyncMethodTraits<Interface>::GetOrdinals().empty(),
+             Interface::HasUninterruptableMethods_,
+             std::make_unique<typename Interface::ResponseValidator_>(),
+             Interface::Name_, Interface::MessageToStableIPCHash_,
+diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
+index b9c92d5ec9eab57972cf870efff51fe09e381623..c04999f0ac8881cc86ed34761ddeb8e8dfc83164 100644
+--- a/mojo/public/cpp/bindings/lib/multiplex_router.cc
++++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
+@@ -1067,6 +1067,12 @@ bool MultiplexRouter::ProcessIncomingMessage(
+ 
+   bool can_direct_call;
+   if (message->has_flag(Message::kFlagIsSync)) {
++    if (!message->has_flag(Message::kFlagIsResponse) &&
++        !base::Contains(endpoint->client()->sync_method_ordinals(),
++                        message->name())) {
++      RaiseErrorInNonTestingMode();
++      return true;
++    }
+     can_direct_call = client_call_behavior != NO_DIRECT_CLIENT_CALLS &&
+                       endpoint->task_runner()->RunsTasksInCurrentSequence();
+   } else {
+diff --git a/mojo/public/cpp/bindings/lib/sync_method_traits.h b/mojo/public/cpp/bindings/lib/sync_method_traits.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..2b334f8d01c2edb7c3e6b98fb8d35925aded11ab
+--- /dev/null
++++ b/mojo/public/cpp/bindings/lib/sync_method_traits.h
+@@ -0,0 +1,31 @@
++// Copyright 2022 The Chromium Authors
++// 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_LIB_SYNC_METHOD_TRAITS_H_
++#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SYNC_METHOD_TRAITS_H_
++
++#include <stdint.h>
++
++#include <type_traits>
++
++#include "base/containers/span.h"
++
++namespace mojo::internal {
++
++template <typename Interface, typename SFINAE = void>
++struct SyncMethodTraits {
++  static constexpr base::span<const uint32_t> GetOrdinals() { return {}; }
++};
++
++template <typename Interface>
++struct SyncMethodTraits<Interface,
++                        std::void_t<decltype(Interface::kSyncMethodOrdinals)>> {
++  static constexpr base::span<const uint32_t> GetOrdinals() {
++    return Interface::kSyncMethodOrdinals;
++  }
++};
++
++}  // namespace mojo::internal
++
++#endif  // MOJO_PUBLIC_CPP_BINDINGS_LIB_SYNC_METHOD_TRAITS_H_
+diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
+index 248176f6f350b57bb5138924c68c4393ba1c11a8..25cfe56438d099ddcb4ed04a4e83b9abb89b8d14 100644
+--- a/mojo/public/cpp/bindings/tests/BUILD.gn
++++ b/mojo/public/cpp/bindings/tests/BUILD.gn
+@@ -65,6 +65,7 @@ source_set("tests") {
+     ":mojo_public_bindings_test_utils",
+     ":test_extra_cpp_template_mojom",
+     ":test_mojom",
++    ":test_mojom__generate_message_ids",
+     "//base/test:test_support",
+     "//mojo/core/test:test_support",
+     "//mojo/public/cpp/bindings",
+diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
+index 2c44aaedd8b1a9415d41d8215266aad5033e34e3..1bb2c2d7c03f3b2069e09c1746b17cfea0477b0b 100644
+--- a/mojo/public/cpp/bindings/tests/bindings_perftest.cc
++++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
+@@ -213,12 +213,12 @@ TEST_F(MojoBindingsPerftest, MultiplexRouterPingPong) {
+ 
+   InterfaceEndpointClient client0(
+       router0->CreateLocalEndpointHandle(kPrimaryInterfaceId), &paddle0,
+-      nullptr, false, base::ThreadTaskRunnerHandle::Get(), 0u,
+-      kTestInterfaceName, MessageToStableIPCHash, MessageToMethodName);
++      nullptr, {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      MessageToStableIPCHash, MessageToMethodName);
+   InterfaceEndpointClient client1(
+       router1->CreateLocalEndpointHandle(kPrimaryInterfaceId), &paddle1,
+-      nullptr, false, base::ThreadTaskRunnerHandle::Get(), 0u,
+-      kTestInterfaceName, MessageToStableIPCHash, MessageToMethodName);
++      nullptr, {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      MessageToStableIPCHash, MessageToMethodName);
+ 
+   paddle0.set_sender(&client0);
+   paddle1.set_sender(&client1);
+@@ -265,8 +265,8 @@ TEST_F(MojoBindingsPerftest, MultiplexRouterDispatchCost) {
+   CounterReceiver receiver;
+   InterfaceEndpointClient client(
+       router->CreateLocalEndpointHandle(kPrimaryInterfaceId), &receiver,
+-      nullptr, false, base::ThreadTaskRunnerHandle::Get(), 0u,
+-      kTestInterfaceName, MessageToStableIPCHash, MessageToMethodName);
++      nullptr, {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      MessageToStableIPCHash, MessageToMethodName);
+ 
+   static const uint32_t kIterations[] = {1000, 3000000};
+ 
+diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
+index 300ead4aefd2d5167c29c0afae4ea7c1a3f85426..1dcb7bcddc04cc8584300e4add052cb2059613cf 100644
+--- a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
++++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
+@@ -74,13 +74,13 @@ class MultiplexRouterTest : public testing::Test {
+ 
+ TEST_F(MultiplexRouterTest, BasicRequestResponse) {
+   InterfaceEndpointClient client0(
+-      std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(), {},
++      base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+   ResponseGenerator generator;
+   InterfaceEndpointClient client1(
+       std::move(endpoint1_), &generator, std::make_unique<PassThroughFilter>(),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+ 
+   Message request;
+@@ -123,13 +123,13 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) {
+ 
+ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
+   InterfaceEndpointClient client0(
+-      std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(), {},
++      base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+   ResponseGenerator generator;
+   InterfaceEndpointClient client1(
+       std::move(endpoint1_), &generator, std::make_unique<PassThroughFilter>(),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+ 
+   Message request;
+@@ -173,15 +173,15 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
+ TEST_F(MultiplexRouterTest, LazyResponses) {
+   InterfaceEndpointClient client0(
+       std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+   base::RunLoop run_loop;
+   LazyResponseGenerator generator(run_loop.QuitClosure());
+   InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+-                                  base::WrapUnique(new PassThroughFilter()),
+-                                  false, base::ThreadTaskRunnerHandle::Get(),
+-                                  0u, kTestInterfaceName,
+-                                  MessageToStableIPCHash, MessageToMethodName);
++                                  base::WrapUnique(new PassThroughFilter()), {},
++                                  base::ThreadTaskRunnerHandle::Get(), 0u,
++                                  kTestInterfaceName, MessageToStableIPCHash,
++                                  MessageToMethodName);
+ 
+   Message request;
+   AllocRequestMessage(1, "hello", &request);
+@@ -247,7 +247,7 @@ TEST_F(MultiplexRouterTest, MissingResponses) {
+   base::RunLoop run_loop0, run_loop1;
+   InterfaceEndpointClient client0(
+       std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()),
+-      false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++      {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+       MessageToStableIPCHash, MessageToMethodName);
+   bool error_handler_called0 = false;
+   client0.set_connection_error_handler(base::BindOnce(
+@@ -256,10 +256,10 @@ TEST_F(MultiplexRouterTest, MissingResponses) {
+   base::RunLoop run_loop3;
+   LazyResponseGenerator generator(run_loop3.QuitClosure());
+   InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
+-                                  base::WrapUnique(new PassThroughFilter()),
+-                                  false, base::ThreadTaskRunnerHandle::Get(),
+-                                  0u, kTestInterfaceName,
+-                                  MessageToStableIPCHash, MessageToMethodName);
++                                  base::WrapUnique(new PassThroughFilter()), {},
++                                  base::ThreadTaskRunnerHandle::Get(), 0u,
++                                  kTestInterfaceName, MessageToStableIPCHash,
++                                  MessageToMethodName);
+   bool error_handler_called1 = false;
+   client1.set_connection_error_handler(base::BindOnce(
+       &ForwardErrorHandler, &error_handler_called1, run_loop1.QuitClosure()));
+@@ -306,13 +306,13 @@ TEST_F(MultiplexRouterTest, LateResponse) {
+   {
+     InterfaceEndpointClient client0(
+         std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(),
+-        false, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+-        MessageToStableIPCHash, MessageToMethodName);
+-    InterfaceEndpointClient client1(
+-        std::move(endpoint1_), &generator,
+-        std::make_unique<PassThroughFilter>(), false,
+-        base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
++        {}, base::ThreadTaskRunnerHandle::Get(), 0u, kTestInterfaceName,
+         MessageToStableIPCHash, MessageToMethodName);
++    InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
++                                    std::make_unique<PassThroughFilter>(), {},
++                                    base::ThreadTaskRunnerHandle::Get(), 0u,
++                                    kTestInterfaceName, MessageToStableIPCHash,
++                                    MessageToMethodName);
+ 
+     Message request;
+     AllocRequestMessage(1, "hello", &request);
+diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+index d794d29df07d289d34b3b1aae9f8574fc914c050..6de90c8ec9619358e97d50726d7c8c6820df7990 100644
+--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
++++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+@@ -2,6 +2,7 @@
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+ 
++#include <tuple>
+ #include <utility>
+ 
+ #include "base/barrier_closure.h"
+@@ -9,15 +10,21 @@
+ #include "base/check.h"
+ #include "base/run_loop.h"
+ #include "base/sequence_token.h"
++#include "base/task/sequenced_task_runner.h"
+ #include "base/task/thread_pool.h"
+ #include "base/test/bind.h"
+ #include "base/test/task_environment.h"
+ #include "base/threading/sequence_bound.h"
++#include "base/threading/sequenced_task_runner_handle.h"
+ #include "base/threading/thread.h"
+ #include "base/time/time.h"
+ #include "mojo/public/cpp/bindings/associated_receiver.h"
+ #include "mojo/public/cpp/bindings/associated_receiver_set.h"
+ #include "mojo/public/cpp/bindings/associated_remote.h"
++#include "mojo/public/cpp/bindings/lib/message_fragment.h"
++#include "mojo/public/cpp/bindings/lib/send_message_helper.h"
++#include "mojo/public/cpp/bindings/lib/serialization_util.h"
++#include "mojo/public/cpp/bindings/message.h"
+ #include "mojo/public/cpp/bindings/receiver.h"
+ #include "mojo/public/cpp/bindings/receiver_set.h"
+ #include "mojo/public/cpp/bindings/remote.h"
+@@ -27,10 +34,16 @@
+ #include "mojo/public/cpp/bindings/shared_remote.h"
+ #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+ #include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
++#include "mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom-shared-message-ids.h"
+ #include "mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom.h"
+ #include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h"
+ #include "testing/gtest/include/gtest/gtest.h"
+ 
++// This needs to be included last, since it forward declares a bunch of classes
++// but depends on those definitions to be included by headers that sort
++// lexicographically after.
++#include "mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom-params-data.h"
++
+ namespace mojo {
+ namespace test {
+ namespace sync_method_unittest {
+@@ -1703,6 +1716,237 @@ TEST_P(DisableSyncInterruptTest, SharedRemoteNoInterruptWhenDisabled) {
+ INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(SyncInterruptTest);
+ INSTANTIATE_MOJO_BINDINGS_TEST_SUITE_P(DisableSyncInterruptTest);
+ 
++class OneSyncImpl;
++
++class NoSyncImpl : public mojom::NoSync {
++ public:
++  explicit NoSyncImpl(PendingReceiver<mojom::NoSync> receiver)
++      : receiver_(this, std::move(receiver)) {}
++
++  explicit NoSyncImpl(
++      PendingAssociatedReceiver<mojom::NoSync> associated_receiver)
++      : associated_receiver_(this, std::move(associated_receiver)) {}
++
++  // mojom::NoSync implementation:
++  void Method(MethodCallback callback) override;
++  void BindNoSync(PendingAssociatedReceiver<mojom::NoSync> receiver) override;
++  void BindOneSync(PendingAssociatedReceiver<mojom::OneSync> receiver) override;
++
++ private:
++  Receiver<mojom::NoSync> receiver_{this};
++  AssociatedReceiver<mojom::NoSync> associated_receiver_{this};
++
++  std::unique_ptr<NoSyncImpl> associated_no_sync_;
++  std::unique_ptr<OneSyncImpl> associated_one_sync_;
++};
++
++class OneSyncImpl : public mojom::OneSync {
++ public:
++  explicit OneSyncImpl(PendingReceiver<mojom::OneSync> receiver)
++      : receiver_(this, std::move(receiver)) {}
++
++  explicit OneSyncImpl(
++      PendingAssociatedReceiver<mojom::OneSync> associated_receiver)
++      : associated_receiver_(this, std::move(associated_receiver)) {}
++
++  // mojom::OneSync implementation:
++  void Method(MethodCallback callback) override;
++  void SyncMethod(SyncMethodCallback callback) override;
++  void BindNoSync(PendingAssociatedReceiver<mojom::NoSync> receiver) override;
++  void BindOneSync(PendingAssociatedReceiver<mojom::OneSync> receiver) override;
++
++ private:
++  Receiver<mojom::OneSync> receiver_{this};
++  AssociatedReceiver<mojom::OneSync> associated_receiver_{this};
++
++  std::unique_ptr<NoSyncImpl> associated_no_sync_;
++  std::unique_ptr<OneSyncImpl> associated_one_sync_;
++};
++
++void NoSyncImpl::Method(MethodCallback callback) {
++  EXPECT_TRUE(false);
++  std::move(callback).Run();
++}
++
++void NoSyncImpl::BindNoSync(PendingAssociatedReceiver<mojom::NoSync> receiver) {
++  associated_no_sync_ = std::make_unique<NoSyncImpl>(std::move(receiver));
++}
++
++void NoSyncImpl::BindOneSync(
++    PendingAssociatedReceiver<mojom::OneSync> receiver) {
++  associated_one_sync_ = std::make_unique<OneSyncImpl>(std::move(receiver));
++}
++
++void OneSyncImpl::Method(MethodCallback callback) {
++  EXPECT_TRUE(false);
++  std::move(callback).Run();
++}
++
++void OneSyncImpl::SyncMethod(MethodCallback callback) {
++  std::move(callback).Run();
++}
++
++void OneSyncImpl::BindNoSync(
++    PendingAssociatedReceiver<mojom::NoSync> receiver) {
++  associated_no_sync_ = std::make_unique<NoSyncImpl>(std::move(receiver));
++}
++
++void OneSyncImpl::BindOneSync(
++    PendingAssociatedReceiver<mojom::OneSync> receiver) {
++  associated_one_sync_ = std::make_unique<OneSyncImpl>(std::move(receiver));
++}
++
++class NoResponseExpectedResponder : public MessageReceiver {
++ public:
++  explicit NoResponseExpectedResponder() = default;
++
++  // MessageReceiver implementation:
++  bool Accept(Message* message) override {
++    EXPECT_TRUE(false);
++    return true;
++  }
++};
++
++class SyncFlagValidationTest : public ::testing::TestWithParam<uint32_t> {
++ protected:
++  Message MakeNoSyncMethodMessage() {
++    const uint32_t flags =
++        // Always set the sync flag, as that's the primary point of the test.
++        Message::kFlagIsSync |
++        // InterfaceEndpointClient requires this flag if sending a message with
++        // a responder.
++        Message::kFlagExpectsResponse | GetParam();
++    Message message(mojom::internal::kNoSync_Method_Name, flags, 0, 0, nullptr);
++    ::mojo::internal::MessageFragment<
++        mojom::internal::NoSync_Method_Params_Data>
++        params(message);
++    params.Allocate();
++    return message;
++  }
++
++  Message MakeOneSyncMethodMessage() {
++    const uint32_t flags =
++        // Always set the sync flag, as that's the primary point of the test.
++        Message::kFlagIsSync |
++        // InterfaceEndpointClient requires this flag if sending a message with
++        // a responder.
++        Message::kFlagExpectsResponse | GetParam();
++    Message message(mojom::internal::kOneSync_Method_Name, flags, 0, 0,
++                    nullptr);
++    ::mojo::internal::MessageFragment<
++        mojom::internal::NoSync_Method_Params_Data>
++        params(message);
++    params.Allocate();
++    return message;
++  }
++
++  void FlushPostedTasks() {
++    base::RunLoop run_loop;
++    base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
++                                                     run_loop.QuitClosure());
++    run_loop.Run();
++  }
++
++ private:
++  base::test::SingleThreadTaskEnvironment task_environment;
++};
++
++TEST_P(SyncFlagValidationTest, NonSync) {
++  Remote<mojom::NoSync> remote;
++  NoSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  Message message = MakeNoSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++TEST_P(SyncFlagValidationTest, OneSync) {
++  Remote<mojom::OneSync> remote;
++  OneSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  Message message = MakeOneSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++TEST_P(SyncFlagValidationTest, NoSyncAssociatedWithNoSync) {
++  Remote<mojom::NoSync> remote;
++  NoSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  AssociatedRemote<mojom::NoSync> associated_remote;
++  remote->BindNoSync(associated_remote.BindNewEndpointAndPassReceiver());
++
++  FlushPostedTasks();
++
++  Message message = MakeNoSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *associated_remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++TEST_P(SyncFlagValidationTest, OneSyncAssociatedWithNoSync) {
++  Remote<mojom::NoSync> remote;
++  NoSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  AssociatedRemote<mojom::OneSync> associated_remote;
++  remote->BindOneSync(associated_remote.BindNewEndpointAndPassReceiver());
++
++  FlushPostedTasks();
++
++  Message message = MakeOneSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *associated_remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++TEST_P(SyncFlagValidationTest, NoSyncAssociatedWithOneSync) {
++  Remote<mojom::OneSync> remote;
++  OneSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  AssociatedRemote<mojom::NoSync> associated_remote;
++  remote->BindNoSync(associated_remote.BindNewEndpointAndPassReceiver());
++
++  FlushPostedTasks();
++
++  Message message = MakeNoSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *associated_remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++TEST_P(SyncFlagValidationTest, OneSyncAssociatedWithOneSync) {
++  Remote<mojom::OneSync> remote;
++  OneSyncImpl impl(remote.BindNewPipeAndPassReceiver());
++
++  AssociatedRemote<mojom::OneSync> associated_remote;
++  remote->BindOneSync(associated_remote.BindNewEndpointAndPassReceiver());
++
++  FlushPostedTasks();
++
++  Message message = MakeOneSyncMethodMessage();
++  auto responder = std::make_unique<NoResponseExpectedResponder>();
++  ASSERT_TRUE(remote.internal_state()->endpoint_client_for_test());
++  ::mojo::internal::SendMojoMessage(
++      *associated_remote.internal_state()->endpoint_client_for_test(), message,
++      std::move(responder));
++}
++
++INSTANTIATE_TEST_SUITE_P(,
++                         SyncFlagValidationTest,
++                         ::testing::Values(0, Message::kFlagIsResponse));
++
+ }  // namespace
+ }  // namespace sync_method_unittest
+ }  // namespace test
+diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom b/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
+index 951442b3585ad22f936568e211ad41f8ae358705..0cc5f7c6d288f988b6114ff6b5b80546558eb378 100644
+--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
++++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.test-mojom
+@@ -49,3 +49,20 @@ interface Ponger {
+ interface SyncService {
+   [Sync] SyncCall() => ();
+ };
++
++interface NoSync {
++  Method() => ();
++
++  BindNoSync(pending_associated_receiver<NoSync> no_sync);
++  BindOneSync(pending_associated_receiver<OneSync> one_sync);
++};
++
++interface OneSync {
++  Method() => ();
++
++  [Sync]
++  SyncMethod() => ();
++
++  BindNoSync(pending_associated_receiver<NoSync> no_sync);
++  BindOneSync(pending_associated_receiver<OneSync> one_sync);
++};
+diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
+index c09a1ec210acbc8d09cdf4d4e09916a8809fa586..57d9f66922475c73f65b4d2713ad50cbb3ae0f23 100644
+--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
++++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
+@@ -29,7 +29,12 @@ class {{export_attribute}} {{interface.name}}
+ {%- endif %}
+   static constexpr uint32_t Version_ = {{interface.version}};
+   static constexpr bool PassesAssociatedKinds_ = {% if interface|passes_associated_kinds %}true{% else %}false{% endif %};
+-  static constexpr bool HasSyncMethods_ = {% if interface|has_sync_methods %}true{% else %}false{% endif %};
++{%- set sync_method_ordinals = interface|get_sync_method_ordinals -%}
++{%- if sync_method_ordinals %}
++  static inline constexpr uint32_t kSyncMethodOrdinals[] = {
++    {{sync_method_ordinals|sort|join(', \n')|indent(4)}}
++  };
++{%- endif %}
+   static constexpr bool HasUninterruptableMethods_ =
+       {%- if interface|has_uninterruptable_methods %} true
+       {%- else %} false{% endif %};
+diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl
+index af3bc5168beb5f9e5b9cfe63354dbdb6b29ff8a1..ab71e91dab403f4c552165eba5da7e32a61b1b83 100644
+--- a/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl
++++ b/mojo/public/tools/bindings/generators/cpp_templates/module-params-data.h.tmpl
+@@ -17,13 +17,15 @@
+ #pragma clang diagnostic ignored "-Wunused-private-field"
+ #endif
+ 
++namespace mojo::internal {
++class ValidationContext;
++}
++
+ {%- for namespace in namespaces_as_array %}
+ namespace {{namespace}} {
+ {%- endfor %}
+ namespace internal {
+ 
+-class ValidationContext;
+-
+ {#--- Interface parameter definitions #}
+ {%- for interface in interfaces %}
+ {%-   for method in interface.methods %}
+diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+index 014f2bf04da4f2e11a13d57d910ecc8a8b489113..add5a877cb7e38da4599d3ae76ea0bd9486637da 100644
+--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
++++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+@@ -403,7 +403,7 @@ class Generator(generator.Generator):
+         "get_qualified_name_for_kind": self._GetQualifiedNameForKind,
+         "has_callbacks": mojom.HasCallbacks,
+         "has_packed_method_ordinals": HasPackedMethodOrdinals,
+-        "has_sync_methods": mojom.HasSyncMethods,
++        "get_sync_method_ordinals": mojom.GetSyncMethodOrdinals,
+         "has_uninterruptable_methods": mojom.HasUninterruptableMethods,
+         "method_supports_lazy_serialization":
+         self._MethodSupportsLazySerialization,
+diff --git a/mojo/public/tools/mojom/mojom/generate/module.py b/mojo/public/tools/mojom/mojom/generate/module.py
+index 160ad1ef37d7bab86fb15081e0b202845820f8b5..a34d9f0e8134281806a6dc7e7f21e649bce95674 100644
+--- a/mojo/public/tools/mojom/mojom/generate/module.py
++++ b/mojo/public/tools/mojom/mojom/generate/module.py
+@@ -1739,11 +1739,8 @@ def MethodPassesInterfaces(method):
+   return _AnyMethodParameterRecursive(method, IsInterfaceKind)
+ 
+ 
+-def HasSyncMethods(interface):
+-  for method in interface.methods:
+-    if method.sync:
+-      return True
+-  return False
++def GetSyncMethodOrdinals(interface):
++  return [method.ordinal for method in interface.methods if method.sync]
+ 
+ 
+ def HasUninterruptableMethods(interface):