/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef replace_malloc_bridge_h
#define replace_malloc_bridge_h

/*
 * The replace-malloc bridge allows bidirectional method calls between
 * a program and the replace-malloc library that has been loaded for it.
 * In Firefox, this is used to allow method calls between code in libxul
 * and code in the replace-malloc library, without libxul needing to link
 * against that library or vice-versa.
 *
 * Subsystems can add methods for their own need. Replace-malloc libraries
 * can decide to implement those methods or not.
 *
 * Replace-malloc libraries can provide such a bridge by implementing
 * a ReplaceMallocBridge-derived class, and a replace_get_bridge function
 * returning an instance of that class. The default methods in
 * ReplaceMallocBridge are expected to return values that callers would
 * understand as "the bridge doesn't implement this method", so that a
 * replace-malloc library doesn't have to implement all methods.
 *
 * The ReplaceMallocBridge class contains definitions for methods for
 * all replace-malloc libraries. Each library picks the methods it wants
 * to reply to in its ReplaceMallocBridge-derived class instance.
 * All methods of ReplaceMallocBridge must be virtual. Similarly,
 * anything passed as an argument to those methods must be plain data, or
 * an instance of a class with only virtual methods.
 *
 * Binary compatibility is expected to be maintained, such that a newer
 * Firefox can be used with an old replace-malloc library, or an old
 * Firefox can be used with a newer replace-malloc library. As such, only
 * new virtual methods should be added to ReplaceMallocBridge, and
 * each change should have a corresponding bump of the mVersion value.
 * At the same time, each virtual method should have a corresponding
 * wrapper calling the virtual method on the instance from
 * ReplaceMallocBridge::Get(), giving it the version the virtual method
 * was added.
 *
 * Parts that are not relevant to the replace-malloc library end of the
 * bridge are hidden when REPLACE_MALLOC_IMPL is not defined, which is
 * the case when including replace_malloc.h.
 */

struct ReplaceMallocBridge;

#include "mozilla/Types.h"

MOZ_BEGIN_EXTERN_C

#ifndef REPLACE_MALLOC_IMPL
/* Returns the replace-malloc bridge if there is one to be returned. */
MFBT_API ReplaceMallocBridge* get_bridge();
#endif

/* Table of malloc functions.
 *   e.g. void* (*malloc)(size_t), etc.
 */
#define MALLOC_DECL(name, return_type, ...) \
  typedef return_type(name ## _impl_t)(__VA_ARGS__);

#include "malloc_decls.h"

#define MALLOC_DECL(name, return_type, ...) \
  name ## _impl_t * name;

typedef struct {
#include "malloc_decls.h"
} malloc_table_t;


/* Table of malloc hook functions.
 * Those functions are called with the arguments and results of malloc
 * functions after they are called.
 *   e.g. void* (*malloc_hook)(void*, size_t), etc.
 * They can either return the result they're given, or alter it before
 * returning it.
 * The hooks corresponding to functions, like free(void*), that return no
 * value, don't take an extra argument.
 * The table must at least contain a pointer for malloc_hook and free_hook
 * functions. They will be used as fallback if no pointer is given for
 * other allocation functions, like calloc_hook.
 */
#define MALLOC_DECL(name, return_type, ...) \
  return_type (*name ## _hook)(return_type, __VA_ARGS__);
#define MALLOC_DECL_VOID(name, ...) \
  void (*name ## _hook)(__VA_ARGS__);

typedef struct {
#include "malloc_decls.h"
  /* Like free_hook, but called before realloc_hook. free_hook is called
   * instead of not given. */
  void (*realloc_hook_before)(void* aPtr);
} malloc_hook_table_t;

MOZ_END_EXTERN_C

#ifdef __cplusplus

namespace mozilla {
namespace dmd {
struct DMDFuncs;
} // namespace dmd

/* Callbacks to register debug file handles for Poison IO interpose.
 * See Mozilla(|Un)RegisterDebugHandle in xpcom/build/PoisonIOInterposer.h */
struct DebugFdRegistry
{
  virtual void RegisterHandle(intptr_t aFd);

  virtual void UnRegisterHandle(intptr_t aFd);
};

} // namespace mozilla

struct ReplaceMallocBridge
{
  ReplaceMallocBridge() : mVersion(3) {}

  /* This method was added in version 1 of the bridge. */
  virtual mozilla::dmd::DMDFuncs* GetDMDFuncs() { return nullptr; }

  /* Send a DebugFdRegistry instance to the replace-malloc library so that
   * it can register/unregister file descriptors whenever needed. The
   * instance is valid until the process dies.
   * This method was added in version 2 of the bridge. */
  virtual void InitDebugFd(mozilla::DebugFdRegistry&) {}

  /* Register a list of malloc functions and hook functions to the
   * replace-malloc library so that it can choose to dispatch to them
   * when needed. The details of what is dispatched when is left to the
   * replace-malloc library.
   * Passing a nullptr for either table will unregister a previously
   * registered table under the same name.
   * Returns nullptr if registration failed.
   * If registration succeeded, a table of "pure" malloc functions is
   * returned. Those "pure" malloc functions won't call hooks.
   * /!\ Do not rely on registration/unregistration to be instantaneous.
   * Functions from a previously registered table may still be called for
   * a brief time after RegisterHook returns.
   * This method was added in version 3 of the bridge. */
  virtual const malloc_table_t*
  RegisterHook(const char* aName, const malloc_table_t* aTable,
               const malloc_hook_table_t* aHookTable) { return nullptr; }

#ifndef REPLACE_MALLOC_IMPL
  /* Returns the replace-malloc bridge if its version is at least the
   * requested one. */
  static ReplaceMallocBridge* Get(int aMinimumVersion) {
    static ReplaceMallocBridge* sSingleton = get_bridge();
    return (sSingleton && sSingleton->mVersion >= aMinimumVersion)
      ? sSingleton : nullptr;
  }
#endif

protected:
  const int mVersion;
};

#ifndef REPLACE_MALLOC_IMPL
/* Class containing wrappers for calls to ReplaceMallocBridge methods.
 * Those wrappers need to be static methods in a class because compilers
 * complain about unused static global functions, and linkers complain
 * about multiple definitions of non-static global functions.
 * Using a separate class from ReplaceMallocBridge allows the function
 * names to be identical. */
struct ReplaceMalloc
{
  /* Don't call this method from performance critical code. Use
   * mozilla::dmd::DMDFuncs::Get() instead, it has less overhead. */
  static mozilla::dmd::DMDFuncs* GetDMDFuncs()
  {
    auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 1);
    return singleton ? singleton->GetDMDFuncs() : nullptr;
  }

  static void InitDebugFd(mozilla::DebugFdRegistry& aRegistry)
  {
    auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 2);
    if (singleton) {
      singleton->InitDebugFd(aRegistry);
    }
  }

  static const malloc_table_t*
  RegisterHook(const char* aName, const malloc_table_t* aTable,
               const malloc_hook_table_t* aHookTable)
  {
    auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 3);
    return singleton ? singleton->RegisterHook(aName, aTable, aHookTable)
                     : nullptr;
  }
};
#endif

#endif /* __cplusplus */

#endif /* replace_malloc_bridge_h */
