1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ppapi/thunk/enter.h"
6 
7 #include "base/bind.h"
8 #include "base/logging.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/synchronization/lock.h"
12 #include "ppapi/shared_impl/ppapi_globals.h"
13 #include "ppapi/shared_impl/tracked_callback.h"
14 #include "ppapi/thunk/ppb_instance_api.h"
15 #include "ppapi/thunk/resource_creation_api.h"
16 
17 namespace ppapi {
18 namespace {
19 
IsMainThread()20 bool IsMainThread() {
21   return
22       PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
23 }
24 
CurrentThreadHandlingBlockingMessage()25 bool CurrentThreadHandlingBlockingMessage() {
26   ppapi::MessageLoopShared* current =
27       PpapiGlobals::Get()->GetCurrentMessageLoop();
28   return current && current->CurrentlyHandlingBlockingMessage();
29 }
30 
31 }  // namespace
32 
33 namespace thunk {
34 
35 namespace subtle {
36 
EnterBase()37 EnterBase::EnterBase() {}
38 
EnterBase(PP_Resource resource)39 EnterBase::EnterBase(PP_Resource resource) : resource_(GetResource(resource)) {}
40 
EnterBase(PP_Instance instance,SingletonResourceID resource_id)41 EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id)
42     : resource_(GetSingletonResource(instance, resource_id)) {
43   if (!resource_)
44     retval_ = PP_ERROR_BADARGUMENT;
45 }
46 
EnterBase(PP_Resource resource,const PP_CompletionCallback & callback)47 EnterBase::EnterBase(PP_Resource resource,
48                      const PP_CompletionCallback& callback)
49     : EnterBase(resource) {
50   callback_ = new TrackedCallback(resource_, callback);
51 }
52 
EnterBase(PP_Instance instance,SingletonResourceID resource_id,const PP_CompletionCallback & callback)53 EnterBase::EnterBase(PP_Instance instance,
54                      SingletonResourceID resource_id,
55                      const PP_CompletionCallback& callback)
56     : EnterBase(instance, resource_id) {
57   callback_ = new TrackedCallback(resource_, callback);
58 }
59 
~EnterBase()60 EnterBase::~EnterBase() {
61   // callback_ is cleared any time it is run, scheduled to be run, or once we
62   // know it will be completed asynchronously. So by this point it should be
63   // null.
64   DCHECK(!callback_) << "|callback_| is not null. Did you forget to call "
65                         "|EnterBase::SetResult| in the interface's thunk?";
66 }
67 
SetResult(int32_t result)68 int32_t EnterBase::SetResult(int32_t result) {
69   if (!callback_) {
70     // It doesn't make sense to call SetResult if there is no callback.
71     NOTREACHED();
72     retval_ = result;
73     return result;
74   }
75   if (result == PP_OK_COMPLETIONPENDING) {
76     retval_ = result;
77     if (callback_->is_blocking()) {
78       DCHECK(!IsMainThread());  // We should have returned an error before this.
79       retval_ = callback_->BlockUntilComplete();
80     } else {
81       // The callback is not blocking and the operation will complete
82       // asynchronously, so there's nothing to do.
83       retval_ = result;
84     }
85   } else {
86     // The function completed synchronously.
87     if (callback_->is_required()) {
88       // This is a required callback, so we must issue it asynchronously.
89       callback_->PostRun(result);
90       retval_ = PP_OK_COMPLETIONPENDING;
91     } else {
92       // The callback is blocking or optional, so all we need to do is mark
93       // the callback as completed so that it won't be issued later.
94       callback_->MarkAsCompleted();
95       retval_ = result;
96     }
97   }
98   callback_ = nullptr;
99   return retval_;
100 }
101 
102 // static
GetResource(PP_Resource resource)103 Resource* EnterBase::GetResource(PP_Resource resource) {
104   return PpapiGlobals::Get()->GetResourceTracker()->GetResource(resource);
105 }
106 
107 // static
GetSingletonResource(PP_Instance instance,SingletonResourceID resource_id)108 Resource* EnterBase::GetSingletonResource(PP_Instance instance,
109                                           SingletonResourceID resource_id) {
110   PPB_Instance_API* ppb_instance =
111       PpapiGlobals::Get()->GetInstanceAPI(instance);
112   if (!ppb_instance)
113     return nullptr;
114 
115   return ppb_instance->GetSingletonResource(instance, resource_id);
116 }
117 
SetStateForCallbackError(bool report_error)118 void EnterBase::SetStateForCallbackError(bool report_error) {
119   if (PpapiGlobals::Get()->IsHostGlobals()) {
120     // In-process plugins can't make PPAPI calls off the main thread.
121     CHECK(IsMainThread());
122   }
123   if (callback_) {
124     if (callback_->is_blocking() && IsMainThread()) {
125       // Blocking callbacks are never allowed on the main thread.
126       callback_->MarkAsCompleted();
127       callback_ = nullptr;
128       retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
129       if (report_error) {
130         std::string message(
131             "Blocking callbacks are not allowed on the main thread.");
132         PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
133                                                     std::string(), message);
134       }
135     } else if (callback_->is_blocking() &&
136                CurrentThreadHandlingBlockingMessage()) {
137       // Blocking callbacks are not allowed while handling a blocking message.
138       callback_->MarkAsCompleted();
139       callback_ = nullptr;
140       retval_ = PP_ERROR_WOULD_BLOCK_THREAD;
141       if (report_error) {
142         std::string message("Blocking callbacks are not allowed while handling "
143                             "a blocking message from JavaScript.");
144         PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
145                                                     std::string(), message);
146       }
147     } else if (!IsMainThread() &&
148                callback_->has_null_target_loop() &&
149                !callback_->is_blocking()) {
150       // On a non-main thread, there must be a valid target loop for non-
151       // blocking callbacks, or we will have no place to run them.
152 
153       // If the callback is required, there's no nice way to tell the plugin.
154       // We can't run their callback asynchronously without a message loop, and
155       // the plugin won't expect any return code other than
156       // PP_OK_COMPLETIONPENDING. So we crash to make the problem more obvious.
157       if (callback_->is_required()) {
158         std::string message("Attempted to use a required callback, but there "
159                             "is no attached message loop on which to run the "
160                             "callback.");
161         PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
162                                                     std::string(), message);
163         LOG(FATAL) << message;
164       }
165 
166       callback_->MarkAsCompleted();
167       callback_ = nullptr;
168       retval_ = PP_ERROR_NO_MESSAGE_LOOP;
169       if (report_error) {
170         std::string message(
171             "The calling thread must have a message loop attached.");
172         PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
173                                                     std::string(), message);
174       }
175     }
176   }
177 }
178 
ClearCallback()179 void EnterBase::ClearCallback() {
180   callback_ = nullptr;
181 }
182 
SetStateForResourceError(PP_Resource pp_resource,Resource * resource_base,void * object,bool report_error)183 void EnterBase::SetStateForResourceError(PP_Resource pp_resource,
184                                          Resource* resource_base,
185                                          void* object,
186                                          bool report_error) {
187   // Check for callback errors. If we get any, SetStateForCallbackError will
188   // emit a log message. But we also want to check for resource errors. If there
189   // are both kinds of errors, we'll emit two log messages and return
190   // PP_ERROR_BADRESOURCE.
191   SetStateForCallbackError(report_error);
192 
193   if (object)
194     return;  // Everything worked.
195 
196   if (callback_ && callback_->is_required()) {
197     callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADRESOURCE));
198     callback_ = nullptr;
199     retval_ = PP_OK_COMPLETIONPENDING;
200   } else {
201     if (callback_)
202       callback_->MarkAsCompleted();
203     callback_ = nullptr;
204     retval_ = PP_ERROR_BADRESOURCE;
205   }
206 
207   // We choose to silently ignore the error when the pp_resource is null
208   // because this is a pretty common case and we don't want to have lots
209   // of errors in the log. This should be an obvious case to debug.
210   if (report_error && pp_resource) {
211     std::string message;
212     if (resource_base) {
213       message = base::StringPrintf(
214           "0x%X is not the correct type for this function.",
215           pp_resource);
216     } else {
217       message = base::StringPrintf(
218           "0x%X is not a valid resource ID.",
219           pp_resource);
220     }
221     PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
222                                                 std::string(), message);
223   }
224 }
225 
SetStateForFunctionError(PP_Instance pp_instance,void * object,bool report_error)226 void EnterBase::SetStateForFunctionError(PP_Instance pp_instance,
227                                          void* object,
228                                          bool report_error) {
229   // Check for callback errors. If we get any, SetStateForCallbackError will
230   // emit a log message. But we also want to check for instance errors. If there
231   // are both kinds of errors, we'll emit two log messages and return
232   // PP_ERROR_BADARGUMENT.
233   SetStateForCallbackError(report_error);
234 
235   if (object)
236     return;  // Everything worked.
237 
238   if (callback_ && callback_->is_required()) {
239     callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADARGUMENT));
240     callback_ = nullptr;
241     retval_ = PP_OK_COMPLETIONPENDING;
242   } else {
243     if (callback_)
244       callback_->MarkAsCompleted();
245     callback_ = nullptr;
246     retval_ = PP_ERROR_BADARGUMENT;
247   }
248 
249   // We choose to silently ignore the error when the pp_instance is null as
250   // for PP_Resources above.
251   if (report_error && pp_instance) {
252     std::string message;
253     message = base::StringPrintf(
254         "0x%X is not a valid instance ID.",
255         pp_instance);
256     PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
257                                                 std::string(), message);
258   }
259 }
260 
261 }  // namespace subtle
262 
EnterInstance(PP_Instance instance)263 EnterInstance::EnterInstance(PP_Instance instance)
264     : EnterBase(),
265       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
266   SetStateForFunctionError(instance, functions_, true);
267 }
268 
EnterInstance(PP_Instance instance,const PP_CompletionCallback & callback)269 EnterInstance::EnterInstance(PP_Instance instance,
270                              const PP_CompletionCallback& callback)
271     : EnterBase(0 /* resource */, callback),
272       // TODO(dmichael): This means that the callback_ we get is not associated
273       //                 even with the instance, but we should handle that for
274       //                 MouseLock (maybe others?).
275       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
276   SetStateForFunctionError(instance, functions_, true);
277 }
278 
~EnterInstance()279 EnterInstance::~EnterInstance() {
280 }
281 
EnterInstanceNoLock(PP_Instance instance)282 EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
283     : EnterBase(),
284       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
285   SetStateForFunctionError(instance, functions_, true);
286 }
287 
EnterInstanceNoLock(PP_Instance instance,const PP_CompletionCallback & callback)288 EnterInstanceNoLock::EnterInstanceNoLock(
289     PP_Instance instance,
290     const PP_CompletionCallback& callback)
291     : EnterBase(0 /* resource */, callback),
292       // TODO(dmichael): This means that the callback_ we get is not associated
293       //                 even with the instance, but we should handle that for
294       //                 MouseLock (maybe others?).
295       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
296   SetStateForFunctionError(instance, functions_, true);
297 }
298 
~EnterInstanceNoLock()299 EnterInstanceNoLock::~EnterInstanceNoLock() {
300 }
301 
EnterResourceCreation(PP_Instance instance)302 EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
303     : EnterBase(),
304       functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
305   SetStateForFunctionError(instance, functions_, true);
306 }
307 
~EnterResourceCreation()308 EnterResourceCreation::~EnterResourceCreation() {
309 }
310 
EnterResourceCreationNoLock(PP_Instance instance)311 EnterResourceCreationNoLock::EnterResourceCreationNoLock(PP_Instance instance)
312     : EnterBase(),
313       functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
314   SetStateForFunctionError(instance, functions_, true);
315 }
316 
~EnterResourceCreationNoLock()317 EnterResourceCreationNoLock::~EnterResourceCreationNoLock() {
318 }
319 
320 }  // namespace thunk
321 }  // namespace ppapi
322