Prevent transceiver crash during SDP local rejection

Ensure that transceivers remain valid when handling locally rejected
media content during SDP updates. Previously, the transceiver was
std::move()ed out of the update vector prematurely, which could cause it
to be destroyed before associated worker tasks had finished executing.

Fixed: chromium:504194151
Change-Id: Id38ce125cbe83a4ea709adfd1951613642cafc8e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/466140
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47528}
diff --git a/pc/peer_connection_jsep_unittest.cc b/pc/peer_connection_jsep_unittest.cc
index 104591b..a4ebec6 100644
--- a/pc/peer_connection_jsep_unittest.cc
+++ b/pc/peer_connection_jsep_unittest.cc
@@ -1031,6 +1031,20 @@
   EXPECT_EQ(second_mid, caller_second_transceiver->mid());
 }
 
+TEST_F(PeerConnectionJsepTest, LocallyRejectedTransceiverDoesNotCrash) {
+  auto caller = CreatePeerConnection();
+  auto transceiver = caller->AddTransceiver(MediaType::AUDIO);
+
+  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
+
+  transceiver->StopInternal();
+
+  // The reoffer will have a rejected media section.
+  // Setting it as local description triggers
+  // MaybeHandleLocallyRejectedTransceiver.
+  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
+}
+
 // Test that an m= section is *not* recycled if the media section is only
 // rejected in the pending remote description and there is no current local
 // description.
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index c3a66d6..09f9ca5 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -4311,9 +4311,11 @@
     // Handle locally rejected content. This code path is only needed for apps
     // that SDP munge. Remote rejected content is handled in
     // ApplyRemoteDescriptionUpdateTransceiverState().
+    // Do not use std::move here to ensure that the transceiver stays alive
+    // in the `transceivers_to_update` vector until the end of this function.
+    // This guarantees that it outlives the execution of `worker_tasks.Run()`.
     MaybeHandleLocallyRejectedTransceiver(source, new_session, update.content,
-                                          std::move(update.transceiver),
-                                          worker_tasks);
+                                          update.transceiver, worker_tasks);
   }
 
   error = network_teardown_tasks.Run();