chromium/mojo/core/handle_table.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "mojo/core/handle_table.h"

#include <stdint.h>

#include <limits>

#include "base/trace_event/memory_dump_manager.h"

namespace mojo {
namespace core {

namespace {

const char* GetNameForDispatcherType(Dispatcher::Type type) {
  switch (type) {
    case Dispatcher::Type::UNKNOWN:
      return "unknown";
    case Dispatcher::Type::MESSAGE_PIPE:
      return "message_pipe";
    case Dispatcher::Type::DATA_PIPE_PRODUCER:
      return "data_pipe_producer";
    case Dispatcher::Type::DATA_PIPE_CONSUMER:
      return "data_pipe_consumer";
    case Dispatcher::Type::SHARED_BUFFER:
      return "shared_buffer";
    case Dispatcher::Type::WATCHER:
      return "watcher";
    case Dispatcher::Type::PLATFORM_HANDLE:
      return "platform_handle";
    case Dispatcher::Type::INVITATION:
      return "invitation";
  }
  NOTREACHED();
}

}  // namespace

HandleTable::EntriesAccessor::EntriesAccessor() = default;

HandleTable::EntriesAccessor::~EntriesAccessor() = default;

bool HandleTable::EntriesAccessor::Add(const MojoHandle handle, Entry entry) {
  return handles_.emplace(handle, std::move(entry)).second;
}

const scoped_refptr<Dispatcher>* HandleTable::EntriesAccessor::GetDispatcher(
    const MojoHandle handle) {
  if (last_read_handle_ != MOJO_HANDLE_INVALID && last_read_handle_ == handle) {
    return &last_read_dispatcher_;
  }
  const auto iter = handles_.find(handle);
  if (iter == handles_.end()) {
    return nullptr;
  }
  last_read_handle_ = handle;
  last_read_dispatcher_ = iter->second.dispatcher;
  return &last_read_dispatcher_;
}

HandleTable::Entry* HandleTable::EntriesAccessor::GetMutable(
    const MojoHandle handle) {
  const auto iter = handles_.find(handle);
  return iter == handles_.end() ? nullptr : &iter->second;
}

MojoResult HandleTable::EntriesAccessor::Remove(
    const MojoHandle handle,
    const HandleTable::EntriesAccessor::RemovalCondition removal_condition,
    scoped_refptr<Dispatcher>* dispatcher) {
  auto iter = handles_.find(handle);
  if (iter == handles_.end()) {
    return MOJO_RESULT_NOT_FOUND;
  }
  const bool is_busy = iter->second.busy;
  const bool remove_only_if_busy =
      removal_condition == RemovalCondition::kRemoveOnlyIfBusy;
  if (remove_only_if_busy == is_busy) {
    if (dispatcher != nullptr) {
      *dispatcher = iter->second.dispatcher;
    }
    if (iter->first == last_read_handle_) {
      last_read_handle_ = MOJO_HANDLE_INVALID;
      last_read_dispatcher_.reset();
    }
    handles_.erase(iter);
  }
  return is_busy ? MOJO_RESULT_BUSY : MOJO_RESULT_OK;
}

const std::unordered_map<MojoHandle, HandleTable::Entry>&
HandleTable::EntriesAccessor::GetUnderlyingMap() const {
  return handles_;
}

HandleTable::HandleTable() = default;

HandleTable::~HandleTable() = default;

base::Lock& HandleTable::GetLock() {
  return lock_;
}

MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
  // Oops, we're out of handles.
  if (next_available_handle_ == MOJO_HANDLE_INVALID)
    return MOJO_HANDLE_INVALID;

  MojoHandle handle = next_available_handle_++;
  const bool inserted = entries_.Add(handle, Entry(std::move(dispatcher)));
  DCHECK(inserted);

