text: /******************************************************************************
   *
   *  Copyright (C) 2014 Google, Inc.
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at:
   *
   *  http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   *
   ******************************************************************************/
  
  #define LOG_TAG "bt_osi_allocation_tracker"
  
  #include "osi/include/allocation_tracker.h"
  
  #include <base/logging.h>
  #include <stdlib.h>
  #include <string.h>
  #include <mutex>
  #include <unordered_map>
  
  #include "osi/include/allocator.h"
  #include "osi/include/log.h"
  #include "osi/include/osi.h"
  
  typedef struct {
    uint8_t allocator_id;
    void* ptr;
    size_t size;
    bool freed;
  } allocation_t;
  
  static const size_t canary_size = 8;
  static char canary[canary_size];
  static std::unordered_map<void*, allocation_t*> allocations;
  static std::mutex tracker_lock;
  static bool enabled = false;
  
  // Memory allocation statistics
  static size_t alloc_counter = 0;
  static size_t free_counter = 0;
  static size_t alloc_total_size = 0;
  static size_t free_total_size = 0;
  
  void allocation_tracker_init(void) {
    std::unique_lock<std::mutex> lock(tracker_lock);
    if (enabled) return;
  
    // randomize the canary contents
    for (size_t i = 0; i < canary_size; i++) canary[i] = (char)osi_rand();
  
    LOG_DEBUG(LOG_TAG, "canary initialized");
  
    enabled = true;
  }
  
  // Test function only. Do not call in the normal course of operations.
  void allocation_tracker_uninit(void) {
    std::unique_lock<std::mutex> lock(tracker_lock);
    if (!enabled) return;
  
    allocations.clear();
    enabled = false;
  }
  
  void allocation_tracker_reset(void) {
    std::unique_lock<std::mutex> lock(tracker_lock);
    if (!enabled) return;
  
    allocations.clear();
  }
  
  size_t allocation_tracker_expect_no_allocations(void) {
    std::unique_lock<std::mutex> lock(tracker_lock);
    if (!enabled) return 0;
  
    size_t unfreed_memory_size = 0;
  
    for (const auto& entry : allocations) {
      allocation_t* allocation = entry.second;
      if (!allocation->freed) {
        unfreed_memory_size +=
            allocation->size;  // Report back the unfreed byte count
        LOG_ERROR(LOG_TAG,
                  "%s found unfreed allocation. address: 0x%zx size: %zd bytes",
                  __func__, (uintptr_t)allocation->ptr, allocation->size);
      }
    }
  
    return unfreed_memory_size;
  }
  
  void* allocation_tracker_notify_alloc(uint8_t allocator_id, void* ptr,
                                        size_t requested_size) {
    char* return_ptr;
    {
      std::unique_lock<std::mutex> lock(tracker_lock);
      if (!enabled || !ptr) return ptr;
  
      // Keep statistics
      alloc_counter++;
      alloc_total_size += allocation_tracker_resize_for_canary(requested_size);
  
      return_ptr = ((char*)ptr) + canary_size;
  
      auto map_entry = allocations.find(return_ptr);
      allocation_t* allocation;
      if (map_entry != allocations.end()) {
        allocation = map_entry->second;
        CHECK(allocation->freed);  // Must have been freed before
      } else {
        allocation = (allocation_t*)calloc(1, sizeof(allocation_t));
        allocations[return_ptr] = allocation;
      }
  
      allocation->allocator_id = allocator_id;
      allocation->freed = false;
      allocation->size = requested_size;
      allocation->ptr = return_ptr;
    }
  
    // Add the canary on both sides
    memcpy(return_ptr - canary_size, canary, canary_size);
    memcpy(return_ptr + requested_size, canary, canary_size);
  
    return return_ptr;
  }
  
  void* allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,
                                       void* ptr) {
    std::unique_lock<std::mutex> lock(tracker_lock);
  
    if (!enabled || !ptr) return ptr;
  
    auto map_entry = allocations.find(ptr);
    CHECK(map_entry != allocations.end());
    allocation_t* allocation = map_entry->second;
    CHECK(allocation);          // Must have been tracked before
    CHECK(!allocation->freed);  // Must not be a double free
    CHECK(allocation->allocator_id ==
          allocator_id);  // Must be from the same allocator
  
    // Keep statistics
    free_counter++;
    free_total_size += allocation_tracker_resize_for_canary(allocation->size);
  
    allocation->freed = true;
  
    UNUSED_ATTR const char* beginning_canary = ((char*)ptr) - canary_size;
    UNUSED_ATTR const char* end_canary = ((char*)ptr) + allocation->size;
  
    for (size_t i = 0; i < canary_size; i++) {
      CHECK(beginning_canary[i] == canary[i]);
      CHECK(end_canary[i] == canary[i]);
    }
  
    // Free the hash map entry to avoid unlimited memory usage growth.
    // Double-free of memory is detected with "assert(allocation)" above
    // as the allocation entry will not be present.
    allocations.erase(ptr);
    free(allocation);
  
    return ((char*)ptr) - canary_size;
  }
  
  size_t allocation_tracker_resize_for_canary(size_t size) {
    return (!enabled) ? size : size + (2 * canary_size);
  }
  
  void osi_allocator_debug_dump(int fd) {
    dprintf(fd, "\nBluetooth Memory Allocation Statistics:\n");
  
    std::unique_lock<std::mutex> lock(tracker_lock);
  
    dprintf(fd, "  Total allocated/free/used counts : %zu / %zu / %zu\n",
            alloc_counter, free_counter, alloc_counter - free_counter);
    dprintf(fd, "  Total allocated/free/used octets : %zu / %zu / %zu\n",
            alloc_total_size, free_total_size,
            alloc_total_size - free_total_size);
  }
  