// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ppapi/shared_impl/resource_tracker.h"
#include <memory>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "ppapi/shared_impl/callback_tracker.h"
#include "ppapi/shared_impl/id_assignment.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/proxy_lock.h"
#include "ppapi/shared_impl/resource.h"
namespace ppapi {
ResourceTracker::ResourceTracker(ThreadMode thread_mode)
: last_resource_value_(0) {
if (thread_mode == SINGLE_THREADED)
thread_checker_ = std::make_unique<base::ThreadChecker>();
}
ResourceTracker::~ResourceTracker() {}
void ResourceTracker::CheckThreadingPreconditions() const {
DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
#ifndef NDEBUG
ProxyLock::AssertAcquired();
#endif
}
Resource* ResourceTracker::GetResource(PP_Resource res) const {
CheckThreadingPreconditions();
ResourceMap::const_iterator i = live_resources_.find(res);
if (i == live_resources_.end())
return NULL;
return i->second.first;
}
void ResourceTracker::AddRefResource(PP_Resource res) {
CheckThreadingPreconditions();
DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE))
<< res << " is not a PP_Resource.";
DCHECK(CanOperateOnResource(res));
ResourceMap::iterator i = live_resources_.find(res);
if (i == live_resources_.end())
return;
// Prevent overflow of refcount.
if (i->second.second ==
std::numeric_limits<ResourceAndRefCount::second_type>::max())
return;
// When we go from 0 to 1 plugin ref count, keep an additional "real" ref
// on its behalf.
if (i->second.second == 0)
i->second.first->AddRef();
i->second.second++;
return;
}
void ResourceTracker::ReleaseResource(PP_Resource res) {
CheckThreadingPreconditions();
DLOG_IF(ERROR, !CheckIdType(res, PP_ID_TYPE_RESOURCE))
<< res << " is not a PP_Resource.";
DCHECK(CanOperateOnResource(res));
ResourceMap::iterator i = live_resources_.find(res);
if (i == live_resources_.end())
return;
// Prevent underflow of refcount.
if (i->second.second == 0)
return;
i->second.second--;
if (i->second.second == 0) {
LastPluginRefWasDeleted(i->second.first);
// When we go from 1 to 0 plugin ref count, free the additional "real" ref
// on its behalf. THIS WILL MOST LIKELY RELEASE THE OBJECT AND REMOVE IT
// FROM OUR LIST.
i->second.first->Release();
}
}
void ResourceTracker::DidCreateInstance(PP_Instance instance) {
CheckThreadingPreconditions();
// Due to the infrastructure of some tests, the instance is registered
// twice in a few cases. It would be nice not to do that and assert here
// instead.
if (instance_map_.find(instance) != instance_map_.end())
return;
instance_map_[instance] = base::WrapUnique(new InstanceData);
}
void ResourceTracker::DidDeleteInstance(PP_Instance instance) {
CheckThreadingPreconditions();
InstanceMap::iterator found_instance = instance_map_.find(instance);
// Due to the infrastructure of some tests, the instance is unregistered
// twice in a few cases. It would be nice not to do that and assert here
// instead.
if (found_instance == instance_map_.end())
return;
InstanceData& data = *found_instance->second;
// Force release all plugin references to resources associated with the
// deleted instance. Make a copy since as we iterate through them, each one
// will remove itself from the tracking info individually.
ResourceSet to_delete = data.resources;
ResourceSet::iterator cur = to_delete.begin();
while (cur != to_delete.end()) {
// Note that it's remotely possible for the object to already be deleted
// from the live resources. One case is if a resource object is holding
// the last ref to another. When we release the first one, it will release
// the second one. So the second one will be gone when we eventually get
// to it.
ResourceMap::iterator found_resource = live_resources_.find(*cur);
if (found_resource != live_resources_.end()) {
Resource* resource = found_resource->second.first;
if (found_resource->second.second > 0) {
LastPluginRefWasDeleted(resource);
found_resource->second.second = 0;
// This will most likely delete the resource object and remove it
// from the live_resources_ list.
resource->Release();
}
}
cur++;
}
// In general the above pass will delete all the resources and there won't
// be any left in the map. However, if parts of the implementation are still
// holding on to internal refs, we need to tell them that the instance is
// gone.
to_delete = data.resources;
cur = to_delete.begin();
while (cur != to_delete.end()) {
ResourceMap::iterator found_resource = live_resources_.find(*cur);
if (found_resource != live_resources_.end())
found_resource->second.first->NotifyInstanceWasDeleted();
cur++;
}
instance_map_.erase(instance);
}
int ResourceTracker::GetLiveObjectsForInstance(PP_Instance instance) const {
CheckThreadingPreconditions();
InstanceMap::const_iterator found = instance_map_.find(instance);
if (found == instance_map_.end())
return 0;
return static_cast<int>(found->second->resources.size());
}
void ResourceTracker::UseOddResourceValueInDebugMode() {
#if !defined(NDEBUG)
DCHECK_EQ(0, last_resource_value_);
++last_resource_value_;
#endif
}
PP_Resource ResourceTracker::AddResource(Resource* object) {
CheckThreadingPreconditions();
// If the plugin manages to create too many resources, don't do crazy stuff.
if (last_resource_value_ >= kMaxPPId)
return 0;
// Allocate an ID. Note there's a rare error condition below that means we
// could end up not using |new_id|, but that's harmless.
PP_Resource new_id = MakeTypedId(GetNextResourceValue(), PP_ID_TYPE_RESOURCE);
// Some objects have a 0 instance, meaning they aren't associated with any
// instance, so they won't be in |instance_map_|. This is (as of this writing)
// only true of the PPB_MessageLoop resource for the main thread.
if (object->pp_instance()) {
InstanceMap::iterator found = instance_map_.find(object->pp_instance());
if (found == instance_map_.end()) {
// If you hit this, it's likely somebody forgot to call DidCreateInstance,
// the resource was created with an invalid PP_Instance, or the renderer
// side tried to create a resource for a plugin that crashed/exited. This
// could happen for OOP plugins where due to reentrancies in context of
// outgoing sync calls the renderer can send events after a plugin has
// exited.
VLOG(1) << "Failed to find plugin instance in instance map";
return 0;
}
found->second->resources.insert(new_id);
}
live_resources_[new_id] = ResourceAndRefCount(object, 0);
return new_id;
}
void ResourceTracker::RemoveResource(Resource* object) {
CheckThreadingPreconditions();
PP_Resource pp_resource = object->pp_resource();
InstanceMap::iterator found = instance_map_.find(object->pp_instance());
if (found != instance_map_.end())
found->second->resources.erase(pp_resource);
live_resources_.erase(pp_resource);
}
void ResourceTracker::LastPluginRefWasDeleted(Resource* object) {
// Bug http://crbug.com/134611 indicates that sometimes the resource tracker
// is null here. This should never be the case since if we have a resource in
// the tracker, it should always have a valid instance associated with it
// (except for the resource for the main thread's message loop, which has
// instance set to 0).
// As a result, we do some CHECKs here to see what types of problems the
// instance might have before dispatching.
//
// TODO(brettw) remove these checks when this bug is no longer relevant.
// Note, we do an imperfect check here; this might be a loop that's not the
// main one.
const bool is_message_loop = (object->AsPPB_MessageLoop_API() != NULL);
CHECK(object->pp_instance() || is_message_loop);
CallbackTracker* callback_tracker =
PpapiGlobals::Get()->GetCallbackTrackerForInstance(object->pp_instance());
CHECK(callback_tracker || is_message_loop);
if (callback_tracker)
callback_tracker->PostAbortForResource(object->pp_resource());
object->NotifyLastPluginRefWasDeleted();
}
int32_t ResourceTracker::GetNextResourceValue() {
#if defined(NDEBUG)
return ++last_resource_value_;
#else
// In debug mode, the least significant bit indicates which side (renderer
// or plugin process) created the resource. Increment by 2 so it's always the
// same.
last_resource_value_ += 2;
return last_resource_value_;
#endif
}
bool ResourceTracker::CanOperateOnResource(PP_Resource res) {
#if defined(NDEBUG)
return true;
#else
// The invalid PP_Resource value could appear at both sides.
if (res == 0)
return true;
// Skipping the type bits, the least significant bit of |res| should be the
// same as that of |last_resource_value_|.
return ((res >> kPPIdTypeBits) & 1) == (last_resource_value_ & 1);
#endif
}
} // namespace ppapi