  return handle;
}

bool HandleTable::AddDispatchersFromTransit(
    const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
    MojoHandle* handles) {
  // Oops, we're out of handles.
  if (next_available_handle_ == MOJO_HANDLE_INVALID) {
    return false;
  }

  // MOJO_HANDLE_INVALID is zero.
  DCHECK_GE(next_available_handle_, 1u);

  // If this insertion would cause handle overflow, we're out of handles.
  const uintptr_t num_handles_available =
      std::numeric_limits<uintptr_t>::max() - next_available_handle_ + 1;
  if (num_handles_available < dispatchers.size()) {
    return false;
  }

  for (size_t i = 0; i < dispatchers.size(); ++i) {
    MojoHandle handle = MOJO_HANDLE_INVALID;
    if (dispatchers[i].dispatcher) {
      handle = next_available_handle_++;
      const bool inserted =
          entries_.Add(handle, Entry(dispatchers[i].dispatcher));
      DCHECK(inserted);
    }
    handles[i] = handle;
  }

  return true;
}

scoped_refptr<Dispatcher> HandleTable::GetDispatcher(MojoHandle handle) {
  const scoped_refptr<Dispatcher>* dispatcher = entries_.GetDispatcher(handle);
  return dispatcher == nullptr ? nullptr : *dispatcher;
}

MojoResult HandleTable::GetAndRemoveDispatcher(
    MojoHandle handle,
    scoped_refptr<Dispatcher>* dispatcher) {
  scoped_refptr<Dispatcher> removed_dispatcher;
  const MojoResult remove_result = entries_.Remove(
      handle, EntriesAccessor::RemovalCondition::kRemoveOnlyIfNotBusy,
      &removed_dispatcher);
  if (remove_result == MOJO_RESULT_NOT_FOUND) {
    return MOJO_RESULT_INVALID_ARGUMENT;
  }
  if (remove_result == MOJO_RESULT_BUSY) {
    return MOJO_RESULT_BUSY;
  }
  *dispatcher = std::move(removed_dispatcher);
  return MOJO_RESULT_OK;
}

MojoResult HandleTable::BeginTransit(
    const MojoHandle* handles,
    size_t num_handles,
    std::vector<Dispatcher::DispatcherInTransit>* dispatchers) {
  dispatchers->reserve(dispatchers->size() + num_handles);
  for (size_t i = 0; i < num_handles; ++i) {
    Entry* entry = entries_.GetMutable(handles[i]);
    if (entry == nullptr) {
      return MOJO_RESULT_INVALID_ARGUMENT;
    }
    if (entry->busy) {
      return MOJO_RESULT_BUSY;
    }

    Dispatcher::DispatcherInTransit d;
    d.local_handle = handles[i];
    d.dispatcher = entry->dispatcher;
    if (!d.dispatcher->BeginTransit())
      return MOJO_RESULT_BUSY;
    entry->busy = true;
    dispatchers->push_back(d);
  }
  return MOJO_RESULT_OK;
}

void HandleTable::CompleteTransitAndClose(
    const std::vector<Dispatcher::DispatcherInTransit>& dispatchers) {
  for (const auto& dispatcher : dispatchers) {
    const MojoResult remove_result =
        entries_.Remove(dispatcher.local_handle,
                        EntriesAccessor::RemovalCondition::kRemoveOnlyIfBusy,
                        /*dispatcher=*/nullptr);
    DCHECK(remove_result == MOJO_RESULT_BUSY);
    dispatcher.dispatcher->CompleteTransitAndClose();
  }
}

void HandleTable::CancelTransit(
    const std::vector<Dispatcher::DispatcherInTransit>& dispatchers) {
  for (const auto& dispatcher : dispatchers) {
    Entry* entry = entries_.GetMutable(dispatcher.local_handle);
    DCHECK(entry != nullptr && entry->busy);
    entry->busy = false;
    dispatcher.dispatcher->CancelTransit();
  }
}

void HandleTable::GetActiveHandlesForTest(std::vector<MojoHandle>* handles) {
  handles->clear();
  for (const auto& entry : entries_.GetUnderlyingMap())
    handles->push_back(entry.first);
}

// MemoryDumpProvider implementation.
bool HandleTable::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
                               base::trace_event::ProcessMemoryDump* pmd) {
  // Create entries for all relevant dispatcher types to ensure they are present
  // in the final dump.
  std::map<Dispatcher::Type, int> handle_count;
  handle_count[Dispatcher::Type::MESSAGE_PIPE];
  handle_count[Dispatcher::Type::DATA_PIPE_PRODUCER];
  handle_count[Dispatcher::Type::DATA_PIPE_CONSUMER];
  handle_count[Dispatcher::Type::SHARED_BUFFER];
  handle_count[Dispatcher::Type::WATCHER];
  handle_count[Dispatcher::Type::PLATFORM_HANDLE];
  handle_count[Dispatcher::Type::INVITATION];

  // Count the number of each dispatcher type.
  {
    base::AutoLock lock(GetLock());
    for (const auto& entry : entries_.GetUnderlyingMap()) {
      ++handle_count[entry.second.dispatcher->GetType()];
    }
  }

  for (const auto& entry : handle_count) {
    base::trace_event::MemoryAllocatorDump* inner_dump =
        pmd->CreateAllocatorDump(std::string("mojo/") +
                                 GetNameForDispatcherType(entry.first));
    inner_dump->AddScalar(
        base::trace_event::MemoryAllocatorDump::kNameObjectCount,
        base::trace_event::MemoryAllocatorDump::kUnitsObjects, entry.second);
  }

  return true;
}

HandleTable::Entry::Entry(scoped_refptr<Dispatcher> dispatcher)
    : dispatcher(std::move(dispatcher)) {}

HandleTable::Entry::~Entry() = default;
HandleTable::Entry::Entry(const Entry& entry) = default;

}  // namespace core
}  // namespace mojo