/src/mozilla-central/ipc/glue/MessageLink.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * vim: sw=4 ts=4 et : |
3 | | */ |
4 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
5 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
6 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
7 | | |
8 | | #include "mozilla/ipc/MessageLink.h" |
9 | | #include "mozilla/ipc/MessageChannel.h" |
10 | | #include "mozilla/ipc/BrowserProcessSubThread.h" |
11 | | #include "mozilla/ipc/ProtocolUtils.h" |
12 | | #include "chrome/common/ipc_channel.h" |
13 | | |
14 | | #include "mozilla/Assertions.h" |
15 | | #include "mozilla/DebugOnly.h" |
16 | | #include "nsDebug.h" |
17 | | #include "nsExceptionHandler.h" |
18 | | #include "nsISupportsImpl.h" |
19 | | #include "nsPrintfCString.h" |
20 | | #include "nsXULAppAPI.h" |
21 | | |
22 | | using namespace mozilla; |
23 | | using namespace std; |
24 | | |
25 | | // We rely on invariants about the lifetime of the transport: |
26 | | // |
27 | | // - outlives this MessageChannel |
28 | | // - deleted on the IO thread |
29 | | // |
30 | | // These invariants allow us to send messages directly through the |
31 | | // transport without having to worry about orphaned Send() tasks on |
32 | | // the IO thread touching MessageChannel memory after it's been deleted |
33 | | // on the worker thread. We also don't need to refcount the |
34 | | // Transport, because whatever task triggers its deletion only runs on |
35 | | // the IO thread, and only runs after this MessageChannel is done with |
36 | | // the Transport. |
37 | | |
38 | | namespace mozilla { |
39 | | namespace ipc { |
40 | | |
41 | | MessageLink::MessageLink(MessageChannel *aChan) |
42 | | : mChan(aChan) |
43 | 0 | { |
44 | 0 | } |
45 | | |
46 | | MessageLink::~MessageLink() |
47 | 0 | { |
48 | | #ifdef DEBUG |
49 | | mChan = nullptr; |
50 | | #endif |
51 | | } |
52 | | |
53 | | ProcessLink::ProcessLink(MessageChannel *aChan) |
54 | | : MessageLink(aChan) |
55 | | , mTransport(nullptr) |
56 | | , mIOLoop(nullptr) |
57 | | , mExistingListener(nullptr) |
58 | 0 | { |
59 | 0 | } |
60 | | |
61 | | ProcessLink::~ProcessLink() |
62 | 0 | { |
63 | | #ifdef DEBUG |
64 | | mTransport = nullptr; |
65 | | mIOLoop = nullptr; |
66 | | mExistingListener = nullptr; |
67 | | #endif |
68 | | } |
69 | | |
70 | | void |
71 | | ProcessLink::Open(mozilla::ipc::Transport* aTransport, MessageLoop *aIOLoop, Side aSide) |
72 | 0 | { |
73 | 0 | mChan->AssertWorkerThread(); |
74 | 0 |
|
75 | 0 | MOZ_ASSERT(aTransport, "need transport layer"); |
76 | 0 |
|
77 | 0 | // FIXME need to check for valid channel |
78 | 0 |
|
79 | 0 | mTransport = aTransport; |
80 | 0 |
|
81 | 0 | // FIXME figure out whether we're in parent or child, grab IO loop |
82 | 0 | // appropriately |
83 | 0 | bool needOpen = true; |
84 | 0 | if(aIOLoop) { |
85 | 0 | // We're a child or using the new arguments. Either way, we |
86 | 0 | // need an open. |
87 | 0 | needOpen = true; |
88 | 0 | mChan->mSide = (aSide == UnknownSide) ? ChildSide : aSide; |
89 | 0 | } else { |
90 | 0 | MOZ_ASSERT(aSide == UnknownSide, "expected default side arg"); |
91 | 0 |
|
92 | 0 | // parent |
93 | 0 | mChan->mSide = ParentSide; |
94 | 0 | needOpen = false; |
95 | 0 | aIOLoop = XRE_GetIOMessageLoop(); |
96 | 0 | } |
97 | 0 |
|
98 | 0 | mIOLoop = aIOLoop; |
99 | 0 |
|
100 | 0 | NS_ASSERTION(mIOLoop, "need an IO loop"); |
101 | 0 | NS_ASSERTION(mChan->mWorkerLoop, "need a worker loop"); |
102 | 0 |
|
103 | 0 | // If we were never able to open the transport, immediately post an error message. |
104 | 0 | if (mTransport->Unsound_IsClosed()) { |
105 | 0 | mIOLoop->PostTask( |
106 | 0 | NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelConnectError", |
107 | 0 | this, |
108 | 0 | &ProcessLink::OnChannelConnectError)); |
109 | 0 | return; |
110 | 0 | } |
111 | 0 | |
112 | 0 | { |
113 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
114 | 0 |
|
115 | 0 | if (needOpen) { |
116 | 0 | // Transport::Connect() has not been called. Call it so |
117 | 0 | // we start polling our pipe and processing outgoing |
118 | 0 | // messages. |
119 | 0 | mIOLoop->PostTask( |
120 | 0 | NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelOpened", |
121 | 0 | this, |
122 | 0 | &ProcessLink::OnChannelOpened)); |
123 | 0 | } else { |
124 | 0 | // Transport::Connect() has already been called. Take |
125 | 0 | // over the channel from the previous listener and process |
126 | 0 | // any queued messages. |
127 | 0 | mIOLoop->PostTask(NewNonOwningRunnableMethod( |
128 | 0 | "ipc::ProcessLink::OnTakeConnectedChannel", |
129 | 0 | this, |
130 | 0 | &ProcessLink::OnTakeConnectedChannel)); |
131 | 0 | } |
132 | 0 |
|
133 | 0 | // Wait until one of the runnables above changes the state of the |
134 | 0 | // channel. Note that the state could be changed again after that (to |
135 | 0 | // ChannelClosing, for example, by the IO thread). We can rely on it not |
136 | 0 | // changing back to Closed: only the worker thread changes it to closed, |
137 | 0 | // and we're on the worker thread, blocked. |
138 | 0 | while (mChan->mChannelState == ChannelClosed) { |
139 | 0 | mChan->mMonitor->Wait(); |
140 | 0 | } |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | | void |
145 | | ProcessLink::EchoMessage(Message *msg) |
146 | 0 | { |
147 | 0 | mChan->AssertWorkerThread(); |
148 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
149 | 0 |
|
150 | 0 | mIOLoop->PostTask( |
151 | 0 | NewNonOwningRunnableMethod<Message*>("ipc::ProcessLink::OnEchoMessage", |
152 | 0 | this, |
153 | 0 | &ProcessLink::OnEchoMessage, |
154 | 0 | msg)); |
155 | 0 | // OnEchoMessage takes ownership of |msg| |
156 | 0 | } |
157 | | |
158 | | void |
159 | | ProcessLink::SendMessage(Message *msg) |
160 | 0 | { |
161 | 0 | if (msg->size() > IPC::Channel::kMaximumMessageSize) { |
162 | 0 | CrashReporter::AnnotateCrashReport( |
163 | 0 | CrashReporter::Annotation::IPCMessageName, |
164 | 0 | nsDependentCString(msg->name())); |
165 | 0 | CrashReporter::AnnotateCrashReport( |
166 | 0 | CrashReporter::Annotation::IPCMessageSize, |
167 | 0 | static_cast<int>(msg->size())); |
168 | 0 | MOZ_CRASH("IPC message size is too large"); |
169 | 0 | } |
170 | 0 |
|
171 | 0 | if (!mChan->mIsPostponingSends) { |
172 | 0 | mChan->AssertWorkerThread(); |
173 | 0 | } |
174 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
175 | 0 |
|
176 | 0 | mIOLoop->PostTask(NewNonOwningRunnableMethod<Message*>( |
177 | 0 | "IPC::Channel::Send", mTransport, &Transport::Send, msg)); |
178 | 0 | } |
179 | | |
180 | | void |
181 | | ProcessLink::SendClose() |
182 | 0 | { |
183 | 0 | mChan->AssertWorkerThread(); |
184 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
185 | 0 |
|
186 | 0 | mIOLoop->PostTask(NewNonOwningRunnableMethod( |
187 | 0 | "ipc::ProcessLink::OnCloseChannel", this, &ProcessLink::OnCloseChannel)); |
188 | 0 | } |
189 | | |
190 | | ThreadLink::ThreadLink(MessageChannel *aChan, MessageChannel *aTargetChan) |
191 | | : MessageLink(aChan), |
192 | | mTargetChan(aTargetChan) |
193 | 0 | { |
194 | 0 | } |
195 | | |
196 | | ThreadLink::~ThreadLink() |
197 | 0 | { |
198 | 0 | MOZ_ASSERT(mChan); |
199 | 0 | MOZ_ASSERT(mChan->mMonitor); |
200 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
201 | 0 |
|
202 | 0 | // Bug 848949: We need to prevent the other side |
203 | 0 | // from sending us any more messages to avoid Use-After-Free. |
204 | 0 | // The setup here is as shown: |
205 | 0 | // |
206 | 0 | // (Us) (Them) |
207 | 0 | // MessageChannel MessageChannel |
208 | 0 | // | ^ \ / ^ | |
209 | 0 | // | | X | | |
210 | 0 | // v | / \ | v |
211 | 0 | // ThreadLink ThreadLink |
212 | 0 | // |
213 | 0 | // We want to null out the diagonal link from their ThreadLink |
214 | 0 | // to our MessageChannel. Note that we must hold the monitor so |
215 | 0 | // that we do this atomically with respect to them trying to send |
216 | 0 | // us a message. Since the channels share the same monitor this |
217 | 0 | // also protects against the two ~ThreadLink() calls racing. |
218 | 0 | if (mTargetChan) { |
219 | 0 | MOZ_ASSERT(mTargetChan->mLink); |
220 | 0 | static_cast<ThreadLink*>(mTargetChan->mLink)->mTargetChan = nullptr; |
221 | 0 | } |
222 | 0 | mTargetChan = nullptr; |
223 | 0 | } |
224 | | |
225 | | void |
226 | | ThreadLink::EchoMessage(Message *msg) |
227 | 0 | { |
228 | 0 | mChan->AssertWorkerThread(); |
229 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
230 | 0 |
|
231 | 0 | mChan->OnMessageReceivedFromLink(std::move(*msg)); |
232 | 0 | delete msg; |
233 | 0 | } |
234 | | |
235 | | void |
236 | | ThreadLink::SendMessage(Message *msg) |
237 | 0 | { |
238 | 0 | if (!mChan->mIsPostponingSends) { |
239 | 0 | mChan->AssertWorkerThread(); |
240 | 0 | } |
241 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
242 | 0 |
|
243 | 0 | if (mTargetChan) |
244 | 0 | mTargetChan->OnMessageReceivedFromLink(std::move(*msg)); |
245 | 0 | delete msg; |
246 | 0 | } |
247 | | |
248 | | void |
249 | | ThreadLink::SendClose() |
250 | 0 | { |
251 | 0 | mChan->AssertWorkerThread(); |
252 | 0 | mChan->mMonitor->AssertCurrentThreadOwns(); |
253 | 0 |
|
254 | 0 | mChan->mChannelState = ChannelClosed; |
255 | 0 |
|
256 | 0 | // In a ProcessLink, we would close our half the channel. This |
257 | 0 | // would show up on the other side as an error on the I/O thread. |
258 | 0 | // The I/O thread would then invoke OnChannelErrorFromLink(). |
259 | 0 | // As usual, we skip that process and just invoke the |
260 | 0 | // OnChannelErrorFromLink() method directly. |
261 | 0 | if (mTargetChan) |
262 | 0 | mTargetChan->OnChannelErrorFromLink(); |
263 | 0 | } |
264 | | |
265 | | bool |
266 | | ThreadLink::Unsound_IsClosed() const |
267 | 0 | { |
268 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
269 | 0 | return mChan->mChannelState == ChannelClosed; |
270 | 0 | } |
271 | | |
272 | | uint32_t |
273 | | ThreadLink::Unsound_NumQueuedMessages() const |
274 | 0 | { |
275 | 0 | // ThreadLinks don't have a message queue. |
276 | 0 | return 0; |
277 | 0 | } |
278 | | |
279 | | // |
280 | | // The methods below run in the context of the IO thread |
281 | | // |
282 | | |
283 | | void |
284 | | ProcessLink::OnMessageReceived(Message&& msg) |
285 | 0 | { |
286 | 0 | AssertIOThread(); |
287 | 0 | NS_ASSERTION(mChan->mChannelState != ChannelError, "Shouldn't get here!"); |
288 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
289 | 0 | mChan->OnMessageReceivedFromLink(std::move(msg)); |
290 | 0 | } |
291 | | |
292 | | void |
293 | | ProcessLink::OnEchoMessage(Message* msg) |
294 | 0 | { |
295 | 0 | AssertIOThread(); |
296 | 0 | OnMessageReceived(std::move(*msg)); |
297 | 0 | delete msg; |
298 | 0 | } |
299 | | |
300 | | void |
301 | | ProcessLink::OnChannelOpened() |
302 | 0 | { |
303 | 0 | AssertIOThread(); |
304 | 0 |
|
305 | 0 | { |
306 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
307 | 0 |
|
308 | 0 | mExistingListener = mTransport->set_listener(this); |
309 | | #ifdef DEBUG |
310 | | if (mExistingListener) { |
311 | | std::queue<Message> pending; |
312 | | mExistingListener->GetQueuedMessages(pending); |
313 | | MOZ_ASSERT(pending.empty()); |
314 | | } |
315 | | #endif // DEBUG |
316 | |
|
317 | 0 | mChan->mChannelState = ChannelOpening; |
318 | 0 | lock.Notify(); |
319 | 0 | } |
320 | 0 | /*assert*/mTransport->Connect(); |
321 | 0 | } |
322 | | |
323 | | void |
324 | | ProcessLink::OnTakeConnectedChannel() |
325 | 0 | { |
326 | 0 | AssertIOThread(); |
327 | 0 |
|
328 | 0 | std::queue<Message> pending; |
329 | 0 | { |
330 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
331 | 0 |
|
332 | 0 | mChan->mChannelState = ChannelConnected; |
333 | 0 |
|
334 | 0 | mExistingListener = mTransport->set_listener(this); |
335 | 0 | if (mExistingListener) { |
336 | 0 | mExistingListener->GetQueuedMessages(pending); |
337 | 0 | } |
338 | 0 | lock.Notify(); |
339 | 0 | } |
340 | 0 |
|
341 | 0 | // Dispatch whatever messages the previous listener had queued up. |
342 | 0 | while (!pending.empty()) { |
343 | 0 | OnMessageReceived(std::move(pending.front())); |
344 | 0 | pending.pop(); |
345 | 0 | } |
346 | 0 | } |
347 | | |
348 | | void |
349 | | ProcessLink::OnChannelConnected(int32_t peer_pid) |
350 | 0 | { |
351 | 0 | AssertIOThread(); |
352 | 0 |
|
353 | 0 | bool notifyChannel = false; |
354 | 0 |
|
355 | 0 | { |
356 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
357 | 0 | // Do not force it into connected if it has errored out, started |
358 | 0 | // closing, etc. Note that we can be in the Connected state already |
359 | 0 | // since the parent starts out Connected. |
360 | 0 | if (mChan->mChannelState == ChannelOpening || |
361 | 0 | mChan->mChannelState == ChannelConnected) |
362 | 0 | { |
363 | 0 | mChan->mChannelState = ChannelConnected; |
364 | 0 | mChan->mMonitor->Notify(); |
365 | 0 | notifyChannel = true; |
366 | 0 | } |
367 | 0 | } |
368 | 0 |
|
369 | 0 | if (mExistingListener) { |
370 | 0 | mExistingListener->OnChannelConnected(peer_pid); |
371 | 0 | } |
372 | 0 |
|
373 | 0 | if (notifyChannel) { |
374 | 0 | mChan->OnChannelConnected(peer_pid); |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | | void |
379 | | ProcessLink::OnChannelConnectError() |
380 | 0 | { |
381 | 0 | AssertIOThread(); |
382 | 0 |
|
383 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
384 | 0 |
|
385 | 0 | mChan->OnChannelErrorFromLink(); |
386 | 0 | } |
387 | | |
388 | | void |
389 | | ProcessLink::OnChannelError() |
390 | 0 | { |
391 | 0 | AssertIOThread(); |
392 | 0 |
|
393 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
394 | 0 |
|
395 | 0 | MOZ_ALWAYS_TRUE(this == mTransport->set_listener(mExistingListener)); |
396 | 0 |
|
397 | 0 | mChan->OnChannelErrorFromLink(); |
398 | 0 | } |
399 | | |
400 | | void |
401 | | ProcessLink::OnCloseChannel() |
402 | 0 | { |
403 | 0 | AssertIOThread(); |
404 | 0 |
|
405 | 0 | mTransport->Close(); |
406 | 0 |
|
407 | 0 | MonitorAutoLock lock(*mChan->mMonitor); |
408 | 0 |
|
409 | 0 | DebugOnly<IPC::Channel::Listener*> previousListener = |
410 | 0 | mTransport->set_listener(mExistingListener); |
411 | 0 |
|
412 | 0 | // OnChannelError may have reset the listener already. |
413 | 0 | MOZ_ASSERT(previousListener == this || |
414 | 0 | previousListener == mExistingListener); |
415 | 0 |
|
416 | 0 | mChan->mChannelState = ChannelClosed; |
417 | 0 | mChan->mMonitor->Notify(); |
418 | 0 | } |
419 | | |
420 | | bool |
421 | | ProcessLink::Unsound_IsClosed() const |
422 | 0 | { |
423 | 0 | return mTransport->Unsound_IsClosed(); |
424 | 0 | } |
425 | | |
426 | | uint32_t |
427 | | ProcessLink::Unsound_NumQueuedMessages() const |
428 | 0 | { |
429 | 0 | return mTransport->Unsound_NumQueuedMessages(); |
430 | 0 | } |
431 | | |
432 | | } // namespace ipc |
433 | | } // namespace mozilla |